@bolyra/sdk 0.2.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/README.md +76 -0
- package/dist/delegation.d.ts +26 -0
- package/dist/delegation.d.ts.map +1 -0
- package/dist/delegation.js +31 -0
- package/dist/delegation.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +54 -0
- package/dist/errors.js.map +1 -0
- package/dist/handshake.d.ts +41 -0
- package/dist/handshake.d.ts.map +1 -0
- package/dist/handshake.js +152 -0
- package/dist/handshake.js.map +1 -0
- package/dist/identity.d.ts +42 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +90 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +39 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +117 -0
- package/dist/utils.js.map +1 -0
- package/package.json +37 -0
- package/src/circomlibjs.d.ts +5 -0
- package/src/delegation.ts +45 -0
- package/src/errors.ts +69 -0
- package/src/handshake.ts +186 -0
- package/src/identity.ts +114 -0
- package/src/index.ts +37 -0
- package/src/snarkjs.d.ts +26 -0
- package/src/types.ts +75 -0
- package/src/utils.ts +97 -0
package/src/identity.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { HumanIdentity, AgentCredential, Permission } from './types';
|
|
2
|
+
import { poseidon2, poseidon5, eddsaSign, derivePublicKey, derivePublicKeyScalar } from './utils';
|
|
3
|
+
import { InvalidPermissionError } from './errors';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a human identity (EdDSA keypair + commitment).
|
|
7
|
+
* Compatible with Semaphore v4 identity scheme.
|
|
8
|
+
*
|
|
9
|
+
* @param secret - A secret value (random bigint or derived from a seed phrase).
|
|
10
|
+
* KEEP THIS PRIVATE — it is the human's authentication key.
|
|
11
|
+
* @returns HumanIdentity with secret, publicKey, and commitment
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const identity = await createHumanIdentity(BigInt(crypto.getRandomValues(new Uint8Array(32)).reduce((a, b) => a * 256n + BigInt(b), 0n)));
|
|
16
|
+
* console.log(identity.commitment); // Poseidon2(Ax, Ay) — enroll this in humanTree
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export async function createHumanIdentity(
|
|
20
|
+
secret: bigint,
|
|
21
|
+
): Promise<HumanIdentity> {
|
|
22
|
+
// HumanUniqueness circuit uses BabyPbk (direct scalar multiply),
|
|
23
|
+
// NOT EdDSA prv2pub. Use derivePublicKeyScalar here.
|
|
24
|
+
const publicKey = await derivePublicKeyScalar(secret);
|
|
25
|
+
const commitment = await poseidon2(publicKey.x, publicKey.y);
|
|
26
|
+
return { secret, publicKey, commitment };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create an AI agent credential signed by the operator.
|
|
31
|
+
*
|
|
32
|
+
* @param modelHash - Hash of the model identifier (e.g., sha256("gpt-4o"))
|
|
33
|
+
* @param operatorPrivateKey - Operator's EdDSA private key (signs the credential)
|
|
34
|
+
* @param permissions - Array of Permission flags (cumulative encoding enforced)
|
|
35
|
+
* @param expiryTimestamp - Unix timestamp when the credential expires
|
|
36
|
+
* @returns AgentCredential with all fields + operator signature + commitment
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const credential = await createAgentCredential(
|
|
41
|
+
* hashModel("gpt-4o"),
|
|
42
|
+
* operatorKey,
|
|
43
|
+
* [Permission.READ_DATA, Permission.WRITE_DATA, Permission.FINANCIAL_SMALL],
|
|
44
|
+
* BigInt(Math.floor(Date.now() / 1000) + 86400) // +1 day
|
|
45
|
+
* );
|
|
46
|
+
* console.log(credential.commitment); // enroll this in agentTree
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export async function createAgentCredential(
|
|
50
|
+
modelHash: bigint,
|
|
51
|
+
operatorPrivateKey: bigint | Buffer,
|
|
52
|
+
permissions: Permission[],
|
|
53
|
+
expiryTimestamp: bigint,
|
|
54
|
+
): Promise<AgentCredential> {
|
|
55
|
+
const bitmask = permissionsToBitmask(permissions);
|
|
56
|
+
validateCumulativeBitEncoding(bitmask);
|
|
57
|
+
|
|
58
|
+
const operatorPublicKey = await derivePublicKey(
|
|
59
|
+
typeof operatorPrivateKey === 'bigint'
|
|
60
|
+
? operatorPrivateKey
|
|
61
|
+
: BigInt('0x' + operatorPrivateKey.toString('hex')),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const commitment = await poseidon5(
|
|
65
|
+
modelHash,
|
|
66
|
+
operatorPublicKey.x,
|
|
67
|
+
operatorPublicKey.y,
|
|
68
|
+
bitmask,
|
|
69
|
+
expiryTimestamp,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const signature = await eddsaSign(operatorPrivateKey, commitment);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
modelHash,
|
|
76
|
+
operatorPublicKey,
|
|
77
|
+
permissionBitmask: bitmask,
|
|
78
|
+
expiryTimestamp,
|
|
79
|
+
signature,
|
|
80
|
+
commitment,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Convert an array of Permission flags to a 64-bit bitmask */
|
|
85
|
+
export function permissionsToBitmask(permissions: Permission[]): bigint {
|
|
86
|
+
let bitmask = 0n;
|
|
87
|
+
for (const p of permissions) {
|
|
88
|
+
bitmask |= 1n << BigInt(p);
|
|
89
|
+
}
|
|
90
|
+
return bitmask;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Validate cumulative bit encoding: bit 4 implies 2+3, bit 3 implies 2 */
|
|
94
|
+
export function validateCumulativeBitEncoding(bitmask: bigint): void {
|
|
95
|
+
const bit2 = (bitmask >> 2n) & 1n;
|
|
96
|
+
const bit3 = (bitmask >> 3n) & 1n;
|
|
97
|
+
const bit4 = (bitmask >> 4n) & 1n;
|
|
98
|
+
|
|
99
|
+
if (bit4 && !bit3) {
|
|
100
|
+
throw new InvalidPermissionError(
|
|
101
|
+
'FINANCIAL_UNLIMITED (bit 4) requires FINANCIAL_MEDIUM (bit 3)',
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (bit4 && !bit2) {
|
|
105
|
+
throw new InvalidPermissionError(
|
|
106
|
+
'FINANCIAL_UNLIMITED (bit 4) requires FINANCIAL_SMALL (bit 2)',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (bit3 && !bit2) {
|
|
110
|
+
throw new InvalidPermissionError(
|
|
111
|
+
'FINANCIAL_MEDIUM (bit 3) requires FINANCIAL_SMALL (bit 2)',
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Core types
|
|
2
|
+
export type {
|
|
3
|
+
HumanIdentity,
|
|
4
|
+
AgentCredential,
|
|
5
|
+
HandshakeResult,
|
|
6
|
+
DelegationResult,
|
|
7
|
+
Proof,
|
|
8
|
+
BolyraConfig,
|
|
9
|
+
} from './types';
|
|
10
|
+
|
|
11
|
+
// Permission enum
|
|
12
|
+
export { Permission } from './types';
|
|
13
|
+
|
|
14
|
+
// Identity creation
|
|
15
|
+
export {
|
|
16
|
+
createHumanIdentity,
|
|
17
|
+
createAgentCredential,
|
|
18
|
+
permissionsToBitmask,
|
|
19
|
+
validateCumulativeBitEncoding,
|
|
20
|
+
} from './identity';
|
|
21
|
+
|
|
22
|
+
// Handshake (v0.2 — real proof generation via snarkjs)
|
|
23
|
+
export { proveHandshake, verifyHandshake } from './handshake';
|
|
24
|
+
|
|
25
|
+
// Delegation (stubs — coming in v0.3)
|
|
26
|
+
export { delegate, verifyDelegation } from './delegation';
|
|
27
|
+
|
|
28
|
+
// Errors
|
|
29
|
+
export {
|
|
30
|
+
BolyraError,
|
|
31
|
+
ProofGenerationError,
|
|
32
|
+
VerificationError,
|
|
33
|
+
InvalidPermissionError,
|
|
34
|
+
ExpiredCredentialError,
|
|
35
|
+
ScopeEscalationError,
|
|
36
|
+
StaleProofError,
|
|
37
|
+
} from './errors';
|
package/src/snarkjs.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
declare module 'snarkjs' {
|
|
2
|
+
export namespace groth16 {
|
|
3
|
+
function fullProve(
|
|
4
|
+
input: any,
|
|
5
|
+
wasmPath: string,
|
|
6
|
+
zkeyPath: string,
|
|
7
|
+
): Promise<{ proof: any; publicSignals: string[] }>;
|
|
8
|
+
function verify(
|
|
9
|
+
vkey: any,
|
|
10
|
+
publicSignals: string[],
|
|
11
|
+
proof: any,
|
|
12
|
+
): Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
export namespace plonk {
|
|
15
|
+
function fullProve(
|
|
16
|
+
input: any,
|
|
17
|
+
wasmPath: string,
|
|
18
|
+
zkeyPath: string,
|
|
19
|
+
): Promise<{ proof: any; publicSignals: string[] }>;
|
|
20
|
+
function verify(
|
|
21
|
+
vkey: any,
|
|
22
|
+
publicSignals: string[],
|
|
23
|
+
proof: any,
|
|
24
|
+
): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** EdDSA identity for a human participant */
|
|
2
|
+
export interface HumanIdentity {
|
|
3
|
+
/** EdDSA secret scalar (KEEP PRIVATE) */
|
|
4
|
+
secret: bigint;
|
|
5
|
+
/** Baby Jubjub public key coordinates */
|
|
6
|
+
publicKey: { x: bigint; y: bigint };
|
|
7
|
+
/** Poseidon2(Ax, Ay) — leaf in humanTree */
|
|
8
|
+
commitment: bigint;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** AI agent credential */
|
|
12
|
+
export interface AgentCredential {
|
|
13
|
+
modelHash: bigint;
|
|
14
|
+
operatorPublicKey: { x: bigint; y: bigint };
|
|
15
|
+
permissionBitmask: bigint;
|
|
16
|
+
expiryTimestamp: bigint;
|
|
17
|
+
/** EdDSA signature of operator over credential commitment */
|
|
18
|
+
signature: { R8: { x: bigint; y: bigint }; S: bigint };
|
|
19
|
+
/** Poseidon5(modelHash, Ax, Ay, bitmask, expiry) — leaf in agentTree */
|
|
20
|
+
commitment: bigint;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Permission bits (cumulative encoding) */
|
|
24
|
+
export enum Permission {
|
|
25
|
+
READ_DATA = 0,
|
|
26
|
+
WRITE_DATA = 1,
|
|
27
|
+
FINANCIAL_SMALL = 2, // < $100
|
|
28
|
+
FINANCIAL_MEDIUM = 3, // < $10,000 (implies SMALL)
|
|
29
|
+
FINANCIAL_UNLIMITED = 4, // unlimited (implies MEDIUM + SMALL)
|
|
30
|
+
SIGN_ON_BEHALF = 5,
|
|
31
|
+
SUB_DELEGATE = 6,
|
|
32
|
+
ACCESS_PII = 7,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Result of a mutual handshake verification */
|
|
36
|
+
export interface HandshakeResult {
|
|
37
|
+
/** Human's nullifier (unique per scope) */
|
|
38
|
+
humanNullifier: bigint;
|
|
39
|
+
/** Agent's nullifier (unique per session) */
|
|
40
|
+
agentNullifier: bigint;
|
|
41
|
+
/** Session nonce used */
|
|
42
|
+
sessionNonce: bigint;
|
|
43
|
+
/** Agent's scope commitment (chain seed for delegation) */
|
|
44
|
+
scopeCommitment: bigint;
|
|
45
|
+
/** Whether the handshake was verified on-chain */
|
|
46
|
+
verified: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Result of a delegation */
|
|
50
|
+
export interface DelegationResult {
|
|
51
|
+
/** New scope commitment for the next hop */
|
|
52
|
+
newScopeCommitment: bigint;
|
|
53
|
+
/** Delegation nullifier (unique per delegation per session) */
|
|
54
|
+
delegationNullifier: bigint;
|
|
55
|
+
/** Hop number in the chain (0-indexed) */
|
|
56
|
+
hopIndex: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Proof with public signals ready for on-chain verification */
|
|
60
|
+
export interface Proof {
|
|
61
|
+
proof: any; // snarkjs proof object
|
|
62
|
+
publicSignals: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Configuration for the SDK */
|
|
66
|
+
export interface BolyraConfig {
|
|
67
|
+
/** RPC URL for the target chain (default: Base Sepolia) */
|
|
68
|
+
rpcUrl?: string;
|
|
69
|
+
/** Address of the IdentityRegistry contract */
|
|
70
|
+
registryAddress?: string;
|
|
71
|
+
/** Path to circuit WASM files (default: bundled) */
|
|
72
|
+
circuitDir?: string;
|
|
73
|
+
/** Path to zkey files (default: bundled) */
|
|
74
|
+
zkeyDir?: string;
|
|
75
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy-initialized crypto primitives.
|
|
3
|
+
* circomlibjs requires async factory calls; we cache them on first use.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let _poseidon: any = null;
|
|
7
|
+
let _eddsa: any = null;
|
|
8
|
+
let _babyJub: any = null;
|
|
9
|
+
let _F: any = null;
|
|
10
|
+
|
|
11
|
+
async function ensureCrypto(): Promise<void> {
|
|
12
|
+
if (_poseidon) return;
|
|
13
|
+
const circomlibjs = await import('circomlibjs');
|
|
14
|
+
_poseidon = await circomlibjs.buildPoseidon();
|
|
15
|
+
_eddsa = await circomlibjs.buildEddsa();
|
|
16
|
+
_babyJub = await circomlibjs.buildBabyjub();
|
|
17
|
+
_F = _poseidon.F;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Poseidon hash with 2 inputs. Returns a bigint. */
|
|
21
|
+
export async function poseidon2(a: bigint, b: bigint): Promise<bigint> {
|
|
22
|
+
await ensureCrypto();
|
|
23
|
+
const hash = _poseidon([a, b]);
|
|
24
|
+
return _F.toObject(hash);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Poseidon hash with 5 inputs. Returns a bigint. */
|
|
28
|
+
export async function poseidon5(
|
|
29
|
+
a: bigint,
|
|
30
|
+
b: bigint,
|
|
31
|
+
c: bigint,
|
|
32
|
+
d: bigint,
|
|
33
|
+
e: bigint,
|
|
34
|
+
): Promise<bigint> {
|
|
35
|
+
await ensureCrypto();
|
|
36
|
+
const hash = _poseidon([a, b, c, d, e]);
|
|
37
|
+
return _F.toObject(hash);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Derive EdDSA public key from a private key buffer (Baby Jubjub).
|
|
42
|
+
* Uses eddsa.prv2pub which matches what EdDSAPoseidonVerifier expects:
|
|
43
|
+
* hash the key, clamp bits per RFC 8032, then multiply base point.
|
|
44
|
+
*
|
|
45
|
+
* IMPORTANT: This is NOT the same as babyJub.mulPointEscalar(Base8, scalar).
|
|
46
|
+
* The HumanUniqueness circuit uses BabyPbk (direct scalar multiply) for
|
|
47
|
+
* the human identity. The AgentPolicy circuit uses EdDSAPoseidonVerifier
|
|
48
|
+
* which expects prv2pub-derived keys. Use the right function for each.
|
|
49
|
+
*/
|
|
50
|
+
export async function derivePublicKey(
|
|
51
|
+
secret: bigint | Buffer,
|
|
52
|
+
): Promise<{ x: bigint; y: bigint }> {
|
|
53
|
+
await ensureCrypto();
|
|
54
|
+
const key =
|
|
55
|
+
typeof secret === 'bigint'
|
|
56
|
+
? Buffer.from(secret.toString(16).padStart(64, '0'), 'hex')
|
|
57
|
+
: secret;
|
|
58
|
+
// Use eddsa.prv2pub which matches EdDSAPoseidonVerifier's key derivation
|
|
59
|
+
const pubKey = _eddsa.prv2pub(key);
|
|
60
|
+
return {
|
|
61
|
+
x: _F.toObject(pubKey[0]),
|
|
62
|
+
y: _F.toObject(pubKey[1]),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Derive public key via direct scalar multiplication (Baby Jubjub).
|
|
68
|
+
* Used by HumanUniqueness circuit's BabyPbk component.
|
|
69
|
+
*/
|
|
70
|
+
export async function derivePublicKeyScalar(
|
|
71
|
+
secret: bigint,
|
|
72
|
+
): Promise<{ x: bigint; y: bigint }> {
|
|
73
|
+
await ensureCrypto();
|
|
74
|
+
const pubKey = _babyJub.mulPointEscalar(_babyJub.Base8, secret);
|
|
75
|
+
return {
|
|
76
|
+
x: _F.toObject(pubKey[0]),
|
|
77
|
+
y: _F.toObject(pubKey[1]),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Sign a message (field element) with EdDSA. */
|
|
82
|
+
export async function eddsaSign(
|
|
83
|
+
privateKey: bigint | Buffer,
|
|
84
|
+
message: bigint,
|
|
85
|
+
): Promise<{ R8: { x: bigint; y: bigint }; S: bigint }> {
|
|
86
|
+
await ensureCrypto();
|
|
87
|
+
const key =
|
|
88
|
+
typeof privateKey === 'bigint'
|
|
89
|
+
? Buffer.from(privateKey.toString(16).padStart(64, '0'), 'hex')
|
|
90
|
+
: privateKey;
|
|
91
|
+
const msgFe = _F.e(message);
|
|
92
|
+
const sig = _eddsa.signPoseidon(key, msgFe);
|
|
93
|
+
return {
|
|
94
|
+
R8: { x: _F.toObject(sig.R8[0]), y: _F.toObject(sig.R8[1]) },
|
|
95
|
+
S: sig.S,
|
|
96
|
+
};
|
|
97
|
+
}
|