@peac/audit 0.16.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +240 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +236 -2
- package/dist/index.mjs.map +1 -1
- package/dist/merkle.d.ts +91 -0
- package/dist/merkle.d.ts.map +1 -0
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1690,6 +1690,240 @@ function normalizeIat(iat) {
|
|
|
1690
1690
|
if (!Number.isNaN(parsed)) return parsed;
|
|
1691
1691
|
return 0;
|
|
1692
1692
|
}
|
|
1693
|
+
var MERKLE_TREE_ALG = "peac.merkle.ct-sorted-set-sha256-v1";
|
|
1694
|
+
var MERKLE_HASH_ALG = "sha256";
|
|
1695
|
+
var MerkleInputError = class extends Error {
|
|
1696
|
+
code;
|
|
1697
|
+
constructor(code, message) {
|
|
1698
|
+
super(message);
|
|
1699
|
+
this.name = "MerkleInputError";
|
|
1700
|
+
this.code = code;
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
var DIGEST_REF_RE = /^sha256:[0-9a-f]{64}$/;
|
|
1704
|
+
var LEAF_PREFIX = 0;
|
|
1705
|
+
var NODE_PREFIX = 1;
|
|
1706
|
+
function assertDigestRef(value, code, label) {
|
|
1707
|
+
if (typeof value !== "string" || !DIGEST_REF_RE.test(value)) {
|
|
1708
|
+
throw new MerkleInputError(
|
|
1709
|
+
code,
|
|
1710
|
+
`${label} must match sha256:<hex64> (lowercase); received ${describe(value)}`
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
function describe(value) {
|
|
1715
|
+
if (typeof value === "string") return value.length > 74 ? `${value.slice(0, 71)}...` : value;
|
|
1716
|
+
return Object.prototype.toString.call(value);
|
|
1717
|
+
}
|
|
1718
|
+
function decodeDigest(ref) {
|
|
1719
|
+
return Uint8Array.from(Buffer.from(ref.slice("sha256:".length), "hex"));
|
|
1720
|
+
}
|
|
1721
|
+
function encodeDigest(bytes) {
|
|
1722
|
+
return `sha256:${Buffer.from(bytes).toString("hex")}`;
|
|
1723
|
+
}
|
|
1724
|
+
function sha256(...chunks) {
|
|
1725
|
+
const h = crypto$1.createHash("sha256");
|
|
1726
|
+
for (const c of chunks) h.update(c);
|
|
1727
|
+
return Uint8Array.from(h.digest());
|
|
1728
|
+
}
|
|
1729
|
+
function leafHash(digest) {
|
|
1730
|
+
return sha256(Uint8Array.of(LEAF_PREFIX), digest);
|
|
1731
|
+
}
|
|
1732
|
+
function nodeHash(left, right) {
|
|
1733
|
+
return sha256(Uint8Array.of(NODE_PREFIX), left, right);
|
|
1734
|
+
}
|
|
1735
|
+
function largestPow2LessThan(n) {
|
|
1736
|
+
let k = 1;
|
|
1737
|
+
while (k * 2 < n) k *= 2;
|
|
1738
|
+
return k;
|
|
1739
|
+
}
|
|
1740
|
+
function compareBytes(a, b) {
|
|
1741
|
+
const len = Math.min(a.length, b.length);
|
|
1742
|
+
for (let i = 0; i < len; i++) {
|
|
1743
|
+
if (a[i] !== b[i]) return a[i] - b[i];
|
|
1744
|
+
}
|
|
1745
|
+
return a.length - b.length;
|
|
1746
|
+
}
|
|
1747
|
+
function bytesEqual(a, b) {
|
|
1748
|
+
return a.length === b.length && crypto$1.timingSafeEqual(a, b);
|
|
1749
|
+
}
|
|
1750
|
+
function isSafePositiveInteger(value) {
|
|
1751
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
1752
|
+
}
|
|
1753
|
+
function isSafeNonNegativeInteger(value) {
|
|
1754
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1755
|
+
}
|
|
1756
|
+
function isOdd(value) {
|
|
1757
|
+
return value % 2 === 1;
|
|
1758
|
+
}
|
|
1759
|
+
function halve(value) {
|
|
1760
|
+
return Math.floor(value / 2);
|
|
1761
|
+
}
|
|
1762
|
+
function merkleTreeHash(digests, start, end) {
|
|
1763
|
+
const n = end - start;
|
|
1764
|
+
if (n === 1) return leafHash(digests[start]);
|
|
1765
|
+
const k = largestPow2LessThan(n);
|
|
1766
|
+
return nodeHash(
|
|
1767
|
+
merkleTreeHash(digests, start, start + k),
|
|
1768
|
+
merkleTreeHash(digests, start + k, end)
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
function inclusionPath(m, digests, start, end) {
|
|
1772
|
+
const n = end - start;
|
|
1773
|
+
if (n === 1) return [];
|
|
1774
|
+
const k = largestPow2LessThan(n);
|
|
1775
|
+
if (m < k) {
|
|
1776
|
+
return [
|
|
1777
|
+
...inclusionPath(m, digests, start, start + k),
|
|
1778
|
+
merkleTreeHash(digests, start + k, end)
|
|
1779
|
+
];
|
|
1780
|
+
}
|
|
1781
|
+
return [
|
|
1782
|
+
...inclusionPath(m - k, digests, start + k, end),
|
|
1783
|
+
merkleTreeHash(digests, start, start + k)
|
|
1784
|
+
];
|
|
1785
|
+
}
|
|
1786
|
+
function foldInclusionProof(leaf, siblings, leafIndex, treeSize) {
|
|
1787
|
+
if (leafIndex >= treeSize) return null;
|
|
1788
|
+
let fn = leafIndex;
|
|
1789
|
+
let sn = treeSize - 1;
|
|
1790
|
+
let r = leaf;
|
|
1791
|
+
for (const p of siblings) {
|
|
1792
|
+
if (sn === 0) return null;
|
|
1793
|
+
if (isOdd(fn) || fn === sn) {
|
|
1794
|
+
r = nodeHash(p, r);
|
|
1795
|
+
if (!isOdd(fn)) {
|
|
1796
|
+
while (!isOdd(fn) && fn !== 0) {
|
|
1797
|
+
fn = halve(fn);
|
|
1798
|
+
sn = halve(sn);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
} else {
|
|
1802
|
+
r = nodeHash(r, p);
|
|
1803
|
+
}
|
|
1804
|
+
fn = halve(fn);
|
|
1805
|
+
sn = halve(sn);
|
|
1806
|
+
}
|
|
1807
|
+
return sn === 0 ? r : null;
|
|
1808
|
+
}
|
|
1809
|
+
function toSortedDigests(refs) {
|
|
1810
|
+
if (!Array.isArray(refs) || refs.length === 0) {
|
|
1811
|
+
throw new MerkleInputError(
|
|
1812
|
+
"audit.merkle.empty_input",
|
|
1813
|
+
"refs must be a non-empty array of sha256:<hex64> receipt refs"
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1817
|
+
const entries = [];
|
|
1818
|
+
for (const ref of refs) {
|
|
1819
|
+
assertDigestRef(ref, "audit.merkle.invalid_receipt_ref", "receipt_ref");
|
|
1820
|
+
if (seen.has(ref)) {
|
|
1821
|
+
throw new MerkleInputError(
|
|
1822
|
+
"audit.merkle.duplicate_receipt_ref",
|
|
1823
|
+
`duplicate receipt_ref: ${ref}`
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
seen.add(ref);
|
|
1827
|
+
entries.push({ ref, digest: decodeDigest(ref) });
|
|
1828
|
+
}
|
|
1829
|
+
entries.sort((a, b) => compareBytes(a.digest, b.digest));
|
|
1830
|
+
return { sorted: entries.map((e) => e.digest), sortedRefs: entries.map((e) => e.ref) };
|
|
1831
|
+
}
|
|
1832
|
+
function buildReceiptMerkleCommitment(refs) {
|
|
1833
|
+
const { sorted } = toSortedDigests(refs);
|
|
1834
|
+
return {
|
|
1835
|
+
tree_alg: MERKLE_TREE_ALG,
|
|
1836
|
+
hash_alg: MERKLE_HASH_ALG,
|
|
1837
|
+
root: encodeDigest(merkleTreeHash(sorted, 0, sorted.length)),
|
|
1838
|
+
tree_size: sorted.length
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function generateReceiptMerkleInclusionProof(refs, targetRef) {
|
|
1842
|
+
assertDigestRef(targetRef, "audit.merkle.invalid_receipt_ref", "targetRef");
|
|
1843
|
+
const { sorted, sortedRefs } = toSortedDigests(refs);
|
|
1844
|
+
const leafIndex = sortedRefs.indexOf(targetRef);
|
|
1845
|
+
if (leafIndex === -1) {
|
|
1846
|
+
throw new MerkleInputError(
|
|
1847
|
+
"audit.merkle.target_not_found",
|
|
1848
|
+
`targetRef not in refs: ${targetRef}`
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
return {
|
|
1852
|
+
tree_alg: MERKLE_TREE_ALG,
|
|
1853
|
+
hash_alg: MERKLE_HASH_ALG,
|
|
1854
|
+
leaf_index: leafIndex,
|
|
1855
|
+
tree_size: sorted.length,
|
|
1856
|
+
hashes: inclusionPath(leafIndex, sorted, 0, sorted.length).map(encodeDigest)
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function verifyReceiptMerkleInclusion(commitment, targetRef, proof) {
|
|
1860
|
+
if (commitment === null || typeof commitment !== "object") {
|
|
1861
|
+
throw new MerkleInputError("audit.merkle.invalid_commitment", "commitment must be an object");
|
|
1862
|
+
}
|
|
1863
|
+
if (commitment.tree_alg !== MERKLE_TREE_ALG) {
|
|
1864
|
+
throw new MerkleInputError(
|
|
1865
|
+
"audit.merkle.unsupported_tree_alg",
|
|
1866
|
+
`unsupported commitment tree_alg: ${describe(commitment.tree_alg)}`
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
if (commitment.hash_alg !== MERKLE_HASH_ALG) {
|
|
1870
|
+
throw new MerkleInputError(
|
|
1871
|
+
"audit.merkle.unsupported_hash_alg",
|
|
1872
|
+
`unsupported commitment hash_alg: ${describe(commitment.hash_alg)}`
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
assertDigestRef(commitment.root, "audit.merkle.invalid_merkle_root", "commitment.root");
|
|
1876
|
+
if (!isSafePositiveInteger(commitment.tree_size)) {
|
|
1877
|
+
throw new MerkleInputError(
|
|
1878
|
+
"audit.merkle.invalid_commitment",
|
|
1879
|
+
`commitment.tree_size must be a positive safe integer; received ${describe(commitment.tree_size)}`
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
assertDigestRef(targetRef, "audit.merkle.invalid_receipt_ref", "targetRef");
|
|
1883
|
+
if (proof === null || typeof proof !== "object") {
|
|
1884
|
+
throw new MerkleInputError("audit.merkle.invalid_proof", "proof must be an object");
|
|
1885
|
+
}
|
|
1886
|
+
if (proof.tree_alg !== MERKLE_TREE_ALG) {
|
|
1887
|
+
throw new MerkleInputError(
|
|
1888
|
+
"audit.merkle.unsupported_tree_alg",
|
|
1889
|
+
`unsupported tree_alg: ${describe(proof.tree_alg)}`
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
if (proof.hash_alg !== MERKLE_HASH_ALG) {
|
|
1893
|
+
throw new MerkleInputError(
|
|
1894
|
+
"audit.merkle.unsupported_hash_alg",
|
|
1895
|
+
`unsupported hash_alg: ${describe(proof.hash_alg)}`
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1898
|
+
if (!isSafePositiveInteger(proof.tree_size)) {
|
|
1899
|
+
throw new MerkleInputError(
|
|
1900
|
+
"audit.merkle.invalid_proof",
|
|
1901
|
+
`tree_size must be a positive safe integer; received ${describe(proof.tree_size)}`
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
if (!isSafeNonNegativeInteger(proof.leaf_index) || proof.leaf_index >= proof.tree_size) {
|
|
1905
|
+
throw new MerkleInputError(
|
|
1906
|
+
"audit.merkle.invalid_proof",
|
|
1907
|
+
`leaf_index must be a safe integer in [0, tree_size); received ${describe(proof.leaf_index)}`
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
if (!Array.isArray(proof.hashes)) {
|
|
1911
|
+
throw new MerkleInputError("audit.merkle.invalid_proof", "proof.hashes must be an array");
|
|
1912
|
+
}
|
|
1913
|
+
const siblings = [];
|
|
1914
|
+
for (const h of proof.hashes) {
|
|
1915
|
+
assertDigestRef(h, "audit.merkle.invalid_proof_hash", "proof.hashes[]");
|
|
1916
|
+
siblings.push(decodeDigest(h));
|
|
1917
|
+
}
|
|
1918
|
+
if (proof.tree_size !== commitment.tree_size) return false;
|
|
1919
|
+
const recomputed = foldInclusionProof(
|
|
1920
|
+
leafHash(decodeDigest(targetRef)),
|
|
1921
|
+
siblings,
|
|
1922
|
+
proof.leaf_index,
|
|
1923
|
+
proof.tree_size
|
|
1924
|
+
);
|
|
1925
|
+
return recomputed !== null && bytesEqual(recomputed, decodeDigest(commitment.root));
|
|
1926
|
+
}
|
|
1693
1927
|
|
|
1694
1928
|
// src/index.ts
|
|
1695
1929
|
var AUDIT_PACKAGE_VERSION = "0.9.27";
|
|
@@ -1702,11 +1936,15 @@ exports.BUNDLE_VERSION = BUNDLE_VERSION;
|
|
|
1702
1936
|
exports.COMMERCE_BUNDLE_VERSION = COMMERCE_BUNDLE_VERSION;
|
|
1703
1937
|
exports.DISPUTE_BUNDLE_VERSION = DISPUTE_BUNDLE_VERSION;
|
|
1704
1938
|
exports.DISPUTE_BUNDLE_VERSION_v2 = BUNDLE_VERSION2;
|
|
1939
|
+
exports.MERKLE_HASH_ALG = MERKLE_HASH_ALG;
|
|
1940
|
+
exports.MERKLE_TREE_ALG = MERKLE_TREE_ALG;
|
|
1941
|
+
exports.MerkleInputError = MerkleInputError;
|
|
1705
1942
|
exports.UNCLASSIFIED_LIFECYCLE_BUCKET = UNCLASSIFIED_LIFECYCLE_BUCKET;
|
|
1706
1943
|
exports.VERIFICATION_REPORT_VERSION = VERIFICATION_REPORT_VERSION;
|
|
1707
1944
|
exports.addProtocolEvidence = addProtocolEvidence;
|
|
1708
1945
|
exports.addReceiptRef = addReceiptRef;
|
|
1709
1946
|
exports.addTimelineEntry = addTimelineEntry;
|
|
1947
|
+
exports.buildReceiptMerkleCommitment = buildReceiptMerkleCommitment;
|
|
1710
1948
|
exports.computeCommerceSummary = computeCommerceSummary;
|
|
1711
1949
|
exports.correlateByTrace = correlateByTrace;
|
|
1712
1950
|
exports.createAuditEntry = createAuditEntry;
|
|
@@ -1723,6 +1961,7 @@ exports.formatJsonlLine = formatJsonlLine;
|
|
|
1723
1961
|
exports.formatReportText = formatReportText;
|
|
1724
1962
|
exports.generateAuditId = generateAuditId;
|
|
1725
1963
|
exports.generateBundleSummary = generateBundleSummary;
|
|
1964
|
+
exports.generateReceiptMerkleInclusionProof = generateReceiptMerkleInclusionProof;
|
|
1726
1965
|
exports.getBundleContentHash = getBundleContentHash;
|
|
1727
1966
|
exports.groupByLifecycle = groupByLifecycle;
|
|
1728
1967
|
exports.isValidAuditEntry = isValidAuditEntry;
|
|
@@ -1737,5 +1976,6 @@ exports.serializeReport = serializeReport;
|
|
|
1737
1976
|
exports.validateAuditEntry = validateAuditEntry;
|
|
1738
1977
|
exports.verifyBundle = verifyBundle;
|
|
1739
1978
|
exports.verifyBundleIntegrity = verifyBundleIntegrity;
|
|
1979
|
+
exports.verifyReceiptMerkleInclusion = verifyReceiptMerkleInclusion;
|
|
1740
1980
|
//# sourceMappingURL=index.cjs.map
|
|
1741
1981
|
//# sourceMappingURL=index.cjs.map
|