Skip to content

feat(zk): send the ERC-20 intent commitment with the pricing quote - #15

Open
ozwaldorf wants to merge 3 commits into
mainfrom
feat/zk-intent-commitment
Open

feat(zk): send the ERC-20 intent commitment with the pricing quote#15
ozwaldorf wants to merge 3 commits into
mainfrom
feat/zk-intent-commitment

Conversation

@ozwaldorf

Copy link
Copy Markdown
Member

The ZK branches move ERC-20 settlement behind a hiding commitment, so /pricing/quote now rejects an ERC-20 escrow without one:

POST /pricing/quote 400
ApiError: ERC-20 escrow requires a ZK intent

The API cannot derive the commitment: it hides the recipient and amount under a salt taken from the per-escrow blinding scalar, which only the depositor and the enclave hold. The client computes it and the API encodes it verbatim into the constructor.

What this adds

src/internal/zk.ts mirrors nomad-types::zk byte for byte:

salt       = keccak256("mirage/zk/intent-salt/v1" ++ s ++ instance_domain ++ request_id ++ row_index)
commitment = keccak256(v2 ++ instance_domain ++ chainId ++ escrow ++ request_id
                       ++ row_index ++ assetKind ++ asset ++ recipient ++ amount ++ salt)

Validated against the committed fixtures (test/fixtures/zk_vectors.json, generated by cargo test -p nomad-types --test zk_vectors) so an encoding drift fails here rather than as an unverifiable proof. All nine cases reproduce both preimage and digest.

Escrow address ordering

The commitment binds the escrow address, which depends on how many approvals precede deployment, and that count comes from deposit_by_asset in the very quote the commitment must accompany. Resolved by taking an unsigned preview first, then predicting from nonce + offset (0 on the atomic path, one per approval bucket otherwise). Getting this wrong is silent: the quote and deployment both succeed and only the proof fails, so a failed nonce read is fatal rather than degraded.

No Signal schema change

The enclave reads instanceDomain and requestId from the escrow and rederives the salt from the scalar it already receives sealed, so nothing new crosses the envelope.

Notes

  • Encoded against nomad feat/zk-escrow-wiring; its preimage is byte-identical to feat/zk-commitment-encoders and both pin RELATION_VERSION = 2. The API side was read from the pr-101 checkout.
  • Single-row only (rowIndex: 0), matching ZK_ROW_INDEX on the node.
  • 134/134 tests pass; typecheck clean.

An ERC-20 escrow now settles against a hiding commitment rather than stored
transfer details, so the API requires an intent it cannot derive: the salt comes
from the per-escrow blinding scalar the depositor generates.

Adds byte-exact encoders mirroring nomad-types::zk, validated against the
committed vectors, and threads the intent through prepareTransfer. The
commitment binds the escrow address, so the deployment nonce is resolved from an
unsigned preview first: deployment follows its approvals, and an atomic batch
spends one nonce for the whole sequence.

No Signal schema change is needed. The enclave reads the domain and request id
from the escrow and rederives the salt from the scalar it already receives.
…ck API

The mock assumed every request carried a sender and blinded signers, so the
unsigned preview the intent flow needs failed on a missing field. Mirrors the
API contract: no sender means no signers and no deployable fields, and an
ERC-20 quote requires an intent that other kinds reject.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant