use alloy::{
primitives::{Address, Signature, U256},
signers::{Signer, SignerSync},
sol,
sol_types::{Eip712Domain, SolStruct, eip712_domain},
};
sol!(
#[allow(clippy::too_many_arguments)]
#[sol(rpc, ignore_unlinked)]
WorldIdRegistry,
"abi/WorldIDRegistryAbi.json"
);
mod sol_types {
use alloy::sol;
sol! {
struct UpdateAuthenticator {
uint64 leafIndex;
address oldAuthenticatorAddress;
address newAuthenticatorAddress;
uint32 pubkeyId;
uint256 newAuthenticatorPubkey;
uint256 newOffchainSignerCommitment;
uint256 nonce;
}
struct InsertAuthenticator {
uint64 leafIndex;
address newAuthenticatorAddress;
uint32 pubkeyId;
uint256 newAuthenticatorPubkey;
uint256 newOffchainSignerCommitment;
uint256 nonce;
}
struct RemoveAuthenticator {
uint64 leafIndex;
address authenticatorAddress;
uint32 pubkeyId;
uint256 authenticatorPubkey;
uint256 newOffchainSignerCommitment;
uint256 nonce;
}
struct RecoverAccount {
uint64 leafIndex;
address newAuthenticatorAddress;
uint256 newAuthenticatorPubkey;
uint256 newOffchainSignerCommitment;
uint256 nonce;
}
struct InitiateRecoveryAgentUpdate {
uint64 leafIndex;
address newRecoveryAgent;
uint256 nonce;
}
struct CancelRecoveryAgentUpdate {
uint64 leafIndex;
uint256 nonce;
}
}
}
pub type UpdateAuthenticatorTypedData = sol_types::UpdateAuthenticator;
pub type InsertAuthenticatorTypedData = sol_types::InsertAuthenticator;
pub type RemoveAuthenticatorTypedData = sol_types::RemoveAuthenticator;
pub type RecoverAccountTypedData = sol_types::RecoverAccount;
pub type InitiateRecoveryAgentUpdateTypedData = sol_types::InitiateRecoveryAgentUpdate;
pub type CancelRecoveryAgentUpdateTypedData = sol_types::CancelRecoveryAgentUpdate;
#[must_use]
pub const fn domain(chain_id: u64, verifying_contract: Address) -> Eip712Domain {
eip712_domain!(
name: "WorldIDRegistry",
version: "1.0",
chain_id: chain_id,
verifying_contract: verifying_contract,
)
}
#[allow(clippy::too_many_arguments)]
pub fn sign_update_authenticator<S: SignerSync + Sync>(
signer: &S,
leaf_index: u64,
old_authenticator_address: Address,
new_authenticator_address: Address,
pubkey_id: u32,
new_authenticator_pubkey: U256,
new_offchain_signer_commitment: U256,
nonce: U256,
domain: &Eip712Domain,
) -> anyhow::Result<Signature> {
let payload = UpdateAuthenticatorTypedData {
leafIndex: leaf_index,
oldAuthenticatorAddress: old_authenticator_address,
newAuthenticatorAddress: new_authenticator_address,
pubkeyId: pubkey_id,
newAuthenticatorPubkey: new_authenticator_pubkey,
newOffchainSignerCommitment: new_offchain_signer_commitment,
nonce,
};
let digest = payload.eip712_signing_hash(domain);
Ok(signer.sign_hash_sync(&digest)?)
}
#[allow(clippy::too_many_arguments)]
pub fn sign_insert_authenticator<S: SignerSync + Sync>(
signer: &S,
leaf_index: u64,
new_authenticator_address: Address,
pubkey_id: u32,
new_authenticator_pubkey: U256,
new_offchain_signer_commitment: U256,
nonce: U256,
domain: &Eip712Domain,
) -> anyhow::Result<Signature> {
let payload = InsertAuthenticatorTypedData {
leafIndex: leaf_index,
newAuthenticatorAddress: new_authenticator_address,
pubkeyId: pubkey_id,
newAuthenticatorPubkey: new_authenticator_pubkey,
newOffchainSignerCommitment: new_offchain_signer_commitment,
nonce,
};
let digest = payload.eip712_signing_hash(domain);
Ok(signer.sign_hash_sync(&digest)?)
}
#[allow(clippy::too_many_arguments)]
pub fn sign_remove_authenticator<S: SignerSync + Sync>(
signer: &S,
leaf_index: u64,
authenticator_address: Address,
pubkey_id: u32,
authenticator_pubkey: U256,
new_offchain_signer_commitment: U256,
nonce: U256,
domain: &Eip712Domain,
) -> anyhow::Result<Signature> {
let payload = RemoveAuthenticatorTypedData {
leafIndex: leaf_index,
authenticatorAddress: authenticator_address,
pubkeyId: pubkey_id,
authenticatorPubkey: authenticator_pubkey,
newOffchainSignerCommitment: new_offchain_signer_commitment,
nonce,
};
let digest = payload.eip712_signing_hash(domain);
Ok(signer.sign_hash_sync(&digest)?)
}
pub async fn sign_recover_account<S: Signer + Sync>(
signer: &S,
leaf_index: u64,
new_authenticator_address: Address,
new_authenticator_pubkey: U256,
new_offchain_signer_commitment: U256,
nonce: U256,
domain: &Eip712Domain,
) -> anyhow::Result<Signature> {
let payload = RecoverAccountTypedData {
leafIndex: leaf_index,
newAuthenticatorAddress: new_authenticator_address,
newAuthenticatorPubkey: new_authenticator_pubkey,
newOffchainSignerCommitment: new_offchain_signer_commitment,
nonce,
};
let digest = payload.eip712_signing_hash(domain);
Ok(signer.sign_hash(&digest).await?)
}
pub fn sign_initiate_recovery_agent_update<S: SignerSync + Sync>(
signer: &S,
leaf_index: u64,
new_recovery_agent: Address,
nonce: U256,
domain: &Eip712Domain,
) -> anyhow::Result<Signature> {
let payload = InitiateRecoveryAgentUpdateTypedData {
leafIndex: leaf_index,
newRecoveryAgent: new_recovery_agent,
nonce,
};
let digest = payload.eip712_signing_hash(domain);
Ok(signer.sign_hash_sync(&digest)?)
}
pub fn sign_cancel_recovery_agent_update<S: SignerSync + Sync>(
signer: &S,
leaf_index: u64,
nonce: U256,
domain: &Eip712Domain,
) -> anyhow::Result<Signature> {
let payload = CancelRecoveryAgentUpdateTypedData {
leafIndex: leaf_index,
nonce,
};
let digest = payload.eip712_signing_hash(domain);
Ok(signer.sign_hash_sync(&digest)?)
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::{
primitives::{Address, U256, address},
signers::local::PrivateKeySigner,
};
fn test_domain() -> Eip712Domain {
domain(1, address!("0x1111111111111111111111111111111111111111"))
}
#[test]
fn sign_initiate_recovery_agent_update_roundtrip() {
let signer = PrivateKeySigner::random();
let domain = test_domain();
let leaf_index: u64 = 42;
let new_recovery_agent: Address = address!("0x2222222222222222222222222222222222222222");
let nonce = U256::from(7u64);
let sig = sign_initiate_recovery_agent_update(
&signer,
leaf_index,
new_recovery_agent,
nonce,
&domain,
)
.expect("signing must succeed");
let payload = InitiateRecoveryAgentUpdateTypedData {
leafIndex: leaf_index,
newRecoveryAgent: new_recovery_agent,
nonce,
};
let digest = payload.eip712_signing_hash(&domain);
let recovered = sig.recover_address_from_prehash(&digest).unwrap();
assert_eq!(recovered, signer.address());
}
#[test]
fn sign_cancel_recovery_agent_update_roundtrip() {
let signer = PrivateKeySigner::random();
let domain = test_domain();
let leaf_index: u64 = 99;
let nonce = U256::from(3u64);
let sig = sign_cancel_recovery_agent_update(&signer, leaf_index, nonce, &domain)
.expect("signing must succeed");
let payload = CancelRecoveryAgentUpdateTypedData {
leafIndex: leaf_index,
nonce,
};
let digest = payload.eip712_signing_hash(&domain);
let recovered = sig.recover_address_from_prehash(&digest).unwrap();
assert_eq!(recovered, signer.address());
}
#[test]
fn sign_initiate_recovery_agent_update_different_leaf_indices() {
let signer = PrivateKeySigner::random();
let domain = test_domain();
let new_recovery_agent: Address = address!("0x3333333333333333333333333333333333333333");
let nonce = U256::ZERO;
let sig1 =
sign_initiate_recovery_agent_update(&signer, 1, new_recovery_agent, nonce, &domain)
.unwrap();
let sig2 =
sign_initiate_recovery_agent_update(&signer, 2, new_recovery_agent, nonce, &domain)
.unwrap();
assert_ne!(sig1.as_bytes(), sig2.as_bytes());
}
#[test]
fn sign_cancel_recovery_agent_update_different_nonces() {
let signer = PrivateKeySigner::random();
let domain = test_domain();
let sig1 =
sign_cancel_recovery_agent_update(&signer, 1, U256::from(0u64), &domain).unwrap();
let sig2 =
sign_cancel_recovery_agent_update(&signer, 1, U256::from(1u64), &domain).unwrap();
assert_ne!(sig1.as_bytes(), sig2.as_bytes());
}
#[tokio::test]
async fn sign_recover_account_roundtrip() {
let signer = PrivateKeySigner::random();
let domain = test_domain();
let leaf_index: u64 = 10;
let new_authenticator_address = address!("0x4444444444444444444444444444444444444444");
let new_authenticator_pubkey = U256::from(123u64);
let new_offchain_signer_commitment = U256::from(456u64);
let nonce = U256::from(1u64);
let sig = sign_recover_account(
&signer,
leaf_index,
new_authenticator_address,
new_authenticator_pubkey,
new_offchain_signer_commitment,
nonce,
&domain,
)
.await
.expect("signing must succeed");
let payload = RecoverAccountTypedData {
leafIndex: leaf_index,
newAuthenticatorAddress: new_authenticator_address,
newAuthenticatorPubkey: new_authenticator_pubkey,
newOffchainSignerCommitment: new_offchain_signer_commitment,
nonce,
};
let digest = payload.eip712_signing_hash(&domain);
let recovered = sig.recover_address_from_prehash(&digest).unwrap();
assert_eq!(recovered, signer.address());
}
}