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