diff --git a/coins/bitcoin/src/wallet.rs b/coins/bitcoin/src/wallet.rs index 26d602fb..e641e7c1 100644 --- a/coins/bitcoin/src/wallet.rs +++ b/coins/bitcoin/src/wallet.rs @@ -8,7 +8,12 @@ use rand_core::RngCore; use transcript::{Transcript, RecommendedTranscript}; use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar}; -use frost::{curve::Secp256k1, ThresholdKeys, FrostError, algorithm::Schnorr, sign::*}; +use frost::{ + curve::{Ciphersuite, Secp256k1}, + ThresholdKeys, FrostError, + algorithm::Schnorr, + sign::*, +}; use bitcoin::{ hashes::Hash, @@ -21,6 +26,7 @@ use crate::crypto::{BitcoinHram, make_even}; #[derive(Clone, Debug)] pub struct SpendableOutput { + pub offset: Scalar, pub output: TxOut, pub outpoint: OutPoint, } @@ -32,6 +38,7 @@ impl SpendableOutput { pub fn read(r: &mut R) -> io::Result { Ok(SpendableOutput { + offset: Secp256k1::read_F(r)?, output: TxOut::consensus_decode(r) .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid TxOut"))?, outpoint: OutPoint::consensus_decode(r) @@ -40,14 +47,15 @@ impl SpendableOutput { } pub fn serialize(&self) -> Vec { - let mut res = serialize(&self.output); + let mut res = self.offset.to_bytes().to_vec(); + self.output.consensus_encode(&mut res).unwrap(); self.outpoint.consensus_encode(&mut res).unwrap(); res } } #[derive(Clone, Debug)] -pub struct SignableTransaction(Transaction, Vec); +pub struct SignableTransaction(Transaction, Vec, Vec); impl SignableTransaction { fn calculate_weight(inputs: usize, payments: &[(Address, u64)], change: Option<&Address>) -> u64 { @@ -81,6 +89,7 @@ impl SignableTransaction { fee: u64, ) -> Option { let input_sat = inputs.iter().map(|input| input.output.value).sum::(); + let offsets = inputs.iter().map(|input| input.offset).collect(); let tx_ins = inputs .iter() .map(|input| TxIn { @@ -116,6 +125,7 @@ impl SignableTransaction { Some(SignableTransaction( Transaction { version: 2, lock_time: PackedLockTime::ZERO, input: tx_ins, output: tx_outs }, + offsets, inputs.drain(..).map(|input| input.output).collect(), )) } @@ -140,10 +150,14 @@ impl SignableTransaction { } let mut sigs = vec![]; - for _ in 0 .. tx.input.len() { + for i in 0 .. tx.input.len() { // TODO: Use the above transcript here sigs.push( - AlgorithmMachine::new(Schnorr::::new(), keys.clone()).unwrap(), + AlgorithmMachine::new( + Schnorr::::new(), + keys.clone().offset(self.1[i]), + ) + .unwrap(), ); } @@ -237,7 +251,7 @@ impl SignMachine for TransactionSignMachine { .collect::>(); let mut cache = SighashCache::new(&self.tx.0); - let prevouts = Prevouts::All(&self.tx.1); + let prevouts = Prevouts::All(&self.tx.2); let mut shares = Vec::with_capacity(self.sigs.len()); let sigs = self diff --git a/processor/src/coin/bitcoin.rs b/processor/src/coin/bitcoin.rs index f5352aee..57f925b7 100644 --- a/processor/src/coin/bitcoin.rs +++ b/processor/src/coin/bitcoin.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{io, collections::HashMap}; use async_trait::async_trait; @@ -41,13 +41,12 @@ impl BlockTrait for Block { pub struct Fee(u64); #[derive(Clone, Debug)] -pub struct Output(SpendableOutput); +pub struct Output(SpendableOutput, OutputType); impl OutputTrait for Output { type Id = [u8; 36]; - // TODO: Implement later fn kind(&self) -> OutputType { - OutputType::External + self.1 } fn id(&self) -> Self::Id { @@ -59,11 +58,13 @@ impl OutputTrait for Output { } fn serialize(&self) -> Vec { - self.0.serialize() + let mut res = self.0.serialize(); + self.1.write(&mut res).unwrap(); + res } fn read(reader: &mut R) -> io::Result { - SpendableOutput::read(reader).map(Output) + Ok(Output(SpendableOutput::read(reader)?, OutputType::read(reader)?)) } } @@ -74,10 +75,32 @@ pub struct SignableTransaction { actual: BSignableTransaction, } +fn next_key(mut key: ProjectivePoint, i: usize) -> (ProjectivePoint, Scalar) { + let mut offset = Scalar::ZERO; + for _ in 0 .. i { + key += ProjectivePoint::GENERATOR; + offset += Scalar::ONE; + + let even_offset; + (key, even_offset) = make_even(key); + offset += Scalar::from(even_offset); + } + (key, offset) +} + +fn branch(key: ProjectivePoint) -> (ProjectivePoint, Scalar) { + next_key(key, 1) +} + +fn change(key: ProjectivePoint) -> (ProjectivePoint, Scalar) { + next_key(key, 2) +} + #[derive(Clone, Debug)] pub struct Bitcoin { pub(crate) rpc: Rpc, } + impl Bitcoin { pub async fn new(url: String) -> Bitcoin { Bitcoin { rpc: Rpc::new(url) } @@ -129,9 +152,8 @@ impl Coin for Bitcoin { ) } - // TODO: Implement later fn branch_address(&self, key: ProjectivePoint) -> Self::Address { - self.address(key) + self.address(branch(key).0) } async fn get_latest_block_number(&self) -> Result { @@ -149,17 +171,31 @@ impl Coin for Bitcoin { block: &Self::Block, key: ProjectivePoint, ) -> Result, CoinError> { - let main_addr = self.address(key); + let external = (key, Scalar::ZERO); + let branch = branch(key); + let change = change(key); + + let entry = + |pair: (_, _), kind| (self.address(pair.0).script_pubkey().to_bytes(), (pair.1, kind)); + let scripts = HashMap::from([ + entry(external, OutputType::External), + entry(branch, OutputType::Branch), + entry(change, OutputType::Change), + ]); let mut outputs = Vec::new(); // Skip the coinbase transaction which is burdened by maturity for tx in &block.txdata[1 ..] { for (vout, output) in tx.output.iter().enumerate() { - if output.script_pubkey == main_addr.script_pubkey() { - outputs.push(Output(SpendableOutput { - output: output.clone(), - outpoint: OutPoint { txid: tx.txid(), vout: u32::try_from(vout).unwrap() }, - })); + if let Some(info) = scripts.get(&output.script_pubkey.to_bytes()) { + outputs.push(Output( + SpendableOutput { + offset: info.0, + output: output.clone(), + outpoint: OutPoint { txid: tx.txid(), vout: u32::try_from(vout).unwrap() }, + }, + info.1, + )); } } } @@ -174,7 +210,7 @@ impl Coin for Bitcoin { _: usize, mut inputs: Vec, payments: &[(Address, u64)], - change: Option, + change_key: Option, fee: Fee, ) -> Result { Ok(SignableTransaction { @@ -183,8 +219,7 @@ impl Coin for Bitcoin { actual: BSignableTransaction::new( inputs.drain(..).map(|input| input.0).collect(), payments, - // TODO: Diversify to a proper change address - change.map(|change| self.address(change)), + change_key.map(|change_key| self.address(change(change_key).0)), fee.0, ) .ok_or(CoinError::NotEnoughFunds)?, diff --git a/processor/src/coin/mod.rs b/processor/src/coin/mod.rs index aa742a98..3a81731e 100644 --- a/processor/src/coin/mod.rs +++ b/processor/src/coin/mod.rs @@ -1,4 +1,4 @@ -use std::marker::Send; +use std::io; use async_trait::async_trait; use thiserror::Error; @@ -36,6 +36,27 @@ pub enum OutputType { Change, } +impl OutputType { + fn write(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&[match self { + OutputType::External => 0, + OutputType::Branch => 1, + OutputType::Change => 2, + }]) + } + + fn read(reader: &mut R) -> io::Result { + let mut byte = [0; 1]; + reader.read_exact(&mut byte)?; + Ok(match byte[0] { + 0 => OutputType::External, + 1 => OutputType::Branch, + 2 => OutputType::Change, + _ => Err(io::Error::new(io::ErrorKind::Other, "invalid OutputType"))?, + }) + } +} + pub trait Output: Sized + Clone { type Id: Clone + Copy + AsRef<[u8]>;