Privacy

Private Checkout v1

Prove the hackathon private-checkout moment: encrypted amount validation plus a private fulfillment trigger.

Privacy target

Private Checkout v1 is a Private Checkout Proof MVP. It proves that a checkout contract can validate encrypted amount equality and emit only a fulfillment-safe paid/rejected result.

The implemented local-dev token is ConfidentialUSDMock: an official-style mintable confidential cUSDT mock keyed by wallet address. It is not a MetaMask ERC20 token and should not be tested through a public ERC20 transfer.

The privacy claim is scoped to checkout business data in PrivateCheckoutSettlement storage and events. Public observers should not see merchant wallet, payout wallet, project id, order id, or amount there. In the direct-wallet MVP, the paying wallet is still visible as the EVM transaction sender, and withdraw recipient privacy is not claimed.

DataChain treatmentWho knows in v1
Buyer walletDirect-wallet MVP submits as msg.sender; not stored as an order fieldPublic chain observers can see tx sender; ZamaPay can map the checkout
Merchant walletCheckout uses settlementBucketCommitment and bucketOwnerCommitmentZamaPay, merchant backend, and withdraw observers
Payout walletNot stored or emitted during checkout/payment; v1 withdraw recipient is calldataMerchant backend and withdraw observers
Project / order idHashed into orderCommitmentZamaPay and merchant backend
Gross and paid amountFHE encrypted handlesZamaPay in v1, hidden from public chain observers
Paid/rejectedPublic boolean after decrypting acceptedEveryone

MVP boundary

The design has two layers. The first is required for the hackathon proof. The second must be explicitly named so the demo does not confuse encrypted equality with real asset settlement.

LayerWhat it provesv1 position
Private Checkout ProofexpectedAmount and paidAmount stay encrypted; the contract checks FHE.eq and reveals only accepted.Required in the hackathon demo.
Payment RailThe buyer actually paid, or the demo honestly simulated settlement before finalization.Implemented as mock confidential cUSDT balance on local-dev.
Merchant Settlement / WithdrawMerchant net, platform fee, and payout close without per-order public disclosure.Implemented for local-dev; payout-recipient privacy is not claimed in v1.

Field contract

Use this field contract as the implementation boundary. Core checkout values may exist on-chain only as FHE handles. Public checkout fields must be commitments, coarse status, or time bounds. The direct-wallet payer is public as the transaction sender, and v1 withdraw reveals the authorized recipient in calldata.

BoundaryFieldTypeMeaningPublic rule
On-chain encrypted, v1 coreexpectedAmounteuint64Order amount due.Stored only as an FHE handle; never emitted as plaintext.
merchantNetAmounteuint64Merchant net split for this checkout.Imported with the same input proof; only added to encrypted pending if payment succeeds.
platformFeeAmounteuint64Platform fee split for this checkout.Imported with the same input proof; only added to encrypted pending if payment succeeds.
splitCheckeboolEncrypted result of merchantNetAmount + platformFeeAmount == expectedAmount.Never decrypted per order; gates payment acceptance.
paidAmountexternalEuint64Buyer-submitted payment amount.Submitted with inputProof and imported through FHE.fromExternal.
paymentCheckeboolEncrypted result of paidAmount == expectedAmount.Only this boolean is publicly decrypted as accepted.
On-chain encrypted, v1 settlementencryptedMerchantPending[settlementBucketCommitment]euint64Merchant aggregate settlement balance.Accrued only by accepted checkouts; moved by merchant-authorized encrypted withdraw.
encryptedPlatformPendingeuint64Platform aggregate fee balance.Fee balance stays encrypted until platform settlement is explicitly added.
On-chain publicorderCommitmentbytes32hash(orderId, projectId, amount, salt).Stable order reference only; raw ids and amount stay off-chain.
settlementBucketCommitmentbytes32hash(merchantId, settlementEpoch, randomSalt), not merchant address.Rotate by checkout, batch, day, or week; never use a permanent merchant id.
bucketOwnerCommitmentbytes32hash(settlementBucketCommitment, bucketOwner).Submitted during checkout creation instead of the raw merchant wallet.
paymentStatusenumcreated / submitted / accepted / rejected / expired.Coarse fulfillment state; no counterparty or amount data.
expiresAtuint64Checkout deadline.Public time bound used to reject stale payment attempts.
paidAtuint256Finalization timestamp.Time signal only; do not pair it with raw order or wallet fields.
buyer tx senderaddressWallet that submits submitPrivatePayment in the direct MVP.Public because of EVM mechanics; do not claim payer-address privacy in this MVP.
Never public in checkout calldata/eventsmerchant addressaddressMerchant wallet or dashboard identity.Checkout events do not emit it; withdraw authorization may reveal it.
Withdraw calldatapayout wallet during withdrawaddressSettlement destination.Bound by merchant EIP-712 authorization; local-dev does not claim payout-recipient privacy.
Never public on-chainamountDue plaintextuint64 / token minor unitsPlain order amount.Use encrypted expectedAmount or an order commitment instead.
merchantNet plaintextuint64 / token minor unitsPlain merchant net split.Use encrypted settlement accumulators and merchant-only dashboard projection.
platformFee plaintextuint64 / token minor unitsPlain platform fee split.Use encrypted settlement accumulators and platform-only projection.
projectId plaintextstring / bytesZamaPay project id.Hash into orderCommitment; never store the raw business id.
orderId plaintextstring / bytesMerchant order id.Hash into orderCommitment; never store the raw order id.

