Skip to main content

blvm_consensus/script/
signature.rs

1//! ECDSA signature verification for script execution.
2//!
3//! BIP66 strict DER, BIP62 LOW_S, assumevalid optimization.
4
5use crate::error::Result;
6use crate::types::Natural;
7use blvm_spec_lock::spec_locked;
8use secp256k1::{ecdsa::Signature, Context, Message, PublicKey, Secp256k1, Verification};
9
10use super::SigVersion;
11
12#[cfg(feature = "production")]
13use std::thread_local;
14
15#[cfg(feature = "production")]
16thread_local! {
17    static SECP256K1_CONTEXT: Secp256k1<secp256k1::All> = Secp256k1::new();
18}
19
20/// Run a closure with the thread-local secp256k1 context. Used by mod.rs for verify_signature calls.
21#[cfg(feature = "production")]
22pub(crate) fn with_secp_context<F, R>(f: F) -> R
23where
24    F: FnOnce(&Secp256k1<secp256k1::All>) -> R,
25{
26    SECP256K1_CONTEXT.with(f)
27}
28
29/// Verify ECDSA signature using secp256k1.
30/// BIP66: strict DER. BIP62: LOW_S, STRICTENC.
31#[allow(clippy::too_many_arguments)]
32pub(crate) fn verify_signature<C: Context + Verification>(
33    secp: &Secp256k1<C>,
34    pubkey_bytes: &[u8],
35    signature_bytes: &[u8],
36    sighash: &[u8; 32],
37    flags: u32,
38    height: Natural,
39    network: crate::types::Network,
40    sigversion: SigVersion,
41) -> Result<bool> {
42    if signature_bytes.is_empty() {
43        return Ok(false);
44    }
45    let sig_len = signature_bytes.len();
46    let sighash_byte = signature_bytes[sig_len - 1];
47    let der_sig = &signature_bytes[..sig_len - 1];
48
49    if flags & 0x04 != 0
50        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)?
51    {
52        return Ok(false);
53    }
54
55    if flags & 0x02 != 0 {
56        let base_sighash = sighash_byte & !0x80;
57        if !(0x01..=0x03).contains(&base_sighash) {
58            return Ok(false);
59        }
60    }
61
62    let signature = if flags & 0x04 != 0 {
63        match Signature::from_der(der_sig) {
64            Ok(sig) => sig,
65            Err(_) => return Ok(false),
66        }
67    } else {
68        match Signature::from_der_lax(der_sig) {
69            Ok(sig) => sig,
70            Err(_) => return Ok(false),
71        }
72    };
73
74    if flags & 0x08 != 0 {
75        let before = signature.serialize_compact();
76        let mut normalized = signature;
77        normalized.normalize_s();
78        if before != normalized.serialize_compact() {
79            return Ok(false);
80        }
81    }
82
83    if flags & 0x02 != 0 {
84        if pubkey_bytes.len() < 33 {
85            return Ok(false);
86        }
87        if pubkey_bytes[0] == 0x04 {
88            if pubkey_bytes.len() != 65 {
89                return Ok(false);
90            }
91        } else if pubkey_bytes[0] == 0x02 || pubkey_bytes[0] == 0x03 {
92            if pubkey_bytes.len() != 33 {
93                return Ok(false);
94            }
95        } else {
96            return Ok(false);
97        }
98    }
99
100    const SCRIPT_VERIFY_WITNESS_PUBKEYTYPE: u32 = 0x8000;
101    if (flags & SCRIPT_VERIFY_WITNESS_PUBKEYTYPE) != 0
102        && sigversion == SigVersion::WitnessV0
103        && !(pubkey_bytes.len() == 33 && (pubkey_bytes[0] == 0x02 || pubkey_bytes[0] == 0x03))
104    {
105        return Ok(false);
106    }
107
108    let pubkey = match PublicKey::from_slice(pubkey_bytes) {
109        Ok(pk) => pk,
110        Err(_) => return Ok(false),
111    };
112
113    let normalized_signature = if flags & 0x08 != 0 {
114        signature
115    } else {
116        let mut s = signature;
117        s.normalize_s();
118        s
119    };
120
121    let sig_compact = normalized_signature.serialize_compact();
122    let pk_compressed = pubkey.serialize();
123    crate::secp256k1_backend::verify_ecdsa(sighash, &sig_compact, &pk_compressed)
124}
125
126/// Verify pre-extracted ECDSA (P2PKH/P2PK) inline without re-parsing script_sig.
127#[cfg(feature = "production")]
128pub fn verify_pre_extracted_ecdsa(
129    pubkey_bytes: &[u8],
130    signature_bytes: &[u8],
131    sighash: &[u8; 32],
132    flags: u32,
133    height: Natural,
134    network: crate::types::Network,
135) -> Result<bool> {
136    SECP256K1_CONTEXT.with(|secp| {
137        verify_signature(
138            secp,
139            pubkey_bytes,
140            signature_bytes,
141            sighash,
142            flags,
143            height,
144            network,
145            SigVersion::Base,
146        )
147    })
148}
149
150#[cfg(feature = "production")]
151#[allow(dead_code)]
152fn parse_task_for_batch(
153    pubkey_bytes: &[u8],
154    signature_bytes: &[u8],
155    sighash: &[u8; 32],
156    flags: u32,
157    height: Natural,
158    network: crate::types::Network,
159) -> Result<Option<(PublicKey, secp256k1::ecdsa::Signature, Message)>> {
160    use secp256k1::ecdsa::Signature;
161
162    if signature_bytes.is_empty() {
163        return Ok(None);
164    }
165    let sig_len = signature_bytes.len();
166    let sighash_byte = signature_bytes[sig_len - 1];
167    let der_sig = &signature_bytes[..sig_len - 1];
168
169    if flags & 0x04 != 0
170        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)?
171    {
172        return Ok(None);
173    }
174    if flags & 0x02 != 0 {
175        let base_sighash = sighash_byte & !0x80;
176        if !(0x01..=0x03).contains(&base_sighash) {
177            return Ok(None);
178        }
179    }
180
181    let signature = if flags & 0x04 != 0 {
182        match Signature::from_der(der_sig) {
183            Ok(sig) => sig,
184            Err(_) => return Ok(None),
185        }
186    } else {
187        match Signature::from_der_lax(der_sig) {
188            Ok(sig) => sig,
189            Err(_) => return Ok(None),
190        }
191    };
192
193    let original_compact = signature.serialize_compact();
194    let mut normalized_signature = signature;
195    normalized_signature.normalize_s();
196    let norm_compact = normalized_signature.serialize_compact();
197    if flags & 0x08 != 0 && original_compact != norm_compact {
198        return Ok(None);
199    }
200
201    if flags & 0x02 != 0 {
202        if pubkey_bytes.len() < 33 {
203            return Ok(None);
204        }
205        if pubkey_bytes[0] == 0x04 {
206            if pubkey_bytes.len() != 65 {
207                return Ok(None);
208            }
209        } else if pubkey_bytes[0] == 0x02 || pubkey_bytes[0] == 0x03 {
210            if pubkey_bytes.len() != 33 {
211                return Ok(None);
212            }
213        } else {
214            return Ok(None);
215        }
216    }
217
218    let pubkey = match PublicKey::from_slice(pubkey_bytes) {
219        Ok(pk) => pk,
220        Err(_) => return Ok(None),
221    };
222    let message = match Message::from_digest_slice(sighash) {
223        Ok(m) => m,
224        Err(_) => return Ok(None),
225    };
226
227    Ok(Some((pubkey, normalized_signature, message)))
228}
229
230#[cfg(feature = "production")]
231#[spec_locked("5.2")]
232pub fn batch_verify_signatures(
233    verification_tasks: &[(&[u8], &[u8], [u8; 32])],
234    flags: u32,
235    height: Natural,
236    network: crate::types::Network,
237) -> Result<Vec<bool>> {
238    #[cfg(feature = "profile")]
239    let _t0 = std::time::Instant::now();
240
241    if verification_tasks.is_empty() {
242        #[cfg(feature = "profile")]
243        crate::script_profile::add_multisig_ns(_t0.elapsed().as_nanos() as u64);
244        return Ok(Vec::new());
245    }
246
247    #[cfg(feature = "rayon")]
248    {
249        use rayon::prelude::*;
250        let r: Result<Vec<bool>> = verification_tasks
251            .par_iter()
252            .map(|(pubkey_bytes, signature_bytes, sighash)| {
253                SECP256K1_CONTEXT.with(|secp| {
254                    verify_signature(
255                        secp,
256                        pubkey_bytes,
257                        signature_bytes,
258                        sighash,
259                        flags,
260                        height,
261                        network,
262                        SigVersion::Base,
263                    )
264                })
265            })
266            .collect();
267        #[cfg(feature = "profile")]
268        crate::script_profile::add_multisig_ns(_t0.elapsed().as_nanos() as u64);
269        r
270    }
271
272    #[cfg(not(feature = "rayon"))]
273    {
274        let mut results = Vec::with_capacity(verification_tasks.len());
275        for (pubkey_bytes, signature_bytes, sighash) in verification_tasks {
276            let secp = Secp256k1::new();
277            let result = verify_signature(
278                &secp,
279                pubkey_bytes,
280                signature_bytes,
281                sighash,
282                flags,
283                height,
284                network,
285                SigVersion::Base,
286            )?;
287            results.push(result);
288        }
289        #[cfg(feature = "profile")]
290        crate::script_profile::add_multisig_ns(_t0.elapsed().as_nanos() as u64);
291        Ok(results)
292    }
293}