bip-schnorr
Advanced tools
Comparing version 0.5.0 to 0.6.0
{ | ||
"name": "bip-schnorr", | ||
"version": "0.5.0", | ||
"description": "Pure JavaScript implementation of the BIP schnorr signature scheme", | ||
"version": "0.6.0", | ||
"description": "Pure JavaScript implementation of the BIP schnorr signature scheme and the muSig multi-signature scheme", | ||
"main": "./src/index.js", | ||
@@ -6,0 +6,0 @@ "engines": { |
186
README.md
@@ -17,2 +17,5 @@ # Pure JavaScript implementation of BIP340 Schnorr Signatures for secp256k1 | ||
The MuSig implementation is based upon the C implementation in the | ||
[secp256k1-zkp fork](https://github.com/ElementsProject/secp256k1-zkp) | ||
I am by no means an expert in high performance JavaScript or the underlying cryptography. | ||
@@ -89,2 +92,155 @@ This library is slow, not peer reviewed at all, not tested (outside of passing the official test vectors) against | ||
### muSig | ||
```javascript | ||
const Buffer = require('safe-buffer').Buffer; | ||
const BigInteger = require('bigi'); | ||
const randomBytes = require('random-bytes'); | ||
const randomBuffer = (len) => Buffer.from(randomBytes.sync(len)); | ||
const schnorr = require('bip-schnorr'); | ||
const convert = schnorr.convert; | ||
const muSig = schnorr.muSig; | ||
// data known to every participant | ||
const publicData = { | ||
pubKeys: [ | ||
Buffer.from('846f34fdb2345f4bf932cb4b7d278fb3af24f44224fb52ae551781c3a3cad68a', 'hex'), | ||
Buffer.from('cd836b1d42c51d80cef695a14502c21d2c3c644bc82f6a7052eb29247cf61f4f', 'hex'), | ||
Buffer.from('b8c1765111002f09ba35c468fab273798a9058d1f8a4e276f45a1f1481dd0bdb', 'hex'), | ||
], | ||
message: convert.hash(Buffer.from('muSig is awesome!', 'utf8')), | ||
pubKeyHash: null, | ||
pubKeyCombined: null, | ||
commitments: [], | ||
nonces: [], | ||
nonceCombined: null, | ||
partialSignatures: [], | ||
signature: null, | ||
}; | ||
// data only known by the individual party, these values are never shared | ||
// between the signers! | ||
const signerPrivateData = [ | ||
// signer 1 | ||
{ | ||
privateKey: BigInteger.fromHex('add2b25e2d356bec3770305391cbc80cab3a40057ad836bcb49ef3eed74a3fee'), | ||
session: null, | ||
}, | ||
// signer 2 | ||
{ | ||
privateKey: BigInteger.fromHex('0a1645eef5a10e1f5011269abba9fd85c4f0cc70820d6f102fb7137f2988ad78'), | ||
session: null, | ||
}, | ||
// signer 3 | ||
{ | ||
privateKey: BigInteger.fromHex('2031e7fed15c770519707bb092a6337215530e921ccea42030c15d86e8eaf0b8'), | ||
session: null, | ||
} | ||
]; | ||
// ----------------------------------------------------------------------- | ||
// Step 1: Combine the public keys | ||
// The public keys P_i are combined into the combined public key P. | ||
// This can be done by every signer individually or by the initializing | ||
// party and then be distributed to every participant. | ||
// ----------------------------------------------------------------------- | ||
publicData.pubKeyHash = muSig.computeEll(publicData.pubKeys); | ||
publicData.pubKeyCombined = muSig.pubKeyCombine(publicData.pubKeys, publicData.pubKeyHash); | ||
// ----------------------------------------------------------------------- | ||
// Step 2: Create the private signing session | ||
// Each signing party does this in private. The session ID *must* be | ||
// unique for every call to sessionInitialize, otherwise it's trivial for | ||
// an attacker to extract the secret key! | ||
// ----------------------------------------------------------------------- | ||
signerPrivateData.forEach((data, idx) => { | ||
const sessionId = randomBuffer(32); // must never be reused between sessions! | ||
data.session = muSig.sessionInitialize( | ||
sessionId, | ||
data.privateKey, | ||
publicData.message, | ||
publicData.pubKeyCombined, | ||
publicData.pubKeyHash, | ||
idx | ||
); | ||
}); | ||
const signerSession = signerPrivateData[0].session; | ||
// ----------------------------------------------------------------------- | ||
// Step 3: Exchange commitments (communication round 1) | ||
// The signers now exchange the commitments H(R_i). This is simulated here | ||
// by copying the values from the private data to public data array. | ||
// ----------------------------------------------------------------------- | ||
for (let i = 0; i < publicData.pubKeys.length; i++) { | ||
publicData.commitments[i] = signerPrivateData[i].session.commitment; | ||
} | ||
// ----------------------------------------------------------------------- | ||
// Step 4: Get nonces (communication round 2) | ||
// Now that everybody has commited to the session, the nonces (R_i) can be | ||
// exchanged. Again, this is simulated by copying. | ||
// ----------------------------------------------------------------------- | ||
for (let i = 0; i < publicData.pubKeys.length; i++) { | ||
publicData.nonces[i] = signerPrivateData[i].session.nonce; | ||
} | ||
// ----------------------------------------------------------------------- | ||
// Step 5: Combine nonces | ||
// The nonces can now be combined into R. Each participant should do this | ||
// and keep track of whether the nonce was negated or not. This is needed | ||
// for the later steps. | ||
// ----------------------------------------------------------------------- | ||
publicData.nonceCombined = muSig.sessionNonceCombine(signerSession, publicData.nonces); | ||
signerPrivateData.forEach(data => (data.session.combinedNonceParity = signerSession.combinedNonceParity)); | ||
// ----------------------------------------------------------------------- | ||
// Step 6: Generate partial signatures | ||
// Every participant can now create their partial signature s_i over the | ||
// given message. | ||
// ----------------------------------------------------------------------- | ||
signerPrivateData.forEach(data => { | ||
data.session.partialSignature = muSig.partialSign(data.session, publicData.message, publicData.nonceCombined, publicData.pubKeyCombined); | ||
}); | ||
// ----------------------------------------------------------------------- | ||
// Step 7: Exchange partial signatures (communication round 3) | ||
// The partial signature of each signer is exchanged with the other | ||
// participants. Simulated here by copying. | ||
// ----------------------------------------------------------------------- | ||
for (let i = 0; i < publicData.pubKeys.length; i++) { | ||
publicData.partialSignatures[i] = signerPrivateData[i].session.partialSignature; | ||
} | ||
// ----------------------------------------------------------------------- | ||
// Step 8: Verify individual partial signatures | ||
// Every participant should verify the partial signatures received by the | ||
// other participants. | ||
// ----------------------------------------------------------------------- | ||
for (let i = 0; i < publicData.pubKeys.length; i++) { | ||
muSig.partialSigVerify( | ||
signerSession, | ||
publicData.partialSignatures[i], | ||
publicData.nonceCombined, | ||
i, | ||
publicData.pubKeys[i], | ||
publicData.nonces[i] | ||
); | ||
} | ||
// ----------------------------------------------------------------------- | ||
// Step 9: Combine partial signatures | ||
// Finally, the partial signatures can be combined into the full signature | ||
// (s, R) that can be verified against combined public key P. | ||
// ----------------------------------------------------------------------- | ||
publicData.signature = muSig.partialSigCombine(publicData.nonceCombined, publicData.partialSignatures); | ||
// ----------------------------------------------------------------------- | ||
// Step 10: Verify signature | ||
// The resulting signature can now be verified as a normal Schnorr | ||
// signature (s, R) over the message m and public key P. | ||
// ----------------------------------------------------------------------- | ||
const pkCombined = convert.intToBuffer(publicData.pubKeyCombined.affineX); | ||
schnorr.verify(pkCombined, publicData.message, publicData.signature); | ||
``` | ||
## API | ||
@@ -101,2 +257,32 @@ | ||
### schnorr.muSig.computeEll(pubKeys : Buffer[]) : Buffer | ||
Generate `ell` which is the hash over all public keys participating in a muSig session. | ||
### schnorr.muSig.pubKeyCombine(pubKeys : Buffer[], pubKeyHash : Buffer) : Point | ||
Creates the special rogue-key-resistant combined public key `P` by applying the MuSig coefficient | ||
to each public key `P_i` before adding them together. | ||
### schnorr.muSig.sessionInitialize(sessionId : Buffer, privateKey : BigInteger, message : Buffer, pubKeyCombined : Point, ell : Buffer, idx : number) : Session | ||
Creates a signing session. Each participant must create a session and *must not share* the content | ||
of the session apart from the commitment and later the nonce. | ||
**It is absolutely necessary that the session ID | ||
is unique for every call of `sessionInitialize`. Otherwise | ||
it's trivial for an attacker to extract the secret key!** | ||
### schnorr.muSig.sessionNonceCombine(session : Session, nonces : Buffer[]) : Buffer | ||
Combines multiple nonces `R_i` into the combined nonce `R`. | ||
### schnorr.muSig.partialSign(session : Session, message : Buffer, nonceCombined : Buffer, pubKeyCombined : Buffer) : BigInteger | ||
Creates a partial signature `s_i` for a participant. | ||
### schnorr.muSig.partialSigVerify(session : Session, partialSig : BigInteger, nonceCombined : Buffer, idx : number, pubKey : Buffer, nonce : Buffer) : void | ||
Verifies a partial signature `s_i` against the participant's public key `P_i`. | ||
Throws an `Error` if verification fails. | ||
### schnorr.muSig.partialSigCombine(nonceCombined : Buffer, partialSigs : BigInteger[]) : Buffer | ||
Combines multiple partial signatures into a Schnorr signature `(s, R)` that can be verified against | ||
the combined public key `P`. | ||
## Implementations in different languages | ||
@@ -103,0 +289,0 @@ * [Go implementation](https://github.com/hbakhtiyor/schnorr/) |
@@ -26,3 +26,3 @@ const BigInteger = require('bigi'); | ||
function bip340CheckPubKeyArr(pubKeys) { | ||
function checkPubKeyArr(pubKeys) { | ||
checkArray('pubKeys', pubKeys); | ||
@@ -48,2 +48,9 @@ for (let i = 0; i < pubKeys.length; i++) { | ||
function checkNonceArr(nonces) { | ||
checkArray('nonces', nonces); | ||
for (let i = 0; i < nonces.length; i++) { | ||
checkBuffer('nonce', nonces[i], 32, i); | ||
} | ||
} | ||
function checkPrivateKey(privateKey, idx) { | ||
@@ -69,3 +76,3 @@ const idxStr = (idx !== undefined ? '[' + idx + ']' : ''); | ||
function checkBatchVerifyParams(pubKeys, messages, signatures) { | ||
bip340CheckPubKeyArr(pubKeys); | ||
checkPubKeyArr(pubKeys); | ||
checkMessageArr(messages); | ||
@@ -78,2 +85,15 @@ checkSignatureArr(signatures); | ||
function checkSessionParams(sessionId, privateKey, message, pubKeyCombined, ell) { | ||
checkSignParams(privateKey, message); | ||
checkBuffer('sessionId', sessionId, 32); | ||
checkPoint('pubKeyCombined', pubKeyCombined); | ||
checkBuffer('ell', ell, 32); | ||
} | ||
function checkPoint(name, P) { | ||
if (!P.curve || P.curve.isInfinity(P)) { | ||
throw new Error(name + ' must be a point on the curve') | ||
} | ||
} | ||
function checkRange(name, scalar) { | ||
@@ -111,2 +131,3 @@ if (scalar.compareTo(one) < 0 || scalar.compareTo(n.subtract(one)) > 0) { | ||
module.exports = { | ||
checkSessionParams, | ||
checkSignParams, | ||
@@ -118,3 +139,6 @@ checkVerifyParams, | ||
checkPointExists, | ||
checkPubKeyArr, | ||
checkArray, | ||
checkNonceArr, | ||
checkAux, | ||
}; |
const schnorr = require('./schnorr'); | ||
schnorr.check = require('./check'); | ||
schnorr.convert = require('./convert'); | ||
schnorr.muSig = require('./mu-sig'); | ||
schnorr.taproot = require('./taproot'); | ||
module.exports = schnorr; |
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
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
194742
20
3847
319
1