@sig-net/midnight 0.24.1 → 0.25.0-rc.2

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 (39) hide show
  1. package/README.md +6 -5
  2. package/dist/compact-descriptors.d.ts +5 -7
  3. package/dist/compact-descriptors.d.ts.map +1 -1
  4. package/dist/compact-descriptors.js +6 -18
  5. package/dist/compact-descriptors.js.map +1 -1
  6. package/dist/constants.d.ts +5 -2
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +5 -2
  9. package/dist/constants.js.map +1 -1
  10. package/dist/ecdsa-attestation.d.ts +54 -11
  11. package/dist/ecdsa-attestation.d.ts.map +1 -1
  12. package/dist/ecdsa-attestation.js +90 -39
  13. package/dist/ecdsa-attestation.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/managed/compiler/contract-info.json +251 -143
  19. package/dist/managed/compiler/contract-manifest.json +8 -8
  20. package/dist/managed/contract/index.d.ts +75 -46
  21. package/dist/managed/contract/index.js +604 -585
  22. package/dist/managed/contract/index.js.map +1 -1
  23. package/dist/mpc-output-cache.js +2 -2
  24. package/dist/mpc-output-cache.js.map +1 -1
  25. package/dist/signet-contract-events.d.ts +15 -8
  26. package/dist/signet-contract-events.d.ts.map +1 -1
  27. package/dist/signet-contract-events.js +7 -4
  28. package/dist/signet-contract-events.js.map +1 -1
  29. package/dist/signet-evtype2tx-record-decoding.js +2 -2
  30. package/dist/signet-request-response-reader.d.ts.map +1 -1
  31. package/dist/signet-request-response-reader.js +12 -4
  32. package/dist/signet-request-response-reader.js.map +1 -1
  33. package/dist/signet-requests.d.ts +2 -2
  34. package/dist/signet-requests.js +2 -2
  35. package/dist/testing.js +1 -1
  36. package/dist/testing.js.map +1 -1
  37. package/package.json +2 -2
  38. package/src/Signet.compact +109 -45
  39. package/src/circuits.compact +40 -32
@@ -76,6 +76,7 @@ module Signet {
76
76
  evmType2TxWord,
77
77
  evmType2TxAccessEntry,
78
78
  evmType2TxStorageKey,
79
+ attestedOutput,
79
80
  }
80
81
 
81
82
  // ---- SignBidirectionalEvent ------------------------------------------------------------------------
