use byteorder::{LittleEndian, WriteBytesExt};
use messages::{OutPoint, Payload, Tx, TxOut};
use script::{next_op, op_codes, Script};
use std::io::Write;
use util::{sha256d, var_int, Amount, Error, Hash256, Result, Serializable};
pub const SIGHASH_ALL: u8 = 0x01;
pub const SIGHASH_NONE: u8 = 0x02;
pub const SIGHASH_SINGLE: u8 = 0x03;
pub const SIGHASH_ANYONECANPAY: u8 = 0x80;
pub const SIGHASH_FORKID: u8 = 0x40;
const FORK_ID: u32 = 0;
pub fn sighash(
tx: &Tx,
n_input: usize,
script_code: &[u8],
amount: Amount,
sighash_type: u8,
cache: &mut SigHashCache,
) -> Result<Hash256> {
if sighash_type & SIGHASH_FORKID != 0 {
bip143_sighash(tx, n_input, script_code, amount, sighash_type, cache)
} else {
legacy_sighash(tx, n_input, script_code, sighash_type)
}
}
pub struct SigHashCache {
hash_prevouts: Option<Hash256>,
hash_sequence: Option<Hash256>,
hash_outputs: Option<Hash256>,
}
impl SigHashCache {
pub fn new() -> SigHashCache {
SigHashCache {
hash_prevouts: None,
hash_sequence: None,
hash_outputs: None,
}
}
}
fn bip143_sighash(
tx: &Tx,
n_input: usize,
script_code: &[u8],
amount: Amount,
sighash_type: u8,
cache: &mut SigHashCache,
) -> Result<Hash256> {
if n_input >= tx.inputs.len() {
return Err(Error::BadArgument("input out of tx_in range".to_string()));
}
let mut s = Vec::with_capacity(tx.size());
let base_type = sighash_type & 31;
let anyone_can_pay = sighash_type & SIGHASH_ANYONECANPAY != 0;
s.write_u32::<LittleEndian>(tx.version)?;
if !anyone_can_pay {
if cache.hash_prevouts.is_none() {
let mut prev_outputs = Vec::with_capacity(OutPoint::SIZE * tx.inputs.len());
for input in tx.inputs.iter() {
input.prev_output.write(&mut prev_outputs)?;
}
cache.hash_prevouts = Some(sha256d(&prev_outputs));
}
s.write(&cache.hash_prevouts.unwrap().0)?;
} else {
s.write(&[0; 32])?;
}
if !anyone_can_pay && base_type != SIGHASH_SINGLE && base_type != SIGHASH_NONE {
if cache.hash_sequence.is_none() {
let mut sequences = Vec::with_capacity(4 * tx.inputs.len());
for tx_in in tx.inputs.iter() {
sequences.write_u32::<LittleEndian>(tx_in.sequence)?;
}
cache.hash_sequence = Some(sha256d(&sequences));
}
s.write(&cache.hash_sequence.unwrap().0)?;
} else {
s.write(&[0; 32])?;
}
tx.inputs[n_input].prev_output.write(&mut s)?;
var_int::write(script_code.len() as u64, &mut s)?;
s.write(&script_code)?;
s.write_i64::<LittleEndian>(amount.0)?;
s.write_u32::<LittleEndian>(tx.inputs[n_input].sequence)?;
if base_type != SIGHASH_SINGLE && base_type != SIGHASH_NONE {
if cache.hash_outputs.is_none() {
let mut size = 0;
for tx_out in tx.outputs.iter() {
size += tx_out.size();
}
let mut outputs = Vec::with_capacity(size);
for tx_out in tx.outputs.iter() {
tx_out.write(&mut outputs)?;
}
cache.hash_outputs = Some(sha256d(&outputs));
}
s.write(&cache.hash_outputs.unwrap().0)?;
} else if base_type == SIGHASH_SINGLE && n_input < tx.outputs.len() {
let mut outputs = Vec::with_capacity(tx.outputs[n_input].size());
tx.outputs[n_input].write(&mut outputs)?;
s.write(&sha256d(&outputs).0)?;
} else {
s.write(&[0; 32])?;
}
s.write_u32::<LittleEndian>(tx.lock_time)?;
s.write_u32::<LittleEndian>((FORK_ID << 8) | sighash_type as u32)?;
Ok(sha256d(&s))
}
fn legacy_sighash(
tx: &Tx,
n_input: usize,
script_code: &[u8],
sighash_type: u8,
) -> Result<Hash256> {
if n_input >= tx.inputs.len() {
return Err(Error::BadArgument("input out of tx_in range".to_string()));
}
let mut s = Vec::with_capacity(tx.size());
let base_type = sighash_type & 31;
let anyone_can_pay = sighash_type & SIGHASH_ANYONECANPAY != 0;
let mut sub_script = Vec::with_capacity(script_code.len());
let mut i = 0;
while i < script_code.len() {
let next = next_op(i, script_code);
if script_code[i] != op_codes::OP_CODESEPARATOR {
sub_script.extend_from_slice(&script_code[i..next]);
}
i = next;
}
s.write_u32::<LittleEndian>(tx.version)?;
let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
var_int::write(n_inputs as u64, &mut s)?;
for i in 0..tx.inputs.len() {
let i = if anyone_can_pay { n_input } else { i };
let mut tx_in = tx.inputs[i].clone();
if i == n_input {
tx_in.sig_script = Script(Vec::with_capacity(4 + sub_script.len()));
tx_in.sig_script.0.extend_from_slice(&sub_script);
} else {
tx_in.sig_script = Script(vec![]);
if base_type == SIGHASH_NONE || base_type == SIGHASH_SINGLE {
tx_in.sequence = 0;
}
}
tx_in.write(&mut s)?;
if anyone_can_pay {
break;
}
}
let tx_out_list = if base_type == SIGHASH_NONE {
vec![]
} else if base_type == SIGHASH_SINGLE {
if n_input >= tx.outputs.len() {
return Err(Error::BadArgument("input out of tx_out range".to_string()));
}
let mut truncated_out = tx.outputs.clone();
truncated_out.truncate(n_input + 1);
truncated_out
} else {
tx.outputs.clone()
};
var_int::write(tx_out_list.len() as u64, &mut s)?;
for i in 0..tx_out_list.len() {
if i == n_input && base_type == SIGHASH_SINGLE {
let empty = TxOut {
amount: Amount(-1),
pk_script: Script(vec![]),
};
empty.write(&mut s)?;
} else {
tx_out_list[i].write(&mut s)?;
}
}
s.write_u32::<LittleEndian>(tx.lock_time)?;
s.write_u32::<LittleEndian>(sighash_type as u32)?;
Ok(sha256d(&s))
}
#[cfg(test)]
mod tests {
use super::*;
use address::legacyaddr_decode;
use hex;
use messages::{OutPoint, TxIn};
use network::Network;
use transaction::p2pkh;
#[test]
fn bip143_sighash_test() {
let pk_script = hex::decode("76a91402b74813b047606b4b3fbdfb1a6e8e053fdb8dab88ac").unwrap();
let addr = "mfmKD4cP6Na7T8D87XRSiR7shA1HNGSaec";
let hash160 = legacyaddr_decode(addr, Network::Testnet).unwrap().0;
let tx = Tx {
version: 2,
inputs: vec![TxIn {
prev_output: OutPoint {
hash: Hash256::decode(
"f671dc000ad12795e86b59b27e0c367d9b026bbd4141c227b9285867a53bb6f7",
).unwrap(),
index: 0,
},
sig_script: Script(vec![]),
sequence: 0,
}],
outputs: vec![
TxOut {
amount: Amount(100),
pk_script: p2pkh::create_pk_script(&hash160),
},
TxOut {
amount: Amount(259899900),
pk_script: p2pkh::create_pk_script(&hash160),
},
],
lock_time: 0,
};
let mut cache = SigHashCache::new();
let sighash_type = SIGHASH_ALL | SIGHASH_FORKID;
let sighash = bip143_sighash(
&tx,
0,
&pk_script,
Amount(260000000),
sighash_type,
&mut cache,
).unwrap();
let expected = "1e2121837829018daf3aeadab76f1a542c49a3600ded7bd74323ee74ce0d840c";
assert!(sighash.0.to_vec() == hex::decode(expected).unwrap());
assert!(cache.hash_prevouts.is_some());
assert!(cache.hash_sequence.is_some());
assert!(cache.hash_outputs.is_some());
}
#[test]
fn legacy_sighash_test() {
let pk_script = hex::decode("76a914d951eb562f1ff26b6cbe89f04eda365ea6bd95ce88ac").unwrap();
let tx = Tx {
version: 1,
inputs: vec![TxIn {
prev_output: OutPoint {
hash: Hash256::decode(
"bf6c1139ea01ca054b8d00aa0a088daaeab4f3b8e111626c6be7d603a9dd8dff",
).unwrap(),
index: 0,
},
sig_script: Script(vec![]),
sequence: 0xffffffff,
}],
outputs: vec![TxOut {
amount: Amount(49990000),
pk_script: Script(
hex::decode("76a9147865b0b301119fc3eadc7f3406ff1339908e46d488ac").unwrap(),
),
}],
lock_time: 0,
};
let sighash = legacy_sighash(&tx, 0, &pk_script, SIGHASH_ALL).unwrap();
let expected = "ad16084eccf26464a84c5ee2f8b96b4daff9a3154ac3c1b320346aed042abe57";
assert!(sighash.0.to_vec() == hex::decode(expected).unwrap());
}
}