Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .wasm-budget.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"receipt_anchor": 24576,
"refund_vault": 37376
"refund_vault": 49152
}
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ breaking changes bump the **minor** version, and they are called out as such.
`.git/HEAD`, the resolved branch ref, the index and `src/` so a cached build
cannot report a stale hash. A `test_commit_meta_is_well_formed` test in both
crates pins the embedded commit to 40 hex characters.
- **Commit-reveal API on `RefundVault`** (issue #128): new `commit`,
`reveal_refund`, `reveal_withdraw`, `get_commitment` and
`get_commit_reveal_delay` functions. A merchant commits an opaque
`sha256(plaintext || salt)` hash, waits the minimum
`COMMIT_REVEAL_DELAY` (10 ledgers), then reveals the plaintext + salt to
execute the identical refund/withdraw. The commitment is consumed on success.
New error codes: `CommitmentNotFound` (302), `CommitmentMismatch` (303),
`CommitmentNotDue` (304), `CommitmentAlreadyUsed` (305).

### Changed

Expand All @@ -48,6 +56,16 @@ breaking changes bump the **minor** version, and they are called out as such.
has not authorised, and `withdraw` stays merchant-only. The existing
`test_deposit_from_non_merchant_fails` pins the behaviour and is annotated as
deliberate.
- **Front-running of refunds/withdraws is mitigated** (issue #128): the
merchant no longer reveals a refund/withdraw's full parameters in a single
callable transaction. Instead they `commit` an opaque `sha256(plaintext ||
salt)` hash on-chain first and only `reveal` the plaintext + salt after the
minimum `COMMIT_REVEAL_DELAY`. A mempool observer cannot reconstruct or
reorder the operation from the commitment alone. The `commit` on the revealed
plaintext is verified against the stored hash (`CommitmentMismatch` on
mismatch) and consumed on success, so a front-running replay is impossible.
Security audit tests in `commit_reveal_tests.rs` pin the delay boundary, the
mismatch rejection and the opacity of the commitment.

## [0.3.0] — 2026-08-26

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ Holds merchant float and executes refunds bounded by an on-chain policy.
| `pause()` | Pauses operations for emergency stops. Merchant auth required. |
| `unpause()` | Resumes paused operations. Merchant auth required. |
| `extend_refund_ttl(payment_ref)` | Extends the TTL of a refund record to prevent archival. Publicly callable. |
| `commit(commitment: BytesN<32>)` | Merchant commits an opaque `sha256(plaintext || salt)` hash ahead of a refund/withdraw, to prevent mempool front-running. Merchant auth required. See the security model for the plaintext layout and salt. |
| `reveal_refund(payment_ref, recipient, amount, paid_at_ledger, payment_amount, salt)` | Reveals the plaintext + salt for a previously committed refund after `COMMIT_REVEAL_DELAY` ledgers, verifies it against the stored commitment, then executes the refund. |
| `reveal_withdraw(amount, to, salt)` | Reveals the plaintext + salt for a previously committed withdraw after `COMMIT_REVEAL_DELAY` ledgers, verifies it, then executes the withdraw. |
| `get_commitment(commitment: BytesN<32>) -> Option<CommitRecord>` | Returns the commit record (with `committed_at_ledger`) for a commitment, if one is pending. |
| `get_commit_reveal_delay() -> u32` | Returns the minimum ledger delay between commit and reveal. |

Emits:

Expand Down Expand Up @@ -190,6 +195,12 @@ contracts instead of per-contract tables.
| 19 | `ExceedsPayment` | Cumulative refunds would exceed the payment ceiling. |
| 100 | `BatchNotFound` | The requested batch does not exist (or was pruned). |
| 101 | `BatchTooLarge` | A batch larger than `MAX_BATCH_SIZE` was submitted. |
| 300 | `NoPendingPolicy` | No policy change is pending. |
| 301 | `TimelockNotExpired` | The policy timelock has not yet elapsed. |
| 302 | `CommitmentNotFound` | No commitment stored for the given hash. |
| 303 | `CommitmentMismatch` | The revealed plaintext does not hash to the committed commitment. |
| 304 | `CommitmentNotDue` | `COMMIT_REVEAL_DELAY` ledgers have not yet elapsed since commit. |
| 305 | `CommitmentAlreadyUsed` | The commitment was already consumed by a successful reveal. |

Codes are stable: new variants are appended with fresh values, never renumbered.

Expand Down
8 changes: 8 additions & 0 deletions contracts/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,12 @@ pub enum Error {
NoPendingPolicy = 300,
/// The timelock period has not yet elapsed.
TimelockNotExpired = 301,
/// A commit-reveal reveal referenced a commitment that was never committed.
CommitmentNotFound = 302,
/// The revealed plaintext does not match the committed hash.
CommitmentMismatch = 303,
/// The minimum commit-to-reveal ledger delay has not yet elapsed.
CommitmentNotDue = 304,
/// The commitment was already consumed by a prior reveal.
CommitmentAlreadyUsed = 305,
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading
Loading