@@ -111,8 +112,9 @@ module Signet {
111
112
  // Target chain transaction parameters.
112
113
  txParamType: TxParamType; // Off-chain tag indicating what type txParams is set to.
113
114
  txParams: TxParams; // The generic transaction-param payload
114
- executionDest: Bytes<32>; // The MPC's routing key for the target chain, in CAIP-2 form
115
- // (https://chainagnostic.org/CAIPs/caip-2). An Ethereum target
115
+ executionDest: Bytes<64>; // The MPC's routing key for the target chain, in CAIP-2 form
116
+ // (https://chainagnostic.org/CAIPs/caip-2), zero-padded: 64 bytes
117
+ // hold any CAIP-2 id (at most 41). An Ethereum target
116
118
  // (mainnet, Sepolia or a local anvil) uses ethereumCaip2Id():
117
119
  // the network itself is txParams' chainId.
118
120
 
@@ -169,7 +171,7 @@ module Signet {
169
171
  algo: MPCSignatureAlgorithm,
170
172
  txParamType: TxParamType,
171
173
  txParams: TxParams,
172
- executionDest: Bytes<32>,
174
+ executionDest: Bytes<64>,
173
175
  signatureDest: MPCDestination,
174
176
  params: Bytes<64>,
175
177
  outputDeserializationSchema: Bytes<LenOutputDeserialization>,
@@ -221,7 +223,7 @@ module Signet {
221
223
  algo: MPCSignatureAlgorithm;
222
224
  txParamType: TxParamType;
223
225
  txParamsDigest: Bytes<32>;
224
- executionDest: Bytes<32>;
226
+ executionDest: Bytes<64>;
225
227
  }
226
228
 
227
229
  // Off-chain recomputation must prefix the preimage with HashDomain.requestId
@@ -333,7 +335,8 @@ module Signet {
333
335
  // RespondBidirectionalEvent: the MPC's attestation of a foreign execution, the
334
336
  // final step of the sign bidirectional flow.
335
337
  //
336
- // attestationDigest = calculateSignetAttestationDigestV1(requestId, blockHeight, outputKind, serializedOutput)
338
+ // outputHash = calculateAttestedOutputHashV1(serializedOutput)
339
+ // attestationDigest = calculateSignetAttestationDigestV1(requestId, blockHeight, outputKind, serializedOutputLength, outputHash)
337
340
  // signature = signSECP256k1(mpcResponseKey, attestationDigest)
338
341
  //
339
342
  // mpcResponseKey is derived like request signing keys, with the path set to the
@@ -341,10 +344,12 @@ module Signet {
341
344
  // verbatim, unlike a request's hex-encoded 32 path bytes):
342
345
  // mpcResponseKey = f(mpcRootKey[keyVersion], caip2ChainId, sender, "midnight response key")
343
346
  //
344
- // The output travels off chain and the digest is recomputed at verification
345
- // time (verifyRespondBidirectionalEventV1). The signature is the ONLY thing that
346
- // distinguishes a genuine post from garbage: the singleton emits every post
347
- // unverified.
347
+ // The output travels off chain. The event carries everything the digest is
348
+ // computed from, so its signature checks without the output
349
+ // (verifyRespondBidirectionalAttestationV1), and the output checks against the
350
+ // event's length and hash (verifyAttestedOutputV1). The signature
351
+ // is the ONLY thing that distinguishes a genuine post from garbage: the
352
+ // singleton emits every post unverified.
348
353
  export struct RespondBidirectionalEventV1 {
349
354
  // The request this attestation answers. Signed into the digest: a verified
350
355
  // post settles exactly the request it names, so a client consumes this id
@@ -356,13 +361,17 @@ module Signet {
356
361
  blockHeight: Uint<64>;
357
362
  // The MPC's verdict on the execution, signed into the digest.
358
363
  outputKind: OutputKind;
359
- // Byte width of the serialised output the digest commits to (0 for a failed
360
- // or unviable execution), so a client holding no output yet knows which
361
- // width to settle at.
364
+ // Byte width of the serialised output (0 for a failed or unviable
365
+ // execution), signed into the digest. The output hash does not commit to
366
+ // the width, so this does: a verifier checks it against the width of the
367
+ // output it was handed.
362
368
  serializedOutputLength: Uint<64>;
369
+ // calculateAttestedOutputHashV1 of the serialised output, signed into the
370
+ // digest.
371
+ outputHash: Bytes<32>;
363
372
  // The attestation digest the signature is over (calculateSignetAttestationDigestV1),
364
- // so a reader holding no output can check the signature. The verifying
365
- // circuit recomputes it from the output and never reads this copy.
373
+ // for off-chain readers. The verifying circuits recompute it from the
374
+ // fields above and never read this copy.
366
375
  digest: Bytes<32>;
367
376
  // The MPC's ECDSA signature over the attestation digest.
368
377
  signature: Signature;
@@ -377,49 +386,64 @@ module Signet {
377
386
  unviable, // a finalised transaction of another request from the same account took the nonce: the attested output is empty (Bytes<0>)
378
387
  }
379
388
 
380
- // The 32-byte attestation digest of a remote execution output:
381
- // attestationDigest = upgradeFromTransient(transientHash([HashDomain.attestationDigest, requestId, blockHeight, outputKind, serializedOutputLength, serializedOutput]))
389
+ // The 32-byte hash of a serialised execution output that the attestation
390
+ // digest commits to:
391
+ // outputHash = upgradeFromTransient(transientHash([HashDomain.attestedOutput, serializedOutput]))
392
+ // The hash packs the bytes into field elements without their width, so
393
+ // outputs that differ only in trailing zero bytes hash alike: the width is
394
+ // committed separately, as serializedOutputLength. Off-chain code uses the
395
+ // TypeScript twin:
396
+ // import { calculateAttestedOutputHash } from "@sig-net/midnight";
397
+ export pure circuit calculateAttestedOutputHashV1<#serializedOutputLength>(
398
+ serializedOutput: Bytes<serializedOutputLength>,
399
+ ): Bytes<32> {
400
+ return upgradeFromTransient(transientHash<[HashDomain, Bytes<serializedOutputLength>]>([HashDomain.attestedOutput, serializedOutput]));
401
+ }
402
+
403
+ // The 32-byte attestation digest the MPC signs:
404
+ // attestationDigest = upgradeFromTransient(transientHash([HashDomain.attestationDigest, requestId, blockHeight, outputKind, serializedOutputLength, outputHash]))
382
405
  // blockHeight is the height of the finalised target-chain block holding the
383
406
  // transaction the attestation describes, in the target chain's own
384
- // numbering (a slot on Solana).
385
- // Off-chain code uses the TypeScript twin:
386
- // import { calculateSignetAttestationDigest } from "@sig-net/midnight/testing";
387
- export pure circuit calculateSignetAttestationDigestV1<#serializedOutputLength>(
407
+ // numbering (a slot on Solana). Every input is fixed-width, so the digest of
408
+ // any output is computed by this one circuit.
409
+ export pure circuit calculateSignetAttestationDigestV1(
388
410
  requestId: RequestId,
389
411
  blockHeight: Uint<64>,
390
412
  outputKind: OutputKind,
391
- serializedOutput: Bytes<serializedOutputLength>,
413
+ serializedOutputLength: Uint<64>,
414
+ outputHash: Bytes<32>,
392
415
  ): Bytes<32> {
393
- return upgradeFromTransient(transientHash<[HashDomain, RequestId, Uint<64>, OutputKind, Uint<64>, Bytes<serializedOutputLength>]>([HashDomain.attestationDigest, requestId, blockHeight, outputKind, serializedOutputLength, serializedOutput]));
416
+ return upgradeFromTransient(transientHash<[HashDomain, RequestId, Uint<64>, OutputKind, Uint<64>, Bytes<32>]>([HashDomain.attestationDigest, requestId, blockHeight, outputKind, serializedOutputLength, outputHash]));
394
417
  }
395
418
 
396
- // Verifies a RespondBidirectionalEvent against its execution output and MPC
397
- // response key: recomputes the digest over the request id, block height,
398
- // output kind, output length and caller's output, then verifies its signature. Client
399
- // contracts run this before consuming outputs, as the singleton emits
400
- // responses unverified. The caller supplies
401
- // serializedOutput at its exact unpadded width, and the event record with
402
- // signature.bigR.x and signature.s byte-reversed into LITTLE-endian: the
403
- // scalar casts below read little-endian bytes, and the wire/ledger records
404
- // are big-endian, so the reversal happens off-chain where it costs nothing
405
- // (the TS SDK's respondBidirectionalEventToCircuitInput does it). A wrong
406
- // byte order fails verification, never a false accept. Of the event, requestId,
407
- // blockHeight, outputKind, signature.bigR.x and signature.s enter verification:
408
- // serializedOutputLength and digest serve off-chain readers. The digest binds
409
- // the event's requestId, so a client MUST consume that request and no other. An s
410
- // that reduces to 0 mod the curve order aborts the
411
- // circuit rather than returning false, so sift candidate posts off chain
412
- // first (verifyRespondBidirectionalSignature).
413
- export pure circuit verifyRespondBidirectionalEventV1<#serializedOutputLength>(
414
- serializedOutput: Bytes<serializedOutputLength>,
419
+ // Verifies a RespondBidirectionalEvent's signature against the MPC response
420
+ // key, without the output: recomputes the digest from the event's request
421
+ // id, block height, output kind, output length and output hash. The event's
422
+ // digest field is never read. A true result proves the MPC attested this
423
+ // outcome for this request, and an output the event carries only as its
424
+ // length and hash: a client that consumes output bytes checks them against
425
+ // that length and hash with verifyAttestedOutputV1, or verifies with
426
+ // verifyRespondBidirectionalEventV1, which does both.
427
+ // The caller supplies the event record with signature.bigR.x and
428
+ // signature.s byte-reversed into LITTLE-endian: the scalar casts below read
429
+ // little-endian bytes, and the wire/ledger records are big-endian, so the
430
+ // reversal happens off-chain where it costs nothing (the TS SDK's
431
+ // respondBidirectionalEventToCircuitInput does it). A wrong byte order fails
432
+ // verification, never a false accept. The digest binds the event's
433
+ // requestId, so a client MUST consume that request and no other. An s that
434
+ // reduces to 0 mod the curve order aborts the circuit rather than returning
435
+ // false, so sift candidate posts off chain first
436
+ // (verifyRespondBidirectionalAttestation).
437
+ export pure circuit verifyRespondBidirectionalAttestationV1(
415
438
  respondBidirectionalEvent: RespondBidirectionalEventV1,
416
439
  mpcResponseKey: Secp256k1Point
417
440
  ): Boolean {
418
- const digest = calculateSignetAttestationDigestV1<serializedOutputLength>(
441
+ const digest = calculateSignetAttestationDigestV1(
419
442
  respondBidirectionalEvent.requestId,
420
443
  respondBidirectionalEvent.blockHeight,
421
444
  respondBidirectionalEvent.outputKind,
422
- serializedOutput,
445
+ respondBidirectionalEvent.serializedOutputLength,
446
+ respondBidirectionalEvent.outputHash,
423
447
  );
424
448
 
425
449
  return secp256k1EcdsaVerify(
@@ -432,6 +456,46 @@ module Signet {
432
456
  );
433
457
  }
434
458
 
459
+ // Whether serializedOutput is the output an attestation declares: its width
460
+ // is attestedOutputLength and its hash attestedOutputHash. Both checks are
461
+ // needed, as the hash alone does not commit to the width (see
462
+ // calculateAttestedOutputHashV1). Proves nothing about the attestation
463
+ // itself: the length and hash must come from a RespondBidirectionalEvent that
464
+ // passed verifyRespondBidirectionalAttestationV1, read from the event or
465
+ // from a copy the contract stored when it verified the event.
466
+ export pure circuit verifyAttestedOutputV1<#serializedOutputLength>(
467
+ serializedOutput: Bytes<serializedOutputLength>,
468
+ attestedOutputLength: Uint<64>,
469
+ attestedOutputHash: Bytes<32>
470
+ ): Boolean {
471
+ const lengthMatches = attestedOutputLength == serializedOutputLength;
472
+ const hashMatches = attestedOutputHash == calculateAttestedOutputHashV1<serializedOutputLength>(serializedOutput);
473
+ return lengthMatches && hashMatches;
474
+ }
475
+
476
+ // Verifies a RespondBidirectionalEvent against its execution output and MPC
477
+ // response key: the output is the one the event attests (verifyAttestedOutputV1
478
+ // against the event's length and hash) and the event's signature verifies
479
+ // (verifyRespondBidirectionalAttestationV1, whose byte-order and abort
480
+ // contract applies here too). Client contracts run this before consuming
481
+ // outputs, as the singleton emits responses unverified. The caller supplies
482
+ // serializedOutput at its exact unpadded width.
483
+ export pure circuit verifyRespondBidirectionalEventV1<#serializedOutputLength>(
484
+ serializedOutput: Bytes<serializedOutputLength>,
485
+ respondBidirectionalEvent: RespondBidirectionalEventV1,
486
+ mpcResponseKey: Secp256k1Point
487
+ ): Boolean {
488
+ // Both checks run unconditionally: the signature check's scalar casts
489
+ // cannot sit in the short-circuited operand of &&.
490
+ const outputMatches = verifyAttestedOutputV1<serializedOutputLength>(
491
+ serializedOutput,
492
+ respondBidirectionalEvent.serializedOutputLength,
493
+ respondBidirectionalEvent.outputHash
494
+ );
495
+ const attested = verifyRespondBidirectionalAttestationV1(respondBidirectionalEvent, mpcResponseKey);
496
+ return outputMatches && attested;
497
+ }
498
+
435
499
  // ---- EVM Type2 Transaction Parameters --------------------------------------------------------------
436
500
 
437
501
  // The caip2Id of every request targeting Ethereum, whichever Ethereum network the
@@ -441,8 +505,8 @@ module Signet {
441
505
  // the network's own CAIP-2 id included. EvmType2TxParams.chainId is what binds
442
506
  // the signed transaction to one network. Ethereum only: another EVM chain, such as
443
507
  // BNB Smart Chain, is a separate chain to the MPC, never one of Ethereum's networks.
444
- export pure circuit ethereumCaip2Id(): Bytes<32> {
445
- return pad(32, "eip155:1");
508
+ export pure circuit ethereumCaip2Id(): Bytes<64> {
509
+ return pad(64, "eip155:1");
446
510
  }
447
511
 
448
512
  // The EVM transaction to be signed, decomposed into typed fields
@@ -12,6 +12,8 @@ export {
12
12
  HashDomain,
13
13
  OutputKind,
14
14
  calculateRequestIdV1,
15
+ calculateSignetAttestationDigestV1,
16
+ verifyRespondBidirectionalAttestationV1,
15
17
  constructSignBidirectionalEventNotificationV1,
16
18
  ethereumCaip2Id,
17
19
  evmAddressAbiWord,
@@ -22,56 +24,62 @@ export {
22
24
  checkedTruncationU128
23
25
  };
24
26
 
25
- // ---- Fixed-width attestation oracles (tests only) --------------------------
26
- // The size-generic digest and verify circuits cannot appear in the export
27
- // list above. These fixed-width instantiations exist solely to pin the TS
28
- // twin in ecdsa-attestation.ts against the compiled circuits (widths 0, 1, 2,
29
- // 32 and 100, where 0 is the empty output of a failed or unviable execution).
30
- // Client contracts instantiate the generic circuits at their own width.
31
-
32
- export pure circuit calculateSignetAttestationDigest0(
33
- requestId: RequestId,
34
- blockHeight: Uint<64>,
35
- outputKind: OutputKind,
27
+ // ---- Fixed-width output hash oracles (tests only) --------------------------
28
+ // The size-generic output hash and verify circuits cannot appear in the
29
+ // export list above. These fixed-width instantiations exist solely to pin
30
+ // the TS twin in ecdsa-attestation.ts against the compiled circuits (widths
31
+ // 0, 1, 2, 32 and 100, where 0 is the empty output of a failed or unviable
32
+ // execution). Client contracts instantiate the generic circuits at their own
33
+ // width.
34
+
35
+ export pure circuit calculateAttestedOutputHash0(
36
36
  serializedOutput: Bytes<0>
37
37
  ): Bytes<32> {
38
- return calculateSignetAttestationDigestV1<0>(requestId, blockHeight, outputKind, serializedOutput);
38
+ return calculateAttestedOutputHashV1<0>(serializedOutput);
39
39
  }
40
40
 
41
- export pure circuit calculateSignetAttestationDigest1(
42
- requestId: RequestId,
43
- blockHeight: Uint<64>,
44
- outputKind: OutputKind,
41
+ export pure circuit calculateAttestedOutputHash1(
45
42
  serializedOutput: Bytes<1>
46
43
  ): Bytes<32> {
47
- return calculateSignetAttestationDigestV1<1>(requestId, blockHeight, outputKind, serializedOutput);
44
+ return calculateAttestedOutputHashV1<1>(serializedOutput);
48
45
  }
49
46
 
50
- export pure circuit calculateSignetAttestationDigest2(
51
- requestId: RequestId,
52
- blockHeight: Uint<64>,
53
- outputKind: OutputKind,
47
+ export pure circuit calculateAttestedOutputHash2(
54
48
  serializedOutput: Bytes<2>
55
49
  ): Bytes<32> {
56
- return calculateSignetAttestationDigestV1<2>(requestId, blockHeight, outputKind, serializedOutput);
50
+ return calculateAttestedOutputHashV1<2>(serializedOutput);
57
51
  }
58
52
 
59
- export pure circuit calculateSignetAttestationDigest32(
60
- requestId: RequestId,
61
- blockHeight: Uint<64>,
62
- outputKind: OutputKind,
53
+ export pure circuit calculateAttestedOutputHash32(
63
54
  serializedOutput: Bytes<32>
64
55
  ): Bytes<32> {
65
- return calculateSignetAttestationDigestV1<32>(requestId, blockHeight, outputKind, serializedOutput);
56
+ return calculateAttestedOutputHashV1<32>(serializedOutput);
66
57
  }
67
58
 
68
- export pure circuit calculateSignetAttestationDigest100(
69
- requestId: RequestId,
70
- blockHeight: Uint<64>,
71
- outputKind: OutputKind,
59
+ export pure circuit calculateAttestedOutputHash100(
72
60
  serializedOutput: Bytes<100>
73
61
  ): Bytes<32> {
74
- return calculateSignetAttestationDigestV1<100>(requestId, blockHeight, outputKind, serializedOutput);
62
+ return calculateAttestedOutputHashV1<100>(serializedOutput);
63
+ }
64
+
65
+ // Fixed-width instantiations of verifyAttestedOutputV1, pinning its TS twin
66
+ // verifyAttestedOutput, including the trailing-zero case the length check
67
+ // exists for (a 2-byte output against a 1-byte attestation).
68
+
69
+ export pure circuit verifyAttestedOutput1(
70
+ serializedOutput: Bytes<1>,
71
+ attestedOutputLength: Uint<64>,
72
+ attestedOutputHash: Bytes<32>
73
+ ): Boolean {
74
+ return verifyAttestedOutputV1<1>(serializedOutput, attestedOutputLength, attestedOutputHash);
75
+ }
76
+
77
+ export pure circuit verifyAttestedOutput2(
78
+ serializedOutput: Bytes<2>,
79
+ attestedOutputLength: Uint<64>,
80
+ attestedOutputHash: Bytes<32>
81
+ ): Boolean {
82
+ return verifyAttestedOutputV1<2>(serializedOutput, attestedOutputLength, attestedOutputHash);
75
83
  }
76
84
 
77
85
  // ---- Fixed-width transaction digest oracles (tests only) --------------------