Context
The original PR #52 introduced a canton-snap.prepared-transaction.v1 envelope contract on canton_signHash: the snap re-derived the multihash from a canonical-JSON envelope, verified it against the hash provided by the dApp, displayed the envelope fields in the confirmation dialog, and only then signed sha256(transactionHash). The intent was to remove blind hash signing — the user sees and the snap cryptographically verifies what gets authorized.
The check was reverted in commit 3fff26a (this branch, after merge) because no released canton-middleware build emits the envelope, so the strict gate broke every transfer ("Middleware did not return a secure prepared_transaction envelope; update canton-middleware before using snap signing").
This issue tracks reintroducing the envelope verification once middleware can produce it. Pair with: ChainSafe/canton-middleware — Emit prepared_transaction envelope in /transfer/prepare and /transfer/incoming/{id}/prepare.
Scope (snap-side)
Reinstate the verifier roughly as it appeared at commit 5fe8bc4 (file references are to that commit):
packages/snap/src/constants.ts — restore PREPARED_TRANSACTION_SCHEMA = "canton-snap.prepared-transaction.v1".
packages/snap/src/types.ts — restore the PreparedTransaction interface and change SignHashParams to { preparedTransaction; keyIndex? }.
packages/snap/src/validation.ts — restore parsePreparedTransaction, canonicalJson, buildPreparedHashInput, validatePreparedDetails, buildDialogDetails, sha256Multihash, the MAX_PREPARED_* bounds, and the inline canonical-JSON algorithm spec. Remove parseSignHash.
packages/snap/src/index.ts — handleSignHash parses the envelope, signs sha256(transactionHash) (not hashBytes directly), and feeds the envelope fields into the dialog.
packages/snap/src/dialogs.ts — restore the "verified" copy + per-detail line rendering.
packages/snap/test/validation.test.ts and test/index.test.js — restore envelope round-trip tests (mismatch, schema, oversize, non-ASCII).
Canonical-JSON spec (must match middleware byte-for-byte)
- ECMA-262
JSON.stringify for strings, finite numbers, booleans, null.
- Reject
NaN / ±Infinity.
- Object keys sorted by codepoint (
Array#sort).
- No whitespace; no trailing commas.
- Literal UTF-8 for non-ASCII (no
\\u escaping required, no HTML escaping of <>&).
- Whitelisted top-level fields:
schema, operation, tokenSymbol, amount, recipient?, sender?, network?, transferId?, expiresAt?, partyId?, details? (Record<string,string>).
transactionHash MUST equal 0x1220 || sha256(canonicalJson(envelope)). Bounds: ≤20 details entries; every string ≤200 chars; canonical envelope ≤64 KiB.
Acceptance criteria
Out of scope
- Schema versioning / negotiation. v1 is the only supported schema; the snap should reject anything else outright.
- Middleware-side work (separate issue in canton-middleware repo).
Dependencies
Blocked by: canton-middleware emitting the prepared_transaction envelope.
Context
The original PR #52 introduced a
canton-snap.prepared-transaction.v1envelope contract oncanton_signHash: the snap re-derived the multihash from a canonical-JSON envelope, verified it against the hash provided by the dApp, displayed the envelope fields in the confirmation dialog, and only then signedsha256(transactionHash). The intent was to remove blind hash signing — the user sees and the snap cryptographically verifies what gets authorized.The check was reverted in commit
3fff26a(this branch, after merge) because no releasedcanton-middlewarebuild emits the envelope, so the strict gate broke every transfer ("Middleware did not return a secure prepared_transaction envelope; update canton-middleware before using snap signing").This issue tracks reintroducing the envelope verification once middleware can produce it. Pair with: ChainSafe/canton-middleware — Emit prepared_transaction envelope in /transfer/prepare and /transfer/incoming/{id}/prepare.
Scope (snap-side)
Reinstate the verifier roughly as it appeared at commit
5fe8bc4(file references are to that commit):packages/snap/src/constants.ts— restorePREPARED_TRANSACTION_SCHEMA = "canton-snap.prepared-transaction.v1".packages/snap/src/types.ts— restore thePreparedTransactioninterface and changeSignHashParamsto{ preparedTransaction; keyIndex? }.packages/snap/src/validation.ts— restoreparsePreparedTransaction,canonicalJson,buildPreparedHashInput,validatePreparedDetails,buildDialogDetails,sha256Multihash, theMAX_PREPARED_*bounds, and the inline canonical-JSON algorithm spec. RemoveparseSignHash.packages/snap/src/index.ts—handleSignHashparses the envelope, signssha256(transactionHash)(nothashBytesdirectly), and feeds the envelope fields into the dialog.packages/snap/src/dialogs.ts— restore the "verified" copy + per-detail line rendering.packages/snap/test/validation.test.tsandtest/index.test.js— restore envelope round-trip tests (mismatch, schema, oversize, non-ASCII).Canonical-JSON spec (must match middleware byte-for-byte)
JSON.stringifyfor strings, finite numbers, booleans,null.NaN/±Infinity.Array#sort).\\uescaping required, no HTML escaping of<>&).schema,operation,tokenSymbol,amount,recipient?,sender?,network?,transferId?,expiresAt?,partyId?,details?(Record<string,string>).transactionHashMUST equal0x1220 || sha256(canonicalJson(envelope)). Bounds: ≤20 details entries; every string ≤200 chars; canonical envelope ≤64 KiB.Acceptance criteria
canton_signHashrejects calls withoutpreparedTransaction(no fallback to raw hash).\"preparedTransaction.transactionHash does not match canonical transaction data\".operation / tokenSymbol / amount / recipient / senderanddetails.*from the verified envelope, not from a separate metadata param.data.prepared_transactionand pass it through tosignHash.Out of scope
Dependencies
Blocked by: canton-middleware emitting the
prepared_transactionenvelope.