-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: EIP-1271 support #55
Changes from all commits
2a4d2ab
1947232
67ce3c7
f4b36d6
90ac16b
62b9f9f
ed663fc
6be145b
685200f
2dcdec9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
use {super::get_rpc_url::GetRpcUrl, crate::domain::ProjectId, url::Url}; | ||
|
||
// https://github.com/WalletConnect/blockchain-api/blob/master/SUPPORTED_CHAINS.md | ||
const SUPPORTED_CHAINS: [&str; 26] = [ | ||
"eip155:1", | ||
"eip155:5", | ||
"eip155:11155111", | ||
"eip155:10", | ||
"eip155:420", | ||
"eip155:42161", | ||
"eip155:421613", | ||
"eip155:137", | ||
"eip155:80001", | ||
"eip155:1101", | ||
"eip155:42220", | ||
"eip155:1313161554", | ||
"eip155:1313161555", | ||
"eip155:56", | ||
"eip155:56", | ||
"eip155:43114", | ||
"eip155:43113", | ||
"eip155:324", | ||
"eip155:280", | ||
"near", | ||
"eip155:100", | ||
"solana:4sgjmw1sunhzsxgspuhpqldx6wiyjntz", | ||
"eip155:8453", | ||
"eip155:84531", | ||
"eip155:7777777", | ||
"eip155:999", | ||
]; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct BlockchainApiProvider { | ||
project_id: ProjectId, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we using NotifyServer and Keys Server project_ids? Might be good to add it to allowlist to not have rate limiting there There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call out! This should be fine for now, as 1 request per second is 86k/day. Limit/day is 100k. Notify Server does 1-2 msgs/s and Keys Server does 0.1/s. Also most people have EOAs not smart accounts. We'll add metrics soon for this so we can see more details. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also we have metrics in Cloud already for RPC usage, so we should see this too as-is. |
||
} | ||
|
||
impl BlockchainApiProvider { | ||
pub fn new(project_id: ProjectId) -> Self { | ||
Self { project_id } | ||
} | ||
} | ||
|
||
impl GetRpcUrl for BlockchainApiProvider { | ||
fn get_rpc_url(&self, chain_id: String) -> Option<Url> { | ||
if SUPPORTED_CHAINS.contains(&chain_id.as_str()) { | ||
Some( | ||
format!( | ||
"https://rpc.walletconnect.com/v1?chainId={chain_id}&projectId={}", | ||
self.project_id | ||
) | ||
.parse() | ||
.expect("Provider URL should be valid"), | ||
) | ||
} else { | ||
None | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
use url::Url; | ||
|
||
pub trait GetRpcUrl { | ||
fn get_rpc_url(&self, chain_id: String) -> Option<Url>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
use { | ||
super::CacaoError, | ||
alloy_primitives::{Address, FixedBytes}, | ||
alloy_providers::provider::{Provider, TempProvider}, | ||
alloy_rpc_types::{CallInput, CallRequest}, | ||
alloy_sol_types::{sol, SolCall}, | ||
alloy_transport_http::Http, | ||
url::Url, | ||
}; | ||
|
||
pub mod blockchain_api; | ||
pub mod get_rpc_url; | ||
|
||
pub const EIP1271: &str = "eip1271"; | ||
|
||
// https://eips.ethereum.org/EIPS/eip-1271 | ||
const MAGIC_VALUE: u32 = 0x1626ba7e; | ||
sol! { | ||
function isValidSignature( | ||
bytes32 _hash, | ||
bytes memory _signature) | ||
public | ||
view | ||
returns (bytes4 magicValue); | ||
} | ||
|
||
pub async fn verify_eip1271( | ||
signature: Vec<u8>, | ||
address: Address, | ||
hash: &[u8; 32], | ||
provider: Url, | ||
) -> Result<bool, CacaoError> { | ||
let provider = Provider::new(Http::new(provider)); | ||
|
||
let call_request = CallRequest { | ||
to: Some(address), | ||
input: CallInput::new( | ||
isValidSignatureCall { | ||
_hash: FixedBytes::from(hash), | ||
_signature: signature, | ||
} | ||
.abi_encode() | ||
.into(), | ||
), | ||
..Default::default() | ||
}; | ||
|
||
let result = provider.call(call_request, None).await.map_err(|e| { | ||
if let Some(error_response) = e.as_error_resp() { | ||
if error_response.message.starts_with("execution reverted:") { | ||
CacaoError::Verification | ||
} else { | ||
CacaoError::Eip1271Internal(e) | ||
} | ||
} else { | ||
CacaoError::Eip1271Internal(e) | ||
} | ||
})?; | ||
|
||
if result[..4] == MAGIC_VALUE.to_be_bytes().to_vec() { | ||
Ok(true) | ||
} else { | ||
Err(CacaoError::Verification) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use { | ||
super::*, | ||
crate::auth::cacao::signature::{eip191::eip191_bytes, strip_hex_prefix}, | ||
alloy_primitives::address, | ||
sha3::{Digest, Keccak256}, | ||
}; | ||
|
||
// Manual test. Paste address, signature, message, and project ID to verify | ||
// function | ||
#[tokio::test] | ||
#[ignore] | ||
async fn test_eip1271() { | ||
let address = address!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); | ||
let signature = "xxx"; | ||
let signature = data_encoding::HEXLOWER_PERMISSIVE | ||
.decode(strip_hex_prefix(signature).as_bytes()) | ||
.map_err(|_| CacaoError::Verification) | ||
.unwrap(); | ||
let message = "xxx"; | ||
let hash = &Keccak256::new_with_prefix(eip191_bytes(message)).finalize()[..] | ||
.try_into() | ||
.unwrap(); | ||
let provider = "https://rpc.walletconnect.com/v1?chainId=eip155:1&projectId=xxx" | ||
.parse() | ||
.unwrap(); | ||
assert!(verify_eip1271(signature, address, hash, provider) | ||
.await | ||
.unwrap()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
use { | ||
super::CacaoError, | ||
crate::auth::cacao::signature::strip_hex_prefix, | ||
sha3::{Digest, Keccak256}, | ||
}; | ||
|
||
pub const EIP191: &str = "eip191"; | ||
|
||
pub fn eip191_bytes(message: &str) -> Vec<u8> { | ||
format!( | ||
"\u{0019}Ethereum Signed Message:\n{}{}", | ||
message.as_bytes().len(), | ||
message | ||
) | ||
.into() | ||
} | ||
|
||
pub fn verify_eip191(signature: &[u8], address: &str, hash: Keccak256) -> Result<bool, CacaoError> { | ||
use k256::ecdsa::{RecoveryId, Signature as Sig, VerifyingKey}; | ||
|
||
let sig = Sig::try_from(&signature[..64]).map_err(|_| CacaoError::Verification)?; | ||
let recovery_id = | ||
RecoveryId::try_from(&signature[64] % 27).map_err(|_| CacaoError::Verification)?; | ||
|
||
let recovered_key = VerifyingKey::recover_from_digest(hash, &sig, recovery_id) | ||
.map_err(|_| CacaoError::Verification)?; | ||
|
||
let add = &Keccak256::default() | ||
.chain_update(&recovered_key.to_encoded_point(false).as_bytes()[1..]) | ||
.finalize()[12..]; | ||
|
||
let address_encoded = data_encoding::HEXLOWER_PERMISSIVE.encode(add); | ||
|
||
if address_encoded.to_lowercase() != strip_hex_prefix(address).to_lowercase() { | ||
Err(CacaoError::Verification) | ||
} else { | ||
Ok(true) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered checking the response of Blockchain API to determine support or not, but this would make the CACAO verification function more coupled to Blockchain API so decided to hardcode the supported list here for now.
In the future we should either pull the list dynamically, or refactor the CACAO to use a more generic error handling and be able to interpret the response code. I slightly prefer the check-before-request approach to avoid redundant requests.
Captured in this issue: WalletConnect/notify-server#345 (comment)