Bitcoin SpendableOutput::new

This commit is contained in:
Luke Parker 2023-03-19 23:22:51 -04:00
parent 60ca3a9599
commit 0aa6b561b7
No known key found for this signature in database
3 changed files with 72 additions and 42 deletions

View file

@ -1,8 +1,10 @@
use k256::{ use k256::{
elliptic_curve::sec1::{Tag, ToEncodedPoint}, elliptic_curve::sec1::{Tag, ToEncodedPoint},
ProjectivePoint, Scalar, ProjectivePoint,
}; };
use frost::{curve::Secp256k1, ThresholdKeys};
use bitcoin::XOnlyPublicKey; use bitcoin::XOnlyPublicKey;
/// Get the x coordinate of a non-infinity, even point. Panics on invalid input. /// 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) (key, c)
} }
/// Tweak keys to ensure they're usable with Bitcoin.
pub fn tweak_keys(keys: &ThresholdKeys<Secp256k1>) -> ThresholdKeys<Secp256k1> {
let (_, offset) = make_even(keys.group_key());
keys.offset(Scalar::from(offset))
}

View file

@ -9,7 +9,7 @@ use rand_core::{RngCore, CryptoRng};
use transcript::{Transcript, RecommendedTranscript}; use transcript::{Transcript, RecommendedTranscript};
use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar}; use k256::{elliptic_curve::sec1::{Tag, ToEncodedPoint}, Scalar, ProjectivePoint};
use frost::{ use frost::{
curve::{Ciphersuite, Secp256k1}, curve::{Ciphersuite, Secp256k1},
Participant, ThresholdKeys, FrostError, Participant, ThresholdKeys, FrostError,
@ -19,11 +19,12 @@ use frost::{
use bitcoin::{ use bitcoin::{
hashes::Hash, hashes::Hash,
consensus::encode::{Decodable, serialize}, consensus::encode::{Decodable, serialize},
schnorr::TweakedPublicKey,
util::sighash::{SchnorrSighashType, SighashCache, Prevouts}, 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] #[rustfmt::skip]
// https://github.com/bitcoin/bitcoin/blob/306ccd4927a2efe325c8d84be1bdb79edeb29b04/src/policy/policy.h#L27 // 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 //https://github.com/bitcoin/bitcoin/blob/a245429d680eb95cf4c0c78e58e63e3f0f5d979a/src/test/transaction_tests.cpp#L815-L816
const DUST: u64 = 674; const DUST: u64 = 674;
/// Return the Taproot address for a public key.
pub fn address(network: Network, key: ProjectivePoint) -> Option<Address> {
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. /// A spendable output.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct SpendableOutput { pub struct SpendableOutput {
/// The scalar offset to obtain the key usable to spend this output. // The scalar offset to obtain the key usable to spend this output.
/// //
/// This field exists in order to support HDKD schemes. // This field exists in order to support HDKD schemes.
pub offset: Scalar, offset: Scalar,
/// The output to spend. // The output to spend.
pub output: TxOut, output: TxOut,
/// The TX ID and vout of the output to spend. // The TX ID and vout of the output to spend.
pub outpoint: OutPoint, outpoint: OutPoint,
} }
impl SpendableOutput { impl SpendableOutput {
/// The unique ID for this output (TX ID and vout). /// Construct a SpendableOutput from an output.
pub fn id(&self) -> [u8; 36] { pub fn new(key: ProjectivePoint, offset: Option<Scalar>, tx: &Transaction, o: usize) -> Option<SpendableOutput> {
serialize(&self.outpoint).try_into().unwrap() 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. /// Read a SpendableOutput from a generic satisfying Read.

View file

@ -3,26 +3,21 @@ use std::{io, collections::HashMap};
use async_trait::async_trait; use async_trait::async_trait;
use transcript::RecommendedTranscript; use transcript::RecommendedTranscript;
use k256::{ use k256::{ProjectivePoint, Scalar};
ProjectivePoint, Scalar,
elliptic_curve::sec1::{ToEncodedPoint, Tag},
};
use frost::{curve::Secp256k1, ThresholdKeys}; use frost::{curve::Secp256k1, ThresholdKeys};
use bitcoin_serai::{ use bitcoin_serai::{
bitcoin::{ bitcoin::{
hashes::Hash as HashTrait, hashes::Hash as HashTrait,
schnorr::TweakedPublicKey,
consensus::{Encodable, Decodable}, consensus::{Encodable, Decodable},
psbt::serialize::Serialize, psbt::serialize::Serialize,
OutPoint, OutPoint,
blockdata::script::Instruction, blockdata::script::Instruction,
Transaction, Block, Network, Address as BAddress, Transaction, Block, Network,
}, },
crypto::{x_only, make_even}, crypto::{make_even, tweak_keys},
wallet::{ wallet::{
SpendableOutput, TransactionError, SignableTransaction as BSignableTransaction, address, SpendableOutput, TransactionError, SignableTransaction as BSignableTransaction, TransactionMachine,
TransactionMachine,
}, },
rpc::{RpcError, Rpc}, rpc::{RpcError, Rpc},
}; };
@ -32,7 +27,7 @@ use bitcoin_serai::bitcoin::{
secp256k1::{SECP256K1, SecretKey, Message}, secp256k1::{SECP256K1, SecretKey, Message},
PrivateKey, PublicKey, EcdsaSighashType, PrivateKey, PublicKey, EcdsaSighashType,
blockdata::script::Builder, blockdata::script::Builder,
PackedLockTime, Sequence, Script, Witness, TxIn, TxOut, PackedLockTime, Sequence, Script, Witness, TxIn, TxOut, Address as BAddress,
}; };
use serai_client::coins::bitcoin::Address; use serai_client::coins::bitcoin::Address;
@ -78,11 +73,13 @@ impl OutputTrait for Output {
} }
fn id(&self) -> Self::Id { 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 { fn amount(&self) -> u64 {
self.output.output.value self.output.value()
} }
fn data(&self) -> &[u8] { fn data(&self) -> &[u8] {
@ -275,17 +272,12 @@ impl Coin for Bitcoin {
const MAX_INPUTS: usize = 520; const MAX_INPUTS: usize = 520;
const MAX_OUTPUTS: usize = 520; const MAX_OUTPUTS: usize = 520;
fn tweak_keys(key: &mut ThresholdKeys<Self::Curve>) { fn tweak_keys(keys: &mut ThresholdKeys<Self::Curve>) {
let (_, offset) = make_even(key.group_key()); *keys = tweak_keys(keys);
*key = key.offset(Scalar::from(offset));
} }
fn address(key: ProjectivePoint) -> Self::Address { fn address(key: ProjectivePoint) -> Address {
assert!(key.to_encoded_point(true).tag() == Tag::CompressedEvenY, "YKey is odd"); Address(address(Network::Bitcoin, key).unwrap())
Address(BAddress::p2tr_tweaked(
TweakedPublicKey::dangerous_assume_tweaked(x_only(&key)),
Network::Bitcoin,
))
} }
fn branch_address(key: ProjectivePoint) -> Self::Address { 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()) { if let Some(info) = scripts.get(&output.script_pubkey.to_bytes()) {
outputs.push(Output { outputs.push(Output {
kind: info.1, kind: info.1,
output: SpendableOutput { output: SpendableOutput::new(key, Some(info.0), tx, vout).unwrap(),
offset: info.0,
output: output.clone(),
outpoint: OutPoint { txid: tx.txid(), vout: u32::try_from(vout).unwrap() },
},
data: (|| { data: (|| {
for output in &tx.output { for output in &tx.output {
if output.script_pubkey.is_op_return() { if output.script_pubkey.is_op_return() {
@ -413,7 +401,7 @@ impl Coin for Bitcoin {
transcript: plan.transcript(), transcript: plan.transcript(),
actual: signable(&plan, Some(tx_fee)).unwrap(), actual: signable(&plan, Some(tx_fee)).unwrap(),
}, },
plan.inputs[0].output.outpoint, *plan.inputs[0].output.outpoint(),
)), )),
branch_outputs, branch_outputs,
)) ))