npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@grottonetworking/bbs-signatures

v0.1.5

Published

JavaScript implementation of the BBS+ signature suite for node or browser

Downloads

47

Readme

JavaScript BBS Signatures

This repository contains an all JavaScript implementation of the emerging BBS+ digital signature standard being developed in conjunction with the Decentralized Identity Foundation (DIF) and the Crypto Forum Research Group (CFRG) of the IRTF.

This version of our implementation conforms to the procedures and validates against the test vectors in the 05 draft, i.e., draft-irtf-cfrg-bbs-signatures-5.

You can find the latest draft specification at The BBS Signature Scheme (DIF) this work then feeds into the IRTF draft which is available at draft-irtf-cfrg-bbs-signatures.

Note that there are other draft implementations available notably in Rust, Typescript, etc... This is a JavaScript only implementation with a single dependency on the noble-curves project which has a single dependency on the noble-hash project. This implementation can be used with Node.js or in the browser. We provide Node.js based example code and you can see our BBS Signature Demo where we use this library in the browser to demonstrate interactively the properties of BBS signatures.

BBS Signature Basics

BBS signatures are different from other common digital signatures schemes such as EdDSA or ECDSA in that it supports:

  1. The signing of multiple messages at a time, rather than a single message.
  2. A single short signature covering all the messages.
  3. The ability to derive a secondary signature, called a proof in BBS terminology, that may choose not to disclose some of the messages in the original signature. This is called selective disclosure.
  4. The digital proofs generated in the previous section are unlinkable to each other in the cryptographic sense, and cannot be used for tracking. Note that this does not apply to any information disclosed.

Both the original signature and any proofs generated are verified with the original issuers public key. The contents of individual messages covered by a signature may not be changed, nor can new messages be added to a proof only messages can be withheld from a proof and still have the proof verify.

One prime example of the use of BBS signatures is in a three party issuer, holder, verifier model. For example suppose the issuer is the department of motor vehicles (DMV) and wants to issue the digital equivalent of a drivers license which contains an assortment of information. A fictitious set of information is shown below in JSON format for a tree living in northern California:

[   "FirstName: Sequoia",
    "LastName: Sempervirens",
    "Address: Jedediah Smith Redwoods State Park, California",
    "Date of Birth: 1200/03/21",
    "Height: 296 feet",
    "Eyes: None",
    "Hair: Brown bark, green needles",
    "Picture: Encoded photo",
    "License Class: None, Trees can't drive" ]

The DMV would use its secret key to produce a signature on this list of messages. It would then return to the holder, the aforementioned tree in out case, a bundle of information containing the signature, i.e., something like:

{
  "publicKey": "b65b7cbff4e81b723456a13936b6bcc77a078bf6291765f3ae13170072249dd7daa7ec1bd82b818ab60198030b45b8fa159c155fc3841a9ad4045e37161c9f0d9a4f361b93cfdc67d365f3be1a398e56aa173d7a55e01b4a8dd2494e7fb90da7",
  "header": "11223344556677889900aabbccddeeff",
  "messages": [
    "FirstName: Sequoia",
    "LastName: Sempervirens",
    "Address: Jedediah Smith Redwoods State Park, California",
    "Date of Birth: 1200/03/21",
    "Height: 296 feet",
    "Eyes: None",
    "Hair: Brown bark, green needles",
    "Picture: Encoded photo",
    "License Class: None, Trees can't drive"
  ],
  "signature": "94bb93062e05bc702d0ab222b861fd0311533d6dcbcad4050e45dd2392de951a912915af08bd87b2284807432245f9960e1f59680a59cd9fae490dee659d63fd3922a728e2ba3ee33db6bcc806ec2cea3d7264489a42ca09deeac7ca88b1811c2158b51d81560832daf6a0000037a87a"
}

Note that this "document" contains a lot of potentially sensitive information. Hence should be sent securely from the issuer to the holder .

Now suppose that the tree needs to prove its age, e.g., in order to gain entrance to a club or bar, but doesn't want to divulge any extra information besides the minimum necessary, i.e., it just wants to show the following to the bouncer at the door in a verifiable manner:

