@worldcoin/idkit-core 4.0.1-dev.2039000 → 4.0.1-dev.370b7c0
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 +18 -8
- package/dist/hashing.cjs +28 -0
- package/dist/hashing.d.cts +9 -0
- package/dist/hashing.d.ts +9 -0
- package/dist/hashing.js +26 -0
- package/dist/idkit_wasm_bg.wasm +0 -0
- package/dist/index.cjs +68 -50
- package/dist/index.d.cts +4 -98
- package/dist/index.d.ts +4 -98
- package/dist/index.js +68 -50
- package/dist/signing.cjs +76 -0
- package/dist/signing.d.cts +36 -0
- package/dist/signing.d.ts +36 -0
- package/dist/signing.js +73 -0
- package/package.json +15 -1
package/README.md
CHANGED
|
@@ -10,12 +10,10 @@ npm install @worldcoin/idkit-core
|
|
|
10
10
|
|
|
11
11
|
## Backend: Generate RP Signature
|
|
12
12
|
|
|
13
|
-
The RP signature authenticates your verification requests. Generate it server-side:
|
|
13
|
+
The RP signature authenticates your verification requests. Generate it server-side using the `/signing` subpath (pure JS, no WASM init needed):
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
await IDKit.initServer();
|
|
16
|
+
import { signRequest } from "@worldcoin/idkit-core/signing";
|
|
19
17
|
|
|
20
18
|
// Never expose RP_SIGNING_KEY to clients
|
|
21
19
|
const sig = signRequest("my-action", process.env.RP_SIGNING_KEY);
|
|
@@ -24,8 +22,8 @@ const sig = signRequest("my-action", process.env.RP_SIGNING_KEY);
|
|
|
24
22
|
res.json({
|
|
25
23
|
sig: sig.sig,
|
|
26
24
|
nonce: sig.nonce,
|
|
27
|
-
created_at:
|
|
28
|
-
expires_at:
|
|
25
|
+
created_at: sig.createdAt,
|
|
26
|
+
expires_at: sig.expiresAt,
|
|
29
27
|
});
|
|
30
28
|
```
|
|
31
29
|
|
|
@@ -38,8 +36,6 @@ For common verification scenarios with World ID 3.0 backward compatibility:
|
|
|
38
36
|
```typescript
|
|
39
37
|
import { IDKit, orbLegacy } from "@worldcoin/idkit-core";
|
|
40
38
|
|
|
41
|
-
await IDKit.init();
|
|
42
|
-
|
|
43
39
|
// Fetch signature from your backend
|
|
44
40
|
const rpSig = await fetch("/api/rp-signature").then((r) => r.json());
|
|
45
41
|
|
|
@@ -100,3 +96,17 @@ const response = await fetch(
|
|
|
100
96
|
|
|
101
97
|
const { success } = await response.json();
|
|
102
98
|
```
|
|
99
|
+
|
|
100
|
+
## Subpath Exports
|
|
101
|
+
|
|
102
|
+
Pure JS subpath exports are available for server-side use without WASM initialization:
|
|
103
|
+
|
|
104
|
+
| Subpath | Exports |
|
|
105
|
+
| ---------- | ---------------------------------------------------------------- |
|
|
106
|
+
| `/signing` | `signRequest`, `computeRpSignatureMessage`, `RpSignature` (type) |
|
|
107
|
+
| `/hashing` | `hashSignal` |
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { signRequest } from "@worldcoin/idkit-core/signing";
|
|
111
|
+
import { hashSignal } from "@worldcoin/idkit-core/hashing";
|
|
112
|
+
```
|
package/dist/hashing.cjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var sha3 = require('@noble/hashes/sha3');
|
|
4
|
+
var utils = require('@noble/hashes/utils');
|
|
5
|
+
|
|
6
|
+
// src/lib/hashing.ts
|
|
7
|
+
function hashToField(input) {
|
|
8
|
+
const hash = BigInt("0x" + utils.bytesToHex(sha3.keccak_256(input))) >> 8n;
|
|
9
|
+
return utils.hexToBytes(hash.toString(16).padStart(64, "0"));
|
|
10
|
+
}
|
|
11
|
+
function hashSignal(signal) {
|
|
12
|
+
let input;
|
|
13
|
+
if (signal instanceof Uint8Array) {
|
|
14
|
+
input = signal;
|
|
15
|
+
} else if (signal.startsWith("0x") && isValidHex(signal.slice(2))) {
|
|
16
|
+
input = utils.hexToBytes(signal.slice(2));
|
|
17
|
+
} else {
|
|
18
|
+
input = new TextEncoder().encode(signal);
|
|
19
|
+
}
|
|
20
|
+
return "0x" + utils.bytesToHex(hashToField(input));
|
|
21
|
+
}
|
|
22
|
+
function isValidHex(s) {
|
|
23
|
+
if (s.length === 0) return false;
|
|
24
|
+
if (s.length % 2 !== 0) return false;
|
|
25
|
+
return /^[0-9a-fA-F]+$/.test(s);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.hashSignal = hashSignal;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashes a signal to its field element hex representation.
|
|
3
|
+
*
|
|
4
|
+
* @param signal - The signal to hash (string or Uint8Array)
|
|
5
|
+
* @returns 0x-prefixed hex string representing the signal hash
|
|
6
|
+
*/
|
|
7
|
+
declare function hashSignal(signal: string | Uint8Array): string;
|
|
8
|
+
|
|
9
|
+
export { hashSignal };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashes a signal to its field element hex representation.
|
|
3
|
+
*
|
|
4
|
+
* @param signal - The signal to hash (string or Uint8Array)
|
|
5
|
+
* @returns 0x-prefixed hex string representing the signal hash
|
|
6
|
+
*/
|
|
7
|
+
declare function hashSignal(signal: string | Uint8Array): string;
|
|
8
|
+
|
|
9
|
+
export { hashSignal };
|
package/dist/hashing.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
2
|
+
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
|
3
|
+
|
|
4
|
+
// src/lib/hashing.ts
|
|
5
|
+
function hashToField(input) {
|
|
6
|
+
const hash = BigInt("0x" + bytesToHex(keccak_256(input))) >> 8n;
|
|
7
|
+
return hexToBytes(hash.toString(16).padStart(64, "0"));
|
|
8
|
+
}
|
|
9
|
+
function hashSignal(signal) {
|
|
10
|
+
let input;
|
|
11
|
+
if (signal instanceof Uint8Array) {
|
|
12
|
+
input = signal;
|
|
13
|
+
} else if (signal.startsWith("0x") && isValidHex(signal.slice(2))) {
|
|
14
|
+
input = hexToBytes(signal.slice(2));
|
|
15
|
+
} else {
|
|
16
|
+
input = new TextEncoder().encode(signal);
|
|
17
|
+
}
|
|
18
|
+
return "0x" + bytesToHex(hashToField(input));
|
|
19
|
+
}
|
|
20
|
+
function isValidHex(s) {
|
|
21
|
+
if (s.length === 0) return false;
|
|
22
|
+
if (s.length % 2 !== 0) return false;
|
|
23
|
+
return /^[0-9a-fA-F]+$/.test(s);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { hashSignal };
|
package/dist/idkit_wasm_bg.wasm
CHANGED
|
Binary file
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var sha3 = require('@noble/hashes/sha3');
|
|
4
|
+
var utils = require('@noble/hashes/utils');
|
|
5
|
+
var hmac = require('@noble/hashes/hmac');
|
|
6
|
+
var sha2 = require('@noble/hashes/sha2');
|
|
7
|
+
var secp256k1 = require('@noble/secp256k1');
|
|
8
|
+
|
|
3
9
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
4
10
|
var __defProp = Object.defineProperty;
|
|
5
11
|
var __export = (target, all2) => {
|
|
@@ -388,12 +394,12 @@ function hashSignal(signal) {
|
|
|
388
394
|
wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
|
|
389
395
|
}
|
|
390
396
|
}
|
|
391
|
-
function __wasm_bindgen_func_elem_960(arg0, arg1, arg2) {
|
|
392
|
-
wasm.__wasm_bindgen_func_elem_960(arg0, arg1, addHeapObject(arg2));
|
|
393
|
-
}
|
|
394
397
|
function __wasm_bindgen_func_elem_597(arg0, arg1) {
|
|
395
398
|
wasm.__wasm_bindgen_func_elem_597(arg0, arg1);
|
|
396
399
|
}
|
|
400
|
+
function __wasm_bindgen_func_elem_960(arg0, arg1, arg2) {
|
|
401
|
+
wasm.__wasm_bindgen_func_elem_960(arg0, arg1, addHeapObject(arg2));
|
|
402
|
+
}
|
|
397
403
|
function __wasm_bindgen_func_elem_1345(arg0, arg1, arg2, arg3) {
|
|
398
404
|
wasm.__wasm_bindgen_func_elem_1345(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
|
399
405
|
}
|
|
@@ -1852,10 +1858,6 @@ var idkit_wasm_default = __wbg_init;
|
|
|
1852
1858
|
// src/lib/wasm.ts
|
|
1853
1859
|
var wasmInitialized = false;
|
|
1854
1860
|
var wasmInitPromise = null;
|
|
1855
|
-
async function importNodeModule(specifier) {
|
|
1856
|
-
const dynamicImport = Function("moduleName", "return import(moduleName)");
|
|
1857
|
-
return dynamicImport(specifier);
|
|
1858
|
-
}
|
|
1859
1861
|
async function initIDKit() {
|
|
1860
1862
|
if (wasmInitialized) {
|
|
1861
1863
|
return;
|
|
@@ -1874,36 +1876,6 @@ async function initIDKit() {
|
|
|
1874
1876
|
})();
|
|
1875
1877
|
return wasmInitPromise;
|
|
1876
1878
|
}
|
|
1877
|
-
async function initIDKitServer() {
|
|
1878
|
-
if (wasmInitialized) {
|
|
1879
|
-
return;
|
|
1880
|
-
}
|
|
1881
|
-
if (wasmInitPromise) {
|
|
1882
|
-
return wasmInitPromise;
|
|
1883
|
-
}
|
|
1884
|
-
wasmInitPromise = (async () => {
|
|
1885
|
-
try {
|
|
1886
|
-
const { readFile } = await importNodeModule(
|
|
1887
|
-
"node:fs/promises"
|
|
1888
|
-
);
|
|
1889
|
-
const { fileURLToPath } = await importNodeModule(
|
|
1890
|
-
"node:url"
|
|
1891
|
-
);
|
|
1892
|
-
const { dirname, join } = await importNodeModule(
|
|
1893
|
-
"node:path"
|
|
1894
|
-
);
|
|
1895
|
-
const modulePath = fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
1896
|
-
const wasmPath = join(dirname(modulePath), "idkit_wasm_bg.wasm");
|
|
1897
|
-
const wasmBuffer = await readFile(wasmPath);
|
|
1898
|
-
await idkit_wasm_default({ module_or_path: wasmBuffer });
|
|
1899
|
-
wasmInitialized = true;
|
|
1900
|
-
} catch (error) {
|
|
1901
|
-
wasmInitPromise = null;
|
|
1902
|
-
throw new Error(`Failed to initialize IDKit WASM for server: ${error}`);
|
|
1903
|
-
}
|
|
1904
|
-
})();
|
|
1905
|
-
return wasmInitPromise;
|
|
1906
|
-
}
|
|
1907
1879
|
|
|
1908
1880
|
// src/transports/native.ts
|
|
1909
1881
|
function isInWorldApp() {
|
|
@@ -2313,10 +2285,6 @@ function proveSession2(sessionId, config) {
|
|
|
2313
2285
|
});
|
|
2314
2286
|
}
|
|
2315
2287
|
var IDKit = {
|
|
2316
|
-
/** Initialize WASM for browser environments (not needed in World App) */
|
|
2317
|
-
init: initIDKit,
|
|
2318
|
-
/** Initialize WASM for Node.js/server environments */
|
|
2319
|
-
initServer: initIDKitServer,
|
|
2320
2288
|
/** Create a new verification request */
|
|
2321
2289
|
request: createRequest,
|
|
2322
2290
|
/** Create a new session (no action, no existing session_id) */
|
|
@@ -2359,21 +2327,71 @@ var isServerEnvironment = () => {
|
|
|
2359
2327
|
}
|
|
2360
2328
|
return false;
|
|
2361
2329
|
};
|
|
2330
|
+
function hashToField(input) {
|
|
2331
|
+
const hash = BigInt("0x" + utils.bytesToHex(sha3.keccak_256(input))) >> 8n;
|
|
2332
|
+
return utils.hexToBytes(hash.toString(16).padStart(64, "0"));
|
|
2333
|
+
}
|
|
2334
|
+
function hashSignal2(signal) {
|
|
2335
|
+
let input;
|
|
2336
|
+
if (signal instanceof Uint8Array) {
|
|
2337
|
+
input = signal;
|
|
2338
|
+
} else if (signal.startsWith("0x") && isValidHex(signal.slice(2))) {
|
|
2339
|
+
input = utils.hexToBytes(signal.slice(2));
|
|
2340
|
+
} else {
|
|
2341
|
+
input = new TextEncoder().encode(signal);
|
|
2342
|
+
}
|
|
2343
|
+
return "0x" + utils.bytesToHex(hashToField(input));
|
|
2344
|
+
}
|
|
2345
|
+
function isValidHex(s) {
|
|
2346
|
+
if (s.length === 0) return false;
|
|
2347
|
+
if (s.length % 2 !== 0) return false;
|
|
2348
|
+
return /^[0-9a-fA-F]+$/.test(s);
|
|
2349
|
+
}
|
|
2362
2350
|
|
|
2363
|
-
// src/lib/
|
|
2364
|
-
|
|
2351
|
+
// src/lib/signing.ts
|
|
2352
|
+
secp256k1.etc.hmacSha256Sync = (key, ...msgs) => hmac.hmac(sha2.sha256, key, secp256k1.etc.concatBytes(...msgs));
|
|
2353
|
+
var DEFAULT_TTL_SEC = 300;
|
|
2354
|
+
function computeRpSignatureMessage(nonceBytes, createdAt, expiresAt) {
|
|
2355
|
+
const message = new Uint8Array(48);
|
|
2356
|
+
message.set(nonceBytes, 0);
|
|
2357
|
+
const view = new DataView(message.buffer);
|
|
2358
|
+
view.setBigUint64(32, BigInt(createdAt), false);
|
|
2359
|
+
view.setBigUint64(40, BigInt(expiresAt), false);
|
|
2360
|
+
return message;
|
|
2361
|
+
}
|
|
2362
|
+
function signRequest2(_action, signingKeyHex, ttl = DEFAULT_TTL_SEC) {
|
|
2365
2363
|
if (!isServerEnvironment()) {
|
|
2366
2364
|
throw new Error(
|
|
2367
2365
|
"signRequest can only be used in Node.js environments. This function requires access to signing keys and should never be called from browser/client-side code."
|
|
2368
2366
|
);
|
|
2369
2367
|
}
|
|
2370
|
-
const
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2368
|
+
const keyHex = signingKeyHex.startsWith("0x") ? signingKeyHex.slice(2) : signingKeyHex;
|
|
2369
|
+
if (!/^[0-9a-fA-F]+$/.test(keyHex)) {
|
|
2370
|
+
throw new Error("Invalid signing key: contains non-hex characters");
|
|
2371
|
+
}
|
|
2372
|
+
if (keyHex.length !== 64) {
|
|
2373
|
+
throw new Error(
|
|
2374
|
+
`Invalid signing key: expected 32 bytes (64 hex chars), got ${keyHex.length / 2} bytes`
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
const privKey = secp256k1.etc.hexToBytes(keyHex);
|
|
2378
|
+
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
2379
|
+
const nonceBytes = hashToField(randomBytes);
|
|
2380
|
+
const createdAt = Math.floor(Date.now() / 1e3);
|
|
2381
|
+
const expiresAt = createdAt + ttl;
|
|
2382
|
+
const message = computeRpSignatureMessage(nonceBytes, createdAt, expiresAt);
|
|
2383
|
+
const msgHash = sha3.keccak_256(message);
|
|
2384
|
+
const recSig = secp256k1.sign(msgHash, privKey);
|
|
2385
|
+
const compact = recSig.toCompactRawBytes();
|
|
2386
|
+
const sig65 = new Uint8Array(65);
|
|
2387
|
+
sig65.set(compact, 0);
|
|
2388
|
+
sig65[64] = recSig.recovery + 27;
|
|
2389
|
+
return {
|
|
2390
|
+
sig: "0x" + utils.bytesToHex(sig65),
|
|
2391
|
+
nonce: "0x" + utils.bytesToHex(nonceBytes),
|
|
2392
|
+
createdAt,
|
|
2393
|
+
expiresAt
|
|
2394
|
+
};
|
|
2377
2395
|
}
|
|
2378
2396
|
|
|
2379
2397
|
exports.CredentialRequest = CredentialRequest;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export { RpSignature, signRequest } from './signing.cjs';
|
|
2
|
+
export { hashSignal } from './hashing.cjs';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Configuration types for IDKit
|
|
3
6
|
*
|
|
@@ -240,64 +243,6 @@ type ConstraintNode =
|
|
|
240
243
|
| { any: ConstraintNode[] }
|
|
241
244
|
| { all: ConstraintNode[] };
|
|
242
245
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
interface RpSignature {
|
|
246
|
-
sig: string;
|
|
247
|
-
nonce: string;
|
|
248
|
-
createdAt: number;
|
|
249
|
-
expiresAt: number;
|
|
250
|
-
toJSON(): { sig: string; nonce: string; createdAt: number; expiresAt: number };
|
|
251
|
-
}
|
|
252
|
-
declare class RpSignature {
|
|
253
|
-
private constructor();
|
|
254
|
-
free(): void;
|
|
255
|
-
[Symbol.dispose](): void;
|
|
256
|
-
/**
|
|
257
|
-
* Converts to JSON
|
|
258
|
-
*
|
|
259
|
-
* # Errors
|
|
260
|
-
*
|
|
261
|
-
* Returns an error if setting object properties fails
|
|
262
|
-
*/
|
|
263
|
-
toJSON(): any;
|
|
264
|
-
/**
|
|
265
|
-
* Gets the creation timestamp
|
|
266
|
-
*/
|
|
267
|
-
readonly createdAt: bigint;
|
|
268
|
-
/**
|
|
269
|
-
* Gets the expiration timestamp
|
|
270
|
-
*/
|
|
271
|
-
readonly expiresAt: bigint;
|
|
272
|
-
/**
|
|
273
|
-
* Gets the signature as hex string (0x-prefixed, 65 bytes)
|
|
274
|
-
*/
|
|
275
|
-
readonly sig: string;
|
|
276
|
-
/**
|
|
277
|
-
* Gets the nonce as hex string (0x-prefixed field element)
|
|
278
|
-
*/
|
|
279
|
-
readonly nonce: string;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* WASM initialization and management
|
|
284
|
-
*/
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Initializes the WASM module for browser environments
|
|
288
|
-
* Uses fetch-based loading (works with http/https URLs)
|
|
289
|
-
* This must be called before using any WASM-powered functions
|
|
290
|
-
* Safe to call multiple times - initialization only happens once
|
|
291
|
-
*/
|
|
292
|
-
declare function initIDKit(): Promise<void>;
|
|
293
|
-
/**
|
|
294
|
-
* Initializes the WASM module for Node.js/server environments
|
|
295
|
-
* Uses fs-based loading since Node.js fetch doesn't support file:// URLs
|
|
296
|
-
* This must be called before using any WASM-powered functions
|
|
297
|
-
* Safe to call multiple times - initialization only happens once
|
|
298
|
-
*/
|
|
299
|
-
declare function initIDKitServer(): Promise<void>;
|
|
300
|
-
|
|
301
246
|
/**
|
|
302
247
|
* Result types - re-exported from WASM bindings
|
|
303
248
|
*
|
|
@@ -641,10 +586,6 @@ declare function proveSession(sessionId: string, config: IDKitSessionConfig): ID
|
|
|
641
586
|
* ```
|
|
642
587
|
*/
|
|
643
588
|
declare const IDKit: {
|
|
644
|
-
/** Initialize WASM for browser environments (not needed in World App) */
|
|
645
|
-
init: typeof initIDKit;
|
|
646
|
-
/** Initialize WASM for Node.js/server environments */
|
|
647
|
-
initServer: typeof initIDKitServer;
|
|
648
589
|
/** Create a new verification request */
|
|
649
590
|
request: typeof createRequest;
|
|
650
591
|
/** Create a new session (no action, no existing session_id) */
|
|
@@ -687,39 +628,4 @@ declare const isWeb: () => boolean;
|
|
|
687
628
|
*/
|
|
688
629
|
declare const isNode: () => boolean;
|
|
689
630
|
|
|
690
|
-
|
|
691
|
-
* Signs an RP request for World ID proof verification
|
|
692
|
-
*
|
|
693
|
-
* **Backend-only**: This function should ONLY be used in Node.js/server environments.
|
|
694
|
-
* Never use this in browser/client-side code as it requires access to your signing key.
|
|
695
|
-
*
|
|
696
|
-
* This function generates a cryptographic signature that authenticates your proof request.
|
|
697
|
-
* The returned signature, nonce, and timestamps should be passed as `rp_context` to the client.
|
|
698
|
-
*
|
|
699
|
-
* @param action - The action tied to the proof request
|
|
700
|
-
* @param signingKeyHex - The ECDSA private key as hex (0x-prefixed or not, 32 bytes)
|
|
701
|
-
* @param ttlSeconds - Optional time-to-live in seconds (defaults to 300 = 5 minutes)
|
|
702
|
-
* @returns RpSignature object with sig, nonce, createdAt, expiresAt to use as rp_context
|
|
703
|
-
* @throws Error if called in non-Node.js environment or if parameters are invalid
|
|
704
|
-
*
|
|
705
|
-
* @example
|
|
706
|
-
* ```typescript
|
|
707
|
-
* import { signRequest } from '@worldcoin/idkit-core'
|
|
708
|
-
*
|
|
709
|
-
* const signingKey = process.env.RP_SIGNING_KEY // Load from secure env var
|
|
710
|
-
* const signature = signRequest('my-action', signingKey)
|
|
711
|
-
* console.log(signature.sig, signature.nonce, signature.createdAt, signature.expiresAt)
|
|
712
|
-
* ```
|
|
713
|
-
*/
|
|
714
|
-
declare function signRequest(action: string, signingKeyHex: string, ttlSeconds?: number): RpSignature;
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* Hashes a Signal (string or Uint8Array) to its hash representation.
|
|
718
|
-
* This is the same hashing used internally when constructing proof requests.
|
|
719
|
-
*
|
|
720
|
-
* @param signal - The signal to hash (string or Uint8Array)
|
|
721
|
-
* @returns 0x-prefixed hex string representing the signal hash
|
|
722
|
-
*/
|
|
723
|
-
declare function hashSignal(signal: string | Uint8Array): string;
|
|
724
|
-
|
|
725
|
-
export { type AbiEncodedValue, type ConstraintNode, CredentialRequest, type CredentialRequestType, type CredentialType, type DocumentLegacyPreset, IDKit, type IDKitCompletionResult, type IDKitErrorCode, IDKitErrorCodes, type IDKitRequest, type IDKitRequestConfig, type IDKitResult, type IDKitResultSession, type IDKitSessionConfig, type OrbLegacyPreset, type Preset, type ResponseItemSession, type ResponseItemV3, type ResponseItemV4, type RpContext, RpSignature, type SecureDocumentLegacyPreset, type Status$1 as Status, type WaitOptions, all, any, documentLegacy, hashSignal, isNode, isReactNative, isWeb, orbLegacy, secureDocumentLegacy, signRequest };
|
|
631
|
+
export { type AbiEncodedValue, type ConstraintNode, CredentialRequest, type CredentialRequestType, type CredentialType, type DocumentLegacyPreset, IDKit, type IDKitCompletionResult, type IDKitErrorCode, IDKitErrorCodes, type IDKitRequest, type IDKitRequestConfig, type IDKitResult, type IDKitResultSession, type IDKitSessionConfig, type OrbLegacyPreset, type Preset, type ResponseItemSession, type ResponseItemV3, type ResponseItemV4, type RpContext, type SecureDocumentLegacyPreset, type Status$1 as Status, type WaitOptions, all, any, documentLegacy, isNode, isReactNative, isWeb, orbLegacy, secureDocumentLegacy };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export { RpSignature, signRequest } from './signing.js';
|
|
2
|
+
export { hashSignal } from './hashing.js';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Configuration types for IDKit
|
|
3
6
|
*
|
|
@@ -240,64 +243,6 @@ type ConstraintNode =
|
|
|
240
243
|
| { any: ConstraintNode[] }
|
|
241
244
|
| { all: ConstraintNode[] };
|
|
242
245
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
interface RpSignature {
|
|
246
|
-
sig: string;
|
|
247
|
-
nonce: string;
|
|
248
|
-
createdAt: number;
|
|
249
|
-
expiresAt: number;
|
|
250
|
-
toJSON(): { sig: string; nonce: string; createdAt: number; expiresAt: number };
|
|
251
|
-
}
|
|
252
|
-
declare class RpSignature {
|
|
253
|
-
private constructor();
|
|
254
|
-
free(): void;
|
|
255
|
-
[Symbol.dispose](): void;
|
|
256
|
-
/**
|
|
257
|
-
* Converts to JSON
|
|
258
|
-
*
|
|
259
|
-
* # Errors
|
|
260
|
-
*
|
|
261
|
-
* Returns an error if setting object properties fails
|
|
262
|
-
*/
|
|
263
|
-
toJSON(): any;
|
|
264
|
-
/**
|
|
265
|
-
* Gets the creation timestamp
|
|
266
|
-
*/
|
|
267
|
-
readonly createdAt: bigint;
|
|
268
|
-
/**
|
|
269
|
-
* Gets the expiration timestamp
|
|
270
|
-
*/
|
|
271
|
-
readonly expiresAt: bigint;
|
|
272
|
-
/**
|
|
273
|
-
* Gets the signature as hex string (0x-prefixed, 65 bytes)
|
|
274
|
-
*/
|
|
275
|
-
readonly sig: string;
|
|
276
|
-
/**
|
|
277
|
-
* Gets the nonce as hex string (0x-prefixed field element)
|
|
278
|
-
*/
|
|
279
|
-
readonly nonce: string;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* WASM initialization and management
|
|
284
|
-
*/
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Initializes the WASM module for browser environments
|
|
288
|
-
* Uses fetch-based loading (works with http/https URLs)
|
|
289
|
-
* This must be called before using any WASM-powered functions
|
|
290
|
-
* Safe to call multiple times - initialization only happens once
|
|
291
|
-
*/
|
|
292
|
-
declare function initIDKit(): Promise<void>;
|
|
293
|
-
/**
|
|
294
|
-
* Initializes the WASM module for Node.js/server environments
|
|
295
|
-
* Uses fs-based loading since Node.js fetch doesn't support file:// URLs
|
|
296
|
-
* This must be called before using any WASM-powered functions
|
|
297
|
-
* Safe to call multiple times - initialization only happens once
|
|
298
|
-
*/
|
|
299
|
-
declare function initIDKitServer(): Promise<void>;
|
|
300
|
-
|
|
301
246
|
/**
|
|
302
247
|
* Result types - re-exported from WASM bindings
|
|
303
248
|
*
|
|
@@ -641,10 +586,6 @@ declare function proveSession(sessionId: string, config: IDKitSessionConfig): ID
|
|
|
641
586
|
* ```
|
|
642
587
|
*/
|
|
643
588
|
declare const IDKit: {
|
|
644
|
-
/** Initialize WASM for browser environments (not needed in World App) */
|
|
645
|
-
init: typeof initIDKit;
|
|
646
|
-
/** Initialize WASM for Node.js/server environments */
|
|
647
|
-
initServer: typeof initIDKitServer;
|
|
648
589
|
/** Create a new verification request */
|
|
649
590
|
request: typeof createRequest;
|
|
650
591
|
/** Create a new session (no action, no existing session_id) */
|
|
@@ -687,39 +628,4 @@ declare const isWeb: () => boolean;
|
|
|
687
628
|
*/
|
|
688
629
|
declare const isNode: () => boolean;
|
|
689
630
|
|
|
690
|
-
|
|
691
|
-
* Signs an RP request for World ID proof verification
|
|
692
|
-
*
|
|
693
|
-
* **Backend-only**: This function should ONLY be used in Node.js/server environments.
|
|
694
|
-
* Never use this in browser/client-side code as it requires access to your signing key.
|
|
695
|
-
*
|
|
696
|
-
* This function generates a cryptographic signature that authenticates your proof request.
|
|
697
|
-
* The returned signature, nonce, and timestamps should be passed as `rp_context` to the client.
|
|
698
|
-
*
|
|
699
|
-
* @param action - The action tied to the proof request
|
|
700
|
-
* @param signingKeyHex - The ECDSA private key as hex (0x-prefixed or not, 32 bytes)
|
|
701
|
-
* @param ttlSeconds - Optional time-to-live in seconds (defaults to 300 = 5 minutes)
|
|
702
|
-
* @returns RpSignature object with sig, nonce, createdAt, expiresAt to use as rp_context
|
|
703
|
-
* @throws Error if called in non-Node.js environment or if parameters are invalid
|
|
704
|
-
*
|
|
705
|
-
* @example
|
|
706
|
-
* ```typescript
|
|
707
|
-
* import { signRequest } from '@worldcoin/idkit-core'
|
|
708
|
-
*
|
|
709
|
-
* const signingKey = process.env.RP_SIGNING_KEY // Load from secure env var
|
|
710
|
-
* const signature = signRequest('my-action', signingKey)
|
|
711
|
-
* console.log(signature.sig, signature.nonce, signature.createdAt, signature.expiresAt)
|
|
712
|
-
* ```
|
|
713
|
-
*/
|
|
714
|
-
declare function signRequest(action: string, signingKeyHex: string, ttlSeconds?: number): RpSignature;
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* Hashes a Signal (string or Uint8Array) to its hash representation.
|
|
718
|
-
* This is the same hashing used internally when constructing proof requests.
|
|
719
|
-
*
|
|
720
|
-
* @param signal - The signal to hash (string or Uint8Array)
|
|
721
|
-
* @returns 0x-prefixed hex string representing the signal hash
|
|
722
|
-
*/
|
|
723
|
-
declare function hashSignal(signal: string | Uint8Array): string;
|
|
724
|
-
|
|
725
|
-
export { type AbiEncodedValue, type ConstraintNode, CredentialRequest, type CredentialRequestType, type CredentialType, type DocumentLegacyPreset, IDKit, type IDKitCompletionResult, type IDKitErrorCode, IDKitErrorCodes, type IDKitRequest, type IDKitRequestConfig, type IDKitResult, type IDKitResultSession, type IDKitSessionConfig, type OrbLegacyPreset, type Preset, type ResponseItemSession, type ResponseItemV3, type ResponseItemV4, type RpContext, RpSignature, type SecureDocumentLegacyPreset, type Status$1 as Status, type WaitOptions, all, any, documentLegacy, hashSignal, isNode, isReactNative, isWeb, orbLegacy, secureDocumentLegacy, signRequest };
|
|
631
|
+
export { type AbiEncodedValue, type ConstraintNode, CredentialRequest, type CredentialRequestType, type CredentialType, type DocumentLegacyPreset, IDKit, type IDKitCompletionResult, type IDKitErrorCode, IDKitErrorCodes, type IDKitRequest, type IDKitRequestConfig, type IDKitResult, type IDKitResultSession, type IDKitSessionConfig, type OrbLegacyPreset, type Preset, type ResponseItemSession, type ResponseItemV3, type ResponseItemV4, type RpContext, type SecureDocumentLegacyPreset, type Status$1 as Status, type WaitOptions, all, any, documentLegacy, isNode, isReactNative, isWeb, orbLegacy, secureDocumentLegacy };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
2
|
+
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
|
3
|
+
import { hmac } from '@noble/hashes/hmac';
|
|
4
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
5
|
+
import { etc, sign } from '@noble/secp256k1';
|
|
6
|
+
|
|
1
7
|
var __defProp = Object.defineProperty;
|
|
2
8
|
var __export = (target, all2) => {
|
|
3
9
|
for (var name in all2)
|
|
@@ -385,12 +391,12 @@ function hashSignal(signal) {
|
|
|
385
391
|
wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
|
|
386
392
|
}
|
|
387
393
|
}
|
|
388
|
-
function __wasm_bindgen_func_elem_960(arg0, arg1, arg2) {
|
|
389
|
-
wasm.__wasm_bindgen_func_elem_960(arg0, arg1, addHeapObject(arg2));
|
|
390
|
-
}
|
|
391
394
|
function __wasm_bindgen_func_elem_597(arg0, arg1) {
|
|
392
395
|
wasm.__wasm_bindgen_func_elem_597(arg0, arg1);
|
|
393
396
|
}
|
|
397
|
+
function __wasm_bindgen_func_elem_960(arg0, arg1, arg2) {
|
|
398
|
+
wasm.__wasm_bindgen_func_elem_960(arg0, arg1, addHeapObject(arg2));
|
|
399
|
+
}
|
|
394
400
|
function __wasm_bindgen_func_elem_1345(arg0, arg1, arg2, arg3) {
|
|
395
401
|
wasm.__wasm_bindgen_func_elem_1345(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
|
396
402
|
}
|
|
@@ -1849,10 +1855,6 @@ var idkit_wasm_default = __wbg_init;
|
|
|
1849
1855
|
// src/lib/wasm.ts
|
|
1850
1856
|
var wasmInitialized = false;
|
|
1851
1857
|
var wasmInitPromise = null;
|
|
1852
|
-
async function importNodeModule(specifier) {
|
|
1853
|
-
const dynamicImport = Function("moduleName", "return import(moduleName)");
|
|
1854
|
-
return dynamicImport(specifier);
|
|
1855
|
-
}
|
|
1856
1858
|
async function initIDKit() {
|
|
1857
1859
|
if (wasmInitialized) {
|
|
1858
1860
|
return;
|
|
@@ -1871,36 +1873,6 @@ async function initIDKit() {
|
|
|
1871
1873
|
})();
|
|
1872
1874
|
return wasmInitPromise;
|
|
1873
1875
|
}
|
|
1874
|
-
async function initIDKitServer() {
|
|
1875
|
-
if (wasmInitialized) {
|
|
1876
|
-
return;
|
|
1877
|
-
}
|
|
1878
|
-
if (wasmInitPromise) {
|
|
1879
|
-
return wasmInitPromise;
|
|
1880
|
-
}
|
|
1881
|
-
wasmInitPromise = (async () => {
|
|
1882
|
-
try {
|
|
1883
|
-
const { readFile } = await importNodeModule(
|
|
1884
|
-
"node:fs/promises"
|
|
1885
|
-
);
|
|
1886
|
-
const { fileURLToPath } = await importNodeModule(
|
|
1887
|
-
"node:url"
|
|
1888
|
-
);
|
|
1889
|
-
const { dirname, join } = await importNodeModule(
|
|
1890
|
-
"node:path"
|
|
1891
|
-
);
|
|
1892
|
-
const modulePath = fileURLToPath(import.meta.url);
|
|
1893
|
-
const wasmPath = join(dirname(modulePath), "idkit_wasm_bg.wasm");
|
|
1894
|
-
const wasmBuffer = await readFile(wasmPath);
|
|
1895
|
-
await idkit_wasm_default({ module_or_path: wasmBuffer });
|
|
1896
|
-
wasmInitialized = true;
|
|
1897
|
-
} catch (error) {
|
|
1898
|
-
wasmInitPromise = null;
|
|
1899
|
-
throw new Error(`Failed to initialize IDKit WASM for server: ${error}`);
|
|
1900
|
-
}
|
|
1901
|
-
})();
|
|
1902
|
-
return wasmInitPromise;
|
|
1903
|
-
}
|
|
1904
1876
|
|
|
1905
1877
|
// src/transports/native.ts
|
|
1906
1878
|
function isInWorldApp() {
|
|
@@ -2310,10 +2282,6 @@ function proveSession2(sessionId, config) {
|
|
|
2310
2282
|
});
|
|
2311
2283
|
}
|
|
2312
2284
|
var IDKit = {
|
|
2313
|
-
/** Initialize WASM for browser environments (not needed in World App) */
|
|
2314
|
-
init: initIDKit,
|
|
2315
|
-
/** Initialize WASM for Node.js/server environments */
|
|
2316
|
-
initServer: initIDKitServer,
|
|
2317
2285
|
/** Create a new verification request */
|
|
2318
2286
|
request: createRequest,
|
|
2319
2287
|
/** Create a new session (no action, no existing session_id) */
|
|
@@ -2356,21 +2324,71 @@ var isServerEnvironment = () => {
|
|
|
2356
2324
|
}
|
|
2357
2325
|
return false;
|
|
2358
2326
|
};
|
|
2327
|
+
function hashToField(input) {
|
|
2328
|
+
const hash = BigInt("0x" + bytesToHex(keccak_256(input))) >> 8n;
|
|
2329
|
+
return hexToBytes(hash.toString(16).padStart(64, "0"));
|
|
2330
|
+
}
|
|
2331
|
+
function hashSignal2(signal) {
|
|
2332
|
+
let input;
|
|
2333
|
+
if (signal instanceof Uint8Array) {
|
|
2334
|
+
input = signal;
|
|
2335
|
+
} else if (signal.startsWith("0x") && isValidHex(signal.slice(2))) {
|
|
2336
|
+
input = hexToBytes(signal.slice(2));
|
|
2337
|
+
} else {
|
|
2338
|
+
input = new TextEncoder().encode(signal);
|
|
2339
|
+
}
|
|
2340
|
+
return "0x" + bytesToHex(hashToField(input));
|
|
2341
|
+
}
|
|
2342
|
+
function isValidHex(s) {
|
|
2343
|
+
if (s.length === 0) return false;
|
|
2344
|
+
if (s.length % 2 !== 0) return false;
|
|
2345
|
+
return /^[0-9a-fA-F]+$/.test(s);
|
|
2346
|
+
}
|
|
2359
2347
|
|
|
2360
|
-
// src/lib/
|
|
2361
|
-
|
|
2348
|
+
// src/lib/signing.ts
|
|
2349
|
+
etc.hmacSha256Sync = (key, ...msgs) => hmac(sha256, key, etc.concatBytes(...msgs));
|
|
2350
|
+
var DEFAULT_TTL_SEC = 300;
|
|
2351
|
+
function computeRpSignatureMessage(nonceBytes, createdAt, expiresAt) {
|
|
2352
|
+
const message = new Uint8Array(48);
|
|
2353
|
+
message.set(nonceBytes, 0);
|
|
2354
|
+
const view = new DataView(message.buffer);
|
|
2355
|
+
view.setBigUint64(32, BigInt(createdAt), false);
|
|
2356
|
+
view.setBigUint64(40, BigInt(expiresAt), false);
|
|
2357
|
+
return message;
|
|
2358
|
+
}
|
|
2359
|
+
function signRequest2(_action, signingKeyHex, ttl = DEFAULT_TTL_SEC) {
|
|
2362
2360
|
if (!isServerEnvironment()) {
|
|
2363
2361
|
throw new Error(
|
|
2364
2362
|
"signRequest can only be used in Node.js environments. This function requires access to signing keys and should never be called from browser/client-side code."
|
|
2365
2363
|
);
|
|
2366
2364
|
}
|
|
2367
|
-
const
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2365
|
+
const keyHex = signingKeyHex.startsWith("0x") ? signingKeyHex.slice(2) : signingKeyHex;
|
|
2366
|
+
if (!/^[0-9a-fA-F]+$/.test(keyHex)) {
|
|
2367
|
+
throw new Error("Invalid signing key: contains non-hex characters");
|
|
2368
|
+
}
|
|
2369
|
+
if (keyHex.length !== 64) {
|
|
2370
|
+
throw new Error(
|
|
2371
|
+
`Invalid signing key: expected 32 bytes (64 hex chars), got ${keyHex.length / 2} bytes`
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
const privKey = etc.hexToBytes(keyHex);
|
|
2375
|
+
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
2376
|
+
const nonceBytes = hashToField(randomBytes);
|
|
2377
|
+
const createdAt = Math.floor(Date.now() / 1e3);
|
|
2378
|
+
const expiresAt = createdAt + ttl;
|
|
2379
|
+
const message = computeRpSignatureMessage(nonceBytes, createdAt, expiresAt);
|
|
2380
|
+
const msgHash = keccak_256(message);
|
|
2381
|
+
const recSig = sign(msgHash, privKey);
|
|
2382
|
+
const compact = recSig.toCompactRawBytes();
|
|
2383
|
+
const sig65 = new Uint8Array(65);
|
|
2384
|
+
sig65.set(compact, 0);
|
|
2385
|
+
sig65[64] = recSig.recovery + 27;
|
|
2386
|
+
return {
|
|
2387
|
+
sig: "0x" + bytesToHex(sig65),
|
|
2388
|
+
nonce: "0x" + bytesToHex(nonceBytes),
|
|
2389
|
+
createdAt,
|
|
2390
|
+
expiresAt
|
|
2391
|
+
};
|
|
2374
2392
|
}
|
|
2375
2393
|
|
|
2376
2394
|
export { CredentialRequest, IDKit, IDKitErrorCodes, all, any, documentLegacy, hashSignal2 as hashSignal, isNode, isReactNative, isWeb, orbLegacy, secureDocumentLegacy, signRequest2 as signRequest };
|
package/dist/signing.cjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var sha3 = require('@noble/hashes/sha3');
|
|
4
|
+
var utils = require('@noble/hashes/utils');
|
|
5
|
+
var hmac = require('@noble/hashes/hmac');
|
|
6
|
+
var sha2 = require('@noble/hashes/sha2');
|
|
7
|
+
var secp256k1 = require('@noble/secp256k1');
|
|
8
|
+
|
|
9
|
+
// src/lib/signing.ts
|
|
10
|
+
|
|
11
|
+
// src/lib/platform.ts
|
|
12
|
+
var isServerEnvironment = () => {
|
|
13
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (typeof globalThis.Deno !== "undefined") {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (typeof globalThis.Bun !== "undefined") {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
};
|
|
24
|
+
function hashToField(input) {
|
|
25
|
+
const hash = BigInt("0x" + utils.bytesToHex(sha3.keccak_256(input))) >> 8n;
|
|
26
|
+
return utils.hexToBytes(hash.toString(16).padStart(64, "0"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/lib/signing.ts
|
|
30
|
+
secp256k1.etc.hmacSha256Sync = (key, ...msgs) => hmac.hmac(sha2.sha256, key, secp256k1.etc.concatBytes(...msgs));
|
|
31
|
+
var DEFAULT_TTL_SEC = 300;
|
|
32
|
+
function computeRpSignatureMessage(nonceBytes, createdAt, expiresAt) {
|
|
33
|
+
const message = new Uint8Array(48);
|
|
34
|
+
message.set(nonceBytes, 0);
|
|
35
|
+
const view = new DataView(message.buffer);
|
|
36
|
+
view.setBigUint64(32, BigInt(createdAt), false);
|
|
37
|
+
view.setBigUint64(40, BigInt(expiresAt), false);
|
|
38
|
+
return message;
|
|
39
|
+
}
|
|
40
|
+
function signRequest(_action, signingKeyHex, ttl = DEFAULT_TTL_SEC) {
|
|
41
|
+
if (!isServerEnvironment()) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"signRequest can only be used in Node.js environments. This function requires access to signing keys and should never be called from browser/client-side code."
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const keyHex = signingKeyHex.startsWith("0x") ? signingKeyHex.slice(2) : signingKeyHex;
|
|
47
|
+
if (!/^[0-9a-fA-F]+$/.test(keyHex)) {
|
|
48
|
+
throw new Error("Invalid signing key: contains non-hex characters");
|
|
49
|
+
}
|
|
50
|
+
if (keyHex.length !== 64) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Invalid signing key: expected 32 bytes (64 hex chars), got ${keyHex.length / 2} bytes`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const privKey = secp256k1.etc.hexToBytes(keyHex);
|
|
56
|
+
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
57
|
+
const nonceBytes = hashToField(randomBytes);
|
|
58
|
+
const createdAt = Math.floor(Date.now() / 1e3);
|
|
59
|
+
const expiresAt = createdAt + ttl;
|
|
60
|
+
const message = computeRpSignatureMessage(nonceBytes, createdAt, expiresAt);
|
|
61
|
+
const msgHash = sha3.keccak_256(message);
|
|
62
|
+
const recSig = secp256k1.sign(msgHash, privKey);
|
|
63
|
+
const compact = recSig.toCompactRawBytes();
|
|
64
|
+
const sig65 = new Uint8Array(65);
|
|
65
|
+
sig65.set(compact, 0);
|
|
66
|
+
sig65[64] = recSig.recovery + 27;
|
|
67
|
+
return {
|
|
68
|
+
sig: "0x" + utils.bytesToHex(sig65),
|
|
69
|
+
nonce: "0x" + utils.bytesToHex(nonceBytes),
|
|
70
|
+
createdAt,
|
|
71
|
+
expiresAt
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
exports.computeRpSignatureMessage = computeRpSignatureMessage;
|
|
76
|
+
exports.signRequest = signRequest;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
interface RpSignature {
|
|
2
|
+
sig: string;
|
|
3
|
+
nonce: string;
|
|
4
|
+
createdAt: number;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Builds the 48-byte message that gets signed for RP signature verification.
|
|
9
|
+
*
|
|
10
|
+
* Message format: nonce(32) || createdAt_u64_be(8) || expiresAt_u64_be(8)
|
|
11
|
+
*
|
|
12
|
+
* Matches Rust `compute_rp_signature_msg`:
|
|
13
|
+
* https://github.com/worldcoin/world-id-protocol/blob/0008eab1efe200e572f27258793f9be5cb32858b/crates/primitives/src/rp.rs#L95-L105
|
|
14
|
+
*
|
|
15
|
+
* @param nonceBytes - 32-byte nonce as Uint8Array
|
|
16
|
+
* @param createdAt - unix timestamp in seconds
|
|
17
|
+
* @param expiresAt - unix timestamp in seconds
|
|
18
|
+
* @returns 48-byte message ready to be hashed and signed
|
|
19
|
+
*/
|
|
20
|
+
declare function computeRpSignatureMessage(nonceBytes: Uint8Array, createdAt: number, expiresAt: number): Uint8Array;
|
|
21
|
+
/**
|
|
22
|
+
* Signs an RP request using pure JS (no WASM required).
|
|
23
|
+
*
|
|
24
|
+
* Algorithm matches Rust implementation in rust/core/src/rp_signature.rs
|
|
25
|
+
*
|
|
26
|
+
* Nonce generation matches `from_arbitrary_raw_bytes`:
|
|
27
|
+
* https://github.com/worldcoin/world-id-protocol/blob/31405df8bcd5a2784e04ad9890cf095111dcac13/crates/primitives/src/lib.rs#L134-L149
|
|
28
|
+
*
|
|
29
|
+
* @param action - The action tied to the proof request (accepted for API compat, not used in signature)
|
|
30
|
+
* @param signingKeyHex - The ECDSA private key as hex (0x-prefixed or not, 32 bytes)
|
|
31
|
+
* @param ttl - Time-to-live in seconds (defaults to 300 = 5 minutes)
|
|
32
|
+
* @returns RpSignature object with sig, nonce, createdAt, expiresAt
|
|
33
|
+
*/
|
|
34
|
+
declare function signRequest(_action: string, signingKeyHex: string, ttl?: number): RpSignature;
|
|
35
|
+
|
|
36
|
+
export { type RpSignature, computeRpSignatureMessage, signRequest };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
interface RpSignature {
|
|
2
|
+
sig: string;
|
|
3
|
+
nonce: string;
|
|
4
|
+
createdAt: number;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Builds the 48-byte message that gets signed for RP signature verification.
|
|
9
|
+
*
|
|
10
|
+
* Message format: nonce(32) || createdAt_u64_be(8) || expiresAt_u64_be(8)
|
|
11
|
+
*
|
|
12
|
+
* Matches Rust `compute_rp_signature_msg`:
|
|
13
|
+
* https://github.com/worldcoin/world-id-protocol/blob/0008eab1efe200e572f27258793f9be5cb32858b/crates/primitives/src/rp.rs#L95-L105
|
|
14
|
+
*
|
|
15
|
+
* @param nonceBytes - 32-byte nonce as Uint8Array
|
|
16
|
+
* @param createdAt - unix timestamp in seconds
|
|
17
|
+
* @param expiresAt - unix timestamp in seconds
|
|
18
|
+
* @returns 48-byte message ready to be hashed and signed
|
|
19
|
+
*/
|
|
20
|
+
declare function computeRpSignatureMessage(nonceBytes: Uint8Array, createdAt: number, expiresAt: number): Uint8Array;
|
|
21
|
+
/**
|
|
22
|
+
* Signs an RP request using pure JS (no WASM required).
|
|
23
|
+
*
|
|
24
|
+
* Algorithm matches Rust implementation in rust/core/src/rp_signature.rs
|
|
25
|
+
*
|
|
26
|
+
* Nonce generation matches `from_arbitrary_raw_bytes`:
|
|
27
|
+
* https://github.com/worldcoin/world-id-protocol/blob/31405df8bcd5a2784e04ad9890cf095111dcac13/crates/primitives/src/lib.rs#L134-L149
|
|
28
|
+
*
|
|
29
|
+
* @param action - The action tied to the proof request (accepted for API compat, not used in signature)
|
|
30
|
+
* @param signingKeyHex - The ECDSA private key as hex (0x-prefixed or not, 32 bytes)
|
|
31
|
+
* @param ttl - Time-to-live in seconds (defaults to 300 = 5 minutes)
|
|
32
|
+
* @returns RpSignature object with sig, nonce, createdAt, expiresAt
|
|
33
|
+
*/
|
|
34
|
+
declare function signRequest(_action: string, signingKeyHex: string, ttl?: number): RpSignature;
|
|
35
|
+
|
|
36
|
+
export { type RpSignature, computeRpSignatureMessage, signRequest };
|
package/dist/signing.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
2
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
3
|
+
import { hmac } from '@noble/hashes/hmac';
|
|
4
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
5
|
+
import { etc, sign } from '@noble/secp256k1';
|
|
6
|
+
|
|
7
|
+
// src/lib/signing.ts
|
|
8
|
+
|
|
9
|
+
// src/lib/platform.ts
|
|
10
|
+
var isServerEnvironment = () => {
|
|
11
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (typeof globalThis.Deno !== "undefined") {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (typeof globalThis.Bun !== "undefined") {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
};
|
|
22
|
+
function hashToField(input) {
|
|
23
|
+
const hash = BigInt("0x" + bytesToHex(keccak_256(input))) >> 8n;
|
|
24
|
+
return hexToBytes(hash.toString(16).padStart(64, "0"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/lib/signing.ts
|
|
28
|
+
etc.hmacSha256Sync = (key, ...msgs) => hmac(sha256, key, etc.concatBytes(...msgs));
|
|
29
|
+
var DEFAULT_TTL_SEC = 300;
|
|
30
|
+
function computeRpSignatureMessage(nonceBytes, createdAt, expiresAt) {
|
|
31
|
+
const message = new Uint8Array(48);
|
|
32
|
+
message.set(nonceBytes, 0);
|
|
33
|
+
const view = new DataView(message.buffer);
|
|
34
|
+
view.setBigUint64(32, BigInt(createdAt), false);
|
|
35
|
+
view.setBigUint64(40, BigInt(expiresAt), false);
|
|
36
|
+
return message;
|
|
37
|
+
}
|
|
38
|
+
function signRequest(_action, signingKeyHex, ttl = DEFAULT_TTL_SEC) {
|
|
39
|
+
if (!isServerEnvironment()) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"signRequest can only be used in Node.js environments. This function requires access to signing keys and should never be called from browser/client-side code."
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const keyHex = signingKeyHex.startsWith("0x") ? signingKeyHex.slice(2) : signingKeyHex;
|
|
45
|
+
if (!/^[0-9a-fA-F]+$/.test(keyHex)) {
|
|
46
|
+
throw new Error("Invalid signing key: contains non-hex characters");
|
|
47
|
+
}
|
|
48
|
+
if (keyHex.length !== 64) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Invalid signing key: expected 32 bytes (64 hex chars), got ${keyHex.length / 2} bytes`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const privKey = etc.hexToBytes(keyHex);
|
|
54
|
+
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
55
|
+
const nonceBytes = hashToField(randomBytes);
|
|
56
|
+
const createdAt = Math.floor(Date.now() / 1e3);
|
|
57
|
+
const expiresAt = createdAt + ttl;
|
|
58
|
+
const message = computeRpSignatureMessage(nonceBytes, createdAt, expiresAt);
|
|
59
|
+
const msgHash = keccak_256(message);
|
|
60
|
+
const recSig = sign(msgHash, privKey);
|
|
61
|
+
const compact = recSig.toCompactRawBytes();
|
|
62
|
+
const sig65 = new Uint8Array(65);
|
|
63
|
+
sig65.set(compact, 0);
|
|
64
|
+
sig65[64] = recSig.recovery + 27;
|
|
65
|
+
return {
|
|
66
|
+
sig: "0x" + bytesToHex(sig65),
|
|
67
|
+
nonce: "0x" + bytesToHex(nonceBytes),
|
|
68
|
+
createdAt,
|
|
69
|
+
expiresAt
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { computeRpSignatureMessage, signRequest };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@worldcoin/idkit-core",
|
|
3
|
-
"version": "4.0.1-dev.
|
|
3
|
+
"version": "4.0.1-dev.370b7c0",
|
|
4
4
|
"description": "Core IDKit SDK for World ID - Pure TypeScript, no dependencies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./signing": {
|
|
16
|
+
"types": "./dist/signing.d.ts",
|
|
17
|
+
"import": "./dist/signing.js",
|
|
18
|
+
"require": "./dist/signing.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./hashing": {
|
|
21
|
+
"types": "./dist/hashing.d.ts",
|
|
22
|
+
"import": "./dist/hashing.js",
|
|
23
|
+
"require": "./dist/hashing.cjs"
|
|
14
24
|
}
|
|
15
25
|
},
|
|
16
26
|
"files": [
|
|
@@ -40,6 +50,10 @@
|
|
|
40
50
|
"type": "git",
|
|
41
51
|
"url": "https://github.com/worldcoin/idkit"
|
|
42
52
|
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@noble/hashes": "^1.7.2",
|
|
55
|
+
"@noble/secp256k1": "^2.2.3"
|
|
56
|
+
},
|
|
43
57
|
"devDependencies": {
|
|
44
58
|
"@types/node": "^20.19.30",
|
|
45
59
|
"@vitest/coverage-v8": "^4.0.18",
|