@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 +153 -0
- package/dist/index.d.mts +171 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.js +262 -0
- package/dist/index.mjs +221 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @sonate/verify-sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@sonate/verify-sdk)
|
|
4
|
+
[](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
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|