[    "Date of Birth: 1200/03/21",
    "Picture: Encoded photo" ]

It then uses the BBS proof generation algorithm to generate proof information such as:

{
  "pk": "b65b7cbff4e81b723456a13936b6bcc77a078bf6291765f3ae13170072249dd7daa7ec1bd82b818ab60198030b45b8fa159c155fc3841a9ad4045e37161c9f0d9a4f361b93cfdc67d365f3be1a398e56aa173d7a55e01b4a8dd2494e7fb90da7",
  "header": "11223344556677889900aabbccddeeff",
  "ph": "",
  "disclosedIndexes": [
    3,
    7
  ],
  "disclosedMsgs": [
    "Date of Birth: 1200/03/21",
    "Picture: Encoded photo"
  ],
  "proof": "8432472ce9cb174043856753306c290d87ecc8564038d34f18f5ac0127166aefd2e0c67b680285aff3401efc0c834a9ca133d85e8695d9085847c5d537cda5c37740df5d9eb5abf3d8e5f1242336d01eaf11dd80a06efd43e887ade2c3f5352ca8c53d8b7217d90c46af1f45a70ebeff512e6dc689bea361c91c2d1bc38b063ae78958d6e3a09d88a3cd3d78bc94c6c96f8788dad9c17e8afac3e0bb1a8c2e83f6c3d718c723ea36a06c9ac0b81561b66bacbf68b56ff09007df6b5258f36eb2ff1886e081702b829cd43da22510e1b35f07bc316f391f88807843d9a81ccfd42731196f5d3ac377c0679e7a0e58d30a5529e7d04bc350769bbe44d26416a77b7d799594e570370c649ddfbabbfc3e830e223ba53d86b19a9a3291038e425419af90f5ecd9c25d309cf6ce17b8339177363eb113a065b853cd4a6b47ff8b04ac7799fb931f4a42d754685c81852c34734aba447ec2a414df702e66dc67570b4603d2cd1f0af406925e4525738fbd4b8a4799d9fd6cd0a2c7761f49c76cb048f004cf09939621a7368da2ae5ac671b16a00f108fd57002bd835c9760758ef11f6b074ab40e7e47eb3fc31ecb1af00cf033c8dd2cfba0ea4d36388d9f15f8e58813988abe14b89075b37a3caa98cb4328e4daf98253ce16b1f4152358ef86f34695997086a1927f67fe69d7df94391a3661a9cd0340cf5618d9334f97cf6db5f25c203b55c866a014bc7981668fd208f55"
}

The bouncer at the door, the verifier, verifies this against the DMV's public key, i.e., the two messages disclosed were part of the orginal signed information and have not been modified.

Now suppose this tree is very thirsty and visits another bar, given California's issue with drought the next bar states it will only serve trees from a state or national parks. So the tree includes its address information as well as its photo and date of birth as follows:

[   "Address: Jedediah Smith Redwoods State Park, California",
    "Date of Birth: 1200/03/21",
    "Picture: Encoded photo" ]

It produces another BBS proof such as:

