Giter Club home page Giter Club logo

noble-bls12-381's Introduction

Deprecated. The repo has been merged into noble-curves

Please switch to:

npm install @noble/curves
// Import in js
import { bls12_381 } from '@noble/curves/bls12-381';

noble-bls12-381

Fastest JS implementation of BLS12-381. Auditable, secure, 0-dependency aggregated signatures & pairings.

The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:

  • Construct zk-SNARKs at the 128-bit security
  • Use threshold signatures, which allows a user to sign lots of messages with one signature and verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.

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.

This library belongs to noble crypto

noble-crypto — high-security, easily auditable set of contained cryptographic libraries and tools.

  • No dependencies, protection against supply chain attacks
  • Auditable TypeScript / JS code
  • Supported in all major browsers and stable node.js versions
  • All releases are signed with PGP keys
  • Check out homepage & all libraries: curves (4kb versions secp256k1, ed25519), hashes

Usage

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
  • app.ts: import * as bls from "https://deno.land/x/bls12_381/mod.ts";
  • imports.json: {"imports": {"crypto": "https://deno.land/[email protected]/node/crypto.ts"}}

API

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.
  • Returns Uint8Array: encoded publicKey for signature verification

Note: 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 & signed
  • privateKey: Uint8Array | string | bigint - private key which will sign the hash
  • Returns Uint8Array | string | PointG2: encoded signature

Check 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 function
  • message: Uint8Array | string - message hash that needs to be verified
  • publicKey: Uint8Array | string - e.g. that was generated from privateKey by getPublicKey
  • Returns Promise<boolean>: true / false whether the signature matches hash
aggregatePublicKeys(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
  • Returns Uint8Array | PointG1: one aggregated public key which calculated from public keys
aggregateSignatures(signatures)
function aggregateSignatures(signatures: (Uint8Array | string)[]): Uint8Array;
function aggregateSignatures(signatures: PointG2[]): PointG2;
  • signatures: (Uint8Array | string | PointG2)[] - e.g. that have been generated by sign
  • Returns Uint8Array | PointG2: one aggregated signature which calculated from signatures
verifyBatch(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 function
  • messages: (Uint8Array | string | PointG2)[] - messages hashes that needs to be verified
  • publicKeys: (Uint8Array | string | PointG1)[] - e.g. that were generated from privateKeys by getPublicKey
  • Returns Promise<boolean>: true / false whether the signature matches hashes
pairing(pointG1, pointG2)
function pairing(
  pointG1: PointG1,
  pointG2: PointG2,
  withFinalExponent: boolean = true
): Fp12
  • pointG1: PointG1 - simple point, x, y are bigints
  • pointG2: PointG2 - point over curve with complex numbers ((x₁, x₂+i), (y₁, y₂+i)) - pairs of bigints
  • withFinalExponent: boolean - should the result be powered by curve order; very slow
  • Returns Fp12: paired point over 12-degree extension field.

Utilities

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.

  • BLS Relies on Bilinear Pairing (expensive)
  • Private Keys: 32 bytes
  • Public Keys: 48 bytes: 381 bit affine x coordinate, encoded into 48 big-endian bytes.
  • Signatures: 96 bytes: two 381 bit integers (affine x coordinate), encoded into two 48 big-endian byte arrays.
    • The signature is a point on the G2 subgroup, which is defined over a finite field with elements twice as big as the G1 curve (G2 is over Fp2 rather than Fp. Fp2 is analogous to the complex numbers).
  • The 12 stands for the Embedding degree.

Formulas:

  • P = pk x G - public keys
  • S = pk x H(m) - signing
  • e(P, H(m)) == e(G, S) - verification using pairings
  • e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si)) - signature aggregation

The 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 label
  • RAND_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;
}

Speed

To achieve the best speed out of all JS / Python implementations, the library employs different optimizations:

  • cyclotomic exponentation
  • endomorphism for clearing cofactor
  • pairing precomputation

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

