@sig-net/midnight 0.19.0 → 0.21.0-rc.1

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 (85) hide show
  1. package/README.md +42 -2
  2. package/dist/abi-serde.d.ts +32 -30
  3. package/dist/abi-serde.d.ts.map +1 -1
  4. package/dist/abi-serde.js +139 -75
  5. package/dist/abi-serde.js.map +1 -1
  6. package/dist/byte-codecs.d.ts +62 -0
  7. package/dist/byte-codecs.d.ts.map +1 -0
  8. package/dist/byte-codecs.js +116 -0
  9. package/dist/byte-codecs.js.map +1 -0
  10. package/dist/compact-descriptors.d.ts +89 -0
  11. package/dist/compact-descriptors.d.ts.map +1 -0
  12. package/dist/compact-descriptors.js +139 -0
  13. package/dist/compact-descriptors.js.map +1 -0
  14. package/dist/constants.d.ts +18 -43
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +19 -45
  17. package/dist/constants.js.map +1 -1
  18. package/dist/ecdsa-attestation.d.ts +66 -96
  19. package/dist/ecdsa-attestation.d.ts.map +1 -1
  20. package/dist/ecdsa-attestation.js +115 -140
  21. package/dist/ecdsa-attestation.js.map +1 -1
  22. package/dist/epsilon-derivation.d.ts +24 -31
  23. package/dist/epsilon-derivation.d.ts.map +1 -1
  24. package/dist/epsilon-derivation.js +52 -62
  25. package/dist/epsilon-derivation.js.map +1 -1
  26. package/dist/index.d.ts +10 -9
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +17 -8
  29. package/dist/index.js.map +1 -1
  30. package/dist/managed/compiler/contract-info.json +0 -22
  31. package/dist/managed/compiler/contract-manifest.json +8 -8
  32. package/dist/managed/contract/index.d.ts +0 -3
  33. package/dist/managed/contract/index.js +102 -225
  34. package/dist/managed/contract/index.js.map +1 -1
  35. package/dist/raw-contract-state.d.ts +25 -0
  36. package/dist/raw-contract-state.d.ts.map +1 -0
  37. package/dist/raw-contract-state.js +52 -0
  38. package/dist/raw-contract-state.js.map +1 -0
  39. package/dist/signature-requests-state-reader.d.ts +18 -44
  40. package/dist/signature-requests-state-reader.d.ts.map +1 -1
  41. package/dist/signature-requests-state-reader.js +82 -146
  42. package/dist/signature-requests-state-reader.js.map +1 -1
  43. package/dist/signature-response-verification.d.ts +9 -8
  44. package/dist/signature-response-verification.d.ts.map +1 -1
  45. package/dist/signature-response-verification.js +20 -17
  46. package/dist/signature-response-verification.js.map +1 -1
  47. package/dist/signet-contract-events.d.ts +67 -66
  48. package/dist/signet-contract-events.d.ts.map +1 -1
  49. package/dist/signet-contract-events.js +57 -52
  50. package/dist/signet-contract-events.js.map +1 -1
  51. package/dist/signet-evtype2tx-record-decoding.d.ts +18 -0
  52. package/dist/signet-evtype2tx-record-decoding.d.ts.map +1 -0
  53. package/dist/signet-evtype2tx-record-decoding.js +97 -0
  54. package/dist/signet-evtype2tx-record-decoding.js.map +1 -0
  55. package/dist/signet-evtype2tx-requests.d.ts +43 -87
  56. package/dist/signet-evtype2tx-requests.d.ts.map +1 -1
  57. package/dist/signet-evtype2tx-requests.js +59 -130
  58. package/dist/signet-evtype2tx-requests.js.map +1 -1
  59. package/dist/signet-request-id.d.ts +14 -0
  60. package/dist/signet-request-id.d.ts.map +1 -0
  61. package/dist/signet-request-id.js +27 -0
  62. package/dist/signet-request-id.js.map +1 -0
  63. package/dist/signet-request-response-reader.d.ts +34 -56
  64. package/dist/signet-request-response-reader.d.ts.map +1 -1
  65. package/dist/signet-request-response-reader.js +56 -68
  66. package/dist/signet-request-response-reader.js.map +1 -1
  67. package/dist/signet-requests.d.ts +28 -92
  68. package/dist/signet-requests.d.ts.map +1 -1
  69. package/dist/signet-requests.js +39 -141
  70. package/dist/signet-requests.js.map +1 -1
  71. package/dist/testing.d.ts +3 -0
  72. package/dist/testing.d.ts.map +1 -0
  73. package/dist/testing.js +20 -0
  74. package/dist/testing.js.map +1 -0
  75. package/package.json +12 -3
  76. package/src/Signet.compact +149 -198
  77. package/src/circuits.compact +9 -16
  78. package/dist/signature-state-reading.d.ts +0 -43
  79. package/dist/signature-state-reading.d.ts.map +0 -1
  80. package/dist/signature-state-reading.js +0 -74
  81. package/dist/signature-state-reading.js.map +0 -1
  82. package/dist/signet-request-feed.d.ts +0 -126
  83. package/dist/signet-request-feed.d.ts.map +0 -1
  84. package/dist/signet-request-feed.js +0 -216
  85. package/dist/signet-request-feed.js.map +0 -1
@@ -4,69 +4,55 @@
4
4
  // =======================================================================================================
5
5
  //
6
6
  // Terms:
