From 358a6dcba05273850c80bd0da467606df2f685dc Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 25 Mar 2024 13:01:58 +0000 Subject: [PATCH 1/7] add keystore example --- Cargo.toml | 4 +- examples/wallets/examples/keystore/alice.json | 20 +++++++++ examples/wallets/examples/keystore_signer.rs | 45 +++++++++++++++++++ .../wallets/examples/private_key_signer.rs | 2 +- 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 examples/wallets/examples/keystore/alice.json create mode 100644 examples/wallets/examples/keystore_signer.rs diff --git a/Cargo.toml b/Cargo.toml index 6e8a9837..b95b5f1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,11 +37,11 @@ alloy = { git = "https://github.com/alloy-rs/alloy", rev = "fd8f065", features = "signers", # "signer-aws", # "signer-gcp", + "signer-keystore", "signer-ledger", + "signer-mnemonic", "signer-trezor", "signer-wallet", - # "signer-keystore", - "signer-mnemonic", "signer-yubihsm", # "transports", "transport-http", diff --git a/examples/wallets/examples/keystore/alice.json b/examples/wallets/examples/keystore/alice.json new file mode 100644 index 00000000..f189c06e --- /dev/null +++ b/examples/wallets/examples/keystore/alice.json @@ -0,0 +1,20 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "93f3de411f33a2027780f4b758fc79a7" + }, + "ciphertext": "92dd80b88bc29c4814ba2df782962d8a61bc8171e2da4504f7c86abf87c700a2", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 8192, + "p": 1, + "r": 8, + "salt": "048ddfcdbc645b3f25de25fa5e1c22e2cf66f1c842cade848b55aab8ef74b9e7" + }, + "mac": "b65b8441b6d778247d72d5f46a9fe5ee3311c80391f023b9e034dee0b400c091" + }, + "id": "fa6c4d94-1f55-4959-bdc9-ef82e59f9ae2", + "version": 3 +} \ No newline at end of file diff --git a/examples/wallets/examples/keystore_signer.rs b/examples/wallets/examples/keystore_signer.rs new file mode 100644 index 00000000..0ba578b5 --- /dev/null +++ b/examples/wallets/examples/keystore_signer.rs @@ -0,0 +1,45 @@ +//! Example of using a local wallet to sign and broadcast a transaction on a local Anvil node. + +use alloy::{ + network::EthereumSigner, + node_bindings::Anvil, + primitives::{address, U256}, + providers::{Provider, ProviderBuilder}, + rpc::{client::RpcClient, types::eth::request::TransactionRequest}, + signers::wallet::Wallet, +}; +use eyre::Result; + +#[tokio::main] +async fn main() -> Result<()> { + // Spin up an Anvil node. + let anvil = Anvil::new().block_time(1).try_spawn()?; + + // Read the private key from the keystore and set up the wallet. + // The private key belongs to Alice, the first default Anvil account. + let wallet = + Wallet::decrypt_keystore("./examples/wallets/examples/keystore/alice.json", "test")?; + + // Create a provider with the signer. + let http = anvil.endpoint().parse()?; + let provider = ProviderBuilder::new() + .signer(EthereumSigner::from(wallet)) + .on_client(RpcClient::new_http(http)); + + // Create a transaction. + let tx = TransactionRequest { + value: Some(U256::from(100)), + to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into(), + nonce: Some(0), + gas_price: Some(U256::from(20e9)), + gas: Some(U256::from(21000)), + ..Default::default() + }; + + // Broadcast the transaction and wait for the receipt. + let receipt = provider.send_transaction(tx).await?.with_confirmations(1).get_receipt().await?; + + println!("Send transaction: {:?}", receipt.transaction_hash); + + Ok(()) +} diff --git a/examples/wallets/examples/private_key_signer.rs b/examples/wallets/examples/private_key_signer.rs index c54137b9..095c2daf 100644 --- a/examples/wallets/examples/private_key_signer.rs +++ b/examples/wallets/examples/private_key_signer.rs @@ -15,7 +15,7 @@ async fn main() -> Result<()> { // Spin up an Anvil node. let anvil = Anvil::new().block_time(1).try_spawn()?; - // Set up the wallets. + // Set up the wallet. let wallet: LocalWallet = anvil.keys()[0].clone().into(); // Create a provider with the signer. From f66c18d247e13ae18b37d2d3d2792b04bbcb68f1 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 25 Mar 2024 13:27:02 +0000 Subject: [PATCH 2/7] add example of creating a keystore --- Cargo.toml | 1 + README.md | 3 +- examples/wallets/Cargo.toml | 2 + examples/wallets/examples/create_keystore.rs | 43 +++++++++++++++++++ examples/wallets/examples/keystore_signer.rs | 9 ++-- .../wallets/examples/private_key_signer.rs | 2 +- 6 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 examples/wallets/examples/create_keystore.rs diff --git a/Cargo.toml b/Cargo.toml index b95b5f1d..e0bddc86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ tokio = "1" # misc eyre = "0.6.12" +rand = "0.8.5" reqwest = { version = "0.11", default-features = false } ## serde diff --git a/README.md b/README.md index 4c6533b9..fdac0502 100644 --- a/README.md +++ b/README.md @@ -78,4 +78,5 @@ cargo run --example mnemonic_signer - [x] [Sign permit hash](./examples/wallets/examples/sign_permit_hash.rs) - [x] [Trezor signer](./examples/wallets/examples/trezor_signer.rs) - [x] [Yubi signer](./examples/wallets/examples/yubi_signer.rs) - - [ ] Keystore signer \ No newline at end of file + - [x] [Keystore signer](./examples/wallets/examples/keystore_signer.rs) + - [x] [Create keystore](./examples/wallets/examples/create_keystore.rs) \ No newline at end of file diff --git a/examples/wallets/Cargo.toml b/examples/wallets/Cargo.toml index 9fe49233..a0732cb1 100644 --- a/examples/wallets/Cargo.toml +++ b/examples/wallets/Cargo.toml @@ -15,6 +15,8 @@ alloy.workspace = true alloy-sol-types.workspace = true eyre.workspace = true +rand.workspace = true reqwest.workspace = true serde.workspace = true +tempfile = "3.10.1" tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/examples/wallets/examples/create_keystore.rs b/examples/wallets/examples/create_keystore.rs new file mode 100644 index 00000000..cf5b9286 --- /dev/null +++ b/examples/wallets/examples/create_keystore.rs @@ -0,0 +1,43 @@ +//! Example of creating a keystore file from a private key and password, and then reading it back. + +use alloy::{primitives::hex, signers::wallet::Wallet}; +use eyre::Result; +use rand::thread_rng; +use std::fs::read_to_string; +use tempfile::tempdir; + +#[tokio::main] +async fn main() -> Result<()> { + let dir = tempdir()?; + let mut rng = thread_rng(); + + // Password to encrypt the keystore file with. + let password = "test"; + + // Set up a local wallet from the first default Anvil account (Alice). + let private_key = + hex::decode("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")?; + + // Create a keystore file from the private key. + let (key, uuid) = + Wallet::encrypt_keystore(&dir, &mut rng, private_key, password, None).unwrap(); + + let key_file_path = dir.path().join(uuid.clone()); + + println!("Wrote keystore for {:?} to {:?}", key.address(), key_file_path); + + // Read the keystore file back. + let wallet = Wallet::decrypt_keystore(key_file_path.clone(), password)?; + + println!("Read keystore from {:?}, recovered address: {:?}", key_file_path, wallet.address()); + + // Assert that the address of the original key and the recovered key are the same. + assert_eq!(wallet.address(), key.address()); + + // Display the contents of the keystore file. + let keystore_contents = read_to_string(key_file_path)?; + + print!("Keystore file contents: {:?}\n", keystore_contents); + + Ok(()) +} diff --git a/examples/wallets/examples/keystore_signer.rs b/examples/wallets/examples/keystore_signer.rs index 0ba578b5..9fae2ea7 100644 --- a/examples/wallets/examples/keystore_signer.rs +++ b/examples/wallets/examples/keystore_signer.rs @@ -1,4 +1,4 @@ -//! Example of using a local wallet to sign and broadcast a transaction on a local Anvil node. +//! Example of using a keystore wallet to sign and broadcast a transaction on a local Anvil node. use alloy::{ network::EthereumSigner, @@ -15,10 +15,13 @@ async fn main() -> Result<()> { // Spin up an Anvil node. let anvil = Anvil::new().block_time(1).try_spawn()?; - // Read the private key from the keystore and set up the wallet. + // Password to decrypt the keystore file with. + let password = "test"; + + // Set up a wallet using Alice's keystore file. // The private key belongs to Alice, the first default Anvil account. let wallet = - Wallet::decrypt_keystore("./examples/wallets/examples/keystore/alice.json", "test")?; + Wallet::decrypt_keystore("./examples/wallets/examples/keystore/alice.json", password)?; // Create a provider with the signer. let http = anvil.endpoint().parse()?; diff --git a/examples/wallets/examples/private_key_signer.rs b/examples/wallets/examples/private_key_signer.rs index 095c2daf..c1ca67a6 100644 --- a/examples/wallets/examples/private_key_signer.rs +++ b/examples/wallets/examples/private_key_signer.rs @@ -15,7 +15,7 @@ async fn main() -> Result<()> { // Spin up an Anvil node. let anvil = Anvil::new().block_time(1).try_spawn()?; - // Set up the wallet. + // Set up a local wallet from the first default Anvil account (Alice). let wallet: LocalWallet = anvil.keys()[0].clone().into(); // Create a provider with the signer. From 1e68146d7dc0bc56aa97db84f51b8b57252125aa Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 25 Mar 2024 13:34:54 +0000 Subject: [PATCH 3/7] chore: cleanup --- Cargo.toml | 1 - examples/wallets/Cargo.toml | 2 +- examples/wallets/examples/create_keystore.rs | 26 +++++++++++--------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0bddc86..b95b5f1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,6 @@ tokio = "1" # misc eyre = "0.6.12" -rand = "0.8.5" reqwest = { version = "0.11", default-features = false } ## serde diff --git a/examples/wallets/Cargo.toml b/examples/wallets/Cargo.toml index a0732cb1..80bdd9e8 100644 --- a/examples/wallets/Cargo.toml +++ b/examples/wallets/Cargo.toml @@ -15,7 +15,7 @@ alloy.workspace = true alloy-sol-types.workspace = true eyre.workspace = true -rand.workspace = true +rand = "0.8.5" reqwest.workspace = true serde.workspace = true tempfile = "3.10.1" diff --git a/examples/wallets/examples/create_keystore.rs b/examples/wallets/examples/create_keystore.rs index cf5b9286..8e5cf097 100644 --- a/examples/wallets/examples/create_keystore.rs +++ b/examples/wallets/examples/create_keystore.rs @@ -11,28 +11,32 @@ async fn main() -> Result<()> { let dir = tempdir()?; let mut rng = thread_rng(); - // Password to encrypt the keystore file with. - let password = "test"; - - // Set up a local wallet from the first default Anvil account (Alice). + // Private key of Alice, the first default Anvil account. let private_key = hex::decode("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")?; - // Create a keystore file from the private key. - let (key, uuid) = + // Password to encrypt the keystore file with. + let password = "test"; + + // Create a keystore file from the private key of Alice, returning a [Wallet] instance. + let (wallet, file_path) = Wallet::encrypt_keystore(&dir, &mut rng, private_key, password, None).unwrap(); - let key_file_path = dir.path().join(uuid.clone()); + let key_file_path = dir.path().join(file_path.clone()); - println!("Wrote keystore for {:?} to {:?}", key.address(), key_file_path); + println!("Wrote keystore for {:?} to {:?}", wallet.address(), key_file_path); // Read the keystore file back. - let wallet = Wallet::decrypt_keystore(key_file_path.clone(), password)?; + let recovered_wallet = Wallet::decrypt_keystore(key_file_path.clone(), password)?; - println!("Read keystore from {:?}, recovered address: {:?}", key_file_path, wallet.address()); + println!( + "Read keystore from {:?}, recovered address: {:?}", + key_file_path, + recovered_wallet.address() + ); // Assert that the address of the original key and the recovered key are the same. - assert_eq!(wallet.address(), key.address()); + assert_eq!(wallet.address(), recovered_wallet.address()); // Display the contents of the keystore file. let keystore_contents = read_to_string(key_file_path)?; From 026a2a6554a9aa232f2d55947c267694531c0be0 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 25 Mar 2024 13:54:27 +0000 Subject: [PATCH 4/7] nit: use hex! macro and remove 1 confirmations, other examples are clear enough --- examples/wallets/examples/create_keystore.rs | 15 +++++++-------- examples/wallets/examples/keystore_signer.rs | 7 ++++--- examples/wallets/examples/private_key_signer.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/wallets/examples/create_keystore.rs b/examples/wallets/examples/create_keystore.rs index 8e5cf097..2b2c1150 100644 --- a/examples/wallets/examples/create_keystore.rs +++ b/examples/wallets/examples/create_keystore.rs @@ -12,8 +12,7 @@ async fn main() -> Result<()> { let mut rng = thread_rng(); // Private key of Alice, the first default Anvil account. - let private_key = - hex::decode("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")?; + let private_key = hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); // Password to encrypt the keystore file with. let password = "test"; @@ -22,16 +21,16 @@ async fn main() -> Result<()> { let (wallet, file_path) = Wallet::encrypt_keystore(&dir, &mut rng, private_key, password, None).unwrap(); - let key_file_path = dir.path().join(file_path.clone()); + let keystore_file_path = dir.path().join(file_path.clone()); - println!("Wrote keystore for {:?} to {:?}", wallet.address(), key_file_path); + println!("Wrote keystore for {:?} to {:?}", wallet.address(), keystore_file_path); // Read the keystore file back. - let recovered_wallet = Wallet::decrypt_keystore(key_file_path.clone(), password)?; + let recovered_wallet = Wallet::decrypt_keystore(keystore_file_path.clone(), password)?; println!( "Read keystore from {:?}, recovered address: {:?}", - key_file_path, + keystore_file_path, recovered_wallet.address() ); @@ -39,9 +38,9 @@ async fn main() -> Result<()> { assert_eq!(wallet.address(), recovered_wallet.address()); // Display the contents of the keystore file. - let keystore_contents = read_to_string(key_file_path)?; + let keystore_contents = read_to_string(keystore_file_path)?; - print!("Keystore file contents: {:?}\n", keystore_contents); + println!("Keystore file contents: {:?}", keystore_contents); Ok(()) } diff --git a/examples/wallets/examples/keystore_signer.rs b/examples/wallets/examples/keystore_signer.rs index 9fae2ea7..0d023350 100644 --- a/examples/wallets/examples/keystore_signer.rs +++ b/examples/wallets/examples/keystore_signer.rs @@ -9,6 +9,7 @@ use alloy::{ signers::wallet::Wallet, }; use eyre::Result; +use std::path::Path; #[tokio::main] async fn main() -> Result<()> { @@ -20,8 +21,8 @@ async fn main() -> Result<()> { // Set up a wallet using Alice's keystore file. // The private key belongs to Alice, the first default Anvil account. - let wallet = - Wallet::decrypt_keystore("./examples/wallets/examples/keystore/alice.json", password)?; + let keystore_file_path = Path::new("./examples/wallets/examples/keystore/alice.json"); + let wallet = Wallet::decrypt_keystore(keystore_file_path, password)?; // Create a provider with the signer. let http = anvil.endpoint().parse()?; @@ -40,7 +41,7 @@ async fn main() -> Result<()> { }; // Broadcast the transaction and wait for the receipt. - let receipt = provider.send_transaction(tx).await?.with_confirmations(1).get_receipt().await?; + let receipt = provider.send_transaction(tx).await?.get_receipt().await?; println!("Send transaction: {:?}", receipt.transaction_hash); diff --git a/examples/wallets/examples/private_key_signer.rs b/examples/wallets/examples/private_key_signer.rs index c1ca67a6..c55560ea 100644 --- a/examples/wallets/examples/private_key_signer.rs +++ b/examples/wallets/examples/private_key_signer.rs @@ -35,7 +35,7 @@ async fn main() -> Result<()> { }; // Broadcast the transaction and wait for the receipt. - let receipt = provider.send_transaction(tx).await?.with_confirmations(1).get_receipt().await?; + let receipt = provider.send_transaction(tx).await?.get_receipt().await?; println!("Send transaction: {:?}", receipt.transaction_hash); From de607bf14393edc17898ff95e17b9c32136254f6 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 25 Mar 2024 14:06:04 +0000 Subject: [PATCH 5/7] fix: use CARGO_MANIFEST_DIR with file join to allow users to run example from anywhere --- examples/wallets/examples/keystore_signer.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/wallets/examples/keystore_signer.rs b/examples/wallets/examples/keystore_signer.rs index 0d023350..64a73ee5 100644 --- a/examples/wallets/examples/keystore_signer.rs +++ b/examples/wallets/examples/keystore_signer.rs @@ -9,7 +9,7 @@ use alloy::{ signers::wallet::Wallet, }; use eyre::Result; -use std::path::Path; +use std::{env, path::PathBuf}; #[tokio::main] async fn main() -> Result<()> { @@ -21,7 +21,8 @@ async fn main() -> Result<()> { // Set up a wallet using Alice's keystore file. // The private key belongs to Alice, the first default Anvil account. - let keystore_file_path = Path::new("./examples/wallets/examples/keystore/alice.json"); + let keystore_file_path = + PathBuf::from(env::var("CARGO_MANIFEST_DIR")?).join("examples/keystore/alice.json"); let wallet = Wallet::decrypt_keystore(keystore_file_path, password)?; // Create a provider with the signer. From 76e75443bba22253b15813d57a1b9589ba576d3a Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 25 Mar 2024 16:47:48 +0000 Subject: [PATCH 6/7] remove DS_Store --- .gitignore | 1 + examples/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 examples/.DS_Store diff --git a/.gitignore b/.gitignore index eb892bc5..d8f4ddf8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .vscode .idea .env +.DS_STORE \ No newline at end of file diff --git a/examples/.DS_Store b/examples/.DS_Store deleted file mode 100644 index 1032597f9dad9c6edbaa21aa8f4a0f78199ee699..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKyH3L}6umB$TAr!^Rg7T;AyHTuLlt&J2gC+_2#Q)#S_%V87R15`D;xX*AT|Ua zz|LOet$a^nAKNEKiCq(s$hXTmq6`tGQ5cgWNIinvxkMtNYo>uh zj!~sDRj5g=+HQnxE35<7f#2o;f4jrfz&GpDF&W=)u2Ayp4JCK-sW+?cdtR~T7txyD zzu3Bde0^DsW0nVGHulV!*I7V{4yi5Il`1mU7Lzf-vv)B}UFTCD3lBIxly*2zQ(A@PN%w?ktQ{&Cs z*@~XCYaCNqJS(J!0@m}_1A8_{dQ_p!)&c8)b>Q0pULPD3Mpt8?P%a%v3EXmfl92<~ ze3oEzG`boCg=j$t)fK3^3Vp;7sypf(&2u#d3RQO!a%PO9XBPT|BIN9-cO;#JtI%fa zfOR13z>vDl@czFRfBp}5vUk=2>%hNqKqS3_m&YUNy*2W1yw^G?+bA5&3lz#ONP0V# g7rYhEpvXX*!xunTW1tW<2>VAs+F+A);71+!0twL0wg3PC From 02802aa57ac05bd71a6dd319f2ad97023a8aa19d Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 25 Mar 2024 16:52:52 +0000 Subject: [PATCH 7/7] merge in main --- .gitignore | 2 +- Cargo.toml | 5 ----- examples/wallets/Cargo.toml | 1 - examples/wallets/examples/sign_permit_hash.rs | 3 ++- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index d8f4ddf8..a0e36f6c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ .vscode .idea .env -.DS_STORE \ No newline at end of file +.DS_Store diff --git a/Cargo.toml b/Cargo.toml index f2bb8b79..55a00649 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,11 +50,6 @@ alloy = { git = "https://github.com/alloy-rs/alloy", rev = "fd8f065", features = "transport-ws", "pubsub", ] } -# TODO: sol! macro somehow requires this to be present -alloy-sol-types = { version = "0.6.4", default-features = false, features = [ - "std", -] } - # async tokio = "1" diff --git a/examples/wallets/Cargo.toml b/examples/wallets/Cargo.toml index 80bdd9e8..f51a5d36 100644 --- a/examples/wallets/Cargo.toml +++ b/examples/wallets/Cargo.toml @@ -12,7 +12,6 @@ repository.workspace = true [dev-dependencies] alloy.workspace = true -alloy-sol-types.workspace = true eyre.workspace = true rand = "0.8.5" diff --git a/examples/wallets/examples/sign_permit_hash.rs b/examples/wallets/examples/sign_permit_hash.rs index cab7c093..4c1045a0 100644 --- a/examples/wallets/examples/sign_permit_hash.rs +++ b/examples/wallets/examples/sign_permit_hash.rs @@ -3,7 +3,8 @@ use alloy::{ primitives::{address, keccak256, U256}, signers::{wallet::LocalWallet, Signer}, - sol_types::{eip712_domain, sol, SolStruct}, + sol, + sol_types::{eip712_domain, SolStruct}, }; use eyre::Result; use serde::Serialize;