notarization verifier: the covering anchor is the first one STRICTLY after the block (an anchor's certificate signs its parent); 32 zero bytes are not a digest; the certificate form is named (v1, v2 scheme-tagged, v3 round-bound) and an unknown form fails the sealed link
This commit is contained in:
parent
d23b6f2908
commit
37f603d06f
@ -32,6 +32,7 @@
|
|||||||
* Exit: 0 every measured link passed, 1 something failed, 2 nothing could be measured.
|
* Exit: 0 every measured link passed, 1 something failed, 2 nothing could be measured.
|
||||||
*/
|
*/
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
import { pathToFileURL } from 'node:url';
|
||||||
|
|
||||||
// ---------- Keccak-256, inlined so this file needs nothing installed ----------
|
// ---------- Keccak-256, inlined so this file needs nothing installed ----------
|
||||||
const RC = [1n,0x8082n,0x800000000000808an,0x8000000080008000n,0x808bn,0x80000001n,0x8000000080008081n,
|
const RC = [1n,0x8082n,0x800000000000808an,0x8000000080008000n,0x808bn,0x80000001n,0x8000000080008081n,
|
||||||
@ -106,6 +107,47 @@ const areTextASCII = (u8, s) => {
|
|||||||
const t = Array.from(u8).map((x) => String.fromCharCode(x)).join('');
|
const t = Array.from(u8).map((x) => String.fromCharCode(x)).join('');
|
||||||
return t.includes(s);
|
return t.includes(s);
|
||||||
};
|
};
|
||||||
|
// A 32-byte vanityData of all zeros is NOT a digest: the Nethermind-derived client writes 32 zero bytes where it anchors nothing
|
||||||
|
// (measured 2026-09-11 on chain 2800, at the admission of the tenth validator), so "32 bytes, no 'besu'" alone called every one of
|
||||||
|
// its blocks an anchor. The anchor digest is a keccak256 output; zero is refused here as it is in every gate of the house.
|
||||||
|
const esteZero = (u8) => Array.from(u8).every((b) => b === 0);
|
||||||
|
// Minimal RLP reader (lists and strings), enough to name the FORM of the certificate in element 6 of extraData.
|
||||||
|
function citesteRLP(u8, pos) {
|
||||||
|
const p = u8[pos];
|
||||||
|
if (p < 0x80) return { list: false, bytes: u8.slice(pos, pos + 1), next: pos + 1 };
|
||||||
|
if (p < 0xb8) { const n = p - 0x80; return { list: false, bytes: u8.slice(pos + 1, pos + 1 + n), next: pos + 1 + n }; }
|
||||||
|
if (p < 0xc0) { const k = p - 0xb7; let n = 0; for (let j = 0; j < k; j++) n = n * 256 + u8[pos + 1 + j]; return { list: false, bytes: u8.slice(pos + 1 + k, pos + 1 + k + n), next: pos + 1 + k + n }; }
|
||||||
|
let k = 0, n;
|
||||||
|
if (p < 0xf8) n = p - 0xc0; else { k = p - 0xf7; n = 0; for (let j = 0; j < k; j++) n = n * 256 + u8[pos + 1 + j]; }
|
||||||
|
const start = pos + 1 + k, end = start + n, items = []; let c = start;
|
||||||
|
while (c < end) { const it = citesteRLP(u8, c); items.push(it); c = it.next; }
|
||||||
|
return { list: true, items, next: end };
|
||||||
|
}
|
||||||
|
// The certificate's form, dispatched on its FIRST element exactly as both clients and the public verifier do (2026-10-07, 1(b)):
|
||||||
|
// a list is v1 (Falcon-512 rows), the scalar 2 is v2 (scheme-tagged, AERE-PQ-ANCHOR-2), the scalar 3 is v3 (round-bound: every seal
|
||||||
|
// is over the parent's commit message for the ONE round the certificate names, AERE-PQ-ANCHOR-3). Anything else is named unknown,
|
||||||
|
// never read as one of them. This tool does not verify the seals (it installs nothing); it names what the anchor carries.
|
||||||
|
// Exported: this is a second copy of the form (this file stays dependency-free on purpose), so an equivalence probe compares it with
|
||||||
|
// the shared certificate decoder row by row on the 1(b) vectors.
|
||||||
|
export function formaCertificatului(extra) {
|
||||||
|
let top; try { top = citesteRLP(extra, 0); } catch { return { forma: 'unreadable', text: 'extraData does not decode as RLP' }; }
|
||||||
|
if (!top.list) return { forma: 'unreadable', text: 'extraData is not an RLP list' };
|
||||||
|
if (top.items.length < 6) return { forma: 'none', text: 'no certificate element (extraData has ' + top.items.length + ' elements)' };
|
||||||
|
const el = top.items[5];
|
||||||
|
if (!el.list) return { forma: 'unknown', text: 'element 6 is not a list' };
|
||||||
|
if (el.items.length === 0) return { forma: 'none', text: 'element 6 is the empty list (no certificate)' };
|
||||||
|
const first = el.items[0];
|
||||||
|
if (first.list) return { forma: 'v1', text: 'certificate v1, ' + el.items.length + ' Falcon-512 rows (no scheme tags, no round)' };
|
||||||
|
const scalar = (it) => (it.list ? null : Array.from(it.bytes).reduce((a, b) => a * 256 + b, 0));
|
||||||
|
if (first.bytes.length === 1 && first.bytes[0] === 2 && el.items.length === 2 && el.items[1].list) return { forma: 'v2', text: 'certificate v2 (scheme-tagged, AERE-PQ-ANCHOR-2), ' + el.items[1].items.length + ' rows' };
|
||||||
|
// v3: the round is a SCALAR (a list there is an unknown form, as in the house decoder; found 2026-10-07 by the decoder-equivalence
|
||||||
|
// probe on the form vector vrsf-03-runda-lista, which this copy read as "v3, round null")
|
||||||
|
if (first.bytes.length === 1 && first.bytes[0] === 3 && el.items.length === 3 && !el.items[1].list && el.items[2].list) {
|
||||||
|
const r = scalar(el.items[1]);
|
||||||
|
return { forma: 'v3', round: r, text: 'certificate v3, round ' + r + ' (round-bound seals, AERE-PQ-ANCHOR-3), ' + el.items[2].items.length + ' rows' };
|
||||||
|
}
|
||||||
|
return { forma: 'unknown', text: 'certificate of UNKNOWN form (opens with ' + (first.list ? 'a list' : '0x' + Array.from(first.bytes).map((b) => b.toString(16).padStart(2, '0')).join('')) + ', ' + el.items.length + ' elements)' };
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- report, three verdicts only ----------
|
// ---------- report, three verdicts only ----------
|
||||||
const linii = [];
|
const linii = [];
|
||||||
@ -127,7 +169,9 @@ async function rpc(method, params) {
|
|||||||
return j.result;
|
return j.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
// Run only as a program: imported (by the decoder-equivalence probe) this file exports formaCertificatului and does nothing else.
|
||||||
|
const RULAT_DIRECT = !!process.argv[1] && pathToFileURL(fs.realpathSync(process.argv[1])).href.toLowerCase() === import.meta.url.toLowerCase();
|
||||||
|
if (RULAT_DIRECT) (async () => {
|
||||||
if (!CALE || !fs.existsSync(CALE)) {
|
if (!CALE || !fs.existsSync(CALE)) {
|
||||||
console.error('folosire: node verifica-certificat.mjs certificat.json [--fisier document] [--rpc URL]');
|
console.error('folosire: node verifica-certificat.mjs certificat.json [--fisier document] [--rpc URL]');
|
||||||
process.exitCode = 2; return;
|
process.exitCode = 2; return;
|
||||||
@ -222,7 +266,10 @@ async function rpc(method, params) {
|
|||||||
NM('sealed', 'the block is covered by the chain post-quantum validator certificate',
|
NM('sealed', 'the block is covered by the chain post-quantum validator certificate',
|
||||||
'this certificate carries no anchorBlock / anchorInterval, and this check will not fall back to a hard-coded schedule: those numbers change when the validator set changes. Re-issue the certificate with them, or verify the anchor by hand.');
|
'this certificate carries no anchorBlock / anchorInterval, and this check will not fall back to a hard-coded schedule: those numbers change when the validator set changes. Re-issue the certificate with them, or verify the anchor by hand.');
|
||||||
} else {
|
} else {
|
||||||
const urm = nr <= start ? start : start + Math.ceil((nr - start) / ancora) * ancora;
|
// the covering anchor is the first one STRICTLY after the block: an anchor's certificate signs its PARENT (2026-10-01; until
|
||||||
|
// then "at or after", so a record notarized in an anchor block itself was reported covered by that anchor)
|
||||||
|
const dupa = nr + 1;
|
||||||
|
const urm = dupa <= start ? start : start + Math.ceil((dupa - start) / ancora) * ancora;
|
||||||
const blk = await rpc('eth_getBlockByNumber', ['0x' + urm.toString(16), false]);
|
const blk = await rpc('eth_getBlockByNumber', ['0x' + urm.toString(16), false]);
|
||||||
if (!blk) NM('sealed', 'the block is covered by the chain post-quantum validator certificate', 'the covering anchor at ' + urm.toLocaleString('en-US') + ' is not on chain yet; re-run later');
|
if (!blk) NM('sealed', 'the block is covered by the chain post-quantum validator certificate', 'the covering anchor at ' + urm.toLocaleString('en-US') + ' is not on chain yet; re-run later');
|
||||||
else {
|
else {
|
||||||
@ -238,14 +285,21 @@ async function rpc(method, params) {
|
|||||||
const marime = de_baza === null
|
const marime = de_baza === null
|
||||||
? 'header ' + oct + ' bytes (plain-header baseline NOT MEASURED: the neighbouring header did not load)'
|
? 'header ' + oct + ' bytes (plain-header baseline NOT MEASURED: the neighbouring header did not load)'
|
||||||
: 'header ' + oct + ' bytes against a measured plain header of ' + de_baza + ' bytes at ' + (urm - 1).toLocaleString('en-US') + ', so ' + (oct - de_baza) + ' bytes of validator certificate';
|
: 'header ' + oct + ' bytes against a measured plain header of ' + de_baza + ' bytes at ' + (urm - 1).toLocaleString('en-US') + ', so ' + (oct - de_baza) + ' bytes of validator certificate';
|
||||||
|
const forma = formaCertificatului(octeti(blk.extraData));
|
||||||
if (!vanity) {
|
if (!vanity) {
|
||||||
NM('sealed', 'the block is covered by the chain post-quantum validator certificate',
|
NM('sealed', 'the block is covered by the chain post-quantum validator certificate',
|
||||||
'extraData at anchor ' + urm.toLocaleString('en-US') + ' did not decode as an RLP list, so nothing is claimed either way');
|
'extraData at anchor ' + urm.toLocaleString('en-US') + ' did not decode as an RLP list, so nothing is claimed either way');
|
||||||
|
} else if (vanity.length === 32 && esteZero(vanity)) {
|
||||||
|
FAIL('sealed', 'the first anchor after this block carries a post-quantum validator certificate',
|
||||||
|
'anchor ' + urm.toLocaleString('en-US') + ' carries 32 ZERO bytes as vanityData: not a digest (a client writes zeros where it anchors nothing); ' + forma.text + '; ' + marime);
|
||||||
|
} else if (vanity.length === 32 && !areTextASCII(vanity, 'besu') && !['v1', 'v2', 'v3'].includes(forma.forma)) {
|
||||||
|
FAIL('sealed', 'the first anchor after this block carries a post-quantum validator certificate of a known form',
|
||||||
|
'anchor ' + urm.toLocaleString('en-US') + ' has a 32-byte vanityData ' + hex(vanity) + ' but ' + forma.text + ' (known forms: v1, v2 scheme-tagged, v3 round-bound); ' + marime);
|
||||||
} else if (vanity.length === 32 && !areTextASCII(vanity, 'besu')) {
|
} else if (vanity.length === 32 && !areTextASCII(vanity, 'besu')) {
|
||||||
PASS('sealed', 'the anchor at or after this block carries a post-quantum validator certificate whose digest is inside the block hash preimage, so rewriting this block later needs post-quantum forgeries',
|
PASS('sealed', 'the first anchor after this block carries a post-quantum validator certificate whose digest is inside the block hash preimage, so rewriting this block later needs post-quantum forgeries',
|
||||||
'anchor ' + urm.toLocaleString('en-US') + ', vanityData is a 32-byte digest ' + hex(vanity) + '; ' + marime);
|
'anchor ' + urm.toLocaleString('en-US') + ', vanityData is a 32-byte digest ' + hex(vanity) + '; ' + forma.text + '; ' + marime);
|
||||||
} else {
|
} else {
|
||||||
FAIL('sealed', 'the anchor at or after this block carries a post-quantum validator certificate',
|
FAIL('sealed', 'the first anchor after this block carries a post-quantum validator certificate',
|
||||||
'anchor ' + urm.toLocaleString('en-US') + ' carries ordinary vanityData (' + vanity.length + ' bytes' + (areTextASCII(vanity, 'besu') ? ", client string containing 'besu'" : '') + '), not an anchor digest; ' + marime);
|
'anchor ' + urm.toLocaleString('en-US') + ' carries ordinary vanityData (' + vanity.length + ' bytes' + (areTextASCII(vanity, 'besu') ? ", client string containing 'besu'" : '') + '), not an anchor digest; ' + marime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user