@worldcoin/idkit-server 1.0.0
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.cjs +76 -0
- package/dist/index.d.cts +36 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +73 -0
- package/package.json +56 -0
package/dist/index.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
|
+
|
|
25
|
+
// src/lib/signing.ts
|
|
26
|
+
secp256k1.etc.hmacSha256Sync = (key, ...msgs) => hmac.hmac(sha2.sha256, key, secp256k1.etc.concatBytes(...msgs));
|
|
27
|
+
var DEFAULT_TTL_SEC = 300;
|
|
28
|
+
function hashToField(input) {
|
|
29
|
+
const hash = BigInt("0x" + utils.bytesToHex(sha3.keccak_256(input))) >> 8n;
|
|
30
|
+
return utils.hexToBytes(hash.toString(16).padStart(64, "0"));
|
|
31
|
+
}
|
|
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;
|
package/dist/index.d.cts
ADDED
|
@@ -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/index.d.ts
ADDED
|
@@ -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/index.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
|
+
|
|
23
|
+
// src/lib/signing.ts
|
|
24
|
+
etc.hmacSha256Sync = (key, ...msgs) => hmac(sha256, key, etc.concatBytes(...msgs));
|
|
25
|
+
var DEFAULT_TTL_SEC = 300;
|
|
26
|
+
function hashToField(input) {
|
|
27
|
+
const hash = BigInt("0x" + bytesToHex(keccak_256(input))) >> 8n;
|
|
28
|
+
return hexToBytes(hash.toString(16).padStart(64, "0"));
|
|
29
|
+
}
|
|
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
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@worldcoin/idkit-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Server-side IDKit SDK for World ID - RP signing utilities",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"type-check": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"test:coverage": "vitest run --coverage"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"worldcoin",
|
|
29
|
+
"world-id",
|
|
30
|
+
"idkit",
|
|
31
|
+
"identity",
|
|
32
|
+
"proof-of-personhood",
|
|
33
|
+
"zero-knowledge",
|
|
34
|
+
"server"
|
|
35
|
+
],
|
|
36
|
+
"author": "Tools for Humanity",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/worldcoin/idkit"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@noble/hashes": "^1.7.2",
|
|
47
|
+
"@noble/secp256k1": "^2.2.3"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^20.19.30",
|
|
51
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
52
|
+
"tsup": "^8.5.1",
|
|
53
|
+
"typescript": "^5.9.3",
|
|
54
|
+
"vitest": "^4.0.18"
|
|
55
|
+
}
|
|
56
|
+
}
|