mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-22 15:19:06 +00:00
Bitcoin SpendableOutput::new
This commit is contained in:
parent
60ca3a9599
commit
0aa6b561b7
3 changed files with 72 additions and 42 deletions
|
@ -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))
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
))
|
))
|
||||||
|
|
Loading…
Reference in a new issue