bip-schnorr
Advanced tools
Comparing version 0.1.1 to 0.2.0
{ | ||
"name": "bip-schnorr", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "Pure JavaScript implementation of the BIP schnorr signature scheme", | ||
@@ -17,3 +17,3 @@ "main": "./src/index.js", | ||
"scripts": { | ||
"coverage-coveralls": "nyc mocha && nyc report --reporter=text-lcov | coveralls", | ||
"coverage-coveralls": "nyc mocha ./test/schnorr-*.spec.js && nyc report --reporter=text-lcov | coveralls", | ||
"coverage-html": "nyc report --reporter=html", | ||
@@ -20,0 +20,0 @@ "coverage": "nyc --check-coverage --branches 85 --functions 90 --lines 90 mocha ./test/schnorr-*.spec.js", |
@@ -47,2 +47,3 @@ # Pure JavaScript implementation of the Schnorr BIP | ||
const bipSchnorr = require('bip-schnorr'); | ||
const convert = bipSchnorr.convert; | ||
@@ -88,7 +89,7 @@ // signing | ||
// aggregating signatures (not part of BIP!) | ||
// aggregating signatures (naive Schnorr key aggregation, not part of BIP!) | ||
const privateKey1 = BigInteger.fromHex('B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF'); | ||
const privateKey2 = BigInteger.fromHex('C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7'); | ||
const message = Buffer.from('243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89', 'hex'); | ||
const aggregatedSignature = bipSchnorr.aggregateSignatures([privateKey1, privateKey2], message); | ||
const aggregatedSignature = bipSchnorr.naiveKeyAggregation([privateKey1, privateKey2], message); | ||
@@ -98,5 +99,5 @@ // verifying an aggregated signature | ||
const publicKey2 = Buffer.from('03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B', 'hex'); | ||
const sumOfPublicKeys = bipSchnorr.pubKeyToPoint(publicKey1).add(bipSchnorr.publicKeyToPoint(publicKey2)); | ||
const sumOfPublicKeys = convert.pubKeyToPoint(publicKey1).add(convert.pubKeyToPoint(publicKey2)); | ||
try { | ||
bipSchnorr.verify(sumOfPublicKeys.getEncoded(true), message, aggregatedSignature); | ||
bipSchnorr.verify(convert.pointToBuffer(sumOfPublicKeys), message, aggregatedSignature); | ||
console.log('The signature is valid.'); | ||
@@ -106,2 +107,22 @@ } catch (e) { | ||
} | ||
// muSig non-interactive (not part of any BIP yet, see https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures/) | ||
const privateKey1 = BigInteger.fromHex('B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF'); | ||
const privateKey2 = BigInteger.fromHex('C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7'); | ||
const message = Buffer.from('243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89', 'hex'); | ||
const aggregatedSignature = bipSchnorr.muSigNonInteractive([privateKey1, privateKey2], message); | ||
// verifying an aggregated signature | ||
const publicKey1 = Buffer.from('02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659', 'hex'); | ||
const publicKey2 = Buffer.from('03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B', 'hex'); | ||
const L = convert.hash(Buffer.concat([publicKey1, publicKey2])); | ||
const a1 = convert.bufferToInt(convert.hash(Buffer.concat([L, publicKey1]))); | ||
const a2 = convert.bufferToInt(convert.hash(Buffer.concat([L, publicKey2]))); | ||
const X = convert.pubKeyToPoint(publicKey1).multiply(a1).add(convert.pubKeyToPoint(publicKey2).multiply(a2)); | ||
try { | ||
bipSchnorr.verify(convert.pointToBuffer(X), message, aggregatedSignature); | ||
console.log('The signature is valid.'); | ||
} catch (e) { | ||
console.error('The signature verification failed: ' + e); | ||
} | ||
``` | ||
@@ -120,8 +141,23 @@ | ||
### bipSchnorr.aggregateSignatures(privateKeys : BigInteger[], message : Buffer) : Buffer | ||
### bipSchnorr.naiveKeyAggregation(privateKeys : BigInteger[], message : Buffer) : Buffer | ||
Aggregates multiple signatures of different private keys over the same message into a single 64-byte signature. | ||
### bipSchnorr.pubKeyToPoint(pubKey : Buffer) : Point | ||
Returns the point on the `secp256k1` curve that corresponds to the given 33-byte public key. | ||
This is just a demo of how the naive Schnorr multi-signature (or key aggregation scheme) can work. | ||
**This scheme is not secure,** it is prone to so-called rogue-key attacks. | ||
See [Key Aggregation for Schnorr Signatures](https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures/) | ||
by Blockstream. | ||
Use the **muSig** scheme that prevents that attack. | ||
### bipSchnorr.muSigNonInteractive(privateKeys : BigInteger[], message : Buffer) : Buffer | ||
Aggregates multiple signatures of different private keys over the same message into a single 64-byte signature | ||
using a scheme that is safe from rogue-key attacks. | ||
This non-interactive scheme requires the knowledge of all private keys that are participating in the | ||
multi-signature creation. Use the **muSigInteractive** scheme that requires two steps to create | ||
a signature with parties not sharing their private key. | ||
## Implementations in different languages | ||
* [Go implementation](https://github.com/hbakhtiyor/schnorr/) | ||
## Performance | ||
@@ -164,2 +200,2 @@ | ||
Done in 333.35s. | ||
``` | ||
``` |
const BigInteger = require('bigi'); | ||
const Buffer = require('safe-buffer').Buffer; | ||
const ecurve = require('ecurve'); | ||
const sha256 = require('js-sha256'); | ||
const randomBytes = require('random-bytes'); | ||
const curve = ecurve.getCurveByName('secp256k1'); | ||
const check = require('./check'); | ||
const convert = require('./convert'); | ||
const curve = ecurve.getCurveByName('secp256k1'); | ||
const concat = Buffer.concat; | ||
const G = curve.G; | ||
const p = curve.p; | ||
const n = curve.n; | ||
const VERSION = 'v0.1.1'; | ||
const zero = BigInteger.ZERO; | ||
@@ -25,21 +26,16 @@ const one = BigInteger.ONE; | ||
const P = G.multiply(privateKey); | ||
const rX = intToBuffer(R.affineX); | ||
const e = getE(rX, P, message); | ||
return Buffer.concat([rX, intToBuffer(k.add(e.multiply(privateKey)).mod(n))]); | ||
const Rx = convert.intToBuffer(R.affineX); | ||
const e = getE(Rx, P, message); | ||
return concat([Rx, convert.intToBuffer(k.add(e.multiply(privateKey)).mod(n))]); | ||
} | ||
function verify(pubKey, message, signature) { | ||
checkVerifyParams(pubKey, message, signature); | ||
check.checkVerifyParams(pubKey, message, signature); | ||
// https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#verification | ||
const P = pubKeyToPoint(pubKey); | ||
const r = bufferToInt(signature.slice(0, 32)); | ||
if (r.compareTo(p) >= 0) { | ||
throw new Error('r is larger than or equal to field size'); | ||
} | ||
const s = bufferToInt(signature.slice(32, 64)); | ||
if (s.compareTo(n) >= 0) { | ||
throw new Error('s is larger than or equal to curve order'); | ||
} | ||
const e = bufferToInt(hash(Buffer.concat([intToBuffer(r), pointToBuffer(P), message]))).mod(n); | ||
const P = convert.pubKeyToPoint(pubKey); | ||
const r = convert.bufferToInt(signature.slice(0, 32)); | ||
const s = convert.bufferToInt(signature.slice(32, 64)); | ||
check.checkSignatureInput(r, s); | ||
const e = convert.bufferToInt(convert.hash(concat([convert.intToBuffer(r), convert.pointToBuffer(P), message]))).mod(n); | ||
const sG = G.multiply(s); | ||
@@ -54,3 +50,3 @@ const eP = P.multiply(e); | ||
function batchVerify(pubKeys, messages, signatures) { | ||
checkBatchVerifyParams(pubKeys, messages, signatures); | ||
check.checkBatchVerifyParams(pubKeys, messages, signatures); | ||
@@ -61,12 +57,7 @@ // https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#Batch_Verification | ||
for (let i = 0; i < pubKeys.length; i++) { | ||
const P = pubKeyToPoint(pubKeys[i]); | ||
const r = bufferToInt(signatures[i].slice(0, 32)); | ||
if (r.compareTo(p) >= 0) { | ||
throw new Error('r is larger than or equal to field size'); | ||
} | ||
const s = bufferToInt(signatures[i].slice(32, 64)); | ||
if (s.compareTo(n) >= 0) { | ||
throw new Error('s is larger than or equal to curve order'); | ||
} | ||
const e = getE(intToBuffer(r), P, messages[i]); | ||
const P = convert.pubKeyToPoint(pubKeys[i]); | ||
const r = convert.bufferToInt(signatures[i].slice(0, 32)); | ||
const s = convert.bufferToInt(signatures[i].slice(32, 64)); | ||
check.checkSignatureInput(r, s); | ||
const e = getE(convert.intToBuffer(r), P, messages[i]); | ||
const c = r.pow(three).add(seven).mod(p); | ||
@@ -94,3 +85,3 @@ const y = c.modPow(p.add(one).divide(four), p); | ||
function aggregateSignatures(privateKeys, message) { | ||
function naiveKeyAggregation(privateKeys, message) { | ||
if (!privateKeys || !privateKeys.length) { | ||
@@ -115,4 +106,4 @@ throw new Error('privateKeys must be an array with one or more elements'); | ||
} | ||
const rX = intToBuffer(R.affineX); | ||
let e = getE(rX, P, message); | ||
const Rx = convert.intToBuffer(R.affineX); | ||
let e = getE(Rx, P, message); | ||
let s = zero; | ||
@@ -123,5 +114,50 @@ for (let i = 0; i < k0s.length; i++) { | ||
} | ||
return Buffer.concat([rX, intToBuffer(s.mod(n))]); | ||
return concat([Rx, convert.intToBuffer(s.mod(n))]); | ||
} | ||
function muSigNonInteractive(privateKeys, message) { | ||
if (!privateKeys || !privateKeys.length) { | ||
throw new Error('privateKeys must be an array with one or more elements'); | ||
} | ||
// https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures/ | ||
const rs = []; | ||
const Xs = []; | ||
let R = null; | ||
for (let privateKey of privateKeys) { | ||
const ri = deterministicGetK0(privateKey, message); | ||
const Ri = G.multiply(ri); | ||
const Xi = G.multiply(privateKey); | ||
rs.push(ri); | ||
Xs.push(Xi); | ||
if (R === null) { | ||
R = Ri; | ||
} else { | ||
R = R.add(Ri); | ||
} | ||
} | ||
const L = convert.hash(concat(Xs.map(convert.pointToBuffer))); | ||
const as = []; | ||
let X = null; | ||
for (let Xi of Xs) { | ||
const a = convert.bufferToInt(convert.hash(concat([L, convert.pointToBuffer(Xi)]))); | ||
const summand = Xi.multiply(a); | ||
as.push(a); | ||
if (X === null) { | ||
X = summand; | ||
} else { | ||
X = X.add(summand); | ||
} | ||
} | ||
let Rx = convert.intToBuffer(R.affineX); | ||
let e = getE(Rx, X, message); | ||
let s = zero; | ||
for (let i = 0; i < rs.length; i++) { | ||
const ri = getK(R, rs[i]); | ||
s = s.add(ri.add(e.multiply(as[i]).multiply(privateKeys[i])).mod(n)); | ||
} | ||
return concat([Rx, convert.intToBuffer(s.mod(n))]); | ||
} | ||
function deterministicGetK0(privateKey, message) { | ||
@@ -137,7 +173,6 @@ if (!BigInteger.isBigInteger(privateKey)) { | ||
} | ||
checkRange(privateKey); | ||
check.checkRange(privateKey); | ||
const concat = Buffer.concat([intToBuffer(privateKey), message]); | ||
const h = hash(concat); | ||
const i = bufferToInt(h); | ||
const h = convert.hash(concat([convert.intToBuffer(privateKey), message])); | ||
const i = convert.bufferToInt(h); | ||
const k0 = i.mod(n); | ||
@@ -158,91 +193,12 @@ if (k0.signum() === 0) { | ||
function getE(rX, P, m) { | ||
return bufferToInt(hash(Buffer.concat([rX, pointToBuffer(P), m]))).mod(n); | ||
function getE(Rx, P, m) { | ||
return convert.bufferToInt(convert.hash(concat([Rx, convert.pointToBuffer(P), m]))).mod(n); | ||
} | ||
function checkVerifyParams(pubKey, message, signature, idx) { | ||
const idxStr = (idx !== undefined ? '[' + idx + ']' : ''); | ||
if (!Buffer.isBuffer(pubKey)) { | ||
throw new Error('pubKey' + idxStr + ' must be a Buffer'); | ||
} | ||
if (!Buffer.isBuffer(message)) { | ||
throw new Error('message' + idxStr + ' must be a Buffer'); | ||
} | ||
if (!Buffer.isBuffer(signature)) { | ||
throw new Error('signature' + idxStr + ' must be a Buffer'); | ||
} | ||
if (pubKey.length !== 33) { | ||
throw new Error('pubKey' + idxStr + ' must be 33 bytes long'); | ||
} | ||
if (message.length !== 32) { | ||
throw new Error('message' + idxStr + ' must be 32 bytes long'); | ||
} | ||
if (signature.length !== 64) { | ||
throw new Error('signature' + idxStr + ' must be 64 bytes long'); | ||
} | ||
} | ||
function checkBatchVerifyParams(pubKeys, messages, signatures) { | ||
if (!pubKeys || !pubKeys.length) { | ||
throw new Error('pubKeys must be an array with one or more elements'); | ||
} | ||
if (!messages || !messages.length) { | ||
throw new Error('messages must be an array with one or more elements'); | ||
} | ||
if (!signatures || !signatures.length) { | ||
throw new Error('signatures must be an array with one or more elements'); | ||
} | ||
if (pubKeys.length !== messages.length || messages.length !== signatures.length) { | ||
throw new Error('all parameters must be an array with the same length') | ||
} | ||
for (let i = 0; i < pubKeys.length; i++) { | ||
checkVerifyParams(pubKeys[i], messages[i], signatures[i], i); | ||
} | ||
} | ||
function checkRange(privateKey) { | ||
if (privateKey.compareTo(one) < 0 || privateKey.compareTo(n.subtract(one)) > 0) { | ||
throw new Error('privateKey must be an integer in the range 1..n-1') | ||
} | ||
} | ||
function bufferToInt(buffer) { | ||
return BigInteger.fromBuffer(buffer); | ||
} | ||
function intToBuffer(bigInteger) { | ||
return bigInteger.toBuffer(32); | ||
} | ||
function hash(buffer) { | ||
return Buffer.from(sha256.create().update(buffer).array()); | ||
} | ||
function pointToBuffer(point) { | ||
return point.getEncoded(true); | ||
} | ||
function pubKeyToPoint(pubKey) { | ||
if (pubKey.length !== 33) { | ||
throw new Error('pubKey must be 33 bytes long'); | ||
} | ||
const pubKeyEven = (pubKey[0] - 0x02) === 0; | ||
const x = bufferToInt(pubKey.slice(1, 33)); | ||
const P = curve.pointFromX(!pubKeyEven, x); | ||
if (curve.isInfinity(P)) { | ||
throw new Error('point is at infinity'); | ||
} | ||
const pEven = P.affineY.isEven(); | ||
if (pubKeyEven !== pEven) { | ||
throw new Error('point does not exist'); | ||
} | ||
return P; | ||
} | ||
function randomA() { | ||
let a = null; | ||
for (; ;) { | ||
a = bufferToInt(Buffer.from(randomBytes.sync(32))); | ||
a = convert.bufferToInt(Buffer.from(randomBytes.sync(32))); | ||
try { | ||
checkRange(a); | ||
check.checkRange(a); | ||
return a; | ||
@@ -256,8 +212,7 @@ } catch (e) { | ||
module.exports = { | ||
VERSION, | ||
sign, | ||
verify, | ||
batchVerify, | ||
aggregateSignatures, | ||
pubKeyToPoint, | ||
naiveKeyAggregation, | ||
muSigNonInteractive, | ||
}; |
const schnorr = require('./bip-schnorr'); | ||
schnorr.check = require('./check'); | ||
schnorr.convert = require('./convert'); | ||
module.exports = schnorr; |
@@ -9,3 +9,2 @@ /* global describe, it, beforeEach */ | ||
const curve = ecurve.getCurveByName('secp256k1'); | ||
const G = curve.G; | ||
const n = curve.n; | ||
@@ -63,7 +62,17 @@ | ||
describe('aggregateSignatures', () => { | ||
describe('naiveKeyAggregation', () => { | ||
it('can check parameters', () => { | ||
// when / then | ||
try { bipSchnorr.naiveKeyAggregation(null, m); } catch (e) { assertError(e, 'privateKeys must be an array with one or more elements'); } | ||
try { bipSchnorr.naiveKeyAggregation([], m); } catch (e) { assertError(e, 'privateKeys must be an array with one or more elements'); } | ||
}); | ||
}); | ||
describe('muSigNonInteractive', () => { | ||
it('can check parameters', () => { | ||
// when / then | ||
try { bipSchnorr.muSigNonInteractive(null, m); } catch (e) { assertError(e, 'privateKeys must be an array with one or more elements'); } | ||
try { bipSchnorr.muSigNonInteractive([], m); } catch (e) { assertError(e, 'privateKeys must be an array with one or more elements'); } | ||
}); | ||
}); | ||
}); |
@@ -6,2 +6,3 @@ /* global describe, it, beforeEach */ | ||
const bipSchnorr = require('../src/bip-schnorr'); | ||
const convert = require('../src/convert'); | ||
const randomBytes = require('random-bytes'); | ||
@@ -17,2 +18,5 @@ const ecurve = require('ecurve'); | ||
const randomInt = (len) => BigInteger.fromBuffer(Buffer.from(randomBytes.sync(len))).mod(n); | ||
const randomBuffer = (len) => Buffer.from(randomBytes.sync(len)); | ||
describe('random tests', () => { | ||
@@ -26,5 +30,5 @@ describe('verify', () => { | ||
for (let i = 0; i < NUM_RANDOM_TESTS; i++) { | ||
const d = BigInteger.fromBuffer(Buffer.from(randomBytes.sync(32))).mod(n); | ||
const pubKey = G.multiply(d).getEncoded(true); | ||
const message = Buffer.from(randomBytes.sync(32)); | ||
const d = randomInt(32); | ||
const pubKey = convert.pointToBuffer(G.multiply(d)); | ||
const message = randomBuffer(32); | ||
privateKeys.push(d); | ||
@@ -62,5 +66,5 @@ pubKeys.push(pubKey); | ||
for (let i = 0; i < NUM_RANDOM_TESTS; i++) { | ||
const d = BigInteger.fromBuffer(Buffer.from(randomBytes.sync(32))).mod(n); | ||
const pubKey = G.multiply(d).getEncoded(true); | ||
const message = Buffer.from(randomBytes.sync(32)); | ||
const d = randomInt(32); | ||
const pubKey = convert.pointToBuffer(G.multiply(d)); | ||
const message = randomBuffer(32); | ||
const signature = bipSchnorr.sign(d, message); | ||
@@ -89,12 +93,12 @@ pubKeys.push(pubKey); | ||
describe('aggregateSignatures', () => { | ||
describe('naiveKeyAggregation', () => { | ||
for (let i = 1; i <= NUM_RANDOM_TESTS / 2; i++) { | ||
it('can aggregate signatures of two random private keys over same message, run #' + i, () => { | ||
// given | ||
const d1 = BigInteger.fromBuffer(Buffer.from(randomBytes.sync(32))).mod(n); | ||
const d2 = BigInteger.fromBuffer(Buffer.from(randomBytes.sync(32))).mod(n); | ||
const d1 = randomInt(32); | ||
const d2 = randomInt(32); | ||
const pubKey1 = G.multiply(d1); | ||
const pubKey2 = G.multiply(d2); | ||
const message = Buffer.from(randomBytes.sync(32)); | ||
const signature = bipSchnorr.aggregateSignatures([d1, d2], message); | ||
const message = randomBuffer(32); | ||
const signature = bipSchnorr.naiveKeyAggregation([d1, d2], message); | ||
@@ -105,3 +109,3 @@ // when | ||
try { | ||
bipSchnorr.verify(pubKey1.add(pubKey2).getEncoded(true), message, signature); | ||
bipSchnorr.verify(convert.pointToBuffer(pubKey1.add(pubKey2)), message, signature); | ||
} catch (e) { | ||
@@ -119,3 +123,3 @@ result = false; | ||
for (let i = 1; i <= NUM_RANDOM_TESTS / 8; i++) { | ||
const message = Buffer.from(randomBytes.sync(32)); | ||
const message = randomBuffer(32); | ||
@@ -127,3 +131,3 @@ it('can aggregate signatures of ' + NUM_RANDOM_TESTS + ' random private keys over the same message, run #' + i, (done) => { | ||
for (let i = 0; i < NUM_RANDOM_TESTS; i++) { | ||
const d = BigInteger.fromBuffer(Buffer.from(randomBytes.sync(32))).mod(n); | ||
const d = randomInt(32); | ||
const P = G.multiply(d); | ||
@@ -137,3 +141,3 @@ if (i === 0) { | ||
} | ||
const signature = bipSchnorr.aggregateSignatures(privateKeys, message); | ||
const signature = bipSchnorr.naiveKeyAggregation(privateKeys, message); | ||
@@ -144,3 +148,3 @@ // when | ||
try { | ||
bipSchnorr.verify(sumPubKey.getEncoded(true), message, signature); | ||
bipSchnorr.verify(convert.pointToBuffer(sumPubKey), message, signature); | ||
} catch (e) { | ||
@@ -159,2 +163,83 @@ result = false; | ||
}); | ||
describe('muSigNonInteractive', () => { | ||
for (let i = 1; i <= NUM_RANDOM_TESTS / 2; i++) { | ||
it('can aggregate signatures of two random private keys over same message, run #' + i, () => { | ||
// given | ||
const x1 = randomInt(32); | ||
const x2 = randomInt(32); | ||
const X1 = G.multiply(x1); | ||
const X2 = G.multiply(x2); | ||
const L = convert.hash(Buffer.concat([convert.pointToBuffer(X1), convert.pointToBuffer(X2)])); | ||
const a1 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X1)]))); | ||
const a2 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X2)]))); | ||
const X = X1.multiply(a1).add(X2.multiply(a2)); | ||
const message = randomBuffer(32); | ||
const signature = bipSchnorr.muSigNonInteractive([x1, x2], message); | ||
// when | ||
let result = true; | ||
let error = null; | ||
try { | ||
bipSchnorr.verify(convert.pointToBuffer(X), message, signature); | ||
} catch (e) { | ||
result = false; | ||
error = e; | ||
} | ||
// then | ||
assert.strictEqual(result, true, error); | ||
assert.strictEqual(error, null); | ||
}); | ||
} | ||
for (let i = 1; i <= NUM_RANDOM_TESTS / 8; i++) { | ||
const message = randomBuffer(32); | ||
it('can aggregate signatures of ' + NUM_RANDOM_TESTS + ' random private keys over the same message, run #' + i, (done) => { | ||
// given | ||
const privateKeys = []; | ||
const publicKeys = []; | ||
let pubKeyBuf = null; | ||
for (let i = 0; i < NUM_RANDOM_TESTS; i++) { | ||
const xi = randomInt(32); | ||
const Xi = G.multiply(xi); | ||
if (i === 0) { | ||
pubKeyBuf = convert.pointToBuffer(Xi); | ||
} else { | ||
pubKeyBuf = Buffer.concat([pubKeyBuf, convert.pointToBuffer(Xi)]); | ||
} | ||
privateKeys.push(xi); | ||
publicKeys.push(Xi); | ||
} | ||
const L = convert.hash(pubKeyBuf); | ||
let X = null; | ||
for (let i = 0; i < NUM_RANDOM_TESTS; i++) { | ||
const a = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(publicKeys[i])]))); | ||
if (i === 0) { | ||
X = publicKeys[i].multiply(a); | ||
} else { | ||
X = X.add(publicKeys[i].multiply(a)); | ||
} | ||
} | ||
const signature = bipSchnorr.muSigNonInteractive(privateKeys, message); | ||
// when | ||
let result = true; | ||
let error = null; | ||
try { | ||
bipSchnorr.verify(convert.pointToBuffer(X), message, signature); | ||
} catch (e) { | ||
result = false; | ||
error = e; | ||
} | ||
// then | ||
assert.strictEqual(result, true, error); | ||
assert.strictEqual(error, null); | ||
done(); | ||
}).timeout(RANDOM_TEST_TIMEOUT); | ||
} | ||
}); | ||
}); |
@@ -6,2 +6,3 @@ /* global describe, it, beforeEach */ | ||
const bipSchnorr = require('../src/bip-schnorr'); | ||
const convert = require('../src/convert'); | ||
const ecurve = require('ecurve'); | ||
@@ -112,3 +113,3 @@ | ||
describe('aggregateSignatures', () => { | ||
describe('naiveKeyAggregation', () => { | ||
const vectorsWithPrivateKeys = testVectors | ||
@@ -129,3 +130,3 @@ .filter(vec => vec.d !== null) | ||
const m = Buffer.from(vec1.m, 'hex'); | ||
const signature = bipSchnorr.aggregateSignatures([d1, d2], m); | ||
const signature = bipSchnorr.naiveKeyAggregation([d1, d2], m); | ||
@@ -135,3 +136,3 @@ // when | ||
try { | ||
bipSchnorr.verify(P1.add(P2).getEncoded(true), m, signature); | ||
bipSchnorr.verify(convert.pointToBuffer(P1.add(P2)), m, signature); | ||
result = true; | ||
@@ -147,3 +148,3 @@ } catch (e) { | ||
const m = Buffer.from(vec1.m, 'hex'); | ||
const signature = bipSchnorr.aggregateSignatures([d2, d3], m); | ||
const signature = bipSchnorr.naiveKeyAggregation([d2, d3], m); | ||
@@ -153,3 +154,3 @@ // when | ||
try { | ||
bipSchnorr.verify(P2.add(P3).getEncoded(true), m, signature); | ||
bipSchnorr.verify(convert.pointToBuffer(P2.add(P3)), m, signature); | ||
result = true; | ||
@@ -165,3 +166,3 @@ } catch (e) { | ||
const m = Buffer.from(vec1.m, 'hex'); | ||
const signature = bipSchnorr.aggregateSignatures([d1, d2, d3], m); | ||
const signature = bipSchnorr.naiveKeyAggregation([d1, d2, d3], m); | ||
@@ -171,3 +172,3 @@ // when | ||
try { | ||
bipSchnorr.verify(P.getEncoded(true), m, signature); | ||
bipSchnorr.verify(convert.pointToBuffer(P), m, signature); | ||
result = true; | ||
@@ -184,3 +185,3 @@ } catch (e) { | ||
const message = Buffer.from('243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89', 'hex'); | ||
const aggregatedSignature = bipSchnorr.aggregateSignatures([privateKey1, privateKey2], message); | ||
const aggregatedSignature = bipSchnorr.naiveKeyAggregation([privateKey1, privateKey2], message); | ||
assert.strictEqual(aggregatedSignature.toString('hex'), 'd60d7f81c15d57b04f8f6074de17f1b9eef2e0a9c9b2e93550c15b45d6998dc24ef5e393b356e7c334f36cee15e0f5f1e9ce06e7911793ddb9bd922d545b7525'); | ||
@@ -191,6 +192,6 @@ | ||
const publicKey2 = Buffer.from('03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B', 'hex'); | ||
const sumOfPublicKeys = bipSchnorr.pubKeyToPoint(publicKey1).add(bipSchnorr.pubKeyToPoint(publicKey2)); | ||
const sumOfPublicKeys = convert.pubKeyToPoint(publicKey1).add(convert.pubKeyToPoint(publicKey2)); | ||
let result = false; | ||
try { | ||
bipSchnorr.verify(sumOfPublicKeys.getEncoded(true), message, aggregatedSignature); | ||
bipSchnorr.verify(convert.pointToBuffer(sumOfPublicKeys), message, aggregatedSignature); | ||
result = true; | ||
@@ -200,6 +201,105 @@ } catch (e) { | ||
} | ||
assert.strictEqual(sumOfPublicKeys.getEncoded(true).toString('hex'), '03f0a6305d39a34582ba49a78bdf38ced935b3efce1e889d6820103665f35ee45b'); | ||
assert.strictEqual(convert.pointToBuffer(sumOfPublicKeys).toString('hex'), '03f0a6305d39a34582ba49a78bdf38ced935b3efce1e889d6820103665f35ee45b'); | ||
assert.strictEqual(result, true); | ||
}); | ||
}); | ||
describe('muSigNonInteractive', () => { | ||
const vectorsWithPrivateKeys = testVectors | ||
.filter(vec => vec.d !== null) | ||
.filter(vec => BigInteger.fromHex(vec.d).compareTo(BigInteger.ONE) > 0); | ||
const vec1 = vectorsWithPrivateKeys[0]; | ||
const x1 = BigInteger.fromHex(vectorsWithPrivateKeys[0].d); | ||
const x2 = BigInteger.fromHex(vectorsWithPrivateKeys[1].d); | ||
const x3 = BigInteger.fromHex(vectorsWithPrivateKeys[2].d); | ||
const X1 = G.multiply(x1); | ||
const X2 = G.multiply(x2); | ||
const X3 = G.multiply(x3); | ||
it('can sign and verify two aggregated signatures over same message', () => { | ||
// given | ||
const m = Buffer.from(vec1.m, 'hex'); | ||
const L = convert.hash(Buffer.concat([convert.pointToBuffer(X1), convert.pointToBuffer(X2)])); | ||
const a1 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X1)]))); | ||
const a2 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X2)]))); | ||
const X = X1.multiply(a1).add(X2.multiply(a2)); | ||
const signature = bipSchnorr.muSigNonInteractive([x1, x2], m); | ||
// when | ||
let result = false; | ||
try { | ||
bipSchnorr.verify(convert.pointToBuffer(X), m, signature); | ||
result = true; | ||
} catch (e) { | ||
result = false; | ||
} | ||
assert.strictEqual(result, true); | ||
}); | ||
it('can sign and verify two more aggregated signatures over same message', () => { | ||
// given | ||
const L = convert.hash(Buffer.concat([convert.pointToBuffer(X2), convert.pointToBuffer(X3)])); | ||
const a2 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X2)]))); | ||
const a3 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X3)]))); | ||
const X = X2.multiply(a2).add(X3.multiply(a3)); | ||
const m = Buffer.from(vec1.m, 'hex'); | ||
const signature = bipSchnorr.muSigNonInteractive([x2, x3], m); | ||
// when | ||
let result = false; | ||
try { | ||
bipSchnorr.verify(convert.pointToBuffer(X), m, signature); | ||
result = true; | ||
} catch (e) { | ||
result = false; | ||
} | ||
assert.strictEqual(result, true); | ||
}); | ||
it('can sign and verify three aggregated signatures over same message', () => { | ||
// given | ||
const L = convert.hash(Buffer.concat([convert.pointToBuffer(X1), convert.pointToBuffer(X2), convert.pointToBuffer(X3)])); | ||
const a1 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X1)]))); | ||
const a2 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X2)]))); | ||
const a3 = convert.bufferToInt(convert.hash(Buffer.concat([L, convert.pointToBuffer(X3)]))); | ||
const X = X1.multiply(a1).add(X2.multiply(a2)).add(X3.multiply(a3)); | ||
const m = Buffer.from(vec1.m, 'hex'); | ||
const signature = bipSchnorr.muSigNonInteractive([x1, x2, x3], m); | ||
// when | ||
let result = false; | ||
try { | ||
bipSchnorr.verify(convert.pointToBuffer(X), m, signature); | ||
result = true; | ||
} catch (e) { | ||
result = false; | ||
} | ||
assert.strictEqual(result, true); | ||
}); | ||
it('can aggregate and verify example in README', () => { | ||
const privateKey1 = BigInteger.fromHex('B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF'); | ||
const privateKey2 = BigInteger.fromHex('C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7'); | ||
const message = Buffer.from('243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89', 'hex'); | ||
const aggregatedSignature = bipSchnorr.muSigNonInteractive([privateKey1, privateKey2], message); | ||
assert.strictEqual(aggregatedSignature.toString('hex'), 'd60d7f81c15d57b04f8f6074de17f1b9eef2e0a9c9b2e93550c15b45d6998dc298fde09fcea69e99b195a371d7a7e879a40474c67e4b63fb2cd5c6b7a3058156'); | ||
// verifying an aggregated signature | ||
const publicKey1 = Buffer.from('02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659', 'hex'); | ||
const publicKey2 = Buffer.from('03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B', 'hex'); | ||
const L = convert.hash(Buffer.concat([publicKey1, publicKey2])); | ||
const a1 = convert.bufferToInt(convert.hash(Buffer.concat([L, publicKey1]))); | ||
const a2 = convert.bufferToInt(convert.hash(Buffer.concat([L, publicKey2]))); | ||
const X = convert.pubKeyToPoint(publicKey1).multiply(a1).add(convert.pubKeyToPoint(publicKey2).multiply(a2)); | ||
let result = false; | ||
try { | ||
bipSchnorr.verify(convert.pointToBuffer(X), message, aggregatedSignature); | ||
result = true; | ||
} catch (e) { | ||
result = false; | ||
} | ||
assert.strictEqual(convert.pointToBuffer(X).toString('hex'), '03a6c519a533b1e8ff578672af695a6f7f8cebb29b7d391e5c5fcfb91dcd597fb8'); | ||
assert.strictEqual(result, true); | ||
}); | ||
}); | ||
}); |
@@ -64,3 +64,3 @@ const bipSchnorr = require('../src/bip-schnorr'); | ||
try { | ||
const result = bipSchnorr.aggregateSignatures(privateKeys, messages[0]); | ||
const result = bipSchnorr.naiveKeyAggregation(privateKeys, messages[0]); | ||
if (!result || result.length !== 64) { | ||
@@ -67,0 +67,0 @@ console.error('Aggregating signatures failed!'); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
59838
15
1145
196
0