1#![doc = include_str!("../README.md")]
2
3pub mod reexports {
18 pub use ct_codecs;
19 pub use libsodium_rs;
20}
21
22use ct_codecs::{Encoder, Hex};
23use libsodium_rs::crypto_core::ristretto255;
24use libsodium_rs::crypto_generichash;
25use libsodium_rs::crypto_scalarmult::ristretto255 as scalarmult_ristretto;
26use libsodium_rs::utils;
27use std::error::Error;
28use std::fmt;
29use std::hash::{Hash, Hasher};
30
31pub type Scalar = [u8; ristretto255::SCALARBYTES];
33
34pub type Point = [u8; ristretto255::BYTES];
36
37#[derive(Debug)]
39pub enum ProxySignatureError {
40 Libsodium(libsodium_rs::SodiumError),
42 InvalidDelegation,
44 ProxyKeyMismatch,
46 Utf8Error(std::str::Utf8Error),
48 InvalidLength { expected: usize, actual: usize },
50}
51
52impl fmt::Display for ProxySignatureError {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 match self {
55 ProxySignatureError::Libsodium(e) => write!(f, "Libsodium error: {}", e),
56 ProxySignatureError::InvalidDelegation => write!(f, "Invalid delegation token"),
57 ProxySignatureError::ProxyKeyMismatch => {
58 write!(f, "Proxy key derivation mismatch")
59 }
60 ProxySignatureError::Utf8Error(e) => write!(f, "UTF-8 error: {}", e),
61 ProxySignatureError::InvalidLength { expected, actual } => {
62 write!(
63 f,
64 "Invalid length: expected {} bytes, got {}",
65 expected, actual
66 )
67 }
68 }
69 }
70}
71
72impl Error for ProxySignatureError {}
73
74impl From<libsodium_rs::SodiumError> for ProxySignatureError {
75 fn from(error: libsodium_rs::SodiumError) -> Self {
76 ProxySignatureError::Libsodium(error)
77 }
78}
79
80impl From<std::str::Utf8Error> for ProxySignatureError {
81 fn from(error: std::str::Utf8Error) -> Self {
82 ProxySignatureError::Utf8Error(error)
83 }
84}
85
86pub type Result<T> = std::result::Result<T, ProxySignatureError>;
88
89#[derive(Clone, PartialEq, Eq)]
92pub struct KeyPair {
93 pub sk: Scalar,
95 pub pk: Point,
97}
98
99impl KeyPair {
100 pub fn new() -> Result<Self> {
108 let sk = ristretto255::scalar_random();
109 let pk = scalarmult_ristretto::scalarmult_base(&sk)?;
110 Ok(KeyPair { sk, pk })
111 }
112
113 pub fn public_key(&self) -> &Point {
115 &self.pk
116 }
117
118 pub fn secret_key(&self) -> &Scalar {
120 &self.sk
121 }
122
123 pub fn from_secret_key(sk: Scalar) -> Result<Self> {
134 let pk = scalarmult_ristretto::scalarmult_base(&sk)?;
135 Ok(KeyPair { sk, pk })
136 }
137}
138
139impl fmt::Debug for KeyPair {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 f.debug_struct("KeyPair")
142 .field("sk", &"<redacted>")
143 .field("pk", &Hex::encode_to_string(self.pk).unwrap())
144 .finish()
145 }
146}
147
148impl fmt::Display for KeyPair {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 write!(
151 f,
152 "KeyPair(pk: {})",
153 &Hex::encode_to_string(self.pk).unwrap()[..8]
154 )
155 }
156}
157
158impl AsRef<Point> for KeyPair {
159 fn as_ref(&self) -> &Point {
160 &self.pk
161 }
162}
163
164impl From<&KeyPair> for Point {
165 fn from(keypair: &KeyPair) -> Self {
166 keypair.pk
167 }
168}
169
170impl Hash for KeyPair {
171 fn hash<H: Hasher>(&self, state: &mut H) {
172 self.pk.hash(state);
174 }
175}
176
177fn hash_to_scalar(parts: &[&[u8]]) -> Result<Scalar> {
188 let mut state = crypto_generichash::State::new(None, 64)?;
189 for part in parts {
190 state.update(part);
191 }
192 let hash = state.finalize();
193 Ok(ristretto255::scalar_reduce(&hash)?)
194}
195
196#[derive(Clone, PartialEq, Eq)]
198pub struct SchnorrSignature {
199 pub s: Scalar,
201 pub r: Point,
203}
204
205impl SchnorrSignature {
206 pub fn sign(sk: &Scalar, msg: &[u8]) -> Result<Self> {
218 let k = ristretto255::scalar_random();
219 let r = scalarmult_ristretto::scalarmult_base(&k)?;
220
221 let e = hash_to_scalar(&[msg, &r])?;
222 let e_times_sk = ristretto255::scalar_mul(&e, sk)?;
223 let s = ristretto255::scalar_add(&k, &e_times_sk)?;
224
225 Ok(SchnorrSignature { s, r })
226 }
227
228 pub fn verify(&self, pk: &Point, msg: &[u8]) -> Result<bool> {
240 let e = hash_to_scalar(&[msg, &self.r])?;
241 let left = scalarmult_ristretto::scalarmult_base(&self.s)?;
242 let e_times_pk = scalarmult_ristretto::scalarmult(&e, pk)?;
243 let right = ristretto255::add(&self.r, &e_times_pk)?;
244
245 Ok(utils::memcmp(&left, &right))
247 }
248}
249
250impl fmt::Debug for SchnorrSignature {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 f.debug_struct("SchnorrSignature")
253 .field("s", &Hex::encode_to_string(self.s).unwrap())
254 .field("r", &Hex::encode_to_string(self.r).unwrap())
255 .finish()
256 }
257}
258
259impl fmt::Display for SchnorrSignature {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 write!(
262 f,
263 "Sig({}...)",
264 &Hex::encode_to_string(self.s).unwrap()[..8]
265 )
266 }
267}
268
269impl Hash for SchnorrSignature {
270 fn hash<H: Hasher>(&self, state: &mut H) {
271 self.s.hash(state);
272 self.r.hash(state);
273 }
274}
275
276#[derive(Clone, PartialEq, Eq)]
278pub struct DelegationToken {
279 pub warrant: Vec<u8>,
281 pub rw: Point,
283 pub sw: Scalar,
285 pub b_pub: Point,
287}
288
289impl DelegationToken {
290 pub fn warrant_str(&self) -> Result<&str> {
292 std::str::from_utf8(&self.warrant).map_err(ProxySignatureError::Utf8Error)
293 }
294
295 pub fn warrant_string_lossy(&self) -> std::borrow::Cow<'_, str> {
297 String::from_utf8_lossy(&self.warrant)
298 }
299
300 pub fn proxy_public_key(&self) -> &Point {
302 &self.b_pub
303 }
304
305 pub fn create(a_keys: &KeyPair, b_pub: &Point, warrant: &[u8]) -> Result<Self> {
322 let a_nonce = ristretto255::scalar_random();
323 let rw = scalarmult_ristretto::scalarmult_base(&a_nonce)?;
324
325 let e_w = hash_to_scalar(&[warrant, &rw, b_pub])?;
327 let e_times_sk = ristretto255::scalar_mul(&e_w, &a_keys.sk)?;
328 let sw = ristretto255::scalar_add(&a_nonce, &e_times_sk)?;
329
330 Ok(DelegationToken {
331 warrant: warrant.to_vec(),
332 rw,
333 sw,
334 b_pub: *b_pub,
335 })
336 }
337
338 pub fn verify(&self, a_pub: &Point) -> Result<bool> {
349 let e_w = hash_to_scalar(&[&self.warrant, &self.rw, &self.b_pub])?;
351 let left = scalarmult_ristretto::scalarmult_base(&self.sw)?;
352 let e_times_a = scalarmult_ristretto::scalarmult(&e_w, a_pub)?;
353 let right = ristretto255::add(&self.rw, &e_times_a)?;
354
355 Ok(utils::memcmp(&left, &right))
357 }
358}
359
360impl fmt::Debug for DelegationToken {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 f.debug_struct("DelegationToken")
363 .field("warrant", &String::from_utf8_lossy(&self.warrant))
364 .field("rw", &Hex::encode_to_string(self.rw).unwrap())
365 .field("sw", &"<redacted>")
366 .field("b_pub", &Hex::encode_to_string(self.b_pub).unwrap())
367 .finish()
368 }
369}
370
371impl fmt::Display for DelegationToken {
372 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373 write!(
374 f,
375 "DelegationToken(warrant: {:?}, proxy: {}...)",
376 String::from_utf8_lossy(&self.warrant),
377 &Hex::encode_to_string(self.b_pub).unwrap()[..8]
378 )
379 }
380}
381
382#[derive(Clone, PartialEq, Eq)]
384pub struct ProxyPublicContext {
385 pub warrant: Vec<u8>,
387 pub rw: Point,
389 pub yp: Point,
391}
392
393impl ProxyPublicContext {
394 pub fn new(warrant: Vec<u8>, rw: Point, yp: Point) -> Self {
396 ProxyPublicContext { warrant, rw, yp }
397 }
398
399 pub fn from_token(token: &DelegationToken, yp: Point) -> Self {
401 ProxyPublicContext {
402 warrant: token.warrant.clone(),
403 rw: token.rw,
404 yp,
405 }
406 }
407
408 pub fn warrant_str(&self) -> Result<&str> {
410 std::str::from_utf8(&self.warrant).map_err(ProxySignatureError::Utf8Error)
411 }
412}
413
414impl fmt::Debug for ProxyPublicContext {
415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416 f.debug_struct("ProxyPublicContext")
417 .field("warrant", &String::from_utf8_lossy(&self.warrant))
418 .field("rw", &Hex::encode_to_string(self.rw).unwrap())
419 .field("yp", &Hex::encode_to_string(self.yp).unwrap())
420 .finish()
421 }
422}
423
424impl fmt::Display for ProxyPublicContext {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 write!(
427 f,
428 "ProxyContext(warrant: {:?})",
429 String::from_utf8_lossy(&self.warrant)
430 )
431 }
432}
433
434#[derive(Clone, PartialEq, Eq)]
437pub struct ProxyKeyPair {
438 pub sk: Scalar,
440 pub pk: Point,
442}
443
444impl ProxyKeyPair {
445 pub fn sign(&self, message: &[u8]) -> Result<SchnorrSignature> {
456 SchnorrSignature::sign(&self.sk, message)
457 }
458
459 pub fn public_key(&self) -> &Point {
461 &self.pk
462 }
463
464 pub fn secret_key(&self) -> &Scalar {
466 &self.sk
467 }
468
469 pub fn verify(&self, message: &[u8], signature: &SchnorrSignature) -> Result<bool> {
481 signature.verify(&self.pk, message)
482 }
483}
484
485impl fmt::Debug for ProxyKeyPair {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 f.debug_struct("ProxyKeyPair")
488 .field("sk", &"<redacted>")
489 .field("pk", &Hex::encode_to_string(self.pk).unwrap())
490 .finish()
491 }
492}
493
494impl fmt::Display for ProxyKeyPair {
495 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496 write!(
497 f,
498 "ProxyKeyPair(pk: {}...)",
499 &Hex::encode_to_string(self.pk).unwrap()[..8]
500 )
501 }
502}
503
504impl AsRef<Point> for ProxyKeyPair {
505 fn as_ref(&self) -> &Point {
506 &self.pk
507 }
508}
509
510impl From<&ProxyKeyPair> for Point {
511 fn from(proxy: &ProxyKeyPair) -> Self {
512 proxy.pk
513 }
514}
515
516impl Hash for ProxyKeyPair {
517 fn hash<H: Hasher>(&self, state: &mut H) {
518 self.pk.hash(state);
520 }
521}
522
523#[derive(Clone, PartialEq, Eq, Debug)]
525pub struct ProxyKeys {
526 pub sk: Scalar,
528 pub pk: Point,
530}
531
532impl ProxyKeys {
533 pub fn new(sk: Scalar, pk: Point) -> Self {
535 ProxyKeys { sk, pk }
536 }
537}
538
539pub fn derive_proxy_keys(
554 b_keys: &KeyPair,
555 a_pub: &Point,
556 token: &DelegationToken,
557) -> Result<ProxyKeys> {
558 if !utils::memcmp(&b_keys.pk, &token.b_pub) {
560 return Err(ProxySignatureError::ProxyKeyMismatch);
561 }
562
563 if !token.verify(a_pub)? {
564 return Err(ProxySignatureError::InvalidDelegation);
565 }
566
567 let e_w = hash_to_scalar(&[&token.warrant, &token.rw, &token.b_pub])?;
569 let xp = ristretto255::scalar_add(&token.sw, &b_keys.sk)?;
570
571 let yp_from_secret = scalarmult_ristretto::scalarmult_base(&xp)?;
573 let e_times_a = scalarmult_ristretto::scalarmult(&e_w, a_pub)?;
574 let temp = ristretto255::add(&token.rw, &e_times_a)?;
575 let yp_from_relation = ristretto255::add(&temp, &b_keys.pk)?;
576
577 if !utils::memcmp(&yp_from_secret, &yp_from_relation) {
579 return Err(ProxySignatureError::ProxyKeyMismatch);
580 }
581
582 Ok(ProxyKeys::new(xp, yp_from_secret))
583}
584
585pub fn derive_proxy_key_pair(
599 b_keys: &KeyPair,
600 a_pub: &Point,
601 token: &DelegationToken,
602) -> Result<ProxyKeyPair> {
603 let keys = derive_proxy_keys(b_keys, a_pub, token)?;
604 Ok(ProxyKeyPair {
605 sk: keys.sk,
606 pk: keys.pk,
607 })
608}
609
610pub fn proxy_sign(xp: &Scalar, message: &[u8]) -> Result<SchnorrSignature> {
622 SchnorrSignature::sign(xp, message)
623}
624
625pub fn proxy_verify(
640 a_pub: &Point,
641 b_pub: &Point,
642 message: &[u8],
643 sig: &SchnorrSignature,
644 ctx: &ProxyPublicContext,
645) -> Result<bool> {
646 let e_w = hash_to_scalar(&[&ctx.warrant, &ctx.rw, b_pub])?;
648 let e_times_a = scalarmult_ristretto::scalarmult(&e_w, a_pub)?;
649 let temp = ristretto255::add(&ctx.rw, &e_times_a)?;
650 let yp = ristretto255::add(&temp, b_pub)?;
651
652 if !utils::memcmp(&ctx.yp, &yp) {
654 return Ok(false);
655 }
656
657 sig.verify(&yp, message)
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn test_key_generation() -> Result<()> {
666 let keypair = KeyPair::new()?;
667 assert_eq!(keypair.sk.len(), ristretto255::SCALARBYTES);
668 assert_eq!(keypair.pk.len(), ristretto255::BYTES);
669
670 let keypair2 = KeyPair::new()?;
672 assert!(!utils::memcmp(&keypair.sk, &keypair2.sk));
673 assert!(!utils::memcmp(&keypair.pk, &keypair2.pk));
674
675 Ok(())
676 }
677
678 #[test]
679 fn test_schnorr_signature() -> Result<()> {
680 let keypair = KeyPair::new()?;
681 let message = b"Test message for signing";
682
683 let sig = SchnorrSignature::sign(&keypair.sk, message)?;
685 assert!(sig.verify(&keypair.pk, message)?);
686
687 let wrong_message = b"Different message";
689 assert!(!sig.verify(&keypair.pk, wrong_message)?);
690
691 let other_keypair = KeyPair::new()?;
693 assert!(!sig.verify(&other_keypair.pk, message)?);
694
695 Ok(())
696 }
697
698 #[test]
699 fn test_delegation_token() -> Result<()> {
700 let a = KeyPair::new()?;
701 let b = KeyPair::new()?;
702 let warrant = b"Proxy B may sign for A until 2024-12-31";
703
704 let token = DelegationToken::create(&a, &b.pk, warrant)?;
706 assert!(token.verify(&a.pk)?);
707
708 assert!(!token.verify(&b.pk)?);
710
711 let mut bad_token = token.clone();
713 bad_token.warrant = b"Modified warrant".to_vec();
714 assert!(!bad_token.verify(&a.pk)?);
715
716 Ok(())
717 }
718
719 #[test]
720 fn test_proxy_signature_flow() -> Result<()> {
721 let a = KeyPair::new()?; let b = KeyPair::new()?; let warrant = b"Proxy B may sign on behalf of A for project X";
727 let token = DelegationToken::create(&a, &b.pk, warrant)?;
728 assert!(token.verify(&a.pk)?);
729
730 let keys = derive_proxy_keys(&b, &a.pk, &token)?;
732
733 let message = b"Authorize payment of 100 units";
735 let sig = proxy_sign(&keys.sk, message)?;
736
737 let ctx = ProxyPublicContext {
739 warrant: warrant.to_vec(),
740 rw: token.rw,
741 yp: keys.pk,
742 };
743
744 assert!(proxy_verify(&a.pk, &b.pk, message, &sig, &ctx)?);
745
746 let wrong_message = b"Authorize payment of 200 units";
748 assert!(!proxy_verify(&a.pk, &b.pk, wrong_message, &sig, &ctx)?);
749
750 let bad_ctx = ProxyPublicContext {
752 warrant: b"Different warrant".to_vec(),
753 rw: token.rw,
754 yp: keys.pk,
755 };
756 assert!(!proxy_verify(&a.pk, &b.pk, message, &sig, &bad_ctx)?);
757
758 let c = KeyPair::new()?;
760 assert!(!proxy_verify(&a.pk, &c.pk, message, &sig, &ctx)?);
761
762 Ok(())
763 }
764
765 #[test]
766 fn test_invalid_delegation() -> Result<()> {
767 let a = KeyPair::new()?;
768 let b = KeyPair::new()?;
769 let warrant = b"Test warrant";
770
771 let mut token = DelegationToken::create(&a, &b.pk, warrant)?;
773
774 token.sw[0] ^= 0xFF;
776
777 match derive_proxy_keys(&b, &a.pk, &token) {
779 Err(ProxySignatureError::InvalidDelegation) => (),
780 _ => panic!("Expected InvalidDelegation error"),
781 }
782
783 Ok(())
784 }
785
786 #[test]
787 fn test_proxy_binding_prevents_transfer() -> Result<()> {
788 let a = KeyPair::new()?;
789 let b = KeyPair::new()?;
790 let c = KeyPair::new()?; let warrant = b"Proxy B may sign for A";
792
793 let token = DelegationToken::create(&a, &b.pk, warrant)?;
795 assert!(token.verify(&a.pk)?);
796
797 let keys_b = derive_proxy_keys(&b, &a.pk, &token)?;
799 assert_eq!(keys_b.sk.len(), 32);
800 assert_eq!(keys_b.pk.len(), 32);
801
802 match derive_proxy_keys(&c, &a.pk, &token) {
804 Err(ProxySignatureError::ProxyKeyMismatch) => (),
805 _ => panic!("Expected ProxyKeyMismatch error for wrong proxy"),
806 }
807
808 let message = b"Test message";
810 let sig = proxy_sign(&keys_b.sk, message)?;
811
812 let ctx = ProxyPublicContext {
814 warrant: warrant.to_vec(),
815 rw: token.rw,
816 yp: keys_b.pk,
817 };
818 assert!(proxy_verify(&a.pk, &b.pk, message, &sig, &ctx)?);
819
820 assert!(!proxy_verify(&a.pk, &c.pk, message, &sig, &ctx)?);
822
823 Ok(())
824 }
825
826 #[test]
827 fn test_proxy_cannot_redelegate() -> Result<()> {
828 let a = KeyPair::new()?;
829 let b = KeyPair::new()?;
830 let warrant_ab = b"Proxy B may sign for A";
831
832 let token_ab = DelegationToken::create(&a, &b.pk, warrant_ab)?;
834 assert!(token_ab.verify(&a.pk)?);
835
836 let proxy_keys = derive_proxy_key_pair(&b, &a.pk, &token_ab)?;
838
839 let message = b"Test message";
841 let sig = proxy_keys.sign(message)?;
842 assert!(sig.verify(&proxy_keys.pk, message)?);
843
844 let sig2 = proxy_sign(&proxy_keys.sk, b"Another message")?;
853 assert_eq!(sig2.s.len(), 32);
854
855 Ok(())
856 }
857
858 #[test]
859 fn test_trait_implementations() -> Result<()> {
860 let keypair = KeyPair::new()?;
862 let debug_str = format!("{:?}", keypair);
863 assert!(debug_str.contains("<redacted>"));
864 assert!(!debug_str.contains(&Hex::encode_to_string(keypair.sk).unwrap()));
865
866 let display_str = format!("{}", keypair);
868 assert!(display_str.starts_with("KeyPair(pk: "));
869
870 let keypair2 = KeyPair::from_secret_key(keypair.sk)?;
872 assert_eq!(keypair, keypair2);
873
874 use std::collections::HashMap;
876 let mut map = HashMap::new();
877 map.insert(keypair.clone(), "test value");
878 assert_eq!(map.get(&keypair2), Some(&"test value"));
879
880 assert_eq!(keypair.public_key(), &keypair.pk);
882 assert_eq!(keypair.secret_key(), &keypair.sk);
883
884 let pk_ref: &Point = keypair.as_ref();
886 assert_eq!(pk_ref, &keypair.pk);
887
888 let pk: Point = Point::from(&keypair);
890 assert_eq!(pk, keypair.pk);
891
892 let b = KeyPair::new()?;
894 let warrant = b"Test warrant";
895 let token = DelegationToken::create(&keypair, &b.pk, warrant)?;
896
897 let token_debug = format!("{:?}", token);
899 assert!(token_debug.contains("<redacted>"));
900 assert!(!token_debug.contains(&Hex::encode_to_string(token.sw).unwrap()));
901
902 let token_display = format!("{}", token);
904 assert!(token_display.contains("Test warrant"));
905
906 assert_eq!(token.warrant_str()?, "Test warrant");
908 assert_eq!(token.proxy_public_key(), &b.pk);
909
910 let ctx = ProxyPublicContext::from_token(&token, keypair.pk);
912 assert_eq!(ctx.warrant, token.warrant);
913 assert_eq!(ctx.rw, token.rw);
914
915 let msg = b"Test message";
917 let sig = SchnorrSignature::sign(&keypair.sk, msg)?;
918 let sig2 = SchnorrSignature::sign(&keypair.sk, msg)?;
919 assert_ne!(sig, sig2); let mut sig_map = HashMap::new();
923 sig_map.insert(sig.clone(), "signature 1");
924 assert_eq!(sig_map.get(&sig), Some(&"signature 1"));
925
926 Ok(())
927 }
928}