Installations
npm install bip-schnorr
Releases
Unable to fetch releases
Developer
guggero
Developer Guide
Module System
CommonJS
Min. Node Version
>=8.0.0
Typescript Support
No
Node Version
16.18.0
NPM Version
8.19.2
Statistics
130 Stars
99 Commits
32 Forks
9 Watching
2 Branches
8 Contributors
Updated on 21 Oct 2024
Bundle Size
44.59 kB
Minified
15.54 kB
Minified + Gzipped
Languages
JavaScript (100%)
Total Downloads
Cumulative downloads
Total Downloads
2,543,191
Last day
48.6%
4,482
Compared to previous day
Last week
-13.4%
27,969
Compared to previous week
Last month
51.3%
132,541
Compared to previous month
Last year
19.8%
901,957
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Dependencies
5
Pure JavaScript implementation of BIP340 Schnorr Signatures for secp256k1
This is a pure JavaScript implementation of the standard 64-byte Schnorr signature scheme over the elliptic curve secp256k1.
The code is based upon the BIP340 proposal.
The current version passes all test vectors provided here.
The MuSig implementation is based upon the C implementation in the secp256k1-zkp fork
I am by no means an expert in high performance JavaScript or the underlying cryptography. This library is slow, not peer reviewed at all, not tested (outside of passing the official test vectors) against other, real implementations and should therefore only be used for educational purposes! Please do not use for production setups!
How to install
NPM:
1npm install --save bip-schnorr
yarn:
1yarn add bip-schnorr
How to use
NOTE: All parameters are either of type BigInteger
or Buffer
(or an array of those).
Schnorr
1const Buffer = require('safe-buffer').Buffer; 2const BigInteger = require('bigi'); 3const schnorr = require('bip-schnorr'); 4const convert = schnorr.convert; 5 6// signing 7 8// PrivateKey as BigInteger from bigi or valid hex string 9const privateKey = BigInteger.fromHex('B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF'); 10const privateKeyHex = 'B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF'; 11const message = Buffer.from('243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89', 'hex'); 12const createdSignature = schnorr.sign(privateKey, message); 13const createdSignatureFromHex = schnorr.sign(privateKeyHex, message); 14console.log('The signature is: ' + createdSignature.toString('hex')); 15console.log('The signature is: ' + createdSignatureFromHex.toString('hex')); 16 17// verifying 18const publicKey = Buffer.from('DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659', 'hex'); 19const signatureToVerify = Buffer.from('6D461BEB2F2DA00027D884FD13A24E2AE85CAECCA8AAA2D41777217EC38FB4960A67D47BC4F0722754EDB0E9017072600FFE4030C2E73771DCD3773F46A62652', 'hex'); 20try { 21 schnorr.verify(publicKey, message, signatureToVerify); 22 console.log('The signature is valid.'); 23} catch (e) { 24 console.error('The signature verification failed: ' + e); 25} 26 27// batch verifying 28const publicKeys = [ 29 Buffer.from('9D03B28781BD34C3250E4250FEB4543AF02AC6529398EBF776AAA5C3BDA10CFD', 'hex'), 30 Buffer.from('141F9A1B6360A717A7C71CB67E98D57513A84101192DC048F4382B5DF1B3C756', 'hex'), 31 Buffer.from('F986619C277577317E362101E08F8ACF63B34623B6A4758C2254398F70564D5A', 'hex'), 32]; 33const messages = [ 34 Buffer.from('243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89', 'hex'), 35 Buffer.from('5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C', 'hex'), 36 Buffer.from('B2F0CD8ECB23C1710903F872C31B0FD37E15224AF457722A87C5E0C7F50FFFB3', 'hex'), 37]; 38const signatures = [ 39 Buffer.from('1C621A42A3397988B63FC8F6F5EA81F8C88A71E2D30B1D7F3681CC9CB99E5AC022E52FC927DCA01B3BD3A16793F06996A5FE8A9B3FA7A91EC8934AF15F12FCF8', 'hex'), 40 Buffer.from('E94ECF2B0446171E44D62311EBDB631612B8AC5C4A5974033C61B924BD11B24AFC118CB661C18B0C94FDCD3F10C6F8B3F8DDA44A20DC4308430F0396EE9F477C', 'hex'), 41 Buffer.from('F25929B90A93130BF85EC6ABA70DA6B26FDFC37F71C7E268342873575CA0C01375F372B31E5C218E30CAE08DEAEF47F37096C7E11D506EC8DC9221109B79FB2D', 'hex'), 42]; 43try { 44 schnorr.batchVerify(publicKeys, messages, signatures); 45 console.log('The signatures are valid.'); 46} catch (e) { 47 console.error('The signature verification failed: ' + e); 48}
muSig
1const Buffer = require('safe-buffer').Buffer; 2const BigInteger = require('bigi'); 3const randomBytes = require('random-bytes'); 4const randomBuffer = (len) => Buffer.from(randomBytes.sync(len)); 5const schnorr = require('bip-schnorr'); 6const convert = schnorr.convert; 7const muSig = schnorr.muSig; 8 9// data known to every participant 10const publicData = { 11 pubKeys: [ 12 Buffer.from('846f34fdb2345f4bf932cb4b7d278fb3af24f44224fb52ae551781c3a3cad68a', 'hex'), 13 Buffer.from('cd836b1d42c51d80cef695a14502c21d2c3c644bc82f6a7052eb29247cf61f4f', 'hex'), 14 Buffer.from('b8c1765111002f09ba35c468fab273798a9058d1f8a4e276f45a1f1481dd0bdb', 'hex'), 15 ], 16 message: convert.hash(Buffer.from('muSig is awesome!', 'utf8')), 17 pubKeyHash: null, 18 pubKeyCombined: null, 19 pubKeyParity: null, 20 commitments: [], 21 nonces: [], 22 nonceCombined: null, 23 partialSignatures: [], 24 signature: null, 25}; 26 27// data only known by the individual party, these values are never shared 28// between the signers! 29const signerPrivateData = [ 30 // signer 1 31 { 32 privateKey: BigInteger.fromHex('add2b25e2d356bec3770305391cbc80cab3a40057ad836bcb49ef3eed74a3fee'), 33 session: null, 34 }, 35 // signer 2 36 { 37 privateKey: BigInteger.fromHex('0a1645eef5a10e1f5011269abba9fd85c4f0cc70820d6f102fb7137f2988ad78'), 38 session: null, 39 }, 40 // signer 3 41 { 42 privateKey: BigInteger.fromHex('2031e7fed15c770519707bb092a6337215530e921ccea42030c15d86e8eaf0b8'), 43 session: null, 44 } 45]; 46 47// ----------------------------------------------------------------------- 48// Step 1: Combine the public keys 49// The public keys P_i are combined into the combined public key P. 50// This can be done by every signer individually or by the initializing 51// party and then be distributed to every participant. 52// ----------------------------------------------------------------------- 53publicData.pubKeyHash = muSig.computeEll(publicData.pubKeys); 54const pkCombined = muSig.pubKeyCombine(publicData.pubKeys, publicData.pubKeyHash); 55publicData.pubKeyCombined = convert.intToBuffer(pkCombined.affineX); 56publicData.pubKeyParity = math.isEven(pkCombined); 57 58// ----------------------------------------------------------------------- 59// Step 2: Create the private signing session 60// Each signing party does this in private. The session ID *must* be 61// unique for every call to sessionInitialize, otherwise it's trivial for 62// an attacker to extract the secret key! 63// ----------------------------------------------------------------------- 64signerPrivateData.forEach((data, idx) => { 65 const sessionId = randomBuffer(32); // must never be reused between sessions! 66 data.session = muSig.sessionInitialize( 67 sessionId, 68 data.privateKey, 69 publicData.message, 70 publicData.pubKeyCombined, 71 publicData.pubKeyParity, 72 publicData.pubKeyHash, 73 idx 74 ); 75}); 76const signerSession = signerPrivateData[0].session; 77 78// ----------------------------------------------------------------------- 79// Step 3: Exchange commitments (communication round 1) 80// The signers now exchange the commitments H(R_i). This is simulated here 81// by copying the values from the private data to public data array. 82// ----------------------------------------------------------------------- 83for (let i = 0; i < publicData.pubKeys.length; i++) { 84 publicData.commitments[i] = signerPrivateData[i].session.commitment; 85} 86 87// ----------------------------------------------------------------------- 88// Step 4: Get nonces (communication round 2) 89// Now that everybody has commited to the session, the nonces (R_i) can be 90// exchanged. Again, this is simulated by copying. 91// ----------------------------------------------------------------------- 92for (let i = 0; i < publicData.pubKeys.length; i++) { 93 publicData.nonces[i] = signerPrivateData[i].session.nonce; 94} 95 96// ----------------------------------------------------------------------- 97// Step 5: Combine nonces 98// The nonces can now be combined into R. Each participant should do this 99// and keep track of whether the nonce was negated or not. This is needed 100// for the later steps. 101// ----------------------------------------------------------------------- 102publicData.nonceCombined = muSig.sessionNonceCombine(signerSession, publicData.nonces); 103signerPrivateData.forEach(data => (data.session.combinedNonceParity = signerSession.combinedNonceParity)); 104 105// ----------------------------------------------------------------------- 106// Step 6: Generate partial signatures 107// Every participant can now create their partial signature s_i over the 108// given message. 109// ----------------------------------------------------------------------- 110signerPrivateData.forEach(data => { 111 data.session.partialSignature = muSig.partialSign(data.session, publicData.message, publicData.nonceCombined, publicData.pubKeyCombined); 112}); 113 114// ----------------------------------------------------------------------- 115// Step 7: Exchange partial signatures (communication round 3) 116// The partial signature of each signer is exchanged with the other 117// participants. Simulated here by copying. 118// ----------------------------------------------------------------------- 119for (let i = 0; i < publicData.pubKeys.length; i++) { 120 publicData.partialSignatures[i] = signerPrivateData[i].session.partialSignature; 121} 122 123// ----------------------------------------------------------------------- 124// Step 8: Verify individual partial signatures 125// Every participant should verify the partial signatures received by the 126// other participants. 127// ----------------------------------------------------------------------- 128for (let i = 0; i < publicData.pubKeys.length; i++) { 129 muSig.partialSigVerify( 130 signerSession, 131 publicData.partialSignatures[i], 132 publicData.nonceCombined, 133 i, 134 publicData.pubKeys[i], 135 publicData.nonces[i] 136 ); 137} 138 139// ----------------------------------------------------------------------- 140// Step 9: Combine partial signatures 141// Finally, the partial signatures can be combined into the full signature 142// (s, R) that can be verified against combined public key P. 143// ----------------------------------------------------------------------- 144publicData.signature = muSig.partialSigCombine(publicData.nonceCombined, publicData.partialSignatures); 145 146// ----------------------------------------------------------------------- 147// Step 10: Verify signature 148// The resulting signature can now be verified as a normal Schnorr 149// signature (s, R) over the message m and public key P. 150// ----------------------------------------------------------------------- 151schnorr.verify(publicData.pubKeyCombined, publicData.message, publicData.signature);
API
schnorr.sign(privateKey : BigInteger | string, message : Buffer) : Buffer
Sign a 32-byte message with the private key, returning a 64-byte signature.
schnorr.verify(pubKey : Buffer, message : Buffer, signature : Buffer) : void
Verify a 64-byte signature of a 32-byte message against the public key. Throws an Error
if verification fails.
schnorr.batchVerify(pubKeys : Buffer[], messages : Buffer[], signatures : Buffer[]) : void
Verify a list of 64-byte signatures as a batch operation. Throws an Error
if verification fails.
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 : Buffer, pkParity : boolean, 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
Performance
The code is not yet optimized for performance.
The following results were achieved on an Intel Core i7-6500U running on linux/amd64 with node v10.23.0:
1$ node test/schnorr.benchmark.js 2Sign (batch size: 1) x 26.12 ops/sec ±2.68% (47 runs sampled) 40291 us/op 25 sig/s 3Sign (batch size: 2) x 13.36 ops/sec ±0.88% (37 runs sampled) 77550 us/op 26 sig/s 4Sign (batch size: 4) x 6.78 ops/sec ±1.33% (21 runs sampled) 149622 us/op 27 sig/s 5Sign (batch size: 8) x 3.38 ops/sec ±0.93% (13 runs sampled) 297823 us/op 27 sig/s 6Sign (batch size: 16) x 1.69 ops/sec ±0.51% (9 runs sampled) 591927 us/op 27 sig/s 7Sign (batch size: 32) x 0.85 ops/sec ±0.27% (7 runs sampled) 1177938 us/op 27 sig/s 8Sign (batch size: 64) x 0.42 ops/sec ±0.63% (6 runs sampled) 2383795 us/op 27 sig/s 9Verify (batch size: 1) x 26.22 ops/sec ±0.76% (47 runs sampled) 39417 us/op 25 sig/s 10Verify (batch size: 2) x 13.04 ops/sec ±0.57% (36 runs sampled) 78548 us/op 25 sig/s 11Verify (batch size: 4) x 6.57 ops/sec ±0.83% (21 runs sampled) 153775 us/op 26 sig/s 12Verify (batch size: 8) x 3.28 ops/sec ±0.60% (13 runs sampled) 305802 us/op 26 sig/s 13Verify (batch size: 16) x 1.65 ops/sec ±0.58% (9 runs sampled) 605158 us/op 26 sig/s 14Verify (batch size: 32) x 0.83 ops/sec ±0.70% (7 runs sampled) 1214640 us/op 26 sig/s 15Verify (batch size: 64) x 0.41 ops/sec ±0.45% (6 runs sampled) 2428993 us/op 26 sig/s 16Batch Verify (batch size: 1) x 25.84 ops/sec ±0.82% (47 runs sampled) 39838 us/op 25 sig/s 17Batch Verify (batch size: 2) x 8.80 ops/sec ±1.02% (26 runs sampled) 115088 us/op 17 sig/s 18Batch Verify (batch size: 4) x 4.39 ops/sec ±0.64% (15 runs sampled) 231074 us/op 17 sig/s 19Batch Verify (batch size: 8) x 2.20 ops/sec ±0.36% (10 runs sampled) 457815 us/op 17 sig/s 20Batch Verify (batch size: 16) x 1.10 ops/sec ±0.56% (7 runs sampled) 909321 us/op 18 sig/s 21Batch Verify (batch size: 32) x 0.55 ops/sec ±0.28% (6 runs sampled) 1825425 us/op 18 sig/s 22Batch Verify (batch size: 64) x 0.26 ops/sec ±7.04% (5 runs sampled) 3832114 us/op 17 sig/s 23Done in 279.18s.
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
8 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c
- Warn: Project is vulnerable to: GHSA-2j2x-2gpw-g8fm
- Warn: Project is vulnerable to: GHSA-896r-f27r-55mw
- Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3
- Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6
- Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3
Reason
Found 1/12 approved changesets -- score normalized to 0
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
branch protection not enabled on development/release branches
Details
- Warn: branch protection not enabled for branch 'master'
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 19 are checked with a SAST tool
Score
2
/10
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More