UNPKG

@bug-fix/drand-client

Version:

A client to the drand randomness beacon network.

116 lines (93 loc) 3.99 kB
import { bls12_381 as bls } from '@noble/curves/bls12-381' import { sha256 } from '@noble/hashes/sha256' import { ensureBytes } from '@noble/curves/abstract/utils' import {Buffer} from 'buffer' import { G2ChainedBeacon, ChainInfo, isChainedBeacon, isUnchainedBeacon, RandomnessBeacon, G2UnchainedBeacon, isG1G2SwappedBeacon, G1UnchainedBeacon, isG1Rfc9380 } from './index' type PointG1 = typeof bls.G1.ProjectivePoint.ZERO type PointG2 = typeof bls.G2.ProjectivePoint.ZERO async function verifyBeacon(chainInfo: ChainInfo, beacon: RandomnessBeacon, expectedRound: number): Promise<boolean> { const publicKey = chainInfo.public_key if (beacon.round !== expectedRound) { console.error('round was not the expected round') return false } if (!await randomnessIsValid(beacon)) { console.error('randomness did not match the signature') return false } if (isChainedBeacon(beacon, chainInfo)) { return bls.verify(beacon.signature, await chainedBeaconMessage(beacon), publicKey) } if (isUnchainedBeacon(beacon, chainInfo)) { return bls.verify(beacon.signature, await unchainedBeaconMessage(beacon), publicKey) } if (isG1G2SwappedBeacon(beacon, chainInfo)) { return verifySigOnG1(beacon.signature, await unchainedBeaconMessage(beacon), publicKey) } if (isG1Rfc9380(beacon, chainInfo)) { return verifySigOnG1(beacon.signature, await unchainedBeaconMessage(beacon), publicKey, 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_') } console.error(`Beacon type ${chainInfo.schemeID} was not supported or the beacon was not of the purported type`) return false } // @noble/curves/bls12-381 has not yet implemented public keys on G2, so we've implemented a manual verification for beacons on G1 type G1Hex = Uint8Array | string | PointG1; type G2Hex = Uint8Array | string | PointG2; function normP1(point: G1Hex): PointG1 { return point instanceof bls.G1.ProjectivePoint ? point : bls.G1.ProjectivePoint.fromHex(point); } function normP2(point: G2Hex): PointG2 { return point instanceof bls.G2.ProjectivePoint ? point : bls.G2.ProjectivePoint.fromHex(point); } function normP1Hash(point: G1Hex, domainSeparationTag: string): PointG1 { return point instanceof bls.G1.ProjectivePoint ? point : (bls.G1.hashToCurve(ensureBytes('point', point), {DST: domainSeparationTag}) as PointG1); } export async function verifySigOnG1( signature: G1Hex, message: G1Hex, publicKey: G2Hex, // default DST is the invalid one used for 'bls-unchained-on-g1' for backwards compat domainSeparationTag= 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_' ): Promise<boolean> { const P = normP2(publicKey); const Hm = normP1Hash(message, domainSeparationTag); const G = bls.G2.ProjectivePoint.BASE; const S = normP1(signature); const ePHm = bls.pairing(Hm, P.negate(), true); const eGS = bls.pairing(S, G, true); const exp = bls.fields.Fp12.mul(eGS, ePHm); return bls.fields.Fp12.eql(exp, bls.fields.Fp12.ONE); } async function chainedBeaconMessage(beacon: G2ChainedBeacon): Promise<Uint8Array> { const message = Buffer.concat([ signatureBuffer(beacon.previous_signature), roundBuffer(beacon.round) ]) return sha256(message) } async function unchainedBeaconMessage(beacon: G2UnchainedBeacon | G1UnchainedBeacon): Promise<Uint8Array> { return sha256(roundBuffer(beacon.round)) } function signatureBuffer(sig: string) { return Buffer.from(sig, 'hex') } function roundBuffer(round: number) { const buffer = Buffer.alloc(8) buffer.writeBigUInt64BE(BigInt(round)) return buffer } async function randomnessIsValid(beacon: RandomnessBeacon): Promise<boolean> { const expectedRandomness = sha256(Buffer.from(beacon.signature, 'hex')) return Buffer.from(beacon.randomness, 'hex').compare(expectedRandomness) == 0 } export {verifyBeacon, roundBuffer}