@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.
@@ -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
+ }