@sig-net/midnight 0.0.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 (90) hide show
  1. package/README.md +20 -0
  2. package/dist/constants.d.ts +62 -0
  3. package/dist/constants.d.ts.map +1 -0
  4. package/dist/constants.js +83 -0
  5. package/dist/constants.js.map +1 -0
  6. package/dist/epsilon-derivation.d.ts +32 -0
  7. package/dist/epsilon-derivation.d.ts.map +1 -0
  8. package/dist/epsilon-derivation.js +54 -0
  9. package/dist/epsilon-derivation.js.map +1 -0
  10. package/dist/index.d.ts +26 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +30 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/managed/compiler/contract-info.json +165 -0
  15. package/dist/managed/compiler/contract-manifest.json +32 -0
  16. package/dist/managed/contract/index.d.ts +65 -0
  17. package/dist/managed/contract/index.js +611 -0
  18. package/dist/managed/contract/index.js.map +8 -0
  19. package/dist/schnorr.d.ts +100 -0
  20. package/dist/schnorr.d.ts.map +1 -0
  21. package/dist/schnorr.js +144 -0
  22. package/dist/schnorr.js.map +1 -0
  23. package/dist/signature-requests-state-reader.d.ts +74 -0
  24. package/dist/signature-requests-state-reader.d.ts.map +1 -0
  25. package/dist/signature-requests-state-reader.js +167 -0
  26. package/dist/signature-requests-state-reader.js.map +1 -0
  27. package/dist/signature-response-verification.d.ts +27 -0
  28. package/dist/signature-response-verification.d.ts.map +1 -0
  29. package/dist/signature-response-verification.js +45 -0
  30. package/dist/signature-response-verification.js.map +1 -0
  31. package/dist/signature-state-reading.d.ts +36 -0
  32. package/dist/signature-state-reading.d.ts.map +1 -0
  33. package/dist/signature-state-reading.js +62 -0
  34. package/dist/signature-state-reading.js.map +1 -0
  35. package/dist/signet-contract-state-reader.d.ts +146 -0
  36. package/dist/signet-contract-state-reader.d.ts.map +1 -0
  37. package/dist/signet-contract-state-reader.js +226 -0
  38. package/dist/signet-contract-state-reader.js.map +1 -0
  39. package/dist/signet-event-observer.d.ts +102 -0
  40. package/dist/signet-event-observer.d.ts.map +1 -0
  41. package/dist/signet-event-observer.js +127 -0
  42. package/dist/signet-event-observer.js.map +1 -0
  43. package/dist/signet-events.d.ts +175 -0
  44. package/dist/signet-events.d.ts.map +1 -0
  45. package/dist/signet-events.js +210 -0
  46. package/dist/signet-events.js.map +1 -0
  47. package/dist/signet-request-feed.d.ts +78 -0
  48. package/dist/signet-request-feed.d.ts.map +1 -0
  49. package/dist/signet-request-feed.js +116 -0
  50. package/dist/signet-request-feed.js.map +1 -0
  51. package/dist/signet-request-resolver.d.ts +58 -0
  52. package/dist/signet-request-resolver.d.ts.map +1 -0
  53. package/dist/signet-request-resolver.js +76 -0
  54. package/dist/signet-request-resolver.js.map +1 -0
  55. package/dist/signet-request-response-reader.d.ts +160 -0
  56. package/dist/signet-request-response-reader.d.ts.map +1 -0
  57. package/dist/signet-request-response-reader.js +198 -0
  58. package/dist/signet-request-response-reader.js.map +1 -0
  59. package/dist/signet-requests.d.ts +363 -0
  60. package/dist/signet-requests.d.ts.map +1 -0
  61. package/dist/signet-requests.js +497 -0
  62. package/dist/signet-requests.js.map +1 -0
  63. package/dist/signet-response-feed.d.ts +161 -0
  64. package/dist/signet-response-feed.d.ts.map +1 -0
  65. package/dist/signet-response-feed.js +234 -0
  66. package/dist/signet-response-feed.js.map +1 -0
  67. package/package.json +47 -0
  68. package/src/Schnorr.compact +63 -0
  69. package/src/Signet.compact +529 -0
  70. package/src/circuits.compact +36 -0
  71. package/src/constants.ts +97 -0
  72. package/src/epsilon-derivation.ts +62 -0
  73. package/src/index.ts +31 -0
  74. package/src/managed/compiler/contract-info.json +165 -0
  75. package/src/managed/compiler/contract-manifest.json +32 -0
  76. package/src/managed/contract/index.d.ts +65 -0
  77. package/src/managed/contract/index.js +611 -0
  78. package/src/managed/contract/index.js.map +8 -0
  79. package/src/schnorr.ts +210 -0
  80. package/src/signature-requests-state-reader.ts +223 -0
  81. package/src/signature-response-verification.ts +64 -0
  82. package/src/signature-state-reading.ts +87 -0
  83. package/src/signet-contract-state-reader.ts +391 -0
  84. package/src/signet-event-observer.ts +186 -0
  85. package/src/signet-events.ts +337 -0
  86. package/src/signet-request-feed.ts +158 -0
  87. package/src/signet-request-resolver.ts +116 -0
  88. package/src/signet-request-response-reader.ts +324 -0
  89. package/src/signet-requests.ts +737 -0
  90. package/src/signet-response-feed.ts +325 -0
