Skip to content

Commit

Permalink
feat: EIP-1271 support (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
chris13524 authored Feb 5, 2024
1 parent b4683d7 commit 7a20551
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 88 deletions.
15 changes: 15 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ fmt:
echo ' ^^^^^^ To install `rustup component add rustfmt`, see https://github.com/rust-lang/rustfmt for details'
fi

fmt-imports:
#!/bin/bash
set -euo pipefail

if command -v cargo-fmt >/dev/null; then
echo '==> Running rustfmt'
cargo +nightly fmt -- --config group_imports=StdExternalCrate,imports_granularity=One
else
echo '==> rustfmt not found in PATH, skipping'
fi

unit: lint test test-all

devloop: unit fmt-imports

# Run commit checker
commit-check:
#!/bin/bash
Expand Down
15 changes: 14 additions & 1 deletion relay_rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,17 @@ once_cell = "1.16"
jsonwebtoken = "8.1"
k256 = { version = "0.13", optional = true }
sha3 = { version = "0.10", optional = true }
sha2 = { version = "0.10.6" }
sha2 = { version = "0.10.6" }
reqwest = { version = "0.11", features = ["default-tls"] }
url = "2"
alloy-providers = { git = "https://github.com/alloy-rs/alloy.git", rev = "e6f98e1" }
alloy-transport = { git = "https://github.com/alloy-rs/alloy.git", rev = "e6f98e1" }
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy.git", rev = "e6f98e1" }
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy.git", rev = "e6f98e1" }
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy.git", rev = "e6f98e1" }
alloy-json-abi = "0.6.2"
alloy-sol-types = "0.6.2"
alloy-primitives = "0.6.2"

[dev-dependencies]
tokio = { version = "1.35.1", features = ["test-util", "macros"] }
20 changes: 17 additions & 3 deletions relay_rpc/src/auth/cacao.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use {
self::{header::Header, payload::Payload, signature::Signature},
self::{
header::Header,
payload::Payload,
signature::{eip1271::get_rpc_url::GetRpcUrl, Signature},
},
core::fmt::Debug,
serde::{Deserialize, Serialize},
serde_json::value::RawValue,
std::fmt::{Display, Write},
};

Expand All @@ -21,11 +26,20 @@ pub enum CacaoError {
#[error("Invalid payload resources")]
PayloadResources,

#[error("Invalid address")]
AddressInvalid,

#[error("Unsupported signature type")]
UnsupportedSignature,

#[error("Provider not available for that chain")]
ProviderNotAvailable,

#[error("Unable to verify")]
Verification,

#[error("Internal EIP-1271 resolution error: {0}")]
Eip1271Internal(alloy_json_rpc::RpcError<alloy_transport::TransportErrorKind, Box<RawValue>>),
}

impl From<std::fmt::Error> for CacaoError {
Expand Down Expand Up @@ -77,10 +91,10 @@ pub struct Cacao {
impl Cacao {
const ETHEREUM: &'static str = "Ethereum";

pub fn verify(&self) -> Result<bool, CacaoError> {
pub async fn verify(&self, provider: &impl GetRpcUrl) -> Result<bool, CacaoError> {
self.p.validate()?;
self.h.validate()?;
self.s.verify(self)
self.s.verify(self, provider).await
}

pub fn siwe_message(&self) -> Result<String, CacaoError> {
Expand Down
74 changes: 0 additions & 74 deletions relay_rpc/src/auth/cacao/signature.rs

This file was deleted.

59 changes: 59 additions & 0 deletions relay_rpc/src/auth/cacao/signature/eip1271/blockchain_api.rs
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,
}

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
}
}
}
5 changes: 5 additions & 0 deletions relay_rpc/src/auth/cacao/signature/eip1271/get_rpc_url.rs
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>;
}
98 changes: 98 additions & 0 deletions relay_rpc/src/auth/cacao/signature/eip1271/mod.rs
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());
}
}
39 changes: 39 additions & 0 deletions relay_rpc/src/auth/cacao/signature/eip191.rs
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)
}
}
Loading

0 comments on commit 7a20551

Please sign in to comment.