@sig-net/midnight 0.24.0-rc.2 → 0.24.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +26 -21
  2. package/dist/compact-descriptors.d.ts +23 -8
  3. package/dist/compact-descriptors.d.ts.map +1 -1
  4. package/dist/compact-descriptors.js +34 -31
  5. package/dist/compact-descriptors.js.map +1 -1
  6. package/dist/constants.d.ts +1 -1
  7. package/dist/constants.js +1 -1
  8. package/dist/ecdsa-attestation.d.ts +6 -6
  9. package/dist/ecdsa-attestation.d.ts.map +1 -1
  10. package/dist/ecdsa-attestation.js +7 -5
  11. package/dist/ecdsa-attestation.js.map +1 -1
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +3 -3
  15. package/dist/index.js.map +1 -1
  16. package/dist/managed/compiler/contract-info.json +1580 -122
  17. package/dist/managed/compiler/contract-manifest.json +8 -8
  18. package/dist/managed/contract/index.d.ts +352 -0
  19. package/dist/managed/contract/index.js +1042 -208
  20. package/dist/managed/contract/index.js.map +1 -1
  21. package/dist/mpc-output-cache.js +2 -2
  22. package/dist/mpc-output-cache.js.map +1 -1
  23. package/dist/signature-requests-state-reader.js +3 -3
  24. package/dist/signature-requests-state-reader.js.map +1 -1
  25. package/dist/signet-contract-events.d.ts +5 -5
  26. package/dist/signet-contract-events.d.ts.map +1 -1
  27. package/dist/signet-evtype2tx-record-decoding.d.ts.map +1 -1
  28. package/dist/signet-evtype2tx-record-decoding.js +5 -4
  29. package/dist/signet-evtype2tx-record-decoding.js.map +1 -1
  30. package/dist/signet-evtype2tx-requests.d.ts +17 -12
  31. package/dist/signet-evtype2tx-requests.d.ts.map +1 -1
  32. package/dist/signet-evtype2tx-requests.js +92 -16
  33. package/dist/signet-evtype2tx-requests.js.map +1 -1
  34. package/dist/signet-request-id.d.ts +5 -3
  35. package/dist/signet-request-id.d.ts.map +1 -1
  36. package/dist/signet-request-id.js +20 -12
  37. package/dist/signet-request-id.js.map +1 -1
  38. package/dist/signet-requests.d.ts +30 -34
  39. package/dist/signet-requests.d.ts.map +1 -1
  40. package/dist/signet-requests.js +15 -23
  41. package/dist/signet-requests.js.map +1 -1
  42. package/package.json +2 -2
  43. package/src/Signet.compact +204 -92
  44. package/src/circuits.compact +61 -9
@@ -18,17 +18,22 @@
18
18
  // import "@sig-net/midnight/src/Signet";
19
19
  //
20
20
  // Key types at a glance (each declaration below carries the full contract):
21
- // - SignBidirectionalEvent: a client contract's structured request for a signature on a transaction.
22
- // - RequestId: 32-byte id of a request, the transientHash of its RequestIdPreimage (the record
23
- // minus the serialisation schemas).
24
- // - SignBidirectionalEventMap: the RequestId -> SignBidirectionalEvent map a client contract
21
+ // - SignBidirectionalEventV1: a client contract's structured request for a signature on a transaction.
22
+ // - RequestId: 32-byte id derived by calculateRequestIdV1.
23
+ // - SignBidirectionalEventMapV1: the RequestId -> SignBidirectionalEventV1 map a client contract
25
24
  // declares in its own ledger for the MPC to read requests from.
26
25
  // - SignetSigner: the contract interface client contracts call to notify the Signet singleton.
27
- // - SignBidirectionalEventNotification: the versioned notification delivered through that call.
28
- // - SignatureRespondedEvent: the MPC's posted signature answering a request.
29
- // - RespondBidirectionalEvent: the MPC's posted attestation that the foreign chain executed the
26
+ // - SignBidirectionalEventNotification: the notification delivered through that call, versioned by
27
+ // the version byte it carries.
28
+ // - SignatureRespondedEventV1: the MPC's posted signature answering a request.
29
+ // - RespondBidirectionalEventV1: the MPC's posted attestation that the foreign chain executed the
30
30
  // signed transaction.
31
31
  //
32
+ // The V1 suffix marks the wire and hash-domain artefacts of protocol version 1 (the structs the
33
+ // ledger stores and the singleton emits, and the circuits that hash or verify them). Vocabulary
34
+ // shared across versions (RequestId, OutputKind, Signature, the enums, the transaction parameter
35
+ // structs, which TxParamType tags) and the version-tagged notification carry no suffix.
36
+ //
32
37
  // These key types are best understood within the sign bidirectional flow that they enable:
33
38
  // 1. Client contract stores a SignBidirectionalEvent in a SignBidirectionalEventMap in its own ledger
34
39
  // (against the calculated RequestId) and requests a signature from the MPC via a cross contract call
