@iam-protocol/pulse-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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/index.d.mts +376 -0
- package/dist/index.d.ts +376 -0
- package/dist/index.js +14316 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14238 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
- package/src/challenge/lissajous.ts +56 -0
- package/src/challenge/phrase.ts +29 -0
- package/src/config.ts +40 -0
- package/src/extraction/kinematic.ts +101 -0
- package/src/extraction/mfcc.ts +93 -0
- package/src/extraction/statistics.ts +59 -0
- package/src/extraction/types.ts +17 -0
- package/src/hashing/poseidon.ts +92 -0
- package/src/hashing/simhash.ts +87 -0
- package/src/hashing/types.ts +16 -0
- package/src/identity/anchor.ts +75 -0
- package/src/identity/types.ts +18 -0
- package/src/index.ts +43 -0
- package/src/proof/prover.ts +87 -0
- package/src/proof/serializer.ts +79 -0
- package/src/proof/types.ts +31 -0
- package/src/pulse.ts +397 -0
- package/src/sensor/audio.ts +94 -0
- package/src/sensor/motion.ts +83 -0
- package/src/sensor/touch.ts +65 -0
- package/src/sensor/types.ts +55 -0
- package/src/submit/relayer.ts +58 -0
- package/src/submit/types.ts +15 -0
- package/src/submit/wallet.ts +167 -0
- package/src/types.d.ts +14 -0
- package/test/integration.test.ts +102 -0
- package/test/poseidon.test.ts +81 -0
- package/test/serializer.test.ts +86 -0
- package/test/simhash.test.ts +57 -0
- package/test/statistics.test.ts +51 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +10 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PROGRAM_IDS } from "../config";
|
|
2
|
+
import type { IdentityState, StoredVerificationData } from "./types";
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = "iam-protocol-verification-data";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fetch identity state from the on-chain IdentityState PDA.
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchIdentityState(
|
|
10
|
+
walletPubkey: string,
|
|
11
|
+
connection: any
|
|
12
|
+
): Promise<IdentityState | null> {
|
|
13
|
+
try {
|
|
14
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
15
|
+
const anchor = await import("@coral-xyz/anchor");
|
|
16
|
+
|
|
17
|
+
const programId = new PublicKey(PROGRAM_IDS.iamAnchor);
|
|
18
|
+
const [identityPda] = PublicKey.findProgramAddressSync(
|
|
19
|
+
[Buffer.from("identity"), new PublicKey(walletPubkey).toBuffer()],
|
|
20
|
+
programId
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const accountInfo = await connection.getAccountInfo(identityPda);
|
|
24
|
+
if (!accountInfo) return null;
|
|
25
|
+
|
|
26
|
+
// Decode using Anchor's BorshAccountsCoder
|
|
27
|
+
const idl = await anchor.Program.fetchIdl(programId, {
|
|
28
|
+
connection,
|
|
29
|
+
} as any);
|
|
30
|
+
if (!idl) return null;
|
|
31
|
+
|
|
32
|
+
const coder = new anchor.BorshAccountsCoder(idl);
|
|
33
|
+
const decoded = coder.decode("identityState", accountInfo.data);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
owner: decoded.owner.toBase58(),
|
|
37
|
+
creationTimestamp: decoded.creationTimestamp.toNumber(),
|
|
38
|
+
lastVerificationTimestamp: decoded.lastVerificationTimestamp.toNumber(),
|
|
39
|
+
verificationCount: decoded.verificationCount,
|
|
40
|
+
trustScore: decoded.trustScore,
|
|
41
|
+
currentCommitment: new Uint8Array(decoded.currentCommitment),
|
|
42
|
+
mint: decoded.mint.toBase58(),
|
|
43
|
+
};
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Store verification data locally for re-verification.
|
|
51
|
+
* Uses localStorage (browser) or in-memory fallback (Node.js).
|
|
52
|
+
*/
|
|
53
|
+
export function storeVerificationData(data: StoredVerificationData): void {
|
|
54
|
+
try {
|
|
55
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
|
56
|
+
} catch {
|
|
57
|
+
// localStorage not available (Node.js or private browsing)
|
|
58
|
+
inMemoryStore = data;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Load previously stored verification data.
|
|
64
|
+
*/
|
|
65
|
+
export function loadVerificationData(): StoredVerificationData | null {
|
|
66
|
+
try {
|
|
67
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
68
|
+
if (!raw) return inMemoryStore;
|
|
69
|
+
return JSON.parse(raw);
|
|
70
|
+
} catch {
|
|
71
|
+
return inMemoryStore;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let inMemoryStore: StoredVerificationData | null = null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** On-chain identity state (mirrors the Anchor program's IdentityState) */
|
|
2
|
+
export interface IdentityState {
|
|
3
|
+
owner: string;
|
|
4
|
+
creationTimestamp: number;
|
|
5
|
+
lastVerificationTimestamp: number;
|
|
6
|
+
verificationCount: number;
|
|
7
|
+
trustScore: number;
|
|
8
|
+
currentCommitment: Uint8Array;
|
|
9
|
+
mint: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Local storage of previous verification data (needed for re-verification) */
|
|
13
|
+
export interface StoredVerificationData {
|
|
14
|
+
fingerprint: number[];
|
|
15
|
+
salt: string;
|
|
16
|
+
commitment: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Main SDK
|
|
2
|
+
export { PulseSDK, PulseSession } from "./pulse";
|
|
3
|
+
|
|
4
|
+
// Configuration
|
|
5
|
+
export type { PulseConfig } from "./config";
|
|
6
|
+
export { PROGRAM_IDS, DEFAULT_THRESHOLD, FINGERPRINT_BITS, MIN_CAPTURE_MS, MAX_CAPTURE_MS, DEFAULT_CAPTURE_MS } from "./config";
|
|
7
|
+
|
|
8
|
+
// Hashing
|
|
9
|
+
export type { TemporalFingerprint, TBH, PackedFingerprint } from "./hashing/types";
|
|
10
|
+
export { simhash, hammingDistance } from "./hashing/simhash";
|
|
11
|
+
export {
|
|
12
|
+
computeCommitment,
|
|
13
|
+
generateSalt,
|
|
14
|
+
generateTBH,
|
|
15
|
+
packBits,
|
|
16
|
+
bigintToBytes32,
|
|
17
|
+
} from "./hashing/poseidon";
|
|
18
|
+
|
|
19
|
+
// Feature extraction
|
|
20
|
+
export type { StatsSummary, FeatureVector, FusedFeatureVector } from "./extraction/types";
|
|
21
|
+
export { mean, variance, skewness, kurtosis, condense, fuseFeatures } from "./extraction/statistics";
|
|
22
|
+
|
|
23
|
+
// Proof generation
|
|
24
|
+
export type { SolanaProof, CircuitInput, ProofResult } from "./proof/types";
|
|
25
|
+
export { serializeProof, toBigEndian32 } from "./proof/serializer";
|
|
26
|
+
export { generateProof, generateSolanaProof, prepareCircuitInput } from "./proof/prover";
|
|
27
|
+
|
|
28
|
+
// Submission
|
|
29
|
+
export type { SubmissionResult, VerificationResult } from "./submit/types";
|
|
30
|
+
export { submitViaWallet } from "./submit/wallet";
|
|
31
|
+
export { submitViaRelayer } from "./submit/relayer";
|
|
32
|
+
|
|
33
|
+
// Identity
|
|
34
|
+
export type { IdentityState, StoredVerificationData } from "./identity/types";
|
|
35
|
+
export { fetchIdentityState, storeVerificationData, loadVerificationData } from "./identity/anchor";
|
|
36
|
+
|
|
37
|
+
// Sensor types
|
|
38
|
+
export type { AudioCapture, MotionSample, TouchSample, SensorData, CaptureOptions, CaptureStage, StageState } from "./sensor/types";
|
|
39
|
+
|
|
40
|
+
// Challenge
|
|
41
|
+
export { generatePhrase } from "./challenge/phrase";
|
|
42
|
+
export { randomLissajousParams, generateLissajousPoints } from "./challenge/lissajous";
|
|
43
|
+
export type { LissajousParams, Point2D } from "./challenge/lissajous";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { TBH } from "../hashing/types";
|
|
2
|
+
import type { CircuitInput, ProofResult, SolanaProof } from "./types";
|
|
3
|
+
import { serializeProof } from "./serializer";
|
|
4
|
+
import { DEFAULT_THRESHOLD } from "../config";
|
|
5
|
+
|
|
6
|
+
// Use dynamic import for snarkjs (it's a CJS module)
|
|
7
|
+
let snarkjsModule: any = null;
|
|
8
|
+
|
|
9
|
+
async function getSnarkjs(): Promise<any> {
|
|
10
|
+
if (!snarkjsModule) {
|
|
11
|
+
snarkjsModule = await import("snarkjs");
|
|
12
|
+
}
|
|
13
|
+
return snarkjsModule;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Prepare circuit input from current and previous TBH data.
|
|
18
|
+
*/
|
|
19
|
+
export function prepareCircuitInput(
|
|
20
|
+
current: TBH,
|
|
21
|
+
previous: TBH,
|
|
22
|
+
threshold: number = DEFAULT_THRESHOLD
|
|
23
|
+
): CircuitInput {
|
|
24
|
+
return {
|
|
25
|
+
ft_new: current.fingerprint,
|
|
26
|
+
ft_prev: previous.fingerprint,
|
|
27
|
+
salt_new: current.salt.toString(),
|
|
28
|
+
salt_prev: previous.salt.toString(),
|
|
29
|
+
commitment_new: current.commitment.toString(),
|
|
30
|
+
commitment_prev: previous.commitment.toString(),
|
|
31
|
+
threshold: threshold.toString(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate a Groth16 proof for the Hamming distance circuit.
|
|
37
|
+
*
|
|
38
|
+
* @param input - Circuit input (fingerprints, salts, commitments, threshold)
|
|
39
|
+
* @param wasmPath - Path or URL to iam_hamming.wasm
|
|
40
|
+
* @param zkeyPath - Path or URL to iam_hamming_final.zkey
|
|
41
|
+
*/
|
|
42
|
+
export async function generateProof(
|
|
43
|
+
input: CircuitInput,
|
|
44
|
+
wasmPath: string,
|
|
45
|
+
zkeyPath: string
|
|
46
|
+
): Promise<ProofResult> {
|
|
47
|
+
const snarkjs = await getSnarkjs();
|
|
48
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
49
|
+
input,
|
|
50
|
+
wasmPath,
|
|
51
|
+
zkeyPath
|
|
52
|
+
);
|
|
53
|
+
return { proof, publicSignals };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a proof and serialize it for Solana submission.
|
|
58
|
+
*/
|
|
59
|
+
export async function generateSolanaProof(
|
|
60
|
+
current: TBH,
|
|
61
|
+
previous: TBH,
|
|
62
|
+
wasmPath: string,
|
|
63
|
+
zkeyPath: string,
|
|
64
|
+
threshold?: number
|
|
65
|
+
): Promise<SolanaProof> {
|
|
66
|
+
const input = prepareCircuitInput(current, previous, threshold);
|
|
67
|
+
const { proof, publicSignals } = await generateProof(
|
|
68
|
+
input,
|
|
69
|
+
wasmPath,
|
|
70
|
+
zkeyPath
|
|
71
|
+
);
|
|
72
|
+
return serializeProof(proof, publicSignals);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Verify a proof locally using snarkjs (for debugging/testing).
|
|
77
|
+
*/
|
|
78
|
+
export async function verifyProofLocally(
|
|
79
|
+
proof: any,
|
|
80
|
+
publicSignals: string[],
|
|
81
|
+
vkeyPath: string
|
|
82
|
+
): Promise<boolean> {
|
|
83
|
+
const snarkjs = await getSnarkjs();
|
|
84
|
+
const fs = await import("fs");
|
|
85
|
+
const vk = JSON.parse(fs.readFileSync(vkeyPath, "utf-8"));
|
|
86
|
+
return snarkjs.groth16.verify(vk, publicSignals, proof);
|
|
87
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BN254_BASE_FIELD,
|
|
3
|
+
PROOF_A_SIZE,
|
|
4
|
+
PROOF_B_SIZE,
|
|
5
|
+
PROOF_C_SIZE,
|
|
6
|
+
TOTAL_PROOF_SIZE,
|
|
7
|
+
NUM_PUBLIC_INPUTS,
|
|
8
|
+
} from "../config";
|
|
9
|
+
import type { RawProof, SolanaProof } from "./types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert a decimal string to a 32-byte big-endian Uint8Array.
|
|
13
|
+
*/
|
|
14
|
+
export function toBigEndian32(decStr: string): Uint8Array {
|
|
15
|
+
let n = BigInt(decStr);
|
|
16
|
+
const bytes = new Uint8Array(32);
|
|
17
|
+
for (let i = 31; i >= 0; i--) {
|
|
18
|
+
bytes[i] = Number(n & BigInt(0xff));
|
|
19
|
+
n >>= BigInt(8);
|
|
20
|
+
}
|
|
21
|
+
return bytes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Negate a G1 y-coordinate for groth16-solana proof_a format.
|
|
26
|
+
*/
|
|
27
|
+
function negateG1Y(yDecStr: string): Uint8Array {
|
|
28
|
+
const y = BigInt(yDecStr);
|
|
29
|
+
const yNeg = (BN254_BASE_FIELD - y) % BN254_BASE_FIELD;
|
|
30
|
+
return toBigEndian32(yNeg.toString());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Serialize an snarkjs proof into the 256-byte format groth16-solana expects.
|
|
35
|
+
*
|
|
36
|
+
* proof_a: 64 bytes (x + negated y)
|
|
37
|
+
* proof_b: 128 bytes (G2 with reversed coordinate ordering: c1 before c0)
|
|
38
|
+
* proof_c: 64 bytes (x + y)
|
|
39
|
+
*/
|
|
40
|
+
export function serializeProof(
|
|
41
|
+
proof: RawProof,
|
|
42
|
+
publicSignals: string[]
|
|
43
|
+
): SolanaProof {
|
|
44
|
+
// proof_a: x (32 bytes) + negated y (32 bytes)
|
|
45
|
+
const a0 = toBigEndian32(proof.pi_a[0]!);
|
|
46
|
+
const a1 = negateG1Y(proof.pi_a[1]!);
|
|
47
|
+
const proofA = new Uint8Array(PROOF_A_SIZE);
|
|
48
|
+
proofA.set(a0, 0);
|
|
49
|
+
proofA.set(a1, 32);
|
|
50
|
+
|
|
51
|
+
// proof_b: G2 reversed coordinate ordering
|
|
52
|
+
const b00 = toBigEndian32(proof.pi_b[0]![1]!); // c1 first
|
|
53
|
+
const b01 = toBigEndian32(proof.pi_b[0]![0]!); // c0 second
|
|
54
|
+
const b10 = toBigEndian32(proof.pi_b[1]![1]!);
|
|
55
|
+
const b11 = toBigEndian32(proof.pi_b[1]![0]!);
|
|
56
|
+
const proofB = new Uint8Array(PROOF_B_SIZE);
|
|
57
|
+
proofB.set(b00, 0);
|
|
58
|
+
proofB.set(b01, 32);
|
|
59
|
+
proofB.set(b10, 64);
|
|
60
|
+
proofB.set(b11, 96);
|
|
61
|
+
|
|
62
|
+
// proof_c: x + y (no negation)
|
|
63
|
+
const c0 = toBigEndian32(proof.pi_c[0]!);
|
|
64
|
+
const c1 = toBigEndian32(proof.pi_c[1]!);
|
|
65
|
+
const proofC = new Uint8Array(PROOF_C_SIZE);
|
|
66
|
+
proofC.set(c0, 0);
|
|
67
|
+
proofC.set(c1, 32);
|
|
68
|
+
|
|
69
|
+
// Combine into single 256-byte blob
|
|
70
|
+
const proofBytes = new Uint8Array(TOTAL_PROOF_SIZE);
|
|
71
|
+
proofBytes.set(proofA, 0);
|
|
72
|
+
proofBytes.set(proofB, PROOF_A_SIZE);
|
|
73
|
+
proofBytes.set(proofC, PROOF_A_SIZE + PROOF_B_SIZE);
|
|
74
|
+
|
|
75
|
+
// Public inputs as 32-byte big-endian arrays
|
|
76
|
+
const publicInputs = publicSignals.map((s) => toBigEndian32(s));
|
|
77
|
+
|
|
78
|
+
return { proofBytes, publicInputs };
|
|
79
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Serialized proof ready for on-chain submission */
|
|
2
|
+
export interface SolanaProof {
|
|
3
|
+
proofBytes: Uint8Array;
|
|
4
|
+
publicInputs: Uint8Array[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Raw snarkjs proof output */
|
|
8
|
+
export interface RawProof {
|
|
9
|
+
pi_a: string[];
|
|
10
|
+
pi_b: string[][];
|
|
11
|
+
pi_c: string[];
|
|
12
|
+
protocol: string;
|
|
13
|
+
curve: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Circuit input for proof generation */
|
|
17
|
+
export interface CircuitInput {
|
|
18
|
+
ft_new: number[];
|
|
19
|
+
ft_prev: number[];
|
|
20
|
+
salt_new: string;
|
|
21
|
+
salt_prev: string;
|
|
22
|
+
commitment_new: string;
|
|
23
|
+
commitment_prev: string;
|
|
24
|
+
threshold: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Proof generation result */
|
|
28
|
+
export interface ProofResult {
|
|
29
|
+
proof: RawProof;
|
|
30
|
+
publicSignals: string[];
|
|
31
|
+
}
|