@zanii/train 0.1.0 → 0.2.1
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -16
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +33 -13
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2BA,yFAAyF;AACzF,UAAU,IAAI;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,mBAAmB,CAAC;IAC1B,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AA8BD,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,gBAAgB,CAa5G;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAExD;AAED,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,CAAC,EAAE,gBAAgB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAEzG;AAQD,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,GAAG,EAAE,IAAI,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,qBAAqB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAS3F;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,CAMjG;AAED,6FAA6F;AAC7F,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,EAAE,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAY3F;AAED,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,EAAE,CAAC,EAAE,gBAAgB,GAAG,OAAO,CASjG"}
|
package/dist/index.js
CHANGED
|
@@ -23,13 +23,25 @@
|
|
|
23
23
|
* The manifest commits refs (`hmac:v1:…` / `sha256:…`), never content.
|
|
24
24
|
*/
|
|
25
25
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
26
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
27
26
|
import { canonicalBytes, inclusionProof, jcsHash, leafHash, merkleRoot, verifyInclusion } from '@zanii/core';
|
|
27
|
+
// Refs must sort identically in JS (UTF-16 code units) and Python (code points), and sit
|
|
28
|
+
// strictly between the sentinels '' and '' (U+FFFF) — so: non-empty, BMP-only.
|
|
29
|
+
function assertRef(r) {
|
|
30
|
+
if (typeof r !== 'string' || r.length === 0)
|
|
31
|
+
throw new Error('refs must be non-empty strings');
|
|
32
|
+
for (const c of r) {
|
|
33
|
+
if (c.codePointAt(0) >= 0xffff)
|
|
34
|
+
throw new Error(`ref contains a code point >= U+FFFF (breaks the sorted-Merkle sentinel): ${JSON.stringify(r)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
28
37
|
/** Sorted, de-duplicated member refs → the adjacency leaves that get Merkle-committed. */
|
|
29
38
|
function pairsOf(refs) {
|
|
39
|
+
for (const r of refs)
|
|
40
|
+
assertRef(r);
|
|
30
41
|
const sorted = [...new Set(refs)].sort();
|
|
42
|
+
// the empty set is one full sentinel-to-sentinel gap — absence is trivially provable
|
|
31
43
|
if (sorted.length === 0)
|
|
32
|
-
return [];
|
|
44
|
+
return [{ lo: '', hi: '' }];
|
|
33
45
|
const pairs = [];
|
|
34
46
|
// sentinel-bounded gaps so the smallest/largest members are covered too
|
|
35
47
|
pairs.push({ lo: '', hi: sorted[0] });
|
|
@@ -48,7 +60,7 @@ export function buildManifest(fields) {
|
|
|
48
60
|
if (Number.isNaN(Date.parse(fields.createdAt)))
|
|
49
61
|
throw new Error('createdAt must be an ISO timestamp');
|
|
50
62
|
const pairs = pairsOf(fields.refs);
|
|
51
|
-
const root =
|
|
63
|
+
const root = bytesToHex(merkleRoot(pairs.map(pairLeaf)));
|
|
52
64
|
return {
|
|
53
65
|
v: 1,
|
|
54
66
|
type: 'training.manifest',
|
|
@@ -71,6 +83,7 @@ const inclusionPath = (index, leaves) => inclusionProof(index, leaves).map(bytes
|
|
|
71
83
|
const verifyPath = (leaf, index, size, path, root) => verifyInclusion(leaf, index, size, path.map(hexToBytes), root);
|
|
72
84
|
/** Build a proof that `ref` is ABSENT from the training set. Throws if it is present. */
|
|
73
85
|
export function proveAbsence(m, refs, ref) {
|
|
86
|
+
assertRef(ref);
|
|
74
87
|
const sorted = [...new Set(refs)].sort();
|
|
75
88
|
if (sorted.includes(ref))
|
|
76
89
|
throw new Error(`ref is PRESENT — cannot prove absence`);
|
|
@@ -92,22 +105,33 @@ export function proveMembership(m, refs, ref) {
|
|
|
92
105
|
}
|
|
93
106
|
/** Verify a subject-absence proof against a manifest — offline, no trust in the operator. */
|
|
94
107
|
export function verifyAbsence(proof, m) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
try {
|
|
109
|
+
const p = proof ?? {};
|
|
110
|
+
if (p.v !== 1 || p.type !== 'training.absence' || p.run_id !== m.run_id)
|
|
111
|
+
return false;
|
|
112
|
+
const gap = p.gap ?? {};
|
|
113
|
+
if (!(gap.lo < p.ref && p.ref < gap.hi))
|
|
114
|
+
return false; // the gap must actually cover the ref
|
|
115
|
+
if (gap.lo === gap.hi)
|
|
116
|
+
return false; // a member leaf is not a gap
|
|
117
|
+
const root = hexToBytes(m.root.replace('sha256:', ''));
|
|
118
|
+
return verifyPath(pairLeaf(gap), p.index, p.size, p.path, root);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false; // malformed proof (bad hex, missing fields) is a failed verification, not a crash
|
|
122
|
+
}
|
|
104
123
|
}
|
|
105
124
|
/** Verify a membership proof against a manifest. */
|
|
106
125
|
export function verifyMembership(proof, m) {
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
try {
|
|
127
|
+
const p = proof ?? {};
|
|
128
|
+
if (p.v !== 1 || p.type !== 'training.membership' || p.run_id !== m.run_id)
|
|
129
|
+
return false;
|
|
130
|
+
const root = hexToBytes(m.root.replace('sha256:', ''));
|
|
131
|
+
return verifyPath(pairLeaf({ lo: p.ref, hi: p.ref }), p.index, p.size, p.path, root);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
109
134
|
return false;
|
|
110
|
-
|
|
111
|
-
return verifyPath(pairLeaf({ lo: p.ref, hi: p.ref }), p.index, p.size, p.path, root);
|
|
135
|
+
}
|
|
112
136
|
}
|
|
113
137
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zanii/train",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Training-data provenance: hash a training set into a sorted-Merkle manifest, then PROVE a named subject's data was absent from a real training run - a non-membership proof anyone can check offline. 'It was never used to train a model' becomes a yes, not a promise. Proves what was recorded went in; cannot prove an off-manifest copy didn't (state the enclave boundary).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ledger.zanii.agency",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@noble/hashes": "^1.7.1",
|
|
35
|
-
"@zanii/core": "0.
|
|
35
|
+
"@zanii/core": "^0.5.2"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -p tsconfig.build.json",
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* The manifest commits refs (`hmac:v1:…` / `sha256:…`), never content.
|
|
24
24
|
*/
|
|
25
25
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
26
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
27
26
|
import { canonicalBytes, inclusionProof, jcsHash, leafHash, merkleRoot, verifyInclusion } from '@zanii/core';
|
|
28
27
|
|
|
29
28
|
/** One committed gap between two adjacent sorted refs. A member leaf has `lo === hi`. */
|
|
@@ -44,10 +43,21 @@ export interface TrainingManifest {
|
|
|
44
43
|
created_at: string;
|
|
45
44
|
}
|
|
46
45
|
|
|
46
|
+
// Refs must sort identically in JS (UTF-16 code units) and Python (code points), and sit
|
|
47
|
+
// strictly between the sentinels '' and '' (U+FFFF) — so: non-empty, BMP-only.
|
|
48
|
+
function assertRef(r: string): void {
|
|
49
|
+
if (typeof r !== 'string' || r.length === 0) throw new Error('refs must be non-empty strings');
|
|
50
|
+
for (const c of r) {
|
|
51
|
+
if (c.codePointAt(0)! >= 0xffff) throw new Error(`ref contains a code point >= U+FFFF (breaks the sorted-Merkle sentinel): ${JSON.stringify(r)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
/** Sorted, de-duplicated member refs → the adjacency leaves that get Merkle-committed. */
|
|
48
56
|
function pairsOf(refs: string[]): Pair[] {
|
|
57
|
+
for (const r of refs) assertRef(r);
|
|
49
58
|
const sorted = [...new Set(refs)].sort();
|
|
50
|
-
|
|
59
|
+
// the empty set is one full sentinel-to-sentinel gap — absence is trivially provable
|
|
60
|
+
if (sorted.length === 0) return [{ lo: '', hi: '' }];
|
|
51
61
|
const pairs: Pair[] = [];
|
|
52
62
|
// sentinel-bounded gaps so the smallest/largest members are covered too
|
|
53
63
|
pairs.push({ lo: '', hi: sorted[0]! });
|
|
@@ -66,7 +76,7 @@ export function buildManifest(fields: { runId: string; refs: string[]; createdAt
|
|
|
66
76
|
if (!fields.runId) throw new Error('runId is required');
|
|
67
77
|
if (Number.isNaN(Date.parse(fields.createdAt))) throw new Error('createdAt must be an ISO timestamp');
|
|
68
78
|
const pairs = pairsOf(fields.refs);
|
|
69
|
-
const root =
|
|
79
|
+
const root = bytesToHex(merkleRoot(pairs.map(pairLeaf)));
|
|
70
80
|
return {
|
|
71
81
|
v: 1,
|
|
72
82
|
type: 'training.manifest',
|
|
@@ -117,6 +127,7 @@ export interface MembershipProof {
|
|
|
117
127
|
|
|
118
128
|
/** Build a proof that `ref` is ABSENT from the training set. Throws if it is present. */
|
|
119
129
|
export function proveAbsence(m: TrainingManifest, refs: string[], ref: string): AbsenceProof {
|
|
130
|
+
assertRef(ref);
|
|
120
131
|
const sorted = [...new Set(refs)].sort();
|
|
121
132
|
if (sorted.includes(ref)) throw new Error(`ref is PRESENT — cannot prove absence`);
|
|
122
133
|
const pairs = pairsOf(refs);
|
|
@@ -137,18 +148,27 @@ export function proveMembership(m: TrainingManifest, refs: string[], ref: string
|
|
|
137
148
|
|
|
138
149
|
/** Verify a subject-absence proof against a manifest — offline, no trust in the operator. */
|
|
139
150
|
export function verifyAbsence(proof: AbsenceProof | undefined, m: TrainingManifest): boolean {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
151
|
+
try {
|
|
152
|
+
const p = proof ?? ({} as AbsenceProof);
|
|
153
|
+
if (p.v !== 1 || p.type !== 'training.absence' || p.run_id !== m.run_id) return false;
|
|
154
|
+
const gap = p.gap ?? ({} as Pair);
|
|
155
|
+
if (!(gap.lo < p.ref && p.ref < gap.hi)) return false; // the gap must actually cover the ref
|
|
156
|
+
if (gap.lo === gap.hi) return false; // a member leaf is not a gap
|
|
157
|
+
const root = hexToBytes(m.root.replace('sha256:', ''));
|
|
158
|
+
return verifyPath(pairLeaf(gap), p.index, p.size, p.path, root);
|
|
159
|
+
} catch {
|
|
160
|
+
return false; // malformed proof (bad hex, missing fields) is a failed verification, not a crash
|
|
161
|
+
}
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
/** Verify a membership proof against a manifest. */
|
|
149
165
|
export function verifyMembership(proof: MembershipProof | undefined, m: TrainingManifest): boolean {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
try {
|
|
167
|
+
const p = proof ?? ({} as MembershipProof);
|
|
168
|
+
if (p.v !== 1 || p.type !== 'training.membership' || p.run_id !== m.run_id) return false;
|
|
169
|
+
const root = hexToBytes(m.root.replace('sha256:', ''));
|
|
170
|
+
return verifyPath(pairLeaf({ lo: p.ref, hi: p.ref }), p.index, p.size, p.path, root);
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
154
174
|
}
|