use super::{BundleActionArgs, EthEncodeError};
use ethabi::{encode, Token, Uint};
use sha3::{Digest, Keccak256};
#[derive(Debug, Clone)]
pub struct PrivacyCallArgs {
pub actions: Vec<BundleActionArgs>,
pub binding_sig: [[u8; 32]; 3],
}
fn selector(signature: &[u8]) -> [u8; 4] {
Keccak256::digest(signature)[..4]
.try_into()
.expect("selector is 4 bytes")
}
fn bundle_actions_token(actions: &[BundleActionArgs]) -> Token {
Token::Array(
actions
.iter()
.map(|a| {
let pub_fields_token = Token::FixedArray(
a.pub_fields
.iter()
.map(|b| Token::Uint(Uint::from_big_endian(b)))
.collect(),
);
let spend_auth_sig_token = Token::FixedArray(
a.spend_auth_sig
.iter()
.map(|b| Token::Uint(Uint::from_big_endian(b)))
.collect(),
);
Token::Tuple(vec![
Token::FixedBytes(a.cmx.to_vec()),
Token::Bytes(a.enc_ciphertext.clone()),
Token::Bytes(a.out_ciphertext.clone()),
Token::FixedBytes(a.epk.to_vec()),
Token::FixedBytes(a.nf_old.to_vec()),
Token::FixedBytes(a.anchor.to_vec()),
Token::Bytes(a.proof.clone()),
pub_fields_token,
spend_auth_sig_token,
])
})
.collect(),
)
}
fn privacy_call_token(call: &PrivacyCallArgs) -> Token {
let actions_bytes = encode(&[bundle_actions_token(&call.actions)]);
let binding_sig_token = Token::FixedArray(
call.binding_sig
.iter()
.map(|b| Token::Uint(Uint::from_big_endian(b)))
.collect(),
);
Token::Tuple(vec![Token::Bytes(actions_bytes), binding_sig_token])
}
pub fn privacy_call_commit(call: &PrivacyCallArgs) -> [u8; 32] {
let encoded = encode(&[privacy_call_token(call)]);
Keccak256::digest(&encoded).into()
}
fn with_selector(sel: [u8; 4], body: Vec<u8>) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + body.len());
out.extend_from_slice(&sel);
out.extend_from_slice(&body);
out
}
pub fn encode_perc20_transfer_calldata(call: &PrivacyCallArgs) -> Vec<u8> {
let body = encode(&[privacy_call_token(call)]);
with_selector(selector(b"transfer((bytes,uint256[3]))"), body)
}
pub fn encode_perc20_transfer_executor_calldata(
executor: &[u8; 20],
call: &PrivacyCallArgs,
) -> Vec<u8> {
let tokens = vec![
Token::Address(ethabi::Address::from(*executor)),
privacy_call_token(call),
];
let body = encode(&tokens);
with_selector(selector(b"transfer(address,(bytes,uint256[3]))"), body)
}
pub fn encode_wrapped_shield_calldata(amount_units: u64, call: &PrivacyCallArgs) -> Vec<u8> {
let tokens = vec![Token::Uint(Uint::from(amount_units)), privacy_call_token(call)];
let body = encode(&tokens);
with_selector(selector(b"shield(uint256,(bytes,uint256[3]))"), body)
}
pub fn encode_wrapped_unshield_calldata(
amount_units: u64,
recipient: &[u8; 20],
call: &PrivacyCallArgs,
) -> Vec<u8> {
let tokens = vec![
Token::Uint(Uint::from(amount_units)),
Token::Address(ethabi::Address::from(*recipient)),
privacy_call_token(call),
];
let body = encode(&tokens);
with_selector(selector(b"unshield(uint256,address,(bytes,uint256[3]))"), body)
}
pub fn compute_swap_id(
initiator: &[u8; 20],
pool_a: &[u8; 20],
pool_b: &[u8; 20],
htlc_hash: &[u8; 32],
commit_a: &[u8; 32],
rk_bx: &[u8; 32],
rk_by: &[u8; 32],
salt: &[u8; 32],
) -> [u8; 32] {
let encoded = encode(&[
Token::Address(ethabi::Address::from(*initiator)),
Token::Address(ethabi::Address::from(*pool_a)),
Token::Address(ethabi::Address::from(*pool_b)),
Token::FixedBytes(htlc_hash.to_vec()),
Token::FixedBytes(commit_a.to_vec()),
Token::Uint(Uint::from_big_endian(rk_bx)),
Token::Uint(Uint::from_big_endian(rk_by)),
Token::FixedBytes(salt.to_vec()),
]);
Keccak256::digest(&encoded).into()
}
pub fn encode_swap_initiate_calldata(
pool_a: &[u8; 20],
pool_b: &[u8; 20],
htlc_hash: &[u8; 32],
commit_a: &[u8; 32],
rk_bx: &[u8; 32],
rk_by: &[u8; 32],
deadline: u64,
salt: &[u8; 32],
) -> Vec<u8> {
let tokens = vec![
Token::Address(ethabi::Address::from(*pool_a)),
Token::Address(ethabi::Address::from(*pool_b)),
Token::FixedBytes(htlc_hash.to_vec()),
Token::FixedBytes(commit_a.to_vec()),
Token::Uint(Uint::from_big_endian(rk_bx)),
Token::Uint(Uint::from_big_endian(rk_by)),
Token::Uint(Uint::from(deadline)),
Token::FixedBytes(salt.to_vec()),
];
let body = encode(&tokens);
with_selector(
selector(b"initiateSwap(address,address,bytes32,bytes32,uint256,uint256,uint64,bytes32)"),
body,
)
}
pub fn encode_swap_join_calldata(
swap_id: &[u8; 32],
commit_b: &[u8; 32],
joiner_sig: &[[u8; 32]; 3],
) -> Vec<u8> {
let joiner_sig_token = Token::FixedArray(
joiner_sig
.iter()
.map(|b| Token::Uint(Uint::from_big_endian(b)))
.collect(),
);
let tokens = vec![
Token::FixedBytes(swap_id.to_vec()),
Token::FixedBytes(commit_b.to_vec()),
joiner_sig_token,
];
let body = encode(&tokens);
with_selector(
selector(b"joinSwap(bytes32,bytes32,uint256[3])"),
body,
)
}
pub fn encode_swap_settle_calldata(
swap_id: &[u8; 32],
secret: &[u8; 32],
call_a: &PrivacyCallArgs,
call_b: &PrivacyCallArgs,
) -> Vec<u8> {
let tokens = vec![
Token::FixedBytes(swap_id.to_vec()),
Token::FixedBytes(secret.to_vec()),
privacy_call_token(call_a),
privacy_call_token(call_b),
];
let body = encode(&tokens);
with_selector(
selector(b"settle(bytes32,bytes32,(bytes,uint256[3]),(bytes,uint256[3]))"),
body,
)
}
pub fn perc20_transfer_selector() -> [u8; 4] { selector(b"transfer((bytes,uint256[3]))") }
pub fn perc20_transfer_executor_selector() -> [u8; 4] { selector(b"transfer(address,(bytes,uint256[3]))") }
pub fn wrapped_shield_selector() -> [u8; 4] { selector(b"shield(uint256,(bytes,uint256[3]))") }
pub fn wrapped_unshield_selector() -> [u8; 4] { selector(b"unshield(uint256,address,(bytes,uint256[3]))") }
pub fn swap_initiate_selector() -> [u8; 4] { selector(b"initiateSwap(address,address,bytes32,bytes32,uint256,uint256,uint64,bytes32)") }
pub fn swap_join_selector() -> [u8; 4] { selector(b"joinSwap(bytes32,bytes32,uint256[3])") }
pub fn swap_settle_selector() -> [u8; 4] { selector(b"settle(bytes32,bytes32,(bytes,uint256[3]),(bytes,uint256[3]))") }
#[allow(dead_code)]
fn _assert_error_in_scope(_e: EthEncodeError) {}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_action() -> BundleActionArgs {
BundleActionArgs {
cmx: [1u8; 32],
enc_ciphertext: vec![0u8; 580],
out_ciphertext: vec![0u8; 80],
epk: [2u8; 32],
nf_old: [3u8; 32],
anchor: [4u8; 32],
proof: vec![0xabu8; 256],
pub_fields: [[5u8; 32]; 8],
spend_auth_sig: [[6u8; 32]; 3],
}
}
fn dummy_call() -> PrivacyCallArgs {
PrivacyCallArgs { actions: vec![dummy_action()], binding_sig: [[7u8; 32]; 3] }
}
#[test]
fn selectors_match_onchain() {
assert_eq!(perc20_transfer_selector(), [0xed, 0xa1, 0xa0, 0xac]);
assert_eq!(perc20_transfer_executor_selector(), [0xc7, 0xb9, 0x21, 0xd3]);
assert_eq!(wrapped_shield_selector(), [0x04, 0x11, 0xcb, 0xab]);
assert_eq!(wrapped_unshield_selector(), [0x53, 0x64, 0x4c, 0x61]);
assert_eq!(swap_initiate_selector(), [0x6d, 0xb7, 0x97, 0x4d]);
assert_eq!(swap_join_selector(), [0x8b, 0xbe, 0x82, 0x1a]);
assert_eq!(swap_settle_selector(), [0xc7, 0xec, 0xe1, 0x5f]);
}
#[test]
fn calldata_prefixes_correct_selector() {
let call = dummy_call();
assert_eq!(&encode_perc20_transfer_calldata(&call)[..4], &perc20_transfer_selector());
assert_eq!(
&encode_perc20_transfer_executor_calldata(&[0xEFu8; 20], &call)[..4],
&perc20_transfer_executor_selector()
);
assert_eq!(&encode_wrapped_shield_calldata(1000, &call)[..4], &wrapped_shield_selector());
assert_eq!(
&encode_wrapped_unshield_calldata(1000, &[0xDEu8; 20], &call)[..4],
&wrapped_unshield_selector()
);
}
#[test]
fn shield_and_transfer_share_privacy_call_tail() {
let call = dummy_call();
let transfer = encode_perc20_transfer_calldata(&call);
let shield = encode_wrapped_shield_calldata(1000, &call);
let t_tail = &transfer[4 + 32..]; let s_tail = &shield[4 + 64..]; assert_eq!(t_tail, s_tail, "PrivacyCall encoding must be reused verbatim");
}
#[test]
fn commit_is_deterministic_keccak() {
let call = dummy_call();
let c1 = privacy_call_commit(&call);
let c2 = privacy_call_commit(&call);
assert_eq!(c1, c2);
let mut other = call.clone();
other.binding_sig[2] = [0x9u8; 32];
assert_ne!(privacy_call_commit(&other), c1);
}
#[test]
fn swap_id_matches_abi_encode_layout() {
let base = compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[8u8; 32], &[9u8; 32], &[6u8; 32]);
assert_eq!(
base,
compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[8u8; 32], &[9u8; 32], &[6u8; 32])
);
assert_ne!(
base,
compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[8u8; 32], &[9u8; 32], &[7u8; 32])
);
assert_ne!(
base,
compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[0xAu8; 32], &[9u8; 32], &[6u8; 32])
);
}
}