Skip to main content

lichen_client_sdk/
keypair.rs

1//! Keypair and public key management
2
3pub use lichen_core::{Address, Keypair as CoreKeypair, PqPublicKey, PqSignature, Pubkey};
4
5/// Keypair wrapper with SDK convenience methods
6pub struct Keypair(CoreKeypair);
7
8impl Keypair {
9    /// Generate a new random keypair
10    pub fn new() -> Self {
11        Self(CoreKeypair::new())
12    }
13
14    /// Create keypair from seed bytes
15    pub fn from_seed(seed: &[u8; 32]) -> Self {
16        Self(CoreKeypair::from_seed(seed))
17    }
18
19    /// Get the 32-byte account address.
20    pub fn pubkey(&self) -> Pubkey {
21        self.0.pubkey()
22    }
23
24    /// Get the full PQ verifying key.
25    pub fn public_key(&self) -> PqPublicKey {
26        self.0.public_key()
27    }
28
29    /// Get seed for saving
30    pub fn to_seed(&self) -> [u8; 32] {
31        self.0.to_seed()
32    }
33
34    /// Sign a message with a self-contained PQ signature.
35    pub fn sign(&self, message: &[u8]) -> PqSignature {
36        self.0.sign(message)
37    }
38
39    /// Verify a self-contained PQ signature against an address.
40    pub fn verify(address: &Pubkey, message: &[u8], signature: &PqSignature) -> bool {
41        CoreKeypair::verify(address, message, signature)
42    }
43
44    /// Get reference to inner keypair
45    pub fn inner(&self) -> &CoreKeypair {
46        &self.0
47    }
48}
49
50impl Default for Keypair {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn new_keypair_has_unique_pubkey() {
62        let kp1 = Keypair::new();
63        let kp2 = Keypair::new();
64        assert_ne!(kp1.pubkey(), kp2.pubkey());
65    }
66
67    #[test]
68    fn from_seed_deterministic() {
69        let seed = [42u8; 32];
70        let kp1 = Keypair::from_seed(&seed);
71        let kp2 = Keypair::from_seed(&seed);
72        assert_eq!(kp1.pubkey(), kp2.pubkey());
73    }
74
75    #[test]
76    fn different_seeds_different_keys() {
77        let kp1 = Keypair::from_seed(&[1u8; 32]);
78        let kp2 = Keypair::from_seed(&[2u8; 32]);
79        assert_ne!(kp1.pubkey(), kp2.pubkey());
80    }
81
82    #[test]
83    fn to_seed_roundtrip() {
84        let seed = [99u8; 32];
85        let kp = Keypair::from_seed(&seed);
86        assert_eq!(kp.to_seed(), seed);
87    }
88
89    #[test]
90    fn public_key_roundtrip_matches_address() {
91        let kp = Keypair::new();
92        assert_eq!(kp.public_key().address(), kp.pubkey());
93    }
94
95    #[test]
96    fn sign_produces_valid_pq_signature() {
97        let kp = Keypair::new();
98        let sig = kp.sign(b"hello lichen");
99        assert!(sig.validate().is_ok());
100        assert_eq!(sig.signer_address(), kp.pubkey());
101        assert!(sig.sig.len() > 64);
102    }
103
104    #[test]
105    fn sign_deterministic() {
106        let kp = Keypair::from_seed(&[7u8; 32]);
107        let sig1 = kp.sign(b"msg");
108        let sig2 = kp.sign(b"msg");
109        assert_eq!(sig1, sig2);
110    }
111
112    #[test]
113    fn sign_different_messages_differ() {
114        let kp = Keypair::new();
115        let sig1 = kp.sign(b"aaa");
116        let sig2 = kp.sign(b"bbb");
117        assert_ne!(sig1, sig2);
118    }
119
120    #[test]
121    fn inner_returns_core_keypair() {
122        let kp = Keypair::from_seed(&[5u8; 32]);
123        assert_eq!(kp.inner().pubkey(), kp.pubkey());
124    }
125
126    #[test]
127    fn verify_accepts_self_contained_signature() {
128        let kp = Keypair::from_seed(&[6u8; 32]);
129        let message = b"pq rust sdk";
130        let signature = kp.sign(message);
131        assert!(Keypair::verify(&kp.pubkey(), message, &signature));
132        assert!(!Keypair::verify(&kp.pubkey(), b"tampered", &signature));
133    }
134
135    #[test]
136    fn default_works() {
137        let kp = Keypair::default();
138        // just verify it doesn't panic
139        let _ = kp.pubkey();
140    }
141
142    #[test]
143    fn pubkey_is_32_bytes() {
144        let kp = Keypair::new();
145        assert_eq!(kp.pubkey().0.len(), 32);
146    }
147}