@mikeargento/bitgraph-audit 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +4 -0
  2. package/dist/audit.d.ts +8 -6
  3. package/dist/audit.d.ts.map +1 -1
  4. package/dist/audit.js +28 -8
  5. package/dist/audit.js.map +1 -1
  6. package/dist/ceilings.d.ts +25 -0
  7. package/dist/ceilings.d.ts.map +1 -1
  8. package/dist/ceilings.js +159 -31
  9. package/dist/ceilings.js.map +1 -1
  10. package/dist/cli.js +40 -3
  11. package/dist/cli.js.map +1 -1
  12. package/dist/exports.d.ts +39 -0
  13. package/dist/exports.d.ts.map +1 -0
  14. package/dist/exports.js +371 -0
  15. package/dist/exports.js.map +1 -0
  16. package/dist/index.d.ts +5 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +4 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/ingest.d.ts.map +1 -1
  21. package/dist/ingest.js +179 -19
  22. package/dist/ingest.js.map +1 -1
  23. package/dist/report-json.d.ts.map +1 -1
  24. package/dist/report-json.js +15 -0
  25. package/dist/report-json.js.map +1 -1
  26. package/dist/report-md.d.ts.map +1 -1
  27. package/dist/report-md.js +225 -0
  28. package/dist/report-md.js.map +1 -1
  29. package/dist/settlement-blobs.d.ts +131 -0
  30. package/dist/settlement-blobs.d.ts.map +1 -0
  31. package/dist/settlement-blobs.js +682 -0
  32. package/dist/settlement-blobs.js.map +1 -0
  33. package/dist/types.d.ts +254 -3
  34. package/dist/types.d.ts.map +1 -1
  35. package/package.json +5 -3
  36. package/src/__tests__/fixtures/settlement/0x018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5c.bin +0 -0
  37. package/src/__tests__/fixtures/settlement/ceiling-51979918.json +101 -0
  38. package/src/__tests__/fixtures/settlement/pointer-51979918.json +69 -0
  39. package/src/__tests__/settlement.test.ts +254 -0
  40. package/src/audit.ts +30 -8
  41. package/src/ceilings.ts +167 -33
  42. package/src/cli.ts +39 -3
  43. package/src/exports.ts +478 -0
  44. package/src/index.ts +16 -0
  45. package/src/ingest.ts +192 -18
  46. package/src/report-json.ts +15 -0
  47. package/src/report-md.ts +248 -0
  48. package/src/settlement-blobs.ts +689 -0
  49. package/src/types.ts +234 -2
