fuels_accounts/signers/
private_key.rs

1use async_trait::async_trait;
2use fuel_crypto::{Message, PublicKey, SecretKey, Signature};
3use fuels_core::{
4    traits::Signer,
5    types::{
6        bech32::{Bech32Address, FUEL_BECH32_HRP},
7        errors::Result,
8    },
9};
10use rand::{CryptoRng, Rng, RngCore};
11use zeroize::{Zeroize, ZeroizeOnDrop};
12
13/// Generates a random mnemonic phrase given a random number generator and the number of words to
14/// generate, `count`.
15pub fn generate_mnemonic_phrase<R: Rng>(rng: &mut R, count: usize) -> Result<String> {
16    Ok(fuel_crypto::generate_mnemonic_phrase(rng, count)?)
17}
18
19#[derive(Clone, Zeroize, ZeroizeOnDrop)]
20pub struct PrivateKeySigner {
21    private_key: SecretKey,
22    #[zeroize(skip)]
23    address: Bech32Address,
24}
25
26impl std::fmt::Debug for PrivateKeySigner {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        f.debug_struct("PrivateKeySigner")
29            .field("private_key", &"REDACTED")
30            .field("address", &self.address)
31            .finish()
32    }
33}
34
35impl PrivateKeySigner {
36    pub fn new(private_key: SecretKey) -> Self {
37        let public = PublicKey::from(&private_key);
38        let hashed = public.hash();
39        let address = Bech32Address::new(FUEL_BECH32_HRP, hashed);
40
41        Self {
42            private_key,
43            address,
44        }
45    }
46
47    pub fn random(rng: &mut (impl CryptoRng + RngCore)) -> Self {
48        Self::new(SecretKey::random(rng))
49    }
50
51    pub fn address(&self) -> &Bech32Address {
52        &self.address
53    }
54}
55
56#[async_trait]
57impl Signer for PrivateKeySigner {
58    async fn sign(&self, message: Message) -> Result<Signature> {
59        let sig = Signature::sign(&self.private_key, &message);
60
61        Ok(sig)
62    }
63
64    fn address(&self) -> &Bech32Address {
65        &self.address
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use std::str::FromStr;
72
73    use rand::{rngs::StdRng, SeedableRng};
74
75    use crate::signers::derivation::DEFAULT_DERIVATION_PATH;
76
77    use super::*;
78
79    #[tokio::test]
80    async fn mnemonic_generation() -> Result<()> {
81        let mnemonic = generate_mnemonic_phrase(&mut rand::thread_rng(), 12)?;
82        let _wallet = PrivateKeySigner::new(SecretKey::new_from_mnemonic_phrase_with_path(
83            &mnemonic,
84            DEFAULT_DERIVATION_PATH,
85        )?);
86
87        Ok(())
88    }
89
90    #[tokio::test]
91    async fn sign_and_verify() -> Result<()> {
92        // ANCHOR: sign_message
93        let mut rng = StdRng::seed_from_u64(2322u64);
94        let mut secret_seed = [0u8; 32];
95        rng.fill_bytes(&mut secret_seed);
96
97        let secret = secret_seed.as_slice().try_into()?;
98
99        // Create a signer using the private key created above.
100        let signer = PrivateKeySigner::new(secret);
101
102        let message = Message::new("my message".as_bytes());
103        let signature = signer.sign(message).await?;
104
105        // Check if signature is what we expect it to be
106        assert_eq!(signature, Signature::from_str("0x8eeb238db1adea4152644f1cd827b552dfa9ab3f4939718bb45ca476d167c6512a656f4d4c7356bfb9561b14448c230c6e7e4bd781df5ee9e5999faa6495163d")?);
107
108        // Recover address that signed the message
109        let recovered_address = signature.recover(&message)?;
110
111        assert_eq!(signer.address().hash(), recovered_address.hash());
112
113        // Verify signature
114        signature.verify(&recovered_address, &message)?;
115        // ANCHOR_END: sign_message
116
117        Ok(())
118    }
119}