Skip to content

Instantly share code, notes, and snippets.

@mikedotexe
Created August 12, 2025 04:17
Show Gist options
  • Save mikedotexe/acb29901d032bf77f566b9f950ba9438 to your computer and use it in GitHub Desktop.
Save mikedotexe/acb29901d032bf77f566b9f950ba9438 to your computer and use it in GitHub Desktop.
noble and crypto stuff.js
// 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'));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment