2022-06-01 07:30:57 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2022-05-28 23:56:59 +00:00
|
|
|
use frost::{Curve, MultisigKeys};
|
|
|
|
|
2022-06-01 07:30:57 +00:00
|
|
|
use crate::{CoinError, Coin};
|
|
|
|
|
|
|
|
pub struct WalletKeys<C: Curve> {
|
|
|
|
keys: MultisigKeys<C>,
|
|
|
|
creation_height: usize
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<C: Curve> WalletKeys<C> {
|
|
|
|
pub fn new(keys: MultisigKeys<C>, creation_height: usize) -> WalletKeys<C> {
|
|
|
|
WalletKeys { keys, creation_height }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bind this key to a specific network by applying an additive offset
|
|
|
|
// While it would be fine to just C::id(), including the group key creates distinct
|
|
|
|
// offsets instead of static offsets. Under a statically offset system, a BTC key could
|
|
|
|
// have X subtracted to find the potential group key, and then have Y added to find the
|
|
|
|
// potential ETH group key. While this shouldn't be an issue, as this isn't a private
|
|
|
|
// system, there are potentially other benefits to binding this to a specific group key
|
|
|
|
// It's no longer possible to influence group key gen to key cancel without breaking the hash
|
|
|
|
// function, although that degree of influence means key gen is broken already
|
|
|
|
fn bind(&self, chain: &[u8]) -> MultisigKeys<C> {
|
|
|
|
self.keys.offset(
|
|
|
|
C::hash_to_F(
|
|
|
|
&[
|
|
|
|
b"Serai Processor Wallet",
|
|
|
|
chain,
|
|
|
|
&C::G_to_bytes(&self.keys.group_key())
|
|
|
|
].concat()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct CoinDb {
|
|
|
|
// Height this coin has been scanned to
|
|
|
|
scanned_height: usize,
|
|
|
|
// Acknowledged height for a given canonical height
|
|
|
|
acknowledged_heights: HashMap<usize, usize>
|
|
|
|
}
|
2022-05-28 23:56:59 +00:00
|
|
|
|
2022-06-01 07:30:57 +00:00
|
|
|
pub struct Wallet<C: Coin> {
|
|
|
|
db: CoinDb,
|
|
|
|
coin: C,
|
|
|
|
keys: Vec<MultisigKeys<C::Curve>>,
|
|
|
|
pending: Vec<(usize, MultisigKeys<C::Curve>)>,
|
2022-05-28 23:56:59 +00:00
|
|
|
outputs: Vec<C::Output>
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<C: Coin> Wallet<C> {
|
2022-06-01 07:30:57 +00:00
|
|
|
pub fn new(coin: C) -> Wallet<C> {
|
2022-05-28 23:56:59 +00:00
|
|
|
Wallet {
|
2022-06-01 07:30:57 +00:00
|
|
|
db: CoinDb {
|
|
|
|
scanned_height: 0,
|
|
|
|
acknowledged_heights: HashMap::new(),
|
|
|
|
},
|
2022-05-28 23:56:59 +00:00
|
|
|
|
2022-06-01 07:30:57 +00:00
|
|
|
coin,
|
|
|
|
|
|
|
|
keys: vec![],
|
|
|
|
pending: vec![],
|
2022-05-28 23:56:59 +00:00
|
|
|
outputs: vec![]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-01 07:30:57 +00:00
|
|
|
pub fn scanned_height(&self) -> usize { self.db.scanned_height }
|
|
|
|
pub fn acknowledge_height(&mut self, canonical: usize, height: usize) {
|
|
|
|
debug_assert!(!self.db.acknowledged_heights.contains_key(&canonical));
|
|
|
|
self.db.acknowledged_heights.insert(canonical, height);
|
|
|
|
}
|
|
|
|
pub fn acknowledged_height(&self, canonical: usize) -> usize {
|
|
|
|
self.db.acknowledged_heights[&canonical]
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_keys(&mut self, keys: &WalletKeys<C::Curve>) {
|
|
|
|
// Doesn't use +1 as this is height, not block index, and poll moves by block index
|
|
|
|
self.pending.push((self.acknowledged_height(keys.creation_height), keys.bind(C::id())));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn poll(&mut self) -> Result<(), CoinError> {
|
|
|
|
let confirmed_height = self.coin.get_height().await? - C::confirmations();
|
|
|
|
for h in self.scanned_height() .. confirmed_height {
|
|
|
|
let mut k = 0;
|
|
|
|
while k < self.pending.len() {
|
|
|
|
if h == self.pending[k].0 {
|
|
|
|
self.keys.push(self.pending.swap_remove(k).1);
|
|
|
|
} else {
|
|
|
|
k += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let block = self.coin.get_block(h).await?;
|
|
|
|
for keys in &self.keys {
|
|
|
|
let outputs = self.coin.get_outputs(&block, keys.group_key());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-05-28 23:56:59 +00:00
|
|
|
}
|