Gathering detailed insights and metrics for bip-schnorr
Gathering detailed insights and metrics for bip-schnorr
Gathering detailed insights and metrics for bip-schnorr
Gathering detailed insights and metrics for bip-schnorr
npm install bip-schnorr
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
130 Stars
99 Commits
32 Forks
9 Watching
2 Branches
8 Contributors
Updated on 21 Oct 2024
Minified
Minified + Gzipped
JavaScript (100%)
Cumulative downloads
Total Downloads
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
5
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!
NPM:
1npm install --save bip-schnorr
yarn:
1yarn add bip-schnorr
NOTE: All parameters are either of type BigInteger
or Buffer
(or an array of those).
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}
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);
Sign a 32-byte message with the private key, returning a 64-byte signature.
Verify a 64-byte signature of a 32-byte message against the public key. Throws an Error
if verification fails.
Verify a list of 64-byte signatures as a batch operation. Throws an Error
if verification fails.
Generate ell
which is the hash over all public keys participating in a muSig session.
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.
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!
Combines multiple nonces R_i
into the combined nonce R
.
Creates a partial signature s_i
for a participant.
Verifies a partial signature s_i
against the participant's public key P_i
.
Throws an Error
if verification fails.
Combines multiple partial signatures into a Schnorr signature (s, R)
that can be verified against
the combined public key P
.
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
Reason
8 existing vulnerabilities detected
Details
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
Reason
security policy file not detected
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
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