1use anyhow::{Context, Result};
20use blake3::Hasher;
21use serde::{Deserialize, Serialize};
22use std::fmt;
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct FourWordsV1 {
27 indices: [u16; 4],
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub struct Key([u8; 32]);
34
35pub type Word = String;
37
38impl FourWordsV1 {
39 pub fn new(indices: [u16; 4]) -> Self {
41 Self { indices }
42 }
43
44 pub fn indices(&self) -> &[u16; 4] {
46 &self.indices
47 }
48}
49
50impl Key {
51 pub fn new(bytes: [u8; 32]) -> Self {
53 Self(bytes)
54 }
55
56 pub fn as_bytes(&self) -> &[u8; 32] {
58 &self.0
59 }
60
61 pub fn to_hex(&self) -> String {
63 hex::encode(self.0)
64 }
65
66 pub fn from_hex(s: &str) -> Result<Self> {
68 let bytes = hex::decode(s).context("Invalid hex")?;
69 if bytes.len() != 32 {
70 anyhow::bail!("Key must be 32 bytes");
71 }
72 let mut arr = [0u8; 32];
73 arr.copy_from_slice(&bytes);
74 Ok(Self(arr))
75 }
76}
77
78impl From<[u8; 32]> for Key {
79 fn from(value: [u8; 32]) -> Self {
80 Key(value)
81 }
82}
83
84impl From<Key> for [u8; 32] {
85 fn from(value: Key) -> Self {
86 value.0
87 }
88}
89
90impl fmt::Display for Key {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "{}", self.to_hex())
93 }
94}
95
96pub fn fw_check(words: [Word; 4]) -> bool {
98 let enc = four_word_networking::FourWordEncoding::new(
101 words[0].clone(),
102 words[1].clone(),
103 words[2].clone(),
104 words[3].clone(),
105 );
106 four_word_networking::FourWordEncoder::new()
107 .decode_ipv4(&enc)
108 .is_ok()
109}
110
111pub fn fw_to_key(words: [Word; 4]) -> Result<Key> {
113 if !fw_check(words.clone()) {
115 anyhow::bail!("Invalid four-words");
116 }
117
118 let joined = words.join("-");
120 let mut hasher = Hasher::new();
121 hasher.update(joined.as_bytes());
122 let hash = hasher.finalize();
123
124 Ok(Key(*hash.as_bytes()))
125}
126
127pub fn compute_key(context: &str, content: &[u8]) -> Key {
129 let mut hasher = Hasher::new();
130 hasher.update(context.as_bytes());
131 hasher.update(content);
132 let hash = hasher.finalize();
133 Key(*hash.as_bytes())
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_fw_check() {
142 let test_words = [
146 "word1".to_string(),
147 "word2".to_string(),
148 "word3".to_string(),
149 "word4".to_string(),
150 ];
151 let _ = fw_check(test_words);
153
154 let invalid = [
156 "".to_string(),
157 "".to_string(),
158 "".to_string(),
159 "".to_string(),
160 ];
161 assert!(!fw_check(invalid));
162 }
163
164 #[test]
165 fn test_fw_to_key() {
166 let invalid_words = [
169 "notindictionary1".to_string(),
170 "notindictionary2".to_string(),
171 "notindictionary3".to_string(),
172 "notindictionary4".to_string(),
173 ];
174
175 let result = fw_to_key(invalid_words);
177 assert!(result.is_err());
178
179 }
182
183 #[test]
184 fn test_key_hex() {
185 let bytes = [42u8; 32];
186 let key = Key::new(bytes);
187 let hex = key.to_hex();
188 let recovered = Key::from_hex(&hex).unwrap();
189 assert_eq!(key, recovered);
190 }
191}