Noble BLS12-381 pairing-friendly curve. High-security, easily auditable, 0-dep aggregated signatures & pubkey.
npm install @nikkolasg/noble-bls12-381bls12-381, a pairing-friendly elliptic curve construction.
This is a Barreto-Lynn-Scott curve with an embedding degree of 12. It's optimal for zk-SNARKs at the 128-bit security level.
It allows simple construction of threshold signatures, which allows a user to
sign lots of messages with one signature and verify them swiftly in a batch.
> noble-crypto — high-security, easily auditable set of contained cryptographic libraries and tools.
- No dependencies
- Easily auditable TypeScript/JS code
- Uses es2019 bigint. Supported in Chrome, Firefox, node 10+
- All releases are signed and trusted
- Check out all libraries:
secp256k1,
ed25519,
bls12-381,
ripemd160,
secretbox-aes-gcm
> npm install noble-bls12-381
``js
import * as bls from "bls12-381";
const DOMAIN = 2;
const PRIVATE_KEY = 0xa665a45920422f9d417e4867ef;
const HASH_MESSAGE = new Uint8Array([99, 100, 101, 102, 103]);
(async () => {
const publicKey = bls.getPublicKey(PRIVATE_KEY);
const signature = await bls.sign(HASH_MESSAGE, PRIVATE_KEY, DOMAIN);
const isCorrect = await bls.verify(HASH_MESSAGE, publicKey, signature, DOMAIN);
})();
`
`js
import * as bls from "bls12-381";
const DOMAIN = 2;
const PRIVATE_KEYS = [81, 455, 19];
const HASH_MESSAGE = new Uint8Array([99, 100, 101, 102, 103]);
(async () => {
const publicKeys = PRIVATE_KEYS.map(bls.getPublicKey);
const signatures = await Promise.all(PRIVATE_KEYS.map(p => bls.sign(HASH_MESSAGE, p, DOMAIN)));
const publicKey = await bls.aggregatePublicKeys(publicKeys);
const signature = await bls.aggregateSignatures(signatures);
const isCorrect = await bls.verify(HASH_MESSAGE, publicKey, signature, DOMAIN);
})();
`
`js
import * as bls from "bls12-381";
const DOMAIN = 2;
const PRIVATE_KEYS = [81, 455, 19];
const HASH_MESSAGES = ["deadbeef", "111111", "aaaaaabbbbbb"];
(async () => {
const publicKeys = PRIVATE_KEYS.map(bls.getPublicKey);
const signatures = await Promise.all(PRIVATE_KEYS.map((p, i) => bls.sign(HASH_MESSAGES[i], p, DOMAIN)));
const signature = await bls.aggregateSignatures(signatures);
const isCorrect = await bls.verifyMultiple(HASH_MESSAGES, publicKeys, signature, DOMAIN);
})();
`
- getPublicKey(privateKey)
- sign(hash, privateKey, domain)
- verify(hash, publicKey, signature, domain)
- aggregatePublicKeys(publicKeys)
- aggregateSignatures(signatures)
- verifyMultiple(hashes, publicKeys, signature, domain)
- pairing(4dPoint, 2dPoint)
- Helpers
##### getPublicKey(privateKey)`typescript`
function getPublicKey(privateKey: Uint8Array | string | bigint): Uint8Array;privateKey: Uint8Array | string | bigint
- will be used to generate public key.Point(x, y)
Public key is generated by executing scalar multiplication of a base Point(x, y) by a fixed
integer. The result is another which we will by default encode to hex Uint8Array.Uint8Array
- Returns : encoded publicKey for signature verification
##### sign(hash, privateKey, domain)`typescript`
function sign(
hash: Uint8Array | string,
privateKey: Uint8Array | string | bigint,
domain: Uint8Array | string | bigint
): Promisehash: Uint8Array | string
- - message hash which would be signedprivateKey: Uint8Array | string | bigint
- - private key which will sign the hashdomain: Uint8Array | string | bigint
- - signature version. Different domains will give different signatures. Setting a new domain in an upgraded system prevents it from being affected by the old messages and signatures.Uint8Array
- Returns : encoded signature
##### verify(hash, publicKey, signature, domain)`typescript`
function verify(
hash: Uint8Array | string,
publicKey: Uint8Array | string,
signature: Uint8Array | string,
domain: Uint8Array | string | bigint
): Promisehash: Uint8Array | string
- - message hash that needs to be verifiedpublicKey: Uint8Array | string
- - e.g. that was generated from privateKey by getPublicKeysignature: Uint8Array | string
- - object returned by the sign or aggregateSignatures functionPromise
- Returns : true / false whether the signature matches hash
##### aggregatePublicKeys(publicKeys)`typescript`
function aggregatePublicKeys(publicKeys: Uint8Array[] | string[]): Uint8Array;publicKeys: Uint8Array[] | string[]
- - e.g. that have been generated from privateKey by getPublicKeyUint8Array
- Returns : one aggregated public key which calculated from public keys
##### aggregateSignatures(signatures)`typescript`
function aggregateSignatures(signatures: Uint8Array[] | string[]): Uint8Array;signatures: Uint8Array[] | string[]
- - e.g. that have been generated by signUint8Array
- Returns : one aggregated signature which calculated from signatures
##### verifyMultiple(hashes, publicKeys, signature, domain)`typescript`
function verifyMultiple(
hashes: Uint8Array[] | string[],
publicKeys: Uint8Array[] | string[],
signature: Uint8Array | string,
domain: Uint8Array | string | bigint
): Promisehashes: Uint8Array[] | string[]
- - messages hashes that needs to be verifiedpublicKeys: Uint8Array[] | string[]
- - e.g. that were generated from privateKeys by getPublicKeysignature: Uint8Array | string
- - object returned by the aggregateSignatures functionPromise
- Returns : true / false whether the signature matches hashes
##### pairing(4dPoint, 2dPoint)`typescript`
function pairing(
4dPoint: Point<[bigint, bigint]>,
2dPoint: Point
withFinalExponent: boolean = true
): Point<[bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]>4dPoint: Point<[bigint, bigint]>
- - 4d point (((x, x_1), (y, y_1)))2dPoint: Point
- - simple point (x, y are encoded in the bigint).withFinalExponent: boolean
- - if the flag setted as true then result will be powered by curve order else will be not.Point
- Returns : paired 12 dimensional point.
##### Helpers
`typescript
// 𝔽p
bls.P // 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn
// Prime order
bls.PRIME_ORDER // 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n
// Hash base point (x, y)
bls.G1 // 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n
// x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507
// y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569
// Signature base point ((x_1, x_2), (y_1, y_2))
bls.G2
// x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758, 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160
// y = 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582, 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905
// Classes
bls.Fp // Subgroup
bls.Fp2 // 2-dimensional number
bls.Fp12 // 12-dimensional number
bls.Point // Elliptic curve point
`
BLS12-381 is a pairing-friendly elliptic curve construction from the BLS family, with embedding degree 12. It is built over a 381-bit prime field GF(p) with...
* z = -0xd2010000000100000x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
* p = (z - 1)2 ((z4 - z2 + 1) / 3) + z
* = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
* q = z4 - z2 + 1
* =
... yielding two source groups G1 and G2, each of 255-bit prime order q, such that an efficiently computable non-degenerate bilinear pairing function e exists into a third target group GT. Specifically, G1 is the q-order subgroup of E(Fp) : y^2 = x^3 + 4 and G2 is the q-order subgroup of E'(Fp2) : y2 = x3 + 4(u + 1) where the extention field Fp2 is defined as Fp(u) / (u2 + 1).
BLS12-381 is chosen so that z has small Hamming weight (to improve pairing performance) and also so that GF(q) has a large 232 primitive root of unity for performing radix-2 fast Fourier transforms for efficient multi-point evaluation and interpolation. It is also chosen so that it exists in a particularly efficient and rigid subfamily of BLS12 curves.
The library is pretty slow right now, but it's still good enough for many everyday cases.
``
getPublicKey#test x 1,080 ops/sec ±0.88% (85 runs sampled)
sign#test x 16.32 ops/sec ±1.08% (75 runs sampled)
aggregateSignatures#test x 161 ops/sec ±0.92% (79 runs sampled)
verify#test x 0.48 ops/sec ±0.74% (7 runs sampled)
Pairing#test x 1.05 ops/sec ±1.43% (7 runs sampled)
Noble is production-ready & secure. Our goal is to have it audited by a good security expert.
We're using built-in JS BigInt, which is "unsuitable for use in cryptography" as per official spec. This means that the lib is vulnerable to timing attacks. But:
1. JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve in a scripting language.
2. 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.
3. Overall they are quite rare; for our particular usage they're unimportant. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Try LibreSSL & similar low-level libraries & languages.
4. 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.
MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.