Skip to main content

blue_lang_runtime/
crypto.rs

1//! Real cryptography — BLAKE3 hashing and Ed25519 signatures — as blue's own
2//! surface.
3//!
4//! ```text
5//! blake3_hex(data)                               → 64 hex chars (BLAKE3-256)
6//! ed25519_keypair(seed_hex)                      → [secret_hex, public_hex]
7//! ed25519_sign(secret_hex, message)              → 128 hex chars
8//! ed25519_verify(public_hex, message, sig_hex)   → true / false
9//! ```
10//!
11//! `data` and `message` are a string (its UTF-8 bytes) or a list of byte
12//! integers `0..=255`. Keys and signatures are hex, in and out: blue has no
13//! byte type, and hex is the one spelling of a key that survives a JSON
14//! document, a log line and a terminal unchanged. Output is lowercase; input
15//! accepts either case.
16//!
17//! # Pure layer, not `sys` — and why that is a decision, not a default
18//!
19//! Every function here is a deterministic function of its arguments. None
20//! reads a clock, a file, the environment or an entropy source, so none is a
21//! host effect, and the `sys` layer is reserved for exactly those (it is
22//! feature-gated because each of its names is a host import). These live
23//! beside `json`: installed unconditionally, lowering to **no import**, kept by
24//! the `wasm32-unknown-unknown` consumer.
25//!
26//! The one place that could have forced them into `sys` is key generation,
27//! which normally draws from the OS. It does not here: `ed25519_keypair` takes
28//! the 32-byte seed from the CALLER. That is what keeps the layer pure, and it
29//! is also what makes it testable — RFC 8032's vectors are seeds, so a
30//! deterministic keypair is the only kind a test can check against them. Where
31//! a caller's seed comes from (a file, a secret store, a future host entropy
32//! primitive) is a host question and belongs in `sys` when it is asked.
33//!
34//! # No C, and the crates chosen for it
35//!
36//! Ed25519 is `ed25519-dalek` over `curve25519-dalek`; neither compiles C.
37//! BLAKE3 is the official `blake3` crate with its **`pure`** feature, which
38//! turns off the C and assembly SIMD builds its `build.rs` otherwise performs
39//! (on aarch64 it compiled NEON C intrinsics into this very crate before this
40//! module existed — `inputs` already hashed with it). Features are pinned
41//! explicitly in `Cargo.toml`, so a default that links C cannot arrive on a
42//! version bump. The irreducible remainder is stated rather than hidden:
43//! `cc` is still in the dependency tree, as an unconditional
44//! `[build-dependencies]` entry of `blake3` that Cargo cannot drop, and on
45//! x86 `blake3`'s build script still asks the C compiler whether it accepts
46//! `-mavx512f` before choosing the Rust implementation. No C object is built
47//! or linked on any target.
48//!
49//! # A typed error on bad input, never a silent `false`
50//!
51//! `ed25519_verify` answers `false` for exactly one thing: a well-formed
52//! signature that does not verify. A key that is not hex, a signature of the
53//! wrong length, a public key that is not a curve point, a message that is
54//! neither text nor bytes — each RAISES, naming what was wrong. A verifier that
55//! folded "you passed me garbage" into "the signature is bad" would let a
56//! wiring bug read as a forgery caught, and a forgery caught read as a wiring
57//! bug. [`CryptoError`] is the closed set of those refusals, with one
58//! `Display`.
59//!
60//! # Strict verification
61//!
62//! Verification is `verify_strict`: it rejects non-canonical signatures and
63//! small-order public keys, which the permissive `verify` accepts. A signed
64//! event log wants one valid signature per (key, message), not several, and a
65//! small-order key "verifies" signatures nobody made. A small-order key is
66//! refused as bad input ([`CryptoError::WeakPublicKey`]) rather than answered
67//! `false`, because no message could ever verify under it.
68//!
69//! # What this does not protect
70//!
71//! The secret key arrives and leaves as a blue string. `SigningKey` zeroizes
72//! its own copy on drop, but the interpreter's string values are not zeroized,
73//! so a secret passed through blue lives in ordinary heap memory until it is
74//! reclaimed. This layer computes signatures correctly; it is not a key vault.
75
76use std::sync::Arc;
77
78use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
79use hmac::{Hmac, Mac};
80use sha1::Sha1;
81use sha2::{Digest, Sha256, Sha512};
82use tatara_lisp_eval::ffi::Arity;
83use tatara_lisp_eval::{EvalError, Interpreter, Value};
84
85/// Bytes in an Ed25519 secret key (the RFC 8032 seed) and in a public key.
86pub const KEY_BYTES: usize = 32;
87/// Bytes in an Ed25519 signature.
88pub const SIGNATURE_BYTES: usize = 64;
89
90/// Every way an argument to this layer can be refused.
91///
92/// Closed, and each arm reads differently, so a reader of the raised message is
93/// sent to the one thing that was wrong.
94#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
95pub enum CryptoError {
96    /// A key or signature argument that is not a string at all.
97    #[error("the {what} must be a hex string; got {got}")]
98    NotHexText {
99        what: &'static str,
100        got: &'static str,
101    },
102    /// A character outside `0-9a-fA-F`, at a character position.
103    #[error("the {what} is not hex: character {position} is `{found}`")]
104    NotHex {
105        what: &'static str,
106        position: usize,
107        found: char,
108    },
109    /// An odd number of hex digits cannot be whole bytes.
110    #[error("the {what} has an odd number of hex digits ({digits})")]
111    OddHex { what: &'static str, digits: usize },
112    /// Well-formed hex of the wrong size.
113    #[error("the {what} is {got} bytes; an Ed25519 {what} is {expected}")]
114    WrongLength {
115        what: &'static str,
116        expected: usize,
117        got: usize,
118    },
119    /// Something that is neither text nor a byte list.
120    #[error("a message is a string or a list of bytes; got {got}")]
121    NotAMessage { got: &'static str },
122    /// A byte-list element that is not an integer.
123    #[error("element {index} of the byte list is not an integer; got {got}")]
124    NotAByte { index: usize, got: &'static str },
125    /// A byte-list element outside `0..=255`.
126    #[error("element {index} of the byte list is {value}, outside 0..=255")]
127    ByteOutOfRange { index: usize, value: i64 },
128    /// 32 bytes that do not decompress to a point on the curve.
129    #[error("the public key is not a point on the Ed25519 curve")]
130    NotACurvePoint,
131    /// A point of small order: every signature under it is meaningless.
132    #[error("the public key has small order and cannot vouch for any message")]
133    WeakPublicKey,
134    #[error("the algorithm must be a string or keyword; got {got}")]
135    NotAnAlgorithm { got: &'static str },
136    #[error("unknown algorithm `{name}`; the built ones are {known}")]
137    UnknownAlgorithm { name: String, known: &'static str },
138    #[error("{name} has no keyed form here; a keyed digest is one of {known}")]
139    NoMac {
140        name: &'static str,
141        known: &'static str,
142    },
143    #[error("the key is not usable as a keyed-digest key")]
144    BadMacKey,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum Algorithm {
149    Sha1,
150    Sha256,
151    Sha512,
152    Blake3,
153}
154
155impl Algorithm {
156    pub const DIGESTS: &'static str = "sha1, sha256, sha512, blake3";
157    pub const MACS: &'static str = "sha1, sha256, sha512";
158
159    pub fn parse(name: &str) -> Result<Self, CryptoError> {
160        match name {
161            "sha1" => Ok(Algorithm::Sha1),
162            "sha256" => Ok(Algorithm::Sha256),
163            "sha512" => Ok(Algorithm::Sha512),
164            "blake3" => Ok(Algorithm::Blake3),
165            other => Err(CryptoError::UnknownAlgorithm {
166                name: other.to_string(),
167                known: Self::DIGESTS,
168            }),
169        }
170    }
171
172    pub fn from_value(v: &Value) -> Result<Self, CryptoError> {
173        match v {
174            Value::Str(s) => Self::parse(s),
175            Value::Keyword(k) => Self::parse(k),
176            other => Err(CryptoError::NotAnAlgorithm {
177                got: other.type_name(),
178            }),
179        }
180    }
181
182    pub fn name(self) -> &'static str {
183        match self {
184            Algorithm::Sha1 => "sha1",
185            Algorithm::Sha256 => "sha256",
186            Algorithm::Sha512 => "sha512",
187            Algorithm::Blake3 => "blake3",
188        }
189    }
190}
191
192/// The bytes a message or hash input stands for.
193///
194/// A string is its UTF-8 bytes, so `"r"` and `[114]` are one message — bytes
195/// are bytes, whichever way they were spelled. `nil` is refused rather than
196/// read as empty: blue has two empties (`nil != []`), and a message that
197/// silently became nil is a bug to surface, not an empty string to sign.
198pub fn message_bytes(v: &Value) -> Result<Vec<u8>, CryptoError> {
199    match v {
200        Value::Str(s) => Ok(s.as_bytes().to_vec()),
201        Value::List(items) => items
202            .iter()
203            .enumerate()
204            .map(|(index, item)| match item {
205                Value::Int(n) => {
206                    u8::try_from(*n).map_err(|_| CryptoError::ByteOutOfRange { index, value: *n })
207                }
208                other => Err(CryptoError::NotAByte {
209                    index,
210                    got: other.type_name(),
211                }),
212            })
213            .collect(),
214        other => Err(CryptoError::NotAMessage {
215            got: other.type_name(),
216        }),
217    }
218}
219
220pub fn hex_vec(v: &Value, what: &'static str) -> Result<Vec<u8>, CryptoError> {
221    match v {
222        Value::Str(s) => decode_hex(s, what),
223        other => Err(CryptoError::NotHexText {
224            what,
225            got: other.type_name(),
226        }),
227    }
228}
229
230/// Decode a hex argument of exactly `N` bytes.
231pub fn hex_array<const N: usize>(v: &Value, what: &'static str) -> Result<[u8; N], CryptoError> {
232    let bytes = hex_vec(v, what)?;
233    <[u8; N]>::try_from(bytes.as_slice()).map_err(|_| CryptoError::WrongLength {
234        what,
235        expected: N,
236        got: bytes.len(),
237    })
238}
239
240/// Strict hex: digits only, either case, an even count. No `0x`, no spaces.
241fn decode_hex(text: &str, what: &'static str) -> Result<Vec<u8>, CryptoError> {
242    let digits: Vec<u8> = text
243        .chars()
244        .enumerate()
245        .map(|(position, found)| {
246            found
247                .to_digit(16)
248                // `to_digit(16)` answers at most 15, so the narrowing is exact.
249                .map(|d| d as u8)
250                .ok_or(CryptoError::NotHex {
251                    what,
252                    position,
253                    found,
254                })
255        })
256        .collect::<Result<_, _>>()?;
257    if !digits.len().is_multiple_of(2) {
258        return Err(CryptoError::OddHex {
259            what,
260            digits: digits.len(),
261        });
262    }
263    Ok(digits
264        .as_chunks::<2>()
265        .0
266        .iter()
267        .map(|[hi, lo]| (hi << 4) | lo)
268        .collect())
269}
270
271/// Lowercase hex, the one output spelling.
272pub fn encode_hex(bytes: &[u8]) -> String {
273    const DIGITS: &[u8; 16] = b"0123456789abcdef";
274    let mut out = String::with_capacity(bytes.len() * 2);
275    for b in bytes {
276        out.push(char::from(DIGITS[usize::from(b >> 4)]));
277        out.push(char::from(DIGITS[usize::from(b & 0x0f)]));
278    }
279    out
280}
281
282/// BLAKE3-256 of `data`, as lowercase hex.
283pub fn blake3_hex(data: &[u8]) -> String {
284    blake3::hash(data).to_hex().to_string()
285}
286
287/// The public key for an RFC 8032 secret key (the 32-byte seed).
288pub fn ed25519_public(secret: &[u8; KEY_BYTES]) -> [u8; KEY_BYTES] {
289    SigningKey::from_bytes(secret).verifying_key().to_bytes()
290}
291
292/// Sign `message` with an RFC 8032 secret key. Ed25519 signing is
293/// deterministic: one (key, message) pair has one signature.
294pub fn ed25519_sign(secret: &[u8; KEY_BYTES], message: &[u8]) -> [u8; SIGNATURE_BYTES] {
295    SigningKey::from_bytes(secret).sign(message).to_bytes()
296}
297
298/// Verify strictly. `Ok(false)` is a well-formed signature that does not
299/// verify; `Err` is input that could not have verified anything.
300pub fn ed25519_verify(
301    public: &[u8; KEY_BYTES],
302    message: &[u8],
303    signature: &[u8; SIGNATURE_BYTES],
304) -> Result<bool, CryptoError> {
305    let key = VerifyingKey::from_bytes(public).map_err(|_| CryptoError::NotACurvePoint)?;
306    if key.is_weak() {
307        return Err(CryptoError::WeakPublicKey);
308    }
309    let signature = Signature::from_bytes(signature);
310    Ok(key.verify_strict(message, &signature).is_ok())
311}
312
313pub fn digest_hex(algorithm: Algorithm, data: &[u8]) -> String {
314    match algorithm {
315        Algorithm::Sha1 => encode_hex(&Sha1::digest(data)),
316        Algorithm::Sha256 => encode_hex(&Sha256::digest(data)),
317        Algorithm::Sha512 => encode_hex(&Sha512::digest(data)),
318        Algorithm::Blake3 => blake3_hex(data),
319    }
320}
321
322fn mac_hex<M: Mac>(
323    key: &[u8],
324    data: &[u8],
325    new: fn(&[u8]) -> Result<M, hmac::digest::InvalidLength>,
326) -> Result<String, CryptoError> {
327    let mut mac = new(key).map_err(|_| CryptoError::BadMacKey)?;
328    mac.update(data);
329    Ok(encode_hex(&mac.finalize().into_bytes()))
330}
331
332pub fn hmac_hex(algorithm: Algorithm, key: &[u8], data: &[u8]) -> Result<String, CryptoError> {
333    match algorithm {
334        Algorithm::Sha1 => mac_hex(key, data, Hmac::<Sha1>::new_from_slice),
335        Algorithm::Sha256 => mac_hex(key, data, Hmac::<Sha256>::new_from_slice),
336        Algorithm::Sha512 => mac_hex(key, data, Hmac::<Sha512>::new_from_slice),
337        Algorithm::Blake3 => Err(CryptoError::NoMac {
338            name: algorithm.name(),
339            known: Algorithm::MACS,
340        }),
341    }
342}
343
344pub fn scan_differences(a: &[u8], b: &[u8]) -> (u8, usize) {
345    let width = a.len().max(b.len());
346    let mut acc = 0u8;
347    let mut visited = 0usize;
348    for i in 0..width {
349        let x = a.get(i).copied().unwrap_or(0);
350        let y = b.get(i).copied().unwrap_or(0);
351        acc |= x ^ y;
352        visited += 1;
353    }
354    (acc, visited)
355}
356
357pub fn bytes_equal(a: &[u8], b: &[u8]) -> bool {
358    let (acc, _) = scan_differences(a, b);
359    let lengths = (a.len() ^ b.len()) as u64;
360    std::hint::black_box(acc) == 0 && std::hint::black_box(lengths) == 0
361}
362
363/// Raise a refusal as the named primitive's error.
364fn refuse(name: &'static str, span: tatara_lisp::Span) -> impl Fn(CryptoError) -> EvalError {
365    move |e| EvalError::native_fn(name, e.to_string(), span)
366}
367
368fn hex_value(bytes: &[u8]) -> Value {
369    Value::Str(Arc::from(encode_hex(bytes)))
370}
371
372/// Install blue's cryptography surface.
373pub fn install_crypto_stdlib<H: 'static>(interp: &mut Interpreter<H>) {
374    interp.register_fn(
375        "blake3_hex",
376        Arity::Exact(1),
377        |a: &[Value], _h: &mut H, span| {
378            let data = message_bytes(&a[0]).map_err(refuse("blake3_hex", span))?;
379            Ok(Value::Str(Arc::from(blake3_hex(&data))))
380        },
381    );
382
383    interp.register_fn(
384        "digest_hex",
385        Arity::Exact(2),
386        |a: &[Value], _h: &mut H, span| {
387            let algorithm = Algorithm::from_value(&a[0]).map_err(refuse("digest_hex", span))?;
388            let data = message_bytes(&a[1]).map_err(refuse("digest_hex", span))?;
389            Ok(Value::Str(Arc::from(digest_hex(algorithm, &data))))
390        },
391    );
392
393    interp.register_fn(
394        "hmac_hex",
395        Arity::Exact(3),
396        |a: &[Value], _h: &mut H, span| {
397            let algorithm = Algorithm::from_value(&a[0]).map_err(refuse("hmac_hex", span))?;
398            let key = message_bytes(&a[1]).map_err(refuse("hmac_hex", span))?;
399            let data = message_bytes(&a[2]).map_err(refuse("hmac_hex", span))?;
400            let tag = hmac_hex(algorithm, &key, &data).map_err(refuse("hmac_hex", span))?;
401            Ok(Value::Str(Arc::from(tag)))
402        },
403    );
404
405    interp.register_fn(
406        "bytes_equal?",
407        Arity::Exact(2),
408        |a: &[Value], _h: &mut H, span| {
409            let left = message_bytes(&a[0]).map_err(refuse("bytes_equal?", span))?;
410            let right = message_bytes(&a[1]).map_err(refuse("bytes_equal?", span))?;
411            Ok(Value::Bool(bytes_equal(&left, &right)))
412        },
413    );
414
415    interp.register_fn(
416        "ed25519_keypair",
417        Arity::Exact(1),
418        |a: &[Value], _h: &mut H, span| {
419            let seed: [u8; KEY_BYTES] =
420                hex_array(&a[0], "seed").map_err(refuse("ed25519_keypair", span))?;
421            Ok(Value::list([
422                hex_value(&seed),
423                hex_value(&ed25519_public(&seed)),
424            ]))
425        },
426    );
427
428    interp.register_fn(
429        "ed25519_sign",
430        Arity::Exact(2),
431        |a: &[Value], _h: &mut H, span| {
432            let secret: [u8; KEY_BYTES] =
433                hex_array(&a[0], "secret key").map_err(refuse("ed25519_sign", span))?;
434            let message = message_bytes(&a[1]).map_err(refuse("ed25519_sign", span))?;
435            Ok(hex_value(&ed25519_sign(&secret, &message)))
436        },
437    );
438
439    interp.register_fn(
440        "ed25519_verify",
441        Arity::Exact(3),
442        |a: &[Value], _h: &mut H, span| {
443            let public: [u8; KEY_BYTES] =
444                hex_array(&a[0], "public key").map_err(refuse("ed25519_verify", span))?;
445            let message = message_bytes(&a[1]).map_err(refuse("ed25519_verify", span))?;
446            let signature: [u8; SIGNATURE_BYTES] =
447                hex_array(&a[2], "signature").map_err(refuse("ed25519_verify", span))?;
448            let verdict = ed25519_verify(&public, &message, &signature)
449                .map_err(refuse("ed25519_verify", span))?;
450            Ok(Value::Bool(verdict))
451        },
452    );
453}
454
455#[cfg(test)]
456mod tests {
457    //! The oracle is never this module. Every expected value below is copied
458    //! from a document written by someone else: RFC 8032 §7.1 for Ed25519, and
459    //! BLAKE3's official `test_vectors/test_vectors.json` for hashing (whose
460    //! input for length `n` is the byte sequence `i % 251` for `i` in `0..n`).
461    //!
462    //! # Red runs, recorded
463    //!
464    //! Each was performed on 2026-09-24, observed to fail, and reverted.
465    //!
466    //! 1. **A signer that signs the wrong bytes.** `ed25519_sign` changed to
467    //!    sign `message` with one `0x00` byte appended: 4 red —
468    //!    `rfc8032_test_1`, `_2`, `_3` on `signature mismatch` (their public
469    //!    keys still matched, which is why keys and signatures are asserted
470    //!    separately), and `a_signature_round_trips_through_blue`, because the
471    //!    verifier was still honest. A round trip alone would NOT catch a
472    //!    signer and verifier broken the same way; the vectors would.
473    //! 2. **A verifier that always says yes.** `ed25519_verify` changed to
474    //!    return `Ok(true)` after the key checks: 3 red —
475    //!    `a_tampered_message_does_not_verify`, `a_wrong_key_does_not_verify`,
476    //!    `a_tampered_signature_does_not_verify`. Every vector test stayed
477    //!    green, which is the case for the controls: vectors only ever ask a
478    //!    verifier to say yes.
479    //! 3. **A hash of the wrong bytes.** `blake3_hex` changed to
480    //!    `blake3::keyed_hash` with an all-zero key: 3 red — every `blake3_*`
481    //!    vector test, the empty input included. `text_and_bytes_are_one_message`
482    //!    stayed green, as it must: it compares the function with itself, so
483    //!    it proves the byte mapping and nothing about the hash.
484    use super::*;
485
486    fn eval(src: &str) -> Value {
487        crate::run(src)
488            .unwrap_or_else(|e| panic!("{src:?}: {e}"))
489            .value
490    }
491
492    fn s(src: &str) -> String {
493        match eval(src) {
494            Value::Str(v) => v.to_string(),
495            other => panic!("{src:?} produced {other:?}"),
496        }
497    }
498
499    fn b(src: &str) -> bool {
500        match eval(src) {
501            Value::Bool(v) => v,
502            other => panic!("{src:?} produced {other:?}"),
503        }
504    }
505
506    fn err(src: &str) -> String {
507        crate::run(src)
508            .map(|r| r.value)
509            .expect_err("must raise")
510            .to_string()
511    }
512
513    /// One RFC 8032 §7.1 vector, joined from the RFC's wrapped lines.
514    struct Vector {
515        secret: &'static str,
516        public: &'static str,
517        /// The message as a blue expression.
518        message: &'static str,
519        signature: &'static str,
520    }
521
522    const TEST_1: Vector = Vector {
523        secret: "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
524        public: "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
525        message: "\"\"",
526        signature: "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155\
527                    5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
528    };
529
530    /// MESSAGE `72` — one byte, which is ASCII `r`, so it is written as text.
531    const TEST_2: Vector = Vector {
532        secret: "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
533        public: "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
534        message: "\"r\"",
535        signature: "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da\
536                    085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
537    };
538
539    /// MESSAGE `af82` — not valid UTF-8, so it can only be written as bytes.
540    /// This is the vector that exercises the byte-list path.
541    const TEST_3: Vector = Vector {
542        secret: "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
543        public: "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
544        message: "[175, 130]",
545        signature: "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac\
546                    18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
547    };
548
549    fn check_vector(v: &Vector) {
550        let pair = eval(&["ed25519_keypair(\"", v.secret, "\")"].concat());
551        let Value::List(pair) = pair else {
552            panic!("keypair must be a list, got {pair:?}")
553        };
554        assert!(matches!(&pair[0], Value::Str(x) if &**x == v.secret));
555        assert!(
556            matches!(&pair[1], Value::Str(x) if &**x == v.public),
557            "public key mismatch: {:?}",
558            pair[1]
559        );
560        let sig = s(&["ed25519_sign(\"", v.secret, "\", ", v.message, ")"].concat());
561        assert_eq!(sig, v.signature, "signature mismatch");
562        assert!(b(&[
563            "ed25519_verify(\"",
564            v.public,
565            "\", ",
566            v.message,
567            ", \"",
568            v.signature,
569            "\")"
570        ]
571        .concat()));
572    }
573
574    #[test]
575    fn rfc8032_test_1() {
576        check_vector(&TEST_1);
577    }
578
579    #[test]
580    fn rfc8032_test_2() {
581        check_vector(&TEST_2);
582    }
583
584    #[test]
585    fn rfc8032_test_3() {
586        check_vector(&TEST_3);
587    }
588
589    /// BLAKE3's official vectors: input of length `n` is `i % 251`.
590    fn official_input(n: usize) -> Vec<u8> {
591        (0..n).map(|i| (i % 251) as u8).collect()
592    }
593
594    #[test]
595    fn blake3_the_empty_input() {
596        assert_eq!(
597            s("blake3_hex(\"\")"),
598            "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
599        );
600        // The same empty input, spelled as bytes.
601        assert_eq!(
602            s("blake3_hex([])"),
603            "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
604        );
605    }
606
607    #[test]
608    fn blake3_one_byte_through_blue() {
609        assert_eq!(
610            s("blake3_hex([0])"),
611            "2d3adedff11b61f14c886e35afa036736dcd87a74d27b5c1510225d0f592e213"
612        );
613    }
614
615    /// Across the one-chunk boundary (1024 bytes) and into a multi-chunk tree,
616    /// where a SIMD or tree-hashing path would diverge first.
617    #[test]
618    fn blake3_across_the_chunk_boundary() {
619        for (n, want) in [
620            (
621                3,
622                "e1be4d7a8ab5560aa4199eea339849ba8e293d55ca0a81006726d184519e647f",
623            ),
624            (
625                1023,
626                "10108970eeda3eb932baac1428c7a2163b0e924c9a9e25b35bba72b28f70bd11",
627            ),
628            (
629                1024,
630                "42214739f095a406f3fc83deb889744ac00df831c10daa55189b5d121c855af7",
631            ),
632            (
633                1025,
634                "d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444",
635            ),
636            (
637                8193,
638                "bab6c09cb8ce8cf459261398d2e7aef35700bf488116ceb94a36d0f5f1b7bc3b",
639            ),
640        ] {
641            assert_eq!(blake3_hex(&official_input(n)), want, "input_len {n}");
642        }
643    }
644
645    /// A string hashes as its UTF-8 bytes, so text and bytes agree.
646    #[test]
647    fn text_and_bytes_are_one_message() {
648        assert_eq!(s("blake3_hex(\"abc\")"), s("blake3_hex([97, 98, 99])"));
649        // "é" is two bytes in UTF-8, not one character code.
650        assert_eq!(s("blake3_hex(\"é\")"), s("blake3_hex([195, 169])"));
651    }
652
653    #[test]
654    fn a_signature_round_trips_through_blue() {
655        let sig = s(&["ed25519_sign(\"", TEST_2.secret, "\", \"a permit\")"].concat());
656        assert!(b(&[
657            "ed25519_verify(\"",
658            TEST_2.public,
659            "\", \"a permit\", \"",
660            &sig,
661            "\")"
662        ]
663        .concat()));
664    }
665
666    // ── controls: what must NOT verify ─────────────────────────────────
667
668    #[test]
669    fn a_tampered_message_does_not_verify() {
670        // TEST 2 signs "r"; the same signature over "s" must fail.
671        assert!(!b(&[
672            "ed25519_verify(\"",
673            TEST_2.public,
674            "\", \"s\", \"",
675            TEST_2.signature,
676            "\")"
677        ]
678        .concat()));
679    }
680
681    #[test]
682    fn a_wrong_key_does_not_verify() {
683        // TEST 2's signature, checked against TEST 1's key.
684        assert!(!b(&[
685            "ed25519_verify(\"",
686            TEST_1.public,
687            "\", \"r\", \"",
688            TEST_2.signature,
689            "\")"
690        ]
691        .concat()));
692    }
693
694    #[test]
695    fn a_tampered_signature_does_not_verify() {
696        // Flip the last hex digit of TEST 1's signature.
697        let mut forged = TEST_1.signature.to_string();
698        forged.pop();
699        forged.push('a');
700        assert_ne!(forged, TEST_1.signature);
701        assert!(!b(&[
702            "ed25519_verify(\"",
703            TEST_1.public,
704            "\", \"\", \"",
705            &forged,
706            "\")"
707        ]
708        .concat()));
709    }
710
711    // ── bad input raises, and names what was wrong ─────────────────────
712
713    #[test]
714    fn a_short_key_is_a_named_error_not_false() {
715        let e = err("ed25519_verify(\"abcd\", \"m\", \"00\")");
716        assert!(e.contains("ed25519_verify"), "{e}");
717        assert!(e.contains("public key is 2 bytes"), "{e}");
718    }
719
720    #[test]
721    fn non_hex_is_a_named_error() {
722        let bad = ["\"zz", &TEST_1.secret[2..], "\""].concat();
723        let e = err(&["ed25519_sign(", &bad, ", \"m\")"].concat());
724        assert!(e.contains("not hex: character 0 is `z`"), "{e}");
725    }
726
727    #[test]
728    fn odd_hex_is_a_named_error() {
729        let e = err("ed25519_keypair(\"abc\")");
730        assert!(e.contains("odd number of hex digits (3)"), "{e}");
731    }
732
733    #[test]
734    fn a_key_that_is_not_a_string_is_a_named_error() {
735        let e = err("ed25519_keypair(42)");
736        assert!(e.contains("seed must be a hex string; got int"), "{e}");
737    }
738
739    #[test]
740    fn a_non_message_is_a_named_error() {
741        let e = err("blake3_hex(nil)");
742        assert!(
743            e.contains("a message is a string or a list of bytes"),
744            "{e}"
745        );
746        let e = err("blake3_hex(42)");
747        assert!(e.contains("; got int"), "{e}");
748    }
749
750    #[test]
751    fn a_byte_out_of_range_is_a_named_error() {
752        let e = err("blake3_hex([1, 256])");
753        assert!(e.contains("element 1 of the byte list is 256"), "{e}");
754        let e = err("blake3_hex([1, \"x\"])");
755        assert!(
756            e.contains("element 1 of the byte list is not an integer; got string"),
757            "{e}"
758        );
759    }
760
761    /// Uppercase hex is accepted; output is always lowercase.
762    #[test]
763    fn hex_input_is_case_insensitive_and_output_is_lowercase() {
764        let upper = TEST_1.secret.to_uppercase();
765        let pair = eval(&["ed25519_keypair(\"", &upper, "\")"].concat());
766        let Value::List(pair) = pair else { panic!() };
767        assert!(matches!(&pair[0], Value::Str(x) if &**x == TEST_1.secret));
768        assert!(matches!(&pair[1], Value::Str(x) if &**x == TEST_1.public));
769    }
770
771    /// A 32-byte string that is not a compressed curve point is refused, not
772    /// answered `false`. `y = 2` has no square root for `x` on Ed25519, which
773    /// is a property of the curve and not of this code.
774    #[test]
775    fn a_public_key_off_the_curve_is_a_named_error() {
776        let mut off = [0u8; KEY_BYTES];
777        off[0] = 2;
778        assert!(
779            VerifyingKey::from_bytes(&off).is_err(),
780            "control: y=2 is off-curve"
781        );
782        let e = err(&[
783            "ed25519_verify(\"",
784            &encode_hex(&off),
785            "\", \"m\", \"",
786            TEST_1.signature,
787            "\")",
788        ]
789        .concat());
790        assert!(e.contains("not a point on the Ed25519 curve"), "{e}");
791    }
792
793    /// The identity point (encoded `01 00 … 00`) has order 1 — the weakest key
794    /// there is — and must be refused before any signature is considered.
795    #[test]
796    fn a_small_order_public_key_is_a_named_error() {
797        let mut identity = [0u8; KEY_BYTES];
798        identity[0] = 1;
799        let key = VerifyingKey::from_bytes(&identity).expect("the identity decompresses");
800        assert!(key.is_weak(), "control: the identity is small-order");
801        let e = err(&[
802            "ed25519_verify(\"",
803            &encode_hex(&identity),
804            "\", \"m\", \"",
805            TEST_1.signature,
806            "\")",
807        ]
808        .concat());
809        assert!(e.contains("small order"), "{e}");
810    }
811
812    #[test]
813    fn hex_round_trips() {
814        let all: Vec<u8> = (0..=255).collect();
815        let text = encode_hex(&all);
816        assert_eq!(decode_hex(&text, "t").expect("round trip"), all);
817        assert_eq!(decode_hex("", "t").expect("empty"), Vec::<u8>::new());
818    }
819
820    const SHA: [(&str, &str, &str, &str); 3] = [
821        ("sha1", "da39a3ee5e6b4b0d3255bfef95601890afd80709", "a9993e364706816aba3e25717850c26c9cd0d89d", "6244e66451a1c8695db9731ce2c4fd5de25ccf87"),
822        ("sha256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "4cbc96099a6467ce002461f10549b4898265ebe6188b45efacc44293516e62c4"),
823        ("sha512", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", "aee650458ddcbcd4557531b27f1cded4c577f576079b086f57f3c6b9e037a7c0b3cdf7ff58afa09fe279076d1107d5d554f4d34fc6f03d8fd12f93d892802ac3"),
824    ];
825
826    fn bytes_literal(byte: u8, count: usize) -> String {
827        format!("[{}]", vec![byte.to_string(); count].join(", "))
828    }
829
830    #[test]
831    fn digests_match_fips_180_for_the_empty_input_and_abc() {
832        for (name, empty, abc, _) in SHA {
833            assert_eq!(
834                s(&format!("digest_hex(\"{name}\", \"\")")),
835                empty,
836                "{name} empty"
837            );
838            assert_eq!(
839                s(&format!("digest_hex(\"{name}\", \"abc\")")),
840                abc,
841                "{name} abc"
842            );
843        }
844    }
845
846    #[test]
847    fn digests_take_text_and_bytes_and_a_keyword_name() {
848        for (name, _, abc, _) in SHA {
849            assert_eq!(
850                s(&format!("digest_hex(\"{name}\", [97, 98, 99])")),
851                abc,
852                "{name} bytes"
853            );
854            assert_eq!(
855                s(&format!("digest_hex(:{name}, \"abc\")")),
856                abc,
857                "{name} keyword"
858            );
859        }
860        assert_eq!(s("digest_hex(\"blake3\", \"\")"), s("blake3_hex(\"\")"));
861    }
862
863    #[test]
864    fn digests_differ_by_algorithm_and_by_input() {
865        let all: std::collections::BTreeSet<String> = ["sha1", "sha256", "sha512", "blake3"]
866            .iter()
867            .map(|n| s(&format!("digest_hex(\"{n}\", \"abc\")")))
868            .collect();
869        assert_eq!(all.len(), 4);
870        assert_ne!(
871            s("digest_hex(\"sha256\", \"abc\")"),
872            s("digest_hex(\"sha256\", \"abd\")")
873        );
874    }
875
876    #[test]
877    fn digest_hex_refuses_what_it_cannot_place() {
878        assert!(err("digest_hex(\"md5\", \"x\")").contains("unknown algorithm `md5`"));
879        assert!(err("digest_hex(\"SHA256\", \"x\")").contains("unknown algorithm"));
880        assert!(err("digest_hex(256, \"x\")").contains("string or keyword"));
881        assert!(err("digest_hex(nil, \"x\")").contains("string or keyword"));
882        assert!(err("digest_hex(\"sha256\", nil)").contains("string or a list of bytes"));
883        assert!(err("digest_hex(\"sha256\", [256])").contains("outside 0..=255"));
884        assert!(err("digest_hex(\"sha256\")").contains("digest_hex"));
885    }
886
887    #[test]
888    fn hmac_matches_rfc_4231_and_rfc_2202() {
889        let key = bytes_literal(0x0b, 20);
890        let long_key = bytes_literal(0xaa, 131);
891        let cases: [(&str, &str, &str, &str); 3] = [
892            ("sha1", "b617318655057264e28bc0b6fb378c8ef146be00", "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79", "90d0dace1c1bdc957339307803160335bde6df2b"),
893            ("sha256", "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"),
894            ("sha512", "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854", "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737", "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"),
895        ];
896        for (name, one, two, long) in cases {
897            assert_eq!(
898                s(&format!("hmac_hex(\"{name}\", {key}, \"Hi There\")")),
899                one,
900                "{name} case 1"
901            );
902            assert_eq!(
903                s(&format!(
904                    "hmac_hex(:{name}, \"Jefe\", \"what do ya want for nothing?\")"
905                )),
906                two,
907                "{name} case 2"
908            );
909            assert_eq!(
910                s(&format!("hmac_hex(\"{name}\", {long_key}, \"Test Using Larger Than Block-Size Key - Hash Key First\")")),
911                long,
912                "{name} long key"
913            );
914        }
915    }
916
917    #[test]
918    fn hmac_with_an_empty_key_is_defined_and_differs_from_the_plain_digest() {
919        for (name, _, _, empty_key) in SHA {
920            assert_eq!(
921                s(&format!("hmac_hex(\"{name}\", \"\", \"x\")")),
922                empty_key,
923                "{name}"
924            );
925            assert_ne!(
926                s(&format!("hmac_hex(\"{name}\", \"\", \"x\")")),
927                s(&format!("digest_hex(\"{name}\", \"x\")"))
928            );
929        }
930        assert_eq!(
931            s("hmac_hex(\"sha256\", [], \"x\")"),
932            s("hmac_hex(\"sha256\", \"\", \"x\")")
933        );
934    }
935
936    #[test]
937    fn hmac_depends_on_the_key_and_the_data() {
938        let base = s("hmac_hex(\"sha256\", \"k\", \"m\")");
939        assert_ne!(base, s("hmac_hex(\"sha256\", \"k2\", \"m\")"));
940        assert_ne!(base, s("hmac_hex(\"sha256\", \"k\", \"m2\")"));
941    }
942
943    #[test]
944    fn hmac_refuses_blake3_unknown_names_and_bad_arguments() {
945        assert!(err("hmac_hex(\"blake3\", \"k\", \"m\")").contains("no keyed form"));
946        assert!(err("hmac_hex(\"md5\", \"k\", \"m\")").contains("unknown algorithm"));
947        assert!(err("hmac_hex(1, \"k\", \"m\")").contains("string or keyword"));
948        assert!(err("hmac_hex(\"sha256\", nil, \"m\")").contains("string or a list of bytes"));
949        assert!(err("hmac_hex(\"sha256\", \"k\", nil)").contains("string or a list of bytes"));
950        assert!(err("hmac_hex(\"sha256\", \"k\")").contains("hmac_hex"));
951    }
952
953    #[test]
954    fn bytes_equal_answers_for_text_bytes_and_empties() {
955        assert!(b("bytes_equal?(\"abc\", \"abc\")"));
956        assert!(b("bytes_equal?(\"abc\", [97, 98, 99])"));
957        assert!(b("bytes_equal?(\"\", [])"));
958        assert!(!b("bytes_equal?(\"abc\", \"abd\")"));
959        assert!(!b("bytes_equal?(\"abc\", \"ab\")"));
960        assert!(!b("bytes_equal?(\"ab\", \"abc\")"));
961        assert!(!b("bytes_equal?(\"\", \"a\")"));
962        assert!(!b("bytes_equal?([1], [1, 0])"));
963    }
964
965    #[test]
966    fn bytes_equal_refuses_what_is_not_a_message() {
967        assert!(err("bytes_equal?(nil, \"a\")").contains("string or a list of bytes"));
968        assert!(err("bytes_equal?(\"a\", 1)").contains("string or a list of bytes"));
969        assert!(err("bytes_equal?([300], \"a\")").contains("outside 0..=255"));
970        assert!(err("bytes_equal?(\"a\")").contains("bytes_equal?"));
971    }
972
973    #[test]
974    fn the_scan_visits_every_position_whatever_the_first_difference() {
975        let reference = vec![7u8; 64];
976        for first_difference in 0..64 {
977            let mut other = reference.clone();
978            other[first_difference] ^= 0xff;
979            let (acc, visited) = scan_differences(&reference, &other);
980            assert_ne!(acc, 0, "difference at {first_difference} must register");
981            assert_eq!(
982                visited, 64,
983                "stopped early when the first difference was at {first_difference}"
984            );
985        }
986        let (acc, visited) = scan_differences(&reference, &reference);
987        assert_eq!((acc, visited), (0, 64));
988    }
989
990    #[test]
991    fn the_scan_covers_the_longer_side_when_lengths_differ() {
992        assert_eq!(scan_differences(b"", b"abcd").1, 4);
993        assert_eq!(scan_differences(b"abcd", b"").1, 4);
994        assert_eq!(scan_differences(b"x", b"yzzz").1, 4);
995        assert_eq!(scan_differences(b"", b"").1, 0);
996    }
997
998    #[test]
999    fn the_scan_accumulates_every_difference_not_just_the_last() {
1000        let (acc, _) = scan_differences(&[0b0001, 0b0000, 0b0000], &[0b0000, 0b0000, 0b0000]);
1001        assert_ne!(acc, 0, "a difference before equal tail bytes must survive");
1002        let (acc, _) = scan_differences(&[0b0001, 0b0010], &[0b0000, 0b0000]);
1003        assert_eq!(acc, 0b0011);
1004    }
1005}