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}