ethers_signer_factory/
lib.rs

1//! # Key Derivation and Signing Functions
2//!
3//! This crate provides a set of functions for key derivation and signing of Ethereum transactions
4//! and messages. It uses the HKDF (HMAC-based Key Derivation Function) with SHA-512 as the base
5//! key derivation algorithm and SHA-256 for expanding keys.
6//!
7//! ## Functions
8//!
9//! - `get_derived_key`: Computes a derived key of length SHA256_OUTPUT_LEN bytes using HKDF-SHA512.
10//! - `get_wallet_address`: Computes the Ethereum address associated with a derived key.
11//! - `sign_transaction`: Signs an Ethereum transaction using a derived key and returns the
12//!   generated signature.
13//! - `sign_message`: Signs an Ethereum message using a derived key and returns the generated
14//!   signature.
15//! - `sign_typed_data`: Sign EIP-712 typed data using a derived key and returns the generated signature.
16//! - `get_signer`: Returns a signer wallet from a derived key with an chain ID.
17//!
18//! ## Example
19//!
20//! ```rust
21//! #[tokio::main]
22//! async fn main() {
23//!     use ethers_signer_factory::{get_derived_key, get_wallet_address, sign_transaction, sign_message, sign_typed_data, get_signer};
24//!     use ethers_contract_derive::{Eip712, EthAbiType};
25//!     use ethers_core::types::transaction::eip2718::TypedTransaction;
26//!     use ethers_core::types::{TransactionRequest, Address};
27//!    
28//!     let key: [u8; 64] = [0u8; 64];
29//!     let salt: [u8; 64] = [0u8; 64];
30//!     let info = "some_key_id";
31//!    
32//!     // Example 1: Derive a key.
33//!     let derived_key = get_derived_key(&key, &salt, info).expect("Key derivation failed");
34//!     assert_eq!(
35//!         hex::encode(derived_key).as_str(),
36//!         "de61002dc1676e6b59b2dd27da3f32280defe3bc384f77de6eee372f70ceaae7"
37//!     );
38//!    
39//!     // Example 2: Get an Ethereum address from the derived key.
40//!     let wallet_address: Address = get_wallet_address(&key, &salt, info).expect("Wallet creation failed");
41//!     assert_eq!(
42//!         hex::encode(wallet_address.as_bytes()).as_str(),
43//!         "ea4d3e00f3d283cdb0e4d4fb783f208a5095fcb7"
44//!     );
45//!    
46//!     // Example 3. Get a Signer Wallet from the derived key.
47//!     let chain_id = 1;
48//!     let _wallet = get_signer(&key, &salt, info, chain_id).expect("Signer creation failed");
49//!    
50//!     // Example 4: Sign a transaction.
51//!     let tx = TypedTransaction::Legacy(TransactionRequest::default().from(wallet_address));
52//!     let signed_tx =
53//!         sign_transaction(&key, &salt, info, &tx).expect("Transaction signing failed");
54//!    
55//!     assert_eq!(
56//!         hex::encode(signed_tx.to_vec().as_slice()).as_str(),
57//!         "815dd9b736c52fed571b2d5d985c52d78593a6d4a602f8455e884371270760132c8cd1f163af8303dc83aaaf48d2a03bff3697f6c922951809c19c0593841b3426"
58//!     );
59//!    
60//!     // Example 5: Sign a message.
61//!     let message = "Some message to sign";
62//!     let signature = sign_message(&key, &salt, info, message).expect("Message signing failed");
63//!    
64//!     assert_eq!(
65//!         hex::encode(signature.to_vec().as_slice()).as_str(),
66//!         "35867e62e5a2c4dd52947c0cbb4af6afc95564d5abb0584bec2f9669046f81aa0d22dba8e6844fb125b93fec9a3e3684916dc4541e45bdc824cf18b5ab2409c81c"
67//!     );
68//!    
69//!     // Example 6: Sign Typed data.
70//!     #[derive(Clone, Default, EthAbiType, Eip712)]
71//!     struct TestTypedData {
72//!         value: u32,
73//!     }
74//!    
75//!     let test_payload = TestTypedData { value: 1 };
76//!     let signature = sign_typed_data(&key, &salt, info, &test_payload)
77//!         .await
78//!         .expect("Sign typed data failed");
79//!    
80//!     assert_eq!(
81//!         hex::encode(signature.to_vec().as_slice()).as_str(),
82//!         "cdd86e133a50b7d2c4f5bff092dbfe4086ed559bb0718e9c1c9bce31171f0ff44df3cc0dcd20388ad99be26b6eca0c104bff48eb6ad8135bec9d76aee2c6930f1b"
83//!     );
84//! }
85//! ```
86//!
87//! ## Dependencies
88//!
89//! This crate depends on the following external libraries:
90//!
91//! - `ethers_core`: Ethereum core library for types and utilities.
92//! - `ethers_signers`: Ethereum wallet and signing functionality.
93//! - `ring`: Cryptographic library for key derivation.
94//! - `hex`: Hex strings encoding and decoding library.
95//!
96//! ## Error Handling
97//!
98//! Functions in this crate return a `Result` type that can contain errors specific to key
99//! derivation and signing. Refer to the individual function documentation for details on error
100//! types and how to handle them.
101
102use ethers_core::k256::ecdsa::SigningKey;
103use ethers_core::types::transaction::eip712::Eip712;
104use ethers_core::types::{transaction::eip2718::TypedTransaction, Address, Signature};
105use ethers_core::utils::secret_key_to_address;
106use ethers_signers::{Signer, Wallet};
107use ring::digest::{SHA256_OUTPUT_LEN, SHA512_OUTPUT_LEN};
108use ring::hkdf;
109
110pub mod error;
111
112type Result<T> = std::result::Result<T, error::Error>;
113
114/// Derives a cryptographic key of length 32 bytes using the HKDF-SHA512
115/// key derivation function. The input key and salt must both be 64 bytes each.
116///
117/// # Arguments
118///
119/// * `key` - The input key material.
120/// * `salt` - The salt value used for salt extraction.
121/// * `info` - A string that serves as additional context for key derivation.
122///
123/// # Returns
124///
125/// A Result containing the derived key as a fixed-size array of 32 bytes or an error if
126/// key derivation fails.
127pub fn get_derived_key(
128    key: &[u8; SHA512_OUTPUT_LEN],
129    salt: &[u8; SHA512_OUTPUT_LEN],
130    info: &str,
131) -> Result<[u8; SHA256_OUTPUT_LEN]> {
132    // Create a Salt instance for HKDF-SHA512 using the exposed salt value.
133    let salt = hkdf::Salt::new(hkdf::HKDF_SHA512, salt);
134
135    // Extract the PRK using the salt and exposed key value.
136    let prk = salt.extract(key);
137
138    // Convert the info string to bytes.
139    let info = &[info.as_bytes()];
140
141    // Expand the PRK using HKDF-SHA256 to obtain the output key material (OKM).
142    let okm = prk.expand(info, hkdf::HKDF_SHA256)?;
143
144    // Initialize a buffer to store the derived key.
145    let mut buffer = [0u8; SHA256_OUTPUT_LEN];
146
147    // Fill the buffer with the derived key material.
148    okm.fill(&mut buffer)?;
149
150    // Return the derived key buffer.
151    Ok(buffer)
152}
153
154/// Derives a wallet address from the given input key material, salt, and info string.
155///
156/// # Arguments
157///
158/// * `key` - The input key material.
159/// * `salt` - The salt value used for salt extraction.
160/// * `info` - A string that serves as additional context for key derivation.
161///
162/// # Returns
163///
164/// A Result containing the Ethereum address associated with the derived key or an error
165/// if key derivation or address generation fails.
166pub fn get_wallet_address(
167    key: &[u8; SHA512_OUTPUT_LEN],
168    salt: &[u8; SHA512_OUTPUT_LEN],
169    info: &str,
170) -> Result<Address> {
171    // Get the derived key buffer.
172    let buffer = get_derived_key(key, salt, info)?;
173
174    // Create a signing key from the derived key buffer.
175    let signing_key = SigningKey::from_bytes(buffer.as_slice().into())
176        .map_err(ethers_signers::WalletError::from)?;
177
178    // Calculate the Ethereum address associated with the signing key.
179    Ok(secret_key_to_address(&signing_key))
180}
181
182/// Signs a TypedTransaction using a derived key, salt, and info string.
183///
184/// # Arguments
185///
186/// * `key` - The input key material.
187/// * `salt` - The salt value used for salt extraction.
188/// * `info` - A string that serves as additional context for key derivation.
189/// * `tx` - A TypedTransaction to be signed.
190///
191/// # Returns
192///
193/// A Result containing the RLP-encoded transaction with the attached signature or an error
194/// if key derivation or transaction signing fails.
195pub fn sign_transaction(
196    key: &[u8; SHA512_OUTPUT_LEN],
197    salt: &[u8; SHA512_OUTPUT_LEN],
198    info: &str,
199    tx: &TypedTransaction,
200) -> Result<Signature> {
201    // Get the derived key buffer.
202    let buffer = get_derived_key(key, salt, info)?;
203
204    // Create a wallet from the derived key buffer.
205    let wallet = Wallet::from_bytes(&buffer)?;
206
207    // Sign the transaction using the wallet and obtain the signature.
208    let signature = wallet.sign_transaction_sync(tx)?;
209
210    // Return the signature.
211    Ok(signature)
212}
213
214/// Signs a message using a derived key, salt, and info string.
215///
216/// # Arguments
217///
218/// * `key` - The input key material.
219/// * `salt` - The salt value used for salt extraction.
220/// * `info` - A string that serves as additional context for key derivation.
221/// * `message` - The message to be signed.
222///
223/// # Returns
224///
225/// A Result containing the cryptographic signature of the message or an error if key
226/// derivation or message signing fails.
227pub fn sign_message<M>(
228    key: &[u8; SHA512_OUTPUT_LEN],
229    salt: &[u8; SHA512_OUTPUT_LEN],
230    info: &str,
231    message: M,
232) -> Result<Signature>
233where
234    M: AsRef<[u8]>,
235{
236    // Get the derived key buffer.
237    let buffer = get_derived_key(key, salt, info)?;
238
239    // Create a wallet from the derived key buffer.
240    let wallet = Wallet::from_bytes(&buffer)?;
241
242    // Hash the provided message to obtain the message hash.
243    let message_hash = ethers_core::utils::hash_message(message);
244
245    // Sign the message hash using the wallet and obtain the signature.
246    let signature = wallet.sign_hash(message_hash)?;
247
248    // Return the generated signature.
249    Ok(signature)
250}
251
252/// Signs typed data using a derived key and returns the generated signature.
253///
254/// # Arguments
255///
256/// * `key` - The input key for key derivation.
257/// * `salt` - The salt value used for salt extraction.
258/// * `info` - A string that serves as additional context for key derivation.
259/// * `payload` - A reference to the typed data payload that needs to be signed.
260///
261/// # Returns
262///
263/// A Result containing the generated signature or an error if the key derivation
264/// or signing process fails.
265pub async fn sign_typed_data<T>(
266    key: &[u8; SHA512_OUTPUT_LEN],
267    salt: &[u8; SHA512_OUTPUT_LEN],
268    info: &str,
269    payload: &T,
270) -> Result<Signature>
271where
272    T: Eip712 + Send + Sync,
273{
274    // Get the derived key buffer.
275    let buffer = get_derived_key(key, salt, info)?;
276
277    // Create a wallet from the derived key buffer.
278    let wallet = Wallet::from_bytes(&buffer)?;
279
280    // Sign the message hash using the wallet and obtain the signature.
281    let signature = wallet.sign_typed_data(payload).await?;
282
283    // Return the generated signature.
284    Ok(signature)
285}
286
287/// Gets a signer wallet from a derived key with an optional chain ID.
288///
289/// # Arguments
290///
291/// * `key` - The input key for key derivation.
292/// * `salt` - The salt value used for salt extraction.
293/// * `info` - A string that serves as additional context for key derivation.
294/// * `chain_id` - The chain ID to associate with the signer wallet.
295///
296/// # Returns
297///
298/// A Result containing the signer wallet with the specified chain ID or an error if
299/// the key derivation or wallet creation process fails.
300pub fn get_signer(
301    key: &[u8; SHA512_OUTPUT_LEN],
302    salt: &[u8; SHA512_OUTPUT_LEN],
303    info: &str,
304    chain_id: u64,
305) -> Result<Wallet<SigningKey>> {
306    // Get the derived key buffer.
307    let buffer = get_derived_key(key, salt, info)?;
308
309    // Create a wallet from the derived key buffer and set the chain ID.
310    let wallet = Wallet::from_bytes(&buffer)?.with_chain_id(chain_id);
311
312    // Return the wallet.
313    Ok(wallet)
314}
315
316#[cfg(test)]
317mod tests {
318    use ethers_contract_derive::{Eip712, EthAbiType};
319    use ethers_core::types::transaction::eip2718::TypedTransaction;
320    use ethers_core::types::TransactionRequest;
321
322    use crate::{
323        get_derived_key, get_signer, get_wallet_address, sign_message, sign_transaction,
324        sign_typed_data,
325    };
326
327    #[tokio::test]
328    async fn main() {
329        let key: [u8; 64] = [0u8; 64];
330        let salt: [u8; 64] = [0u8; 64];
331
332        let info = "some_key_id";
333
334        // Example 1: Derive a key.
335        let derived_key = get_derived_key(&key, &salt, info).expect("Key derivation failed");
336        assert_eq!(
337            hex::encode(derived_key).as_str(),
338            "de61002dc1676e6b59b2dd27da3f32280defe3bc384f77de6eee372f70ceaae7"
339        );
340
341        // Example 2: Get an Ethereum address from the derived key.
342        let wallet_address = get_wallet_address(&key, &salt, info).expect("Wallet creation failed");
343        assert_eq!(
344            hex::encode(wallet_address.as_bytes()).as_str(),
345            "ea4d3e00f3d283cdb0e4d4fb783f208a5095fcb7"
346        );
347
348        // Example 3. Get a Signer Wallet from the derived key.
349        let chain_id = 1;
350        let _wallet = get_signer(&key, &salt, info, chain_id).expect("Signer creation failed");
351
352        // Example 4: Sign a transaction.
353        let tx = TypedTransaction::Legacy(TransactionRequest::default().from(wallet_address));
354        let signed_tx =
355            sign_transaction(&key, &salt, info, &tx).expect("Transaction signing failed");
356
357        assert_eq!(
358            hex::encode(signed_tx.to_vec().as_slice()).as_str(),
359            "815dd9b736c52fed571b2d5d985c52d78593a6d4a602f8455e884371270760132c8cd1f163af8303dc83aaaf48d2a03bff3697f6c922951809c19c0593841b3426"
360        );
361
362        // Example 5: Sign a message.
363        let message = "Some message to sign";
364        let signature = sign_message(&key, &salt, info, message).expect("Message signing failed");
365
366        assert_eq!(
367            hex::encode(signature.to_vec().as_slice()).as_str(),
368            "35867e62e5a2c4dd52947c0cbb4af6afc95564d5abb0584bec2f9669046f81aa0d22dba8e6844fb125b93fec9a3e3684916dc4541e45bdc824cf18b5ab2409c81c"
369        );
370
371        // Example 6: Sign Typed data.
372        #[derive(Clone, Default, EthAbiType, Eip712)]
373        struct TestTypedData {
374            value: u32,
375        }
376
377        let test_payload = TestTypedData { value: 1 };
378        let signature = sign_typed_data(&key, &salt, info, &test_payload)
379            .await
380            .expect("Sign typed data failed");
381
382        assert_eq!(
383            hex::encode(signature.to_vec().as_slice()).as_str(),
384            "cdd86e133a50b7d2c4f5bff092dbfe4086ed559bb0718e9c1c9bce31171f0ff44df3cc0dcd20388ad99be26b6eca0c104bff48eb6ad8135bec9d76aee2c6930f1b"
385        );
386    }
387}