7
- // - client contract(s): contracts on the Midnight blockchain that integrate with the Signet Distributed MPC
8
- // to execute arbitrary transactions on foreign blockchains.
9
- // - Signet singleton: a single contract on the Midnight blockchain through which clients notify the MPC of
10
- // signature requests, and through which the MPC posts back signatures and signed attestations
11
- // of foreign executions. Each post is emitted as a contract event (the execution
12
- // outputs themselves travel off chain).
7
+ // - client contract(s): contracts on the Midnight blockchain that integrate with the Signet
8
+ // Distributed MPC network to execute arbitrary transactions on foreign
9
+ // blockchains.
10
+ // - Signet singleton: a single contract on the Midnight blockchain through which clients notify
11
+ // the MPC of signature requests, and through which the MPC posts back
12
+ // signatures and signed attestations of foreign executions. Each post is
13
+ // emitted as a contract event (the execution outputs themselves travel off
14
+ // chain).
13
15
  //
14
16
  // Usage:
15
17
  // // Import this module at the start of a contract (resolved through node_modules via COMPACT_PATH):
16
18
  // import "@sig-net/midnight/src/Signet";
17
19
  //
18
- // Key types at a Glance:
19
- // - SignBidirectionalEvent: Structured request for a signature on a transaction. Generically configured by client
20
- // contracts based on the type of transaction for which a signature is requested
21
- // (EVM Type 2, Solana etc.).
22
- // - RequestId: 32 byte chain agnostic nominal id, calculated as hash of SignBidirectionalEvent (see calculateRequestId).
23
- // - SignBidirectionalEventMap: Named mapping of RequestId to SignBidirectionalEvent. Declared by client contracts
24
- // in their ledger. Client contract tells MPC field location of this map when making a cross
25
- // contract call, via SignetSigner, to the Signet singleton. The MPC looks here to find
26
- // SignBidirectionalEvents.
27
- // - SignetSigner: Contract interface used by client contract to make cross contract calls to
28
- // Signet singleton to notify the MPC of a SignBidirectionalEvent.
29
- // - SignBidirectionalEventNotification: Notification delivered by client contracts via cross contract call to Signet singleton
30
- // (via SignetSigner interface) to notify the MPC of a SignBidirectionalEvent.
31
- // - SignatureRespondedEvent: Counterpart to SignBidirectionalEvent containing MPC generated signature requested by a SignBidirectionalEvent.
32
- // Posted by the MPC through the Signet singleton, which emits it as a contract
33
- // event. Clients extract it to build a signed transaction.
34
- // - RespondBidirectionalEvent: Attestation of a transaction executed on a foreign blockchain: the MPC's ECDSA
35
- // signature over the attestation digest of the request id and serialised output
36
- // (see calculateSignetAttestationDigest), signed with the MPC's RESPONSE key for
37
- // the requesting contract: the key derived from the client contract's address +
38
- // the fixed path "midnight response key". Both the digest and the output itself
39
- // travel off chain. Posted by the MPC through the Signet singleton, which
40
- // emits it as a contract event. Clients extract it, verify it (see
41
- // verifyRespondBidirectionalEvent) and deliver it back to the client contract.
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 keccak256 of the whole SignBidirectionalEvent record.
23
+ // - SignBidirectionalEventMap: the RequestId -> SignBidirectionalEvent map a client contract
24
+ // declares in its own ledger for the MPC to read requests from.
25
+ // - SignetSigner: the contract interface client contracts call to notify the Signet singleton.
26
+ // - SignBidirectionalEventNotification: the versioned notification delivered through that call.
27
+ // - SignatureRespondedEvent: the MPC's posted signature answering a request.
28
+ // - RespondBidirectionalEvent: the MPC's posted attestation that the foreign chain executed the
29
+ // signed transaction.
42
30
  //
43
31
  // These key types are best understood within the sign bidirectional flow that they enable:
44
32
  // 1. Client contract stores a SignBidirectionalEvent in a SignBidirectionalEventMap in its own ledger
45
33
  // (against the calculated RequestId) and requests a signature from the MPC via a cross contract call
46
- // against the SignetSigner interface. The Signet singleton emits the
47
- // SignBidirectionalEventNotification as a contract event, the RequestId disclosed beside it.
48
- // 2. MPC polls the Signet singleton contract's events for SignBidirectionalEventNotifications and uses
49
- // each one to look the declared SignBidirectionalEvent signature request up in the named client's own ledger.
50
- // It generates the signature and posts a SignatureRespondedEvent (the requested signature) through
51
- // the Signet singleton, which emits it as a contract event.
52
- // 3. Client polls the Signet singleton contract's SignatureRespondedEvent events. Each event carries
53
- // the request id it answers beside the signature. The id is unauthenticated routing data: the client
54
- // reads its own request's posts by id, verifies each candidate, and takes the first one whose
55
- // signature recovers to its derived key. Then it broadcasts the signed txn to the destination chain.
34
+ // against the SignetSigner interface. The Signet singleton emits the SignBidirectionalEventNotification
35
+ // as a contract event.
36
+ // 2. MPC polls the Signet singleton contract's events for SignBidirectionalEventNotifications and
37
+ // uses each one to look the declared SignBidirectionalEvent signature request up in the named
38
+ // client's own ledger. It generates the signature and posts a SignatureRespondedEvent (the requested
39
+ // signature) through the Signet singleton, which emits it as a contract event.
40
+ // 3. Client polls the Signet singleton contract's SignatureRespondedEvent events, identifying the response
41
+ // associated with their request by RequestId. Clients validate the signature is correct by checking with
42
+ // the requested signing key. Then it broadcasts the signed txn to the destination chain.
56
43
  // 4. MPC observes txn execution on destination chain and posts a RespondBidirectionalEvent attestation
