UNPKG

shamir-secret-sharing-bn254

Version:

![build](https://github.com/CluEleSsUK/shamir-secret-sharing-bn254/actions/workflows/build.yml/badge.svg)

134 lines (114 loc) 5.16 kB
import {bn254} from "@kevincharm/noble-bn254-drand" import {randomBytes} from "@noble/hashes/utils" // these are some helper wrappers so you don't end up passing the wrong byte array to the wrong function. // most functions take both variants anyway export type SecretKey = { sk: Uint8Array } export type PublicKey = { pk: Uint8Array } export type PublicKeyShare = { index: bigint, pk: Uint8Array } export type SecretKeyShare = { index: bigint, share: bigint } export function createPrivateKey(): SecretKey { return {sk: bn254.utils.randomPrivateKey()} } export function createPublicKey(secretKey: SecretKey | Uint8Array): PublicKey { const sk = secretKey instanceof Uint8Array ? secretKey : secretKey.sk const fieldElement = bn254.fields.Fr.fromBytes(sk) const pk = bn254.G2.ProjectivePoint.BASE.multiply(fieldElement) return {pk: pk.toRawBytes()} } export function createPublicKeyShare(secretKeyShare: SecretKeyShare): PublicKeyShare { return { index: secretKeyShare.index, pk: bn254.G2.ProjectivePoint.BASE.multiply(secretKeyShare.share).toRawBytes() } } export function sign(secretKey: SecretKey | Uint8Array, message: Uint8Array): Uint8Array { const sk = secretKey instanceof Uint8Array ? secretKey : secretKey.sk return bn254.signShortSignature(message, sk) } export function signPartial(secretKeyShare: SecretKeyShare, message: Uint8Array): Uint8Array { const share = secretKeyShare instanceof Uint8Array ? secretKeyShare : secretKeyShare.share return bn254.signShortSignature(message, share) } export function verify(publicKey: PublicKey | Uint8Array, message: Uint8Array, signature: Uint8Array): boolean { const pk = publicKey instanceof Uint8Array ? publicKey : publicKey.pk return bn254.verifyShortSignature(signature, message, pk) } export function verifyPartial(publicKey: PublicKeyShare | Uint8Array, message: Uint8Array, partialSignature: PartialSignature | Uint8Array): boolean { const sig = partialSignature instanceof Uint8Array ? partialSignature : partialSignature.signature const pk = publicKey instanceof Uint8Array ? publicKey : publicKey.pk return bn254.verifyShortSignature(sig, message, pk) } export type PartialSignature = { index: bigint, signature: Uint8Array } const Fr = bn254.fields.Fr // aggregatePartialSignatures takes an array of partial signatures and creates a final group signature // (presuming there are threshold or more partials!) export function aggregateGroupSignature(partials: Array<PartialSignature>): Uint8Array { const xs = partials.map((entry) => entry.index) let agg = bn254.G1.ProjectivePoint.ZERO for (let i = 0; i < partials.length; i++) { const entry = partials[i] const sig = bn254.G1.ProjectivePoint.fromHex(entry.signature) const term = sig.multiply(lagrangeCoeff0(i, xs)) agg = agg.add(term) } return agg.toRawBytes() } function lagrangeCoeff0(index: number, xs: bigint[]): bigint { const xi = xs[index] if (xi == null) { throw new Error("xi expected a value") } let num = 1n let den = 1n for (let i = 0; i < xs.length; i++) { if (i === index) continue const xj = xs[i] if (xj == null) { throw new Error("xj expected a value") } num = Fr.mul(num, Fr.neg(xj)) // (0 − xj) den = Fr.mul(den, Fr.sub(xi, xj)) // (xi − xj) } return Fr.div(num, den) } // split splits a secret key into `n` shares with threshold `t` export function split(secretKey: SecretKey | Uint8Array, numberOfShares: number, threshold: number): Array<SecretKeyShare> { const sk = secretKey instanceof Uint8Array ? secretKey : secretKey.sk if (threshold > numberOfShares) { throw new Error("threshold can'threshold be lower than node count - you probably have the parameters the wrong way round") } if (threshold < 2) { throw new Error("threshold less than two means everyone can recover the secret") } // sample random polynomial of degree (threshold-1), evaluations[0]=secret const evaluations: bigint[] = [encodeBigint(sk)] for (let i = 1; i < threshold; i++) { evaluations.push(randomFr()) } // evaluate at x = 1..numberOfShares const shares: Array<SecretKeyShare> = [] for (let x = 1n; x <= numberOfShares; x++) { let share = 0n // horner’s method: share = evaluations[0] + evaluations[1]*x + ... + evaluations[threshold-1]*x^(threshold-1) for (let i = evaluations.length - 1; i >= 0; i--) { const rhs = evaluations[i] if (rhs == null) { throw new Error("invalid split") } share = bn254.fields.Fr.add(bn254.fields.Fr.mul(share, x), rhs) } shares.push({index: x, share}) } return shares } function randomFr(): bigint { while (true) { const fr = encodeBigint(randomBytes(bn254.fields.Fr.BYTES)) if (fr < bn254.fields.Fr.ORDER) { return fr } } } function encodeBigint(input: Uint8Array): bigint { return bn254.fields.Fr.fromBytes(input) }