From f73b1154a57cafaec9a38c95c25fb6b603d4e620 Mon Sep 17 00:00:00 2001 From: Tomas Date: Tue, 30 Jul 2024 12:53:34 -0300 Subject: [PATCH] Signer functionality (#54) This PR adds a signer crate to handle Signer instantiation. --------- Co-authored-by: ricomateo --- Cargo.lock | 56 ++++++++----- Cargo.toml | 7 +- crates/signer/Cargo.toml | 17 ++++ crates/signer/mockdata/dummy.key.json | 19 +++++ crates/signer/src/lib.rs | 6 ++ crates/signer/src/signer.rs | 116 ++++++++++++++++++++++++++ 6 files changed, 199 insertions(+), 22 deletions(-) create mode 100644 crates/signer/Cargo.toml create mode 100644 crates/signer/mockdata/dummy.key.json create mode 100644 crates/signer/src/lib.rs create mode 100644 crates/signer/src/signer.rs diff --git a/Cargo.lock b/Cargo.lock index d441d46a..786969fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6e6436a9530f25010d13653e206fab4c9feddacf21a54de8d7311b275bc56b" +checksum = "413902aa18a97569e60f679c23f46a18db1656d87ab4d4e49d0e1e52042f66df" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeaccd50238126e3a0ff9387c7c568837726ad4f4e399b528ca88104d6c25ef" +checksum = "bc05b04ac331a9f07e3a4036ef7926e49a8bf84a99a1ccfc7e2ab55a5fcbb372" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -187,9 +187,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" +checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" dependencies = [ "alloy-rlp", "bytes", @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" +checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -391,9 +391,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" +checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -410,9 +410,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" +checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" dependencies = [ "alloy-json-abi", "const-hex", @@ -427,18 +427,19 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa2fbd22d353d8685bd9fee11ba2d8b5c3b1d11e56adb3265fcf1f32bfdf404" +checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" dependencies = [ + "serde", "winnow 0.6.13", ] [[package]] name = "alloy-sol-types" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" +checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -1557,6 +1558,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "eigen-signer" +version = "0.0.1-alpha" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "eth-keystore", + "hex-literal", + "k256", + "thiserror", +] + [[package]] name = "eigen-testing-utils" version = "0.0.1-alpha" @@ -4279,9 +4295,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -4555,9 +4571,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" dependencies = [ "paste", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index ddc76d4a..1ceb2fd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,13 @@ members = [ "crates/types/", "crates/metrics/", "crates/types/", + "crates/signer/", "crates/logging/", "examples/info-operator-service/", "testing/testing-utils/", "examples/avsregistry-read", "examples/avsregistry-write", - "examples/anvil-utils", + "examples/anvil-utils" ] resolver = "2" @@ -88,7 +89,9 @@ thiserror = "1.0" tracing = "0.1.40" tracing-subscriber = { version = "0.3", features = ["json"] } hyper = "0.14.25" - +eth-keystore = "0.5.0" +hex-literal = "0.4.1" +k256 = "0.13.3" #misc parking_lot = "0.12" diff --git a/crates/signer/Cargo.toml b/crates/signer/Cargo.toml new file mode 100644 index 00000000..14ec785f --- /dev/null +++ b/crates/signer/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "eigen-signer" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true + +[dependencies] +alloy-consensus.workspace = true +alloy-network.workspace = true +alloy-primitives.workspace = true +alloy-signer.workspace = true +alloy-signer-local.workspace = true +eth-keystore.workspace = true +hex-literal.workspace = true +k256.workspace = true +thiserror.workspace = true diff --git a/crates/signer/mockdata/dummy.key.json b/crates/signer/mockdata/dummy.key.json new file mode 100644 index 00000000..7895ed91 --- /dev/null +++ b/crates/signer/mockdata/dummy.key.json @@ -0,0 +1,19 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "6087dab2f9fdbbfaddc31a909735c1e6" + }, + "ciphertext": "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", + "kdf": "pbkdf2", + "kdfparams": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" + }, + "mac": "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" + }, + "id": "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version": 3 +} diff --git a/crates/signer/src/lib.rs b/crates/signer/src/lib.rs new file mode 100644 index 00000000..e78f58cd --- /dev/null +++ b/crates/signer/src/lib.rs @@ -0,0 +1,6 @@ +#![doc( + html_logo_url = "https://github.com/supernovahs/eigensdk-rs/assets/91280922/bd13caec-3c00-4afc-839a-b83d2890beb5", + issue_tracker_base_url = "https://github.com/supernovahs/eigen-rs/issues/" +)] + +pub mod signer; diff --git a/crates/signer/src/signer.rs b/crates/signer/src/signer.rs new file mode 100644 index 00000000..0c44d353 --- /dev/null +++ b/crates/signer/src/signer.rs @@ -0,0 +1,116 @@ +use alloy_signer_local::PrivateKeySigner; +use eth_keystore::decrypt_key; +use std::path::Path; +use thiserror::Error; + +/// Represents the input params to create a signer +#[derive(Debug)] +pub enum Config { + /// Hexadecimal private key + PrivateKey(String), + /// Keystore path and password + Keystore(String, String), + /// Web3 endpoint and address + Web3(String, String), +} + +#[derive(Error, Debug)] +/// Possible errors raised in signer creation +pub enum SignerError { + #[error("invalid private key")] + InvalidPrivateKey, + #[error("invalid keystore password")] + InvalidPassword, +} + +impl Config { + /// Creates a signer from given config. + pub fn signer_from_config(c: Config) -> Result { + // TODO: check chain id to select signer + match c { + Config::PrivateKey(key) => key + .parse::() + .map_err(|_| SignerError::InvalidPrivateKey), + Config::Keystore(path, password) => { + let keypath = Path::new(&path); + let private_key = + decrypt_key(keypath, password).map_err(|_| SignerError::InvalidPassword)?; + PrivateKeySigner::from_slice(&private_key) + .map_err(|_| SignerError::InvalidPrivateKey) + } + Config::Web3(_endpoint, _address) => { + todo!() // We are implementing this in a following PR + } + } + } +} + +#[cfg(test)] +mod test { + use super::Config; + use alloy_consensus::TxLegacy; + use alloy_network::TxSignerSync; + use alloy_primitives::{bytes, Address, U256}; + use alloy_signer::Signature; + use alloy_signer_local::PrivateKeySigner; + use hex_literal::hex; + use std::str::FromStr; + + const PRIVATE_KEY: &str = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7"; + const ADDRESS: [u8; 20] = hex!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); + const SIGNATURE_R: &str = + "99963972037857174861280476053118856715670512199525969754644366601434507134123"; + const SIGNATURE_S: &str = + "54587766196536123534774489028213321677166972433316011091332824361042811624091"; + const SIGNATURE_Y_PARITY: u64 = 37; + const KEYSTORE_PATH: &str = "mockdata/dummy.key.json"; + const KEYSTORE_PASSWORD: &str = "testpassword"; + + #[test] + fn sign_transaction_with_private_key() { + let config = Config::PrivateKey(PRIVATE_KEY.into()); + let mut tx = TxLegacy { + to: Address::from(ADDRESS).into(), + value: U256::from(1_000_000_000), + gas_limit: 2_000_000, + nonce: 0, + gas_price: 21_000_000_000, + input: bytes!(), + chain_id: Some(1), + }; + + let signer = Config::signer_from_config(config); + + let signature = signer.unwrap().sign_transaction_sync(&mut tx).unwrap(); + let expected_signature = Signature::from_rs_and_parity( + U256::from_str(SIGNATURE_R).unwrap(), + U256::from_str(SIGNATURE_S).unwrap(), + SIGNATURE_Y_PARITY, + ) + .unwrap(); + assert_eq!(signature, expected_signature); + } + + #[test] + fn sign_transaction_with_keystore() { + let config = Config::Keystore(KEYSTORE_PATH.into(), KEYSTORE_PASSWORD.into()); + let mut tx = TxLegacy { + to: Address::from(ADDRESS).into(), + value: U256::from(1_000_000_000), + gas_limit: 2_000_000, + nonce: 0, + gas_price: 21_000_000_000, + input: bytes!(), + chain_id: Some(1), + }; + + let private_key = hex!("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); + let expected_signer = PrivateKeySigner::from_slice(&private_key).unwrap(); + let expected_signature = expected_signer.sign_transaction_sync(&mut tx).unwrap(); + + let signer = Config::signer_from_config(config); + let signature = signer.unwrap().sign_transaction_sync(&mut tx).unwrap(); + + assert_eq!(signature, expected_signature); + } +}