@oleary-labs/signet-sdk 0.1.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/admin.d.ts +38 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/admin.js +112 -0
- package/dist/admin.js.map +1 -0
- package/dist/authkey-session.d.ts +64 -0
- package/dist/authkey-session.d.ts.map +1 -0
- package/dist/authkey-session.js +164 -0
- package/dist/authkey-session.js.map +1 -0
- package/dist/bootstrap.d.ts +30 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +60 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/bundler.d.ts +85 -0
- package/dist/bundler.d.ts.map +1 -0
- package/dist/bundler.js +160 -0
- package/dist/bundler.js.map +1 -0
- package/dist/delegate.d.ts +57 -0
- package/dist/delegate.d.ts.map +1 -0
- package/dist/delegate.js +111 -0
- package/dist/delegate.js.map +1 -0
- package/dist/frostVerify.d.ts +23 -0
- package/dist/frostVerify.d.ts.map +1 -0
- package/dist/frostVerify.js +69 -0
- package/dist/frostVerify.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/jwks.d.ts +28 -0
- package/dist/jwks.d.ts.map +1 -0
- package/dist/jwks.js +81 -0
- package/dist/jwks.js.map +1 -0
- package/dist/jwt.d.ts +27 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +50 -0
- package/dist/jwt.js.map +1 -0
- package/dist/keygen.d.ts +26 -0
- package/dist/keygen.d.ts.map +1 -0
- package/dist/keygen.js +60 -0
- package/dist/keygen.js.map +1 -0
- package/dist/oauth.d.ts +34 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +119 -0
- package/dist/oauth.js.map +1 -0
- package/dist/request.d.ts +42 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +115 -0
- package/dist/request.js.map +1 -0
- package/dist/scopedSign.d.ts +82 -0
- package/dist/scopedSign.d.ts.map +1 -0
- package/dist/scopedSign.js +130 -0
- package/dist/scopedSign.js.map +1 -0
- package/dist/server-prover.d.ts +29 -0
- package/dist/server-prover.d.ts.map +1 -0
- package/dist/server-prover.js +54 -0
- package/dist/server-prover.js.map +1 -0
- package/dist/session.d.ts +14 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +29 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/userop.d.ts +104 -0
- package/dist/userop.d.ts.map +1 -0
- package/dist/userop.js +212 -0
- package/dist/userop.js.map +1 -0
- package/dist/x402.d.ts +127 -0
- package/dist/x402.d.ts.map +1 -0
- package/dist/x402.js +167 -0
- package/dist/x402.js.map +1 -0
- package/package.json +64 -0
- package/src/admin.ts +178 -0
- package/src/authkey-session.ts +241 -0
- package/src/bootstrap.ts +106 -0
- package/src/bundler.ts +256 -0
- package/src/delegate.ts +163 -0
- package/src/frostVerify.ts +79 -0
- package/src/generate-inputs.ts +158 -0
- package/src/index.ts +43 -0
- package/src/jwks.ts +92 -0
- package/src/jwt.ts +74 -0
- package/src/keygen.ts +89 -0
- package/src/oauth.ts +157 -0
- package/src/partial-sha.ts +99 -0
- package/src/proof.ts +99 -0
- package/src/request.ts +174 -0
- package/src/scopedSign.ts +184 -0
- package/src/server-prover.ts +76 -0
- package/src/session.ts +33 -0
- package/src/types.ts +63 -0
- package/src/userop.ts +368 -0
- package/src/witness.ts +132 -0
- package/src/x402.ts +275 -0
package/src/request.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-authenticated request signing.
|
|
3
|
+
*
|
|
4
|
+
* After auth, every keygen/sign request must include a signature
|
|
5
|
+
* over the canonical request hash, produced with the session private key.
|
|
6
|
+
*
|
|
7
|
+
* Canonical hash: SHA256(groupID ":" keyID ":" nonce ":" timestamp_8BE [":" messageHash])
|
|
8
|
+
* Signature: 64-byte [R || S] secp256k1 ECDSA (no recovery byte)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SessionKeypair, IdTokenClaims } from "./types";
|
|
12
|
+
import { bytesToHex } from "./session";
|
|
13
|
+
|
|
14
|
+
/** Signed request ready to POST to a node. */
|
|
15
|
+
export interface SignedRequest {
|
|
16
|
+
group_id: string;
|
|
17
|
+
key_suffix?: string;
|
|
18
|
+
session_pub: string;
|
|
19
|
+
request_sig: string;
|
|
20
|
+
nonce: string;
|
|
21
|
+
timestamp: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Signed request with message_hash for /v1/sign. */
|
|
25
|
+
export interface SignedSignRequest extends SignedRequest {
|
|
26
|
+
message_hash: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Derive the key ID that the node will resolve for this session.
|
|
31
|
+
*
|
|
32
|
+
* For OAuth sessions: iss:sub or iss:sub:suffix
|
|
33
|
+
* e.g. https://accounts.google.com:114810956681671373980
|
|
34
|
+
*/
|
|
35
|
+
export function deriveKeyId(claims: IdTokenClaims, keySuffix?: string, identity?: string): string {
|
|
36
|
+
const base = identity ?? `${claims.iss}:${claims.sub}`;
|
|
37
|
+
return keySuffix ? `${base}:${keySuffix}` : base;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build and sign a keygen request.
|
|
42
|
+
*
|
|
43
|
+
* @param identity - For auth key cert sessions, pass the identity string.
|
|
44
|
+
* The key ID becomes `identity[:suffix]` instead of `iss:sub[:suffix]`.
|
|
45
|
+
*/
|
|
46
|
+
export async function signKeygenRequest(
|
|
47
|
+
keypair: SessionKeypair,
|
|
48
|
+
claims: IdTokenClaims,
|
|
49
|
+
groupId: string,
|
|
50
|
+
keySuffix?: string,
|
|
51
|
+
identity?: string,
|
|
52
|
+
): Promise<SignedRequest> {
|
|
53
|
+
const normalizedGroupId = groupId.toLowerCase();
|
|
54
|
+
const keyId = deriveKeyId(claims, keySuffix, identity);
|
|
55
|
+
const nonce = generateNonce();
|
|
56
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
57
|
+
|
|
58
|
+
const hash = await canonicalRequestHash(normalizedGroupId, keyId, nonce, timestamp);
|
|
59
|
+
const sig = await signHash(keypair.privateKey, hash);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
group_id: normalizedGroupId,
|
|
63
|
+
key_suffix: keySuffix,
|
|
64
|
+
session_pub: keypair.publicKeyHex,
|
|
65
|
+
request_sig: bytesToHex(sig),
|
|
66
|
+
nonce,
|
|
67
|
+
timestamp,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build and sign a threshold signing request.
|
|
73
|
+
*/
|
|
74
|
+
export async function signSignRequest(
|
|
75
|
+
keypair: SessionKeypair,
|
|
76
|
+
claims: IdTokenClaims,
|
|
77
|
+
groupId: string,
|
|
78
|
+
messageHash: Uint8Array,
|
|
79
|
+
keySuffix?: string,
|
|
80
|
+
identity?: string,
|
|
81
|
+
): Promise<SignedSignRequest> {
|
|
82
|
+
const normalizedGroupId = groupId.toLowerCase();
|
|
83
|
+
const keyId = deriveKeyId(claims, keySuffix, identity);
|
|
84
|
+
const nonce = generateNonce();
|
|
85
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
86
|
+
|
|
87
|
+
const hash = await canonicalRequestHash(
|
|
88
|
+
normalizedGroupId,
|
|
89
|
+
keyId,
|
|
90
|
+
nonce,
|
|
91
|
+
timestamp,
|
|
92
|
+
messageHash
|
|
93
|
+
);
|
|
94
|
+
const sig = await signHash(keypair.privateKey, hash);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
group_id: normalizedGroupId,
|
|
98
|
+
key_suffix: keySuffix,
|
|
99
|
+
session_pub: keypair.publicKeyHex,
|
|
100
|
+
request_sig: bytesToHex(sig),
|
|
101
|
+
nonce,
|
|
102
|
+
timestamp,
|
|
103
|
+
message_hash: bytesToHex(messageHash),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Compute the canonical request hash matching the Go node's format.
|
|
109
|
+
*
|
|
110
|
+
* SHA256(groupID ":" keyID ":" nonce ":" timestamp_8BE [":" messageHash])
|
|
111
|
+
*/
|
|
112
|
+
async function canonicalRequestHash(
|
|
113
|
+
groupId: string,
|
|
114
|
+
keyId: string,
|
|
115
|
+
nonce: string,
|
|
116
|
+
timestamp: number,
|
|
117
|
+
messageHash?: Uint8Array
|
|
118
|
+
): Promise<Uint8Array> {
|
|
119
|
+
const parts: Uint8Array[] = [];
|
|
120
|
+
const enc = new TextEncoder();
|
|
121
|
+
|
|
122
|
+
parts.push(enc.encode(groupId));
|
|
123
|
+
parts.push(enc.encode(":"));
|
|
124
|
+
parts.push(enc.encode(keyId));
|
|
125
|
+
parts.push(enc.encode(":"));
|
|
126
|
+
parts.push(enc.encode(nonce));
|
|
127
|
+
parts.push(enc.encode(":"));
|
|
128
|
+
|
|
129
|
+
// timestamp as 8-byte big-endian
|
|
130
|
+
const tsBuf = new ArrayBuffer(8);
|
|
131
|
+
const view = new DataView(tsBuf);
|
|
132
|
+
view.setBigUint64(0, BigInt(timestamp));
|
|
133
|
+
parts.push(new Uint8Array(tsBuf));
|
|
134
|
+
|
|
135
|
+
if (messageHash && messageHash.length > 0) {
|
|
136
|
+
parts.push(enc.encode(":"));
|
|
137
|
+
parts.push(messageHash);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Concatenate
|
|
141
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
142
|
+
const buf = new Uint8Array(total);
|
|
143
|
+
let offset = 0;
|
|
144
|
+
for (const p of parts) {
|
|
145
|
+
buf.set(p, offset);
|
|
146
|
+
offset += p.length;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const digest = await crypto.subtle.digest("SHA-256", buf);
|
|
150
|
+
return new Uint8Array(digest);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Sign a 32-byte hash with the session private key.
|
|
155
|
+
* Returns 64-byte [R || S] signature (no recovery byte).
|
|
156
|
+
*
|
|
157
|
+
* Uses signAsync which works without configuring hashes.sha256.
|
|
158
|
+
* lowS: true to match go-ethereum's crypto.VerifySignature.
|
|
159
|
+
*/
|
|
160
|
+
async function signHash(
|
|
161
|
+
privateKey: Uint8Array,
|
|
162
|
+
hash: Uint8Array
|
|
163
|
+
): Promise<Uint8Array> {
|
|
164
|
+
const { signAsync } = await import("@noble/secp256k1");
|
|
165
|
+
// prehash: false — our input is already SHA-256'd, don't hash again
|
|
166
|
+
const sig = await signAsync(hash, privateKey, { lowS: true, prehash: false });
|
|
167
|
+
return new Uint8Array(sig);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function generateNonce(): string {
|
|
171
|
+
const bytes = new Uint8Array(16);
|
|
172
|
+
crypto.getRandomValues(bytes);
|
|
173
|
+
return bytesToHex(bytes);
|
|
174
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured EIP-712 signing for scoped keys.
|
|
3
|
+
*
|
|
4
|
+
* Scoped keys reject raw hash signing — the caller must provide a
|
|
5
|
+
* structured payload that the node verifies against the key's scope
|
|
6
|
+
* before computing the hash and signing.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SessionKeypair, IdTokenClaims } from "./types";
|
|
10
|
+
import { signKeygenRequest } from "./request";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Types
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export interface EIP712Domain {
|
|
17
|
+
name?: string;
|
|
18
|
+
version?: string;
|
|
19
|
+
chainId: number;
|
|
20
|
+
verifyingContract: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface EIP712TypedData {
|
|
24
|
+
domain: EIP712Domain;
|
|
25
|
+
types: Record<string, Array<{ name: string; type: string }>>;
|
|
26
|
+
primaryType: string;
|
|
27
|
+
message: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ScopedSignResult {
|
|
31
|
+
signature: string; // raw signature hex
|
|
32
|
+
ecdsaSignature: string; // ECDSA-formatted (r, s, v)
|
|
33
|
+
curve: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Scope construction
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build an EIP-712 domain scope (scheme 0x03).
|
|
42
|
+
*
|
|
43
|
+
* Format: 0x03 | chainId (8 bytes, uint64 BE) | verifyingContract (20 bytes)
|
|
44
|
+
* Total: 29 bytes.
|
|
45
|
+
*/
|
|
46
|
+
export function buildEIP712Scope(chainId: number, verifyingContract: string): string {
|
|
47
|
+
const buf = new Uint8Array(29);
|
|
48
|
+
buf[0] = 0x03;
|
|
49
|
+
|
|
50
|
+
// chainId as 8-byte big-endian
|
|
51
|
+
const view = new DataView(buf.buffer);
|
|
52
|
+
view.setBigUint64(1, BigInt(chainId));
|
|
53
|
+
|
|
54
|
+
// verifyingContract as 20 bytes
|
|
55
|
+
const addr = verifyingContract.startsWith("0x")
|
|
56
|
+
? verifyingContract.slice(2)
|
|
57
|
+
: verifyingContract;
|
|
58
|
+
for (let i = 0; i < 20; i++) {
|
|
59
|
+
buf[9 + i] = parseInt(addr.slice(i * 2, i * 2 + 2), 16);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return "0x" + Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Structured signing
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sign a structured EIP-712 payload with a scoped key.
|
|
71
|
+
*
|
|
72
|
+
* The node extracts the domain from the typed data, verifies it matches
|
|
73
|
+
* the key's scope, computes hashTypedData, and threshold-signs.
|
|
74
|
+
*
|
|
75
|
+
* @param nodeUrl - Target group node URL
|
|
76
|
+
* @param proxyEndpoint - CORS proxy URL
|
|
77
|
+
* @param groupId - Group contract address
|
|
78
|
+
* @param keyId - The scoped sub-key to sign with
|
|
79
|
+
* @param curve - Key curve (e.g. "ecdsa_secp256k1")
|
|
80
|
+
* @param typedData - Full EIP-712 typed data structure
|
|
81
|
+
* @param sessionKeypair - Active session keypair
|
|
82
|
+
* @param claims - OAuth/identity claims for session auth
|
|
83
|
+
* @param identity - For auth key cert sessions
|
|
84
|
+
*/
|
|
85
|
+
export async function signTypedData(
|
|
86
|
+
nodeUrl: string,
|
|
87
|
+
proxyEndpoint: string,
|
|
88
|
+
groupId: string,
|
|
89
|
+
keyId: string,
|
|
90
|
+
curve: string,
|
|
91
|
+
typedData: EIP712TypedData,
|
|
92
|
+
sessionKeypair: SessionKeypair,
|
|
93
|
+
claims: IdTokenClaims,
|
|
94
|
+
identity?: string,
|
|
95
|
+
): Promise<ScopedSignResult> {
|
|
96
|
+
// Build session-authenticated request (no message hash — payload is sent separately)
|
|
97
|
+
// The canonical hash must use the full sub-key ID (identity + suffix).
|
|
98
|
+
// Extract suffix from keyId: "oauth:iss:sub:suffix" → suffix is last segment
|
|
99
|
+
// The identity param is "iss:sub", so we need to add the suffix.
|
|
100
|
+
const keyParts = keyId.split(":");
|
|
101
|
+
const keySuffix = keyParts.length > 1 ? keyParts[keyParts.length - 1] : undefined;
|
|
102
|
+
|
|
103
|
+
const signReq = await signKeygenRequest(
|
|
104
|
+
sessionKeypair,
|
|
105
|
+
claims,
|
|
106
|
+
groupId,
|
|
107
|
+
keySuffix,
|
|
108
|
+
identity,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const res = await fetch(proxyEndpoint, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: {
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
"x-node-url": nodeUrl,
|
|
116
|
+
"x-node-path": "/v1/sign",
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
group_id: groupId.toLowerCase(),
|
|
120
|
+
key_id: keyId,
|
|
121
|
+
key_suffix: keySuffix,
|
|
122
|
+
curve,
|
|
123
|
+
payload: {
|
|
124
|
+
scheme: "eip712",
|
|
125
|
+
typed_data: typedData,
|
|
126
|
+
},
|
|
127
|
+
session_pub: signReq.session_pub,
|
|
128
|
+
request_sig: signReq.request_sig,
|
|
129
|
+
nonce: signReq.nonce,
|
|
130
|
+
timestamp: signReq.timestamp,
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
const body = await res.text();
|
|
136
|
+
throw new Error(`Scoped sign failed: ${res.status} — ${body}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const data = await res.json();
|
|
140
|
+
return {
|
|
141
|
+
signature: data.signature,
|
|
142
|
+
ecdsaSignature: data.ecdsa_signature,
|
|
143
|
+
curve: data.curve ?? curve,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Presets
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
export const CHAIN_PRESETS = [
|
|
152
|
+
{
|
|
153
|
+
label: "USDC on Base",
|
|
154
|
+
chainId: 8453,
|
|
155
|
+
contractName: "USDC",
|
|
156
|
+
verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
157
|
+
eip712Name: "USD Coin",
|
|
158
|
+
eip712Version: "2",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
label: "USDC on Base Sepolia",
|
|
162
|
+
chainId: 84532,
|
|
163
|
+
contractName: "USDC",
|
|
164
|
+
verifyingContract: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
165
|
+
eip712Name: "USD Coin",
|
|
166
|
+
eip712Version: "2",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
label: "USDC on Ethereum",
|
|
170
|
+
chainId: 1,
|
|
171
|
+
contractName: "USDC",
|
|
172
|
+
verifyingContract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
173
|
+
eip712Name: "USD Coin",
|
|
174
|
+
eip712Version: "2",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
label: "USDC on Sepolia",
|
|
178
|
+
chainId: 11155111,
|
|
179
|
+
contractName: "USDC",
|
|
180
|
+
verifyingContract: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
181
|
+
eip712Name: "USD Coin",
|
|
182
|
+
eip712Version: "2",
|
|
183
|
+
},
|
|
184
|
+
] as const;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side ZK proof generation via the bundler's /v1/prove endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Delegates JWT proof generation to the bundler instead of running
|
|
5
|
+
* noir + bb.js client-side via WASM. Faster (~2-3s vs 2-7s) and
|
|
6
|
+
* avoids shipping heavy WASM binaries to the browser.
|
|
7
|
+
*
|
|
8
|
+
* The returned proof + claims + modulus are everything needed to
|
|
9
|
+
* call authenticateWithBootstrap.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface ServerProofResult {
|
|
13
|
+
proof: Uint8Array;
|
|
14
|
+
sub: string;
|
|
15
|
+
iss: string;
|
|
16
|
+
exp: number;
|
|
17
|
+
aud: string;
|
|
18
|
+
azp: string;
|
|
19
|
+
jwksModulus: Uint8Array;
|
|
20
|
+
sessionPub: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate a ZK proof of a JWT via the bundler's server-side prover.
|
|
25
|
+
*
|
|
26
|
+
* @param bundlerProxyUrl - URL of the bundler proxy (e.g. "/api/bundler")
|
|
27
|
+
* @param jwt - Raw JWT from OAuth provider
|
|
28
|
+
* @param sessionPubHex - 33-byte compressed secp256k1 public key, hex-encoded
|
|
29
|
+
*/
|
|
30
|
+
export async function generateServerProof(
|
|
31
|
+
bundlerProxyUrl: string,
|
|
32
|
+
jwt: string,
|
|
33
|
+
sessionPubHex: string,
|
|
34
|
+
apiKey?: string,
|
|
35
|
+
): Promise<ServerProofResult> {
|
|
36
|
+
const headers: Record<string, string> = {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"x-bundler-path": "/v1/prove",
|
|
39
|
+
};
|
|
40
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
41
|
+
|
|
42
|
+
const res = await fetch(bundlerProxyUrl, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers,
|
|
45
|
+
body: JSON.stringify({ jwt, session_pub: sessionPubHex }),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const result = await res.json();
|
|
49
|
+
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const msg = result.error ?? JSON.stringify(result);
|
|
52
|
+
throw new Error(`Server proof generation failed: ${res.status} — ${msg}`);
|
|
53
|
+
}
|
|
54
|
+
if (result.error) {
|
|
55
|
+
const msg = typeof result.error === "string" ? result.error : JSON.stringify(result.error);
|
|
56
|
+
throw new Error(`Server proof generation failed: ${msg}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
proof: hexToBytes(result.proof),
|
|
61
|
+
sub: result.sub,
|
|
62
|
+
iss: result.iss,
|
|
63
|
+
exp: result.exp,
|
|
64
|
+
aud: result.aud,
|
|
65
|
+
azp: result.azp,
|
|
66
|
+
jwksModulus: hexToBytes(result.jwks_modulus),
|
|
67
|
+
sessionPub: result.session_pub,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hexToBytes(hex: string): Uint8Array {
|
|
72
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
73
|
+
return new Uint8Array(
|
|
74
|
+
(clean.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16))
|
|
75
|
+
);
|
|
76
|
+
}
|
package/src/session.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session keypair management (secp256k1).
|
|
3
|
+
*
|
|
4
|
+
* Generates an ephemeral keypair for authenticating with bootstrap nodes.
|
|
5
|
+
* The private key is held in memory only — never persisted.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SessionKeypair } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate a new ephemeral secp256k1 session keypair.
|
|
12
|
+
*/
|
|
13
|
+
export async function generateSessionKeypair(): Promise<SessionKeypair> {
|
|
14
|
+
const { utils, getPublicKey } = await import("@noble/secp256k1");
|
|
15
|
+
const privateKey = utils.randomSecretKey();
|
|
16
|
+
const publicKeyBytes = getPublicKey(privateKey, true); // compressed
|
|
17
|
+
const publicKeyHex = bytesToHex(publicKeyBytes);
|
|
18
|
+
return { privateKey, publicKeyHex };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
22
|
+
return Array.from(bytes)
|
|
23
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
24
|
+
.join("");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function hexToBytes(hex: string): Uint8Array {
|
|
28
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
29
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
30
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
31
|
+
}
|
|
32
|
+
return bytes;
|
|
33
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the Signet SDK.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Decoded Google ID token claims. */
|
|
6
|
+
export interface IdTokenClaims {
|
|
7
|
+
iss: string; // "https://accounts.google.com"
|
|
8
|
+
sub: string; // stable numeric user ID
|
|
9
|
+
email: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
picture?: string;
|
|
12
|
+
azp: string; // OAuth client ID
|
|
13
|
+
aud: string;
|
|
14
|
+
exp: number; // unix timestamp
|
|
15
|
+
iat: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** RSA public key from JWKS. */
|
|
19
|
+
export interface JWKSKey {
|
|
20
|
+
kid: string;
|
|
21
|
+
kty: string;
|
|
22
|
+
alg: string;
|
|
23
|
+
n: string; // base64url-encoded modulus
|
|
24
|
+
e: string; // base64url-encoded exponent
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Session keypair (secp256k1). */
|
|
28
|
+
export interface SessionKeypair {
|
|
29
|
+
privateKey: Uint8Array;
|
|
30
|
+
publicKeyHex: string; // 33-byte compressed, hex-encoded
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Witness inputs for the jwt_auth noir circuit. */
|
|
34
|
+
export interface CircuitWitness {
|
|
35
|
+
// Private witness
|
|
36
|
+
data: number[]; // JWT signed data bytes, padded to 1024
|
|
37
|
+
dataLen: number; // actual length of signed data
|
|
38
|
+
base64DecodeOffset: number; // header length + 1
|
|
39
|
+
redcParamsLimbs: string[]; // 18 limbs, decimal strings
|
|
40
|
+
signatureLimbs: string[]; // 18 limbs, decimal strings
|
|
41
|
+
|
|
42
|
+
// Public inputs
|
|
43
|
+
pubkeyModulusLimbs: string[]; // 18 limbs, decimal strings
|
|
44
|
+
iss: string;
|
|
45
|
+
sub: string;
|
|
46
|
+
exp: number;
|
|
47
|
+
aud: string;
|
|
48
|
+
azp: string;
|
|
49
|
+
sessionPub: number[]; // 33 bytes
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Auth request body for bootstrap node /v1/auth (OAuth/ZK path). */
|
|
53
|
+
export interface NodeAuthRequest {
|
|
54
|
+
group_id: string;
|
|
55
|
+
session_pub: string; // hex
|
|
56
|
+
proof: string; // hex
|
|
57
|
+
sub: string;
|
|
58
|
+
iss: string;
|
|
59
|
+
exp: number;
|
|
60
|
+
aud: string;
|
|
61
|
+
azp: string;
|
|
62
|
+
jwks_modulus: string; // hex
|
|
63
|
+
}
|