use crate::error::{Error, Result};
use crate::primitives::encoding::{bounded_capacity, Reader, Writer};
use crate::primitives::hash::sha256d;
use std::collections::HashMap;
pub const SIGHASH_ALL: u32 = 0x00000001;
pub const SIGHASH_NONE: u32 = 0x00000002;
pub const SIGHASH_SINGLE: u32 = 0x00000003;
pub const SIGHASH_FORKID: u32 = 0x00000040;
pub const SIGHASH_ANYONECANPAY: u32 = 0x00000080;
const SIGHASH_BASE_MASK: u32 = 0x1F;
#[derive(Debug, Clone)]
pub struct TxInput {
pub txid: [u8; 32],
pub output_index: u32,
pub script: Vec<u8>,
pub sequence: u32,
}
#[derive(Debug, Clone)]
pub struct TxOutput {
pub satoshis: u64,
pub script: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct RawTransaction {
pub version: i32,
pub inputs: Vec<TxInput>,
pub outputs: Vec<TxOutput>,
pub locktime: u32,
}
pub fn parse_transaction(raw: &[u8]) -> Result<RawTransaction> {
let mut reader = Reader::new(raw);
let version = reader.read_i32_le()?;
let input_count = reader.read_var_int_num()?;
let mut inputs = Vec::with_capacity(bounded_capacity(input_count, reader.remaining(), 41));
for _ in 0..input_count {
let txid_bytes = reader.read_bytes(32)?;
let mut txid = [0u8; 32];
txid.copy_from_slice(txid_bytes);
let output_index = reader.read_u32_le()?;
let script = reader.read_var_bytes()?.to_vec();
let sequence = reader.read_u32_le()?;
inputs.push(TxInput {
txid,
output_index,
script,
sequence,
});
}
let output_count = reader.read_var_int_num()?;
let mut outputs = Vec::with_capacity(bounded_capacity(output_count, reader.remaining(), 9));
for _ in 0..output_count {
let satoshis = reader.read_u64_le()?;
let script = reader.read_var_bytes()?.to_vec();
outputs.push(TxOutput { satoshis, script });
}
let locktime = reader.read_u32_le()?;
Ok(RawTransaction {
version,
inputs,
outputs,
locktime,
})
}
#[derive(Debug)]
pub struct SighashParams<'a> {
pub version: i32,
pub inputs: &'a [TxInput],
pub outputs: &'a [TxOutput],
pub locktime: u32,
pub input_index: usize,
pub subscript: &'a [u8],
pub satoshis: u64,
pub scope: u32,
}
fn compute_hash_prevouts(inputs: &[TxInput], scope: u32) -> [u8; 32] {
if (scope & SIGHASH_ANYONECANPAY) != 0 {
return [0u8; 32];
}
let mut writer = Writer::new();
for input in inputs {
writer.write_bytes(&input.txid);
writer.write_u32_le(input.output_index);
}
sha256d(writer.as_bytes())
}
fn compute_hash_sequence(inputs: &[TxInput], scope: u32) -> [u8; 32] {
let base_type = scope & SIGHASH_BASE_MASK;
if (scope & SIGHASH_ANYONECANPAY) != 0
|| base_type == SIGHASH_SINGLE
|| base_type == SIGHASH_NONE
{
return [0u8; 32];
}
let mut writer = Writer::new();
for input in inputs {
writer.write_u32_le(input.sequence);
}
sha256d(writer.as_bytes())
}
fn compute_hash_outputs(outputs: &[TxOutput], input_index: usize, scope: u32) -> [u8; 32] {
let base_type = scope & SIGHASH_BASE_MASK;
if base_type != SIGHASH_SINGLE && base_type != SIGHASH_NONE {
let mut writer = Writer::new();
for output in outputs {
writer.write_u64_le(output.satoshis);
writer.write_var_int(output.script.len() as u64);
writer.write_bytes(&output.script);
}
sha256d(writer.as_bytes())
} else if base_type == SIGHASH_SINGLE && input_index < outputs.len() {
let output = &outputs[input_index];
let mut writer = Writer::new();
writer.write_u64_le(output.satoshis);
writer.write_var_int(output.script.len() as u64);
writer.write_bytes(&output.script);
sha256d(writer.as_bytes())
} else {
[0u8; 32]
}
}
pub fn build_sighash_preimage(params: &SighashParams) -> Vec<u8> {
let mut cache = SighashCache::from_parts(
params.version,
params.inputs,
params.outputs,
params.locktime,
);
match cache.preimage(
params.input_index,
params.subscript,
params.satoshis,
params.scope,
) {
Ok(preimage) => preimage,
Err(_) => panic!(
"input index {} out of range (transaction has {} inputs)",
params.input_index,
params.inputs.len()
),
}
}
#[derive(Debug, Clone)]
pub struct SighashCache<'t> {
version: i32,
inputs: &'t [TxInput],
outputs: &'t [TxOutput],
locktime: u32,
hash_prevouts: Option<[u8; 32]>,
hash_sequence: Option<[u8; 32]>,
hash_outputs_all: Option<[u8; 32]>,
hash_outputs_single: HashMap<usize, [u8; 32]>,
}
impl<'t> SighashCache<'t> {
pub fn new(tx: &'t RawTransaction) -> Self {
Self::from_parts(tx.version, &tx.inputs, &tx.outputs, tx.locktime)
}
pub fn from_parts(
version: i32,
inputs: &'t [TxInput],
outputs: &'t [TxOutput],
locktime: u32,
) -> Self {
Self {
version,
inputs,
outputs,
locktime,
hash_prevouts: None,
hash_sequence: None,
hash_outputs_all: None,
hash_outputs_single: HashMap::new(),
}
}
pub fn hash_prevouts(&mut self, scope: u32) -> [u8; 32] {
if (scope & SIGHASH_ANYONECANPAY) != 0 {
return [0u8; 32];
}
let inputs = self.inputs;
*self
.hash_prevouts
.get_or_insert_with(|| compute_hash_prevouts(inputs, scope))
}
pub fn hash_sequence(&mut self, scope: u32) -> [u8; 32] {
let base_type = scope & SIGHASH_BASE_MASK;
if (scope & SIGHASH_ANYONECANPAY) != 0
|| base_type == SIGHASH_SINGLE
|| base_type == SIGHASH_NONE
{
return [0u8; 32];
}
let inputs = self.inputs;
*self
.hash_sequence
.get_or_insert_with(|| compute_hash_sequence(inputs, scope))
}
pub fn hash_outputs(&mut self, input_index: usize, scope: u32) -> [u8; 32] {
let base_type = scope & SIGHASH_BASE_MASK;
let outputs = self.outputs;
if base_type != SIGHASH_SINGLE && base_type != SIGHASH_NONE {
*self
.hash_outputs_all
.get_or_insert_with(|| compute_hash_outputs(outputs, input_index, scope))
} else if base_type == SIGHASH_SINGLE && input_index < outputs.len() {
*self
.hash_outputs_single
.entry(input_index)
.or_insert_with(|| compute_hash_outputs(outputs, input_index, scope))
} else {
[0u8; 32]
}
}
pub fn preimage(
&mut self,
input_index: usize,
subscript: &[u8],
satoshis: u64,
scope: u32,
) -> Result<Vec<u8>> {
let inputs = self.inputs;
let input = inputs.get(input_index).ok_or_else(|| {
Error::CryptoError(format!(
"Input index {} out of range (transaction has {} inputs)",
input_index,
inputs.len()
))
})?;
let hash_prevouts = self.hash_prevouts(scope);
let hash_sequence = self.hash_sequence(scope);
let hash_outputs = self.hash_outputs(input_index, scope);
let mut writer = Writer::with_capacity(
4 + 32 + 32 + 32 + 4 + 9 + subscript.len() + 8 + 4 + 32 + 4 + 4, );
writer.write_i32_le(self.version);
writer.write_bytes(&hash_prevouts);
writer.write_bytes(&hash_sequence);
writer.write_bytes(&input.txid);
writer.write_u32_le(input.output_index);
writer.write_var_int(subscript.len() as u64);
writer.write_bytes(subscript);
writer.write_u64_le(satoshis);
writer.write_u32_le(input.sequence);
writer.write_bytes(&hash_outputs);
writer.write_u32_le(self.locktime);
writer.write_u32_le(scope);
Ok(writer.into_bytes())
}
pub fn sighash(
&mut self,
input_index: usize,
subscript: &[u8],
satoshis: u64,
scope: u32,
) -> Result<[u8; 32]> {
let preimage = self.preimage(input_index, subscript, satoshis, scope)?;
let mut hash = sha256d(&preimage);
hash.reverse();
Ok(hash)
}
pub fn sighash_for_signing(
&mut self,
input_index: usize,
subscript: &[u8],
satoshis: u64,
scope: u32,
) -> Result<[u8; 32]> {
let preimage = self.preimage(input_index, subscript, satoshis, scope)?;
Ok(sha256d(&preimage))
}
}
pub fn compute_sighash(params: &SighashParams) -> [u8; 32] {
let preimage = build_sighash_preimage(params);
let mut hash = sha256d(&preimage);
hash.reverse();
hash
}
pub fn compute_sighash_for_signing(params: &SighashParams) -> [u8; 32] {
let preimage = build_sighash_preimage(params);
sha256d(&preimage)
}
pub fn compute_sighash_from_raw(
raw_tx: &[u8],
input_index: usize,
subscript: &[u8],
satoshis: u64,
scope: u32,
) -> Result<[u8; 32]> {
let tx = parse_transaction(raw_tx)?;
if input_index >= tx.inputs.len() {
return Err(Error::CryptoError(format!(
"Input index {} out of range (transaction has {} inputs)",
input_index,
tx.inputs.len()
)));
}
Ok(compute_sighash(&SighashParams {
version: tx.version,
inputs: &tx.inputs,
outputs: &tx.outputs,
locktime: tx.locktime,
input_index,
subscript,
satoshis,
scope,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_transaction() {
let raw = hex::decode(
"01000000\
01\
0000000000000000000000000000000000000000000000000000000000000000\
ffffffff\
00\
ffffffff\
01\
0000000000000000\
00\
00000000",
)
.unwrap();
let tx = parse_transaction(&raw).unwrap();
assert_eq!(tx.version, 1);
assert_eq!(tx.inputs.len(), 1);
assert_eq!(tx.outputs.len(), 1);
assert_eq!(tx.locktime, 0);
assert_eq!(tx.inputs[0].output_index, 0xffffffff); assert_eq!(tx.inputs[0].sequence, 0xffffffff);
}
#[test]
fn test_sighash_constants() {
assert_eq!(SIGHASH_ALL, 0x01);
assert_eq!(SIGHASH_NONE, 0x02);
assert_eq!(SIGHASH_SINGLE, 0x03);
assert_eq!(SIGHASH_FORKID, 0x40);
assert_eq!(SIGHASH_ANYONECANPAY, 0x80);
}
#[test]
fn test_base_type_extraction() {
assert_eq!(SIGHASH_ALL & SIGHASH_BASE_MASK, SIGHASH_ALL);
assert_eq!(
(SIGHASH_ALL | SIGHASH_FORKID) & SIGHASH_BASE_MASK,
SIGHASH_ALL
);
assert_eq!(
(SIGHASH_SINGLE | SIGHASH_ANYONECANPAY | SIGHASH_FORKID) & SIGHASH_BASE_MASK,
SIGHASH_SINGLE
);
}
#[test]
fn test_hash_prevouts_anyonecanpay() {
let inputs = vec![TxInput {
txid: [1u8; 32],
output_index: 0,
script: vec![],
sequence: 0xffffffff,
}];
let hash = compute_hash_prevouts(&inputs, SIGHASH_ALL | SIGHASH_ANYONECANPAY);
assert_eq!(hash, [0u8; 32]);
let hash = compute_hash_prevouts(&inputs, SIGHASH_ALL);
assert_ne!(hash, [0u8; 32]);
}
#[test]
fn test_hash_sequence_conditions() {
let inputs = vec![TxInput {
txid: [1u8; 32],
output_index: 0,
script: vec![],
sequence: 0xffffffff,
}];
let hash = compute_hash_sequence(&inputs, SIGHASH_ALL | SIGHASH_ANYONECANPAY);
assert_eq!(hash, [0u8; 32]);
let hash = compute_hash_sequence(&inputs, SIGHASH_SINGLE);
assert_eq!(hash, [0u8; 32]);
let hash = compute_hash_sequence(&inputs, SIGHASH_NONE);
assert_eq!(hash, [0u8; 32]);
let hash = compute_hash_sequence(&inputs, SIGHASH_ALL);
assert_ne!(hash, [0u8; 32]);
}
#[test]
fn test_hash_outputs_all() {
let outputs = vec![
TxOutput {
satoshis: 1000,
script: vec![0x76, 0xa9],
},
TxOutput {
satoshis: 2000,
script: vec![0x76, 0xa9, 0x14],
},
];
let hash = compute_hash_outputs(&outputs, 0, SIGHASH_ALL);
assert_ne!(hash, [0u8; 32]);
}
#[test]
fn test_hash_outputs_single() {
let outputs = vec![
TxOutput {
satoshis: 1000,
script: vec![0x76, 0xa9],
},
TxOutput {
satoshis: 2000,
script: vec![0x76, 0xa9, 0x14],
},
];
let hash = compute_hash_outputs(&outputs, 0, SIGHASH_SINGLE);
assert_ne!(hash, [0u8; 32]);
let hash = compute_hash_outputs(&outputs, 5, SIGHASH_SINGLE);
assert_eq!(hash, [0u8; 32]);
}
#[test]
fn test_hash_outputs_none() {
let outputs = vec![TxOutput {
satoshis: 1000,
script: vec![0x76, 0xa9],
}];
let hash = compute_hash_outputs(&outputs, 0, SIGHASH_NONE);
assert_eq!(hash, [0u8; 32]);
}
fn cache_test_tx(n_in: usize, n_out: usize) -> RawTransaction {
let inputs = (0..n_in)
.map(|i| {
let mut txid = [0u8; 32];
txid[0] = i as u8;
txid[31] = 0xaa;
TxInput {
txid,
output_index: i as u32,
script: vec![],
sequence: 0xffff_ffff - i as u32,
}
})
.collect();
let outputs = (0..n_out)
.map(|i| {
let mut script = vec![0x76, 0xa9, 0x14];
script.extend((0..20).map(|j| (i + j) as u8));
script.extend([0x88, 0xac]);
TxOutput {
satoshis: 1000 + i as u64,
script,
}
})
.collect();
RawTransaction {
version: 1,
inputs,
outputs,
locktime: 17,
}
}
const CACHE_TEST_LOCK: [u8; 25] = [
0x76, 0xa9, 0x14, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0x88, 0xac,
];
#[test]
fn sighash_cache_preimage_equals_free_functions_all_scope_classes() {
let tx = cache_test_tx(7, 3);
let scopes = [
SIGHASH_ALL | SIGHASH_FORKID,
SIGHASH_NONE | SIGHASH_FORKID,
SIGHASH_SINGLE | SIGHASH_FORKID,
SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
SIGHASH_NONE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
SIGHASH_SINGLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
0x04 | SIGHASH_FORKID,
0x1f | SIGHASH_FORKID,
];
for &scope in &scopes {
let mut cache = SighashCache::new(&tx);
for i in 0..tx.inputs.len() {
let params = SighashParams {
version: tx.version,
inputs: &tx.inputs,
outputs: &tx.outputs,
locktime: tx.locktime,
input_index: i,
subscript: &CACHE_TEST_LOCK,
satoshis: 500 + i as u64,
scope,
};
let cached = cache
.preimage(i, &CACHE_TEST_LOCK, 500 + i as u64, scope)
.unwrap();
assert_eq!(
cached,
build_sighash_preimage(¶ms),
"preimage: scope {scope:#x} input {i}"
);
assert_eq!(
cache
.sighash(i, &CACHE_TEST_LOCK, 500 + i as u64, scope)
.unwrap(),
compute_sighash(¶ms),
"sighash (display order): scope {scope:#x} input {i}"
);
assert_eq!(
cache
.sighash_for_signing(i, &CACHE_TEST_LOCK, 500 + i as u64, scope)
.unwrap(),
compute_sighash_for_signing(¶ms),
"sighash (signing order): scope {scope:#x} input {i}"
);
}
}
}
#[test]
fn sighash_cache_mixed_scopes_on_one_cache() {
let tx = cache_test_tx(6, 2);
let scopes = [
SIGHASH_ALL | SIGHASH_FORKID,
SIGHASH_SINGLE | SIGHASH_FORKID,
SIGHASH_NONE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
SIGHASH_ALL | SIGHASH_FORKID,
SIGHASH_SINGLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
SIGHASH_NONE | SIGHASH_FORKID,
];
let mut cache = SighashCache::new(&tx);
for (i, &scope) in scopes.iter().enumerate() {
let cached = cache.preimage(i, &CACHE_TEST_LOCK, 42, scope).unwrap();
let fresh = build_sighash_preimage(&SighashParams {
version: tx.version,
inputs: &tx.inputs,
outputs: &tx.outputs,
locktime: tx.locktime,
input_index: i,
subscript: &CACHE_TEST_LOCK,
satoshis: 42,
scope,
});
assert_eq!(cached, fresh, "input {i} scope {scope:#x}");
}
}
#[test]
fn sighash_cache_single_out_of_range_zeros() {
let tx = cache_test_tx(5, 2);
let scope = SIGHASH_SINGLE | SIGHASH_FORKID;
let mut cache = SighashCache::new(&tx);
for i in 2..5 {
let cached = cache.preimage(i, &CACHE_TEST_LOCK, 42, scope).unwrap();
let fresh = build_sighash_preimage(&SighashParams {
version: tx.version,
inputs: &tx.inputs,
outputs: &tx.outputs,
locktime: tx.locktime,
input_index: i,
subscript: &CACHE_TEST_LOCK,
satoshis: 42,
scope,
});
assert_eq!(cached, fresh, "input {i}");
assert_eq!(&cached[cached.len() - 40..cached.len() - 8], &[0u8; 32]);
}
}
#[test]
fn sighash_cache_from_parts_equals_new() {
let tx = cache_test_tx(3, 3);
let scope = SIGHASH_ALL | SIGHASH_FORKID;
let mut a = SighashCache::new(&tx);
let mut b = SighashCache::from_parts(tx.version, &tx.inputs, &tx.outputs, tx.locktime);
for i in 0..3 {
assert_eq!(
a.preimage(i, &CACHE_TEST_LOCK, 7, scope).unwrap(),
b.preimage(i, &CACHE_TEST_LOCK, 7, scope).unwrap(),
);
}
}
#[test]
fn sighash_cache_input_index_out_of_range_errors() {
let tx = cache_test_tx(2, 1);
let mut cache = SighashCache::new(&tx);
let scope = SIGHASH_ALL | SIGHASH_FORKID;
assert!(cache.preimage(2, &CACHE_TEST_LOCK, 1, scope).is_err());
assert!(cache.sighash(9, &CACHE_TEST_LOCK, 1, scope).is_err());
assert!(cache
.sighash_for_signing(2, &CACHE_TEST_LOCK, 1, scope)
.is_err());
assert!(cache.preimage(1, &CACHE_TEST_LOCK, 1, scope).is_ok());
}
#[test]
fn sighash_cache_midstates_computed_once() {
let tx = cache_test_tx(4, 2);
let mut cache = SighashCache::new(&tx);
cache
.preimage(
0,
&CACHE_TEST_LOCK,
1,
SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
)
.unwrap();
assert!(cache.hash_prevouts.is_none());
assert!(cache.hash_sequence.is_none());
cache
.preimage(0, &CACHE_TEST_LOCK, 1, SIGHASH_ALL | SIGHASH_FORKID)
.unwrap();
let prevouts = cache.hash_prevouts;
let sequence = cache.hash_sequence;
let outputs_all = cache.hash_outputs_all;
assert!(prevouts.is_some() && sequence.is_some() && outputs_all.is_some());
cache
.preimage(3, &CACHE_TEST_LOCK, 1, SIGHASH_ALL | SIGHASH_FORKID)
.unwrap();
assert_eq!(cache.hash_prevouts, prevouts);
assert_eq!(cache.hash_sequence, sequence);
assert_eq!(cache.hash_outputs_all, outputs_all);
cache
.preimage(1, &CACHE_TEST_LOCK, 1, SIGHASH_SINGLE | SIGHASH_FORKID)
.unwrap();
assert!(cache.hash_outputs_single.contains_key(&1));
cache
.preimage(3, &CACHE_TEST_LOCK, 1, SIGHASH_SINGLE | SIGHASH_FORKID)
.unwrap();
assert!(!cache.hash_outputs_single.contains_key(&3));
}
}