Interfaces for Verifiable Secret Sharing (VSS) in TS/JS
Vsslib provides modular building blocks for implementing threshold-cryptographic protocols based on Verifiable Secret Sharing (VSS), e.g., Distributed Key Generation (DKG) schemes.
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 {
...
}
}
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.
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.
See here for details.
npm install vsslib
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.
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.
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.
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.
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);
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.
// 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);
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.
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.
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.
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.
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 {
...
}
}
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 {
...
}
}
Involved parties agree first on some public reference:
import { randomPublic } from "vsslib";
const publicBytes = await randomPublic(ctx);
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.
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 {
...
}
}
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 {
...
}
}
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.
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.
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 });
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 {
...
}
}
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 {
...
}
}
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.
Constant-time operations have been applied where possible. Strict resistance against side-channel-attacks is improssible to attain, since JS is a garbage-collected and just-in-time compiled language. This moreover depends crucially on the cryptographic backend implementation.
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.
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.
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.
npx tsx examples/<file> --help
$ ./test.sh --help
$ npm run lint
$ npm run build
$ npm run docs
$ npm run run-publish