fuels_signers/
lib.rs

1extern crate core;
2
3pub mod provider;
4pub mod wallet;
5
6use std::error::Error;
7
8use async_trait::async_trait;
9#[doc(no_inline)]
10pub use fuel_crypto;
11use fuel_crypto::Signature;
12use fuels_types::{bech32::Bech32Address, transaction::Transaction};
13pub use wallet::{Wallet, WalletUnlocked};
14
15/// Trait for signing transactions and messages
16///
17/// Implement this trait to support different signing modes, e.g. Ledger, hosted etc.
18#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
19#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
20pub trait Signer: std::fmt::Debug + Send + Sync {
21    type Error: Error + Send + Sync;
22    /// Signs the hash of the provided message
23    async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
24        &self,
25        message: S,
26    ) -> Result<Signature, Self::Error>;
27
28    /// Signs the transaction
29    async fn sign_transaction<T: Transaction + Send>(
30        &self,
31        message: &mut T,
32    ) -> Result<Signature, Self::Error>;
33
34    /// Returns the signer's Fuel Address
35    fn address(&self) -> &Bech32Address;
36}
37
38#[cfg(test)]
39mod tests {
40    use std::str::FromStr;
41
42    use fuel_crypto::{Message, SecretKey};
43    use fuel_tx::{
44        Address, AssetId, Bytes32, Input, Output, Transaction as FuelTransaction, TxPointer, UtxoId,
45    };
46    use fuels_types::transaction::{ScriptTransaction, Transaction};
47    use rand::{rngs::StdRng, RngCore, SeedableRng};
48
49    use super::*;
50    use crate::wallet::WalletUnlocked;
51
52    #[tokio::test]
53    async fn sign_and_verify() -> Result<(), Box<dyn Error>> {
54        // ANCHOR: sign_message
55        let mut rng = StdRng::seed_from_u64(2322u64);
56        let mut secret_seed = [0u8; 32];
57        rng.fill_bytes(&mut secret_seed);
58
59        let secret = unsafe { SecretKey::from_bytes_unchecked(secret_seed) };
60
61        // Create a wallet using the private key created above.
62        let wallet = WalletUnlocked::new_from_private_key(secret, None);
63
64        let message = "my message";
65
66        let signature = wallet.sign_message(message).await?;
67
68        // Check if signature is what we expect it to be
69        assert_eq!(signature, Signature::from_str("0x8eeb238db1adea4152644f1cd827b552dfa9ab3f4939718bb45ca476d167c6512a656f4d4c7356bfb9561b14448c230c6e7e4bd781df5ee9e5999faa6495163d")?);
70
71        // Recover address that signed the message
72        let message = Message::new(message);
73        let recovered_address = signature.recover(&message)?;
74
75        assert_eq!(wallet.address().hash(), recovered_address.hash());
76
77        // Verify signature
78        signature.verify(&recovered_address, &message)?;
79        Ok(())
80        // ANCHOR_END: sign_message
81    }
82
83    #[tokio::test]
84    async fn sign_tx_and_verify() -> Result<(), Box<dyn Error>> {
85        // ANCHOR: sign_tx
86        let secret = SecretKey::from_str(
87            "5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
88        )?;
89        let wallet = WalletUnlocked::new_from_private_key(secret, None);
90
91        // Set up a dummy transaction.
92        let input_coin = Input::coin_signed(
93            UtxoId::new(Bytes32::zeroed(), 0),
94            Address::from_str(
95                "0xf1e92c42b90934aa6372e30bc568a326f6e66a1a0288595e6e3fbd392a4f3e6e",
96            )?,
97            10000000,
98            AssetId::from([0u8; 32]),
99            TxPointer::default(),
100            0,
101            0,
102        );
103
104        let output_coin = Output::coin(
105            Address::from_str(
106                "0xc7862855b418ba8f58878db434b21053a61a2025209889cc115989e8040ff077",
107            )?,
108            1,
109            AssetId::from([0u8; 32]),
110        );
111
112        let mut tx: ScriptTransaction = FuelTransaction::script(
113            0,
114            1000000,
115            0,
116            hex::decode("24400000")?,
117            vec![],
118            vec![input_coin],
119            vec![output_coin],
120            vec![],
121        )
122        .into();
123
124        // Sign the transaction.
125        let signature = wallet.sign_transaction(&mut tx).await?;
126        let message = unsafe { Message::from_bytes_unchecked(*tx.id()) };
127
128        // Check if signature is what we expect it to be
129        assert_eq!(signature, Signature::from_str("34482a581d1fe01ba84900581f5321a8b7d4ec65c3e7ca0de318ff8fcf45eb2c793c4b99e96400673e24b81b7aa47f042cad658f05a84e2f96f365eb0ce5a511")?);
130
131        // Recover address that signed the transaction
132        let recovered_address = signature.recover(&message)?;
133
134        assert_eq!(wallet.address().hash(), recovered_address.hash());
135
136        // Verify signature
137        signature.verify(&recovered_address, &message)?;
138        Ok(())
139        // ANCHOR_END: sign_tx
140    }
141}