Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
noble-bls12-381
Advanced tools
Fastest JS implementation of BLS12-381. Auditable, secure, 0-dependency aggregated signatures & pairings
Fastest implementation of BLS12-381 in a scripting language. The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs pairing-curves-09, bls-sigs-04, hash-to-curve-11.
To learn more about internals, check out BLS12-381 for the rest of us & key concepts of pairings. To try it live, see the online demo & threshold sigs demo.
noble-crypto — high-security, easily auditable set of contained cryptographic libraries and tools.
Use NPM in node.js / browser, or include single file from GitHub's releases page:
npm install noble-bls12-381
const bls = require('noble-bls12-381');
// if you're using single file, use global variable nobleBls12381
// You can use Uint8Array, or hex string for readability
const privateKey = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c';
const privateKeys = [
'18f020b98eb798752a50ed0563b079c125b0db5dd0b1060d1c1b47d4a193e1e4',
'ed69a8c50cf8c9836be3b67c7eeff416612d45ba39a5c099d48fa668bf558c9c',
'16ae669f3be7a2121e17d0c68c05a8f3d6bef21ec0f2315f1d7aec12484e4cf5'
];
const message = '64726e3da8';
const messages = ['d2', '0d98', '05caf3'];
(async () => {
const publicKey = bls.getPublicKey(privateKey);
const publicKeys = privateKeys.map(bls.getPublicKey);
const signature = await bls.sign(message, privateKey);
const isCorrect = await bls.verify(signature, message, publicKey);
console.log('key', publicKey);
console.log('signature', signature);
console.log('is correct:', isCorrect);
// Sign 1 msg with 3 keys
const signatures2 = await Promise.all(privateKeys.map(p => bls.sign(message, p)));
const aggPubKey2 = bls.aggregatePublicKeys(publicKeys);
const aggSignature2 = bls.aggregateSignatures(signatures2);
const isCorrect2 = await bls.verify(aggSignature2, message, aggPubKey2);
console.log();
console.log('signatures are', signatures2);
console.log('merged to one signature', aggSignature2);
console.log('is correct:', isCorrect2);
// Sign 3 msgs with 3 keys
const signatures3 = await Promise.all(privateKeys.map((p, i) => bls.sign(messages[i], p)));
const aggSignature3 = bls.aggregateSignatures(signatures3);
const isCorrect3 = await bls.verifyBatch(aggSignature3, messages, publicKeys);
console.log();
console.log('keys', publicKeys);
console.log('signatures', signatures3);
console.log('merged to one signature', aggSignature3);
console.log('is correct:', isCorrect3);
})();
getPublicKey(privateKey)
sign(message, privateKey)
verify(signature, message, publicKey)
aggregatePublicKeys(publicKeys)
aggregateSignatures(signatures)
verifyBatch(signature, messages, publicKeys)
pairing(G1Point, G2Point)
getPublicKey(privateKey)
function getPublicKey(privateKey: Uint8Array | bigint): Uint8Array;
function getPublicKey(privateKey: string): string;
privateKey: Uint8Array | string | bigint
will be used to generate public key.
Public key is generated by executing scalar multiplication of a base Point(x, y) by a fixed
integer. The result is another Point(x, y)
which we will by default encode to hex Uint8Array.Uint8Array
: encoded publicKey for signature verificationNote: if you need spec-based KeyGen
, use paulmillr/bls12-381-keygen. It should work properly with ETH2 and FIL keys.
sign(message, privateKey)
function sign(message: Uint8Array, privateKey: Uint8Array): Promise<Uint8Array>;
function sign(message: string, privateKey: string): Promise<Uint8Array>;
function sign(message: PointG2, privateKey: Uint8Array | string | bigint): Promise<PointG2>;
message: Uint8Array | string
- message which would be hashed & signedprivateKey: Uint8Array | string | bigint
- private key which will sign the hashUint8Array | string | PointG2
: encoded signatureCheck out Internals section on instructions about domain separation tag (DST).
verify(signature, message, publicKey)
function verify(
signature: Uint8Array | string | PointG2,
message: Uint8Array | string | PointG2,
publicKey: Uint8Array | string | PointG1
): Promise<boolean>
signature: Uint8Array | string
- object returned by the sign
or aggregateSignatures
functionmessage: Uint8Array | string
- message hash that needs to be verifiedpublicKey: Uint8Array | string
- e.g. that was generated from privateKey
by getPublicKey
Promise<boolean>
: true
/ false
whether the signature matches hashaggregatePublicKeys(publicKeys)
function aggregatePublicKeys(publicKeys: Uint8Array[]): Uint8Array;
function aggregatePublicKeys(publicKeys: string[]): string;
function aggregatePublicKeys(publicKeys: PointG1[]): PointG1;
publicKeys: (Uint8Array | string | PointG1)[]
- e.g. that have been generated from privateKey
by getPublicKey
Uint8Array | PointG1
: one aggregated public key which calculated from public keysaggregateSignatures(signatures)
function aggregateSignatures(signatures: Uint8Array[]): Uint8Array;
function aggregateSignatures(signatures: string[]): string;
function aggregateSignatures(signatures: PointG2[]): PointG2;
signatures: (Uint8Array | string | PointG2)[]
- e.g. that have been generated by sign
Uint8Array | PointG2
: one aggregated signature which calculated from signaturesverifyBatch(signature, messages, publicKeys)
function verifyBatch(
signature: Uint8Array | string | PointG2,
messages: (Uint8Array | string | PointG2)[],
publicKeys: (Uint8Array | string | PointG1)[]
): Promise<boolean>
signature: Uint8Array | string | PointG2
- object returned by the aggregateSignatures
functionmessages: (Uint8Array | string | PointG2)[]
- messages hashes that needs to be verifiedpublicKeys: (Uint8Array | string | PointG1)[]
- e.g. that were generated from privateKeys
by getPublicKey
Promise<boolean>
: true
/ false
whether the signature matches hashespairing(pointG1, pointG2)
function pairing(
pointG1: PointG1,
pointG2: PointG2,
withFinalExponent: boolean = true
): Fp12
pointG1: PointG1
- simple point, x, y
are bigintspointG2: PointG2
- point over curve with complex numbers ((x₁, x₂+i), (y₁, y₂+i)
) - pairs of bigintswithFinalExponent: boolean
- should the result be powered by curve order; very slowFp12
: paired point over 12-degree extension field.// characteristic; z + (z⁴ - z² + 1)(z - 1)²/3
bls.CURVE.P // 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
// curve order; z⁴ − z² + 1
bls.CURVE.r // 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
// cofactor; (z - 1)²/3
bls.curve.h // 0x396c8c005555e1568c00aaab0000aaab
// G1 base point coordinates (x, y)
bls.CURVE.Gx
// x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507
bls.CURVE.Gy
// y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569
// G2 base point coordinates (x₁, x₂+i), (y₁, y₂+i)
bls.CURVE.G2x
// x =
// 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758,
// 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160
bls.CURVE.G2y
// y =
// 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582,
// 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905
// Classes
bls.Fp // field over Fp
bls.Fp2 // field over Fp₂
bls.Fp12 // finite extension field over irreducible polynominal
bls.PointG1 // projective point (xyz) at G1
bls.PointG2 // projective point (xyz) at G2
The library uses G1 for public keys and G2 for signatures. Adding support for G1 signatures is planned.
Formulas:
P = pk x G
- public keysS = pk x H(m)
- signinge(P, H(m)) == e(G, S)
- verification using pairingse(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
- signature aggregationThe BLS parameters for the library are:
PK_IN
G1
HASH_OR_ENCODE
true
DST
BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_
- use bls.utils.getDSTLabel()
& bls.utils.setDSTLabel("...")
to read/change the Domain Separation Tag labelRAND_BITS
64
Filecoin uses little endian byte arrays for private keys - so ensure to reverse byte order if you'll use it with FIL.
To achieve the best speed out of all JS / Python implementations, the library employs different optimizations:
Benchmarks measured with Apple M1:
getPublicKey x 598 ops/sec @ 1ms/op
sign x 36 ops/sec @ 27ms/op
verify x 28 ops/sec @ 35ms/op
pairing x 69 ops/sec @ 14ms/op
aggregatePublicKeys/8 x 84 ops/sec @ 11ms/op
aggregateSignatures/8 x 40 ops/sec @ 24ms/op
with compression / decompression disabled:
sign/nc x 54 ops/sec @ 18ms/op
verify/nc x 47 ops/sec @ 21ms/op
aggregatePublicKeys/32 x 787 ops/sec @ 1ms/op
aggregatePublicKeys/128 x 558 ops/sec @ 1ms/op
aggregatePublicKeys/512 x 256 ops/sec @ 3ms/op
aggregatePublicKeys/2048 x 81 ops/sec @ 12ms/op
aggregateSignatures/32 x 452 ops/sec @ 2ms/op
aggregateSignatures/128 x 240 ops/sec @ 4ms/op
aggregateSignatures/512 x 81 ops/sec @ 12ms/op
aggregateSignatures/2048 x 22 ops/sec @ 43ms/op
Noble is production-ready.
We're using built-in JS BigInt
, which is "unsuitable for use in cryptography" as per official spec. This means that the lib is potentially vulnerable to timing attacks. But, JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve in a scripting language. Which means any other JS library doesn't use constant-time bigints. Including bn.js or anything else. Even statically typed Rust, a language without GC, makes it harder to achieve constant-time for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages.
We however consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading rootkits with every npm install
. Our goal is to minimize this attack vector.
npm install
to install build dependencies like TypeScriptnpm run build
to compile TypeScript codenpm run test
to run jest on test/index.ts
Special thanks to Roman Koblov, who have helped to improve pairing speed.
MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.
FAQs
Fastest JS implementation of BLS12-381. Auditable, secure, 0-dependency aggregated signatures & pairings
We found that noble-bls12-381 demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.