Skip to content

Commit

Permalink
Smart Contract Wallet Verification Tests (#1132)
Browse files Browse the repository at this point in the history
* wip-test

* update chain matching

* spelling

* test validation service

* assert more

* wip

* test updates

* cleanup

* http api considerations

* cleanup

* http api doesnt support scw yet

* lint

* add_verifier -> add_provider

* naming update

* deps cleanup
  • Loading branch information
codabrink authored Oct 11, 2024
1 parent 0f84a88 commit 1602825
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 20 deletions.
1 change: 1 addition & 0 deletions mls_validation_service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ openmls_basic_credential = { workspace = true, features = ["test-utils"] }
rand = { workspace = true }
sha2.workspace = true
xmtp_id = { workspace = true, features = ["test-utils"] }
xmtp_mls = { workspace = true, features = ["test-utils"] }
86 changes: 81 additions & 5 deletions mls_validation_service/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,19 +313,30 @@ fn validate_group_message(message: Vec<u8>) -> Result<ValidateGroupMessageResult

#[cfg(test)]
mod tests {
use std::sync::Arc;

use associations::AccountId;
use ed25519_dalek::SigningKey;
use ethers::signers::{LocalWallet, Signer as _};
use ethers::{
abi::Token,
signers::{LocalWallet, Signer as _},
types::{Bytes, H256, U256},
};
use openmls::{
extensions::{ApplicationIdExtension, Extension, Extensions},
prelude::{tls_codec::Serialize, Credential as OpenMlsCredential, CredentialWithKey},
prelude_test::KeyPackage,
};
use openmls_basic_credential::SignatureKeyPair;
use openmls_rust_crypto::OpenMlsRustCrypto;
use xmtp_id::associations::{
generate_inbox_id,
test_utils::{rand_string, rand_u64, MockSmartContractSignatureVerifier},
unverified::{UnverifiedAction, UnverifiedIdentityUpdate},
use xmtp_id::{
associations::{
generate_inbox_id,
test_utils::{rand_string, rand_u64, MockSmartContractSignatureVerifier},
unverified::{UnverifiedAction, UnverifiedIdentityUpdate},
},
is_smart_contract,
scw_verifier::tests::{with_smart_contracts, CoinbaseSmartWallet},
};
use xmtp_mls::configuration::CIPHERSUITE;
use xmtp_proto::xmtp::{
Expand Down Expand Up @@ -503,4 +514,69 @@ mod tests {
assert_eq!(first_response.credential, None);
assert_eq!(first_response.installation_public_key, Vec::<u8>::new());
}

#[tokio::test]
async fn test_validate_scw() {
with_smart_contracts(|anvil, _provider, client, smart_contracts| async move {
let key = anvil.keys()[0].clone();
let wallet: LocalWallet = key.clone().into();

let owners = vec![Bytes::from(H256::from(wallet.address()).0.to_vec())];

let scw_factory = smart_contracts.coinbase_smart_wallet_factory();
let nonce = U256::from(0);

let scw_addr = scw_factory
.get_address(owners.clone(), nonce)
.await
.unwrap();

let contract_call = scw_factory.create_account(owners.clone(), nonce);
contract_call.send().await.unwrap().await.unwrap();

assert!(is_smart_contract(scw_addr, anvil.endpoint(), None)
.await
.unwrap());

let hash = H256::random().into();
let smart_wallet = CoinbaseSmartWallet::new(
scw_addr,
Arc::new(client.with_signer(wallet.clone().with_chain_id(anvil.chain_id()))),
);
let replay_safe_hash = smart_wallet.replay_safe_hash(hash).call().await.unwrap();
let account_id = AccountId::new_evm(anvil.chain_id(), format!("{scw_addr:?}"));

let signature = ethers::abi::encode(&[Token::Tuple(vec![
Token::Uint(U256::from(0)),
Token::Bytes(wallet.sign_hash(replay_safe_hash.into()).unwrap().to_vec()),
])]);

let resp = ValidationService::default()
.verify_smart_contract_wallet_signatures(Request::new(
VerifySmartContractWalletSignaturesRequest {
signatures: vec![VerifySmartContractWalletSignatureRequestSignature {
account_id: account_id.into(),
block_number: None,
hash: hash.to_vec(),
signature,
}],
},
))
.await
.unwrap();

let VerifySmartContractWalletSignaturesResponse { responses } = resp.into_inner();

assert_eq!(responses.len(), 1);
assert_eq!(
responses[0],
VerifySmartContractWalletSignaturesValidationResponse {
is_valid: true,
block_number: Some(1),
error: None
}
);
})
.await;
}
}
4 changes: 4 additions & 0 deletions xmtp_id/src/associations/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ impl AccountId {
pub fn get_account_address(&self) -> &str {
&self.account_address
}

pub fn get_chain_id(&self) -> &str {
&self.chain_id
}
}

/// Decode the `legacy_signed_private_key` to legacy private / public key pairs & sign the `signature_text` with the private key.
Expand Down
3 changes: 2 additions & 1 deletion xmtp_id/src/scw_verifier/chain_rpc_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ impl SmartContractSignatureVerifier for RpcSmartContractWalletVerifier {
}
}

#[cfg(test)]
#[cfg(any(test, feature = "test-utils"))]
#[allow(unused_imports)]
pub mod tests {
use crate::is_smart_contract;

Expand Down
5 changes: 5 additions & 0 deletions xmtp_id/src/scw_verifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ impl MultiSmartContractSignatureVerifier {
});
self
}

pub fn add_verifier(&mut self, id: String, url: String) {
self.verifiers
.insert(id, Box::new(RpcSmartContractWalletVerifier::new(url)));
}
}

