Skip to main content

runar_lang/
prelude.rs

1//! Prelude — import everything needed for Rúnar contract development.
2//!
3//! ```ignore
4//! use runar::prelude::*;
5//! ```
6
7use sha2::{Digest, Sha256 as Sha256Hasher};
8
9// Re-export macros so `use runar::prelude::*` gets them too.
10pub use runar_lang_macros::{contract, methods, public, stateful_contract};
11
12// ---------------------------------------------------------------------------
13// Scalar types — type aliases so Rust arithmetic operators work directly
14// ---------------------------------------------------------------------------
15
16/// Rúnar integer (maps to Bitcoin Script numbers).
17pub type Int = i64;
18
19/// Alias for Int.
20pub type Bigint = i64;
21
22// ---------------------------------------------------------------------------
23// Byte-string types
24// ---------------------------------------------------------------------------
25
26/// A public key (compressed or uncompressed).
27pub type PubKey = Vec<u8>;
28
29/// A DER-encoded signature.
30pub type Sig = Vec<u8>;
31
32/// A 20-byte address (typically hash160 of a public key).
33pub type Addr = Vec<u8>;
34
35/// An arbitrary byte sequence.
36pub type ByteString = Vec<u8>;
37
38/// A 32-byte SHA-256 hash.
39pub type Sha256 = Vec<u8>;
40
41/// A 20-byte RIPEMD-160 hash.
42pub type Ripemd160 = Vec<u8>;
43
44/// Sighash preimage for transaction validation.
45pub type SigHashPreimage = Vec<u8>;
46
47/// A Rabin signature.
48pub type RabinSig = Vec<u8>;
49
50/// A Rabin public key.
51pub type RabinPubKey = Vec<u8>;
52
53/// A 64-byte EC point (x[32] || y[32], big-endian, no prefix).
54pub type Point = Vec<u8>;
55
56// ---------------------------------------------------------------------------
57// Output snapshot (for stateful contracts with add_output)
58// ---------------------------------------------------------------------------
59
60/// A recorded output from `add_output`.
61#[derive(Debug, Clone)]
62pub struct OutputSnapshot {
63    pub satoshis: Bigint,
64    pub values: Vec<Vec<u8>>,
65}
66
67// ---------------------------------------------------------------------------
68// Real crypto verification (ECDSA, Rabin) + mocked checkPreimage
69// ---------------------------------------------------------------------------
70
71/// Real ECDSA verification over the fixed TEST_MESSAGE.
72///
73/// Verifies the given DER-encoded signature against the compressed public
74/// key using secp256k1 over the canonical test message
75/// `"runar-test-message-v1"`. Handles optional trailing sighash byte.
76pub fn check_sig(sig: &[u8], pk: &[u8]) -> bool {
77    crate::ecdsa::ecdsa_verify(sig, pk)
78}
79
80/// Real ordered multi-sig ECDSA verification over the fixed TEST_MESSAGE.
81///
82/// Each signature in `sigs` must correspond to a public key in `pks`
83/// (in order). All signatures must be valid.
84pub fn check_multi_sig(sigs: &[&[u8]], pks: &[&[u8]]) -> bool {
85    if sigs.len() != pks.len() {
86        return false;
87    }
88    for (sig, pk) in sigs.iter().zip(pks.iter()) {
89        if !crate::ecdsa::ecdsa_verify(sig, pk) {
90            return false;
91        }
92    }
93    true
94}
95
96/// Always returns `true` in test mode (preimage verification is mocked).
97pub fn check_preimage(_preimage: &[u8]) -> bool {
98    true
99}
100
101/// Real Rabin signature verification.
102///
103/// Equation: `(sig^2 + padding) mod n == SHA256(msg) mod n`
104/// where all byte slices are interpreted as unsigned little-endian big integers.
105pub fn verify_rabin_sig(msg: &[u8], sig: &[u8], padding: &[u8], pk: &[u8]) -> bool {
106    crate::rabin::rabin_verify(msg, sig, padding, pk)
107}
108
109/// Real WOTS+ signature verification using SHA-256 hash chains.
110pub fn verify_wots(msg: &[u8], sig: &[u8], pk: &[u8]) -> bool {
111    crate::wots::wots_verify_impl(msg, sig, pk)
112}
113
114// SLH-DSA (SPHINCS+) SHA-256 variants — real FIPS 205 verification.
115
116/// Real SLH-DSA-SHA2-128s verification (FIPS 205).
117pub fn verify_slh_dsa_sha2_128s(msg: &[u8], sig: &[u8], pk: &[u8]) -> bool {
118    crate::slh_dsa::slh_verify(&crate::slh_dsa::SLH_SHA2_128S, msg, sig, pk)
119}
120
121/// Real SLH-DSA-SHA2-128f verification (FIPS 205).
122pub fn verify_slh_dsa_sha2_128f(msg: &[u8], sig: &[u8], pk: &[u8]) -> bool {
123    crate::slh_dsa::slh_verify(&crate::slh_dsa::SLH_SHA2_128F, msg, sig, pk)
124}
125
126/// Real SLH-DSA-SHA2-192s verification (FIPS 205).
127pub fn verify_slh_dsa_sha2_192s(msg: &[u8], sig: &[u8], pk: &[u8]) -> bool {
128    crate::slh_dsa::slh_verify(&crate::slh_dsa::SLH_SHA2_192S, msg, sig, pk)
129}
130
131/// Real SLH-DSA-SHA2-192f verification (FIPS 205).
132pub fn verify_slh_dsa_sha2_192f(msg: &[u8], sig: &[u8], pk: &[u8]) -> bool {
133    crate::slh_dsa::slh_verify(&crate::slh_dsa::SLH_SHA2_192F, msg, sig, pk)
134}
135
136/// Real SLH-DSA-SHA2-256s verification (FIPS 205).
137pub fn verify_slh_dsa_sha2_256s(msg: &[u8], sig: &[u8], pk: &[u8]) -> bool {
138    crate::slh_dsa::slh_verify(&crate::slh_dsa::SLH_SHA2_256S, msg, sig, pk)
139}
140
141/// Real SLH-DSA-SHA2-256f verification (FIPS 205).
142pub fn verify_slh_dsa_sha2_256f(msg: &[u8], sig: &[u8], pk: &[u8]) -> bool {
143    crate::slh_dsa::slh_verify(&crate::slh_dsa::SLH_SHA2_256F, msg, sig, pk)
144}
145
146// ---------------------------------------------------------------------------
147// EC (elliptic curve) functions — real secp256k1 arithmetic for testing.
148// In compiled Bitcoin Script, these map to EC codegen opcodes.
149// ---------------------------------------------------------------------------
150
151pub use crate::ec::{
152    ec_add, ec_encode_compressed, ec_make_point, ec_mod_reduce, ec_mul, ec_mul_gen,
153    ec_negate, ec_on_curve, ec_point_x, ec_point_y,
154};
155
156pub use crate::wots::{wots_keygen, wots_sign, WotsKeyPair};
157
158pub use crate::slh_dsa::{
159    slh_keygen, slh_sign, slh_verify, SlhKeyPair, SlhParams,
160    SLH_SHA2_128S, SLH_SHA2_128F, SLH_SHA2_192S, SLH_SHA2_192F,
161    SLH_SHA2_256S, SLH_SHA2_256F,
162};
163
164pub use crate::ecdsa::{
165    sign_test_message, pub_key_from_priv_key, ecdsa_verify,
166    TEST_MESSAGE, TEST_MESSAGE_DIGEST,
167};
168
169pub use crate::test_keys::{TestKeyPair, ALICE, BOB, CHARLIE};
170
171pub use crate::rabin::rabin_sign_trivial;
172
173// ---------------------------------------------------------------------------
174// Real hash functions
175// ---------------------------------------------------------------------------
176
177/// RIPEMD160(SHA256(data)) — produces a 20-byte address.
178pub fn hash160(data: &[u8]) -> Addr {
179    let sha = Sha256Hasher::digest(data);
180    let mut hasher = ripemd::Ripemd160::new();
181    hasher.update(&sha);
182    hasher.finalize().to_vec()
183}
184
185/// SHA256(SHA256(data)) — produces a 32-byte hash.
186pub fn hash256(data: &[u8]) -> Sha256 {
187    let h1 = Sha256Hasher::digest(data);
188    let h2 = Sha256Hasher::digest(&h1);
189    h2.to_vec()
190}
191
192/// Single SHA-256 hash.
193pub fn sha256(data: &[u8]) -> Sha256 {
194    Sha256Hasher::digest(data).to_vec()
195}
196
197/// Single RIPEMD-160 hash.
198pub fn ripemd160(data: &[u8]) -> Ripemd160 {
199    let mut hasher = ripemd::Ripemd160::new();
200    hasher.update(data);
201    hasher.finalize().to_vec()
202}
203
204// ---------------------------------------------------------------------------
205// Mock BLAKE3 functions (compiler intrinsics — stubs return 32 zero bytes)
206// ---------------------------------------------------------------------------
207
208/// Mock BLAKE3 single-block compression.
209/// In compiled Bitcoin Script this expands to ~10,000 opcodes.
210/// The mock returns 32 zero bytes for business-logic testing.
211pub fn blake3_compress(_chaining_value: &[u8], _block: &[u8]) -> ByteString {
212    vec![0u8; 32]
213}
214
215/// Mock BLAKE3 hash for messages up to 64 bytes.
216/// In compiled Bitcoin Script this uses the IV as the chaining value and
217/// applies zero-padding before calling the compression function.
218/// The mock returns 32 zero bytes for business-logic testing.
219pub fn blake3_hash(_message: &[u8]) -> ByteString {
220    vec![0u8; 32]
221}
222
223// ---------------------------------------------------------------------------
224// Real SHA-256 compression (FIPS 180-4 Section 6.2.2)
225// ---------------------------------------------------------------------------
226
227const SHA256_K: [u32; 64] = [
228    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
229    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
230    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
231    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
232    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
233    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
234    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
235    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
236    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
237    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
238    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
239    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
240    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
241    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
242    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
243    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
244];
245
246/// Real SHA-256 single-block compression function (FIPS 180-4 Section 6.2.2).
247///
248/// Takes a 32-byte state (8 big-endian u32 words) and a 64-byte block,
249/// returns the 32-byte updated state.
250pub fn sha256_compress(state: &[u8], block: &[u8]) -> ByteString {
251    assert!(state.len() == 32, "sha256_compress: state must be 32 bytes");
252    assert!(block.len() == 64, "sha256_compress: block must be 64 bytes");
253
254    // Parse state as 8 big-endian u32 words
255    let mut h = [0u32; 8];
256    for i in 0..8 {
257        h[i] = u32::from_be_bytes(state[i * 4..i * 4 + 4].try_into().unwrap());
258    }
259
260    // Parse block as 16 big-endian u32 words
261    let mut w = [0u32; 64];
262    for i in 0..16 {
263        w[i] = u32::from_be_bytes(block[i * 4..i * 4 + 4].try_into().unwrap());
264    }
265
266    // W-expansion for t = 16..64
267    for t in 16..64 {
268        let s0 = w[t - 15].rotate_right(7) ^ w[t - 15].rotate_right(18) ^ (w[t - 15] >> 3);
269        let s1 = w[t - 2].rotate_right(17) ^ w[t - 2].rotate_right(19) ^ (w[t - 2] >> 10);
270        w[t] = w[t - 16]
271            .wrapping_add(s0)
272            .wrapping_add(w[t - 7])
273            .wrapping_add(s1);
274    }
275
276    // Initialize working variables
277    let mut a = h[0];
278    let mut b = h[1];
279    let mut c = h[2];
280    let mut d = h[3];
281    let mut e = h[4];
282    let mut f = h[5];
283    let mut g = h[6];
284    let mut hh = h[7];
285
286    // 64 rounds
287    for t in 0..64 {
288        let big_s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
289        let ch = (e & f) ^ ((!e) & g);
290        let temp1 = hh
291            .wrapping_add(big_s1)
292            .wrapping_add(ch)
293            .wrapping_add(SHA256_K[t])
294            .wrapping_add(w[t]);
295        let big_s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
296        let maj = (a & b) ^ (a & c) ^ (b & c);
297        let temp2 = big_s0.wrapping_add(maj);
298
299        hh = g;
300        g = f;
301        f = e;
302        e = d.wrapping_add(temp1);
303        d = c;
304        c = b;
305        b = a;
306        a = temp1.wrapping_add(temp2);
307    }
308
309    // Compute final hash values
310    h[0] = h[0].wrapping_add(a);
311    h[1] = h[1].wrapping_add(b);
312    h[2] = h[2].wrapping_add(c);
313    h[3] = h[3].wrapping_add(d);
314    h[4] = h[4].wrapping_add(e);
315    h[5] = h[5].wrapping_add(f);
316    h[6] = h[6].wrapping_add(g);
317    h[7] = h[7].wrapping_add(hh);
318
319    // Encode as big-endian bytes
320    let mut result = vec![0u8; 32];
321    for i in 0..8 {
322        result[i * 4..i * 4 + 4].copy_from_slice(&h[i].to_be_bytes());
323    }
324    result
325}
326
327/// Real SHA-256 finalize function.
328///
329/// Takes the current 32-byte state, remaining unprocessed bytes, and the
330/// total message bit length. Applies SHA-256 padding (append 0x80, zero-pad,
331/// append 64-bit big-endian bit length) and runs the final 1-2 compression rounds.
332pub fn sha256_finalize(state: &[u8], remaining: &[u8], msg_bit_len: i64) -> ByteString {
333    assert!(state.len() == 32, "sha256_finalize: state must be 32 bytes");
334    assert!(remaining.len() <= 64, "sha256_finalize: remaining must be <= 64 bytes");
335
336    let rem_len = remaining.len();
337
338    // Build padded buffer: remaining + 0x80 + zeros + 8-byte big-endian bit length
339    // If remaining + 1 (0x80) + 8 (bit length) <= 64, it fits in one block.
340    // Otherwise we need two blocks.
341    if rem_len < 56 {
342        // Fits in one 64-byte block
343        let mut block = [0u8; 64];
344        block[..rem_len].copy_from_slice(remaining);
345        block[rem_len] = 0x80;
346        // Last 8 bytes = bit length in big-endian
347        let bit_len = msg_bit_len as u64;
348        block[56..64].copy_from_slice(&bit_len.to_be_bytes());
349        sha256_compress(state, &block)
350    } else {
351        // Needs two blocks
352        let mut block1 = [0u8; 64];
353        block1[..rem_len].copy_from_slice(remaining);
354        block1[rem_len] = 0x80;
355        // First block: remaining + 0x80 + zeros (no room for bit length)
356        let intermediate = sha256_compress(state, &block1);
357
358        // Second block: all zeros except last 8 bytes = bit length
359        let mut block2 = [0u8; 64];
360        let bit_len = msg_bit_len as u64;
361        block2[56..64].copy_from_slice(&bit_len.to_be_bytes());
362        sha256_compress(&intermediate, &block2)
363    }
364}
365
366// ---------------------------------------------------------------------------
367// Mock preimage extraction functions
368// ---------------------------------------------------------------------------
369
370/// Returns 0 in test mode.
371pub fn extract_locktime(_p: &[u8]) -> Int {
372    0
373}
374
375/// Returns the first 32 bytes of the preimage in test mode.
376/// Tests set `tx_preimage = hash256(expected_output_bytes)` so the assertion
377/// `hash256(outputs) == extract_output_hash(tx_preimage)` passes.
378/// Falls back to 32 zero bytes when the preimage is unset or shorter than 32 bytes.
379pub fn extract_output_hash(p: &[u8]) -> ByteString {
380    if p.len() >= 32 {
381        p[..32].to_vec()
382    } else {
383        vec![0u8; 32]
384    }
385}
386
387/// Returns `hash256([0u8; 72])` in test mode.
388/// This is consistent with passing `all_prevouts = [0u8; 72]` in tests,
389/// since `extract_outpoint` also returns 36 zero bytes.
390pub fn extract_hash_prevouts(_p: &[u8]) -> Sha256 {
391    hash256(&vec![0u8; 72])
392}
393
394/// Returns 36 zero bytes in test mode.
395pub fn extract_outpoint(_p: &[u8]) -> ByteString {
396    vec![0u8; 36]
397}
398
399/// Returns a mock state script (empty bytes).
400pub fn get_state_script<T>(_contract: &T) -> ByteString {
401    vec![]
402}
403
404// ---------------------------------------------------------------------------
405// Utility functions
406// ---------------------------------------------------------------------------
407
408/// Returns a substring of a byte string starting at `start` with the given `length`.
409pub fn substr(data: &[u8], start: i64, length: i64) -> ByteString {
410    let s = start as usize;
411    let l = length as usize;
412    data[s..s + l].to_vec()
413}
414
415/// Converts an integer to a byte string of the specified length
416/// using Bitcoin Script's little-endian signed magnitude encoding.
417/// Accepts a reference to match Rúnar contract calling convention.
418pub fn num2bin(v: &Bigint, length: usize) -> ByteString {
419    let mut buf = vec![0u8; length];
420    if *v == 0 || length == 0 {
421        return buf;
422    }
423    let abs = v.unsigned_abs();
424    let mut val = abs;
425    for byte in buf.iter_mut() {
426        if val == 0 {
427            break;
428        }
429        *byte = (val & 0xff) as u8;
430        val >>= 8;
431    }
432    if *v < 0 {
433        buf[length - 1] |= 0x80;
434    }
435    buf
436}
437
438/// Converts a byte string (Bitcoin Script LE signed-magnitude) back to an integer.
439/// Inverse of `num2bin`.
440pub fn bin2num(data: &[u8]) -> Bigint {
441    if data.is_empty() {
442        return 0;
443    }
444    let last = data[data.len() - 1];
445    let negative = (last & 0x80) != 0;
446    let mut result: u64 = (last & 0x7f) as u64;
447    for i in (0..data.len() - 1).rev() {
448        result = (result << 8) | data[i] as u64;
449    }
450    if negative {
451        -(result as i64)
452    } else {
453        result as i64
454    }
455}
456
457/// Concatenates two byte strings.
458pub fn cat(a: &[u8], b: &[u8]) -> ByteString {
459    let mut result = a.to_vec();
460    result.extend_from_slice(b);
461    result
462}
463
464// ---------------------------------------------------------------------------
465// Math functions
466// ---------------------------------------------------------------------------
467
468/// Safe division — panics if b is zero.
469pub fn safediv(a: Int, b: Int) -> Int {
470    assert!(b != 0, "safediv: division by zero");
471    a / b
472}
473
474/// Safe modulo — panics if b is zero.
475pub fn safemod(a: Int, b: Int) -> Int {
476    assert!(b != 0, "safemod: modulo by zero");
477    a % b
478}
479
480/// Clamp value to [lo, hi].
481pub fn clamp(value: Int, lo: Int, hi: Int) -> Int {
482    if value < lo { lo } else if value > hi { hi } else { value }
483}
484
485/// Sign of a number: -1, 0, or 1.
486pub fn sign(n: Int) -> Int {
487    if n > 0 { 1 } else if n < 0 { -1 } else { 0 }
488}
489
490/// Exponentiation for non-negative exponents. Panics on i64 overflow.
491pub fn pow(base: Int, exp: Int) -> Int {
492    assert!(exp >= 0, "pow: negative exponent");
493    let mut result: Int = 1;
494    for _ in 0..exp {
495        result = result.checked_mul(base).unwrap_or_else(|| {
496            panic!("runar: i64 overflow in {} * {} — Bitcoin Script supports arbitrary precision but Rust tests use i64", result, base)
497        });
498    }
499    result
500}
501
502/// (a * b) / c — panics on i64 overflow in a*b.
503pub fn mul_div(a: Int, b: Int, c: Int) -> Int {
504    assert!(c != 0, "mulDiv: division by zero");
505    let product = a.checked_mul(b).unwrap_or_else(|| {
506        panic!("runar: i64 overflow in {} * {} — Bitcoin Script supports arbitrary precision but Rust tests use i64", a, b)
507    });
508    product / c
509}
510
511/// (amount * bps) / 10000 — basis point percentage. Panics on i64 overflow.
512pub fn percent_of(amount: Int, bps: Int) -> Int {
513    let product = amount.checked_mul(bps).unwrap_or_else(|| {
514        panic!("runar: i64 overflow in {} * {} — Bitcoin Script supports arbitrary precision but Rust tests use i64", amount, bps)
515    });
516    product / 10000
517}
518
519/// Integer square root via Newton's method. Panics on i64 overflow.
520pub fn sqrt(n: Int) -> Int {
521    assert!(n >= 0, "sqrt: negative input");
522    if n == 0 { return 0; }
523    let mut guess = n;
524    for _ in 0..256 {
525        let sum = guess.checked_add(n / guess).unwrap_or_else(|| {
526            panic!("runar: i64 overflow in sqrt — Bitcoin Script supports arbitrary precision but Rust tests use i64")
527        });
528        let next = sum / 2;
529        if next >= guess { break; }
530        guess = next;
531    }
532    guess
533}
534
535/// Greatest common divisor via Euclidean algorithm.
536/// Panics if either argument is i64::MIN (|MIN| overflows i64).
537pub fn gcd(mut a: Int, mut b: Int) -> Int {
538    a = a.checked_abs().unwrap_or_else(|| {
539        panic!("runar: i64 overflow in gcd — |i64::MIN| not representable; Bitcoin Script supports arbitrary precision but Rust tests use i64")
540    });
541    b = b.checked_abs().unwrap_or_else(|| {
542        panic!("runar: i64 overflow in gcd — |i64::MIN| not representable; Bitcoin Script supports arbitrary precision but Rust tests use i64")
543    });
544    while b != 0 { let t = b; b = a % b; a = t; }
545    a
546}
547
548/// Division returning quotient.
549pub fn divmod(a: Int, b: Int) -> Int {
550    assert!(b != 0, "divmod: division by zero");
551    a / b
552}
553
554/// Approximate floor(log2(n)).
555pub fn log2(n: Int) -> Int {
556    if n <= 0 { return 0; }
557    let mut bits: Int = 0;
558    let mut val = n;
559    while val > 1 { val >>= 1; bits += 1; }
560    bits
561}
562
563/// Boolean cast — returns true if n is non-zero.
564pub fn bool_cast(n: Int) -> bool {
565    n != 0
566}
567
568// ---------------------------------------------------------------------------
569// Test helpers
570// ---------------------------------------------------------------------------
571
572/// Returns a dummy signature for testing.
573pub fn mock_sig() -> Sig {
574    vec![0u8; 72]
575}
576
577/// Returns a dummy compressed public key for testing.
578pub fn mock_pub_key() -> PubKey {
579    let mut pk = vec![0u8; 33];
580    pk[0] = 0x02;
581    pk
582}
583
584/// Returns a dummy sighash preimage for testing.
585pub fn mock_preimage() -> SigHashPreimage {
586    vec![0u8; 181]
587}
588
589#[cfg(test)]
590mod tests {
591    use super::*;
592
593    #[test]
594    fn test_check_sig_real_ecdsa() {
595        let sig = ALICE.sign_test_message();
596        assert!(check_sig(&sig, ALICE.pub_key));
597    }
598
599    #[test]
600    fn test_check_sig_rejects_wrong_key() {
601        let sig = ALICE.sign_test_message();
602        assert!(!check_sig(&sig, BOB.pub_key));
603    }
604
605    #[test]
606    fn test_check_multi_sig_real() {
607        let alice_sig = ALICE.sign_test_message();
608        let bob_sig = BOB.sign_test_message();
609        assert!(check_multi_sig(
610            &[alice_sig.as_slice(), bob_sig.as_slice()],
611            &[ALICE.pub_key, BOB.pub_key],
612        ));
613    }
614
615    #[test]
616    fn test_check_multi_sig_rejects_wrong_order() {
617        let alice_sig = ALICE.sign_test_message();
618        let bob_sig = BOB.sign_test_message();
619        // Wrong order: alice sig checked against bob key
620        assert!(!check_multi_sig(
621            &[alice_sig.as_slice(), bob_sig.as_slice()],
622            &[BOB.pub_key, ALICE.pub_key],
623        ));
624    }
625
626    #[test]
627    fn test_check_preimage_always_true() {
628        assert!(check_preimage(&mock_preimage()));
629    }
630
631    #[test]
632    fn test_hash160_produces_20_bytes() {
633        assert_eq!(hash160(b"hello").len(), 20);
634    }
635
636    #[test]
637    fn test_hash160_deterministic() {
638        assert_eq!(hash160(b"test data"), hash160(b"test data"));
639    }
640
641    #[test]
642    fn test_hash256_produces_32_bytes() {
643        assert_eq!(hash256(b"hello").len(), 32);
644    }
645
646    #[test]
647    fn test_hash256_deterministic() {
648        assert_eq!(hash256(b"test data"), hash256(b"test data"));
649    }
650
651    #[test]
652    fn test_sha256_produces_32_bytes() {
653        assert_eq!(sha256(b"hello").len(), 32);
654    }
655
656    #[test]
657    fn test_ripemd160_produces_20_bytes() {
658        assert_eq!(ripemd160(b"hello").len(), 20);
659    }
660
661    #[test]
662    fn test_num2bin_zero() {
663        assert_eq!(num2bin(&0, 4), vec![0, 0, 0, 0]);
664    }
665
666    #[test]
667    fn test_num2bin_positive() {
668        assert_eq!(num2bin(&42, 4)[0], 42);
669    }
670
671    #[test]
672    fn test_num2bin_negative() {
673        let result = num2bin(&-42, 4);
674        assert_eq!(result[0], 42);
675        assert!(result[3] & 0x80 != 0);
676    }
677
678    #[test]
679    fn test_mock_sig_length() {
680        assert_eq!(mock_sig().len(), 72);
681    }
682
683    #[test]
684    fn test_mock_pub_key_length() {
685        let pk = mock_pub_key();
686        assert_eq!(pk.len(), 33);
687        assert_eq!(pk[0], 0x02);
688    }
689
690    // -----------------------------------------------------------------------
691    // Overflow boundary tests — i64 limitation detection
692    // -----------------------------------------------------------------------
693
694    #[test]
695    fn test_pow_small_values() {
696        assert_eq!(pow(2, 10), 1024);
697        assert_eq!(pow(3, 0), 1);
698    }
699
700    #[test]
701    #[should_panic(expected = "i64 overflow")]
702    fn test_pow_overflow() {
703        pow(i64::MAX, 2);
704    }
705
706    #[test]
707    fn test_mul_div_small_values() {
708        assert_eq!(mul_div(100, 3, 2), 150);
709    }
710
711    #[test]
712    #[should_panic(expected = "i64 overflow")]
713    fn test_mul_div_overflow() {
714        mul_div(i64::MAX, 2, 1);
715    }
716
717    #[test]
718    fn test_percent_of_small_values() {
719        assert_eq!(percent_of(10000, 2500), 2500);
720    }
721
722    #[test]
723    #[should_panic(expected = "i64 overflow")]
724    fn test_percent_of_overflow() {
725        percent_of(i64::MAX, 5000);
726    }
727
728    #[test]
729    fn test_gcd_small_values() {
730        assert_eq!(gcd(12, 8), 4);
731    }
732
733    #[test]
734    #[should_panic(expected = "i64 overflow")]
735    fn test_gcd_min_panics() {
736        gcd(i64::MIN, 1);
737    }
738
739    // -----------------------------------------------------------------------
740    // safediv
741    // -----------------------------------------------------------------------
742
743    #[test]
744    fn test_safediv_positive() {
745        // 10 / 3 truncates toward zero in Rust
746        assert_eq!(safediv(10, 3), 3);
747    }
748
749    #[test]
750    fn test_safediv_truncates_toward_zero() {
751        // Rust integer division truncates toward zero: -7 / 2 == -3 (not -4)
752        assert_eq!(safediv(-7, 2), -3);
753    }
754
755    #[test]
756    #[should_panic(expected = "safediv: division by zero")]
757    fn test_safediv_by_zero_panics() {
758        safediv(42, 0);
759    }
760
761    // -----------------------------------------------------------------------
762    // safemod
763    // -----------------------------------------------------------------------
764
765    #[test]
766    fn test_safemod_positive() {
767        assert_eq!(safemod(10, 3), 1);
768    }
769
770    #[test]
771    fn test_safemod_negative() {
772        // Rust % follows the sign of the dividend: -7 % 2 == -1
773        assert_eq!(safemod(-7, 2), -1);
774    }
775
776    // -----------------------------------------------------------------------
777    // clamp
778    // -----------------------------------------------------------------------
779
780    #[test]
781    fn test_clamp_within_range() {
782        assert_eq!(clamp(5, 0, 10), 5);
783    }
784
785    #[test]
786    fn test_clamp_below() {
787        assert_eq!(clamp(-1, 0, 10), 0);
788    }
789
790    #[test]
791    fn test_clamp_above() {
792        assert_eq!(clamp(15, 0, 10), 10);
793    }
794
795    // -----------------------------------------------------------------------
796    // sign
797    // -----------------------------------------------------------------------
798
799    #[test]
800    fn test_sign_positive() {
801        assert_eq!(sign(42), 1);
802    }
803
804    #[test]
805    fn test_sign_negative() {
806        assert_eq!(sign(-42), -1);
807    }
808
809    #[test]
810    fn test_sign_zero() {
811        assert_eq!(sign(0), 0);
812    }
813
814    // -----------------------------------------------------------------------
815    // sqrt
816    // -----------------------------------------------------------------------
817
818    #[test]
819    fn test_sqrt_perfect_square() {
820        assert_eq!(sqrt(9), 3);
821    }
822
823    #[test]
824    fn test_sqrt_non_perfect() {
825        // floor(sqrt(10)) == 3
826        assert_eq!(sqrt(10), 3);
827    }
828
829    // -----------------------------------------------------------------------
830    // log2
831    // -----------------------------------------------------------------------
832
833    #[test]
834    fn test_log2_power_of_two() {
835        assert_eq!(log2(8), 3);
836    }
837
838    #[test]
839    fn test_log2_non_power() {
840        // floor(log2(9)) == 3
841        assert_eq!(log2(9), 3);
842    }
843
844    // -----------------------------------------------------------------------
845    // SHA-256 compress / finalize
846    // -----------------------------------------------------------------------
847
848    /// Decode a hex string to bytes.
849    fn hex_decode(s: &str) -> Vec<u8> {
850        assert!(s.len() % 2 == 0, "hex string must have even length");
851        (0..s.len())
852            .step_by(2)
853            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
854            .collect()
855    }
856
857    /// Encode bytes as a lowercase hex string.
858    fn hex_encode(data: &[u8]) -> String {
859        data.iter().map(|b| format!("{:02x}", b)).collect()
860    }
861
862    #[test]
863    fn test_sha256_compress_abc() {
864        let state = hex_decode("6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19");
865        let block = hex_decode(
866            "6162638000000000000000000000000000000000000000000000000000000000\
867             0000000000000000000000000000000000000000000000000000000000000018"
868        );
869        let result = sha256_compress(&state, &block);
870        assert_eq!(
871            hex_encode(&result),
872            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
873        );
874    }
875
876    #[test]
877    fn test_sha256_finalize_abc() {
878        let state = hex_decode("6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19");
879        let result = sha256_finalize(&state, b"abc", 24);
880        assert_eq!(
881            hex_encode(&result),
882            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
883        );
884    }
885
886    #[test]
887    fn test_sha256_finalize_empty() {
888        let state = hex_decode("6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19");
889        let result = sha256_finalize(&state, b"", 0);
890        assert_eq!(
891            hex_encode(&result),
892            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
893        );
894    }
895
896    #[test]
897    fn test_sha256_finalize_cross_verify() {
898        let state = hex_decode("6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19");
899        for msg in &["", "abc", "hello world"] {
900            let finalized = sha256_finalize(&state, msg.as_bytes(), (msg.len() * 8) as i64);
901            let hashed = sha256(msg.as_bytes());
902            assert_eq!(finalized, hashed, "mismatch for {:?}", msg);
903        }
904    }
905}