diff --git a/Cargo.toml b/Cargo.toml index b097d24..5846f20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,16 +11,16 @@ keywords = [ "crypto", "bitcoin" ] readme = "README.md" [features] -use-serde = ["serde", "bitcoin/use-serde"] +use-serde = ["serde", "bitcoin/serde"] [dependencies] -bitcoin = "0.26" +bitcoin = "0.29.1" rand = "0.7" rust-crypto = "0.2" serde = { version = "1", optional = true, features = ["derive"] } [dev-dependencies] -bitcoin = { version = "0.26", features = ["use-serde", "bitcoinconsensus"] } +bitcoin = { version = "0.29.1", features = ["serde", "bitcoinconsensus"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src/account.rs b/src/account.rs index 6e4ea17..89f57ec 100644 --- a/src/account.rs +++ b/src/account.rs @@ -24,11 +24,11 @@ use bitcoin::{ blockdata::script::Builder, blockdata::{ opcodes::all, - transaction::{SigHashType, TxOut}, + transaction::{EcdsaSighashType, TxOut}, }, network::constants::Network, - util::bip143, util::bip32::{ChildNumber, ExtendedPrivKey}, + util::sighash::SighashCache, Address, OutPoint, PrivateKey, PublicKey, Script, Transaction, }; use crypto::{ @@ -212,7 +212,7 @@ impl MasterAccount { pub fn sign( &self, transaction: &mut Transaction, - hash_type: SigHashType, + hash_type: EcdsaSighashType, resolver: &R, unlocker: &mut Unlocker, ) -> Result @@ -333,10 +333,11 @@ impl Unlocker { tweak: Option>, ) -> Result { let sub_account_key = self.sub_account_key(address_type, account, sub_account)?; - let mut key = self + let key = self .context .private_child(&sub_account_key, ChildNumber::Normal { index })? .private_key; + let mut key = PrivateKey::new(key, self.network); if let Some(tweak) = tweak { self.context.tweak_add(&mut key, tweak.as_slice())?; } @@ -569,10 +570,11 @@ impl Account { } pub fn compute_base_public_key(&self, kix: u32) -> Result { - Ok(self + let key = self .context .public_child(&self.master_public, ChildNumber::Normal { index: kix })? - .public_key) + .public_key; + Ok(PublicKey::new(key)) } /// get a previously instantiated key @@ -634,7 +636,7 @@ impl Account { pub fn sign( &self, transaction: &mut Transaction, - hash_type: SigHashType, + hash_type: EcdsaSighashType, resolver: R, unlocker: &mut Unlocker, ) -> Result @@ -644,7 +646,7 @@ impl Account { let mut signed = 0; //TODO(stevenroose) try to prevent this clone here let txclone = transaction.clone(); - let mut bip143hasher = bip143::SigHashCache::new(&txclone); + let mut bip143hasher = SighashCache::new(&txclone); for (ix, input) in transaction.input.iter_mut().enumerate() { if let Some(spend) = resolver(&input.previous_output) { if let Some((kix, instantiated)) = self @@ -665,11 +667,11 @@ impl Account { let sighash = txclone.signature_hash( ix, &instantiated.address.script_pubkey(), - hash_type.as_u32(), + hash_type.to_u32(), ); let signature = self.context.sign(&sighash[..], &pk)?.serialize_der(); let mut with_hashtype = signature.to_vec(); - with_hashtype.push(hash_type.as_u32() as u8); + with_hashtype.push(hash_type.to_u32() as u8); input.script_sig = Builder::new() .push_slice(with_hashtype.as_slice()) .push_slice(instantiated.public.to_bytes().as_slice()) @@ -678,26 +680,26 @@ impl Account { signed += 1; } AccountAddressType::P2WPKH => { - if hash_type.as_u32() & SigHashType::All.as_u32() == 0 { + if hash_type.to_u32() & EcdsaSighashType::All.to_u32() == 0 { return Err(Error::Unsupported("can only sign all inputs for now")); } input.script_sig = Script::new(); - let sighash = bip143hasher.signature_hash( + let sighash = bip143hasher.segwit_signature_hash( ix, &instantiated.script_code, spend.value, hash_type, - ); + )?; let signature = self.context.sign(&sighash[..], &pk)?.serialize_der(); let mut with_hashtype = signature.to_vec(); - with_hashtype.push(hash_type.as_u32() as u8); + with_hashtype.push(hash_type.to_u32() as u8); input.witness.clear(); input.witness.push(with_hashtype); input.witness.push(instantiated.public.to_bytes()); signed += 1; } AccountAddressType::P2SHWPKH => { - if hash_type.as_u32() & SigHashType::All.as_u32() == 0 { + if hash_type.to_u32() & EcdsaSighashType::All.to_u32() == 0 { return Err(Error::Unsupported("can only sign all inputs for now")); } input.script_sig = Builder::new() @@ -712,34 +714,34 @@ impl Account { .into_script()[..], ) .into_script(); - let sighash = bip143hasher.signature_hash( + let sighash = bip143hasher.segwit_signature_hash( ix, &instantiated.script_code, spend.value, hash_type, - ); + )?; let signature = self.context.sign(&sighash[..], &pk)?.serialize_der(); let mut with_hashtype = signature.to_vec(); - with_hashtype.push(hash_type.as_u32() as u8); + with_hashtype.push(hash_type.to_u32() as u8); input.witness.clear(); input.witness.push(with_hashtype); input.witness.push(instantiated.public.to_bytes()); signed += 1; } AccountAddressType::P2WSH(_) => { - if hash_type.as_u32() & SigHashType::All.as_u32() == 0 { + if hash_type.to_u32() & EcdsaSighashType::All.to_u32() == 0 { return Err(Error::Unsupported("can only sign all inputs for now")); } input.script_sig = Script::new(); - let sighash = bip143hasher.signature_hash( + let sighash = bip143hasher.segwit_signature_hash( ix, &instantiated.script_code, spend.value, hash_type, - ); + )?; let signature = self.context.sign(&sighash[..], &pk)?.serialize_der(); let mut with_hashtype = signature.to_vec(); - with_hashtype.push(hash_type.as_u32() as u8); + with_hashtype.push(hash_type.to_u32() as u8); input.witness.clear(); input.witness.push(with_hashtype); input.witness.push(instantiated.script_code.to_bytes()); @@ -778,9 +780,10 @@ impl InstantiatedKey { where W: FnOnce(&PublicKey, Option) -> Script, { - let mut public = context + let key = context .public_child(master, ChildNumber::Normal { index: kix })? .public_key; + let mut public = PublicKey::new(key); if let Some(tweak) = tweak { context.tweak_exp_add(&mut public, tweak)?; } @@ -882,19 +885,22 @@ mod test { use std::io::Read; use std::path::PathBuf; - use bitcoin::hashes::hex::FromHex; use bitcoin::blockdata::opcodes::all; use bitcoin::blockdata::script::Builder; - use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut}; + use bitcoin::blockdata::transaction::{OutPoint, Sequence, TxIn, TxOut}; + use bitcoin::hashes::{hex::FromHex, Hash}; use bitcoin::network::constants::Network; use bitcoin::util::bip32::ChildNumber; + use bitcoin::PackedLockTime; + use bitcoin::Witness; use rand::Rng; use serde_json::Value; use super::*; const PASSPHRASE: &str = "correct horse battery staple"; - const RBF: u32 = 0xffffffff - 2; + const RBF: Sequence = Sequence(0xffffffff - 2); + const ZERO_LOCK_TIME: PackedLockTime = PackedLockTime(0); #[test] fn seed_encrypt_decrypt() { @@ -921,18 +927,18 @@ mod test { let input_transaction = Transaction { input: vec![TxIn { previous_output: OutPoint { - txid: bitcoin::Txid::default(), + txid: bitcoin::Txid::all_zeros(), vout: 0, }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: source.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; let txid = input_transaction.txid(); @@ -941,14 +947,14 @@ mod test { input: vec![TxIn { previous_output: OutPoint { txid, vout: 0 }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: target.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; @@ -959,7 +965,7 @@ mod test { master .sign( &mut spending_transaction, - SigHashType::All, + EcdsaSighashType::All, &(|_| Some(input_transaction.output[0].clone())), &mut unlocker ) @@ -991,18 +997,18 @@ mod test { let input_transaction = Transaction { input: vec![TxIn { previous_output: OutPoint { - txid: bitcoin::Txid::default(), + txid: bitcoin::Txid::all_zeros(), vout: 0, }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: source.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; let txid = input_transaction.txid(); @@ -1011,14 +1017,14 @@ mod test { input: vec![TxIn { previous_output: OutPoint { txid, vout: 0 }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: target.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; @@ -1029,7 +1035,7 @@ mod test { master .sign( &mut spending_transaction, - SigHashType::All, + EcdsaSighashType::All, &(|_| Some(input_transaction.output[0].clone())), &mut unlocker ) @@ -1061,18 +1067,18 @@ mod test { let input_transaction = Transaction { input: vec![TxIn { previous_output: OutPoint { - txid: bitcoin::Txid::default(), + txid: bitcoin::Txid::all_zeros(), vout: 0, }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: source.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; @@ -1082,14 +1088,14 @@ mod test { input: vec![TxIn { previous_output: OutPoint { txid, vout: 0 }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: target.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; @@ -1100,7 +1106,7 @@ mod test { master .sign( &mut spending_transaction, - SigHashType::All, + EcdsaSighashType::All, &(|_| Some(input_transaction.output[0].clone())), &mut unlocker ) @@ -1145,18 +1151,18 @@ mod test { let input_transaction = Transaction { input: vec![TxIn { previous_output: OutPoint { - txid: bitcoin::Txid::default(), + txid: bitcoin::Txid::all_zeros(), vout: 0, }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: source.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; let txid = input_transaction.txid(); @@ -1165,14 +1171,14 @@ mod test { input: vec![TxIn { previous_output: OutPoint { txid, vout: 0 }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: target.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; @@ -1183,7 +1189,7 @@ mod test { master .sign( &mut spending_transaction, - SigHashType::All, + EcdsaSighashType::All, &(|_| Some(input_transaction.output[0].clone())), &mut unlocker ) @@ -1233,18 +1239,18 @@ mod test { let input_transaction = Transaction { input: vec![TxIn { previous_output: OutPoint { - txid: bitcoin::Txid::default(), + txid: bitcoin::Txid::all_zeros(), vout: 0, }, sequence: RBF, - witness: Vec::new(), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: source.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; let txid = input_transaction.txid(); @@ -1252,15 +1258,15 @@ mod test { let mut spending_transaction = Transaction { input: vec![TxIn { previous_output: OutPoint { txid, vout: 0 }, - sequence: CSV as u32, - witness: Vec::new(), + sequence: Sequence(CSV as u32), + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: target.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; @@ -1271,7 +1277,7 @@ mod test { master .sign( &mut spending_transaction, - SigHashType::All, + EcdsaSighashType::All, &(|_| Some(input_transaction.output[0].clone())), &mut unlocker ) @@ -1290,15 +1296,15 @@ mod test { let mut spending_transaction = Transaction { input: vec![TxIn { previous_output: OutPoint { txid, vout: 0 }, - sequence: (CSV - 1) as u32, // this one should not be able to spend - witness: Vec::new(), + sequence: Sequence((CSV - 1) as u32), // this one should not be able to spend + witness: Witness::default(), script_sig: Script::new(), }], output: vec![TxOut { script_pubkey: target.script_pubkey(), value: 5000000000, }], - lock_time: 0, + lock_time: ZERO_LOCK_TIME, version: 2, }; @@ -1306,7 +1312,7 @@ mod test { master .sign( &mut spending_transaction, - SigHashType::All, + EcdsaSighashType::All, &(|_| Some(input_transaction.output[0].clone())), &mut unlocker ) diff --git a/src/coins.rs b/src/coins.rs index 4c53ff6..2455371 100644 --- a/src/coins.rs +++ b/src/coins.rs @@ -320,19 +320,20 @@ mod test { time::{SystemTime, UNIX_EPOCH}, }; -use bitcoin::hashes::hex::FromHex; use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::script::Builder; + use bitcoin::hashes::{hex::FromHex, Hash}; use bitcoin::util::bip32::ExtendedPubKey; use bitcoin::{ - network::constants::Network, Address, Block, BlockHeader, OutPoint, - Transaction, TxIn, TxOut, + network::constants::Network, Address, Block, BlockHeader, OutPoint, PackedLockTime, + Sequence, Transaction, TxIn, TxOut, }; use account::{Account, AccountAddressType, MasterAccount, Unlocker}; use coins::Coins; const NEW_COINS: u64 = 5000000000; + const ZERO_LOCK_TIME: PackedLockTime = PackedLockTime(0); fn new_block(prev: &bitcoin::BlockHash) -> Block { Block { @@ -345,7 +346,7 @@ use bitcoin::hashes::hex::FromHex; nonce: 0, bits: 0x1d00ffff, prev_blockhash: prev.clone(), - merkle_root: bitcoin::TxMerkleNode::default(), + merkle_root: bitcoin::TxMerkleNode::all_zeros(), }, txdata: Vec::new(), } @@ -354,12 +355,12 @@ use bitcoin::hashes::hex::FromHex; fn coin_base(miner: Address, height: u32) -> Transaction { Transaction { version: 2, - lock_time: 0, + lock_time: ZERO_LOCK_TIME, input: vec![TxIn { - sequence: 0xffffffff, - witness: Vec::new(), + sequence: Sequence(0xffffffff), + witness: bitcoin::Witness::default(), previous_output: OutPoint { - txid: bitcoin::Txid::default(), + txid: bitcoin::Txid::all_zeros(), vout: 0, }, script_sig: Builder::new().push_int(height as i64).into_script(), @@ -373,7 +374,7 @@ use bitcoin::hashes::hex::FromHex; fn add_tx(block: &mut Block, tx: Transaction) { block.txdata.push(tx); - block.header.merkle_root = block.merkle_root(); + block.header.merkle_root = block.compute_merkle_root().unwrap(); } fn new_master() -> MasterAccount { diff --git a/src/context.rs b/src/context.rs index 754cc1b..0f37d4c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -16,7 +16,7 @@ //! //! # Key derivation //! -use bitcoin::secp256k1::{All, Message, Secp256k1, Signature}; +use bitcoin::secp256k1::{ecdsa::Signature, All, Message, Scalar, Secp256k1}; use bitcoin::{ network::constants::Network, util::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey}, @@ -25,6 +25,7 @@ use bitcoin::{ use account::Seed; use error::Error; +use std::convert::TryInto; pub struct SecpContext { secp: Secp256k1, @@ -51,7 +52,7 @@ impl SecpContext { &self, extended_private_key: &ExtendedPrivKey, ) -> ExtendedPubKey { - ExtendedPubKey::from_private(&self.secp, extended_private_key) + ExtendedPubKey::from_priv(&self.secp, extended_private_key) } pub fn private_child( @@ -75,16 +76,26 @@ impl SecpContext { } pub fn sign(&self, digest: &[u8], key: &PrivateKey) -> Result { - Ok(self.secp.sign(&Message::from_slice(digest)?, &key.key)) + Ok(self + .secp + .sign_ecdsa(&Message::from_slice(digest)?, &key.inner)) } pub fn tweak_add(&self, key: &mut PrivateKey, tweak: &[u8]) -> Result<(), Error> { - key.key.add_assign(tweak)?; + // Convert tweak here since Scalar omits Debug derivation so we can't add to KeyDerivation + let tweak = + Scalar::from_be_bytes(tweak.try_into().map_err(|e| Error::TryFromSliceError(e))?) + .map_err(|e| Error::OutOfRangeError(e))?; + key.inner.add_tweak(&tweak)?; Ok(()) } pub fn tweak_exp_add(&self, key: &mut PublicKey, tweak: &[u8]) -> Result<(), Error> { - key.key.add_exp_assign(&self.secp, tweak)?; + // Convert tweak here since Scalar omits Debug derivation so we can't add to KeyDerivation + let tweak = + Scalar::from_be_bytes(tweak.try_into().map_err(|e| Error::TryFromSliceError(e))?) + .map_err(|e| Error::OutOfRangeError(e))?; + key.inner.add_exp_tweak(&self.secp, &tweak)?; Ok(()) } } diff --git a/src/error.rs b/src/error.rs index 42c73b8..abaddd5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -42,6 +42,12 @@ pub enum Error { SecpError(bitcoin::secp256k1::Error), /// cipher error SymmetricCipherError(symmetriccipher::SymmetricCipherError), + // sighash error + SighashError(bitcoin::util::sighash::Error), + // tryfromslice error + TryFromSliceError(std::array::TryFromSliceError), + // outofrange error + OutOfRangeError(bitcoin::secp256k1::scalar::OutOfRangeError), } impl error::Error for Error { @@ -59,6 +65,9 @@ impl error::Error for Error { Error::KeyDerivation(ref err) => Some(err), Error::SecpError(ref err) => Some(err), Error::SymmetricCipherError(_) => None, + Error::SighashError(ref err) => Some(err), + Error::TryFromSliceError(ref err) => Some(err), + Error::OutOfRangeError(ref err) => Some(err), } } } @@ -83,6 +92,9 @@ impl fmt::Display for Error { &symmetriccipher::SymmetricCipherError::InvalidPadding => "invalid padding", } ), + Error::SighashError(ref err) => write!(f, "Sighash error: {}", err), + Error::TryFromSliceError(ref err) => write!(f, "TryFromSlice error: {}", err), + Error::OutOfRangeError(ref err) => write!(f, "OutOfRangeError error: {}", err), } } } @@ -97,9 +109,7 @@ impl convert::From for io::Error { fn from(err: Error) -> io::Error { match err { Error::IO(e) => e, - _ => { - io::Error::new(io::ErrorKind::Other, err.to_string()) - } + _ => io::Error::new(io::ErrorKind::Other, err.to_string()), } } } @@ -127,3 +137,21 @@ impl convert::From for Error { Error::SecpError(err) } } + +impl convert::From for Error { + fn from(err: bitcoin::util::sighash::Error) -> Error { + Error::SighashError(err) + } +} + +impl convert::From for Error { + fn from(err: std::array::TryFromSliceError) -> Error { + Error::TryFromSliceError(err) + } +} + +impl convert::From for Error { + fn from(err: bitcoin::secp256k1::scalar::OutOfRangeError) -> Error { + Error::OutOfRangeError(err) + } +}