#[async_trait]
Expand Down
4 changes: 2 additions & 2 deletions xmtp_mls/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ impl RetryableError for WrappedApiError {

#[derive(Debug)]
pub struct ApiClientWrapper<ApiClient> {
api_client: ApiClient,
retry_strategy: Retry,
pub(crate) api_client: ApiClient,
pub(crate) retry_strategy: Retry,
}

impl<ApiClient> ApiClientWrapper<ApiClient>
Expand Down
101 changes: 100 additions & 1 deletion xmtp_mls/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,13 @@ mod tests {
use crate::builder::ClientBuilderError;
use crate::identity::IdentityError;
use crate::retry::Retry;

use crate::XmtpApi;
use crate::{
api::test_utils::*, identity::Identity, storage::identity::StoredIdentity,
utils::test::rand_vec, Store,
};

use openmls::credentials::{Credential, CredentialType};
use openmls_basic_credential::SignatureKeyPair;
use openmls_traits::types::SignatureScheme;
Expand Down Expand Up @@ -380,7 +382,7 @@ mod tests {
let (legacy_key, legacy_account_address) = generate_random_legacy_key().await;
let identity_strategy = IdentityStrategy::CreateIfNotFound(
generate_inbox_id(&legacy_account_address, &0),
legacy_account_address.to_string(),
legacy_account_address.clone(),
0,
Some(legacy_key.clone()),
);
Expand Down Expand Up @@ -681,4 +683,101 @@ mod tests {
.unwrap();
assert_eq!(client_d.installation_public_key(), keybytes_a);
}

#[tokio::test]
#[cfg(not(feature = "http-api"))]
async fn test_remote_is_valid_signature() {
use crate::utils::test::TestClient;
use ethers::{
abi::Token,
signers::{LocalWallet, Signer as _},
types::{Bytes, H256, U256},
};
use std::sync::Arc;
use xmtp_id::associations::AccountId;
use xmtp_id::is_smart_contract;
use xmtp_id::scw_verifier::tests::{with_smart_contracts, CoinbaseSmartWallet};
use xmtp_id::scw_verifier::{
MultiSmartContractSignatureVerifier, SmartContractSignatureVerifier,
};

with_smart_contracts(|anvil, _provider, client, smart_contracts| async move {
let key = anvil.keys()[0].clone();
let wallet: LocalWallet = key.clone().into();

let owners = vec![Bytes::from(H256::from(wallet.address()).0.to_vec())];

let scw_factory = smart_contracts.coinbase_smart_wallet_factory();
let nonce = U256::from(0);

let scw_addr = scw_factory
.get_address(owners.clone(), nonce)
.await
.unwrap();

let contract_call = scw_factory.create_account(owners.clone(), nonce);

contract_call.send().await.unwrap().await.unwrap();

assert!(is_smart_contract(scw_addr, anvil.endpoint(), None)
.await
.unwrap());

let identity_strategy = IdentityStrategy::CreateIfNotFound(
generate_inbox_id(&wallet.address().to_string(), &0),
wallet.address().to_string(),
0,
None,
);
let store = EncryptedMessageStore::new(
StorageOption::Persistent(tmp_path()),
EncryptedMessageStore::generate_enc_key(),
)
.unwrap();
let api_client: Client<TestClient> = ClientBuilder::new(identity_strategy)
.store(store)
.local_client()
.await
.build()
.await
.unwrap();

let hash = H256::random().into();
let smart_wallet = CoinbaseSmartWallet::new(
scw_addr,
Arc::new(client.with_signer(wallet.clone().with_chain_id(anvil.chain_id()))),
);
let replay_safe_hash = smart_wallet.replay_safe_hash(hash).call().await.unwrap();
let account_id = AccountId::new_evm(anvil.chain_id(), format!("{scw_addr:?}"));

let signature: Bytes = ethers::abi::encode(&[Token::Tuple(vec![
Token::Uint(U256::from(0)),
Token::Bytes(wallet.sign_hash(replay_safe_hash.into()).unwrap().to_vec()),
])])
.into();

let valid_response = api_client
.smart_contract_signature_verifier()
.is_valid_signature(account_id.clone(), hash, signature.clone(), None)
.await
.unwrap();

// The mls validation service can't connect to our anvil instance, so it'll return false
// This is to make sure the communication at least works.
assert!(!valid_response.is_valid);
assert_eq!(valid_response.block_number, None);

// So let's immitate more or less what the mls validation is doing locally, and validate there.
let mut multi_verifier = MultiSmartContractSignatureVerifier::default();
multi_verifier.add_verifier(account_id.get_chain_id().to_string(), anvil.endpoint());
let response = multi_verifier
.is_valid_signature(account_id, hash, signature, None)
.await
.unwrap();

assert!(response.is_valid);
assert!(response.block_number.is_some());
})
.await;
}
}
2 changes: 1 addition & 1 deletion xmtp_mls/src/groups/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,7 @@ impl MlsGroup {
Ok(mutable_metadata.admin_list)
}

/// Retrieves the super admin list of the group from the group's mutable metadata extension.
/// Retrieves the super admin list of the group from the group's mutable metadata extension.
pub fn super_admin_list(
&self,
provider: impl OpenMlsProvider,
Expand Down
30 changes: 20 additions & 10 deletions xmtp_mls/src/utils/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ use rand::{
use std::sync::Arc;
use tokio::{sync::Notify, time::error::Elapsed};
use xmtp_api_grpc::grpc_api_helper::Client as GrpcClient;
use xmtp_id::{
associations::{
generate_inbox_id,
test_utils::MockSmartContractSignatureVerifier,
unverified::{UnverifiedRecoverableEcdsaSignature, UnverifiedSignature},
},
scw_verifier::MultiSmartContractSignatureVerifier,
use xmtp_id::associations::{
generate_inbox_id,
test_utils::MockSmartContractSignatureVerifier,
unverified::{UnverifiedRecoverableEcdsaSignature, UnverifiedSignature},
};

use crate::{
Expand All @@ -25,6 +22,12 @@ use crate::{
Client, InboxOwner, XmtpApi, XmtpTestClient,
};

#[cfg(feature = "http-api")]
use xmtp_id::scw_verifier::MultiSmartContractSignatureVerifier;

#[cfg(not(feature = "http-api"))]
use xmtp_id::scw_verifier::RemoteSignatureVerifier;

#[cfg(feature = "http-api")]
use xmtp_api_http::XmtpHttpApiClient;

Expand Down Expand Up @@ -75,7 +78,7 @@ impl XmtpTestClient for GrpcClient {
}

async fn create_dev() -> Self {
GrpcClient::create("https://grpc.dev.xmtp.network:443".into(), false)
GrpcClient::create("https://grpc.dev.xmtp.network:443".into(), true)
.await
.unwrap()
}
Expand Down Expand Up @@ -108,8 +111,15 @@ impl ClientBuilder<TestClient> {
}

pub async fn local_client(self) -> Self {
self.api_client(<TestClient as XmtpTestClient>::create_local().await)
.scw_signature_verifier(MultiSmartContractSignatureVerifier::default())
let api_client = <TestClient as XmtpTestClient>::create_local().await;
#[cfg(not(feature = "http-api"))]
let scw_signature_verifier =
RemoteSignatureVerifier::new(api_client.identity_client().clone());
#[cfg(feature = "http-api")]
let scw_signature_verifier = MultiSmartContractSignatureVerifier::default();

self.api_client(api_client)
.scw_signature_verifier(scw_signature_verifier)
}

pub async fn new_test_client(owner: &impl InboxOwner) -> Client<TestClient> {
Expand Down

0 comments on commit 1602825

Please sign in to comment.