@zanii/blackbox 0.14.0 → 0.15.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/README.md +6 -0
- package/dist/cli.js +40 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/otlp/protobuf.d.ts +4 -0
- package/dist/otlp/protobuf.js +167 -0
- package/dist/shares/index.d.ts +8 -0
- package/dist/shares/index.js +84 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,6 +83,12 @@ verifyAnswerCredential(credential, lines, bodies, packs); // { ok, problems }: t
|
|
|
83
83
|
|
|
84
84
|
The same checks run offline as pure functions: `toolHallucinations`, `groundingFindings`, `factsIn`, `loadReferencePacks`, `answerRisk`, `detectorAccuracy`.
|
|
85
85
|
|
|
86
|
+
## New in 0.15.0
|
|
87
|
+
|
|
88
|
+
- M-of-N shares: `splitSecret` / `combineShares`, and `blackbox key-split --m 3 --n 5` / `blackbox key-combine`, so no single custodian holds the gateway's key passphrase.
|
|
89
|
+
- `otlpFromProtobuf(bytes)`: OTLP/HTTP protobuf spans, read without a protobuf library (gateway v0.17 takes them on `/v1/traces`).
|
|
90
|
+
- Works with gateway v0.17: the passphrase from Google Cloud KMS or Azure Key Vault, retention per tenant, closed-session summaries kept, the trace exported while a session runs.
|
|
91
|
+
|
|
86
92
|
## New in 0.14.0
|
|
87
93
|
|
|
88
94
|
- Price uplifts (`uplifts` in a price table): US-only inference, regional endpoints and fast modes priced above list, per deployment (`upliftPercent`).
|
package/dist/cli.js
CHANGED
|
@@ -53,7 +53,9 @@ const USAGE = `usage (spec/cli.md):
|
|
|
53
53
|
blackbox reconcile-system --connector <connector.json> --log <export.csv|jsonl> [--pack <pack.json>] <session_id>
|
|
54
54
|
blackbox workspace-receipt (a Claude Code PreToolUse / PostToolUse hook; reads the hook JSON on stdin)
|
|
55
55
|
blackbox attest (a Claude Code PostToolUse hook: spec/attestation.md §4)
|
|
56
|
-
blackbox policy-hook (a Claude Code / Codex PreToolUse hook: asks the gateway's policy; fails closed)
|
|
56
|
+
blackbox policy-hook (a Claude Code / Codex PreToolUse hook: asks the gateway's policy; fails closed)
|
|
57
|
+
blackbox key-split --m <M> --n <N> < secret (M-of-N shares of a secret, one per line: spec/operations.md §4b)
|
|
58
|
+
blackbox key-combine <share> <share> … (the secret back, on stdout)`;
|
|
57
59
|
async function main(argv) {
|
|
58
60
|
const [command, ...rest] = argv;
|
|
59
61
|
if (command === "mcp")
|
|
@@ -70,6 +72,10 @@ async function main(argv) {
|
|
|
70
72
|
return attestCommand();
|
|
71
73
|
if (command === "policy-hook")
|
|
72
74
|
return policyHookCommand();
|
|
75
|
+
if (command === "key-split")
|
|
76
|
+
return keySplitCommand(rest);
|
|
77
|
+
if (command === "key-combine")
|
|
78
|
+
return keyCombineCommand(rest);
|
|
73
79
|
if (command === "verify")
|
|
74
80
|
return verifyCommand(rest);
|
|
75
81
|
if (command === "export")
|
|
@@ -218,6 +224,39 @@ async function attestCommand() {
|
|
|
218
224
|
}
|
|
219
225
|
return 0;
|
|
220
226
|
}
|
|
227
|
+
/** spec/operations.md §4b: the secret on stdin, as M-of-N shares on stdout, one a line. */
|
|
228
|
+
async function keySplitCommand(rest) {
|
|
229
|
+
const { flags } = parse(rest, []);
|
|
230
|
+
const m = Number(flags.m);
|
|
231
|
+
const n = Number(flags.n);
|
|
232
|
+
const parts = [];
|
|
233
|
+
for await (const chunk of process.stdin)
|
|
234
|
+
parts.push(chunk);
|
|
235
|
+
const secret = Buffer.from(Buffer.concat(parts)
|
|
236
|
+
.toString("utf8")
|
|
237
|
+
.replace(/\r?\n$/, ""), "utf8");
|
|
238
|
+
try {
|
|
239
|
+
const { splitSecret } = await import("./shares/index.js");
|
|
240
|
+
process.stdout.write(`${splitSecret(secret, m, n).join("\n")}\n`);
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
process.stderr.write(`blackbox key-split: ${error instanceof Error ? error.message : error}\n`);
|
|
245
|
+
return 2;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** spec/operations.md §4b: M shares (arguments) back to the secret, on stdout. */
|
|
249
|
+
async function keyCombineCommand(rest) {
|
|
250
|
+
try {
|
|
251
|
+
const { combineShares } = await import("./shares/index.js");
|
|
252
|
+
process.stdout.write(`${Buffer.from(combineShares(rest)).toString("utf8")}\n`);
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
process.stderr.write(`blackbox key-combine: ${error instanceof Error ? error.message : error}\n`);
|
|
257
|
+
return 2;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
221
260
|
/** spec/policy.md §2a: a PreToolUse hook. Deny: exit 2, the reason on stderr (the agent sees it).
|
|
222
261
|
* Ask: Claude Code's `permissionDecision: "ask"` on stdout. Allow: exit 0, nothing printed. Any
|
|
223
262
|
* failure denies, unless BLACKBOX_HOOK_FAIL_OPEN=1. */
|
package/dist/index.d.ts
CHANGED
|
@@ -39,10 +39,11 @@ export { MANAGED_END, MANAGED_START, managedBlock, protectedEdit } from "./hooks
|
|
|
39
39
|
export { type Investigation, investigation, renderInvestigation, } from "./investigate/index.ts";
|
|
40
40
|
export { type McpWrapOptions, mcpWrap } from "./mcp-wrap/index.ts";
|
|
41
41
|
export { commitContent, memoryChain, memoryEntry, verifyMemoryContent, verifyMemoryEntry, } from "./memory/index.ts";
|
|
42
|
-
export { type AmountUnit, anthropicStatement, type Baseline, baselines, type Connector, checkConnector, checkStatement, csvStatement, customStatement, decimalToMicroUsd, fuel, modelSpend, openaiStatement, pageParams, parseXml, reconcileBilling, requestFor, resolvePath, type Statement, } from "./money/index.ts";
|
|
42
|
+
export { type AmountUnit, anthropicStatement, type Baseline, baselines, type Connector, checkConnector, checkStatement, csvStatement, customStatement, decimalToMicroUsd, fuel, fxRate, fxUnit, modelSpend, openaiStatement, pageParams, parseXml, reconcileBilling, requestFor, resolvePath, type Statement, } from "./money/index.ts";
|
|
43
43
|
export { deadlinesDue, filingPack, type OccurrenceFacts, type OccurrenceFramework, type OccurrenceReport, occurrenceFacts, occurrenceReport, renderOccurrence, } from "./occurrence/index.ts";
|
|
44
44
|
export { ingestOcsf, type OcsfEvent, type OcsfVersion, ocsfLine, toOcsf } from "./ocsf/index.ts";
|
|
45
45
|
export { fromOtlp, type OtlpEvent, toOtlp } from "./otlp/index.ts";
|
|
46
|
+
export { otlpFromProtobuf } from "./otlp/protobuf.ts";
|
|
46
47
|
export { checkIdentifier, checkPack, corePack, type PackEntry, packData, packFrameworks, } from "./packs/index.ts";
|
|
47
48
|
export { type PolicyChange, type PolicyDelta, policyDelta } from "./policy/delta.ts";
|
|
48
49
|
export { type Draft, type DraftGroup, policyDrafts } from "./policy/drafts.ts";
|
|
@@ -62,6 +63,7 @@ export { ORPHAN_TEXT, repairToolCalls } from "./replay/repair.ts";
|
|
|
62
63
|
export { checkSearch, type SearchMatch, type SearchQuery, searchEvents } from "./search/index.ts";
|
|
63
64
|
export { type DrainResult, drainSpools } from "./session/drain.ts";
|
|
64
65
|
export { type AnswerCheck, BlackboxSession, checkEgress, type EgressCheck, type FlightPlan, type LlmCallIds, type SessionError, type SessionOptions, type SessionState, type SessionStats, session, stableEventId, } from "./session/index.ts";
|
|
66
|
+
export { combineShares, splitSecret } from "./shares/index.ts";
|
|
65
67
|
export { assembleSla, assessCompliance, buildSlaBody, signSla, slaHash, verifySla, } from "./sla/index.ts";
|
|
66
68
|
export { assembleSuccession, buildSuccessionBody, type Succession, type SuccessionBody, type SuccessionReason, signSuccession, successionHash, verifyLineage, verifySuccession, } from "./succession/index.ts";
|
|
67
69
|
export { type TimestampReport, timestampRequest, verifyTimestamp, } from "./timestamp/index.ts";
|
package/dist/index.js
CHANGED
|
@@ -41,10 +41,11 @@ export { MANAGED_END, MANAGED_START, managedBlock, protectedEdit } from "./hooks
|
|
|
41
41
|
export { investigation, renderInvestigation, } from "./investigate/index.js";
|
|
42
42
|
export { mcpWrap } from "./mcp-wrap/index.js";
|
|
43
43
|
export { commitContent, memoryChain, memoryEntry, verifyMemoryContent, verifyMemoryEntry, } from "./memory/index.js";
|
|
44
|
-
export { anthropicStatement, baselines, checkConnector, checkStatement, csvStatement, customStatement, decimalToMicroUsd, fuel, modelSpend, openaiStatement, pageParams, parseXml, reconcileBilling, requestFor, resolvePath, } from "./money/index.js";
|
|
44
|
+
export { anthropicStatement, baselines, checkConnector, checkStatement, csvStatement, customStatement, decimalToMicroUsd, fuel, fxRate, fxUnit, modelSpend, openaiStatement, pageParams, parseXml, reconcileBilling, requestFor, resolvePath, } from "./money/index.js";
|
|
45
45
|
export { deadlinesDue, filingPack, occurrenceFacts, occurrenceReport, renderOccurrence, } from "./occurrence/index.js";
|
|
46
46
|
export { ingestOcsf, ocsfLine, toOcsf } from "./ocsf/index.js";
|
|
47
47
|
export { fromOtlp, toOtlp } from "./otlp/index.js";
|
|
48
|
+
export { otlpFromProtobuf } from "./otlp/protobuf.js";
|
|
48
49
|
export { checkIdentifier, checkPack, corePack, packData, packFrameworks, } from "./packs/index.js";
|
|
49
50
|
export { policyDelta } from "./policy/delta.js";
|
|
50
51
|
export { policyDrafts } from "./policy/drafts.js";
|
|
@@ -64,6 +65,7 @@ export { ORPHAN_TEXT, repairToolCalls } from "./replay/repair.js";
|
|
|
64
65
|
export { checkSearch, searchEvents } from "./search/index.js";
|
|
65
66
|
export { drainSpools } from "./session/drain.js";
|
|
66
67
|
export { BlackboxSession, checkEgress, session, stableEventId, } from "./session/index.js";
|
|
68
|
+
export { combineShares, splitSecret } from "./shares/index.js";
|
|
67
69
|
export { assembleSla, assessCompliance, buildSlaBody, signSla, slaHash, verifySla, } from "./sla/index.js";
|
|
68
70
|
export { assembleSuccession, buildSuccessionBody, signSuccession, successionHash, verifyLineage, verifySuccession, } from "./succession/index.js";
|
|
69
71
|
export { timestampRequest, verifyTimestamp, } from "./timestamp/index.js";
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// OTLP/HTTP protobuf (spec/otlp.md §4): an ExportTraceServiceRequest's bytes as the OTLP/JSON
|
|
2
|
+
// shape `fromOtlp` reads. A reader for just the trace messages, no protobuf library: wire types
|
|
3
|
+
// varint, 64-bit, length-delimited and 32-bit; unknown fields are skipped. Mirrors
|
|
4
|
+
// sdks/python/src/zanii_blackbox/otlp_protobuf.py; pinned by spec/vectors/otlp-protobuf.json.
|
|
5
|
+
function fields(buf) {
|
|
6
|
+
const out = [];
|
|
7
|
+
let i = 0;
|
|
8
|
+
const varint = () => {
|
|
9
|
+
let r = 0n;
|
|
10
|
+
let s = 0n;
|
|
11
|
+
for (;;) {
|
|
12
|
+
if (i >= buf.length)
|
|
13
|
+
throw new Error("protobuf: a varint runs past the end");
|
|
14
|
+
const b = buf[i++];
|
|
15
|
+
r |= BigInt(b & 0x7f) << s;
|
|
16
|
+
if (!(b & 0x80))
|
|
17
|
+
return r;
|
|
18
|
+
s += 7n;
|
|
19
|
+
if (s > 63n)
|
|
20
|
+
throw new Error("protobuf: a varint over 64 bits");
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const fixed = (size) => {
|
|
24
|
+
if (i + size > buf.length)
|
|
25
|
+
throw new Error("protobuf: a fixed field runs past the end");
|
|
26
|
+
let v = 0n;
|
|
27
|
+
for (let k = size - 1; k >= 0; k--)
|
|
28
|
+
v = (v << 8n) | BigInt(buf[i + k]);
|
|
29
|
+
i += size;
|
|
30
|
+
return v;
|
|
31
|
+
};
|
|
32
|
+
while (i < buf.length) {
|
|
33
|
+
const key = Number(varint());
|
|
34
|
+
const n = key >>> 3;
|
|
35
|
+
const wire = key & 7;
|
|
36
|
+
if (wire === 0)
|
|
37
|
+
out.push({ n, wire, v: varint() });
|
|
38
|
+
else if (wire === 1)
|
|
39
|
+
out.push({ n, wire, v: fixed(8) });
|
|
40
|
+
else if (wire === 5)
|
|
41
|
+
out.push({ n, wire, v: fixed(4) });
|
|
42
|
+
else if (wire === 2) {
|
|
43
|
+
const len = Number(varint());
|
|
44
|
+
if (i + len > buf.length)
|
|
45
|
+
throw new Error("protobuf: a field runs past the end");
|
|
46
|
+
out.push({ n, wire, v: buf.subarray(i, i + len) });
|
|
47
|
+
i += len;
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
throw new Error(`protobuf: wire type ${wire} isn't supported`);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
const text = (b) => new TextDecoder().decode(b);
|
|
55
|
+
const hex = (b) => Buffer.from(b).toString("hex");
|
|
56
|
+
const bytes = (f) => f.v;
|
|
57
|
+
const big = (f) => f.v;
|
|
58
|
+
const float64 = (bits) => {
|
|
59
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
60
|
+
view.setBigUint64(0, bits, true);
|
|
61
|
+
return view.getFloat64(0, true);
|
|
62
|
+
};
|
|
63
|
+
function anyValue(b) {
|
|
64
|
+
for (const f of fields(b)) {
|
|
65
|
+
if (f.n === 1 && f.wire === 2)
|
|
66
|
+
return { stringValue: text(bytes(f)) };
|
|
67
|
+
if (f.n === 2 && f.wire === 0)
|
|
68
|
+
return { boolValue: big(f) !== 0n };
|
|
69
|
+
if (f.n === 3 && f.wire === 0)
|
|
70
|
+
return { intValue: BigInt.asIntN(64, big(f)).toString() };
|
|
71
|
+
if (f.n === 4 && f.wire === 1)
|
|
72
|
+
return { doubleValue: float64(big(f)) };
|
|
73
|
+
if (f.n === 5 && f.wire === 2)
|
|
74
|
+
return {
|
|
75
|
+
arrayValue: {
|
|
76
|
+
values: fields(bytes(f))
|
|
77
|
+
.filter((x) => x.n === 1 && x.wire === 2)
|
|
78
|
+
.map((x) => anyValue(bytes(x))),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
if (f.n === 6 && f.wire === 2)
|
|
82
|
+
return { kvlistValue: { values: keyValues(bytes(f), 1) } };
|
|
83
|
+
if (f.n === 7 && f.wire === 2)
|
|
84
|
+
return { bytesValue: Buffer.from(bytes(f)).toString("base64") };
|
|
85
|
+
}
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
/** The repeated KeyValue at field `at` of a message. */
|
|
89
|
+
function keyValues(b, at) {
|
|
90
|
+
return fields(b)
|
|
91
|
+
.filter((f) => f.n === at && f.wire === 2)
|
|
92
|
+
.map((f) => {
|
|
93
|
+
let key = "";
|
|
94
|
+
let value = {};
|
|
95
|
+
for (const g of fields(bytes(f))) {
|
|
96
|
+
if (g.n === 1 && g.wire === 2)
|
|
97
|
+
key = text(bytes(g));
|
|
98
|
+
if (g.n === 2 && g.wire === 2)
|
|
99
|
+
value = anyValue(bytes(g));
|
|
100
|
+
}
|
|
101
|
+
return { key, value };
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function span(b) {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const f of fields(b)) {
|
|
107
|
+
if (f.n === 1 && f.wire === 2)
|
|
108
|
+
out.traceId = hex(bytes(f));
|
|
109
|
+
else if (f.n === 2 && f.wire === 2)
|
|
110
|
+
out.spanId = hex(bytes(f));
|
|
111
|
+
else if (f.n === 4 && f.wire === 2 && bytes(f).length)
|
|
112
|
+
out.parentSpanId = hex(bytes(f));
|
|
113
|
+
else if (f.n === 5 && f.wire === 2)
|
|
114
|
+
out.name = text(bytes(f));
|
|
115
|
+
else if (f.n === 6 && f.wire === 0)
|
|
116
|
+
out.kind = Number(big(f));
|
|
117
|
+
else if (f.n === 7 && f.wire === 1)
|
|
118
|
+
out.startTimeUnixNano = big(f).toString();
|
|
119
|
+
else if (f.n === 8 && f.wire === 1)
|
|
120
|
+
out.endTimeUnixNano = big(f).toString();
|
|
121
|
+
else if (f.n === 15 && f.wire === 2) {
|
|
122
|
+
const status = {};
|
|
123
|
+
for (const g of fields(bytes(f))) {
|
|
124
|
+
if (g.n === 2 && g.wire === 2)
|
|
125
|
+
status.message = text(bytes(g));
|
|
126
|
+
if (g.n === 3 && g.wire === 0)
|
|
127
|
+
status.code = Number(big(g));
|
|
128
|
+
}
|
|
129
|
+
out.status = status;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
out.attributes = keyValues(b, 9);
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
/** spec/otlp.md §4: an ExportTraceServiceRequest's bytes as OTLP/JSON. Throws on bad bytes. */
|
|
136
|
+
export function otlpFromProtobuf(buf) {
|
|
137
|
+
return {
|
|
138
|
+
resourceSpans: fields(buf)
|
|
139
|
+
.filter((f) => f.n === 1 && f.wire === 2)
|
|
140
|
+
.map((rs) => {
|
|
141
|
+
const out = { scopeSpans: [] };
|
|
142
|
+
for (const f of fields(bytes(rs))) {
|
|
143
|
+
if (f.n === 1 && f.wire === 2)
|
|
144
|
+
out.resource = { attributes: keyValues(bytes(f), 1) };
|
|
145
|
+
if (f.n === 2 && f.wire === 2) {
|
|
146
|
+
const ss = { spans: [] };
|
|
147
|
+
for (const g of fields(bytes(f))) {
|
|
148
|
+
if (g.n === 1 && g.wire === 2) {
|
|
149
|
+
const scope = {};
|
|
150
|
+
for (const h of fields(bytes(g))) {
|
|
151
|
+
if (h.n === 1 && h.wire === 2)
|
|
152
|
+
scope.name = text(bytes(h));
|
|
153
|
+
if (h.n === 2 && h.wire === 2)
|
|
154
|
+
scope.version = text(bytes(h));
|
|
155
|
+
}
|
|
156
|
+
ss.scope = scope;
|
|
157
|
+
}
|
|
158
|
+
if (g.n === 2 && g.wire === 2)
|
|
159
|
+
ss.spans.push(span(bytes(g)));
|
|
160
|
+
}
|
|
161
|
+
out.scopeSpans.push(ss);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits `secret` into `n` shares, any `m` of which rebuild it: `zks1-<m>-<x>-<check>-<hex>`,
|
|
3
|
+
* where `check` (8 hex of the secret's SHA-256) catches a share from another secret. `random`
|
|
4
|
+
* gives the polynomial's coefficients (the tests pass fixed ones; never do that for real).
|
|
5
|
+
*/
|
|
6
|
+
export declare function splitSecret(secret: Uint8Array, m: number, n: number, random?: (count: number) => Uint8Array): string[];
|
|
7
|
+
/** Rebuilds a secret from at least `m` of its shares; throws if they're too few or don't fit. */
|
|
8
|
+
export declare function combineShares(shares: readonly string[]): Uint8Array;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// M-of-N secret shares (spec/operations.md §4b): Shamir's scheme over GF(256), byte by byte, so any
|
|
2
|
+
// M of N custodians can rebuild a secret (the key passphrase) and fewer learn nothing about it.
|
|
3
|
+
// Mirrors sdks/python/src/zanii_blackbox/shares.py; pinned by spec/vectors/shares.json.
|
|
4
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
+
// GF(256) with the AES polynomial x^8 + x^4 + x^3 + x + 1, generator 3
|
|
6
|
+
const EXP = new Uint8Array(512);
|
|
7
|
+
const LOG = new Uint8Array(256);
|
|
8
|
+
{
|
|
9
|
+
let x = 1;
|
|
10
|
+
for (let i = 0; i < 255; i++) {
|
|
11
|
+
EXP[i] = x;
|
|
12
|
+
LOG[x] = i;
|
|
13
|
+
x ^= (x << 1) ^ (x & 0x80 ? 0x11b : 0);
|
|
14
|
+
x &= 0xff;
|
|
15
|
+
}
|
|
16
|
+
for (let i = 255; i < 512; i++)
|
|
17
|
+
EXP[i] = EXP[i - 255];
|
|
18
|
+
}
|
|
19
|
+
const mul = (a, b) => a === 0 || b === 0 ? 0 : EXP[LOG[a] + LOG[b]];
|
|
20
|
+
const div = (a, b) => a === 0 ? 0 : EXP[LOG[a] + 255 - LOG[b]];
|
|
21
|
+
const check = (secret) => createHash("sha256").update(secret).digest("hex").slice(0, 8);
|
|
22
|
+
/**
|
|
23
|
+
* Splits `secret` into `n` shares, any `m` of which rebuild it: `zks1-<m>-<x>-<check>-<hex>`,
|
|
24
|
+
* where `check` (8 hex of the secret's SHA-256) catches a share from another secret. `random`
|
|
25
|
+
* gives the polynomial's coefficients (the tests pass fixed ones; never do that for real).
|
|
26
|
+
*/
|
|
27
|
+
export function splitSecret(secret, m, n, random = (count) => new Uint8Array(randomBytes(count))) {
|
|
28
|
+
if (!Number.isInteger(m) || !Number.isInteger(n) || m < 2 || n < m || n > 255)
|
|
29
|
+
throw new Error("shares need 2 ≤ m ≤ n ≤ 255");
|
|
30
|
+
if (secret.length < 1 || secret.length > 4096)
|
|
31
|
+
throw new Error("a secret is 1-4096 bytes");
|
|
32
|
+
const ys = Array.from({ length: n }, () => new Uint8Array(secret.length));
|
|
33
|
+
for (let i = 0; i < secret.length; i++) {
|
|
34
|
+
const coefficients = [secret[i], ...random(m - 1)];
|
|
35
|
+
for (let x = 1; x <= n; x++) {
|
|
36
|
+
let y = 0;
|
|
37
|
+
for (let k = coefficients.length - 1; k >= 0; k--)
|
|
38
|
+
y = mul(y, x) ^ coefficients[k]; // Horner, in GF(256)
|
|
39
|
+
ys[x - 1][i] = y;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const c = check(secret);
|
|
43
|
+
return ys.map((y, i) => `zks1-${m}-${i + 1}-${c}-${Buffer.from(y).toString("hex")}`);
|
|
44
|
+
}
|
|
45
|
+
/** Rebuilds a secret from at least `m` of its shares; throws if they're too few or don't fit. */
|
|
46
|
+
export function combineShares(shares) {
|
|
47
|
+
const parsed = shares.map((s) => {
|
|
48
|
+
const p = /^zks1-(\d{1,3})-(\d{1,3})-([0-9a-f]{8})-((?:[0-9a-f]{2})+)$/.exec(s.trim());
|
|
49
|
+
if (!p)
|
|
50
|
+
throw new Error("not a share (zks1-<m>-<x>-<check>-<hex>)");
|
|
51
|
+
return {
|
|
52
|
+
m: Number(p[1]),
|
|
53
|
+
x: Number(p[2]),
|
|
54
|
+
check: p[3],
|
|
55
|
+
y: Buffer.from(p[4], "hex"),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
const first = parsed[0];
|
|
59
|
+
if (!first)
|
|
60
|
+
throw new Error("no shares");
|
|
61
|
+
if (parsed.some((p) => p.m !== first.m || p.check !== first.check || p.y.length !== first.y.length))
|
|
62
|
+
throw new Error("the shares aren't of one secret");
|
|
63
|
+
const unique = [...new Map(parsed.map((p) => [p.x, p])).values()];
|
|
64
|
+
if (unique.some((p) => p.x < 1 || p.x > 255))
|
|
65
|
+
throw new Error("a share's index is out of range");
|
|
66
|
+
if (unique.length < first.m)
|
|
67
|
+
throw new Error(`${first.m} shares are needed, ${unique.length} given`);
|
|
68
|
+
const use = unique.slice(0, first.m);
|
|
69
|
+
const secret = new Uint8Array(first.y.length);
|
|
70
|
+
for (let i = 0; i < secret.length; i++) {
|
|
71
|
+
let s = 0;
|
|
72
|
+
for (const [j, pj] of use.entries()) {
|
|
73
|
+
let l = 1; // the Lagrange basis at 0
|
|
74
|
+
for (const [k, pk] of use.entries())
|
|
75
|
+
if (k !== j)
|
|
76
|
+
l = mul(l, div(pk.x, pk.x ^ pj.x));
|
|
77
|
+
s ^= mul(pj.y[i], l);
|
|
78
|
+
}
|
|
79
|
+
secret[i] = s;
|
|
80
|
+
}
|
|
81
|
+
if (check(secret) !== first.check)
|
|
82
|
+
throw new Error("the shares don't fit together");
|
|
83
|
+
return secret;
|
|
84
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.15.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// The SDK version, on its own so the CLI can print it without loading the whole SDK.
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.15.1";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zanii/blackbox",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.1",
|
|
5
5
|
"description": "The flight recorder for AI agents: sessions, a zero-loss spool, framework hooks, approvals, and offline verification of the gateway's hash-chained record.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ai-agents",
|