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_cli/chat.py +193 -0
- epi_cli/main.py +7 -1
- epi_core/__init__.py +1 -1
- epi_core/container.py +10 -2
- epi_core/schemas.py +6 -1
- epi_core/serialize.py +38 -9
- epi_core/trust.py +10 -0
- epi_recorder/__init__.py +1 -1
- epi_recorder/patcher.py +110 -1
- epi_recorder-2.1.3.dist-info/METADATA +577 -0
- {epi_recorder-2.1.1.dist-info → epi_recorder-2.1.3.dist-info}/RECORD +17 -15
- {epi_recorder-2.1.1.dist-info → epi_recorder-2.1.3.dist-info}/WHEEL +1 -1
- epi_viewer_static/app.js +54 -20
- epi_viewer_static/crypto.js +517 -0
- epi_recorder-2.1.1.dist-info/METADATA +0 -159
- {epi_recorder-2.1.1.dist-info → epi_recorder-2.1.3.dist-info}/entry_points.txt +0 -0
- {epi_recorder-2.1.1.dist-info → epi_recorder-2.1.3.dist-info}/licenses/LICENSE +0 -0
- {epi_recorder-2.1.1.dist-info → epi_recorder-2.1.3.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
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
|
+
}
|