@sig-net/midnight 0.19.0 → 0.20.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.
- package/README.md +42 -2
- package/dist/abi-serde.d.ts +32 -30
- package/dist/abi-serde.d.ts.map +1 -1
- package/dist/abi-serde.js +139 -75
- package/dist/abi-serde.js.map +1 -1
- package/dist/byte-codecs.d.ts +62 -0
- package/dist/byte-codecs.d.ts.map +1 -0
- package/dist/byte-codecs.js +116 -0
- package/dist/byte-codecs.js.map +1 -0
- package/dist/compact-descriptors.d.ts +89 -0
- package/dist/compact-descriptors.d.ts.map +1 -0
- package/dist/compact-descriptors.js +139 -0
- package/dist/compact-descriptors.js.map +1 -0
- package/dist/constants.d.ts +18 -43
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +19 -45
- package/dist/constants.js.map +1 -1
- package/dist/ecdsa-attestation.d.ts +49 -97
- package/dist/ecdsa-attestation.d.ts.map +1 -1
- package/dist/ecdsa-attestation.js +78 -142
- package/dist/ecdsa-attestation.js.map +1 -1
- package/dist/epsilon-derivation.d.ts +24 -31
- package/dist/epsilon-derivation.d.ts.map +1 -1
- package/dist/epsilon-derivation.js +52 -62
- package/dist/epsilon-derivation.js.map +1 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -8
- package/dist/index.js.map +1 -1
- package/dist/managed/compiler/contract-info.json +0 -22
- package/dist/managed/compiler/contract-manifest.json +8 -8
- package/dist/managed/contract/index.d.ts +0 -3
- package/dist/managed/contract/index.js +65 -80
- package/dist/managed/contract/index.js.map +1 -1
- package/dist/raw-contract-state.d.ts +25 -0
- package/dist/raw-contract-state.d.ts.map +1 -0
- package/dist/raw-contract-state.js +52 -0
- package/dist/raw-contract-state.js.map +1 -0
- package/dist/signature-requests-state-reader.d.ts +18 -44
- package/dist/signature-requests-state-reader.d.ts.map +1 -1
- package/dist/signature-requests-state-reader.js +82 -146
- package/dist/signature-requests-state-reader.js.map +1 -1
- package/dist/signature-response-verification.d.ts +9 -8
- package/dist/signature-response-verification.d.ts.map +1 -1
- package/dist/signature-response-verification.js +20 -17
- package/dist/signature-response-verification.js.map +1 -1
- package/dist/signet-contract-events.d.ts +56 -64
- package/dist/signet-contract-events.d.ts.map +1 -1
- package/dist/signet-contract-events.js +46 -48
- package/dist/signet-contract-events.js.map +1 -1
- package/dist/signet-evtype2tx-record-decoding.d.ts +18 -0
- package/dist/signet-evtype2tx-record-decoding.d.ts.map +1 -0
- package/dist/signet-evtype2tx-record-decoding.js +97 -0
- package/dist/signet-evtype2tx-record-decoding.js.map +1 -0
- package/dist/signet-evtype2tx-requests.d.ts +43 -87
- package/dist/signet-evtype2tx-requests.d.ts.map +1 -1
- package/dist/signet-evtype2tx-requests.js +59 -130
- package/dist/signet-evtype2tx-requests.js.map +1 -1
- package/dist/signet-request-id.d.ts +14 -0
- package/dist/signet-request-id.d.ts.map +1 -0
- package/dist/signet-request-id.js +27 -0
- package/dist/signet-request-id.js.map +1 -0
- package/dist/signet-request-response-reader.d.ts +34 -56
- package/dist/signet-request-response-reader.d.ts.map +1 -1
- package/dist/signet-request-response-reader.js +56 -68
- package/dist/signet-request-response-reader.js.map +1 -1
- package/dist/signet-requests.d.ts +28 -92
- package/dist/signet-requests.d.ts.map +1 -1
- package/dist/signet-requests.js +39 -141
- package/dist/signet-requests.js.map +1 -1
- package/dist/testing.d.ts +3 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +20 -0
- package/dist/testing.js.map +1 -0
- package/package.json +12 -3
- package/src/Signet.compact +110 -171
- package/src/circuits.compact +9 -16
- package/dist/signature-state-reading.d.ts +0 -43
- package/dist/signature-state-reading.d.ts.map +0 -1
- package/dist/signature-state-reading.js +0 -74
- package/dist/signature-state-reading.js.map +0 -1
- package/dist/signet-request-feed.d.ts +0 -126
- package/dist/signet-request-feed.d.ts.map +0 -1
- package/dist/signet-request-feed.js +0 -216
- package/dist/signet-request-feed.js.map +0 -1
|
@@ -1,104 +1,32 @@
|
|
|
1
|
-
// secp256k1 ECDSA helpers: the TS side of
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// scalar bytes Compact's casts read, and the attestation digest's TS twin.
|
|
1
|
+
// secp256k1 ECDSA helpers: the TS side of Signet.compact's respond flows:
|
|
2
|
+
// SIGNING (which needs the secret scalar, so it cannot be a circuit), key
|
|
3
|
+
// parsing/formatting, the signature-record codecs both respond events
|
|
4
|
+
// share, and the attestation digest's TS twin.
|
|
6
5
|
// Everything provable stays in Compact where possible: in-circuit
|
|
7
|
-
// verification is `verifyRespondBidirectionalEvent
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// verification is `verifyRespondBidirectionalEvent`. The digest circuit is
|
|
7
|
+
// size-generic and the compiler cannot export size-generic circuits
|
|
8
|
+
// top-level, so the digest is
|
|
10
9
|
// the ONE sanctioned TS twin here, pinned byte-for-byte against the
|
|
11
10
|
// fixed-width oracle circuits circuits.compact exports (see
|
|
12
11
|
// tests/ecdsa-attestation.test.ts).
|
|
13
12
|
//
|
|
14
13
|
// This belongs in github.com/sig-net/signet.js as its Midnight adapter,
|
|
15
14
|
// kept here until upstreamed.
|
|
16
|
-
import { ethers } from "ethers";
|
|
17
15
|
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
16
|
+
import { ethers, Signature, toBeHex } from "ethers";
|
|
17
|
+
import { bigintToBytes32BE, bytesToBigintBE, stripHexPrefix } from "./byte-codecs.js";
|
|
18
18
|
/** secp256k1 curve (group) order n. */
|
|
19
19
|
export const SECP256K1_ORDER = secp256k1.Point.Fn.ORDER;
|
|
20
|
-
/** BLS12-381 scalar field order (the Compact `Field` type modulus). */
|
|
21
|
-
export const BLS_ORDER = 52435875175126190479447740508185965837690552500527637822603658699938581184513n;
|
|
22
|
-
/**
|
|
23
|
-
* Convert little-endian bytes to a bigint. Matches Compact's
|
|
24
|
-
* `Bytes<32> as Field` / `Bytes<32> as Secp256k1Scalar` interpretation (both
|
|
25
|
-
* casts are little-endian).
|
|
26
|
-
*
|
|
27
|
-
* @param bytes - Little-endian byte array.
|
|
28
|
-
* @returns The decoded non-negative integer.
|
|
29
|
-
*/
|
|
30
|
-
export function bytesToBigint(bytes) {
|
|
31
|
-
let result = 0n;
|
|
32
|
-
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
33
|
-
// The index is in range by construction: the assertion satisfies
|
|
34
|
-
// consumers compiling with noUncheckedIndexedAccess.
|
|
35
|
-
result = (result << 8n) | BigInt(bytes[i]);
|
|
36
|
-
}
|
|
37
|
-
return result;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Convert a bigint to exactly 32 little-endian bytes. Matches Compact's
|
|
41
|
-
* `Field as Bytes<32>` / `Secp256k1Scalar as Bytes<32>` encoding (the inverse
|
|
42
|
-
* of {@link bytesToBigint}). Negative inputs are interpreted in the BLS
|
|
43
|
-
* scalar field.
|
|
44
|
-
*
|
|
45
|
-
* @param n - The integer to encode.
|
|
46
|
-
* @returns The 32-byte little-endian encoding.
|
|
47
|
-
*/
|
|
48
|
-
export function bigintToBytes32(n) {
|
|
49
|
-
const buf = new Uint8Array(32);
|
|
50
|
-
let v = n < 0n ? n + BLS_ORDER : n;
|
|
51
|
-
for (let i = 0; i < 32; i++) {
|
|
52
|
-
buf[i] = Number(v & 0xffn);
|
|
53
|
-
v >>= 8n;
|
|
54
|
-
}
|
|
55
|
-
return buf;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Convert big-endian bytes to a bigint (SEC1 coordinate order, the byte
|
|
59
|
-
* order the respond events' stored signature uses).
|
|
60
|
-
*
|
|
61
|
-
* @param bytes - Big-endian byte array.
|
|
62
|
-
* @returns The decoded non-negative integer.
|
|
63
|
-
*/
|
|
64
|
-
export function bytesToBigintBE(bytes) {
|
|
65
|
-
let value = 0n;
|
|
66
|
-
for (const byte of bytes) {
|
|
67
|
-
value = (value << 8n) | BigInt(byte);
|
|
68
|
-
}
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Convert a bigint to exactly 32 BIG-endian bytes (SEC1 scalar/coordinate
|
|
73
|
-
* order, also the ABI word encoding): the big-endian counterpart of
|
|
74
|
-
* {@link bigintToBytes32} and the inverse of {@link bytesToBigintBE}.
|
|
75
|
-
*
|
|
76
|
-
* @param value - The non-negative integer to encode.
|
|
77
|
-
* @returns The 32-byte big-endian encoding.
|
|
78
|
-
* @throws Error if the value is negative or does not fit 32 bytes.
|
|
79
|
-
*/
|
|
80
|
-
export function bigintToBytes32BE(value) {
|
|
81
|
-
if (value < 0n || value >= 1n << 256n) {
|
|
82
|
-
throw new Error(`value does not fit 32 big-endian bytes: ${value}`);
|
|
83
|
-
}
|
|
84
|
-
const out = new Uint8Array(32);
|
|
85
|
-
let v = value;
|
|
86
|
-
for (let i = 31; i >= 0; i--) {
|
|
87
|
-
out[i] = Number(v & 0xffn);
|
|
88
|
-
v >>= 8n;
|
|
89
|
-
}
|
|
90
|
-
return out;
|
|
91
|
-
}
|
|
92
20
|
/**
|
|
93
21
|
* ECDSA-sign a 32-byte digest with a secp256k1 secret key, exactly as the
|
|
94
|
-
* MPC signs the attestation digest:
|
|
95
|
-
*
|
|
96
|
-
* convention `secp256k1EcdsaVerify` checks in-circuit.
|
|
22
|
+
* MPC signs the attestation digest: the result verifies in-circuit against
|
|
23
|
+
* the matching public key. For posters and test fixtures.
|
|
97
24
|
*
|
|
98
25
|
* @param digest - The 32-byte digest to sign (e.g. the
|
|
99
26
|
* {@link calculateSignetAttestationDigest} output).
|
|
100
27
|
* @param secretKey - The 32-byte secp256k1 secret key.
|
|
101
28
|
* @returns The signature with its recovery id.
|
|
29
|
+
* @throws {Error} If no recovery id reproduces the signer's own public key.
|
|
102
30
|
*/
|
|
103
31
|
export function signAttestationDigest(digest, secretKey) {
|
|
104
32
|
const sigBytes = secp256k1.sign(digest, secretKey, { prehash: false });
|
|
@@ -107,10 +35,7 @@ export function signAttestationDigest(digest, secretKey) {
|
|
|
107
35
|
const pk = secp256k1.getPublicKey(secretKey, false);
|
|
108
36
|
const pkHex = Buffer.from(pk).toString("hex");
|
|
109
37
|
for (const recoveryId of [0, 1]) {
|
|
110
|
-
const recovered = sig
|
|
111
|
-
.addRecoveryBit(recoveryId)
|
|
112
|
-
.recoverPublicKey(digest)
|
|
113
|
-
.toHex(false);
|
|
38
|
+
const recovered = sig.addRecoveryBit(recoveryId).recoverPublicKey(digest).toHex(false);
|
|
114
39
|
if (recovered === pkHex) {
|
|
115
40
|
return { r: sig.r, s: sig.s, recoveryId };
|
|
116
41
|
}
|
|
@@ -119,22 +44,17 @@ export function signAttestationDigest(digest, secretKey) {
|
|
|
119
44
|
throw new Error("signature does not recover to its own public key");
|
|
120
45
|
}
|
|
121
46
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* x = `r` and the parity `recoveryId` names, undoing the compression an
|
|
125
|
-
* `r || s || v` signature performs. For MPC-side posters (the fakenet
|
|
126
|
-
* signer, tests). The negligible caveat: an R.x that overflowed the curve
|
|
127
|
-
* order cannot be told apart from its reduced twin, so reconstruction picks
|
|
128
|
-
* the x equal to `r` itself.
|
|
47
|
+
* Encode a scalar-form signature as the stored-form record both respond
|
|
48
|
+
* events carry. For MPC-side posters (the fakenet signer) and test fixtures.
|
|
129
49
|
*
|
|
130
50
|
* @param signature - The scalar-form signature (the signer's output shape).
|
|
131
51
|
* @returns The stored-form signature: R as a full point, big-endian bytes.
|
|
132
|
-
* @throws Error
|
|
52
|
+
* @throws {Error} If the recovery id is not 0 or 1, or `r` is not the x
|
|
133
53
|
* coordinate of a secp256k1 point.
|
|
134
54
|
*/
|
|
135
55
|
export function ecdsaSignatureToMpcSignature(signature) {
|
|
136
56
|
if (signature.recoveryId !== 0 && signature.recoveryId !== 1) {
|
|
137
|
-
throw new Error(`expected a recovery id of 0 or 1, got ${signature.recoveryId}`);
|
|
57
|
+
throw new Error(`expected a recovery id of 0 or 1, got ${String(signature.recoveryId)}`);
|
|
138
58
|
}
|
|
139
59
|
// SEC1 compressed form: parity prefix (02 even, 03 odd) || x big-endian.
|
|
140
60
|
const parityPrefix = signature.recoveryId === 0 ? "02" : "03";
|
|
@@ -144,7 +64,9 @@ export function ecdsaSignatureToMpcSignature(signature) {
|
|
|
144
64
|
point = secp256k1.Point.fromHex(`${parityPrefix}${xHex}`);
|
|
145
65
|
}
|
|
146
66
|
catch (error) {
|
|
147
|
-
throw new Error(`signature r is not the x coordinate of a secp256k1 point (${String(error)})
|
|
67
|
+
throw new Error(`signature r is not the x coordinate of a secp256k1 point (${String(error)})`, {
|
|
68
|
+
cause: error,
|
|
69
|
+
});
|
|
148
70
|
}
|
|
149
71
|
const uncompressed = point.toBytes(false); // 0x04 || x || y
|
|
150
72
|
return {
|
|
@@ -154,17 +76,14 @@ export function ecdsaSignatureToMpcSignature(signature) {
|
|
|
154
76
|
};
|
|
155
77
|
}
|
|
156
78
|
/**
|
|
157
|
-
*
|
|
79
|
+
* Decode a stored-form signature back to scalar form: the inverse of
|
|
158
80
|
* {@link ecdsaSignatureToMpcSignature}, for off-chain consumers building a
|
|
159
|
-
* transaction from the response.
|
|
160
|
-
*
|
|
161
|
-
* are unauthenticated and a malformed record is garbage. `bigR.y` is NOT
|
|
162
|
-
* checked against the curve or the recovery id: signature verification is
|
|
163
|
-
* the authority, not this decoder.
|
|
81
|
+
* transaction from the response. Checks shape only: acceptance does not mean
|
|
82
|
+
* the signature verifies.
|
|
164
83
|
*
|
|
165
84
|
* @param signature - The stored-form signature as posted.
|
|
166
85
|
* @returns The scalar-form signature.
|
|
167
|
-
* @throws Error
|
|
86
|
+
* @throws {Error} If a component has the wrong byte length or the recovery id
|
|
168
87
|
* is not 0 or 1.
|
|
169
88
|
*/
|
|
170
89
|
export function mpcSignatureToEcdsaSignature(signature) {
|
|
@@ -173,7 +92,7 @@ export function mpcSignatureToEcdsaSignature(signature) {
|
|
|
173
92
|
throw new Error("expected 32-byte bigR.x/bigR.y/s in a stored signature");
|
|
174
93
|
}
|
|
175
94
|
if (recoveryId !== 0n && recoveryId !== 1n) {
|
|
176
|
-
throw new Error(`expected a recovery id of 0 or 1, got ${recoveryId}`);
|
|
95
|
+
throw new Error(`expected a recovery id of 0 or 1, got ${String(recoveryId)}`);
|
|
177
96
|
}
|
|
178
97
|
return {
|
|
179
98
|
r: bytesToBigintBE(bigR.x) % SECP256K1_ORDER,
|
|
@@ -181,6 +100,43 @@ export function mpcSignatureToEcdsaSignature(signature) {
|
|
|
181
100
|
recoveryId: Number(recoveryId),
|
|
182
101
|
};
|
|
183
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Decode a posted response signature record into an ethers
|
|
105
|
+
* {@link Signature}. Ethers' `r` is the ECDSA scalar (`bigR.x` reduced mod
|
|
106
|
+
* the curve order), not the raw coordinate.
|
|
107
|
+
*
|
|
108
|
+
* @param response - The posted signature record.
|
|
109
|
+
* @returns The ethers signature.
|
|
110
|
+
* @throws {Error} If the record is malformed (see {@link mpcSignatureToEcdsaSignature}).
|
|
111
|
+
*/
|
|
112
|
+
export function signatureRespondedEventToSignature(response) {
|
|
113
|
+
const { r, s, recoveryId } = mpcSignatureToEcdsaSignature(response.signature);
|
|
114
|
+
return Signature.from({
|
|
115
|
+
r: toBeHex(r, 32),
|
|
116
|
+
s: toBeHex(s, 32),
|
|
117
|
+
v: recoveryId + 27,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Encode an ethers signature as the {@link SignatureRespondedEvent} record a
|
|
122
|
+
* responder posts: the inverse of {@link signatureRespondedEventToSignature}.
|
|
123
|
+
* Responder-side (the fakenet posts through this; clients only verify), so
|
|
124
|
+
* it is exported through the `./testing` entry point with the other
|
|
125
|
+
* posting-side helpers.
|
|
126
|
+
*
|
|
127
|
+
* @param signature - The signature to encode (`r`/`s` as 0x hex, `yParity` 0 or 1).
|
|
128
|
+
* @returns The response record, ready to post.
|
|
129
|
+
* @throws {Error} If `r` is not the x coordinate of a secp256k1 point.
|
|
130
|
+
*/
|
|
131
|
+
export function signatureToSignatureRespondedEvent(signature) {
|
|
132
|
+
return {
|
|
133
|
+
signature: ecdsaSignatureToMpcSignature({
|
|
134
|
+
r: BigInt(signature.r),
|
|
135
|
+
s: BigInt(signature.s),
|
|
136
|
+
recoveryId: signature.yParity,
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
184
140
|
/**
|
|
185
141
|
* Parse a secp256k1 public key from SEC1 hex (compressed or uncompressed,
|
|
186
142
|
* optional `0x` prefix) into the Compact runtime's `Secp256k1Point` shape:
|
|
@@ -188,16 +144,18 @@ export function mpcSignatureToEcdsaSignature(signature) {
|
|
|
188
144
|
*
|
|
189
145
|
* @param value - The SEC1 hex public key.
|
|
190
146
|
* @returns The parsed point.
|
|
191
|
-
* @throws Error
|
|
147
|
+
* @throws {Error} If the value is not a valid secp256k1 public key.
|
|
192
148
|
*/
|
|
193
149
|
export function parseSecp256k1PublicKey(value) {
|
|
194
|
-
const hex =
|
|
150
|
+
const hex = stripHexPrefix(value);
|
|
195
151
|
let point;
|
|
196
152
|
try {
|
|
197
153
|
point = secp256k1.Point.fromHex(hex);
|
|
198
154
|
}
|
|
199
155
|
catch (error) {
|
|
200
|
-
throw new Error(`not a secp256k1 public key in SEC1 hex: "${value}" (${String(error)})
|
|
156
|
+
throw new Error(`not a secp256k1 public key in SEC1 hex: "${value}" (${String(error)})`, {
|
|
157
|
+
cause: error,
|
|
158
|
+
});
|
|
201
159
|
}
|
|
202
160
|
const uncompressed = point.toBytes(false); // 0x04 || x || y
|
|
203
161
|
return {
|
|
@@ -236,18 +194,10 @@ export function secp256k1PublicKeyOf(secretKey) {
|
|
|
236
194
|
/**
|
|
237
195
|
* The attestation digest of a respond-bidirectional response:
|
|
238
196
|
* `keccak256(requestId || serializedOutput)`, the 32-byte digest the MPC
|
|
239
|
-
* ECDSA-signs to attest a remote execution.
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* compiled, so this is the one sanctioned re-implementation, pinned
|
|
244
|
-
* byte-for-byte against the fixed-width oracle circuits in
|
|
245
|
-
* circuits.compact). The constructions agree because the circuit's keccak
|
|
246
|
-
* preimage of the `[RequestId, Bytes<N>]` pair is the raw concatenated
|
|
247
|
-
* bytes, and it matches the MPC's respond-bidirectional hash
|
|
248
|
-
* (`hash(request_id || serialized_output)`: one flat concatenation, one
|
|
249
|
-
* hash). The output is hashed AS GIVEN, at its exact length: no padding and
|
|
250
|
-
* no length binding.
|
|
197
|
+
* ECDSA-signs to attest a remote execution. TS twin of the size-generic
|
|
198
|
+
* Compact circuit of the same name, pinned against its fixed-width oracle
|
|
199
|
+
* circuits in tests. The output is hashed as given, at its exact length:
|
|
200
|
+
* no padding, no length prefix.
|
|
251
201
|
*
|
|
252
202
|
* @param requestId - The 32-byte request id the response answers.
|
|
253
203
|
* @param serializedOutput - The serialised execution output, exact unpadded bytes.
|
|
@@ -257,31 +207,17 @@ export function calculateSignetAttestationDigest(requestId, serializedOutput) {
|
|
|
257
207
|
return ethers.getBytes(ethers.keccak256(ethers.concat([requestId, serializedOutput])));
|
|
258
208
|
}
|
|
259
209
|
/**
|
|
260
|
-
* Off-chain
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
* check is the ONLY way to tell a genuine post from garbage: the signet
|
|
266
|
-
* contract's event log is unauthenticated and every post is emitted
|
|
267
|
-
* unverified. Use it to sift the candidates
|
|
268
|
-
* {@link SignetRequestResponseReader.getRespondBidirectionalEvents}
|
|
269
|
-
* returns before handing one to a contract. It is the off-chain counterpart
|
|
270
|
-
* of the in-circuit `verifyRespondBidirectionalEvent` and answers the same
|
|
271
|
-
* question, so a post this accepts is the post that proves at claim time.
|
|
272
|
-
*
|
|
273
|
-
* Malformed records are garbage, not faults: a signature whose components
|
|
274
|
-
* have the wrong width, whose `r` is off-curve, or which simply does not
|
|
275
|
-
* verify all return `false` rather than throwing. High-`s` signatures are
|
|
276
|
-
* accepted, matching the in-circuit verification (which enforces no low-`s`
|
|
277
|
-
* policy) rather than the signer's own normalisation.
|
|
210
|
+
* Off-chain twin of the in-circuit `verifyRespondBidirectionalEvent`: checks
|
|
211
|
+
* a posted respond-bidirectional attestation against the execution output
|
|
212
|
+
* and the contract's pinned MPC response key. Clients run it to sift
|
|
213
|
+
* candidate posts before calling a contract: a post this accepts verifies
|
|
214
|
+
* in-circuit. Malformed records return `false` rather than throwing.
|
|
278
215
|
*
|
|
279
216
|
* @param requestId - The 32-byte request id the response answers.
|
|
280
217
|
* @param serializedOutput - The serialised execution output, exact unpadded bytes.
|
|
281
218
|
* @param event - The posted record to check, as read off the ledger.
|
|
282
|
-
* @param mpcResponseKey - The response key the requesting contract pinned
|
|
283
|
-
*
|
|
284
|
-
* path (see {@link deriveMidnightResponseKey}).
|
|
219
|
+
* @param mpcResponseKey - The response key the requesting contract pinned
|
|
220
|
+
* (see {@link deriveMidnightResponseKey}).
|
|
285
221
|
* @returns Whether the post is a genuine attestation of that output.
|
|
286
222
|
*/
|
|
287
223
|
export function verifyRespondBidirectionalSignature(requestId, serializedOutput, event, mpcResponseKey) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ecdsa-attestation.js","sourceRoot":"","sources":["../src/ecdsa-attestation.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"ecdsa-attestation.js","sourceRoot":"","sources":["../src/ecdsa-attestation.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,37 +12,33 @@ export declare const EPSILON_DERIVATION_PREFIX = "sig.network v2.0.0 epsilon der
|
|
|
12
12
|
export declare const MIDNIGHT_TESTNET_CHAIN_ID = "midnight:testnet";
|
|
13
13
|
/**
|
|
14
14
|
* The FIXED derivation path of the MPC's respond-bidirectional RESPONSE key
|
|
15
|
-
* for Midnight client contracts
|
|
16
|
-
* `<chain> response key` convention
|
|
17
|
-
* chain-signatures/node/src/respond_bidirectional.rs
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* address is a hash over the deploy (constructor arguments included), so the
|
|
24
|
-
* key cannot exist before the deploy transaction is built. Client contracts
|
|
25
|
-
* therefore pin its hash with a one-shot `initialise` circuit right after
|
|
26
|
-
* deploy (never a constructor seal) and verify RespondBidirectionalEvents
|
|
27
|
-
* against the pin.
|
|
15
|
+
* for Midnight client contracts. It enters the derivation string verbatim,
|
|
16
|
+
* mirroring the real MPC's per-chain `<chain> response key` convention
|
|
17
|
+
* (sig-net/mpc chain-signatures/node/src/respond_bidirectional.rs). The
|
|
18
|
+
* response key is derived PER CLIENT CONTRACT from (the client contract's
|
|
19
|
+
* own address, this path): it is not the MPC root key and not the key that
|
|
20
|
+
* signs the requested transaction. Client contracts pin its hash with a
|
|
21
|
+
* one-shot `initialise` circuit after deploy and verify
|
|
22
|
+
* RespondBidirectionalEvents against the pin.
|
|
28
23
|
*/
|
|
29
24
|
export declare const MIDNIGHT_RESPOND_BIDIRECTIONAL_PATH = "midnight response key";
|
|
30
25
|
/**
|
|
31
26
|
* Derive the EVM address the MPC network signs from for a given Midnight
|
|
32
27
|
* contract and derivation path, using the sig-net v2.0.0 epsilon scheme:
|
|
33
|
-
* `epsilon = keccak256("<prefix>:<chainId>:<
|
|
34
|
-
* `derivedPubKey = mpcRootPubKey + epsilon * G` on secp256k1.
|
|
35
|
-
*
|
|
36
|
-
* The MPC treats `path` as an opaque string: the vault's own account uses the
|
|
37
|
-
* literal path `"vault"`, a user's account uses the lowercase hex of their
|
|
38
|
-
* identity commitment.
|
|
28
|
+
* `epsilon = keccak256("<prefix>:<chainId>:<requester>:<path>")` and
|
|
29
|
+
* `derivedPubKey = mpcRootPubKey + epsilon * G` on secp256k1. The MPC
|
|
30
|
+
* treats `path` as an opaque string.
|
|
39
31
|
*
|
|
40
32
|
* @param mpcSecp256k1PubkeyHex - The MPC root secp256k1 public key as 0x-hex
|
|
41
|
-
* (compressed or uncompressed
|
|
33
|
+
* (compressed or uncompressed, normalised internally).
|
|
42
34
|
* @param contractAddress - The Midnight contract address the request
|
|
43
|
-
* originates from (
|
|
44
|
-
*
|
|
45
|
-
*
|
|
35
|
+
* originates from (`0x` prefix optional, case-insensitive: it enters the
|
|
36
|
+
* derivation string through {@link normaliseRequesterAddress}).
|
|
37
|
+
* @param path - The derivation path string. For an account derived from an
|
|
38
|
+
* on-ledger request record, this is the MPC's rendering of the record's
|
|
39
|
+
* `path: Bytes<32>`: the lowercase hex of the FULL 32 bytes, no `0x`
|
|
40
|
+
* prefix and no trimming ({@link bytesToHex} of the raw bytes), so
|
|
41
|
+
* `0xab..00` and `0xab..` derive different accounts.
|
|
46
42
|
* @param chainId - CAIP-2 chain id component of the derivation string.
|
|
47
43
|
* @returns The derived EVM address as a 0x-prefixed EIP-55 checksummed string.
|
|
48
44
|
*/
|
|
@@ -63,10 +59,8 @@ export declare function deriveEpsilon(requester: string, path: string, chainId?:
|
|
|
63
59
|
/**
|
|
64
60
|
* Derive the MPC's respond-bidirectional RESPONSE key for one client
|
|
65
61
|
* contract, public side: what the client pins via its `initialise` circuit
|
|
66
|
-
*
|
|
67
|
-
* {@link MIDNIGHT_RESPOND_BIDIRECTIONAL_PATH} for the scheme
|
|
68
|
-
* is the client contract's own address, mirroring the real MPC's
|
|
69
|
-
* sender-scoped derivation).
|
|
62
|
+
* and what response verification checks against. See
|
|
63
|
+
* {@link MIDNIGHT_RESPOND_BIDIRECTIONAL_PATH} for the scheme.
|
|
70
64
|
*
|
|
71
65
|
* @param mpcSecp256k1PubkeyHex - The MPC root secp256k1 public key as 0x-hex
|
|
72
66
|
* (compressed or uncompressed).
|
|
@@ -79,14 +73,13 @@ export declare function deriveMidnightResponseKey(mpcSecp256k1PubkeyHex: string,
|
|
|
79
73
|
* Derive the MPC's respond-bidirectional RESPONSE key for one client
|
|
80
74
|
* contract, secret side: `(rootSecret + epsilon) mod n`. MPC-side only
|
|
81
75
|
* (the fakenet signer, test harnesses): a real client never holds the root
|
|
82
|
-
* key.
|
|
76
|
+
* key.
|
|
83
77
|
*
|
|
84
|
-
* @param mpcRootSecretKey - The 32-byte MPC root secret key (big-endian
|
|
85
|
-
* standard secp256k1 encoding).
|
|
78
|
+
* @param mpcRootSecretKey - The 32-byte MPC root secret key (big-endian).
|
|
86
79
|
* @param clientContractAddress - The client contract's Midnight address
|
|
87
80
|
* (`0x` prefix optional, case-insensitive).
|
|
88
81
|
* @returns The 32-byte response secret key (big-endian).
|
|
89
|
-
* @throws Error
|
|
82
|
+
* @throws {Error} If the root key is not 32 bytes or the derived scalar is 0.
|
|
90
83
|
*/
|
|
91
84
|
export declare function deriveMidnightResponseSecretKey(mpcRootSecretKey: Uint8Array, clientContractAddress: string): Uint8Array;
|
|
92
85
|
//# sourceMappingURL=epsilon-derivation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epsilon-derivation.d.ts","sourceRoot":"","sources":["../src/epsilon-derivation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"epsilon-derivation.d.ts","sourceRoot":"","sources":["../src/epsilon-derivation.ts"],"names":[],"mappings":"AAWA,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,0CAA0C,CAAC;AAEjF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mCAAmC,0BAA0B,CAAC;AAe3E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,qBAAqB,EAAE,MAAM,EAC7B,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAkC,GAC1C,MAAM,CAQR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAkC,GAC1C,MAAM,CAKR;AAuBD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,qBAAqB,EAAE,MAAM,EAC7B,qBAAqB,EAAE,MAAM,GAC5B,cAAc,CAQhB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,+BAA+B,CAC7C,gBAAgB,EAAE,UAAU,EAC5B,qBAAqB,EAAE,MAAM,GAC5B,UAAU,CAeZ"}
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This belongs in github.com/sig-net/signet.js, kept here until upstreamed.
|
|
4
4
|
//
|
|
5
|
-
// v2.0.0 (COLON-separated) is the only scheme the MPC answers
|
|
6
|
-
//
|
|
7
|
-
// the Compact contracts also assert `keyVersion >= 1`, which selects v2. The
|
|
8
|
-
// two schemes hash different strings and derive different accounts, so a
|
|
9
|
-
// v1 signer silently gets the wrong address rather than an error.
|
|
5
|
+
// v2.0.0 (COLON-separated) is the only scheme the MPC answers: the Compact
|
|
6
|
+
// contracts assert `keyVersion >= 1`, which selects v2.
|
|
10
7
|
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
11
8
|
import { computeAddress, keccak256, SigningKey, toUtf8Bytes } from "ethers";
|
|
9
|
+
import { bigintToBytes32BE, bytesToBigintBE, stripHexPrefix } from "./byte-codecs.js";
|
|
12
10
|
import { SECP256K1_ORDER } from "./ecdsa-attestation.js";
|
|
13
11
|
/**
|
|
14
12
|
* Domain prefix of the sig-net v2.0.0 epsilon derivation scheme. The full
|
|
@@ -23,42 +21,50 @@ export const EPSILON_DERIVATION_PREFIX = "sig.network v2.0.0 epsilon derivation"
|
|
|
23
21
|
export const MIDNIGHT_TESTNET_CHAIN_ID = "midnight:testnet";
|
|
24
22
|
/**
|
|
25
23
|
* The FIXED derivation path of the MPC's respond-bidirectional RESPONSE key
|
|
26
|
-
* for Midnight client contracts
|
|
27
|
-
* `<chain> response key` convention
|
|
28
|
-
* chain-signatures/node/src/respond_bidirectional.rs
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* address is a hash over the deploy (constructor arguments included), so the
|
|
35
|
-
* key cannot exist before the deploy transaction is built. Client contracts
|
|
36
|
-
* therefore pin its hash with a one-shot `initialise` circuit right after
|
|
37
|
-
* deploy (never a constructor seal) and verify RespondBidirectionalEvents
|
|
38
|
-
* against the pin.
|
|
24
|
+
* for Midnight client contracts. It enters the derivation string verbatim,
|
|
25
|
+
* mirroring the real MPC's per-chain `<chain> response key` convention
|
|
26
|
+
* (sig-net/mpc chain-signatures/node/src/respond_bidirectional.rs). The
|
|
27
|
+
* response key is derived PER CLIENT CONTRACT from (the client contract's
|
|
28
|
+
* own address, this path): it is not the MPC root key and not the key that
|
|
29
|
+
* signs the requested transaction. Client contracts pin its hash with a
|
|
30
|
+
* one-shot `initialise` circuit after deploy and verify
|
|
31
|
+
* RespondBidirectionalEvents against the pin.
|
|
39
32
|
*/
|
|
40
33
|
export const MIDNIGHT_RESPOND_BIDIRECTIONAL_PATH = "midnight response key";
|
|
34
|
+
/**
|
|
35
|
+
* Normalise a Midnight contract address for use as the requester component
|
|
36
|
+
* of the derivation string: strip an optional `0x` prefix and lowercase.
|
|
37
|
+
* Both sides of the protocol (the deploy pinning a key and the MPC signing
|
|
38
|
+
* with it) derive through this, so the rendering always agrees.
|
|
39
|
+
*
|
|
40
|
+
* @param contractAddress - The Midnight contract address, with or without `0x`.
|
|
41
|
+
* @returns The address as lowercase hex with no prefix.
|
|
42
|
+
*/
|
|
43
|
+
function normaliseRequesterAddress(contractAddress) {
|
|
44
|
+
return stripHexPrefix(contractAddress).toLowerCase();
|
|
45
|
+
}
|
|
41
46
|
/**
|
|
42
47
|
* Derive the EVM address the MPC network signs from for a given Midnight
|
|
43
48
|
* contract and derivation path, using the sig-net v2.0.0 epsilon scheme:
|
|
44
|
-
* `epsilon = keccak256("<prefix>:<chainId>:<
|
|
45
|
-
* `derivedPubKey = mpcRootPubKey + epsilon * G` on secp256k1.
|
|
46
|
-
*
|
|
47
|
-
* The MPC treats `path` as an opaque string: the vault's own account uses the
|
|
48
|
-
* literal path `"vault"`, a user's account uses the lowercase hex of their
|
|
49
|
-
* identity commitment.
|
|
49
|
+
* `epsilon = keccak256("<prefix>:<chainId>:<requester>:<path>")` and
|
|
50
|
+
* `derivedPubKey = mpcRootPubKey + epsilon * G` on secp256k1. The MPC
|
|
51
|
+
* treats `path` as an opaque string.
|
|
50
52
|
*
|
|
51
53
|
* @param mpcSecp256k1PubkeyHex - The MPC root secp256k1 public key as 0x-hex
|
|
52
|
-
* (compressed or uncompressed
|
|
54
|
+
* (compressed or uncompressed, normalised internally).
|
|
53
55
|
* @param contractAddress - The Midnight contract address the request
|
|
54
|
-
* originates from (
|
|
55
|
-
*
|
|
56
|
-
*
|
|
56
|
+
* originates from (`0x` prefix optional, case-insensitive: it enters the
|
|
57
|
+
* derivation string through {@link normaliseRequesterAddress}).
|
|
58
|
+
* @param path - The derivation path string. For an account derived from an
|
|
59
|
+
* on-ledger request record, this is the MPC's rendering of the record's
|
|
60
|
+
* `path: Bytes<32>`: the lowercase hex of the FULL 32 bytes, no `0x`
|
|
61
|
+
* prefix and no trimming ({@link bytesToHex} of the raw bytes), so
|
|
62
|
+
* `0xab..00` and `0xab..` derive different accounts.
|
|
57
63
|
* @param chainId - CAIP-2 chain id component of the derivation string.
|
|
58
64
|
* @returns The derived EVM address as a 0x-prefixed EIP-55 checksummed string.
|
|
59
65
|
*/
|
|
60
66
|
export function deriveEvmAddress(mpcSecp256k1PubkeyHex, contractAddress, path, chainId = MIDNIGHT_TESTNET_CHAIN_ID) {
|
|
61
|
-
const derivedPoint = deriveChildPoint(mpcSecp256k1PubkeyHex, contractAddress, path, chainId);
|
|
67
|
+
const derivedPoint = deriveChildPoint(mpcSecp256k1PubkeyHex, normaliseRequesterAddress(contractAddress), path, chainId);
|
|
62
68
|
return computeAddress(`0x${derivedPoint.toHex(false)}`);
|
|
63
69
|
}
|
|
64
70
|
/**
|
|
@@ -79,32 +85,26 @@ export function deriveEpsilon(requester, path, chainId = MIDNIGHT_TESTNET_CHAIN_
|
|
|
79
85
|
// server's scalar arithmetic reduces implicitly.
|
|
80
86
|
return BigInt(keccak256(toUtf8Bytes(fullPath))) % SECP256K1_ORDER;
|
|
81
87
|
}
|
|
82
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Derive the child public key as a noble curve point (internal shape).
|
|
90
|
+
*
|
|
91
|
+
* @param mpcSecp256k1PubkeyHex - The MPC root public key in SEC1 hex.
|
|
92
|
+
* @param requester - The normalised requester address.
|
|
93
|
+
* @param path - The derivation path component.
|
|
94
|
+
* @param chainId - The chain id component.
|
|
95
|
+
* @returns The derived child point on secp256k1.
|
|
96
|
+
*/
|
|
83
97
|
function deriveChildPoint(mpcSecp256k1PubkeyHex, requester, path, chainId) {
|
|
84
98
|
const epsilon = deriveEpsilon(requester, path, chainId);
|
|
85
99
|
const rootPubKeyHex = SigningKey.computePublicKey(mpcSecp256k1PubkeyHex, false);
|
|
86
100
|
const rootPoint = secp256k1.Point.fromHex(rootPubKeyHex.slice(2));
|
|
87
101
|
return epsilon === 0n ? rootPoint : rootPoint.add(secp256k1.Point.BASE.multiply(epsilon));
|
|
88
102
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Normalise a Midnight contract address for use as the requester component
|
|
91
|
-
* of the derivation string: strip an optional `0x` prefix and lowercase.
|
|
92
|
-
* Both sides of the protocol (the deploy pinning a key and the MPC signing
|
|
93
|
-
* with it) derive through this, so the rendering always agrees.
|
|
94
|
-
*/
|
|
95
|
-
function normaliseRequesterAddress(contractAddress) {
|
|
96
|
-
const hex = contractAddress.startsWith("0x") || contractAddress.startsWith("0X")
|
|
97
|
-
? contractAddress.slice(2)
|
|
98
|
-
: contractAddress;
|
|
99
|
-
return hex.toLowerCase();
|
|
100
|
-
}
|
|
101
103
|
/**
|
|
102
104
|
* Derive the MPC's respond-bidirectional RESPONSE key for one client
|
|
103
105
|
* contract, public side: what the client pins via its `initialise` circuit
|
|
104
|
-
*
|
|
105
|
-
* {@link MIDNIGHT_RESPOND_BIDIRECTIONAL_PATH} for the scheme
|
|
106
|
-
* is the client contract's own address, mirroring the real MPC's
|
|
107
|
-
* sender-scoped derivation).
|
|
106
|
+
* and what response verification checks against. See
|
|
107
|
+
* {@link MIDNIGHT_RESPOND_BIDIRECTIONAL_PATH} for the scheme.
|
|
108
108
|
*
|
|
109
109
|
* @param mpcSecp256k1PubkeyHex - The MPC root secp256k1 public key as 0x-hex
|
|
110
110
|
* (compressed or uncompressed).
|
|
@@ -120,34 +120,24 @@ export function deriveMidnightResponseKey(mpcSecp256k1PubkeyHex, clientContractA
|
|
|
120
120
|
* Derive the MPC's respond-bidirectional RESPONSE key for one client
|
|
121
121
|
* contract, secret side: `(rootSecret + epsilon) mod n`. MPC-side only
|
|
122
122
|
* (the fakenet signer, test harnesses): a real client never holds the root
|
|
123
|
-
* key.
|
|
123
|
+
* key.
|
|
124
124
|
*
|
|
125
|
-
* @param mpcRootSecretKey - The 32-byte MPC root secret key (big-endian
|
|
126
|
-
* standard secp256k1 encoding).
|
|
125
|
+
* @param mpcRootSecretKey - The 32-byte MPC root secret key (big-endian).
|
|
127
126
|
* @param clientContractAddress - The client contract's Midnight address
|
|
128
127
|
* (`0x` prefix optional, case-insensitive).
|
|
129
128
|
* @returns The 32-byte response secret key (big-endian).
|
|
130
|
-
* @throws Error
|
|
129
|
+
* @throws {Error} If the root key is not 32 bytes or the derived scalar is 0.
|
|
131
130
|
*/
|
|
132
131
|
export function deriveMidnightResponseSecretKey(mpcRootSecretKey, clientContractAddress) {
|
|
133
132
|
if (mpcRootSecretKey.length !== 32) {
|
|
134
|
-
throw new Error(`MPC root secret key must be 32 bytes, got ${mpcRootSecretKey.length}`);
|
|
135
|
-
}
|
|
136
|
-
let root = 0n;
|
|
137
|
-
for (const byte of mpcRootSecretKey) {
|
|
138
|
-
root = (root << 8n) | BigInt(byte);
|
|
133
|
+
throw new Error(`MPC root secret key must be 32 bytes, got ${String(mpcRootSecretKey.length)}`);
|
|
139
134
|
}
|
|
135
|
+
const root = bytesToBigintBE(mpcRootSecretKey);
|
|
140
136
|
const epsilon = deriveEpsilon(normaliseRequesterAddress(clientContractAddress), MIDNIGHT_RESPOND_BIDIRECTIONAL_PATH, MIDNIGHT_TESTNET_CHAIN_ID);
|
|
141
137
|
const child = (root + epsilon) % SECP256K1_ORDER;
|
|
142
138
|
if (child === 0n) {
|
|
143
139
|
throw new Error("derived response secret key is zero (invalid scalar)");
|
|
144
140
|
}
|
|
145
|
-
|
|
146
|
-
let value = child;
|
|
147
|
-
for (let i = 31; i >= 0; i--) {
|
|
148
|
-
out[i] = Number(value & 0xffn);
|
|
149
|
-
value >>= 8n;
|
|
150
|
-
}
|
|
151
|
-
return out;
|
|
141
|
+
return bigintToBytes32BE(child);
|
|
152
142
|
}
|
|
153
143
|
//# sourceMappingURL=epsilon-derivation.js.map
|