@@ -0,0 +1,324 @@
1
+ // One-stop client-side reader for the signet request/response flow: fetch a
2
+ // request record from the requester contract's ledger, enumerate the posts in
3
+ // the signet contract's signature response log, and verify each candidate
4
+ // against the request.
5
+ // This is the helper the Signet.compact "Response Ledger Layout" comment
6
+ // promises — the log is UNAUTHENTICATED, so a client must verify every post
7
+ // and take the first valid one; this class packages that flow so every
8
+ // consumer (CLI poller, integration tests, a future UI) shares one
9
+ // implementation. Single-shot by design: each call queries state once; the
10
+ // caller owns any poll loop.
11
+
12
+ import type { PublicDataProvider } from "@midnight-ntwrk/midnight-js-types";
13
+ import type { Transaction } from "ethers";
14
+
15
+ import { readSignetRequestsLedgerFromState } from "./signature-requests-state-reader.ts";
16
+ import {
17
+ readSignetContractLedgerFromState,
18
+ signetResponseIndexKey,
19
+ type SignatureResponse,
20
+ type RespondBidirectional,
21
+ } from "./signet-contract-state-reader.ts";
22
+ import { recoverSignatureResponseSigner } from "./signature-response-verification.ts";
23
+ import type { RawContractState } from "./signature-state-reading.ts";
24
+ import {
25
+ signBidirectionalRequestToSignedEVMTransaction,
26
+ signBidirectionalRequestToUnsignedEVMTransaction,
27
+ type SignBidirectionalRequest,
28
+ type RequestIdHex,
29
+ } from "./signet-requests.ts";
30
+
31
+ /**
32
+ * The least of midnight-js's `PublicDataProvider` the reader needs: raw
33
+ * contract state by address. Declared structurally (rather than a `Pick`) so
34
+ * tests can satisfy it with a plain stub; any full `PublicDataProvider`
35
+ * (e.g. `indexerPublicDataProvider`) is assignable to it.
36
+ */
37
+ export interface SignetPublicStateSource {
38
+ /**
39
+ * Retrieve the on-chain state of a contract at the latest block.
40
+ *
41
+ * @param contractAddress - The contract address to query.
42
+ * @returns The state (its `data` is the raw ledger tree), or `null` when
43
+ * the address holds no contract.
44
+ */
45
+ queryContractState(
46
+ contractAddress: Parameters<PublicDataProvider["queryContractState"]>[0],
47
+ ): Promise<{ data: RawContractState } | null>;
48
+ }
49
+
50
+ /** Everything a {@link SignetRequestResponseReader} needs to operate. */
51
+ export interface SignetRequestResponseReaderConfig {
52
+ /** Address of the signet-compliant requester contract (e.g. the vault). */
53
+ readonly requesterContractAddress: string;
54
+ /** Address of the central signet contract. */
55
+ readonly signetContractAddress: string;
56
+ /** Source of raw contract state, e.g. midnight-js's `indexerPublicDataProvider`. */
57
+ readonly publicDataProvider: SignetPublicStateSource;
58
+ }
59
+
60
+ /** The verdict on one posted response, in `count` order. */
61
+ export interface SignatureResponseVerdict {
62
+ /** 0-based position of the post in the request's response log. */
63
+ count: bigint;
64
+ /** The posted signature record, verbatim. */
65
+ response: SignatureResponse;
66
+ /** Recovered signer address — absent when the signature did not decode. */
67
+ signer?: string;
68
+ /** Why the post was rejected; absent when the post is valid. */
69
+ rejectedReason?: string;
70
+ }
71
+
72
+ /** Result of {@link SignetRequestResponseReader['getVerifiedSignatureResponse']}. */
73
+ export interface VerifiedSignatureResponseResult {
74
+ /**
75
+ * The first valid response (lowest count), or `undefined` when no valid
76
+ * response has been posted yet — poll again.
77
+ */
78
+ verified?: SignatureResponse;
79
+ /**
80
+ * One verdict per post, count order. Pure data: the reader never logs, so
81
+ * callers decide how to surface rejected posts.
82
+ */
83
+ verdicts: SignatureResponseVerdict[];
84
+ }
85
+
86
+ /**
87
+ * Reader over one requester contract / signet contract pair.
88
+ * Construct once per pair and reuse: fetched request records are cached (they
89
+ * are immutable — the ledger key is their hash), so repeated verification
90
+ * calls cost one responses-contract query each.
91
+ */
92
+ export class SignetRequestResponseReader {
93
+ private readonly config: SignetRequestResponseReaderConfig;
94
+
95
+ // Request records never change once stored; cache them across calls.
96
+ private readonly requestCache = new Map<
97
+ RequestIdHex,
98
+ SignBidirectionalRequest
99
+ >();
100
+
101
+ /**
102
+ * @param config - The contract pair and state source to read through.
103
+ */
104
+ constructor(config: SignetRequestResponseReaderConfig) {
105
+ this.config = config;
106
+ }
107
+
108
+ /**
109
+ * Query one contract's raw state, throwing a uniform error when absent.
110
+ *
111
+ * @param contractAddress - The contract to query.
112
+ * @param role - Human name of the contract for the error message.
113
+ * @returns The raw ledger state tree.
114
+ * @throws Error when the address holds no contract state.
115
+ */
116
+ private async queryRawState(
117
+ contractAddress: string,
118
+ role: string,
119
+ ): Promise<RawContractState> {
120
+ const state =
121
+ await this.config.publicDataProvider.queryContractState(contractAddress);
122
+ if (!state?.data) {
123
+ throw new Error(
124
+ `no state data found for ${role} contract '${contractAddress}' - is it deployed?`,
125
+ );
126
+ }
127
+ return state.data;
128
+ }
129
+
130
+ /**
131
+ * Fetch the request record for `requestId` from the requester contract's
132
+ * ledger (request index, field 0 by the signet layout convention). Cached
133
+ * after the first fetch.
134
+ *
135
+ * @param requestId - The request id to look up.
136
+ * @returns The stored request record.
137
+ * @throws Error when the requester contract has no state or holds no
138
+ * request under `requestId`.
139
+ */
140
+ async getSignatureRequest(
141
+ requestId: RequestIdHex,
142
+ ): Promise<SignBidirectionalRequest> {
143
+ const cached = this.requestCache.get(requestId);
144
+ if (cached !== undefined) {
145
+ return cached;
146
+ }
147
+ const raw = await this.queryRawState(
148
+ this.config.requesterContractAddress,
149
+ "requester",
150
+ );
151
+ const { requestsIndex } = readSignetRequestsLedgerFromState(raw);
152
+ const request = requestsIndex.get(requestId);
153
+ if (request === undefined) {
154
+ throw new Error(
155
+ `request ${requestId} is not on the requester contract's ledger — was it submitted?`,
156
+ );
157
+ }
158
+ this.requestCache.set(requestId, request);
159
+ return request;
160
+ }
161
+
162
+ /**
163
+ * Fetch every response posted for `requestId`, in post (count) order.
164
+ * UNVERIFIED — any of them may be garbage; see
165
+ * {@link getVerifiedSignatureResponse}.
166
+ *
167
+ * @param requestId - The request id whose posts to enumerate.
168
+ * @returns The posted payloads, index = count; empty when none yet.
169
+ * @throws Error when the responses contract has no state, or its counter
170
+ * disagrees with the log (inconsistent ledger).
171
+ */
172
+ async getSignatureResponses(
173
+ requestId: RequestIdHex,
174
+ ): Promise<SignatureResponse[]> {
175
+ const raw = await this.queryRawState(
176
+ this.config.signetContractAddress,
177
+ "signet contract",
178
+ );
179
+ const { signatureResponseCounterIndex, signatureResponseIndex } =
180
+ readSignetContractLedgerFromState(raw);
181
+ const totalPosts = signatureResponseCounterIndex.get(requestId) ?? 0n;
182
+ const responses: SignatureResponse[] = [];
183
+ for (let count = 0n; count < totalPosts; count++) {
184
+ const response = signatureResponseIndex.get(
185
+ signetResponseIndexKey(requestId, count),
186
+ );
187
+ if (response === undefined) {
188
+ throw new Error(
189
+ `response log has no entry ${count} for request ${requestId} ` +
190
+ `despite its counter reading ${totalPosts} — ledger state is inconsistent`,
191
+ );
192
+ }
193
+ responses.push(response);
194
+ }
195
+ return responses;
196
+ }
197
+
198
+ /**
199
+ * Fetch and verify the responses posted for `requestId`: each post's
200
+ * `{ bigR, s, recoveryId }` signature must recover to `expectedSigner`
201
+ * (compared case-insensitively) over the signing hash of the transaction
202
+ * the request record describes. The first valid post wins; every post gets
203
+ * a verdict so callers can report the noise.
204
+ *
205
+ * @param requestId - The request id to fetch a verified response for.
206
+ * @param expectedSigner - The EVM address (0x hex, any case) the genuine
207
+ * response must be signed by — the requester's MPC-derived address.
208
+ * @returns The first valid response (if any) plus per-post verdicts.
209
+ * @throws Error when either contract has no state, the request is not on
210
+ * the requester's ledger, or the responses ledger is inconsistent.
211
+ */
212
+ async getVerifiedSignatureResponse(
213
+ requestId: RequestIdHex,
214
+ expectedSigner: string,
215
+ ): Promise<VerifiedSignatureResponseResult> {
216
+ const request = await this.getSignatureRequest(requestId);
217
+ const responses = await this.getSignatureResponses(requestId);
218
+ const verdicts = responses.map(
219
+ (response, index): SignatureResponseVerdict => {
220
+ const count = BigInt(index);
221
+ let signer: string;
222
+ try {
223
+ signer = recoverSignatureResponseSigner(request, response);
224
+ } catch (error) {
225
+ return {
226
+ count,
227
+ response,
228
+ rejectedReason: `not a decodable signature (${String(error)})`,
229
+ };
230
+ }
231
+ if (signer.toLowerCase() !== expectedSigner.toLowerCase()) {
232
+ return {
233
+ count,
234
+ response,
235
+ signer,
236
+ rejectedReason: `signed by ${signer}, expected ${expectedSigner}`,
237
+ };
238
+ }
239
+ return { count, response, signer };
240
+ },
241
+ );
242
+ return {
243
+ verified: verdicts.find((v) => v.rejectedReason === undefined)?.response,
244
+ verdicts,
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Rebuild the unsigned EIP-1559 transaction for `requestId` — the exact
250
+ * transaction the MPC signs, assembled from the request record's decomposed
251
+ * fields. No responses-contract query: this needs only the request record
252
+ * (fetched via {@link getSignatureRequest}, cached).
253
+ *
254
+ * @param requestId - The request id whose transaction to rebuild.
255
+ * @returns The unsigned ethers transaction (`unsignedHash` is the MPC's
256
+ * signing digest).
257
+ * @throws Error when the requester contract has no state or holds no
258
+ * request under `requestId`.
259
+ */
260
+ async getUnsignedEVMTransaction(
261
+ requestId: RequestIdHex,
262
+ ): Promise<Transaction> {
263
+ return signBidirectionalRequestToUnsignedEVMTransaction(
264
+ await this.getSignatureRequest(requestId),
265
+ );
266
+ }
267
+
268
+ /**
269
+ * Assemble the broadcast-ready signed EIP-1559 transaction for `requestId`:
270
+ * rebuild the request's transaction and attach the first VERIFIED response
271
+ * signed by `expectedSigner` (see {@link getVerifiedSignatureResponse} — the
272
+ * response log is unauthenticated, so an `expectedSigner` is required and
273
+ * unverified posts are never attached).
274
+ *
275
+ * @param requestId - The request id to produce a signed transaction for.
276
+ * @param expectedSigner - The EVM address (0x hex, any case) the genuine
277
+ * response must be signed by — the requester's MPC-derived address.
278
+ * @returns The signed ethers transaction (`serialized` is the payload for
279
+ * `eth_sendRawTransaction`), or `undefined` when no valid response has
280
+ * been posted yet — poll again.
281
+ * @throws Error when either contract has no state, the request is not on the
282
+ * requester's ledger, or the responses ledger is inconsistent.
283
+ */
284
+ async getSignedEVMTransaction(
285
+ requestId: RequestIdHex,
286
+ expectedSigner: string,
287
+ ): Promise<Transaction | undefined> {
288
+ const { verified } = await this.getVerifiedSignatureResponse(
289
+ requestId,
290
+ expectedSigner,
291
+ );
292
+ if (verified === undefined) {
293
+ return undefined;
294
+ }
295
+ // getSignatureRequest is cached — getVerifiedSignatureResponse already
296
+ // fetched it, so this is a free lookup, not a second query.
297
+ const request = await this.getSignatureRequest(requestId);
298
+ return signBidirectionalRequestToSignedEVMTransaction(request, verified);
299
+ }
300
+
301
+ /**
302
+ * Fetch the MPC's respond-bidirectional attestation for `requestId`,
303
+ * if posted. The signet contract verified it IN-CIRCUIT at post time
304
+ * (Schnorr over `(requestId, hash(serializedOutput, outputLen))` against
305
+ * the sealed MPC key), so it is single-slot and needs no off-chain
306
+ * verification or verdicts — `undefined` simply means not posted yet, poll
307
+ * again.
308
+ *
309
+ * @param requestId - The request id whose attestation to fetch.
310
+ * @returns The attestation record, or `undefined` when none is posted.
311
+ * @throws Error when the signet contract has no state on-chain.
312
+ */
313
+ async getRespondBidirectional(
314
+ requestId: RequestIdHex,
315
+ ): Promise<RespondBidirectional | undefined> {
316
+ const raw = await this.queryRawState(
317
+ this.config.signetContractAddress,
318
+ "signet contract",
319
+ );
320
+ return readSignetContractLedgerFromState(raw).respondBidirectionalIndex.get(
321
+ requestId,
322
+ );
323
+ }
324
+ }