@sig-net/midnight 0.11.0 → 0.12.0

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 (48) hide show
  1. package/README.md +9 -9
  2. package/dist/abi-serde.d.ts +89 -0
  3. package/dist/abi-serde.d.ts.map +1 -0
  4. package/dist/abi-serde.js +393 -0
  5. package/dist/abi-serde.js.map +1 -0
  6. package/dist/constants.d.ts +23 -14
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +28 -20
  9. package/dist/constants.js.map +1 -1
  10. package/dist/ecdsa-attestation.d.ts +38 -18
  11. package/dist/ecdsa-attestation.d.ts.map +1 -1
  12. package/dist/ecdsa-attestation.js +52 -25
  13. package/dist/ecdsa-attestation.js.map +1 -1
  14. package/dist/epsilon-derivation.js +1 -1
  15. package/dist/epsilon-derivation.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/managed/compiler/contract-info.json +180 -122
  21. package/dist/managed/compiler/contract-manifest.json +8 -8
  22. package/dist/managed/contract/index.d.ts +38 -28
  23. package/dist/managed/contract/index.js +302 -149
  24. package/dist/managed/contract/index.js.map +1 -1
  25. package/dist/signature-requests-state-reader.d.ts +13 -13
  26. package/dist/signature-requests-state-reader.js +22 -22
  27. package/dist/signature-requests-state-reader.js.map +1 -1
  28. package/dist/signature-response-verification.d.ts +1 -1
  29. package/dist/signature-response-verification.js +3 -3
  30. package/dist/signature-response-verification.js.map +1 -1
  31. package/dist/signet-contract-state-reader.d.ts +33 -36
  32. package/dist/signet-contract-state-reader.d.ts.map +1 -1
  33. package/dist/signet-contract-state-reader.js +26 -28
  34. package/dist/signet-contract-state-reader.js.map +1 -1
  35. package/dist/signet-evtype2tx-requests.d.ts +21 -21
  36. package/dist/signet-evtype2tx-requests.js +16 -16
  37. package/dist/signet-evtype2tx-requests.js.map +1 -1
  38. package/dist/signet-request-response-reader.d.ts +23 -23
  39. package/dist/signet-request-response-reader.d.ts.map +1 -1
  40. package/dist/signet-request-response-reader.js +28 -28
  41. package/dist/signet-request-response-reader.js.map +1 -1
  42. package/dist/signet-requests.d.ts +27 -27
  43. package/dist/signet-requests.d.ts.map +1 -1
  44. package/dist/signet-requests.js +28 -29
  45. package/dist/signet-requests.js.map +1 -1
  46. package/package.json +3 -2
  47. package/src/Signet.compact +78 -63
  48. package/src/circuits.compact +45 -2
@@ -29,13 +29,14 @@
29
29
  // (via SignetSigner interface) to notify the MPC of a SignBidirectionalEvent.
30
30
  // - SignatureRespondedEvent: Counterpart to SignBidirectionalEvent containing MPC generated signature requested by a SignBidirectionalEvent.
31
31
  // Posted by MPC back to Signet singleton for clients to extract to build a signed transaction.
32
- // - RespondBidirectionalEvent: Structure containing the output of a transaction executed on a foreign blockchain,
33
- // ECDSA-signed over the request id and output (see signetAttestationDigest) by the
34
- // MPC's RESPONSE key for the requesting contract: the key derived from the client
35
- // contract's address + the fixed path "midnight response key" (not the root key,
36
- // and not the request-signing key). Posted by MPC back to Signet singleton for
37
- // clients to extract, verify (see verifyRespondBidirectionalEvent) and deliver back
38
- // to the client contract.
32
+ // - RespondBidirectionalEvent: Attestation of a transaction executed on a foreign blockchain: the attestation
33
+ // digest of the request id and serialised output (see calculateSignetAttestationDigest)
34
+ // plus the MPC's ECDSA signature over that digest, signed with the MPC's RESPONSE
35
+ // key for the requesting contract: the key derived from the client contract's
36
+ // address + the fixed path "midnight response key". The output itself travels
37
+ // off chain. Posted by MPC back to Signet singleton for clients to extract,
38
+ // verify (see verifyRespondBidirectionalEvent) and deliver back to the client
39
+ // contract.
39
40
  //
