Skip to main content

freebird_crypto/
lib.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright 2025 The Carpocratian Church of Commonality and Equality, Inc.
3
4//! Cryptographic primitives for Freebird
5//!
6//! This module provides high-level APIs for VOPRF operations using the
7//! internal P-256 implementation in voprf/.
8//!
9//! # Memory Zeroization Security
10//!
11//! Freebird implements comprehensive memory zeroization to protect cryptographic
12//! key material from memory dumps, cold boot attacks, and other extraction methods.
13//!
14//! ## Automatic Zeroization
15//!
16//! - **Scalar values (blinding factors, secret keys)**: The `Scalar` type from
17//!   RustCrypto's `elliptic-curve` crate implements `DefaultIsZeroes`, ensuring
18//!   automatic memory zeroization when dropped. This applies to:
19//!   - VOPRF blinding factors (`r` in `BlindState`)
20//!   - DLEQ proof ephemeral scalars (`r` in `prove()`)
21//!   - Secret keys in VOPRF operations
22//!
23//! - **Software provider secret keys**: The `SoftwareCryptoProvider` explicitly
24//!   zeroizes its secret key in the `Drop` implementation.
25//!
26//! - **PKCS11 provider MAC keys**: The `Pkcs11CryptoProvider` zeroizes the
27//!   `mac_base_key` derived from the HSM in its `Drop` implementation.
28//!
29//! ## Explicit Zeroization (via Zeroizing wrapper)
30//!
31//! - **MAC keys**: All MAC keys derived for token authentication are wrapped in
32//!   `Zeroizing<[u8; 32]>` to ensure they are erased immediately after use:
33//!   - Issuer token MAC computation
34//!   - Verifier token MAC verification
35//!   - Batch issuance MAC operations
36//!
37//! ## Non-Secret Values (No Zeroization)
38//!
39//! - **Elliptic curve points** (`ProjectivePoint`, `AffinePoint`): These are
40//!   public values that do not require zeroization.
41//! - **Token data**: Tokens are meant to be shared and do not contain secrets.
42//! - **Public keys**: Public keys are intentionally shareable.
43//!
44//! ## Verification
45//!
46//! To verify zeroization is working correctly, use memory analysis tools or
47//! run the zeroization tests in the test suite.
48
49use base64ct::{Base64UrlUnpadded, Encoding};
50use hmac::{Hmac, Mac};
51use sha2::{Digest, Sha256};
52
53type HmacSha256 = Hmac<Sha256>;
54
55// Internal VOPRF implementation (was vendor/voprf_p256)
56pub mod voprf;
57use voprf as v;
58
59// Cryptographic provider abstraction for software and HSM backends
60pub mod provider;
61
62#[derive(Debug)]
63pub enum Error {
64    Decode,
65    Verify,
66    Internal,
67}
68
69pub struct Client(v::Client);
70pub struct Server(v::Server);
71pub struct Verifier(v::Verifier);
72
73pub struct BlindState {
74    inner: v::BlindState,
75}
76
77/// Deterministic nullifier seed for anti-double-spend.
78pub fn nullifier_key(issuer_id: &str, token_output_b64: &str) -> String {
79    let mut h = Sha256::new();
80    h.update(issuer_id.as_bytes());
81    h.update(token_output_b64.as_bytes());
82    Base64UrlUnpadded::encode_string(&h.finalize())
83}
84
85impl Client {
86    pub fn new(ctx: &[u8]) -> Self {
87        Self(v::Client::new(ctx))
88    }
89
90    /// Blind caller-provided input bytes. Returns (blinded_b64, state).
91    pub fn blind(&mut self, input: &[u8]) -> Result<(String, BlindState), Error> {
92        let (blinded_raw, st) = self.0.blind(input).map_err(|_| Error::Internal)?;
93        Ok((
94            Base64UrlUnpadded::encode_string(&blinded_raw),
95            BlindState { inner: st },
96        ))
97    }
98
99    /// Finalize with issuer evaluation token (base64url) and issuer pubkey (SEC1 compressed).
100    /// Returns (token_b64, token_output_b64).
101    pub fn finalize(
102        self,
103        state: BlindState,
104        evaluation_b64: &str,
105        issuer_pubkey_sec1_compressed: &[u8],
106    ) -> Result<(String, String), Error> {
107        let eval_raw = Base64UrlUnpadded::decode_vec(evaluation_b64).map_err(|_| Error::Decode)?;
108        let (token_raw, out_raw) = self
109            .0
110            .finalize(state.inner, &eval_raw, issuer_pubkey_sec1_compressed)
111            .map_err(|_| Error::Verify)?;
112        Ok((
113            Base64UrlUnpadded::encode_string(&token_raw),
114            Base64UrlUnpadded::encode_string(&out_raw),
115        ))
116    }
117}
118
119impl Server {
120    pub fn from_secret_key(sk_bytes: [u8; 32], ctx: &[u8]) -> Result<Self, Error> {
121        v::Server::from_secret_key(sk_bytes, ctx)
122            .map(Self)
123            .map_err(|_| Error::Internal)
124    }
125
126    pub fn public_key_sec1_compressed(&self) -> [u8; 33] {
127        self.0.public_key_sec1_compressed()
128    }
129
130    /// Evaluate a single blinded element (base64url), return evaluation/token bytes (base64url).
131    pub fn evaluate_with_proof(&self, blinded_b64: &str) -> Result<String, Error> {
132        let blinded_raw = Base64UrlUnpadded::decode_vec(blinded_b64).map_err(|_| Error::Decode)?;
133        let eval_raw = self.0.evaluate(&blinded_raw).map_err(|_| Error::Internal)?;
134        Ok(Base64UrlUnpadded::encode_string(&eval_raw))
135    }
136}
137
138impl Verifier {
139    pub fn new(ctx: &[u8]) -> Self {
140        Self(v::Verifier::new(ctx))
141    }
142
143    /// Verify opaque token locally and derive token_output used for nullifier.
144    pub fn verify(
145        &self,
146        token_b64: &str,
147        issuer_pubkey_sec1_compressed: &[u8],
148    ) -> Result<String, Error> {
149        let tok_raw = Base64UrlUnpadded::decode_vec(token_b64).map_err(|_| Error::Decode)?;
150        let out_raw = self
151            .0
152            .verify(&tok_raw, issuer_pubkey_sec1_compressed)
153            .map_err(|_| Error::Verify)?;
154        Ok(Base64UrlUnpadded::encode_string(&out_raw))
155    }
156}
157
158/// Token MAC constants
159pub const TOKEN_MAC_LEN: usize = 32; // HMAC-SHA256 output size
160
161/// Token signature constants (for public-key metadata authentication)
162pub const TOKEN_SIGNATURE_LEN: usize = 64; // ECDSA signature (r: 32 bytes, s: 32 bytes)
163
164/// Token format versions
165///
166/// These distinguish between different token authentication schemes to enable
167/// backward-compatible migration from MAC-based to signature-based auth.
168pub const TOKEN_FORMAT_V1_MAC: u8 = 0x01; // VOPRF (131) + MAC (32) = 163 bytes
169pub const TOKEN_FORMAT_V2_SIGNATURE: u8 = 0x02; // VOPRF (131) + ECDSA (64) = 195 bytes
170
171/// Total token lengths including authentication
172pub const TOKEN_LEN_V1: usize = 131 + TOKEN_MAC_LEN; // 163 bytes
173pub const TOKEN_LEN_V2: usize = 131 + TOKEN_SIGNATURE_LEN; // 195 bytes
174
175/// Compute HMAC-SHA256 over token and metadata to prevent tampering
176///
177/// MAC = HMAC-SHA256(mac_key, token_bytes || kid || exp || issuer_id)
178///
179/// # Arguments
180/// * `mac_key` - 32-byte MAC key (should be derived from server secret key)
181/// * `token_bytes` - The VOPRF token bytes [VERSION||A||B||Proof]
182/// * `kid` - Key identifier
183/// * `exp` - Expiration timestamp (Unix seconds)
184/// * `issuer_id` - Issuer identifier
185///
186/// # Returns
187/// 32-byte HMAC tag
188pub fn compute_token_mac(
189    mac_key: &[u8; 32],
190    token_bytes: &[u8],
191    kid: &str,
192    exp: i64,
193    issuer_id: &str,
194) -> [u8; 32] {
195    let mut mac = HmacSha256::new_from_slice(mac_key).expect("HMAC can take key of any size");
196
197    // MAC over: token || kid || exp || issuer_id
198    mac.update(token_bytes);
199    mac.update(kid.as_bytes());
200    mac.update(&exp.to_be_bytes());
201    mac.update(issuer_id.as_bytes());
202
203    mac.finalize().into_bytes().into()
204}
205
206/// Verify HMAC-SHA256 over token and metadata (constant-time)
207///
208/// # Arguments
209/// * `mac_key` - 32-byte MAC key
210/// * `token_bytes` - The VOPRF token bytes [VERSION||A||B||Proof]
211/// * `received_mac` - The MAC tag to verify
212/// * `kid` - Key identifier
213/// * `exp` - Expiration timestamp
214/// * `issuer_id` - Issuer identifier
215///
216/// # Returns
217/// true if MAC is valid, false otherwise (constant-time comparison)
218pub fn verify_token_mac(
219    mac_key: &[u8; 32],
220    token_bytes: &[u8],
221    received_mac: &[u8; 32],
222    kid: &str,
223    exp: i64,
224    issuer_id: &str,
225) -> bool {
226    let computed = compute_token_mac(mac_key, token_bytes, kid, exp, issuer_id);
227
228    // Constant-time comparison using subtle
229    use subtle::ConstantTimeEq;
230    bool::from(computed.ct_eq(received_mac))
231}
232
233// ============================================================================
234// ECDSA Signature-based Metadata Authentication (Federation-Ready)
235// ============================================================================
236//
237// This provides an alternative to HMAC-based MAC that enables multi-issuer
238// federation. Instead of requiring verifiers to possess issuer secret keys,
239// verifiers only need public keys to verify token metadata signatures.
240//
241// This is the cryptographic foundation for Layer 1 of multi-issuer federation.
242
243/// Compute ECDSA signature over token metadata to prevent tampering
244///
245/// This replaces the HMAC-based MAC scheme with public-key signatures,
246/// enabling verifiers to authenticate tokens using only the issuer's public key.
247///
248/// Signature = ECDSA_Sign(issuer_sk, SHA256(token_bytes || kid || exp || issuer_id))
249///
250/// # Arguments
251/// * `issuer_sk` - Issuer's ECDSA secret key (32 bytes)
252/// * `token_bytes` - The VOPRF token bytes [VERSION||A||B||Proof] (131 bytes)
253/// * `kid` - Key identifier
254/// * `exp` - Expiration timestamp (Unix seconds)
255/// * `issuer_id` - Issuer identifier
256///
257/// # Returns
258/// 64-byte ECDSA signature (r || s, each 32 bytes)
259///
260/// # Security
261/// - Uses deterministic ECDSA (RFC 6979) for reproducibility
262/// - Signs over SHA256 hash of metadata (standard ECDSA message preparation)
263/// - Same P-256 curve as VOPRF operations
264pub fn compute_token_signature(
265    issuer_sk: &[u8; 32],
266    token_bytes: &[u8],
267    kid: &str,
268    exp: i64,
269    issuer_id: &str,
270) -> Result<[u8; 64], Error> {
271    use p256::ecdsa::{signature::Signer, SigningKey};
272
273    // Construct message to sign (same as MAC scheme)
274    let mut msg = Vec::new();
275    msg.extend_from_slice(token_bytes);
276    msg.extend_from_slice(kid.as_bytes());
277    msg.extend_from_slice(&exp.to_be_bytes());
278    msg.extend_from_slice(issuer_id.as_bytes());
279
280    // Hash the message (ECDSA signs the hash, not raw message)
281    let msg_hash = Sha256::digest(&msg);
282
283    // Create signing key from secret key bytes
284    let signing_key = SigningKey::from_bytes(issuer_sk.into()).map_err(|_| Error::Internal)?;
285
286    // Sign (uses deterministic ECDSA by default in p256 crate)
287    let signature: p256::ecdsa::Signature = signing_key.sign(&msg_hash);
288
289    // Convert to raw 64-byte format (r || s)
290    Ok(signature.to_bytes().into())
291}
292
293/// Verify ECDSA signature over token metadata (constant-time)
294///
295/// Verifies that the token metadata signature is valid using the issuer's
296/// public key. This enables federation because verifiers don't need secret keys.
297///
298/// # Arguments
299/// * `issuer_pubkey` - Issuer's public key (33 bytes, SEC1 compressed)
300/// * `token_bytes` - The VOPRF token bytes [VERSION||A||B||Proof] (131 bytes)
301/// * `received_signature` - The signature to verify (64 bytes, r || s)
302/// * `kid` - Key identifier
303/// * `exp` - Expiration timestamp (Unix seconds)
304/// * `issuer_id` - Issuer identifier
305///
306/// # Returns
307/// true if signature is valid, false otherwise (constant-time comparison)
308pub fn verify_token_signature(
309    issuer_pubkey: &[u8],
310    token_bytes: &[u8],
311    received_signature: &[u8; 64],
312    kid: &str,
313    exp: i64,
314    issuer_id: &str,
315) -> bool {
316    use p256::ecdsa::{signature::Verifier, VerifyingKey};
317
318    // Construct message (same as signing)
319    let mut msg = Vec::new();
320    msg.extend_from_slice(token_bytes);
321    msg.extend_from_slice(kid.as_bytes());
322    msg.extend_from_slice(&exp.to_be_bytes());
323    msg.extend_from_slice(issuer_id.as_bytes());
324
325    // Hash the message
326    let msg_hash = Sha256::digest(&msg);
327
328    // Parse public key (SEC1 compressed format)
329    let verifying_key = match VerifyingKey::from_sec1_bytes(issuer_pubkey) {
330        Ok(key) => key,
331        Err(_) => return false,
332    };
333
334    // Parse signature
335    let signature = match p256::ecdsa::Signature::from_bytes(received_signature.into()) {
336        Ok(sig) => sig,
337        Err(_) => return false,
338    };
339
340    // Verify signature (constant-time in the underlying implementation)
341    verifying_key.verify(&msg_hash, &signature).is_ok()
342}
343
344/// Derive MAC key from server secret key using HKDF with domain separation
345///
346/// This ensures the MAC key is cryptographically independent from the
347/// VOPRF secret key, following the principle of key separation.
348///
349/// # Arguments
350/// * `server_sk` - Server's secret key (32 bytes)
351/// * `issuer_id` - Issuer identifier for domain separation
352/// * `key_id` - Key identifier (kid)
353/// * `epoch` - Epoch number for key rotation (0 for initial deployment)
354///
355/// # Returns
356/// Derived 32-byte MAC key
357///
358/// # Security
359/// - Uses HKDF-SHA256 for cryptographic key derivation
360/// - Domain-separated by issuer_id, key_id, and epoch
361/// - Enables forward secrecy through epoch rotation
362pub fn derive_mac_key_v2(
363    server_sk: &[u8; 32],
364    issuer_id: &str,
365    key_id: &str,
366    epoch: u32,
367) -> [u8; 32] {
368    use hkdf::Hkdf;
369
370    // Domain-separated info string
371    let info = format!("freebird-mac-v1|{}|{}|{}", issuer_id, key_id, epoch);
372
373    let hkdf = Hkdf::<Sha256>::new(
374        Some(b"freebird-mac-salt"), // Salt for additional entropy
375        server_sk,
376    );
377
378    let mut mac_key = [0u8; 32];
379    hkdf.expand(info.as_bytes(), &mut mac_key)
380        .expect("32 bytes is a valid HKDF output length");
381
382    mac_key
383}
384
385// ============================================================================
386// Generic Message Signatures (for Layer 2 Federation)
387// ============================================================================
388//
389// These functions provide generic ECDSA signing/verification for any message,
390// used by Layer 2 federation for vouches, revocations, and other trust signals.
391
392/// Sign an arbitrary message with an issuer's secret key
393///
394/// This is a generic signing function used for federation messages like
395/// vouches and revocations. Uses deterministic ECDSA (RFC 6979).
396///
397/// # Arguments
398/// * `secret_key` - Issuer's 32-byte secret key
399/// * `message` - The message bytes to sign
400///
401/// # Returns
402/// 64-byte ECDSA signature (r || s) or error
403pub fn sign_message(secret_key: &[u8; 32], message: &[u8]) -> Result<[u8; 64], Error> {
404    use p256::ecdsa::{signature::Signer, SigningKey};
405
406    // Hash the message first
407    let msg_hash = Sha256::digest(message);
408
409    // Create signing key from secret
410    let signing_key = SigningKey::from_bytes(secret_key.into()).map_err(|_| Error::Internal)?;
411
412    // Sign (deterministic, using RFC 6979)
413    let signature: p256::ecdsa::Signature = signing_key.sign(&msg_hash);
414
415    Ok(signature.to_bytes().into())
416}
417
418/// Verify an arbitrary message signature with an issuer's public key
419///
420/// This is a generic verification function used for federation messages.
421///
422/// # Arguments
423/// * `public_key` - Issuer's public key (SEC1 compressed, 33 bytes)
424/// * `message` - The message bytes that were signed
425/// * `signature` - The 64-byte ECDSA signature to verify
426///
427/// # Returns
428/// true if signature is valid, false otherwise
429pub fn verify_message_signature(public_key: &[u8], message: &[u8], signature: &[u8; 64]) -> bool {
430    use p256::ecdsa::{signature::Verifier, VerifyingKey};
431
432    // Hash the message
433    let msg_hash = Sha256::digest(message);
434
435    // Parse public key
436    let verifying_key = match VerifyingKey::from_sec1_bytes(public_key) {
437        Ok(key) => key,
438        Err(_) => return false,
439    };
440
441    // Parse signature
442    let sig = match p256::ecdsa::Signature::from_bytes(signature.into()) {
443        Ok(s) => s,
444        Err(_) => return false,
445    };
446
447    // Verify signature
448    verifying_key.verify(&msg_hash, &sig).is_ok()
449}
450
451/// Derive MAC key from server secret key using HKDF (legacy, simple version)
452///
453/// This ensures the MAC key is cryptographically independent from the
454/// VOPRF secret key, following the principle of key separation.
455///
456/// # Arguments
457/// * `server_sk` - Server's secret key (32 bytes)
458/// * `info` - Optional context/domain separation (e.g., "freebird:mac:v1")
459///
460/// # Returns
461/// Derived 32-byte MAC key
462pub fn derive_mac_key(server_sk: &[u8; 32], info: &[u8]) -> [u8; 32] {
463    use hkdf::Hkdf;
464
465    let hkdf = Hkdf::<Sha256>::new(None, server_sk);
466    let mut mac_key = [0u8; 32];
467    hkdf.expand(info, &mut mac_key)
468        .expect("32 bytes is a valid HKDF output length");
469    mac_key
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    #[test]
477    fn end_to_end() {
478        let ctx = b"freebird-v1";
479        let sk = [7u8; 32];
480
481        let server = Server::from_secret_key(sk, ctx).unwrap();
482        let pk = server.public_key_sec1_compressed();
483
484        // client blinds input
485        let mut client = Client::new(ctx);
486        let (blinded_b64, st) = client.blind(b"hello world").unwrap();
487
488        // server evaluates
489        let eval_b64 = server.evaluate_with_proof(&blinded_b64).unwrap();
490
491        // client finalizes
492        let (token_b64, out_cli_b64) = client.finalize(st, &eval_b64, &pk).unwrap();
493
494        // verifier derives same output
495        let verifier = Verifier::new(ctx);
496        let out_ver_b64 = verifier.verify(&token_b64, &pk).unwrap();
497
498        assert_eq!(out_cli_b64, out_ver_b64);
499
500        // nullifier determinism
501        let n1 = nullifier_key("issuer:freebird:v1", &out_ver_b64);
502        let n2 = nullifier_key("issuer:freebird:v1", &out_ver_b64);
503        assert_eq!(n1, n2);
504        assert!(!n1.is_empty());
505    }
506
507    #[test]
508    fn test_mac_computation_and_verification() {
509        let mac_key = [42u8; 32];
510        let token = vec![1, 2, 3, 4, 5];
511        let kid = "test-kid-001";
512        let exp = 1234567890i64;
513        let issuer_id = "test-issuer";
514
515        // Compute MAC
516        let mac = compute_token_mac(&mac_key, &token, kid, exp, issuer_id);
517        assert_eq!(mac.len(), 32);
518
519        // Verify MAC succeeds
520        assert!(verify_token_mac(
521            &mac_key, &token, &mac, kid, exp, issuer_id
522        ));
523
524        // Tampered token fails
525        let mut bad_token = token.clone();
526        bad_token[0] ^= 1;
527        assert!(!verify_token_mac(
528            &mac_key, &bad_token, &mac, kid, exp, issuer_id
529        ));
530
531        // Tampered kid fails
532        assert!(!verify_token_mac(
533            &mac_key,
534            &token,
535            &mac,
536            "wrong-kid",
537            exp,
538            issuer_id
539        ));
540
541        // Tampered exp fails
542        assert!(!verify_token_mac(
543            &mac_key,
544            &token,
545            &mac,
546            kid,
547            exp + 1,
548            issuer_id
549        ));
550
551        // Tampered issuer_id fails
552        assert!(!verify_token_mac(
553            &mac_key,
554            &token,
555            &mac,
556            kid,
557            exp,
558            "wrong-issuer"
559        ));
560
561        // Wrong MAC fails
562        let wrong_mac = [0u8; 32];
563        assert!(!verify_token_mac(
564            &mac_key, &token, &wrong_mac, kid, exp, issuer_id
565        ));
566    }
567
568    #[test]
569    fn test_mac_key_derivation() {
570        let sk = [7u8; 32];
571        let info1 = b"freebird:mac:v1";
572        let info2 = b"freebird:mac:v2";
573
574        let key1a = derive_mac_key(&sk, info1);
575        let key1b = derive_mac_key(&sk, info1);
576        let key2 = derive_mac_key(&sk, info2);
577
578        // Deterministic derivation
579        assert_eq!(key1a, key1b);
580
581        // Different contexts produce different keys
582        assert_ne!(key1a, key2);
583
584        // Keys should not be all zeros
585        assert_ne!(key1a, [0u8; 32]);
586    }
587
588    #[test]
589    fn test_mac_key_derivation_v2() {
590        let sk = [7u8; 32];
591        let issuer = "test-issuer";
592        let kid = "key-001";
593
594        // Same parameters produce same key (deterministic)
595        let key1 = derive_mac_key_v2(&sk, issuer, kid, 0);
596        let key2 = derive_mac_key_v2(&sk, issuer, kid, 0);
597        assert_eq!(key1, key2);
598
599        // Different epoch produces different key
600        let key_epoch1 = derive_mac_key_v2(&sk, issuer, kid, 1);
601        assert_ne!(key1, key_epoch1);
602
603        // Different issuer produces different key
604        let key_issuer2 = derive_mac_key_v2(&sk, "other-issuer", kid, 0);
605        assert_ne!(key1, key_issuer2);
606
607        // Different kid produces different key
608        let key_kid2 = derive_mac_key_v2(&sk, issuer, "key-002", 0);
609        assert_ne!(key1, key_kid2);
610
611        // Keys should not be all zeros
612        assert_ne!(key1, [0u8; 32]);
613    }
614
615    #[test]
616    fn test_mac_constant_time() {
617        // This test doesn't prove constant-time behavior but verifies
618        // that the comparison works correctly for all bit patterns
619        let mac_key = [42u8; 32];
620        let token = vec![1, 2, 3];
621        let kid = "kid";
622        let exp = 123i64;
623        let issuer = "issuer";
624
625        let correct_mac = compute_token_mac(&mac_key, &token, kid, exp, issuer);
626
627        // Test all single-bit flips
628        for byte_idx in 0..32 {
629            for bit_idx in 0..8 {
630                let mut wrong_mac = correct_mac;
631                wrong_mac[byte_idx] ^= 1 << bit_idx;
632                assert!(!verify_token_mac(
633                    &mac_key, &token, &wrong_mac, kid, exp, issuer
634                ));
635            }
636        }
637    }
638
639    // ========================================================================
640    // Signature-based Authentication Tests
641    // ========================================================================
642
643    #[test]
644    fn test_signature_computation_and_verification() {
645        let sk = [7u8; 32];
646        let ctx = b"freebird-v1";
647
648        // Create server to get public key
649        let server = Server::from_secret_key(sk, ctx).unwrap();
650        let pubkey = server.public_key_sec1_compressed();
651
652        let token = vec![1, 2, 3, 4, 5];
653        let kid = "test-kid-001";
654        let exp = 1234567890i64;
655        let issuer_id = "test-issuer";
656
657        // Compute signature
658        let signature = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
659        assert_eq!(signature.len(), 64);
660
661        // Verify signature succeeds
662        assert!(verify_token_signature(
663            &pubkey, &token, &signature, kid, exp, issuer_id
664        ));
665
666        // Tampered token fails
667        let mut bad_token = token.clone();
668        bad_token[0] ^= 1;
669        assert!(!verify_token_signature(
670            &pubkey, &bad_token, &signature, kid, exp, issuer_id
671        ));
672
673        // Tampered kid fails
674        assert!(!verify_token_signature(
675            &pubkey,
676            &token,
677            &signature,
678            "wrong-kid",
679            exp,
680            issuer_id
681        ));
682
683        // Tampered exp fails
684        assert!(!verify_token_signature(
685            &pubkey,
686            &token,
687            &signature,
688            kid,
689            exp + 1,
690            issuer_id
691        ));
692
693        // Tampered issuer_id fails
694        assert!(!verify_token_signature(
695            &pubkey,
696            &token,
697            &signature,
698            kid,
699            exp,
700            "wrong-issuer"
701        ));
702
703        // Wrong signature fails
704        let wrong_signature = [0u8; 64];
705        assert!(!verify_token_signature(
706            &pubkey,
707            &token,
708            &wrong_signature,
709            kid,
710            exp,
711            issuer_id
712        ));
713
714        // Tampered signature fails
715        let mut bad_signature = signature;
716        bad_signature[0] ^= 1;
717        assert!(!verify_token_signature(
718            &pubkey,
719            &token,
720            &bad_signature,
721            kid,
722            exp,
723            issuer_id
724        ));
725    }
726
727    #[test]
728    fn test_signature_determinism() {
729        let sk = [7u8; 32];
730        let ctx = b"freebird-v1";
731        let server = Server::from_secret_key(sk, ctx).unwrap();
732        let pubkey = server.public_key_sec1_compressed();
733
734        let token = vec![1, 2, 3, 4, 5];
735        let kid = "test-kid-001";
736        let exp = 1234567890i64;
737        let issuer_id = "test-issuer";
738
739        // Signatures should be deterministic (RFC 6979)
740        let sig1 = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
741        let sig2 = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
742        assert_eq!(sig1, sig2);
743
744        // Both should verify
745        assert!(verify_token_signature(
746            &pubkey, &token, &sig1, kid, exp, issuer_id
747        ));
748        assert!(verify_token_signature(
749            &pubkey, &token, &sig2, kid, exp, issuer_id
750        ));
751    }
752
753    #[test]
754    fn test_signature_different_keys() {
755        let sk1 = [7u8; 32];
756        let sk2 = [8u8; 32];
757        let ctx = b"freebird-v1";
758
759        let server1 = Server::from_secret_key(sk1, ctx).unwrap();
760        let server2 = Server::from_secret_key(sk2, ctx).unwrap();
761        let pubkey1 = server1.public_key_sec1_compressed();
762        let pubkey2 = server2.public_key_sec1_compressed();
763
764        let token = vec![1, 2, 3, 4, 5];
765        let kid = "test-kid-001";
766        let exp = 1234567890i64;
767        let issuer_id = "test-issuer";
768
769        // Sign with key 1
770        let sig1 = compute_token_signature(&sk1, &token, kid, exp, issuer_id).unwrap();
771
772        // Verify with key 1's public key succeeds
773        assert!(verify_token_signature(
774            &pubkey1, &token, &sig1, kid, exp, issuer_id
775        ));
776
777        // Verify with key 2's public key fails
778        assert!(!verify_token_signature(
779            &pubkey2, &token, &sig1, kid, exp, issuer_id
780        ));
781    }
782
783    #[test]
784    fn test_signature_invalid_pubkey() {
785        let sk = [7u8; 32];
786        let token = vec![1, 2, 3, 4, 5];
787        let kid = "test-kid-001";
788        let exp = 1234567890i64;
789        let issuer_id = "test-issuer";
790
791        let signature = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
792
793        // Invalid public key (not a valid SEC1 compressed point)
794        let bad_pubkey = [0xFFu8; 33];
795        assert!(!verify_token_signature(
796            &bad_pubkey,
797            &token,
798            &signature,
799            kid,
800            exp,
801            issuer_id
802        ));
803
804        // Wrong length public key
805        let short_pubkey = [0x02u8; 32];
806        assert!(!verify_token_signature(
807            &short_pubkey,
808            &token,
809            &signature,
810            kid,
811            exp,
812            issuer_id
813        ));
814    }
815
816    #[test]
817    fn test_signature_with_real_voprf_token() {
818        // End-to-end test with actual VOPRF token
819        let sk = [7u8; 32];
820        let ctx = b"freebird-v1";
821
822        let server = Server::from_secret_key(sk, ctx).unwrap();
823        let pk = server.public_key_sec1_compressed();
824
825        // Generate a real VOPRF token
826        let mut client = Client::new(ctx);
827        let (blinded_b64, st) = client.blind(b"hello world").unwrap();
828        let eval_b64 = server.evaluate_with_proof(&blinded_b64).unwrap();
829        let (token_b64, _) = client.finalize(st, &eval_b64, &pk).unwrap();
830
831        // Decode token to bytes
832        let token_bytes = base64ct::Base64UrlUnpadded::decode_vec(&token_b64).unwrap();
833        assert_eq!(token_bytes.len(), 131); // VOPRF token is 131 bytes
834
835        let kid = "test-kid-001";
836        let exp = 1234567890i64;
837        let issuer_id = "issuer:freebird:v1";
838
839        // Sign the real token
840        let signature = compute_token_signature(&sk, &token_bytes, kid, exp, issuer_id).unwrap();
841
842        // Verify signature
843        assert!(verify_token_signature(
844            &pk,
845            &token_bytes,
846            &signature,
847            kid,
848            exp,
849            issuer_id
850        ));
851
852        // Tampered token should fail
853        let mut bad_token = token_bytes.clone();
854        bad_token[0] ^= 1;
855        assert!(!verify_token_signature(
856            &pk, &bad_token, &signature, kid, exp, issuer_id
857        ));
858    }
859
860    // Generic message signing tests (for Layer 2 Federation)
861
862    #[test]
863    fn test_generic_message_signing() {
864        let sk = [42u8; 32];
865        let ctx = b"freebird-v1";
866        let server = Server::from_secret_key(sk, ctx).unwrap();
867        let pubkey = server.public_key_sec1_compressed();
868
869        let message = b"Hello, Federation!";
870
871        // Sign message
872        let signature = sign_message(&sk, message).unwrap();
873        assert_eq!(signature.len(), 64);
874
875        // Verify signature
876        assert!(verify_message_signature(&pubkey, message, &signature));
877
878        // Wrong message should fail
879        let wrong_message = b"Wrong message";
880        assert!(!verify_message_signature(
881            &pubkey,
882            wrong_message,
883            &signature
884        ));
885
886        // Wrong public key should fail
887        let sk2 = [43u8; 32];
888        let server2 = Server::from_secret_key(sk2, ctx).unwrap();
889        let pubkey2 = server2.public_key_sec1_compressed();
890        assert!(!verify_message_signature(&pubkey2, message, &signature));
891    }
892
893    #[test]
894    fn test_generic_message_determinism() {
895        let sk = [42u8; 32];
896        let message = b"Deterministic test message";
897
898        // Same inputs should produce same signature (RFC 6979)
899        let sig1 = sign_message(&sk, message).unwrap();
900        let sig2 = sign_message(&sk, message).unwrap();
901        assert_eq!(sig1, sig2);
902    }
903
904    #[test]
905    fn test_generic_message_different_lengths() {
906        let sk = [42u8; 32];
907        let ctx = b"freebird-v1";
908        let server = Server::from_secret_key(sk, ctx).unwrap();
909        let pubkey = server.public_key_sec1_compressed();
910
911        // Test with messages of different lengths
912        let short_msg = b"Hi";
913        let long_msg = b"This is a much longer message that tests whether the signing function handles variable-length inputs correctly.";
914
915        let sig_short = sign_message(&sk, short_msg).unwrap();
916        let sig_long = sign_message(&sk, long_msg).unwrap();
917
918        assert!(verify_message_signature(&pubkey, short_msg, &sig_short));
919        assert!(verify_message_signature(&pubkey, long_msg, &sig_long));
920
921        // Cross-verification should fail
922        assert!(!verify_message_signature(&pubkey, short_msg, &sig_long));
923        assert!(!verify_message_signature(&pubkey, long_msg, &sig_short));
924    }
925
926    #[test]
927    fn test_generic_message_empty() {
928        let sk = [42u8; 32];
929        let ctx = b"freebird-v1";
930        let server = Server::from_secret_key(sk, ctx).unwrap();
931        let pubkey = server.public_key_sec1_compressed();
932
933        // Empty message should still work
934        let empty_msg = b"";
935        let sig = sign_message(&sk, empty_msg).unwrap();
936        assert!(verify_message_signature(&pubkey, empty_msg, &sig));
937    }
938
939    #[test]
940    fn test_generic_message_invalid_signature_bytes() {
941        let sk = [42u8; 32];
942        let ctx = b"freebird-v1";
943        let server = Server::from_secret_key(sk, ctx).unwrap();
944        let pubkey = server.public_key_sec1_compressed();
945
946        let message = b"Test message";
947
948        // Invalid signature (all zeros)
949        let bad_sig = [0u8; 64];
950        assert!(!verify_message_signature(&pubkey, message, &bad_sig));
951
952        // Invalid signature (all 0xFF)
953        let bad_sig2 = [0xFFu8; 64];
954        assert!(!verify_message_signature(&pubkey, message, &bad_sig2));
955    }
956}