aere-proof-of-software/tools/proof-of-software/test/pos.test.mjs
Aere Network 065f84e34a Aere Proof of Software 1.4.0: an attestation of what was built, from what, by whom and when, verifiable without trusting Aere Network
Every artifact's SHA-256 and size, the SBOM's digest, the git commit and tree of the source, a hybrid signature (classical + ML-DSA,
both required) and, when notarized, a first-seen time on Aere Network covered by the validators' post-quantum certificate. verify
recomputes everything from the files; --rebuild-from repeats an npm pack build from a clone the verifier chose; the SBOM is
re-derived from the committed package-lock.json or, new in 1.4.0, from the committed go.mod and go.sum; --signer requires the
signing keys you expect (1.4.0); a developer credential binds the keys to a person, judged against a trust root you choose. The
builder's declared date can only accuse, never acquit (1.4.0). Includes the GitHub Action and the hybrid signature library it uses.

Laid out as in the development repository (tools/proof-of-software/, sdk-pq-sign/, sdk/) so that nothing is rewritten for
publication. Tests and negative controls measured on 2026-09-29 are listed in README.md.
2026-09-29 22:46:57 +03:00

151 lines
12 KiB
JavaScript

// Proof of Software: fiecare afirmatie cu perechea ei negativa. Offline; cu AERE_SCRIE=1 si AERE_CHEIE_FISIER face o
// notarizare reala (o tranzactie pe 2800) si citeste dovada ei.
import assert from 'node:assert';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { attest, verify, buildStatement, packNpmFromTree } from '../pos.mjs';
import * as pq from '../../../sdk-pq-sign/index.mjs';
let treceri = 0; const esecuri = [];
async function test(nume, fn) { try { await fn(); treceri++; console.log(' OK ' + nume); } catch (e) { esecuri.push(nume); console.log(' ESEC ' + nume + ' — ' + (e.message || e)); } }
const D = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-'));
const A = path.join(D, 'app.tgz'), B = path.join(D, 'app.sha'), S = path.join(D, 'sbom.json');
fs.writeFileSync(A, Buffer.from('binary artifact bytes 12345')); fs.writeFileSync(B, 'checksums'); fs.writeFileSync(S, JSON.stringify({ bomFormat: 'CycloneDX', components: [] }));
const keys = pq.generateKeyPair({ alg: 'secp256k1+ml-dsa-65' });
await test('statement: artifacts with sha256 + size, sbom hashed, builder, time', () => {
const s = buildStatement({ artifacts: [A, B], sbom: S, name: 'app', version: '1.0.0', cwd: D });
assert.strictEqual(s.artifacts.length, 2); assert.match(s.artifacts[0].sha256, /^0x[0-9a-f]{64}$/); assert.strictEqual(s.artifacts[0].bytes, 27);
assert.strictEqual(s.sbom.name, 'sbom.json'); assert.strictEqual(s.subject.version, '1.0.0'); assert.ok(s.createdAt);
});
await test('two artifacts with the same name are refused (verification matches by name)', () => {
const D2 = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-')); fs.mkdirSync(path.join(D2, 'x')); fs.writeFileSync(path.join(D2, 'x', 'app.tgz'), 'a');
assert.throws(() => buildStatement({ artifacts: [A, path.join(D2, 'x', 'app.tgz')], cwd: D }), /share the name/);
});
const att = await attest({ artifacts: [A, B], sbom: S, name: 'app', version: '1.0.0', keys, cwd: D });
await test('attest + verify with the same files: VALID, signature both halves, on chain absent (reported, not assumed)', async () => {
const r = await verify(att, [A, B, S]);
assert.strictEqual(r.valid, true, JSON.stringify(r.checks));
assert.ok(r.checks.find((c) => c.name.startsWith('hybrid signature') && c.pass === true));
assert.ok(r.checks.find((c) => c.name === 'on chain' && c.pass === null));
});
await test('a tampered artifact (one byte) makes verification INVALID', async () => {
const T = path.join(D, 'app.tgz.tampered'); fs.mkdirSync(path.join(D, 't'), { recursive: true }); const T2 = path.join(D, 't', 'app.tgz');
fs.writeFileSync(T2, Buffer.from('binary artifact bytes 12346'));
const r = await verify(att, [T2, B, S]); assert.strictEqual(r.valid, false); assert.ok(r.checks.find((c) => c.name.includes('app.tgz') && c.pass === false));
});
await test('a missing artifact is INVALID (absent artifact is a failure, absent SBOM is only reported)', async () => {
const r = await verify(att, [A]); assert.strictEqual(r.valid, false);
assert.ok(r.checks.find((c) => c.name.includes('app.sha') && c.pass === false));
assert.ok(r.checks.find((c) => c.name.startsWith('sbom') && c.pass === null));
});
await test('an edited statement (version changed) breaks statementHash and the signature', async () => {
const e = JSON.parse(JSON.stringify(att)); e.statement.subject.version = '1.0.1';
const r = await verify(e, [A, B, S]); assert.strictEqual(r.valid, false);
assert.ok(r.checks.find((c) => c.name.startsWith('statementHash') && c.pass === false));
assert.ok(r.checks.find((c) => c.name.startsWith('hybrid signature') && c.pass === false));
});
await test('a flipped bit in the post-quantum half of the signature is INVALID', async () => {
const e = JSON.parse(JSON.stringify(att)); const b = pq.fromHex(e.signature.pqSignature); b[5] ^= 1; e.signature.pqSignature = pq.toHex(b);
const r = await verify(e, [A, B, S]); assert.strictEqual(r.valid, false);
});
await test('without keys: no signature, still VALID on digests alone, signature reported absent', async () => {
const a2 = await attest({ artifacts: [A], cwd: D }); const r = await verify(a2, [A]);
assert.strictEqual(r.valid, true); assert.ok(r.checks.find((c) => c.name === 'hybrid signature' && c.pass === null));
});
await test('CLI: keygen, attest, verify round trip through files; tampered file fails with exit 1', () => {
const pos = path.join(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')), '..', 'pos.mjs');
const K = path.join(D, 'keys.json'), O = path.join(D, 'attestation.json');
execFileSync('node', [pos, 'keygen', '--out', K, '--alg', 'ed25519+ml-dsa-44'], { stdio: 'pipe' });
const out = execFileSync('node', [pos, 'attest', '--out', O, '--name', 'app', '--version', '2', '--sbom', S, '--keys', K, A, B], { stdio: 'pipe' }).toString();
assert.match(out, /signature ed25519\+ml-dsa-44/);
const v = execFileSync('node', [pos, 'verify', O, A, B, S], { stdio: 'pipe' }).toString(); assert.match(v, /VALID: every present claim holds/);
let code = 0; try { execFileSync('node', [pos, 'verify', O, path.join(D, 't', 'app.tgz'), B, S], { stdio: 'pipe' }); } catch (e) { code = e.status; }
assert.strictEqual(code, 1);
});
// ---- legarea de ARBORELE git si verificarea prin RECONSTRUCTIE (1.1.0). Un depozit de proba cu un pachet npm mic.
const G = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-git-'));
const g = (args, cwd = G) => execFileSync('git', ['-c', 'core.autocrlf=false', '-c', 'user.name=proba', '-c', 'user.email=proba@example.invalid', ...args], { cwd, stdio: 'pipe' }).toString().trim();
g(['init', '-q']); fs.mkdirSync(path.join(G, 'pkg', 'lib'), { recursive: true }); fs.mkdirSync(path.join(G, 'altceva'));
fs.writeFileSync(path.join(G, 'pkg', 'package.json'), JSON.stringify({ name: 'proba-pos', version: '0.0.1', main: 'lib/index.js', files: ['lib', 'LICENSE'] }, null, 1) + '\n');
fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 1;\n'); fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\nline two\n');
fs.writeFileSync(path.join(G, 'altceva', 'x.txt'), 'nu face parte din pachet\n');
g(['add', '-A']); g(['commit', '-q', '-m', 'pachet de proba']);
const OUT1 = path.join(G, '..', path.basename(G) + '-out'); fs.mkdirSync(OUT1);
const TGZ = packNpmFromTree(G, 'HEAD:pkg', OUT1);
await test('source tree: --source-path records path, tree and pathDirty; build records npm-pack and the npm version', () => {
const s = buildStatement({ artifacts: [TGZ], sourcePath: 'pkg', build: 'npm-pack', cwd: G });
assert.strictEqual(s.source.path, 'pkg'); assert.strictEqual(s.source.tree, g(['rev-parse', 'HEAD:pkg'])); assert.strictEqual(s.source.pathDirty, false);
assert.strictEqual(s.build.kind, 'npm-pack'); assert.match(s.build.npm, /^\d+\./);
assert.throws(() => buildStatement({ artifacts: [TGZ], sourcePath: 'nu-exista', cwd: G }), /not tracked at HEAD/);
});
await test('the listing of a tree does not depend on the directory it is asked from (ls-tree --full-tree)', () => {
const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sub-'));
const t2 = packNpmFromTree(path.join(G, 'altceva'), 'HEAD:pkg', o);
assert.strictEqual(fs.readFileSync(t2).equals(fs.readFileSync(TGZ)), true, 'same tree asked from a subdirectory gave other bytes');
});
await test('the artifact is a function of the COMMITTED tree: CRLF in the working copy changes a working-copy pack, not this one', () => {
fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\r\nline two\r\n');
const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-crlf-'));
const again = packNpmFromTree(G, 'HEAD:pkg', o);
assert.strictEqual(fs.readFileSync(again).equals(fs.readFileSync(TGZ)), true);
const wc = execFileSync('npm pack --silent --pack-destination "' + o + '"', { cwd: path.join(G, 'pkg'), shell: true, stdio: 'pipe' }).toString().trim().split(/\r?\n/).pop();
fs.renameSync(path.join(o, wc), path.join(o, 'din-copia-de-lucru.tgz'));
assert.strictEqual(fs.readFileSync(path.join(o, 'din-copia-de-lucru.tgz')).equals(fs.readFileSync(TGZ)), false, 'control: the working-copy pack should differ');
fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\nline two\n');
});
const attG = await attest({ artifacts: [TGZ], name: 'proba-pos', version: '0.0.1', sourcePath: 'pkg', build: 'npm-pack', keys, cwd: G });
await test('verify --rebuild-from: a clone chosen by the verifier reproduces the attested artifact byte for byte', async () => {
const C = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-clona-')); g(['clone', '-q', G, C], os.tmpdir());
const r = await verify(attG, [TGZ], { rebuildFrom: C });
assert.strictEqual(r.valid, true, JSON.stringify(r.checks));
assert.ok(r.checks.find((c) => c.name.startsWith('rebuild: npm pack of the attested tree reproduces') && c.pass === true));
const r0 = await verify(attG, [TGZ]); assert.ok(r0.checks.find((c) => c.name === 'rebuild' && c.pass === null), 'without --rebuild-from the rebuild is reported as not attempted');
});
await test('an artifact that did NOT come from the attested tree fails the rebuild (digest differs), though its own digest matches', async () => {
fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 2; // schimbat si necomis\n');
const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-strain-'));
const wc = execFileSync('npm pack --silent --pack-destination "' + o + '"', { cwd: path.join(G, 'pkg'), shell: true, stdio: 'pipe' }).toString().trim().split(/\r?\n/).pop();
const strain = path.join(o, wc);
const a = await attest({ artifacts: [strain], sourcePath: 'pkg', build: 'npm-pack', cwd: G });
assert.strictEqual(a.statement.source.pathDirty, true, 'the statement itself says the path was dirty');
const plain = await verify(a, [strain]); assert.strictEqual(plain.valid, true, 'digests alone hold: that is why the rebuild matters');
const r = await verify(a, [strain], { rebuildFrom: G }); assert.strictEqual(r.valid, false);
assert.ok(r.checks.find((c) => c.name.startsWith('rebuild: npm pack of the attested tree reproduces') && c.pass === false && /got 0x/.test(c.detail)));
g(['checkout', '-q', '--', 'pkg/lib/index.js']);
});
await test('a clone that does not hold the attested commit fails on the SOURCE check, before any build', async () => {
const E = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-gol-')); g(['init', '-q'], E); fs.writeFileSync(path.join(E, 'a'), 'a'); g(['add', '-A'], E); g(['commit', '-q', '-m', 'alt depozit'], E);
const r = await verify(attG, [TGZ], { rebuildFrom: E }); assert.strictEqual(r.valid, false);
assert.ok(r.checks.find((c) => c.name.startsWith('source:') && c.pass === false && /not in this clone/.test(c.detail)));
assert.ok(!r.checks.find((c) => c.name.startsWith('rebuild: npm pack')), 'no build was attempted');
});
await test('a statement without a source tree cannot be rebuilt, and asking for it is INVALID, not silently skipped', async () => {
const r = await verify(att, [A, B, S], { rebuildFrom: G }); assert.strictEqual(r.valid, false);
assert.ok(r.checks.find((c) => c.name === 'rebuild: the statement names a source tree' && c.pass === false));
});
const CHEIE_F = process.env.AERE_CHEIE_FISIER;
if (process.env.AERE_SCRIE === '1' && CHEIE_F) {
const { AereCloud } = await import('../../../sdk/index.mjs');
const cloud = new AereCloud({ apiKey: fs.readFileSync(CHEIE_F, 'utf8').trim() });
await test('LIVE: attest with notarization on chain 2800, then verify reads the proof (notarized, receipt matches)', async () => {
const a = await attest({ artifacts: [A, B], sbom: S, name: 'app', version: 'live', keys, cloud, cwd: D });
assert.ok(a.notarization && a.notarization.txHash && a.notarization.block > 0, JSON.stringify(a.notarization));
await new Promise((r) => setTimeout(r, 4000));
const r = await verify(a, [A, B, S], { cloud });
assert.strictEqual(r.valid, true, JSON.stringify(r.checks));
assert.ok(r.checks.find((c) => c.name === 'on chain: statementHash is notarized' && c.pass === true));
console.log(' ' + r.checks.filter((c) => c.name.startsWith('on chain')).map((c) => c.name).join(' | '));
});
} else console.log(' (sarit) notarizarea reala: AERE_SCRIE=1 si AERE_CHEIE_FISIER=<fisier> (scrie o tranzactie pe 2800)');
console.log(`\n${treceri} treceri, ${esecuri.length} esecuri`);
if (esecuri.length) process.exitCode = 1;