UNPKG

@scure/btc-signer

Version:

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

152 lines 6.51 kB
/** * BTC P2P layer from BIP324. * * Experimental ElligatorSwift implementation: * Schnorr-like x-only ECDH with public keys indistinguishable from uniformly random bytes. * * Documented in * [BIP324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki), * [libsecp](https://github.com/bitcoin/bitcoin/blob/master/src/secp256k1/doc/ellswift.md). * * SwiftEC: Shallue-van de Woestijne Indifferentiable Function to Elliptic Curves. * Documented in https://eprint.iacr.org/2022/759.pdf. * * Curve25519 & P-521 are incompatible with SwiftEC. Differences from SwiftEC: * * undefined inputs are remapped * * y-parity is encoded in u/t values * * @module */ import { FpIsSquare } from '@noble/curves/abstract/modular.js'; import { concatBytes, abytes } from '@noble/curves/utils.js'; import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js'; import { randomBytes } from '@noble/hashes/utils.js'; import { tagSchnorr } from "./utils.js"; const Point = secp256k1.Point; const Fp = Point.Fp; const Fn = Point.Fn; const _1n = BigInt(1); const _2n = BigInt(2); const MINUS_3_SQRT = Fp.sqrt(Fp.create(BigInt(-3))); const _3n = BigInt(3); const _4n = BigInt(4); const _7n = BigInt(7); const isValidX = (x) => FpIsSquare(Fp, Fp.add(Fp.mul(Fp.mul(x, x), x), _7n)); const trySqrt = (x) => { try { return Fp.sqrt(x); } catch (_e) { } }; /** * Experimental ElligatorSwift implementation: * Schnorr-like x-only ECDH with public keys indistinguishable from uniformly random bytes. * Documented in BIP324. */ export const elligatorSwift = { // (internal stuff, exported for tests only): decode(u, _inv(x, u)) = x _inv: (x, u, ellCase) => { if (!Number.isSafeInteger(ellCase) || ellCase < 0 || ellCase > 7) throw new Error(`elligatorSwift._inv: wrong case=${ellCase}`); let v, s; // Most rejections happens in 3 condition (in comments, ~33% each) const u2 = Fp.mul(u, u); // u**2 const u3 = Fp.mul(u2, u); // u**3 if ((ellCase & 2) === 0) { if (isValidX(Fp.sub(Fp.neg(x), u))) return; // [1 condition] v = x; s = Fp.div(Fp.neg(Fp.add(u3, _7n)), Fp.add(Fp.add(u2, Fp.mul(u, v)), Fp.mul(v, v))); // = -(u**3 + 7) / (u**2 + u*v + v**2) } else { s = Fp.sub(x, u); // x - u if (Fp.is0(s)) return; const t0 = Fp.add(u3, _7n); // (u**3 + 7) const t1 = Fp.mul(Fp.mul(_3n, s), u2); // 3 * s * u**2 // r = (-s * (4 * (u**3 + 7) + 3 * s * u**2)).sqrt() const r = trySqrt(Fp.mul(Fp.neg(s), Fp.add(Fp.mul(_4n, t0), t1))); if (r === undefined) return; // [2 condition] if (ellCase & 1 && Fp.is0(r)) return; v = Fp.div(Fp.add(Fp.neg(u), Fp.div(r, s)), _2n); // v = (-u + r / s) / 2 } const w = trySqrt(s); if (w === undefined) return; // [3 condition] const last = ellCase & 5; // ellCase = 0..8, last = 0,1,4,5 const t0 = last & 1 ? Fp.add(_1n, MINUS_3_SQRT) : Fp.sub(_1n, MINUS_3_SQRT); const w0 = last === 0 || last === 5 ? Fp.neg(w) : w; // -w | w // w0 * (u * t0 / 2 + v) return Fp.mul(w0, Fp.add(Fp.div(Fp.mul(u, t0), _2n), v)); }, // Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding encode: (x) => { // 200k test cycles per keygen: avg=4 max=48 // seems too much, but same as for reference implementation while (true) { // random scalar 1..Fp.ORDER const u = Fp.create(Fp.fromBytes(secp256k1.utils.randomSecretKey())); const ellCase = randomBytes(1)[0] & 7; // [0..8) const t = elligatorSwift._inv(x, u, ellCase); if (!t) continue; return concatBytes(Fp.toBytes(u), Fp.toBytes(t)); } }, // Decode elligatorSwift point to xonly decode: (data) => { const _data = abytes(data, 64, 'data'); let u = Fp.create(Fp.fromBytes(_data.subarray(0, 32), true)); let t = Fp.create(Fp.fromBytes(_data.subarray(32, 64), true)); if (Fp.is0(u)) u = Fp.create(_1n); if (Fp.is0(t)) t = Fp.create(_1n); const u3 = Fp.mul(Fp.mul(u, u), u); // u**3 const u3plus7 = Fp.add(u3, _7n); // u**3 + t**2 + 7 == 0 -> t = 2 * t if (Fp.is0(Fp.add(u3plus7, Fp.mul(t, t)))) t = Fp.add(t, t); // X = (u**3 + 7 - t**2) / (2 * t) const x = Fp.div(Fp.sub(u3plus7, Fp.mul(t, t)), Fp.add(t, t)); // Y = (X + t) / (MINUS_3_SQRT * u); const y = Fp.div(Fp.add(x, t), Fp.mul(MINUS_3_SQRT, u)); // try different cases let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2, if (isValidX(res)) return Fp.toBytes(res); res = Fp.div(Fp.sub(Fp.div(Fp.neg(x), y), u), _2n); // (-X / Y - u) / 2 if (isValidX(res)) return Fp.toBytes(res); res = Fp.div(Fp.sub(Fp.div(x, y), u), _2n); // (X / Y - u) / 2 if (isValidX(res)) return Fp.toBytes(res); throw new Error('elligatorSwift: cannot decode public key'); }, // Generate pair (public key, secret key) keygen: () => { const privateKey = secp256k1.utils.randomSecretKey(); const p = Point.BASE.multiply(Point.Fn.fromBytes(privateKey)); const publicKey = elligatorSwift.encode(p.x); return { privateKey, publicKey }; }, // Generates shared secret between a pub key and a priv key getSharedSecret: (privateKeyA, publicKeyB) => { const pub = elligatorSwift.decode(publicKeyB); const priv = abytes(privateKeyA, 32, 'privKey'); const point = schnorr.utils.lift_x(Fp.fromBytes(pub)); const d = Fn.fromBytes(priv); return Fp.toBytes(point.multiply(d).x); }, // BIP324 shared secret getSharedSecretBip324: (privateKeyOurs, publicKeyTheirs, publicKeyOurs, initiating) => { const ours = abytes(publicKeyOurs, undefined, 'publicKeyOurs'); const theirs = abytes(publicKeyTheirs, undefined, 'publicKeyTheirs'); const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs); const pubs = initiating ? [ours, theirs] : [theirs, ours]; return tagSchnorr('bip324_ellswift_xonly_ecdh', ...pubs, ecdhPoint); }, }; //# sourceMappingURL=p2p.js.map