// crypto-verify.js // Usage: // import { verifyEd25519, verifySecp256k1 } from './crypto-verify.js' // const ok1 = await verifyEd25519(msgBytes, sig64, pub32); // const ok2 = await verifySecp256k1(msgBytes, sig64, pub33or65); import * as secp from '@noble/secp256k1'; import * as ed from '@noble/ed25519'; export async function verifyEd25519(message, signature, publicKey) { const msg = toBytes(message); const sig = toBytes(signature); const pub = toBytes(publicKey); if (await ed25519Available()) { const key = await crypto.subtle.importKey( 'raw', pub, // 32 bytes { name: 'Ed25519' }, false, ['verify'], ); return crypto.subtle.verify({ name: 'Ed25519' }, key, sig, msg); } // Fallback: noble-ed25519 return ed.verify(sig, msg, pub); } export async function verifySecp256k1(message, signature, publicKey, opts = {}) { // Web Crypto does NOT support secp256k1; use noble. // By default we prehash with SHA-256 (change if your protocol uses keccak256, etc.) const { hash = 'SHA-256' } = opts; const msg = toBytes(message); const sig = toBytes(signature); const pub = toBytes(publicKey); // 33 (compressed) or 65 (uncompressed) bytes const digest = await hashBytes(msg, hash); // 32 bytes return secp.verify(sig, digest, pub, { lowS: true }); } // --- helpers --- async function ed25519Available() { try { if (!globalThis.crypto?.subtle) return false; await crypto.subtle.generateKey({ name: 'Ed25519' }, false, ['sign', 'verify']); return true; } catch { return false; } } async function hashBytes(bytes, algo) { if (!globalThis.crypto?.subtle) throw new Error('Web Crypto not available'); const out = await crypto.subtle.digest(algo, toBytes(bytes)); return new Uint8Array(out); } function toBytes(x) { if (x instanceof Uint8Array) return x; if (typeof x === 'string') { const s = x.trim(); if (/^[0-9a-fA-F]+$/.test(s)) return hexToBytes(s); if (/^[A-Za-z0-9+/=]+$/.test(s)) return base64ToBytes(s); } throw new TypeError('Expected Uint8Array, hex, or base64 string'); } function hexToBytes(hex) { if (hex.length % 2) throw new Error('hex length must be even'); const out = new Uint8Array(hex.length / 2); for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.substr(i * 2, 2), 16); return out; } function base64ToBytes(b64) { if (typeof atob === 'function') { const bin = atob(b64); const out = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); return out; } else { // Node return Uint8Array.from(Buffer.from(b64, 'base64')); } }