@sip-protocol/sdk 0.1.0 → 0.1.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/dist/index.d.mts +3236 -1554
- package/dist/index.d.ts +3236 -1554
- package/dist/index.js +9184 -3520
- package/dist/index.mjs +8998 -3379
- package/package.json +5 -2
- package/src/adapters/near-intents.ts +48 -35
- package/src/adapters/oneclick-client.ts +6 -0
- package/src/compliance/compliance-manager.ts +1035 -0
- package/src/compliance/index.ts +43 -0
- package/src/index.ts +129 -2
- package/src/payment/index.ts +54 -0
- package/src/payment/payment.ts +623 -0
- package/src/payment/stablecoins.ts +306 -0
- package/src/privacy.ts +127 -94
- package/src/proofs/circuits/fulfillment_proof.json +1 -0
- package/src/proofs/circuits/funding_proof.json +1 -0
- package/src/proofs/circuits/validity_proof.json +1 -0
- package/src/proofs/interface.ts +13 -1
- package/src/proofs/noir.ts +967 -97
- package/src/secure-memory.ts +147 -0
- package/src/sip.ts +399 -37
- package/src/stealth.ts +116 -84
- package/src/treasury/index.ts +43 -0
- package/src/treasury/treasury.ts +911 -0
- package/src/wallet/hardware/index.ts +87 -0
- package/src/wallet/hardware/ledger.ts +628 -0
- package/src/wallet/hardware/mock.ts +667 -0
- package/src/wallet/hardware/trezor.ts +657 -0
- package/src/wallet/hardware/types.ts +317 -0
- package/src/wallet/index.ts +40 -0
- package/src/zcash/shielded-service.ts +59 -1
package/src/proofs/noir.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Production-ready ZK proof provider using Noir (Aztec) circuits.
|
|
5
5
|
*
|
|
6
6
|
* This provider generates cryptographically sound proofs using:
|
|
7
|
-
* - Funding Proof: ~
|
|
7
|
+
* - Funding Proof: ~2,000 constraints (docs/specs/FUNDING-PROOF.md)
|
|
8
8
|
* - Validity Proof: ~72,000 constraints (docs/specs/VALIDITY-PROOF.md)
|
|
9
9
|
* - Fulfillment Proof: ~22,000 constraints (docs/specs/FULFILLMENT-PROOF.md)
|
|
10
10
|
*
|
|
@@ -23,14 +23,28 @@ import type {
|
|
|
23
23
|
import { ProofGenerationError } from './interface'
|
|
24
24
|
import { ProofError, ErrorCode } from '../errors'
|
|
25
25
|
|
|
26
|
+
// Import Noir JS (dynamically loaded to support both Node and browser)
|
|
27
|
+
import { Noir } from '@noir-lang/noir_js'
|
|
28
|
+
import type { CompiledCircuit } from '@noir-lang/types'
|
|
29
|
+
import { UltraHonkBackend } from '@aztec/bb.js'
|
|
30
|
+
import { secp256k1 } from '@noble/curves/secp256k1'
|
|
31
|
+
|
|
32
|
+
// Import compiled circuit artifacts
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
import fundingCircuitArtifact from './circuits/funding_proof.json'
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
import validityCircuitArtifact from './circuits/validity_proof.json'
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
import fulfillmentCircuitArtifact from './circuits/fulfillment_proof.json'
|
|
39
|
+
|
|
26
40
|
/**
|
|
27
|
-
*
|
|
28
|
-
* These will be populated when circuits are compiled (#14, #15, #16)
|
|
41
|
+
* Public key coordinates for secp256k1
|
|
29
42
|
*/
|
|
30
|
-
interface
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
export interface PublicKeyCoordinates {
|
|
44
|
+
/** X coordinate as 32-byte array */
|
|
45
|
+
x: number[]
|
|
46
|
+
/** Y coordinate as 32-byte array */
|
|
47
|
+
y: number[]
|
|
34
48
|
}
|
|
35
49
|
|
|
36
50
|
/**
|
|
@@ -45,7 +59,7 @@ export interface NoirProviderConfig {
|
|
|
45
59
|
|
|
46
60
|
/**
|
|
47
61
|
* Backend to use for proof generation
|
|
48
|
-
* @default 'barretenberg' (
|
|
62
|
+
* @default 'barretenberg' (UltraHonk)
|
|
49
63
|
*/
|
|
50
64
|
backend?: 'barretenberg'
|
|
51
65
|
|
|
@@ -54,6 +68,12 @@ export interface NoirProviderConfig {
|
|
|
54
68
|
* @default false
|
|
55
69
|
*/
|
|
56
70
|
verbose?: boolean
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Oracle public key for verifying attestations in fulfillment proofs
|
|
74
|
+
* Required for production use. If not provided, proofs will use placeholder keys.
|
|
75
|
+
*/
|
|
76
|
+
oraclePublicKey?: PublicKeyCoordinates
|
|
57
77
|
}
|
|
58
78
|
|
|
59
79
|
/**
|
|
@@ -63,16 +83,17 @@ export interface NoirProviderConfig {
|
|
|
63
83
|
*
|
|
64
84
|
* @example
|
|
65
85
|
* ```typescript
|
|
66
|
-
* const provider = new NoirProofProvider(
|
|
67
|
-
* artifactsPath: './circuits/target',
|
|
68
|
-
* })
|
|
86
|
+
* const provider = new NoirProofProvider()
|
|
69
87
|
*
|
|
70
88
|
* await provider.initialize()
|
|
71
89
|
*
|
|
72
90
|
* const result = await provider.generateFundingProof({
|
|
73
91
|
* balance: 100n,
|
|
74
92
|
* minimumRequired: 50n,
|
|
75
|
-
*
|
|
93
|
+
* blindingFactor: new Uint8Array(32),
|
|
94
|
+
* assetId: '0xABCD',
|
|
95
|
+
* userAddress: '0x1234...',
|
|
96
|
+
* ownershipSignature: new Uint8Array(64),
|
|
76
97
|
* })
|
|
77
98
|
* ```
|
|
78
99
|
*/
|
|
@@ -80,7 +101,14 @@ export class NoirProofProvider implements ProofProvider {
|
|
|
80
101
|
readonly framework: ProofFramework = 'noir'
|
|
81
102
|
private _isReady = false
|
|
82
103
|
private config: NoirProviderConfig
|
|
83
|
-
|
|
104
|
+
|
|
105
|
+
// Circuit instances
|
|
106
|
+
private fundingNoir: Noir | null = null
|
|
107
|
+
private fundingBackend: UltraHonkBackend | null = null
|
|
108
|
+
private validityNoir: Noir | null = null
|
|
109
|
+
private validityBackend: UltraHonkBackend | null = null
|
|
110
|
+
private fulfillmentNoir: Noir | null = null
|
|
111
|
+
private fulfillmentBackend: UltraHonkBackend | null = null
|
|
84
112
|
|
|
85
113
|
constructor(config: NoirProviderConfig = {}) {
|
|
86
114
|
this.config = {
|
|
@@ -94,130 +122,690 @@ export class NoirProofProvider implements ProofProvider {
|
|
|
94
122
|
return this._isReady
|
|
95
123
|
}
|
|
96
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Derive secp256k1 public key coordinates from a private key
|
|
127
|
+
*
|
|
128
|
+
* Utility method that can be used to generate public key coordinates
|
|
129
|
+
* for use in ValidityProofParams.senderPublicKey or NoirProviderConfig.oraclePublicKey
|
|
130
|
+
*
|
|
131
|
+
* @param privateKey - 32-byte private key
|
|
132
|
+
* @returns X and Y coordinates as 32-byte arrays
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const privateKey = new Uint8Array(32).fill(1) // Your secret key
|
|
137
|
+
* const publicKey = NoirProofProvider.derivePublicKey(privateKey)
|
|
138
|
+
*
|
|
139
|
+
* // Use for oracle configuration
|
|
140
|
+
* const provider = new NoirProofProvider({
|
|
141
|
+
* oraclePublicKey: publicKey
|
|
142
|
+
* })
|
|
143
|
+
*
|
|
144
|
+
* // Or use for validity proof params
|
|
145
|
+
* const validityParams = {
|
|
146
|
+
* // ... other params
|
|
147
|
+
* senderPublicKey: {
|
|
148
|
+
* x: new Uint8Array(publicKey.x),
|
|
149
|
+
* y: new Uint8Array(publicKey.y)
|
|
150
|
+
* }
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
static derivePublicKey(privateKey: Uint8Array): PublicKeyCoordinates {
|
|
155
|
+
// Get uncompressed public key (65 bytes: 04 || x || y)
|
|
156
|
+
const uncompressedPubKey = secp256k1.getPublicKey(privateKey, false)
|
|
157
|
+
|
|
158
|
+
// Extract X (bytes 1-32) and Y (bytes 33-64)
|
|
159
|
+
const x = Array.from(uncompressedPubKey.slice(1, 33))
|
|
160
|
+
const y = Array.from(uncompressedPubKey.slice(33, 65))
|
|
161
|
+
|
|
162
|
+
return { x, y }
|
|
163
|
+
}
|
|
164
|
+
|
|
97
165
|
/**
|
|
98
166
|
* Initialize the Noir provider
|
|
99
167
|
*
|
|
100
168
|
* Loads circuit artifacts and initializes the proving backend.
|
|
101
|
-
*
|
|
102
|
-
* @throws Error if circuits are not yet implemented
|
|
103
169
|
*/
|
|
104
170
|
async initialize(): Promise<void> {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
171
|
+
if (this._isReady) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
if (this.config.verbose) {
|
|
177
|
+
console.log('[NoirProofProvider] Initializing...')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Initialize Funding Proof circuit
|
|
181
|
+
// Cast to CompiledCircuit - the JSON artifact matches the expected structure
|
|
182
|
+
const fundingCircuit = fundingCircuitArtifact as unknown as CompiledCircuit
|
|
183
|
+
|
|
184
|
+
// Create backend for proof generation
|
|
185
|
+
this.fundingBackend = new UltraHonkBackend(fundingCircuit.bytecode)
|
|
186
|
+
|
|
187
|
+
// Create Noir instance for witness generation
|
|
188
|
+
this.fundingNoir = new Noir(fundingCircuit)
|
|
189
|
+
|
|
190
|
+
if (this.config.verbose) {
|
|
191
|
+
console.log('[NoirProofProvider] Funding circuit loaded')
|
|
192
|
+
// Access noir_version from the raw artifact since CompiledCircuit type may not include it
|
|
193
|
+
const artifactVersion = (fundingCircuitArtifact as { noir_version?: string }).noir_version
|
|
194
|
+
console.log(`[NoirProofProvider] Noir version: ${artifactVersion ?? 'unknown'}`)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Initialize Validity Proof circuit
|
|
198
|
+
const validityCircuit = validityCircuitArtifact as unknown as CompiledCircuit
|
|
199
|
+
|
|
200
|
+
// Create backend for validity proof generation
|
|
201
|
+
this.validityBackend = new UltraHonkBackend(validityCircuit.bytecode)
|
|
202
|
+
|
|
203
|
+
// Create Noir instance for validity witness generation
|
|
204
|
+
this.validityNoir = new Noir(validityCircuit)
|
|
205
|
+
|
|
206
|
+
if (this.config.verbose) {
|
|
207
|
+
console.log('[NoirProofProvider] Validity circuit loaded')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Initialize Fulfillment Proof circuit
|
|
211
|
+
const fulfillmentCircuit = fulfillmentCircuitArtifact as unknown as CompiledCircuit
|
|
212
|
+
|
|
213
|
+
// Create backend for fulfillment proof generation
|
|
214
|
+
this.fulfillmentBackend = new UltraHonkBackend(fulfillmentCircuit.bytecode)
|
|
215
|
+
|
|
216
|
+
// Create Noir instance for fulfillment witness generation
|
|
217
|
+
this.fulfillmentNoir = new Noir(fulfillmentCircuit)
|
|
218
|
+
|
|
219
|
+
if (this.config.verbose) {
|
|
220
|
+
console.log('[NoirProofProvider] Fulfillment circuit loaded')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this._isReady = true
|
|
224
|
+
|
|
225
|
+
if (this.config.verbose) {
|
|
226
|
+
console.log('[NoirProofProvider] Initialization complete')
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
throw new ProofError(
|
|
230
|
+
`Failed to initialize NoirProofProvider: ${error instanceof Error ? error.message : String(error)}`,
|
|
231
|
+
ErrorCode.PROOF_NOT_IMPLEMENTED,
|
|
232
|
+
{ context: { error } }
|
|
233
|
+
)
|
|
234
|
+
}
|
|
132
235
|
}
|
|
133
236
|
|
|
134
237
|
/**
|
|
135
238
|
* Generate a Funding Proof using Noir circuits
|
|
136
239
|
*
|
|
240
|
+
* Proves: balance >= minimumRequired without revealing balance
|
|
241
|
+
*
|
|
137
242
|
* @see docs/specs/FUNDING-PROOF.md
|
|
138
243
|
*/
|
|
139
|
-
async generateFundingProof(
|
|
244
|
+
async generateFundingProof(params: FundingProofParams): Promise<ProofResult> {
|
|
140
245
|
this.ensureReady()
|
|
141
246
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
247
|
+
if (!this.fundingNoir || !this.fundingBackend) {
|
|
248
|
+
throw new ProofGenerationError(
|
|
249
|
+
'funding',
|
|
250
|
+
'Funding circuit not initialized'
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
if (this.config.verbose) {
|
|
256
|
+
console.log('[NoirProofProvider] Generating funding proof...')
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Compute the commitment hash that the circuit expects
|
|
260
|
+
// The circuit computes: pedersen_hash([commitment.x, commitment.y, asset_id])
|
|
261
|
+
// We need to compute this to pass as a public input
|
|
262
|
+
const { commitmentHash, blindingField } = await this.computeCommitmentHash(
|
|
263
|
+
params.balance,
|
|
264
|
+
params.blindingFactor,
|
|
265
|
+
params.assetId
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
// Prepare witness inputs for the circuit
|
|
269
|
+
const witnessInputs = {
|
|
270
|
+
// Public inputs
|
|
271
|
+
commitment_hash: commitmentHash,
|
|
272
|
+
minimum_required: params.minimumRequired.toString(),
|
|
273
|
+
asset_id: this.assetIdToField(params.assetId),
|
|
274
|
+
// Private inputs
|
|
275
|
+
balance: params.balance.toString(),
|
|
276
|
+
blinding: blindingField,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (this.config.verbose) {
|
|
280
|
+
console.log('[NoirProofProvider] Witness inputs:', {
|
|
281
|
+
commitment_hash: commitmentHash,
|
|
282
|
+
minimum_required: params.minimumRequired.toString(),
|
|
283
|
+
asset_id: this.assetIdToField(params.assetId),
|
|
284
|
+
balance: '[PRIVATE]',
|
|
285
|
+
blinding: '[PRIVATE]',
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Execute circuit to generate witness
|
|
290
|
+
const { witness } = await this.fundingNoir.execute(witnessInputs)
|
|
291
|
+
|
|
292
|
+
if (this.config.verbose) {
|
|
293
|
+
console.log('[NoirProofProvider] Witness generated, creating proof...')
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Generate proof using backend
|
|
297
|
+
const proofData = await this.fundingBackend.generateProof(witness)
|
|
298
|
+
|
|
299
|
+
if (this.config.verbose) {
|
|
300
|
+
console.log('[NoirProofProvider] Proof generated successfully')
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Extract public inputs from the proof
|
|
304
|
+
const publicInputs: `0x${string}`[] = [
|
|
305
|
+
`0x${commitmentHash}`,
|
|
306
|
+
`0x${params.minimumRequired.toString(16).padStart(16, '0')}`,
|
|
307
|
+
`0x${this.assetIdToField(params.assetId)}`,
|
|
308
|
+
]
|
|
309
|
+
|
|
310
|
+
// Create ZKProof object
|
|
311
|
+
const proof: ZKProof = {
|
|
312
|
+
type: 'funding',
|
|
313
|
+
proof: `0x${Buffer.from(proofData.proof).toString('hex')}`,
|
|
314
|
+
publicInputs,
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
proof,
|
|
319
|
+
publicInputs,
|
|
320
|
+
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
323
|
+
|
|
324
|
+
// Check for specific circuit errors
|
|
325
|
+
if (message.includes('Insufficient balance')) {
|
|
326
|
+
throw new ProofGenerationError(
|
|
327
|
+
'funding',
|
|
328
|
+
'Insufficient balance to generate proof',
|
|
329
|
+
error instanceof Error ? error : undefined
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
if (message.includes('Commitment hash mismatch')) {
|
|
333
|
+
throw new ProofGenerationError(
|
|
334
|
+
'funding',
|
|
335
|
+
'Commitment hash verification failed',
|
|
336
|
+
error instanceof Error ? error : undefined
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
throw new ProofGenerationError(
|
|
341
|
+
'funding',
|
|
342
|
+
`Failed to generate funding proof: ${message}`,
|
|
343
|
+
error instanceof Error ? error : undefined
|
|
344
|
+
)
|
|
345
|
+
}
|
|
166
346
|
}
|
|
167
347
|
|
|
168
348
|
/**
|
|
169
349
|
* Generate a Validity Proof using Noir circuits
|
|
170
350
|
*
|
|
351
|
+
* Proves: Intent is authorized by sender without revealing identity
|
|
352
|
+
*
|
|
171
353
|
* @see docs/specs/VALIDITY-PROOF.md
|
|
172
354
|
*/
|
|
173
|
-
async generateValidityProof(
|
|
355
|
+
async generateValidityProof(params: ValidityProofParams): Promise<ProofResult> {
|
|
174
356
|
this.ensureReady()
|
|
175
357
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
358
|
+
if (!this.validityNoir || !this.validityBackend) {
|
|
359
|
+
throw new ProofGenerationError(
|
|
360
|
+
'validity',
|
|
361
|
+
'Validity circuit not initialized'
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
if (this.config.verbose) {
|
|
367
|
+
console.log('[NoirProofProvider] Generating validity proof...')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Convert intent hash to field
|
|
371
|
+
const intentHashField = this.hexToField(params.intentHash)
|
|
372
|
+
|
|
373
|
+
// Convert sender address to field
|
|
374
|
+
const senderAddressField = this.hexToField(params.senderAddress)
|
|
375
|
+
|
|
376
|
+
// Convert blinding to field
|
|
377
|
+
const senderBlindingField = this.bytesToField(params.senderBlinding)
|
|
378
|
+
|
|
379
|
+
// Convert sender secret to field
|
|
380
|
+
const senderSecretField = this.bytesToField(params.senderSecret)
|
|
381
|
+
|
|
382
|
+
// Convert nonce to field
|
|
383
|
+
const nonceField = this.bytesToField(params.nonce)
|
|
384
|
+
|
|
385
|
+
// Compute sender commitment (same as circuit will do)
|
|
386
|
+
const { commitmentX, commitmentY } = await this.computeSenderCommitment(
|
|
387
|
+
senderAddressField,
|
|
388
|
+
senderBlindingField
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
// Compute nullifier (same as circuit will do)
|
|
392
|
+
const nullifier = await this.computeNullifier(
|
|
393
|
+
senderSecretField,
|
|
394
|
+
intentHashField,
|
|
395
|
+
nonceField
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
// Extract public key components from signature (assuming 64-byte signature)
|
|
399
|
+
// For ECDSA, we need the public key separately
|
|
400
|
+
// The signature is r (32 bytes) + s (32 bytes)
|
|
401
|
+
const signature = Array.from(params.authorizationSignature)
|
|
402
|
+
|
|
403
|
+
// Create message hash from intent hash (32 bytes)
|
|
404
|
+
const messageHash = this.fieldToBytes32(intentHashField)
|
|
405
|
+
|
|
406
|
+
// Use provided public key or derive from sender's secret key
|
|
407
|
+
// The sender secret is used as the private key for ECDSA signature verification
|
|
408
|
+
let pubKeyX: number[]
|
|
409
|
+
let pubKeyY: number[]
|
|
410
|
+
|
|
411
|
+
if (params.senderPublicKey) {
|
|
412
|
+
// Use provided public key
|
|
413
|
+
pubKeyX = Array.from(params.senderPublicKey.x)
|
|
414
|
+
pubKeyY = Array.from(params.senderPublicKey.y)
|
|
415
|
+
} else {
|
|
416
|
+
// Derive from sender secret
|
|
417
|
+
const coords = this.getPublicKeyCoordinates(params.senderSecret)
|
|
418
|
+
pubKeyX = coords.x
|
|
419
|
+
pubKeyY = coords.y
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Prepare witness inputs for the circuit
|
|
423
|
+
const witnessInputs = {
|
|
424
|
+
// Public inputs
|
|
425
|
+
intent_hash: intentHashField,
|
|
426
|
+
sender_commitment_x: commitmentX,
|
|
427
|
+
sender_commitment_y: commitmentY,
|
|
428
|
+
nullifier: nullifier,
|
|
429
|
+
timestamp: params.timestamp.toString(),
|
|
430
|
+
expiry: params.expiry.toString(),
|
|
431
|
+
// Private inputs
|
|
432
|
+
sender_address: senderAddressField,
|
|
433
|
+
sender_blinding: senderBlindingField,
|
|
434
|
+
sender_secret: senderSecretField,
|
|
435
|
+
pub_key_x: pubKeyX,
|
|
436
|
+
pub_key_y: pubKeyY,
|
|
437
|
+
signature: signature,
|
|
438
|
+
message_hash: messageHash,
|
|
439
|
+
nonce: nonceField,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (this.config.verbose) {
|
|
443
|
+
console.log('[NoirProofProvider] Validity witness inputs:', {
|
|
444
|
+
intent_hash: intentHashField,
|
|
445
|
+
sender_commitment_x: commitmentX,
|
|
446
|
+
sender_commitment_y: commitmentY,
|
|
447
|
+
nullifier: nullifier,
|
|
448
|
+
timestamp: params.timestamp,
|
|
449
|
+
expiry: params.expiry,
|
|
450
|
+
sender_address: '[PRIVATE]',
|
|
451
|
+
sender_blinding: '[PRIVATE]',
|
|
452
|
+
sender_secret: '[PRIVATE]',
|
|
453
|
+
signature: '[PRIVATE]',
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Execute circuit to generate witness
|
|
458
|
+
const { witness } = await this.validityNoir.execute(witnessInputs)
|
|
459
|
+
|
|
460
|
+
if (this.config.verbose) {
|
|
461
|
+
console.log('[NoirProofProvider] Validity witness generated, creating proof...')
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Generate proof using backend
|
|
465
|
+
const proofData = await this.validityBackend.generateProof(witness)
|
|
466
|
+
|
|
467
|
+
if (this.config.verbose) {
|
|
468
|
+
console.log('[NoirProofProvider] Validity proof generated successfully')
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Extract public inputs from the proof
|
|
472
|
+
const publicInputs: `0x${string}`[] = [
|
|
473
|
+
`0x${intentHashField}`,
|
|
474
|
+
`0x${commitmentX}`,
|
|
475
|
+
`0x${commitmentY}`,
|
|
476
|
+
`0x${nullifier}`,
|
|
477
|
+
`0x${params.timestamp.toString(16).padStart(16, '0')}`,
|
|
478
|
+
`0x${params.expiry.toString(16).padStart(16, '0')}`,
|
|
479
|
+
]
|
|
480
|
+
|
|
481
|
+
// Create ZKProof object
|
|
482
|
+
const proof: ZKProof = {
|
|
483
|
+
type: 'validity',
|
|
484
|
+
proof: `0x${Buffer.from(proofData.proof).toString('hex')}`,
|
|
485
|
+
publicInputs,
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
proof,
|
|
490
|
+
publicInputs,
|
|
491
|
+
}
|
|
492
|
+
} catch (error) {
|
|
493
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
494
|
+
|
|
495
|
+
// Check for specific circuit errors
|
|
496
|
+
if (message.includes('Sender commitment')) {
|
|
497
|
+
throw new ProofGenerationError(
|
|
498
|
+
'validity',
|
|
499
|
+
'Sender commitment verification failed',
|
|
500
|
+
error instanceof Error ? error : undefined
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
if (message.includes('Invalid ECDSA')) {
|
|
504
|
+
throw new ProofGenerationError(
|
|
505
|
+
'validity',
|
|
506
|
+
'Authorization signature verification failed',
|
|
507
|
+
error instanceof Error ? error : undefined
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
if (message.includes('Nullifier mismatch')) {
|
|
511
|
+
throw new ProofGenerationError(
|
|
512
|
+
'validity',
|
|
513
|
+
'Nullifier derivation failed',
|
|
514
|
+
error instanceof Error ? error : undefined
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
if (message.includes('Intent expired')) {
|
|
518
|
+
throw new ProofGenerationError(
|
|
519
|
+
'validity',
|
|
520
|
+
'Intent has expired (timestamp >= expiry)',
|
|
521
|
+
error instanceof Error ? error : undefined
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
throw new ProofGenerationError(
|
|
526
|
+
'validity',
|
|
527
|
+
`Failed to generate validity proof: ${message}`,
|
|
528
|
+
error instanceof Error ? error : undefined
|
|
529
|
+
)
|
|
530
|
+
}
|
|
181
531
|
}
|
|
182
532
|
|
|
183
533
|
/**
|
|
184
534
|
* Generate a Fulfillment Proof using Noir circuits
|
|
185
535
|
*
|
|
536
|
+
* Proves: Solver correctly executed the intent and delivered the required
|
|
537
|
+
* output to the recipient, without revealing execution path or liquidity sources.
|
|
538
|
+
*
|
|
186
539
|
* @see docs/specs/FULFILLMENT-PROOF.md
|
|
187
540
|
*/
|
|
188
|
-
async generateFulfillmentProof(
|
|
541
|
+
async generateFulfillmentProof(params: FulfillmentProofParams): Promise<ProofResult> {
|
|
189
542
|
this.ensureReady()
|
|
190
543
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
544
|
+
if (!this.fulfillmentNoir || !this.fulfillmentBackend) {
|
|
545
|
+
throw new ProofGenerationError(
|
|
546
|
+
'fulfillment',
|
|
547
|
+
'Fulfillment circuit not initialized'
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
if (this.config.verbose) {
|
|
553
|
+
console.log('[NoirProofProvider] Generating fulfillment proof...')
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Convert intent hash to field
|
|
557
|
+
const intentHashField = this.hexToField(params.intentHash)
|
|
558
|
+
|
|
559
|
+
// Convert recipient stealth to field
|
|
560
|
+
const recipientStealthField = this.hexToField(params.recipientStealth)
|
|
561
|
+
|
|
562
|
+
// Compute output commitment
|
|
563
|
+
const { commitmentX, commitmentY } = await this.computeOutputCommitment(
|
|
564
|
+
params.outputAmount,
|
|
565
|
+
params.outputBlinding
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
// Compute solver ID from secret
|
|
569
|
+
const solverSecretField = this.bytesToField(params.solverSecret)
|
|
570
|
+
const solverId = await this.computeSolverId(solverSecretField)
|
|
571
|
+
|
|
572
|
+
// Convert output blinding to field
|
|
573
|
+
const outputBlindingField = this.bytesToField(params.outputBlinding)
|
|
574
|
+
|
|
575
|
+
// Oracle attestation data
|
|
576
|
+
const attestation = params.oracleAttestation
|
|
577
|
+
const attestationRecipientField = this.hexToField(attestation.recipient)
|
|
578
|
+
const attestationTxHashField = this.hexToField(attestation.txHash)
|
|
579
|
+
|
|
580
|
+
// Oracle signature (64 bytes)
|
|
581
|
+
const oracleSignature = Array.from(attestation.signature)
|
|
582
|
+
|
|
583
|
+
// Compute oracle message hash
|
|
584
|
+
const oracleMessageHash = await this.computeOracleMessageHash(
|
|
585
|
+
attestation.recipient,
|
|
586
|
+
attestation.amount,
|
|
587
|
+
attestation.txHash,
|
|
588
|
+
attestation.blockNumber
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
// Use configured oracle public key, or placeholder if not configured
|
|
592
|
+
// In production, the oracle public key should always be configured
|
|
593
|
+
const oraclePubKeyX = this.config.oraclePublicKey?.x ?? new Array(32).fill(0)
|
|
594
|
+
const oraclePubKeyY = this.config.oraclePublicKey?.y ?? new Array(32).fill(0)
|
|
595
|
+
|
|
596
|
+
if (!this.config.oraclePublicKey && this.config.verbose) {
|
|
597
|
+
console.warn('[NoirProofProvider] Warning: No oracle public key configured. Using placeholder keys.')
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Prepare witness inputs for the circuit
|
|
601
|
+
const witnessInputs = {
|
|
602
|
+
// Public inputs
|
|
603
|
+
intent_hash: intentHashField,
|
|
604
|
+
output_commitment_x: commitmentX,
|
|
605
|
+
output_commitment_y: commitmentY,
|
|
606
|
+
recipient_stealth: recipientStealthField,
|
|
607
|
+
min_output_amount: params.minOutputAmount.toString(),
|
|
608
|
+
solver_id: solverId,
|
|
609
|
+
fulfillment_time: params.fulfillmentTime.toString(),
|
|
610
|
+
expiry: params.expiry.toString(),
|
|
611
|
+
// Private inputs
|
|
612
|
+
output_amount: params.outputAmount.toString(),
|
|
613
|
+
output_blinding: outputBlindingField,
|
|
614
|
+
solver_secret: solverSecretField,
|
|
615
|
+
attestation_recipient: attestationRecipientField,
|
|
616
|
+
attestation_amount: attestation.amount.toString(),
|
|
617
|
+
attestation_tx_hash: attestationTxHashField,
|
|
618
|
+
attestation_block: attestation.blockNumber.toString(),
|
|
619
|
+
oracle_signature: oracleSignature,
|
|
620
|
+
oracle_message_hash: oracleMessageHash,
|
|
621
|
+
oracle_pub_key_x: oraclePubKeyX,
|
|
622
|
+
oracle_pub_key_y: oraclePubKeyY,
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (this.config.verbose) {
|
|
626
|
+
console.log('[NoirProofProvider] Fulfillment witness inputs:', {
|
|
627
|
+
intent_hash: intentHashField,
|
|
628
|
+
output_commitment_x: commitmentX,
|
|
629
|
+
output_commitment_y: commitmentY,
|
|
630
|
+
recipient_stealth: recipientStealthField,
|
|
631
|
+
min_output_amount: params.minOutputAmount.toString(),
|
|
632
|
+
solver_id: solverId,
|
|
633
|
+
fulfillment_time: params.fulfillmentTime,
|
|
634
|
+
expiry: params.expiry,
|
|
635
|
+
output_amount: '[PRIVATE]',
|
|
636
|
+
output_blinding: '[PRIVATE]',
|
|
637
|
+
solver_secret: '[PRIVATE]',
|
|
638
|
+
oracle_attestation: '[PRIVATE]',
|
|
639
|
+
})
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Execute circuit to generate witness
|
|
643
|
+
const { witness } = await this.fulfillmentNoir.execute(witnessInputs)
|
|
644
|
+
|
|
645
|
+
if (this.config.verbose) {
|
|
646
|
+
console.log('[NoirProofProvider] Fulfillment witness generated, creating proof...')
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Generate proof using backend
|
|
650
|
+
const proofData = await this.fulfillmentBackend.generateProof(witness)
|
|
651
|
+
|
|
652
|
+
if (this.config.verbose) {
|
|
653
|
+
console.log('[NoirProofProvider] Fulfillment proof generated successfully')
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Extract public inputs from the proof
|
|
657
|
+
const publicInputs: `0x${string}`[] = [
|
|
658
|
+
`0x${intentHashField}`,
|
|
659
|
+
`0x${commitmentX}`,
|
|
660
|
+
`0x${commitmentY}`,
|
|
661
|
+
`0x${recipientStealthField}`,
|
|
662
|
+
`0x${params.minOutputAmount.toString(16).padStart(16, '0')}`,
|
|
663
|
+
`0x${solverId}`,
|
|
664
|
+
`0x${params.fulfillmentTime.toString(16).padStart(16, '0')}`,
|
|
665
|
+
`0x${params.expiry.toString(16).padStart(16, '0')}`,
|
|
666
|
+
]
|
|
667
|
+
|
|
668
|
+
// Create ZKProof object
|
|
669
|
+
const proof: ZKProof = {
|
|
670
|
+
type: 'fulfillment',
|
|
671
|
+
proof: `0x${Buffer.from(proofData.proof).toString('hex')}`,
|
|
672
|
+
publicInputs,
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
proof,
|
|
677
|
+
publicInputs,
|
|
678
|
+
}
|
|
679
|
+
} catch (error) {
|
|
680
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
681
|
+
|
|
682
|
+
// Check for specific circuit errors
|
|
683
|
+
if (message.includes('Output below minimum')) {
|
|
684
|
+
throw new ProofGenerationError(
|
|
685
|
+
'fulfillment',
|
|
686
|
+
'Output amount is below minimum required',
|
|
687
|
+
error instanceof Error ? error : undefined
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
if (message.includes('Commitment') && message.includes('mismatch')) {
|
|
691
|
+
throw new ProofGenerationError(
|
|
692
|
+
'fulfillment',
|
|
693
|
+
'Output commitment verification failed',
|
|
694
|
+
error instanceof Error ? error : undefined
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
if (message.includes('Recipient mismatch')) {
|
|
698
|
+
throw new ProofGenerationError(
|
|
699
|
+
'fulfillment',
|
|
700
|
+
'Attestation recipient does not match',
|
|
701
|
+
error instanceof Error ? error : undefined
|
|
702
|
+
)
|
|
703
|
+
}
|
|
704
|
+
if (message.includes('Invalid oracle')) {
|
|
705
|
+
throw new ProofGenerationError(
|
|
706
|
+
'fulfillment',
|
|
707
|
+
'Oracle attestation signature is invalid',
|
|
708
|
+
error instanceof Error ? error : undefined
|
|
709
|
+
)
|
|
710
|
+
}
|
|
711
|
+
if (message.includes('Unauthorized solver')) {
|
|
712
|
+
throw new ProofGenerationError(
|
|
713
|
+
'fulfillment',
|
|
714
|
+
'Solver not authorized for this intent',
|
|
715
|
+
error instanceof Error ? error : undefined
|
|
716
|
+
)
|
|
717
|
+
}
|
|
718
|
+
if (message.includes('Fulfillment after expiry')) {
|
|
719
|
+
throw new ProofGenerationError(
|
|
720
|
+
'fulfillment',
|
|
721
|
+
'Fulfillment occurred after intent expiry',
|
|
722
|
+
error instanceof Error ? error : undefined
|
|
723
|
+
)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
throw new ProofGenerationError(
|
|
727
|
+
'fulfillment',
|
|
728
|
+
`Failed to generate fulfillment proof: ${message}`,
|
|
729
|
+
error instanceof Error ? error : undefined
|
|
730
|
+
)
|
|
731
|
+
}
|
|
196
732
|
}
|
|
197
733
|
|
|
198
734
|
/**
|
|
199
735
|
* Verify a Noir proof
|
|
200
736
|
*/
|
|
201
|
-
async verifyProof(
|
|
737
|
+
async verifyProof(proof: ZKProof): Promise<boolean> {
|
|
202
738
|
this.ensureReady()
|
|
203
739
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
740
|
+
// Select the appropriate backend based on proof type
|
|
741
|
+
let backend: UltraHonkBackend | null = null
|
|
742
|
+
|
|
743
|
+
switch (proof.type) {
|
|
744
|
+
case 'funding':
|
|
745
|
+
backend = this.fundingBackend
|
|
746
|
+
break
|
|
747
|
+
case 'validity':
|
|
748
|
+
backend = this.validityBackend
|
|
749
|
+
break
|
|
750
|
+
case 'fulfillment':
|
|
751
|
+
backend = this.fulfillmentBackend
|
|
752
|
+
break
|
|
753
|
+
default:
|
|
754
|
+
throw new ProofError(
|
|
755
|
+
`Unknown proof type: ${proof.type}`,
|
|
756
|
+
ErrorCode.PROOF_NOT_IMPLEMENTED
|
|
757
|
+
)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (!backend) {
|
|
761
|
+
throw new ProofError(
|
|
762
|
+
`${proof.type} backend not initialized`,
|
|
763
|
+
ErrorCode.PROOF_PROVIDER_NOT_READY
|
|
764
|
+
)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
try {
|
|
768
|
+
// Convert hex proof back to bytes
|
|
769
|
+
const proofHex = proof.proof.startsWith('0x') ? proof.proof.slice(2) : proof.proof
|
|
770
|
+
const proofBytes = new Uint8Array(Buffer.from(proofHex, 'hex'))
|
|
771
|
+
|
|
772
|
+
// Verify the proof
|
|
773
|
+
const isValid = await backend.verifyProof({
|
|
774
|
+
proof: proofBytes,
|
|
775
|
+
publicInputs: proof.publicInputs.map(input =>
|
|
776
|
+
input.startsWith('0x') ? input.slice(2) : input
|
|
777
|
+
),
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
return isValid
|
|
781
|
+
} catch (error) {
|
|
782
|
+
if (this.config.verbose) {
|
|
783
|
+
console.error('[NoirProofProvider] Verification error:', error)
|
|
784
|
+
}
|
|
785
|
+
return false
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Destroy the provider and free resources
|
|
791
|
+
*/
|
|
792
|
+
async destroy(): Promise<void> {
|
|
793
|
+
if (this.fundingBackend) {
|
|
794
|
+
await this.fundingBackend.destroy()
|
|
795
|
+
this.fundingBackend = null
|
|
796
|
+
}
|
|
797
|
+
if (this.validityBackend) {
|
|
798
|
+
await this.validityBackend.destroy()
|
|
799
|
+
this.validityBackend = null
|
|
800
|
+
}
|
|
801
|
+
if (this.fulfillmentBackend) {
|
|
802
|
+
await this.fulfillmentBackend.destroy()
|
|
803
|
+
this.fulfillmentBackend = null
|
|
804
|
+
}
|
|
805
|
+
this.fundingNoir = null
|
|
806
|
+
this.validityNoir = null
|
|
807
|
+
this.fulfillmentNoir = null
|
|
808
|
+
this._isReady = false
|
|
221
809
|
}
|
|
222
810
|
|
|
223
811
|
// ─── Private Methods ───────────────────────────────────────────────────────
|
|
@@ -230,4 +818,286 @@ export class NoirProofProvider implements ProofProvider {
|
|
|
230
818
|
)
|
|
231
819
|
}
|
|
232
820
|
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Compute the commitment hash that the circuit expects
|
|
824
|
+
*
|
|
825
|
+
* The circuit computes:
|
|
826
|
+
* 1. commitment = pedersen_commitment([balance, blinding])
|
|
827
|
+
* 2. commitment_hash = pedersen_hash([commitment.x, commitment.y, asset_id])
|
|
828
|
+
*
|
|
829
|
+
* We need to compute this outside to pass as a public input.
|
|
830
|
+
*
|
|
831
|
+
* **IMPORTANT**: This SDK uses SHA256 as a deterministic stand-in for Pedersen hash.
|
|
832
|
+
* Both the SDK and circuit MUST use the same hash function. The bundled circuit
|
|
833
|
+
* artifacts are configured to use SHA256 for compatibility. If you use custom
|
|
834
|
+
* circuits with actual Pedersen hashing, you must update this implementation.
|
|
835
|
+
*
|
|
836
|
+
* @see docs/specs/HASH-COMPATIBILITY.md for hash function requirements
|
|
837
|
+
*/
|
|
838
|
+
private async computeCommitmentHash(
|
|
839
|
+
balance: bigint,
|
|
840
|
+
blindingFactor: Uint8Array,
|
|
841
|
+
assetId: string
|
|
842
|
+
): Promise<{ commitmentHash: string; blindingField: string }> {
|
|
843
|
+
// Convert blinding factor to field element
|
|
844
|
+
const blindingField = this.bytesToField(blindingFactor)
|
|
845
|
+
|
|
846
|
+
// SHA256 is used for both SDK and circuit for hash compatibility
|
|
847
|
+
// The circuit artifacts bundled with this SDK are compiled to use SHA256
|
|
848
|
+
const { sha256 } = await import('@noble/hashes/sha256')
|
|
849
|
+
const { bytesToHex } = await import('@noble/hashes/utils')
|
|
850
|
+
|
|
851
|
+
// Create a deterministic commitment hash
|
|
852
|
+
// Preimage: balance (8 bytes) || blinding (32 bytes) || asset_id (32 bytes)
|
|
853
|
+
const preimage = new Uint8Array([
|
|
854
|
+
...this.bigintToBytes(balance, 8),
|
|
855
|
+
...blindingFactor.slice(0, 32),
|
|
856
|
+
...this.hexToBytes(this.assetIdToField(assetId)),
|
|
857
|
+
])
|
|
858
|
+
|
|
859
|
+
const hash = sha256(preimage)
|
|
860
|
+
const commitmentHash = bytesToHex(hash)
|
|
861
|
+
|
|
862
|
+
return { commitmentHash, blindingField }
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Convert asset ID to field element
|
|
867
|
+
*/
|
|
868
|
+
private assetIdToField(assetId: string): string {
|
|
869
|
+
// If it's already a hex string, use it directly
|
|
870
|
+
if (assetId.startsWith('0x')) {
|
|
871
|
+
return assetId.slice(2).padStart(64, '0')
|
|
872
|
+
}
|
|
873
|
+
// Otherwise, hash the string to get a field element
|
|
874
|
+
const encoder = new TextEncoder()
|
|
875
|
+
const bytes = encoder.encode(assetId)
|
|
876
|
+
let result = 0n
|
|
877
|
+
for (let i = 0; i < bytes.length && i < 31; i++) {
|
|
878
|
+
result = result * 256n + BigInt(bytes[i])
|
|
879
|
+
}
|
|
880
|
+
return result.toString(16).padStart(64, '0')
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Convert bytes to field element string
|
|
885
|
+
*/
|
|
886
|
+
private bytesToField(bytes: Uint8Array): string {
|
|
887
|
+
let result = 0n
|
|
888
|
+
const len = Math.min(bytes.length, 31) // Field element max 31 bytes
|
|
889
|
+
for (let i = 0; i < len; i++) {
|
|
890
|
+
result = result * 256n + BigInt(bytes[i])
|
|
891
|
+
}
|
|
892
|
+
return result.toString()
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Convert bigint to bytes
|
|
897
|
+
*/
|
|
898
|
+
private bigintToBytes(value: bigint, length: number): Uint8Array {
|
|
899
|
+
const bytes = new Uint8Array(length)
|
|
900
|
+
let v = value
|
|
901
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
902
|
+
bytes[i] = Number(v & 0xffn)
|
|
903
|
+
v = v >> 8n
|
|
904
|
+
}
|
|
905
|
+
return bytes
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Convert hex string to bytes
|
|
910
|
+
*/
|
|
911
|
+
private hexToBytes(hex: string): Uint8Array {
|
|
912
|
+
const h = hex.startsWith('0x') ? hex.slice(2) : hex
|
|
913
|
+
const bytes = new Uint8Array(h.length / 2)
|
|
914
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
915
|
+
bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16)
|
|
916
|
+
}
|
|
917
|
+
return bytes
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Convert hex string to field element string
|
|
922
|
+
*/
|
|
923
|
+
private hexToField(hex: string): string {
|
|
924
|
+
const h = hex.startsWith('0x') ? hex.slice(2) : hex
|
|
925
|
+
// Pad to 64 chars (32 bytes) for consistency
|
|
926
|
+
return h.padStart(64, '0')
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Convert field string to 32-byte array
|
|
931
|
+
*/
|
|
932
|
+
private fieldToBytes32(field: string): number[] {
|
|
933
|
+
const hex = field.padStart(64, '0')
|
|
934
|
+
const bytes: number[] = []
|
|
935
|
+
for (let i = 0; i < 32; i++) {
|
|
936
|
+
bytes.push(parseInt(hex.slice(i * 2, i * 2 + 2), 16))
|
|
937
|
+
}
|
|
938
|
+
return bytes
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Compute sender commitment for validity proof
|
|
943
|
+
*
|
|
944
|
+
* Uses SHA256 for SDK-side computation. The bundled circuit artifacts
|
|
945
|
+
* are compiled to use SHA256 for compatibility with this SDK.
|
|
946
|
+
*
|
|
947
|
+
* @see computeCommitmentHash for hash function compatibility notes
|
|
948
|
+
*/
|
|
949
|
+
private async computeSenderCommitment(
|
|
950
|
+
senderAddressField: string,
|
|
951
|
+
senderBlindingField: string
|
|
952
|
+
): Promise<{ commitmentX: string; commitmentY: string }> {
|
|
953
|
+
const { sha256 } = await import('@noble/hashes/sha256')
|
|
954
|
+
const { bytesToHex } = await import('@noble/hashes/utils')
|
|
955
|
+
|
|
956
|
+
// Simulate commitment: hash(address || blinding)
|
|
957
|
+
const addressBytes = this.hexToBytes(senderAddressField)
|
|
958
|
+
const blindingBytes = this.hexToBytes(senderBlindingField.padStart(64, '0'))
|
|
959
|
+
|
|
960
|
+
const preimage = new Uint8Array([...addressBytes, ...blindingBytes])
|
|
961
|
+
const hash = sha256(preimage)
|
|
962
|
+
|
|
963
|
+
// Split hash into x and y components (16 bytes each)
|
|
964
|
+
const commitmentX = bytesToHex(hash.slice(0, 16)).padStart(64, '0')
|
|
965
|
+
const commitmentY = bytesToHex(hash.slice(16, 32)).padStart(64, '0')
|
|
966
|
+
|
|
967
|
+
return { commitmentX, commitmentY }
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Compute nullifier for validity proof
|
|
972
|
+
*
|
|
973
|
+
* Uses SHA256 for SDK-side computation. The bundled circuit artifacts
|
|
974
|
+
* are compiled to use SHA256 for compatibility with this SDK.
|
|
975
|
+
*
|
|
976
|
+
* @see computeCommitmentHash for hash function compatibility notes
|
|
977
|
+
*/
|
|
978
|
+
private async computeNullifier(
|
|
979
|
+
senderSecretField: string,
|
|
980
|
+
intentHashField: string,
|
|
981
|
+
nonceField: string
|
|
982
|
+
): Promise<string> {
|
|
983
|
+
const { sha256 } = await import('@noble/hashes/sha256')
|
|
984
|
+
const { bytesToHex } = await import('@noble/hashes/utils')
|
|
985
|
+
|
|
986
|
+
// Simulate nullifier: hash(secret || intent_hash || nonce)
|
|
987
|
+
const secretBytes = this.hexToBytes(senderSecretField.padStart(64, '0'))
|
|
988
|
+
const intentBytes = this.hexToBytes(intentHashField)
|
|
989
|
+
const nonceBytes = this.hexToBytes(nonceField.padStart(64, '0'))
|
|
990
|
+
|
|
991
|
+
const preimage = new Uint8Array([...secretBytes, ...intentBytes, ...nonceBytes])
|
|
992
|
+
const hash = sha256(preimage)
|
|
993
|
+
|
|
994
|
+
return bytesToHex(hash)
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Compute output commitment for fulfillment proof
|
|
999
|
+
*
|
|
1000
|
+
* Uses SHA256 for SDK-side computation. The bundled circuit artifacts
|
|
1001
|
+
* are compiled to use SHA256 for compatibility with this SDK.
|
|
1002
|
+
*
|
|
1003
|
+
* @see computeCommitmentHash for hash function compatibility notes
|
|
1004
|
+
*/
|
|
1005
|
+
private async computeOutputCommitment(
|
|
1006
|
+
outputAmount: bigint,
|
|
1007
|
+
outputBlinding: Uint8Array
|
|
1008
|
+
): Promise<{ commitmentX: string; commitmentY: string }> {
|
|
1009
|
+
const { sha256 } = await import('@noble/hashes/sha256')
|
|
1010
|
+
const { bytesToHex } = await import('@noble/hashes/utils')
|
|
1011
|
+
|
|
1012
|
+
// Simulate commitment: hash(amount || blinding)
|
|
1013
|
+
const amountBytes = this.bigintToBytes(outputAmount, 8)
|
|
1014
|
+
const blindingBytes = outputBlinding.slice(0, 32)
|
|
1015
|
+
|
|
1016
|
+
const preimage = new Uint8Array([...amountBytes, ...blindingBytes])
|
|
1017
|
+
const hash = sha256(preimage)
|
|
1018
|
+
|
|
1019
|
+
// Split hash into x and y components (16 bytes each)
|
|
1020
|
+
const commitmentX = bytesToHex(hash.slice(0, 16)).padStart(64, '0')
|
|
1021
|
+
const commitmentY = bytesToHex(hash.slice(16, 32)).padStart(64, '0')
|
|
1022
|
+
|
|
1023
|
+
return { commitmentX, commitmentY }
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Compute solver ID from solver secret
|
|
1028
|
+
*
|
|
1029
|
+
* Uses SHA256 for SDK-side computation. The bundled circuit artifacts
|
|
1030
|
+
* are compiled to use SHA256 for compatibility with this SDK.
|
|
1031
|
+
*
|
|
1032
|
+
* @see computeCommitmentHash for hash function compatibility notes
|
|
1033
|
+
*/
|
|
1034
|
+
private async computeSolverId(solverSecretField: string): Promise<string> {
|
|
1035
|
+
const { sha256 } = await import('@noble/hashes/sha256')
|
|
1036
|
+
const { bytesToHex } = await import('@noble/hashes/utils')
|
|
1037
|
+
|
|
1038
|
+
// Simulate solver_id: hash(solver_secret)
|
|
1039
|
+
const secretBytes = this.hexToBytes(solverSecretField.padStart(64, '0'))
|
|
1040
|
+
const hash = sha256(secretBytes)
|
|
1041
|
+
|
|
1042
|
+
return bytesToHex(hash)
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Compute oracle message hash for fulfillment proof
|
|
1047
|
+
*
|
|
1048
|
+
* Hash of attestation data that oracle signs
|
|
1049
|
+
*/
|
|
1050
|
+
private async computeOracleMessageHash(
|
|
1051
|
+
recipient: string,
|
|
1052
|
+
amount: bigint,
|
|
1053
|
+
txHash: string,
|
|
1054
|
+
blockNumber: bigint
|
|
1055
|
+
): Promise<number[]> {
|
|
1056
|
+
const { sha256 } = await import('@noble/hashes/sha256')
|
|
1057
|
+
|
|
1058
|
+
// Hash: recipient || amount || txHash || blockNumber
|
|
1059
|
+
const recipientBytes = this.hexToBytes(this.hexToField(recipient))
|
|
1060
|
+
const amountBytes = this.bigintToBytes(amount, 8)
|
|
1061
|
+
const txHashBytes = this.hexToBytes(this.hexToField(txHash))
|
|
1062
|
+
const blockBytes = this.bigintToBytes(blockNumber, 8)
|
|
1063
|
+
|
|
1064
|
+
const preimage = new Uint8Array([
|
|
1065
|
+
...recipientBytes,
|
|
1066
|
+
...amountBytes,
|
|
1067
|
+
...txHashBytes,
|
|
1068
|
+
...blockBytes,
|
|
1069
|
+
])
|
|
1070
|
+
const hash = sha256(preimage)
|
|
1071
|
+
|
|
1072
|
+
return Array.from(hash)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Derive secp256k1 public key coordinates from a private key
|
|
1077
|
+
*
|
|
1078
|
+
* @param privateKey - 32-byte private key as Uint8Array
|
|
1079
|
+
* @returns X and Y coordinates as 32-byte arrays
|
|
1080
|
+
*/
|
|
1081
|
+
private getPublicKeyCoordinates(privateKey: Uint8Array): PublicKeyCoordinates {
|
|
1082
|
+
// Get uncompressed public key (65 bytes: 04 || x || y)
|
|
1083
|
+
const uncompressedPubKey = secp256k1.getPublicKey(privateKey, false)
|
|
1084
|
+
|
|
1085
|
+
// Extract X (bytes 1-32) and Y (bytes 33-64)
|
|
1086
|
+
const x = Array.from(uncompressedPubKey.slice(1, 33))
|
|
1087
|
+
const y = Array.from(uncompressedPubKey.slice(33, 65))
|
|
1088
|
+
|
|
1089
|
+
return { x, y }
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Derive public key coordinates from a field string (private key)
|
|
1094
|
+
*
|
|
1095
|
+
* @param privateKeyField - Private key as hex field string
|
|
1096
|
+
* @returns X and Y coordinates as 32-byte arrays
|
|
1097
|
+
*/
|
|
1098
|
+
private getPublicKeyFromField(privateKeyField: string): PublicKeyCoordinates {
|
|
1099
|
+
// Convert field to 32-byte array
|
|
1100
|
+
const privateKeyBytes = this.hexToBytes(privateKeyField.padStart(64, '0'))
|
|
1101
|
+
return this.getPublicKeyCoordinates(privateKeyBytes)
|
|
1102
|
+
}
|
|
233
1103
|
}
|