OverviewFilesCommitsIssuesDocsReleasesPeers

ap-bridge / spike / rsa-vector.mjs

58 lines · javascript ref:
// Keystone spike: prove pure-JS RSA-2048 PKCS#1 v1.5 / SHA-256 works under Bare, byte-for-byte
// against openssl. If this passes, the rest of M1 is assembly — HTTP Signatures are just this
// primitive plus string formatting.
//
// Run: bare spike/rsa-vector.mjs   (after spike/gen-keys.sh)
//
// PKCS#1 v1.5 is deterministic (unlike PSS), so a correct implementation must produce the EXACT
// bytes openssl does. That makes this a real conformance test, not a smoke test.

import fs from 'bare-fs'
import b4a from 'b4a'
import { sign, verify, parsePrivateKey, parsePublicKey } from '../lib/rsa.js'

const dir = new URL('.', import.meta.url).pathname
const read = (f) => fs.readFileSync(dir + f)

let pass = 0
let fail = 0
const check = (name, ok, detail = '') => {
  if (ok) { pass++; console.log(`  ok   ${name}`) } else { fail++; console.log(`  FAIL ${name} ${detail}`) }
}

console.log('RSA-2048 PKCS#1 v1.5 / SHA-256 under Bare\n')

const privPem = b4a.toString(read('test-key.pem'))
const pubPem = b4a.toString(read('test-pub.pem'))
const message = read('test-msg.txt')
const opensslSig = read('test.sig')

// --- parsing ---
const priv = parsePrivateKey(privPem)
const pub = parsePublicKey(pubPem)
check('private key parses', priv.n > 0n && priv.d > 0n)
check('public key parses', pub.n > 0n)
check('modulus is 2048-bit', priv.size === 256, `got ${priv.size} bytes`)
check('public exponent is 65537', pub.e === 65537n, `got ${pub.e}`)
check('public modulus matches private', pub.n === priv.n)

// --- THE test: our signature must equal openssl's, byte for byte ---
const ours = sign(message, priv)
check('signature length is 256 bytes', ours.length === 256, `got ${ours.length}`)
check('★ signature is byte-identical to openssl', b4a.equals(ours, opensslSig),
  `\n       ours:    ${b4a.toString(ours.subarray(0, 16), 'hex')}…\n       openssl: ${b4a.toString(opensslSig.subarray(0, 16), 'hex')}…`)

// --- verification both directions ---
check('we verify openssl\'s signature', verify(message, opensslSig, pub))
check('we verify our own signature', verify(message, ours, pub))

// --- negative cases: a verifier that never says no is worthless ---
check('rejects a tampered message', !verify(b4a.from('not the message'), ours, pub))
const bitFlipped = b4a.from(ours)
bitFlipped[100] ^= 0x01
check('rejects a bit-flipped signature', !verify(message, bitFlipped, pub))
check('rejects a truncated signature', !verify(message, ours.subarray(0, 255), pub))

console.log(`\n${fail === 0 ? '✅' : '❌'} ${pass} passed, ${fail} failed`)
if (fail > 0) throw new Error('RSA spike failed — do NOT build the signing layer on this')