vsslib v1.0.0
vsslib
Interfaces for Verifiable Secret Sharing (VSS) in TS/JS
:warning: This library requires security audit. Use at you own risk for the moment.
Vsslib provides modular building blocks for implementing threshold-cryptographic protocols based on Verifiable Secret Sharing (VSS), e.g., Distributed Key Generation (DKG) schemes.
Quick example
Local setup
Involved parties agree on a common cryptosystem.
import { initBackend } from "vsslib";
// Initiate cryptosystem instance over the ED25519 elliptic curve
const ctx = initBackend("ed25519");
Dealer's side
import { shareSecret, createFeldmanPackets } from "vsslib";
// Generate a Shamir (5, 3)-sharing for some uniformly random secret
const { sharing } = await shareSecret(ctx, 5, 3);
// Generate verifiable packets for the totality of secret shares
const { packets, commitments } = await sharing.createFeldmanPackets();
Broadcast the commitments and send the packets to the respective shareholders in private.
Shareholders' side
import { parseFeldmanPacket, InvalidSecretShare } from "vsslib";
// Extract and verify secret share from received packet
try {
const { share } = await parseFeldmanPacket(ctx, commitments, packet);
// Store retrieved share locally
...
} catch (err) {
if (err instanceof InvalidSecretShare) {
// Follow rejection policy as specified by context
...
} else {
...
}
}
Overview
Vsslib provides modular building blocks for implementing threshold-cryptographic protocols based on Shamir's Secret Sharing (SSS). It focuses on primitives that make the sharing process verifiable on behalf of the involved parties (Feldman and Pedersen VSS schemes) and as such applicable in contexts with zero or low trust assumptions.
Backend
Vsslib is designed to be agnostic with respect to the underlying cryptosystem and to admit pluggable backends. It abstracts away algebraic details by internally interacting with a generic cryptosystem interface, which backend implementations are expected to conform with.
Vsslib comes with several backends based on
noble-curves
,
but any implementation wrapped with the prescribed interface
should do the job. Refer to vsslib/backend
for details.
Security
:warning: This library requires security audit. Use at your own risk for the moment.
See here for details.
Table of contents
Installation
npm install vsslib
Usage
Preliminaries
Vsslib operates over discrete-log based cryptosystems agnostically and you will need to carry an instance of the respective backend in order to interact with the library API.
Cryptosystem setup
Vsslib provides out-of-the box several backends that can be initialized as follows.
import { initBackend } from "vsslib";
const ctx = initBackend("ed25519");
The currently provided backends are ed25519
, ed448
and jubjub
.
Note You can use any custom or other implementation, provided that it conforms to or has been wrapped with the internally prescribed interface. Refer to
vsslib/backend
for details.
Secret generation
Generate a keypair in raw-bytes mode as follows.
import { randomSecret } from "vsslib";
const { secret, publicBytes } = await randomSecret(ctx);
Note
secret
is the little-endian representation of a uniformly random scalar modulo the underlying group order.publicBytes
is the byte representation of the respective group element.
Extraction of public counterpart
import { extractPublic } from "vsslib";
const publicBytes = await extractPublic(ctx, secret);
Warning Throws error if
secret
is not a valid byte representation with respect to the underlying cryptosystem.
Shamir's Secret Sharing (SSS)
Sharing the secret
Generate a (n, t)
-sharing of a given secret as follows.
import { shareSecret } from "vsslib";
const { sharing } = await shareSecret(ctx, n, t, secret);
Warning Throws error if the condition
1 <= t <= n < q
is violated, whereq
stands for the underlying group order, or the providedsecret
is not a valid byte respresentation with respect to the underlying cryptosystem.
If not provided, the secret is created on the fly.
const { secret, sharing } = await shareSecret(ctx, n, t);
Sharing with predefined shares
Generate a (n, t)
-sharing with up to t-1
predefined shares as follows.
const { secret: value1 } = await randomSecret(ctx);
const { secret: value2 } = await randomSecret(ctx);
...
const { sharing } = await shareSecret(ctx, n, t, secret, [
{ index: 1, value: value1 },
{ index: 2, value: value2 },
...
])
Warning Throws error if the predefined shares are not less than
t
, or any of the provided indices in not in the range(0,..., t - 1]
, or any of the provided values is not a valid byte representation with respect to the underlying cryptosystem.
Basic sharing interface
// Access the original secret
const secret = sharing.getOriginalSecret();
// Access all secret shares
const secretShares = await sharing.getSecretShares();
// Access all public shares
const publicShares = await sharing.getPublicShares();
// Access the i-th share
const { secretShare, publicShare } = await sharing.getShare(i);
Access the public counterpart of a secret share as follows.
import { extractPublicShare } from "vsslib";
const publicShare = await extractPublicShare(ctx, secretShare);
Combining operations
Combination of secret shares
Combine any collection of secret shares in the sense of interpolation as follows.
import { combineSecretShares } from "vsslib";
const combinedSecret = await combineSecretShares(ctx, secretShares);
This yields the original secret only if the number of provided shares is at least equal
to the threshold t
.
In order to ensure that the operation completes only if at least t
shares are
provided, make sure to pass the threshold parameter explicitly.
const combinedSecret = await combineSecretShares(ctx, secretShares, t);
Warning Throws error if less than
t
shares are provided.
Combination of public shares
Combine any collection of public shares using interpolation in the exponent as follows.
import { combinePublicShares } from "vsslib";
const combinedPublic = await combinePublicShares(ctx, publicShares);
This yields the public counterpart of the original secret only if
the number of provided shares is at least equal to the threshold t
.
In order to ensure that the operation completes only if at least t
shares are
provided, make sure to pass the threshold parameter explicitly.
const combinedPublic = await combinePublicShares(ctx, publicShares, t);
Warning Throws error if less than
t
shares are provided.
Verifiable Secret Sharing (VSS)
In practice, shareholders need to defend against malicious dealers and verify the consistency of their respective shares, i.e., ensure that they have indeed occured from the same sharing. This is attained by means of additional information attached to the individual shares and used to verify them against some public quantity related to the sharing process. Verifiable Secret Sharing (VSS) schemes extend Shamir's Sharing by including this information to the share packets.
Vsslib provides implementations of the Feldman and Pedersen VSS schemes, which are the most widely used in practice. Verifiable packets are directly extracted from the sharing instance.
Warning Correctly applying VSS when implementing DKG protocols is out of the library's scope. In particular, it is the user's responsibility to handle verification errors appropriately adhering to the prescribed complaint policy and ensure that only non-byzantine parties end up with a secret share.
Feldman scheme
Generation of Feldman commitments and packets
Given a sharing, generate Feldman commitments and verifiable packets for the totality of secret shares as follows.
const { packets, commitments } = await sharing.createFeldmanPackets();
Note
commitments
are intended for broadcast whilepackets
are sent to the respective shareholders in private.
Verification and extraction of secret share
Extract and verify secret share from the received packet as follows.
import { parseFeldmanPacket } from "vsslib";
const { share } = await parseFeldmanPacket(ctx, commitments, packet);
This throws vsslib.InvalidSecretShare
if the included share is found to
be invalid against the provided commitments. You will usually have to handle
this error in order to adhere to some specified rejection policy:
import { InvalidSecretShare } from "vsslib";
try {
const { share } = await parseFeldmanPacket(ctx, commitments, packet);
// Store locally the retrieved secret share
...
} catch (err) {
if (err instanceof InvalidSecretShare) {
// Follow rejection policy as specified by context
...
} else {
...
}
}
Standalone verification of secret share
If already available through different channels, a secret share can be directly verified against the commitments as follows.
import { verifyFeldmanCommitments, InvalidSecretShare } from "vsslib";
try {
await verifyFeldmanCommitments(ctx, share, commitments);
} catch (err) {
if (err instanceof InvalidSecretShare) {
// Follow rejection policy as specified by context
...
} else {
...
}
}
Pedersen scheme
Involved parties agree first on some public reference:
import { randomPublic } from "vsslib";
const publicBytes = await randomPublic(ctx);
Generation of Pedersen commitments and packets
Given a sharing, generate Pedersen commitments and verifiable packets for the totality of secret shares as follows.
const { packets, commitments } = await sharing.createPedersenPackets(publicBytes);
Note
commitments
are intended for broadcast whilepackets
are sent to the respective shareholders in private.
Verification and extraction of secret share
Extract and verify secret share from the received packet as follows.
import { parsePedersenPacket } from "vsslib";
const { share, binding } = await parsePedersenPacket(ctx, commitments, publicBytes, packet);
Note The included secret
binding
is implicitly used during verification and can be discarded.
This throws vsslib.InvalidSecretShare
if the included share is found to
be invalid against the provided commitments. You will usually have to handle
this error in order to adhere to some specified rejection policy:
import { InvalidSecretShare } from "vsslib";
try {
const { share, binding } = await parsePedersenPacket(ctx, commitments, publicBytes, packet);
// Store locally the retrieved secret share
...
} catch (err) {
if (err instanceof InvalidSecretShare) {
// Follow rejection policy as specified by context
...
} else {
...
}
}
Standalone verification of secret share
If already available through different channels, a secret share can be directly verified along with its binding against the commitments as follows.
import { verifyPedersenCommitments, InvalidSecretShare } from "vsslib";
try {
await verifyPedersenCommitments(ctx, share, binding, publicBytes, commitments);
} catch (err) {
if (err instanceof InvalidSecretShare) {
// Follow rejection policy as specified by context
...
} else {
...
}
}
Verifiable public recovery
When reconstructing the public counterpart of a shared secret, the combiner usually needs to verify the aggregated public shares. Specifically, acclaimed shareholders may be expected to prove knowledge of their respective secret shares in a zero-knowledge (ZK) fashion (e.g., for public key certification purposes).
Warning This operation does not verify per se the consistency of the public shares; specifically, it does not ensure that they combine to the public counterpart of a secret that has indeed been distributed by means of Shamir's sharing.
Note Refer to Sec. Combination of public shares for an operation that bypasses verification of public shares.
Generation of packets
Create a verifiable packet for a secret share as follows.
import { createSchnorrPacket } from "vsslib";
const packet = await createSchnorrPacket(ctx, share, { algorithm: "sha256" });
This consists of the public share and a NIZK (Schnorr) proof-of-knowledge of
the secret counterart.
The optional algorithm
parameter specifies the hash function used for proof
generation (defaults to SHA256).
Note Involved shareholders are expected to use the same hash function.
Nonce inclusion
The combiner may need to defend against replay attacks by maintaining some kind of state between itself and individual shareholders. It can do so by storing a nonce per session and shareholder. Upon receiving its respective nonce through some secure channel, the shareholder includes it in packet generation as follows.
const packet = await createSchnorrPacket(ctx, share, { ..., nonce });
Recovery operation
After aggregating the packets, the combiner can recover the group public key as follows.
import { recoverPublic } from "vsslib";
const { recovered } = await recoverPublic(ctx, packets, { algorithm: "sha256", threshold: t });
This verifies the attached Schnorr proofs against the respective public
shares and combines the latter in the sense of interpolation.
The optional algorithm
parameter specifies
the hash function used for the verification of individual
proofs (defaults to SHA256).
The operation throws vsslib.InvalidPublicShare
upon the first proof that fails to verify.
The optional threshold
parameter ensures that the operation completes only if
at least t
packets are provided,
otherwise it throws vsslib.InvalidInput
error.
You will usually have to handle errors in order
to adhere to some specified rejection policy:
import { InvalidPublicShare, InvalidInput } from "vsslib";
try {
const { recovered } = await recoverPublic(ctx, packets, { algorithm: "sha256", threshold: t });
...
} catch (err) {
if (err instanceof InvalidPublicShare) {
// Abort and follow policy as specified by context
...
} else if (err instanceof InvalidInput) {
// Abort and follow policy as specified by context; makes sense only
// if the `threshold` parameter has been provided.
...
} else {
...
}
}
Nonce-based recovery
If certain shareholders are expected to have included a nonce when generating their packets, these must be explicitly passed into the recovery operation so that the respective proofs verify.
try {
const { recovered } = await recoverPublic(ctx, packets, {
...,
nonces: {
1: ...,
2: ...,
...
}
});
...
} catch (err) {
if (err instanceof InvalidPublicShare) {
// Abort and follow policy as specified by context
...
} else {
...
}
}
Recovery with accurate blaming
For security investigation purposes, the combiner may want to trace cheating shareholders. This presupposes that the recovery operation completes irrespective of potential verification failures so that malicious shareholders can be listed in a blame index.
const { recovered, blame } = await recoverPublic(ctx, packets, { ..., errorOnInvalid: false });
if (blame.length > 0) {
// Hold cheating shareholders accountable according to specified policy
...
}
This returns the computation result along with a list blame
,
containing the public shares of cheating shareholders.
Warning Make sure to always check the
blame
index when using theerrorOnInvalid: false
option.
Security
:warning: This library requires security audit. Use at your own risk for the moment.
Input validation
Vsslib's interface operates with the byte representations of scalars and group elements, taking care to always ensure that the involved bytestrings are valid representations with respect to the underlying cryptosystem.
Support for NIZK proofs
Threshold-cryptographic security against malicious shareholders
is usually attained by means of non-interactive zero-knowledge (NIZK) proofs
for contextual statemenets.
Vsslib provides NIZK infrastructure (vsslib/nizk
)
for proving knowledge of generic discrete-log based linear relations
over arbitrary groups and hash functions.
Defence against replay attacks
In practice, plain usage of NIZK proofs is usually susceptible to replay attack. Vsslib allows inclusion of nonces when generating a NIZK proof, capable of maintaining state between the verifier and the involved provers. A nonce can be any bytestring, e.g., cryptographically secure random bytes, unique session identifiers, synchronized counters, or combinations thereof. It is the user's responsibility to ensure that its design is secure in the particular application context.
Development
Examples
npx tsx examples/<file> --help
Tests
$ ./test.sh --help
Lint
$ npm run lint
Build
$ npm run build
Documentation
$ npm run docs
Publish
$ npm run run-publish
9 months ago