epi-recorder 2.1.1__py3-none-any.whl → 2.1.3__py3-none-any.whl

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.
epi_viewer_static/app.js CHANGED
@@ -21,33 +21,67 @@ function loadEPIData() {
21
21
  }
22
22
 
23
23
  // Render trust badge
24
- function renderTrustBadge(manifest) {
24
+ async function renderTrustBadge(manifest) {
25
25
  const badge = document.getElementById('trust-badge');
26
26
  if (!badge) return;
27
27
 
28
- const hasSig = manifest.signature != null;
29
-
28
+ // Initial state: checking
29
+ badge.innerHTML = `
30
+ <div class="trust-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-600">
31
+ <svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
32
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
33
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
34
+ </svg>
35
+ Verifying...
36
+ </div>
37
+ `;
38
+
39
+ // Check verification logic availability
40
+ if (typeof window.verifyManifestSignature !== 'function') {
41
+ renderBadgeResult(false, 'Missing crypto lib', manifest.signature != null);
42
+ return;
43
+ }
44
+
45
+ try {
46
+ const result = await window.verifyManifestSignature(manifest);
47
+ console.log("Verification Result:", result);
48
+ renderBadgeResult(result.valid, result.reason, manifest.signature != null);
49
+ } catch (e) {
50
+ console.error("Verification error:", e);
51
+ renderBadgeResult(false, e.message, manifest.signature != null);
52
+ }
53
+ }
54
+
55
+ function renderBadgeResult(isValid, reason, hasSignature) {
56
+ const badge = document.getElementById('trust-badge');
30
57
  let badgeHTML;
31
- if (hasSig) {
58
+
59
+ if (isValid) {
32
60
  badgeHTML = `
33
- <div class="trust-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
61
+ <div class="trust-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800" title="Cryptographically Verified">
34
62
  <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
35
63
  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
36
64
  </svg>
37
- Signed
65
+ Verified
38
66
  </div>
39
67
  `;
40
- } else {
68
+ } else if (!hasSignature) {
41
69
  badgeHTML = `
42
70
  <div class="trust-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
71
+ Unsigned
72
+ </div>
73
+ `;
74
+ } else {
75
+ // Has signature but INVALID
76
+ badgeHTML = `
77
+ <div class="trust-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800" title="Hash Mismatch: ${reason}">
43
78
  <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
44
- <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
79
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
45
80
  </svg>
46
- Unsigned
81
+ TAMPERED
47
82
  </div>
48
83
  `;
49
84
  }
50
-
51
85
  badge.innerHTML = badgeHTML;
52
86
  }
53
87
 
@@ -57,10 +91,10 @@ function renderMetadata(manifest) {
57
91
  const metadataSection = document.createElement('div');
58
92
  metadataSection.className = 'bg-blue-50 rounded-lg p-4 mb-6';
59
93
  metadataSection.innerHTML = '<h3 class="text-lg font-semibold text-gray-900 mb-3">Recording Metadata</h3>';
60
-
94
+
61
95
  const metadataContent = document.createElement('div');
62
96
  metadataContent.className = 'space-y-3';
63
-
97
+
64
98
  // Goal
65
99
  if (manifest.goal) {
66
100
  const goalDiv = document.createElement('div');
@@ -70,7 +104,7 @@ function renderMetadata(manifest) {
70
104
  `;
71
105
  metadataContent.appendChild(goalDiv);
72
106
  }
73
-
107
+
74
108
  // Notes
75
109
  if (manifest.notes) {
76
110
  const notesDiv = document.createElement('div');
@@ -80,7 +114,7 @@ function renderMetadata(manifest) {
80
114
  `;
81
115
  metadataContent.appendChild(notesDiv);
82
116
  }
83
-
117
+
84
118
  // Metrics
85
119
  if (manifest.metrics && Object.keys(manifest.metrics).length > 0) {
86
120
  const metricsDiv = document.createElement('div');
@@ -92,7 +126,7 @@ function renderMetadata(manifest) {
92
126
  metricsDiv.innerHTML = metricsHtml;
93
127
  metadataContent.appendChild(metricsDiv);
94
128
  }
95
-
129
+
96
130
  // Approved by
97
131
  if (manifest.approved_by) {
98
132
  const approvedDiv = document.createElement('div');
@@ -102,7 +136,7 @@ function renderMetadata(manifest) {
102
136
  `;
103
137
  metadataContent.appendChild(approvedDiv);
104
138
  }
105
-
139
+
106
140
  // Tags
107
141
  if (manifest.tags && manifest.tags.length > 0) {
108
142
  const tagsDiv = document.createElement('div');
@@ -114,7 +148,7 @@ function renderMetadata(manifest) {
114
148
  tagsDiv.innerHTML = tagsHtml;
115
149
  metadataContent.appendChild(tagsDiv);
116
150
  }
117
-
151
+
118
152
  // Only add metadata section if there's content to show
119
153
  if (metadataContent.children.length > 0) {
120
154
  metadataSection.appendChild(metadataContent);
@@ -218,7 +252,7 @@ function renderLLMRequest(content) {
218
252
  const bgColor = isUser ? 'bg-blue-100' : 'bg-gray-100';
219
253
  const textColor = isUser ? 'text-blue-900' : 'text-gray-900';
220
254
  const align = isUser ? 'ml-auto' : 'mr-auto';
221
-
255
+
222
256
  html += `
223
257
  <div class="chat-bubble ${align} ${bgColor} ${textColor} rounded-lg px-4 py-2 text-sm">
224
258
  <div class="text-xs font-medium mb-1 uppercase">${msg.role}</div>
@@ -315,7 +349,7 @@ function renderTimeline(steps) {
315
349
  }
316
350
 
317
351
  // Initialize viewer
318
- function init() {
352
+ async function init() {
319
353
  const data = loadEPIData();
320
354
  if (!data) {
321
355
  document.body.innerHTML = `
@@ -329,7 +363,7 @@ function init() {
329
363
  return;
330
364
  }
331
365
 
332
- renderTrustBadge(data.manifest);
366
+ await renderTrustBadge(data.manifest);
333
367
  renderMetadata(data.manifest); // New metadata section
334
368
  renderManifest(data.manifest);
335
369
  renderTimeline(data.steps);
@@ -0,0 +1,517 @@
1
+ /*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
2
+ // Bundled for EPI Viewer
3
+
4
+ const noble = (function () {
5
+ const ed25519_CURVE = {
6
+ p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
7
+ n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
8
+ h: 8n,
9
+ a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
10
+ d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
11
+ Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
12
+ Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n,
13
+ };
14
+ const { p: P, n: N, Gx, Gy, a: _a, d: _d, h } = ed25519_CURVE;
15
+ const L = 32;
16
+ const L2 = 64;
17
+
18
+ const captureTrace = (...args) => {
19
+ if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {
20
+ Error.captureStackTrace(...args);
21
+ }
22
+ };
23
+ const err = (message = '') => {
24
+ const e = new Error(message);
25
+ captureTrace(e, err);
26
+ throw e;
27
+ };
28
+ const isBig = (n) => typeof n === 'bigint';
29
+ const isStr = (s) => typeof s === 'string';
30
+ const isBytes = (a) => a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
31
+ const abytes = (value, length, title = '') => {
32
+ const bytes = isBytes(value);
33
+ const len = value?.length;
34
+ const needsLen = length !== undefined;
35
+ if (!bytes || (needsLen && len !== length)) {
36
+ const prefix = title && `"${title}" `;
37
+ const ofLen = needsLen ? ` of length ${length}` : '';
38
+ const got = bytes ? `length=${len}` : `type=${typeof value}`;
39
+ err(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);
40
+ }
41
+ return value;
42
+ };
43
+ const u8n = (len) => new Uint8Array(len);
44
+ const u8fr = (buf) => Uint8Array.from(buf);
45
+ const padh = (n, pad) => n.toString(16).padStart(pad, '0');
46
+ const bytesToHex = (b) => Array.from(abytes(b))
47
+ .map((e) => padh(e, 2))
48
+ .join('');
49
+ const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
50
+ const _ch = (ch) => {
51
+ if (ch >= C._0 && ch <= C._9) return ch - C._0;
52
+ if (ch >= C.A && ch <= C.F) return ch - (C.A - 10);
53
+ if (ch >= C.a && ch <= C.f) return ch - (C.a - 10);
54
+ return;
55
+ };
56
+ const hexToBytes = (hex) => {
57
+ const e = 'hex invalid';
58
+ if (!isStr(hex)) return err(e);
59
+ const hl = hex.length;
60
+ const al = hl / 2;
61
+ if (hl % 2) return err(e);
62
+ const array = u8n(al);
63
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
64
+ const n1 = _ch(hex.charCodeAt(hi));
65
+ const n2 = _ch(hex.charCodeAt(hi + 1));
66
+ if (n1 === undefined || n2 === undefined) return err(e);
67
+ array[ai] = n1 * 16 + n2;
68
+ }
69
+ return array;
70
+ };
71
+ const cr = () => globalThis?.crypto;
72
+ const subtle = () => cr()?.subtle ?? err('crypto.subtle must be defined, consider polyfill');
73
+
74
+ const concatBytes = (...arrs) => {
75
+ const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0));
76
+ let pad = 0;
77
+ arrs.forEach(a => { r.set(a, pad); pad += a.length; });
78
+ return r;
79
+ };
80
+
81
+ const randomBytes = (len = L) => {
82
+ const c = cr();
83
+ return c.getRandomValues(u8n(len));
84
+ };
85
+ const big = BigInt;
86
+ const assertRange = (n, min, max, msg = 'bad number: out of range') => (isBig(n) && min <= n && n < max ? n : err(msg));
87
+ const M = (a, b = P) => {
88
+ const r = a % b;
89
+ return r >= 0n ? r : b + r;
90
+ };
91
+ const modN = (a) => M(a, N);
92
+
93
+ const invert = (num, md) => {
94
+ if (num === 0n || md <= 0n) err('no inverse n=' + num + ' mod=' + md);
95
+ let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
96
+ while (a !== 0n) {
97
+ const q = b / a, r = b % a;
98
+ const m = x - u * q, n = y - v * q;
99
+ b = a, a = r, x = u, y = v, u = m, v = n;
100
+ }
101
+ return b === 1n ? M(x, md) : err('no inverse');
102
+ };
103
+
104
+ const B256 = 2n ** 256n;
105
+
106
+ class Point {
107
+ static BASE;
108
+ static ZERO;
109
+ X; Y; Z; T;
110
+ constructor(X, Y, Z, T) {
111
+ const max = B256;
112
+ this.X = assertRange(X, 0n, max);
113
+ this.Y = assertRange(Y, 0n, max);
114
+ this.Z = assertRange(Z, 1n, max);
115
+ this.T = assertRange(T, 0n, max);
116
+ Object.freeze(this);
117
+ }
118
+ static CURVE() { return ed25519_CURVE; }
119
+
120
+ static fromAffine(p) { return new Point(p.x, p.y, 1n, M(p.x * p.y)); }
121
+
122
+ static fromBytes(hex, zip215 = false) {
123
+ const d = _d;
124
+ const normed = u8fr(abytes(hex, L));
125
+ const lastByte = hex[31];
126
+ normed[31] = lastByte & ~0x80;
127
+ const y = bytesToNumLE(normed);
128
+ const max = zip215 ? B256 : P;
129
+ assertRange(y, 0n, max);
130
+ const y2 = M(y * y);
131
+ const u = M(y2 - 1n);
132
+ const v = M(d * y2 + 1n);
133
+ let { isValid, value: x } = uvRatio(u, v);
134
+ if (!isValid) err('bad point: y not sqrt');
135
+ const isXOdd = (x & 1n) === 1n;
136
+ const isLastByteOdd = (lastByte & 0x80) !== 0;
137
+ if (!zip215 && x === 0n && isLastByteOdd) err('bad point: x==0, isLastByteOdd');
138
+ if (isLastByteOdd !== isXOdd) x = M(-x);
139
+ return new Point(x, y, 1n, M(x * y));
140
+ }
141
+ static fromHex(hex, zip215) { return Point.fromBytes(hexToBytes(hex), zip215); }
142
+ get x() { return this.toAffine().x; }
143
+ get y() { return this.toAffine().y; }
144
+
145
+ assertValidity() {
146
+ const a = _a;
147
+ const d = _d;
148
+ const p = this;
149
+ if (p.is0()) return err('bad point: ZERO');
150
+ const { X, Y, Z, T } = p;
151
+ const X2 = M(X * X);
152
+ const Y2 = M(Y * Y);
153
+ const Z2 = M(Z * Z);
154
+ const Z4 = M(Z2 * Z2);
155
+ const aX2 = M(X2 * a);
156
+ const left = M(Z2 * M(aX2 + Y2));
157
+ const right = M(Z4 + M(d * M(X2 * Y2)));
158
+ if (left !== right) return err('bad point: equation left != right (1)');
159
+ const XY = M(X * Y);
160
+ const ZT = M(Z * T);
161
+ if (XY !== ZT) return err('bad point: equation left != right (2)');
162
+ return this;
163
+ }
164
+
165
+ equals(other) {
166
+ const { X: X1, Y: Y1, Z: Z1 } = this;
167
+ const { X: X2, Y: Y2, Z: Z2 } = apoint(other);
168
+ const X1Z2 = M(X1 * Z2);
169
+ const X2Z1 = M(X2 * Z1);
170
+ const Y1Z2 = M(Y1 * Z2);
171
+ const Y2Z1 = M(Y2 * Z1);
172
+ return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
173
+ }
174
+ is0() { return this.equals(I); }
175
+ negate() { return new Point(M(-this.X), this.Y, this.Z, M(-this.T)); }
176
+
177
+ double() {
178
+ const { X: X1, Y: Y1, Z: Z1 } = this;
179
+ const a = _a;
180
+ const A = M(X1 * X1);
181
+ const B = M(Y1 * Y1);
182
+ const C = M(2n * M(Z1 * Z1));
183
+ const D = M(a * A);
184
+ const x1y1 = X1 + Y1;
185
+ const E = M(M(x1y1 * x1y1) - A - B);
186
+ const G = D + B;
187
+ const F = G - C;
188
+ const H = D - B;
189
+ const X3 = M(E * F);
190
+ const Y3 = M(G * H);
191
+ const T3 = M(E * H);
192
+ const Z3 = M(F * G);
193
+ return new Point(X3, Y3, Z3, T3);
194
+ }
195
+
196
+ add(other) {
197
+ const { X: X1, Y: Y1, Z: Z1, T: T1 } = this;
198
+ const { X: X2, Y: Y2, Z: Z2, T: T2 } = apoint(other);
199
+ const a = _a;
200
+ const d = _d;
201
+ const A = M(X1 * X2);
202
+ const B = M(Y1 * Y2);
203
+ const C = M(T1 * d * T2);
204
+ const D = M(Z1 * Z2);
205
+ const E = M((X1 + Y1) * (X2 + Y2) - A - B);
206
+ const F = M(D - C);
207
+ const G = M(D + C);
208
+ const H = M(B - a * A);
209
+ const X3 = M(E * F);
210
+ const Y3 = M(G * H);
211
+ const T3 = M(E * H);
212
+ const Z3 = M(F * G);
213
+ return new Point(X3, Y3, Z3, T3);
214
+ }
215
+ subtract(other) { return this.add(apoint(other).negate()); }
216
+
217
+ multiply(n, safe = true) {
218
+ if (!safe && (n === 0n || this.is0())) return I;
219
+ assertRange(n, 1n, N);
220
+ if (n === 1n) return this;
221
+ if (this.equals(G)) return wNAF(n).p;
222
+ let p = I;
223
+ let f = G;
224
+ for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
225
+ if (n & 1n) p = p.add(d);
226
+ else if (safe) f = f.add(d);
227
+ }
228
+ return p;
229
+ }
230
+ multiplyUnsafe(scalar) { return this.multiply(scalar, false); }
231
+
232
+ toAffine() {
233
+ const { X, Y, Z } = this;
234
+ if (this.equals(I)) return { x: 0n, y: 1n };
235
+ const iz = invert(Z, P);
236
+ if (M(Z * iz) !== 1n) err('invalid inverse');
237
+ const x = M(X * iz);
238
+ const y = M(Y * iz);
239
+ return { x, y };
240
+ }
241
+ toBytes() {
242
+ const { x, y } = this.assertValidity().toAffine();
243
+ const b = numTo32bLE(y);
244
+ b[31] |= x & 1n ? 0x80 : 0;
245
+ return b;
246
+ }
247
+ toHex() { return bytesToHex(this.toBytes()); }
248
+ clearCofactor() { return this.multiply(big(h), false); }
249
+ isSmallOrder() { return this.clearCofactor().is0(); }
250
+ isTorsionFree() {
251
+ let p = this.multiply(N / 2n, false).double();
252
+ if (N % 2n) p = p.add(this);
253
+ return p.is0();
254
+ }
255
+ }
256
+
257
+ const G = new Point(Gx, Gy, 1n, M(Gx * Gy));
258
+ const I = new Point(0n, 1n, 1n, 0n);
259
+ Point.BASE = G;
260
+ Point.ZERO = I;
261
+
262
+ const numTo32bLE = (num) => hexToBytes(padh(assertRange(num, 0n, B256), L2)).reverse();
263
+ const bytesToNumLE = (b) => big('0x' + bytesToHex(u8fr(abytes(b)).reverse()));
264
+
265
+ const pow2 = (x, power) => {
266
+ let r = x;
267
+ while (power-- > 0n) { r *= r; r %= P; }
268
+ return r;
269
+ };
270
+
271
+ const pow_2_252_3 = (x) => {
272
+ const x2 = (x * x) % P;
273
+ const b2 = (x2 * x) % P;
274
+ const b4 = (pow2(b2, 2n) * b2) % P;
275
+ const b5 = (pow2(b4, 1n) * x) % P;
276
+ const b10 = (pow2(b5, 5n) * b5) % P;
277
+ const b20 = (pow2(b10, 10n) * b10) % P;
278
+ const b40 = (pow2(b20, 20n) * b20) % P;
279
+ const b80 = (pow2(b40, 40n) * b40) % P;
280
+ const b160 = (pow2(b80, 80n) * b80) % P;
281
+ const b240 = (pow2(b160, 80n) * b80) % P;
282
+ const b250 = (pow2(b240, 10n) * b10) % P;
283
+ const pow_p_5_8 = (pow2(b250, 2n) * x) % P;
284
+ return { pow_p_5_8, b2 };
285
+ };
286
+
287
+ const RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n;
288
+
289
+ const uvRatio = (u, v) => {
290
+ const v3 = M(v * v * v);
291
+ const v7 = M(v3 * v3 * v);
292
+ const pow = pow_2_252_3(u * v7).pow_p_5_8;
293
+ let x = M(u * v3 * pow);
294
+ const vx2 = M(v * x * x);
295
+ const root1 = x;
296
+ const root2 = M(x * RM1);
297
+ const useRoot1 = vx2 === u;
298
+ const useRoot2 = vx2 === M(-u);
299
+ const noRoot = vx2 === M(-u * RM1);
300
+ if (useRoot1) x = root1;
301
+ if (useRoot2 || noRoot) x = root2;
302
+ if ((M(x) & 1n) === 1n) x = M(-x);
303
+ return { isValid: useRoot1 || useRoot2, value: x };
304
+ };
305
+
306
+ const modL_LE = (hash) => modN(bytesToNumLE(hash));
307
+
308
+ const callHash = (name) => {
309
+ const fn = hashes[name];
310
+ if (typeof fn !== 'function') err('hashes.' + name + ' not set');
311
+ return fn;
312
+ };
313
+ const sha512a = (...m) => hashes.sha512Async(concatBytes(...m));
314
+ const sha512s = (...m) => callHash('sha512')(concatBytes(...m));
315
+
316
+ const hash2extK = (hashed) => {
317
+ const head = hashed.slice(0, L);
318
+ head[0] &= 248;
319
+ head[31] &= 127;
320
+ head[31] |= 64;
321
+ const prefix = hashed.slice(L, L2);
322
+ const scalar = modL_LE(head);
323
+ const point = G.multiply(scalar);
324
+ const pointBytes = point.toBytes();
325
+ return { head, prefix, scalar, point, pointBytes };
326
+ };
327
+
328
+ const apoint = (p) => (p instanceof Point ? p : err('Point expected'));
329
+
330
+ const getExtendedPublicKeyAsync = (secretKey) => sha512a(abytes(secretKey, L)).then(hash2extK);
331
+ const getExtendedPublicKey = (secretKey) => hash2extK(sha512s(abytes(secretKey, L)));
332
+ const getPublicKeyAsync = (secretKey) => getExtendedPublicKeyAsync(secretKey).then((p) => p.pointBytes);
333
+ const getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes;
334
+
335
+ const hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
336
+ const hashFinishS = (res) => res.finish(sha512s(res.hashable));
337
+
338
+ const defaultVerifyOpts = { zip215: true };
339
+ const _verify = (sig, msg, pub, opts = defaultVerifyOpts) => {
340
+ sig = abytes(sig, L2);
341
+ msg = abytes(msg);
342
+ pub = abytes(pub, L);
343
+ const { zip215 } = opts;
344
+ let A; let R; let s; let SB;
345
+ let hashable = Uint8Array.of();
346
+ try {
347
+ A = Point.fromBytes(pub, zip215);
348
+ R = Point.fromBytes(sig.slice(0, L), zip215);
349
+ s = bytesToNumLE(sig.slice(L, L2));
350
+ SB = G.multiply(s, false);
351
+ hashable = concatBytes(R.toBytes(), A.toBytes(), msg);
352
+ }
353
+ catch (error) { }
354
+ const finish = (hashed) => {
355
+ if (SB == null) return false;
356
+ if (!zip215 && A.isSmallOrder()) return false;
357
+ const k = modL_LE(hashed);
358
+ const RkA = R.add(A.multiply(k, false));
359
+ return RkA.add(SB.negate()).clearCofactor().is0();
360
+ };
361
+ return { hashable, finish };
362
+ };
363
+
364
+ const verifyAsync = async (signature, message, publicKey, opts = defaultVerifyOpts) => hashFinishA(_verify(signature, message, publicKey, opts));
365
+
366
+ const etc = {
367
+ bytesToHex: bytesToHex,
368
+ hexToBytes: hexToBytes,
369
+ concatBytes: concatBytes,
370
+ mod: M,
371
+ invert: invert,
372
+ randomBytes: randomBytes,
373
+ };
374
+
375
+ const hashes = {
376
+ sha512Async: async (message) => {
377
+ const s = subtle();
378
+ const m = concatBytes(message);
379
+ return u8n(await s.digest('SHA-512', m.buffer));
380
+ },
381
+ sha512: undefined,
382
+ };
383
+
384
+ const W = 8;
385
+ const scalarBits = 256;
386
+ const pwindows = Math.ceil(scalarBits / W) + 1;
387
+ const pwindowSize = 2 ** (W - 1);
388
+ const precompute = () => {
389
+ const points = [];
390
+ let p = G;
391
+ let b = p;
392
+ for (let w = 0; w < pwindows; w++) {
393
+ b = p;
394
+ points.push(b);
395
+ for (let i = 1; i < pwindowSize; i++) {
396
+ b = b.add(p);
397
+ points.push(b);
398
+ }
399
+ p = b.double();
400
+ }
401
+ return points;
402
+ };
403
+ let Gpows = undefined;
404
+ const ctneg = (cnd, p) => {
405
+ const n = p.negate();
406
+ return cnd ? n : p;
407
+ };
408
+ const wNAF = (n) => {
409
+ const comp = Gpows || (Gpows = precompute());
410
+ let p = I;
411
+ let f = G;
412
+ const pow_2_w = 2 ** W;
413
+ const maxNum = pow_2_w;
414
+ const mask = big(pow_2_w - 1);
415
+ const shiftBy = big(W);
416
+ for (let w = 0; w < pwindows; w++) {
417
+ let wbits = Number(n & mask);
418
+ n >>= shiftBy;
419
+ if (wbits > pwindowSize) {
420
+ wbits -= maxNum;
421
+ n += 1n;
422
+ }
423
+ const off = w * pwindowSize;
424
+ const offF = off;
425
+ const offP = off + Math.abs(wbits) - 1;
426
+ const isEven = w % 2 !== 0;
427
+ const isNeg = wbits < 0;
428
+ if (wbits === 0) {
429
+ f = f.add(ctneg(isEven, comp[offF]));
430
+ }
431
+ else {
432
+ p = p.add(ctneg(isNeg, comp[offP]));
433
+ }
434
+ }
435
+ return { p, f };
436
+ };
437
+
438
+ return { verifyAsync, etc };
439
+ })();
440
+
441
+ // ==========================================
442
+ // EPI Viewer Verification Logic
443
+ // ==========================================
444
+
445
+ async function verifyManifestSignature(manifest) {
446
+ console.log("Verifying manifest signature...", manifest);
447
+
448
+ // 1. Check if signature exists
449
+ if (!manifest.signature) {
450
+ console.warn("No signature found");
451
+ return { valid: false, reason: "No signature" };
452
+ }
453
+
454
+ // 2. Parse signature string "ed25519:<name>:<hex>"
455
+ const parts = manifest.signature.split(':');
456
+ if (parts.length !== 3 || parts[0] !== 'ed25519') {
457
+ console.error("Invalid signature format");
458
+ return { valid: false, reason: "Invalid format" };
459
+ }
460
+
461
+ const keyName = parts[1];
462
+ const sigHex = parts[2];
463
+
464
+ // 3. Get Public Key
465
+ if (!manifest.public_key) {
466
+ console.warn("Manifest missing public_key field for verification");
467
+ return { valid: false, reason: "Missing Public Key" };
468
+ }
469
+
470
+ const pubKeyBytes = noble.etc.hexToBytes(manifest.public_key);
471
+
472
+ // 4. Compute Canonical JSON Hash of Manifest (excluding signature)
473
+ const manifestCopy = JSON.parse(JSON.stringify(manifest));
474
+ delete manifestCopy.signature;
475
+
476
+ // Recursive canonical JSON stringify
477
+ const canonicalJson = (obj) => {
478
+ if (Array.isArray(obj)) {
479
+ return '[' + obj.map(canonicalJson).join(',') + ']';
480
+ } else if (typeof obj === 'object' && obj !== null) {
481
+ const keys = Object.keys(obj).sort();
482
+ let result = '{';
483
+ for (let i = 0; i < keys.length; i++) {
484
+ const key = keys[i];
485
+ if (i > 0) result += ',';
486
+ result += JSON.stringify(key) + ':' + canonicalJson(obj[key]);
487
+ }
488
+ result += '}';
489
+ return result;
490
+ } else {
491
+ return JSON.stringify(obj);
492
+ }
493
+ };
494
+
495
+ const jsonString = canonicalJson(manifestCopy);
496
+ const msgBytes = new TextEncoder().encode(jsonString);
497
+
498
+ try {
499
+ // 5. Verify Hash
500
+ // The backend signs the SHA-256 hash of the content.
501
+ // So we must convert content -> SHA-256 hash -> verify against signature
502
+ const hashBuffer = await crypto.subtle.digest('SHA-256', msgBytes);
503
+ const hashArray = new Uint8Array(hashBuffer);
504
+
505
+ const sigBytes = noble.etc.hexToBytes(sigHex);
506
+
507
+ const isValid = await noble.verifyAsync(sigBytes, hashArray, pubKeyBytes);
508
+
509
+ if (isValid) {
510
+ return { valid: true, reason: "Cryptographically verified, including Public Key integrity" };
511
+ } else {
512
+ return { valid: false, reason: "Signature mismatch" };
513
+ }
514
+ } catch (e) {
515
+ return { valid: false, reason: e.message };
516
+ }
517
+ }