@bcts/provenance-mark 1.0.0-alpha.16 → 1.0.0-alpha.18
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 +1 -1
- package/dist/index.cjs +421 -383
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -106
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +122 -106
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +421 -383
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +417 -385
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -11
- package/src/envelope.ts +39 -153
- package/src/index.ts +3 -1
- package/src/mark-info.ts +1 -1
- package/src/mark.ts +107 -40
- package/src/validate.ts +39 -54
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { chacha20 } from "@noble/ciphers/chacha.js";
|
|
|
6
6
|
import { randomData } from "@bcts/rand";
|
|
7
7
|
import { PROVENANCE_MARK } from "@bcts/tags";
|
|
8
8
|
import { BytewordsStyle, UR, decodeBytewords, encodeBytemojisIdentifier, encodeBytewords, encodeBytewordsIdentifier } from "@bcts/uniform-resources";
|
|
9
|
-
import { Envelope } from "@bcts/envelope";
|
|
9
|
+
import { Envelope, FormatContext, registerTagsIn as registerTagsIn$1, withFormatContextMut } from "@bcts/envelope";
|
|
10
10
|
|
|
11
11
|
//#region rolldown:runtime
|
|
12
12
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
@@ -16,54 +16,54 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
16
16
|
/**
|
|
17
17
|
* Error types for Provenance Mark operations.
|
|
18
18
|
*/
|
|
19
|
-
let ProvenanceMarkErrorType = /* @__PURE__ */ function(ProvenanceMarkErrorType
|
|
19
|
+
let ProvenanceMarkErrorType = /* @__PURE__ */ function(ProvenanceMarkErrorType) {
|
|
20
20
|
/** Invalid Seed length */
|
|
21
|
-
ProvenanceMarkErrorType
|
|
21
|
+
ProvenanceMarkErrorType["InvalidSeedLength"] = "InvalidSeedLength";
|
|
22
22
|
/** Duplicate key */
|
|
23
|
-
ProvenanceMarkErrorType
|
|
23
|
+
ProvenanceMarkErrorType["DuplicateKey"] = "DuplicateKey";
|
|
24
24
|
/** Missing key */
|
|
25
|
-
ProvenanceMarkErrorType
|
|
25
|
+
ProvenanceMarkErrorType["MissingKey"] = "MissingKey";
|
|
26
26
|
/** Invalid key */
|
|
27
|
-
ProvenanceMarkErrorType
|
|
27
|
+
ProvenanceMarkErrorType["InvalidKey"] = "InvalidKey";
|
|
28
28
|
/** Extra keys */
|
|
29
|
-
ProvenanceMarkErrorType
|
|
29
|
+
ProvenanceMarkErrorType["ExtraKeys"] = "ExtraKeys";
|
|
30
30
|
/** Invalid key length for the given resolution */
|
|
31
|
-
ProvenanceMarkErrorType
|
|
31
|
+
ProvenanceMarkErrorType["InvalidKeyLength"] = "InvalidKeyLength";
|
|
32
32
|
/** Invalid next key length for the given resolution */
|
|
33
|
-
ProvenanceMarkErrorType
|
|
33
|
+
ProvenanceMarkErrorType["InvalidNextKeyLength"] = "InvalidNextKeyLength";
|
|
34
34
|
/** Invalid chain ID length for the given resolution */
|
|
35
|
-
ProvenanceMarkErrorType
|
|
35
|
+
ProvenanceMarkErrorType["InvalidChainIdLength"] = "InvalidChainIdLength";
|
|
36
36
|
/** Invalid message length for the given resolution */
|
|
37
|
-
ProvenanceMarkErrorType
|
|
37
|
+
ProvenanceMarkErrorType["InvalidMessageLength"] = "InvalidMessageLength";
|
|
38
38
|
/** Invalid CBOR data in info field */
|
|
39
|
-
ProvenanceMarkErrorType
|
|
39
|
+
ProvenanceMarkErrorType["InvalidInfoCbor"] = "InvalidInfoCbor";
|
|
40
40
|
/** Date out of range for serialization */
|
|
41
|
-
ProvenanceMarkErrorType
|
|
41
|
+
ProvenanceMarkErrorType["DateOutOfRange"] = "DateOutOfRange";
|
|
42
42
|
/** Invalid date components */
|
|
43
|
-
ProvenanceMarkErrorType
|
|
43
|
+
ProvenanceMarkErrorType["InvalidDate"] = "InvalidDate";
|
|
44
44
|
/** Missing required URL parameter */
|
|
45
|
-
ProvenanceMarkErrorType
|
|
45
|
+
ProvenanceMarkErrorType["MissingUrlParameter"] = "MissingUrlParameter";
|
|
46
46
|
/** Year out of range for 2-byte serialization */
|
|
47
|
-
ProvenanceMarkErrorType
|
|
47
|
+
ProvenanceMarkErrorType["YearOutOfRange"] = "YearOutOfRange";
|
|
48
48
|
/** Invalid month or day */
|
|
49
|
-
ProvenanceMarkErrorType
|
|
49
|
+
ProvenanceMarkErrorType["InvalidMonthOrDay"] = "InvalidMonthOrDay";
|
|
50
50
|
/** Resolution serialization error */
|
|
51
|
-
ProvenanceMarkErrorType
|
|
51
|
+
ProvenanceMarkErrorType["ResolutionError"] = "ResolutionError";
|
|
52
52
|
/** Bytewords encoding/decoding error */
|
|
53
|
-
ProvenanceMarkErrorType
|
|
53
|
+
ProvenanceMarkErrorType["BytewordsError"] = "BytewordsError";
|
|
54
54
|
/** CBOR encoding/decoding error */
|
|
55
|
-
ProvenanceMarkErrorType
|
|
55
|
+
ProvenanceMarkErrorType["CborError"] = "CborError";
|
|
56
56
|
/** URL parsing error */
|
|
57
|
-
ProvenanceMarkErrorType
|
|
57
|
+
ProvenanceMarkErrorType["UrlError"] = "UrlError";
|
|
58
58
|
/** Base64 decoding error */
|
|
59
|
-
ProvenanceMarkErrorType
|
|
59
|
+
ProvenanceMarkErrorType["Base64Error"] = "Base64Error";
|
|
60
60
|
/** JSON serialization error */
|
|
61
|
-
ProvenanceMarkErrorType
|
|
61
|
+
ProvenanceMarkErrorType["JsonError"] = "JsonError";
|
|
62
62
|
/** Integer conversion error */
|
|
63
|
-
ProvenanceMarkErrorType
|
|
63
|
+
ProvenanceMarkErrorType["IntegerConversionError"] = "IntegerConversionError";
|
|
64
64
|
/** Validation error */
|
|
65
|
-
ProvenanceMarkErrorType
|
|
66
|
-
return ProvenanceMarkErrorType
|
|
65
|
+
ProvenanceMarkErrorType["ValidationError"] = "ValidationError";
|
|
66
|
+
return ProvenanceMarkErrorType;
|
|
67
67
|
}({});
|
|
68
68
|
/**
|
|
69
69
|
* Error class for Provenance Mark operations.
|
|
@@ -258,12 +258,12 @@ function dateToDateString(date) {
|
|
|
258
258
|
* Resolution levels for provenance marks.
|
|
259
259
|
* Higher resolution provides more security but larger mark sizes.
|
|
260
260
|
*/
|
|
261
|
-
let ProvenanceMarkResolution = /* @__PURE__ */ function(ProvenanceMarkResolution
|
|
262
|
-
ProvenanceMarkResolution
|
|
263
|
-
ProvenanceMarkResolution
|
|
264
|
-
ProvenanceMarkResolution
|
|
265
|
-
ProvenanceMarkResolution
|
|
266
|
-
return ProvenanceMarkResolution
|
|
261
|
+
let ProvenanceMarkResolution = /* @__PURE__ */ function(ProvenanceMarkResolution) {
|
|
262
|
+
ProvenanceMarkResolution[ProvenanceMarkResolution["Low"] = 0] = "Low";
|
|
263
|
+
ProvenanceMarkResolution[ProvenanceMarkResolution["Medium"] = 1] = "Medium";
|
|
264
|
+
ProvenanceMarkResolution[ProvenanceMarkResolution["Quartile"] = 2] = "Quartile";
|
|
265
|
+
ProvenanceMarkResolution[ProvenanceMarkResolution["High"] = 3] = "High";
|
|
266
|
+
return ProvenanceMarkResolution;
|
|
267
267
|
}({});
|
|
268
268
|
/**
|
|
269
269
|
* Convert a resolution to its numeric value.
|
|
@@ -694,9 +694,9 @@ var ProvenanceSeed = class ProvenanceSeed {
|
|
|
694
694
|
/**
|
|
695
695
|
* Create a new seed using custom random data.
|
|
696
696
|
*/
|
|
697
|
-
static newUsing(randomData
|
|
698
|
-
if (randomData
|
|
699
|
-
return ProvenanceSeed.fromBytes(randomData
|
|
697
|
+
static newUsing(randomData) {
|
|
698
|
+
if (randomData.length < PROVENANCE_SEED_LENGTH) throw new ProvenanceMarkError(ProvenanceMarkErrorType.InvalidSeedLength, void 0, { actual: randomData.length });
|
|
699
|
+
return ProvenanceSeed.fromBytes(randomData.slice(0, PROVENANCE_SEED_LENGTH));
|
|
700
700
|
}
|
|
701
701
|
/**
|
|
702
702
|
* Create a new seed from a passphrase.
|
|
@@ -802,6 +802,279 @@ function fromBase64(base64) {
|
|
|
802
802
|
throw new Error("atob not available and require is not defined");
|
|
803
803
|
}
|
|
804
804
|
|
|
805
|
+
//#endregion
|
|
806
|
+
//#region src/validate.ts
|
|
807
|
+
/**
|
|
808
|
+
* Format for validation report output.
|
|
809
|
+
*/
|
|
810
|
+
let ValidationReportFormat = /* @__PURE__ */ function(ValidationReportFormat) {
|
|
811
|
+
/** Human-readable text format */
|
|
812
|
+
ValidationReportFormat["Text"] = "text";
|
|
813
|
+
/** Compact JSON format (no whitespace) */
|
|
814
|
+
ValidationReportFormat["JsonCompact"] = "json-compact";
|
|
815
|
+
/** Pretty-printed JSON format (with indentation) */
|
|
816
|
+
ValidationReportFormat["JsonPretty"] = "json-pretty";
|
|
817
|
+
return ValidationReportFormat;
|
|
818
|
+
}({});
|
|
819
|
+
/**
|
|
820
|
+
* Format a validation issue as a string.
|
|
821
|
+
*/
|
|
822
|
+
function formatValidationIssue(issue) {
|
|
823
|
+
switch (issue.type) {
|
|
824
|
+
case "HashMismatch": return `hash mismatch: expected ${issue.expected}, got ${issue.actual}`;
|
|
825
|
+
case "KeyMismatch": return "key mismatch: current hash was not generated from next key";
|
|
826
|
+
case "SequenceGap": return `sequence number gap: expected ${issue.expected}, got ${issue.actual}`;
|
|
827
|
+
case "DateOrdering": return `date must be equal or later: previous is ${issue.previous}, next is ${issue.next}`;
|
|
828
|
+
case "NonGenesisAtZero": return "non-genesis mark at sequence 0";
|
|
829
|
+
case "InvalidGenesisKey": return "genesis mark must have key equal to chain_id";
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Get the chain ID as a hex string for display.
|
|
834
|
+
*/
|
|
835
|
+
function chainIdHex(report) {
|
|
836
|
+
return hexEncode(report.chainId);
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Check if the validation report has any issues.
|
|
840
|
+
*/
|
|
841
|
+
function hasIssues(report) {
|
|
842
|
+
for (const chain of report.chains) if (!chain.hasGenesis) return true;
|
|
843
|
+
for (const chain of report.chains) for (const seq of chain.sequences) for (const mark of seq.marks) if (mark.issues.length > 0) return true;
|
|
844
|
+
if (report.chains.length > 1) return true;
|
|
845
|
+
if (report.chains.length === 1 && report.chains[0].sequences.length > 1) return true;
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Check if the validation report contains interesting information.
|
|
850
|
+
*/
|
|
851
|
+
function isInteresting(report) {
|
|
852
|
+
if (report.chains.length === 0) return false;
|
|
853
|
+
for (const chain of report.chains) if (!chain.hasGenesis) return true;
|
|
854
|
+
if (report.chains.length === 1) {
|
|
855
|
+
const chain = report.chains[0];
|
|
856
|
+
if (chain.sequences.length === 1) {
|
|
857
|
+
if (chain.sequences[0].marks.every((m) => m.issues.length === 0)) return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Format the validation report as human-readable text.
|
|
864
|
+
*/
|
|
865
|
+
function formatText(report) {
|
|
866
|
+
if (!isInteresting(report)) return "";
|
|
867
|
+
const lines = [];
|
|
868
|
+
lines.push(`Total marks: ${report.marks.length}`);
|
|
869
|
+
lines.push(`Chains: ${report.chains.length}`);
|
|
870
|
+
lines.push("");
|
|
871
|
+
for (let chainIdx = 0; chainIdx < report.chains.length; chainIdx++) {
|
|
872
|
+
const chain = report.chains[chainIdx];
|
|
873
|
+
const chainIdStr = chainIdHex(chain);
|
|
874
|
+
const shortChainId = chainIdStr.length > 8 ? chainIdStr.slice(0, 8) : chainIdStr;
|
|
875
|
+
lines.push(`Chain ${chainIdx + 1}: ${shortChainId}`);
|
|
876
|
+
if (!chain.hasGenesis) lines.push(" Warning: No genesis mark found");
|
|
877
|
+
for (const seq of chain.sequences) for (const flaggedMark of seq.marks) {
|
|
878
|
+
const mark = flaggedMark.mark;
|
|
879
|
+
const shortId = mark.identifier();
|
|
880
|
+
const seqNum = mark.seq();
|
|
881
|
+
const annotations = [];
|
|
882
|
+
if (mark.isGenesis()) annotations.push("genesis mark");
|
|
883
|
+
for (const issue of flaggedMark.issues) {
|
|
884
|
+
let issueStr;
|
|
885
|
+
switch (issue.type) {
|
|
886
|
+
case "SequenceGap":
|
|
887
|
+
issueStr = `gap: ${issue.expected} missing`;
|
|
888
|
+
break;
|
|
889
|
+
case "DateOrdering":
|
|
890
|
+
issueStr = `date ${issue.previous} < ${issue.next}`;
|
|
891
|
+
break;
|
|
892
|
+
case "HashMismatch":
|
|
893
|
+
issueStr = "hash mismatch";
|
|
894
|
+
break;
|
|
895
|
+
case "KeyMismatch":
|
|
896
|
+
issueStr = "key mismatch";
|
|
897
|
+
break;
|
|
898
|
+
case "NonGenesisAtZero":
|
|
899
|
+
issueStr = "non-genesis at seq 0";
|
|
900
|
+
break;
|
|
901
|
+
case "InvalidGenesisKey":
|
|
902
|
+
issueStr = "invalid genesis key";
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
annotations.push(issueStr);
|
|
906
|
+
}
|
|
907
|
+
if (annotations.length === 0) lines.push(` ${seqNum}: ${shortId}`);
|
|
908
|
+
else lines.push(` ${seqNum}: ${shortId} (${annotations.join(", ")})`);
|
|
909
|
+
}
|
|
910
|
+
lines.push("");
|
|
911
|
+
}
|
|
912
|
+
return lines.join("\n").trimEnd();
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Format the validation report.
|
|
916
|
+
*/
|
|
917
|
+
function formatReport(report, format) {
|
|
918
|
+
switch (format) {
|
|
919
|
+
case ValidationReportFormat.Text: return formatText(report);
|
|
920
|
+
case ValidationReportFormat.JsonCompact: return JSON.stringify(reportToJSON(report));
|
|
921
|
+
case ValidationReportFormat.JsonPretty: return JSON.stringify(reportToJSON(report), null, 2);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Convert a report to a JSON-serializable object.
|
|
926
|
+
*/
|
|
927
|
+
function reportToJSON(report) {
|
|
928
|
+
return {
|
|
929
|
+
marks: report.marks.map((m) => m.urString()),
|
|
930
|
+
chains: report.chains.map((chain) => ({
|
|
931
|
+
chain_id: hexEncode(chain.chainId),
|
|
932
|
+
has_genesis: chain.hasGenesis,
|
|
933
|
+
marks: chain.marks.map((m) => m.urString()),
|
|
934
|
+
sequences: chain.sequences.map((seq) => ({
|
|
935
|
+
start_seq: seq.startSeq,
|
|
936
|
+
end_seq: seq.endSeq,
|
|
937
|
+
marks: seq.marks.map((fm) => ({
|
|
938
|
+
mark: fm.mark.urString(),
|
|
939
|
+
issues: fm.issues.map(issueToJSON)
|
|
940
|
+
}))
|
|
941
|
+
}))
|
|
942
|
+
}))
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Convert a ValidationIssue to JSON matching Rust's serde format.
|
|
947
|
+
*
|
|
948
|
+
* Rust uses `#[serde(tag = "type", content = "data")]` which wraps
|
|
949
|
+
* struct variant data in a `"data"` field. Unit variants have no
|
|
950
|
+
* `"data"` field.
|
|
951
|
+
*/
|
|
952
|
+
function issueToJSON(issue) {
|
|
953
|
+
switch (issue.type) {
|
|
954
|
+
case "HashMismatch": return {
|
|
955
|
+
type: "HashMismatch",
|
|
956
|
+
data: {
|
|
957
|
+
expected: issue.expected,
|
|
958
|
+
actual: issue.actual
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
case "SequenceGap": return {
|
|
962
|
+
type: "SequenceGap",
|
|
963
|
+
data: {
|
|
964
|
+
expected: issue.expected,
|
|
965
|
+
actual: issue.actual
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
case "DateOrdering": return {
|
|
969
|
+
type: "DateOrdering",
|
|
970
|
+
data: {
|
|
971
|
+
previous: issue.previous,
|
|
972
|
+
next: issue.next
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
case "KeyMismatch": return { type: "KeyMismatch" };
|
|
976
|
+
case "NonGenesisAtZero": return { type: "NonGenesisAtZero" };
|
|
977
|
+
case "InvalidGenesisKey": return { type: "InvalidGenesisKey" };
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Build sequence bins for a chain.
|
|
982
|
+
*/
|
|
983
|
+
function buildSequenceBins(marks) {
|
|
984
|
+
const sequences = [];
|
|
985
|
+
let currentSequence = [];
|
|
986
|
+
for (let i = 0; i < marks.length; i++) {
|
|
987
|
+
const mark = marks[i];
|
|
988
|
+
if (i === 0) currentSequence.push({
|
|
989
|
+
mark,
|
|
990
|
+
issues: []
|
|
991
|
+
});
|
|
992
|
+
else {
|
|
993
|
+
const prev = marks[i - 1];
|
|
994
|
+
try {
|
|
995
|
+
prev.precedesOpt(mark);
|
|
996
|
+
currentSequence.push({
|
|
997
|
+
mark,
|
|
998
|
+
issues: []
|
|
999
|
+
});
|
|
1000
|
+
} catch (e) {
|
|
1001
|
+
if (currentSequence.length > 0) sequences.push(createSequenceReport(currentSequence));
|
|
1002
|
+
let issue;
|
|
1003
|
+
if (e instanceof ProvenanceMarkError && e.details?.["validationIssue"] !== void 0) issue = e.details["validationIssue"];
|
|
1004
|
+
else issue = { type: "KeyMismatch" };
|
|
1005
|
+
currentSequence = [{
|
|
1006
|
+
mark,
|
|
1007
|
+
issues: [issue]
|
|
1008
|
+
}];
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (currentSequence.length > 0) sequences.push(createSequenceReport(currentSequence));
|
|
1013
|
+
return sequences;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Create a sequence report from flagged marks.
|
|
1017
|
+
*/
|
|
1018
|
+
function createSequenceReport(marks) {
|
|
1019
|
+
return {
|
|
1020
|
+
startSeq: marks.length > 0 ? marks[0].mark.seq() : 0,
|
|
1021
|
+
endSeq: marks.length > 0 ? marks[marks.length - 1].mark.seq() : 0,
|
|
1022
|
+
marks
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Validate a collection of provenance marks.
|
|
1027
|
+
*/
|
|
1028
|
+
function validate(marks) {
|
|
1029
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1030
|
+
const deduplicatedMarks = [];
|
|
1031
|
+
for (const mark of marks) {
|
|
1032
|
+
const key = `${mark.res()}:${hexEncode(mark.message())}`;
|
|
1033
|
+
if (!seen.has(key)) {
|
|
1034
|
+
seen.add(key);
|
|
1035
|
+
deduplicatedMarks.push(mark);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
const chainBins = /* @__PURE__ */ new Map();
|
|
1039
|
+
for (const mark of deduplicatedMarks) {
|
|
1040
|
+
const chainIdKey = hexEncode(mark.chainId());
|
|
1041
|
+
const bin = chainBins.get(chainIdKey);
|
|
1042
|
+
if (bin !== void 0) bin.push(mark);
|
|
1043
|
+
else chainBins.set(chainIdKey, [mark]);
|
|
1044
|
+
}
|
|
1045
|
+
const chains = [];
|
|
1046
|
+
for (const [chainIdKey, chainMarks] of chainBins) {
|
|
1047
|
+
chainMarks.sort((a, b) => a.seq() - b.seq());
|
|
1048
|
+
const hasGenesis = chainMarks.length > 0 && chainMarks[0].seq() === 0 && chainMarks[0].isGenesis();
|
|
1049
|
+
const sequences = buildSequenceBins(chainMarks);
|
|
1050
|
+
chains.push({
|
|
1051
|
+
chainId: hexDecode(chainIdKey),
|
|
1052
|
+
hasGenesis,
|
|
1053
|
+
marks: chainMarks,
|
|
1054
|
+
sequences
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
chains.sort((a, b) => hexEncode(a.chainId).localeCompare(hexEncode(b.chainId)));
|
|
1058
|
+
return {
|
|
1059
|
+
marks: deduplicatedMarks,
|
|
1060
|
+
chains
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Helper function to encode bytes as hex.
|
|
1065
|
+
*/
|
|
1066
|
+
function hexEncode(bytes) {
|
|
1067
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Helper function to decode hex to bytes.
|
|
1071
|
+
*/
|
|
1072
|
+
function hexDecode(hex) {
|
|
1073
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
1074
|
+
for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
1075
|
+
return bytes;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
805
1078
|
//#endregion
|
|
806
1079
|
//#region src/mark.ts
|
|
807
1080
|
/**
|
|
@@ -953,6 +1226,33 @@ var ProvenanceMark = class ProvenanceMark {
|
|
|
953
1226
|
return prefix ? `\u{1F151} ${s}` : s;
|
|
954
1227
|
}
|
|
955
1228
|
/**
|
|
1229
|
+
* A compact 8-letter identifier derived from the upper-case ByteWords
|
|
1230
|
+
* identifier by taking the first and last letter of each ByteWords word
|
|
1231
|
+
* (4 words x 2 letters = 8 letters).
|
|
1232
|
+
*
|
|
1233
|
+
* Example: "ABLE ACID ALSO APEX" -> "AEADAOAX"
|
|
1234
|
+
* If prefix is true, prepends the provenance mark prefix character.
|
|
1235
|
+
*/
|
|
1236
|
+
bytewordsMinimalIdentifier(prefix) {
|
|
1237
|
+
const full = encodeBytewordsIdentifier(this._hash.slice(0, 4));
|
|
1238
|
+
const words = full.split(/\s+/);
|
|
1239
|
+
let out = "";
|
|
1240
|
+
if (words.length === 4) for (const w of words) {
|
|
1241
|
+
if (w.length === 0) continue;
|
|
1242
|
+
out += w[0].toUpperCase();
|
|
1243
|
+
out += w[w.length - 1].toUpperCase();
|
|
1244
|
+
}
|
|
1245
|
+
if (out.length !== 8) {
|
|
1246
|
+
out = "";
|
|
1247
|
+
const compact = full.replace(/[^a-zA-Z]/g, "").toUpperCase();
|
|
1248
|
+
for (let i = 0; i + 3 < compact.length; i += 4) {
|
|
1249
|
+
out += compact[i];
|
|
1250
|
+
out += compact[i + 3];
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return prefix ? `\u{1F151} ${out}` : out;
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
956
1256
|
* Get the first four bytes of the hash as Bytemoji.
|
|
957
1257
|
*/
|
|
958
1258
|
bytemojiIdentifier(prefix) {
|
|
@@ -972,18 +1272,39 @@ var ProvenanceMark = class ProvenanceMark {
|
|
|
972
1272
|
}
|
|
973
1273
|
/**
|
|
974
1274
|
* Check if this mark precedes another mark, throwing on validation errors.
|
|
1275
|
+
* Errors carry a structured `validationIssue` in their details, matching Rust's
|
|
1276
|
+
* `Error::Validation(ValidationIssue)` pattern.
|
|
975
1277
|
*/
|
|
976
1278
|
precedesOpt(next) {
|
|
977
|
-
if (next._seq === 0) throw new ProvenanceMarkError(ProvenanceMarkErrorType.ValidationError,
|
|
978
|
-
if (arraysEqual(next._key, next._chainId)) throw new ProvenanceMarkError(ProvenanceMarkErrorType.ValidationError,
|
|
979
|
-
if (this._seq !== next._seq - 1)
|
|
980
|
-
|
|
1279
|
+
if (next._seq === 0) throw new ProvenanceMarkError(ProvenanceMarkErrorType.ValidationError, "non-genesis mark at sequence 0", { validationIssue: { type: "NonGenesisAtZero" } });
|
|
1280
|
+
if (arraysEqual(next._key, next._chainId)) throw new ProvenanceMarkError(ProvenanceMarkErrorType.ValidationError, "genesis mark must have key equal to chain_id", { validationIssue: { type: "InvalidGenesisKey" } });
|
|
1281
|
+
if (this._seq !== next._seq - 1) {
|
|
1282
|
+
const issue = {
|
|
1283
|
+
type: "SequenceGap",
|
|
1284
|
+
expected: this._seq + 1,
|
|
1285
|
+
actual: next._seq
|
|
1286
|
+
};
|
|
1287
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ValidationError, `sequence gap: expected ${this._seq + 1}, got ${next._seq}`, { validationIssue: issue });
|
|
1288
|
+
}
|
|
1289
|
+
if (this._date > next._date) {
|
|
1290
|
+
const dateStr = this._date.toISOString().replace(".000Z", "Z");
|
|
1291
|
+
const nextDateStr = next._date.toISOString().replace(".000Z", "Z");
|
|
1292
|
+
const issue = {
|
|
1293
|
+
type: "DateOrdering",
|
|
1294
|
+
previous: dateStr,
|
|
1295
|
+
next: nextDateStr
|
|
1296
|
+
};
|
|
1297
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ValidationError, `date ordering: ${dateStr} > ${nextDateStr}`, { validationIssue: issue });
|
|
1298
|
+
}
|
|
981
1299
|
const expectedHash = ProvenanceMark.makeHash(this._res, this._key, next._key, this._chainId, this._seqBytes, this._dateBytes, this._infoBytes);
|
|
982
|
-
if (!arraysEqual(this._hash, expectedHash))
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1300
|
+
if (!arraysEqual(this._hash, expectedHash)) {
|
|
1301
|
+
const issue = {
|
|
1302
|
+
type: "HashMismatch",
|
|
1303
|
+
expected: bytesToHex(expectedHash),
|
|
1304
|
+
actual: bytesToHex(this._hash)
|
|
1305
|
+
};
|
|
1306
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ValidationError, `hash mismatch: expected ${bytesToHex(expectedHash)}, got ${bytesToHex(this._hash)}`, { validationIssue: issue });
|
|
1307
|
+
}
|
|
987
1308
|
}
|
|
988
1309
|
/**
|
|
989
1310
|
* Check if a sequence of marks is valid.
|
|
@@ -1181,30 +1502,35 @@ var ProvenanceMark = class ProvenanceMark {
|
|
|
1181
1502
|
return new ProvenanceMark(res, key, hash, chainId, seqBytes, dateBytes, infoBytes, seq, date);
|
|
1182
1503
|
}
|
|
1183
1504
|
/**
|
|
1184
|
-
*
|
|
1505
|
+
* Validate a collection of provenance marks.
|
|
1185
1506
|
*
|
|
1186
|
-
*
|
|
1507
|
+
* Matches Rust: `ProvenanceMark::validate()` which delegates to
|
|
1508
|
+
* `ValidationReport::validate()`.
|
|
1509
|
+
*/
|
|
1510
|
+
static validate(marks) {
|
|
1511
|
+
return validate(marks);
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Convert this provenance mark to a Gordian Envelope.
|
|
1187
1515
|
*
|
|
1188
|
-
*
|
|
1516
|
+
* Creates a leaf envelope containing the tagged CBOR representation.
|
|
1517
|
+
* Matches Rust: `Envelope::new(mark.to_cbor())` which creates a CBOR leaf.
|
|
1189
1518
|
*/
|
|
1190
1519
|
intoEnvelope() {
|
|
1191
|
-
return Envelope.
|
|
1520
|
+
return Envelope.newLeaf(this.taggedCbor());
|
|
1192
1521
|
}
|
|
1193
1522
|
/**
|
|
1194
1523
|
* Extract a ProvenanceMark from a Gordian Envelope.
|
|
1195
1524
|
*
|
|
1525
|
+
* Matches Rust: `envelope.subject().try_leaf()?.try_into()`
|
|
1526
|
+
*
|
|
1196
1527
|
* @param envelope - The envelope to extract from
|
|
1197
1528
|
* @returns The extracted provenance mark
|
|
1198
1529
|
* @throws ProvenanceMarkError if extraction fails
|
|
1199
1530
|
*/
|
|
1200
1531
|
static fromEnvelope(envelope) {
|
|
1201
|
-
const
|
|
1202
|
-
if (
|
|
1203
|
-
const envCase = envelope.case();
|
|
1204
|
-
if (envCase.type === "node") {
|
|
1205
|
-
const subjectBytes = envCase.subject.asByteString();
|
|
1206
|
-
if (subjectBytes !== void 0) return ProvenanceMark.fromCborData(subjectBytes);
|
|
1207
|
-
}
|
|
1532
|
+
const leaf = envelope.subject().asLeaf();
|
|
1533
|
+
if (leaf !== void 0) return ProvenanceMark.fromTaggedCbor(leaf);
|
|
1208
1534
|
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: "Could not extract ProvenanceMark from envelope" });
|
|
1209
1535
|
}
|
|
1210
1536
|
};
|
|
@@ -1269,8 +1595,8 @@ var ProvenanceMarkGenerator = class ProvenanceMarkGenerator {
|
|
|
1269
1595
|
/**
|
|
1270
1596
|
* Create a new generator with custom random data.
|
|
1271
1597
|
*/
|
|
1272
|
-
static newUsing(res, randomData
|
|
1273
|
-
const seed = ProvenanceSeed.newUsing(randomData
|
|
1598
|
+
static newUsing(res, randomData) {
|
|
1599
|
+
const seed = ProvenanceSeed.newUsing(randomData);
|
|
1274
1600
|
return ProvenanceMarkGenerator.newWithSeed(res, seed);
|
|
1275
1601
|
}
|
|
1276
1602
|
/**
|
|
@@ -1397,276 +1723,6 @@ var ProvenanceMarkGenerator = class ProvenanceMarkGenerator {
|
|
|
1397
1723
|
}
|
|
1398
1724
|
};
|
|
1399
1725
|
|
|
1400
|
-
//#endregion
|
|
1401
|
-
//#region src/validate.ts
|
|
1402
|
-
/**
|
|
1403
|
-
* Format for validation report output.
|
|
1404
|
-
*/
|
|
1405
|
-
let ValidationReportFormat = /* @__PURE__ */ function(ValidationReportFormat$1) {
|
|
1406
|
-
/** Human-readable text format */
|
|
1407
|
-
ValidationReportFormat$1["Text"] = "text";
|
|
1408
|
-
/** Compact JSON format (no whitespace) */
|
|
1409
|
-
ValidationReportFormat$1["JsonCompact"] = "json-compact";
|
|
1410
|
-
/** Pretty-printed JSON format (with indentation) */
|
|
1411
|
-
ValidationReportFormat$1["JsonPretty"] = "json-pretty";
|
|
1412
|
-
return ValidationReportFormat$1;
|
|
1413
|
-
}({});
|
|
1414
|
-
/**
|
|
1415
|
-
* Format a validation issue as a string.
|
|
1416
|
-
*/
|
|
1417
|
-
function formatValidationIssue(issue) {
|
|
1418
|
-
switch (issue.type) {
|
|
1419
|
-
case "HashMismatch": return `hash mismatch: expected ${issue.expected}, got ${issue.actual}`;
|
|
1420
|
-
case "KeyMismatch": return "key mismatch: current hash was not generated from next key";
|
|
1421
|
-
case "SequenceGap": return `sequence number gap: expected ${issue.expected}, got ${issue.actual}`;
|
|
1422
|
-
case "DateOrdering": return `date must be equal or later: previous is ${issue.previous}, next is ${issue.next}`;
|
|
1423
|
-
case "NonGenesisAtZero": return "non-genesis mark at sequence 0";
|
|
1424
|
-
case "InvalidGenesisKey": return "genesis mark must have key equal to chain_id";
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
/**
|
|
1428
|
-
* Get the chain ID as a hex string for display.
|
|
1429
|
-
*/
|
|
1430
|
-
function chainIdHex(report) {
|
|
1431
|
-
return hexEncode(report.chainId);
|
|
1432
|
-
}
|
|
1433
|
-
/**
|
|
1434
|
-
* Check if the validation report has any issues.
|
|
1435
|
-
*/
|
|
1436
|
-
function hasIssues(report) {
|
|
1437
|
-
for (const chain of report.chains) if (!chain.hasGenesis) return true;
|
|
1438
|
-
for (const chain of report.chains) for (const seq of chain.sequences) for (const mark of seq.marks) if (mark.issues.length > 0) return true;
|
|
1439
|
-
if (report.chains.length > 1) return true;
|
|
1440
|
-
if (report.chains.length === 1 && report.chains[0].sequences.length > 1) return true;
|
|
1441
|
-
return false;
|
|
1442
|
-
}
|
|
1443
|
-
/**
|
|
1444
|
-
* Check if the validation report contains interesting information.
|
|
1445
|
-
*/
|
|
1446
|
-
function isInteresting(report) {
|
|
1447
|
-
if (report.chains.length === 0) return false;
|
|
1448
|
-
for (const chain of report.chains) if (!chain.hasGenesis) return true;
|
|
1449
|
-
if (report.chains.length === 1) {
|
|
1450
|
-
const chain = report.chains[0];
|
|
1451
|
-
if (chain.sequences.length === 1) {
|
|
1452
|
-
if (chain.sequences[0].marks.every((m) => m.issues.length === 0)) return false;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
return true;
|
|
1456
|
-
}
|
|
1457
|
-
/**
|
|
1458
|
-
* Format the validation report as human-readable text.
|
|
1459
|
-
*/
|
|
1460
|
-
function formatText(report) {
|
|
1461
|
-
if (!isInteresting(report)) return "";
|
|
1462
|
-
const lines = [];
|
|
1463
|
-
lines.push(`Total marks: ${report.marks.length}`);
|
|
1464
|
-
lines.push(`Chains: ${report.chains.length}`);
|
|
1465
|
-
lines.push("");
|
|
1466
|
-
for (let chainIdx = 0; chainIdx < report.chains.length; chainIdx++) {
|
|
1467
|
-
const chain = report.chains[chainIdx];
|
|
1468
|
-
const chainIdStr = chainIdHex(chain);
|
|
1469
|
-
const shortChainId = chainIdStr.length > 8 ? chainIdStr.slice(0, 8) : chainIdStr;
|
|
1470
|
-
lines.push(`Chain ${chainIdx + 1}: ${shortChainId}`);
|
|
1471
|
-
if (!chain.hasGenesis) lines.push(" Warning: No genesis mark found");
|
|
1472
|
-
for (const seq of chain.sequences) for (const flaggedMark of seq.marks) {
|
|
1473
|
-
const mark = flaggedMark.mark;
|
|
1474
|
-
const shortId = mark.identifier();
|
|
1475
|
-
const seqNum = mark.seq();
|
|
1476
|
-
const annotations = [];
|
|
1477
|
-
if (mark.isGenesis()) annotations.push("genesis mark");
|
|
1478
|
-
for (const issue of flaggedMark.issues) {
|
|
1479
|
-
let issueStr;
|
|
1480
|
-
switch (issue.type) {
|
|
1481
|
-
case "SequenceGap":
|
|
1482
|
-
issueStr = `gap: ${issue.expected} missing`;
|
|
1483
|
-
break;
|
|
1484
|
-
case "DateOrdering":
|
|
1485
|
-
issueStr = `date ${issue.previous} < ${issue.next}`;
|
|
1486
|
-
break;
|
|
1487
|
-
case "HashMismatch":
|
|
1488
|
-
issueStr = "hash mismatch";
|
|
1489
|
-
break;
|
|
1490
|
-
case "KeyMismatch":
|
|
1491
|
-
issueStr = "key mismatch";
|
|
1492
|
-
break;
|
|
1493
|
-
case "NonGenesisAtZero":
|
|
1494
|
-
issueStr = "non-genesis at seq 0";
|
|
1495
|
-
break;
|
|
1496
|
-
case "InvalidGenesisKey":
|
|
1497
|
-
issueStr = "invalid genesis key";
|
|
1498
|
-
break;
|
|
1499
|
-
}
|
|
1500
|
-
annotations.push(issueStr);
|
|
1501
|
-
}
|
|
1502
|
-
if (annotations.length === 0) lines.push(` ${seqNum}: ${shortId}`);
|
|
1503
|
-
else lines.push(` ${seqNum}: ${shortId} (${annotations.join(", ")})`);
|
|
1504
|
-
}
|
|
1505
|
-
lines.push("");
|
|
1506
|
-
}
|
|
1507
|
-
return lines.join("\n").trimEnd();
|
|
1508
|
-
}
|
|
1509
|
-
/**
|
|
1510
|
-
* Format the validation report.
|
|
1511
|
-
*/
|
|
1512
|
-
function formatReport(report, format) {
|
|
1513
|
-
switch (format) {
|
|
1514
|
-
case ValidationReportFormat.Text: return formatText(report);
|
|
1515
|
-
case ValidationReportFormat.JsonCompact: return JSON.stringify(reportToJSON(report));
|
|
1516
|
-
case ValidationReportFormat.JsonPretty: return JSON.stringify(reportToJSON(report), null, 2);
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
/**
|
|
1520
|
-
* Convert a report to a JSON-serializable object.
|
|
1521
|
-
*/
|
|
1522
|
-
function reportToJSON(report) {
|
|
1523
|
-
return {
|
|
1524
|
-
marks: report.marks.map((m) => m.toUrlEncoding()),
|
|
1525
|
-
chains: report.chains.map((chain) => ({
|
|
1526
|
-
chain_id: hexEncode(chain.chainId),
|
|
1527
|
-
has_genesis: chain.hasGenesis,
|
|
1528
|
-
marks: chain.marks.map((m) => m.toUrlEncoding()),
|
|
1529
|
-
sequences: chain.sequences.map((seq) => ({
|
|
1530
|
-
start_seq: seq.startSeq,
|
|
1531
|
-
end_seq: seq.endSeq,
|
|
1532
|
-
marks: seq.marks.map((fm) => ({
|
|
1533
|
-
mark: fm.mark.toUrlEncoding(),
|
|
1534
|
-
issues: fm.issues
|
|
1535
|
-
}))
|
|
1536
|
-
}))
|
|
1537
|
-
}))
|
|
1538
|
-
};
|
|
1539
|
-
}
|
|
1540
|
-
/**
|
|
1541
|
-
* Build sequence bins for a chain.
|
|
1542
|
-
*/
|
|
1543
|
-
function buildSequenceBins(marks) {
|
|
1544
|
-
const sequences = [];
|
|
1545
|
-
let currentSequence = [];
|
|
1546
|
-
for (let i = 0; i < marks.length; i++) {
|
|
1547
|
-
const mark = marks[i];
|
|
1548
|
-
if (i === 0) currentSequence.push({
|
|
1549
|
-
mark,
|
|
1550
|
-
issues: []
|
|
1551
|
-
});
|
|
1552
|
-
else {
|
|
1553
|
-
const prev = marks[i - 1];
|
|
1554
|
-
try {
|
|
1555
|
-
prev.precedesOpt(mark);
|
|
1556
|
-
currentSequence.push({
|
|
1557
|
-
mark,
|
|
1558
|
-
issues: []
|
|
1559
|
-
});
|
|
1560
|
-
} catch (e) {
|
|
1561
|
-
if (currentSequence.length > 0) sequences.push(createSequenceReport(currentSequence));
|
|
1562
|
-
currentSequence = [{
|
|
1563
|
-
mark,
|
|
1564
|
-
issues: [parseValidationError(e, prev, mark)]
|
|
1565
|
-
}];
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
if (currentSequence.length > 0) sequences.push(createSequenceReport(currentSequence));
|
|
1570
|
-
return sequences;
|
|
1571
|
-
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Parse a validation error into a ValidationIssue.
|
|
1574
|
-
*/
|
|
1575
|
-
function parseValidationError(e, prev, next) {
|
|
1576
|
-
const message = e instanceof Error ? e.message : "";
|
|
1577
|
-
if (message !== "" && message.includes("non-genesis mark at sequence 0")) return { type: "NonGenesisAtZero" };
|
|
1578
|
-
if (message !== "" && message.includes("genesis mark must have key equal to chain_id")) return { type: "InvalidGenesisKey" };
|
|
1579
|
-
if (message !== "" && message.includes("sequence gap")) {
|
|
1580
|
-
const match = /expected (\d+), got (\d+)/.exec(message);
|
|
1581
|
-
if (match !== null) return {
|
|
1582
|
-
type: "SequenceGap",
|
|
1583
|
-
expected: parseInt(match[1], 10),
|
|
1584
|
-
actual: parseInt(match[2], 10)
|
|
1585
|
-
};
|
|
1586
|
-
}
|
|
1587
|
-
if (message !== "" && message.includes("date ordering")) return {
|
|
1588
|
-
type: "DateOrdering",
|
|
1589
|
-
previous: prev.date().toISOString(),
|
|
1590
|
-
next: next.date().toISOString()
|
|
1591
|
-
};
|
|
1592
|
-
if (message !== "" && message.includes("hash mismatch")) {
|
|
1593
|
-
const match = /expected: (\w+), actual: (\w+)/.exec(message);
|
|
1594
|
-
if (match !== null) return {
|
|
1595
|
-
type: "HashMismatch",
|
|
1596
|
-
expected: match[1],
|
|
1597
|
-
actual: match[2]
|
|
1598
|
-
};
|
|
1599
|
-
return {
|
|
1600
|
-
type: "HashMismatch",
|
|
1601
|
-
expected: "",
|
|
1602
|
-
actual: ""
|
|
1603
|
-
};
|
|
1604
|
-
}
|
|
1605
|
-
return { type: "KeyMismatch" };
|
|
1606
|
-
}
|
|
1607
|
-
/**
|
|
1608
|
-
* Create a sequence report from flagged marks.
|
|
1609
|
-
*/
|
|
1610
|
-
function createSequenceReport(marks) {
|
|
1611
|
-
return {
|
|
1612
|
-
startSeq: marks.length > 0 ? marks[0].mark.seq() : 0,
|
|
1613
|
-
endSeq: marks.length > 0 ? marks[marks.length - 1].mark.seq() : 0,
|
|
1614
|
-
marks
|
|
1615
|
-
};
|
|
1616
|
-
}
|
|
1617
|
-
/**
|
|
1618
|
-
* Validate a collection of provenance marks.
|
|
1619
|
-
*/
|
|
1620
|
-
function validate(marks) {
|
|
1621
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1622
|
-
const deduplicatedMarks = [];
|
|
1623
|
-
for (const mark of marks) {
|
|
1624
|
-
const key = mark.toUrlEncoding();
|
|
1625
|
-
if (!seen.has(key)) {
|
|
1626
|
-
seen.add(key);
|
|
1627
|
-
deduplicatedMarks.push(mark);
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
const chainBins = /* @__PURE__ */ new Map();
|
|
1631
|
-
for (const mark of deduplicatedMarks) {
|
|
1632
|
-
const chainIdKey = hexEncode(mark.chainId());
|
|
1633
|
-
const bin = chainBins.get(chainIdKey);
|
|
1634
|
-
if (bin !== void 0) bin.push(mark);
|
|
1635
|
-
else chainBins.set(chainIdKey, [mark]);
|
|
1636
|
-
}
|
|
1637
|
-
const chains = [];
|
|
1638
|
-
for (const [chainIdKey, chainMarks] of chainBins) {
|
|
1639
|
-
chainMarks.sort((a, b) => a.seq() - b.seq());
|
|
1640
|
-
const hasGenesis = chainMarks.length > 0 && chainMarks[0].seq() === 0 && chainMarks[0].isGenesis();
|
|
1641
|
-
const sequences = buildSequenceBins(chainMarks);
|
|
1642
|
-
chains.push({
|
|
1643
|
-
chainId: hexDecode(chainIdKey),
|
|
1644
|
-
hasGenesis,
|
|
1645
|
-
marks: chainMarks,
|
|
1646
|
-
sequences
|
|
1647
|
-
});
|
|
1648
|
-
}
|
|
1649
|
-
chains.sort((a, b) => hexEncode(a.chainId).localeCompare(hexEncode(b.chainId)));
|
|
1650
|
-
return {
|
|
1651
|
-
marks: deduplicatedMarks,
|
|
1652
|
-
chains
|
|
1653
|
-
};
|
|
1654
|
-
}
|
|
1655
|
-
/**
|
|
1656
|
-
* Helper function to encode bytes as hex.
|
|
1657
|
-
*/
|
|
1658
|
-
function hexEncode(bytes) {
|
|
1659
|
-
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1660
|
-
}
|
|
1661
|
-
/**
|
|
1662
|
-
* Helper function to decode hex to bytes.
|
|
1663
|
-
*/
|
|
1664
|
-
function hexDecode(hex) {
|
|
1665
|
-
const bytes = new Uint8Array(hex.length / 2);
|
|
1666
|
-
for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
1667
|
-
return bytes;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
1726
|
//#endregion
|
|
1671
1727
|
//#region src/mark-info.ts
|
|
1672
1728
|
/**
|
|
@@ -1716,7 +1772,7 @@ var ProvenanceMarkInfo = class ProvenanceMarkInfo {
|
|
|
1716
1772
|
const lines = [];
|
|
1717
1773
|
lines.push("---");
|
|
1718
1774
|
lines.push("");
|
|
1719
|
-
lines.push(this._mark.date().toISOString());
|
|
1775
|
+
lines.push(this._mark.date().toISOString().replace(".000Z", "Z"));
|
|
1720
1776
|
lines.push("");
|
|
1721
1777
|
lines.push(`#### ${this._ur.toString()}`);
|
|
1722
1778
|
lines.push("");
|
|
@@ -1771,110 +1827,86 @@ var ProvenanceMarkInfo = class ProvenanceMarkInfo {
|
|
|
1771
1827
|
/**
|
|
1772
1828
|
* Registers provenance mark tags in the global format context.
|
|
1773
1829
|
*
|
|
1774
|
-
*
|
|
1775
|
-
* provenance marks in a human-readable format.
|
|
1830
|
+
* Matches Rust: register_tags()
|
|
1776
1831
|
*/
|
|
1777
1832
|
function registerTags() {
|
|
1778
|
-
|
|
1833
|
+
withFormatContextMut((context) => {
|
|
1834
|
+
registerTagsIn(context);
|
|
1835
|
+
});
|
|
1779
1836
|
}
|
|
1780
1837
|
/**
|
|
1781
1838
|
* Registers provenance mark tags in a specific format context.
|
|
1782
1839
|
*
|
|
1840
|
+
* Matches Rust: register_tags_in()
|
|
1841
|
+
*
|
|
1783
1842
|
* @param context - The format context to register tags in
|
|
1784
1843
|
*/
|
|
1785
1844
|
function registerTagsIn(context) {
|
|
1786
|
-
context
|
|
1787
|
-
|
|
1845
|
+
registerTagsIn$1(context);
|
|
1846
|
+
context.tags().setSummarizer(BigInt(PROVENANCE_MARK.value), (untaggedCbor, _flat) => {
|
|
1847
|
+
try {
|
|
1848
|
+
return {
|
|
1849
|
+
ok: true,
|
|
1850
|
+
value: ProvenanceMark.fromUntaggedCbor(untaggedCbor).toString()
|
|
1851
|
+
};
|
|
1852
|
+
} catch {
|
|
1853
|
+
return {
|
|
1854
|
+
ok: false,
|
|
1855
|
+
error: {
|
|
1856
|
+
type: "Custom",
|
|
1857
|
+
message: "invalid provenance mark"
|
|
1858
|
+
}
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1788
1861
|
});
|
|
1789
1862
|
}
|
|
1790
|
-
const globalTagsContext = { setSummarizer(_tag, _summarizer) {} };
|
|
1791
1863
|
/**
|
|
1792
1864
|
* Convert a ProvenanceMark to an Envelope.
|
|
1793
1865
|
*
|
|
1794
|
-
*
|
|
1866
|
+
* Delegates to ProvenanceMark.intoEnvelope() — single source of truth.
|
|
1795
1867
|
*
|
|
1796
1868
|
* @param mark - The provenance mark to convert
|
|
1797
1869
|
* @returns An envelope containing the mark
|
|
1798
1870
|
*/
|
|
1799
1871
|
function provenanceMarkToEnvelope(mark) {
|
|
1800
|
-
return
|
|
1872
|
+
return mark.intoEnvelope();
|
|
1801
1873
|
}
|
|
1802
1874
|
/**
|
|
1803
1875
|
* Extract a ProvenanceMark from an Envelope.
|
|
1804
1876
|
*
|
|
1877
|
+
* Delegates to ProvenanceMark.fromEnvelope() — single source of truth.
|
|
1878
|
+
*
|
|
1805
1879
|
* @param envelope - The envelope to extract from
|
|
1806
1880
|
* @returns The extracted provenance mark
|
|
1807
1881
|
* @throws ProvenanceMarkError if extraction fails
|
|
1808
1882
|
*/
|
|
1809
1883
|
function provenanceMarkFromEnvelope(envelope) {
|
|
1810
|
-
|
|
1811
|
-
if (bytes !== void 0) return ProvenanceMark.fromCborData(bytes);
|
|
1812
|
-
const envCase = envelope.case();
|
|
1813
|
-
if (envCase.type === "node") {
|
|
1814
|
-
const subjectBytes = envCase.subject.asByteString();
|
|
1815
|
-
if (subjectBytes !== void 0) return ProvenanceMark.fromCborData(subjectBytes);
|
|
1816
|
-
}
|
|
1817
|
-
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: "Could not extract ProvenanceMark from envelope" });
|
|
1884
|
+
return ProvenanceMark.fromEnvelope(envelope);
|
|
1818
1885
|
}
|
|
1819
1886
|
/**
|
|
1820
1887
|
* Convert a ProvenanceMarkGenerator to an Envelope.
|
|
1821
1888
|
*
|
|
1822
|
-
*
|
|
1823
|
-
* - type: "provenance-generator"
|
|
1824
|
-
* - res: The resolution
|
|
1825
|
-
* - seed: The seed
|
|
1826
|
-
* - next-seq: The next sequence number
|
|
1827
|
-
* - rng-state: The RNG state
|
|
1889
|
+
* Delegates to ProvenanceMarkGenerator.intoEnvelope() — single source of truth.
|
|
1828
1890
|
*
|
|
1829
1891
|
* @param generator - The generator to convert
|
|
1830
1892
|
* @returns An envelope containing the generator
|
|
1831
1893
|
*/
|
|
1832
1894
|
function provenanceMarkGeneratorToEnvelope(generator) {
|
|
1833
|
-
|
|
1834
|
-
envelope = envelope.addType("provenance-generator");
|
|
1835
|
-
envelope = envelope.addAssertion("res", resolutionToNumber(generator.res()));
|
|
1836
|
-
envelope = envelope.addAssertion("seed", generator.seed().toBytes());
|
|
1837
|
-
envelope = envelope.addAssertion("next-seq", generator.nextSeq());
|
|
1838
|
-
envelope = envelope.addAssertion("rng-state", generator.rngState().toBytes());
|
|
1839
|
-
return envelope;
|
|
1895
|
+
return generator.intoEnvelope();
|
|
1840
1896
|
}
|
|
1841
1897
|
/**
|
|
1842
1898
|
* Extract a ProvenanceMarkGenerator from an Envelope.
|
|
1843
1899
|
*
|
|
1900
|
+
* Delegates to ProvenanceMarkGenerator.fromEnvelope() — single source of truth.
|
|
1901
|
+
*
|
|
1844
1902
|
* @param envelope - The envelope to extract from
|
|
1845
1903
|
* @returns The extracted generator
|
|
1846
1904
|
* @throws ProvenanceMarkError if extraction fails
|
|
1847
1905
|
*/
|
|
1848
1906
|
function provenanceMarkGeneratorFromEnvelope(envelope) {
|
|
1849
|
-
|
|
1850
|
-
if (!env.hasType("provenance-generator")) throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: "Envelope is not a provenance-generator" });
|
|
1851
|
-
const chainId = env.subject().asByteString();
|
|
1852
|
-
if (chainId === void 0) throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: "Could not extract chain ID" });
|
|
1853
|
-
const extractAssertion = (predicate) => {
|
|
1854
|
-
const assertions = env.assertionsWithPredicate(predicate);
|
|
1855
|
-
if (assertions.length === 0) throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: `Missing ${predicate} assertion` });
|
|
1856
|
-
const assertionCase = assertions[0].case();
|
|
1857
|
-
if (assertionCase.type !== "assertion") throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: `Invalid ${predicate} assertion` });
|
|
1858
|
-
const obj = assertionCase.assertion.object();
|
|
1859
|
-
const objCase = obj.case();
|
|
1860
|
-
if (objCase.type === "leaf") return {
|
|
1861
|
-
cbor: objCase.cbor,
|
|
1862
|
-
bytes: obj.asByteString()
|
|
1863
|
-
};
|
|
1864
|
-
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: `Invalid ${predicate} value` });
|
|
1865
|
-
};
|
|
1866
|
-
const res = resolutionFromCbor(extractAssertion("res").cbor);
|
|
1867
|
-
const seedValue = extractAssertion("seed");
|
|
1868
|
-
if (seedValue.bytes === void 0) throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: "Invalid seed data" });
|
|
1869
|
-
const seed = ProvenanceSeed.fromBytes(seedValue.bytes);
|
|
1870
|
-
const seqValue = extractAssertion("next-seq");
|
|
1871
|
-
const nextSeq = Number(seqValue.cbor);
|
|
1872
|
-
const rngValue = extractAssertion("rng-state");
|
|
1873
|
-
if (rngValue.bytes === void 0) throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, void 0, { message: "Invalid rng-state data" });
|
|
1874
|
-
const rngState = RngState.fromBytes(rngValue.bytes);
|
|
1875
|
-
return ProvenanceMarkGenerator.new(res, seed, chainId, nextSeq, rngState);
|
|
1907
|
+
return ProvenanceMarkGenerator.fromEnvelope(envelope);
|
|
1876
1908
|
}
|
|
1877
1909
|
|
|
1878
1910
|
//#endregion
|
|
1879
|
-
export { PROVENANCE_SEED_LENGTH, ProvenanceMark, ProvenanceMarkError, ProvenanceMarkErrorType, ProvenanceMarkGenerator, ProvenanceMarkInfo, ProvenanceMarkResolution, ProvenanceSeed, RNG_STATE_LENGTH, RngState, SHA256_SIZE, ValidationReportFormat, Xoshiro256StarStar, chainIdHex, chainIdRange, dateBytesLength, dateBytesRange, dateFromIso8601, dateToDateString, dateToIso8601, deserialize2Bytes, deserialize4Bytes, deserialize6Bytes, deserializeDate, deserializeSeq, extendKey, fixedLength, formatReport, formatValidationIssue, hasIssues, hashRange, hkdfHmacSha256, infoRangeStart, keyRange, linkLength, obfuscate, provenanceMarkFromEnvelope, provenanceMarkGeneratorFromEnvelope, provenanceMarkGeneratorToEnvelope, provenanceMarkToEnvelope, rangeOfDaysInMonth, registerTags, registerTagsIn, resolutionFromCbor, resolutionFromNumber, resolutionToCbor, resolutionToNumber, resolutionToString, seqBytesLength, seqBytesRange, serialize2Bytes, serialize4Bytes, serialize6Bytes, serializeDate, serializeSeq, sha256, sha256Prefix, validate };
|
|
1911
|
+
export { FormatContext, PROVENANCE_SEED_LENGTH, ProvenanceMark, ProvenanceMarkError, ProvenanceMarkErrorType, ProvenanceMarkGenerator, ProvenanceMarkInfo, ProvenanceMarkResolution, ProvenanceSeed, RNG_STATE_LENGTH, RngState, SHA256_SIZE, ValidationReportFormat, Xoshiro256StarStar, chainIdHex, chainIdRange, dateBytesLength, dateBytesRange, dateFromIso8601, dateToDateString, dateToIso8601, deserialize2Bytes, deserialize4Bytes, deserialize6Bytes, deserializeDate, deserializeSeq, extendKey, fixedLength, formatReport, formatValidationIssue, hasIssues, hashRange, hkdfHmacSha256, infoRangeStart, keyRange, linkLength, obfuscate, provenanceMarkFromEnvelope, provenanceMarkGeneratorFromEnvelope, provenanceMarkGeneratorToEnvelope, provenanceMarkToEnvelope, rangeOfDaysInMonth, registerTags, registerTagsIn, resolutionFromCbor, resolutionFromNumber, resolutionToCbor, resolutionToNumber, resolutionToString, seqBytesLength, seqBytesRange, serialize2Bytes, serialize4Bytes, serialize6Bytes, serializeDate, serializeSeq, sha256, sha256Prefix, validate };
|
|
1880
1912
|
//# sourceMappingURL=index.mjs.map
|