Smart Wallets
When working with Smart Wallets - question on how to verify their signatures may arise. This blog post will give you a basic understanding of theory and practice regarding this question.
TL;DR
Smart Wallets' Contracts have no key-pair to sign and verify messages.
Instead Wallet's Contract will implement EIP-1271 and provide isValidSignature
method to call it on.
Theory
Before I faced an issue to deal with Smart Wallets I had understanding of crypto wallet as ordinary Externally Owned Accounts with key-pair: private key used to sing messages and public keys used to verify messages' signatures.
It turns out that Smart Wallets
lack this feature - they are contracts deployed to a blockchain network
and can confirm their signature using EIP-1271.
They use a state and isValidSignature
method to provide an ability to verify their signatures.
Pracice
If you are interested in writing some code and spending some ethereum - welcome to our next section. Here we will create an Ambire Wallet, sign some message, transform Ethereum ABI to Rust API and verify the signature in the end.
Wallet Creation and Activation
- Go to https://wallet.ambire.com/ and create new wallet
- Transfer some ethereum to it
Message Sign
- Go to https://example.walletconnect.org/ and connect your Ambire Wallet
- Click on
personal_sign
and follow instructions - Copy information about:
- message
- address
- result
API Generation
- Go to Etherscan and find your's Smart Wallet's Contract by it's address
- Switch to Contract tab and look into ABI section, find
isValidSignature
method there - Create a new Rust project, for example:
cargo new swsv
- Add dependencies:
hx Cargo.toml
[package] name = "swsv" version = "0.1.0" edition = "2021" [dependencies] ethers = "0.6.2" hex = "^0.4.3" serde_json = "^1.0.64" tokio = { version = "^1.5", features = ["full"] }
- Write API generation code:
hx src/main.rs
(paste yours wallet's address in URL)
use ethers::prelude::*; fn main() { Abigen::new( "AmbireSmartWallet", "https://etherscan.io/address/YOUR_WALLET_ADDRESS", ) .unwrap() .generate() .unwrap() .write_to_file("src/ambire.rs") .unwrap(); }
- Connect newly generated module:
hx src/main.rs
use ethers::prelude::*; mod ambire; fn main() {}
Call Execution
- Call EIP-1271 method on a contract:
hx src/main.rs
use std::sync::Arc; use ethers::prelude::*; mod ambire; use ambire::AmbireSmartWallet; #[tokio::main] async fn main() { //TODO: Get your token from https://docs.infura.io/infura/getting-started and put in URL below let provider = Provider::<Http>::try_from("https://mainnet.infura.io/v3/YOUR_INFURA_TOKEN") .expect("could not instantiate HTTP Provider"); //TODO: Put yours wallet's address here let address = "YOUR_WALLET_ADDRESS" .parse::<Address>() .unwrap(); //TODO: Put yours message here let hash = hash_message("YOUR_MESSAGE").to_fixed_bytes(); let contract = AmbireSmartWallet::new(address, Arc::new(provider)); //TODO: Put yours signature here without prefix `0x` let sig = Bytes::from(hex::decode("SIGNATURE").unwrap()); let result = contract.is_valid_signature(hash, sig).call().await.unwrap(); assert_eq!("1626ba7e", hex::encode(result)); }