use crate::error::{Error, Result};
use crate::primitives::encoding::{Reader, Writer};
use crate::primitives::hash::sha256d;
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(input_count);
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(output_count);
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 input = ¶ms.inputs[params.input_index];
let hash_prevouts = compute_hash_prevouts(params.inputs, params.scope);
let hash_sequence = compute_hash_sequence(params.inputs, params.scope);
let hash_outputs = compute_hash_outputs(params.outputs, params.input_index, params.scope);
let mut writer = Writer::with_capacity(
4 + 32 + 32 + 32 + 4 + 9 + params.subscript.len() + 8 + 4 + 32 + 4 + 4, );
writer.write_i32_le(params.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(params.subscript.len() as u64);
writer.write_bytes(params.subscript);
writer.write_u64_le(params.satoshis);
writer.write_u32_le(input.sequence);
writer.write_bytes(&hash_outputs);
writer.write_u32_le(params.locktime);
writer.write_u32_le(params.scope);
writer.into_bytes()
}
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]);
}
}