1use base64ct::{Base64UrlUnpadded, Encoding};
50use hmac::{Hmac, Mac};
51use sha2::{Digest, Sha256};
52
53type HmacSha256 = Hmac<Sha256>;
54
55pub mod voprf;
57use voprf as v;
58
59pub 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
77pub 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 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 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 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 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
158pub const TOKEN_MAC_LEN: usize = 32; pub const TOKEN_SIGNATURE_LEN: usize = 64; pub const TOKEN_FORMAT_V1_MAC: u8 = 0x01; pub const TOKEN_FORMAT_V2_SIGNATURE: u8 = 0x02; pub const TOKEN_LEN_V1: usize = 131 + TOKEN_MAC_LEN; pub const TOKEN_LEN_V2: usize = 131 + TOKEN_SIGNATURE_LEN; pub 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.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
206pub 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 use subtle::ConstantTimeEq;
230 bool::from(computed.ct_eq(received_mac))
231}
232
233pub 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 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 let msg_hash = Sha256::digest(&msg);
282
283 let signing_key = SigningKey::from_bytes(issuer_sk.into()).map_err(|_| Error::Internal)?;
285
286 let signature: p256::ecdsa::Signature = signing_key.sign(&msg_hash);
288
289 Ok(signature.to_bytes().into())
291}
292
293pub 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 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 let msg_hash = Sha256::digest(&msg);
327
328 let verifying_key = match VerifyingKey::from_sec1_bytes(issuer_pubkey) {
330 Ok(key) => key,
331 Err(_) => return false,
332 };
333
334 let signature = match p256::ecdsa::Signature::from_bytes(received_signature.into()) {
336 Ok(sig) => sig,
337 Err(_) => return false,
338 };
339
340 verifying_key.verify(&msg_hash, &signature).is_ok()
342}
343
344pub 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 let info = format!("freebird-mac-v1|{}|{}|{}", issuer_id, key_id, epoch);
372
373 let hkdf = Hkdf::<Sha256>::new(
374 Some(b"freebird-mac-salt"), 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
385pub fn sign_message(secret_key: &[u8; 32], message: &[u8]) -> Result<[u8; 64], Error> {
404 use p256::ecdsa::{signature::Signer, SigningKey};
405
406 let msg_hash = Sha256::digest(message);
408
409 let signing_key = SigningKey::from_bytes(secret_key.into()).map_err(|_| Error::Internal)?;
411
412 let signature: p256::ecdsa::Signature = signing_key.sign(&msg_hash);
414
415 Ok(signature.to_bytes().into())
416}
417
418pub fn verify_message_signature(public_key: &[u8], message: &[u8], signature: &[u8; 64]) -> bool {
430 use p256::ecdsa::{signature::Verifier, VerifyingKey};
431
432 let msg_hash = Sha256::digest(message);
434
435 let verifying_key = match VerifyingKey::from_sec1_bytes(public_key) {
437 Ok(key) => key,
438 Err(_) => return false,
439 };
440
441 let sig = match p256::ecdsa::Signature::from_bytes(signature.into()) {
443 Ok(s) => s,
444 Err(_) => return false,
445 };
446
447 verifying_key.verify(&msg_hash, &sig).is_ok()
449}
450
451pub 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 let mut client = Client::new(ctx);
486 let (blinded_b64, st) = client.blind(b"hello world").unwrap();
487
488 let eval_b64 = server.evaluate_with_proof(&blinded_b64).unwrap();
490
491 let (token_b64, out_cli_b64) = client.finalize(st, &eval_b64, &pk).unwrap();
493
494 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 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 let mac = compute_token_mac(&mac_key, &token, kid, exp, issuer_id);
517 assert_eq!(mac.len(), 32);
518
519 assert!(verify_token_mac(
521 &mac_key, &token, &mac, kid, exp, issuer_id
522 ));
523
524 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 assert!(!verify_token_mac(
533 &mac_key,
534 &token,
535 &mac,
536 "wrong-kid",
537 exp,
538 issuer_id
539 ));
540
541 assert!(!verify_token_mac(
543 &mac_key,
544 &token,
545 &mac,
546 kid,
547 exp + 1,
548 issuer_id
549 ));
550
551 assert!(!verify_token_mac(
553 &mac_key,
554 &token,
555 &mac,
556 kid,
557 exp,
558 "wrong-issuer"
559 ));
560
561 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 assert_eq!(key1a, key1b);
580
581 assert_ne!(key1a, key2);
583
584 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 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 let key_epoch1 = derive_mac_key_v2(&sk, issuer, kid, 1);
601 assert_ne!(key1, key_epoch1);
602
603 let key_issuer2 = derive_mac_key_v2(&sk, "other-issuer", kid, 0);
605 assert_ne!(key1, key_issuer2);
606
607 let key_kid2 = derive_mac_key_v2(&sk, issuer, "key-002", 0);
609 assert_ne!(key1, key_kid2);
610
611 assert_ne!(key1, [0u8; 32]);
613 }
614
615 #[test]
616 fn test_mac_constant_time() {
617 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 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 #[test]
644 fn test_signature_computation_and_verification() {
645 let sk = [7u8; 32];
646 let ctx = b"freebird-v1";
647
648 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 let signature = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
659 assert_eq!(signature.len(), 64);
660
661 assert!(verify_token_signature(
663 &pubkey, &token, &signature, kid, exp, issuer_id
664 ));
665
666 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 assert!(!verify_token_signature(
675 &pubkey,
676 &token,
677 &signature,
678 "wrong-kid",
679 exp,
680 issuer_id
681 ));
682
683 assert!(!verify_token_signature(
685 &pubkey,
686 &token,
687 &signature,
688 kid,
689 exp + 1,
690 issuer_id
691 ));
692
693 assert!(!verify_token_signature(
695 &pubkey,
696 &token,
697 &signature,
698 kid,
699 exp,
700 "wrong-issuer"
701 ));
702
703 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 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 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 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 let sig1 = compute_token_signature(&sk1, &token, kid, exp, issuer_id).unwrap();
771
772 assert!(verify_token_signature(
774 &pubkey1, &token, &sig1, kid, exp, issuer_id
775 ));
776
777 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 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 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 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 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 let token_bytes = base64ct::Base64UrlUnpadded::decode_vec(&token_b64).unwrap();
833 assert_eq!(token_bytes.len(), 131); let kid = "test-kid-001";
836 let exp = 1234567890i64;
837 let issuer_id = "issuer:freebird:v1";
838
839 let signature = compute_token_signature(&sk, &token_bytes, kid, exp, issuer_id).unwrap();
841
842 assert!(verify_token_signature(
844 &pk,
845 &token_bytes,
846 &signature,
847 kid,
848 exp,
849 issuer_id
850 ));
851
852 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 #[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 let signature = sign_message(&sk, message).unwrap();
873 assert_eq!(signature.len(), 64);
874
875 assert!(verify_message_signature(&pubkey, message, &signature));
877
878 let wrong_message = b"Wrong message";
880 assert!(!verify_message_signature(
881 &pubkey,
882 wrong_message,
883 &signature
884 ));
885
886 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 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 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 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 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 let bad_sig = [0u8; 64];
950 assert!(!verify_message_signature(&pubkey, message, &bad_sig));
951
952 let bad_sig2 = [0xFFu8; 64];
954 assert!(!verify_message_signature(&pubkey, message, &bad_sig2));
955 }
956}