57
- // through the central Signet contract, which emits it as a contract event beside the request id.
44
+ // through the Signet singleton, which emits it as a contract event beside the request id.
58
45
  // The client polls its request's posts and takes the first one that verifies over the request id
59
- // and output.
60
- // The client recovers the execution output itself off chain (for EVM: the mined
46
+ // and output. The client recovers the execution output itself off chain (for EVM: the mined
61
47
  // call's return data via debug_traceTransaction, also served by the fakenet
62
48
  // responder's /responses/{requestId} helper API) and hands output plus attestation
63
49
  // to its verify circuit. See "Output recovery" in this package's README.
64
50
  //
65
51
  // Read more:
66
- // - For full integration examples (such as an ERC20 cross chain vault example) see
67
- // the Midnight Examples repository at: https://github.com/sig-net/midnight-examples.
68
52
  // - The sign bidirectional flow and key derivation: this package's README
69
53
  // ("Sign bidirectional flow", "Derived keys").
54
+ // - For full integration examples (such as an ERC20 cross chain vault example) see
55
+ // the Midnight Examples repository at: https://github.com/sig-net/midnight-examples.
70
56
  // - Compact language reference (modules, types):
71
57
  // https://docs.midnight.network/compact/reference/compact-reference
72
58
 
@@ -77,12 +63,15 @@ module Signet {
77
63
 
78
64
  // Structured request for a signature on a transaction. Generically configured by client contracts
79
65
  // based on the type of transaction for which a signature is requested (EVM Type 2, Solana etc.).
66
+ //
67
+ // KEY: texual order of the fields in this structure must remain fixed as is as their order
68
+ // is relied upon for consistent hashing and decoding from ledger state.
80
69
  export struct SignBidirectionalEvent<TxParams, #LenOutputDeserialization, #LenRespondSerialization> {
81
70
  // Address of the client contract that stores and notifies of this SignBidirectionalEvent.
82
71
  // Set with kernel.self().
83
72
  sender: ContractAddress;
84
73
 
85
- // Optional nonce field settable by client contracts to ensure SignBidirectionalEvent uniqueness if desired.
74
+ // Optional nonce, set by client contracts to make otherwise-identical requests unique.
86
75
  requestNonce: Uint<64>;
87
76
 
88
77
  // MPC Key Derivation Parameters.
@@ -90,19 +79,19 @@ module Signet {
90
79
  // derivedSigningKey = f(mpcRootKey[keyVersion], caip2ChainId, sender, hex::encode(path))
91
80
  // where caip2ChainId is the CAIP-2 id of the ORIGINATING chain (the Midnight
92
81
  // variant for requests made from Midnight contracts), not this record's target
93
- // caip2Id. The path is 32 opaque bytes of the client contract's choosing (e.g a
82
+ // caip2Id. The path is 32 opaque bytes of the client contract's choosing (e.g. a
94
83
  // fixed literal for a contract-owned account or a hash of a caller's secret for
95
- // per-user accounts): no format is enforced, any 32 bytes are valid. CRITICAL:
96
- // the MPC renders the path as hex::encode(path) before deriving (lowercase hex
84
+ // per-user accounts): no format is enforced, any 32 bytes are valid.
85
+ // CRITICAL: the MPC renders the path as hex::encode(path) for key derivation (lowercase hex
97
86
  // of the full 32 bytes, no trimming, no 0x prefix). The sender is always part
98
87
  // of the derivation, so no contract can reach another contract's derived keys.
99
- keyVersion: Uint<8>; // MPC root-key version to derive from. Set to 1.
88
+ keyVersion: Uint<8>; // MPC root-key version to derive from. Currently 1.
100
89
  path: Bytes<32>; // Key derivation path: 32 opaque bytes, client contract's choice.
101
90
 
102
91
  // MPC Parameters.
103
92
  algo: MPCSignatureAlgorithm; // Signature algorithm scheme, e.g. ecdsa
104
93
  dest: MPCDestination; // Destination field reserved for future use. Set to unused.
105
- params: Bytes<64>; // Extra MPC parameters reserved for future use. Leave blank.
94
+ params: Bytes<64>; // Extra MPC parameters reserved for future use. Zero-fill.
106
95
 
107
96
  // Specific destination chain Transaction parameters.
108
97
  txParamType: TxParamType; // Off-chain tag indicating what type txParams is set to.
@@ -128,7 +117,7 @@ module Signet {
128
117
  reserved, // Reserved for future use. Exists to keep enum >= 2 (proof server bug).
129
118
  }
130
119
 
131
- // MPCDestination used to specify destination field for SignBidirectionalEvent.
120
+ // MPCDestination specifies the destination field of a SignBidirectionalEvent.
132
121
  // Currently unused.
133
122
  export enum MPCDestination {
134
123
  unused,
@@ -142,7 +131,7 @@ module Signet {
142
131
  reserved, // Reserved for future use. Exists to keep enum >= 2 (proof server bug).
143
132
  }
144
133
 
145
- // Construction helper for the canonical Signet SignBidirectionalEvent
134
+ // Construction helper for the canonical Signet SignBidirectionalEvent.
146
135
  export pure circuit constructSignBidirectionalEvent<TxParams, #LenOutputDeserialization, #LenRespondSerialization>(
147
136
  sender: ContractAddress,
148
137
  requestNonce: Uint<64>,
@@ -157,10 +146,8 @@ module Signet {
157
146
  outputDeserializationSchema: Bytes<LenOutputDeserialization>,
158
147
  respondSerializationSchema: Bytes<LenRespondSerialization>,
159
148
  ): SignBidirectionalEvent<TxParams, LenOutputDeserialization, LenRespondSerialization> {
160
- // validate supported values
161
149
  assert(keyVersion >= 1, "keyVersion must be >= 1");
162
150
 
163
- // construct and return request
164
151
  return SignBidirectionalEvent<TxParams, LenOutputDeserialization, LenRespondSerialization> {
165
152
  sender,
166
153
  requestNonce,
@@ -179,16 +166,13 @@ module Signet {
179
166
 
180
167
  // ---- SignBidirectionalEvent RequestId --------------------------------------------------------------
181
168
 
182
- // 32 byte chain agnostic nominal id, calculated as persistent hash of SignBidirectionalEvent
183
- // (see calculateRequestId).
169
+ // 32 byte chain agnostic nominal id, calculated as keccak256 of the whole
170
+ // SignBidirectionalEvent record (see calculateRequestId).
184
171
  export new type RequestId = Bytes<32>;
185
172
 
186
- // keccak256 of the whole record, so the id commits to every field including sender
187
- // (which scopes ids per contract). No domain-separation tag, deliberately.
188
- //
189
- // Hashes the field-aligned binary representation (each field re-padded to its declared
190
- // width), so off-chain consumers must reproduce that layout exactly. Type argument is
191
- // the INPUT type, the return is always Bytes<32>.
173
+ // keccak256 of the whole record: the id commits to every field, including the
174
+ // sender, and carries no domain-separation tag. Off-chain twins must reproduce
175
+ // the field-aligned binary layout exactly.
192
176
  export pure circuit calculateRequestId<TxParams, #LenOutputDeserialization, #LenRespondSerialization>(
193
177
  request: SignBidirectionalEvent<TxParams, LenOutputDeserialization, LenRespondSerialization>
194
178
  ): RequestId {
@@ -224,7 +208,7 @@ module Signet {
224
208
  // (via SignetSigner interface) to notify the MPC of a SignBidirectionalEvent.
225
209
  export struct SignBidirectionalEventNotification {
226
210
  // Notification payload layout version.
227
- // Curent Layout Version: 1 (see constructSignBidirectionalEventNotificationV1)
211
+ // Current layout version: 1 (see constructSignBidirectionalEventNotificationV1)
228
212
  version: Uint<8>;
229
213
  // Payload containing serialised notification payload.
230
214
  payload: Bytes<128>;
@@ -232,32 +216,16 @@ module Signet {
232
216
 
233
217
  // Construction helper for the V1 SignBidirectionalEventNotification.
234
218
  //
235
- // The payload is a pointer for the MPC. It names the contract that holds the
236
- // SignBidirectionalEventMap (callerAddress) and the position of that map in the
237
- // contract's raw on-chain state (requestsPathDepth + requestsPath). The MPC
238
- // follows the path through the raw state tree to read the authenticated
239
- // request, without the caller's compiled contract.
219
+ // The payload is a pointer for the MPC: the contract that holds the
220
+ // SignBidirectionalEventMap (callerAddress) and the position of that map in
221
+ // the contract's raw ledger state tree (requestsPathDepth + requestsPath).
240
222
  //
241
- // Why a path: compactc packs a contract's public ledger fields into a tree of
242
- // state values whose array nodes hold at most 15 entries (the compiler's
243
- // maximum-ledger-segment-length). A contract with 15 or fewer ledger fields
244
- // keeps each field directly in the root array, so field N sits at path [N]
245
- // (depth 1). Past 15 fields the compiler splits the declaration list into
246
- // segments of at most 15, remainder segment first, and the root array holds
247
- // the segments. Each split adds one level to the tree and one entry to every
248
- // field's path. Example: 20 fields split into segments of 5 and 15, so
249
- // fields 0..4 sit at [0, 0]..[0, 4] and fields 5..19 sit at
250
- // [1, 0]..[1, 14] (depth 2).
251
- //
252
- // How to determine the values: read the path from your contract's compiled
253
- // artifacts, do not derive it by hand. The compiler records each field's
254
- // resolved path as its "index" in managed/<contract>/compiler/contract-info.json
255
- // (a bare number N means path [N]). The generated
256
- // managed/<contract>/contract/index.js accessors walk the same indices, for
257
- // example state.asArray()[1].asArray()[14] for a map recorded at [1, 14].
258
- // Set requestsPathDepth to the number of path entries and requestsPath to
259
- // the path itself, zero padded to 4 entries: [1, 14] becomes depth 2 and
260
- // path [1, 14, 0, 0].
223
+ // Read the path from your contract's compiled artifacts, never derive it by
224
+ // hand: each field's resolved path is its "index" in
225
+ // managed/<contract>/compiler/contract-info.json (a bare number N means path
226
+ // [N]). Set requestsPathDepth to the number of entries and requestsPath to
227
+ // the path zero padded to 4 entries: [1, 14] becomes depth 2 and path
228
+ // [1, 14, 0, 0].
261
229
  export pure circuit constructSignBidirectionalEventNotificationV1(
262
230
  // Address of the client contract that stores and notifies of this SignBidirectionalEvent.
263
231
  // Set with kernel.self(). MUST match sender in associated SignBidirectionalEvent.
@@ -289,65 +257,48 @@ module Signet {
289
257
  y: Bytes<32>;
290
258
  }
291
259
 
292
- // The MPC's canonical ECDSA signature: bigR the full nonce point (so consumers
293
- // never decompress), s big-endian, recoveryId the parity of R.y (0 or 1).
294
- // Deliberately identical to the Signature struct every other sig-net signer
295
- // contract exposes (EVM ChainSignatures.sol, the Solana signet program) and to
296
- // the MPC's own Signature { big_r, s, recovery_id }: one vocabulary across
297
- // chains. The scheme is fixed by the request's own MPCSignatureAlgorithm.
298
- //
299
- // Stored bytes are BIG-endian throughout, the canonical SEC1 order. The scalar
300
- // casts read little-endian, so verifyRespondBidirectionalEvent reverses on the
301
- // way in, keeping the stored record canonical.
260
+ // The MPC's canonical ECDSA signature: bigR the full nonce point, s the
261
+ // signature scalar, recoveryId the parity of R.y (0 or 1). The same Signature
262
+ // vocabulary as the EVM and Solana signer contracts and the MPC's own record.
263
+ // Stored bytes are BIG-endian (SEC1) throughout.
302
264
  export struct Signature {
303
265
  bigR: AffinePoint;
304
266
  s: Bytes<32>;
305
267
  recoveryId: Uint<8>;
306
268
  }
307
269
 
308
- // Counterpart to SignBidirectionalEvent containing MPC generated signature requested by a SignBidirectionalEvent.
309
- // Posted by MPC back to Signet singleton for clients to extract to build a signed transaction.
270
+ // Counterpart to SignBidirectionalEvent: the MPC generated signature a request asked for.
271
+ // Posted by the MPC through the Signet singleton for clients to extract and build a
272
+ // signed transaction from.
310
273
  export struct SignatureRespondedEvent {
311
274
  // The requested signature over the transaction the request describes.
312
275
  signature: Signature;
313
276
  }
314
277
 
315
- // RespondBidirectionalEvent is the final step of the Sig Network Sign Bidirectional flow.
316
- // It holds an MPC ECDSA signature of an attestation digest verifying foreign chain execution output.
317
- //
318
- // The attestation digest is a keccak256 hash of the request id concatenated with the serialised foreign
319
- // transaction execution output:
320
- // attestationDigest = keccak256(requestId + serialisedOutput)
278
+ // RespondBidirectionalEvent: the MPC's attestation of a foreign execution, the
279
+ // final step of the sign bidirectional flow.
321
280
  //
322
- // The signature is the ECDSA secp256k1 signature over the attestation digest by the MPC with the
323
- // client contract's MPC response key:
324
- // signature = signSECP256k1(mpcResponseKey, attestationDigest)
281
+ // attestationDigest = keccak256(requestId + serializedOutput)
282
+ // signature = signSECP256k1(mpcResponseKey, attestationDigest)
325
283
  //
326
- // Where the MPC response key is a per-client-contract key derived using the same derivation function
327
- // as request signing keys, with the path set to the fixed "midnight response key" (a protocol
328
- // string that enters the derivation verbatim, unlike a request's 32 path bytes which are
329
- // hex-encoded first):
284
+ // mpcResponseKey is derived like request signing keys, with the path set to the
285
+ // fixed "midnight response key" (a protocol string that enters the derivation
286
+ // verbatim, unlike a request's hex-encoded 32 path bytes):
330
287
  // mpcResponseKey = f(mpcRootKey[keyVersion], caip2ChainId, sender, "midnight response key")
331
288
  //
332
- // Storage & Verification:
333
- // The execution output is of variable size, so it travels off chain, and the digest
334
- // over it is recomputed rather than stored. The client fetches the output, recomputes
335
- // the attestation digest and verifies the signature in-circuit via
336
- // verifyRespondBidirectionalEvent against its derived MPC response key. The signature
337
- // is the ONLY thing that distinguishes a genuine post from garbage: the Signet
338
- // singleton is an unauthenticated log and emits every post unverified.
289
+ // The output travels off chain and the digest is recomputed at verification
290
+ // time (verifyRespondBidirectionalEvent). The signature is the ONLY thing that
291
+ // distinguishes a genuine post from garbage: the singleton emits every post
292
+ // unverified.
339
293
  export struct RespondBidirectionalEvent {
340
294
  // The MPC's ECDSA signature over the attestation digest.
341
295
  signature: Signature;
342
296
  }
343
297
 
344
- // Calculates the 32-byte attestation digest for a remote execution output:
298
+ // The 32-byte attestation digest of a remote execution output:
345
299
  // attestationDigest = keccak256(requestId + serializedOutput)
346
- //
347
- // Inputs are concatenated raw with no padding or length prefixes. The output digest is
348
- // interpreted as a big-endian integer during ECDSA signature verification.
349
- //
350
- // Note: Off-chain code should use the matching TypeScript twin in `ecdsa-attestation.ts`.
300
+ // Inputs are concatenated raw, no padding or length prefixes. Off-chain code
301
+ // uses the TypeScript twin in `ecdsa-attestation.ts`.
351
302
  export pure circuit calculateSignetAttestationDigest<#serializedOutputLength>(
352
303
  requestId: RequestId,
353
304
  serializedOutput: Bytes<serializedOutputLength>,
@@ -355,32 +306,19 @@ module Signet {
355
306
  return keccak256<[RequestId, Bytes<serializedOutputLength>]>([requestId, serializedOutput]);
356
307
  }
357
308
 
358
- // Same Bytes -> Vector<Uint<8>> idiom numericAbiWord uses. Pulls in a byte-decomposition
359
- // lookup table, a fixed cost the calling circuit pays once and shares with any other
360
- // byte-level work it does, so reverse at the boundary and never per element.
361
- pure circuit reverseBytes32(b: Bytes<32>): Bytes<32> {
362
- const v = b as Vector<32, Uint<8>>;
363
- return Bytes[v[31], v[30], v[29], v[28], v[27], v[26], v[25], v[24],
364
- v[23], v[22], v[21], v[20], v[19], v[18], v[17], v[16],
365
- v[15], v[14], v[13], v[12], v[11], v[10], v[9], v[8],
366
- v[7], v[6], v[5], v[4], v[3], v[2], v[1], v[0]];
367
- }
368
-
369
- // Verifies a RespondBidirectionalEvent against its execution output and MPC response key.
370
- //
371
- // Verification steps:
372
- // 1. Recompute digest: digest = keccak256(requestId + serializedOutput)
373
- // 2. Verify signature: secp256k1EcdsaVerify(digest, signature, mpcResponseKey) == true
374
- //
375
- // The caller supplies serializedOutput at its exact unpadded width, per
376
- // calculateSignetAttestationDigest, and hands over the event record as stored: the
377
- // big-endian-to-little-endian re-encoding of the signature scalars happens here, in one
378
- // place for every integrator. ECDSA's r is bigR.x reduced mod the curve order; bigR.x is
379
- // taken as-is, as it is already below the order for all but a ~2^-128 fraction of
380
- // signatures, and one that is not simply fails to verify.
381
- //
382
- // Note: Client contracts must run this check before consuming outputs, as the Signet
383
- // singleton emits responses unverified.
309
+ // Verifies a RespondBidirectionalEvent against its execution output and MPC
310
+ // response key: recomputes the attestation digest and checks the ECDSA
311
+ // signature over it. Client contracts run this before consuming outputs, as
312
+ // the singleton emits responses unverified. The caller supplies
313
+ // serializedOutput at its exact unpadded width, and the event record with
314
+ // signature.bigR.x and signature.s byte-reversed into LITTLE-endian: the
315
+ // scalar casts below read little-endian bytes, and the wire/ledger records
316
+ // are big-endian, so the reversal happens off-chain where it costs nothing
317
+ // (the TS SDK's respondBidirectionalEventToCircuitInput does it). A wrong
318
+ // byte order fails verification, never a false accept. Only bigR.x and s
319
+ // enter verification. An s that reduces to 0 mod the curve order aborts the
320
+ // circuit rather than returning false, so sift candidate posts off chain
321
+ // first (verifyRespondBidirectionalSignature).
384
322
  export pure circuit verifyRespondBidirectionalEvent<#serializedOutputLength>(
385
323
  requestId: RequestId,
386
324
  serializedOutput: Bytes<serializedOutputLength>,
@@ -395,38 +333,29 @@ module Signet {
395
333
  return secp256k1EcdsaVerify(
396
334
  digest,
397
335
  Secp256k1EcdsaSignature {
398
- r: reverseBytes32(respondBidirectionalEvent.signature.bigR.x) as Secp256k1Scalar,
399
- s: reverseBytes32(respondBidirectionalEvent.signature.s) as Secp256k1Scalar,
336
+ r: respondBidirectionalEvent.signature.bigR.x as Secp256k1Scalar,
337
+ s: respondBidirectionalEvent.signature.s as Secp256k1Scalar,
400
338
  },
401
339
  mpcResponseKey
402
340
  );
403
341
  }
404
342
 
405
- // Hash a secp256k1 public key exactly as client contracts pin it at deploy time
406
- // (sealed `persistentHash<Secp256k1Point>`), so off-chain code can recompute the
407
- // pin without re-deploying.
408
- export pure circuit signetKeyHash(pk: Secp256k1Point): Bytes<32> {
409
- return persistentHash<Secp256k1Point>(pk);
410
- }
411
-
412
- // ---- EVM Type2 Transaction Parameters ---------------------------------------------------
343
+ // ---- EVM Type2 Transaction Parameters --------------------------------------------------------------
413
344
 
414
345
  // The EVM transaction to be signed, decomposed into typed fields
415
- // (EIP-1559 / type-2: https://eips.ethereum.org/EIPS/eip-1559).
416
- // Decomposed so the requesting contract can enforce invariants field by
417
- // field, and the MPC re-assembles and RLP-encodes off-chain. (The MPC's chain-agnostic primitive carries an opaque
418
- // serialized_transaction instead, and the decomposition follows its Canton
419
- // adapter's EvmType2TransactionParams.)
346
+ // (EIP-1559 / type-2: https://eips.ethereum.org/EIPS/eip-1559) so the
347
+ // requesting contract can enforce invariants field by field. The MPC
348
+ // re-assembles and RLP-encodes off chain.
349
+ //
350
+ // Every variable-length part is a compile-time CAPACITY (a generic size
351
+ // parameter the requesting contract fixes) plus a runtime count of how many
352
+ // leading slots are real. Unused capacity is hashed as part of the record
353
+ // (zero-fill) but never reaches the assembled transaction. Field order is
354
+ // the canonical EIP-1559 RLP order and the request-id hash order: part of
355
+ // the wire contract, each count directly before the slot it bounds.
420
356
  //
421
- // Compact has no runtime-dynamic sizes, so every variable-length EVM tx
422
- // part follows one scheme: a compile-time CAPACITY (a generic size
423
- // parameter each requesting contract fixes as its throttle) plus a runtime
424
- // count that says how many leading slots are real. Off-chain consumers use
425
- // the counts. Unused capacity is hashed as part of the record (zero-fill by
426
- // convention) but never reaches the assembled transaction.
427
- // Canonical EIP-1559 payload order, which is the RLP order the MPC re-assembles into.
428
- // Each count sits directly before the slot it bounds. This order is also the
429
- // request-id hash order, so it is part of the wire contract.
357
+ // KEY: texual order of the fields in this structure must remain fixed as is as their order
358
+ // is relied upon for consistent hashing and decoding from ledger state.
430
359
  export struct EvmType2TxParams<#maxCalldataWords, #maxAccessListEntries, #maxStorageKeysPerEntry> {
431
360
  chainId: Uint<64>; // EVM chain id (also expressed in caip2Id)
432
361
  nonce: Uint<64>; // account nonce of the derived sender
@@ -443,6 +372,9 @@ module Signet {
443
372
  // One EIP-2930 access-list entry (https://eips.ethereum.org/EIPS/eip-2930):
444
373
  // an address plus the storage keys the transaction pre-declares. Keys are
445
374
  // raw 32-byte values, copied verbatim into the RLP encoding.
375
+ //
376
+ // KEY: texual order of the fields in this structure must remain fixed as is as their order
377
+ // is relied upon for consistent hashing and decoding from ledger state.
446
378
  export struct EvmAccessListEntry<#maxStorageKeys> {
447
379
  address: Bytes<20>;
448
380
  storageKeyCount: Uint<8>; // how many leading storageKeys are real
@@ -455,6 +387,9 @@ module Signet {
455
387
  // the MPC signs a transaction composed of exactly these words. See the
456
388
  // ABI Calldata Word Construction Utilities below for help constructing
457
389
  // canonical ABI words for common types.
390
+ //
391
+ // KEY: texual order of the fields in this structure must remain fixed as is as their order
392
+ // is relied upon for consistent hashing and decoding from ledger state.
458
393
  export struct EvmCalldata<#maxWords> {
459
394
  selector: Bytes<4>; // the literal first 4 calldata bytes
460
395
  noWords: Uint<16>; // No. of used words in the words list
@@ -491,28 +426,44 @@ module Signet {
491
426
  return Bytes[...default<Bytes<31>>, value as Uint<8>];
492
427
  }
493
428
 
494
- // Read a numeric ABI word back into a Uint<128>: the big-endian fold of
495
- // bytes 16..31. The leading half must be zero (a canonical Uint<128> word);
496
- // rejecting junk there keeps settle circuits from silently truncating a
497
- // wider value. Max fold result is 2^128 - 1, well inside the field.
429
+ // Read a numeric ABI word back into a Uint<128>: the inverse of
430
+ // numericAbiWord. Asserts the leading 16 bytes are zero (a canonical
431
+ // Uint<128> word). Decomposes the word into bytes exactly once (each
432
+ // slice would redecompose the whole word) and folds v[16..31] by Horner.
498
433
  export pure circuit abiWordToUint128(word: Bytes<32>): Uint<128> {
499
- assert(slice<16>(word, 0) as Field == 0 as Field, "ABI word exceeds Uint<128>");
500
- const b0 = slice<1>(word, 16) as Field;
501
- const b1 = b0 * 256 + (slice<1>(word, 17) as Field);
502
- const b2 = b1 * 256 + (slice<1>(word, 18) as Field);
503
- const b3 = b2 * 256 + (slice<1>(word, 19) as Field);
504
- const b4 = b3 * 256 + (slice<1>(word, 20) as Field);
505
- const b5 = b4 * 256 + (slice<1>(word, 21) as Field);
506
- const b6 = b5 * 256 + (slice<1>(word, 22) as Field);
507
- const b7 = b6 * 256 + (slice<1>(word, 23) as Field);
508
- const b8 = b7 * 256 + (slice<1>(word, 24) as Field);
509
- const b9 = b8 * 256 + (slice<1>(word, 25) as Field);
510
- const b10 = b9 * 256 + (slice<1>(word, 26) as Field);
511
- const b11 = b10 * 256 + (slice<1>(word, 27) as Field);
512
- const b12 = b11 * 256 + (slice<1>(word, 28) as Field);
513
- const b13 = b12 * 256 + (slice<1>(word, 29) as Field);
514
- const b14 = b13 * 256 + (slice<1>(word, 30) as Field);
515
- const b15 = b14 * 256 + (slice<1>(word, 31) as Field);
434
+ const v = word as Vector<32, Uint<8>>;
435
+ assert(v[0] == 0, "ABI word exceeds Uint<128>");
436
+ assert(v[1] == 0, "ABI word exceeds Uint<128>");
437
+ assert(v[2] == 0, "ABI word exceeds Uint<128>");
438
+ assert(v[3] == 0, "ABI word exceeds Uint<128>");
439
+ assert(v[4] == 0, "ABI word exceeds Uint<128>");
440
+ assert(v[5] == 0, "ABI word exceeds Uint<128>");
441
+ assert(v[6] == 0, "ABI word exceeds Uint<128>");
442
+ assert(v[7] == 0, "ABI word exceeds Uint<128>");
443
+ assert(v[8] == 0, "ABI word exceeds Uint<128>");
444
+ assert(v[9] == 0, "ABI word exceeds Uint<128>");
445
+ assert(v[10] == 0, "ABI word exceeds Uint<128>");
446
+ assert(v[11] == 0, "ABI word exceeds Uint<128>");
447
+ assert(v[12] == 0, "ABI word exceeds Uint<128>");
448
+ assert(v[13] == 0, "ABI word exceeds Uint<128>");
449
+ assert(v[14] == 0, "ABI word exceeds Uint<128>");
450
+ assert(v[15] == 0, "ABI word exceeds Uint<128>");
451
+ const b0 = v[16] as Field;
452
+ const b1 = b0 * 256 + (v[17] as Field);
453
+ const b2 = b1 * 256 + (v[18] as Field);
454
+ const b3 = b2 * 256 + (v[19] as Field);
455
+ const b4 = b3 * 256 + (v[20] as Field);
456
+ const b5 = b4 * 256 + (v[21] as Field);
457
+ const b6 = b5 * 256 + (v[22] as Field);
458
+ const b7 = b6 * 256 + (v[23] as Field);
459
+ const b8 = b7 * 256 + (v[24] as Field);
460
+ const b9 = b8 * 256 + (v[25] as Field);
461
+ const b10 = b9 * 256 + (v[26] as Field);
462
+ const b11 = b10 * 256 + (v[27] as Field);
463
+ const b12 = b11 * 256 + (v[28] as Field);
464
+ const b13 = b12 * 256 + (v[29] as Field);
465
+ const b14 = b13 * 256 + (v[30] as Field);
466
+ const b15 = b14 * 256 + (v[31] as Field);
516
467
  return b15 as Uint<128>;
517
468
  }
518
469
 
@@ -1,18 +1,14 @@
1
1
  pragma language_version >= 0.25;
2
2
 
3
- // Compiled export surface of the Signet module. Signet.compact is
4
- // module-only and compiles to nothing on its own, so this contract-less
5
- // program re-exports its pure circuits: the compiler emits them as
6
- // pureCircuits for unit tests and off-chain code to reuse instead of
7
- // re-porting the algorithms. Only non-generic circuits can be exported
8
- // (the compiler cannot export type-parameterised circuits top-level).
9
- // Compile with --skip-zk and --feature-zkir-v3.
3
+ // Compiled export surface of the Signet module: a contract-less program
4
+ // re-exporting its pure circuits as pureCircuits for unit tests and
5
+ // off-chain code. Only non-generic circuits can be exported. Compile with
6
+ // --skip-zk and --feature-zkir-v3.
10
7
 
11
8
  import CompactStandardLibrary;
12
9
  import "./Signet";
13
10
 
14
11
  export {
15
- signetKeyHash,
16
12
  constructSignBidirectionalEventNotificationV1,
17
13
  evmAddressAbiWord,
18
14
  numericAbiWord,
@@ -22,14 +18,11 @@ export {
22
18
  };
23
19
 
24
20
  // ---- Fixed-width attestation oracles (tests only) --------------------------
25
- // calculateSignetAttestationDigest and verifyRespondBidirectionalEvent are
26
- // size-generic, so they cannot appear in the export list above. These
27
- // fixed-width instantiations exist SOLELY to pin the off-chain TS twin
28
- // (calculateSignetAttestationDigest in ecdsa-attestation.ts) byte-for-byte
29
- // against the compiled circuits: 1 is the smallest packed output (a lone
30
- // bool), 32 one ABI word, 100 a deliberately non-word-aligned width.
31
- // Off-chain code must use the TS twin; client contracts instantiate the
32
- // generic circuits at the width their own respond schema implies.
21
+ // The size-generic digest and verify circuits cannot appear in the export
22
+ // list above. These fixed-width instantiations exist solely to pin the TS
23
+ // twin in ecdsa-attestation.ts against the compiled circuits (widths 1, 32
24
+ // and 100). Client contracts instantiate the generic circuits at their own
25
+ // width.
33
26
 
34
27
  export pure circuit calculateSignetAttestationDigest1(
35
28
  requestId: RequestId,
@@ -1,43 +0,0 @@
1
- import { CompactTypeBytes, CompactTypeUnsignedInteger, type CompactType, type StateValue } from "@midnight-ntwrk/compact-runtime";
2
- import { type RequestId } from "./signet-requests.ts";
3
- /** Descriptor for a Compact `Uint<64>` (8-byte unsigned integer). */
4
- export declare const u64: CompactTypeUnsignedInteger;
5
- /** Descriptor for a Compact `Bytes<32>`. */
6
- export declare const bytes32: CompactTypeBytes;
7
- /**
8
- * Descriptor for a request id ledger key (Compact `RequestId`, a
9
- * nominal `Bytes<32>`). Use it to encode a {@link RequestId} into the
10
- * aligned form the state tree stores, or decode one back.
11
- */
12
- export declare const requestIdType: CompactType<RequestId>;
13
- /**
14
- * What the indexer / simulator hands us: a bare `StateValue`, or anything
15
- * wrapping one under `.state` (e.g. `ChargedState`,
16
- * `queryContractState(address).data`).
17
- */
18
- export type RawContractState = StateValue | {
19
- state: StateValue;
20
- };
21
- /**
22
- * Follow a resolved ledger-tree path to its node in raw state: the
23
- * MPC-perspective primitive, given only a contract's raw state and the path a
24
- * notification carries. It walks the path node for node, exactly as the
25
- * compiled `ledger()` accessor does, and never inspects array widths or
26
- * re-derives the compiler's chunk structure.
27
- *
28
- * The path is the value compactc records for a field in the client's own
29
- * `contract-info.json` (`"index"`): a single-element path (`[4]`) for a flat
30
- * contract's field, a longer one (`[1, 14]`) once a contract declares more
31
- * than 15 fields and the compiler stores them in a chunk tree. Because the
32
- * consumer is told the path rather than guessing it, no field whose own value
33
- * is an array can be mistaken for a compiler chunk.
34
- *
35
- * @param raw - Raw contract state from the indexer or simulator.
36
- * @param path - Resolved chunk-tree path in declaration order, from the
37
- * notification (which sources it from the client's `contract-info.json`).
38
- * @returns The `StateValue` node at the end of the path.
39
- * @throws Error if `path` is empty, steps into a non-array, or an index is out
40
- * of range.
41
- */
42
- export declare function signetFieldNodeByPath(raw: RawContractState, path: readonly number[]): StateValue;
43
- //# sourceMappingURL=signature-state-reading.d.ts.map