Utilize zeroize (#76)
* Apply Zeroize to nonces used in Bulletproofs
Also makes bit decomposition constant time for a given amount of
outputs.
* Fix nonce reuse for single-signer CLSAG
* Attach Zeroize to most structures in Monero, and ZOnDrop to anything with private data
* Zeroize private keys and nonces
* Merge prepare_outputs and prepare_transactions
* Ensure CLSAG is constant time
* Pass by borrow where needed, bug fixes
The past few commitments have been one in-progress chunk which I've
broken up as best read.
* Add Zeroize to FROST structs
Still needs to zeroize internally, yet next step. Not quite as
aggressive as Monero, partially due to the limitations of HashMaps,
partially due to less concern about metadata, yet does still delete a
few smaller items of metadata (group key, context string...).
* Remove Zeroize from most Monero multisig structs
These structs largely didn't have private data, just fields with private
data, yet those fields implemented ZeroizeOnDrop making them already
covered. While there is still traces of the transaction left in RAM,
fully purging that was never the intent.
* Use Zeroize within dleq
bitvec doesn't offer Zeroize, so a manual zeroing has been implemented.
* Use Zeroize for random_nonce
It isn't perfect, due to the inability to zeroize the digest, and due to
kp256 requiring a few transformations. It does the best it can though.
Does move the per-curve random_nonce to a provided one, which is allowed
as of https://github.com/cfrg/draft-irtf-cfrg-frost/pull/231.
* Use Zeroize on FROST keygen/signing
* Zeroize constant time multiexp.
* Correct when FROST keygen zeroizes
* Move the FROST keys Arc into FrostKeys
Reduces amount of instances in memory.
* Manually implement Debug for FrostCore to not leak the secret share
* Misc bug fixes
* clippy + multiexp test bug fixes
* Correct FROST key gen share summation
It leaked our own share for ourself.
* Fix cross-group DLEq tests
2022-08-03 03:25:18 -05:00
|
|
|
use std::collections::HashMap;
|
2022-06-01 03:30:57 -04:00
|
|
|
|
2022-06-10 09:36:07 -04:00
|
|
|
use rand_core::OsRng;
|
|
|
|
|
|
2022-06-28 01:25:26 -04:00
|
|
|
use group::GroupEncoding;
|
2022-06-05 15:10:50 -04:00
|
|
|
|
2022-06-28 01:25:26 -04:00
|
|
|
use transcript::{Transcript, RecommendedTranscript};
|
2022-07-15 01:26:07 -04:00
|
|
|
use frost::{
|
|
|
|
|
curve::Curve,
|
|
|
|
|
FrostKeys,
|
|
|
|
|
sign::{PreprocessMachine, SignMachine, SignatureMachine},
|
|
|
|
|
};
|
2022-05-28 19:56:59 -04:00
|
|
|
|
2022-07-15 01:26:07 -04:00
|
|
|
use crate::{
|
|
|
|
|
coin::{CoinError, Output, Coin},
|
|
|
|
|
SignError, Network,
|
|
|
|
|
};
|
2022-06-01 03:30:57 -04:00
|
|
|
|
|
|
|
|
pub struct WalletKeys<C: Curve> {
|
2022-06-28 00:06:12 -04:00
|
|
|
keys: FrostKeys<C>,
|
2022-07-15 01:26:07 -04:00
|
|
|
creation_height: usize,
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<C: Curve> WalletKeys<C> {
|
2022-06-28 00:06:12 -04:00
|
|
|
pub fn new(keys: FrostKeys<C>, creation_height: usize) -> WalletKeys<C> {
|
2022-06-01 03:30:57 -04:00
|
|
|
WalletKeys { keys, creation_height }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Bind this key to a specific network by applying an additive offset
|
2022-06-03 23:22:08 -04:00
|
|
|
// While it would be fine to just C::ID, including the group key creates distinct
|
2022-06-01 03:30:57 -04:00
|
|
|
// 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
|
2022-06-03 01:37:12 -04:00
|
|
|
// function as well, although that degree of influence means key gen is broken already
|
2022-06-28 00:06:12 -04:00
|
|
|
fn bind(&self, chain: &[u8]) -> FrostKeys<C> {
|
2022-06-03 01:37:12 -04:00
|
|
|
const DST: &[u8] = b"Serai Processor Wallet Chain Bind";
|
2022-06-24 18:58:24 -04:00
|
|
|
let mut transcript = RecommendedTranscript::new(DST);
|
2022-06-03 01:37:12 -04:00
|
|
|
transcript.append_message(b"chain", chain);
|
2022-06-03 23:22:08 -04:00
|
|
|
transcript.append_message(b"curve", C::ID);
|
2022-06-28 01:25:26 -04:00
|
|
|
transcript.append_message(b"group_key", self.keys.group_key().to_bytes().as_ref());
|
2022-06-03 01:37:12 -04:00
|
|
|
self.keys.offset(C::hash_to_F(DST, &transcript.challenge(b"offset")))
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-05 06:00:21 -04:00
|
|
|
pub trait CoinDb {
|
|
|
|
|
// Set a height as scanned to
|
|
|
|
|
fn scanned_to_height(&mut self, height: usize);
|
|
|
|
|
// Acknowledge a given coin height for a canonical height
|
|
|
|
|
fn acknowledge_height(&mut self, canonical: usize, height: usize);
|
|
|
|
|
|
|
|
|
|
// Adds an output to the DB. Returns false if the output was already added
|
|
|
|
|
fn add_output<O: Output>(&mut self, output: &O) -> bool;
|
|
|
|
|
|
|
|
|
|
// Height this coin has been scanned to
|
|
|
|
|
fn scanned_height(&self) -> usize;
|
|
|
|
|
// Acknowledged height for a given canonical height
|
|
|
|
|
fn acknowledged_height(&self, canonical: usize) -> usize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct MemCoinDb {
|
2022-06-01 03:30:57 -04:00
|
|
|
// Height this coin has been scanned to
|
|
|
|
|
scanned_height: usize,
|
|
|
|
|
// Acknowledged height for a given canonical height
|
2022-06-05 06:00:21 -04:00
|
|
|
acknowledged_heights: HashMap<usize, usize>,
|
2022-07-15 01:26:07 -04:00
|
|
|
outputs: HashMap<Vec<u8>, Vec<u8>>,
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
2022-05-28 19:56:59 -04:00
|
|
|
|
2022-06-05 06:00:21 -04:00
|
|
|
impl MemCoinDb {
|
|
|
|
|
pub fn new() -> MemCoinDb {
|
2022-07-15 01:26:07 -04:00
|
|
|
MemCoinDb { scanned_height: 0, acknowledged_heights: HashMap::new(), outputs: HashMap::new() }
|
2022-06-05 06:00:21 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CoinDb for MemCoinDb {
|
|
|
|
|
fn scanned_to_height(&mut self, height: usize) {
|
|
|
|
|
self.scanned_height = height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn acknowledge_height(&mut self, canonical: usize, height: usize) {
|
|
|
|
|
debug_assert!(!self.acknowledged_heights.contains_key(&canonical));
|
|
|
|
|
self.acknowledged_heights.insert(canonical, height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_output<O: Output>(&mut self, output: &O) -> bool {
|
|
|
|
|
// This would be insecure as we're indexing by ID and this will replace the output as a whole
|
|
|
|
|
// Multiple outputs may have the same ID in edge cases such as Monero, where outputs are ID'd
|
2022-08-21 06:36:53 -04:00
|
|
|
// by output key, not by hash + index
|
2022-06-05 06:00:21 -04:00
|
|
|
// self.outputs.insert(output.id(), output).is_some()
|
|
|
|
|
let id = output.id().as_ref().to_vec();
|
|
|
|
|
if self.outputs.contains_key(&id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
self.outputs.insert(id, output.serialize());
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn scanned_height(&self) -> usize {
|
|
|
|
|
self.scanned_height
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn acknowledged_height(&self, canonical: usize) -> usize {
|
|
|
|
|
self.acknowledged_heights[&canonical]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 09:12:27 -04:00
|
|
|
fn select_inputs<C: Coin>(inputs: &mut Vec<C::Output>) -> (Vec<C::Output>, u64) {
|
|
|
|
|
// Sort to ensure determinism. Inefficient, yet produces the most legible code to be optimized
|
|
|
|
|
// later
|
2022-09-04 21:23:38 -04:00
|
|
|
inputs.sort_by_key(|a| a.amount());
|
2022-06-10 09:12:27 -04:00
|
|
|
|
|
|
|
|
// Select the maximum amount of outputs possible
|
|
|
|
|
let res = inputs.split_off(inputs.len() - C::MAX_INPUTS.min(inputs.len()));
|
|
|
|
|
// Calculate their sum value, minus the fee needed to spend them
|
|
|
|
|
let sum = res.iter().map(|input| input.amount()).sum();
|
|
|
|
|
// sum -= C::MAX_FEE; // TODO
|
|
|
|
|
(res, sum)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn select_outputs<C: Coin>(
|
|
|
|
|
payments: &mut Vec<(C::Address, u64)>,
|
2022-07-15 01:26:07 -04:00
|
|
|
value: &mut u64,
|
2022-06-10 09:12:27 -04:00
|
|
|
) -> Vec<(C::Address, u64)> {
|
|
|
|
|
// Prioritize large payments which will most efficiently use large inputs
|
|
|
|
|
payments.sort_by(|a, b| a.1.cmp(&b.1));
|
|
|
|
|
|
|
|
|
|
// Grab the payments this will successfully fund
|
|
|
|
|
let mut outputs = vec![];
|
|
|
|
|
let mut p = payments.len();
|
|
|
|
|
while p != 0 {
|
|
|
|
|
p -= 1;
|
|
|
|
|
if *value >= payments[p].1 {
|
|
|
|
|
*value -= payments[p].1;
|
|
|
|
|
// Swap remove will either pop the tail or insert an element that wouldn't fit, making it
|
|
|
|
|
// always safe to move past
|
|
|
|
|
outputs.push(payments.swap_remove(p));
|
|
|
|
|
}
|
|
|
|
|
// Doesn't break in this else case as a smaller payment may still fit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
outputs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optimizes on the expectation selected/inputs are sorted from lowest value to highest
|
|
|
|
|
fn refine_inputs<C: Coin>(
|
|
|
|
|
selected: &mut Vec<C::Output>,
|
|
|
|
|
inputs: &mut Vec<C::Output>,
|
2022-07-15 01:26:07 -04:00
|
|
|
mut remaining: u64,
|
2022-06-10 09:12:27 -04:00
|
|
|
) {
|
|
|
|
|
// Drop unused inputs
|
|
|
|
|
let mut s = 0;
|
|
|
|
|
while remaining > selected[s].amount() {
|
|
|
|
|
remaining -= selected[s].amount();
|
|
|
|
|
s += 1;
|
|
|
|
|
}
|
|
|
|
|
// Add them back to the inputs pool
|
|
|
|
|
inputs.extend(selected.drain(.. s));
|
|
|
|
|
|
|
|
|
|
// Replace large inputs with smaller ones
|
|
|
|
|
for s in (0 .. selected.len()).rev() {
|
2022-07-22 02:34:36 -04:00
|
|
|
for input in inputs.iter_mut() {
|
2022-06-10 09:12:27 -04:00
|
|
|
// Doesn't break due to inputs no longer being sorted
|
|
|
|
|
// This could be made faster if we prioritized small input usage over transaction size/fees
|
|
|
|
|
// TODO: Consider. This would implicitly consolidate inputs which would be advantageous
|
2022-07-22 02:34:36 -04:00
|
|
|
if selected[s].amount() < input.amount() {
|
2022-06-10 09:12:27 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we can successfully replace this input, do so
|
2022-07-22 02:34:36 -04:00
|
|
|
let diff = selected[s].amount() - input.amount();
|
2022-06-10 09:12:27 -04:00
|
|
|
if remaining > diff {
|
|
|
|
|
remaining -= diff;
|
|
|
|
|
|
|
|
|
|
let old = selected[s].clone();
|
2022-07-22 02:34:36 -04:00
|
|
|
selected[s] = input.clone();
|
|
|
|
|
*input = old;
|
2022-06-10 09:12:27 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn select_inputs_outputs<C: Coin>(
|
|
|
|
|
inputs: &mut Vec<C::Output>,
|
2022-07-15 01:26:07 -04:00
|
|
|
outputs: &mut Vec<(C::Address, u64)>,
|
2022-06-10 09:12:27 -04:00
|
|
|
) -> (Vec<C::Output>, Vec<(C::Address, u64)>) {
|
2022-07-22 02:34:36 -04:00
|
|
|
if inputs.is_empty() {
|
2022-06-10 09:12:27 -04:00
|
|
|
return (vec![], vec![]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (mut selected, mut value) = select_inputs::<C>(inputs);
|
|
|
|
|
|
|
|
|
|
let outputs = select_outputs::<C>(outputs, &mut value);
|
2022-07-22 02:34:36 -04:00
|
|
|
if outputs.is_empty() {
|
2022-06-10 09:12:27 -04:00
|
|
|
inputs.extend(selected);
|
|
|
|
|
return (vec![], vec![]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
refine_inputs::<C>(&mut selected, inputs, value);
|
|
|
|
|
(selected, outputs)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-05 06:00:21 -04:00
|
|
|
pub struct Wallet<D: CoinDb, C: Coin> {
|
|
|
|
|
db: D,
|
2022-06-01 03:30:57 -04:00
|
|
|
coin: C,
|
Utilize zeroize (#76)
* Apply Zeroize to nonces used in Bulletproofs
Also makes bit decomposition constant time for a given amount of
outputs.
* Fix nonce reuse for single-signer CLSAG
* Attach Zeroize to most structures in Monero, and ZOnDrop to anything with private data
* Zeroize private keys and nonces
* Merge prepare_outputs and prepare_transactions
* Ensure CLSAG is constant time
* Pass by borrow where needed, bug fixes
The past few commitments have been one in-progress chunk which I've
broken up as best read.
* Add Zeroize to FROST structs
Still needs to zeroize internally, yet next step. Not quite as
aggressive as Monero, partially due to the limitations of HashMaps,
partially due to less concern about metadata, yet does still delete a
few smaller items of metadata (group key, context string...).
* Remove Zeroize from most Monero multisig structs
These structs largely didn't have private data, just fields with private
data, yet those fields implemented ZeroizeOnDrop making them already
covered. While there is still traces of the transaction left in RAM,
fully purging that was never the intent.
* Use Zeroize within dleq
bitvec doesn't offer Zeroize, so a manual zeroing has been implemented.
* Use Zeroize for random_nonce
It isn't perfect, due to the inability to zeroize the digest, and due to
kp256 requiring a few transformations. It does the best it can though.
Does move the per-curve random_nonce to a provided one, which is allowed
as of https://github.com/cfrg/draft-irtf-cfrg-frost/pull/231.
* Use Zeroize on FROST keygen/signing
* Zeroize constant time multiexp.
* Correct when FROST keygen zeroizes
* Move the FROST keys Arc into FrostKeys
Reduces amount of instances in memory.
* Manually implement Debug for FrostCore to not leak the secret share
* Misc bug fixes
* clippy + multiexp test bug fixes
* Correct FROST key gen share summation
It leaked our own share for ourself.
* Fix cross-group DLEq tests
2022-08-03 03:25:18 -05:00
|
|
|
keys: Vec<(FrostKeys<C::Curve>, Vec<C::Output>)>,
|
2022-07-15 01:26:07 -04:00
|
|
|
pending: Vec<(usize, FrostKeys<C::Curve>)>,
|
2022-05-28 19:56:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-06-05 06:00:21 -04:00
|
|
|
impl<D: CoinDb, C: Coin> Wallet<D, C> {
|
|
|
|
|
pub fn new(db: D, coin: C) -> Wallet<D, C> {
|
2022-07-15 01:26:07 -04:00
|
|
|
Wallet { db, coin, keys: vec![], pending: vec![] }
|
2022-05-28 19:56:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-15 01:26:07 -04:00
|
|
|
pub fn scanned_height(&self) -> usize {
|
|
|
|
|
self.db.scanned_height()
|
|
|
|
|
}
|
2022-06-01 03:30:57 -04:00
|
|
|
pub fn acknowledge_height(&mut self, canonical: usize, height: usize) {
|
2022-06-05 06:00:21 -04:00
|
|
|
self.db.acknowledge_height(canonical, height);
|
2022-06-09 02:48:53 -04:00
|
|
|
if height > self.db.scanned_height() {
|
|
|
|
|
self.db.scanned_to_height(height);
|
|
|
|
|
}
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
|
|
|
|
pub fn acknowledged_height(&self, canonical: usize) -> usize {
|
2022-06-05 06:00:21 -04:00
|
|
|
self.db.acknowledged_height(canonical)
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2022-06-03 23:22:08 -04:00
|
|
|
self.pending.push((self.acknowledged_height(keys.creation_height), keys.bind(C::ID)));
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
|
|
|
|
|
2022-06-09 02:48:53 -04:00
|
|
|
pub fn address(&self) -> C::Address {
|
|
|
|
|
self.coin.address(self.keys[self.keys.len() - 1].0.group_key())
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-01 03:30:57 -04:00
|
|
|
pub async fn poll(&mut self) -> Result<(), CoinError> {
|
2022-06-09 02:48:53 -04:00
|
|
|
if self.coin.get_height().await? < C::CONFIRMATIONS {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
let confirmed_block = self.coin.get_height().await? - C::CONFIRMATIONS;
|
|
|
|
|
|
|
|
|
|
for b in self.scanned_height() ..= confirmed_block {
|
2022-06-03 22:46:48 -04:00
|
|
|
// If any keys activated at this height, shift them over
|
|
|
|
|
{
|
|
|
|
|
let mut k = 0;
|
|
|
|
|
while k < self.pending.len() {
|
2022-06-05 06:00:21 -04:00
|
|
|
// TODO
|
2022-06-09 02:48:53 -04:00
|
|
|
//if b < self.pending[k].0 {
|
|
|
|
|
//} else if b == self.pending[k].0 {
|
|
|
|
|
if b <= self.pending[k].0 {
|
Utilize zeroize (#76)
* Apply Zeroize to nonces used in Bulletproofs
Also makes bit decomposition constant time for a given amount of
outputs.
* Fix nonce reuse for single-signer CLSAG
* Attach Zeroize to most structures in Monero, and ZOnDrop to anything with private data
* Zeroize private keys and nonces
* Merge prepare_outputs and prepare_transactions
* Ensure CLSAG is constant time
* Pass by borrow where needed, bug fixes
The past few commitments have been one in-progress chunk which I've
broken up as best read.
* Add Zeroize to FROST structs
Still needs to zeroize internally, yet next step. Not quite as
aggressive as Monero, partially due to the limitations of HashMaps,
partially due to less concern about metadata, yet does still delete a
few smaller items of metadata (group key, context string...).
* Remove Zeroize from most Monero multisig structs
These structs largely didn't have private data, just fields with private
data, yet those fields implemented ZeroizeOnDrop making them already
covered. While there is still traces of the transaction left in RAM,
fully purging that was never the intent.
* Use Zeroize within dleq
bitvec doesn't offer Zeroize, so a manual zeroing has been implemented.
* Use Zeroize for random_nonce
It isn't perfect, due to the inability to zeroize the digest, and due to
kp256 requiring a few transformations. It does the best it can though.
Does move the per-curve random_nonce to a provided one, which is allowed
as of https://github.com/cfrg/draft-irtf-cfrg-frost/pull/231.
* Use Zeroize on FROST keygen/signing
* Zeroize constant time multiexp.
* Correct when FROST keygen zeroizes
* Move the FROST keys Arc into FrostKeys
Reduces amount of instances in memory.
* Manually implement Debug for FrostCore to not leak the secret share
* Misc bug fixes
* clippy + multiexp test bug fixes
* Correct FROST key gen share summation
It leaked our own share for ourself.
* Fix cross-group DLEq tests
2022-08-03 03:25:18 -05:00
|
|
|
self.keys.push((self.pending.swap_remove(k).1, vec![]));
|
2022-06-03 22:46:48 -04:00
|
|
|
} else {
|
|
|
|
|
k += 1;
|
|
|
|
|
}
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-09 02:48:53 -04:00
|
|
|
let block = self.coin.get_block(b).await?;
|
2022-06-03 22:46:48 -04:00
|
|
|
for (keys, outputs) in self.keys.iter_mut() {
|
|
|
|
|
outputs.extend(
|
2022-07-15 01:26:07 -04:00
|
|
|
self
|
|
|
|
|
.coin
|
|
|
|
|
.get_outputs(&block, keys.group_key())
|
2022-08-22 12:15:14 -04:00
|
|
|
.await?
|
2022-07-15 01:26:07 -04:00
|
|
|
.iter()
|
|
|
|
|
.cloned()
|
|
|
|
|
.filter(|output| self.db.add_output(output)),
|
2022-06-03 22:46:48 -04:00
|
|
|
);
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
2022-06-09 02:48:53 -04:00
|
|
|
|
|
|
|
|
// Blocks are zero-indexed while heights aren't
|
|
|
|
|
self.db.scanned_to_height(b + 1);
|
2022-06-01 03:30:57 -04:00
|
|
|
}
|
2022-06-09 02:48:53 -04:00
|
|
|
|
2022-06-01 03:30:57 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-06-03 22:46:48 -04:00
|
|
|
|
2022-06-05 06:00:21 -04:00
|
|
|
// This should be called whenever new outputs are received, meaning there was a new block
|
|
|
|
|
// If these outputs were received and sent to Substrate, it should be called after they're
|
|
|
|
|
// included in a block and we have results to act on
|
|
|
|
|
// If these outputs weren't sent to Substrate (change), it should be called immediately
|
|
|
|
|
// with all payments still queued from the last call
|
2022-06-03 22:46:48 -04:00
|
|
|
pub async fn prepare_sends(
|
|
|
|
|
&mut self,
|
|
|
|
|
canonical: usize,
|
2022-06-19 12:19:32 -04:00
|
|
|
payments: Vec<(C::Address, u64)>,
|
2022-07-15 01:26:07 -04:00
|
|
|
fee: C::Fee,
|
2022-06-05 06:00:21 -04:00
|
|
|
) -> Result<(Vec<(C::Address, u64)>, Vec<C::SignableTransaction>), CoinError> {
|
2022-07-22 02:34:36 -04:00
|
|
|
if payments.is_empty() {
|
2022-06-05 06:00:21 -04:00
|
|
|
return Ok((vec![], vec![]));
|
2022-06-03 22:46:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let acknowledged_height = self.acknowledged_height(canonical);
|
|
|
|
|
|
2022-06-05 06:00:21 -04:00
|
|
|
// TODO: Log schedule outputs when MAX_OUTPUTS is lower than payments.len()
|
2022-06-03 22:46:48 -04:00
|
|
|
// Payments is the first set of TXs in the schedule
|
|
|
|
|
// As each payment re-appears, let mut payments = schedule[payment] where the only input is
|
|
|
|
|
// the source payment
|
2022-06-10 09:12:27 -04:00
|
|
|
// let (mut payments, schedule) = schedule(payments);
|
2022-06-03 22:46:48 -04:00
|
|
|
let mut payments = payments;
|
|
|
|
|
|
|
|
|
|
let mut txs = vec![];
|
|
|
|
|
for (keys, outputs) in self.keys.iter_mut() {
|
2022-07-22 02:34:36 -04:00
|
|
|
while !outputs.is_empty() {
|
2022-06-10 09:12:27 -04:00
|
|
|
let (inputs, outputs) = select_inputs_outputs::<C>(outputs, &mut payments);
|
|
|
|
|
// If we can no longer process any payments, move to the next set of keys
|
2022-07-22 02:34:36 -04:00
|
|
|
if outputs.is_empty() {
|
2022-06-10 09:12:27 -04:00
|
|
|
debug_assert_eq!(inputs.len(), 0);
|
2022-06-03 22:46:48 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 09:12:27 -04:00
|
|
|
// Create the transcript for this transaction
|
2022-06-24 18:58:24 -04:00
|
|
|
let mut transcript = RecommendedTranscript::new(b"Serai Processor Wallet Send");
|
2022-07-15 01:26:07 -04:00
|
|
|
transcript
|
|
|
|
|
.append_message(b"canonical_height", &u64::try_from(canonical).unwrap().to_le_bytes());
|
2022-06-05 15:10:50 -04:00
|
|
|
transcript.append_message(
|
|
|
|
|
b"acknowledged_height",
|
2022-07-15 01:26:07 -04:00
|
|
|
&u64::try_from(acknowledged_height).unwrap().to_le_bytes(),
|
2022-06-05 15:10:50 -04:00
|
|
|
);
|
2022-07-15 01:26:07 -04:00
|
|
|
transcript.append_message(b"index", &u64::try_from(txs.len()).unwrap().to_le_bytes());
|
2022-06-10 09:12:27 -04:00
|
|
|
|
2022-07-15 01:26:07 -04:00
|
|
|
let tx = self
|
|
|
|
|
.coin
|
|
|
|
|
.prepare_send(keys.clone(), transcript, acknowledged_height, inputs, &outputs, fee)
|
|
|
|
|
.await?;
|
2022-06-03 22:46:48 -04:00
|
|
|
// self.db.save_tx(tx) // TODO
|
|
|
|
|
txs.push(tx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-05 06:00:21 -04:00
|
|
|
Ok((payments, txs))
|
2022-06-03 22:46:48 -04:00
|
|
|
}
|
2022-06-10 09:36:07 -04:00
|
|
|
|
|
|
|
|
pub async fn attempt_send<N: Network>(
|
|
|
|
|
&mut self,
|
|
|
|
|
network: &mut N,
|
|
|
|
|
prepared: C::SignableTransaction,
|
2022-07-15 01:26:07 -04:00
|
|
|
included: Vec<u16>,
|
2022-06-10 09:36:07 -04:00
|
|
|
) -> Result<(Vec<u8>, Vec<<C::Output as Output>::Id>), SignError> {
|
2022-07-15 01:26:07 -04:00
|
|
|
let attempt =
|
2022-07-22 02:34:36 -04:00
|
|
|
self.coin.attempt_send(prepared, &included).await.map_err(SignError::CoinError)?;
|
2022-06-10 09:36:07 -04:00
|
|
|
|
2022-06-24 08:40:14 -04:00
|
|
|
let (attempt, commitments) = attempt.preprocess(&mut OsRng);
|
2022-07-22 02:34:36 -04:00
|
|
|
let commitments = network.round(commitments).await.map_err(SignError::NetworkError)?;
|
2022-06-24 08:40:14 -04:00
|
|
|
|
2022-07-22 02:34:36 -04:00
|
|
|
let (attempt, share) = attempt.sign(commitments, b"").map_err(SignError::FrostError)?;
|
|
|
|
|
let shares = network.round(share).await.map_err(SignError::NetworkError)?;
|
2022-06-24 08:40:14 -04:00
|
|
|
|
2022-07-22 02:34:36 -04:00
|
|
|
let tx = attempt.complete(shares).map_err(SignError::FrostError)?;
|
2022-06-10 09:36:07 -04:00
|
|
|
|
2022-07-22 02:34:36 -04:00
|
|
|
self.coin.publish_transaction(&tx).await.map_err(SignError::CoinError)
|
2022-06-10 09:36:07 -04:00
|
|
|
}
|
2022-05-28 19:56:59 -04:00
|
|
|
}
|