shamir-secret-sharing-bn254
Version:

134 lines (114 loc) • 5.16 kB
text/typescript
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)
}