{
  "pk": "b65b7cbff4e81b723456a13936b6bcc77a078bf6291765f3ae13170072249dd7daa7ec1bd82b818ab60198030b45b8fa159c155fc3841a9ad4045e37161c9f0d9a4f361b93cfdc67d365f3be1a398e56aa173d7a55e01b4a8dd2494e7fb90da7",
  "header": "11223344556677889900aabbccddeeff",
  "ph": "",
  "disclosedIndexes": [
    2,
    3,
    7
  ],
  "disclosedMsgs": [
    "Address: Jedediah Smith Redwoods State Park, California",
    "Date of Birth: 1200/03/21",
    "Picture: Encoded photo"
  ],
  "proof": "80c4b1301cd5964e1e69fd1a95d074fb30b085a733ae56d4bc427da89dfaf9dc63bf32e6d5d027d9ba6d3f59091aeb8e92e3baead45b9be6d501013478d4fbdd6cdbf637f201bd2c8c840cc55b2b33562e6429c88b96866e0ca86ce242408e8aaf3abb41d8c89bf436058504f43f8e7f31bec5a28a571666421201971341e731cafba531188d67f40553166c616a7c4839f7a605b3a774c158e1a45841d1eb07bd8534390010027f14bae9dc59bd75a63b6b532940d1ea98bdfb2354bf25b464f8b685fd4723aca13fbb58a5b3c8a1f143cba92acda6a731ba876b12b55b4c1fffa79699d3cc3f271f27b4a34ef4b356582147fdd19477c9bd9cfc7ba270de1bbe3c68038df39bebc289e265008631720b17aa4ca2bc89769f791b78bec9bcf7b19886f14130a232f7368649ea2fa2c843ecb9014ba4d622e80cef0fcbbc557aad02e3e5fac75569e6d0428662034ba93c2a50c4c17336656d4b44c3d9170e22460f699b514af2583e9011877443d30d50515db20845242b2d55a0a8960ae4b19cb6604bca7c47cd3fbef486a0ed76953eb450096123121826fa12a5b45a7a57554d92ab1d6b0c9e1df880504093f7a87159173ade25480fdce339611c96ad0dcf8b83543bbded15b21af56f8aff918957743e4ca81a5113be17b0a834807b374df76a1196b3152cca3646acc7055667"
}

Which the bartender at the second bar can verify with the DMV's public key. By unlinkable we mean the cryptographic proof field in these two proofs are uncorrelated and can't be used to track the tree from bar to bar. The tree, however, has disclosed its "date of birth" and "photo" to both bars which could be used for tracking purpose.

You may have noticed that the proofs in these two examples have different sizes. Currently BBS signatures have a size of 112 bytes, while BBS proofs have a size of $304 + U*32$ bytes where U is the number of undisclosed messages. We say currently since new work may reduce these numbers a bit while preserving security properties.

Library Usage

Key Generation

For signing a secret (also known as a private) key is needed. For signature and proof verification the verifiers need the corresponding public key. A recomended procedure for deriving an appropriate secret key from some initial random bytes is given in the BBS specification and available via the keyGen() function. See the KeyGenExample.js file for details.

import {API_ID_BBS_SHAKE, bytesToHex, keyGen, publicFromPrivate} from '@grottonetworking/bbs-signatures';
import crypto from 'crypto';

const bytesLength = 40; // >= 32 bytes
// Generate random initial key material -- Node.js
const keyMaterial = new Uint8Array(crypto.randomBytes(bytesLength).buffer);
const keyInfo = new TextEncoder().encode('BBS-Example Key info');
const sk_bytes = await keyGen(keyMaterial, keyInfo, API_ID_BBS_SHAKE);
console.log(`Private key, length ${sk_bytes.length}, (hex):`);
console.log(bytesToHex(sk_bytes));
const pub_bytes = publicFromPrivate(sk_bytes);
console.log(`Public key, length ${pub_bytes.length}, (hex):`);
console.log(bytesToHex(pub_bytes));

Message Encoding

Since BBS works works with multiple messages the encoding (really cryptographic processing) of the messages is done as separate step. This is accomplished with the messages_to_scalars() function. For example:

import {API_ID_BBS_SHAKE, messages_to_scalars, numberToHex} from '@grottonetworking/bbs-signatures';

const messages = [
  'FirstName: Sequoia',
  'LastName: Sempervirens',
  'Address: Jedediah Smith Redwoods State Park, California',
  'Date of Birth: 1200/03/21',
  'Height: 296 feet',
  'Eyes: None',
  'Hair: Brown bark, green needles',
  'Picture: Encoded photo',
  'License Class: None, Trees can\'t drive'
];

const te = new TextEncoder(); // To convert strings to byte arrays
const messagesOctets = messages.map(msg => te.encode(msg));
const msg_scalars = await messages_to_scalars(messagesOctets, API_ID_BBS_SHAKE);
for(let i = 0; i < messages.length; i++) {
  console.log(`msg ${i} ${messages[i]}`);
  console.log(`scalar (hex): ${numberToHex(msg_scalars[i], 32)}`);
}

Generator Preparation

