Skip to main content

guardian_shared/
hex.rs

1use miden_protocol::Word;
2use miden_protocol::crypto::dsa::falcon512_poseidon2::{PublicKey, Signature};
3use miden_protocol::utils::serde::{Deserializable, Serializable};
4
5/// Trait for converting types to hex strings with `0x` prefix
6pub trait IntoHex {
7    fn into_hex(self) -> String;
8}
9
10/// Trait for parsing types from hex strings (with or without `0x` prefix)
11pub trait FromHex: Sized {
12    fn from_hex(hex: &str) -> Result<Self, String>;
13}
14
15impl IntoHex for &PublicKey {
16    fn into_hex(self) -> String {
17        let mut pubkey_bytes = Vec::new();
18        self.write_into(&mut pubkey_bytes);
19        format!("0x{}", hex::encode(pubkey_bytes))
20    }
21}
22
23impl IntoHex for PublicKey {
24    fn into_hex(self) -> String {
25        (&self).into_hex()
26    }
27}
28
29impl FromHex for PublicKey {
30    fn from_hex(hex: &str) -> Result<Self, String> {
31        let hex_str = hex.strip_prefix("0x").unwrap_or(hex);
32        let bytes = hex::decode(hex_str).map_err(|e| format!("Invalid public key hex: {e}"))?;
33        PublicKey::read_from_bytes(&bytes)
34            .map_err(|e| format!("Failed to deserialize public key: {e}"))
35    }
36}
37
38impl IntoHex for Signature {
39    fn into_hex(self) -> String {
40        let signature_bytes = self.to_bytes();
41        format!("0x{}", hex::encode(&signature_bytes))
42    }
43}
44
45impl FromHex for Signature {
46    fn from_hex(hex: &str) -> Result<Self, String> {
47        let hex_str = hex.strip_prefix("0x").unwrap_or(hex);
48        let bytes = hex::decode(hex_str).map_err(|e| format!("Invalid signature hex: {e}"))?;
49
50        const EXPECTED_SIG_LEN: usize = 1524;
51        if bytes.len() != EXPECTED_SIG_LEN {
52            return Err(format!(
53                "Signature must be exactly {EXPECTED_SIG_LEN} bytes, got {} bytes",
54                bytes.len()
55            ));
56        }
57
58        Signature::read_from_bytes(&bytes)
59            .map_err(|e| format!("Failed to deserialize signature: {e}"))
60    }
61}
62
63impl IntoHex for Word {
64    fn into_hex(self) -> String {
65        format!("0x{}", hex::encode(self.as_bytes()))
66    }
67}
68
69impl IntoHex for &Word {
70    fn into_hex(self) -> String {
71        format!("0x{}", hex::encode(self.as_bytes()))
72    }
73}
74
75impl FromHex for Word {
76    fn from_hex(hex: &str) -> Result<Self, String> {
77        let hex_str = hex.strip_prefix("0x").unwrap_or(hex);
78        let bytes = hex::decode(hex_str).map_err(|e| format!("Invalid word hex: {e}"))?;
79
80        Word::read_from_bytes(&bytes).map_err(|e| format!("Failed to deserialize word: {e}"))
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey;
88
89    #[test]
90    fn test_public_key_into_hex() {
91        let secret_key = SecretKey::new();
92        let public_key = secret_key.public_key();
93
94        // Test reference implementation
95        let hex1 = (&public_key).into_hex();
96        assert!(hex1.starts_with("0x"));
97        assert_eq!(hex1.len(), 2 + (897 * 2));
98
99        // Test owned implementation
100        let hex2 = public_key.into_hex();
101        assert_eq!(hex1, hex2);
102    }
103
104    #[test]
105    fn test_public_key_from_hex_roundtrip() {
106        let secret_key = SecretKey::new();
107        let original_pubkey = secret_key.public_key();
108
109        // Convert to hex
110        let hex = original_pubkey.into_hex();
111
112        // Parse back from hex
113        let parsed_pubkey = PublicKey::from_hex(&hex).expect("Failed to parse public key");
114
115        // Verify roundtrip
116        assert_eq!(hex, parsed_pubkey.into_hex());
117    }
118
119    #[test]
120    fn test_public_key_from_hex_without_prefix() {
121        let secret_key = SecretKey::new();
122        let public_key = secret_key.public_key();
123        let hex_with_prefix = public_key.into_hex();
124
125        // Remove 0x prefix
126        let hex_without_prefix = hex_with_prefix.strip_prefix("0x").unwrap();
127
128        // Both should parse successfully
129        let pubkey1 = PublicKey::from_hex(&hex_with_prefix).unwrap();
130        let pubkey2 = PublicKey::from_hex(hex_without_prefix).unwrap();
131
132        assert_eq!(pubkey1.into_hex(), pubkey2.into_hex());
133    }
134
135    #[test]
136    fn test_signature_into_hex() {
137        use miden_protocol::Word;
138        let secret_key = SecretKey::new();
139        let message = Word::from([1u32, 2, 3, 4]);
140        let signature = secret_key.sign(message);
141
142        let hex = signature.into_hex();
143        assert!(hex.starts_with("0x"));
144        assert_eq!(hex.len(), 2 + (1524 * 2));
145    }
146
147    #[test]
148    fn test_signature_from_hex_roundtrip() {
149        use miden_protocol::Word;
150        let secret_key = SecretKey::new();
151        let message = Word::from([1u32, 2, 3, 4]);
152        let original_sig = secret_key.sign(message);
153
154        // Convert to hex
155        let hex = original_sig.into_hex();
156
157        // Parse back from hex
158        let parsed_sig = Signature::from_hex(&hex).expect("Failed to parse signature");
159
160        // Verify roundtrip
161        assert_eq!(hex, parsed_sig.into_hex());
162    }
163
164    #[test]
165    fn test_signature_from_hex_validates_length() {
166        // Too short
167        let result = Signature::from_hex("0x1234");
168        assert!(result.is_err());
169        assert!(result.unwrap_err().contains("1524 bytes"));
170    }
171
172    #[test]
173    fn test_word_into_hex() {
174        let word = Word::from([1u32, 2, 3, 4]);
175        let hex = word.into_hex();
176        assert!(hex.starts_with("0x"));
177        // Word is 32 bytes (4 x 8-byte Felt values)
178        assert_eq!(hex.len(), 2 + (32 * 2));
179    }
180
181    #[test]
182    fn test_word_from_hex_roundtrip() {
183        let original = Word::from([0xdeadbeefu32, 0xcafebabe, 0x12345678, 0x87654321]);
184        let hex = original.into_hex();
185        let parsed = Word::from_hex(&hex).expect("Failed to parse word");
186        assert_eq!(original, parsed);
187    }
188
189    #[test]
190    fn test_word_from_hex_without_prefix() {
191        let word = Word::from([1u32, 2, 3, 4]);
192        let hex_with_prefix = word.into_hex();
193        let hex_without_prefix = hex_with_prefix.strip_prefix("0x").unwrap();
194
195        let word1 = Word::from_hex(&hex_with_prefix).unwrap();
196        let word2 = Word::from_hex(hex_without_prefix).unwrap();
197        assert_eq!(word1, word2);
198    }
199
200    #[test]
201    fn test_word_reference_into_hex() {
202        let word = Word::from([1u32, 2, 3, 4]);
203        let hex1 = (&word).into_hex();
204        let hex2 = word.into_hex();
205        assert_eq!(hex1, hex2);
206    }
207}