use crate::crypto::OptimizedSha256;
use crate::error::{ConsensusError, Result};
use blvm_spec_lock::spec_locked;
#[cfg(all(feature = "secp256k1-fallback", not(feature = "blvm-secp256k1")))]
use secp256k1::{schnorr::Signature, Message, XOnlyPublicKey};
#[cfg(feature = "production")]
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "production")]
pub struct SchnorrSignatureCollector {
soa: Option<std::sync::Arc<SchnorrSoAStorage>>,
next_idx: AtomicUsize,
tasks: crossbeam_queue::SegQueue<(usize, (Vec<u8>, Vec<u8>, Vec<u8>))>,
streaming_results: crossbeam_queue::SegQueue<Vec<(usize, bool)>>,
}
#[cfg(feature = "production")]
struct SchnorrSoAStorage {
inner: std::sync::Mutex<SchnorrSoAInner>,
next_slot: AtomicUsize,
}
#[cfg(feature = "production")]
struct SchnorrSoAInner {
indices: Vec<usize>,
msgs: Vec<[u8; 32]>,
pubkeys: Vec<[u8; 32]>,
sigs: Vec<[u8; 64]>,
}
#[cfg(feature = "production")]
impl Default for SchnorrSignatureCollector {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "production")]
impl SchnorrSignatureCollector {
pub fn new() -> Self {
Self {
soa: None,
next_idx: AtomicUsize::new(0),
tasks: crossbeam_queue::SegQueue::new(),
streaming_results: crossbeam_queue::SegQueue::new(),
}
}
pub fn uses_soa(&self) -> bool {
self.soa.is_some()
}
pub fn new_with_capacity(cap: usize) -> Self {
let soa = if cap == 0 {
None
} else {
Some(std::sync::Arc::new(SchnorrSoAStorage {
inner: std::sync::Mutex::new(SchnorrSoAInner {
indices: vec![0; cap],
msgs: vec![[0u8; 32]; cap],
pubkeys: vec![[0u8; 32]; cap],
sigs: vec![[0u8; 64]; cap],
}),
next_slot: AtomicUsize::new(0),
}))
};
Self {
soa,
next_idx: AtomicUsize::new(0),
tasks: crossbeam_queue::SegQueue::new(),
streaming_results: crossbeam_queue::SegQueue::new(),
}
}
#[cfg(all(feature = "production", feature = "rayon"))]
pub fn try_verify_chunk(&self, chunk_size: usize) {
if self.soa.is_some() || chunk_size == 0 {
return;
}
let mut chunk: Vec<_> = std::iter::from_fn(|| self.tasks.pop())
.take(chunk_size)
.collect();
if chunk.is_empty() {
return;
}
chunk.sort_by_key(|t| t.0);
let indices: Vec<usize> = chunk.iter().map(|(i, _)| *i).collect();
let tasks: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)> = chunk.into_iter().map(|(_, t)| t).collect();
let task_refs: Vec<(&[u8], &[u8], &[u8])> = tasks
.iter()
.map(|(msg, pk, sig)| (msg.as_slice(), pk.as_slice(), sig.as_slice()))
.collect();
if let Ok(results) = batch_verify_signatures_from_stack(&task_refs) {
let partial: Vec<(usize, bool)> = indices.into_iter().zip(results).collect();
self.streaming_results.push(partial);
}
}
#[cfg(all(feature = "production", not(feature = "rayon")))]
pub fn try_verify_chunk(&self, _chunk_size: usize) {}
pub fn collect(&self, message: &[u8], pubkey: &[u8], signature: &[u8]) -> usize {
let idx = self.next_idx.fetch_add(1, Ordering::Relaxed);
self.collect_with_index(idx, message, pubkey, signature);
idx
}
pub fn collect_with_index(
&self,
global_index: usize,
message: &[u8],
pubkey: &[u8],
signature: &[u8],
) {
if let Some(ref soa) = self.soa {
if message.len() != 32 || pubkey.len() != 32 || signature.len() != 64 {
return;
}
let slot = soa.next_slot.fetch_add(1, Ordering::Relaxed);
if let Ok(mut inner) = soa.inner.lock() {
if slot < inner.indices.len() {
inner.indices[slot] = global_index;
inner.msgs[slot].copy_from_slice(message);
inner.pubkeys[slot].copy_from_slice(pubkey);
inner.sigs[slot].copy_from_slice(signature);
}
}
} else {
self.tasks.push((
global_index,
(message.to_vec(), pubkey.to_vec(), signature.to_vec()),
));
}
}
pub fn verify_batch(&self) -> Result<Vec<bool>> {
if let Some(ref soa) = self.soa {
let count = soa.next_slot.load(Ordering::Relaxed);
if count == 0 {
return Ok(Vec::new());
}
return Self::verify_soa_batch(soa, count);
}
#[cfg(all(feature = "production", feature = "rayon"))]
let streaming: Vec<Vec<(usize, bool)>> =
std::iter::from_fn(|| self.streaming_results.pop()).collect();
let mut tasks: Vec<(usize, (Vec<u8>, Vec<u8>, Vec<u8>))> =
std::iter::from_fn(|| self.tasks.pop()).collect();
if tasks.is_empty() {
#[cfg(all(feature = "production", feature = "rayon"))]
if !streaming.is_empty() {
let mut merged: Vec<(usize, bool)> = streaming.into_iter().flatten().collect();
merged.sort_by_key(|(i, _)| *i);
return Ok(merged.into_iter().map(|(_, v)| v).collect());
}
return Ok(Vec::new());
}
tasks.sort_by_key(|t| t.0);
let indices: Vec<usize> = tasks.iter().map(|(i, _)| *i).collect();
let tasks: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)> = tasks.into_iter().map(|(_, t)| t).collect();
let task_refs: Vec<(&[u8], &[u8], &[u8])> = tasks
.iter()
.map(|(msg, pk, sig)| (msg.as_slice(), pk.as_slice(), sig.as_slice()))
.collect();
let remainder_results = batch_verify_signatures_from_stack(&task_refs)?;
let mut merged: Vec<(usize, bool)> = indices.into_iter().zip(remainder_results).collect();
#[cfg(feature = "rayon")]
for partial in streaming {
merged.extend(partial);
}
merged.sort_by_key(|(i, _)| *i);
Ok(merged.into_iter().map(|(_, v)| v).collect())
}
fn verify_soa_batch(soa: &SchnorrSoAStorage, count: usize) -> Result<Vec<bool>> {
let inner = soa
.inner
.lock()
.map_err(|_| ConsensusError::BlockValidation("SoA lock poisoned".into()))?;
let task_refs: Vec<(&[u8], &[u8], &[u8])> = (0..count)
.map(|slot| {
(
inner.msgs[slot].as_slice(),
inner.pubkeys[slot].as_slice(),
inner.sigs[slot].as_slice(),
)
})
.collect();
let results = batch_verify_signatures_from_stack(&task_refs)?;
let mut merged: Vec<(usize, bool)> = (0..count)
.map(|slot| (inner.indices[slot], results[slot]))
.collect();
merged.sort_by_key(|(i, _)| *i);
Ok(merged.into_iter().map(|(_, v)| v).collect())
}
pub fn clear(&self) {
while self.tasks.pop().is_some() {}
self.next_idx.store(0, Ordering::Relaxed);
#[cfg(all(feature = "production", feature = "rayon"))]
while self.streaming_results.pop().is_some() {}
}
pub fn is_empty(&self) -> bool {
self.tasks.is_empty()
}
#[cfg(feature = "rayon")]
pub fn extend_from(&self, other: &Self) {
if self.soa.is_some() {
return;
}
while let Some((_, task)) = other.tasks.pop() {
let new_idx = self.next_idx.fetch_add(1, Ordering::Relaxed);
self.tasks.push((new_idx, task));
}
}
}
#[spec_locked("5.4.8")]
pub fn verify_signature_from_stack(
message: &[u8],
pubkey: &[u8],
signature: &[u8],
#[cfg(feature = "production")] collector: Option<&SchnorrSignatureCollector>,
) -> Result<bool> {
if pubkey.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: crate::error::ScriptErrorCode::PubkeyType,
message: "OP_CHECKSIGFROMSTACK: pubkey size is zero".into(),
});
}
if pubkey.len() == 32 {
if signature.len() != 64 {
return Ok(false);
}
#[cfg(feature = "production")]
if let Some(collector) = collector {
collector.collect(message, pubkey, signature);
return Ok(true);
}
let message_hash = OptimizedSha256::new().hash(message);
#[cfg(feature = "blvm-secp256k1")]
{
let sig_arr: [u8; 64] = match signature.try_into() {
Ok(a) => a,
Err(_) => return Ok(false),
};
let pk_arr: [u8; 32] = match pubkey.try_into() {
Ok(a) => a,
Err(_) => return Ok(false),
};
return crate::secp256k1_backend::verify_schnorr(&sig_arr, &message_hash, &pk_arr);
}
#[cfg(all(feature = "secp256k1-fallback", not(feature = "blvm-secp256k1")))]
{
let pubkey_xonly = match XOnlyPublicKey::from_slice(pubkey) {
Ok(pk) => pk,
Err(_) => return Ok(false),
};
let sig = match Signature::from_slice(signature) {
Ok(s) => s,
Err(_) => return Ok(false),
};
let _msg = Message::from_digest_slice(&message_hash)
.map_err(|_| ConsensusError::InvalidSignature("Invalid message".into()))?;
let pk_bytes: [u8; 32] = pubkey_xonly.serialize();
return Ok(crate::secp256k1_backend::verify_schnorr(
&sig.serialize(),
&message_hash,
&pk_bytes,
)?);
}
#[allow(unreachable_code)]
Ok(false)
} else {
Ok(true)
}
}
#[cfg(feature = "production")]
#[spec_locked("5.4.8")]
pub fn batch_verify_signatures_from_stack(
verification_tasks: &[(&[u8], &[u8], &[u8])],
) -> Result<Vec<bool>> {
if verification_tasks.is_empty() {
return Ok(Vec::new());
}
let mut valid_tasks = Vec::new();
let mut task_indices = Vec::new(); let mut results = vec![true; verification_tasks.len()];
for (idx, (message, pubkey, signature)) in verification_tasks.iter().enumerate() {
if pubkey.len() == 32 && signature.len() == 64 {
valid_tasks.push((idx, message, pubkey, signature));
task_indices.push(idx);
} else if pubkey.is_empty() {
results[idx] = false;
} else if pubkey.len() != 32 {
continue;
} else if signature.len() != 64 {
results[idx] = false;
}
}
if valid_tasks.is_empty() {
return Ok(results);
}
let mut sigs_bytes: Vec<[u8; 64]> = Vec::new();
let mut pubkeys_bytes: Vec<[u8; 32]> = Vec::new();
let mut msgs: Vec<[u8; 32]> = Vec::new();
for (idx, message, pubkey, signature) in &valid_tasks {
#[cfg(feature = "blvm-secp256k1")]
{
let _pk: &[u8] = pubkey; }
#[cfg(all(feature = "secp256k1-fallback", not(feature = "blvm-secp256k1")))]
{
if XOnlyPublicKey::from_slice(pubkey).is_err() {
results[*idx] = false;
continue;
}
}
#[cfg(all(feature = "secp256k1-fallback", not(feature = "blvm-secp256k1")))]
{
if Signature::from_slice(signature).is_err() {
results[*idx] = false;
continue;
}
}
let digest: [u8; 32] = if message.len() == 32 {
let mut d = [0u8; 32];
d.copy_from_slice(message);
d
} else {
OptimizedSha256::new().hash(message)
};
let mut sig_arr = [0u8; 64];
sig_arr.copy_from_slice(signature);
let mut pk_arr = [0u8; 32];
pk_arr.copy_from_slice(pubkey);
sigs_bytes.push(sig_arr);
pubkeys_bytes.push(pk_arr);
msgs.push(digest);
}
if sigs_bytes.is_empty() {
return Ok(results);
}
let msg_refs: Vec<&[u8]> = msgs.iter().map(|m| m.as_slice()).collect();
let batch_bools: Vec<bool> =
crate::secp256k1_backend::verify_schnorr_batch(&sigs_bytes, &msg_refs, &pubkeys_bytes)?;
for (i, &valid) in batch_bools.iter().enumerate() {
results[task_indices[i]] = valid;
}
Ok(results)
}
#[cfg(feature = "production")]
#[spec_locked("5.4.8")]
pub fn verify_tapscript_schnorr_signature(
sighash: &[u8; 32],
pubkey: &[u8],
signature: &[u8],
collector: Option<&SchnorrSignatureCollector>,
) -> Result<bool> {
if pubkey.len() != 32 {
return Ok(false);
}
if signature.len() != 64 {
return Ok(false);
}
if let Some(c) = collector {
c.collect(sighash, pubkey, signature);
return Ok(true);
}
#[cfg(feature = "blvm-secp256k1")]
{
let sig_arr: [u8; 64] = match signature.try_into() {
Ok(a) => a,
Err(_) => return Ok(false),
};
let pk_arr: [u8; 32] = match pubkey.try_into() {
Ok(a) => a,
Err(_) => return Ok(false),
};
return crate::secp256k1_backend::verify_schnorr(&sig_arr, sighash, &pk_arr);
}
#[cfg(all(feature = "secp256k1-fallback", not(feature = "blvm-secp256k1")))]
{
let pubkey_xonly = match XOnlyPublicKey::from_slice(pubkey) {
Ok(pk) => pk,
Err(_) => return Ok(false),
};
let sig = match Signature::from_slice(signature) {
Ok(s) => s,
Err(_) => return Ok(false),
};
let pk_bytes: [u8; 32] = pubkey_xonly.serialize();
return crate::secp256k1_backend::verify_schnorr(&sig.serialize(), sighash, &pk_bytes);
}
#[allow(unreachable_code)]
Ok(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verify_signature_from_stack_zero_pubkey() {
let message = b"test message";
let pubkey = vec![];
let signature = vec![0u8; 64];
let result = verify_signature_from_stack(
message,
&pubkey,
&signature,
#[cfg(feature = "production")]
None,
);
assert!(result.is_err());
}
#[test]
fn test_verify_signature_from_stack_unknown_pubkey_type() {
let message = b"test message";
let pubkey = vec![1u8; 33]; let signature = vec![0u8; 64];
let result = verify_signature_from_stack(
message,
&pubkey,
&signature,
#[cfg(feature = "production")]
None,
);
assert_eq!(result.unwrap(), true);
}
#[test]
fn test_verify_signature_from_stack_invalid_signature_length() {
let message = b"test message";
let pubkey = vec![1u8; 32]; let signature = vec![0u8; 63];
let result = verify_signature_from_stack(
message,
&pubkey,
&signature,
#[cfg(feature = "production")]
None,
);
assert_eq!(result.unwrap(), false);
}
}