Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@noble/bls12-381
Advanced tools
Fastest JS implementation of BLS12-381. Auditable, secure, 0-dependency aggregated signatures & pairings
Fastest JS implementation of BLS12-381. Auditable, secure, 0-dependency aggregated signatures & pairings.
The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs pairing-curves-10, bls-sigs-04, hash-to-curve-12. To learn more about internals, navigate to utilities section.
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 instead: `window.nobleBls12381`
(async () => {
// keys, messages & other inputs can be Uint8Arrays or hex strings
const privateKey = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c';
const message = '64726e3da8';
const publicKey = bls.getPublicKey(privateKey);
const signature = await bls.sign(message, privateKey);
const isValid = await bls.verify(signature, message, publicKey);
console.log({ publicKey, signature, isValid });
// Sign 1 msg with 3 keys
const privateKeys = [
'18f020b98eb798752a50ed0563b079c125b0db5dd0b1060d1c1b47d4a193e1e4',
'ed69a8c50cf8c9836be3b67c7eeff416612d45ba39a5c099d48fa668bf558c9c',
'16ae669f3be7a2121e17d0c68c05a8f3d6bef21ec0f2315f1d7aec12484e4cf5'
];
const messages = ['d2', '0d98', '05caf3'];
const publicKeys = privateKeys.map(bls.getPublicKey);
const signatures2 = await Promise.all(privateKeys.map(p => bls.sign(message, p)));
const aggPubKey2 = bls.aggregatePublicKeys(publicKeys);
const aggSignature2 = bls.aggregateSignatures(signatures2);
const isValid2 = await bls.verify(aggSignature2, message, aggPubKey2);
console.log({ signatures2, aggSignature2, isValid2 });
// 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 isValid3 = await bls.verifyBatch(aggSignature3, messages, publicKeys);
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
})();
To use the module with Deno, you will need import map:
deno run --import-map=imports.json app.ts
import * as bls from "https://deno.land/x/bls12_381/mod.ts";
{"imports": {"crypto": "https://deno.land/std@0.119.0/node/crypto.ts"}}
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 | 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.Uint8Array
: encoded publicKey for signature verificationNote: if you need EIP2333-compliant KeyGen
(eth2/fil), use paulmillr/bls12-381-keygen.
sign(message, privateKey)
function sign(message: Uint8Array | string, privateKey: Uint8Array | 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 Utilities 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 | string)[]): Uint8Array;
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 | string)[]): Uint8Array;
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.Resources that help to understand bls12-381:
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.
// Exports `CURVE`, `utils`, `PointG1`, `PointG2`, `Fp`, `Fp2`, `Fp12` helpers
const utils: {
hashToField(msg: Uint8Array, count: number, options = {}): Promise<bigint[][]>;
bytesToHex: (bytes: Uint8Array): string;
randomBytes: (bytesLength?: number) => Uint8Array;
randomPrivateKey: () => Uint8Array;
sha256: (message: Uint8Array) => Promise<Uint8Array>;
mod: (a: bigint, b = CURVE.P): bigint;
getDSTLabel(): string;
setDSTLabel(newLabel: string): void;
};
// characteristic; z + (z⁴ - z² + 1)(z - 1)²/3
CURVE.P // 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
CURVE.r // curve order; z⁴ − z² + 1, 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
curve.h // cofactor; (z - 1)²/3, 0x396c8c005555e1568c00aaab0000aaab
CURVE.Gx, CURVE.Gy // G1 base point coordinates (x, y)
CURVE.G2x, CURVE.G2y // G2 base point coordinates (x₁, x₂+i), (y₁, y₂+i)
// Classes
bls.Fp // field over Fp
bls.Fp2 // field over Fp₂
bls.Fp12 // finite extension field over irreducible polynominal
// for hashToCurve static method
declare const htfDefaults: {
DST: string;
p: bigint;
m: number;
k: number;
expand: boolean;
hash: Hash;
};
// projective point (xyz) at G1
class PointG1 extends ProjectivePoint<Fp> {
constructor(x: Fp, y: Fp, z?: Fp);
static BASE: PointG1;
static ZERO: PointG1;
static fromHex(bytes: Bytes): PointG1;
static fromPrivateKey(privateKey: PrivateKey): PointG1;
static hashToCurve(msg: Hex, options?: Partial<typeof htfDefaults>): Promise<PointG1>;
toRawBytes(isCompressed?: boolean): Uint8Array;
toHex(isCompressed?: boolean): string;
assertValidity(): this;
millerLoop(P: PointG2): Fp12;
clearCofactor(): PointG1;
}
// projective point (xyz) at G2
class PointG2 extends ProjectivePoint<Fp2> {
constructor(x: Fp2, y: Fp2, z?: Fp2);
static BASE: PointG2;
static ZERO: PointG2;
static hashToCurve(msg: Hex, options?: Partial<typeof htfDefaults>): Promise<PointG2>;
static fromSignature(hex: Bytes): PointG2;
static fromHex(bytes: Bytes): PointG2;
static fromPrivateKey(privateKey: PrivateKey): PointG2;
toSignature(): Uint8Array;
toRawBytes(isCompressed?: boolean): Uint8Array;
toHex(isCompressed?: boolean): string;
assertValidity(): this;
clearCofactor(): PointG2;
}
To achieve the best speed out of all JS / Python implementations, the library employs different optimizations:
Benchmarks measured with Apple M2 on macOS 12.5 with node.js 18.8:
getPublicKey x 818 ops/sec @ 1ms/op
sign x 44 ops/sec @ 22ms/op
verify x 34 ops/sec @ 29ms/op
pairing x 84 ops/sec @ 11ms/op
aggregatePublicKeys/8 x 117 ops/sec @ 8ms/op
aggregateSignatures/8 x 45 ops/sec @ 21ms/op
with compression / decompression disabled:
sign/nc x 60 ops/sec @ 16ms/op
verify/nc x 57 ops/sec @ 17ms/op ± 1.05% (min: 17ms, max: 19ms)
aggregatePublicKeys/32 x 1,163 ops/sec @ 859μs/op
aggregatePublicKeys/128 x 758 ops/sec @ 1ms/op
aggregatePublicKeys/512 x 318 ops/sec @ 3ms/op
aggregatePublicKeys/2048 x 96 ops/sec @ 10ms/op
aggregateSignatures/32 x 516 ops/sec @ 1ms/op
aggregateSignatures/128 x 269 ops/sec @ 3ms/op
aggregateSignatures/512 x 93 ops/sec @ 10ms/op
aggregateSignatures/2048 x 25 ops/sec @ 38ms/op
Noble is production-ready.
We're using built-in JS BigInt
, which is potentially vulnerable to timing attacks as per official spec. 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 malware 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) 2019 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.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.