aleo_agent/account.rs
1//! Account implementations
2
3use std::fmt::{Debug, Formatter};
4use std::str::FromStr;
5
6use super::*;
7use anyhow::{anyhow, Result};
8use indexmap::IndexMap;
9use once_cell::sync::OnceCell;
10use rand_chacha::rand_core::SeedableRng;
11use rand_chacha::ChaChaRng;
12
13#[derive(Clone)]
14pub struct Account {
15 private_key: PrivateKey,
16 view_key: ViewKey,
17 address: Address,
18}
19
20impl Debug for Account {
21 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
22 f.debug_struct("Account")
23 .field("private_key", &self.private_key.to_string())
24 .field("view_key", &self.view_key.to_string())
25 .field("address", &self.address.to_string())
26 .finish()
27 }
28}
29
30impl Default for Account {
31 fn default() -> Account {
32 Self::from_seed(Default::default()).unwrap()
33 }
34}
35
36impl Account {
37 /// Generates a new `Account` using a random seed.
38 pub fn new() -> Result<Self> {
39 let (private_key, view_key, address) = generate_keypair()?;
40 Ok(Account {
41 private_key,
42 view_key,
43 address,
44 })
45 }
46
47 /// Returns the private key of the account.
48 pub fn private_key(&self) -> &PrivateKey {
49 &self.private_key
50 }
51
52 /// Returns the address of the account.
53 pub fn address(&self) -> &Address {
54 &self.address
55 }
56
57 /// Returns the view key of the account.
58 pub fn view_key(&self) -> &ViewKey {
59 &self.view_key
60 }
61
62 /// Encrypts the private key into a ciphertext using a secret.
63 ///
64 /// # Arguments
65 /// * `secret` - The secret used for encryption.
66 ///
67 /// # Returns
68 /// The ciphertext.
69 ///
70 /// # Example
71 /// ```ignore
72 /// use std::str::FromStr;
73 /// use aleo_agent::account::Account;
74 /// use aleo_agent::PrivateKey;
75 ///
76 /// let acc = Account::from_private_key("PRIVATE KEY").unwrap();
77 /// let encrypted_key = acc.get_encrypted_key("secret").expect("failed to encrypt key");
78 /// let recover_account = Account::from_encrypted_key(&encrypted_key, "secret").expect("failed to decrypt key");
79 ///
80 /// assert_eq!(acc.private_key().to_string(), recover_account.private_key().to_string());
81 /// ```
82 pub fn get_encrypted_key(&self, secret: &str) -> Result<Ciphertext> {
83 encrypt_field(&self.private_key.seed(), secret, "private_key")
84 }
85
86 /// Signs a message with the private key.
87 ///
88 /// # Arguments
89 /// * `msg` - The message to sign.
90 ///
91 /// # Returns
92 /// The signature.
93 ///
94 /// # Example
95 /// ```
96 /// use aleo_agent::account::Account;
97 ///
98 /// let acc = Account::new().unwrap();
99 /// let sig = acc.sign("hello".as_bytes()).expect("failed to sign message");
100 ///
101 /// assert!(acc.verify("hello".as_bytes(), &sig));
102 /// ```
103 pub fn sign(&self, msg: &[u8]) -> Result<Signature> {
104 let mut rng = ChaChaRng::from_entropy();
105 self.private_key.sign_bytes(msg, &mut rng)
106 }
107
108 /// Verifies a message signature.
109 ///
110 /// # Arguments
111 /// * `msg` - The message to verify.
112 /// * `signature` - The signature to verify.
113 ///
114 /// # Returns
115 /// `true` if the signature is valid, `false` otherwise.
116 pub fn verify(&self, msg: &[u8], signature: &Signature) -> bool {
117 signature.verify_bytes(&self.address, msg)
118 }
119}
120
121impl Account {
122 /// Generates a new `Account` from a seed.
123 ///
124 /// # Example
125 /// ```
126 /// use rand::Rng;
127 /// use rand_chacha::ChaChaRng;
128 /// use rand_chacha::rand_core::{SeedableRng};
129 /// use aleo_agent::account::Account;
130 /// use aleo_agent::PrivateKey;
131 ///
132 /// let mut rng = ChaChaRng::from_entropy();
133 /// let seed : u64 = rng.gen();
134 /// let account = Account::from_seed(seed).unwrap();
135 ///
136 /// let mut rng_from_seed = ChaChaRng::seed_from_u64(seed);
137 /// let private_key = PrivateKey::new(&mut rng_from_seed).expect("failed to recover private key from seed");
138 ///
139 /// assert_eq!(account.private_key().to_string(), private_key.to_string());
140 /// ```
141 pub fn from_seed(seed: u64) -> Result<Self> {
142 let (private_key, view_key, address) = generate_keypair_from_seed(seed)?;
143 Ok(Account {
144 private_key,
145 view_key,
146 address,
147 })
148 }
149
150 /// Generates a new `Account` from a private key string.
151 ///
152 /// # Example
153 /// ```ignore
154 /// use std::str::FromStr;
155 /// use aleo_agent::account::Account;
156 /// use aleo_agent::PrivateKey;
157 ///
158 /// let private_key = PrivateKey::from_str("YOUR PRIVATE KEY").unwrap();
159 /// let account = Account::from_private_key("YOUR PRIVATE KEY").unwrap();
160 ///
161 /// assert_eq!(account.private_key().to_string(), private_key.to_string());
162 /// ```
163 pub fn from_private_key(key: &str) -> Result<Self> {
164 let private_key = PrivateKey::from_str(key)?;
165 let view_key = ViewKey::try_from(&private_key)?;
166 let address = Address::try_from(&private_key)?;
167 Ok(Account {
168 private_key,
169 view_key,
170 address,
171 })
172 }
173
174 /// Decrypts a private key from ciphertext using a secret.
175 ///
176 /// # Arguments
177 /// * `ciphertext` - The ciphertext of the encrypted private key.
178 /// * `secret` - The secret used for decryption.
179 ///
180 /// # Returns
181 /// The decrypted `Account`.
182 ///
183 /// # Example
184 /// ```ignore
185 /// use std::str::FromStr;
186 /// use aleo_agent::account::Account;
187 /// use aleo_agent::PrivateKey;
188 ///
189 /// let acc = Account::from_private_key("YOUR PRIVATE KET").unwrap();
190 /// let encrypted_key = acc.get_encrypted_key("SECRET").expect("failed to encrypt key");
191 /// let recover_account = Account::from_encrypted_key(&encrypted_key, "secret").expect("failed to decrypt key");
192 ///
193 /// assert_eq!(acc.private_key().to_string(), recover_account.private_key().to_string());
194 /// ```
195 pub fn from_encrypted_key(ciphertext: &Ciphertext, secret: &str) -> Result<Self> {
196 let seed = decrypt_field(ciphertext, secret, "private_key")?;
197 let private_key = PrivateKey::try_from(seed)?;
198 let view_key = ViewKey::try_from(&private_key)?;
199 let address = Address::try_from(&private_key)?;
200 Ok(Account {
201 private_key,
202 view_key,
203 address,
204 })
205 }
206}
207
208// Encrypted a field element into a ciphertext representation
209fn encrypt_field(field: &Field, secret: &str, domain: &str) -> Result<Ciphertext> {
210 // Derive the domain separators and the secret.
211 let domain = Field::new_domain_separator(domain);
212 let secret = Field::new_domain_separator(secret);
213
214 // Generate a nonce
215 let mut rng = rand::thread_rng();
216 let nonce = Uniform::rand(&mut rng);
217
218 // Derive a blinding factor and create an encryption target
219 let blinding = CurrentNetwork::hash_psd2(&[domain, nonce, secret])?;
220 let key = blinding * field;
221 let plaintext = Plaintext::Struct(
222 IndexMap::from_iter(vec![
223 (
224 Identifier::from_str("key")?,
225 Plaintext::from(Literal::Field(key)),
226 ),
227 (
228 Identifier::from_str("nonce")?,
229 Plaintext::from(Literal::Field(nonce)),
230 ),
231 ]),
232 OnceCell::new(),
233 );
234 plaintext.encrypt_symmetric(secret)
235}
236
237// Recover a field element encrypted within ciphertext
238fn decrypt_field(ciphertext: &Ciphertext, secret: &str, domain: &str) -> Result<Field> {
239 let domain = Field::new_domain_separator(domain);
240 let secret = Field::new_domain_separator(secret);
241 let decrypted = ciphertext.decrypt_symmetric(secret)?;
242 let recovered_key = extract_value(&decrypted, "key")?;
243 let recovered_nonce = extract_value(&decrypted, "nonce")?;
244 let recovered_blinding = CurrentNetwork::hash_psd2(&[domain, recovered_nonce, secret])?;
245 Ok(recovered_key / recovered_blinding)
246}
247
248// Extract a field element from a plaintext
249fn extract_value(plaintext: &Plaintext, identifier: &str) -> Result<Field> {
250 let identity = Identifier::from_str(identifier)?;
251 let value = plaintext.find(&[identity])?;
252 match value {
253 Plaintext::Literal(literal, ..) => match literal {
254 Literal::Field(recovered_value) => Ok(recovered_value),
255 _ => Err(anyhow!("Wrong literal type")),
256 },
257 _ => Err(anyhow!("Expected literal")),
258 }
259}
260
261fn generate_keypair_from_seed(seed: u64) -> Result<(PrivateKey, ViewKey, Address)> {
262 let mut rng = ChaChaRng::seed_from_u64(seed);
263 let private_key = PrivateKey::new(&mut rng)?;
264 let view_key = ViewKey::try_from(&private_key)?;
265 let address = Address::try_from(&private_key)?;
266 Ok((private_key, view_key, address))
267}
268
269fn generate_keypair() -> Result<(PrivateKey, ViewKey, Address)> {
270 let mut rng = ChaChaRng::from_entropy();
271 let private_key = PrivateKey::new(&mut rng)?;
272 let view_key = ViewKey::try_from(&private_key)?;
273 let address = Address::try_from(&private_key)?;
274 Ok((private_key, view_key, address))
275}