@forestrie/forestrie-cli 0.9.1 → 0.11.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 +49 -3
- package/dist/cli.js +3515 -590
- package/dist/lib/decode-receipt-labels.d.ts +7 -6
- package/dist/lib/decode-receipt-labels.d.ts.map +1 -1
- package/dist/lib/decode-receipt-labels.js +11 -10
- package/dist/lib/decode-receipt-labels.js.map +1 -1
- package/package.json +5 -5
- package/src/command.ts +2 -0
- package/src/commands/fetch-ks256-attestation.ts +52 -0
- package/src/commands/verify-grant.ts +8 -2
- package/src/commands/verify.ts +8 -2
- package/src/lib/checkpoint-provider.ts +73 -30
- package/src/lib/complete-grant-derive.ts +1 -0
- package/src/lib/create-log-prepare.ts +3 -1
- package/src/lib/create-receipt-derive.ts +16 -0
- package/src/lib/decode-receipt-labels.ts +11 -10
- package/src/lib/delegate-flow.ts +4 -2
- package/src/lib/http-problem.ts +53 -0
- package/src/lib/known-log-key.ts +35 -0
- package/src/lib/resolve-receipt-freshen.ts +9 -11
- package/src/lib/verify-anchored.ts +1 -1
- package/src/lib/verify-checkpoint-chain.ts +2 -2
- package/src/lib/verify-eventscan.ts +2 -2
- package/src/lib/verify-inputs.ts +1 -1
- package/src/lib/verify-report.ts +78 -0
- package/src/main/fetch-ks256-attestation.ts +245 -0
- package/src/main/onboard-genesis.ts +3 -1
- package/src/main/onboard-request.ts +4 -2
- package/src/main/verify.ts +169 -60
- package/src/options/fetch-ks256-attestation.ts +60 -0
- package/src/options/verify.ts +34 -20
package/src/main/verify.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import type { Out } from "@forestrie/cli-kit/reporting";
|
|
3
3
|
import {
|
|
4
|
+
decodeKs256Attestation,
|
|
4
5
|
decodeTrustRootFromGenesis,
|
|
5
6
|
grantCommitmentHashFromGrant,
|
|
6
|
-
|
|
7
|
+
type Ks256Attestation,
|
|
7
8
|
verifyCheckpointChain,
|
|
8
9
|
verifyGrantReceiptOffline,
|
|
9
10
|
verifyGrantReceiptOfflineWithKeys,
|
|
@@ -38,8 +39,12 @@ import {
|
|
|
38
39
|
import {
|
|
39
40
|
loadPayloadVerifyArtifacts,
|
|
40
41
|
loadVerifyArtifacts,
|
|
42
|
+
readBytes,
|
|
41
43
|
} from "../lib/verify-inputs.js";
|
|
44
|
+
import { isKs256Root, parseKnownLogKey } from "../lib/known-log-key.js";
|
|
42
45
|
import {
|
|
46
|
+
applyKs256Rows,
|
|
47
|
+
type Ks256ReportInfo,
|
|
43
48
|
anchorOnlyStageRows,
|
|
44
49
|
buildVerifyReport,
|
|
45
50
|
knownKeyStageRows,
|
|
@@ -170,7 +175,9 @@ async function checkCheckpointChainAnchor(
|
|
|
170
175
|
): Promise<CheckpointChainAnchorCheck> {
|
|
171
176
|
const rootKeys: CryptoKey[] = [];
|
|
172
177
|
if (ctx.knownLogKey !== undefined) {
|
|
173
|
-
|
|
178
|
+
const known = await parseKnownLogKey(ctx.knownLogKey);
|
|
179
|
+
if (isKs256Root(known)) throw new Error(KS256_CHECKPOINT_CHAIN_REFUSAL);
|
|
180
|
+
rootKeys.push(known as CryptoKey);
|
|
174
181
|
}
|
|
175
182
|
if (genesisCbor !== undefined) {
|
|
176
183
|
const root = await decodeTrustRootFromGenesis(genesisCbor);
|
|
@@ -261,20 +268,48 @@ function printAnchorRow(
|
|
|
261
268
|
}
|
|
262
269
|
}
|
|
263
270
|
|
|
271
|
+
const KS256_CHECKPOINT_CHAIN_REFUSAL =
|
|
272
|
+
"--checkpoint-chain verifies checkpoint signatures, which need an ES256 trust root: a KS256 (20-byte address) known log key cannot root them";
|
|
273
|
+
|
|
264
274
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* label-1000 cert still resolves under it, so the anchor survives sealer
|
|
268
|
-
* rotation and the cert-validation path is exercised (FOR-297 D1).
|
|
275
|
+
* Load `--ks256-attestation` (decode failure is an input error). Undefined
|
|
276
|
+
* when the flag is absent.
|
|
269
277
|
*/
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
278
|
+
function loadAttestation(path: string | undefined): Ks256Attestation | undefined {
|
|
279
|
+
if (path === undefined) return undefined;
|
|
280
|
+
const bytes = readBytes(path, "--ks256-attestation");
|
|
281
|
+
try {
|
|
282
|
+
return decodeKs256Attestation(bytes);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
285
|
+
throw new Error(`--ks256-attestation '${path}' is not a valid attestation: ${message}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Root algorithm and attestation facts for the report. */
|
|
290
|
+
async function ks256Info(
|
|
291
|
+
options: VerifyReportContext & { ks256Attestation?: string | undefined },
|
|
292
|
+
genesisCbor: Uint8Array | undefined,
|
|
293
|
+
knownKey: Awaited<ReturnType<typeof parseKnownLogKey>> | undefined,
|
|
294
|
+
attestation: Ks256Attestation | undefined,
|
|
295
|
+
used: boolean,
|
|
296
|
+
): Promise<Ks256ReportInfo | undefined> {
|
|
297
|
+
let ks256Root = false;
|
|
298
|
+
if (knownKey !== undefined) {
|
|
299
|
+
ks256Root = isKs256Root(knownKey);
|
|
300
|
+
} else if (genesisCbor !== undefined) {
|
|
301
|
+
try {
|
|
302
|
+
ks256Root = isKs256Root(await decodeTrustRootFromGenesis(genesisCbor));
|
|
303
|
+
} catch {
|
|
304
|
+
// The offline verify already reported an unreadable genesis.
|
|
305
|
+
}
|
|
276
306
|
}
|
|
277
|
-
|
|
307
|
+
if (!ks256Root && attestation === undefined) return undefined;
|
|
308
|
+
return {
|
|
309
|
+
ks256Root,
|
|
310
|
+
knownKey: knownKey !== undefined,
|
|
311
|
+
...(attestation !== undefined ? { attestation, attestationUsed: used } : {}),
|
|
312
|
+
};
|
|
278
313
|
}
|
|
279
314
|
|
|
280
315
|
/**
|
|
@@ -485,6 +520,7 @@ async function finishVerify(
|
|
|
485
520
|
artifacts: { genesisCbor: Uint8Array | undefined; receiptCbor: Uint8Array },
|
|
486
521
|
result: ReceiptVerifyResult,
|
|
487
522
|
anchorLeaf?: AnchorLeaf,
|
|
523
|
+
ks256?: Ks256ReportInfo,
|
|
488
524
|
): Promise<void> {
|
|
489
525
|
// FOR-297 approach C: in a chain-anchored mode (live read OR cached
|
|
490
526
|
// known-accumulator snapshot), a delegation the offline verifier cannot
|
|
@@ -516,7 +552,11 @@ async function finishVerify(
|
|
|
516
552
|
// checkpoint-chain checks always work from the recomputed peak.
|
|
517
553
|
let recomputedPeak: Uint8Array | undefined;
|
|
518
554
|
if (
|
|
519
|
-
|
|
555
|
+
// A KS256 root cannot locate a detached peak by signature trial
|
|
556
|
+
// (no_es256_trust_key), so it always works from the recomputed peak.
|
|
557
|
+
(ctx.knownLogKey !== undefined ||
|
|
558
|
+
ctx.anchor !== "chain" ||
|
|
559
|
+
ks256?.ks256Root === true) &&
|
|
520
560
|
anchorLeaf !== undefined
|
|
521
561
|
) {
|
|
522
562
|
const recomputed = await recomputeReceiptPeak({
|
|
@@ -595,8 +635,9 @@ async function finishVerify(
|
|
|
595
635
|
|
|
596
636
|
const ok =
|
|
597
637
|
result.ok && (ctx.anchor === "offline" || anchor?.anchored === true);
|
|
598
|
-
const
|
|
638
|
+
const baseRows =
|
|
599
639
|
ctx.knownLogKey !== undefined ? knownKeyStageRows(result) : stageRows(result);
|
|
640
|
+
const rows = ks256 !== undefined ? applyKs256Rows(baseRows, ks256) : baseRows;
|
|
600
641
|
|
|
601
642
|
if (ctx.json) {
|
|
602
643
|
const report = buildVerifyReport({
|
|
@@ -613,7 +654,10 @@ async function finishVerify(
|
|
|
613
654
|
anchor,
|
|
614
655
|
univocity: ctx.univocity,
|
|
615
656
|
logId: ctx.logId,
|
|
616
|
-
...(ctx.knownLogKey !== undefined
|
|
657
|
+
...(ctx.knownLogKey !== undefined || ks256 !== undefined
|
|
658
|
+
? { stagesOverride: rows }
|
|
659
|
+
: {}),
|
|
660
|
+
...(ks256 !== undefined ? { ks256 } : {}),
|
|
617
661
|
});
|
|
618
662
|
out.out(JSON.stringify(report, null, 2));
|
|
619
663
|
} else {
|
|
@@ -674,46 +718,89 @@ export async function runVerify(
|
|
|
674
718
|
options: VerifyOptions,
|
|
675
719
|
): Promise<void> {
|
|
676
720
|
let artifacts: ReturnType<typeof loadPayloadVerifyArtifacts>;
|
|
721
|
+
let attestation: Ks256Attestation | undefined;
|
|
722
|
+
let knownKey: Awaited<ReturnType<typeof parseKnownLogKey>> | undefined;
|
|
677
723
|
try {
|
|
678
724
|
artifacts = loadPayloadVerifyArtifacts(options);
|
|
725
|
+
attestation = loadAttestation(options.ks256Attestation);
|
|
726
|
+
knownKey = await parseKnownKeyForRun(options);
|
|
679
727
|
} catch (err) {
|
|
680
728
|
reportRunError(out, options, "input", err);
|
|
681
729
|
return;
|
|
682
730
|
}
|
|
683
731
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
result = remapKnownKeyFailure(
|
|
690
|
-
await verifyReceiptOfflineWithKeys({
|
|
732
|
+
const run = (att: Ks256Attestation | undefined) =>
|
|
733
|
+
knownKey !== undefined
|
|
734
|
+
? // FOR-297 D1: caller-known log owner key replaces the genesis roots.
|
|
735
|
+
// Genesis, if also given, only supplies the forest chain id.
|
|
736
|
+
verifyReceiptOfflineWithKeys({
|
|
691
737
|
receiptCbor: artifacts.receiptCbor,
|
|
692
738
|
payload: artifacts.payload,
|
|
693
739
|
idtimestampBe8: artifacts.idtimestampBe8,
|
|
694
740
|
trustKeys: [knownKey],
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
741
|
+
...(att !== undefined ? { ks256Attestation: att } : {}),
|
|
742
|
+
...(artifacts.genesisCbor !== undefined
|
|
743
|
+
? { forestGenesisCbor: artifacts.genesisCbor }
|
|
744
|
+
: {}),
|
|
745
|
+
}).then(remapKnownKeyFailure)
|
|
746
|
+
: // Parsing guarantees --genesis when --known-log-key is absent.
|
|
747
|
+
verifyReceiptOffline({
|
|
748
|
+
receiptCbor: artifacts.receiptCbor,
|
|
749
|
+
payload: artifacts.payload,
|
|
750
|
+
idtimestampBe8: artifacts.idtimestampBe8,
|
|
751
|
+
genesisCbor: artifacts.genesisCbor!,
|
|
752
|
+
...(att !== undefined ? { ks256Attestation: att } : {}),
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
let result: ReceiptVerifyResult;
|
|
756
|
+
let ks256: Ks256ReportInfo | undefined;
|
|
757
|
+
try {
|
|
758
|
+
result = await run(attestation);
|
|
759
|
+
ks256 = await ks256Info(
|
|
760
|
+
options,
|
|
761
|
+
artifacts.genesisCbor,
|
|
762
|
+
knownKey,
|
|
763
|
+
attestation,
|
|
764
|
+
// The attestation was needed only if the certificate does not verify
|
|
765
|
+
// by ecrecover alone.
|
|
766
|
+
attestation !== undefined && result.ok ? !(await run(undefined)).ok : false,
|
|
767
|
+
);
|
|
704
768
|
} catch (err) {
|
|
705
769
|
reportRunError(out, options, "verify", err);
|
|
706
770
|
return;
|
|
707
771
|
}
|
|
708
772
|
|
|
709
|
-
await finishVerify(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
773
|
+
await finishVerify(
|
|
774
|
+
out,
|
|
775
|
+
options,
|
|
776
|
+
artifacts,
|
|
777
|
+
result,
|
|
778
|
+
{
|
|
779
|
+
idtimestampBe8: artifacts.idtimestampBe8,
|
|
780
|
+
inner: async () =>
|
|
781
|
+
new Uint8Array(
|
|
782
|
+
// Copy pins the generic to ArrayBuffer for the dom BufferSource type.
|
|
783
|
+
await crypto.subtle.digest("SHA-256", new Uint8Array(artifacts.payload)),
|
|
784
|
+
),
|
|
785
|
+
},
|
|
786
|
+
ks256,
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Parse `--known-log-key`, refusing a KS256 key where the chosen anchor mode
|
|
792
|
+
* needs ES256 checkpoint signatures.
|
|
793
|
+
*/
|
|
794
|
+
async function parseKnownKeyForRun(options: {
|
|
795
|
+
knownLogKey: string | undefined;
|
|
796
|
+
anchor: string;
|
|
797
|
+
}): Promise<Awaited<ReturnType<typeof parseKnownLogKey>> | undefined> {
|
|
798
|
+
if (options.knownLogKey === undefined) return undefined;
|
|
799
|
+
const key = await parseKnownLogKey(options.knownLogKey);
|
|
800
|
+
if (isKs256Root(key) && options.anchor === "checkpoints") {
|
|
801
|
+
throw new Error(KS256_CHECKPOINT_CHAIN_REFUSAL);
|
|
802
|
+
}
|
|
803
|
+
return key;
|
|
717
804
|
}
|
|
718
805
|
|
|
719
806
|
/**
|
|
@@ -728,40 +815,62 @@ export async function runVerifyGrant(
|
|
|
728
815
|
options: VerifyGrantOptions,
|
|
729
816
|
): Promise<void> {
|
|
730
817
|
let artifacts: ReturnType<typeof loadVerifyArtifacts>;
|
|
818
|
+
let attestation: Ks256Attestation | undefined;
|
|
819
|
+
let knownKey: Awaited<ReturnType<typeof parseKnownLogKey>> | undefined;
|
|
731
820
|
try {
|
|
732
821
|
artifacts = loadVerifyArtifacts(options);
|
|
822
|
+
attestation = loadAttestation(options.ks256Attestation);
|
|
823
|
+
knownKey = await parseKnownKeyForRun(options);
|
|
733
824
|
} catch (err) {
|
|
734
825
|
reportRunError(out, options, "input", err);
|
|
735
826
|
return;
|
|
736
827
|
}
|
|
737
828
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
// FOR-297 D1: caller-known log owner key replaces the genesis roots.
|
|
742
|
-
const knownKey = await importKnownLogKey(options.knownLogKey);
|
|
743
|
-
result = remapKnownKeyFailure(
|
|
744
|
-
await verifyGrantReceiptOfflineWithKeys({
|
|
829
|
+
const run = (att: Ks256Attestation | undefined) =>
|
|
830
|
+
knownKey !== undefined
|
|
831
|
+
? verifyGrantReceiptOfflineWithKeys({
|
|
745
832
|
receiptCbor: artifacts.receiptCbor,
|
|
746
833
|
grant: artifacts.grant,
|
|
747
834
|
idtimestampBe8: artifacts.idtimestampBe8,
|
|
748
835
|
trustKeys: [knownKey],
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
836
|
+
...(att !== undefined ? { ks256Attestation: att } : {}),
|
|
837
|
+
...(artifacts.genesisCbor !== undefined
|
|
838
|
+
? { forestGenesisCbor: artifacts.genesisCbor }
|
|
839
|
+
: {}),
|
|
840
|
+
}).then(remapKnownKeyFailure)
|
|
841
|
+
: verifyGrantReceiptOffline({
|
|
842
|
+
receiptCbor: artifacts.receiptCbor,
|
|
843
|
+
grant: artifacts.grant,
|
|
844
|
+
idtimestampBe8: artifacts.idtimestampBe8,
|
|
845
|
+
genesisCbor: artifacts.genesisCbor!,
|
|
846
|
+
...(att !== undefined ? { ks256Attestation: att } : {}),
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
let result: ReceiptVerifyResult;
|
|
850
|
+
let ks256: Ks256ReportInfo | undefined;
|
|
851
|
+
try {
|
|
852
|
+
result = await run(attestation);
|
|
853
|
+
ks256 = await ks256Info(
|
|
854
|
+
options,
|
|
855
|
+
artifacts.genesisCbor,
|
|
856
|
+
knownKey,
|
|
857
|
+
attestation,
|
|
858
|
+
attestation !== undefined && result.ok ? !(await run(undefined)).ok : false,
|
|
859
|
+
);
|
|
758
860
|
} catch (err) {
|
|
759
861
|
reportRunError(out, options, "verify", err);
|
|
760
862
|
return;
|
|
761
863
|
}
|
|
762
864
|
|
|
763
|
-
await finishVerify(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
865
|
+
await finishVerify(
|
|
866
|
+
out,
|
|
867
|
+
options,
|
|
868
|
+
artifacts,
|
|
869
|
+
result,
|
|
870
|
+
{
|
|
871
|
+
idtimestampBe8: artifacts.idtimestampBe8,
|
|
872
|
+
inner: () => grantCommitmentHashFromGrant(artifacts.grant),
|
|
873
|
+
},
|
|
874
|
+
ks256,
|
|
875
|
+
);
|
|
767
876
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { LooseParsedArgs } from "@forestrie/cli-kit";
|
|
2
|
+
import {
|
|
3
|
+
optionalStringOption,
|
|
4
|
+
parseForestrieCommonOptions,
|
|
5
|
+
requiredStringOption,
|
|
6
|
+
type ForestrieCommonOptions,
|
|
7
|
+
} from "./common.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `forestrie fetch-ks256-attestation` (plan-2610-03 D4): ask a KS256 contract
|
|
11
|
+
* root (a Safe) over ERC-1271 whether it signs the receipt's delegation
|
|
12
|
+
* certificate, and cache the block-bound answer as a `--ks256-attestation`
|
|
13
|
+
* artifact for offline `verify`. The twin of `fetch-accumulator`.
|
|
14
|
+
*/
|
|
15
|
+
export type FetchKs256AttestationOptions = ForestrieCommonOptions & {
|
|
16
|
+
/** Receipt whose label-1000 KS256 delegation certificate is attested. */
|
|
17
|
+
receipt: string;
|
|
18
|
+
/** Contract root address override (`0x` + 40 hex). */
|
|
19
|
+
root: string | undefined;
|
|
20
|
+
/** Forest genesis: supplies the KS256 root (unless `--root`) and chain id. */
|
|
21
|
+
genesis: string | undefined;
|
|
22
|
+
/** JSON-RPC endpoint (`RPC_URL`) — the trusted chain reader. */
|
|
23
|
+
rpcUrl: string;
|
|
24
|
+
/** Block to pin the calls to; `latest` resolves to the current block. */
|
|
25
|
+
block: bigint | "latest";
|
|
26
|
+
/** Output path for the attestation CBOR. */
|
|
27
|
+
out: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function parseFetchKs256AttestationOptions(
|
|
31
|
+
args: LooseParsedArgs,
|
|
32
|
+
): FetchKs256AttestationOptions {
|
|
33
|
+
const root = optionalStringOption(args, "root");
|
|
34
|
+
const genesis = optionalStringOption(args, "genesis");
|
|
35
|
+
if (root === undefined && genesis === undefined) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"--root (contract address) and/or --genesis (KS256 bootstrap root) is required",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const blockRaw = optionalStringOption(args, "block") ?? "latest";
|
|
41
|
+
let block: bigint | "latest";
|
|
42
|
+
if (blockRaw === "latest") {
|
|
43
|
+
block = "latest";
|
|
44
|
+
} else if (/^[0-9]+$/.test(blockRaw)) {
|
|
45
|
+
block = BigInt(blockRaw);
|
|
46
|
+
} else {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"--block must be a non-negative decimal block number or 'latest'",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
...parseForestrieCommonOptions(args),
|
|
53
|
+
receipt: requiredStringOption(args, "receipt"),
|
|
54
|
+
root,
|
|
55
|
+
genesis,
|
|
56
|
+
rpcUrl: requiredStringOption(args, "rpc-url", "RPC_URL"),
|
|
57
|
+
block,
|
|
58
|
+
out: requiredStringOption(args, "out"),
|
|
59
|
+
};
|
|
60
|
+
}
|
package/src/options/verify.ts
CHANGED
|
@@ -23,22 +23,28 @@ import {
|
|
|
23
23
|
* Both add `--univocity --log-id --rpc-url` for the chain-anchored check (the
|
|
24
24
|
* only networked path).
|
|
25
25
|
*
|
|
26
|
-
* Trust
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
26
|
+
* Trust roots (protocol: spec/receipt-trust-model.md). They are alternatives,
|
|
27
|
+
* not an ordering — each answers a different subset of the four trust
|
|
28
|
+
* questions, and the report says which root was used:
|
|
29
|
+
* - `--known-log-key` — the known log key root: an owner key the caller holds
|
|
30
|
+
* out of band; offline, no genesis; the key↔log binding is asserted by the
|
|
31
|
+
* key's provenance, not proven; no split-view protection.
|
|
32
|
+
* - `--genesis` — the genesis root: the forest genesis document's bootstrap
|
|
33
|
+
* key, which roots the root log or a delegation directly under it. A deeper
|
|
34
|
+
* child log's authority needs the off-chain grant-chain walk, which no
|
|
35
|
+
* verifier implements (spec/implementation-status.md).
|
|
36
|
+
* - `--known-accumulator` — the known accumulator root, from a cached,
|
|
37
|
+
* auditable chain read: the contract-anchored state, fully offline;
|
|
38
|
+
* `--massif` (tiles) or `--consistency-proof` (portable top-up artifact)
|
|
39
|
+
* extends older receipts to a newer snapshot.
|
|
40
|
+
* - `--checkpoint-chain` — the checkpoint chain root: a retained fold of
|
|
41
|
+
* checkpoint objects, an authenticated accumulator at every retained seal
|
|
42
|
+
* from the public log store alone; signatures root in the genesis or known
|
|
43
|
+
* log key root.
|
|
44
|
+
* - `--rpc-url` — the known accumulator root from a live chain read, plus
|
|
45
|
+
* freshness (the RPC provider is itself a trusted chain reader); buried
|
|
46
|
+
* peaks resolve via the CheckpointPublished history scan (public chain
|
|
47
|
+
* data only).
|
|
42
48
|
*/
|
|
43
49
|
|
|
44
50
|
type AnchorFields = {
|
|
@@ -73,14 +79,21 @@ type AnchorFields = {
|
|
|
73
79
|
/** Lower bound for the CheckpointPublished history scan (FOR-368). */
|
|
74
80
|
fromBlock: bigint | undefined;
|
|
75
81
|
/**
|
|
76
|
-
* Caller-known log OWNER key (the delegation-cert issuer),
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
82
|
+
* Caller-known log OWNER key (the delegation-cert issuer), `KNOWN_LOG_KEY`
|
|
83
|
+
* — FOR-297 D1: base64 `x||y` (64-byte ES256 key), or a 20-byte KS256
|
|
84
|
+
* address (`0x` + 40 hex, or base64). With `--genesis` alongside, genesis
|
|
85
|
+
* supplies only the forest chain id an attestation must match. An offline trust anchor that
|
|
86
|
+
* replaces the genesis root: the known log key root of the trust model.
|
|
87
|
+
* It asserts (does not prove) the key↔log binding, and gives no
|
|
80
88
|
* grant-lifecycle visibility or split-view protection — the grant-chain
|
|
81
89
|
* walk (approach A) derives the binding; chain anchors add split-view.
|
|
82
90
|
*/
|
|
83
91
|
knownLogKey: string | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* ERC-1271 attestation file (`forestrie fetch-ks256-attestation`) for a
|
|
94
|
+
* KS256 contract root. Read and decoded by the runner; never fetches.
|
|
95
|
+
*/
|
|
96
|
+
ks256Attestation: string | undefined;
|
|
84
97
|
};
|
|
85
98
|
|
|
86
99
|
function parseAnchorFields(args: LooseParsedArgs): AnchorFields {
|
|
@@ -151,6 +164,7 @@ function parseAnchorFields(args: LooseParsedArgs): AnchorFields {
|
|
|
151
164
|
logId,
|
|
152
165
|
rpcUrl,
|
|
153
166
|
knownLogKey,
|
|
167
|
+
ks256Attestation: optionalStringOption(args, "ks256-attestation"),
|
|
154
168
|
knownAccumulator,
|
|
155
169
|
massif,
|
|
156
170
|
checkpointChain,
|