Skip to content

Commit

Permalink
Remote signature verifier (#1119)
Browse files Browse the repository at this point in the history
* wip

* nah

* A cleaner approach

* revert gen protos

* lint

* make the remote verifier default

* lint

* update node bindings

* update local client to add scw support

* a bit cleaner
  • Loading branch information
codabrink authored Oct 8, 2024
1 parent fc4439c commit 814c006
Show file tree
Hide file tree
Showing 22 changed files with 830 additions and 577 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ version = "0.0.1"
anyhow = "1.0"
async-stream = "0.3"
async-trait = "0.1.77"
trait-variant = "0.1.2"
chrono = "0.4.38"
ctor = "0.2"
ed25519 = "2.2.3"
Expand Down Expand Up @@ -56,6 +55,7 @@ tokio = { version = "1.35.1", default-features = false }
tonic = "^0.12"
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = "0.3"
trait-variant = "0.1.2"
url = "2.5.0"

# Internal Crate Dependencies
Expand Down
1 change: 1 addition & 0 deletions bindings_ffi/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 13 additions & 13 deletions bindings_ffi/src/mls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ use std::convert::TryInto;
use std::sync::Arc;
use tokio::{sync::Mutex, task::AbortHandle};
use xmtp_api_grpc::grpc_api_helper::Client as TonicApiClient;
use xmtp_id::associations::unverified::NewUnverifiedSmartContractWalletSignature;
use xmtp_id::associations::unverified::UnverifiedSignature;
use xmtp_id::associations::AccountId;
use xmtp_id::associations::AssociationState;
use xmtp_id::associations::MemberIdentifier;
use xmtp_id::scw_verifier::RemoteSignatureVerifier;
use xmtp_id::scw_verifier::SmartContractSignatureVerifier;
use xmtp_id::{
associations::{builder::SignatureRequest, generate_inbox_id as xmtp_id_generate_inbox_id},
Expand Down Expand Up @@ -124,19 +126,23 @@ pub async fn create_client(
legacy_signed_private_key_proto,
);

let scw_verifier = RemoteSignatureVerifier::new(api_client.identity_client().clone());

let xmtp_client: RustXmtpClient = match history_sync_url {
Some(url) => {
ClientBuilder::new(identity_strategy)
.api_client(api_client)
.store(store)
.history_sync_url(&url)
.scw_signature_verifier(scw_verifier)
.build()
.await?
}
None => {
ClientBuilder::new(identity_strategy)
.api_client(api_client)
.store(store)
.scw_signature_verifier(scw_verifier)
.build()
.await?
}
Expand Down Expand Up @@ -211,25 +217,19 @@ impl FfiSignatureRequest {
let mut inner = self.inner.lock().await;
let account_id = AccountId::new_evm(chain_id, address);

let block_number = match block_number {
Some(bn) => bn,
None => {
self.scw_verifier
.current_block_number(&chain_id.to_string())
.await
.map_err(GenericError::Verifier)?
.0[0]
}
};

let signature = UnverifiedSignature::new_smart_contract_wallet(
let new_signature = NewUnverifiedSmartContractWalletSignature::new(
signature_bytes,
account_id,
block_number,
);

inner
.add_signature(signature, self.scw_verifier.clone().as_ref())
.add_new_unverified_smart_contract_signature(
new_signature,
self.scw_verifier.clone().as_ref(),
)
.await?;

Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions bindings_node/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions bindings_node/src/mls_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use xmtp_cryptography::signature::ed25519_public_key_to_address;
use xmtp_id::associations::builder::SignatureRequest;
use xmtp_id::associations::generate_inbox_id as xmtp_id_generate_inbox_id;
use xmtp_id::associations::unverified::UnverifiedSignature;
use xmtp_id::scw_verifier::RemoteSignatureVerifier;
use xmtp_mls::api::ApiClientWrapper;
use xmtp_mls::builder::ClientBuilder;
use xmtp_mls::identity::IdentityStrategy;
Expand Down Expand Up @@ -53,6 +54,8 @@ pub async fn create_client(
.await
.map_err(|_| Error::from_reason("Error creating Tonic API client"))?;

let scw_verifier = RemoteSignatureVerifier::new(api_client.identity_client().clone());

let storage_option = StorageOption::Persistent(db_path);

let store = match encryption_key {
Expand Down Expand Up @@ -81,12 +84,14 @@ pub async fn create_client(
.api_client(api_client)
.store(store)
.history_sync_url(&url)
.scw_signature_verifier(scw_verifier)
.build()
.await
.map_err(ErrorWrapper::from)?,
None => ClientBuilder::new(identity_strategy)
.api_client(api_client)
.store(store)
.scw_signature_verifier(scw_verifier)
.build()
.await
.map_err(ErrorWrapper::from)?,
Expand Down
33 changes: 17 additions & 16 deletions mls_validation_service/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use xmtp_id::{
self, try_map_vec, unverified::UnverifiedIdentityUpdate, AssociationError,
DeserializationError, SignatureError,
},
scw_verifier::SmartContractSignatureVerifier,
scw_verifier::{SmartContractSignatureVerifier, ValidationResponse},
};
use xmtp_mls::{
utils::id::serialize_group_id,
Expand All @@ -18,8 +18,9 @@ use xmtp_mls::{
use xmtp_proto::xmtp::{
identity::{
api::v1::{
verify_smart_contract_wallet_signatures_response::ValidationResponse as VerifySmartContractWalletSignaturesResponseValidationResponse,
UnverifiedSmartContractWalletSignature, VerifySmartContractWalletSignaturesRequest,
verify_smart_contract_wallet_signatures_response::ValidationResponse as VerifySmartContractWalletSignaturesValidationResponse,
VerifySmartContractWalletSignatureRequestSignature,
VerifySmartContractWalletSignaturesRequest,
VerifySmartContractWalletSignaturesResponse,
},
associations::IdentityUpdate as IdentityUpdateProto,
Expand Down Expand Up @@ -199,33 +200,31 @@ async fn validate_inbox_id_key_package(
}

async fn verify_smart_contract_wallet_signatures(
signatures: Vec<UnverifiedSmartContractWalletSignature>,
signatures: Vec<VerifySmartContractWalletSignatureRequestSignature>,
scw_verifier: &dyn SmartContractSignatureVerifier,
) -> Result<Response<VerifySmartContractWalletSignaturesResponse>, Status> {
let mut responses = vec![];
for request in signatures {
for signature in signatures {
let handle = async move {
let Some(signature) = request.scw_signature else {
return Ok::<bool, GrpcServerError>(false);
};

let account_id = signature.account_id.try_into().map_err(|_e| {
GrpcServerError::Deserialization(DeserializationError::InvalidAccountId)
})?;

let valid = scw_verifier
let response = scw_verifier
.is_valid_signature(
account_id,
request.hash.try_into().map_err(|_| {
signature.hash.try_into().map_err(|_| {
GrpcServerError::Deserialization(DeserializationError::InvalidHash)
})?,
signature.signature.into(),
Some(BlockNumber::Number(U64::from(signature.block_number))),
signature
.block_number
.map(|bn| BlockNumber::Number(U64::from(bn))),
)
.await
.map_err(|e| GrpcServerError::Signature(SignatureError::VerifierError(e)))?;

Ok(valid)
Ok::<ValidationResponse, GrpcServerError>(response)
};

responses.push(handle);
Expand All @@ -235,12 +234,14 @@ async fn verify_smart_contract_wallet_signatures(
.await
.into_iter()
.map(|result| match result {
Err(err) => VerifySmartContractWalletSignaturesResponseValidationResponse {
Err(err) => VerifySmartContractWalletSignaturesValidationResponse {
is_valid: false,
block_number: None,
error: Some(format!("{err:?}")),
},
Ok(is_valid) => VerifySmartContractWalletSignaturesResponseValidationResponse {
is_valid,
Ok(response) => VerifySmartContractWalletSignaturesValidationResponse {
is_valid: response.is_valid,
block_number: response.block_number,
error: None,
},
})
Expand Down
5 changes: 2 additions & 3 deletions mls_validation_service/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use tokio::signal::unix::{signal, SignalKind};
use tonic::transport::Server;

use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt as _, EnvFilter};
use xmtp_id::scw_verifier::RpcSmartContractWalletVerifier;
use xmtp_id::scw_verifier::MultiSmartContractSignatureVerifier;
use xmtp_proto::xmtp::mls_validation::v1::validation_api_server::ValidationApiServer;

#[macro_use]
Expand All @@ -30,8 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

let health_server = health_check_server(args.health_check_port as u16);

// TODO:nm replace with real verifier
let scw_verifier = RpcSmartContractWalletVerifier::new("http://fixme.com".to_string());
let scw_verifier = MultiSmartContractSignatureVerifier::new_from_file("chain_urls.json");

let grpc_server = Server::builder()
.add_service(ValidationApiServer::new(ValidationService::new(
Expand Down
4 changes: 4 additions & 0 deletions xmtp_api_grpc/src/grpc_api_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ impl Client {

req
}

pub fn identity_client(&self) -> &ProtoIdentityApiClient<Channel> {
&self.identity_client
}
}

impl ClientWithMetadata for Client {
Expand Down
5 changes: 3 additions & 2 deletions xmtp_id/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ version.workspace = true
[dependencies]
async-trait.workspace = true
chrono.workspace = true
dyn-clone = "1"
ed25519-dalek = { workspace = true, features = ["digest"] }
ethers.workspace = true
futures.workspace = true
hex.workspace = true
tracing.workspace = true
openmls_traits.workspace = true
prost.workspace = true
rand.workspace = true
Expand All @@ -21,10 +21,11 @@ serde_json.workspace = true
sha2.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["macros"] }
tonic.workspace = true
tracing.workspace = true
url = { workspace = true, features = ["serde"] }
xmtp_cryptography.workspace = true
xmtp_proto = { workspace = true, features = ["proto_full"] }
dyn-clone = "1"

[dev-dependencies]
ctor = "0.2.5"
Expand Down
55 changes: 49 additions & 6 deletions xmtp_id/src/associations/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ use super::{
UnsignedRevokeAssociation,
},
unverified::{
UnverifiedAction, UnverifiedAddAssociation, UnverifiedChangeRecoveryAddress,
UnverifiedCreateInbox, UnverifiedIdentityUpdate, UnverifiedRevokeAssociation,
UnverifiedSignature,
NewUnverifiedSmartContractWalletSignature, UnverifiedAction, UnverifiedAddAssociation,
UnverifiedChangeRecoveryAddress, UnverifiedCreateInbox, UnverifiedIdentityUpdate,
UnverifiedRevokeAssociation, UnverifiedSignature, UnverifiedSmartContractWalletSignature,
},
verified_signature::VerifiedSignature,
MemberIdentifier, MemberKind, SignatureError,
};

Expand Down Expand Up @@ -163,6 +164,8 @@ pub enum SignatureRequestError {
MissingSigner,
#[error("Signature error {0}")]
Signature(#[from] SignatureError),
#[error("Unable to get block number")]
BlockNumber,
}

/// A signature request is meant to be sent over the FFI barrier (wrapped in a mutex) to platform SDKs.
Expand Down Expand Up @@ -220,15 +223,55 @@ impl SignatureRequest {
.collect()
}

/// Often the front-end doesn't know the current block number when adding a smart contract.
/// This is for when you want to add a smart-contract wallet,
/// and need the verifier to populate the latest block number for you.
pub async fn add_new_unverified_smart_contract_signature(
&mut self,
mut signature: NewUnverifiedSmartContractWalletSignature,
scw_verifier: &dyn SmartContractSignatureVerifier,
) -> Result<(), SignatureRequestError> {
let verified_signature = VerifiedSignature::from_smart_contract_wallet(
&self.signature_text,
scw_verifier,
&signature.signature_bytes,
signature.account_id.clone(),
&mut signature.block_number,
)
.await?;

let Some(block_number) = signature.block_number else {
return Err(SignatureRequestError::BlockNumber);
};

self.add_verified_signature(
UnverifiedSignature::SmartContractWallet(UnverifiedSmartContractWalletSignature {
account_id: signature.account_id,
block_number,
signature_bytes: signature.signature_bytes,
}),
verified_signature,
)
}

pub async fn add_signature(
&mut self,
signature: UnverifiedSignature,
scw_verifier: &dyn SmartContractSignatureVerifier,
) -> Result<(), SignatureRequestError> {
let verified_sig = signature
let verified_signature = signature
.to_verified(self.signature_text.clone(), scw_verifier)
.await?;
let signer_identity = &verified_sig.signer;

self.add_verified_signature(signature, verified_signature)
}

fn add_verified_signature(
&mut self,
signature: UnverifiedSignature,
verified_signature: VerifiedSignature,
) -> Result<(), SignatureRequestError> {
let signer_identity = &verified_signature.signer;

let missing_signatures = self.missing_signatures();
tracing::info!("Provided Signer: {}", signer_identity);
Expand All @@ -239,7 +282,7 @@ impl SignatureRequest {
return Err(SignatureRequestError::UnknownSigner);
}

self.signatures.insert(verified_sig.signer, signature);
self.signatures.insert(verified_signature.signer, signature);

Ok(())
}
Expand Down
Loading

0 comments on commit 814c006

Please sign in to comment.