export var Algorithm;
(function (Algorithm) {
Algorithm[Algorithm["SECP256K1_SCHNORR"] = 0] = "SECP256K1_SCHNORR";
Algorithm[Algorithm["ML_DSA_44"] = 1] = "ML_DSA_44";
Algorithm[Algorithm["SLH_DSA_SHA2_128S"] = 2] = "SLH_DSA_SHA2_128S";
})(Algorithm || (Algorithm = {}));
const MIN_PQC_KEYGEN_ENTROPY = 128;
const MIN_SECP_KEYGEN_ENTROPY = 32;
const MIN_SECP_MESSAGE_LENGTH = 32;
function minKeygenEntropySize(algorithm) {
return algorithm === Algorithm.SECP256K1_SCHNORR ? MIN_SECP_KEYGEN_ENTROPY : MIN_PQC_KEYGEN_ENTROPY;
}
function algorithmName(algorithm) {
return Algorithm[algorithm] ?? String(algorithm);
}
export class BitcoinPQC {
constructor() {
this.module = null;
this.initialized = false;
}
async init(config) {
if (this.initialized && this.module) {
return;
}
let moduleFactory;
const hasWindow = typeof globalThis.window !== 'undefined' ||
(typeof globalThis !== 'undefined' && 'document' in globalThis);
const isBrowser = hasWindow;
if (isBrowser) {
const globalModule = (typeof globalThis !== 'undefined' ? globalThis.Module : undefined) ||
(typeof globalThis.window !== 'undefined' ? globalThis.window.Module : undefined);
if (globalModule) {
moduleFactory = globalModule;
}
else {
try {
const module = await import('./bitcoinpqc.js');
moduleFactory = module.default || module;
}
catch (error) {
throw new Error('bitcoinpqc.js not found. Make sure it is built and accessible. Error: ' + error.message);
}
}
}
else {
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
const path = require('path');
const fs = require('fs');
const currentFileUrl = import.meta.url;
const __dirname = path.dirname(currentFileUrl.startsWith('file://')
? new URL(currentFileUrl).pathname
: currentFileUrl);
const wasmPath = path.join(__dirname, './bitcoinpqc.js');
if (!fs.existsSync(wasmPath)) {
throw new Error(`bitcoinpqc.js not found at ${wasmPath}. Make sure to build the WASM module first.`);
}
moduleFactory = require(wasmPath);
}
const moduleConfig = {
onRuntimeInitialized: () => {
if (config?.onRuntimeInitialized) {
config.onRuntimeInitialized();
}
},
print: config?.print || ((text) => console.log('WASM:', text)),
printErr: config?.printErr || ((text) => console.error('WASM Error:', text)),
getRandomValues: config?.getRandomValues || await this.getDefaultRandomValues(),
};
this.module = await moduleFactory(moduleConfig);
this.initialized = true;
}
async getDefaultRandomValues() {
if (typeof globalThis !== 'undefined' && typeof globalThis.crypto !== 'undefined' &&
typeof globalThis.crypto.getRandomValues === 'function') {
return (arr) => globalThis.crypto.getRandomValues(arr);
}
const win = (typeof globalThis !== 'undefined' && globalThis.window) ? globalThis.window : undefined;
if (win && typeof win.crypto !== 'undefined' && typeof win.crypto.getRandomValues === 'function') {
return (arr) => win.crypto.getRandomValues(arr);
}
try {
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
const crypto = require('crypto');
return (arr) => {
const randomBytes = crypto.randomBytes(arr.length);
arr.set(randomBytes);
return arr;
};
}
catch (error) {
throw new Error('No random number generator available');
}
}
ensureInitialized() {
if (!this.initialized || !this.module) {
throw new Error('Module not initialized. Call init() first.');
}
}
publicKeySize(algorithm) {
this.ensureInitialized();
return this.module.ccall('bitcoin_pqc_public_key_size', 'number', ['number'], [algorithm]);
}
secretKeySize(algorithm) {
this.ensureInitialized();
return this.module.ccall('bitcoin_pqc_secret_key_size', 'number', ['number'], [algorithm]);
}
signatureSize(algorithm) {
this.ensureInitialized();
return this.module.ccall('bitcoin_pqc_signature_size', 'number', ['number'], [algorithm]);
}
generateKeypair(algorithm, randomData) {
this.ensureInitialized();
const mod = this.module;
const minEntropy = minKeygenEntropySize(algorithm);
if (randomData.length < minEntropy) {
throw new Error(`Random data must be at least ${minEntropy} bytes for ${algorithmName(algorithm)}`);
}
const randomPtr = mod._malloc(randomData.length);
mod.HEAP8.set(randomData, randomPtr);
const keypairPtr = mod._malloc(32);
const result = mod.ccall('bitcoin_pqc_keygen', 'number', ['number', 'number', 'number', 'number'], [algorithm, keypairPtr, randomPtr, randomData.length]);
mod._free(randomPtr);
if (result !== 0) {
mod._free(keypairPtr);
throw new Error(`Key generation failed with error code: ${result}`);
}
const publicKeyPtr = this.readUint32(keypairPtr + 4);
const secretKeyPtr = this.readUint32(keypairPtr + 8);
const publicKeySize = this.readUint32(keypairPtr + 12);
const secretKeySize = this.readUint32(keypairPtr + 16);
const publicKey = new Uint8Array(mod.HEAP8.subarray(publicKeyPtr, publicKeyPtr + publicKeySize));
const secretKey = new Uint8Array(mod.HEAP8.subarray(secretKeyPtr, secretKeyPtr + secretKeySize));
mod.ccall('bitcoin_pqc_keypair_free', 'void', ['number'], [keypairPtr]);
mod._free(keypairPtr);
return {
publicKey,
secretKey,
publicKeySize,
secretKeySize,
};
}
sign(secretKey, message, algorithm) {
this.ensureInitialized();
const mod = this.module;
if (algorithm === Algorithm.SECP256K1_SCHNORR && message.length < MIN_SECP_MESSAGE_LENGTH) {
throw new Error('Message must be at least 32 bytes for SECP256K1_SCHNORR (BIP-340 message hash)');
}
const secretKeyPtr = mod._malloc(secretKey.length);
mod.HEAP8.set(secretKey, secretKeyPtr);
const messagePtr = mod._malloc(message.length);
mod.HEAP8.set(message, messagePtr);
const signaturePtr = mod._malloc(16);
mod.HEAP8.fill(0, signaturePtr, signaturePtr + 16);
if (mod.HEAP32) {
mod.HEAP32[signaturePtr >> 2] = algorithm;
}
else {
mod.HEAP8[signaturePtr] = algorithm & 0xFF;
mod.HEAP8[signaturePtr + 1] = (algorithm >> 8) & 0xFF;
mod.HEAP8[signaturePtr + 2] = (algorithm >> 16) & 0xFF;
mod.HEAP8[signaturePtr + 3] = (algorithm >> 24) & 0xFF;
}
const result = mod.ccall('bitcoin_pqc_sign', 'number', ['number', 'number', 'number', 'number', 'number', 'number'], [algorithm, secretKeyPtr, secretKey.length, messagePtr, message.length, signaturePtr]);
mod._free(secretKeyPtr);
mod._free(messagePtr);
if (result !== 0) {
mod._free(signaturePtr);
throw new Error(`Signing failed with error code: ${result}`);
}
const signatureDataPtr = this.readUint32(signaturePtr + 4);
const signatureSize = this.readUint32(signaturePtr + 8);
const signature = new Uint8Array(mod.HEAP8.subarray(signatureDataPtr, signatureDataPtr + signatureSize));
mod.ccall('bitcoin_pqc_signature_free', 'void', ['number'], [signaturePtr]);
mod._free(signaturePtr);
return {
bytes: signature,
size: signatureSize,
};
}
verify(publicKey, message, signature, algorithm) {
this.ensureInitialized();
const mod = this.module;
if (algorithm === Algorithm.SECP256K1_SCHNORR && message.length < MIN_SECP_MESSAGE_LENGTH) {
throw new Error('Message must be at least 32 bytes for SECP256K1_SCHNORR (BIP-340 message hash)');
}
const publicKeyPtr = mod._malloc(publicKey.length);
mod.HEAP8.set(publicKey, publicKeyPtr);
const messagePtr = mod._malloc(message.length);
mod.HEAP8.set(message, messagePtr);
const signaturePtr = mod._malloc(signature.bytes.length);
mod.HEAP8.set(signature.bytes, signaturePtr);
const result = mod.ccall('bitcoin_pqc_verify', 'number', ['number', 'number', 'number', 'number', 'number', 'number', 'number'], [algorithm, publicKeyPtr, publicKey.length, messagePtr, message.length, signaturePtr, signature.bytes.length]);
mod._free(publicKeyPtr);
mod._free(messagePtr);
mod._free(signaturePtr);
return result === 0; }
readUint32(ptr) {
const mod = this.module;
if (mod.HEAP32) {
return (mod.HEAP32[ptr >> 2] >>> 0);
}
const heap = mod.HEAP8;
return (heap[ptr] | (heap[ptr + 1] << 8) | (heap[ptr + 2] << 16) | (heap[ptr + 3] << 24)) >>> 0;
}
getModule() {
this.ensureInitialized();
return this.module;
}
}
export const bitcoinpqc = new BitcoinPQC();
export default bitcoinpqc;