40
41
  // These key types are best understood within the sign bidirectional flow that they enable:
41
42
  // 1. Client contract stores a SignBidirectionalEvent in a SignBidirectionalEventMap its own ledger
@@ -48,7 +49,7 @@
48
49
  // 3. Client polls the Signet singleton contract SignatureRespondedEventMap for the signature by RequestId,
49
50
  // verifies the posted SignatureRespondedEvent and broadcasts txn to destination chain.
50
51
  // 4. MPC observes txn execution on destination chain, posts a RespondBidirectionalEvent
51
- // attestation to the central Signet contract; the client polls it by RequestId.
52
+ // attestation to the central Signet contract, and the client polls it by RequestId.
52
53
  //
53
54
  // Read more:
54
55
  // - For full integration examples (such as an ERC20 cross chain vault example) see
@@ -170,8 +171,8 @@ module Signet {
170
171
  // (which scopes ids per contract). No domain-separation tag, deliberately.
171
172
  //
172
173
  // Hashes the field-aligned binary representation (each field re-padded to its declared
173
- // width), NOT an ABI/EIP-712 field encoding, so off-chain consumers must reproduce that
174
- // layout exactly. Type argument is the INPUT type; the return is always Bytes<32>.
174
+ // width), so off-chain consumers must reproduce that layout exactly. Type argument is
175
+ // the INPUT type, the return is always Bytes<32>.
175
176
  export pure circuit calculateRequestId<TxParams, #LenOutputDeserialization, #LenRespondSerialization>(
176
177
  request: SignBidirectionalEvent<TxParams, LenOutputDeserialization, LenRespondSerialization>
177
178
  ): RequestId {
@@ -234,13 +235,12 @@ module Signet {
234
235
  // never decompress), s big-endian, recoveryId the parity of R.y (0 or 1).
235
236
  // Deliberately identical to the Signature struct every other sig-net signer
236
237
  // contract exposes (EVM ChainSignatures.sol, the Solana signet program) and to
237
- // the MPC's own Signature { big_r, s, recovery_id } — one vocabulary across
238
- // chains. No algorithm tag is stored: the scheme is fixed by the request's own
239
- // MPCSignatureAlgorithm.
238
+ // the MPC's own Signature { big_r, s, recovery_id }: one vocabulary across
239
+ // chains. The scheme is fixed by the request's own MPCSignatureAlgorithm.
240
240
  //
241
241
  // Stored bytes are BIG-endian throughout, the canonical SEC1 order. The scalar
242
242
  // casts read little-endian, so verifyRespondBidirectionalEvent reverses on the
243
- // way in rather than making the stored record non-canonical.
243
+ // way in, keeping the stored record canonical.
244
244
  export struct Signature {
245
245
  bigR: AffinePoint;
246
246
  s: Bytes<32>;
@@ -254,29 +254,30 @@ module Signet {
254
254
  signature: Signature;
255
255
  }
256
256
 
257
- // Structure containing the output of a transaction executed on a foreign blockchain,
258
- // ECDSA-signed (secp256k1) over the attestation digest of the request id and output
259
- // (see signetAttestationDigest) by the MPC's RESPONSE key for the requesting contract:
257
+ // Attestation of a transaction executed on a foreign blockchain: the 32-byte
258
+ // attestation digest of the request id and serialised output (see
259
+ // calculateSignetAttestationDigest) plus the MPC's ECDSA (secp256k1) signature
260
+ // over that digest, signed with the MPC's RESPONSE key for the requesting contract:
260
261
  // mpcResponseKey = f(mpcRootKey[keyVersion], sender, "midnight response key")
261
262
  // i.e. the same derivation as request-signing keys but with the FIXED
262
263
  // respond-bidirectional path, so it is per-client-contract yet independent of any
263
264
  // request's own path (mirrors the MPC's per-chain "<chain> response key" convention).
264
- // Posted by MPC back to Signet singleton for clients to extract, verify and deliver
265
- // back to the client contract. The singleton stores posts UNVERIFIED: client contracts
266
- // must verify the signature themselves against their MPC response key, via
267
- // verifyRespondBidirectionalEvent.
265
+ // The execution output itself is unbounded, so it travels off chain. Posted by MPC
266
+ // back to Signet singleton for clients to extract, verify and deliver back to the
267
+ // client contract. Clients fetch the full output off chain, recompute the digest
268
+ // and verify via verifyRespondBidirectionalEvent. The singleton stores posts
269
+ // UNVERIFIED: client contracts must verify the signature themselves against their
270
+ // MPC response key.
268
271
  export struct RespondBidirectionalEvent {
269
- // ABI-encoded return data of the remote call (canonical
270
- // serialized_output), zero-padded. First 32-byte word == 1 signals
271
- // success; a 0xdeadbeef prefix is the MPC's error sentinel for a
272
- // failed/absent execution.
273
- serializedOutput: Bytes<128>;
274
-
275
- // Meaningful byte count of serializedOutput.
276
- outputLen: Uint<8>;
277
-
278
- // Signs signetAttestationDigest(requestId, serializedOutput) — outputLen above is
279
- // NOT covered. The verify circuit's r is this signature's bigR.x mod the curve order.
272
+ // The attestation digest the signature signs: keccak256 of the request id
273
+ // concatenated with the serialised output (see calculateSignetAttestationDigest).
274
+ // A checkpoint for off-chain readers: match a fetched output against the
275
+ // attestation (and check the signature) without any other inputs.
276
+ // verifyRespondBidirectionalEvent binds it to the actual output bytes in-circuit.
277
+ attestationDigest: Bytes<32>;
278
+
279
+ // The MPC's ECDSA signature over attestationDigest above. The verify
280
+ // circuit's r is this signature's bigR.x mod the curve order.
280
281
  signature: Signature;
281
282
  }
282
283
 
@@ -287,22 +288,25 @@ module Signet {
287
288
  requestId: RequestId;
288
289
  };
289
290
 
290
- // The 32-byte digest the MPC ECDSA-signs to attest a remote execution: keccak256 over
291
- // requestId (32) ++ serializedOutput (128). The output is hashed as the full zero-padded
292
- // Bytes<128>, not at its meaningful length, because Compact cannot hash a runtime-length
293
- // prefix — an off-chain signer must pad to exactly 128 before hashing.
294
- //
295
- // outputLen is NOT covered. A consumer that treats it as attested must pin it itself.
296
- //
297
- // secp256k1EcdsaVerify reads the digest as a BIG-ENDIAN integer mod the curve order
298
- // (RFC 6979). ONE implementation for everyone: off-chain signers call the compiled copy
299
- // via circuits.compact, never a TS re-implementation.
300
- export pure circuit signetAttestationDigest(
291
+ // The 32-byte attestation digest the MPC ECDSA-signs to attest a remote execution:
292
+ // keccak256 over the request id concatenated with the serialised output, exactly
293
+ // the MPC's respond-bidirectional construction (hash(request_id || serialized_output):
294
+ // one flat concatenation, one hash). The output is hashed AS GIVEN, at its exact
295
+ // width: no padding and no length binding. The width is a generic parameter, fixed
296
+ // by each client contract from its own respond serialisation schema. The digest
297
+ // commits to the output byte-for-byte: the keccak preimage of the
298
+ // [RequestId, Bytes<N>] pair is the raw concatenated bytes.
299
+ // secp256k1EcdsaVerify interprets the digest as a BIG-ENDIAN integer reduced mod
300
+ // the curve order (RFC 6979 convention). ONE implementation for everyone: client
301
+ // contracts call this in-circuit. The compiler cannot export a size-generic circuit
302
+ // top-level, so off-chain code uses the TS twin (calculateSignetAttestationDigest
303
+ // in ecdsa-attestation.ts), pinned byte-for-byte against the fixed-width
304
+ // instantiations circuits.compact exports as test oracles.
305
+ export pure circuit calculateSignetAttestationDigest<#serializedOutputLength>(
301
306
  requestId: RequestId,
302
- serializedOutput: Bytes<128>
307
+ serializedOutput: Bytes<serializedOutputLength>,
303
308
  ): Bytes<32> {
304
- const requestIdBytes = requestId as Bytes<32>;
305
- return keccak256<Bytes<160>>(Bytes[...requestIdBytes, ...serializedOutput]);
309
+ return keccak256<[RequestId, Bytes<serializedOutputLength>]>([requestId, serializedOutput]);
306
310
  }
307
311
 
308
312
  // Same Bytes -> Vector<Uint<8>> idiom numericAbiWord uses. Pulls in a byte-decomposition
@@ -316,33 +320,45 @@ module Signet {
316
320
  v[7], v[6], v[5], v[4], v[3], v[2], v[1], v[0]];
317
321
  }
318
322
 
319
- // Verify a respond-bidirectional attestation against a secp256k1 public key: the
320
- // ECDSA signature must sign the attestation digest of (requestId, output). This is
321
- // the check client contracts run before accepting a response (the Signet singleton
322
- // stores posts unverified); pass the contract's MPC RESPONSE key (derived off-chain
323
- // from the contract's address + the fixed path "midnight response key", typically
324
- // hash-pinned in the contract's ledger at deploy time).
323
+ // Verify a RespondBidirectionalEvent against the serialised output it attests and
324
+ // a secp256k1 public key: the event's digest checkpoint must equal the recomputed
325
+ // attestation digest of (requestId, serializedOutput), and the event's ECDSA
326
+ // signature must sign that digest. This is the check client contracts run before
327
+ // accepting a response (the Signet singleton stores posts unverified). Pass the
328
+ // contract's MPC RESPONSE key (derived off-chain from the contract's address + the
329
+ // fixed path "midnight response key", typically hash-pinned in the contract's
330
+ // ledger at deploy time). The caller supplies serializedOutput at its exact
331
+ // unpadded width, per calculateSignetAttestationDigest.
325
332
  //
326
333
  // Takes the event as stored: the caller hands over the record it read, and the
327
- // big-endian-to-little-endian re-encoding happens here instead of in every
334
+ // big-endian-to-little-endian re-encoding happens here, in one place for every
328
335
  // integrator.
329
336
  //
330
- // ECDSA's r is bigR.x reduced mod the curve order; bigR.x is taken as-is because it
331
- // is already below the order for all but a ~2^-128 fraction of signatures, and one
337
+ // ECDSA's r is bigR.x reduced mod the curve order. bigR.x is taken as-is: it is
338
+ // already below the order for all but a ~2^-128 fraction of signatures, and one
332
339
  // that is not simply fails to verify.
333
- export pure circuit verifyRespondBidirectionalEvent(
340
+ export pure circuit verifyRespondBidirectionalEvent<#serializedOutputLength>(
334
341
  requestId: RequestId,
342
+ serializedOutput: Bytes<serializedOutputLength>,
335
343
  respondBidirectionalEvent: RespondBidirectionalEvent,
336
344
  mpcResponseKey: Secp256k1Point
337
345
  ): Boolean {
338
- return secp256k1EcdsaVerify(
339
- signetAttestationDigest(requestId, respondBidirectionalEvent.serializedOutput),
346
+ const digest = calculateSignetAttestationDigest<serializedOutputLength>(
347
+ requestId,
348
+ serializedOutput,
349
+ );
350
+ // Two consts rather than one `&&` expression: compactc 0.33 ICEs when the
351
+ // secp256k1EcdsaVerify call (its scalar casts) sits inside a subexpression.
352
+ const checkpointMatches = digest == respondBidirectionalEvent.attestationDigest;
353
+ const signatureValid = secp256k1EcdsaVerify(
354
+ digest,
340
355
  Secp256k1EcdsaSignature {
341
356
  r: reverseBytes32(respondBidirectionalEvent.signature.bigR.x) as Secp256k1Scalar,
342
357
  s: reverseBytes32(respondBidirectionalEvent.signature.s) as Secp256k1Scalar,
343
358
  },
344
359
  mpcResponseKey
345
360
  );
361
+ return checkpointMatches && signatureValid;
346
362
  }
347
363
 
348
364
  // Hash a secp256k1 public key exactly as client contracts pin it at deploy time
@@ -356,17 +372,16 @@ module Signet {
356
372
 
357
373
  // The EVM transaction to be signed, decomposed into typed fields
358
374
  // (EIP-1559 / type-2: https://eips.ethereum.org/EIPS/eip-1559).
359
- // Decomposed rather than pre-serialized so the requesting contract can
360
- // enforce invariants field by field; the MPC re-assembles and RLP-encodes
361
- // off-chain. (The MPC's chain-agnostic primitive carries an opaque
362
- // serialized_transaction instead; the decomposition follows its Canton
375
+ // Decomposed so the requesting contract can enforce invariants field by
376
+ // field, and the MPC re-assembles and RLP-encodes off-chain. (The MPC's chain-agnostic primitive carries an opaque
377
+ // serialized_transaction instead, and the decomposition follows its Canton
363
378
  // adapter's EvmType2TransactionParams.)
364
379
  //
365
380
  // Compact has no runtime-dynamic sizes, so every variable-length EVM tx
366
381
  // part follows one scheme: a compile-time CAPACITY (a generic size
367
382
  // parameter each requesting contract fixes as its throttle) plus a runtime
368
383
  // count that says how many leading slots are real. Off-chain consumers use
369
- // the counts; unused capacity is hashed as part of the record (zero-fill by
384
+ // the counts. Unused capacity is hashed as part of the record (zero-fill by
370
385
  // convention) but never reaches the assembled transaction.
371
386
  // Canonical EIP-1559 payload order, which is the RLP order the MPC re-assembles into.
372
387
  // Each count sits directly before the slot it bounds. This order is also the
@@ -12,8 +12,6 @@ import CompactStandardLibrary;
12
12
  import "./Signet";
13
13
 
14
14
  export {
15
- signetAttestationDigest,
16
- verifyRespondBidirectionalEvent,
17
15
  signetKeyHash,
18
16
  constructSignBidirectionalEventNotificationV1,
19
17
  evmAddressAbiWord,
@@ -22,3 +20,48 @@ export {
22
20
  abiWordToUint128,
23
21
  abiWordToBool
24
22
  };
23
+
24
+ // ---- 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.
33
+
34
+ export pure circuit calculateSignetAttestationDigest1(
35
+ requestId: RequestId,
36
+ serializedOutput: Bytes<1>
37
+ ): Bytes<32> {
38
+ return calculateSignetAttestationDigest<1>(requestId, serializedOutput);
39
+ }
40
+
41
+ export pure circuit calculateSignetAttestationDigest32(
42
+ requestId: RequestId,
43
+ serializedOutput: Bytes<32>
44
+ ): Bytes<32> {
45
+ return calculateSignetAttestationDigest<32>(requestId, serializedOutput);
46
+ }
47
+
48
+ export pure circuit calculateSignetAttestationDigest100(
49
+ requestId: RequestId,
50
+ serializedOutput: Bytes<100>
51
+ ): Bytes<32> {
52
+ return calculateSignetAttestationDigest<100>(requestId, serializedOutput);
53
+ }
54
+
55
+ export pure circuit verifyRespondBidirectionalEvent32(
56
+ requestId: RequestId,
57
+ serializedOutput: Bytes<32>,
58
+ respondBidirectionalEvent: RespondBidirectionalEvent,
59
+ mpcResponseKey: Secp256k1Point
60
+ ): Boolean {
61
+ return verifyRespondBidirectionalEvent<32>(
62
+ requestId,
63
+ serializedOutput,
64
+ respondBidirectionalEvent,
65
+ mpcResponseKey
66
+ );
67
+ }