@sig-net/midnight 0.0.1 → 0.0.3
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/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/managed/compiler/contract-info.json +57 -0
- package/dist/managed/compiler/contract-manifest.json +8 -8
- package/dist/managed/contract/index.d.ts +13 -0
- package/dist/managed/contract/index.js +138 -73
- package/dist/managed/contract/index.js.map +1 -1
- package/dist/signature-requests-state-reader.d.ts +5 -5
- package/dist/signature-requests-state-reader.js +5 -5
- package/dist/signet-contract-state-reader.d.ts +88 -2
- package/dist/signet-contract-state-reader.d.ts.map +1 -1
- package/dist/signet-contract-state-reader.js +106 -3
- package/dist/signet-contract-state-reader.js.map +1 -1
- package/dist/signet-request-feed.d.ts +40 -21
- package/dist/signet-request-feed.d.ts.map +1 -1
- package/dist/signet-request-feed.js +79 -34
- package/dist/signet-request-feed.js.map +1 -1
- package/dist/signet-request-resolver.d.ts +11 -11
- package/dist/signet-request-resolver.d.ts.map +1 -1
- package/dist/signet-request-resolver.js +22 -22
- package/dist/signet-request-resolver.js.map +1 -1
- package/dist/signet-requests.d.ts +18 -4
- package/dist/signet-requests.d.ts.map +1 -1
- package/dist/signet-requests.js +29 -6
- package/dist/signet-requests.js.map +1 -1
- package/package.json +2 -2
- package/src/Signet.compact +75 -99
- package/src/circuits.compact +5 -2
- package/dist/signet-event-observer.d.ts +0 -102
- package/dist/signet-event-observer.d.ts.map +0 -1
- package/dist/signet-event-observer.js +0 -127
- package/dist/signet-event-observer.js.map +0 -1
- package/dist/signet-events.d.ts +0 -175
- package/dist/signet-events.d.ts.map +0 -1
- package/dist/signet-events.js +0 -210
- package/dist/signet-events.js.map +0 -1
- package/dist/signet-response-feed.d.ts +0 -161
- package/dist/signet-response-feed.d.ts.map +0 -1
- package/dist/signet-response-feed.js +0 -234
- package/dist/signet-response-feed.js.map +0 -1
- package/src/constants.ts +0 -97
- package/src/epsilon-derivation.ts +0 -62
- package/src/index.ts +0 -31
- package/src/managed/compiler/contract-info.json +0 -165
- package/src/managed/compiler/contract-manifest.json +0 -32
- package/src/managed/contract/index.d.ts +0 -65
- package/src/managed/contract/index.js +0 -611
- package/src/managed/contract/index.js.map +0 -8
- package/src/schnorr.ts +0 -210
- package/src/signature-requests-state-reader.ts +0 -223
- package/src/signature-response-verification.ts +0 -64
- package/src/signature-state-reading.ts +0 -87
- package/src/signet-contract-state-reader.ts +0 -391
- package/src/signet-event-observer.ts +0 -186
- package/src/signet-events.ts +0 -337
- package/src/signet-request-feed.ts +0 -158
- package/src/signet-request-resolver.ts +0 -116
- package/src/signet-request-response-reader.ts +0 -324
- package/src/signet-requests.ts +0 -737
- package/src/signet-response-feed.ts +0 -325
package/src/schnorr.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
// Jubjub Schnorr helpers — the TS side of the shared Schnorr.compact module:
|
|
2
|
-
// key derivation, SIGNING (which needs the secret scalar, so it cannot be a
|
|
3
|
-
// circuit), and key parsing/hashing. Everything provable stays in Compact:
|
|
4
|
-
// the challenge is the COMPILED `pureCircuits.schnorrChallenge`, injected by
|
|
5
|
-
// callers (see SchnorrChallengeFn), and the attestation message is the
|
|
6
|
-
// compiled `pureCircuits.signetAttestationMessage` — this file never
|
|
7
|
-
// re-implements either. Verification lives in circuits only
|
|
8
|
-
// (CompactStandardLibrary's jubjubSchnorrVerify, run by the signet contract
|
|
9
|
-
// at post time).
|
|
10
|
-
//
|
|
11
|
-
// This belongs in github.com/sig-net/signet.js as its Midnight adapter —
|
|
12
|
-
// kept here until upstreamed. The math is Midnight-specific (Jubjub embedded
|
|
13
|
-
// in BLS12-381 via @midnight-ntwrk/compact-runtime EC ops), which is exactly
|
|
14
|
-
// why signet.js does not cover it today.
|
|
15
|
-
|
|
16
|
-
import { randomBytes } from "node:crypto";
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
CompactTypeBytes,
|
|
20
|
-
CompactTypeJubjubPoint,
|
|
21
|
-
CompactTypeVector,
|
|
22
|
-
ecMulGenerator,
|
|
23
|
-
persistentHash,
|
|
24
|
-
type JubjubPoint,
|
|
25
|
-
} from "@midnight-ntwrk/compact-runtime";
|
|
26
|
-
|
|
27
|
-
// Re-exported because it appears throughout this module's public signatures
|
|
28
|
-
// (hashJubjubPoint, parse/formatJubjubPublicKey, JubjubKeypair.pk) — SDK
|
|
29
|
-
// consumers shouldn't have to depend on compact-runtime just for the type.
|
|
30
|
-
export type { JubjubPoint } from "@midnight-ntwrk/compact-runtime";
|
|
31
|
-
|
|
32
|
-
/** Jubjub curve scalar field order (group order of the generator). */
|
|
33
|
-
export const JUBJUB_ORDER = 6554484396890773809930967563523245729705921265872317281365359162392183254199n;
|
|
34
|
-
|
|
35
|
-
/** BLS12-381 scalar field order (the Compact `Field` type modulus). */
|
|
36
|
-
export const BLS_ORDER = 52435875175126190479447740508185965837690552500527637822603658699938581184513n;
|
|
37
|
-
|
|
38
|
-
const bytes32Type = new CompactTypeBytes(32);
|
|
39
|
-
const vec2x32Type = new CompactTypeVector(2, bytes32Type);
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Convert little-endian bytes to a bigint. Matches Compact's
|
|
43
|
-
* `Bytes<32> as Field` interpretation (and the MPC monitor's ABI-word
|
|
44
|
-
* decoding convention).
|
|
45
|
-
*
|
|
46
|
-
* @param bytes - Little-endian byte array.
|
|
47
|
-
* @returns The decoded non-negative integer.
|
|
48
|
-
*/
|
|
49
|
-
export function bytesToBigint(bytes: Uint8Array): bigint {
|
|
50
|
-
let result = 0n;
|
|
51
|
-
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
52
|
-
result = (result << 8n) | BigInt(bytes[i]);
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Convert a bigint to exactly 32 little-endian bytes. Matches Compact's
|
|
59
|
-
* `Field as Bytes<32>` encoding (the inverse of {@link bytesToBigint});
|
|
60
|
-
* negative inputs are interpreted in the BLS scalar field.
|
|
61
|
-
*
|
|
62
|
-
* @param n - The integer to encode.
|
|
63
|
-
* @returns The 32-byte little-endian encoding.
|
|
64
|
-
*/
|
|
65
|
-
export function bigintToBytes32(n: bigint): Uint8Array {
|
|
66
|
-
const buf = new Uint8Array(32);
|
|
67
|
-
let v = n < 0n ? n + BLS_ORDER : n;
|
|
68
|
-
for (let i = 0; i < 32; i++) {
|
|
69
|
-
buf[i] = Number(v & 0xffn);
|
|
70
|
-
v >>= 8n;
|
|
71
|
-
}
|
|
72
|
-
return buf;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** A Jubjub keypair: the secret scalar and its public curve point. */
|
|
76
|
-
export interface JubjubKeypair {
|
|
77
|
-
/** Secret scalar in `[1, JUBJUB_ORDER - 1]`. */
|
|
78
|
-
sk: bigint;
|
|
79
|
-
/** Public key: `sk * G` on Jubjub. */
|
|
80
|
-
pk: JubjubPoint;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Zero-pad an ASCII domain tag to 32 bytes (Compact `pad(32, ...)`). */
|
|
84
|
-
function pad32(tag: string): Uint8Array {
|
|
85
|
-
const padded = new Uint8Array(32);
|
|
86
|
-
new TextEncoder().encodeInto(tag, padded);
|
|
87
|
-
return padded;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Derive a Jubjub keypair from a seed — the same derivation the MPC server
|
|
92
|
-
* performs on its root key, so this MUST stay bit-identical to it:
|
|
93
|
-
* `sk = (persistentHash([pad32("jubjub:auth:"), seed]) mod (order - 1)) + 1`,
|
|
94
|
-
* `pk = sk * G`.
|
|
95
|
-
*
|
|
96
|
-
* @param seed - Seed bytes (e.g. the 32-byte MPC root key).
|
|
97
|
-
* @returns The derived {@link JubjubKeypair}.
|
|
98
|
-
*/
|
|
99
|
-
export function deriveJubjubKeypair(seed: Uint8Array): JubjubKeypair {
|
|
100
|
-
const skBytes = persistentHash(vec2x32Type, [pad32("jubjub:auth:"), seed]);
|
|
101
|
-
const sk = (bytesToBigint(skBytes) % (JUBJUB_ORDER - 1n)) + 1n;
|
|
102
|
-
const pk = ecMulGenerator(sk);
|
|
103
|
-
return { sk, pk };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ---- Schnorr signing ----
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* A Schnorr signature (TS twin of CompactStandardLibrary's
|
|
110
|
-
* `JubjubSchnorrSignature`).
|
|
111
|
-
*/
|
|
112
|
-
export interface SchnorrSignature {
|
|
113
|
-
/** Nonce commitment R = k * G. */
|
|
114
|
-
announcement: JubjubPoint;
|
|
115
|
-
/** Signature scalar s = (k + c * sk) mod JUBJUB_ORDER. */
|
|
116
|
-
response: bigint;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Computes the Schnorr challenge (the Poseidon transientHash output reduced
|
|
121
|
-
* into Jubjub's scalar field, exactly as `jubjubSchnorrVerify` computes it).
|
|
122
|
-
* ALWAYS inject the compiled circuit — signet-midnight's own
|
|
123
|
-
* `pureCircuits.schnorrChallenge` — never a TS re-implementation; keeping it
|
|
124
|
-
* injected keeps this signer decoupled from any one compiled artifact.
|
|
125
|
-
*/
|
|
126
|
-
export type SchnorrChallengeFn = (
|
|
127
|
-
annX: bigint,
|
|
128
|
-
annY: bigint,
|
|
129
|
-
pkX: bigint,
|
|
130
|
-
pkY: bigint,
|
|
131
|
-
msg: bigint[],
|
|
132
|
-
) => bigint;
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Sign a message with Schnorr on Jubjub, matching CompactStandardLibrary's
|
|
136
|
-
* `jubjubSchnorrVerify`: `c = challenge` (already reduced into Jubjub's
|
|
137
|
-
* scalar field by the compiled circuit), `s = (k + c * sk) mod JUBJUB_ORDER`.
|
|
138
|
-
* For signet attestations the message is the compiled
|
|
139
|
-
* `pureCircuits.signetAttestationMessage(requestId, outputData)`.
|
|
140
|
-
*
|
|
141
|
-
* @param sk - Jubjub private key scalar.
|
|
142
|
-
* @param msg - Message field limbs.
|
|
143
|
-
* @param schnorrChallenge - The compiled `pureCircuits.schnorrChallenge`.
|
|
144
|
-
* @returns The signature (announcement R, response s).
|
|
145
|
-
* @throws Error if `sk` reduces to zero mod the Jubjub order.
|
|
146
|
-
*/
|
|
147
|
-
export function schnorrSign(
|
|
148
|
-
sk: bigint,
|
|
149
|
-
msg: bigint[],
|
|
150
|
-
schnorrChallenge: SchnorrChallengeFn,
|
|
151
|
-
): SchnorrSignature {
|
|
152
|
-
const skReduced = ((sk % JUBJUB_ORDER) + JUBJUB_ORDER) % JUBJUB_ORDER;
|
|
153
|
-
if (skReduced === 0n) {
|
|
154
|
-
throw new Error("Private key must be non-zero mod JUBJUB_ORDER");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const pk = ecMulGenerator(skReduced);
|
|
158
|
-
const k = (bytesToBigint(randomBytes(32)) % JUBJUB_ORDER) || 1n;
|
|
159
|
-
const announcement = ecMulGenerator(k);
|
|
160
|
-
|
|
161
|
-
// schnorrChallenge returns the challenge already reduced into Jubjub's
|
|
162
|
-
// scalar field — use it as-is.
|
|
163
|
-
const c = schnorrChallenge(announcement.x, announcement.y, pk.x, pk.y, msg);
|
|
164
|
-
|
|
165
|
-
const response = (((k + c * skReduced) % JUBJUB_ORDER) + JUBJUB_ORDER) % JUBJUB_ORDER;
|
|
166
|
-
return { announcement, response };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Hash a Jubjub point exactly as the circuits do (`persistentHash<JubjubPoint>`)
|
|
171
|
-
* — the form the signet contract seals as `mpcPubKeyHash`.
|
|
172
|
-
*
|
|
173
|
-
* @param point - The point to hash.
|
|
174
|
-
* @returns The 32-byte hash.
|
|
175
|
-
*/
|
|
176
|
-
export function hashJubjubPoint(point: JubjubPoint): Uint8Array {
|
|
177
|
-
return persistentHash(CompactTypeJubjubPoint, point);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Parse a Jubjub public key from its `"x,y"` config/env form (decimal or
|
|
182
|
-
* 0x-hex field coordinates) — how deploys receive `MPC_JUBJUB_PK`.
|
|
183
|
-
*
|
|
184
|
-
* @param value - The `"x,y"` string.
|
|
185
|
-
* @returns The parsed point.
|
|
186
|
-
* @throws Error if the value is not two comma-separated field coordinates.
|
|
187
|
-
*/
|
|
188
|
-
export function parseJubjubPublicKey(value: string): JubjubPoint {
|
|
189
|
-
const parts = value.split(",").map((part) => part.trim());
|
|
190
|
-
if (parts.length !== 2 || parts.some((part) => part === "")) {
|
|
191
|
-
throw new Error(`expected a Jubjub public key as "x,y"; got "${value}"`);
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
return { x: BigInt(parts[0]), y: BigInt(parts[1]) };
|
|
195
|
-
} catch {
|
|
196
|
-
throw new Error(`Jubjub public key coordinates must be decimal or 0x-hex integers; got "${value}"`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Format a Jubjub public key as the `"x,y"` decimal config/env form that
|
|
202
|
-
* {@link parseJubjubPublicKey} accepts — the round-trip inverse, for handing
|
|
203
|
-
* a derived key to deploys as `MPC_JUBJUB_PK`.
|
|
204
|
-
*
|
|
205
|
-
* @param point - The point to format.
|
|
206
|
-
* @returns The `"x,y"` string with decimal coordinates.
|
|
207
|
-
*/
|
|
208
|
-
export function formatJubjubPublicKey(point: JubjubPoint): string {
|
|
209
|
-
return `${point.x},${point.y}`;
|
|
210
|
-
}
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
// MPC-style raw state reader for the signature-REQUESTS side: decode the signet
|
|
2
|
-
// request ledger fields out of a contract's raw state WITHOUT the compiled
|
|
3
|
-
// contract. This is how the MPC monitor consumes signet contracts — it has only
|
|
4
|
-
// a contract address, queries raw state from the indexer
|
|
5
|
-
// (queryContractState(address).data), and decodes by the signet layout
|
|
6
|
-
// convention: the request index is ledger field 0, the request counter field 1.
|
|
7
|
-
// The compiled contract's generated ledger() does exactly this walk internally;
|
|
8
|
-
// the record descriptors themselves are the parameterized twins in
|
|
9
|
-
// signet-requests.ts. The generic tree walk and shared base descriptors live
|
|
10
|
-
// in signature-state-reading.ts.
|
|
11
|
-
|
|
12
|
-
import type { CompactType } from "@midnight-ntwrk/compact-runtime";
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
requestIdHex,
|
|
16
|
-
signBidirectionalRequestDescriptor,
|
|
17
|
-
type RequestIdHex,
|
|
18
|
-
type SignBidirectionalRequest,
|
|
19
|
-
type SignBidirectionalRequestIndex,
|
|
20
|
-
} from "./signet-requests.ts";
|
|
21
|
-
|
|
22
|
-
/** The aligned-value cursor every descriptor's `fromValue` consumes. */
|
|
23
|
-
type AlignedValue = Parameters<CompactType<unknown>["fromValue"]>[0];
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
requestIdType,
|
|
27
|
-
signetFieldNode,
|
|
28
|
-
u64,
|
|
29
|
-
type RawContractState,
|
|
30
|
-
} from "./signature-state-reading.ts";
|
|
31
|
-
|
|
32
|
-
/** Signet layout convention: the request index is ledger field 0. */
|
|
33
|
-
export const SIGNET_REQUESTS_INDEX_FIELD = 0;
|
|
34
|
-
|
|
35
|
-
/** Signet layout convention: the request counter (`SignetNonce`) is ledger field 1. */
|
|
36
|
-
export const SIGNET_NONCE_FIELD = 1;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Aligned-value entry count of a request record EXCLUDING the capacity-scaled
|
|
40
|
-
* vectors: requestNonce (1) + txParamType (1, enums are one atom whatever
|
|
41
|
-
* their byte width) + the EVMType2TxParams fixed fields (to..value = 7,
|
|
42
|
-
* accessListEntryCount = 1) + the calldata Maybe's is_some (1), selector (1)
|
|
43
|
-
* and noWords (1) + the routing fields caip2Id..respondSerializationSchema (8).
|
|
44
|
-
* A stored request cell therefore holds
|
|
45
|
-
* `REQUEST_FIXED_VALUE_ATOMS + maxCalldataWords
|
|
46
|
-
* + maxAccessListEntries·(2 + maxStorageKeysPerEntry)`
|
|
47
|
-
* entries (each calldata word is one Bytes<32> atom; each access-list entry is
|
|
48
|
-
* address + storageKeyCount + its keys).
|
|
49
|
-
*/
|
|
50
|
-
export const REQUEST_FIXED_VALUE_ATOMS = 21;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Recover a record's capacity instantiation (maxCalldataWords,
|
|
54
|
-
* maxAccessListEntries, maxStorageKeysPerEntry) from its aligned-value atom
|
|
55
|
-
* count and decode it. Unlike the old single-vector layout, one atom count no
|
|
56
|
-
* longer determines the capacities uniquely, so candidates are enumerated —
|
|
57
|
-
* access-list-free first (today's only producer, the vault, is <2, 0, 0>) —
|
|
58
|
-
* and validated by the decode itself: the descriptors' Bytes length checks
|
|
59
|
-
* and the enum range check reject wrong splits, and a decode that leaves
|
|
60
|
-
* atoms unconsumed is rejected here.
|
|
61
|
-
*
|
|
62
|
-
* @param atoms - The record cell's aligned value (a fresh copy per attempt).
|
|
63
|
-
* @returns The decoded record.
|
|
64
|
-
* @throws Error if no capacity split decodes the value cleanly.
|
|
65
|
-
*/
|
|
66
|
-
function decodeSignBidirectionalRequest(
|
|
67
|
-
atoms: AlignedValue,
|
|
68
|
-
): SignBidirectionalRequest {
|
|
69
|
-
const variable = atoms.length - REQUEST_FIXED_VALUE_ATOMS;
|
|
70
|
-
if (variable < 0) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
`request record has ${atoms.length} value entries — fewer than the ` +
|
|
73
|
-
`${REQUEST_FIXED_VALUE_ATOMS} its fixed fields need`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
const attempt = (
|
|
77
|
-
maxWords: number,
|
|
78
|
-
maxEntries: number,
|
|
79
|
-
maxKeys: number,
|
|
80
|
-
): SignBidirectionalRequest | undefined => {
|
|
81
|
-
const cursor = [...atoms] as AlignedValue;
|
|
82
|
-
try {
|
|
83
|
-
const record = signBidirectionalRequestDescriptor(
|
|
84
|
-
maxWords,
|
|
85
|
-
maxEntries,
|
|
86
|
-
maxKeys,
|
|
87
|
-
).fromValue(cursor);
|
|
88
|
-
// A clean decode consumes the record exactly.
|
|
89
|
-
return cursor.length === 0 ? record : undefined;
|
|
90
|
-
} catch {
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
// No access list: variable atoms are calldata words alone (one atom each).
|
|
95
|
-
{
|
|
96
|
-
const record = attempt(variable, 0, 0);
|
|
97
|
-
if (record !== undefined) return record;
|
|
98
|
-
}
|
|
99
|
-
// With an access list: E entries of (2 + K) atoms, the rest words.
|
|
100
|
-
for (let entries = 1; entries * 2 <= variable; entries++) {
|
|
101
|
-
for (let keys = 0; entries * (2 + keys) <= variable; keys++) {
|
|
102
|
-
const words = variable - entries * (2 + keys);
|
|
103
|
-
const record = attempt(words, entries, keys);
|
|
104
|
-
if (record !== undefined) return record;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
throw new Error(
|
|
108
|
-
`request record with ${atoms.length} value entries matches no ` +
|
|
109
|
-
`(calldata words, access-list entries, storage keys) capacity split`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* The decoded signet ledger fields of a conforming contract — the complete
|
|
115
|
-
* view the MPC needs: the request index (field 0) and the request counter
|
|
116
|
-
* (field 1, Compact `SignetNonce`), whose value doubles as a cheap
|
|
117
|
-
* change-detection signal (unchanged nonce ⇒ no new requests).
|
|
118
|
-
*/
|
|
119
|
-
export interface SignetRequestsLedger {
|
|
120
|
-
/** The request counter (ledger field {@link SIGNET_NONCE_FIELD}). */
|
|
121
|
-
nonce: bigint;
|
|
122
|
-
/**
|
|
123
|
-
* The request index (ledger field {@link SIGNET_REQUESTS_INDEX_FIELD}),
|
|
124
|
-
* keyed by hex request id.
|
|
125
|
-
*/
|
|
126
|
-
requestsIndex: SignBidirectionalRequestIndex;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* MPC-style read: parse the signet ledger fields out of raw contract state
|
|
131
|
-
* by field position alone — no compiled contract, no generated `ledger()`,
|
|
132
|
-
* only the signet layout convention and the canonical descriptors from
|
|
133
|
-
* signet-requests.ts.
|
|
134
|
-
*
|
|
135
|
-
* @param raw - Raw contract state, e.g. `queryContractState(address).data`
|
|
136
|
-
* from the indexer or `ctx.currentQueryContext.state` from the simulator.
|
|
137
|
-
* @returns The decoded {@link SignetRequestsLedger}.
|
|
138
|
-
* @throws Error if a field is missing, has the wrong state-value shape, or a
|
|
139
|
-
* record matches no capacity split.
|
|
140
|
-
*/
|
|
141
|
-
export function readSignetRequestsLedgerFromState(raw: RawContractState): SignetRequestsLedger {
|
|
142
|
-
const map = signetFieldNode(raw, SIGNET_REQUESTS_INDEX_FIELD).asMap();
|
|
143
|
-
if (map === undefined) {
|
|
144
|
-
throw new Error(`Ledger field ${SIGNET_REQUESTS_INDEX_FIELD} is not a Map`);
|
|
145
|
-
}
|
|
146
|
-
const requestsIndex: SignBidirectionalRequestIndex = new Map();
|
|
147
|
-
for (const key of map.keys()) {
|
|
148
|
-
// fromValue consumes its input, so hand each descriptor a copy.
|
|
149
|
-
const requestId = requestIdType.fromValue([...key.value]);
|
|
150
|
-
const cell = map.get(key)?.asCell();
|
|
151
|
-
if (cell === undefined) continue;
|
|
152
|
-
requestsIndex.set(
|
|
153
|
-
requestIdHex(requestId),
|
|
154
|
-
decodeSignBidirectionalRequest(cell.value),
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const nonceCell = signetFieldNode(raw, SIGNET_NONCE_FIELD).asCell();
|
|
159
|
-
if (nonceCell === undefined) {
|
|
160
|
-
throw new Error(`Ledger field ${SIGNET_NONCE_FIELD} is not a Cell`);
|
|
161
|
-
}
|
|
162
|
-
const nonce = u64.fromValue([...nonceCell.value]);
|
|
163
|
-
|
|
164
|
-
return { nonce, requestsIndex };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Look up ONE request by id in a contract's request index at an arbitrary
|
|
169
|
-
* ledger field — the generalization of {@link readSignetRequestsLedgerFromState}
|
|
170
|
-
* the event path needs: a `SignBidirectionalEvent` names both the requester
|
|
171
|
-
* contract AND which field holds its index, so the field is no longer assumed
|
|
172
|
-
* to be {@link SIGNET_REQUESTS_INDEX_FIELD}.
|
|
173
|
-
*
|
|
174
|
-
* This is the mandatory membership check of the event flow (see
|
|
175
|
-
* signet-request-resolver.ts): `undefined` means the id is NOT a member of the
|
|
176
|
-
* index at `fieldIndex` — the notification is forged, stale, points at the
|
|
177
|
-
* wrong field, or the request is not yet indexed — and the caller MUST drop it.
|
|
178
|
-
* Every non-membership case (field out of range, field is not a Map, id absent,
|
|
179
|
-
* a cell that fails to decode) returns `undefined` rather than throwing, so a
|
|
180
|
-
* malformed or adversarial event can never crash the reader.
|
|
181
|
-
*
|
|
182
|
-
* Only the matched record is decoded (not the whole index), and it is decoded
|
|
183
|
-
* by the same {@link decodeSignBidirectionalRequest} the full reader uses, so
|
|
184
|
-
* the result is `toEqual` to `readSignetRequestsLedgerFromState(raw)
|
|
185
|
-
* .requestsIndex.get(requestId)` when `fieldIndex` is the index field.
|
|
186
|
-
*
|
|
187
|
-
* @param raw - Raw contract state, e.g. `queryContractState(address).data`.
|
|
188
|
-
* @param fieldIndex - Ledger field position of the request index in `raw`.
|
|
189
|
-
* @param requestId - The request id to look up.
|
|
190
|
-
* @returns The stored request record, or `undefined` when it is not a member.
|
|
191
|
-
*/
|
|
192
|
-
export function lookupSignetRequestAt(
|
|
193
|
-
raw: RawContractState,
|
|
194
|
-
fieldIndex: number,
|
|
195
|
-
requestId: RequestIdHex,
|
|
196
|
-
): SignBidirectionalRequest | undefined {
|
|
197
|
-
let node;
|
|
198
|
-
try {
|
|
199
|
-
node = signetFieldNode(raw, fieldIndex);
|
|
200
|
-
} catch {
|
|
201
|
-
return undefined; // field position out of range for this contract
|
|
202
|
-
}
|
|
203
|
-
const map = node.asMap();
|
|
204
|
-
if (map === undefined) {
|
|
205
|
-
return undefined; // the named field is not a request index
|
|
206
|
-
}
|
|
207
|
-
for (const key of map.keys()) {
|
|
208
|
-
// fromValue consumes its input, so hand each descriptor a copy.
|
|
209
|
-
if (requestIdHex(requestIdType.fromValue([...key.value])) !== requestId) {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
const cell = map.get(key)?.asCell();
|
|
213
|
-
if (cell === undefined) {
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
try {
|
|
217
|
-
return decodeSignBidirectionalRequest(cell.value);
|
|
218
|
-
} catch {
|
|
219
|
-
return undefined; // a cell that is not a decodable request record
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return undefined; // id absent from the index
|
|
223
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// Client-side verification of MPC signature responses. Posting to the
|
|
2
|
-
// signet contract's signature response log is UNAUTHENTICATED (see "Signet
|
|
3
|
-
// Layout" in Signet.compact), so a poller must verify every posted response
|
|
4
|
-
// before trusting it: rebuild the unsigned EVM transaction from the on-ledger
|
|
5
|
-
// request record — assembled exactly as the MPC assembles it (sig-net/mpc
|
|
6
|
-
// response server, managed/erc20-vault/signet/calldata-builder.ts) — and
|
|
7
|
-
// check that the posted `{ bigR, s, recoveryId }` signature recovers to the
|
|
8
|
-
// requester's derived EVM address over that transaction's signing hash.
|
|
9
|
-
|
|
10
|
-
import { getAddress, recoverAddress } from "ethers";
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
signBidirectionalRequestToUnsignedEVMTransaction,
|
|
14
|
-
signatureResponseToSignature,
|
|
15
|
-
type SignBidirectionalRequest,
|
|
16
|
-
} from "./signet-requests.ts";
|
|
17
|
-
import type { SignatureResponse } from "./signet-contract-state-reader.ts";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Recover the EVM address that produced a response signature, over the
|
|
21
|
-
* signing hash of the transaction the request describes.
|
|
22
|
-
*
|
|
23
|
-
* @param request - The on-ledger request record the response answers.
|
|
24
|
-
* @param response - The posted signature record answering it.
|
|
25
|
-
* @returns The checksummed recovered signer address.
|
|
26
|
-
* @throws Error if the response is not a decodable signature or the request
|
|
27
|
-
* record is malformed (see
|
|
28
|
-
* {@link signBidirectionalRequestToUnsignedEVMTransaction}).
|
|
29
|
-
*/
|
|
30
|
-
export function recoverSignatureResponseSigner(
|
|
31
|
-
request: SignBidirectionalRequest,
|
|
32
|
-
response: SignatureResponse,
|
|
33
|
-
): string {
|
|
34
|
-
return recoverAddress(
|
|
35
|
-
signBidirectionalRequestToUnsignedEVMTransaction(request).unsignedHash,
|
|
36
|
-
signatureResponseToSignature(response),
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Verify a posted response against its request: does the signature recover
|
|
42
|
-
* to `expectedSigner`? Never throws — a response that is malformed or signed
|
|
43
|
-
* by anyone else is simply not valid, which is the expected state of affairs
|
|
44
|
-
* on an unauthenticated log.
|
|
45
|
-
*
|
|
46
|
-
* @param request - The on-ledger request record the response answers.
|
|
47
|
-
* @param response - The posted signature record answering it.
|
|
48
|
-
* @param expectedSigner - The EVM address (any case, 0x hex) that must have signed.
|
|
49
|
-
* @returns `true` iff the response is a valid signature by `expectedSigner`.
|
|
50
|
-
*/
|
|
51
|
-
export function verifySignatureResponse(
|
|
52
|
-
request: SignBidirectionalRequest,
|
|
53
|
-
response: SignatureResponse,
|
|
54
|
-
expectedSigner: string,
|
|
55
|
-
): boolean {
|
|
56
|
-
try {
|
|
57
|
-
return (
|
|
58
|
-
recoverSignatureResponseSigner(request, response) ===
|
|
59
|
-
getAddress(expectedSigner)
|
|
60
|
-
);
|
|
61
|
-
} catch {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// Common signet raw-state reading, shared by the request and response readers
|
|
2
|
-
// (signature-requests-state-reader.ts, signet-contract-state-reader.ts).
|
|
3
|
-
// This is the MPC-/client-style path: decode a contract's ledger fields out of
|
|
4
|
-
// its raw state WITHOUT the compiled contract, walking the state tree by field
|
|
5
|
-
// position alone. Here live the generic tree walk (RawContractState,
|
|
6
|
-
// signetFieldNode) and the base type descriptors both readers share; each
|
|
7
|
-
// reader adds only the descriptors for its own layout.
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
CompactTypeBytes,
|
|
11
|
-
CompactTypeUnsignedInteger,
|
|
12
|
-
type CompactType,
|
|
13
|
-
type StateValue,
|
|
14
|
-
} from "@midnight-ntwrk/compact-runtime";
|
|
15
|
-
|
|
16
|
-
import { type RequestId } from "./signet-requests.ts";
|
|
17
|
-
|
|
18
|
-
// ---- Shared base type descriptors ----
|
|
19
|
-
// fromValue consumes the aligned value sequentially, so any width change here
|
|
20
|
-
// is silent data corruption, not an error. These mirror the Compact base types
|
|
21
|
-
// used across both signet layouts.
|
|
22
|
-
|
|
23
|
-
/** Descriptor for a Compact `Uint<64>` (8-byte unsigned integer). */
|
|
24
|
-
export const u64 = new CompactTypeUnsignedInteger(18446744073709551615n, 8);
|
|
25
|
-
|
|
26
|
-
/** Descriptor for a Compact `Bytes<32>`. */
|
|
27
|
-
export const bytes32 = new CompactTypeBytes(32);
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Descriptor for a request id ledger key (Compact `RequestId`, a
|
|
31
|
-
* nominal `Bytes<32>`). Use it to encode a {@link RequestId} into the
|
|
32
|
-
* aligned form the state tree stores, or decode one back.
|
|
33
|
-
*/
|
|
34
|
-
export const requestIdType: CompactType<RequestId> = bytes32;
|
|
35
|
-
|
|
36
|
-
// ---- Raw state walk ----
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* What the indexer / simulator hands us: a bare `StateValue`, or anything
|
|
40
|
-
* wrapping one under `.state` (e.g. `ChargedState`,
|
|
41
|
-
* `queryContractState(address).data`).
|
|
42
|
-
*/
|
|
43
|
-
export type RawContractState = StateValue | { state: StateValue };
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Unwrap a {@link RawContractState} to the underlying `StateValue`.
|
|
47
|
-
*
|
|
48
|
-
* @param raw - Bare state value or a `.state`-carrying wrapper.
|
|
49
|
-
* @returns The bare `StateValue`.
|
|
50
|
-
*/
|
|
51
|
-
const unwrap = (raw: RawContractState): StateValue =>
|
|
52
|
-
"state" in raw ? raw.state : raw;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Resolve a flat ledger field index to its node in the raw state tree.
|
|
56
|
-
*
|
|
57
|
-
* The runtime stores ledger fields as a root array and chunks them one level
|
|
58
|
-
* deep once the field count grows, so chunked roots are flattened before
|
|
59
|
-
* indexing. Chunk detection assumes signet field 0 is a `Map`, never a
|
|
60
|
-
* `List` (whose node is also array-typed) — guaranteed by the signet layout
|
|
61
|
-
* convention.
|
|
62
|
-
*
|
|
63
|
-
* @param raw - Raw contract state from the indexer or simulator.
|
|
64
|
-
* @param flatIndex - Zero-based ledger field position in declaration order.
|
|
65
|
-
* @returns The `StateValue` node holding that field.
|
|
66
|
-
* @throws Error if `flatIndex` is beyond the contract's field count.
|
|
67
|
-
*/
|
|
68
|
-
export function signetFieldNode(
|
|
69
|
-
raw: RawContractState,
|
|
70
|
-
flatIndex: number,
|
|
71
|
-
): StateValue {
|
|
72
|
-
const root = unwrap(raw);
|
|
73
|
-
if (root.type() !== "array") {
|
|
74
|
-
if (flatIndex === 0) return root;
|
|
75
|
-
throw new Error(`Field index ${flatIndex} out of range: root is a leaf`);
|
|
76
|
-
}
|
|
77
|
-
const nodes = root.asArray() ?? [];
|
|
78
|
-
const fields =
|
|
79
|
-
nodes[0]?.type() === "array"
|
|
80
|
-
? nodes.flatMap((chunk) => chunk.asArray() ?? [])
|
|
81
|
-
: nodes;
|
|
82
|
-
const node = fields[flatIndex];
|
|
83
|
-
if (node === undefined) {
|
|
84
|
-
throw new Error(`Field index ${flatIndex} out of range`);
|
|
85
|
-
}
|
|
86
|
-
return node;
|
|
87
|
-
}
|