All major BBS operations (signing, signature verification, proof generation, and proof verification) require the use of cryptographic group "generators". There need to be as many "generators" as messages that are or were originally signed. These take a bit of time to compute but can be reused in all the aforementioned operations and do not take up too much space. Hence we break out the preparation of these "generators" into a separate step via the prepareGenerators() function.

Below we show an example that creates the generators and these can be confirmed against the "message generator" test vectors given in the specification. You would never have a need to look at these in a real application.

import {API_ID_BBS_SHAKE, prepareGenerators} from '@grottonetworking/bbs-signatures';

const L = 10;
const gens = await prepareGenerators(L, API_ID_BBS_SHAKE); // Default SHA-256 hash
console.log(`Q1:${gens.Q1.toHex(true)}`); // Elliptic point to compressed hex
console.log(`Q2:${gens.Q2.toHex(true)}`);
for(let i = 0; i < gens.H.length; i++) {
  console.log(`H${i}:${gens.H[i].toHex(true)}`);
}

Signing and Verifying

Generate a signature for a list of messages we need the following: (1) private/public key pair, (2) list of encoded messages, (3) prepared generators. Optionally a "header" (as a byte array) maybe supplied that contains context or application information. To verify a signature we need all of the above but not the private key. This is shown below with the full example given in TreeDMVExample.js. An additional example is given in SignVerifyExaple.js.

// Excerp from TreeDMVExample.js
const header = hexToBytes('11223344556677889900aabbccddeeff');
const signature = await sign(sk_bytes, pk_bytes, header, msg_scalars, gens, API_ID_BBS_SHAKE);
console.log('Signature:');
console.log(bytesToHex(signature));

const verified = await verify(pk_bytes, signature, header, msg_scalars, gens, API_ID_BBS_SHAKE);
console.log(`Algorithm verified: ${verified}`);

Proof Generation and Verification

For proof generation the holder needs the signature, messages, and public key. Then they need to decide which messages they want to disclose. This can be done in code as shown below with the full example given in TreeDMVExample.js. An additional example is contained in ProofGenVerifyExample.js.

const ph = new Uint8Array();
const disclosed_indexes = [3, 7]; // Selective disclosure
const proof = await proofGen(pk_bytes, signature, header, ph, msg_scalars,
  disclosed_indexes, gens, API_ID_BBS_SHAKE);
console.log(`Proof for selective disclosure of messages ${disclosed_indexes}:`);
console.log(bytesToHex(proof));

const disclosedMsgs = msg_scalars.filter(
  (m, i) => disclosed_indexes.includes(i)); // Only the disclosed messages!
const proofValid = await proofVerify(pk_bytes, proof, header, ph, disclosedMsgs,
  disclosed_indexes, gens);
console.log(`Proof verified: ${proofValid}`);

API

Functions

keyGen(key_material, key_info, key_dst, api_id) ⇒ Uint8Array

Produces an appropriate secret key starting from initial key material. This procedure enhances the entropy of the key material but is deterministic so initial key material must be kept secret.

Kind: global function Returns: Uint8Array - Derived secret key as an array of bytes.

| Param | Type | Description | | --- | --- | --- | | key_material | Uint8Array | Secret key material. Must be >= 32 bytes long. | | key_info | Uint8Array | Optional key information. | | key_dst | string | Key domain separation tag, defaults to 'KEYGEN_DST_'. | | api_id | string | The API id for the signature suite. |

publicFromPrivate(privateBytes) ⇒ Uint8Array

Kind: global function Returns: Uint8Array - Containing encoded public key in G2.

| Param | Type | Description | | --- | --- | --- | | privateBytes | Uint8Array | Private key bytes must have length 32. |

sign(SK, PK, header, messages, generators, api_id)

Creates a BBS signature over a list of "messages".

Kind: global function

| Param | Type | Description | | --- | --- | --- | | SK | bigint | Uint8Array | A scalar or byte array for the secret key. | | PK | Uint8Array | Public key as a compressed G2 point raw bytes. | | header | Uint8Array | Header as a byte array. | | messages | Array | Array of scalars (bigint) derived from actual messages. Computed by messages_to_scalars. | | generators | Array | Array of group G1 generators created by the prepareGenerators function. | | api_id | string | The API id for the signature suite. |

