@sip-protocol/sdk 0.2.0 → 0.2.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.
- package/dist/browser.d.mts +2 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +7501 -8281
- package/dist/browser.mjs +625 -18
- package/dist/chunk-4VJHI66K.mjs +12120 -0
- package/dist/chunk-DU7LQDD2.mjs +10148 -0
- package/dist/chunk-UHZKNGIT.mjs +223 -0
- package/dist/chunk-VITVG25F.mjs +982 -0
- package/dist/index.d.mts +31 -493
- package/dist/index.d.ts +31 -493
- package/dist/index.js +3 -2367
- package/dist/index.mjs +17 -19
- package/dist/noir-BHQtFvRk.d.mts +467 -0
- package/dist/noir-BHQtFvRk.d.ts +467 -0
- package/dist/proofs/noir.d.mts +2 -0
- package/dist/proofs/noir.d.ts +2 -0
- package/dist/proofs/noir.js +1862 -0
- package/dist/proofs/noir.mjs +790 -0
- package/package.json +8 -6
- package/src/browser.ts +4 -2
- package/src/index.ts +6 -3
- package/src/proofs/index.ts +15 -7
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fulfillment_proof_default,
|
|
3
|
+
funding_proof_default,
|
|
4
|
+
validity_proof_default
|
|
5
|
+
} from "../chunk-VITVG25F.mjs";
|
|
6
|
+
import {
|
|
7
|
+
ProofError,
|
|
8
|
+
ProofGenerationError
|
|
9
|
+
} from "../chunk-UHZKNGIT.mjs";
|
|
10
|
+
|
|
11
|
+
// src/proofs/noir.ts
|
|
12
|
+
import { Noir } from "@noir-lang/noir_js";
|
|
13
|
+
import { UltraHonkBackend } from "@aztec/bb.js";
|
|
14
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
15
|
+
var NoirProofProvider = class {
|
|
16
|
+
framework = "noir";
|
|
17
|
+
_isReady = false;
|
|
18
|
+
config;
|
|
19
|
+
// Circuit instances
|
|
20
|
+
fundingNoir = null;
|
|
21
|
+
fundingBackend = null;
|
|
22
|
+
validityNoir = null;
|
|
23
|
+
validityBackend = null;
|
|
24
|
+
fulfillmentNoir = null;
|
|
25
|
+
fulfillmentBackend = null;
|
|
26
|
+
constructor(config = {}) {
|
|
27
|
+
this.config = {
|
|
28
|
+
backend: "barretenberg",
|
|
29
|
+
verbose: false,
|
|
30
|
+
...config
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
get isReady() {
|
|
34
|
+
return this._isReady;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Derive secp256k1 public key coordinates from a private key
|
|
38
|
+
*
|
|
39
|
+
* Utility method that can be used to generate public key coordinates
|
|
40
|
+
* for use in ValidityProofParams.senderPublicKey or NoirProviderConfig.oraclePublicKey
|
|
41
|
+
*
|
|
42
|
+
* @param privateKey - 32-byte private key
|
|
43
|
+
* @returns X and Y coordinates as 32-byte arrays
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const privateKey = new Uint8Array(32).fill(1) // Your secret key
|
|
48
|
+
* const publicKey = NoirProofProvider.derivePublicKey(privateKey)
|
|
49
|
+
*
|
|
50
|
+
* // Use for oracle configuration
|
|
51
|
+
* const provider = new NoirProofProvider({
|
|
52
|
+
* oraclePublicKey: publicKey
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
* // Or use for validity proof params
|
|
56
|
+
* const validityParams = {
|
|
57
|
+
* // ... other params
|
|
58
|
+
* senderPublicKey: {
|
|
59
|
+
* x: new Uint8Array(publicKey.x),
|
|
60
|
+
* y: new Uint8Array(publicKey.y)
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
static derivePublicKey(privateKey) {
|
|
66
|
+
const uncompressedPubKey = secp256k1.getPublicKey(privateKey, false);
|
|
67
|
+
const x = Array.from(uncompressedPubKey.slice(1, 33));
|
|
68
|
+
const y = Array.from(uncompressedPubKey.slice(33, 65));
|
|
69
|
+
return { x, y };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Initialize the Noir provider
|
|
73
|
+
*
|
|
74
|
+
* Loads circuit artifacts and initializes the proving backend.
|
|
75
|
+
*/
|
|
76
|
+
async initialize() {
|
|
77
|
+
if (this._isReady) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
if (this.config.verbose) {
|
|
82
|
+
console.log("[NoirProofProvider] Initializing...");
|
|
83
|
+
}
|
|
84
|
+
const fundingCircuit = funding_proof_default;
|
|
85
|
+
this.fundingBackend = new UltraHonkBackend(fundingCircuit.bytecode);
|
|
86
|
+
this.fundingNoir = new Noir(fundingCircuit);
|
|
87
|
+
if (this.config.verbose) {
|
|
88
|
+
console.log("[NoirProofProvider] Funding circuit loaded");
|
|
89
|
+
const artifactVersion = funding_proof_default.noir_version;
|
|
90
|
+
console.log(`[NoirProofProvider] Noir version: ${artifactVersion ?? "unknown"}`);
|
|
91
|
+
}
|
|
92
|
+
const validityCircuit = validity_proof_default;
|
|
93
|
+
this.validityBackend = new UltraHonkBackend(validityCircuit.bytecode);
|
|
94
|
+
this.validityNoir = new Noir(validityCircuit);
|
|
95
|
+
if (this.config.verbose) {
|
|
96
|
+
console.log("[NoirProofProvider] Validity circuit loaded");
|
|
97
|
+
}
|
|
98
|
+
const fulfillmentCircuit = fulfillment_proof_default;
|
|
99
|
+
this.fulfillmentBackend = new UltraHonkBackend(fulfillmentCircuit.bytecode);
|
|
100
|
+
this.fulfillmentNoir = new Noir(fulfillmentCircuit);
|
|
101
|
+
if (this.config.verbose) {
|
|
102
|
+
console.log("[NoirProofProvider] Fulfillment circuit loaded");
|
|
103
|
+
}
|
|
104
|
+
this._isReady = true;
|
|
105
|
+
if (this.config.verbose) {
|
|
106
|
+
console.log("[NoirProofProvider] Initialization complete");
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw new ProofError(
|
|
110
|
+
`Failed to initialize NoirProofProvider: ${error instanceof Error ? error.message : String(error)}`,
|
|
111
|
+
"SIP_4003" /* PROOF_NOT_IMPLEMENTED */,
|
|
112
|
+
{ context: { error } }
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Generate a Funding Proof using Noir circuits
|
|
118
|
+
*
|
|
119
|
+
* Proves: balance >= minimumRequired without revealing balance
|
|
120
|
+
*
|
|
121
|
+
* @see docs/specs/FUNDING-PROOF.md
|
|
122
|
+
*/
|
|
123
|
+
async generateFundingProof(params) {
|
|
124
|
+
this.ensureReady();
|
|
125
|
+
if (!this.fundingNoir || !this.fundingBackend) {
|
|
126
|
+
throw new ProofGenerationError(
|
|
127
|
+
"funding",
|
|
128
|
+
"Funding circuit not initialized"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
if (this.config.verbose) {
|
|
133
|
+
console.log("[NoirProofProvider] Generating funding proof...");
|
|
134
|
+
}
|
|
135
|
+
const { commitmentHash, blindingField } = await this.computeCommitmentHash(
|
|
136
|
+
params.balance,
|
|
137
|
+
params.blindingFactor,
|
|
138
|
+
params.assetId
|
|
139
|
+
);
|
|
140
|
+
const witnessInputs = {
|
|
141
|
+
// Public inputs
|
|
142
|
+
commitment_hash: commitmentHash,
|
|
143
|
+
minimum_required: params.minimumRequired.toString(),
|
|
144
|
+
asset_id: this.assetIdToField(params.assetId),
|
|
145
|
+
// Private inputs
|
|
146
|
+
balance: params.balance.toString(),
|
|
147
|
+
blinding: blindingField
|
|
148
|
+
};
|
|
149
|
+
if (this.config.verbose) {
|
|
150
|
+
console.log("[NoirProofProvider] Witness inputs:", {
|
|
151
|
+
commitment_hash: commitmentHash,
|
|
152
|
+
minimum_required: params.minimumRequired.toString(),
|
|
153
|
+
asset_id: this.assetIdToField(params.assetId),
|
|
154
|
+
balance: "[PRIVATE]",
|
|
155
|
+
blinding: "[PRIVATE]"
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const { witness } = await this.fundingNoir.execute(witnessInputs);
|
|
159
|
+
if (this.config.verbose) {
|
|
160
|
+
console.log("[NoirProofProvider] Witness generated, creating proof...");
|
|
161
|
+
}
|
|
162
|
+
const proofData = await this.fundingBackend.generateProof(witness);
|
|
163
|
+
if (this.config.verbose) {
|
|
164
|
+
console.log("[NoirProofProvider] Proof generated successfully");
|
|
165
|
+
}
|
|
166
|
+
const publicInputs = [
|
|
167
|
+
`0x${commitmentHash}`,
|
|
168
|
+
`0x${params.minimumRequired.toString(16).padStart(16, "0")}`,
|
|
169
|
+
`0x${this.assetIdToField(params.assetId)}`
|
|
170
|
+
];
|
|
171
|
+
const proof = {
|
|
172
|
+
type: "funding",
|
|
173
|
+
proof: `0x${Buffer.from(proofData.proof).toString("hex")}`,
|
|
174
|
+
publicInputs
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
proof,
|
|
178
|
+
publicInputs
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
182
|
+
if (message.includes("Insufficient balance")) {
|
|
183
|
+
throw new ProofGenerationError(
|
|
184
|
+
"funding",
|
|
185
|
+
"Insufficient balance to generate proof",
|
|
186
|
+
error instanceof Error ? error : void 0
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (message.includes("Commitment hash mismatch")) {
|
|
190
|
+
throw new ProofGenerationError(
|
|
191
|
+
"funding",
|
|
192
|
+
"Commitment hash verification failed",
|
|
193
|
+
error instanceof Error ? error : void 0
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
throw new ProofGenerationError(
|
|
197
|
+
"funding",
|
|
198
|
+
`Failed to generate funding proof: ${message}`,
|
|
199
|
+
error instanceof Error ? error : void 0
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Generate a Validity Proof using Noir circuits
|
|
205
|
+
*
|
|
206
|
+
* Proves: Intent is authorized by sender without revealing identity
|
|
207
|
+
*
|
|
208
|
+
* @see docs/specs/VALIDITY-PROOF.md
|
|
209
|
+
*/
|
|
210
|
+
async generateValidityProof(params) {
|
|
211
|
+
this.ensureReady();
|
|
212
|
+
if (!this.validityNoir || !this.validityBackend) {
|
|
213
|
+
throw new ProofGenerationError(
|
|
214
|
+
"validity",
|
|
215
|
+
"Validity circuit not initialized"
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
if (this.config.verbose) {
|
|
220
|
+
console.log("[NoirProofProvider] Generating validity proof...");
|
|
221
|
+
}
|
|
222
|
+
const intentHashField = this.hexToField(params.intentHash);
|
|
223
|
+
const senderAddressField = this.hexToField(params.senderAddress);
|
|
224
|
+
const senderBlindingField = this.bytesToField(params.senderBlinding);
|
|
225
|
+
const senderSecretField = this.bytesToField(params.senderSecret);
|
|
226
|
+
const nonceField = this.bytesToField(params.nonce);
|
|
227
|
+
const { commitmentX, commitmentY } = await this.computeSenderCommitment(
|
|
228
|
+
senderAddressField,
|
|
229
|
+
senderBlindingField
|
|
230
|
+
);
|
|
231
|
+
const nullifier = await this.computeNullifier(
|
|
232
|
+
senderSecretField,
|
|
233
|
+
intentHashField,
|
|
234
|
+
nonceField
|
|
235
|
+
);
|
|
236
|
+
const signature = Array.from(params.authorizationSignature);
|
|
237
|
+
const messageHash = this.fieldToBytes32(intentHashField);
|
|
238
|
+
let pubKeyX;
|
|
239
|
+
let pubKeyY;
|
|
240
|
+
if (params.senderPublicKey) {
|
|
241
|
+
pubKeyX = Array.from(params.senderPublicKey.x);
|
|
242
|
+
pubKeyY = Array.from(params.senderPublicKey.y);
|
|
243
|
+
} else {
|
|
244
|
+
const coords = this.getPublicKeyCoordinates(params.senderSecret);
|
|
245
|
+
pubKeyX = coords.x;
|
|
246
|
+
pubKeyY = coords.y;
|
|
247
|
+
}
|
|
248
|
+
const witnessInputs = {
|
|
249
|
+
// Public inputs
|
|
250
|
+
intent_hash: intentHashField,
|
|
251
|
+
sender_commitment_x: commitmentX,
|
|
252
|
+
sender_commitment_y: commitmentY,
|
|
253
|
+
nullifier,
|
|
254
|
+
timestamp: params.timestamp.toString(),
|
|
255
|
+
expiry: params.expiry.toString(),
|
|
256
|
+
// Private inputs
|
|
257
|
+
sender_address: senderAddressField,
|
|
258
|
+
sender_blinding: senderBlindingField,
|
|
259
|
+
sender_secret: senderSecretField,
|
|
260
|
+
pub_key_x: pubKeyX,
|
|
261
|
+
pub_key_y: pubKeyY,
|
|
262
|
+
signature,
|
|
263
|
+
message_hash: messageHash,
|
|
264
|
+
nonce: nonceField
|
|
265
|
+
};
|
|
266
|
+
if (this.config.verbose) {
|
|
267
|
+
console.log("[NoirProofProvider] Validity witness inputs:", {
|
|
268
|
+
intent_hash: intentHashField,
|
|
269
|
+
sender_commitment_x: commitmentX,
|
|
270
|
+
sender_commitment_y: commitmentY,
|
|
271
|
+
nullifier,
|
|
272
|
+
timestamp: params.timestamp,
|
|
273
|
+
expiry: params.expiry,
|
|
274
|
+
sender_address: "[PRIVATE]",
|
|
275
|
+
sender_blinding: "[PRIVATE]",
|
|
276
|
+
sender_secret: "[PRIVATE]",
|
|
277
|
+
signature: "[PRIVATE]"
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const { witness } = await this.validityNoir.execute(witnessInputs);
|
|
281
|
+
if (this.config.verbose) {
|
|
282
|
+
console.log("[NoirProofProvider] Validity witness generated, creating proof...");
|
|
283
|
+
}
|
|
284
|
+
const proofData = await this.validityBackend.generateProof(witness);
|
|
285
|
+
if (this.config.verbose) {
|
|
286
|
+
console.log("[NoirProofProvider] Validity proof generated successfully");
|
|
287
|
+
}
|
|
288
|
+
const publicInputs = [
|
|
289
|
+
`0x${intentHashField}`,
|
|
290
|
+
`0x${commitmentX}`,
|
|
291
|
+
`0x${commitmentY}`,
|
|
292
|
+
`0x${nullifier}`,
|
|
293
|
+
`0x${params.timestamp.toString(16).padStart(16, "0")}`,
|
|
294
|
+
`0x${params.expiry.toString(16).padStart(16, "0")}`
|
|
295
|
+
];
|
|
296
|
+
const proof = {
|
|
297
|
+
type: "validity",
|
|
298
|
+
proof: `0x${Buffer.from(proofData.proof).toString("hex")}`,
|
|
299
|
+
publicInputs
|
|
300
|
+
};
|
|
301
|
+
return {
|
|
302
|
+
proof,
|
|
303
|
+
publicInputs
|
|
304
|
+
};
|
|
305
|
+
} catch (error) {
|
|
306
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
307
|
+
if (message.includes("Sender commitment")) {
|
|
308
|
+
throw new ProofGenerationError(
|
|
309
|
+
"validity",
|
|
310
|
+
"Sender commitment verification failed",
|
|
311
|
+
error instanceof Error ? error : void 0
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (message.includes("Invalid ECDSA")) {
|
|
315
|
+
throw new ProofGenerationError(
|
|
316
|
+
"validity",
|
|
317
|
+
"Authorization signature verification failed",
|
|
318
|
+
error instanceof Error ? error : void 0
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (message.includes("Nullifier mismatch")) {
|
|
322
|
+
throw new ProofGenerationError(
|
|
323
|
+
"validity",
|
|
324
|
+
"Nullifier derivation failed",
|
|
325
|
+
error instanceof Error ? error : void 0
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (message.includes("Intent expired")) {
|
|
329
|
+
throw new ProofGenerationError(
|
|
330
|
+
"validity",
|
|
331
|
+
"Intent has expired (timestamp >= expiry)",
|
|
332
|
+
error instanceof Error ? error : void 0
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
throw new ProofGenerationError(
|
|
336
|
+
"validity",
|
|
337
|
+
`Failed to generate validity proof: ${message}`,
|
|
338
|
+
error instanceof Error ? error : void 0
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Generate a Fulfillment Proof using Noir circuits
|
|
344
|
+
*
|
|
345
|
+
* Proves: Solver correctly executed the intent and delivered the required
|
|
346
|
+
* output to the recipient, without revealing execution path or liquidity sources.
|
|
347
|
+
*
|
|
348
|
+
* @see docs/specs/FULFILLMENT-PROOF.md
|
|
349
|
+
*/
|
|
350
|
+
async generateFulfillmentProof(params) {
|
|
351
|
+
this.ensureReady();
|
|
352
|
+
if (!this.fulfillmentNoir || !this.fulfillmentBackend) {
|
|
353
|
+
throw new ProofGenerationError(
|
|
354
|
+
"fulfillment",
|
|
355
|
+
"Fulfillment circuit not initialized"
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
if (this.config.verbose) {
|
|
360
|
+
console.log("[NoirProofProvider] Generating fulfillment proof...");
|
|
361
|
+
}
|
|
362
|
+
const intentHashField = this.hexToField(params.intentHash);
|
|
363
|
+
const recipientStealthField = this.hexToField(params.recipientStealth);
|
|
364
|
+
const { commitmentX, commitmentY } = await this.computeOutputCommitment(
|
|
365
|
+
params.outputAmount,
|
|
366
|
+
params.outputBlinding
|
|
367
|
+
);
|
|
368
|
+
const solverSecretField = this.bytesToField(params.solverSecret);
|
|
369
|
+
const solverId = await this.computeSolverId(solverSecretField);
|
|
370
|
+
const outputBlindingField = this.bytesToField(params.outputBlinding);
|
|
371
|
+
const attestation = params.oracleAttestation;
|
|
372
|
+
const attestationRecipientField = this.hexToField(attestation.recipient);
|
|
373
|
+
const attestationTxHashField = this.hexToField(attestation.txHash);
|
|
374
|
+
const oracleSignature = Array.from(attestation.signature);
|
|
375
|
+
const oracleMessageHash = await this.computeOracleMessageHash(
|
|
376
|
+
attestation.recipient,
|
|
377
|
+
attestation.amount,
|
|
378
|
+
attestation.txHash,
|
|
379
|
+
attestation.blockNumber
|
|
380
|
+
);
|
|
381
|
+
const oraclePubKeyX = this.config.oraclePublicKey?.x ?? new Array(32).fill(0);
|
|
382
|
+
const oraclePubKeyY = this.config.oraclePublicKey?.y ?? new Array(32).fill(0);
|
|
383
|
+
if (!this.config.oraclePublicKey && this.config.verbose) {
|
|
384
|
+
console.warn("[NoirProofProvider] Warning: No oracle public key configured. Using placeholder keys.");
|
|
385
|
+
}
|
|
386
|
+
const witnessInputs = {
|
|
387
|
+
// Public inputs
|
|
388
|
+
intent_hash: intentHashField,
|
|
389
|
+
output_commitment_x: commitmentX,
|
|
390
|
+
output_commitment_y: commitmentY,
|
|
391
|
+
recipient_stealth: recipientStealthField,
|
|
392
|
+
min_output_amount: params.minOutputAmount.toString(),
|
|
393
|
+
solver_id: solverId,
|
|
394
|
+
fulfillment_time: params.fulfillmentTime.toString(),
|
|
395
|
+
expiry: params.expiry.toString(),
|
|
396
|
+
// Private inputs
|
|
397
|
+
output_amount: params.outputAmount.toString(),
|
|
398
|
+
output_blinding: outputBlindingField,
|
|
399
|
+
solver_secret: solverSecretField,
|
|
400
|
+
attestation_recipient: attestationRecipientField,
|
|
401
|
+
attestation_amount: attestation.amount.toString(),
|
|
402
|
+
attestation_tx_hash: attestationTxHashField,
|
|
403
|
+
attestation_block: attestation.blockNumber.toString(),
|
|
404
|
+
oracle_signature: oracleSignature,
|
|
405
|
+
oracle_message_hash: oracleMessageHash,
|
|
406
|
+
oracle_pub_key_x: oraclePubKeyX,
|
|
407
|
+
oracle_pub_key_y: oraclePubKeyY
|
|
408
|
+
};
|
|
409
|
+
if (this.config.verbose) {
|
|
410
|
+
console.log("[NoirProofProvider] Fulfillment witness inputs:", {
|
|
411
|
+
intent_hash: intentHashField,
|
|
412
|
+
output_commitment_x: commitmentX,
|
|
413
|
+
output_commitment_y: commitmentY,
|
|
414
|
+
recipient_stealth: recipientStealthField,
|
|
415
|
+
min_output_amount: params.minOutputAmount.toString(),
|
|
416
|
+
solver_id: solverId,
|
|
417
|
+
fulfillment_time: params.fulfillmentTime,
|
|
418
|
+
expiry: params.expiry,
|
|
419
|
+
output_amount: "[PRIVATE]",
|
|
420
|
+
output_blinding: "[PRIVATE]",
|
|
421
|
+
solver_secret: "[PRIVATE]",
|
|
422
|
+
oracle_attestation: "[PRIVATE]"
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
const { witness } = await this.fulfillmentNoir.execute(witnessInputs);
|
|
426
|
+
if (this.config.verbose) {
|
|
427
|
+
console.log("[NoirProofProvider] Fulfillment witness generated, creating proof...");
|
|
428
|
+
}
|
|
429
|
+
const proofData = await this.fulfillmentBackend.generateProof(witness);
|
|
430
|
+
if (this.config.verbose) {
|
|
431
|
+
console.log("[NoirProofProvider] Fulfillment proof generated successfully");
|
|
432
|
+
}
|
|
433
|
+
const publicInputs = [
|
|
434
|
+
`0x${intentHashField}`,
|
|
435
|
+
`0x${commitmentX}`,
|
|
436
|
+
`0x${commitmentY}`,
|
|
437
|
+
`0x${recipientStealthField}`,
|
|
438
|
+
`0x${params.minOutputAmount.toString(16).padStart(16, "0")}`,
|
|
439
|
+
`0x${solverId}`,
|
|
440
|
+
`0x${params.fulfillmentTime.toString(16).padStart(16, "0")}`,
|
|
441
|
+
`0x${params.expiry.toString(16).padStart(16, "0")}`
|
|
442
|
+
];
|
|
443
|
+
const proof = {
|
|
444
|
+
type: "fulfillment",
|
|
445
|
+
proof: `0x${Buffer.from(proofData.proof).toString("hex")}`,
|
|
446
|
+
publicInputs
|
|
447
|
+
};
|
|
448
|
+
return {
|
|
449
|
+
proof,
|
|
450
|
+
publicInputs
|
|
451
|
+
};
|
|
452
|
+
} catch (error) {
|
|
453
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
454
|
+
if (message.includes("Output below minimum")) {
|
|
455
|
+
throw new ProofGenerationError(
|
|
456
|
+
"fulfillment",
|
|
457
|
+
"Output amount is below minimum required",
|
|
458
|
+
error instanceof Error ? error : void 0
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
if (message.includes("Commitment") && message.includes("mismatch")) {
|
|
462
|
+
throw new ProofGenerationError(
|
|
463
|
+
"fulfillment",
|
|
464
|
+
"Output commitment verification failed",
|
|
465
|
+
error instanceof Error ? error : void 0
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
if (message.includes("Recipient mismatch")) {
|
|
469
|
+
throw new ProofGenerationError(
|
|
470
|
+
"fulfillment",
|
|
471
|
+
"Attestation recipient does not match",
|
|
472
|
+
error instanceof Error ? error : void 0
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
if (message.includes("Invalid oracle")) {
|
|
476
|
+
throw new ProofGenerationError(
|
|
477
|
+
"fulfillment",
|
|
478
|
+
"Oracle attestation signature is invalid",
|
|
479
|
+
error instanceof Error ? error : void 0
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
if (message.includes("Unauthorized solver")) {
|
|
483
|
+
throw new ProofGenerationError(
|
|
484
|
+
"fulfillment",
|
|
485
|
+
"Solver not authorized for this intent",
|
|
486
|
+
error instanceof Error ? error : void 0
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (message.includes("Fulfillment after expiry")) {
|
|
490
|
+
throw new ProofGenerationError(
|
|
491
|
+
"fulfillment",
|
|
492
|
+
"Fulfillment occurred after intent expiry",
|
|
493
|
+
error instanceof Error ? error : void 0
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
throw new ProofGenerationError(
|
|
497
|
+
"fulfillment",
|
|
498
|
+
`Failed to generate fulfillment proof: ${message}`,
|
|
499
|
+
error instanceof Error ? error : void 0
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Verify a Noir proof
|
|
505
|
+
*/
|
|
506
|
+
async verifyProof(proof) {
|
|
507
|
+
this.ensureReady();
|
|
508
|
+
let backend = null;
|
|
509
|
+
switch (proof.type) {
|
|
510
|
+
case "funding":
|
|
511
|
+
backend = this.fundingBackend;
|
|
512
|
+
break;
|
|
513
|
+
case "validity":
|
|
514
|
+
backend = this.validityBackend;
|
|
515
|
+
break;
|
|
516
|
+
case "fulfillment":
|
|
517
|
+
backend = this.fulfillmentBackend;
|
|
518
|
+
break;
|
|
519
|
+
default:
|
|
520
|
+
throw new ProofError(
|
|
521
|
+
`Unknown proof type: ${proof.type}`,
|
|
522
|
+
"SIP_4003" /* PROOF_NOT_IMPLEMENTED */
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
if (!backend) {
|
|
526
|
+
throw new ProofError(
|
|
527
|
+
`${proof.type} backend not initialized`,
|
|
528
|
+
"SIP_4004" /* PROOF_PROVIDER_NOT_READY */
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const proofHex = proof.proof.startsWith("0x") ? proof.proof.slice(2) : proof.proof;
|
|
533
|
+
const proofBytes = new Uint8Array(Buffer.from(proofHex, "hex"));
|
|
534
|
+
const isValid = await backend.verifyProof({
|
|
535
|
+
proof: proofBytes,
|
|
536
|
+
publicInputs: proof.publicInputs.map(
|
|
537
|
+
(input) => input.startsWith("0x") ? input.slice(2) : input
|
|
538
|
+
)
|
|
539
|
+
});
|
|
540
|
+
return isValid;
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (this.config.verbose) {
|
|
543
|
+
console.error("[NoirProofProvider] Verification error:", error);
|
|
544
|
+
}
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Destroy the provider and free resources
|
|
550
|
+
*/
|
|
551
|
+
async destroy() {
|
|
552
|
+
if (this.fundingBackend) {
|
|
553
|
+
await this.fundingBackend.destroy();
|
|
554
|
+
this.fundingBackend = null;
|
|
555
|
+
}
|
|
556
|
+
if (this.validityBackend) {
|
|
557
|
+
await this.validityBackend.destroy();
|
|
558
|
+
this.validityBackend = null;
|
|
559
|
+
}
|
|
560
|
+
if (this.fulfillmentBackend) {
|
|
561
|
+
await this.fulfillmentBackend.destroy();
|
|
562
|
+
this.fulfillmentBackend = null;
|
|
563
|
+
}
|
|
564
|
+
this.fundingNoir = null;
|
|
565
|
+
this.validityNoir = null;
|
|
566
|
+
this.fulfillmentNoir = null;
|
|
567
|
+
this._isReady = false;
|
|
568
|
+
}
|
|
569
|
+
// ─── Private Methods ───────────────────────────────────────────────────────
|
|
570
|
+
ensureReady() {
|
|
571
|
+
if (!this._isReady) {
|
|
572
|
+
throw new ProofError(
|
|
573
|
+
"NoirProofProvider not initialized. Call initialize() first.",
|
|
574
|
+
"SIP_4004" /* PROOF_PROVIDER_NOT_READY */
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Compute the commitment hash that the circuit expects
|
|
580
|
+
*
|
|
581
|
+
* The circuit computes:
|
|
582
|
+
* 1. commitment = pedersen_commitment([balance, blinding])
|
|
583
|
+
* 2. commitment_hash = pedersen_hash([commitment.x, commitment.y, asset_id])
|
|
584
|
+
*
|
|
585
|
+
* We need to compute this outside to pass as a public input.
|
|
586
|
+
*
|
|
587
|
+
* **IMPORTANT**: This SDK uses SHA256 as a deterministic stand-in for Pedersen hash.
|
|
588
|
+
* Both the SDK and circuit MUST use the same hash function. The bundled circuit
|
|
589
|
+
* artifacts are configured to use SHA256 for compatibility. If you use custom
|
|
590
|
+
* circuits with actual Pedersen hashing, you must update this implementation.
|
|
591
|
+
*
|
|
592
|
+
* @see docs/specs/HASH-COMPATIBILITY.md for hash function requirements
|
|
593
|
+
*/
|
|
594
|
+
async computeCommitmentHash(balance, blindingFactor, assetId) {
|
|
595
|
+
const blindingField = this.bytesToField(blindingFactor);
|
|
596
|
+
const { sha256 } = await import("@noble/hashes/sha256");
|
|
597
|
+
const { bytesToHex } = await import("@noble/hashes/utils");
|
|
598
|
+
const preimage = new Uint8Array([
|
|
599
|
+
...this.bigintToBytes(balance, 8),
|
|
600
|
+
...blindingFactor.slice(0, 32),
|
|
601
|
+
...this.hexToBytes(this.assetIdToField(assetId))
|
|
602
|
+
]);
|
|
603
|
+
const hash = sha256(preimage);
|
|
604
|
+
const commitmentHash = bytesToHex(hash);
|
|
605
|
+
return { commitmentHash, blindingField };
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Convert asset ID to field element
|
|
609
|
+
*/
|
|
610
|
+
assetIdToField(assetId) {
|
|
611
|
+
if (assetId.startsWith("0x")) {
|
|
612
|
+
return assetId.slice(2).padStart(64, "0");
|
|
613
|
+
}
|
|
614
|
+
const encoder = new TextEncoder();
|
|
615
|
+
const bytes = encoder.encode(assetId);
|
|
616
|
+
let result = 0n;
|
|
617
|
+
for (let i = 0; i < bytes.length && i < 31; i++) {
|
|
618
|
+
result = result * 256n + BigInt(bytes[i]);
|
|
619
|
+
}
|
|
620
|
+
return result.toString(16).padStart(64, "0");
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Convert bytes to field element string
|
|
624
|
+
*/
|
|
625
|
+
bytesToField(bytes) {
|
|
626
|
+
let result = 0n;
|
|
627
|
+
const len = Math.min(bytes.length, 31);
|
|
628
|
+
for (let i = 0; i < len; i++) {
|
|
629
|
+
result = result * 256n + BigInt(bytes[i]);
|
|
630
|
+
}
|
|
631
|
+
return result.toString();
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Convert bigint to bytes
|
|
635
|
+
*/
|
|
636
|
+
bigintToBytes(value, length) {
|
|
637
|
+
const bytes = new Uint8Array(length);
|
|
638
|
+
let v = value;
|
|
639
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
640
|
+
bytes[i] = Number(v & 0xffn);
|
|
641
|
+
v = v >> 8n;
|
|
642
|
+
}
|
|
643
|
+
return bytes;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Convert hex string to bytes
|
|
647
|
+
*/
|
|
648
|
+
hexToBytes(hex) {
|
|
649
|
+
const h = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
650
|
+
const bytes = new Uint8Array(h.length / 2);
|
|
651
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
652
|
+
bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
|
|
653
|
+
}
|
|
654
|
+
return bytes;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Convert hex string to field element string
|
|
658
|
+
*/
|
|
659
|
+
hexToField(hex) {
|
|
660
|
+
const h = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
661
|
+
return h.padStart(64, "0");
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Convert field string to 32-byte array
|
|
665
|
+
*/
|
|
666
|
+
fieldToBytes32(field) {
|
|
667
|
+
const hex = field.padStart(64, "0");
|
|
668
|
+
const bytes = [];
|
|
669
|
+
for (let i = 0; i < 32; i++) {
|
|
670
|
+
bytes.push(parseInt(hex.slice(i * 2, i * 2 + 2), 16));
|
|
671
|
+
}
|
|
672
|
+
return bytes;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Compute sender commitment for validity proof
|
|
676
|
+
*
|
|
677
|
+
* Uses SHA256 for SDK-side computation. The bundled circuit artifacts
|
|
678
|
+
* are compiled to use SHA256 for compatibility with this SDK.
|
|
679
|
+
*
|
|
680
|
+
* @see computeCommitmentHash for hash function compatibility notes
|
|
681
|
+
*/
|
|
682
|
+
async computeSenderCommitment(senderAddressField, senderBlindingField) {
|
|
683
|
+
const { sha256 } = await import("@noble/hashes/sha256");
|
|
684
|
+
const { bytesToHex } = await import("@noble/hashes/utils");
|
|
685
|
+
const addressBytes = this.hexToBytes(senderAddressField);
|
|
686
|
+
const blindingBytes = this.hexToBytes(senderBlindingField.padStart(64, "0"));
|
|
687
|
+
const preimage = new Uint8Array([...addressBytes, ...blindingBytes]);
|
|
688
|
+
const hash = sha256(preimage);
|
|
689
|
+
const commitmentX = bytesToHex(hash.slice(0, 16)).padStart(64, "0");
|
|
690
|
+
const commitmentY = bytesToHex(hash.slice(16, 32)).padStart(64, "0");
|
|
691
|
+
return { commitmentX, commitmentY };
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Compute nullifier for validity proof
|
|
695
|
+
*
|
|
696
|
+
* Uses SHA256 for SDK-side computation. The bundled circuit artifacts
|
|
697
|
+
* are compiled to use SHA256 for compatibility with this SDK.
|
|
698
|
+
*
|
|
699
|
+
* @see computeCommitmentHash for hash function compatibility notes
|
|
700
|
+
*/
|
|
701
|
+
async computeNullifier(senderSecretField, intentHashField, nonceField) {
|
|
702
|
+
const { sha256 } = await import("@noble/hashes/sha256");
|
|
703
|
+
const { bytesToHex } = await import("@noble/hashes/utils");
|
|
704
|
+
const secretBytes = this.hexToBytes(senderSecretField.padStart(64, "0"));
|
|
705
|
+
const intentBytes = this.hexToBytes(intentHashField);
|
|
706
|
+
const nonceBytes = this.hexToBytes(nonceField.padStart(64, "0"));
|
|
707
|
+
const preimage = new Uint8Array([...secretBytes, ...intentBytes, ...nonceBytes]);
|
|
708
|
+
const hash = sha256(preimage);
|
|
709
|
+
return bytesToHex(hash);
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Compute output commitment for fulfillment proof
|
|
713
|
+
*
|
|
714
|
+
* Uses SHA256 for SDK-side computation. The bundled circuit artifacts
|
|
715
|
+
* are compiled to use SHA256 for compatibility with this SDK.
|
|
716
|
+
*
|
|
717
|
+
* @see computeCommitmentHash for hash function compatibility notes
|
|
718
|
+
*/
|
|
719
|
+
async computeOutputCommitment(outputAmount, outputBlinding) {
|
|
720
|
+
const { sha256 } = await import("@noble/hashes/sha256");
|
|
721
|
+
const { bytesToHex } = await import("@noble/hashes/utils");
|
|
722
|
+
const amountBytes = this.bigintToBytes(outputAmount, 8);
|
|
723
|
+
const blindingBytes = outputBlinding.slice(0, 32);
|
|
724
|
+
const preimage = new Uint8Array([...amountBytes, ...blindingBytes]);
|
|
725
|
+
const hash = sha256(preimage);
|
|
726
|
+
const commitmentX = bytesToHex(hash.slice(0, 16)).padStart(64, "0");
|
|
727
|
+
const commitmentY = bytesToHex(hash.slice(16, 32)).padStart(64, "0");
|
|
728
|
+
return { commitmentX, commitmentY };
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Compute solver ID from solver secret
|
|
732
|
+
*
|
|
733
|
+
* Uses SHA256 for SDK-side computation. The bundled circuit artifacts
|
|
734
|
+
* are compiled to use SHA256 for compatibility with this SDK.
|
|
735
|
+
*
|
|
736
|
+
* @see computeCommitmentHash for hash function compatibility notes
|
|
737
|
+
*/
|
|
738
|
+
async computeSolverId(solverSecretField) {
|
|
739
|
+
const { sha256 } = await import("@noble/hashes/sha256");
|
|
740
|
+
const { bytesToHex } = await import("@noble/hashes/utils");
|
|
741
|
+
const secretBytes = this.hexToBytes(solverSecretField.padStart(64, "0"));
|
|
742
|
+
const hash = sha256(secretBytes);
|
|
743
|
+
return bytesToHex(hash);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Compute oracle message hash for fulfillment proof
|
|
747
|
+
*
|
|
748
|
+
* Hash of attestation data that oracle signs
|
|
749
|
+
*/
|
|
750
|
+
async computeOracleMessageHash(recipient, amount, txHash, blockNumber) {
|
|
751
|
+
const { sha256 } = await import("@noble/hashes/sha256");
|
|
752
|
+
const recipientBytes = this.hexToBytes(this.hexToField(recipient));
|
|
753
|
+
const amountBytes = this.bigintToBytes(amount, 8);
|
|
754
|
+
const txHashBytes = this.hexToBytes(this.hexToField(txHash));
|
|
755
|
+
const blockBytes = this.bigintToBytes(blockNumber, 8);
|
|
756
|
+
const preimage = new Uint8Array([
|
|
757
|
+
...recipientBytes,
|
|
758
|
+
...amountBytes,
|
|
759
|
+
...txHashBytes,
|
|
760
|
+
...blockBytes
|
|
761
|
+
]);
|
|
762
|
+
const hash = sha256(preimage);
|
|
763
|
+
return Array.from(hash);
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Derive secp256k1 public key coordinates from a private key
|
|
767
|
+
*
|
|
768
|
+
* @param privateKey - 32-byte private key as Uint8Array
|
|
769
|
+
* @returns X and Y coordinates as 32-byte arrays
|
|
770
|
+
*/
|
|
771
|
+
getPublicKeyCoordinates(privateKey) {
|
|
772
|
+
const uncompressedPubKey = secp256k1.getPublicKey(privateKey, false);
|
|
773
|
+
const x = Array.from(uncompressedPubKey.slice(1, 33));
|
|
774
|
+
const y = Array.from(uncompressedPubKey.slice(33, 65));
|
|
775
|
+
return { x, y };
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Derive public key coordinates from a field string (private key)
|
|
779
|
+
*
|
|
780
|
+
* @param privateKeyField - Private key as hex field string
|
|
781
|
+
* @returns X and Y coordinates as 32-byte arrays
|
|
782
|
+
*/
|
|
783
|
+
getPublicKeyFromField(privateKeyField) {
|
|
784
|
+
const privateKeyBytes = this.hexToBytes(privateKeyField.padStart(64, "0"));
|
|
785
|
+
return this.getPublicKeyCoordinates(privateKeyBytes);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
export {
|
|
789
|
+
NoirProofProvider
|
|
790
|
+
};
|