@sonate/verify-sdk 2.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/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # @sonate/verify-sdk
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@sonate/verify-sdk.svg)](https://www.npmjs.com/package/@sonate/verify-sdk)
4
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+
6
+ Client-side SDK for verifying SONATE Trust Receipts. Works in Node.js and browsers — zero backend calls required.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install @sonate/verify-sdk
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ ```typescript
17
+ import { verify, fetchPublicKey } from '@sonate/verify-sdk';
18
+
19
+ // Fetch the SONATE public key (or provide your own)
20
+ const publicKey = await fetchPublicKey();
21
+
22
+ // Verify a receipt
23
+ const result = await verify(receipt, publicKey);
24
+
25
+ if (result.valid) {
26
+ console.log('All checks passed');
27
+ console.log('Trust score:', result.trustScore);
28
+ } else {
29
+ console.error('Verification failed:', result.errors);
30
+ }
31
+ ```
32
+
33
+ ## API
34
+
35
+ ### `verify(receipt, publicKey)`
36
+
37
+ Full verification with detailed check results.
38
+
39
+ ```typescript
40
+ const result = await verify(receipt, publicKey);
41
+
42
+ // result.valid — overall pass/fail
43
+ // result.checks.structure — required fields present
44
+ // result.checks.signature — Ed25519 signature valid
45
+ // result.checks.chain — hash chain intact
46
+ // result.checks.timestamp — timestamp reasonable
47
+ // result.trustScore — extracted from telemetry (0-100)
48
+ // result.errors — array of error messages
49
+ ```
50
+
51
+ ### `quickVerify(receipt, publicKey)`
52
+
53
+ Boolean-only verification for simple pass/fail checks.
54
+
55
+ ```typescript
56
+ const isValid = await quickVerify(receipt, publicKey);
57
+ ```
58
+
59
+ ### `verifyBatch(receipts, publicKey)`
60
+
61
+ Verify multiple receipts at once.
62
+
63
+ ```typescript
64
+ const { total, valid, invalid, results } = await verifyBatch(receipts, publicKey);
65
+ ```
66
+
67
+ ### `fetchPublicKey(url?)`
68
+
69
+ Fetch a SONATE public key from a backend endpoint.
70
+
71
+ ```typescript
72
+ // Default: fetches from SONATE platform
73
+ const key = await fetchPublicKey();
74
+
75
+ // Custom endpoint
76
+ const key = await fetchPublicKey('https://your-server.com/api/public-key');
77
+ ```
78
+
79
+ ### `canonicalize(obj)`
80
+
81
+ Deterministic JSON serialization (RFC 8785). Useful for building custom verification flows.
82
+
83
+ ```typescript
84
+ import { canonicalize } from '@sonate/verify-sdk';
85
+
86
+ const canonical = canonicalize({ b: 2, a: 1 });
87
+ // '{"a":1,"b":2}'
88
+ ```
89
+
90
+ ## Verification Checks
91
+
92
+ | Check | What it verifies |
93
+ |-------|-----------------|
94
+ | **Structure** | Receipt has `id`, `timestamp`, and `signature` fields |
95
+ | **Signature** | Ed25519 signature over canonical receipt content |
96
+ | **Chain** | `chain_hash` matches `SHA-256(canonical_content + previous_hash)` |
97
+ | **Timestamp** | Not in the future, not older than 1 year |
98
+
99
+ ## Browser Support
100
+
101
+ The SDK uses Web Crypto API in browsers and falls back to Node.js `crypto` module. Ed25519 operations use `@noble/ed25519` for cross-platform compatibility.
102
+
103
+ ```html
104
+ <script type="module">
105
+ import { verify, fetchPublicKey } from '@sonate/verify-sdk';
106
+
107
+ const publicKey = await fetchPublicKey();
108
+ const result = await verify(receiptFromAPI, publicKey);
109
+ console.log('Valid:', result.valid);
110
+ </script>
111
+ ```
112
+
113
+ ## Receipt Format
114
+
115
+ The SDK verifies V2 Trust Receipts:
116
+
117
+ ```typescript
118
+ interface TrustReceipt {
119
+ id: string; // SHA-256 of canonical content
120
+ version: '2.0.0';
121
+ timestamp: string; // ISO 8601
122
+ session_id: string;
123
+ agent_did: string; // did:web:...
124
+ human_did: string;
125
+ mode: 'constitutional' | 'directive';
126
+ interaction: {
127
+ prompt?: string; // Raw content (when included)
128
+ response?: string;
129
+ prompt_hash?: string; // SHA-256 hash (privacy-preserving)
130
+ response_hash?: string;
131
+ model: string;
132
+ };
133
+ chain: {
134
+ previous_hash: string;
135
+ chain_hash: string;
136
+ };
137
+ signature: {
138
+ algorithm: 'Ed25519';
139
+ value: string; // Hex-encoded
140
+ key_version: string;
141
+ };
142
+ }
143
+ ```
144
+
145
+ ## Related Packages
146
+
147
+ - [`@sonate/trust-receipts`](https://www.npmjs.com/package/@sonate/trust-receipts) — Generate signed receipts in your own applications
148
+ - [`@sonate/schemas`](https://www.npmjs.com/package/@sonate/schemas) — JSON Schema + TypeScript types
149
+ - [`@sonate/core`](https://www.npmjs.com/package/@sonate/core) — Core trust protocol implementation
150
+
151
+ ## License
152
+
153
+ MIT
@@ -0,0 +1,171 @@
1
+ /**
2
+ * SONATE Verify SDK
3
+ *
4
+ * Client-side SDK for verifying trust receipts in Node.js and browsers.
5
+ * Zero backend calls required - cryptographic verification is local.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { verify, fetchPublicKey } from '@sonate/verify-sdk';
10
+ *
11
+ * const publicKey = await fetchPublicKey();
12
+ * const result = await verify(receipt, publicKey);
13
+ *
14
+ * if (result.valid) {
15
+ * console.log('All checks passed');
16
+ * }
17
+ * ```
18
+ */
19
+ interface TrustReceipt {
20
+ /** V2 receipt ID (SHA-256 hash of canonical content) */
21
+ id: string;
22
+ /** Receipt schema version */
23
+ version: '2.0.0';
24
+ /** ISO 8601 timestamp */
25
+ timestamp: string;
26
+ /** Session identifier */
27
+ session_id: string;
28
+ /** DID of the AI agent */
29
+ agent_did: string;
30
+ /** DID of the human user */
31
+ human_did: string;
32
+ /** Policy version */
33
+ policy_version?: string;
34
+ /** Governance mode */
35
+ mode: 'constitutional' | 'directive';
36
+ /** AI interaction data */
37
+ interaction: {
38
+ prompt?: string;
39
+ response?: string;
40
+ prompt_hash?: string;
41
+ response_hash?: string;
42
+ model: string;
43
+ provider?: string;
44
+ temperature?: number;
45
+ max_tokens?: number;
46
+ reasoning?: {
47
+ thought_process?: string;
48
+ confidence?: number;
49
+ retrieved_context?: string[];
50
+ };
51
+ };
52
+ /** Trust metrics */
53
+ telemetry?: {
54
+ resonance_score?: number;
55
+ resonance_rm?: number;
56
+ trust_resonance_gap?: number;
57
+ overall_trust_score?: number;
58
+ resonance_quality?: string;
59
+ bedau_index?: number;
60
+ coherence_score?: number;
61
+ truth_debt?: number;
62
+ volatility?: number;
63
+ ciq_metrics?: {
64
+ clarity?: number;
65
+ integrity?: number;
66
+ quality?: number;
67
+ };
68
+ };
69
+ /** Policy state */
70
+ policy_state?: Record<string, any>;
71
+ /** Hash chain for immutability */
72
+ chain: {
73
+ previous_hash: string;
74
+ chain_hash: string;
75
+ chain_length?: number;
76
+ };
77
+ /** Cryptographic signature */
78
+ signature: {
79
+ algorithm: 'Ed25519';
80
+ value: string;
81
+ key_version: string;
82
+ timestamp_signed?: string;
83
+ public_key?: string;
84
+ };
85
+ /** Optional metadata */
86
+ metadata?: Record<string, any>;
87
+ /** @deprecated Use `id` instead */
88
+ self_hash?: string;
89
+ }
90
+ interface VerificationResult {
91
+ valid: boolean;
92
+ checks: {
93
+ structure: {
94
+ passed: boolean;
95
+ message: string;
96
+ };
97
+ signature: {
98
+ passed: boolean;
99
+ message: string;
100
+ };
101
+ chain: {
102
+ passed: boolean;
103
+ message: string;
104
+ };
105
+ timestamp: {
106
+ passed: boolean;
107
+ message: string;
108
+ };
109
+ };
110
+ trustScore: number | null;
111
+ errors: string[];
112
+ receipt: TrustReceipt;
113
+ }
114
+ interface PublicKeyInfo {
115
+ publicKey: string;
116
+ algorithm: string;
117
+ format: string;
118
+ keyId?: string;
119
+ }
120
+ /**
121
+ * Fetch the SONATE platform public key
122
+ */
123
+ declare function fetchPublicKey(url?: string): Promise<string>;
124
+ /**
125
+ * Canonicalize object for signing (deterministic JSON)
126
+ *
127
+ * CRITICAL: This function MUST produce identical output to:
128
+ * - receipt-generator.ts canonicalize()
129
+ * - public-demo.routes.ts canonicalize()
130
+ *
131
+ * Rules:
132
+ * - Recursively sort keys at every nesting level
133
+ * - Filter out undefined values
134
+ * - Arrays preserve order
135
+ */
136
+ declare function canonicalize(obj: any): string;
137
+ /**
138
+ * Verify a SONATE trust receipt
139
+ *
140
+ * @param receipt - The trust receipt to verify
141
+ * @param publicKey - The SONATE public key (hex string)
142
+ * @returns Verification result with detailed checks
143
+ */
144
+ declare function verify(receipt: TrustReceipt, publicKey: string): Promise<VerificationResult>;
145
+ /**
146
+ * Quick verification - returns boolean only
147
+ */
148
+ declare function quickVerify(receipt: TrustReceipt, publicKey: string): Promise<boolean>;
149
+ /**
150
+ * Verify a batch of receipts
151
+ */
152
+ declare function verifyBatch(receipts: TrustReceipt[], publicKey: string): Promise<{
153
+ total: number;
154
+ valid: number;
155
+ invalid: number;
156
+ results: VerificationResult[];
157
+ }>;
158
+ /**
159
+ * Calculate trust score from CIQ metrics
160
+ */
161
+ declare function calculateTrustScore(ciqMetrics: {
162
+ clarity?: number;
163
+ integrity?: number;
164
+ quality?: number;
165
+ }): number;
166
+ /**
167
+ * Check if a receipt is from a trusted issuer
168
+ */
169
+ declare function isTrustedIssuer(receipt: TrustReceipt, trustedIssuers: string[]): boolean;
170
+
171
+ export { type PublicKeyInfo, type TrustReceipt, type VerificationResult, calculateTrustScore, canonicalize, fetchPublicKey, isTrustedIssuer, quickVerify, verify, verifyBatch };
@@ -0,0 +1,171 @@
1
+ /**
2
+ * SONATE Verify SDK
3
+ *
4
+ * Client-side SDK for verifying trust receipts in Node.js and browsers.
5
+ * Zero backend calls required - cryptographic verification is local.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { verify, fetchPublicKey } from '@sonate/verify-sdk';
10
+ *
11
+ * const publicKey = await fetchPublicKey();
12
+ * const result = await verify(receipt, publicKey);
13
+ *
14
+ * if (result.valid) {
15
+ * console.log('All checks passed');
16
+ * }
17
+ * ```
18
+ */
19
+ interface TrustReceipt {
20
+ /** V2 receipt ID (SHA-256 hash of canonical content) */
21
+ id: string;
22
+ /** Receipt schema version */
23
+ version: '2.0.0';
24
+ /** ISO 8601 timestamp */
25
+ timestamp: string;
26
+ /** Session identifier */
27
+ session_id: string;
28
+ /** DID of the AI agent */
29
+ agent_did: string;
30
+ /** DID of the human user */
31
+ human_did: string;
32
+ /** Policy version */
33
+ policy_version?: string;
34
+ /** Governance mode */
35
+ mode: 'constitutional' | 'directive';
36
+ /** AI interaction data */
37
+ interaction: {
38
+ prompt?: string;
39
+ response?: string;
40
+ prompt_hash?: string;
41
+ response_hash?: string;
42
+ model: string;
43
+ provider?: string;
44
+ temperature?: number;
45
+ max_tokens?: number;
46
+ reasoning?: {
47
+ thought_process?: string;
48
+ confidence?: number;
49
+ retrieved_context?: string[];
50
+ };
51
+ };
52
+ /** Trust metrics */
53
+ telemetry?: {
54
+ resonance_score?: number;
55
+ resonance_rm?: number;
56
+ trust_resonance_gap?: number;
57
+ overall_trust_score?: number;
58
+ resonance_quality?: string;
59
+ bedau_index?: number;
60
+ coherence_score?: number;
61
+ truth_debt?: number;
62
+ volatility?: number;
63
+ ciq_metrics?: {
64
+ clarity?: number;
65
+ integrity?: number;
66
+ quality?: number;
67
+ };
68
+ };
69
+ /** Policy state */
70
+ policy_state?: Record<string, any>;
71
+ /** Hash chain for immutability */
72
+ chain: {
73
+ previous_hash: string;
74
+ chain_hash: string;
75
+ chain_length?: number;
76
+ };
77
+ /** Cryptographic signature */
78
+ signature: {
79
+ algorithm: 'Ed25519';
80
+ value: string;
81
+ key_version: string;
82
+ timestamp_signed?: string;
83
+ public_key?: string;
84
+ };
85
+ /** Optional metadata */
86
+ metadata?: Record<string, any>;
87
+ /** @deprecated Use `id` instead */
88
+ self_hash?: string;
89
+ }
90
+ interface VerificationResult {
91
+ valid: boolean;
92
+ checks: {
93
+ structure: {
94
+ passed: boolean;
95
+ message: string;
96
+ };
97
+ signature: {
98
+ passed: boolean;
99
+ message: string;
100
+ };
101
+ chain: {
102
+ passed: boolean;
103
+ message: string;
104
+ };
105
+ timestamp: {
106
+ passed: boolean;
107
+ message: string;
108
+ };
109
+ };
110
+ trustScore: number | null;
111
+ errors: string[];
112
+ receipt: TrustReceipt;
113
+ }
114
+ interface PublicKeyInfo {
115
+ publicKey: string;
116
+ algorithm: string;
117
+ format: string;
118
+ keyId?: string;
119
+ }
120
+ /**
121
+ * Fetch the SONATE platform public key
122
+ */
123
+ declare function fetchPublicKey(url?: string): Promise<string>;
124
+ /**
125
+ * Canonicalize object for signing (deterministic JSON)
126
+ *
127
+ * CRITICAL: This function MUST produce identical output to:
128
+ * - receipt-generator.ts canonicalize()
129
+ * - public-demo.routes.ts canonicalize()
130
+ *
131
+ * Rules:
132
+ * - Recursively sort keys at every nesting level
133
+ * - Filter out undefined values
134
+ * - Arrays preserve order
135
+ */
136
+ declare function canonicalize(obj: any): string;
137
+ /**
138
+ * Verify a SONATE trust receipt
139
+ *
140
+ * @param receipt - The trust receipt to verify
141
+ * @param publicKey - The SONATE public key (hex string)
142
+ * @returns Verification result with detailed checks
143
+ */
144
+ declare function verify(receipt: TrustReceipt, publicKey: string): Promise<VerificationResult>;
145
+ /**
146
+ * Quick verification - returns boolean only
147
+ */
148
+ declare function quickVerify(receipt: TrustReceipt, publicKey: string): Promise<boolean>;
149
+ /**
150
+ * Verify a batch of receipts
151
+ */
152
+ declare function verifyBatch(receipts: TrustReceipt[], publicKey: string): Promise<{
153
+ total: number;
154
+ valid: number;
155
+ invalid: number;
156
+ results: VerificationResult[];
157
+ }>;
158
+ /**
159
+ * Calculate trust score from CIQ metrics
160
+ */
161
+ declare function calculateTrustScore(ciqMetrics: {
162
+ clarity?: number;
163
+ integrity?: number;
164
+ quality?: number;
165
+ }): number;
166
+ /**
167
+ * Check if a receipt is from a trusted issuer
168
+ */
169
+ declare function isTrustedIssuer(receipt: TrustReceipt, trustedIssuers: string[]): boolean;
170
+
171
+ export { type PublicKeyInfo, type TrustReceipt, type VerificationResult, calculateTrustScore, canonicalize, fetchPublicKey, isTrustedIssuer, quickVerify, verify, verifyBatch };
package/dist/index.js ADDED
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ calculateTrustScore: () => calculateTrustScore,
34
+ canonicalize: () => canonicalize,
35
+ fetchPublicKey: () => fetchPublicKey,
36
+ isTrustedIssuer: () => isTrustedIssuer,
37
+ quickVerify: () => quickVerify,
38
+ verify: () => verify,
39
+ verifyBatch: () => verifyBatch
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+ var isBrowser = typeof window !== "undefined";
43
+ var DEFAULT_PUBKEY_URL = "https://yseeku-backend.fly.dev/api/public-demo/public-key";
44
+ async function fetchPublicKey(url) {
45
+ const response = await fetch(url || DEFAULT_PUBKEY_URL);
46
+ if (!response.ok) {
47
+ throw new Error(`Failed to fetch public key: ${response.status}`);
48
+ }
49
+ const data = await response.json();
50
+ return data.data?.publicKey || data.publicKey;
51
+ }
52
+ function hexToBytes(hex) {
53
+ const bytes = new Uint8Array(hex.length / 2);
54
+ for (let i = 0; i < hex.length; i += 2) {
55
+ bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
56
+ }
57
+ return bytes;
58
+ }
59
+ function bytesToHex(bytes) {
60
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
61
+ }
62
+ async function sha256(message) {
63
+ const encoder = new TextEncoder();
64
+ const data = encoder.encode(message);
65
+ if (isBrowser && crypto.subtle) {
66
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
67
+ return bytesToHex(new Uint8Array(hashBuffer));
68
+ } else {
69
+ const nodeCrypto = await import("crypto");
70
+ return nodeCrypto.createHash("sha256").update(message).digest("hex");
71
+ }
72
+ }
73
+ async function verifyEd25519(message, signature, publicKey) {
74
+ try {
75
+ const ed = await import("@noble/ed25519");
76
+ if (ed.etc && !ed.etc.sha512Sync) {
77
+ if (isBrowser && crypto.subtle) {
78
+ ed.etc.sha512Async = async (message2) => {
79
+ const hashBuffer = await crypto.subtle.digest("SHA-512", new Uint8Array(message2));
80
+ return new Uint8Array(hashBuffer);
81
+ };
82
+ } else {
83
+ const nodeCrypto = await import("crypto");
84
+ ed.etc.sha512Sync = (...m) => new Uint8Array(nodeCrypto.createHash("sha512").update(m[0]).digest());
85
+ }
86
+ }
87
+ return await ed.verifyAsync(signature, message, publicKey);
88
+ } catch (error) {
89
+ console.error("Ed25519 verification error:", error);
90
+ return false;
91
+ }
92
+ }
93
+ function canonicalize(obj) {
94
+ if (obj === null || typeof obj !== "object") {
95
+ return JSON.stringify(obj);
96
+ }
97
+ if (Array.isArray(obj)) {
98
+ return "[" + obj.map(canonicalize).join(",") + "]";
99
+ }
100
+ const sortedKeys = Object.keys(obj).sort();
101
+ const pairs = sortedKeys.filter((key) => obj[key] !== void 0).map((key) => JSON.stringify(key) + ":" + canonicalize(obj[key]));
102
+ return "{" + pairs.join(",") + "}";
103
+ }
104
+ async function verify(receipt, publicKey) {
105
+ const result = {
106
+ valid: false,
107
+ checks: {
108
+ structure: { passed: false, message: "" },
109
+ signature: { passed: false, message: "" },
110
+ chain: { passed: false, message: "" },
111
+ timestamp: { passed: false, message: "" }
112
+ },
113
+ trustScore: null,
114
+ errors: [],
115
+ receipt
116
+ };
117
+ try {
118
+ const receiptId = receipt.id || receipt.self_hash;
119
+ if (!receiptId || !receipt.signature?.value) {
120
+ const missing = [];
121
+ if (!receiptId) missing.push("id");
122
+ if (!receipt.signature?.value) missing.push("signature");
123
+ result.checks.structure.message = `Missing required fields: ${missing.join(", ")}`;
124
+ result.errors.push(result.checks.structure.message);
125
+ if (receipt.self_hash && !receipt.id) {
126
+ result.checks.structure.message = 'This receipt uses V1 format (self_hash). V2 format with "id" field is required.';
127
+ result.errors.push(result.checks.structure.message);
128
+ }
129
+ } else {
130
+ result.checks.structure.passed = true;
131
+ result.checks.structure.message = "Valid V2 receipt structure";
132
+ }
133
+ const signatureValue = receipt.signature?.value;
134
+ if (signatureValue && publicKey) {
135
+ const { signature: _sig, ...receiptWithoutSig } = receipt;
136
+ const canonical = canonicalize(receiptWithoutSig);
137
+ const messageBytes = new TextEncoder().encode(canonical);
138
+ const signatureBytes = hexToBytes(signatureValue);
139
+ const publicKeyBytes = hexToBytes(publicKey);
140
+ const isValid = await verifyEd25519(messageBytes, signatureBytes, publicKeyBytes);
141
+ result.checks.signature.passed = isValid;
142
+ result.checks.signature.message = isValid ? "Ed25519 signature verified" : "Signature verification failed - content may have been tampered";
143
+ if (!isValid) {
144
+ result.errors.push("Signature verification failed");
145
+ }
146
+ } else {
147
+ result.checks.signature.message = "No signature or public key provided";
148
+ result.errors.push(result.checks.signature.message);
149
+ }
150
+ if (receipt.chain?.chain_hash && receipt.chain?.previous_hash) {
151
+ const { signature: _sig, ...receiptWithoutSig } = receipt;
152
+ const receiptForChain = {
153
+ ...receiptWithoutSig,
154
+ chain: { ...receipt.chain, chain_hash: "" }
155
+ };
156
+ const contentForChain = canonicalize(receiptForChain);
157
+ const chainContent = contentForChain + receipt.chain.previous_hash;
158
+ const expectedChainHash = await sha256(chainContent);
159
+ const chainValid = expectedChainHash === receipt.chain.chain_hash;
160
+ result.checks.chain.passed = chainValid;
161
+ result.checks.chain.message = chainValid ? "Chain hash verified" : "Chain hash mismatch - receipt may have been tampered";
162
+ if (!chainValid) {
163
+ result.errors.push("Chain hash verification failed");
164
+ }
165
+ } else if (receipt.chain?.previous_hash === "GENESIS") {
166
+ if (receipt.chain?.chain_hash) {
167
+ const { signature: _sig, ...receiptWithoutSig } = receipt;
168
+ const receiptForChain = {
169
+ ...receiptWithoutSig,
170
+ chain: { ...receipt.chain, chain_hash: "" }
171
+ };
172
+ const contentForChain = canonicalize(receiptForChain);
173
+ const chainContent = contentForChain + "GENESIS";
174
+ const expectedChainHash = await sha256(chainContent);
175
+ const chainValid = expectedChainHash === receipt.chain.chain_hash;
176
+ result.checks.chain.passed = chainValid;
177
+ result.checks.chain.message = chainValid ? "Genesis chain hash verified" : "Genesis chain hash mismatch";
178
+ if (!chainValid) {
179
+ result.errors.push("Genesis chain hash verification failed");
180
+ }
181
+ } else {
182
+ result.checks.chain.passed = true;
183
+ result.checks.chain.message = "First receipt in chain (GENESIS)";
184
+ }
185
+ } else {
186
+ result.checks.chain.passed = true;
187
+ result.checks.chain.message = "Chain verification skipped (no chain data)";
188
+ }
189
+ const timestamp = receipt.timestamp;
190
+ if (timestamp) {
191
+ const receiptTime = new Date(timestamp);
192
+ const now = /* @__PURE__ */ new Date();
193
+ const oneYearAgo = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1e3);
194
+ const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1e3);
195
+ if (receiptTime > fiveMinutesFromNow) {
196
+ result.checks.timestamp.passed = false;
197
+ result.checks.timestamp.message = "Timestamp is in the future";
198
+ result.errors.push(result.checks.timestamp.message);
199
+ } else if (receiptTime < oneYearAgo) {
200
+ result.checks.timestamp.passed = false;
201
+ result.checks.timestamp.message = "Timestamp is older than 1 year";
202
+ result.errors.push(result.checks.timestamp.message);
203
+ } else {
204
+ result.checks.timestamp.passed = true;
205
+ result.checks.timestamp.message = `Issued ${receiptTime.toISOString()}`;
206
+ }
207
+ } else {
208
+ result.checks.timestamp.passed = false;
209
+ result.checks.timestamp.message = "No timestamp present";
210
+ result.errors.push(result.checks.timestamp.message);
211
+ }
212
+ if (typeof receipt.telemetry?.overall_trust_score === "number") {
213
+ result.trustScore = Math.round(receipt.telemetry.overall_trust_score);
214
+ } else if (receipt.telemetry?.ciq_metrics) {
215
+ const { clarity = 0, integrity = 0, quality = 0 } = receipt.telemetry.ciq_metrics;
216
+ result.trustScore = Math.round((clarity + integrity + quality) / 3 * 100);
217
+ } else if (receipt.telemetry?.resonance_score !== void 0) {
218
+ result.trustScore = Math.round(receipt.telemetry.resonance_score * 100);
219
+ }
220
+ result.valid = result.checks.structure.passed && result.checks.signature.passed && result.checks.chain.passed && result.checks.timestamp.passed;
221
+ } catch (error) {
222
+ result.errors.push(`Verification error: ${error}`);
223
+ }
224
+ return result;
225
+ }
226
+ async function quickVerify(receipt, publicKey) {
227
+ const result = await verify(receipt, publicKey);
228
+ return result.valid;
229
+ }
230
+ async function verifyBatch(receipts, publicKey) {
231
+ const results = await Promise.all(
232
+ receipts.map((receipt) => verify(receipt, publicKey))
233
+ );
234
+ return {
235
+ total: receipts.length,
236
+ valid: results.filter((r) => r.valid).length,
237
+ invalid: results.filter((r) => !r.valid).length,
238
+ results
239
+ };
240
+ }
241
+ function calculateTrustScore(ciqMetrics) {
242
+ const { clarity = 0, integrity = 0, quality = 0 } = ciqMetrics;
243
+ return Math.round((clarity + integrity + quality) / 3 * 100);
244
+ }
245
+ function isTrustedIssuer(receipt, trustedIssuers) {
246
+ const issuer = receipt.issuer;
247
+ if (!issuer) return false;
248
+ const issuerId = typeof issuer === "string" ? issuer : issuer.id;
249
+ return trustedIssuers.some(
250
+ (trusted) => issuerId === trusted || issuerId.startsWith(trusted)
251
+ );
252
+ }
253
+ // Annotate the CommonJS export names for ESM import in node:
254
+ 0 && (module.exports = {
255
+ calculateTrustScore,
256
+ canonicalize,
257
+ fetchPublicKey,
258
+ isTrustedIssuer,
259
+ quickVerify,
260
+ verify,
261
+ verifyBatch
262
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,221 @@
1
+ // src/index.ts
2
+ var isBrowser = typeof window !== "undefined";
3
+ var DEFAULT_PUBKEY_URL = "https://yseeku-backend.fly.dev/api/public-demo/public-key";
4
+ async function fetchPublicKey(url) {
5
+ const response = await fetch(url || DEFAULT_PUBKEY_URL);
6
+ if (!response.ok) {
7
+ throw new Error(`Failed to fetch public key: ${response.status}`);
8
+ }
9
+ const data = await response.json();
10
+ return data.data?.publicKey || data.publicKey;
11
+ }
12
+ function hexToBytes(hex) {
13
+ const bytes = new Uint8Array(hex.length / 2);
14
+ for (let i = 0; i < hex.length; i += 2) {
15
+ bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
16
+ }
17
+ return bytes;
18
+ }
19
+ function bytesToHex(bytes) {
20
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
21
+ }
22
+ async function sha256(message) {
23
+ const encoder = new TextEncoder();
24
+ const data = encoder.encode(message);
25
+ if (isBrowser && crypto.subtle) {
26
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
27
+ return bytesToHex(new Uint8Array(hashBuffer));
28
+ } else {
29
+ const nodeCrypto = await import("crypto");
30
+ return nodeCrypto.createHash("sha256").update(message).digest("hex");
31
+ }
32
+ }
33
+ async function verifyEd25519(message, signature, publicKey) {
34
+ try {
35
+ const ed = await import("@noble/ed25519");
36
+ if (ed.etc && !ed.etc.sha512Sync) {
37
+ if (isBrowser && crypto.subtle) {
38
+ ed.etc.sha512Async = async (message2) => {
39
+ const hashBuffer = await crypto.subtle.digest("SHA-512", new Uint8Array(message2));
40
+ return new Uint8Array(hashBuffer);
41
+ };
42
+ } else {
43
+ const nodeCrypto = await import("crypto");
44
+ ed.etc.sha512Sync = (...m) => new Uint8Array(nodeCrypto.createHash("sha512").update(m[0]).digest());
45
+ }
46
+ }
47
+ return await ed.verifyAsync(signature, message, publicKey);
48
+ } catch (error) {
49
+ console.error("Ed25519 verification error:", error);
50
+ return false;
51
+ }
52
+ }
53
+ function canonicalize(obj) {
54
+ if (obj === null || typeof obj !== "object") {
55
+ return JSON.stringify(obj);
56
+ }
57
+ if (Array.isArray(obj)) {
58
+ return "[" + obj.map(canonicalize).join(",") + "]";
59
+ }
60
+ const sortedKeys = Object.keys(obj).sort();
61
+ const pairs = sortedKeys.filter((key) => obj[key] !== void 0).map((key) => JSON.stringify(key) + ":" + canonicalize(obj[key]));
62
+ return "{" + pairs.join(",") + "}";
63
+ }
64
+ async function verify(receipt, publicKey) {
65
+ const result = {
66
+ valid: false,
67
+ checks: {
68
+ structure: { passed: false, message: "" },
69
+ signature: { passed: false, message: "" },
70
+ chain: { passed: false, message: "" },
71
+ timestamp: { passed: false, message: "" }
72
+ },
73
+ trustScore: null,
74
+ errors: [],
75
+ receipt
76
+ };
77
+ try {
78
+ const receiptId = receipt.id || receipt.self_hash;
79
+ if (!receiptId || !receipt.signature?.value) {
80
+ const missing = [];
81
+ if (!receiptId) missing.push("id");
82
+ if (!receipt.signature?.value) missing.push("signature");
83
+ result.checks.structure.message = `Missing required fields: ${missing.join(", ")}`;
84
+ result.errors.push(result.checks.structure.message);
85
+ if (receipt.self_hash && !receipt.id) {
86
+ result.checks.structure.message = 'This receipt uses V1 format (self_hash). V2 format with "id" field is required.';
87
+ result.errors.push(result.checks.structure.message);
88
+ }
89
+ } else {
90
+ result.checks.structure.passed = true;
91
+ result.checks.structure.message = "Valid V2 receipt structure";
92
+ }
93
+ const signatureValue = receipt.signature?.value;
94
+ if (signatureValue && publicKey) {
95
+ const { signature: _sig, ...receiptWithoutSig } = receipt;
96
+ const canonical = canonicalize(receiptWithoutSig);
97
+ const messageBytes = new TextEncoder().encode(canonical);
98
+ const signatureBytes = hexToBytes(signatureValue);
99
+ const publicKeyBytes = hexToBytes(publicKey);
100
+ const isValid = await verifyEd25519(messageBytes, signatureBytes, publicKeyBytes);
101
+ result.checks.signature.passed = isValid;
102
+ result.checks.signature.message = isValid ? "Ed25519 signature verified" : "Signature verification failed - content may have been tampered";
103
+ if (!isValid) {
104
+ result.errors.push("Signature verification failed");
105
+ }
106
+ } else {
107
+ result.checks.signature.message = "No signature or public key provided";
108
+ result.errors.push(result.checks.signature.message);
109
+ }
110
+ if (receipt.chain?.chain_hash && receipt.chain?.previous_hash) {
111
+ const { signature: _sig, ...receiptWithoutSig } = receipt;
112
+ const receiptForChain = {
113
+ ...receiptWithoutSig,
114
+ chain: { ...receipt.chain, chain_hash: "" }
115
+ };
116
+ const contentForChain = canonicalize(receiptForChain);
117
+ const chainContent = contentForChain + receipt.chain.previous_hash;
118
+ const expectedChainHash = await sha256(chainContent);
119
+ const chainValid = expectedChainHash === receipt.chain.chain_hash;
120
+ result.checks.chain.passed = chainValid;
121
+ result.checks.chain.message = chainValid ? "Chain hash verified" : "Chain hash mismatch - receipt may have been tampered";
122
+ if (!chainValid) {
123
+ result.errors.push("Chain hash verification failed");
124
+ }
125
+ } else if (receipt.chain?.previous_hash === "GENESIS") {
126
+ if (receipt.chain?.chain_hash) {
127
+ const { signature: _sig, ...receiptWithoutSig } = receipt;
128
+ const receiptForChain = {
129
+ ...receiptWithoutSig,
130
+ chain: { ...receipt.chain, chain_hash: "" }
131
+ };
132
+ const contentForChain = canonicalize(receiptForChain);
133
+ const chainContent = contentForChain + "GENESIS";
134
+ const expectedChainHash = await sha256(chainContent);
135
+ const chainValid = expectedChainHash === receipt.chain.chain_hash;
136
+ result.checks.chain.passed = chainValid;
137
+ result.checks.chain.message = chainValid ? "Genesis chain hash verified" : "Genesis chain hash mismatch";
138
+ if (!chainValid) {
139
+ result.errors.push("Genesis chain hash verification failed");
140
+ }
141
+ } else {
142
+ result.checks.chain.passed = true;
143
+ result.checks.chain.message = "First receipt in chain (GENESIS)";
144
+ }
145
+ } else {
146
+ result.checks.chain.passed = true;
147
+ result.checks.chain.message = "Chain verification skipped (no chain data)";
148
+ }
149
+ const timestamp = receipt.timestamp;
150
+ if (timestamp) {
151
+ const receiptTime = new Date(timestamp);
152
+ const now = /* @__PURE__ */ new Date();
153
+ const oneYearAgo = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1e3);
154
+ const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1e3);
155
+ if (receiptTime > fiveMinutesFromNow) {
156
+ result.checks.timestamp.passed = false;
157
+ result.checks.timestamp.message = "Timestamp is in the future";
158
+ result.errors.push(result.checks.timestamp.message);
159
+ } else if (receiptTime < oneYearAgo) {
160
+ result.checks.timestamp.passed = false;
161
+ result.checks.timestamp.message = "Timestamp is older than 1 year";
162
+ result.errors.push(result.checks.timestamp.message);
163
+ } else {
164
+ result.checks.timestamp.passed = true;
165
+ result.checks.timestamp.message = `Issued ${receiptTime.toISOString()}`;
166
+ }
167
+ } else {
168
+ result.checks.timestamp.passed = false;
169
+ result.checks.timestamp.message = "No timestamp present";
170
+ result.errors.push(result.checks.timestamp.message);
171
+ }
172
+ if (typeof receipt.telemetry?.overall_trust_score === "number") {
173
+ result.trustScore = Math.round(receipt.telemetry.overall_trust_score);
174
+ } else if (receipt.telemetry?.ciq_metrics) {
175
+ const { clarity = 0, integrity = 0, quality = 0 } = receipt.telemetry.ciq_metrics;
176
+ result.trustScore = Math.round((clarity + integrity + quality) / 3 * 100);
177
+ } else if (receipt.telemetry?.resonance_score !== void 0) {
178
+ result.trustScore = Math.round(receipt.telemetry.resonance_score * 100);
179
+ }
180
+ result.valid = result.checks.structure.passed && result.checks.signature.passed && result.checks.chain.passed && result.checks.timestamp.passed;
181
+ } catch (error) {
182
+ result.errors.push(`Verification error: ${error}`);
183
+ }
184
+ return result;
185
+ }
186
+ async function quickVerify(receipt, publicKey) {
187
+ const result = await verify(receipt, publicKey);
188
+ return result.valid;
189
+ }
190
+ async function verifyBatch(receipts, publicKey) {
191
+ const results = await Promise.all(
192
+ receipts.map((receipt) => verify(receipt, publicKey))
193
+ );
194
+ return {
195
+ total: receipts.length,
196
+ valid: results.filter((r) => r.valid).length,
197
+ invalid: results.filter((r) => !r.valid).length,
198
+ results
199
+ };
200
+ }
201
+ function calculateTrustScore(ciqMetrics) {
202
+ const { clarity = 0, integrity = 0, quality = 0 } = ciqMetrics;
203
+ return Math.round((clarity + integrity + quality) / 3 * 100);
204
+ }
205
+ function isTrustedIssuer(receipt, trustedIssuers) {
206
+ const issuer = receipt.issuer;
207
+ if (!issuer) return false;
208
+ const issuerId = typeof issuer === "string" ? issuer : issuer.id;
209
+ return trustedIssuers.some(
210
+ (trusted) => issuerId === trusted || issuerId.startsWith(trusted)
211
+ );
212
+ }
213
+ export {
214
+ calculateTrustScore,
215
+ canonicalize,
216
+ fetchPublicKey,
217
+ isTrustedIssuer,
218
+ quickVerify,
219
+ verify,
220
+ verifyBatch
221
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@sonate/verify-sdk",
3
+ "version": "2.0.0",
4
+ "description": "Client-side SDK for verifying SONATE trust receipts",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format cjs,esm --dts",
21
+ "test": "vitest run",
22
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
23
+ },
24
+ "keywords": [
25
+ "sonate",
26
+ "trust",
27
+ "verification",
28
+ "ed25519",
29
+ "receipts",
30
+ "ai-safety"
31
+ ],
32
+ "author": "SONATE",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/s8ken/SONATE-SDK.git",
37
+ "directory": "packages/verify-sdk"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/s8ken/SONATE-SDK/issues"
41
+ },
42
+ "homepage": "https://github.com/s8ken/SONATE-SDK/tree/main/packages/verify-sdk#readme",
43
+ "devDependencies": {
44
+ "tsup": "^8.0.0",
45
+ "typescript": "^5.0.0",
46
+ "vitest": "^4.0.0"
47
+ },
48
+ "dependencies": {
49
+ "@noble/ed25519": "^3.0.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }