#!/usr/bin/env node // Fly Strategic Group, LLC. Reference implementation of the frozen epb2 reader. // BLAKE3 dependency: @noble/hashes (MIT), https://github.com/paulmillr/noble-hashes import { blake3 } from '@noble/hashes/blake3.js'; import { createHash } from 'node:crypto'; import { readFile, stat, writeFile } from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; const LENGTH=1312000, HALF=656000, ALPHABET='abcdefghijklmnopqrstuvwxyz ,.', DIGITS='0123456789abcdefghijklmnopqrs'; const utf8=s=>new TextEncoder().encode(s); const concat=(...parts)=>{const out=new Uint8Array(parts.reduce((n,p)=>n+p.length,0));let offset=0;for(const p of parts){out.set(p,offset);offset+=p.length;}return out;}; const sha256=bytes=>createHash('sha256').update(bytes).digest('hex'); function stream(domain,message,count){ const seed=blake3(concat(utf8(domain),Uint8Array.of(0),message)),out=new Uint8Array(count);let index=0,low=0,high=0; while(index>>0;if(low===0)high=(high+1)>>>0;if(low===0&&high===0&&indexd>=29))throw Error('Invalid volume digits'); let left=input.slice(0,HALF),right=input.slice(HALF); for(let step=0;step<8;step++){const round=inverse?7-step:step,f=stream(`EPB/v2/feistel/${round}`,inverse?left:right,HALF),next=new Uint8Array(HALF); for(let i=0;i1400000)throw Error('Address exceeds limit'); const match=/^epb2\/(0|[1-9a-s][0-9a-s]*)\/w([0-3])\/s([0-4])\/v([0-9]|[12][0-9]|3[01])(?:\/p([1-9][0-9]{0,2}))?$/.exec(address); if(!match||match[1].length>LENGTH||(match[5]&&Number(match[5])>410))throw Error('Invalid canonical epb2 address'); const location=new Uint8Array(LENGTH);let carry=Number(match[2])*160+Number(match[3])*32+Number(match[4]); for(let i=LENGTH-1,j=match[1].length-1;i>=0;i--,j--){const value=(j>=0?DIGITS.indexOf(match[1][j]):0)*640+carry;location[i]=value%29;carry=Math.floor(value/29);} if(carry)throw Error('Address is outside the Library'); const content=transform(location,true); for(let i=1;i410||content.length!==LENGTH)throw Error('Page must be 1..410'); const start=(page-1)*3200;let text='';for(let row=0;row<40;row++){for(let col=0;col<80;col++)text+=ALPHABET[content[start+row*80+col]];if(row<39)text+='\n';}return text; } // Fixture encoding is used only by --self-test, not by address reconstruction. function fixtureAddress(location){ const quotient=new Uint8Array(LENGTH);let remainder=0; for(let i=0;ii%29); for(let i=0;i1400000)throw Error('Address file exceeds limit'); const address=await readFile(args['--address-file'],'utf8'); // A single file-ending newline is accepted; address contents otherwise canonical. const value=address.endsWith('\n')?address.slice(0,-1):address; const {content,page:addressPage}=reconstructAddress(value),requested=args['--page']; if(requested&&!/^(?:[1-9][0-9]{0,2})$/.test(requested))throw Error('Page must be 1..410'); const page=requested?Number(requested):addressPage;if(!page)throw Error('Choose a page with --page'); const text=pageText(content,page);await writeFile(args['--output'],text,{mode:0o600});console.log(JSON.stringify({algorithm:'epb2',page,rows:40,columns:80,pageTextSha256:sha256(utf8(text))})); } if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href)main().catch(()=>{console.error('Verification failed. Check the address, page, files and epb2 reference version.');process.exitCode=1;});