sdk-js/examples/threshold-account-demo.mjs
Liviu 6d6c18ae41 SDK 0.16.12: the address book and typed clients catch up with the live chain, and client mentions get the same discretion as everywhere else
The published repository served the 0.3.0 line from May while the install
page already pointed here. This carries the uncommitted line: 35+ typed
contract clients over the canonical address book, the compliance surface,
and the hybrid PQC wallet helpers.
2026-08-15 14:01:27 +03:00

66 lines
3.3 KiB
JavaScript

// SPDX-License-Identifier: MIT
//
// Runnable demo: node examples/threshold-account-demo.mjs (after `npm run build`)
//
// End-to-end proof that the AereThresholdAccount SDK produces cryptographically valid
// authorizing legs for a non-custodial t-of-n POST-QUANTUM account, using REAL Falcon-512
// signing/verification off-chain (the same envelope the live precompile verifies). It does not
// touch a chain: it shows exactly what each committee member signs, that ANY t members authorize,
// and that below-threshold or forged legs do not.
import { getBytes } from 'ethers';
import { keygen, verifyLocal, SCHEME, signLeg, encodeLegs, thresholdExecChallenge } from '../dist/index.js';
const line = (s = '') => console.log(s);
const SCH = SCHEME.FALCON512;
const N = 3, T = 2;
line('=== AERE non-custodial post-quantum threshold account — SDK demo (2-of-3 Falcon-512) ===\n');
// 1. A committee of 3 independent post-quantum keys (in production each lives on a separate device).
const members = [];
for (let i = 0; i < N; i++) {
const kp = keygen(SCH, getBytes('0x' + (i + 1).toString(16).padStart(96, '0'))); // 48-byte seed, deterministic for the demo
members.push({ memberIndex: i, publicKey: kp.publicKey, secretKey: kp.secretKey });
line(` member ${i}: Falcon-512 pubkey ${kp.publicKey.length} bytes`);
}
// 2. The account + the call to authorize. (Addresses are placeholders — this demo is off-chain.)
const account = '0x1111111111111111111111111111111111111111';
const target = '0x2222222222222222222222222222222222222222';
const value = 0n;
const data = '0xa1e78d8b'; // some function selector the account would call
const nonce = 0n;
// 3. The EXACT challenge every member must sign, derived byte-for-byte like the contract.
const challenge = thresholdExecChallenge(account, nonce, target, value, data, 2800n);
line(`\n exec challenge @ nonce ${nonce}: ${challenge}`);
// 4. TWO members (0 and 2) each PQC-sign the challenge -> authorizing legs.
const signers = [members[0], members[2]];
const legs = signers.map((m) => signLeg(SCH, { memberIndex: m.memberIndex, secretKey: m.secretKey }, challenge));
// 5. Verify every leg locally with REAL Falcon verification (what the precompile enforces on-chain).
let ok = 0;
for (const leg of legs) {
const member = members[leg.memberIndex];
const valid = verifyLocal(SCH, getBytes(challenge), getBytes(leg.signature), member.publicKey);
line(` leg from member ${leg.memberIndex}: real Falcon verify = ${valid}`);
if (valid) ok++;
}
line(`\n distinct valid signers = ${ok} / threshold ${T} -> ${ok >= T ? 'AUTHORIZED' : 'REJECTED'}`);
// 6. The abi-encoded blob / call args the account.executeThreshold(...) would receive.
const blob = encodeLegs(legs);
line(`\n encoded Leg[] (${(blob.length - 2) / 2} bytes) ready for executeThreshold(target,value,data,legs)`);
// 7. Negative cases: below threshold, and a forged leg.
const oneLeg = [legs[0]];
line(`\n below-threshold (1 leg): distinct valid ${oneLeg.length} < ${T} -> REJECTED (as expected)`);
const forged = getBytes(legs[1].signature);
forged[41] ^= 0x01; // flip a byte inside the Falcon esig
const forgedValid = verifyLocal(SCH, getBytes(challenge), forged, members[2].publicKey);
line(` forged leg: real Falcon verify = ${forgedValid} -> not counted (as expected)`);
line('\n=== demo complete: any 2 of the 3 post-quantum keys authorize; 1 or a forgery does not. ===');