use crate::crypto::OptimizedSha256;
use crate::error::{ConsensusError, Result, ScriptErrorCode};
use crate::opcodes::{OP_HASH160, OP_HASH256};
use crate::types::Network;
use digest::Digest;
use ripemd::Ripemd160;
#[cfg(not(feature = "production"))]
use secp256k1::Secp256k1;
use sha1::Sha1;
use super::signature;
use super::stack::{to_stack_element, StackElement};
use super::SigVersion;
const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
#[cfg(feature = "production")]
fn is_hash_caching_disabled() -> bool {
super::is_caching_disabled()
}
#[cfg(feature = "production")]
pub(crate) fn clear_hash_cache() {
HASH_CACHE.with(|cell| cell.borrow_mut().clear());
}
#[cfg(feature = "production")]
thread_local! {
static HASH_CACHE: std::cell::RefCell<lru::LruCache<[u8; 32], Vec<u8>>> = std::cell::RefCell::new({
use lru::LruCache;
use std::num::NonZeroUsize;
LruCache::new(NonZeroUsize::new(25_000).unwrap())
});
}
#[cfg(feature = "production")]
fn with_hash_cache<F, R>(f: F) -> R
where
F: FnOnce(&mut lru::LruCache<[u8; 32], Vec<u8>>) -> R,
{
HASH_CACHE.with(|cell| f(&mut cell.borrow_mut()))
}
#[cfg(feature = "production")]
fn compute_hash_cache_key(input: &[u8], op_hash160: bool) -> [u8; 32] {
let mut data = input.to_vec();
data.push(if op_hash160 { OP_HASH160 } else { OP_HASH256 });
let hash = OptimizedSha256::new().hash(&data);
let mut key = [0u8; 32];
key.copy_from_slice(&hash);
key
}
pub(crate) fn op_ripemd160(stack: &mut Vec<StackElement>) -> Result<bool> {
if let Some(item) = stack.pop() {
let hash = Ripemd160::digest(&item);
#[cfg(feature = "production")]
{
let mut hash_vec = Vec::with_capacity(20);
hash_vec.extend_from_slice(&hash);
stack.push(to_stack_element(&hash_vec));
}
#[cfg(not(feature = "production"))]
{
stack.push(to_stack_element(&hash));
}
Ok(true)
} else {
Ok(false)
}
}
pub(crate) fn op_sha1(stack: &mut Vec<StackElement>) -> Result<bool> {
if let Some(item) = stack.pop() {
let hash = Sha1::digest(&item);
#[cfg(feature = "production")]
{
let mut hash_vec = Vec::with_capacity(20);
hash_vec.extend_from_slice(&hash);
stack.push(to_stack_element(&hash_vec));
}
#[cfg(not(feature = "production"))]
{
stack.push(to_stack_element(&hash));
}
Ok(true)
} else {
Ok(false)
}
}
pub(crate) fn op_sha256(stack: &mut Vec<StackElement>) -> Result<bool> {
if let Some(item) = stack.pop() {
let hash = OptimizedSha256::new().hash(&item);
#[cfg(feature = "production")]
{
let mut hash_vec = Vec::with_capacity(32);
hash_vec.extend_from_slice(&hash);
stack.push(to_stack_element(&hash_vec));
}
#[cfg(not(feature = "production"))]
{
stack.push(to_stack_element(&hash));
}
Ok(true)
} else {
Ok(false)
}
}
pub(crate) fn op_hash160(stack: &mut Vec<StackElement>) -> Result<bool> {
if let Some(item) = stack.pop() {
#[cfg(feature = "production")]
{
if item.len() == 33 || item.len() == 65 {
let sha256_hash = OptimizedSha256::new().hash(&item);
let ripemd160_hash = Ripemd160::digest(sha256_hash);
stack.push(to_stack_element(&ripemd160_hash));
} else if !is_hash_caching_disabled() {
let cache_key = compute_hash_cache_key(&item, true);
if let Some(cached_result) = with_hash_cache(|c| c.peek(&cache_key).cloned()) {
if cached_result.len() == 20 {
stack.push(to_stack_element(&cached_result));
return Ok(true);
}
}
let sha256_hash = OptimizedSha256::new().hash(&item);
let ripemd160_hash = Ripemd160::digest(sha256_hash);
let mut hash_vec = Vec::with_capacity(20);
hash_vec.extend_from_slice(&ripemd160_hash);
with_hash_cache(|c| c.put(cache_key, hash_vec.clone()));
stack.push(to_stack_element(&hash_vec));
} else {
let sha256_hash = OptimizedSha256::new().hash(&item);
let ripemd160_hash = Ripemd160::digest(sha256_hash);
stack.push(to_stack_element(&ripemd160_hash));
}
}
#[cfg(not(feature = "production"))]
{
let sha256_hash = OptimizedSha256::new().hash(&item);
let ripemd160_hash = Ripemd160::digest(sha256_hash);
stack.push(to_stack_element(&ripemd160_hash));
}
Ok(true)
} else {
Ok(false)
}
}
pub(crate) fn op_hash256(stack: &mut Vec<StackElement>) -> Result<bool> {
if let Some(item) = stack.pop() {
#[cfg(feature = "production")]
{
if !is_hash_caching_disabled() {
let cache_key = compute_hash_cache_key(&item, false);
if let Some(cached_result) = with_hash_cache(|c| c.peek(&cache_key).cloned()) {
if cached_result.len() == 32 {
stack.push(to_stack_element(&cached_result));
return Ok(true);
}
}
}
let hasher = OptimizedSha256::new();
let result = hasher.hash256(&item).to_vec();
if !is_hash_caching_disabled() {
let cache_key = compute_hash_cache_key(&item, false);
with_hash_cache(|c| c.put(cache_key, result.clone()));
}
stack.push(to_stack_element(&result));
}
#[cfg(not(feature = "production"))]
{
let result = OptimizedSha256::new().hash256(&item);
stack.push(to_stack_element(&result));
}
Ok(true)
} else {
Ok(false)
}
}
pub(crate) fn op_checksig_simple(stack: &mut Vec<StackElement>, flags: u32) -> Result<bool> {
if stack.len() < 2 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_CHECKSIG: insufficient stack items".into(),
});
}
let pubkey_bytes = stack.pop().unwrap();
let signature_bytes = stack.pop().unwrap();
let dummy_hash = [0u8; 32];
#[cfg(feature = "production")]
let result = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
&pubkey_bytes,
&signature_bytes,
&dummy_hash,
flags,
0,
Network::Regtest,
SigVersion::Base,
)
});
#[cfg(not(feature = "production"))]
let result = {
let secp = Secp256k1::new();
signature::verify_signature(
&secp,
&pubkey_bytes,
&signature_bytes,
&dummy_hash,
flags,
0,
Network::Regtest,
SigVersion::Base,
)
};
let ok = result.unwrap_or(false);
if !ok && (flags & SCRIPT_VERIFY_NULLFAIL) != 0 && !signature_bytes.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullFail,
message: "OP_CHECKSIG: non-null signature must not fail under NULLFAIL".into(),
});
}
stack.push(to_stack_element(&[if ok { 1 } else { 0 }]));
Ok(true)
}
pub(crate) fn op_checksigverify_simple(stack: &mut Vec<StackElement>, flags: u32) -> Result<bool> {
if stack.len() < 2 {
return Ok(false);
}
let pubkey_bytes = stack.pop().unwrap();
let signature_bytes = stack.pop().unwrap();
let dummy_hash = [0u8; 32];
#[cfg(feature = "production")]
let result = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
&pubkey_bytes,
&signature_bytes,
&dummy_hash,
flags,
0,
Network::Regtest,
SigVersion::Base,
)
});
#[cfg(not(feature = "production"))]
let result = {
let secp = Secp256k1::new();
signature::verify_signature(
&secp,
&pubkey_bytes,
&signature_bytes,
&dummy_hash,
flags,
0,
Network::Regtest,
SigVersion::Base,
)
};
let ok = result.unwrap_or(false);
if !ok && (flags & SCRIPT_VERIFY_NULLFAIL) != 0 && !signature_bytes.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullFail,
message: "OP_CHECKSIGVERIFY: non-null signature must not fail under NULLFAIL".into(),
});
}
Ok(ok)
}