@@ -0,0 +1,101 @@
1
+ {
2
+ "version": "bitgraph-ceiling/1",
3
+ "proofHash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
4
+ "leafIndex": 0,
5
+ "leafCount": 1,
6
+ "merklePath": [],
7
+ "root": "0xdbd541269066e97c8086b20644de7a849db0e6870c6a99be5d102c7a5a72ed37",
8
+ "anchor": {
9
+ "chainId": 8453,
10
+ "writer": "0xf3972408d853c975f86351c311f4310220bbf2a3",
11
+ "txHash": "0x72cc33632237a96db749836905e92e45bd5cc4167403bc7970a69f250b3e9450",
12
+ "rawTx": "0x02f8c082210581f3830f424083a7d8c082721594f3972408d853c975f86351c311f4310220bbf2a380b85442474331dbd541269066e97c8086b20644de7a849db0e6870c6a99be5d102c7a5a72ed378ba45845d1e6e1f87b07275c96aed4b0dfe64d88ef6a654d8029915798526c2f00000000000011c200000000000011c2c001a0e5af8d93a6d54d30963c11ca829d957cd11ee66531caa49c0ffde149bb5f00cca00a390e583140cf6c1050c432659d36312f5557d947e2981ca65d79f0967753bb",
13
+ "payload": "0x42474331dbd541269066e97c8086b20644de7a849db0e6870c6a99be5d102c7a5a72ed378ba45845d1e6e1f87b07275c96aed4b0dfe64d88ef6a654d8029915798526c2f00000000000011c200000000000011c2",
14
+ "blockNumber": 51979918,
15
+ "blockHash": "0x6844d628876ff2663cbd7f8b9bc39b0dc9a86a1d18d68ac26474ea63c6a81d6f",
16
+ "blockTimestamp": 1790749183,
17
+ "blockHeader": "0xf9027aa0d2b1d2cece1bd23dcd34da3463ce2aa9b78da0f3234d1a5bd2d6c328b3a597daa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a06e3c7ae613005ce466f8654717054737c807f4c247710a888dc96d632c14fe97a013c5fda778ed00c84d82e83371f76758e6f27d06c9f201b831f0a1a65b8186bca0ba92bdf88d5ca0474870d955f92010c8f37943e3f61a3e0cc82eab04e4807be9b9010094f1573bb7737459cee15197e72e33f6bcf74c3062eed8912ba5b611b056aced87b8ef52b2f7a57fd755cf51ab0bf738cf4e934796d8330eb7b10f99df345f90fe5a3a96ecffa53cf60f9539814d66bd04c7281f9b7f3bf90371623fdde3d2fc258924b2d2ce96eaf79e0a419f972b2ee3c63a9ecf0e16d9acc6dc5e525f47f7bb14fb8a7f22771cc58da6dbe5fb13b1f9f1beb95834c25e0c0ae1f4758d9d9da74c061bd2c174b86dd6c1d85ba6f2f7f695d247bc5bc47fb56327ed170740c16992737fc89b9efd19f37cf52969d8c35f5d8dcfb526269a31a3d5df9b29ef33cc14ae8117876c1030c8ba293e8337fa3416bd06b1a665e716b2b8923fb95efc80840319268e8417d78400840366d750846abca9ff9101000000640000000500000000004c4b40a07c729a72f790609e3fded11acc8e07e327fc851192e4853b250d6c112734a8c5880000000000000000834c4b40a0f3e930c14f5657a847200d37072baaeec520c0b18bb1a8e96447993e9073c1128385083080a0f42330fe8a6ac89a286b0998d1d8b64e4b589f20d2c9bfd7eeb8f8825d265a29a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
18
+ "txIndex": 39,
19
+ "txInclusionProof": [
20
+ "0xf90131a0be69804be0dcbda3bf05fe812834f3fdef9b549da09fb46ef3229ac7c9fca434a01552d324f554fa4dc75302b67c8d4f6c95839ef6f102566ce5b649bc8069b1e4a05d37222dbcb761956d3f49f5fea434bc54d8f11cc14f2f8491bc2d0f1d6a3004a05f392fdfb31e99909eecf8d2210b3c9efd691bccb07ba8eefc68bc27702feaf3a0dce3951291ca16a11fecd7d780e8f190ec057d7d395979f4001b81ecb9de7bfda06068fb78d9c115e07459c7755194de9392eda67032a12811ec061f8aabc800eba06b8c29af59947deacfda56a72e47e915aa7bbc269ad2ff4f2831c9b6fa41f1bda00cbe0a4ed611ad32b317dd2b9aee8662cf868aed745b90fec59e470f122ea52ea09ccfbc70703bed4fc45d995163f8f8555d3bdc29ae82a33aab2910cf096ecf508080808080808080",
21
+ "0xf90211a0eed9ea4758bfef968546887fedbf9d83e9d2094be7430d186075efa340cf94c4a05603f370f56610111175bad92731a1a09521eac2bbc78deb03e9ce72c7c608bea0f2c2ce9ffc9c3317bf2069d29fb317f19de67072f957c97800ffda8936d00714a00c2c1f480138c2773dc45a5a4d73fa127ad63bfdd48bf050da3a44d333c02e3ea048eddd549ad1f2d13ea9615989b0964881791b701580ad711e0a1a73f274cf4ea0105f1b3bd8f70008d4f5cb6034cf944f6eb0950ce3cc2e10aaffcae685687a87a029c9dfb9c363286f24a04fcee995319181dcb2ec5ecd128bd4d947d542881d60a09ce8bc98c5a8a830c417dacf11ec975fbf91d979ba9b46241d62761abf30f219a03c115a97f043c0beb662747fe5b05ecbe9b5f0b62a230e3d3836860d396061bca040204f0d3070c4f23f52d74ee26f18239e0aa5f1cee64344f8aba4f8afe267afa003471fdcd2e9229871f47179c0fc8a631fcce1ea6c873e34ff4d637da79d0d3da09e6227d6a81591bbaa1ab0c2ace4071e65ec765d335e9fe87428f8d40e6bed2fa052771472ba3d590e71e0bf36d637ec7e1352ed1c77a0eb7f30d67eda5fd1d0dea0a2c479c65c952d40233c15ac4ec831d1c96966d2596113c1e3dd83f87cb26266a03b3018cdfc612efc6a52506030634e07bf32f3cd362a3803e3e9919c01b0e181a02454e7ab077d054cf58ae4413cfd02ccc9b4d313ef1737356bcaa6d7693237ad80",
22
+ "0xf8c620b8c302f8c082210581f3830f424083a7d8c082721594f3972408d853c975f86351c311f4310220bbf2a380b85442474331dbd541269066e97c8086b20644de7a849db0e6870c6a99be5d102c7a5a72ed378ba45845d1e6e1f87b07275c96aed4b0dfe64d88ef6a654d8029915798526c2f00000000000011c200000000000011c2c001a0e5af8d93a6d54d30963c11ca829d957cd11ee66531caa49c0ffde149bb5f00cca00a390e583140cf6c1050c432659d36312f5557d947e2981ca65d79f0967753bb"
23
+ ]
24
+ },
25
+ "status": "finalized",
26
+ "statusObserved": {
27
+ "included": "2026-09-30T06:19:47.000Z",
28
+ "safe": "2026-09-30T06:21:00.000Z",
29
+ "finalized": "2026-09-30T06:35:00.000Z"
30
+ },
31
+ "floor": null,
32
+ "settlement": {
33
+ "version": "bitgraph-settlement/1",
34
+ "baseBlockNumber": 51979918,
35
+ "baseBlockHash": "0x6844d628876ff2663cbd7f8b9bc39b0dc9a86a1d18d68ac26474ea63c6a81d6f",
36
+ "l1": {
37
+ "chainId": 1,
38
+ "txHash": "0xcb575f2482a33ce5678997adcb082d4b68f7927c932a07a7a89090e61c6ea63a",
39
+ "rawTx": "0x03f9013b01832571d584959b8f808501c0d2ae8082520894ff000000000000000000000000000000000084538080c0843b9aca00f8c6a0018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5ca001c01a93e0e3cd2212776aa2d4cc4e0c07c85cb75b968525f4e5859bae6773c4a00118b59a40847e830edd78f12799a36b697087938f5be61d6aca024f98c9b79ba00120c9a9f71037bbe073b4979641a40642a8d6e9babb851bd7e513981f1316d2a0013b850872322a5032994968f043cac34ddf72cb04e387d6b86ac268990b5a17a001b2946a71b7c1e08dff34496e274da1e5ef2dbedd62ed6bab6b07c12d08302701a0f48e45e81f193edea8a7f2e319169201ea084490a282ebfa944a53155125ba90a037f9784866387921294f047d9e00eae3acf249add9880889f797784c496ea9b1",
40
+ "blockNumber": 26088463,
41
+ "blockHash": "0x8608654cd9d6d66c2ce8a80108db7e5202a6d9ad0072f8c81353cc988d3a93f2",
42
+ "blockTimestamp": 1790749235,
43
+ "blockHeader": "0xf90289a0bd893b925b3482c78646c1df70696e0d301cd5d09dcc34dc2ecc24441e4bc40ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794396343362be2a4da1ce0c1c210945346fb82aa49a0e6bd281387b53a52427452d620531da5d0c670d1a72feacf6d4534700bbdad93a083e423e98cf59e848d311a37f7b9511e8ecbdf2f69a3d9a0a96df448eb593dd7a0c31336933abc1b6e3e3f2c8dbbe2ccc65d4bb9179e665cbb7a06b7ff14085242b90100fff3f3fbffffb9fdefffebfdfffebfdfdfdfffbdfebffffefbfff5f7fefeffebbedfffffeffdff7ff7f2d7cbbdfbfffbbffffdffdfffefe71fbfefb9fffebfffffffffdf7efffff9797fedffdf7fffbbeef7ffbd7e5efdbfffff7fbfbff7fbffeb7ffffeabef6efcffffbfff6fbfffbff3ff7dffafff7edfbefffff7fdff7fbf9b74bffdfefd7fffffeddc737ffa5fffff7fdde9f7f7bffff7bf6f7ffdfcffffdf7dbfffffffbfdfffffdff7f7effffbffdfdfffbeafffbfff7dbaffffffefe7d7e37faf77dffef3ffcffdfa75fe7b7fbd77fe7fe7ffff77ff3fffffffdfffffffdfff7ff7fffffffffe7fbdafbffffeffffb77fe7ffff7dfbfbfff7dbf6ffdf8084018e140f84039387008401ff939d846abcaa339be29ca82051756173617220287175617361722e77696e2920e29ca8a0aec208249057820706989e2a8d4882c6157bffb6f112847ca25c02b90f8bb17088000000000000000084057644a9a096b80d5bb80122a62a3905a4df56e93396c4cf0170a22efb1fdb0f1fe35307b3830e0000840ac7fb8ba0aa4880a6b37d3438d341144ce67680506b21b26e54a90835ce3e8aee0b44a3c9a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
44
+ "txIndex": 18,
45
+ "txInclusionProof": [
46
+ "0xf90131a0fcf4c1b6c3daaf8b4161626c0ed9ed20d4dda4438ac031b46a3a591c70ae3628a0d7b589907c8a5208ea55ae515cf9ee745d8e345f70260f3209e8056af35dbb2fa06ed025ba6929892c332be304b1cae217c390e16e73947c1777023bd6f9aad935a0995d136362f36671287a4eed2b7d0a6648b6d09d1e4c0cbfbf505b40a6452b4fa0c8396e60f6bd3f7eb429d358ecacc46ff149613bf5178c0664dc5070d8e8733ca0aa6c0b1d5e761bf8099cad7b1490374d7a919e439972be8c20a1ae13d1f034c9a0c5836776069aed36f670ecbbb053414f1e1abb8d269b2ce5e48bd45e5cfd768aa07968a3dc7587bd078c625feac0c52804e41040666b8c5e6153c863b1364a5551a013bd6039d33245072b97fc66551522295d420529896ab74387cabdcf9f3d725f8080808080808080",
47
+ "0xf90211a0156d28ae6d5df3abc7463d2cec2ac7fb426e48340f7f88ae36a81ca5f9dee8f6a09beca8566f0cba30be3f059b6aeae896fd3eab724eb84c9a10908c6009601945a07313d28dc0816c86e30466c06ab5474f24af70211db48fa9a734015534bd74d8a088f7968cbd63613ef3b06122a44ebcc7365dd75589f18cc58fce72064e0a0b1ea02b68f6d1691bce365cb8830fcf7543b95d42efe211e953b24cf61db79cb7e960a0325f1a33b83b8d23d38db039878112ee1d5eab679dbce8a41ebce109978c7e33a0425202aa631cc52afc7cccb657d001584be56cd3750ef4d0e131396bb2fd8a9aa0c1e1bcba129c311b46690757edd4c01a78fede9ed22c8ba1ac5fa07798d92f50a036a0cf8638d4d02175c4fc6871f58280d33c04eb543477a8ab214e270b25d0c0a0c60e6d24a1fe65c208f72e2a2449e50d2c02e001d1982c88d20a01ab5dcf8532a05ad4837ed4e5635fb687c5ae2129dea7c861ddffbbee442f5ad9faeb49ea5a07a01c74851d4ab86eec6c7bef0da2c9346ce8b52435a54f8570ade2d8c3c6e72861a0ff11b81c0f0a9fc418d4b9ec7b038c325a0e4d136ea0ffd2ddeba867ce4b377fa0b198d867690556a5dccc61c0e550be17b56ce3e6c5c1cbb20cc08892da37dec3a04bd945772c230c04f49eced9fe8f7d56d1f2c7f74143d35a81ce366c5fec91f2a0d01d267cecd0009859471b913c6d58150bde9a1f9c23e820fb0e723c70ddd77f80",
48
+ "0xf9014320b9013f03f9013b01832571d584959b8f808501c0d2ae8082520894ff000000000000000000000000000000000084538080c0843b9aca00f8c6a0018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5ca001c01a93e0e3cd2212776aa2d4cc4e0c07c85cb75b968525f4e5859bae6773c4a00118b59a40847e830edd78f12799a36b697087938f5be61d6aca024f98c9b79ba00120c9a9f71037bbe073b4979641a40642a8d6e9babb851bd7e513981f1316d2a0013b850872322a5032994968f043cac34ddf72cb04e387d6b86ac268990b5a17a001b2946a71b7c1e08dff34496e274da1e5ef2dbedd62ed6bab6b07c12d08302701a0f48e45e81f193edea8a7f2e319169201ea084490a282ebfa944a53155125ba90a037f9784866387921294f047d9e00eae3acf249add9880889f797784c496ea9b1"
49
+ ]
50
+ },
51
+ "blobs": [
52
+ {
53
+ "versionedHash": "0x018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5c",
54
+ "kzgCommitment": "0x803d2d67bd32b2926c8c1f86146f88c2eced163e985d6109c5f399b636f3a81574f46e235ec0bf8138131fdf1ef0ea48",
55
+ "index": 0,
56
+ "file": "0x018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5c.bin"
57
+ },
58
+ {
59
+ "versionedHash": "0x01c01a93e0e3cd2212776aa2d4cc4e0c07c85cb75b968525f4e5859bae6773c4",
60
+ "kzgCommitment": "0x98995a21fb0e2f4e6be022dc43b93a1112d374ac646a8e9589a37708416803db825d5c14de8f26ccb0e1c5681b8321fa",
61
+ "index": 1,
62
+ "file": "0x01c01a93e0e3cd2212776aa2d4cc4e0c07c85cb75b968525f4e5859bae6773c4.bin"
63
+ },
64
+ {
65
+ "versionedHash": "0x0118b59a40847e830edd78f12799a36b697087938f5be61d6aca024f98c9b79b",
66
+ "kzgCommitment": "0x80f0d5947c18906d06fc484165fb9d03b3e5dce7ac42f6cf7ceaae26eb8618d89950425af9fef93e3742c016b8fd801e",
67
+ "index": 2,
68
+ "file": "0x0118b59a40847e830edd78f12799a36b697087938f5be61d6aca024f98c9b79b.bin"
69
+ },
70
+ {
71
+ "versionedHash": "0x0120c9a9f71037bbe073b4979641a40642a8d6e9babb851bd7e513981f1316d2",
72
+ "kzgCommitment": "0x86727febe394e5f86626a81ce56daa98aa4a1c1a30ce3c3b8fe4dc66755f78ede7b12a073337ef39defaaafd6d6c013d",
73
+ "index": 3,
74
+ "file": "0x0120c9a9f71037bbe073b4979641a40642a8d6e9babb851bd7e513981f1316d2.bin"
75
+ },
76
+ {
77
+ "versionedHash": "0x013b850872322a5032994968f043cac34ddf72cb04e387d6b86ac268990b5a17",
78
+ "kzgCommitment": "0x933c609081278e6656f1a772b48597ce5e034e316572182cee0e7814fda66f11e5bdf5988ea6483ab16b8280afda9574",
79
+ "index": 4,
80
+ "file": "0x013b850872322a5032994968f043cac34ddf72cb04e387d6b86ac268990b5a17.bin"
81
+ },
82
+ {
83
+ "versionedHash": "0x01b2946a71b7c1e08dff34496e274da1e5ef2dbedd62ed6bab6b07c12d083027",
84
+ "kzgCommitment": "0x8d89e4a886782ebac5e2a188268a8bc26485abc259b281dac91a889aaa13c1e3ef8e65577029bc217eabb7d7da774f8a",
85
+ "index": 5,
86
+ "file": "0x01b2946a71b7c1e08dff34496e274da1e5ef2dbedd62ed6bab6b07c12d083027.bin"
87
+ }
88
+ ],
89
+ "channel": {
90
+ "id": "0x42b5f4f4249919699b40a61bc497f4b5",
91
+ "frames": 7,
92
+ "compression": "brotli",
93
+ "firstBaseBlock": 51979915,
94
+ "lastBaseBlock": 51979939
95
+ },
96
+ "located": {
97
+ "baseTxIndex": 38,
98
+ "txHash": "0x72cc33632237a96db749836905e92e45bd5cc4167403bc7970a69f250b3e9450"
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,69 @@
1
+ {
2
+ "version": "bitgraph-settlement/1",
3
+ "baseBlockNumber": 51979918,
4
+ "baseBlockHash": "0x6844d628876ff2663cbd7f8b9bc39b0dc9a86a1d18d68ac26474ea63c6a81d6f",
5
+ "l1": {
6
+ "chainId": 1,
7
+ "txHash": "0xcb575f2482a33ce5678997adcb082d4b68f7927c932a07a7a89090e61c6ea63a",
8
+ "rawTx": "0x03f9013b01832571d584959b8f808501c0d2ae8082520894ff000000000000000000000000000000000084538080c0843b9aca00f8c6a0018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5ca001c01a93e0e3cd2212776aa2d4cc4e0c07c85cb75b968525f4e5859bae6773c4a00118b59a40847e830edd78f12799a36b697087938f5be61d6aca024f98c9b79ba00120c9a9f71037bbe073b4979641a40642a8d6e9babb851bd7e513981f1316d2a0013b850872322a5032994968f043cac34ddf72cb04e387d6b86ac268990b5a17a001b2946a71b7c1e08dff34496e274da1e5ef2dbedd62ed6bab6b07c12d08302701a0f48e45e81f193edea8a7f2e319169201ea084490a282ebfa944a53155125ba90a037f9784866387921294f047d9e00eae3acf249add9880889f797784c496ea9b1",
9
+ "blockNumber": 26088463,
10
+ "blockHash": "0x8608654cd9d6d66c2ce8a80108db7e5202a6d9ad0072f8c81353cc988d3a93f2",
11
+ "blockTimestamp": 1790749235,
12
+ "blockHeader": "0xf90289a0bd893b925b3482c78646c1df70696e0d301cd5d09dcc34dc2ecc24441e4bc40ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794396343362be2a4da1ce0c1c210945346fb82aa49a0e6bd281387b53a52427452d620531da5d0c670d1a72feacf6d4534700bbdad93a083e423e98cf59e848d311a37f7b9511e8ecbdf2f69a3d9a0a96df448eb593dd7a0c31336933abc1b6e3e3f2c8dbbe2ccc65d4bb9179e665cbb7a06b7ff14085242b90100fff3f3fbffffb9fdefffebfdfffebfdfdfdfffbdfebffffefbfff5f7fefeffebbedfffffeffdff7ff7f2d7cbbdfbfffbbffffdffdfffefe71fbfefb9fffebfffffffffdf7efffff9797fedffdf7fffbbeef7ffbd7e5efdbfffff7fbfbff7fbffeb7ffffeabef6efcffffbfff6fbfffbff3ff7dffafff7edfbefffff7fdff7fbf9b74bffdfefd7fffffeddc737ffa5fffff7fdde9f7f7bffff7bf6f7ffdfcffffdf7dbfffffffbfdfffffdff7f7effffbffdfdfffbeafffbfff7dbaffffffefe7d7e37faf77dffef3ffcffdfa75fe7b7fbd77fe7fe7ffff77ff3fffffffdfffffffdfff7ff7fffffffffe7fbdafbffffeffffb77fe7ffff7dfbfbfff7dbf6ffdf8084018e140f84039387008401ff939d846abcaa339be29ca82051756173617220287175617361722e77696e2920e29ca8a0aec208249057820706989e2a8d4882c6157bffb6f112847ca25c02b90f8bb17088000000000000000084057644a9a096b80d5bb80122a62a3905a4df56e93396c4cf0170a22efb1fdb0f1fe35307b3830e0000840ac7fb8ba0aa4880a6b37d3438d341144ce67680506b21b26e54a90835ce3e8aee0b44a3c9a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
13
+ "txIndex": 18,
14
+ "txInclusionProof": [
15
+ "0xf90131a0fcf4c1b6c3daaf8b4161626c0ed9ed20d4dda4438ac031b46a3a591c70ae3628a0d7b589907c8a5208ea55ae515cf9ee745d8e345f70260f3209e8056af35dbb2fa06ed025ba6929892c332be304b1cae217c390e16e73947c1777023bd6f9aad935a0995d136362f36671287a4eed2b7d0a6648b6d09d1e4c0cbfbf505b40a6452b4fa0c8396e60f6bd3f7eb429d358ecacc46ff149613bf5178c0664dc5070d8e8733ca0aa6c0b1d5e761bf8099cad7b1490374d7a919e439972be8c20a1ae13d1f034c9a0c5836776069aed36f670ecbbb053414f1e1abb8d269b2ce5e48bd45e5cfd768aa07968a3dc7587bd078c625feac0c52804e41040666b8c5e6153c863b1364a5551a013bd6039d33245072b97fc66551522295d420529896ab74387cabdcf9f3d725f8080808080808080",
16
+ "0xf90211a0156d28ae6d5df3abc7463d2cec2ac7fb426e48340f7f88ae36a81ca5f9dee8f6a09beca8566f0cba30be3f059b6aeae896fd3eab724eb84c9a10908c6009601945a07313d28dc0816c86e30466c06ab5474f24af70211db48fa9a734015534bd74d8a088f7968cbd63613ef3b06122a44ebcc7365dd75589f18cc58fce72064e0a0b1ea02b68f6d1691bce365cb8830fcf7543b95d42efe211e953b24cf61db79cb7e960a0325f1a33b83b8d23d38db039878112ee1d5eab679dbce8a41ebce109978c7e33a0425202aa631cc52afc7cccb657d001584be56cd3750ef4d0e131396bb2fd8a9aa0c1e1bcba129c311b46690757edd4c01a78fede9ed22c8ba1ac5fa07798d92f50a036a0cf8638d4d02175c4fc6871f58280d33c04eb543477a8ab214e270b25d0c0a0c60e6d24a1fe65c208f72e2a2449e50d2c02e001d1982c88d20a01ab5dcf8532a05ad4837ed4e5635fb687c5ae2129dea7c861ddffbbee442f5ad9faeb49ea5a07a01c74851d4ab86eec6c7bef0da2c9346ce8b52435a54f8570ade2d8c3c6e72861a0ff11b81c0f0a9fc418d4b9ec7b038c325a0e4d136ea0ffd2ddeba867ce4b377fa0b198d867690556a5dccc61c0e550be17b56ce3e6c5c1cbb20cc08892da37dec3a04bd945772c230c04f49eced9fe8f7d56d1f2c7f74143d35a81ce366c5fec91f2a0d01d267cecd0009859471b913c6d58150bde9a1f9c23e820fb0e723c70ddd77f80",
17
+ "0xf9014320b9013f03f9013b01832571d584959b8f808501c0d2ae8082520894ff000000000000000000000000000000000084538080c0843b9aca00f8c6a0018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5ca001c01a93e0e3cd2212776aa2d4cc4e0c07c85cb75b968525f4e5859bae6773c4a00118b59a40847e830edd78f12799a36b697087938f5be61d6aca024f98c9b79ba00120c9a9f71037bbe073b4979641a40642a8d6e9babb851bd7e513981f1316d2a0013b850872322a5032994968f043cac34ddf72cb04e387d6b86ac268990b5a17a001b2946a71b7c1e08dff34496e274da1e5ef2dbedd62ed6bab6b07c12d08302701a0f48e45e81f193edea8a7f2e319169201ea084490a282ebfa944a53155125ba90a037f9784866387921294f047d9e00eae3acf249add9880889f797784c496ea9b1"
18
+ ]
19
+ },
20
+ "blobs": [
21
+ {
22
+ "versionedHash": "0x018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5c",
23
+ "kzgCommitment": "0x803d2d67bd32b2926c8c1f86146f88c2eced163e985d6109c5f399b636f3a81574f46e235ec0bf8138131fdf1ef0ea48",
24
+ "index": 0,
25
+ "file": "0x018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5c.bin"
26
+ },
27
+ {
28
+ "versionedHash": "0x01c01a93e0e3cd2212776aa2d4cc4e0c07c85cb75b968525f4e5859bae6773c4",
29
+ "kzgCommitment": "0x98995a21fb0e2f4e6be022dc43b93a1112d374ac646a8e9589a37708416803db825d5c14de8f26ccb0e1c5681b8321fa",
30
+ "index": 1,
31
+ "file": "0x01c01a93e0e3cd2212776aa2d4cc4e0c07c85cb75b968525f4e5859bae6773c4.bin"
32
+ },
33
+ {
34
+ "versionedHash": "0x0118b59a40847e830edd78f12799a36b697087938f5be61d6aca024f98c9b79b",
35
+ "kzgCommitment": "0x80f0d5947c18906d06fc484165fb9d03b3e5dce7ac42f6cf7ceaae26eb8618d89950425af9fef93e3742c016b8fd801e",
36
+ "index": 2,
37
+ "file": "0x0118b59a40847e830edd78f12799a36b697087938f5be61d6aca024f98c9b79b.bin"
38
+ },
39
+ {
40
+ "versionedHash": "0x0120c9a9f71037bbe073b4979641a40642a8d6e9babb851bd7e513981f1316d2",
41
+ "kzgCommitment": "0x86727febe394e5f86626a81ce56daa98aa4a1c1a30ce3c3b8fe4dc66755f78ede7b12a073337ef39defaaafd6d6c013d",
42
+ "index": 3,
43
+ "file": "0x0120c9a9f71037bbe073b4979641a40642a8d6e9babb851bd7e513981f1316d2.bin"
44
+ },
45
+ {
46
+ "versionedHash": "0x013b850872322a5032994968f043cac34ddf72cb04e387d6b86ac268990b5a17",
47
+ "kzgCommitment": "0x933c609081278e6656f1a772b48597ce5e034e316572182cee0e7814fda66f11e5bdf5988ea6483ab16b8280afda9574",
48
+ "index": 4,
49
+ "file": "0x013b850872322a5032994968f043cac34ddf72cb04e387d6b86ac268990b5a17.bin"
50
+ },
51
+ {
52
+ "versionedHash": "0x01b2946a71b7c1e08dff34496e274da1e5ef2dbedd62ed6bab6b07c12d083027",
53
+ "kzgCommitment": "0x8d89e4a886782ebac5e2a188268a8bc26485abc259b281dac91a889aaa13c1e3ef8e65577029bc217eabb7d7da774f8a",
54
+ "index": 5,
55
+ "file": "0x01b2946a71b7c1e08dff34496e274da1e5ef2dbedd62ed6bab6b07c12d083027.bin"
56
+ }
57
+ ],
58
+ "channel": {
59
+ "id": "0x42b5f4f4249919699b40a61bc497f4b5",
60
+ "frames": 7,
61
+ "compression": "brotli",
62
+ "firstBaseBlock": 51979915,
63
+ "lastBaseBlock": 51979939
64
+ },
65
+ "located": {
66
+ "baseTxIndex": 38,
67
+ "txHash": "0x72cc33632237a96db749836905e92e45bd5cc4167403bc7970a69f250b3e9450"
68
+ }
69
+ }
@@ -0,0 +1,254 @@
1
+ // Copyright (c) 2024-2026 Argento Computing Inc.
2
+
3
+ /**
4
+ * The blob layer of a ceiling's settlement on Ethereum, on REAL data.
5
+ *
6
+ * Base block 51,979,918 holds the ceiling transaction 0x72cc…9450. Its batch
7
+ * went to Ethereum block 26,088,463 in channel 0x42b5…f4b5: 7 frames in 7
8
+ * blobs across two batcher transactions (0xcb57…a63a carries frames 0-5,
9
+ * 0x6165…0190 frame 6). The bundle carries ONE blob, frame 0 (131,072
10
+ * bytes): the prototype showed that frame 0 alone decompresses to 577,321
11
+ * bytes, past the ceiling transaction at offset 501,594, so the transaction
12
+ * is located from that one blob while the batch holding it is cut (it ends
13
+ * at 650,075). Decoding the whole channel needs all 7 blobs, which the repo
14
+ * does not carry. Nothing here touches a network.
15
+ *
16
+ * Run, after `npm run build` in packages/audit:
17
+ * node --test packages/audit/src/__tests__/settlement.test.ts
18
+ */
19
+ import { test, describe, before, after } from "node:test";
20
+ import assert from "node:assert/strict";
21
+ import { readFileSync } from "node:fs";
22
+ import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
23
+ import { tmpdir } from "node:os";
24
+ import { join } from "node:path";
25
+ import {
26
+ verifySettlementBlobs, decodeOpBlob, parseFrames, decodeSpanBatch, verifyCeilings, ingestEntries, runAudit, computeExitFlags,
27
+ buildMarkdownReport, BASE_MAINNET_ROLLUP, BLOB_BYTES,
28
+ } from "@mikeargento/bitgraph-audit";
29
+ import type { SettlementBlobsOptions } from "@mikeargento/bitgraph-audit";
30
+ import {
31
+ verifySettlementPointer, BASE_MAINNET_SETTLEMENT_PINS, evmHexToBytes as hexToBytes, evmBytesToHex as bytesToHex, keccak256, rlpEncode,
32
+ type SettlementPointer,
33
+ } from "@mikeargento/bitgraph-verify";
34
+
35
+ const fixtureUrl = (name: string): URL => new URL(`./fixtures/settlement/${name}`, import.meta.url);
36
+ const pointer = JSON.parse(readFileSync(fixtureUrl("pointer-51979918.json"), "utf8")) as SettlementPointer;
37
+ const ceilingText = readFileSync(fixtureUrl("ceiling-51979918.json"), "utf8");
38
+ const ceiling = JSON.parse(ceilingText) as { anchor: { txHash: string; rawTx: string; blockNumber: number; blockHash: string } };
39
+ const VH0 = "0x018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5c";
40
+ const BLOB_FILE = `${VH0}.bin`;
41
+ const blob0 = new Uint8Array(readFileSync(fixtureUrl(BLOB_FILE)));
42
+ const CHANNEL = "0x42b5f4f4249919699b40a61bc497f4b5";
43
+ const CEILING_TX = "0x72cc33632237a96db749836905e92e45bd5cc4167403bc7970a69f250b3e9450";
44
+ const BASE_PARENT = "0xd2b1d2cece1bd23dcd34da3463ce2aa9b78da0f3234d1a5bd2d6c328b3a597da";
45
+ const clone = <T>(x: T): T => JSON.parse(JSON.stringify(x)) as T;
46
+ const blobs = (bytes: Uint8Array = blob0, vh = VH0): Map<string, Uint8Array> => new Map([[vh, bytes]]);
47
+ const expect: SettlementBlobsOptions["expect"] = { txHash: ceiling.anchor.txHash, rawTx: ceiling.anchor.rawTx, baseParentHash: BASE_PARENT };
48
+
49
+ describe("the real blob (frame 0 of the channel)", () => {
50
+ test("decodes to one frame of the channel, the pointer layer accepts the pointer", () => {
51
+ assert.equal(blob0.length, BLOB_BYTES);
52
+ const data = decodeOpBlob(blob0);
53
+ assert.equal(data.length, 130044);
54
+ const frames = parseFrames(data);
55
+ assert.equal(frames.length, 1);
56
+ assert.deepEqual({ ...frames[0], frameData: frames[0].frameData.length }, { channelId: CHANNEL, frameNumber: 0, frameData: 130020, isLast: false });
57
+ assert.equal(verifySettlementPointer(pointer, BASE_MAINNET_SETTLEMENT_PINS).ok, true);
58
+ });
59
+
60
+ test("KZG-verified, decoded as a prefix, the ceiling transaction is located in Base block 51,979,918 at index 38", async () => {
61
+ const r = await verifySettlementBlobs(pointer, blobs(), { expect });
62
+ assert.equal(r.ok, true, r.reason);
63
+ assert.deepEqual(r.checks.map((c) => c.name), ["blob 0", "channel", "decompress", "batches", "block", "transaction"]);
64
+ assert.ok(r.checks.every((c) => c.ok));
65
+ assert.match(r.checks[0].detail!, /KZG commitment matches/);
66
+ assert.deepEqual(r.channel, { id: CHANNEL, framesDecoded: 1, framesTotal: 7, complete: false, compression: "brotli", decodedBytes: 577321, baseBlocks: [51979915, 51979916, 51979917] });
67
+ assert.deepEqual(r.located, {
68
+ baseBlockNumber: 51979918, baseBlockTimestamp: 1790749183, baseParentHash: BASE_PARENT, baseTxIndex: 38, txHash: CEILING_TX,
69
+ rawTx: ceiling.anchor.rawTx, batchComplete: false,
70
+ });
71
+ assert.match(r.checks[5].detail!, /bytes equal the ceiling's rawTx/);
72
+ assert.match(r.checks[4].detail!, /parent 0xd2b1d2ce…97da as the ceiling's header says/);
73
+ });
74
+
75
+ test("without expectations, the pointer's own located.txHash is what gets located", async () => {
76
+ const r = await verifySettlementBlobs(pointer, blobs());
77
+ assert.equal(r.ok, true, r.reason);
78
+ assert.equal(r.located!.baseTxIndex, 38);
79
+ });
80
+
81
+ test("the 4,067,670-byte channel is not decodable whole from one blob, and the audit says so", async () => {
82
+ const p = clone(pointer);
83
+ p.baseBlockNumber = 51979930; // in the channel, past what frame 0 reaches
84
+ const r = await verifySettlementBlobs(p, blobs());
85
+ assert.equal(r.ok, false);
86
+ assert.match(r.reason!, /^block: Base block 51979930 is not among the decoded batches/);
87
+ });
88
+ });
89
+
90
+ describe("rejects", () => {
91
+ const rejects = (name: string, mk: () => { p?: SettlementPointer; b?: Map<string, Uint8Array>; o?: SettlementBlobsOptions }, re: RegExp): void => {
92
+ test(name, async () => {
93
+ const { p, b, o } = mk();
94
+ const r = await verifySettlementBlobs(p ?? pointer, b ?? blobs(), o ?? { expect });
95
+ assert.equal(r.ok, false, `accepted: ${name}`);
96
+ assert.match(r.reason!, re);
97
+ assert.equal(r.located, undefined);
98
+ });
99
+ };
100
+ rejects("a flipped blob byte (KZG)", () => { const x = blob0.slice(); x[5000] ^= 1; return { b: blobs(x) }; }, /^blob 0: .*do not commit to the listed KZG commitment/);
101
+ rejects("a blob of the wrong size", () => ({ b: blobs(blob0.slice(0, 1000)) }), /^blob 0: 1000 bytes, a blob is 131072/);
102
+ rejects("bytes under a versioned hash the pointer does not list", () => ({ b: blobs(blob0, "0x01" + "ab".repeat(31)) }), /^blobs: no bytes were supplied/);
103
+ rejects("no blobs at all", () => ({ b: new Map() }), /^blobs: no bytes/);
104
+ rejects("a pointer naming no blobs", () => { const p = clone(pointer); p.blobs = []; return { p }; }, /^blobs: the pointer names no blobs/);
105
+ rejects("a listed commitment that is not the blob's", () => {
106
+ const p = clone(pointer);
107
+ const c = hexToBytes(p.blobs[0].kzgCommitment); c[20] ^= 1; p.blobs[0].kzgCommitment = bytesToHex(c);
108
+ return { p };
109
+ }, /^blob 0: .*do not commit/);
110
+ rejects("a pointer naming another channel", () => { const p = clone(pointer); p.channel!.id = "0x" + "11".repeat(16); return { p }; }, /^channel: no supplied blob carries a frame of channel/);
111
+ rejects("a pointer claiming zlib", () => { const p = clone(pointer); p.channel!.compression = "zlib"; return { p }; }, /^decompress: the channel is brotli, the pointer says zlib/);
112
+ rejects("a Base block not in the channel", () => { const p = clone(pointer); p.baseBlockNumber = 51979900; return { p }; }, /^block: .*not among/);
113
+ rejects("a parent hash the batch does not carry", () => ({ o: { expect: { ...expect, baseParentHash: "0x" + "ee".repeat(32) } } }), /^block: the batch's parent_hash is/);
114
+ rejects("a ceiling transaction that is not in the batch", () => ({ o: { expect: { txHash: "0x" + "cd".repeat(32) } } }), /^transaction: 0xcdcdcdcd…cdcd is not among the \d+ transactions readable before the cut/);
115
+ rejects("a located index the pointer states wrongly", () => { const p = clone(pointer); p.located!.baseTxIndex = 37; return { p }; }, /^transaction: located at index 38, the pointer says 37/);
116
+ rejects("raw bytes that are not the ceiling's", () => ({ o: { expect: { txHash: CEILING_TX, rawTx: "0x02c0" } } }), /^transaction: the bytes in the batch are not the ceiling's rawTx/);
117
+ });
118
+
119
+ describe("through the audit's ceiling check", () => {
120
+ const entries = (files: Record<string, string | Uint8Array>) =>
121
+ ingestEntries(Object.entries(files).map(([path, v]) => ({ path, open: () => (typeof v === "string" ? new TextEncoder().encode(v) : v) })));
122
+
123
+ test("a ceiling with a pointer and no blob bytes: pointer ok, blobs not in bundle, never a failure", async () => {
124
+ const ingest = await entries({ "base-ceiling/ceiling.json": ceilingText });
125
+ const r = await verifyCeilings(ingest);
126
+ const c = r.checks[0];
127
+ assert.equal(c.status, "unmatched"); // the proof is not in this bundle; the settlement is checked all the same
128
+ assert.equal(c.settlement!.status, "pointer-only");
129
+ assert.deepEqual(c.settlement!.lines, [
130
+ "settlement: pointer ok, Ethereum block 26088463 at 2026-09-30 06:20:35 UTC",
131
+ "settlement: blobs not in bundle",
132
+ ]);
133
+ assert.deepEqual(c.settlement!.pointer.existedBy, { blockNumber: 26088463, blockHash: "0x8608654cd9d6d66c2ce8a80108db7e5202a6d9ad0072f8c81353cc988d3a93f2", blockTimestamp: 1790749235 });
134
+ assert.deepEqual(c.settlement!.blobs, { status: "absent", detail: "blobs not in bundle", listed: 6, supplied: 0 });
135
+ });
136
+
137
+ test("the blob next to the ceiling file: both layers verified, one line each", async () => {
138
+ const ingest = await entries({ "base-ceiling/ceiling.json": ceilingText, [`base-ceiling/${BLOB_FILE}`]: blob0 });
139
+ const c = (await verifyCeilings(ingest)).checks[0];
140
+ assert.equal(c.settlement!.status, "verified");
141
+ assert.equal(c.settlement!.lines[1], "settlement: blobs decoded (frames 0..0 of 7), ceiling tx located in Base block 51979918 (batch cut at the last frame supplied)");
142
+ assert.deepEqual(c.settlement!.blobs.located, { baseBlockNumber: 51979918, baseTxIndex: 38, txHash: CEILING_TX, batchComplete: false, framesDecoded: 1, framesTotal: 7 });
143
+ assert.equal(c.settlement!.blobs.supplied, 1);
144
+ assert.equal(c.status, "unmatched");
145
+ });
146
+
147
+ test("the blob under blobs/ at the bundle root is found too", async () => {
148
+ const ingest = await entries({ "base-ceiling/ceiling.json": ceilingText, [`blobs/${BLOB_FILE}`]: blob0 });
149
+ assert.equal((await verifyCeilings(ingest)).checks[0].settlement!.status, "verified");
150
+ });
151
+
152
+ test("a tampered pointer fails the ceiling", async () => {
153
+ const s = JSON.parse(ceilingText);
154
+ const h = hexToBytes(s.settlement.l1.blockHeader); h[300] ^= 1; s.settlement.l1.blockHeader = bytesToHex(h);
155
+ const c = (await verifyCeilings(await entries({ "c.json": JSON.stringify(s) }))).checks[0];
156
+ assert.equal(c.status, "failed");
157
+ assert.match(c.reason!, /^settlement: pointer FAILED \(header: /);
158
+ assert.equal(c.settlement!.status, "failed");
159
+ assert.equal(c.settlement!.blobs.status, "absent");
160
+ });
161
+
162
+ test("a pointer for another Base block than the ceiling's fails the ceiling", async () => {
163
+ const s = JSON.parse(ceilingText);
164
+ s.anchor.blockNumber = 51967531;
165
+ const c = (await verifyCeilings(await entries({ "c.json": JSON.stringify(s) }))).checks[0];
166
+ assert.equal(c.status, "failed");
167
+ assert.match(c.reason!, /the pointer names Base block 51979918 .* this ceiling is in block 51967531/);
168
+ });
169
+
170
+ test("a wrong blob in the bundle fails the ceiling", async () => {
171
+ const bad = blob0.slice(); bad[70000] ^= 1;
172
+ const c = (await verifyCeilings(await entries({ "c.json": ceilingText, [BLOB_FILE]: bad }))).checks[0];
173
+ assert.equal(c.status, "failed");
174
+ assert.match(c.reason!, /^settlement: blobs FAILED \(blob 0: /);
175
+ });
176
+
177
+ test("other pins refuse the pointer", async () => {
178
+ const c = (await verifyCeilings(await entries({ "c.json": ceilingText }), { settlementPins: { ...BASE_MAINNET_SETTLEMENT_PINS, batcher: "0x" + "11".repeat(20) } })).checks[0];
179
+ assert.equal(c.status, "failed");
180
+ assert.match(c.reason!, /pointer FAILED \(batcher:/);
181
+ });
182
+ });
183
+
184
+ describe("through runAudit on a directory bundle", () => {
185
+ let dir: string;
186
+ before(async () => { dir = await mkdtemp(join(tmpdir(), "audit-settlement-")); });
187
+ after(async () => { await rm(dir, { recursive: true, force: true }); });
188
+
189
+ test("exit 0, and the markdown report carries both settlement lines", async () => {
190
+ const b = join(dir, "ok");
191
+ await mkdir(join(b, "base-ceiling", "blobs"), { recursive: true });
192
+ await writeFile(join(b, "base-ceiling", "ceiling.json"), ceilingText);
193
+ await writeFile(join(b, "base-ceiling", "blobs", BLOB_FILE), blob0);
194
+ const r = await runAudit(b);
195
+ const c = r.ceilings!.checks[0];
196
+ assert.equal(c.settlement!.status, "verified");
197
+ assert.equal(computeExitFlags(r).code, 0);
198
+ const md = buildMarkdownReport(r);
199
+ assert.match(md, / - settlement: pointer ok, Ethereum block 26088463 at 2026-09-30 06:20:35 UTC/);
200
+ assert.match(md, / - settlement: blobs decoded \(frames 0\.\.0 of 7\), ceiling tx located in Base block 51979918/);
201
+ });
202
+
203
+ test("a bad pointer sets exit bit 2", async () => {
204
+ const b = join(dir, "bad");
205
+ await mkdir(b, { recursive: true });
206
+ const s = JSON.parse(ceilingText);
207
+ s.settlement.l1.txIndex = 19;
208
+ await writeFile(join(b, "ceiling.json"), JSON.stringify(s));
209
+ const r = await runAudit(b);
210
+ assert.equal(r.ceilings!.checks[0].status, "failed");
211
+ assert.equal(computeExitFlags(r).code & 2, 2);
212
+ });
213
+ });
214
+
215
+ describe("span batches (type 1), synthetic", () => {
216
+ const uvarint = (n: bigint): number[] => { const out: number[] = []; for (;;) { const b = Number(n & 0x7fn); n >>= 7n; if (n === 0n) { out.push(b); return out; } out.push(b | 0x80); } };
217
+ const bitsBytes = (bits: number[]): number[] => { let v = 0n; bits.forEach((b, i) => { if (b) v |= 1n << BigInt(i); }); const n = Math.ceil(bits.length / 8); const out = new Array<number>(n).fill(0); for (let i = n - 1; i >= 0; i--) { out[i] = Number(v & 0xffn); v >>= 8n; } return out; };
218
+ const u = (n: bigint): Uint8Array => { if (n === 0n) return new Uint8Array(0); let h = n.toString(16); if (h.length % 2) h = "0" + h; return hexToBytes(h); };
219
+
220
+ test("a block with a type-2 and a protected legacy transaction is rebuilt into the same raw transactions", () => {
221
+ const chainId = 8453n;
222
+ const to = hexToBytes("0x" + "ab".repeat(20));
223
+ const r1 = hexToBytes("0x" + "11".repeat(32)), s1 = hexToBytes("0x" + "22".repeat(32));
224
+ const r2 = hexToBytes("0x" + "33".repeat(32)), s2 = hexToBytes("0x" + "44".repeat(32));
225
+ const input = hexToBytes("0xdeadbeef");
226
+ // The raw transactions a Base block would carry.
227
+ const type2 = new Uint8Array([0x02, ...rlpEncode([u(chainId), u(7n), u(1000n), u(2000n), u(21000n), to, u(5n), input, [], u(1n), r1, s1])]);
228
+ const legacy = rlpEncode([u(8n), u(3000n), u(50000n), to, u(0n), new Uint8Array(0), u(chainId * 2n + 35n + 0n), r2, s2]);
229
+ // The span batch: prefix, then columns.
230
+ const relTimestamp = 2n * 1000n;
231
+ const bytes = Uint8Array.from([
232
+ ...uvarint(relTimestamp), ...uvarint(123n), ...new Array(20).fill(1), ...new Array(20).fill(2),
233
+ ...uvarint(1n), ...bitsBytes([0]), ...uvarint(2n),
234
+ ...bitsBytes([0, 0]), ...bitsBytes([1, 0]),
235
+ ...r1, ...s1, ...r2, ...s2,
236
+ ...to, ...to,
237
+ 0x02, ...rlpEncode([u(5n), u(1000n), u(2000n), input, []]),
238
+ ...rlpEncode([u(0n), u(3000n), new Uint8Array(0)]),
239
+ ...uvarint(7n), ...uvarint(8n),
240
+ ...uvarint(21000n), ...uvarint(50000n),
241
+ ...bitsBytes([1]),
242
+ ]);
243
+ const blocks = decodeSpanBatch(bytes, BASE_MAINNET_ROLLUP, chainId);
244
+ assert.equal(blocks.length, 1);
245
+ assert.equal(blocks[0].number, 1000);
246
+ assert.equal(blocks[0].timestamp, BASE_MAINNET_ROLLUP.l2GenesisTime + 2000);
247
+ assert.deepEqual(blocks[0].txs.map((t) => t.hash), [type2, legacy].map((raw) => bytesToHex(keccak256(raw))));
248
+ assert.deepEqual(blocks[0].txs.map((t) => bytesToHex(t.raw)), [type2, legacy].map(bytesToHex));
249
+ });
250
+
251
+ test("trailing bytes are refused", () => {
252
+ assert.throws(() => decodeSpanBatch(Uint8Array.from([...[0, 0], ...new Array(40).fill(0), 1, 0, 0, 0, 0, 0, 0, 0, 0]), BASE_MAINNET_ROLLUP, 8453n), /span batch/);
253
+ });
254
+ });
package/src/audit.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  *
6
6
  * ingest -> verify tiers -> reconstruct -> classify anomalies ->
7
7
  * analyze authorities -> identify anchors -> verify witnesses ->
8
- * derive temporal bounds -> validate attestations
8
+ * derive temporal bounds -> ceilings in time -> validate attestations ->
9
+ * exports (bitgraph-export/1)
9
10
  *
10
11
  * Deterministic given the same bundle: the only wall-clock read in the
11
12
  * entire pipeline is the runMetadata.startedAt stamp taken here. Every
@@ -18,6 +19,7 @@
18
19
  */
19
20
 
20
21
  import { verifyCeilings } from "./ceilings.js";
22
+ import { verifyExports } from "./exports.js";
21
23
  import { ingestBundle } from "./ingest.js";
22
24
  import { verifyObservedProofs } from "./verify-tiers.js";
23
25
  import { reconstructChains } from "./reconstruct.js";
@@ -38,7 +40,7 @@ import type { AuditOptions, AuditResult, ExitFlags, IngestResult } from "./types
38
40
  * this equals package.json's version, so the constant cannot drift silently
39
41
  * across releases.
40
42
  */
41
- export const AUDIT_VERSION = "0.7.0";
43
+ export const AUDIT_VERSION = "0.9.0";
42
44
 
43
45
  /** The audit package's own version. */
44
46
  export function auditToolVersion(): string {
@@ -89,6 +91,17 @@ export async function auditIngest(
89
91
  ? { trustedRootCaDer: options.trustedRootCaDer }
90
92
  : undefined
91
93
  );
94
+ // Exports: an export's Base ceiling is held to the same writer and chain as
95
+ // the ceiling files, and a trust policy's measurement allowlist is the PCR0
96
+ // list its attestation claims are judged against, unless given explicitly.
97
+ const exportOptions = options?.exports ?? {};
98
+ const allowed = options?.trustAnchors?.allowedMeasurements;
99
+ const exports = await verifyExports(ingest, {
100
+ ...exportOptions,
101
+ ...(exportOptions.ceilingWriter === undefined && options?.ceilings?.writer !== undefined ? { ceilingWriter: options.ceilings.writer } : {}),
102
+ ...(exportOptions.baseChainId === undefined && options?.ceilings?.chainId !== undefined ? { baseChainId: options.ceilings.chainId } : {}),
103
+ ...(exportOptions.pcr0 === undefined && allowed !== undefined ? { pcr0: allowed } : {}),
104
+ });
92
105
 
93
106
  return {
94
107
  runMetadata: {
@@ -107,6 +120,7 @@ export async function auditIngest(
107
120
  temporal,
108
121
  attestations,
109
122
  ceilings,
123
+ exports,
110
124
  };
111
125
  }
112
126
 
@@ -143,15 +157,23 @@ const WITNESS_VERIFICATION_FAILURE_CODES: ReadonlySet<string> = new Set([
143
157
  /**
144
158
  * Derive the CLI exit bit flags from an audit result. Semantics are
145
159
  * documented on the ExitFlags type: bit 1 is verification failures
146
- * (including unsupported-version rejections), bit 2 is chain or authority
147
- * anomalies, divergences between valid proofs, or anchor witness
148
- * verification failures. artifact-unavailable is never a failure by itself;
149
- * attestation results and informational anchor findings never set bits;
150
- * benign ingest findings never set bits.
160
+ * (including unsupported-version rejections, and any export with a FALSE
161
+ * claim or an export-shaped file that could not be checked), bit 2 is chain
162
+ * or authority anomalies, divergences between valid proofs, or anchor
163
+ * witness verification failures. artifact-unavailable is never a failure by
164
+ * itself; the attestation stage's results and informational anchor findings
165
+ * never set bits (an export's own attestation claims are part of its
166
+ * verdict); benign ingest findings never set bits.
151
167
  */
152
168
  export function computeExitFlags(result: AuditResult): ExitFlags {
169
+ // An export is checked as one self-contained object, like a carrier: a
170
+ // FALSE verdict (any FALSE claim) is a verification failure, as a bad proof
171
+ // is, and so is an export-shaped file the audit could not check.
172
+ const exportFailures = (result.exports?.checks ?? []).some(
173
+ (e) => e.status !== "checked" || e.verdict === "FALSE"
174
+ );
153
175
  const verificationFailures =
154
- result.verification.failed > 0 || result.ingest.counts.unsupportedVersion > 0;
176
+ result.verification.failed > 0 || result.ingest.counts.unsupportedVersion > 0 || exportFailures;
155
177
  const witnessVerificationFailures = result.witnesses.findings.some((f) =>
156
178
  WITNESS_VERIFICATION_FAILURE_CODES.has(f.code)
157
179
  );