@@ -60,6 +65,16 @@
60
65
  module Signet {
61
66
  import CompactStandardLibrary;
62
67
 
68
+ // Hash-domain indices are protocol u8 values. Append variants only.
69
+ export enum HashDomain {
70
+ requestId,
71
+ attestationDigest,
72
+ evmType2TxHeader,
73
+ evmType2TxWord,
74
+ evmType2TxAccessEntry,
75
+ evmType2TxStorageKey,
76
+ }
77
+
63
78
  // ---- SignBidirectionalEvent ------------------------------------------------------------------------
64
79
 
65
80
  // Structured request for a signature on a transaction. Generically configured by client contracts
@@ -67,10 +82,8 @@ module Signet {
67
82
  //
68
83
  // KEY: texual order of the fields in this structure must remain fixed as is as their order
69
84
  // is relied upon for consistent hashing and decoding from ledger state.
70
- export struct SignBidirectionalEvent<TxParams, #LenOutputDeserialization, #LenRespondSerialization> {
71
- // Address of the client contract that stores and notifies of this SignBidirectionalEvent.
72
- // Set with kernel.self().
73
- sender: ContractAddress;
85
+ export struct SignBidirectionalEventV1<TxParams, #LenOutputDeserialization, #LenRespondSerialization> {
86
+ // ---- RequestIdPreimageV1 fields: hashed into the request id, in this order ----
74
87
 
75
88
  // MPC Key Derivation Parameters.
76
89
  // Every key the MPC signs with is scoped by the requesting contract:
@@ -83,13 +96,13 @@ module Signet {
83
96
  // CRITICAL: the MPC renders the path as hex::encode(path) for key derivation (lowercase hex
84
97
  // of the full 32 bytes, no trimming, no 0x prefix). The sender is always part
85
98
  // of the derivation, so no contract can reach another contract's derived keys.
86
- keyVersion: Uint<8>; // MPC root-key version to derive from. Currently 1.
87
- path: Bytes<32>; // Key derivation path: 32 opaque bytes, client contract's choice.
99
+ keyVersion: Uint<8>; // MPC root-key version to derive from. Currently 1.
100
+ sender: ContractAddress; // Address of the client contract that stores and notifies of this
101
+ // request. Set with kernel.self().
102
+ path: Bytes<32>; // Key derivation path: 32 opaque bytes, client contract's choice.
88
103
 
89
104
  // MPC Parameters.
90
105
  algo: MPCSignatureAlgorithm; // Signature algorithm scheme, e.g. ecdsa
91
- signatureDest: MPCDestination; // Destination field reserved for future use. Set to unused.
92
- params: Bytes<64>; // Extra MPC parameters reserved for future use. Zero-fill.
93
106
 
94
107
  // Specific destination chain Transaction parameters.
95
108
  txParamType: TxParamType; // Off-chain tag indicating what type txParams is set to.
@@ -99,6 +112,11 @@ module Signet {
99
112
  // (mainnet, Sepolia or a local anvil) uses ethereumCaip2Id():
100
113
  // the network itself is txParams' chainId.
101
114
 
115
+ // ---- Protocol-only fields: outside the request id ----
116
+
117
+ signatureDest: MPCDestination; // Destination field reserved for future use. Set to unused.
118
+ params: Bytes<64>; // Extra MPC parameters reserved for future use. Zero-fill.
119
+
102
120
  // Serialisation Schemas.
103
121
  // outputDeserializationSchema tells the MPC how to deserialise the destination chain's
104
122
  // execution output (destination chain -> MPC). respondSerializationSchema tells the MPC
@@ -131,36 +149,39 @@ module Signet {
131
149
  reserved, // Reserved for future use. Exists to keep enum >= 2 (proof server bug).
132
150
  }
133
151
 
134
- // Construction helper for the canonical Signet SignBidirectionalEvent.
135
- export pure circuit constructSignBidirectionalEvent<TxParams, #LenOutputDeserialization, #LenRespondSerialization>(
136
- sender: ContractAddress,
152
+ // Construction helper for the canonical Signet SignBidirectionalEventV1.
153
+ export pure circuit constructSignBidirectionalEventV1<TxParams, #LenOutputDeserialization, #LenRespondSerialization>(
137
154
  keyVersion: Uint<8>,
155
+ sender: ContractAddress,
138
156
  path: Bytes<32>,
139
157
  algo: MPCSignatureAlgorithm,
140
- signatureDest: MPCDestination,
141
- params: Bytes<64>,
142
158
  txParamType: TxParamType,
143
159
  txParams: TxParams,
144
160
  executionDest: Bytes<32>,
161
+ signatureDest: MPCDestination,
162
+ params: Bytes<64>,
145
163
  outputDeserializationSchema: Bytes<LenOutputDeserialization>,
146
164
  respondSerializationSchema: Bytes<LenRespondSerialization>,
147
- ): SignBidirectionalEvent<TxParams, LenOutputDeserialization, LenRespondSerialization> {
165
+ ): SignBidirectionalEventV1<TxParams, LenOutputDeserialization, LenRespondSerialization> {
148
166
  assert(keyVersion >= 1, "keyVersion must be >= 1");
167
+ assert(signatureDest == MPCDestination.unused, "signatureDest must be unused");
168
+ assert(params == default<Bytes<64>>, "params must be zero");
149
169
  // The protocol reserves the "midnight response key" path for the MPC's
150
- // attestation key of this contract (see RespondBidirectionalEvent), and the
170
+ // attestation key of this contract (see RespondBidirectionalEventV1), and the
151
171
  // MPC refuses a request that names it.
152
172
  assert(path != pad(32, "midnight response key"), "path is reserved for the MPC response key");
153
173
 
154
- return SignBidirectionalEvent<TxParams, LenOutputDeserialization, LenRespondSerialization> {
155
- sender,
174
+ // Shorthand struct fields bind positionally: this list follows the declaration order.
175
+ return SignBidirectionalEventV1<TxParams, LenOutputDeserialization, LenRespondSerialization> {
156
176
  keyVersion,
177
+ sender,
157
178
  path,
158
179
  algo,
159
- signatureDest,
160
- params,
161
180
  txParamType,
162
181
  txParams,
163
182
  executionDest,
183
+ signatureDest,
184
+ params,
164
185
  outputDeserializationSchema,
165
186
  respondSerializationSchema,
166
187
  };
@@ -168,54 +189,35 @@ module Signet {
168
189
 
169
190
  // ---- SignBidirectionalEvent RequestId --------------------------------------------------------------
170
191
 
171
- // 32 byte chain agnostic nominal id of a SignBidirectionalEvent, calculated as the
172
- // transientHash of its RequestIdPreimage (see calculateRequestId).
192
+ // 32-byte chain-agnostic nominal id derived by calculateRequestIdV1.
173
193
  export new type RequestId = Bytes<32>;
174
194
 
175
- // The fields of a SignBidirectionalEvent that name one execution: the requesting contract,
176
- // the signing key (version, path, algorithm), the transaction and its execution destination.
177
- // The serialisation schemas and the reserved MPC parameters (signatureDest, params) stay
178
- // out, so two requests for one transaction cannot both be outstanding whatever schemas
179
- // they declare, and the id does not depend on the schemas' declared widths either.
195
+ // The fields of a SignBidirectionalEventV1 that name one execution: the signing key
196
+ // (keyVersion, path, algo), the requesting contract, the transaction and its execution
197
+ // destination. Fields the execution does not depend on (the serialisation schemas and
198
+ // the reserved MPC parameters signatureDest and params) stay out. The transaction
199
+ // enters as a digest computed per TxParamType over its USED entries only
200
+ // (calculateEvmType2TxParamsDigestV1 for evmType2), so one execution has one id
201
+ // whatever capacities the requester compiled with and whatever sits in unused slots.
180
202
  //
181
- // KEY: the field order follows SignBidirectionalEvent's and is relied upon for consistent
182
- // hashing. The TS twin (requestIdPreimageDescriptorWith in signet-requests.ts)
183
- // mirrors it field by field.
184
- export struct RequestIdPreimage<TxParams> {
185
- sender: ContractAddress;
203
+ // KEY: the field order follows SignBidirectionalEventV1's and is relied upon for consistent
204
+ // hashing. The TS twin (requestIdPreimageDescriptor in signet-requests.ts) mirrors it
205
+ // field by field.
206
+ export struct RequestIdPreimageV1 {
186
207
  keyVersion: Uint<8>;
208
+ sender: ContractAddress;
187
209
  path: Bytes<32>;
188
210
  algo: MPCSignatureAlgorithm;
189
211
  txParamType: TxParamType;
190
- txParams: TxParams;
212
+ txParamsDigest: Bytes<32>;
191
213
  executionDest: Bytes<32>;
192
214
  }
193
215
 
194
- // transientHash (Poseidon over the preimage's field-aligned representation) upgraded
195
- // to Bytes<32>: the id commits to every preimage field, including the sender, and
196
- // carries no domain-separation tag. Off-chain twins must reproduce the field-aligned
197
- // layout exactly.
198
- //
199
- // Two properties follow from the field-aligned representation and matter to anyone
200
- // reasoning about these ids:
201
- // - byte 31 is always zero. upgradeFromTransient writes the field element's 31
202
- // little-endian bytes into bytes 0..30, so an id carries ~248 bits of entropy.
203
- // - a Bytes<N> field packs into ceil(N/31) field elements, so trailing zero bytes
204
- // within a chunk do not change the hash. Widths here are fixed at compile time
205
- // and every id commits to its sender, so requests cannot collide in practice.
206
- export pure circuit calculateRequestId<TxParams, #LenOutputDeserialization, #LenRespondSerialization>(
207
- request: SignBidirectionalEvent<TxParams, LenOutputDeserialization, LenRespondSerialization>
208
- ): RequestId {
209
- const preimage = RequestIdPreimage<TxParams> {
210
- sender: request.sender,
211
- keyVersion: request.keyVersion,
212
- path: request.path,
213
- algo: request.algo,
214
- txParamType: request.txParamType,
215
- txParams: request.txParams,
216
- executionDest: request.executionDest,
217
- };
218
- return upgradeFromTransient(transientHash<RequestIdPreimage<TxParams>>(preimage)) as RequestId;
216
+ // Off-chain recomputation must prefix the preimage with HashDomain.requestId
217
+ // and reproduce its field-aligned layout exactly. A ledger-era hash change
218
+ // requires draining pending requests before the fork.
219
+ export pure circuit calculateRequestIdV1(preimage: RequestIdPreimageV1): RequestId {
220
+ return upgradeFromTransient(transientHash<[HashDomain, RequestIdPreimageV1]>([HashDomain.requestId, preimage])) as RequestId;
219
221
  }
220
222
 
221
223
  // ---- Client Contract: SignBidirectionalEvent Map ---------------------------------------------------
@@ -224,7 +226,7 @@ module Signet {
224
226
  // in their ledger. Client contract tells MPC field location of this map when making a cross
225
227
  // contract call, via SignetSigner, to the Signet singleton. The MPC looks here to find
226
228
  // SignBidirectionalEvents.
227
- export new type SignBidirectionalEventMap<TxParams, #LenOutputDeserialization, #LenRespondSerialization> = Map<RequestId, SignBidirectionalEvent<TxParams, LenOutputDeserialization, LenRespondSerialization>>;
229
+ export new type SignBidirectionalEventMapV1<TxParams, #LenOutputDeserialization, #LenRespondSerialization> = Map<RequestId, SignBidirectionalEventV1<TxParams, LenOutputDeserialization, LenRespondSerialization>>;
228
230
 
229
231
  // ---- Cross Contract SignBidirectionalEventNotification ---------------------------------------------
230
232
 
@@ -233,7 +235,7 @@ module Signet {
233
235
  export contract SignetSigner {
234
236
  // Called by client contracts to notify the MPC of a SignBidirectionalEvent.
235
237
  circuit signBidirectional(
236
- // Canonical id of the SignBidirectionalEvent (calculate with calculateRequestId).
238
+ // Canonical id of the SignBidirectionalEvent (calculateEvmType2RequestIdV1 for evmType2).
237
239
  // Disclosed in the emitted notification event: the MPC looks this id up in the
238
240
  // caller's own request map.
239
241
  requestId: RequestId,
@@ -256,7 +258,7 @@ module Signet {
256
258
  // Construction helper for the V1 SignBidirectionalEventNotification.
257
259
  //
258
260
  // The payload is a pointer for the MPC: the contract that holds the
259
- // SignBidirectionalEventMap (callerAddress) and the position of that map in
261
+ // SignBidirectionalEventMapV1 (callerAddress) and the position of that map in
260
262
  // the contract's raw ledger state tree (requestsPathDepth + requestsPath).
261
263
  //
262
264
  // Read the path from your contract's compiled artifacts, never derive it by
@@ -272,7 +274,7 @@ module Signet {
272
274
  // Number of meaningful entries in requestsPath: 1 for a flat (15 or fewer
273
275
  // field) contract, one more per chunk level after that (max 4).
274
276
  requestsPathDepth: Uint<8>,
275
- // Resolved ledger-tree path of the caller's SignBidirectionalEventMap, from
277
+ // Resolved ledger-tree path of the caller's SignBidirectionalEventMapV1, from
276
278
  // its contract-info.json "index". Entries at and beyond requestsPathDepth
277
279
  // are zero.
278
280
  requestsPath: Vector<4, Uint<8>>
@@ -296,10 +298,10 @@ module Signet {
296
298
  y: Bytes<32>;
297
299
  }
298
300
 
299
- // The MPC's canonical ECDSA signature: bigR the full nonce point, s the
300
- // signature scalar, recoveryId the parity of R.y (0 or 1). The same Signature
301
- // vocabulary as the EVM and Solana signer contracts and the MPC's own record.
302
- // Stored bytes are BIG-endian (SEC1) throughout.
301
+ // recoveryId (0 or 1) recovers the signer with the supplied s. Converting
302
+ // high-s to low-s requires replacing s with n - s and flipping recoveryId,
303
+ // where n is the curve's group order, even when bigR is kept unchanged.
304
+ // Point coordinates and s are stored as BIG-endian SEC1 bytes.
303
305
  export struct Signature {
304
306
  bigR: AffinePoint;
305
307
  s: Bytes<32>;
@@ -309,7 +311,7 @@ module Signet {
309
311
  // Counterpart to SignBidirectionalEvent: the MPC generated signature a request asked for.
310
312
  // Posted by the MPC through the Signet singleton for clients to extract and build a
311
313
  // signed transaction from.
312
- export struct SignatureRespondedEvent {
314
+ export struct SignatureRespondedEventV1 {
313
315
  // The request this signature answers. Routing data: the singleton emits it
314
316
  // unverified, and a reader proves the signature against the request's own key.
315
317
  requestId: RequestId;
@@ -320,7 +322,7 @@ module Signet {
320
322
  // RespondBidirectionalEvent: the MPC's attestation of a foreign execution, the
321
323
  // final step of the sign bidirectional flow.
322
324
  //
323
- // attestationDigest = upgradeFromTransient(transientHash([requestId, blockHeight, outputKind, serializedOutputLength, serializedOutput]))
325
+ // attestationDigest = calculateSignetAttestationDigestV1(requestId, blockHeight, outputKind, serializedOutput)
324
326
  // signature = signSECP256k1(mpcResponseKey, attestationDigest)
325
327
  //
326
328
  // mpcResponseKey is derived like request signing keys, with the path set to the
@@ -329,10 +331,10 @@ module Signet {
329
331
  // mpcResponseKey = f(mpcRootKey[keyVersion], caip2ChainId, sender, "midnight response key")
330
332
  //
331
333
  // The output travels off chain and the digest is recomputed at verification
332
- // time (verifyRespondBidirectionalEvent). The signature is the ONLY thing that
334
+ // time (verifyRespondBidirectionalEventV1). The signature is the ONLY thing that
333
335
  // distinguishes a genuine post from garbage: the singleton emits every post
334
336
  // unverified.
335
- export struct RespondBidirectionalEvent {
337
+ export struct RespondBidirectionalEventV1 {
336
338
  // The request this attestation answers. Signed into the digest: a verified
337
339
  // post settles exactly the request it names, so a client consumes this id
338
340
  // and never one taken from elsewhere.
@@ -347,7 +349,7 @@ module Signet {
347
349
  // or unviable execution), so a client holding no output yet knows which
348
350
  // width to settle at.
349
351
  serializedOutputLength: Uint<64>;
350
- // The attestation digest the signature is over (calculateSignetAttestationDigest),
352
+ // The attestation digest the signature is over (calculateSignetAttestationDigestV1),
351
353
  // so a reader holding no output can check the signature. The verifying
352
354
  // circuit recomputes it from the output and never reads this copy.
353
355
  digest: Bytes<32>;
@@ -365,24 +367,24 @@ module Signet {
365
367
  }
366
368
 
367
369
  // The 32-byte attestation digest of a remote execution output:
368
- // attestationDigest = upgradeFromTransient(transientHash([requestId, blockHeight, outputKind, serializedOutputLength, serializedOutput]))
370
+ // attestationDigest = upgradeFromTransient(transientHash([HashDomain.attestationDigest, requestId, blockHeight, outputKind, serializedOutputLength, serializedOutput]))
369
371
  // blockHeight is the height of the finalised destination block holding the
370
372
  // transaction the attestation describes, in the destination chain's own
371
373
  // numbering (a slot on Solana).
372
374
  // Off-chain code uses the TypeScript twin:
373
- // import { calculateSignetAttestationDigest } from "@sig-net/midnight";
374
- export pure circuit calculateSignetAttestationDigest<#serializedOutputLength>(
375
+ // import { calculateSignetAttestationDigest } from "@sig-net/midnight/testing";
376
+ export pure circuit calculateSignetAttestationDigestV1<#serializedOutputLength>(
375
377
  requestId: RequestId,
376
378
  blockHeight: Uint<64>,
377
379
  outputKind: OutputKind,
378
380
  serializedOutput: Bytes<serializedOutputLength>,
379
381
  ): Bytes<32> {
380
- return upgradeFromTransient(transientHash<[RequestId, Uint<64>, OutputKind, Uint<64>, Bytes<serializedOutputLength>]>([requestId, blockHeight, outputKind, serializedOutputLength, serializedOutput]));
382
+ return upgradeFromTransient(transientHash<[HashDomain, RequestId, Uint<64>, OutputKind, Uint<64>, Bytes<serializedOutputLength>]>([HashDomain.attestationDigest, requestId, blockHeight, outputKind, serializedOutputLength, serializedOutput]));
381
383
  }
382
384
 
383
385
  // Verifies a RespondBidirectionalEvent against its execution output and MPC
384
- // response key: recomputes the attestation digest over the event's block
385
- // height and output kind and checks the ECDSA signature over it. Client
386
+ // response key: recomputes the digest over the request id, block height,
387
+ // output kind, output length and caller's output, then verifies its signature. Client
386
388
  // contracts run this before consuming outputs, as the singleton emits
387
389
  // responses unverified. The caller supplies
388
390
  // serializedOutput at its exact unpadded width, and the event record with
@@ -397,12 +399,12 @@ module Signet {
397
399
  // that reduces to 0 mod the curve order aborts the
398
400
  // circuit rather than returning false, so sift candidate posts off chain
399
401
  // first (verifyRespondBidirectionalSignature).
400
- export pure circuit verifyRespondBidirectionalEvent<#serializedOutputLength>(
402
+ export pure circuit verifyRespondBidirectionalEventV1<#serializedOutputLength>(
401
403
  serializedOutput: Bytes<serializedOutputLength>,
402
- respondBidirectionalEvent: RespondBidirectionalEvent,
404
+ respondBidirectionalEvent: RespondBidirectionalEventV1,
403
405
  mpcResponseKey: Secp256k1Point
404
406
  ): Boolean {
405
- const digest = calculateSignetAttestationDigest<serializedOutputLength>(
407
+ const digest = calculateSignetAttestationDigestV1<serializedOutputLength>(
406
408
  respondBidirectionalEvent.requestId,
407
409
  respondBidirectionalEvent.blockHeight,
408
410
  respondBidirectionalEvent.outputKind,
@@ -439,10 +441,11 @@ module Signet {
439
441
  //
440
442
  // Every variable-length part is a compile-time CAPACITY (a generic size
441
443
  // parameter the requesting contract fixes) plus a runtime count of how many
442
- // leading slots are real. Unused capacity is hashed as part of the record
443
- // (zero-fill) but never reaches the assembled transaction. Field order is
444
- // the canonical EIP-1559 RLP order and the request-id hash order: part of
445
- // the wire contract, each count directly before the slot it bounds.
444
+ // leading slots are real. Unused capacity sits in the ledger record but
445
+ // reaches neither the request id (calculateEvmType2TxParamsDigestV1 walks
446
+ // the used slots only) nor the assembled transaction. Field order is the
447
+ // canonical EIP-1559 RLP order and the digest order: part of the wire
448
+ // contract, each count directly before the slot it bounds.
446
449
  //
447
450
  // KEY: texual order of the fields in this structure must remain fixed as is as their order
448
451
  // is relied upon for consistent hashing and decoding from ledger state.
@@ -464,7 +467,7 @@ module Signet {
464
467
  // raw 32-byte values, copied verbatim into the RLP encoding.
465
468
  //
466
469
  // KEY: texual order of the fields in this structure must remain fixed as is as their order
467
- // is relied upon for consistent hashing and decoding from ledger state.
470
+ // is relied upon for consistent hashing and decoding from ledger state.
468
471
  export struct EvmAccessListEntry<#maxStorageKeys> {
469
472
  address: Bytes<20>;
470
473
  storageKeyCount: Uint<8>; // how many leading storageKeys are real
@@ -479,13 +482,122 @@ module Signet {
479
482
  // canonical ABI words for common types.
480
483
  //
481
484
  // KEY: texual order of the fields in this structure must remain fixed as is as their order
482
- // is relied upon for consistent hashing and decoding from ledger state.
485
+ // is relied upon for consistent hashing and decoding from ledger state.
483
486
  export struct EvmCalldata<#maxWords> {
484
487
  selector: Bytes<4>; // the literal first 4 calldata bytes
485
488
  noWords: Uint<16>; // No. of used words in the words list
486
489
  words: Vector<maxWords, Bytes<32>>; // canonical ABI words (big-endian)
487
490
  }
488
491
 
492
+ // Digest of an EvmType2TxParams over the bytes that reach the signed transaction:
493
+ // the scalar fields, then the calldata words, access-list entries and storage keys
494
+ // up to their declared counts, each count hashed beside its entries so the encoding
495
+ // commits to its length. Slots past a count are skipped, and an absent calldata
496
+ // contributes only its absence, so one transaction has one digest whatever
497
+ // capacities the requester compiled its struct with and whatever bytes sit in
498
+ // unused slots. The counts must fit their capacities. Off-chain code uses the
499
+ // TypeScript twin calculateEvmType2TxParamsDigest in @sig-net/midnight.
500
+ //
501
+ // The slot walks are folds carrying the slot index and a running hash: a `for`
502
+ // over `0..n` has no index type at n = 0, and a zero capacity is the common case.
503
+ export pure circuit calculateEvmType2TxParamsDigestV1<#maxCalldataWords, #maxAccessListEntries, #maxStorageKeysPerEntry>(
504
+ txParams: EvmType2TxParams<maxCalldataWords, maxAccessListEntries, maxStorageKeysPerEntry>
505
+ ): Bytes<32> {
506
+ // An absent calldata contributes is_some = false, a zero selector and a zero word count.
507
+ const calldata = txParams.calldata;
508
+ const noWords = calldata.is_some ? calldata.value.noWords : 0 as Uint<16>;
509
+ const selector = calldata.is_some ? calldata.value.selector : default<Bytes<4>>;
510
+
511
+ assert(noWords <= maxCalldataWords, "calldata noWords exceeds capacity");
512
+ assert(txParams.accessListEntryCount <= maxAccessListEntries, "accessListEntryCount exceeds capacity");
513
+
514
+ // For every used access-list entry, confirm its storageKeyCount fits
515
+ // maxStorageKeysPerEntry. A Compact loop needs a compile-time bound, so the walk
516
+ // is a fold over every slot carrying [slot index, all fit so far], and the index
517
+ // picks out the used entries.
518
+ const keyCountsFit = fold(
519
+ (acc: [Uint<8>, Boolean], entry: EvmAccessListEntry<maxStorageKeysPerEntry>): [Uint<8>, Boolean] =>
520
+ [(acc[0] + 1) as Uint<8>, acc[1] && (acc[0] >= txParams.accessListEntryCount || entry.storageKeyCount <= maxStorageKeysPerEntry)],
521
+ [0 as Uint<8>, true],
522
+ txParams.accessList
523
+ );
524
+ assert(keyCountsFit[1], "a used entry's storageKeyCount exceeds capacity");
525
+
526
+ // The scalar fields and the counts, hashed as one field-aligned tuple.
527
+ const head = transientHash<[HashDomain, Uint<64>, Uint<64>, Uint<128>, Uint<128>, Uint<64>, Bytes<20>, Uint<128>, Boolean, Bytes<4>, Uint<16>, Uint<8>]>([
528
+ HashDomain.evmType2TxHeader,
529
+ txParams.chainId,
530
+ txParams.nonce,
531
+ txParams.maxPriorityFeePerGas,
532
+ txParams.maxFeePerGas,
533
+ txParams.gasLimit,
534
+ txParams.to,
535
+ txParams.value,
536
+ calldata.is_some,
537
+ selector,
538
+ noWords,
539
+ txParams.accessListEntryCount
540
+ ]);
541
+
542
+ // The calldata words below noWords, chained onto the head one word at a time.
543
+ // calldata.value is read whether or not the calldata is present: with noWords
544
+ // at zero every step passes the head through.
545
+ const wordsScan = fold(
546
+ (acc: [Uint<16>, Field], word: Bytes<32>): [Uint<16>, Field] =>
547
+ [(acc[0] + 1) as Uint<16>, acc[0] < noWords ? transientHash<[HashDomain, Field, Bytes<32>]>([HashDomain.evmType2TxWord, acc[1], word]) : acc[1]],
548
+ [0 as Uint<16>, head],
549
+ calldata.value.words
550
+ );
551
+
552
+ // The access-list entries below accessListEntryCount, each chained on as its
553
+ // address, its key count and a digest of its storage keys below that count
554
+ // (the inner fold, started from a zero Field).
555
+ const listScan = fold(
556
+ (acc: [Uint<8>, Field], entry: EvmAccessListEntry<maxStorageKeysPerEntry>): [Uint<8>, Field] =>
557
+ [
558
+ (acc[0] + 1) as Uint<8>,
559
+ acc[0] < txParams.accessListEntryCount
560
+ ? transientHash<[HashDomain, Field, Bytes<20>, Uint<8>, Field]>([
561
+ HashDomain.evmType2TxAccessEntry,
562
+ acc[1],
563
+ entry.address,
564
+ entry.storageKeyCount,
565
+ fold(
566
+ (keyAcc: [Uint<8>, Field], key: Bytes<32>): [Uint<8>, Field] =>
567
+ [(keyAcc[0] + 1) as Uint<8>, keyAcc[0] < entry.storageKeyCount ? transientHash<[HashDomain, Field, Bytes<32>]>([HashDomain.evmType2TxStorageKey, keyAcc[1], key]) : keyAcc[1]],
568
+ [0 as Uint<8>, 0 as Field],
569
+ entry.storageKeys
570
+ )[1]
571
+ ])
572
+ : acc[1]
573
+ ],
574
+ [0 as Uint<8>, wordsScan[1]],
575
+ txParams.accessList
576
+ );
577
+
578
+ // The running hash is a Field. upgradeFromTransient writes it out as Bytes<32>.
579
+ return upgradeFromTransient(listScan[1]);
580
+ }
581
+
582
+ // The request id of an EVM type-2 request: its preimage with the transaction digested
583
+ // by calculateEvmType2TxParamsDigestV1. Off-chain code uses the TypeScript twin:
584
+ // import { calculateRequestId } from "@sig-net/midnight";
585
+ export pure circuit calculateEvmType2RequestIdV1<#maxCalldataWords, #maxAccessListEntries, #maxStorageKeysPerEntry, #LenOutputDeserialization, #LenRespondSerialization>(
586
+ request: SignBidirectionalEventV1<EvmType2TxParams<maxCalldataWords, maxAccessListEntries, maxStorageKeysPerEntry>, LenOutputDeserialization, LenRespondSerialization>
587
+ ): RequestId {
588
+ assert(request.txParamType == TxParamType.evmType2, "txParamType must tag txParams as evmType2");
589
+ const preimage = RequestIdPreimageV1 {
590
+ keyVersion: request.keyVersion,
591
+ sender: request.sender,
592
+ path: request.path,
593
+ algo: request.algo,
594
+ txParamType: request.txParamType,
595
+ txParamsDigest: calculateEvmType2TxParamsDigestV1<maxCalldataWords, maxAccessListEntries, maxStorageKeysPerEntry>(request.txParams),
596
+ executionDest: request.executionDest,
597
+ };
598
+ return calculateRequestIdV1(preimage);
599
+ }
600
+
489
601
  // ---- ABI Calldata Word Construction Utilities ------------------------------------------------------
490
602
  // Canonical ABI word builders and readers. Words MUST be stored in canonical
491
603
  // ABI form (big-endian, per the ABI spec). These utilities help construct
@@ -9,7 +9,9 @@ import CompactStandardLibrary;
9
9
  import "./Signet";
10
10
 
11
11
  export {
12
+ HashDomain,
12
13
  OutputKind,
14
+ calculateRequestIdV1,
13
15
  constructSignBidirectionalEventNotificationV1,
14
16
  ethereumCaip2Id,
15
17
  evmAddressAbiWord,
@@ -32,7 +34,7 @@ export pure circuit calculateSignetAttestationDigest0(
32
34
  outputKind: OutputKind,
33
35
  serializedOutput: Bytes<0>
34
36
  ): Bytes<32> {
35
- return calculateSignetAttestationDigest<0>(requestId, blockHeight, outputKind, serializedOutput);
37
+ return calculateSignetAttestationDigestV1<0>(requestId, blockHeight, outputKind, serializedOutput);
36
38
  }
37
39
 
38
40
  export pure circuit calculateSignetAttestationDigest1(
@@ -41,7 +43,7 @@ export pure circuit calculateSignetAttestationDigest1(
41
43
  outputKind: OutputKind,
42
44
  serializedOutput: Bytes<1>
43
45
  ): Bytes<32> {
44
- return calculateSignetAttestationDigest<1>(requestId, blockHeight, outputKind, serializedOutput);
46
+ return calculateSignetAttestationDigestV1<1>(requestId, blockHeight, outputKind, serializedOutput);
45
47
  }
46
48
 
47
49
  export pure circuit calculateSignetAttestationDigest2(
@@ -50,7 +52,7 @@ export pure circuit calculateSignetAttestationDigest2(
50
52
  outputKind: OutputKind,
51
53
  serializedOutput: Bytes<2>
52
54
  ): Bytes<32> {
53
- return calculateSignetAttestationDigest<2>(requestId, blockHeight, outputKind, serializedOutput);
55
+ return calculateSignetAttestationDigestV1<2>(requestId, blockHeight, outputKind, serializedOutput);
54
56
  }
55
57
 
56
58
  export pure circuit calculateSignetAttestationDigest32(
@@ -59,7 +61,7 @@ export pure circuit calculateSignetAttestationDigest32(
59
61
  outputKind: OutputKind,
60
62
  serializedOutput: Bytes<32>
61
63
  ): Bytes<32> {
62
- return calculateSignetAttestationDigest<32>(requestId, blockHeight, outputKind, serializedOutput);
64
+ return calculateSignetAttestationDigestV1<32>(requestId, blockHeight, outputKind, serializedOutput);
63
65
  }
64
66
 
65
67
  export pure circuit calculateSignetAttestationDigest100(
@@ -68,15 +70,47 @@ export pure circuit calculateSignetAttestationDigest100(
68
70
  outputKind: OutputKind,
69
71
  serializedOutput: Bytes<100>
70
72
  ): Bytes<32> {
71
- return calculateSignetAttestationDigest<100>(requestId, blockHeight, outputKind, serializedOutput);
73
+ return calculateSignetAttestationDigestV1<100>(requestId, blockHeight, outputKind, serializedOutput);
74
+ }
75
+
76
+ // ---- Fixed-width transaction digest oracles (tests only) --------------------
77
+ // Pin the TS twin calculateEvmType2TxParamsDigest against the compiled circuit
78
+ // at the caller contracts' <1, 0, 0> shape and at a shape with unused
79
+ // capacity in every dimension.
80
+
81
+ export pure circuit calculateEvmType2TxParamsDigestV1_1_0_0(
82
+ txParams: EvmType2TxParams<1, 0, 0>
83
+ ): Bytes<32> {
84
+ return calculateEvmType2TxParamsDigestV1<1, 0, 0>(txParams);
85
+ }
86
+
87
+ export pure circuit calculateEvmType2TxParamsDigestV1_2_1_2(
88
+ txParams: EvmType2TxParams<2, 1, 2>
89
+ ): Bytes<32> {
90
+ return calculateEvmType2TxParamsDigestV1<2, 1, 2>(txParams);
91
+ }
92
+
93
+ // Pin the TS twin calculateRequestId against the composed request id circuit
94
+ // at the same two shapes, and its tag assert.
95
+
96
+ export pure circuit calculateEvmType2RequestIdV1_1_0_0_34_34(
97
+ request: SignBidirectionalEventV1<EvmType2TxParams<1, 0, 0>, 34, 34>
98
+ ): RequestId {
99
+ return calculateEvmType2RequestIdV1<1, 0, 0, 34, 34>(request);
100
+ }
101
+
102
+ export pure circuit calculateEvmType2RequestIdV1_2_1_2_34_34(
103
+ request: SignBidirectionalEventV1<EvmType2TxParams<2, 1, 2>, 34, 34>
104
+ ): RequestId {
105
+ return calculateEvmType2RequestIdV1<2, 1, 2, 34, 34>(request);
72
106
  }
73
107
 
74
108
  export pure circuit verifyRespondBidirectionalEvent0(
75
109
  serializedOutput: Bytes<0>,
76
- respondBidirectionalEvent: RespondBidirectionalEvent,
110
+ respondBidirectionalEvent: RespondBidirectionalEventV1,
77
111
  mpcResponseKey: Secp256k1Point
78
112
  ): Boolean {
79
- return verifyRespondBidirectionalEvent<0>(
113
+ return verifyRespondBidirectionalEventV1<0>(
80
114
  serializedOutput,
81
115
  respondBidirectionalEvent,
82
116
  mpcResponseKey
@@ -85,12 +119,30 @@ export pure circuit verifyRespondBidirectionalEvent0(
85
119
 
86
120
  export pure circuit verifyRespondBidirectionalEvent32(
87
121
  serializedOutput: Bytes<32>,
88
- respondBidirectionalEvent: RespondBidirectionalEvent,
122
+ respondBidirectionalEvent: RespondBidirectionalEventV1,
89
123
  mpcResponseKey: Secp256k1Point
90
124
  ): Boolean {
91
- return verifyRespondBidirectionalEvent<32>(
125
+ return verifyRespondBidirectionalEventV1<32>(
92
126
  serializedOutput,
93
127
  respondBidirectionalEvent,
94
128
  mpcResponseKey
95
129
  );
96
130
  }
131
+
132
+ export pure circuit constructSignBidirectionalEventV1_1_0_0_34_34(
133
+ request: SignBidirectionalEventV1<EvmType2TxParams<1, 0, 0>, 34, 34>
134
+ ): SignBidirectionalEventV1<EvmType2TxParams<1, 0, 0>, 34, 34> {
135
+ return constructSignBidirectionalEventV1<EvmType2TxParams<1, 0, 0>, 34, 34>(
136
+ request.keyVersion,
137
+ request.sender,
138
+ request.path,
139
+ request.algo,
140
+ request.txParamType,
141
+ request.txParams,
142
+ request.executionDest,
143
+ request.signatureDest,
144
+ request.params,
145
+ request.outputDeserializationSchema,
146
+ request.respondSerializationSchema
147
+ );
148
+ }