From 0aa6b561b7fd45896ebfb5bfdb21fab102ae2a50 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 19 Mar 2023 23:22:51 -0400 Subject: [PATCH] Bitcoin SpendableOutput::new --- coins/bitcoin/src/crypto.rs | 10 +++++- coins/bitcoin/src/wallet.rs | 62 ++++++++++++++++++++++++++-------- processor/src/coins/bitcoin.rs | 42 ++++++++--------------- 3 files changed, 72 insertions(+), 42 deletions(-) diff --git a/coins/bitcoin/src/crypto.rs b/coins/bitcoin/src/crypto.rs index 9dadaa4d..38c8c0fe 100644 --- a/coins/bitcoin/src/crypto.rs +++ b/coins/bitcoin/src/crypto.rs @@ -1,8 +1,10 @@ use k256::{ elliptic_curve::sec1::{Tag, ToEncodedPoint}, - ProjectivePoint, + Scalar, ProjectivePoint, }; +use frost::{curve::Secp256k1, ThresholdKeys}; + use bitcoin::XOnlyPublicKey; /// Get the x coordinate of a non-infinity, even point. Panics on invalid input. @@ -27,3 +29,9 @@ 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)) +} diff --git a/coins/bitcoin/src/wallet.rs b/coins/bitcoin/src/wallet.rs index a7405197..104b04c7 100644 --- a/coins/bitcoin/src/wallet.rs +++ b/coins/bitcoin/src/wallet.rs @@ -9,7 +9,7 @@ use rand_core::{RngCore, CryptoRng}; use transcript::{Transcript, RecommendedTranscript}; -use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar}; +use k256::{elliptic_curve::sec1::{Tag, ToEncodedPoint}, Scalar, ProjectivePoint}; use frost::{ curve::{Ciphersuite, Secp256k1}, Participant, ThresholdKeys, FrostError, @@ -19,11 +19,12 @@ use frost::{ use bitcoin::{ hashes::Hash, consensus::encode::{Decodable, serialize}, + schnorr::TweakedPublicKey, util::sighash::{SchnorrSighashType, SighashCache, Prevouts}, - OutPoint, Script, Sequence, Witness, TxIn, TxOut, PackedLockTime, Transaction, Address, + OutPoint, Script, Sequence, Witness, TxIn, TxOut, PackedLockTime, Transaction, Network, Address, }; -use crate::algorithm::Schnorr; +use crate::{crypto::x_only, algorithm::Schnorr}; #[rustfmt::skip] // https://github.com/bitcoin/bitcoin/blob/306ccd4927a2efe325c8d84be1bdb79edeb29b04/src/policy/policy.h#L27 @@ -33,23 +34,56 @@ 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. - pub offset: Scalar, - /// The output to spend. - pub output: TxOut, - /// The TX ID and vout of the output to spend. - pub outpoint: OutPoint, + // 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 { - /// The unique ID for this output (TX ID and vout). - pub fn id(&self) -> [u8; 36] { - serialize(&self.outpoint).try_into().unwrap() + /// 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. diff --git a/processor/src/coins/bitcoin.rs b/processor/src/coins/bitcoin.rs index bd12da12..54586140 100644 --- a/processor/src/coins/bitcoin.rs +++ b/processor/src/coins/bitcoin.rs @@ -3,26 +3,21 @@ use std::{io, collections::HashMap}; use async_trait::async_trait; use transcript::RecommendedTranscript; -use k256::{ - ProjectivePoint, Scalar, - elliptic_curve::sec1::{ToEncodedPoint, Tag}, -}; +use k256::{ProjectivePoint, Scalar}; use frost::{curve::Secp256k1, ThresholdKeys}; use bitcoin_serai::{ bitcoin::{ hashes::Hash as HashTrait, - schnorr::TweakedPublicKey, consensus::{Encodable, Decodable}, psbt::serialize::Serialize, OutPoint, blockdata::script::Instruction, - Transaction, Block, Network, Address as BAddress, + Transaction, Block, Network, }, - crypto::{x_only, make_even}, + crypto::{make_even, tweak_keys}, wallet::{ - SpendableOutput, TransactionError, SignableTransaction as BSignableTransaction, - TransactionMachine, + address, SpendableOutput, TransactionError, SignableTransaction as BSignableTransaction, TransactionMachine, }, rpc::{RpcError, Rpc}, }; @@ -32,7 +27,7 @@ use bitcoin_serai::bitcoin::{ secp256k1::{SECP256K1, SecretKey, Message}, PrivateKey, PublicKey, EcdsaSighashType, blockdata::script::Builder, - PackedLockTime, Sequence, Script, Witness, TxIn, TxOut, + PackedLockTime, Sequence, Script, Witness, TxIn, TxOut, Address as BAddress, }; use serai_client::coins::bitcoin::Address; @@ -78,11 +73,13 @@ impl OutputTrait for Output { } fn id(&self) -> Self::Id { - OutputId(self.output.id()) + let mut res = OutputId::default(); + self.output.outpoint().consensus_encode(&mut res.as_mut()).unwrap(); + res } fn amount(&self) -> u64 { - self.output.output.value + self.output.value() } fn data(&self) -> &[u8] { @@ -275,17 +272,12 @@ impl Coin for Bitcoin { const MAX_INPUTS: usize = 520; const MAX_OUTPUTS: usize = 520; - fn tweak_keys(key: &mut ThresholdKeys) { - let (_, offset) = make_even(key.group_key()); - *key = key.offset(Scalar::from(offset)); + fn tweak_keys(keys: &mut ThresholdKeys) { + *keys = tweak_keys(keys); } - fn address(key: ProjectivePoint) -> Self::Address { - assert!(key.to_encoded_point(true).tag() == Tag::CompressedEvenY, "YKey is odd"); - Address(BAddress::p2tr_tweaked( - TweakedPublicKey::dangerous_assume_tweaked(x_only(&key)), - Network::Bitcoin, - )) + fn address(key: ProjectivePoint) -> Address { + Address(address(Network::Bitcoin, key).unwrap()) } fn branch_address(key: ProjectivePoint) -> Self::Address { @@ -326,11 +318,7 @@ impl Coin for Bitcoin { if let Some(info) = scripts.get(&output.script_pubkey.to_bytes()) { outputs.push(Output { kind: info.1, - output: SpendableOutput { - offset: info.0, - output: output.clone(), - outpoint: OutPoint { txid: tx.txid(), vout: u32::try_from(vout).unwrap() }, - }, + output: SpendableOutput::new(key, Some(info.0), tx, vout).unwrap(), data: (|| { for output in &tx.output { if output.script_pubkey.is_op_return() { @@ -413,7 +401,7 @@ impl Coin for Bitcoin { transcript: plan.transcript(), actual: signable(&plan, Some(tx_fee)).unwrap(), }, - plan.inputs[0].output.outpoint, + *plan.inputs[0].output.outpoint(), )), branch_outputs, ))