nara-sdk 1.0.39 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/package.json +1 -1
- package/src/quest.ts +67 -56
- package/src/zkid.ts +132 -77
package/README.md
CHANGED
|
@@ -35,12 +35,12 @@ Circuit files: `withdraw.wasm` + `withdraw_final.zkey`, `ownership.wasm` + `owne
|
|
|
35
35
|
On-chain registry for AI agents with identity, memory, and activity tracking:
|
|
36
36
|
|
|
37
37
|
- Register a unique agent ID (lowercase only, no uppercase letters allowed)
|
|
38
|
-
-
|
|
38
|
+
- **Referral system**: register with referral via `registerAgentWithReferral`, or set referral post-registration via `setReferral`
|
|
39
39
|
- Store agent **bio** and **metadata** (JSON) on-chain
|
|
40
40
|
- Upload persistent **memory** via chunked buffer mechanism — auto-chunked ~800-byte writes with resumable uploads
|
|
41
|
-
- **Activity logging
|
|
41
|
+
- **Activity logging**: `logActivity` for standard logging, `logActivityWithReferral` for referral-based logging
|
|
42
42
|
- Memory modes: `new`, `update`, `append`, `auto` (auto-detects)
|
|
43
|
-
- Points are minted as **Token-2022 SPL tokens**
|
|
43
|
+
- Points are minted as **Token-2022 SPL tokens** — separate mints for registration points (`pointMint`), referee rewards (`refereeMint`), and activity rewards (`refereeActivityMint`)
|
|
44
44
|
|
|
45
45
|
## Skills Hub
|
|
46
46
|
|
|
@@ -187,6 +187,7 @@ console.log(isValidRecipient(recipient.publicKey)); // true
|
|
|
187
187
|
```typescript
|
|
188
188
|
import {
|
|
189
189
|
registerAgent,
|
|
190
|
+
registerAgentWithReferral,
|
|
190
191
|
getAgentRecord,
|
|
191
192
|
getAgentInfo,
|
|
192
193
|
getAgentMemory,
|
|
@@ -195,6 +196,7 @@ import {
|
|
|
195
196
|
setMetadata,
|
|
196
197
|
uploadMemory,
|
|
197
198
|
logActivity,
|
|
199
|
+
logActivityWithReferral,
|
|
198
200
|
setReferral,
|
|
199
201
|
deleteAgent,
|
|
200
202
|
Keypair,
|
|
@@ -204,10 +206,12 @@ import { Connection } from "@solana/web3.js";
|
|
|
204
206
|
const connection = new Connection("https://mainnet-api.nara.build/", "confirmed");
|
|
205
207
|
const wallet = Keypair.fromSecretKey(/* your secret key */);
|
|
206
208
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
// 1a. Register an agent (lowercase only, charges registration fee)
|
|
210
|
+
const { signature, agentPubkey } = await registerAgent(connection, wallet, "my-agent");
|
|
211
|
+
|
|
212
|
+
// 1b. Or register with referral
|
|
213
|
+
const result = await registerAgentWithReferral(
|
|
214
|
+
connection, wallet, "my-agent", "referral-agent-id"
|
|
211
215
|
);
|
|
212
216
|
|
|
213
217
|
// 2. Or set referral after registration
|
|
@@ -230,9 +234,11 @@ const bytes = await getAgentMemory(connection, "my-agent");
|
|
|
230
234
|
const extra = Buffer.from(JSON.stringify({ more: "data" }));
|
|
231
235
|
await uploadMemory(connection, wallet, "my-agent", extra, {}, "append");
|
|
232
236
|
|
|
233
|
-
//
|
|
237
|
+
// 7a. Log activity
|
|
234
238
|
await logActivity(connection, wallet, "my-agent", "gpt-4", "chat", "Answered a question");
|
|
235
|
-
|
|
239
|
+
|
|
240
|
+
// 7b. Log activity with referral
|
|
241
|
+
await logActivityWithReferral(connection, wallet, "my-agent", "gpt-4", "chat", "With referral", "referral-agent-id");
|
|
236
242
|
|
|
237
243
|
// 8. Query agent info
|
|
238
244
|
const info = await getAgentInfo(connection, "my-agent");
|
|
@@ -240,7 +246,7 @@ console.log(info.record.agentId, info.record.referralId, info.bio);
|
|
|
240
246
|
|
|
241
247
|
// 9. Query program config
|
|
242
248
|
const config = await getAgentRegistryConfig(connection);
|
|
243
|
-
console.log(config.pointMint.toBase58(), config.pointsSelf, config.
|
|
249
|
+
console.log(config.pointMint.toBase58(), config.refereeMint.toBase58(), config.pointsSelf, config.activityReward);
|
|
244
250
|
```
|
|
245
251
|
|
|
246
252
|
### Skills SDK
|
package/package.json
CHANGED
package/src/quest.ts
CHANGED
|
@@ -21,22 +21,18 @@ import naraQuestIdl from "./idls/nara_quest.json";
|
|
|
21
21
|
const BN254_FIELD =
|
|
22
22
|
21888242871839275222246405745257275088696311157297823662689037894645226208583n;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return srcPath;
|
|
24
|
+
// Lazily resolve default ZK circuit file paths (Node.js only).
|
|
25
|
+
// In browser environments, pass circuitWasmPath/zkeyPath via QuestOptions.
|
|
26
|
+
async function resolveDefaultZkPaths(): Promise<{ wasm: string; zkey: string }> {
|
|
27
|
+
const { fileURLToPath } = await import("url");
|
|
28
|
+
const { dirname, join } = await import("path");
|
|
29
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
return {
|
|
31
|
+
wasm: join(dir, "zk", "answer_proof.wasm"),
|
|
32
|
+
zkey: join(dir, "zk", "answer_proof_final.zkey"),
|
|
33
|
+
};
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
const DEFAULT_CIRCUIT_WASM = findZkFile("answer_proof.wasm");
|
|
38
|
-
const DEFAULT_ZKEY = findZkFile("answer_proof_final.zkey");
|
|
39
|
-
|
|
40
36
|
// ─── Types ───────────────────────────────────────────────────────
|
|
41
37
|
|
|
42
38
|
export interface QuestInfo {
|
|
@@ -77,8 +73,10 @@ export interface SubmitRelayResult {
|
|
|
77
73
|
|
|
78
74
|
export interface QuestOptions {
|
|
79
75
|
programId?: string;
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
/** File path (Node.js), URL string, or pre-loaded Uint8Array (browser) */
|
|
77
|
+
circuitWasmPath?: string | Uint8Array;
|
|
78
|
+
/** File path (Node.js), URL string, or pre-loaded Uint8Array (browser) */
|
|
79
|
+
zkeyPath?: string | Uint8Array;
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
export interface ActivityLog {
|
|
@@ -90,71 +88,79 @@ export interface ActivityLog {
|
|
|
90
88
|
referralAgentId?: string;
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
// ─── ZK utilities
|
|
91
|
+
// ─── ZK utilities (browser-compatible, no Buffer) ───────────────
|
|
92
|
+
|
|
93
|
+
function hexFromBytes(bytes: Uint8Array): string {
|
|
94
|
+
return Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
|
98
|
+
const total = arrays.reduce((sum, a) => sum + a.length, 0);
|
|
99
|
+
const result = new Uint8Array(total);
|
|
100
|
+
let offset = 0;
|
|
101
|
+
for (const a of arrays) { result.set(a, offset); offset += a.length; }
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
94
104
|
|
|
95
|
-
function
|
|
96
|
-
|
|
105
|
+
function bigintToBytes32(v: bigint): Uint8Array {
|
|
106
|
+
const hex = v.toString(16).padStart(64, "0");
|
|
107
|
+
const bytes = new Uint8Array(32);
|
|
108
|
+
for (let i = 0; i < 32; i++) bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
109
|
+
return bytes;
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
function answerToField(answer: string): bigint {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
113
|
+
const encoded = new TextEncoder().encode(answer);
|
|
114
|
+
return BigInt("0x" + hexFromBytes(encoded)) % BN254_FIELD;
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
function hashBytesToFieldStr(hashBytes: number[]): string {
|
|
106
|
-
return BigInt("0x" +
|
|
118
|
+
return BigInt("0x" + hexFromBytes(new Uint8Array(hashBytes))).toString();
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
function pubkeyToCircuitInputs(pubkey: PublicKey): {
|
|
110
122
|
lo: string;
|
|
111
123
|
hi: string;
|
|
112
124
|
} {
|
|
113
|
-
const bytes = pubkey.
|
|
125
|
+
const bytes = pubkey.toBytes();
|
|
114
126
|
return {
|
|
115
|
-
lo: BigInt("0x" + bytes.
|
|
116
|
-
hi: BigInt("0x" + bytes.
|
|
127
|
+
lo: BigInt("0x" + hexFromBytes(bytes.slice(16, 32))).toString(),
|
|
128
|
+
hi: BigInt("0x" + hexFromBytes(bytes.slice(0, 16))).toString(),
|
|
117
129
|
};
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
function proofToSolana(proof: any): ZkProof {
|
|
121
|
-
const negY = (y: string) =>
|
|
122
|
-
const be = (s: string) =>
|
|
133
|
+
const negY = (y: string) => bigintToBytes32(BN254_FIELD - BigInt(y));
|
|
134
|
+
const be = (s: string) => bigintToBytes32(BigInt(s));
|
|
123
135
|
return {
|
|
124
|
-
proofA: Array.from(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
be(proof.pi_b[1][0]),
|
|
133
|
-
])
|
|
134
|
-
),
|
|
135
|
-
proofC: Array.from(
|
|
136
|
-
Buffer.concat([be(proof.pi_c[0]), be(proof.pi_c[1])])
|
|
137
|
-
),
|
|
136
|
+
proofA: Array.from(concatBytes(be(proof.pi_a[0]), negY(proof.pi_a[1]))),
|
|
137
|
+
proofB: Array.from(concatBytes(
|
|
138
|
+
be(proof.pi_b[0][1]),
|
|
139
|
+
be(proof.pi_b[0][0]),
|
|
140
|
+
be(proof.pi_b[1][1]),
|
|
141
|
+
be(proof.pi_b[1][0]),
|
|
142
|
+
)),
|
|
143
|
+
proofC: Array.from(concatBytes(be(proof.pi_c[0]), be(proof.pi_c[1]))),
|
|
138
144
|
};
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
function proofToHex(proof: any): ZkProofHex {
|
|
142
|
-
const negY = (y: string) =>
|
|
143
|
-
const be = (s: string) =>
|
|
148
|
+
const negY = (y: string) => bigintToBytes32(BN254_FIELD - BigInt(y));
|
|
149
|
+
const be = (s: string) => bigintToBytes32(BigInt(s));
|
|
144
150
|
return {
|
|
145
|
-
proofA:
|
|
146
|
-
proofB:
|
|
151
|
+
proofA: hexFromBytes(concatBytes(be(proof.pi_a[0]), negY(proof.pi_a[1]))),
|
|
152
|
+
proofB: hexFromBytes(concatBytes(
|
|
147
153
|
be(proof.pi_b[0][1]),
|
|
148
154
|
be(proof.pi_b[0][0]),
|
|
149
155
|
be(proof.pi_b[1][1]),
|
|
150
156
|
be(proof.pi_b[1][0]),
|
|
151
|
-
|
|
152
|
-
proofC:
|
|
157
|
+
)),
|
|
158
|
+
proofC: hexFromBytes(concatBytes(be(proof.pi_c[0]), be(proof.pi_c[1]))),
|
|
153
159
|
};
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
// Suppress console output from snarkjs WASM during proof generation.
|
|
157
|
-
async function silentProve(snarkjs: any, input: Record<string, string>, wasmPath: string, zkeyPath: string) {
|
|
163
|
+
async function silentProve(snarkjs: any, input: Record<string, string>, wasmPath: string | Uint8Array, zkeyPath: string | Uint8Array) {
|
|
158
164
|
const savedLog = console.log;
|
|
159
165
|
const savedError = console.error;
|
|
160
166
|
console.log = () => {};
|
|
@@ -188,7 +194,7 @@ function createProgram(
|
|
|
188
194
|
|
|
189
195
|
function getPoolPda(programId: PublicKey): PublicKey {
|
|
190
196
|
const [pda] = PublicKey.findProgramAddressSync(
|
|
191
|
-
[
|
|
197
|
+
[new TextEncoder().encode("quest_pool")],
|
|
192
198
|
programId
|
|
193
199
|
);
|
|
194
200
|
return pda;
|
|
@@ -199,7 +205,7 @@ function getWinnerRecordPda(
|
|
|
199
205
|
user: PublicKey
|
|
200
206
|
): PublicKey {
|
|
201
207
|
const [pda] = PublicKey.findProgramAddressSync(
|
|
202
|
-
[
|
|
208
|
+
[new TextEncoder().encode("quest_winner"), user.toBytes()],
|
|
203
209
|
programId
|
|
204
210
|
);
|
|
205
211
|
return pda;
|
|
@@ -279,8 +285,13 @@ export async function generateProof(
|
|
|
279
285
|
round: string,
|
|
280
286
|
options?: QuestOptions
|
|
281
287
|
): Promise<{ solana: ZkProof; hex: ZkProofHex }> {
|
|
282
|
-
|
|
283
|
-
|
|
288
|
+
let wasmSource = options?.circuitWasmPath;
|
|
289
|
+
let zkeySource = options?.zkeyPath;
|
|
290
|
+
if (!wasmSource || !zkeySource) {
|
|
291
|
+
const defaults = await resolveDefaultZkPaths();
|
|
292
|
+
wasmSource ??= defaults.wasm;
|
|
293
|
+
zkeySource ??= defaults.zkey;
|
|
294
|
+
}
|
|
284
295
|
|
|
285
296
|
const snarkjs = await import("snarkjs");
|
|
286
297
|
const answerHashFieldStr = hashBytesToFieldStr(answerHash);
|
|
@@ -295,8 +306,8 @@ export async function generateProof(
|
|
|
295
306
|
pubkey_hi: hi,
|
|
296
307
|
round: round,
|
|
297
308
|
},
|
|
298
|
-
|
|
299
|
-
|
|
309
|
+
wasmSource,
|
|
310
|
+
zkeySource
|
|
300
311
|
);
|
|
301
312
|
|
|
302
313
|
return {
|
|
@@ -455,7 +466,7 @@ export async function computeAnswerHash(answer: string): Promise<number[]> {
|
|
|
455
466
|
const fieldVal = answerToField(answer);
|
|
456
467
|
const hashRaw = poseidon([fieldVal]);
|
|
457
468
|
const hashStr: string = poseidon.F.toString(hashRaw);
|
|
458
|
-
return Array.from(
|
|
469
|
+
return Array.from(bigintToBytes32(BigInt(hashStr)));
|
|
459
470
|
}
|
|
460
471
|
|
|
461
472
|
/**
|
package/src/zkid.ts
CHANGED
|
@@ -7,9 +7,6 @@
|
|
|
7
7
|
* - Ownership can be transferred via ZK proof without revealing the owner's wallet
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createHash } from "crypto";
|
|
11
|
-
import { fileURLToPath } from "url";
|
|
12
|
-
import { dirname, join } from "path";
|
|
13
10
|
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
|
14
11
|
import * as anchor from "@coral-xyz/anchor";
|
|
15
12
|
import { Program, AnchorProvider, Wallet } from "@coral-xyz/anchor";
|
|
@@ -19,16 +16,6 @@ import BN from "bn.js";
|
|
|
19
16
|
import type { NaraZk } from "./idls/nara_zk";
|
|
20
17
|
import { DEFAULT_ZKID_PROGRAM_ID } from "./constants";
|
|
21
18
|
import naraZkIdl from "./idls/nara_zk.json";
|
|
22
|
-
import { createRequire } from "module";
|
|
23
|
-
|
|
24
|
-
// _require is used only for snarkjs (external, loaded at runtime).
|
|
25
|
-
// Supports both ESM (tsx dev) and esbuild CJS bundle (import.meta.url is "" — falsy)
|
|
26
|
-
const _require: NodeRequire = import.meta.url
|
|
27
|
-
? createRequire(import.meta.url)
|
|
28
|
-
: eval("require") as NodeRequire;
|
|
29
|
-
const __dirname: string = import.meta.url
|
|
30
|
-
? dirname(fileURLToPath(import.meta.url))
|
|
31
|
-
: eval("__dirname") as string;
|
|
32
19
|
|
|
33
20
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
34
21
|
|
|
@@ -36,10 +23,22 @@ const BN254_PRIME =
|
|
|
36
23
|
21888242871839275222246405745257275088696311157297823662689037894645226208583n;
|
|
37
24
|
const MERKLE_LEVELS = 64;
|
|
38
25
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
// Lazily resolve default ZK circuit file paths (Node.js only).
|
|
27
|
+
// In browser environments, pass withdrawWasm/withdrawZkey/ownershipWasm/ownershipZkey via ZkIdOptions.
|
|
28
|
+
async function resolveDefaultZkPaths(): Promise<{
|
|
29
|
+
withdrawWasm: string; withdrawZkey: string;
|
|
30
|
+
ownershipWasm: string; ownershipZkey: string;
|
|
31
|
+
}> {
|
|
32
|
+
const { fileURLToPath } = await import("url");
|
|
33
|
+
const { dirname, join } = await import("path");
|
|
34
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
return {
|
|
36
|
+
withdrawWasm: join(dir, "zk", "withdraw.wasm"),
|
|
37
|
+
withdrawZkey: join(dir, "zk", "withdraw_final.zkey"),
|
|
38
|
+
ownershipWasm: join(dir, "zk", "ownership.wasm"),
|
|
39
|
+
ownershipZkey: join(dir, "zk", "ownership_final.zkey"),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
43
42
|
|
|
44
43
|
// ─── Public types ─────────────────────────────────────────────────────────────
|
|
45
44
|
|
|
@@ -69,9 +68,36 @@ export interface ClaimableDeposit {
|
|
|
69
68
|
|
|
70
69
|
export interface ZkIdOptions {
|
|
71
70
|
programId?: string;
|
|
71
|
+
/** File path (Node.js), URL string, or pre-loaded Uint8Array (browser) */
|
|
72
|
+
withdrawWasm?: string | Uint8Array;
|
|
73
|
+
/** File path (Node.js), URL string, or pre-loaded Uint8Array (browser) */
|
|
74
|
+
withdrawZkey?: string | Uint8Array;
|
|
75
|
+
/** File path (Node.js), URL string, or pre-loaded Uint8Array (browser) */
|
|
76
|
+
ownershipWasm?: string | Uint8Array;
|
|
77
|
+
/** File path (Node.js), URL string, or pre-loaded Uint8Array (browser) */
|
|
78
|
+
ownershipZkey?: string | Uint8Array;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── Internal crypto helpers (browser-compatible, no Buffer) ────────────────
|
|
82
|
+
|
|
83
|
+
function hexFromBytes(bytes: Uint8Array): string {
|
|
84
|
+
return Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
|
|
87
|
+
function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
|
88
|
+
const total = arrays.reduce((sum, a) => sum + a.length, 0);
|
|
89
|
+
const result = new Uint8Array(total);
|
|
90
|
+
let offset = 0;
|
|
91
|
+
for (const a of arrays) { result.set(a, offset); offset += a.length; }
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function bigintToBytes32(v: bigint): Uint8Array {
|
|
96
|
+
const hex = v.toString(16).padStart(64, "0");
|
|
97
|
+
const bytes = new Uint8Array(32);
|
|
98
|
+
for (let i = 0; i < 32; i++) bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
99
|
+
return bytes;
|
|
100
|
+
}
|
|
75
101
|
|
|
76
102
|
let _poseidon: any = null;
|
|
77
103
|
|
|
@@ -86,58 +112,60 @@ async function poseidonHash(inputs: bigint[]): Promise<bigint> {
|
|
|
86
112
|
return poseidon.F.toObject(result);
|
|
87
113
|
}
|
|
88
114
|
|
|
89
|
-
function bigIntToBytes32BE(n: bigint):
|
|
115
|
+
function bigIntToBytes32BE(n: bigint): Uint8Array {
|
|
90
116
|
if (n < 0n || n >= BN254_PRIME) {
|
|
91
117
|
throw new Error(`bigint out of BN254 field range: ${n}`);
|
|
92
118
|
}
|
|
93
|
-
return
|
|
119
|
+
return bigintToBytes32(n);
|
|
94
120
|
}
|
|
95
121
|
|
|
96
|
-
function bytes32ToBigInt(buf:
|
|
97
|
-
return BigInt("0x" +
|
|
122
|
+
function bytes32ToBigInt(buf: Uint8Array): bigint {
|
|
123
|
+
return BigInt("0x" + hexFromBytes(buf));
|
|
98
124
|
}
|
|
99
125
|
|
|
100
|
-
function toBytes32(buf:
|
|
126
|
+
function toBytes32(buf: Uint8Array): number[] {
|
|
101
127
|
return Array.from(buf.slice(0, 32));
|
|
102
128
|
}
|
|
103
129
|
|
|
104
|
-
function computeNameHash(name: string):
|
|
105
|
-
|
|
130
|
+
async function computeNameHash(name: string): Promise<Uint8Array> {
|
|
131
|
+
const data = new TextEncoder().encode("nara-zk:" + name);
|
|
132
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
133
|
+
return new Uint8Array(digest);
|
|
106
134
|
}
|
|
107
135
|
|
|
108
|
-
function denomBuf(denomination: BN):
|
|
109
|
-
|
|
136
|
+
function denomBuf(denomination: BN): Uint8Array {
|
|
137
|
+
const bytes = new Uint8Array(8);
|
|
138
|
+
const arr = denomination.toArray("le", 8);
|
|
139
|
+
bytes.set(arr);
|
|
140
|
+
return bytes;
|
|
110
141
|
}
|
|
111
142
|
|
|
112
143
|
function packProof(proof: {
|
|
113
144
|
pi_a: string[];
|
|
114
145
|
pi_b: string[][];
|
|
115
146
|
pi_c: string[];
|
|
116
|
-
}):
|
|
147
|
+
}): Uint8Array {
|
|
117
148
|
const ax = BigInt(proof.pi_a[0]!);
|
|
118
149
|
const ay = BigInt(proof.pi_a[1]!);
|
|
119
150
|
const ay_neg = BN254_PRIME - ay; // negate y: standard G1 negation on BN254
|
|
120
151
|
|
|
121
|
-
const proofA =
|
|
122
|
-
|
|
123
|
-
bigIntToBytes32BE(ay_neg),
|
|
124
|
-
]);
|
|
125
|
-
const proofB = Buffer.concat([
|
|
152
|
+
const proofA = concatBytes(bigIntToBytes32BE(ax), bigIntToBytes32BE(ay_neg));
|
|
153
|
+
const proofB = concatBytes(
|
|
126
154
|
bigIntToBytes32BE(BigInt(proof.pi_b[0]![1]!)), // x.c1
|
|
127
155
|
bigIntToBytes32BE(BigInt(proof.pi_b[0]![0]!)), // x.c0
|
|
128
156
|
bigIntToBytes32BE(BigInt(proof.pi_b[1]![1]!)), // y.c1
|
|
129
157
|
bigIntToBytes32BE(BigInt(proof.pi_b[1]![0]!)), // y.c0
|
|
130
|
-
|
|
131
|
-
const proofC =
|
|
158
|
+
);
|
|
159
|
+
const proofC = concatBytes(
|
|
132
160
|
bigIntToBytes32BE(BigInt(proof.pi_c[0]!)),
|
|
133
161
|
bigIntToBytes32BE(BigInt(proof.pi_c[1]!)),
|
|
134
|
-
|
|
135
|
-
return
|
|
162
|
+
);
|
|
163
|
+
return concatBytes(proofA, proofB, proofC); // 256 bytes total
|
|
136
164
|
}
|
|
137
165
|
|
|
138
166
|
async function buildMerklePath(
|
|
139
167
|
leafIndex: bigint,
|
|
140
|
-
filledSubtrees:
|
|
168
|
+
filledSubtrees: Uint8Array[],
|
|
141
169
|
zeros: bigint[]
|
|
142
170
|
): Promise<{ pathElements: bigint[]; pathIndices: number[] }> {
|
|
143
171
|
const pathElements: bigint[] = new Array(MERKLE_LEVELS);
|
|
@@ -156,10 +184,10 @@ async function buildMerklePath(
|
|
|
156
184
|
// Suppress snarkjs WASM console noise during proof generation.
|
|
157
185
|
async function silentProve(
|
|
158
186
|
input: Record<string, string | string[]>,
|
|
159
|
-
wasmPath: string,
|
|
160
|
-
zkeyPath: string
|
|
187
|
+
wasmPath: string | Uint8Array,
|
|
188
|
+
zkeyPath: string | Uint8Array
|
|
161
189
|
) {
|
|
162
|
-
const snarkjs =
|
|
190
|
+
const snarkjs: any = await import("snarkjs");
|
|
163
191
|
const savedLog = console.log;
|
|
164
192
|
const savedError = console.error;
|
|
165
193
|
console.log = () => {};
|
|
@@ -208,31 +236,31 @@ function createReadProgram(
|
|
|
208
236
|
|
|
209
237
|
// ─── PDA helpers ─────────────────────────────────────────────────────────────
|
|
210
238
|
|
|
211
|
-
function findZkIdPda(nameHashBuf:
|
|
239
|
+
function findZkIdPda(nameHashBuf: Uint8Array, programId: PublicKey): [PublicKey, number] {
|
|
212
240
|
return PublicKey.findProgramAddressSync(
|
|
213
|
-
[
|
|
241
|
+
[new TextEncoder().encode("zk_id"), nameHashBuf],
|
|
214
242
|
programId
|
|
215
243
|
);
|
|
216
244
|
}
|
|
217
245
|
|
|
218
|
-
function findInboxPda(nameHashBuf:
|
|
246
|
+
function findInboxPda(nameHashBuf: Uint8Array, programId: PublicKey): [PublicKey, number] {
|
|
219
247
|
return PublicKey.findProgramAddressSync(
|
|
220
|
-
[
|
|
248
|
+
[new TextEncoder().encode("inbox"), nameHashBuf],
|
|
221
249
|
programId
|
|
222
250
|
);
|
|
223
251
|
}
|
|
224
252
|
|
|
225
253
|
function findConfigPda(programId: PublicKey): [PublicKey, number] {
|
|
226
|
-
return PublicKey.findProgramAddressSync([
|
|
254
|
+
return PublicKey.findProgramAddressSync([new TextEncoder().encode("config")], programId);
|
|
227
255
|
}
|
|
228
256
|
|
|
229
257
|
function findNullifierPda(
|
|
230
258
|
denomination: BN,
|
|
231
|
-
nullifierHash:
|
|
259
|
+
nullifierHash: Uint8Array,
|
|
232
260
|
programId: PublicKey
|
|
233
261
|
): [PublicKey, number] {
|
|
234
262
|
return PublicKey.findProgramAddressSync(
|
|
235
|
-
[
|
|
263
|
+
[new TextEncoder().encode("nullifier"), denomBuf(denomination), nullifierHash],
|
|
236
264
|
programId
|
|
237
265
|
);
|
|
238
266
|
}
|
|
@@ -248,10 +276,10 @@ function findNullifierPda(
|
|
|
248
276
|
* has a unique idSecret, preventing nullifier collisions.
|
|
249
277
|
*/
|
|
250
278
|
export async function deriveIdSecret(keypair: Keypair, name: string): Promise<bigint> {
|
|
251
|
-
const message =
|
|
279
|
+
const message = new TextEncoder().encode(`nara-zk:idsecret:v1:${name}`);
|
|
252
280
|
const sig = nacl.sign.detached(message, keypair.secretKey);
|
|
253
|
-
const digest =
|
|
254
|
-
const n = BigInt("0x" + digest
|
|
281
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", sig));
|
|
282
|
+
const n = BigInt("0x" + hexFromBytes(digest));
|
|
255
283
|
return (n % (BN254_PRIME - 1n)) + 1n;
|
|
256
284
|
}
|
|
257
285
|
|
|
@@ -260,7 +288,7 @@ export async function deriveIdSecret(keypair: Keypair, name: string): Promise<bi
|
|
|
260
288
|
* The ZK withdraw circuit encodes the recipient as a BN254 field element.
|
|
261
289
|
*/
|
|
262
290
|
export function isValidRecipient(pubkey: PublicKey): boolean {
|
|
263
|
-
return bytes32ToBigInt(
|
|
291
|
+
return bytes32ToBigInt(pubkey.toBytes()) < BN254_PRIME;
|
|
264
292
|
}
|
|
265
293
|
|
|
266
294
|
/**
|
|
@@ -285,7 +313,7 @@ export async function getZkIdInfo(
|
|
|
285
313
|
): Promise<ZkIdInfo | null> {
|
|
286
314
|
const program = createReadProgram(connection, options?.programId);
|
|
287
315
|
const programId = new PublicKey(options?.programId ?? DEFAULT_ZKID_PROGRAM_ID);
|
|
288
|
-
const [zkIdPda] = findZkIdPda(computeNameHash(name), programId);
|
|
316
|
+
const [zkIdPda] = findZkIdPda(await computeNameHash(name), programId);
|
|
289
317
|
try {
|
|
290
318
|
const data = await program.account.zkIdAccount.fetch(zkIdPda);
|
|
291
319
|
return {
|
|
@@ -316,7 +344,7 @@ export async function createZkId(
|
|
|
316
344
|
const program = createProgram(connection, payer, options?.programId);
|
|
317
345
|
const programId = new PublicKey(options?.programId ?? DEFAULT_ZKID_PROGRAM_ID);
|
|
318
346
|
|
|
319
|
-
const nameHashBuf = computeNameHash(name);
|
|
347
|
+
const nameHashBuf = await computeNameHash(name);
|
|
320
348
|
const idCommitment = await poseidonHash([idSecret]);
|
|
321
349
|
const idCommitmentBuf = bigIntToBytes32BE(idCommitment);
|
|
322
350
|
|
|
@@ -347,7 +375,7 @@ export async function deposit(
|
|
|
347
375
|
options?: ZkIdOptions
|
|
348
376
|
): Promise<string> {
|
|
349
377
|
const program = createProgram(connection, payer, options?.programId);
|
|
350
|
-
const nameHashBuf = computeNameHash(name);
|
|
378
|
+
const nameHashBuf = await computeNameHash(name);
|
|
351
379
|
|
|
352
380
|
return await program.methods
|
|
353
381
|
.deposit(toBytes32(nameHashBuf), denomination)
|
|
@@ -369,7 +397,7 @@ export async function scanClaimableDeposits(
|
|
|
369
397
|
): Promise<ClaimableDeposit[]> {
|
|
370
398
|
const program = createReadProgram(connection, options?.programId);
|
|
371
399
|
const programId = new PublicKey(options?.programId ?? DEFAULT_ZKID_PROGRAM_ID);
|
|
372
|
-
const nameHashBuf = computeNameHash(name);
|
|
400
|
+
const nameHashBuf = await computeNameHash(name);
|
|
373
401
|
|
|
374
402
|
const [zkIdPda] = findZkIdPda(nameHashBuf, programId);
|
|
375
403
|
const [inboxPda] = findInboxPda(nameHashBuf, programId);
|
|
@@ -453,19 +481,19 @@ export async function withdraw(
|
|
|
453
481
|
|
|
454
482
|
// Fetch Merkle tree state
|
|
455
483
|
const [treePda] = PublicKey.findProgramAddressSync(
|
|
456
|
-
[
|
|
484
|
+
[new TextEncoder().encode("tree"), denomBuf(denominationBN)],
|
|
457
485
|
programId
|
|
458
486
|
);
|
|
459
487
|
const treeData = await program.account.merkleTreeAccount.fetch(treePda);
|
|
460
488
|
|
|
461
489
|
const rootIdx: number = treeData.currentRootIndex;
|
|
462
|
-
const root =
|
|
490
|
+
const root = new Uint8Array((treeData.roots as number[][])[rootIdx]!);
|
|
463
491
|
const filledSubtrees = (treeData.filledSubtrees as number[][]).map(s =>
|
|
464
|
-
|
|
492
|
+
new Uint8Array(s)
|
|
465
493
|
);
|
|
466
494
|
// Use on-chain precomputed zeros (avoids expensive client-side computation)
|
|
467
495
|
const zeros = (treeData.zeros as number[][]).map(z =>
|
|
468
|
-
bytes32ToBigInt(
|
|
496
|
+
bytes32ToBigInt(new Uint8Array(z))
|
|
469
497
|
);
|
|
470
498
|
|
|
471
499
|
const { pathElements, pathIndices } = await buildMerklePath(
|
|
@@ -476,7 +504,7 @@ export async function withdraw(
|
|
|
476
504
|
|
|
477
505
|
const nullifier = await poseidonHash([idSecret, BigInt(depositInfo.depositIndex)]);
|
|
478
506
|
const nullifierHashBuf = bigIntToBytes32BE(nullifier);
|
|
479
|
-
const recipientField = bytes32ToBigInt(
|
|
507
|
+
const recipientField = bytes32ToBigInt(recipient.toBytes());
|
|
480
508
|
|
|
481
509
|
const input = {
|
|
482
510
|
idSecret: idSecret.toString(),
|
|
@@ -488,12 +516,20 @@ export async function withdraw(
|
|
|
488
516
|
recipient: recipientField.toString(),
|
|
489
517
|
};
|
|
490
518
|
|
|
491
|
-
|
|
519
|
+
let wasmSource = options?.withdrawWasm;
|
|
520
|
+
let zkeySource = options?.withdrawZkey;
|
|
521
|
+
if (!wasmSource || !zkeySource) {
|
|
522
|
+
const defaults = await resolveDefaultZkPaths();
|
|
523
|
+
wasmSource ??= defaults.withdrawWasm;
|
|
524
|
+
zkeySource ??= defaults.withdrawZkey;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const { proof } = await silentProve(input, wasmSource, zkeySource);
|
|
492
528
|
const packedProof = packProof(proof);
|
|
493
529
|
|
|
494
530
|
return await program.methods
|
|
495
531
|
.withdraw(
|
|
496
|
-
packedProof,
|
|
532
|
+
Buffer.from(packedProof) as any,
|
|
497
533
|
toBytes32(root),
|
|
498
534
|
toBytes32(nullifierHashBuf),
|
|
499
535
|
recipient,
|
|
@@ -526,13 +562,13 @@ export async function transferZkId(
|
|
|
526
562
|
): Promise<string> {
|
|
527
563
|
const program = createProgram(connection, payer, options?.programId);
|
|
528
564
|
const programId = new PublicKey(options?.programId ?? DEFAULT_ZKID_PROGRAM_ID);
|
|
529
|
-
const nameHashBuf = computeNameHash(name);
|
|
565
|
+
const nameHashBuf = await computeNameHash(name);
|
|
530
566
|
|
|
531
567
|
// Fetch current id_commitment from chain
|
|
532
568
|
const [zkIdPda] = findZkIdPda(nameHashBuf, programId);
|
|
533
569
|
const zkId = await program.account.zkIdAccount.fetch(zkIdPda);
|
|
534
570
|
const currentCommitmentField = bytes32ToBigInt(
|
|
535
|
-
|
|
571
|
+
new Uint8Array(zkId.idCommitment as number[])
|
|
536
572
|
);
|
|
537
573
|
|
|
538
574
|
// Compute new id_commitment
|
|
@@ -544,14 +580,23 @@ export async function transferZkId(
|
|
|
544
580
|
idSecret: currentIdSecret.toString(),
|
|
545
581
|
idCommitment: currentCommitmentField.toString(),
|
|
546
582
|
};
|
|
547
|
-
|
|
583
|
+
|
|
584
|
+
let wasmSource = options?.ownershipWasm;
|
|
585
|
+
let zkeySource = options?.ownershipZkey;
|
|
586
|
+
if (!wasmSource || !zkeySource) {
|
|
587
|
+
const defaults = await resolveDefaultZkPaths();
|
|
588
|
+
wasmSource ??= defaults.ownershipWasm;
|
|
589
|
+
zkeySource ??= defaults.ownershipZkey;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const { proof } = await silentProve(input, wasmSource, zkeySource);
|
|
548
593
|
const packedProof = packProof(proof);
|
|
549
594
|
|
|
550
595
|
return await program.methods
|
|
551
596
|
.transferZkId(
|
|
552
597
|
toBytes32(nameHashBuf),
|
|
553
598
|
toBytes32(newCommitmentBuf),
|
|
554
|
-
packedProof
|
|
599
|
+
Buffer.from(packedProof) as any
|
|
555
600
|
)
|
|
556
601
|
.accounts({ payer: payer.publicKey } as any)
|
|
557
602
|
.rpc();
|
|
@@ -566,7 +611,7 @@ export async function transferZkId(
|
|
|
566
611
|
export async function computeIdCommitment(keypair: Keypair, name: string): Promise<string> {
|
|
567
612
|
const idSecret = await deriveIdSecret(keypair, name);
|
|
568
613
|
const commitment = await poseidonHash([idSecret]);
|
|
569
|
-
return bigIntToBytes32BE(commitment)
|
|
614
|
+
return hexFromBytes(bigIntToBytes32BE(commitment));
|
|
570
615
|
}
|
|
571
616
|
|
|
572
617
|
/**
|
|
@@ -588,12 +633,12 @@ export async function transferZkIdByCommitment(
|
|
|
588
633
|
options?: ZkIdOptions
|
|
589
634
|
): Promise<string> {
|
|
590
635
|
const program = createProgram(connection, payer, options?.programId);
|
|
591
|
-
const nameHashBuf = computeNameHash(name);
|
|
636
|
+
const nameHashBuf = await computeNameHash(name);
|
|
592
637
|
|
|
593
638
|
const [zkIdPda] = findZkIdPda(nameHashBuf, new PublicKey(options?.programId ?? DEFAULT_ZKID_PROGRAM_ID));
|
|
594
639
|
const zkId = await program.account.zkIdAccount.fetch(zkIdPda);
|
|
595
640
|
const currentCommitmentField = bytes32ToBigInt(
|
|
596
|
-
|
|
641
|
+
new Uint8Array(zkId.idCommitment as number[])
|
|
597
642
|
);
|
|
598
643
|
|
|
599
644
|
const newCommitmentBuf = bigIntToBytes32BE(newIdCommitment);
|
|
@@ -602,14 +647,23 @@ export async function transferZkIdByCommitment(
|
|
|
602
647
|
idSecret: currentIdSecret.toString(),
|
|
603
648
|
idCommitment: currentCommitmentField.toString(),
|
|
604
649
|
};
|
|
605
|
-
|
|
650
|
+
|
|
651
|
+
let wasmSource = options?.ownershipWasm;
|
|
652
|
+
let zkeySource = options?.ownershipZkey;
|
|
653
|
+
if (!wasmSource || !zkeySource) {
|
|
654
|
+
const defaults = await resolveDefaultZkPaths();
|
|
655
|
+
wasmSource ??= defaults.ownershipWasm;
|
|
656
|
+
zkeySource ??= defaults.ownershipZkey;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const { proof } = await silentProve(input, wasmSource, zkeySource);
|
|
606
660
|
const packedProof = packProof(proof);
|
|
607
661
|
|
|
608
662
|
return await program.methods
|
|
609
663
|
.transferZkId(
|
|
610
664
|
toBytes32(nameHashBuf),
|
|
611
665
|
toBytes32(newCommitmentBuf),
|
|
612
|
-
packedProof
|
|
666
|
+
Buffer.from(packedProof) as any
|
|
613
667
|
)
|
|
614
668
|
.accounts({ payer: payer.publicKey } as any)
|
|
615
669
|
.rpc();
|
|
@@ -630,18 +684,19 @@ export async function getConfig(
|
|
|
630
684
|
): Promise<{ admin: PublicKey; feeRecipient: PublicKey; feeAmount: number }> {
|
|
631
685
|
const programId = new PublicKey(options?.programId ?? DEFAULT_ZKID_PROGRAM_ID);
|
|
632
686
|
const [configPda] = PublicKey.findProgramAddressSync(
|
|
633
|
-
[
|
|
687
|
+
[new TextEncoder().encode("config")],
|
|
634
688
|
programId
|
|
635
689
|
);
|
|
636
690
|
const accountInfo = await connection.getAccountInfo(configPda);
|
|
637
691
|
if (!accountInfo) {
|
|
638
692
|
throw new Error("ZK ID config account not found");
|
|
639
693
|
}
|
|
640
|
-
const
|
|
694
|
+
const data = accountInfo.data;
|
|
695
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
641
696
|
let offset = 8; // skip discriminator
|
|
642
|
-
const admin = new PublicKey(
|
|
643
|
-
const feeRecipient = new PublicKey(
|
|
644
|
-
const feeAmount = Number(
|
|
697
|
+
const admin = new PublicKey(data.subarray(offset, offset + 32)); offset += 32;
|
|
698
|
+
const feeRecipient = new PublicKey(data.subarray(offset, offset + 32)); offset += 32;
|
|
699
|
+
const feeAmount = Number(view.getBigUint64(offset, true));
|
|
645
700
|
return { admin, feeRecipient, feeAmount };
|
|
646
701
|
}
|
|
647
702
|
|