diff --git a/Cargo.lock b/Cargo.lock index 500dde16cb..0cf483c98e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,7 +273,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -345,7 +345,7 @@ name = "audit-cache" version = "0.0.0" dependencies = [ "colored", - "reqwest 0.12.8", + "reqwest 0.12.9", ] [[package]] @@ -353,7 +353,7 @@ name = "audit-content-security-policy" version = "0.0.0" dependencies = [ "colored", - "reqwest 0.12.8", + "reqwest 0.12.9", ] [[package]] @@ -628,7 +628,7 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.4.0", "piper", ] @@ -643,7 +643,7 @@ dependencies = [ "new_mime_guess", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -801,7 +801,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -990,7 +990,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1001,7 +1001,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1052,7 +1052,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1062,7 +1062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1082,7 +1082,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "unicode-xid", ] @@ -1140,7 +1140,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1356,9 +1356,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210" dependencies = [ "futures-core", "pin-project-lite", @@ -1372,7 +1372,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1741,7 +1741,7 @@ dependencies = [ "http 1.1.0", "hyper 1.5.0", "hyper-util", - "rustls 0.23.15", + "rustls 0.23.16", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -1779,9 +1779,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", @@ -2162,7 +2162,7 @@ dependencies = [ "jsonrpc-derive", "jsonrpc-http-server", "ord-bitcoincore-rpc", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde", "serde_json", "tempfile", @@ -2368,7 +2368,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -2561,7 +2561,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -2813,7 +2813,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -2889,9 +2889,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -2993,7 +2993,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.85", + "syn 2.0.86", "walkdir", ] @@ -3038,9 +3038,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", @@ -3077,9 +3077,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.15" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "once_cell", "rustls-pki-types", @@ -3254,9 +3254,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -3274,13 +3274,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3345,7 +3345,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3430,7 +3430,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3512,9 +3512,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", @@ -3613,28 +3613,28 @@ dependencies = [ "cfg-if 1.0.0", "fastrand 2.1.1", "once_cell", - "rustix 0.38.37", + "rustix 0.38.38", "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3717,7 +3717,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3746,7 +3746,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.15", + "rustls 0.23.16", "rustls-pki-types", "tokio", ] @@ -4046,7 +4046,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "wasm-bindgen-shared", ] @@ -4080,7 +4080,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4177,7 +4177,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -4188,7 +4188,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -4439,7 +4439,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] diff --git a/crates/mockcore/src/lib.rs b/crates/mockcore/src/lib.rs index 3d0510ff53..8bb2c43891 100644 --- a/crates/mockcore/src/lib.rs +++ b/crates/mockcore/src/lib.rs @@ -5,15 +5,19 @@ use { bitcoin::{ address::{Address, NetworkUnchecked}, amount::SignedAmount, + bip32::{ChildNumber, DerivationPath, Xpriv}, block::Header, blockdata::{script, transaction::Version}, consensus::encode::{deserialize, serialize}, hash_types::{BlockHash, TxMerkleNode}, hashes::Hash, + key::{Keypair, Secp256k1, TapTweak, XOnlyPublicKey}, locktime::absolute::LockTime, pow::CompactTarget, - Amount, Block, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, - Wtxid, + secp256k1::{self, rand}, + sighash::{self, SighashCache, TapSighashType}, + Amount, Block, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, + WPubkeyHash, Witness, Wtxid, }, bitcoincore_rpc::json::{ Bip125Replaceable, CreateRawTransactionInput, EstimateMode, FeeRatePercentiles, @@ -40,6 +44,7 @@ use { time::Duration, }, tempfile::TempDir, + wallet::Wallet, }; const COIN_VALUE: u64 = 100_000_000; @@ -47,6 +52,7 @@ const COIN_VALUE: u64 = 100_000_000; mod api; mod server; mod state; +mod wallet; #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct Descriptor { diff --git a/crates/mockcore/src/server.rs b/crates/mockcore/src/server.rs index a79a16ccac..0fe3176b60 100644 --- a/crates/mockcore/src/server.rs +++ b/crates/mockcore/src/server.rs @@ -476,12 +476,20 @@ impl Api for Server { fn sign_raw_transaction_with_wallet( &self, tx: String, - _utxos: Option>, + utxos: Option>, sighash_type: Option<()>, ) -> Result { assert_eq!(sighash_type, None, "sighash_type param not supported"); let mut transaction: Transaction = deserialize(&hex::decode(tx).unwrap()).unwrap(); + + if let Some(utxos) = &utxos { + // sign for zero-value UTXOs produced by `ord wallet sign` + if utxos[0].amount == Some(Amount::ZERO) { + transaction.input[0].witness = self.state().wallet.sign_bip322(&utxos[0], &transaction); + } + } + for input in &mut transaction.input { if input.witness.is_empty() { input.witness = Witness::from_slice(&[&[0; 64]]); diff --git a/crates/mockcore/src/state.rs b/crates/mockcore/src/state.rs index b036c250e3..9105633d50 100644 --- a/crates/mockcore/src/state.rs +++ b/crates/mockcore/src/state.rs @@ -1,11 +1,4 @@ -use { - super::*, - bitcoin::{ - key::{Keypair, Secp256k1, XOnlyPublicKey}, - secp256k1::rand, - WPubkeyHash, - }, -}; +use super::*; #[derive(Debug)] pub struct State { @@ -25,6 +18,7 @@ pub struct State { pub receive_addresses: Vec
, pub change_addresses: Vec
, pub wallets: BTreeSet, + pub wallet: Wallet, } impl State { @@ -54,20 +48,20 @@ impl State { utxos: BTreeMap::new(), version, wallets: BTreeSet::new(), + wallet: Wallet::new(network), } } pub(crate) fn new_address(&mut self, change: bool) -> Address { - let secp256k1 = Secp256k1::new(); - let keypair = Keypair::new(&secp256k1, &mut rand::thread_rng()); - let (public_key, _parity) = XOnlyPublicKey::from_keypair(&keypair); - let address = Address::p2tr(&secp256k1, public_key, None, self.network); + let address = self.wallet.new_address(); + if change { &mut self.change_addresses } else { &mut self.receive_addresses } .push(address.clone()); + address } diff --git a/crates/mockcore/src/wallet.rs b/crates/mockcore/src/wallet.rs new file mode 100644 index 0000000000..370895e9de --- /dev/null +++ b/crates/mockcore/src/wallet.rs @@ -0,0 +1,111 @@ +use super::*; + +#[derive(Debug)] +pub struct Wallet { + address_indices: HashMap, + master_key: Xpriv, + network: Network, + next_index: u32, + secp: Secp256k1, + derivation_path: DerivationPath, +} + +impl Wallet { + pub fn new(network: Network) -> Self { + let derivation_path = DerivationPath::master() + .child(ChildNumber::Hardened { index: 86 }) + .child(ChildNumber::Hardened { index: 0 }) + .child(ChildNumber::Hardened { index: 0 }) + .child(ChildNumber::Normal { index: 0 }); + + Self { + address_indices: HashMap::new(), + master_key: Xpriv::new_master(network, &[]).unwrap(), + network, + next_index: 0, + secp: Secp256k1::new(), + derivation_path, + } + } + + pub fn new_address(&mut self) -> Address { + let address = { + let derived_key = self + .master_key + .derive_priv( + &self.secp, + &self.derivation_path.child(ChildNumber::Normal { + index: self.next_index, + }), + ) + .unwrap(); + + let keypair = derived_key.to_keypair(&self.secp); + let (internal_key, _parity) = XOnlyPublicKey::from_keypair(&keypair); + + let script = ScriptBuf::new_p2tr(&self.secp, internal_key, None); + + Address::from_script(&script, self.network).unwrap() + }; + + self + .address_indices + .insert(address.clone(), self.next_index); + self.next_index += 1; + + address + } + + pub fn sign_bip322( + &self, + to_spend_input: &SignRawTransactionInput, + to_sign: &Transaction, + ) -> Witness { + let address = Address::from_script(&to_spend_input.script_pub_key, self.network).unwrap(); + let index = self.address_indices[&address]; + let derivation_path = self.derivation_path.child(ChildNumber::Normal { index }); + + let private_key = self + .master_key + .derive_priv(&self.secp, &derivation_path) + .unwrap(); + + let keypair = private_key.to_keypair(&self.secp); + let tweaked_keypair = keypair.tap_tweak(&self.secp, None); + + let sighash_type = TapSighashType::All; + + let mut sighash_cache = SighashCache::new(to_sign.clone()); + + let sighash = sighash_cache + .taproot_key_spend_signature_hash( + 0, + &sighash::Prevouts::All(&[TxOut { + value: Amount::from_sat(0), + script_pubkey: to_spend_input.script_pub_key.clone(), + }]), + sighash_type, + ) + .expect("signature hash should compute"); + + let signature = self.secp.sign_schnorr_no_aux_rand( + &secp256k1::Message::from_digest_slice(sighash.as_ref()) + .expect("should be cryptographically secure hash"), + &tweaked_keypair.to_inner(), + ); + + let witness = sighash_cache + .witness_mut(0) + .expect("getting mutable witness reference should work"); + + witness.push( + bitcoin::taproot::Signature { + signature, + sighash_type, + } + .to_vec(), + ); + + witness.to_owned() + } +} diff --git a/tests/wallet/sign.rs b/tests/wallet/sign.rs index 7ae2a9fae2..9b846de23c 100644 --- a/tests/wallet/sign.rs +++ b/tests/wallet/sign.rs @@ -32,4 +32,13 @@ fn sign() { assert_eq!(address, &sign.address); assert_eq!(message, &sign.message); + + CommandBuilder::new(format!( + "verify --address {} --message {message} --witness {}", + address.clone().assume_checked(), + sign.witness, + )) + .core(&core) + .ord(&ord) + .run_and_extract_stdout(); }