UNPKG

@scure/btc-signer

Version:

Audited & minimal library for Bitcoin. Handle transactions, Schnorr, Taproot, UTXO & PSBT

130 lines 5.28 kB
import { schnorr, secp256k1 as secp } from '@noble/curves/secp256k1.js'; import { bytesToNumberBE, numberToBytesBE } from '@noble/curves/utils.js'; import { ripemd160 } from '@noble/hashes/legacy.js'; import { sha256 } from '@noble/hashes/sha2.js'; import { utils as packedUtils, U32LE } from 'micro-packed'; const Point = secp.Point; const Fn = Point.Fn; const CURVE_ORDER = Point.Fn.ORDER; export const hasEven = (y) => y % 2n === 0n; const isBytes = packedUtils.isBytes; const concatBytes = packedUtils.concatBytes; const equalBytes = packedUtils.equalBytes; export { concatBytes, equalBytes, isBytes, sha256 }; export const hash160 = (msg) => ripemd160(sha256(msg)); export const sha256x2 = (...msgs) => sha256(sha256(concatBytes(...msgs))); export const randomPrivateKeyBytes = schnorr.utils.randomSecretKey; export const pubSchnorr = schnorr.getPublicKey; export const pubECDSA = secp.getPublicKey; // low-r signature grinding. Used to reduce tx size by 1 byte. // noble/secp256k1 does not support the feature: it is not used outside of BTC. // We implement it manually, because in BTC it's common. // Not best way, but closest to bitcoin implementation (easier to check) const hasLowR = (sig) => sig.r < CURVE_ORDER / 2n; export function signECDSA(hash, privateKey, lowR = false) { let sig = secp.Signature.fromBytes(secp.sign(hash, privateKey, { prehash: false })); if (lowR && !hasLowR(sig)) { const extraEntropy = new Uint8Array(32); let counter = 0; while (!hasLowR(sig)) { extraEntropy.set(U32LE.encode(counter++)); sig = secp.Signature.fromBytes(secp.sign(hash, privateKey, { prehash: false, extraEntropy })); if (counter > 4294967295) throw new Error('lowR counter overflow: report the error'); } } return sig.toBytes('der'); } export const signSchnorr = schnorr.sign; export const tagSchnorr = schnorr.utils.taggedHash; export const PubT = { ecdsa: 0, schnorr: 1, }; export function validatePubkey(pub, type) { const len = pub.length; if (type === PubT.ecdsa) { if (len === 32) throw new Error('Expected non-Schnorr key'); Point.fromBytes(pub); // does assertValidity return pub; } else if (type === PubT.schnorr) { if (len !== 32) throw new Error('Expected 32-byte Schnorr key'); schnorr.utils.lift_x(bytesToNumberBE(pub)); return pub; } else { throw new Error('Unknown key type'); } } export function tapTweak(a, b) { const u = schnorr.utils; const t = u.taggedHash('TapTweak', a, b); const tn = bytesToNumberBE(t); if (tn >= CURVE_ORDER) throw new Error('tweak higher than curve order'); return tn; } export function taprootTweakPrivKey(privKey, merkleRoot = Uint8Array.of()) { const u = schnorr.utils; const seckey0 = bytesToNumberBE(privKey); // seckey0 = int_from_bytes(seckey0) const P = Point.BASE.multiply(seckey0); // P = point_mul(G, seckey0) // seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0 const seckey = hasEven(P.y) ? seckey0 : Fn.neg(seckey0); const xP = u.pointToBytes(P); // t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h)); >= SECP256K1_ORDER check const t = tapTweak(xP, merkleRoot); // bytes_from_int((seckey + t) % SECP256K1_ORDER) return numberToBytesBE(Fn.add(seckey, t), 32); } export function taprootTweakPubkey(pubKey, h) { const u = schnorr.utils; const t = tapTweak(pubKey, h); // t = int_from_bytes(tagged_hash("TapTweak", pubkey + h)) const P = u.lift_x(bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey)) const Q = P.add(Point.BASE.multiply(t)); // Q = point_add(P, point_mul(G, t)) const parity = hasEven(Q.y) ? 0 : 1; // 0 if has_even_y(Q) else 1 return [u.pointToBytes(Q), parity]; // bytes_from_int(x(Q)) } // Another stupid decision, where lack of standard affects security. // Multisig needs to be generated with some key. // We are using approach from BIP 341/bitcoinjs-lib: SHA256(uncompressedDER(SECP256K1_GENERATOR_POINT)) // It is possible to switch SECP256K1_GENERATOR_POINT with some random point; // but it's too complex to prove. // Also used by bitcoin-core and bitcoinjs-lib export const TAPROOT_UNSPENDABLE_KEY = sha256(Point.BASE.toBytes(false)); export const NETWORK = { bech32: 'bc', pubKeyHash: 0x00, scriptHash: 0x05, wif: 0x80, }; export const TEST_NETWORK = { bech32: 'tb', pubKeyHash: 0x6f, scriptHash: 0xc4, wif: 0xef, }; // Exported for tests, internal method export function compareBytes(a, b) { if (!isBytes(a) || !isBytes(b)) throw new Error(`cmp: wrong type a=${typeof a} b=${typeof b}`); // -1 -> a<b, 0 -> a==b, 1 -> a>b const len = Math.min(a.length, b.length); for (let i = 0; i < len; i++) if (a[i] != b[i]) return Math.sign(a[i] - b[i]); return Math.sign(a.length - b.length); } // Reverses key<->values export function reverseObject(obj) { const res = {}; for (const k in obj) { if (res[obj[k]] !== undefined) throw new Error('duplicate key'); res[obj[k]] = k; } return res; } //# sourceMappingURL=utils.js.map