UNPKG

@nikkolasg/noble-bls12-381

Version:

Noble BLS12-381 pairing-friendly curve. High-security, easily auditable, 0-dep aggregated signatures & pubkey.

234 lines (184 loc) 11.4 kB
# noble-bls12-381 [bls12-381](https://electriccoin.co/blog/new-snark-curve/), 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](https://medium.com/@snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716), 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](https://github.com/paulmillr/noble-secp256k1), [ed25519](https://github.com/paulmillr/noble-ed25519), [bls12-381](https://github.com/paulmillr/noble-bls12-381), [ripemd160](https://github.com/paulmillr/noble-ripemd160), [secretbox-aes-gcm](https://github.com/paulmillr/noble-secretbox-aes-gcm) ## Usage > npm install noble-bls12-381 ### Sign a message ```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); })(); ``` ### Sign 1 message 3 times ```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); })(); ``` ### Sign 3 messages with 3 keys ```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); })(); ``` ## API - [`getPublicKey(privateKey)`](#getpublickeyprivatekey) - [`sign(hash, privateKey, domain)`](#signhash-privatekey-domain) - [`verify(hash, publicKey, signature, domain)`](#verifyhash-publickey-signature-domain) - [`aggregatePublicKeys(publicKeys)`](#aggregatepublickeyspublickeys) - [`aggregateSignatures(signatures)`](#aggregatesignaturessignatures) - [`verifyMultiple(hashes, publicKeys, signature, domain)`](#verifymultiplehashes-publickeys-signature-domain) - [`pairing(4dPoint, 2dPoint)`](#pairing4dpoint-2dpoint) - [Helpers](#helpers) ##### `getPublicKey(privateKey)` ```typescript 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 another `Point(x, y)` which we will by default encode to hex Uint8Array. - Returns `Uint8Array`: encoded publicKey for signature verification ##### `sign(hash, privateKey, domain)` ```typescript function sign( hash: Uint8Array | string, privateKey: Uint8Array | string | bigint, domain: Uint8Array | string | bigint ): Promise<Uint8Array>; ``` - `hash: Uint8Array | string` - message hash which would be signed - `privateKey: Uint8Array | string | bigint` - private key which will sign the hash - `domain: 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)` ```typescript 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 verified - `publicKey: Uint8Array | string` - e.g. that was generated from `privateKey` by `getPublicKey` - `signature: Uint8Array | string` - object returned by the `sign` or `aggregateSignatures` function - Returns `Promise<boolean>`: `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 `getPublicKey` - Returns `Uint8Array`: 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 `sign` - Returns `Uint8Array`: 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 ): Promise<boolean> ``` - `hashes: Uint8Array[] | string[]` - messages hashes that needs to be verified - `publicKeys: Uint8Array[] | string[]` - e.g. that were generated from `privateKeys` by `getPublicKey` - `signature: Uint8Array | string` - object returned by the `aggregateSignatures` function - Returns `Promise<boolean>`: `true` / `false` whether the signature matches hashes ##### `pairing(4dPoint, 2dPoint)` ```typescript 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 the `bigint`). - `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 ```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 ``` ## Curve Description BLS12-381 is a pairing-friendly elliptic curve construction from the [BLS family](https://eprint.iacr.org/2002/088), with embedding degree 12. It is built over a 381-bit prime field `GF(p)` with... * z = `-0xd201000000010000` * p = (z - 1)<sup>2</sup> ((z<sup>4</sup> - z<sup>2</sup> + 1) / 3) + z * = `0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab` * q = z<sup>4</sup> - z<sup>2</sup> + 1 * = `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` ... yielding two **source groups** G<sub>1</sub> and G<sub>2</sub>, each of 255-bit prime order `q`, such that an efficiently computable non-degenerate bilinear pairing function `e` exists into a third **target group** G<sub>T</sub>. Specifically, G<sub>1</sub> is the `q`-order subgroup of E(F<sub>p</sub>) : y^2 = x^3 + 4 and G<sub>2</sub> is the `q`-order subgroup of E'(F<sub>p<sup>2</sup></sub>) : y<sup>2</sup> = x<sup>3</sup> + 4(u + 1) where the extention field F<sub>p<sup>2</sup></sub> is defined as F<sub>p</sub>(u) / (u<sup>2</sup> + 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 2<sup>32</sup> 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](https://github.com/tc39/proposal-bigint#cryptography). This means that the lib is vulnerable to [timing attacks](https://en.wikipedia.org/wiki/Timing_attack). 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](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) 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. ## License MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.