use alloy::hex;
use alloy::primitives::{Address, B256, Signature, U256};
use alloy::signers::local::PrivateKeySigner;
use chrono::Utc;
use rpc::{
CorePublicParameters, PaymentGuaranteeRequest, PaymentGuaranteeRequestClaims,
PaymentGuaranteeRequestClaimsV1, PaymentGuaranteeRequestClaimsV2,
PaymentGuaranteeValidationPolicyV2, SigningScheme, compute_validation_request_hash,
compute_validation_subject_hash,
};
use std::str::FromStr;
use crate::error::SignPaymentError;
use crate::sig::PaymentSigner;
fn create_test_params() -> CorePublicParameters {
CorePublicParameters {
public_key: vec![0u8; 48],
contract_address: "0x0000000000000000000000000000000000000000".to_string(),
ethereum_http_rpc_url: "http://localhost:8545".to_string(),
eip712_name: "4mica".to_string(),
eip712_version: "1".to_string(),
chain_id: 1,
max_accepted_guarantee_version: 1,
accepted_guarantee_versions: vec![1],
active_guarantee_domain_separator:
"0x0000000000000000000000000000000000000000000000000000000000000000".to_string(),
trusted_validation_registries: Vec::new(),
validation_hash_canonicalization_version: "4MICA_VALIDATION_REQUEST_V2".to_string(),
}
}
fn create_test_claims(user_addr: &str, recipient_addr: &str) -> PaymentGuaranteeRequestClaimsV1 {
PaymentGuaranteeRequestClaimsV1 {
user_address: user_addr.to_string(),
recipient_address: recipient_addr.to_string(),
tab_id: U256::from(12345u64),
req_id: U256::ZERO,
amount: U256::from(100u64),
timestamp: Utc::now().timestamp() as u64,
asset_address: "0x0000000000000000000000000000000000000000".into(),
}
}
fn create_test_claims_v2(
user_addr: &str,
recipient_addr: &str,
) -> anyhow::Result<PaymentGuaranteeRequestClaimsV2> {
let tab_id = U256::from(12345u64);
let req_id = U256::ZERO;
let amount = U256::from(100u64);
let timestamp = Utc::now().timestamp() as u64;
let asset_address = "0x0000000000000000000000000000000000000000".to_string();
let validation_subject_hash = compute_validation_subject_hash(
user_addr,
recipient_addr,
tab_id,
req_id,
amount,
&asset_address,
timestamp,
)?;
let mut validation_policy = PaymentGuaranteeValidationPolicyV2 {
validation_registry_address: Address::from_str(
"0x1111111111111111111111111111111111111111",
)?,
validation_request_hash: B256::ZERO,
validation_chain_id: 1,
validator_address: Address::from_str("0x2222222222222222222222222222222222222222")?,
validator_agent_id: U256::from(42u64),
min_validation_score: 80,
validation_subject_hash: B256::from(validation_subject_hash),
job_hash: B256::repeat_byte(0x11),
required_validation_tag: "hard-finality".to_string(),
};
validation_policy.validation_request_hash =
B256::from(compute_validation_request_hash(&validation_policy)?);
Ok(PaymentGuaranteeRequestClaimsV2 {
user_address: user_addr.to_string(),
recipient_address: recipient_addr.to_string(),
tab_id,
req_id,
amount,
asset_address,
timestamp,
validation_policy,
})
}
fn verify_promise_signature(
params: &CorePublicParameters,
req: &PaymentGuaranteeRequest,
) -> Result<(), String> {
let sig_bytes = hex::decode(req.signature.trim_start_matches("0x"))
.map_err(|_| "invalid hex signature".to_string())?;
let sig = Signature::try_from(sig_bytes.as_slice())
.map_err(|_| "invalid signature length".to_string())?;
use rpc::PaymentGuaranteeRequestEssentials;
let user_addr = Address::from_str(req.claims.user_address())
.map_err(|_| "invalid user address".to_string())?;
let recipient_addr = Address::from_str(req.claims.recipient_address())
.map_err(|_| "invalid recipient address".to_string())?;
let digest = match req.scheme {
SigningScheme::Eip712 => crate::digest::eip712_digest_for_claims(params, &req.claims)
.map_err(|_| "failed to compute digest".to_string())?,
SigningScheme::Eip191 => {
crate::digest::eip191_digest_for_claims(&req.claims, user_addr, recipient_addr)
.map_err(|_| "failed to compute digest".to_string())?
}
};
let (user_addr, digest) = (user_addr, digest);
let recovered = sig
.recover_address_from_prehash(&digest)
.map_err(|_| "signature recovery failed".to_string())?;
if recovered != user_addr {
return Err("Invalid signature".into());
}
Ok(())
}
#[tokio::test]
async fn test_eip712_sign_and_verify_success() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let user_addr = wallet.address().to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims(&user_addr, &recipient_addr);
let result = wallet
.sign_request(¶ms, claims.clone(), SigningScheme::Eip712)
.await;
assert!(result.is_ok(), "Signing should succeed");
let payment_sig = result.unwrap();
assert!(matches!(payment_sig.scheme, SigningScheme::Eip712));
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V1(claims),
payment_sig.signature,
payment_sig.scheme,
);
let verify_result = verify_promise_signature(¶ms, &request);
assert!(
verify_result.is_ok(),
"EIP-712 signature verification should succeed"
);
}
#[tokio::test]
async fn test_eip191_sign_and_verify_success() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let user_addr = wallet.address().to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims(&user_addr, &recipient_addr);
let result = wallet
.sign_request(¶ms, claims.clone(), SigningScheme::Eip191)
.await;
assert!(result.is_ok(), "Signing should succeed");
let payment_sig = result.unwrap();
assert!(matches!(payment_sig.scheme, SigningScheme::Eip191));
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V1(claims),
payment_sig.signature,
payment_sig.scheme,
);
let verify_result = verify_promise_signature(¶ms, &request);
assert!(
verify_result.is_ok(),
"EIP-191 signature verification should succeed"
);
}
#[tokio::test]
async fn test_eip712_sign_and_verify_success_v2() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let user_addr = wallet.address().to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims_v2(&user_addr, &recipient_addr).expect("build valid v2");
let result = wallet
.sign_request_v2(¶ms, claims.clone(), SigningScheme::Eip712)
.await;
assert!(result.is_ok(), "Signing v2 should succeed");
let payment_sig = result.expect("v2 signature");
assert!(matches!(payment_sig.scheme, SigningScheme::Eip712));
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V2(Box::new(claims)),
payment_sig.signature,
payment_sig.scheme,
);
let verify_result = verify_promise_signature(¶ms, &request);
assert!(
verify_result.is_ok(),
"V2 EIP-712 signature verification should succeed"
);
}
#[tokio::test]
async fn test_eip191_sign_and_verify_success_v2() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let user_addr = wallet.address().to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims_v2(&user_addr, &recipient_addr).expect("build valid v2");
let result = wallet
.sign_request_v2(¶ms, claims.clone(), SigningScheme::Eip191)
.await;
assert!(result.is_ok(), "Signing v2 should succeed");
let payment_sig = result.expect("v2 signature");
assert!(matches!(payment_sig.scheme, SigningScheme::Eip191));
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V2(Box::new(claims)),
payment_sig.signature,
payment_sig.scheme,
);
let verify_result = verify_promise_signature(¶ms, &request);
assert!(
verify_result.is_ok(),
"V2 EIP-191 signature verification should succeed"
);
}
#[tokio::test]
async fn test_eip712_signature_fails_with_tampered_v2_validation_field() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let user_addr = wallet.address().to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims_v2(&user_addr, &recipient_addr).expect("build valid v2");
let result = wallet
.sign_request_v2(¶ms, claims.clone(), SigningScheme::Eip712)
.await
.expect("v2 signing should succeed");
let mut tampered_claims = claims;
tampered_claims.validation_policy.validation_request_hash = B256::repeat_byte(0x11);
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V2(Box::new(tampered_claims)),
result.signature,
result.scheme,
);
let verify_result = verify_promise_signature(¶ms, &request);
assert!(
verify_result.is_err(),
"V2 EIP-712 signature verification should fail with tampered validation_request_hash"
);
}
#[tokio::test]
async fn test_eip712_signature_fails_with_tampered_claims() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let user_addr = wallet.address().to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims(&user_addr, &recipient_addr);
let result = wallet
.sign_request(¶ms, claims.clone(), SigningScheme::Eip712)
.await
.unwrap();
let mut tampered_claims = claims;
tampered_claims.amount = U256::from(999u64);
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V1(tampered_claims),
result.signature,
result.scheme,
);
let verify_result = verify_promise_signature(¶ms, &request);
assert!(
verify_result.is_err(),
"EIP-712 signature verification should fail with tampered amount"
);
let err = verify_result.unwrap_err();
assert!(
err.to_string().contains("Invalid signature"),
"Expected 'Invalid signature' error, got: {}",
err
);
}
#[tokio::test]
async fn test_eip191_signature_fails_with_tampered_claims() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let user_addr = wallet.address().to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims(&user_addr, &recipient_addr);
let result = wallet
.sign_request(¶ms, claims.clone(), SigningScheme::Eip191)
.await
.unwrap();
let mut tampered_claims = claims;
tampered_claims.recipient_address = "0x9999999999999999999999999999999999999999".to_string();
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V1(tampered_claims),
result.signature,
result.scheme,
);
let verify_result = verify_promise_signature(¶ms, &request);
assert!(
verify_result.is_err(),
"EIP-191 signature verification should fail with tampered recipient"
);
let err = verify_result.unwrap_err();
assert!(
err.to_string().contains("Invalid signature"),
"Expected 'Invalid signature' error, got: {}",
err
);
}
#[tokio::test]
async fn test_sign_request_fails_with_address_mismatch() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let different_addr = "0x9999999999999999999999999999999999999999".to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims(&different_addr, &recipient_addr);
let result = wallet
.sign_request(¶ms, claims, SigningScheme::Eip712)
.await;
assert!(
result.is_err(),
"Signing should fail when signer address doesn't match claims.user_address"
);
let err = result.unwrap_err();
match err {
SignPaymentError::AddressMismatch { signer, claims } => {
assert_eq!(signer, wallet.address());
assert_eq!(claims, different_addr);
}
_ => panic!("Expected AddressMismatch error, got: {:?}", err),
}
}
#[tokio::test]
async fn test_eip712_and_eip191_produce_different_signatures() {
let params = create_test_params();
let wallet = PrivateKeySigner::random();
let user_addr = wallet.address().to_string();
let recipient_addr = "0x1234567890123456789012345678901234567890".to_string();
let claims = create_test_claims(&user_addr, &recipient_addr);
let sig_eip712 = wallet
.sign_request(¶ms, claims.clone(), SigningScheme::Eip712)
.await
.unwrap();
let sig_eip191 = wallet
.sign_request(¶ms, claims.clone(), SigningScheme::Eip191)
.await
.unwrap();
assert_ne!(
sig_eip712.signature, sig_eip191.signature,
"EIP-712 and EIP-191 should produce different signatures"
);
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V1(claims.clone()),
sig_eip712.signature,
SigningScheme::Eip191,
);
let result = verify_promise_signature(¶ms, &request);
assert!(
result.is_err(),
"EIP-712 signature should not validate with EIP-191 scheme"
);
let request = PaymentGuaranteeRequest::new(
PaymentGuaranteeRequestClaims::V1(claims),
sig_eip191.signature,
SigningScheme::Eip712,
);
let result = verify_promise_signature(¶ms, &request);
assert!(
result.is_err(),
"EIP-191 signature should not validate with EIP-712 scheme"
);
}