diff --git a/Cargo.lock b/Cargo.lock index bf98299d..3320988d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -862,6 +862,16 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.13.1" @@ -888,9 +898,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bech32" -version = "0.10.0-beta" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "beef" @@ -947,14 +957,16 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.31.2" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" +checksum = "7170e7750a20974246f17ece04311b4205a6155f1db564c5b224af817663c3ea" dependencies = [ + "base58ck", "bech32", "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", "bitcoin_hashes", - "core2 0.3.3", "hex-conservative", "hex_lit", "secp256k1", @@ -963,13 +975,19 @@ dependencies = [ [[package]] name = "bitcoin-internals" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" dependencies = [ "serde", ] +[[package]] +name = "bitcoin-io" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" + [[package]] name = "bitcoin-serai" version = "0.3.0" @@ -991,13 +1009,22 @@ dependencies = [ ] [[package]] -name = "bitcoin_hashes" -version = "0.13.0" +name = "bitcoin-units" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +checksum = "cb54da0b28892f3c52203a7191534033e051b6f4b52bc15480681b57b7e036f5" dependencies = [ "bitcoin-internals", - "core2 0.3.3", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", "hex-conservative", "serde", ] @@ -1400,7 +1427,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd94671561e36e4e7de75f753f577edafb0e7c05d6e4547229fdf7938fbcd2c3" dependencies = [ - "core2 0.4.0", + "core2", "multibase", "multihash 0.18.1", "serde", @@ -1584,15 +1611,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "core2" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3" -dependencies = [ - "memchr", -] - [[package]] name = "core2" version = "0.4.0" @@ -3128,11 +3146,11 @@ dependencies = [ [[package]] name = "hex-conservative" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" +checksum = "e1aa273bf451e37ed35ced41c71a5e2a4e29064afb104158f2514bcd71c2c986" dependencies = [ - "core2 0.3.3", + "arrayvec", ] [[package]] @@ -3288,7 +3306,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.14", - "socket2 0.4.10", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -4758,7 +4776,7 @@ dependencies = [ "blake2b_simd", "blake2s_simd", "blake3", - "core2 0.4.0", + "core2", "digest 0.10.7", "multihash-derive 0.8.0", "sha2", @@ -4772,7 +4790,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" dependencies = [ - "core2 0.4.0", + "core2", "unsigned-varint", ] @@ -4785,7 +4803,7 @@ dependencies = [ "blake2b_simd", "blake2s_simd", "blake3", - "core2 0.4.0", + "core2", "digest 0.10.7", "multihash-derive 0.9.0", "ripemd", @@ -4815,7 +4833,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "890e72cb7396cb99ed98c1246a97b243cc16394470d94e0bc8b0c2c11d84290e" dependencies = [ - "core2 0.4.0", + "core2", "multihash 0.19.1", "multihash-derive-impl", ] @@ -7548,9 +7566,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" dependencies = [ "bitcoin_hashes", "rand", @@ -7560,9 +7578,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.9.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" dependencies = [ "cc", ] diff --git a/coins/bitcoin/Cargo.toml b/coins/bitcoin/Cargo.toml index 4ff0f79a..66fcc014 100644 --- a/coins/bitcoin/Cargo.toml +++ b/coins/bitcoin/Cargo.toml @@ -23,7 +23,7 @@ thiserror = { version = "1", default-features = false, optional = true } zeroize = { version = "^1.5", default-features = false } rand_core = { version = "0.6", default-features = false } -bitcoin = { version = "0.31", default-features = false, features = ["no-std"] } +bitcoin = { version = "0.32", default-features = false } k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] } @@ -36,7 +36,7 @@ serde_json = { version = "1", default-features = false, optional = true } simple-request = { path = "../../common/request", version = "0.1", default-features = false, features = ["tls", "basic-auth"], optional = true } [dev-dependencies] -secp256k1 = { version = "0.28", default-features = false, features = ["std"] } +secp256k1 = { version = "0.29", default-features = false, features = ["std"] } frost = { package = "modular-frost", path = "../../crypto/frost", features = ["tests"] } diff --git a/coins/bitcoin/src/rpc.rs b/coins/bitcoin/src/rpc.rs index 6778636b..fb1c35d6 100644 --- a/coins/bitcoin/src/rpc.rs +++ b/coins/bitcoin/src/rpc.rs @@ -195,13 +195,13 @@ impl Rpc { // If this was already successfully published, consider this having succeeded if let RpcError::RequestError(Error { code, .. }) = e { if code == RPC_VERIFY_ALREADY_IN_CHAIN { - return Ok(tx.txid()); + return Ok(tx.compute_txid()); } } Err(e)? } }; - if txid != tx.txid() { + if txid != tx.compute_txid() { Err(RpcError::InvalidResponse("returned TX ID inequals calculated TX ID"))?; } Ok(txid) @@ -215,7 +215,7 @@ impl Rpc { let tx: Transaction = encode::deserialize(&bytes) .map_err(|_| RpcError::InvalidResponse("node sent an improperly serialized transaction"))?; - let mut tx_hash = *tx.txid().as_raw_hash().as_byte_array(); + let mut tx_hash = *tx.compute_txid().as_raw_hash().as_byte_array(); tx_hash.reverse(); if hash != &tx_hash { Err(RpcError::InvalidResponse("node replied with a different transaction"))?; diff --git a/coins/bitcoin/src/tests/crypto.rs b/coins/bitcoin/src/tests/crypto.rs index 2170219c..cfc694f4 100644 --- a/coins/bitcoin/src/tests/crypto.rs +++ b/coins/bitcoin/src/tests/crypto.rs @@ -39,7 +39,7 @@ fn test_algorithm() { .verify_schnorr( &Signature::from_slice(&sig) .expect("couldn't convert produced signature to secp256k1::Signature"), - &Message::from(Hash::hash(MESSAGE)), + &Message::from_digest_slice(Hash::hash(MESSAGE).as_ref()).unwrap(), &x_only(&keys[&Participant::new(1).unwrap()].group_key()), ) .unwrap() diff --git a/coins/bitcoin/src/wallet/mod.rs b/coins/bitcoin/src/wallet/mod.rs index 3f099faa..ed6f00ce 100644 --- a/coins/bitcoin/src/wallet/mod.rs +++ b/coins/bitcoin/src/wallet/mod.rs @@ -4,7 +4,7 @@ use std_shims::{ io::{self, Write}, }; #[cfg(feature = "std")] -use std_shims::io::Read; +use std::io::{Read, BufReader}; use k256::{ elliptic_curve::sec1::{Tag, ToEncodedPoint}, @@ -18,8 +18,8 @@ use frost::{ }; use bitcoin::{ - consensus::encode::serialize, key::TweakedPublicKey, address::Payload, OutPoint, ScriptBuf, - TxOut, Transaction, Block, + consensus::encode::serialize, key::TweakedPublicKey, OutPoint, ScriptBuf, TxOut, Transaction, + Block, }; #[cfg(feature = "std")] use bitcoin::consensus::encode::Decodable; @@ -46,12 +46,12 @@ pub fn tweak_keys(keys: &ThresholdKeys) -> ThresholdKeys { /// Return the Taproot address payload for a public key. /// /// If the key is odd, this will return None. -pub fn address_payload(key: ProjectivePoint) -> Option { +pub fn p2tr_script_buf(key: ProjectivePoint) -> Option { if key.to_encoded_point(true).tag() != Tag::CompressedEvenY { return None; } - Some(Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only(&key)))) + Some(ScriptBuf::new_p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only(&key)))) } /// A spendable output. @@ -89,11 +89,17 @@ impl ReceivedOutput { /// Read a ReceivedOutput from a generic satisfying Read. #[cfg(feature = "std")] pub fn read(r: &mut R) -> io::Result { - Ok(ReceivedOutput { - offset: Secp256k1::read_F(r)?, - output: TxOut::consensus_decode(r).map_err(|_| io::Error::other("invalid TxOut"))?, - outpoint: OutPoint::consensus_decode(r).map_err(|_| io::Error::other("invalid OutPoint"))?, - }) + let offset = Secp256k1::read_F(r)?; + let output; + let outpoint; + { + let mut buf_r = BufReader::new(r); + output = + TxOut::consensus_decode(&mut buf_r).map_err(|_| io::Error::other("invalid TxOut"))?; + outpoint = + OutPoint::consensus_decode(&mut buf_r).map_err(|_| io::Error::other("invalid OutPoint"))?; + } + Ok(ReceivedOutput { offset, output, outpoint }) } /// Write a ReceivedOutput to a generic satisfying Write. @@ -124,7 +130,7 @@ impl Scanner { /// Returns None if this key can't be scanned for. pub fn new(key: ProjectivePoint) -> Option { let mut scripts = HashMap::new(); - scripts.insert(address_payload(key)?.script_pubkey(), Scalar::ZERO); + scripts.insert(p2tr_script_buf(key)?, Scalar::ZERO); Some(Scanner { key, scripts }) } @@ -141,9 +147,8 @@ impl Scanner { // chance of being even // That means this should terminate within a very small amount of iterations loop { - match address_payload(self.key + (ProjectivePoint::GENERATOR * offset)) { - Some(address) => { - let script = address.script_pubkey(); + match p2tr_script_buf(self.key + (ProjectivePoint::GENERATOR * offset)) { + Some(script) => { if self.scripts.contains_key(&script) { None?; } @@ -166,7 +171,7 @@ impl Scanner { res.push(ReceivedOutput { offset: *offset, output: output.clone(), - outpoint: OutPoint::new(tx.txid(), vout), + outpoint: OutPoint::new(tx.compute_txid(), vout), }); } } diff --git a/coins/bitcoin/src/wallet/send.rs b/coins/bitcoin/src/wallet/send.rs index 24594ab4..d547c56a 100644 --- a/coins/bitcoin/src/wallet/send.rs +++ b/coins/bitcoin/src/wallet/send.rs @@ -23,7 +23,7 @@ use bitcoin::{ use crate::{ crypto::Schnorr, - wallet::{ReceivedOutput, address_payload}, + wallet::{ReceivedOutput, p2tr_script_buf}, }; #[rustfmt::skip] @@ -248,7 +248,7 @@ impl SignableTransaction { /// Returns the TX ID of the transaction this will create. pub fn txid(&self) -> [u8; 32] { - let mut res = self.tx.txid().to_byte_array(); + let mut res = self.tx.compute_txid().to_byte_array(); res.reverse(); res } @@ -288,7 +288,7 @@ impl SignableTransaction { transcript.append_message(b"signing_input", u32::try_from(i).unwrap().to_le_bytes()); let offset = keys.clone().offset(self.offsets[i]); - if address_payload(offset.group_key())?.script_pubkey() != self.prevouts[i].script_pubkey { + if p2tr_script_buf(offset.group_key())? != self.prevouts[i].script_pubkey { None?; } diff --git a/coins/bitcoin/tests/wallet.rs b/coins/bitcoin/tests/wallet.rs index 9eca20c7..8aa2546e 100644 --- a/coins/bitcoin/tests/wallet.rs +++ b/coins/bitcoin/tests/wallet.rs @@ -22,11 +22,10 @@ use bitcoin_serai::{ hashes::Hash as HashTrait, blockdata::opcodes::all::OP_RETURN, script::{PushBytesBuf, Instruction, Instructions, Script}, - address::NetworkChecked, OutPoint, Amount, TxOut, Transaction, Network, Address, }, wallet::{ - tweak_keys, address_payload, ReceivedOutput, Scanner, TransactionError, SignableTransaction, + tweak_keys, p2tr_script_buf, ReceivedOutput, Scanner, TransactionError, SignableTransaction, }, rpc::Rpc, }; @@ -48,7 +47,7 @@ async fn send_and_get_output(rpc: &Rpc, scanner: &Scanner, key: ProjectivePoint) "generatetoaddress", serde_json::json!([ 1, - Address::::new(Network::Regtest, address_payload(key).unwrap()) + Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap() ]), ) .await @@ -69,7 +68,7 @@ async fn send_and_get_output(rpc: &Rpc, scanner: &Scanner, key: ProjectivePoint) assert_eq!(outputs, scanner.scan_transaction(&block.txdata[0])); assert_eq!(outputs.len(), 1); - assert_eq!(outputs[0].outpoint(), &OutPoint::new(block.txdata[0].txid(), 0)); + assert_eq!(outputs[0].outpoint(), &OutPoint::new(block.txdata[0].compute_txid(), 0)); assert_eq!(outputs[0].value(), block.txdata[0].output[0].value.to_sat()); assert_eq!( @@ -193,7 +192,7 @@ async_sequential! { assert_eq!(output.offset(), Scalar::ZERO); let inputs = vec![output]; - let addr = || Address::::new(Network::Regtest, address_payload(key).unwrap()); + let addr = || Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap(); let payments = vec![(addr(), 1000)]; assert!(SignableTransaction::new(inputs.clone(), &payments, None, None, FEE).is_ok()); @@ -261,14 +260,14 @@ async_sequential! { // Declare payments, change, fee let payments = [ - (Address::::new(Network::Regtest, address_payload(key).unwrap()), 1005), - (Address::::new(Network::Regtest, address_payload(offset_key).unwrap()), 1007) + (Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap(), 1005), + (Address::from_script(&p2tr_script_buf(offset_key).unwrap(), Network::Regtest).unwrap(), 1007) ]; let change_offset = scanner.register_offset(Scalar::random(&mut OsRng)).unwrap(); let change_key = key + (ProjectivePoint::GENERATOR * change_offset); let change_addr = - Address::::new(Network::Regtest, address_payload(change_key).unwrap()); + Address::from_script(&p2tr_script_buf(change_key).unwrap(), Network::Regtest).unwrap(); // Create and sign the TX let tx = SignableTransaction::new( @@ -287,7 +286,7 @@ async_sequential! { // Ensure we can scan it let outputs = scanner.scan_transaction(&tx); for (o, output) in outputs.iter().enumerate() { - assert_eq!(output.outpoint(), &OutPoint::new(tx.txid(), u32::try_from(o).unwrap())); + assert_eq!(output.outpoint(), &OutPoint::new(tx.compute_txid(), u32::try_from(o).unwrap())); assert_eq!(&ReceivedOutput::read::<&[u8]>(&mut output.serialize().as_ref()).unwrap(), output); } @@ -320,7 +319,7 @@ async_sequential! { // This also tests send_raw_transaction and get_transaction, which the RPC test can't // effectively test rpc.send_raw_transaction(&tx).await.unwrap(); - let mut hash = *tx.txid().as_raw_hash().as_byte_array(); + let mut hash = *tx.compute_txid().as_raw_hash().as_byte_array(); hash.reverse(); assert_eq!(tx, rpc.get_transaction(&hash).await.unwrap()); assert_eq!(expected_id, hash); @@ -344,7 +343,7 @@ async_sequential! { &SignableTransaction::new( vec![output], &[], - Some(&Address::::new(Network::Regtest, address_payload(key).unwrap())), + Some(&Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap()), Some(data.clone()), FEE ).unwrap() diff --git a/processor/Cargo.toml b/processor/Cargo.toml index f90f6117..cc010848 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -45,7 +45,7 @@ frost-schnorrkel = { path = "../crypto/schnorrkel", default-features = false } k256 = { version = "^0.13.1", default-features = false, features = ["std"], optional = true } # Bitcoin -secp256k1 = { version = "0.28", default-features = false, features = ["std", "global-context", "rand-std"], optional = true } +secp256k1 = { version = "0.29", default-features = false, features = ["std", "global-context", "rand-std"], optional = true } bitcoin-serai = { path = "../coins/bitcoin", default-features = false, features = ["std"], optional = true } # Ethereum diff --git a/processor/src/networks/bitcoin.rs b/processor/src/networks/bitcoin.rs index 3f8174e4..b7c6c2fb 100644 --- a/processor/src/networks/bitcoin.rs +++ b/processor/src/networks/bitcoin.rs @@ -20,12 +20,12 @@ use bitcoin_serai::{ key::{Parity, XOnlyPublicKey}, consensus::{Encodable, Decodable}, script::Instruction, - address::{NetworkChecked, Address as BAddress}, + address::Address as BAddress, Transaction, Block, Network as BNetwork, ScriptBuf, opcodes::all::{OP_SHA256, OP_EQUALVERIFY}, }, wallet::{ - tweak_keys, address_payload, ReceivedOutput, Scanner, TransactionError, + tweak_keys, p2tr_script_buf, ReceivedOutput, Scanner, TransactionError, SignableTransaction as BSignableTransaction, TransactionMachine, }, rpc::{RpcError, Rpc}, @@ -175,7 +175,7 @@ pub struct Fee(u64); impl TransactionTrait for Transaction { type Id = [u8; 32]; fn id(&self) -> Self::Id { - let mut hash = *self.txid().as_raw_hash().as_byte_array(); + let mut hash = *self.compute_txid().as_raw_hash().as_byte_array(); hash.reverse(); hash } @@ -243,7 +243,8 @@ impl EventualityTrait for Eventuality { buf } fn read_completion(reader: &mut R) -> io::Result { - Transaction::consensus_decode(reader).map_err(|e| io::Error::other(format!("{e}"))) + Transaction::consensus_decode(&mut io::BufReader::new(reader)) + .map_err(|e| io::Error::other(format!("{e}"))) } } @@ -535,11 +536,11 @@ impl Bitcoin { private_key: &PrivateKey, ) -> ScriptBuf { let public_key = PublicKey::from_private_key(SECP256K1, private_key); - let main_addr = BAddress::p2pkh(&public_key, BNetwork::Regtest); + let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest); let mut der = SECP256K1 .sign_ecdsa_low_r( - &Message::from( + &Message::from_digest_slice( SighashCache::new(tx) .legacy_signature_hash( input_index, @@ -547,8 +548,10 @@ impl Bitcoin { EcdsaSighashType::All.to_u32(), ) .unwrap() - .to_raw_hash(), - ), + .to_raw_hash() + .as_ref(), + ) + .unwrap(), &private_key.inner, ) .serialize_der() @@ -577,8 +580,14 @@ const MAX_INPUTS: usize = 520; const MAX_OUTPUTS: usize = 520; fn address_from_key(key: ProjectivePoint) -> Address { - Address::new(BAddress::::new(BNetwork::Bitcoin, address_payload(key).unwrap())) - .unwrap() + Address::new( + BAddress::from_script( + &p2tr_script_buf(key).expect("creating address from key which isn't properly tweaked"), + BNetwork::Bitcoin, + ) + .expect("couldn't go from p2tr script buf to address"), + ) + .expect("couldn't create Serai-representable address for bitcoin address") } #[async_trait] @@ -858,7 +867,7 @@ impl Network for Bitcoin { Err(RpcError::ConnectionError) => Err(NetworkError::ConnectionError)?, // TODO: Distinguish already in pool vs double spend (other signing attempt succeeded) vs // invalid transaction - Err(e) => panic!("failed to publish TX {}: {e}", tx.txid()), + Err(e) => panic!("failed to publish TX {}: {e}", tx.compute_txid()), } Ok(()) } @@ -909,7 +918,7 @@ impl Network for Bitcoin { let secret_key = SecretKey::new(&mut rand_core::OsRng); let private_key = PrivateKey::new(secret_key, BNetwork::Regtest); let public_key = PublicKey::from_private_key(SECP256K1, &private_key); - let main_addr = BAddress::p2pkh(&public_key, BNetwork::Regtest); + let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest); let new_block = self.get_latest_block_number().await.unwrap() + 1; self @@ -923,7 +932,7 @@ impl Network for Bitcoin { version: Version(2), lock_time: LockTime::ZERO, input: vec![TxIn { - previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 }, script_sig: Script::new().into(), sequence: Sequence(u32::MAX), witness: Witness::default(), diff --git a/processor/src/tests/literal/mod.rs b/processor/src/tests/literal/mod.rs index 5c5f3203..238dde1a 100644 --- a/processor/src/tests/literal/mod.rs +++ b/processor/src/tests/literal/mod.rs @@ -70,7 +70,7 @@ mod bitcoin { // btc key pair to send from let private_key = PrivateKey::new(SecretKey::new(&mut rand_core::OsRng), BNetwork::Regtest); let public_key = PublicKey::from_private_key(SECP256K1, &private_key); - let main_addr = BAddress::p2pkh(&public_key, BNetwork::Regtest); + let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest); // get unlocked coins let new_block = btc.get_latest_block_number().await.unwrap() + 1; @@ -107,7 +107,7 @@ mod bitcoin { version: Version(2), lock_time: LockTime::ZERO, input: vec![TxIn { - previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 }, script_sig: Script::new().into(), sequence: Sequence(u32::MAX), witness: Witness::default(), @@ -128,7 +128,7 @@ mod bitcoin { version: Version(2), lock_time: LockTime::ZERO, input: vec![TxIn { - previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 }, script_sig: Script::new().into(), sequence: Sequence(u32::MAX), witness: Witness::new(), @@ -143,12 +143,14 @@ mod bitcoin { // This is the standard script with an extra argument of the InInstruction let mut sig = SECP256K1 .sign_ecdsa_low_r( - &Message::from( + &Message::from_digest_slice( SighashCache::new(&tx) .p2wsh_signature_hash(0, &script, initial_output_value, EcdsaSighashType::All) .unwrap() - .to_raw_hash(), - ), + .to_raw_hash() + .as_ref(), + ) + .unwrap(), &private_key.inner, ) .serialize_der() diff --git a/substrate/client/Cargo.toml b/substrate/client/Cargo.toml index f97e40fb..0eeb3a2f 100644 --- a/substrate/client/Cargo.toml +++ b/substrate/client/Cargo.toml @@ -36,7 +36,7 @@ async-lock = "3" simple-request = { path = "../../common/request", version = "0.1", optional = true } -bitcoin = { version = "0.31", optional = true } +bitcoin = { version = "0.32", optional = true } ciphersuite = { path = "../../crypto/ciphersuite", version = "0.4", optional = true } monero-serai = { path = "../../coins/monero", version = "0.1.4-alpha", optional = true } diff --git a/substrate/client/src/networks/bitcoin.rs b/substrate/client/src/networks/bitcoin.rs index 5ea37898..10965bdf 100644 --- a/substrate/client/src/networks/bitcoin.rs +++ b/substrate/client/src/networks/bitcoin.rs @@ -6,8 +6,8 @@ use bitcoin::{ hashes::{Hash as HashTrait, hash160::Hash}, PubkeyHash, ScriptHash, network::Network, - WitnessVersion, WitnessProgram, - address::{Error, Payload, NetworkChecked, Address as BAddressGeneric}, + WitnessVersion, WitnessProgram, ScriptBuf, + address::{AddressType, NetworkChecked, Address as BAddressGeneric}, }; type BAddress = BAddressGeneric; @@ -17,21 +17,22 @@ pub struct Address(BAddress); impl PartialEq for Address { fn eq(&self, other: &Self) -> bool { - // Since Serai defines the Bitcoin-address specification as a variant of the payload alone, - // define equivalency as the payload alone - self.0.payload() == other.0.payload() + // Since Serai defines the Bitcoin-address specification as a variant of the script alone, + // define equivalency as the script alone + self.0.script_pubkey() == other.0.script_pubkey() } } impl FromStr for Address { - type Err = Error; - fn from_str(str: &str) -> Result { + type Err = (); + fn from_str(str: &str) -> Result { Address::new( BAddressGeneric::from_str(str) - .map_err(|_| Error::UnrecognizedScript)? - .require_network(Network::Bitcoin)?, + .map_err(|_| ())? + .require_network(Network::Bitcoin) + .map_err(|_| ())?, ) - .ok_or(Error::UnrecognizedScript) + .ok_or(()) } } @@ -54,55 +55,65 @@ enum EncodedAddress { impl TryFrom> for Address { type Error = (); fn try_from(data: Vec) -> Result { - Ok(Address(BAddress::new( - Network::Bitcoin, - match EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())? { - EncodedAddress::P2PKH(hash) => { - Payload::PubkeyHash(PubkeyHash::from_raw_hash(Hash::from_byte_array(hash))) - } - EncodedAddress::P2SH(hash) => { - Payload::ScriptHash(ScriptHash::from_raw_hash(Hash::from_byte_array(hash))) - } - EncodedAddress::P2WPKH(hash) => { - Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V0, hash).unwrap()) - } - EncodedAddress::P2WSH(hash) => { - Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V0, hash).unwrap()) - } - EncodedAddress::P2TR(key) => { - Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V1, key).unwrap()) - } - }, - ))) + Ok(Address(match EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())? { + EncodedAddress::P2PKH(hash) => { + BAddress::p2pkh(PubkeyHash::from_raw_hash(Hash::from_byte_array(hash)), Network::Bitcoin) + } + EncodedAddress::P2SH(hash) => { + let script_hash = ScriptHash::from_raw_hash(Hash::from_byte_array(hash)); + let res = + BAddress::from_script(&ScriptBuf::new_p2sh(&script_hash), Network::Bitcoin).unwrap(); + debug_assert_eq!(res.script_hash(), Some(script_hash)); + res + } + EncodedAddress::P2WPKH(hash) => BAddress::from_witness_program( + WitnessProgram::new(WitnessVersion::V0, &hash).unwrap(), + Network::Bitcoin, + ), + EncodedAddress::P2WSH(hash) => BAddress::from_witness_program( + WitnessProgram::new(WitnessVersion::V0, &hash).unwrap(), + Network::Bitcoin, + ), + EncodedAddress::P2TR(key) => BAddress::from_witness_program( + WitnessProgram::new(WitnessVersion::V1, &key).unwrap(), + Network::Bitcoin, + ), + })) } } fn try_to_vec(addr: &Address) -> Result, ()> { + let witness_program = |addr: &Address| { + let script = addr.0.script_pubkey(); + let program_push = script.as_script().instructions().last().ok_or(())?.map_err(|_| ())?; + let program = program_push.push_bytes().ok_or(())?.as_bytes(); + Ok::<_, ()>(program.to_vec()) + }; Ok( - (match addr.0.payload() { - Payload::PubkeyHash(hash) => EncodedAddress::P2PKH(*hash.as_raw_hash().as_byte_array()), - Payload::ScriptHash(hash) => EncodedAddress::P2SH(*hash.as_raw_hash().as_byte_array()), - Payload::WitnessProgram(program) => match program.version() { - WitnessVersion::V0 => { - let program = program.program(); - if program.len() == 20 { - let mut buf = [0; 20]; - buf.copy_from_slice(program.as_ref()); - EncodedAddress::P2WPKH(buf) - } else if program.len() == 32 { - let mut buf = [0; 32]; - buf.copy_from_slice(program.as_ref()); - EncodedAddress::P2WSH(buf) - } else { - Err(())? - } - } - WitnessVersion::V1 => { - let program_ref: &[u8] = program.program().as_ref(); - EncodedAddress::P2TR(program_ref.try_into().map_err(|_| ())?) - } - _ => Err(())?, - }, + (match addr.0.address_type() { + Some(AddressType::P2pkh) => { + EncodedAddress::P2PKH(*addr.0.pubkey_hash().unwrap().as_raw_hash().as_byte_array()) + } + Some(AddressType::P2sh) => { + EncodedAddress::P2SH(*addr.0.script_hash().unwrap().as_raw_hash().as_byte_array()) + } + Some(AddressType::P2wpkh) => { + let program = witness_program(addr)?; + let mut buf = [0; 20]; + buf.copy_from_slice(program.as_ref()); + EncodedAddress::P2WPKH(buf) + } + Some(AddressType::P2wsh) => { + let program = witness_program(addr)?; + let mut buf = [0; 32]; + buf.copy_from_slice(program.as_ref()); + EncodedAddress::P2WSH(buf) + } + Some(AddressType::P2tr) => { + let program = witness_program(addr)?; + let program_ref: &[u8] = program.as_ref(); + EncodedAddress::P2TR(program_ref.try_into().map_err(|_| ())?) + } _ => Err(())?, }) .encode(), diff --git a/tests/full-stack/src/tests/mint_and_burn.rs b/tests/full-stack/src/tests/mint_and_burn.rs index 51b8156c..e1153bae 100644 --- a/tests/full-stack/src/tests/mint_and_burn.rs +++ b/tests/full-stack/src/tests/mint_and_burn.rs @@ -57,7 +57,7 @@ async fn mint_and_burn_test() { }; let addr = Address::p2pkh( - &PublicKey::from_private_key( + PublicKey::from_private_key( SECP256K1, &PrivateKey::new(SecretKey::from_slice(&[0x01; 32]).unwrap(), Network::Bitcoin), ), @@ -266,14 +266,13 @@ async fn mint_and_burn_test() { script::{PushBytesBuf, Script, ScriptBuf, Builder}, absolute::LockTime, transaction::{Version, Transaction}, - address::Payload, - Sequence, Witness, OutPoint, TxIn, Amount, TxOut, Network, + Sequence, Witness, OutPoint, TxIn, Amount, TxOut, Network, Address, }; let private_key = PrivateKey::new(SecretKey::from_slice(&[0x01; 32]).unwrap(), Network::Bitcoin); let public_key = PublicKey::from_private_key(SECP256K1, &private_key); - let addr = Payload::p2pkh(&public_key); + let addr = Address::p2pkh(public_key, Network::Bitcoin); // Use the first block's coinbase let rpc = handles[0].bitcoin(&ops).await; @@ -284,7 +283,7 @@ async fn mint_and_burn_test() { version: Version(2), lock_time: LockTime::ZERO, input: vec![TxIn { - previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 }, script_sig: Script::new().into(), sequence: Sequence(u32::MAX), witness: Witness::default(), @@ -292,17 +291,23 @@ async fn mint_and_burn_test() { output: vec![ TxOut { value: Amount::from_sat(1_100_000_00), - script_pubkey: Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked( - XOnlyPublicKey::from_slice(&bitcoin_key_pair.1[1 ..]).unwrap(), - )) + script_pubkey: Address::p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked( + XOnlyPublicKey::from_slice(&bitcoin_key_pair.1[1 ..]).unwrap(), + ), + Network::Bitcoin, + ) .script_pubkey(), }, TxOut { // change = amount spent - fee value: Amount::from_sat(tx.output[0].value.to_sat() - 1_100_000_00 - 1_000_00), - script_pubkey: Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked( - XOnlyPublicKey::from_slice(&public_key.inner.serialize()[1 ..]).unwrap(), - )) + script_pubkey: Address::p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked( + XOnlyPublicKey::from_slice(&public_key.inner.serialize()[1 ..]).unwrap(), + ), + Network::Bitcoin, + ) .script_pubkey(), }, TxOut { @@ -316,12 +321,14 @@ async fn mint_and_burn_test() { let mut der = SECP256K1 .sign_ecdsa_low_r( - &Message::from( + &Message::from_digest_slice( SighashCache::new(&tx) .legacy_signature_hash(0, &addr.script_pubkey(), EcdsaSighashType::All.to_u32()) .unwrap() - .to_raw_hash(), - ), + .to_raw_hash() + .as_ref(), + ) + .unwrap(), &private_key.inner, ) .serialize_der() @@ -449,9 +456,9 @@ async fn mint_and_burn_test() { let bitcoin_addr = { use bitcoin_serai::bitcoin::{network::Network, key::PublicKey, address::Address}; // Uses Network::Bitcoin since it doesn't actually matter, Serai strips it out - // TODO: Move Serai to Payload from Address + // TODO: Move Serai to ScriptBuf from Address Address::p2pkh( - &loop { + loop { let mut bytes = [0; 33]; OsRng.fill_bytes(&mut bytes); bytes[0] %= 4; diff --git a/tests/processor/src/networks.rs b/tests/processor/src/networks.rs index 61762f71..81c18bfa 100644 --- a/tests/processor/src/networks.rs +++ b/tests/processor/src/networks.rs @@ -126,7 +126,7 @@ impl Wallet { let secret_key = SecretKey::new(&mut rand_core::OsRng); let private_key = PrivateKey::new(secret_key, Network::Regtest); let public_key = PublicKey::from_private_key(SECP256K1, &private_key); - let main_addr = Address::p2pkh(&public_key, Network::Regtest); + let main_addr = Address::p2pkh(public_key, Network::Regtest); let rpc = Rpc::new(rpc_url).await.expect("couldn't connect to the Bitcoin RPC"); @@ -258,10 +258,10 @@ impl Wallet { consensus::Encodable, sighash::{EcdsaSighashType, SighashCache}, script::{PushBytesBuf, Script, ScriptBuf, Builder}, - address::Payload, OutPoint, Sequence, Witness, TxIn, Amount, TxOut, absolute::LockTime, transaction::{Version, Transaction}, + Network, Address, }; const AMOUNT: u64 = 100000000; @@ -269,7 +269,7 @@ impl Wallet { version: Version(2), lock_time: LockTime::ZERO, input: vec![TxIn { - previous_output: OutPoint { txid: input_tx.txid(), vout: 0 }, + previous_output: OutPoint { txid: input_tx.compute_txid(), vout: 0 }, script_sig: Script::new().into(), sequence: Sequence(u32::MAX), witness: Witness::default(), @@ -281,9 +281,12 @@ impl Wallet { }, TxOut { value: Amount::from_sat(AMOUNT), - script_pubkey: Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked( - XOnlyPublicKey::from_slice(&to[1 ..]).unwrap(), - )) + script_pubkey: Address::p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked( + XOnlyPublicKey::from_slice(&to[1 ..]).unwrap(), + ), + Network::Bitcoin, + ) .script_pubkey(), }, ], @@ -303,7 +306,7 @@ impl Wallet { let mut der = SECP256K1 .sign_ecdsa_low_r( - &Message::from( + &Message::from_digest_slice( SighashCache::new(&tx) .legacy_signature_hash( 0, @@ -311,8 +314,10 @@ impl Wallet { EcdsaSighashType::All.to_u32(), ) .unwrap() - .to_raw_hash(), - ), + .to_raw_hash() + .as_ref(), + ) + .unwrap(), &private_key.inner, ) .serialize_der()