use crate::crypto::OptimizedSha256;
use crate::error::Result;
use crate::types::*;
use blvm_spec_lock::spec_locked;
#[inline]
fn write_varint_to_vec(vec: &mut Vec<u8>, value: u64) {
if value < 0xfd {
vec.push(value as u8);
} else if value <= 0xffff {
vec.push(0xfd);
vec.extend_from_slice(&(value as u16).to_le_bytes());
} else if value <= 0xffffffff {
vec.push(0xfe);
vec.extend_from_slice(&(value as u32).to_le_bytes());
} else {
vec.push(0xff);
vec.extend_from_slice(&value.to_le_bytes());
}
}
#[cfg(feature = "production")]
use hashbrown::HashMap as HashBrownMap;
#[cfg(feature = "production")]
use lru::LruCache;
#[cfg(feature = "production")]
use rustc_hash::{FxBuildHasher, FxHasher};
#[cfg(feature = "production")]
use std::cell::RefCell;
#[cfg(feature = "production")]
use std::hash::{Hash as StdHash, Hasher};
#[cfg(feature = "production")]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct SighashCacheKey {
prevout: crate::types::OutPoint,
code_hash: u64,
sighash_byte: u8,
}
#[cfg(feature = "production")]
impl std::hash::Hash for SighashCacheKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.code_hash);
}
}
#[cfg(feature = "production")]
pub type SighashMidstateCache =
std::sync::Arc<std::sync::Mutex<HashBrownMap<SighashCacheKey, [u8; 32], FxBuildHasher>>>;
#[cfg(feature = "production")]
thread_local! {
static SIGHASH_MIDSTATE_CACHE: RefCell<HashBrownMap<SighashCacheKey, [u8; 32], FxBuildHasher>> =
const { RefCell::new(HashBrownMap::with_hasher(FxBuildHasher)) };
}
#[cfg(feature = "production")]
fn insert_midstate_cache(
sighash_cache: Option<&SighashMidstateCache>,
prevout: crate::types::OutPoint,
code: &[u8],
sighash_byte: u8,
hash: [u8; 32],
) {
let key_hash = sighash_cache_hash(&prevout, code, sighash_byte);
let key = SighashCacheKey {
prevout,
code_hash: key_hash,
sighash_byte,
};
if let Some(c) = sighash_cache {
let _ = c.lock().map(|mut g| g.insert(key, hash));
} else {
SIGHASH_MIDSTATE_CACHE.with(|cell| {
cell.borrow_mut().insert(key, hash);
});
}
}
#[cfg(feature = "production")]
#[inline]
fn sighash_cache_hash(prevout: &crate::types::OutPoint, code: &[u8], sighash_byte: u8) -> u64 {
let mut hasher = FxHasher::default();
prevout.hash(&mut hasher);
code.hash(&mut hasher);
sighash_byte.hash(&mut hasher);
hasher.finish()
}
#[cfg(feature = "production")]
thread_local! {
static SIGHASH_CACHE: RefCell<LruCache<[u8; 32], [u8; 32]>> = RefCell::new({
let cap = std::env::var("BLVM_SIGHASH_CACHE_SIZE")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(262_144)
.clamp(65_536, 2_097_152);
LruCache::new(std::num::NonZeroUsize::new(cap).unwrap())
});
}
#[cfg(feature = "production")]
thread_local! {
static SIGHASH_PREIMAGE_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(4096));
}
#[cfg(feature = "production")]
thread_local! {
static BIP143_SERIALIZE_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(131_072)); }
#[cfg(feature = "production")]
thread_local! {
static BIP143_PREIMAGE_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(1024));
}
#[cfg(feature = "production")]
thread_local! {
static BIP143_SINGLE_OUTPUT_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(256));
}
#[cfg(feature = "production")]
thread_local! {
static LEGACY_BATCH_PREIMAGES: std::cell::RefCell<Vec<Vec<u8>>> =
const { std::cell::RefCell::new(Vec::new()) };
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SighashType(pub u8);
impl SighashType {
pub const ALL_LEGACY: Self = SighashType(0x00);
pub const ALL: Self = SighashType(0x01);
pub const NONE: Self = SighashType(0x02);
pub const SINGLE: Self = SighashType(0x03);
pub const ALL_ANYONECANPAY: Self = SighashType(0x81);
pub const NONE_ANYONECANPAY: Self = SighashType(0x82);
pub const SINGLE_ANYONECANPAY: Self = SighashType(0x83);
pub fn from_byte(byte: u8) -> Self {
SighashType(byte)
}
pub fn as_u32(&self) -> u32 {
self.0 as u32
}
pub fn base_type(&self) -> u8 {
self.0 & 0x1f
}
pub fn is_anyonecanpay(&self) -> bool {
self.0 & 0x80 != 0
}
pub fn is_all(&self) -> bool {
let base = self.base_type();
base != 0x02 && base != 0x03
}
pub fn is_none(&self) -> bool {
self.base_type() == 0x02
}
pub fn is_single(&self) -> bool {
self.base_type() == 0x03
}
}
#[cfg(feature = "production")]
#[allow(dead_code)]
#[inline]
fn is_cacheable_sighash_pattern(
tx: &Transaction,
input_index: usize,
sighash_type: SighashType,
) -> bool {
if sighash_type.is_anyonecanpay() {
return false;
}
let base = sighash_type.base_type();
if base == 0x01 || base == 0x00 {
let ni = tx.inputs.len();
let no = tx.outputs.len();
(ni == 1 && (1..=4).contains(&no))
|| ((1..=4).contains(&ni) && no == 1)
|| (ni == 2 && no == 2)
|| (ni == 1 && no == 1)
} else if base == 0x02 {
!tx.inputs.is_empty() && tx.inputs.len() <= 4
} else if base == 0x03 {
input_index < tx.outputs.len() && tx.inputs.len() <= 4
} else {
false
}
}
#[cfg(feature = "production")]
fn sighash_with_cache(preimage: &[u8]) -> Hash {
let hasher = OptimizedSha256::new();
let first_hash: [u8; 32] = hasher.hash(preimage);
SIGHASH_CACHE.with(|cell| {
let mut cache = cell.borrow_mut();
if let Some(cached) = cache.get(&first_hash) {
return *cached;
}
let second_hash = hasher.hash(&first_hash);
let mut result = [0u8; 32];
result.copy_from_slice(&second_hash);
cache.put(first_hash, result);
result
})
}
#[cfg(feature = "production")]
#[spec_locked("5.1.1")]
#[inline]
pub fn compute_legacy_sighash_nocache(
tx: &Transaction,
input_index: usize,
script_code: &[u8],
sighash_byte: u8,
) -> [u8; 32] {
use sha2::{Digest, Sha256};
let sighash_u32 = sighash_byte as u32;
let base_type = sighash_u32 & 0x1f;
let anyone_can_pay = (sighash_u32 & 0x80) != 0;
let hash_none = base_type == 0x02;
let hash_single = base_type == 0x03;
if hash_single && input_index >= tx.outputs.len() {
let mut result = [0u8; 32];
result[0] = 1;
return result;
}
let mut h = Sha256::new();
h.update((tx.version as u32).to_le_bytes());
let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
update_varint(&mut h, n_inputs as u64);
for i in 0..n_inputs {
let actual_i = if anyone_can_pay { input_index } else { i };
let input = &tx.inputs[actual_i];
h.update(input.prevout.hash);
h.update(input.prevout.index.to_le_bytes());
if actual_i == input_index {
update_varint(&mut h, script_code.len() as u64);
h.update(script_code);
} else {
h.update([0u8]);
}
if actual_i != input_index && (hash_single || hash_none) {
h.update(0u32.to_le_bytes());
} else {
h.update((input.sequence as u32).to_le_bytes());
}
}
let n_outputs = if hash_none {
0
} else if hash_single {
input_index + 1
} else {
tx.outputs.len()
};
update_varint(&mut h, n_outputs as u64);
for i in 0..n_outputs {
if hash_single && i != input_index {
h.update((-1i64).to_le_bytes());
h.update([0u8]);
} else {
let output = &tx.outputs[i];
h.update(output.value.to_le_bytes());
update_varint(&mut h, output.script_pubkey.len() as u64);
h.update(output.script_pubkey.as_slice());
}
}
h.update((tx.lock_time as u32).to_le_bytes());
h.update(sighash_u32.to_le_bytes());
let first_hash = h.finalize();
let second_hash = Sha256::digest(first_hash);
let mut result = [0u8; 32];
result.copy_from_slice(&second_hash);
result
}
#[cfg(feature = "production")]
#[inline]
fn update_varint(hasher: &mut sha2::Sha256, value: u64) {
use sha2::Digest;
if value < 0xfd {
hasher.update([value as u8]);
} else if value <= 0xffff {
hasher.update([0xfd]);
hasher.update((value as u16).to_le_bytes());
} else if value <= 0xffffffff {
hasher.update([0xfe]);
hasher.update((value as u32).to_le_bytes());
} else {
hasher.update([0xff]);
hasher.update(value.to_le_bytes());
}
}
#[cfg(feature = "production")]
#[inline]
fn push_varint(buf: &mut Vec<u8>, value: u64) {
if value < 0xfd {
buf.push(value as u8);
} else if value <= 0xffff {
buf.push(0xfd);
buf.extend_from_slice(&(value as u16).to_le_bytes());
} else if value <= 0xffffffff {
buf.push(0xfe);
buf.extend_from_slice(&(value as u32).to_le_bytes());
} else {
buf.push(0xff);
buf.extend_from_slice(&value.to_le_bytes());
}
}
#[cfg(feature = "production")]
#[spec_locked("5.1.1")]
#[inline]
pub fn compute_legacy_sighash_buffered(
tx: &Transaction,
input_index: usize,
script_code: &[u8],
sighash_byte: u8,
) -> [u8; 32] {
use sha2::{Digest, Sha256};
let sighash_u32 = sighash_byte as u32;
let base_type = sighash_u32 & 0x1f;
let anyone_can_pay = (sighash_u32 & 0x80) != 0;
let hash_none = base_type == 0x02;
let hash_single = base_type == 0x03;
if hash_single && input_index >= tx.outputs.len() {
let mut result = [0u8; 32];
result[0] = 1;
return result;
}
thread_local! {
static BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(4096));
}
BUF.with(|cell| {
let mut buf = cell.borrow_mut();
buf.clear();
buf.extend_from_slice(&(tx.version as u32).to_le_bytes());
let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
push_varint(&mut buf, n_inputs as u64);
for i in 0..n_inputs {
let actual_i = if anyone_can_pay { input_index } else { i };
let input = &tx.inputs[actual_i];
buf.extend_from_slice(&input.prevout.hash);
buf.extend_from_slice(&input.prevout.index.to_le_bytes());
if actual_i == input_index {
push_varint(&mut buf, script_code.len() as u64);
buf.extend_from_slice(script_code);
} else {
buf.push(0u8);
}
if actual_i != input_index && (hash_single || hash_none) {
buf.extend_from_slice(&0u32.to_le_bytes());
} else {
buf.extend_from_slice(&(input.sequence as u32).to_le_bytes());
}
}
let n_outputs = if hash_none {
0
} else if hash_single {
input_index + 1
} else {
tx.outputs.len()
};
push_varint(&mut buf, n_outputs as u64);
for i in 0..n_outputs {
if hash_single && i != input_index {
buf.extend_from_slice(&(-1i64).to_le_bytes());
buf.push(0u8);
} else {
let output = &tx.outputs[i];
buf.extend_from_slice(&output.value.to_le_bytes());
push_varint(&mut buf, output.script_pubkey.len() as u64);
buf.extend_from_slice(&output.script_pubkey);
}
}
buf.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
buf.extend_from_slice(&sighash_u32.to_le_bytes());
let first_hash = Sha256::digest(buf.as_slice());
let second_hash = Sha256::digest(first_hash);
let mut result = [0u8; 32];
result.copy_from_slice(&second_hash);
result
})
}
#[cfg(feature = "production")]
#[spec_locked("5.1.1")]
pub fn compute_sighashes_batch(
tx: &Transaction,
script_codes: &[&[u8]],
sighash_bytes: &[u8],
) -> Vec<[u8; 32]> {
use sha2::{Digest, Sha256};
let n = tx.inputs.len();
debug_assert_eq!(script_codes.len(), n);
debug_assert_eq!(sighash_bytes.len(), n);
let mut results = Vec::with_capacity(n);
let all_sighash_all = sighash_bytes.iter().all(|&b| {
let base = (b as u32) & 0x1f;
let acp = (b as u32) & 0x80;
base == 0x01 && acp == 0
});
if !all_sighash_all || n <= 1 {
for i in 0..n {
results.push(compute_legacy_sighash_nocache(
tx,
i,
script_codes[i],
sighash_bytes[i],
));
}
return results;
}
let mut outputs_buf: Vec<u8> = Vec::with_capacity(tx.outputs.len() * 40 + 16);
write_varint_to_vec(&mut outputs_buf, tx.outputs.len() as u64);
for output in tx.outputs.iter() {
outputs_buf.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut outputs_buf, output.script_pubkey.len() as u64);
outputs_buf.extend_from_slice(&output.script_pubkey);
}
outputs_buf.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
let mut running = Sha256::new();
running.update((tx.version as u32).to_le_bytes());
update_varint(&mut running, n as u64);
let mut midstates: Vec<Sha256> = Vec::with_capacity(n);
for j in 0..n {
midstates.push(running.clone());
running.update(tx.inputs[j].prevout.hash);
running.update(tx.inputs[j].prevout.index.to_le_bytes());
running.update([0u8]);
running.update((tx.inputs[j].sequence as u32).to_le_bytes());
}
let sighash_u32_le = 0x01u32.to_le_bytes();
for i in 0..n {
let mut h = midstates[i].clone();
h.update(tx.inputs[i].prevout.hash);
h.update(tx.inputs[i].prevout.index.to_le_bytes());
update_varint(&mut h, script_codes[i].len() as u64);
h.update(script_codes[i]);
h.update((tx.inputs[i].sequence as u32).to_le_bytes());
for j in (i + 1)..n {
h.update(tx.inputs[j].prevout.hash);
h.update(tx.inputs[j].prevout.index.to_le_bytes());
h.update([0u8]);
h.update((tx.inputs[j].sequence as u32).to_le_bytes());
}
h.update(outputs_buf.as_slice());
h.update(sighash_u32_le);
let first_hash = h.finalize();
let second_hash = Sha256::digest(first_hash);
let mut result = [0u8; 32];
result.copy_from_slice(&second_hash);
results.push(result);
}
results
}
#[spec_locked("5.1")]
pub fn calculate_transaction_sighash(
tx: &Transaction,
input_index: usize,
prevouts: &[TransactionOutput],
sighash_type: SighashType,
) -> Result<Hash> {
let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
let prevout_script_pubkeys: Vec<&[u8]> =
prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
if prevout_values.len() != tx.inputs.len() || prevout_script_pubkeys.len() != tx.inputs.len() {
return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
prevout_values.len(),
tx.inputs.len(),
));
}
calculate_transaction_sighash_with_script_code(
tx,
input_index,
&prevout_values,
&prevout_script_pubkeys,
sighash_type,
None,
#[cfg(feature = "production")]
None,
)
}
#[spec_locked("5.1")]
pub fn calculate_transaction_sighash_single_input(
tx: &Transaction,
input_index: usize,
script_for_signing: &[u8],
prevout_value: i64,
sighash_type: SighashType,
#[cfg(feature = "production")] sighash_cache: Option<&SighashMidstateCache>,
) -> Result<Hash> {
if input_index >= tx.inputs.len() {
return Err(crate::error::ConsensusError::InvalidInputIndex(input_index));
}
#[cfg(feature = "production")]
return calculate_transaction_sighash_with_script_code(
tx,
input_index,
&[],
&[],
sighash_type,
Some(script_for_signing),
sighash_cache,
);
#[cfg(not(feature = "production"))]
{
let mut prevout_values = vec![0i64; tx.inputs.len()];
prevout_values[input_index] = prevout_value;
let prevout_script_pubkeys: Vec<&[u8]> = (0..tx.inputs.len())
.map(|i| {
if i == input_index {
script_for_signing
} else {
&[]
}
})
.collect();
calculate_transaction_sighash_with_script_code(
tx,
input_index,
&prevout_values,
&prevout_script_pubkeys,
sighash_type,
Some(script_for_signing),
)
}
}
#[spec_locked("5.1")]
pub fn calculate_transaction_sighash_with_script_code(
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
sighash_type: SighashType,
script_code: Option<&[u8]>,
#[cfg(feature = "production")] sighash_cache: Option<&SighashMidstateCache>,
) -> Result<Hash> {
#[cfg(all(feature = "production", feature = "profile"))]
let _t0 = std::time::Instant::now();
if input_index >= tx.inputs.len() {
return Err(crate::error::ConsensusError::InvalidInputIndex(input_index));
}
if script_code.is_none()
&& (prevout_values.len() != tx.inputs.len()
|| prevout_script_pubkeys.len() != tx.inputs.len())
{
return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
prevout_values.len(),
tx.inputs.len(),
));
}
let sighash_byte = sighash_type.as_u32();
let base_type = sighash_byte & 0x1f;
let anyone_can_pay = (sighash_byte & 0x80) != 0;
let hash_none = base_type == 0x02; let hash_single = base_type == 0x03;
if hash_single && input_index >= tx.outputs.len() {
let mut result = [0u8; 32];
result[0] = 1; return Ok(result);
}
#[cfg(feature = "production")]
{
let prevout = &tx.inputs[input_index].prevout;
let code = script_code.unwrap_or_else(|| prevout_script_pubkeys[input_index]);
let sighash_byte_u8 = sighash_byte as u8;
let hash = sighash_cache_hash(prevout, code, sighash_byte_u8);
let cached = if let Some(cache) = sighash_cache {
cache.lock().ok().and_then(|guard| {
(*guard)
.raw_entry()
.from_hash(hash, |k: &SighashCacheKey| {
k.prevout == *prevout
&& k.code_hash == hash
&& k.sighash_byte == sighash_byte_u8
})
.map(|(_, v)| *v)
})
} else {
SIGHASH_MIDSTATE_CACHE.with(|cell| {
let map = cell.borrow();
map.raw_entry()
.from_hash(hash, |k: &SighashCacheKey| {
k.prevout == *prevout
&& k.code_hash == hash
&& k.sighash_byte == sighash_byte_u8
})
.map(|(_, v)| *v)
})
};
if let Some(cached) = cached {
return Ok(cached);
}
}
#[cfg(feature = "production")]
if tx.inputs.len() == 1 && input_index == 0 && !anyone_can_pay && !hash_none && !hash_single {
let base_type = sighash_byte & 0x1f;
if base_type == 0x01 || base_type == 0x00 {
let n_out = tx.outputs.len();
if n_out == 1 {
if let Ok(h) = build_preimage_1in1out_sighash_all(
tx,
prevout_values,
prevout_script_pubkeys,
script_code,
sighash_byte,
) {
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
#[cfg(feature = "production")]
insert_midstate_cache(
sighash_cache,
tx.inputs[0].prevout,
script_code.unwrap_or_else(|| prevout_script_pubkeys[0]),
sighash_byte as u8,
h,
);
return Ok(h);
}
} else if (2..=16).contains(&n_out) {
if let Ok(h) = build_preimage_1in_nout_sighash_all(
tx,
prevout_script_pubkeys,
script_code,
sighash_byte,
) {
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
#[cfg(feature = "production")]
insert_midstate_cache(
sighash_cache,
tx.inputs[0].prevout,
script_code.unwrap_or_else(|| prevout_script_pubkeys[0]),
sighash_byte as u8,
h,
);
return Ok(h);
}
}
}
}
#[cfg(feature = "production")]
if tx.inputs.len() == 2 && input_index < 2 && !anyone_can_pay && !hash_none && !hash_single {
let base_type = sighash_byte & 0x1f;
if base_type == 0x01 || base_type == 0x00 {
let n_out = tx.outputs.len();
if n_out == 1 {
if let Ok(h) = build_preimage_2in1out_sighash_all(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
script_code,
sighash_byte,
) {
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
#[cfg(feature = "production")]
insert_midstate_cache(
sighash_cache,
tx.inputs[input_index].prevout,
script_code.unwrap_or_else(|| prevout_script_pubkeys[input_index]),
sighash_byte as u8,
h,
);
return Ok(h);
}
} else if n_out == 2 {
if let Ok(h) = build_preimage_2in2out_sighash_all(
tx,
input_index,
prevout_script_pubkeys,
script_code,
sighash_byte,
) {
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
#[cfg(feature = "production")]
insert_midstate_cache(
sighash_cache,
tx.inputs[input_index].prevout,
script_code.unwrap_or_else(|| prevout_script_pubkeys[input_index]),
sighash_byte as u8,
h,
);
return Ok(h);
}
}
}
}
let estimated_size = 4 + 2 + (tx.inputs.len() * 50) + 2 + (tx.outputs.len() * 30) + 4 + 4;
let capacity = estimated_size.min(4096);
#[cfg(feature = "production")]
let (result, preimage_vec) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
let mut preimage = buf_cell.borrow_mut();
preimage.clear();
if preimage.capacity() < capacity {
preimage.reserve(capacity);
}
build_preimage_and_hash(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
script_code,
sighash_byte,
anyone_can_pay,
hash_none,
hash_single,
&mut preimage,
)
});
#[cfg(not(feature = "production"))]
let (result, preimage_vec) = {
let mut preimage = Vec::with_capacity(capacity);
build_preimage_and_hash(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
script_code,
sighash_byte,
anyone_can_pay,
hash_none,
hash_single,
&mut preimage,
)
};
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
#[cfg(feature = "production")]
if let Ok(ref h) = result {
insert_midstate_cache(
sighash_cache,
tx.inputs[input_index].prevout,
script_code.unwrap_or_else(|| prevout_script_pubkeys[input_index]),
sighash_byte as u8,
*h,
);
}
result
}
#[cfg(feature = "production")]
#[inline]
fn build_preimage_1in1out_sighash_all(
tx: &crate::types::Transaction,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
script_code: Option<&[u8]>,
sighash_byte: u32,
) -> Result<Hash> {
let input = &tx.inputs[0];
let output = &tx.outputs[0];
let code = script_code.unwrap_or_else(|| prevout_script_pubkeys[0]);
let capacity = 4 + 2 + 36 + 2 + code.len() + 4 + 2 + 8 + 2 + output.script_pubkey.len() + 4 + 4;
let (result, _) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
let mut preimage = buf_cell.borrow_mut();
preimage.clear();
if preimage.capacity() < capacity {
preimage.reserve(capacity);
}
preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
preimage.push(1); preimage.extend_from_slice(&input.prevout.hash);
preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
write_varint_to_vec(&mut preimage, code.len() as u64);
preimage.extend_from_slice(code);
preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
preimage.push(1); preimage.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut preimage, output.script_pubkey.len() as u64);
preimage.extend_from_slice(&output.script_pubkey);
preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
preimage.extend_from_slice(&sighash_byte.to_le_bytes());
let r = sighash_with_cache(&preimage);
(Ok(r), ())
});
result
}
#[cfg(feature = "production")]
#[inline]
fn build_preimage_1in_nout_sighash_all(
tx: &crate::types::Transaction,
prevout_script_pubkeys: &[&[u8]],
script_code: Option<&[u8]>,
sighash_byte: u32,
) -> Result<Hash> {
let input = &tx.inputs[0];
let code = script_code.unwrap_or_else(|| prevout_script_pubkeys[0]);
let mut capacity = 4 + 2 + 36 + 2 + code.len() + 4 + 2; for out in &tx.outputs {
capacity += 8 + 2 + out.script_pubkey.len();
}
capacity += 4 + 4;
let (result, _) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
let mut preimage = buf_cell.borrow_mut();
preimage.clear();
if preimage.capacity() < capacity {
preimage.reserve(capacity);
}
preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
preimage.push(1); preimage.extend_from_slice(&input.prevout.hash);
preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
write_varint_to_vec(&mut preimage, code.len() as u64);
preimage.extend_from_slice(code);
preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
write_varint_to_vec(&mut preimage, tx.outputs.len() as u64);
for output in &tx.outputs {
preimage.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut preimage, output.script_pubkey.len() as u64);
preimage.extend_from_slice(&output.script_pubkey);
}
preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
preimage.extend_from_slice(&sighash_byte.to_le_bytes());
let r = sighash_with_cache(&preimage);
(Ok(r), ())
});
result
}
#[cfg(feature = "production")]
#[inline]
fn build_preimage_2in1out_sighash_all(
tx: &crate::types::Transaction,
input_index: usize,
_prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
script_code: Option<&[u8]>,
sighash_byte: u32,
) -> Result<Hash> {
let output = &tx.outputs[0];
let code_len = script_code
.map(|s| s.len())
.unwrap_or_else(|| prevout_script_pubkeys[input_index].len());
let capacity =
4 + 2 + 36 + 2 + code_len + 4 + 36 + 2 + 4 + 8 + 2 + output.script_pubkey.len() + 4 + 4;
let (result, _) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
let mut preimage = buf_cell.borrow_mut();
preimage.clear();
if preimage.capacity() < capacity {
preimage.reserve(capacity);
}
preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
preimage.push(2);
for (i, inp) in tx.inputs.iter().enumerate().take(2) {
let (script_len, script_slice): (usize, &[u8]) = if i == input_index {
let c = script_code.unwrap_or_else(|| prevout_script_pubkeys[i]);
(c.len(), c)
} else {
(0, &[][..]) };
preimage.extend_from_slice(&inp.prevout.hash);
preimage.extend_from_slice(&inp.prevout.index.to_le_bytes());
write_varint_to_vec(&mut preimage, script_len as u64);
preimage.extend_from_slice(script_slice);
preimage.extend_from_slice(&(inp.sequence as u32).to_le_bytes());
}
preimage.push(1);
preimage.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut preimage, output.script_pubkey.len() as u64);
preimage.extend_from_slice(&output.script_pubkey);
preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
preimage.extend_from_slice(&sighash_byte.to_le_bytes());
let r = sighash_with_cache(&preimage);
(Ok(r), ())
});
result
}
#[cfg(feature = "production")]
#[inline]
fn build_preimage_2in2out_sighash_all(
tx: &crate::types::Transaction,
input_index: usize,
prevout_script_pubkeys: &[&[u8]],
script_code: Option<&[u8]>,
sighash_byte: u32,
) -> Result<Hash> {
let code_len = script_code
.map(|s| s.len())
.unwrap_or_else(|| prevout_script_pubkeys[input_index].len());
let mut capacity = 4 + 2 + 36 + 2 + code_len + 4 + 36 + 2 + 4;
for out in &tx.outputs {
capacity += 8 + 2 + out.script_pubkey.len();
}
capacity += 4 + 4;
let (result, _) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
let mut preimage = buf_cell.borrow_mut();
preimage.clear();
if preimage.capacity() < capacity {
preimage.reserve(capacity);
}
preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
preimage.push(2);
for (i, inp) in tx.inputs.iter().enumerate().take(2) {
let (script_len, script_slice): (usize, &[u8]) = if i == input_index {
let c = script_code.unwrap_or_else(|| prevout_script_pubkeys[i]);
(c.len(), c)
} else {
(0, &[][..])
};
preimage.extend_from_slice(&inp.prevout.hash);
preimage.extend_from_slice(&inp.prevout.index.to_le_bytes());
write_varint_to_vec(&mut preimage, script_len as u64);
preimage.extend_from_slice(script_slice);
preimage.extend_from_slice(&(inp.sequence as u32).to_le_bytes());
}
write_varint_to_vec(&mut preimage, 2);
for output in &tx.outputs {
preimage.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut preimage, output.script_pubkey.len() as u64);
preimage.extend_from_slice(&output.script_pubkey);
}
preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
preimage.extend_from_slice(&sighash_byte.to_le_bytes());
let r = sighash_with_cache(&preimage);
(Ok(r), ())
});
result
}
#[inline]
fn build_preimage_and_hash(
tx: &crate::types::Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
script_code: Option<&[u8]>,
sighash_byte: u32,
anyone_can_pay: bool,
hash_none: bool,
hash_single: bool,
preimage: &mut Vec<u8>,
) -> (Result<Hash>, ()) {
preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
write_varint_to_vec(preimage, n_inputs as u64);
for i in 0..n_inputs {
let actual_i = if anyone_can_pay { input_index } else { i };
let input = &tx.inputs[actual_i];
preimage.extend_from_slice(&input.prevout.hash);
preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
if actual_i == input_index {
let code = match script_code {
Some(s) => s,
None => prevout_script_pubkeys[actual_i],
};
write_varint_to_vec(preimage, code.len() as u64);
preimage.extend_from_slice(code);
} else {
preimage.push(0); }
if actual_i != input_index && (hash_single || hash_none) {
preimage.extend_from_slice(&0u32.to_le_bytes());
} else {
preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
}
}
let n_outputs = if hash_none {
0
} else if hash_single {
input_index + 1
} else {
tx.outputs.len()
};
write_varint_to_vec(preimage, n_outputs as u64);
for i in 0..n_outputs {
if hash_single && i != input_index {
preimage.extend_from_slice(&(-1i64).to_le_bytes()); preimage.push(0); } else {
let output = &tx.outputs[i];
preimage.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(preimage, output.script_pubkey.len() as u64);
preimage.extend_from_slice(&output.script_pubkey);
}
}
preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
preimage.extend_from_slice(&sighash_byte.to_le_bytes());
#[cfg(feature = "production")]
let result = sighash_with_cache(preimage);
#[cfg(not(feature = "production"))]
let result = {
let hasher = OptimizedSha256::new();
let first_hash = hasher.hash(&preimage);
let second_hash = hasher.hash(&first_hash);
let mut r = [0u8; 32];
r.copy_from_slice(&second_hash);
r
};
(Ok(result), ())
}
#[spec_locked("5.1.1")]
pub fn batch_compute_sighashes(
tx: &Transaction,
prevouts: &[TransactionOutput],
sighash_type: SighashType,
) -> Result<Vec<Hash>> {
if prevouts.len() != tx.inputs.len() {
return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
prevouts.len(),
tx.inputs.len(),
));
}
let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
let prevout_script_pubkeys: Vec<&[u8]> =
prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
#[cfg(feature = "production")]
{
let sighash_byte = sighash_type.as_u32() as u8;
let specs: Vec<(usize, u8, &[u8])> = (0..tx.inputs.len())
.map(|i| (i, sighash_byte, prevout_script_pubkeys[i]))
.collect();
let hashes =
batch_compute_legacy_sighashes(tx, &prevout_values, &prevout_script_pubkeys, &specs)?;
Ok(hashes)
}
#[cfg(not(feature = "production"))]
{
let mut results = Vec::with_capacity(tx.inputs.len());
for i in 0..tx.inputs.len() {
results.push(calculate_transaction_sighash_with_script_code(
tx,
i,
&prevout_values,
&prevout_script_pubkeys,
sighash_type,
None,
)?);
}
Ok(results)
}
}
#[cfg(feature = "production")]
fn build_legacy_sighash_preimage_into(
preimage: &mut Vec<u8>,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
script_code: &[u8],
sighash_byte: u32,
) {
let anyone_can_pay = (sighash_byte & 0x80) != 0;
let hash_none = (sighash_byte & 0x1f) == 0x02;
let hash_single = (sighash_byte & 0x1f) == 0x03;
let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
let n_outputs = if hash_none {
0
} else if hash_single {
input_index + 1
} else {
tx.outputs.len()
};
preimage.clear();
preimage.reserve(512);
preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
write_varint_to_vec(preimage, n_inputs as u64);
for i in 0..n_inputs {
let actual_i = if anyone_can_pay { input_index } else { i };
let input = &tx.inputs[actual_i];
preimage.extend_from_slice(&input.prevout.hash);
preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
if actual_i == input_index {
write_varint_to_vec(preimage, script_code.len() as u64);
preimage.extend_from_slice(script_code);
} else {
preimage.push(0);
}
if actual_i != input_index && (hash_single || hash_none) {
preimage.extend_from_slice(&0u32.to_le_bytes());
} else {
preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
}
}
write_varint_to_vec(preimage, n_outputs as u64);
for i in 0..n_outputs {
let use_missing_output =
hash_single && (i != input_index || input_index >= tx.outputs.len());
if use_missing_output {
preimage.extend_from_slice(&(-1i64).to_le_bytes());
preimage.push(0);
} else {
let output = &tx.outputs[i];
preimage.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(preimage, output.script_pubkey.len() as u64);
preimage.extend_from_slice(&output.script_pubkey);
}
}
preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
preimage.extend_from_slice(&sighash_byte.to_le_bytes());
}
#[cfg(feature = "production")]
#[spec_locked("5.1.1")]
pub fn batch_compute_legacy_sighashes(
tx: &Transaction,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
specs: &[(usize, u8, &[u8])],
) -> Result<Vec<[u8; 32]>> {
if prevout_values.len() != tx.inputs.len() || prevout_script_pubkeys.len() != tx.inputs.len() {
return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
prevout_values.len(),
tx.inputs.len(),
));
}
const SIGHASH_SINGLE_INVALID: [u8; 32] = {
let mut h = [0u8; 32];
h[0] = 1;
h
};
LEGACY_BATCH_PREIMAGES.with(|cell| {
let mut storage = cell.borrow_mut();
storage.resize_with(specs.len(), || Vec::with_capacity(512));
let mut fixed_indices: Vec<usize> = Vec::new();
for (i, &(input_index, sighash_byte, script_code)) in specs.iter().enumerate() {
let hash_single = (sighash_byte & 0x1f) == 0x03;
if hash_single && input_index >= tx.outputs.len() {
fixed_indices.push(i);
continue;
}
build_legacy_sighash_preimage_into(
&mut storage[i],
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
script_code,
sighash_byte as u32,
);
}
let preimage_refs: Vec<&[u8]> = storage
.iter()
.enumerate()
.filter(|(i, _)| !fixed_indices.contains(i))
.map(|(_, v)| v.as_slice())
.collect();
let batch_hashes =
crate::optimizations::simd_vectorization::batch_double_sha256(&preimage_refs);
let mut result = vec![[0u8; 32]; specs.len()];
let mut batch_idx = 0;
for (i, slot) in result.iter_mut().enumerate() {
if fixed_indices.contains(&i) {
*slot = SIGHASH_SINGLE_INVALID;
} else {
*slot = batch_hashes[batch_idx];
batch_idx += 1;
}
}
Ok(result)
})
}
#[cfg(all(feature = "production", feature = "benchmarking"))]
pub fn clear_sighash_templates() {
SIGHASH_CACHE.with(|cell| {
cell.borrow_mut().clear();
});
}
fn encode_varint(value: u64) -> Vec<u8> {
if value < 0xfd {
vec![value as u8]
} else if value <= 0xffff {
let mut result = vec![0xfd];
result.extend_from_slice(&(value as u16).to_le_bytes());
result
} else if value <= 0xffffffff {
let mut result = vec![0xfe];
result.extend_from_slice(&(value as u32).to_le_bytes());
result
} else {
let mut result = vec![0xff];
result.extend_from_slice(&value.to_le_bytes());
result
}
}
#[derive(Clone, Debug)]
pub struct Bip143PrecomputedHashes {
pub hash_prevouts: [u8; 32],
pub hash_sequence: [u8; 32],
pub hash_outputs: [u8; 32],
}
impl Bip143PrecomputedHashes {
#[inline]
pub fn compute(
tx: &Transaction,
_prevout_values: &[i64],
_prevout_script_pubkeys: &[&[u8]],
) -> Self {
#[cfg(feature = "production")]
{
let hash_prevouts = BIP143_SERIALIZE_BUF.with(|cell| {
let mut data = cell.borrow_mut();
data.clear();
data.reserve(tx.inputs.len() * 36);
for input in tx.inputs.iter() {
data.extend_from_slice(&input.prevout.hash);
data.extend_from_slice(&input.prevout.index.to_le_bytes());
}
double_sha256(&data)
});
let hash_sequence = BIP143_SERIALIZE_BUF.with(|cell| {
let mut data = cell.borrow_mut();
data.clear();
data.reserve(tx.inputs.len() * 4);
for input in tx.inputs.iter() {
data.extend_from_slice(&(input.sequence as u32).to_le_bytes());
}
double_sha256(&data)
});
let hash_outputs = BIP143_SERIALIZE_BUF.with(|cell| {
let mut data = cell.borrow_mut();
data.clear();
let cap = tx
.outputs
.iter()
.map(|o| 8 + 5 + o.script_pubkey.len())
.sum::<usize>();
data.reserve(cap);
for output in tx.outputs.iter() {
data.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut data, output.script_pubkey.len() as u64);
data.extend_from_slice(&output.script_pubkey);
}
double_sha256(&data)
});
Self {
hash_prevouts,
hash_sequence,
hash_outputs,
}
}
#[cfg(not(feature = "production"))]
{
let hash_prevouts = {
let mut data = Vec::with_capacity(tx.inputs.len() * 36);
for input in tx.inputs.iter() {
data.extend_from_slice(&input.prevout.hash);
data.extend_from_slice(&input.prevout.index.to_le_bytes());
}
double_sha256(&data)
};
let hash_sequence = {
let mut data = Vec::with_capacity(tx.inputs.len() * 4);
for input in tx.inputs.iter() {
data.extend_from_slice(&(input.sequence as u32).to_le_bytes());
}
double_sha256(&data)
};
let hash_outputs = {
let mut data = Vec::with_capacity(tx.outputs.len() * 34);
for output in tx.outputs.iter() {
data.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut data, output.script_pubkey.len() as u64);
data.extend_from_slice(&output.script_pubkey);
}
double_sha256(&data)
};
Self {
hash_prevouts,
hash_sequence,
hash_outputs,
}
}
}
}
#[inline(always)]
fn double_sha256(data: &[u8]) -> [u8; 32] {
let hasher = OptimizedSha256::new();
hasher.hash256(data)
}
#[spec_locked("11.1.9")]
pub fn calculate_bip143_sighash(
tx: &Transaction,
input_index: usize,
script_code: &[u8],
amount: i64,
sighash_type: u8,
precomputed: Option<&Bip143PrecomputedHashes>,
) -> Result<Hash> {
if input_index >= tx.inputs.len() {
return Err(crate::error::ConsensusError::InvalidInputIndex(input_index));
}
let anyone_can_pay = (sighash_type & 0x80) != 0;
let base_type = sighash_type & 0x1f;
let is_none = base_type == 0x02;
let is_single = base_type == 0x03;
let computed;
let hashes = match precomputed {
Some(h) => h,
None => {
computed = Bip143PrecomputedHashes::compute(tx, &[], &[]);
&computed
}
};
#[cfg(feature = "production")]
let preimage_result = BIP143_PREIMAGE_BUF.with(|buf_cell| {
let mut preimage = buf_cell.borrow_mut();
preimage.clear();
let cap = 160 + script_code.len();
if preimage.capacity() < cap {
preimage.reserve(cap);
}
build_bip143_preimage(
tx,
input_index,
script_code,
amount,
sighash_type,
anyone_can_pay,
is_none,
is_single,
hashes,
&mut preimage,
)
});
#[cfg(not(feature = "production"))]
let preimage_result = {
let mut preimage = Vec::with_capacity(160 + script_code.len());
build_bip143_preimage(
tx,
input_index,
script_code,
amount,
sighash_type,
anyone_can_pay,
is_none,
is_single,
hashes,
&mut preimage,
)
};
preimage_result
}
#[inline]
fn build_bip143_preimage(
tx: &Transaction,
input_index: usize,
script_code: &[u8],
amount: i64,
sighash_type: u8,
anyone_can_pay: bool,
is_none: bool,
is_single: bool,
hashes: &Bip143PrecomputedHashes,
preimage: &mut Vec<u8>,
) -> Result<Hash> {
preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
if anyone_can_pay {
preimage.extend_from_slice(&[0u8; 32]);
} else {
preimage.extend_from_slice(&hashes.hash_prevouts);
}
if anyone_can_pay || is_none || is_single {
preimage.extend_from_slice(&[0u8; 32]);
} else {
preimage.extend_from_slice(&hashes.hash_sequence);
}
let input = &tx.inputs[input_index];
preimage.extend_from_slice(&input.prevout.hash);
preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
write_varint_to_vec(preimage, script_code.len() as u64);
preimage.extend_from_slice(script_code);
preimage.extend_from_slice(&amount.to_le_bytes());
preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
if is_none {
preimage.extend_from_slice(&[0u8; 32]);
} else if is_single {
if input_index < tx.outputs.len() {
let output = &tx.outputs[input_index];
#[cfg(feature = "production")]
let hash_outputs = BIP143_SINGLE_OUTPUT_BUF.with(|buf_cell| {
let mut output_data = buf_cell.borrow_mut();
output_data.clear();
let cap = 8 + 9 + output.script_pubkey.len(); if output_data.capacity() < cap {
output_data.reserve(cap);
}
output_data.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut output_data, output.script_pubkey.len() as u64);
output_data.extend_from_slice(&output.script_pubkey);
double_sha256(&output_data)
});
#[cfg(not(feature = "production"))]
let hash_outputs = {
let mut output_data = Vec::with_capacity(8 + 9 + output.script_pubkey.len());
output_data.extend_from_slice(&output.value.to_le_bytes());
write_varint_to_vec(&mut output_data, output.script_pubkey.len() as u64);
output_data.extend_from_slice(&output.script_pubkey);
double_sha256(&output_data)
};
preimage.extend_from_slice(&hash_outputs);
} else {
preimage.extend_from_slice(&[0u8; 32]);
}
} else {
preimage.extend_from_slice(&hashes.hash_outputs);
}
preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
preimage.extend_from_slice(&(sighash_type as u32).to_le_bytes());
Ok(double_sha256(preimage))
}
#[spec_locked("11.1.9")]
pub fn batch_compute_bip143_sighashes(
tx: &Transaction,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
script_codes: &[&[u8]],
sighash_type: u8,
) -> Result<Vec<Hash>> {
if prevout_values.len() != tx.inputs.len()
|| prevout_script_pubkeys.len() != tx.inputs.len()
|| script_codes.len() != tx.inputs.len()
{
return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
prevout_values.len(),
tx.inputs.len(),
));
}
let precomputed = Bip143PrecomputedHashes::compute(tx, prevout_values, prevout_script_pubkeys);
let mut results = Vec::with_capacity(tx.inputs.len());
for (i, (value, script_code)) in prevout_values.iter().zip(script_codes.iter()).enumerate() {
let sighash =
calculate_bip143_sighash(tx, i, script_code, *value, sighash_type, Some(&precomputed))?;
results.push(sighash);
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::opcodes::*;
#[test]
fn test_sighash_type_parsing() {
assert_eq!(SighashType::from_byte(0x01), SighashType::ALL);
assert_eq!(SighashType::from_byte(0x02), SighashType::NONE);
assert_eq!(SighashType::from_byte(0x03), SighashType::SINGLE);
assert_eq!(SighashType::from_byte(0x00), SighashType::ALL_LEGACY);
assert_eq!(SighashType::from_byte(0x81), SighashType::ALL_ANYONECANPAY);
assert_eq!(SighashType::from_byte(0x82), SighashType::NONE_ANYONECANPAY);
assert_eq!(
SighashType::from_byte(0x83),
SighashType::SINGLE_ANYONECANPAY
);
assert_eq!(SighashType::ALL_ANYONECANPAY.as_u32(), 0x81);
assert_eq!(SighashType::NONE_ANYONECANPAY.as_u32(), 0x82);
assert_eq!(SighashType::SINGLE_ANYONECANPAY.as_u32(), 0x83);
let st = SighashType::from_byte(0x04);
assert!(st.is_all()); assert_eq!(st.as_u32(), 0x04); let st84 = SighashType::from_byte(0x84);
assert!(st84.is_all());
assert!(st84.is_anyonecanpay());
assert_eq!(st84.as_u32(), 0x84);
}
#[test]
fn test_varint_encoding() {
assert_eq!(encode_varint(0), vec![0]);
assert_eq!(encode_varint(252), vec![252]);
assert_eq!(encode_varint(253), vec![0xfd, 253, 0]);
assert_eq!(encode_varint(65535), vec![0xfd, 255, 255]);
assert_eq!(encode_varint(65536), vec![0xfe, 0, 0, 1, 0]);
}
#[test]
fn test_sighash_calculation() {
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1u8; 32].into(),
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![
OP_DUP,
OP_HASH160,
PUSH_20_BYTES,
0x89,
0xab,
0xcd,
0xef,
0x12,
0x34,
0x56,
0x78,
0x9a,
0xbc,
0xde,
0xf0,
0x12,
0x34,
0x56,
0x78,
0x9a,
OP_EQUALVERIFY,
OP_CHECKSIG,
]
.into(), }]
.into(),
lock_time: 0,
};
let prevouts = vec![TransactionOutput {
value: 10000000000,
script_pubkey: vec![
OP_DUP,
OP_HASH160,
PUSH_20_BYTES,
0x89,
0xab,
0xcd,
0xef,
0x12,
0x34,
0x56,
0x78,
0x9a,
0xbc,
0xde,
0xf0,
0x12,
0x34,
0x56,
0x78,
0x9a,
OP_EQUALVERIFY,
OP_CHECKSIG,
],
}];
let sighash = calculate_transaction_sighash(&tx, 0, &prevouts, SighashType::ALL).unwrap();
assert_eq!(sighash.len(), 32);
let sighash_none =
calculate_transaction_sighash(&tx, 0, &prevouts, SighashType::NONE).unwrap();
assert_ne!(sighash, sighash_none);
let sighash_single =
calculate_transaction_sighash(&tx, 0, &prevouts, SighashType::SINGLE).unwrap();
assert_ne!(sighash, sighash_single);
}
#[test]
fn test_sighash_invalid_input_index() {
let tx = Transaction {
version: 1,
inputs: vec![].into(),
outputs: vec![].into(),
lock_time: 0,
};
let result = calculate_transaction_sighash(&tx, 0, &[], SighashType::ALL);
assert!(result.is_err());
}
#[test]
fn test_bip143_sighash() {
let tx = Transaction {
version: 1,
inputs: vec![
TransactionInput {
prevout: OutPoint {
hash: [1u8; 32].into(),
index: 0,
},
script_sig: vec![], sequence: 0xffffffff,
},
TransactionInput {
prevout: OutPoint {
hash: [2u8; 32].into(),
index: 1,
},
script_sig: vec![],
sequence: 0xfffffffe,
},
]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![
OP_0,
PUSH_20_BYTES,
0x89,
0xab,
0xcd,
0xef,
0x12,
0x34,
0x56,
0x78,
0x9a,
0xbc,
0xde,
0xf0,
0x12,
0x34,
0x56,
0x78,
0x9a,
0xbc,
0xde,
0xf0,
]
.into(),
}]
.into(),
lock_time: 0,
};
let prevouts = vec![
TransactionOutput {
value: 10000000000,
script_pubkey: vec![
OP_0,
PUSH_20_BYTES,
0x11,
0x22,
0x33,
0x44,
0x55,
0x66,
0x77,
0x88,
0x99,
0xaa,
0xbb,
0xcc,
0xdd,
0xee,
0xff,
0x00,
0x11,
0x22,
0x33,
0x44,
],
},
TransactionOutput {
value: 8000000000,
script_pubkey: vec![
OP_0,
PUSH_20_BYTES,
0xaa,
0xbb,
0xcc,
0xdd,
0xee,
0xff,
0x00,
0x11,
0x22,
0x33,
0x44,
0x55,
0x66,
0x77,
0x88,
0x99,
0xaa,
0xbb,
0xcc,
0xdd,
],
},
];
let script_code = vec![
OP_DUP,
OP_HASH160,
PUSH_20_BYTES,
0x11,
0x22,
0x33,
0x44,
0x55,
0x66,
0x77,
0x88,
0x99,
0xaa,
0xbb,
0xcc,
0xdd,
0xee,
0xff,
0x00,
0x11,
0x22,
0x33,
OP_EQUALVERIFY,
OP_CHECKSIG,
];
let sighash0 =
calculate_bip143_sighash(&tx, 0, &script_code, prevouts[0].value, 0x01, None).unwrap();
assert_eq!(sighash0.len(), 32);
let sighash1 =
calculate_bip143_sighash(&tx, 1, &script_code, prevouts[1].value, 0x01, None).unwrap();
assert_ne!(sighash0, sighash1);
let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
let prevout_script_pubkeys: Vec<&[u8]> =
prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
let precomputed =
Bip143PrecomputedHashes::compute(&tx, &prevout_values, &prevout_script_pubkeys);
let sighash0_precomputed = calculate_bip143_sighash(
&tx,
0,
&script_code,
prevout_values[0],
0x01,
Some(&precomputed),
)
.unwrap();
assert_eq!(sighash0, sighash0_precomputed);
}
#[test]
fn test_bip143_anyonecanpay() {
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1u8; 32].into(),
index: 0,
},
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![OP_0, PUSH_20_BYTES].into(),
}]
.into(),
lock_time: 0,
};
let script_code = {
let mut s = vec![OP_DUP, OP_HASH160, PUSH_20_BYTES];
s.extend_from_slice(&[0u8; 20]); s.push(OP_EQUALVERIFY);
s.push(OP_CHECKSIG);
s };
let amount = 10000000000i64;
let sighash_all =
calculate_bip143_sighash(&tx, 0, &script_code, amount, 0x01, None).unwrap();
let sighash_anyonecanpay =
calculate_bip143_sighash(&tx, 0, &script_code, amount, 0x81, None).unwrap();
assert_ne!(sighash_all, sighash_anyonecanpay);
}
#[test]
fn test_2input_legacy_sighash_non_signing_empty_script() {
let script_a = vec![
PUSH_33_BYTES,
0x02,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
OP_CHECKSIG,
]; let script_b = vec![
PUSH_33_BYTES,
0x03,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
OP_CHECKSIG,
]; let tx = Transaction {
version: 1,
inputs: vec![
TransactionInput {
prevout: OutPoint {
hash: [1u8; 32].into(),
index: 0,
},
script_sig: vec![].into(),
sequence: 0xffffffff,
},
TransactionInput {
prevout: OutPoint {
hash: [2u8; 32].into(),
index: 1,
},
script_sig: vec![].into(),
sequence: 0xffffffff,
},
]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![OP_DUP, OP_HASH160, PUSH_20_BYTES].into(),
}]
.into(),
lock_time: 0,
};
let pv: Vec<i64> = vec![10_000_000_000, 8_000_000_000];
let psp_ab: Vec<&[u8]> = vec![script_a.as_slice(), script_b.as_slice()];
let psp_aa: Vec<&[u8]> = vec![script_a.as_slice(), script_a.as_slice()];
let sighash_ab = calculate_transaction_sighash_with_script_code(
&tx,
0,
&pv,
&psp_ab,
SighashType::ALL,
None,
#[cfg(feature = "production")]
None,
)
.unwrap();
let sighash_aa = calculate_transaction_sighash_with_script_code(
&tx,
0,
&pv,
&psp_aa,
SighashType::ALL,
None,
#[cfg(feature = "production")]
None,
)
.unwrap();
assert_eq!(sighash_ab, sighash_aa,
"2-input legacy: signing input 0 — input 1 script must be empty; changing input 1 scriptPubKey must not change sighash");
}
#[cfg(feature = "production")]
#[test]
fn test_batch_sighash_single_input_index_ge_outputs() {
let tx = Transaction {
version: 1,
inputs: vec![
TransactionInput {
prevout: OutPoint {
hash: [1u8; 32].into(),
index: 0,
},
script_sig: vec![].into(),
sequence: 0xffffffff,
},
TransactionInput {
prevout: OutPoint {
hash: [2u8; 32].into(),
index: 1,
},
script_sig: vec![].into(),
sequence: 0xffffffff,
},
]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![OP_DUP, OP_HASH160, PUSH_20_BYTES].into(),
}]
.into(),
lock_time: 0,
};
let prevout_values = vec![10_000_000_000i64, 8_000_000_000i64];
let script = vec![OP_DUP, OP_HASH160, PUSH_20_BYTES];
let prevout_script_pubkeys: Vec<&[u8]> = vec![script.as_slice(), script.as_slice()];
let specs = vec![(1usize, 0x03u8, script.as_slice() as &[u8])]; let hashes = super::batch_compute_legacy_sighashes(
&tx,
&prevout_values,
&prevout_script_pubkeys,
&specs,
)
.unwrap();
assert_eq!(hashes.len(), 1);
let mut expected = [0u8; 32];
expected[0] = 1;
assert_eq!(
hashes[0], expected,
"SIGHASH_SINGLE with input_index>=outputs.len() must return 0x0000...0001"
);
}
}