verify(PK, signature, header, messages, generators, api_id) ⇒ boolean

Verify a BBS signature against a public key.

Kind: global function Returns: boolean - - True or False depending on whether the signature is valid.

| Param | Type | Description | | --- | --- | --- | | PK | Uint8Array | Public key as a compressed G2 point raw bytes. | | signature | Uint8Array | A previously computed signature. | | header | Uint8Array | Header used when signature was created. | | messages | Array | Array of scalars (bigint) derived from actual messages. Computed by messages_to_scalars. | | generators | Array | Array of group G1 generators created by the prepareGenerators function. | | api_id | string | The API id for the signature suite. |

proofGen(PK, signature, header, ph, messages, disclosed_indexes, generators, api_id, rand_scalars) ⇒ Uint8Array

Generates an unlinkable, selective disclosure proof based on a signature and message set, and related information.

Kind: global function Returns: Uint8Array - - The proof as an byte array.

| Param | Type | Description | | --- | --- | --- | | PK | Uint8Array | Public key as a compressed G2 point raw bytes. | | signature | Uint8Array | A previously computed signature. | | header | Uint8Array | Header used when signature was created. | | ph | Uint8Array | Presentation header, used during proof creation. | | messages | Array | Array of scalars (bigint) derived from actual messages. Computed by messages_to_scalars. | | disclosed_indexes | Array | Array of sorted (non-repeating) zero based indices of the messages to be disclosed. | | generators | Array | Array of group G1 generators created by the prepareGenerators function. | | api_id | string | The API id for the signature suite. | | rand_scalars | function | A function for generating cryptographically secure random or pseudo random scalars. |

proofVerify(PK, proof, header, ph, disclosed_messages, disclosed_indexes, generators, api_id) ⇒ boolean

Verifies a previously generated proof against original signers public key, and additional information.

Kind: global function Returns: boolean - - True or False depending on whether the proof is valid.

| Param | Type | Description | | --- | --- | --- | | PK | Uint8Array | Public key as a compressed G2 point raw bytes. | | proof | Uint8Array | The proof as a byte array. | | header | Uint8Array | Header used when original signature was created. | | ph | Uint8Array | Presentation header that was used during proof creation. | | disclosed_messages | Array | Array of scalars (bigint) derived from actual disclosed messages. Computed by messages_to_scalars. | | disclosed_indexes | Array | Array of sorted (non-repeating) zero based indices corresponding to the disclosed messages. | | generators | Array | Array of group G1 generators created by the prepareGenerators function. | | api_id | string | The API id for the signature suite. |

numUndisclosed(proofOctets) ⇒ number

Helper function to give the number of undisclosed messages in a proof. This can be added to the number of disclosed messages to calculate the number of generators needed in proof verification.

Kind: global function Returns: number - - The number of undisclosed messages, U.

| Param | Type | Description | | --- | --- | --- | | proofOctets | Uint8Array | Byte array containing the raw bytes of a proof. |

messages_to_scalars(messages, api_id) ⇒ Array

This function converts (hashes) byte array messages into scalars representing the messages for use in signature/proof operations.

Kind: global function Returns: Array - - An array of scalars (bigint) representing the messages.

| Param | Type | Description | | --- | --- | --- | | messages | Array | Messages as an Array of Uint8Arrays, i.e., these byte arrays not strings. | | api_id | string | The API id for the signature suite. |

prepareGenerators(L, api_id) ⇒ Array

Prepares the "group G1 generators" used by the BBS signature suite. These values can be reused in many calls to the sign, verify, proofGen, and proofVerify functions. You must have enough generators for the number of messages. You do not need to know what the "group G1" or a "generator" is! These take a while to compute so we prepare them separately and reuse them.

Kind: global function Returns: Array - - A array of group generators used by the signature/proof suite.

| Param | Type | Description | | --- | --- | --- | | L | number | An integer that indicates the number of generators to be created. This number must be large than the total number of messages in a signature or proof. | | api_id | string | The API id for the signature suite. |