Payment rail

Encrypted equality proves that an encrypted paidAmount equals the encrypted expectedAmount. It does not prove that value moved. The selected rail must be part of the demo contract, backend policy, or test script.

RailWhat it provesUse in hackathon
Direct mock cUSDT confidential balanceBuyer has a demo confidential balance and the buyer-submitted transaction deducts an encrypted amount.Implemented local-dev path.
ERC-7984 / confidential wrapper transferA confidential token balance or transfer amount moves through a token contract.Post-MVP unless address linkage and operator semantics are deliberately handled.

Encrypted vs hidden

Use on-chain encryption when the contract must compute over a value. Use commitments when the chain only needs a stable reference and should not learn the raw identity or business id.

In this design, amounts are encrypted because the settlement contract compares paidAmount with expectedAmount. Merchant, payout wallet, project id, and order id stay out of checkout/payment storage and events. Payer-address privacy is not claimed while the buyer submits the EVM transaction directly; payout-recipient privacy is not claimed for v1 withdraw.

ConceptUse it forRule
On-chain encryptedexpectedAmount, paidAmount, paymentCheckChain can compute, observers cannot read
Not public in checkout/paymentmerchant, payout wallet, projectId, orderIdRaw value never appears in checkout creation, payment submission, payment storage, or payment events
CommitmentorderCommitment, settlementBucketCommitmentHash high-entropy salted business data; rotate settlement buckets

Payment flow

The normal checkout path decrypts only accepted, an ebool. Per-order gross, merchant net, and platform fee stay encrypted and can be handled by settlement batches later.

Expected and paid amounts are encrypted as external inputs with input proofs, then imported by the contract. Local-dev uses Hardhat/FHEVM mock RPC; Sepolia uses Zama's official test relayer through `@zama-fhe/relayer-sdk` `SepoliaConfig`.

Rendering diagram...
accepted = FHE.eq(paidAmount, expectedAmount)

Contract shape

The checkout record stays small. It keeps encrypted gross/net/fee handles for validation, while aggregate pending balances live in bucket mappings outside each checkout.

enum PaymentStatus {
    None,
    Created,
    Submitted,
    Accepted,
    Rejected,
    Expired
}

struct PrivateCheckout {
    bytes32 orderCommitment;
    bytes32 settlementBucketCommitment;
    euint64 expectedAmount;
    euint64 merchantNetAmount;
    euint64 platformFeeAmount;
    ebool splitCheck;
    ebool paymentCheck;
    PaymentStatus status;
    uint64 expiresAt;
    uint256 paidAt;
}

Safety controls

Payment intent and lifecycle controls are part of the MVP, because they stop the demo from becoming a free-card oracle.

ControlRuleFailure blocked
Payment intent bindingBind orderCommitment, encrypted amount handle, asset, chainId, settlement contract, nonce, and expiresAt before adding any future sponsored submitter mode.Replaying one valid encrypted amount against another checkout.
Withdraw authorizationMerchant signs settlementBucketCommitment, withdrawalNonce, bucketOwner, recipient, encryptedAmount handle, inputProofHash, deadline, chainId, and settlement contract.A chain submitter moving an unauthorized bucket or swapping the encrypted withdraw input.
ExpiryReject payment submission after expiresAt.Late payment after merchant order is stale.
Nonce reuseReject reused payment nonce or already-submitted intent.Duplicate payment attempts and replay.
Final status lockAccepted, rejected, and expired checkouts cannot be submitted or finalized again.Double fulfillment.
Rotating bucketsettlementBucketCommitment rotates by checkout, batch, day, or week.Long-lived merchant activity graph.

Acceptance criteria

The demo contract should stay separate from the old transparent settlement shape. Transparent settlement exposed merchant, payoutWallet, payer, and amountDue by design.

Private Checkout v1 succeeds when PrivateCheckoutSettlement events expose only commitments, encrypted handles, status, and timestamps, while CardForge still receives a fulfillment-ready webhook.

01
Create private checkout

Create one private checkout on the active contract environment from a CardForge order and store only commitments plus encrypted expectedAmount on-chain.

02
Submit buyer payment

Submit encrypted payment directly from the buyer wallet. Local-dev uses mock RPC for proofs; Sepolia uses Zama official test relayer proofs.

03
Validate privately

Verify the ConfidentialUSDMock debit, then use FHE equality to compare encrypted paidAmount with encrypted expectedAmount.

04
Finalize fulfillment

Decrypt only accepted, map orderCommitment back to the demo order, and release the card through the existing webhook path.

05
Withdraw aggregate balance

Merchant signs an EIP-712 withdraw authorization; the local chain submitter sends the encrypted withdraw transaction and the read model records the chain hash.

06
Block payment abuse

Prove replay, expired payment, resubmit-after-final, double-finalize, and unauthorized withdraw paths are rejected.

Ready to wire a merchant project?

Create the project in the console, then keep external checkout creation on the project API-key path.

Open console