@bug-fix/drand-client
Version:
A client to the drand randomness beacon network.
116 lines (93 loc) • 3.99 kB
text/typescript
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}