UNPKG

bip-schnorr

Version:

Pure JavaScript implementation of the BIP schnorr signature scheme and the muSig multi-signature scheme

133 lines (117 loc) 3.98 kB
const Buffer = require('safe-buffer').Buffer; const ecurve = require('ecurve'); const curve = ecurve.getCurveByName('secp256k1'); const math = require('./math'); const check = require('./check'); const convert = require('./convert'); const concat = Buffer.concat; const G = curve.G; const n = curve.n; const MUSIG_TAG = convert.hash(Buffer.from('MuSig coefficient')); // Computes ell = SHA256(pubKeys[0], ..., pubKeys[pubKeys.length-1]) with // pubKeys serialized in compressed form. function computeEll(pubKeys) { check.checkPubKeyArr(pubKeys); return convert.hash(concat(pubKeys)) } function computeCoefficient(ell, idx) { const idxBuf = Buffer.alloc(4); idxBuf.writeUInt32LE(idx); const data = concat([MUSIG_TAG, MUSIG_TAG, ell, idxBuf]); return convert.bufferToInt(convert.hash(data)).mod(n); } function pubKeyCombine(pubKeys, pubKeyHash) { const ell = pubKeyHash || computeEll(pubKeys); let X = null; for (let i = 0; i < pubKeys.length; i++) { const Xi = math.liftX(pubKeys[i]); const coefficient = computeCoefficient(ell, i); const summand = Xi.multiply(coefficient); if (X === null) { X = summand; } else { X = X.add(summand); } } return X; } function sessionInitialize(sessionId, privateKey, message, pubKeyCombined, pkParity, ell, idx) { check.checkSessionParams(sessionId, privateKey, message, pubKeyCombined, ell); const session = { sessionId, message, pubKeyCombined, pkParity, ell, idx, }; const coefficient = computeCoefficient(ell, idx); session.secretKey = privateKey.multiply(coefficient).mod(n); session.ownKeyParity = math.isEven(G.multiply(privateKey)); if (session.pkParity !== session.ownKeyParity) { session.secretKey = n.subtract(session.secretKey); } const nonceData = concat([sessionId, message, session.pubKeyCombined, convert.intToBuffer(privateKey)]); session.secretNonce = convert.bufferToInt(convert.hash(nonceData)); check.checkRange('secretNonce', session.secretNonce); const R = G.multiply(session.secretNonce); session.nonce = convert.intToBuffer(R.affineX); session.nonceParity = math.isEven(R); session.commitment = convert.hash(session.nonce); return session; } function sessionNonceCombine(session, nonces) { check.checkNonceArr(nonces); let R = math.liftX(nonces[0]); for (let i = 1; i < nonces.length; i++) { R = R.add(math.liftX(nonces[i])); } session.combinedNonceParity = math.isEven(R); return convert.intToBuffer(R.affineX); } function partialSign(session, message, nonceCombined, pubKeyCombined) { const e = math.getE(nonceCombined, pubKeyCombined, message); const sk = session.secretKey; let k = session.secretNonce; if (session.nonceParity !== session.combinedNonceParity) { k = n.subtract(k); } return sk.multiply(e).add(k).mod(n); } function partialSigVerify(session, partialSig, nonceCombined, idx, pubKey, nonce) { let e = math.getE(nonceCombined, session.pubKeyCombined, session.message); const coefficient = computeCoefficient(session.ell, idx); const Pj = math.liftX(pubKey); const Ri = math.liftX(nonce); if (!session.pkParity) { e = n.subtract(e); } let RP = math.getR(partialSig, e.multiply(coefficient).mod(n), Pj); if (session.combinedNonceParity) { RP = RP.negate(); } const sum = RP.add(Ri); if (!sum.curve.isInfinity(sum)) { throw new Error('partial signature verification failed'); } } function partialSigCombine(nonceCombined, partialSigs) { const R = math.liftX(nonceCombined); check.checkArray('partialSigs', partialSigs); const Rx = convert.intToBuffer(R.affineX); let s = partialSigs[0]; for (let i = 1; i < partialSigs.length; i++) { s = s.add(partialSigs[i]).mod(n); } return concat([Rx, convert.intToBuffer(s)]); } module.exports = { computeEll, computeCoefficient, pubKeyCombine, sessionInitialize, sessionNonceCombine, partialSign, partialSigVerify, partialSigCombine, };