Security

Noble is production-ready.

  1. No public audits have been done yet. Our goal is to crowdfund the audit.
  2. It was developed in a similar fashion to noble-secp256k1, which was audited by a third-party firm.
  3. It was fuzzed by Guido Vranken's cryptofuzz, no serious issues have been found. You can run the fuzzer by yourself to check it.

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.

Contributing

  1. Clone the repository.
  2. npm install to install build dependencies like TypeScript
  3. npm run build to compile TypeScript code
  4. npm run test to run jest on test/index.ts

Special thanks to Roman Koblov, who have helped to improve pairing speed.

License

MIT (c) 2019 Paul Miller (https://paulmillr.com), see LICENSE file.

noble-bls12-381's People

Contributors

azuchi avatar christianpaquin avatar iancoleman avatar mrhornsby avatar paulmillr avatar romarq avatar sajjon avatar silur avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

noble-bls12-381's Issues

hashToCurve defaults to the G2 DST even on G1

As you can see here:

noble-bls12-381/index.ts

Lines 331 to 346 in b06f567

static async hashToCurve(msg: Hex, options?: Partial<typeof htfDefaults>): Promise<PointG1> {
msg = ensureBytes(msg);
const [[u0], [u1]] = await hash_to_field(msg, 2, { m: 1, ...options });
const [x0, y0] = map_to_curve_simple_swu_3mod4(new Fp(u0));
const [x1, y1] = map_to_curve_simple_swu_3mod4(new Fp(u1));
const [x2, y2] = new PointG1(x0, y0).add(new PointG1(x1, y1)).toAffine();
const [x3, y3] = isogenyMapG1(x2, y2);
return new PointG1(x3, y3).clearCofactor();
}
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
static async encodeToCurve(msg: Hex, options?: Partial<typeof htfDefaults>): Promise<PointG1> {
msg = ensureBytes(msg);
const u = await hash_to_field(msg, 1, {
m: 1,
...options,
});

the code will default to the htfDefaults which are meant for G2 when none are passed:

const htfDefaults = {
    DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
    p: math_js_1.CURVE.P,
    m: 2,
    k: 128,
    expand: true,
    hash: sha256,
};

This could be an issue for people meaning to use the library to do things on G1 and not providing their own DST or their own test vectors and thus not detecting the issue. Overall it doesn't look like a big security problem, but it does cause interoperability issues.

how to understand the precomputes of Miller Loop in noble-bls12-381?

Miller Loop is a basic but important algorithm to calculate pairing.

noble-bls12-381 is a clean and independent implementation of bls12-381 pairing without library dependency.

I know the original Miller Loop algorithm, but in above implementation, there exist a tricky pairingPrecomputes, is there any material or description about this pre-computing? thanks a lot

https://github.com/paulmillr/noble-bls12-381/blob/main/index.ts#L395

Support constructing G1 and G2 points from their uncompressed forms

Cannot convert a BigInt value to a number

TypeError: Cannot convert a BigInt value to a number
at Math.pow ()
at Object.../../../../webuy/uniapp/mui1/mui1/node_modules/noble-bls12-381/math.js (pages-wallet-setPwd.js:4330)
at webpack_require (index.js:854)
at fn (index.js:151)
at Object. (pages-wallet-setPwd.js:3760)
at Object.../../../../webuy/uniapp/mui1/mui1/node_modules/noble-bls12-381/index.js (pages-wallet-setPwd.js:4307)
at webpack_require (index.js:854)
at fn (index.js:151)
at Object. (pages-wallet-setPwd.js:2696)
at Object.../../../../webuy/uniapp/mui1/mui1/node_modules/chia-wallet-utils/build/index.js (pages-wallet-setPwd.js:3084)

Error printing zero points

This code fails:

console.log(PointG1.ZERO);
console.log(PointG2.ZERO);

with the following stack trace:

Error: invert: expected positive integers, got n=0 mod=4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
    at invert (/node_modules/@noble/bls12-381/lib/math.js:75:15)
    at Fp.invert (/node_modules/@noble/bls12-381/lib/math.js:108:23)
    at PointG1.toAffine (/node_modules/@noble/bls12-381/lib/math.js:625:28)
    at PointG1.toString (/node_modules/@noble/bls12-381/lib/math.js:619:29)

Should the ProjectivePoint.toString function check for the ZERO point before trying to invert?

Option to choose curve for hash_to_field

Just a quick question regarding hash_to_field as a way to convert arbitrary bytes to Fr

In the ietf draft it says
https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#section-5-4
"The hash_to_field function is also suitable for securely hashing to scalars. For example, when hashing to scalars for an elliptic curve (sub)group with prime order r, it suffices to instantiate hash_to_curve with target field GF(r)."

I've modified noble-bls12-381 locally allowing hash_to_field to take a 4th parameter, field = math_1.CURVE.P and then passing in math_1.CURVE.r to generate bytes for Fr using hash_to_field (also exported hash_to_field function in utils).

From what I can see this is legitimate use of hash_to_field. Is there anything you're aware of that would make this a bad idea?

The idea doesn't seem widely used (unlike hash_g2) but it seems like it should be a robust and standardized kdf from arbitrary bytes to key without rejection sampling. Am I understanding it correctly?

Support for Deno runtime

Currently, this module does not completely support the deno runtime environment.
When trying to use this with deno, the following error occurs:

error: Import 'https://deno.land/x/[email protected]/math' failed: 404 Not Found
    at https://deno.land/x/[email protected]/index.ts:14:0

This is happening because deno requires an explicit extension for import statements. For example, './math.ts' instead of './math'.

HashToCurve on G1 points

I need to perform hash to curve on G1, like this:
https://github.com/kilic/bls12-381/blob/6045b0235e36793a56ad254cc6d2af650c3a584f/g1.go#L827-L846

Is that currently possible with Noble? I wasn't able to find the required function.

If not, would you consider implementing it?
(We'd be happy to sponsor its implementation, since it's necessary to implement a JS lib compatible with our identity-based encryption scheme: https://github.com/drand/kyber/blob/master/encrypt/ibe/ibe.go#L47.)

The default implementation is incompatible with Ethereum specification

This implementation uses BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_ while Ethereum uses BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_. To verify Ethereum signatures one has to set the DST label (bls.utils.setDSTLabel('BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_')). Perhaps worth mentioning this in the README.

On "constant time"

Quoting README.md:

But, JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve in a scripting language.

In the context of cryptographic security "constant time" is not about elapsed wall-clock time it takes to perform an operation, but its invariance in relation to secret material. In other words it's not like there may be no timing variations whatsoever, but whatever variations there are, they may not depend on the secret value. And as long as this condition is met, JIT or Garbage Collector may intervene at any time, including during secret-key operations, and for as long as deemed necessary.

I'm not asserting that achieving constant-time-ness in pure JavaScript is feasible, but JIT or Garbage Collector are not as large of obstacles as suggested. In JavaScript or elsewhere.

Add support for G2 public keys with G1 signatures

Not working with DFinity bls

Description

When I tried to use this library instead of BLS_verify implementation into agent-js library I cannot make it work. I only obtained Error: Invalid G1 point: not on curve Fp. Going into agent-rs library to watch what the original implementation does, I realize that in DFinity libraries PublicKey is G2 point and signature is a G1 point. So, there is anyway to fix this problem?

agent-rs library where implement bls verify https://github.com/dfinity/agent-rs/blob/88606ecc8288592a7bcc1c8d23e877914b3b31da/ic-agent/src/bls/bls12381/bls.rs#L137-L171

Multiply G1, G2 by Fr

Does the lib support multiplication of G1, G2 by Fr?

I don't see it in the code, but I could be wrong.

Make instructions on usage in the browser clearer

I have been spending some time with this already, but I can't get it to work. I can't be the only one.

The readme has basic usage instructions for Node.js and browser, but it is never explained how to include the Javascipt files in your project. I have tried

  • adding index.js and math.js via <script> tag
  • using ESM syntax with import from
  • letting Browserify create a compiled javascript file

None of it worked. I'd have given up, but it does work on NPM Runkit, so I know there is a way to do it, which is frustrating. I tried looking for a CDN version of the code, but it seems it does not yet exist either.

I don't know whether it makes sense to provide a compiled, single-file release version for the browser, but it sure would have made including this on a website easier.

Can this library generate 160 bits (20 bytes) signature?

according to the original paper, BLS seems to support shortest signature size like 160bits, but i cant find any lib that can support that size directly. i know 160bits is less secure, but produce more user friendly signature for keyboard typing, which can be used in some cases.

Sync API

I was attempting to do some benchmark between the existing BLS implementations however this library has an async API which breaks with what https://github.com/ChainSafe/bls currently has. I see that the reason of the async API is the hash function. Could a sync version of sha256 be used so the API stays sync?

Readme single message aggregation example does not work

Using the readme example the below does not verify using node v 12.0.

        const msg = 'hello';
        const privateKeys = [
            '18f020b98eb798752a50ed0563b079c125b0db5dd0b1060d1c1b47d4a193e1e4',
            'ed69a8c50cf8c9836be3b67c7eeff416612d45ba39a5c099d48fa668bf558c9c',
            '16ae669f3be7a2121e17d0c68c05a8f3d6bef21ec0f2315f1d7aec12484e4cf5'
        ];
        const publicKeys = privateKeys.map(bls.getPublicKey);
        const signatures = await Promise.all(privateKeys.map(p => bls.sign(msg, p)));
        const aggPubKey: any  = await bls.aggregatePublicKeys(publicKeys);
        const aggSignature = await bls.aggregateSignatures(signatures);
        const isCorrect = await bls.verify(aggSignature, msg, aggPubKey);

      //=> returns false
      console.log({isCorrect});

Is there a way to verify a single message with only an aggregate signature and aggregate public key?

Hello and thank you for this library! It's really helping me learn about ECC and BLS. I have a use case in mind that does not seem to work, and I am not sure if I am doing it wrong, or if it is just not possible.

A simple addition to your example here illustrates what I'm trying to do:

  // aggSignature3 = 3 different messages signed with 3 different private keys (3 diff individual public sigs) aggregated into one sig
  // aggPubKey2 = 3 public keys corresponding to the 3 private keys above aggregated into one pub key

  // any way to verify each individual message from the group of 3 with only the aggregated sigs and pub keys? or impossible?
  const onlyFirstMessage = messages[0];
  const isCorrect4 = await bls.verify(aggSignature3, onlyFirstMessage, aggPubKey2) // str, str, str
  console.log('is correct: ', isCorrect4); // returns false

Signing similar message results in indentical signature?

Came across an issue where if i make two messages similar enough i can produce the same signature for two different messages, not sure if i should be hashing messages before passing them to bls.sign()? Documentation seems to suggest bls.sign hashes before signing

message: Uint8Array | string - message which would be hashed & signed

var bls = require('noble-bls12-381');

testfunction()

async function testfunction() {

  privKey = "970a3e0249bc5e464291d5eee88ae364d8b794f648950055a382a28ed73d655472f218dae342d3ee4566443ff23a78d85580eb4b11954f6286c6cfc67ad1c082"
  message1 = "test"
  message2 = "tes3"

  let signature1 = await bls.sign(message1, privKey)
  let signature2 = await bls.sign(message2, privKey)

  console.log(signature1 +"     "+ signature2)
};

Result of this is

Sig1

175,108,55,86,26,251,15,127,253,213,104,43,200,96,1,209,89,116,108,207,203,153,209,235,196,140,78,183,134,165,209,6,236,115,195,3,34,60,8,206,114,48,62,206,39,129,17,62,0,138,178,86,37,175,157,169,168,138,78,130,147,204,36,130,255,89,38,124,134,252,16,192,209,18,225,6,107,203,116,129,81,194,75,230,226,178,210,79,239,165,8,110,90,253,199,246  

Sig2

175,108,55,86,26,251,15,127,253,213,104,43,200,96,1,209,89,116,108,207,203,153,209,235,196,140,78,183,134,165,209,6,236,115,195,3,34,60,8,206,114,48,62,206,39,129,17,62,0,138,178,86,37,175,157,169,168,138,78,130,147,204,36,130,255,89,38,124,134,252,16,192,209,18,225,6,107,203,116,129,81,194,75,230,226,178,210,79,239,165,8,110,90,253,199,246

Which are identical, despite signing two different messages "test" and "tes3"

Regarding the support for the Bls12381G2KeyPair.ts in bls12381-key-pair

Hi,
We are using noble-bls12-381 library in our project. And we also have to use the Bls12381G2KeyPair.ts > bls12381-key-pair library in our project.
Now I would like to know whether the noble-bls12-381 could support the Bls12381G2KeyPair.ts library.
Please let me know.
As explained above we will have to use the Bls12381G2KeyPair.ts library from noble-bls12-381.

My email id : [email protected]

Understand why g1_on_curve from paper is slower than cofactor multiplying

https://eprint.iacr.org/2019/814.pdf

The code is slower than this.multiplyUnsafe(CURVE.hEff).isZero(). Need to understand why the "optimization" does not optimize. I mean, hEff aka 0xd201000000010001n has low hamming weight et al; need to understand deeply why the opt is possible.

  // σ(P)
  private sigma(): PointG1 {
    const BETA = 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn;
    const [x, y] = this.toAffine();
    return new PointG1(x.multiply(BETA), y);
  }
  private isTorsionFree(): boolean {
    // (x²−1)/3
    const c1 = 76329603384216526021617858986798044501n;
    const P = this;
    let sP = P.sigma();
    let Q = sP.double();
    sP = sP.sigma();
    Q = Q.subtract(P).subtract(sP).multiplyUnsafe(c1).subtract(sP);
    return Q.isZero();
  }

Expose non-signature API

As specified in the introduction, BLS12-381 is used for non-signature applications such as SNARKs. It may make sense to expose a non-signature API (e.g. directly expose the pairing function).

Problems running example code

I'm getting several different errors while running the example code from the README.

Environment: running locally in node.js on a lightweight express server.

  const privateKey = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c';
  const msg = 'hello';
  const publicKey = bls.getPublicKey(privateKey);
  const signature = await bls.sign(msg, privateKey);
  const isCorrect = await bls.verify(msg, publicKey, signature);
(node:6722) UnhandledPromiseRejectionWarning: Error: Expected hex string or Uint8Array, got 0hello
    at hexToBytes (/home/daniel/Projects/fission/fil-cosigner/node_modules/noble-bls12-381/index.js:74:19)
    at Function.hashToCurve (/home/daniel/Projects/fission/fil-cosigner/node_modules/noble-bls12-381/index.js:241:19)
...

Alrighty, no plaintext msgs. Let's convert the msg to a Uint8Array:

  const fromHexString = hexString =>
    new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));

  const privateKey = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c';
  const msg = fromHexString('hello');
  const publicKey = bls.getPublicKey(privateKey);
  const signature = await bls.sign(msg, privateKey);
  const isCorrect = await bls.verify(msg, publicKey, signature);
(node:8466) UnhandledPromiseRejectionWarning: Error: The given point is not on G1: y**2 = x**3 + b
    at Function.fromCompressedHex (/home/daniel/Projects/fission/fil-cosigner/node_modules/noble-bls12-381/index.js:187:19)
    at normP1 (/home/daniel/Projects/fission/fil-cosigner/node_modules/noble-bls12-381/index.js:323:55)
    at Object.verify (/home/daniel/Projects/fission/fil-cosigner/node_modules/noble-bls12-381/index.js:34
...

Everything works well up to verify. I also tried, hashing the msg to a point on G2 before verify which didn't change anything. This appears to be an issue with the public key, since the public key is supposed to be on G1.

Any thoughts/ideas?

Compatibility with Filecoin

Hey there 🙂

I'm trying to use this library for signing Filecoin messages. Specifically the signature/pubkey aggregation tools which aren't available in the Filecoin WASM lib

However, I've noticed that the public keys don't match up:

private key: 4eeb8f66c557115a7ec37e7debc2b0b9130f0d4a2b74cd64ec478a88e2ac052c
public key (from zondax lib):  84b1498c9332a1639f67c8e2fc49a9bae0ecfb68cb6182f12ad8c30cc62e8c37029aeaeaa5e1685fb2656792b926bc8d
public key (from noble-bls12-381): ac2d1dc191f5f8766ac7cef6877c10ff928b7a8217e1f64ca069499242f91e5f50064c8ee2b337e0b6a68b6808bc11f0

Do you have any quick thoughts on the matter. I'm relatively new to BLS, but from what I can tell, these should all be on the same pair of 2 curves, and I've verified that both this lib & filecoin use G1 for public keys and G2 for signatures.

I've also verified that the zondax lib calculates the same public key as the lotus node does (Go implementation of Filecoin).

Is there something else that I'm not thinking of?

Wrong serialisation of point at infinity?

Hi,

I may be wrong but I think the serialisation of the point at infinity is wrong. I would expect the two high order bits to be set (compressed = true, infinity = true), but converting Z1 to a private key has only the compressed bit set:

800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

As opposed to:

C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

I thought I'd double check by deserialising again and comparing the points:

console.log(Z1);
console.log(publicKeyToG1(publicKeyFromG1(Z1)));

This produces the following:

Point {
  x: Fp { _value: 1n },
  y: Fp { _value: 1n },
  z: Fp { _value: 0n },
  C: [Function: Fp] {
    ORDER: 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787n
  }
}
Point {
  x: Fp { _value: 0n },
  y: Fp { _value: 2n },
  z: Fp { _value: 1n },
  C: [Function: Fp] {
    ORDER: 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787n
  }
}

Thanks

Mark

Issue when using with react-native

Can’t use with react-native:

Uncaught (in promise) TypeError: Cannot convert a BigInt value to a number

Any idea how to solve this?

Possible missing edge case in `modexp` function

Following your suggestion here, I implemented your modexp function in our bigint conversion PR. I may have uncovered an edge case not addressed in your method and wanted to get your input. In the modexptests in the Ethereum/Tests repo, there are a couple of tests that generate a call to the modexp method with a 0 for the exponent and some other value for the modulo and one case in particular passes in a = 0, power = 0, and modulo 1. In your method as currently coded, this would result in a 1 being returned though the expected result in the ethereum test is 0 (I believe based on the understanding that any base ^ 0 = 1 and then 1 % 1 = 0. Does this look like a possible edge case that your code doesn't account for? I've added in a check where the exponent = 0 and then returned 1 % modulo in our code and this satisfies the test case and I believe is a correct result, at least on some definitions of exponentiation (though I'm far from an expert in this area) so I wanted to raise it and get your input.

Cannot assign to 'DST_LABEL' because it is a read-only property.ts(2540)

hi! i got a error when to change DST_LABEL.

import * as bls from 'noble-bls12-381';

// Default domain (DST) is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_`, use `bls.DST` to change it.
bls.DST_LABEL = 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_';


/** Cannot assign to 'DST_LABEL' because it is a read-only property.ts(2540) */

is any other way to change it?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.