@nikkolasg/noble-bls12-381
v0.2.8
Published
Noble BLS12-381 pairing-friendly curve. High-security, easily auditable, 0-dep aggregated signatures & pubkey.
Downloads
22
Maintainers
Readme
noble-bls12-381
bls12-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.
This library belongs to noble crypto
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
Usage
npm install noble-bls12-381
Sign a message
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);
})();
Sign 1 message 3 times
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);
})();
Sign 3 messages with 3 keys
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);
})();
API
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)
function getPublicKey(privateKey: Uint8Array | string | bigint): Uint8Array;
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 anotherPoint(x, y)
which we will by default encode to hex Uint8Array.- Returns
Uint8Array
: encoded publicKey for signature verification
sign(hash, privateKey, domain)
function sign(
hash: Uint8Array | string,
privateKey: Uint8Array | string | bigint,
domain: Uint8Array | string | bigint
): Promise<Uint8Array>;
hash: 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.- Returns
Uint8Array
: encoded signature
verify(hash, publicKey, signature, domain)
function verify(
hash: Uint8Array | string,
publicKey: Uint8Array | string,
signature: Uint8Array | string,
domain: Uint8Array | string | bigint
): Promise<boolean>
hash: Uint8Array | string
- message hash that needs to be verifiedpublicKey: Uint8Array | string
- e.g. that was generated fromprivateKey
bygetPublicKey
signature: Uint8Array | string
- object returned by thesign
oraggregateSignatures
function- Returns
Promise<boolean>
:true
/false
whether the signature matches hash
aggregatePublicKeys(publicKeys)
function aggregatePublicKeys(publicKeys: Uint8Array[] | string[]): Uint8Array;
publicKeys: Uint8Array[] | string[]
- e.g. that have been generated fromprivateKey
bygetPublicKey
- Returns
Uint8Array
: one aggregated public key which calculated from public keys
aggregateSignatures(signatures)
function aggregateSignatures(signatures: Uint8Array[] | string[]): Uint8Array;
signatures: Uint8Array[] | string[]
- e.g. that have been generated bysign
- Returns
Uint8Array
: one aggregated signature which calculated from signatures
verifyMultiple(hashes, publicKeys, signature, domain)
function verifyMultiple(
hashes: Uint8Array[] | string[],
publicKeys: Uint8Array[] | string[],
signature: Uint8Array | string,
domain: Uint8Array | string | bigint
): Promise<boolean>
hashes: Uint8Array[] | string[]
- messages hashes that needs to be verifiedpublicKeys: Uint8Array[] | string[]
- e.g. that were generated fromprivateKeys
bygetPublicKey
signature: Uint8Array | string
- object returned by theaggregateSignatures
function- Returns
Promise<boolean>
:true
/false
whether the signature matches hashes
pairing(4dPoint, 2dPoint)
function pairing(
4dPoint: Point<[bigint, bigint]>,
2dPoint: Point<bigint>,
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<bigint>
- simple point (x, y
are encoded in thebigint
).withFinalExponent: boolean
- if the flag setted as true then result will be powered by curve order else will be not.- Returns
Point<BigintTwelve>
: paired 12 dimensional point.
Helpers
// 𝔽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
Curve Description
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 =
-0xd201000000010000
- p = (z - 1)2 ((z4 - z2 + 1) / 3) + z
- =
0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
- =
- q = z4 - z2 + 1
- =
0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
- =
... 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.
Speed
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)
Security
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:
- 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.
- 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.
- 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.
License
MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.