segwit: add transaction/block serialization support for BIP141/BIP144; bump to 0.8

Do not yet support new sighash type
This commit is contained in:
Andrew Poelstra 2016-08-24 16:20:47 +00:00
parent 19e3adce86
commit af10b153be
5 changed files with 126 additions and 13 deletions

View File

@ -1,7 +1,7 @@
[package] [package]
name = "bitcoin" name = "bitcoin"
version = "0.7.4" version = "0.8.0"
authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"] authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"]
license = "CC0-1.0" license = "CC0-1.0"
homepage = "https://github.com/apoelstra/rust-bitcoin/" homepage = "https://github.com/apoelstra/rust-bitcoin/"

File diff suppressed because one or more lines are too long

View File

@ -61,7 +61,8 @@ fn bitcoin_genesis_tx() -> Transaction {
version: 1, version: 1,
lock_time: 0, lock_time: 0,
input: vec![], input: vec![],
output: vec![] output: vec![],
witness: vec![]
}; };
// Inputs // Inputs

View File

@ -30,7 +30,8 @@ use serde;
use util::hash::Sha256dHash; use util::hash::Sha256dHash;
use blockdata::script::Script; use blockdata::script::Script;
use network::serialize::{serialize, BitcoinHash}; use network::serialize::{serialize, BitcoinHash, SimpleEncoder, SimpleDecoder};
use network::encodable::{ConsensusEncodable, ConsensusDecodable};
/// A reference to a transaction output /// A reference to a transaction output
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
@ -94,9 +95,11 @@ pub struct Transaction {
/// List of inputs /// List of inputs
pub input: Vec<TxIn>, pub input: Vec<TxIn>,
/// List of outputs /// List of outputs
pub output: Vec<TxOut> pub output: Vec<TxOut>,
/// Witness data: for each txin, an array of byte-arrays
pub witness: Vec<Vec<Vec<u8>>>
} }
serde_struct_impl!(Transaction, version, lock_time, input, output); serde_struct_impl!(Transaction, version, lock_time, input, output, witness);
impl Transaction { impl Transaction {
/// Computes a "normalized TXID" which does not include any signatures. /// Computes a "normalized TXID" which does not include any signatures.
@ -107,7 +110,8 @@ impl Transaction {
version: self.version, version: self.version,
lock_time: self.lock_time, lock_time: self.lock_time,
input: self.input.iter().map(|txin| TxIn { script_sig: Script::new(), .. *txin }).collect(), input: self.input.iter().map(|txin| TxIn { script_sig: Script::new(), .. *txin }).collect(),
output: self.output.clone() output: self.output.clone(),
witness: vec![]
}; };
cloned_tx.bitcoin_hash() cloned_tx.bitcoin_hash()
} }
@ -143,7 +147,8 @@ impl Transaction {
version: self.version, version: self.version,
lock_time: self.lock_time, lock_time: self.lock_time,
input: vec![], input: vec![],
output: vec![] output: vec![],
witness: vec![]
}; };
// Add all inputs necessary.. // Add all inputs necessary..
if anyone_can_pay { if anyone_can_pay {
@ -193,7 +198,77 @@ impl BitcoinHash for Transaction {
impl_consensus_encoding!(TxIn, prev_hash, prev_index, script_sig, sequence); impl_consensus_encoding!(TxIn, prev_hash, prev_index, script_sig, sequence);
impl_consensus_encoding!(TxOut, value, script_pubkey); impl_consensus_encoding!(TxOut, value, script_pubkey);
impl_consensus_encoding!(Transaction, version, input, output, lock_time);
impl<S: SimpleEncoder> ConsensusEncodable<S> for Transaction {
fn consensus_encode(&self, s: &mut S) -> Result <(), S::Error> {
try!(self.version.consensus_encode(s));
if self.witness.is_empty() {
try!(self.input.consensus_encode(s));
try!(self.output.consensus_encode(s));
} else {
try!(0u8.consensus_encode(s));
try!(1u8.consensus_encode(s));
try!(self.input.consensus_encode(s));
try!(self.output.consensus_encode(s));
for witness in &self.witness {
try!(witness.consensus_encode(s));
}
}
self.lock_time.consensus_encode(s)
}
}
impl<D: SimpleDecoder> ConsensusDecodable<D> for Transaction {
fn consensus_decode(d: &mut D) -> Result<Transaction, D::Error> {
let version: u32 = try!(ConsensusDecodable::consensus_decode(d));
let input: Vec<TxIn> = try!(ConsensusDecodable::consensus_decode(d));
// segwit
if input.is_empty() {
let segwit_flag: u8 = try!(ConsensusDecodable::consensus_decode(d));
match segwit_flag {
// Empty tx
0 => {
Ok(Transaction {
version: version,
input: input,
output: vec![],
lock_time: try!(ConsensusDecodable::consensus_decode(d)),
witness: vec![]
})
}
// BIP144 input witnesses
1 => {
let input: Vec<TxIn> = try!(ConsensusDecodable::consensus_decode(d));
let output: Vec<TxOut> = try!(ConsensusDecodable::consensus_decode(d));
let mut witness: Vec<Vec<Vec<u8>>> = Vec::with_capacity(input.len());
for _ in 0..input.len() {
witness.push(try!(ConsensusDecodable::consensus_decode(d)));
}
Ok(Transaction {
version: version,
input: input,
output: output,
witness: witness,
lock_time: try!(ConsensusDecodable::consensus_decode(d))
})
}
// We don't support anything else
x => {
Err(d.error(format!("segwit flag {:02x} not understood", x)))
}
}
// non-segwit
} else {
Ok(Transaction {
version: version,
input: input,
output: try!(ConsensusDecodable::consensus_decode(d)),
lock_time: try!(ConsensusDecodable::consensus_decode(d)),
witness: vec![]
})
}
}
}
/// Hashtype of a transaction, encoded in the last byte of a signature /// Hashtype of a transaction, encoded in the last byte of a signature
/// Fixed values so they can be casted as integer types for encoding /// Fixed values so they can be casted as integer types for encoding
@ -254,11 +329,11 @@ impl SigHashType {
mod tests { mod tests {
use strason; use strason;
use super::{Transaction, TxIn, SigHashType}; use super::{Transaction, TxIn};
use blockdata::script::Script; use blockdata::script::Script;
use network::serialize::BitcoinHash; use network::serialize::BitcoinHash;
use network::serialize::deserialize; use network::serialize::{serialize, deserialize};
use util::hash::Sha256dHash; use util::hash::Sha256dHash;
use util::misc::hex_bytes; use util::misc::hex_bytes;
@ -318,16 +393,30 @@ mod tests {
fn run_test_sighash(tx: &str, script: &str, input_index: usize, hash_type: i32, expected_result: &str) { fn run_test_sighash(tx: &str, script: &str, input_index: usize, hash_type: i32, expected_result: &str) {
let tx: Transaction = deserialize(&hex_bytes(tx).unwrap()[..]).unwrap(); let tx: Transaction = deserialize(&hex_bytes(tx).unwrap()[..]).unwrap();
let script = Script::from(hex_bytes(script).unwrap()); let script = Script::from(hex_bytes(script).unwrap());
let sighash = SigHashType::from_u32(hash_type as u32);
let mut raw_expected = hex_bytes(expected_result).unwrap(); let mut raw_expected = hex_bytes(expected_result).unwrap();
raw_expected.reverse(); raw_expected.reverse();
let expected_result = Sha256dHash::from(&raw_expected[..]); let expected_result = Sha256dHash::from(&raw_expected[..]);
let actual_result = tx.signature_hash(input_index, &script, hash_type as u32); let actual_result = tx.signature_hash(input_index, &script, hash_type as u32);
println!("{} outputs {} inputs index {} sighash {:?}", tx.output.len(), tx.input.len(), input_index, sighash);
assert_eq!(actual_result, expected_result); assert_eq!(actual_result, expected_result);
} }
// Test decoding transaction `4be105f158ea44aec57bf12c5817d073a712ab131df6f37786872cfc70734188`
// from testnet, which is the first BIP144-encoded transaction I encountered.
#[test]
fn test_segwit_tx_decode() {
let hex_tx = hex_bytes("010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3603da1b0e00045503bd5704c7dd8a0d0ced13bb5785010800000000000a636b706f6f6c122f4e696e6a61506f6f6c2f5345475749542fffffffff02b4e5a212000000001976a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac0000000000000000266a24aa21a9edf91c46b49eb8a29089980f02ee6b57e7d63d33b18b4fddac2bcd7db2a39837040120000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
let tx: Transaction = deserialize(&hex_tx).unwrap();
let encoded = strason::from_serialize(&tx).unwrap();
let decoded = encoded.into_deserialize().unwrap();
assert_eq!(tx, decoded);
let consensus_encoded = serialize(&tx).unwrap();
assert_eq!(consensus_encoded, hex_tx);
}
// These test vectors were stolen from libbtc, which is Copyright 2014 Jonas Schnelli MIT // These test vectors were stolen from libbtc, which is Copyright 2014 Jonas Schnelli MIT
// They were transformed by replacing {...} with run_test_sighash(...), then the ones containing // They were transformed by replacing {...} with run_test_sighash(...), then the ones containing
// OP_CODESEPARATOR in their pubkeys were removed // OP_CODESEPARATOR in their pubkeys were removed

View File

@ -209,7 +209,7 @@ impl ExtendedPrivKey {
ChildNumber::Normal(n) => { ChildNumber::Normal(n) => {
if n >= (1 << 31) { return Err(Error::InvalidChildNumber(i)) } if n >= (1 << 31) { return Err(Error::InvalidChildNumber(i)) }
// Non-hardened key: compute public data and use that // Non-hardened key: compute public data and use that
hmac.input(&PublicKey::from_secret_key(secp, &self.secret_key).unwrap().serialize_vec(&secp, true)[..]); hmac.input(&PublicKey::from_secret_key(secp, &self.secret_key).unwrap().serialize_vec(secp, true)[..]);
BigEndian::write_u32(&mut be_n, n); BigEndian::write_u32(&mut be_n, n);
} }
ChildNumber::Hardened(n) => { ChildNumber::Hardened(n) => {