@peac/audit 0.16.1 → 0.16.3

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 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