diff --git a/contracts/privacy_pool/src/core/withdraw.rs b/contracts/privacy_pool/src/core/withdraw.rs index b452979..aad6141 100644 --- a/contracts/privacy_pool/src/core/withdraw.rs +++ b/contracts/privacy_pool/src/core/withdraw.rs @@ -18,6 +18,7 @@ pub fn execute( proof: Proof, pub_inputs: PublicInputs, ) -> Result { + let invoker_addr = env.invoker(); // Load and validate pool configuration let pool_config = config::load_pool_config(&env, &pool_id)?; validation::require_not_paused(&pool_config)?; @@ -55,6 +56,14 @@ pub fn execute( let recipient = address_decoder::decode_address(&env, &pub_inputs.recipient); let relayer_opt = address_decoder::decode_optional_relayer(&env, &pub_inputs.relayer); + // ZK-073: If no relayer is specified (zero-relayer semantics), + // enforce that the recipient must be the transaction invoker. + if relayer_opt.is_none() { + if recipient != invoker_addr { + return Err(Error::ZeroRelayerRecipientMismatch); + } + } + // Step 7: Transfer funds transfer_funds( &env, diff --git a/contracts/privacy_pool/src/types/errors.rs b/contracts/privacy_pool/src/types/errors.rs index 5d8c359..44dfd8c 100644 --- a/contracts/privacy_pool/src/types/errors.rs +++ b/contracts/privacy_pool/src/types/errors.rs @@ -45,12 +45,14 @@ pub enum Error { FeeExceedsAmount = 43, /// Relayer address is non-zero but fee is zero InvalidRelayerFee = 44, + /// Recipient address must be invoker for zero-relayer withdrawals + ZeroRelayerRecipientMismatch = 45, /// Recipient address is invalid - InvalidRecipient = 45, + InvalidRecipient = 46, /// Pool ID in public inputs does not match the pool being withdrawn from - InvalidPoolId = 46, + InvalidPoolId = 47, /// Denomination in public inputs does not match the pool denomination - InvalidDenomination = 47, + InvalidDenomination = 48, // ── Verifying Key ────────────────────────────────── /// Verifying key has not been set diff --git a/sdk/src/test/harness/errors.ts b/sdk/src/test/harness/errors.ts index 6316207..d4a8a76 100644 --- a/sdk/src/test/harness/errors.ts +++ b/sdk/src/test/harness/errors.ts @@ -82,9 +82,10 @@ export enum ContractErrorCode { InvalidProof = 42, FeeExceedsAmount = 43, InvalidRelayerFee = 44, - InvalidRecipient = 45, - InvalidPoolId = 46, - InvalidDenomination = 47, + ZeroRelayerRecipientMismatch = 45, + InvalidRecipient = 46, + InvalidPoolId = 47, + InvalidDenomination = 48, // Verifying Key NoVerifyingKey = 50, @@ -419,6 +420,24 @@ function classifyByErrorCode( }; // Recipient errors + case ContractErrorCode.ZeroRelayerRecipientMismatch: + return { + category: ErrorCategory.RECIPIENT_ERROR, + originalMessage: error.message, + errorCode, + actionableMessage: 'Recipient address must be the transaction invoker for zero-relayer withdrawals.', + recommendations: [ + 'When not using a relayer, ensure the withdrawal recipient is your own address', + 'Verify the recipient address matches the invoker address of the transaction', + `Recipient: ${context?.recipient?.slice(0, 16)}...`, + ], + context: { + errorCode, + recipient: context?.recipient, + relayer: context?.relayer, + }, + }; + case ContractErrorCode.InvalidRecipient: return { category: ErrorCategory.RECIPIENT_ERROR,