From 597122b2e00a533c718370196356f37d80186e55 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 20 Mar 2023 01:02:06 -0400 Subject: [PATCH] Add a Scanner to bitcoin-serai Moves the processor to it. This ends up as a net-neutral LoC change to the processor, unfortunately, yet this makes bitcoin-serai safer/easier to use, and increases the processor's usage of bitcoin-serai. Also re-organizes bitcoin-serai a bit. --- coins/bitcoin/src/algorithm.rs | 131 --------------- coins/bitcoin/src/crypto.rs | 141 +++++++++++++++- coins/bitcoin/src/lib.rs | 2 - coins/bitcoin/src/tests/mod.rs | 3 +- coins/bitcoin/src/wallet/mod.rs | 158 ++++++++++++++++++ .../bitcoin/src/{wallet.rs => wallet/send.rs} | 96 +---------- processor/src/coins/bitcoin.rs | 116 +++++++------ processor/src/coins/mod.rs | 2 +- 8 files changed, 363 insertions(+), 286 deletions(-) delete mode 100644 coins/bitcoin/src/algorithm.rs create mode 100644 coins/bitcoin/src/wallet/mod.rs rename coins/bitcoin/src/{wallet.rs => wallet/send.rs} (80%) diff --git a/coins/bitcoin/src/algorithm.rs b/coins/bitcoin/src/algorithm.rs deleted file mode 100644 index c5a668d4..00000000 --- a/coins/bitcoin/src/algorithm.rs +++ /dev/null @@ -1,131 +0,0 @@ -use core::fmt::Debug; -use std::io; - -use lazy_static::lazy_static; - -use zeroize::Zeroizing; -use rand_core::{RngCore, CryptoRng}; - -use sha2::{Digest, Sha256}; -use transcript::Transcript; - -use secp256k1::schnorr::Signature; -use k256::{elliptic_curve::ops::Reduce, U256, Scalar, ProjectivePoint}; -use frost::{ - curve::{Ciphersuite, Secp256k1}, - Participant, ThresholdKeys, ThresholdView, FrostError, - algorithm::{Hram as HramTrait, Algorithm, Schnorr as FrostSchnorr}, -}; - -use crate::crypto::{x, make_even}; - -/// A BIP-340 compatible HRAm for use with the modular-frost Schnorr Algorithm. -/// -/// If passed an odd nonce, it will have the generator added until it is even. -#[derive(Clone, Copy, Debug)] -pub struct Hram {} - -lazy_static! { - static ref TAG_HASH: [u8; 32] = Sha256::digest(b"BIP0340/challenge").into(); -} - -#[allow(non_snake_case)] -impl HramTrait for Hram { - fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar { - // Convert the nonce to be even - let (R, _) = make_even(*R); - - let mut data = Sha256::new(); - data.update(*TAG_HASH); - data.update(*TAG_HASH); - data.update(x(&R)); - data.update(x(A)); - data.update(m); - - Scalar::from_uint_reduced(U256::from_be_slice(&data.finalize())) - } -} - -/// BIP-340 Schnorr signature algorithm. -/// -/// This must be used with a ThresholdKeys whose group key is even. If it is odd, this will panic. -#[derive(Clone)] -pub struct Schnorr(FrostSchnorr); -impl Schnorr { - /// Construct a Schnorr algorithm continuing the specified transcript. - pub fn new(transcript: T) -> Schnorr { - Schnorr(FrostSchnorr::new(transcript)) - } -} - -impl Algorithm for Schnorr { - type Transcript = T; - type Addendum = (); - type Signature = Signature; - - fn transcript(&mut self) -> &mut Self::Transcript { - self.0.transcript() - } - - fn nonces(&self) -> Vec> { - self.0.nonces() - } - - fn preprocess_addendum( - &mut self, - rng: &mut R, - keys: &ThresholdKeys, - ) { - self.0.preprocess_addendum(rng, keys) - } - - fn read_addendum(&self, reader: &mut R) -> io::Result { - self.0.read_addendum(reader) - } - - fn process_addendum( - &mut self, - view: &ThresholdView, - i: Participant, - addendum: (), - ) -> Result<(), FrostError> { - self.0.process_addendum(view, i, addendum) - } - - fn sign_share( - &mut self, - params: &ThresholdView, - nonce_sums: &[Vec<::G>], - nonces: Vec::F>>, - msg: &[u8], - ) -> ::F { - self.0.sign_share(params, nonce_sums, nonces, msg) - } - - #[must_use] - fn verify( - &self, - group_key: ProjectivePoint, - nonces: &[Vec], - sum: Scalar, - ) -> Option { - self.0.verify(group_key, nonces, sum).map(|mut sig| { - // Make the R of the final signature even - let offset; - (sig.R, offset) = make_even(sig.R); - // s = r + cx. Since we added to the r, add to s - sig.s += Scalar::from(offset); - // Convert to a secp256k1 signature - Signature::from_slice(&sig.serialize()[1 ..]).unwrap() - }) - } - - fn verify_share( - &self, - verification_share: ProjectivePoint, - nonces: &[Vec], - share: Scalar, - ) -> Result, ()> { - self.0.verify_share(verification_share, nonces, share) - } -} diff --git a/coins/bitcoin/src/crypto.rs b/coins/bitcoin/src/crypto.rs index 38c8c0fe..ac07652b 100644 --- a/coins/bitcoin/src/crypto.rs +++ b/coins/bitcoin/src/crypto.rs @@ -1,9 +1,27 @@ -use k256::{ - elliptic_curve::sec1::{Tag, ToEncodedPoint}, - Scalar, ProjectivePoint, -}; +use core::fmt::Debug; +use std::io; -use frost::{curve::Secp256k1, ThresholdKeys}; +use lazy_static::lazy_static; + +use zeroize::Zeroizing; +use rand_core::{RngCore, CryptoRng}; + +use sha2::{Digest, Sha256}; +use transcript::Transcript; + +use secp256k1::schnorr::Signature; +use k256::{ + elliptic_curve::{ + ops::Reduce, + sec1::{Tag, ToEncodedPoint}, + }, + U256, Scalar, ProjectivePoint, +}; +use frost::{ + curve::{Ciphersuite, Secp256k1}, + Participant, ThresholdKeys, ThresholdView, FrostError, + algorithm::{Hram as HramTrait, Algorithm, Schnorr as FrostSchnorr}, +}; use bitcoin::XOnlyPublicKey; @@ -30,8 +48,113 @@ pub fn make_even(mut key: ProjectivePoint) -> (ProjectivePoint, u64) { (key, c) } -/// Tweak keys to ensure they're usable with Bitcoin. -pub fn tweak_keys(keys: &ThresholdKeys) -> ThresholdKeys { - let (_, offset) = make_even(keys.group_key()); - keys.offset(Scalar::from(offset)) +/// A BIP-340 compatible HRAm for use with the modular-frost Schnorr Algorithm. +/// +/// If passed an odd nonce, it will have the generator added until it is even. +#[derive(Clone, Copy, Debug)] +pub struct Hram {} + +lazy_static! { + static ref TAG_HASH: [u8; 32] = Sha256::digest(b"BIP0340/challenge").into(); +} + +#[allow(non_snake_case)] +impl HramTrait for Hram { + fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar { + // Convert the nonce to be even + let (R, _) = make_even(*R); + + let mut data = Sha256::new(); + data.update(*TAG_HASH); + data.update(*TAG_HASH); + data.update(x(&R)); + data.update(x(A)); + data.update(m); + + Scalar::from_uint_reduced(U256::from_be_slice(&data.finalize())) + } +} + +/// BIP-340 Schnorr signature algorithm. +/// +/// This must be used with a ThresholdKeys whose group key is even. If it is odd, this will panic. +#[derive(Clone)] +pub struct Schnorr(FrostSchnorr); +impl Schnorr { + /// Construct a Schnorr algorithm continuing the specified transcript. + pub fn new(transcript: T) -> Schnorr { + Schnorr(FrostSchnorr::new(transcript)) + } +} + +impl Algorithm for Schnorr { + type Transcript = T; + type Addendum = (); + type Signature = Signature; + + fn transcript(&mut self) -> &mut Self::Transcript { + self.0.transcript() + } + + fn nonces(&self) -> Vec> { + self.0.nonces() + } + + fn preprocess_addendum( + &mut self, + rng: &mut R, + keys: &ThresholdKeys, + ) { + self.0.preprocess_addendum(rng, keys) + } + + fn read_addendum(&self, reader: &mut R) -> io::Result { + self.0.read_addendum(reader) + } + + fn process_addendum( + &mut self, + view: &ThresholdView, + i: Participant, + addendum: (), + ) -> Result<(), FrostError> { + self.0.process_addendum(view, i, addendum) + } + + fn sign_share( + &mut self, + params: &ThresholdView, + nonce_sums: &[Vec<::G>], + nonces: Vec::F>>, + msg: &[u8], + ) -> ::F { + self.0.sign_share(params, nonce_sums, nonces, msg) + } + + #[must_use] + fn verify( + &self, + group_key: ProjectivePoint, + nonces: &[Vec], + sum: Scalar, + ) -> Option { + self.0.verify(group_key, nonces, sum).map(|mut sig| { + // Make the R of the final signature even + let offset; + (sig.R, offset) = make_even(sig.R); + // s = r + cx. Since we added to the r, add to s + sig.s += Scalar::from(offset); + // Convert to a secp256k1 signature + Signature::from_slice(&sig.serialize()[1 ..]).unwrap() + }) + } + + fn verify_share( + &self, + verification_share: ProjectivePoint, + nonces: &[Vec], + share: Scalar, + ) -> Result, ()> { + self.0.verify_share(verification_share, nonces, share) + } } diff --git a/coins/bitcoin/src/lib.rs b/coins/bitcoin/src/lib.rs index 289104bd..a3d78d8c 100644 --- a/coins/bitcoin/src/lib.rs +++ b/coins/bitcoin/src/lib.rs @@ -3,8 +3,6 @@ pub use bitcoin; /// Cryptographic helpers. pub mod crypto; -/// BIP-340 Schnorr signature algorithm. -pub mod algorithm; /// Wallet functionality to create transactions. pub mod wallet; /// A minimal asynchronous Bitcoin RPC client. diff --git a/coins/bitcoin/src/tests/mod.rs b/coins/bitcoin/src/tests/mod.rs index 96827ab1..0fdc2aa8 100644 --- a/coins/bitcoin/src/tests/mod.rs +++ b/coins/bitcoin/src/tests/mod.rs @@ -14,8 +14,7 @@ use frost::{ }; use crate::{ - crypto::{x_only, make_even}, - algorithm::Schnorr, + crypto::{x_only, make_even, Schnorr}, rpc::Rpc, }; diff --git a/coins/bitcoin/src/wallet/mod.rs b/coins/bitcoin/src/wallet/mod.rs new file mode 100644 index 00000000..c443bc7c --- /dev/null +++ b/coins/bitcoin/src/wallet/mod.rs @@ -0,0 +1,158 @@ +use std::{ + io::{self, Read, Write}, + collections::HashMap, +}; + +use k256::{ + elliptic_curve::sec1::{Tag, ToEncodedPoint}, + Scalar, ProjectivePoint, +}; +use frost::{ + curve::{Ciphersuite, Secp256k1}, + ThresholdKeys, +}; + +use bitcoin::{ + consensus::encode::{Decodable, serialize}, + schnorr::TweakedPublicKey, + OutPoint, Script, TxOut, Transaction, Block, Network, Address, +}; + +use crate::crypto::{x_only, make_even}; + +mod send; +pub use send::*; + +/// Tweak keys to ensure they're usable with Bitcoin. +pub fn tweak_keys(keys: &ThresholdKeys) -> ThresholdKeys { + let (_, offset) = make_even(keys.group_key()); + keys.offset(Scalar::from(offset)) +} + +/// Return the Taproot address for a public key. +pub fn address(network: Network, key: ProjectivePoint) -> Option
{ + if key.to_encoded_point(true).tag() != Tag::CompressedEvenY { + return None; + } + + Some(Address::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only(&key)), network)) +} + +/// A spendable output. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ReceivedOutput { + // The scalar offset to obtain the key usable to spend this output. + // + // This field exists in order to support HDKD schemes. + offset: Scalar, + // The output to spend. + output: TxOut, + // The TX ID and vout of the output to spend. + outpoint: OutPoint, +} + +impl ReceivedOutput { + /// The offset for this output. + pub fn offset(&self) -> Scalar { + self.offset + } + + /// The outpoint for this output. + pub fn outpoint(&self) -> &OutPoint { + &self.outpoint + } + + /// The value of this output. + pub fn value(&self) -> u64 { + self.output.value + } + + /// Read a ReceivedOutput from a generic satisfying Read. + pub fn read(r: &mut R) -> io::Result { + Ok(ReceivedOutput { + 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) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid OutPoint"))?, + }) + } + + /// Write a ReceivedOutput to a generic satisfying Write. + pub fn write(&self, w: &mut W) -> io::Result<()> { + w.write_all(&self.offset.to_bytes())?; + w.write_all(&serialize(&self.output))?; + w.write_all(&serialize(&self.outpoint)) + } + + /// Serialize a ReceivedOutput to a Vec. + pub fn serialize(&self) -> Vec { + let mut res = vec![]; + self.write(&mut res).unwrap(); + res + } +} + +/// A transaction scanner capable of being used with HDKD schemes. +#[derive(Clone, Debug)] +pub struct Scanner { + key: ProjectivePoint, + scripts: HashMap, +} + +impl Scanner { + /// Construct a Scanner for a key. + /// + /// Returns None if this key can't be scanned for. + pub fn new(key: ProjectivePoint) -> Option { + let mut scripts = HashMap::new(); + // Uses Network::Bitcoin since network is irrelevant here + scripts.insert(address(Network::Bitcoin, key)?.script_pubkey(), Scalar::ZERO); + Some(Scanner { key, scripts }) + } + + /// Register an offset to scan for. + /// + /// Due to Bitcoin's requirement that points are even, not every offset may be used. + /// If an offset isn't usable, it will be incremented until it is. If this offset is already + /// present, None is returned. Else, Some(offset) will be, with the used offset. + pub fn register_offset(&mut self, mut offset: Scalar) -> Option { + loop { + match address(Network::Bitcoin, self.key + (ProjectivePoint::GENERATOR * offset)) { + Some(address) => { + let script = address.script_pubkey(); + if self.scripts.contains_key(&script) { + None?; + } + self.scripts.insert(script, offset); + return Some(offset); + } + None => offset += Scalar::ONE, + } + } + } + + /// Scan a transaction. + pub fn scan_transaction(&self, tx: &Transaction) -> Vec { + let mut res = vec![]; + for (vout, output) in tx.output.iter().enumerate() { + if let Some(offset) = self.scripts.get(&output.script_pubkey) { + res.push(ReceivedOutput { + offset: *offset, + output: output.clone(), + outpoint: OutPoint::new(tx.txid(), u32::try_from(vout).unwrap()), + }); + } + } + res + } + + /// Scan a block. + pub fn scan_block(&self, block: &Block) -> Vec { + let mut res = vec![]; + for tx in &block.txdata { + res.extend(self.scan_transaction(tx)); + } + res + } +} diff --git a/coins/bitcoin/src/wallet.rs b/coins/bitcoin/src/wallet/send.rs similarity index 80% rename from coins/bitcoin/src/wallet.rs rename to coins/bitcoin/src/wallet/send.rs index 104b04c7..c3fe1b17 100644 --- a/coins/bitcoin/src/wallet.rs +++ b/coins/bitcoin/src/wallet/send.rs @@ -1,5 +1,5 @@ use std::{ - io::{self, Read, Write}, + io::{self, Read}, collections::HashMap, }; @@ -9,22 +9,16 @@ use rand_core::{RngCore, CryptoRng}; use transcript::{Transcript, RecommendedTranscript}; -use k256::{elliptic_curve::sec1::{Tag, ToEncodedPoint}, Scalar, ProjectivePoint}; -use frost::{ - curve::{Ciphersuite, Secp256k1}, - Participant, ThresholdKeys, FrostError, - sign::*, -}; +use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar}; +use frost::{curve::Secp256k1, Participant, ThresholdKeys, FrostError, sign::*}; use bitcoin::{ hashes::Hash, - consensus::encode::{Decodable, serialize}, - schnorr::TweakedPublicKey, util::sighash::{SchnorrSighashType, SighashCache, Prevouts}, - OutPoint, Script, Sequence, Witness, TxIn, TxOut, PackedLockTime, Transaction, Network, Address, + OutPoint, Script, Sequence, Witness, TxIn, TxOut, PackedLockTime, Transaction, Address, }; -use crate::{crypto::x_only, algorithm::Schnorr}; +use crate::{crypto::Schnorr, wallet::ReceivedOutput}; #[rustfmt::skip] // https://github.com/bitcoin/bitcoin/blob/306ccd4927a2efe325c8d84be1bdb79edeb29b04/src/policy/policy.h#L27 @@ -34,84 +28,6 @@ const MAX_STANDARD_TX_WEIGHT: u64 = 400_000; //https://github.com/bitcoin/bitcoin/blob/a245429d680eb95cf4c0c78e58e63e3f0f5d979a/src/test/transaction_tests.cpp#L815-L816 const DUST: u64 = 674; -/// Return the Taproot address for a public key. -pub fn address(network: Network, key: ProjectivePoint) -> Option
{ - if key.to_encoded_point(true).tag() != Tag::CompressedEvenY { - return None; - } - - Some(Address::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only(&key)), network)) -} - -/// A spendable output. -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct SpendableOutput { - // The scalar offset to obtain the key usable to spend this output. - // - // This field exists in order to support HDKD schemes. - offset: Scalar, - // The output to spend. - output: TxOut, - // The TX ID and vout of the output to spend. - outpoint: OutPoint, -} - -impl SpendableOutput { - /// Construct a SpendableOutput from an output. - pub fn new(key: ProjectivePoint, offset: Option, tx: &Transaction, o: usize) -> Option { - let offset = offset.unwrap_or(Scalar::ZERO); - // Uses Network::Bitcoin since network is irrelevant here - let address = address(Network::Bitcoin, key + (ProjectivePoint::GENERATOR * offset))?; - - let output = tx.output.get(o)?; - - if output.script_pubkey == address.script_pubkey() { - return Some(SpendableOutput { - offset, - output: output.clone(), - outpoint: OutPoint { txid: tx.txid(), vout: u32::try_from(o).unwrap() }, - }); - } - - None - } - - /// The outpoint for this output. - pub fn outpoint(&self) -> &OutPoint { - &self.outpoint - } - - /// The value of this output. - pub fn value(&self) -> u64 { - self.output.value - } - - /// Read a SpendableOutput from a generic satisfying Read. - 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) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid OutPoint"))?, - }) - } - - /// Write a SpendableOutput to a generic satisfying Write. - pub fn write(&self, w: &mut W) -> io::Result<()> { - w.write_all(&self.offset.to_bytes())?; - w.write_all(&serialize(&self.output))?; - w.write_all(&serialize(&self.outpoint)) - } - - /// Serialize a SpendableOutput to a Vec. - pub fn serialize(&self) -> Vec { - let mut res = vec![]; - self.write(&mut res).unwrap(); - res - } -} - #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum TransactionError { #[error("no inputs were specified")] @@ -188,7 +104,7 @@ impl SignableTransaction { /// /// If data is specified, an OP_RETURN output will be added with it. pub fn new( - mut inputs: Vec, + mut inputs: Vec, payments: &[(Address, u64)], change: Option
, data: Option>, diff --git a/processor/src/coins/bitcoin.rs b/processor/src/coins/bitcoin.rs index 54586140..d92704da 100644 --- a/processor/src/coins/bitcoin.rs +++ b/processor/src/coins/bitcoin.rs @@ -3,8 +3,12 @@ use std::{io, collections::HashMap}; use async_trait::async_trait; use transcript::RecommendedTranscript; +use group::ff::PrimeField; use k256::{ProjectivePoint, Scalar}; -use frost::{curve::Secp256k1, ThresholdKeys}; +use frost::{ + curve::{Curve, Secp256k1}, + ThresholdKeys, +}; use bitcoin_serai::{ bitcoin::{ @@ -15,9 +19,9 @@ use bitcoin_serai::{ blockdata::script::Instruction, Transaction, Block, Network, }, - crypto::{make_even, tweak_keys}, wallet::{ - address, SpendableOutput, TransactionError, SignableTransaction as BSignableTransaction, TransactionMachine, + tweak_keys, address, ReceivedOutput, Scanner, TransactionError, + SignableTransaction as BSignableTransaction, TransactionMachine, }, rpc::{RpcError, Rpc}, }; @@ -61,7 +65,7 @@ impl AsMut<[u8]> for OutputId { #[derive(Clone, PartialEq, Eq, Debug)] pub struct Output { kind: OutputType, - output: SpendableOutput, + output: ReceivedOutput, data: Vec, } @@ -96,7 +100,7 @@ impl OutputTrait for Output { fn read(reader: &mut R) -> io::Result { Ok(Output { kind: OutputType::read(reader)?, - output: SpendableOutput::read(reader)?, + output: ReceivedOutput::read(reader)?, data: { let mut data_len = [0; 2]; reader.read_exact(&mut data_len)?; @@ -179,25 +183,35 @@ impl BlockTrait for Block { } } -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) +const KEY_DST: &[u8] = b"Bitcoin Key"; +lazy_static::lazy_static! { + static ref BRANCH_OFFSET: Scalar = Secp256k1::hash_to_F(KEY_DST, b"branch"); + static ref CHANGE_OFFSET: Scalar = Secp256k1::hash_to_F(KEY_DST, b"change"); } -fn branch(key: ProjectivePoint) -> (ProjectivePoint, Scalar) { - next_key(key, 1) -} +fn scanner( + key: ProjectivePoint, +) -> (Scanner, HashMap, HashMap, OutputType>) { + let mut scanner = Scanner::new(key).unwrap(); + let mut offsets = HashMap::from([(OutputType::External, Scalar::ZERO)]); -fn change(key: ProjectivePoint) -> (ProjectivePoint, Scalar) { - next_key(key, 2) + let zero = Scalar::ZERO.to_repr(); + let zero_ref: &[u8] = zero.as_ref(); + let mut kinds = HashMap::from([(zero_ref.to_vec(), OutputType::External)]); + + let mut register = |kind, offset| { + let offset = scanner.register_offset(offset).expect("offset collision"); + offsets.insert(kind, offset); + + let offset = offset.to_repr(); + let offset_ref: &[u8] = offset.as_ref(); + kinds.insert(offset_ref.to_vec(), kind); + }; + + register(OutputType::Branch, *BRANCH_OFFSET); + register(OutputType::Change, *CHANGE_OFFSET); + + (scanner, offsets, kinds) } #[derive(Clone, Debug)] @@ -281,7 +295,8 @@ impl Coin for Bitcoin { } fn branch_address(key: ProjectivePoint) -> Self::Address { - Self::address(branch(key).0) + let (_, offsets, _) = scanner(key); + Self::address(key + (ProjectivePoint::GENERATOR * offsets[&OutputType::Branch])) } async fn get_latest_block_number(&self) -> Result { @@ -299,39 +314,33 @@ impl Coin for Bitcoin { block: &Self::Block, key: ProjectivePoint, ) -> Result, CoinError> { - let external = (key, Scalar::ZERO); - let branch = branch(key); - let change = change(key); + let (scanner, _, kinds) = scanner(key); - let entry = - |pair: (_, _), kind| (Self::address(pair.0).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(); + let mut outputs = vec![]; // Skip the coinbase transaction which is burdened by maturity for tx in &block.txdata[1 ..] { - for (vout, output) in tx.output.iter().enumerate() { - if let Some(info) = scripts.get(&output.script_pubkey.to_bytes()) { - outputs.push(Output { - kind: info.1, - output: SpendableOutput::new(key, Some(info.0), tx, vout).unwrap(), - data: (|| { - for output in &tx.output { - if output.script_pubkey.is_op_return() { - match output.script_pubkey.instructions_minimal().last() { - Some(Ok(Instruction::PushBytes(data))) => return data.to_vec(), - _ => continue, - } + for output in scanner.scan_transaction(tx) { + let offset_repr = output.offset().to_repr(); + let offset_repr_ref: &[u8] = offset_repr.as_ref(); + let kind = kinds[offset_repr_ref]; + + let data = if kind == OutputType::External { + (|| { + for output in &tx.output { + if output.script_pubkey.is_op_return() { + match output.script_pubkey.instructions_minimal().last() { + Some(Ok(Instruction::PushBytes(data))) => return data.to_vec(), + _ => continue, } } - vec![] - })(), - }); - } + } + vec![] + })() + } else { + vec![] + }; + + outputs.push(Output { kind, output, data }) } } @@ -360,7 +369,12 @@ impl Coin for Bitcoin { match BSignableTransaction::new( plan.inputs.iter().map(|input| input.output.clone()).collect(), &payments, - plan.change.map(|key| Self::address(change(key).0).0), + plan + .change + .map(|key| { + let (_, offsets, _) = scanner(key); + Self::address(key + (ProjectivePoint::GENERATOR * offsets[&OutputType::Change])).0 + }), None, fee.0, ) { diff --git a/processor/src/coins/mod.rs b/processor/src/coins/mod.rs index 2c8378da..bc73a7d2 100644 --- a/processor/src/coins/mod.rs +++ b/processor/src/coins/mod.rs @@ -34,7 +34,7 @@ pub trait Id: } impl + AsMut<[u8]> + Debug> Id for I {} -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum OutputType { // Needs to be processed/sent up to Substrate External,