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}