diff --git a/lib/beacon-verification.ts b/lib/beacon-verification.ts index d4222a5..d3a4f8b 100644 --- a/lib/beacon-verification.ts +++ b/lib/beacon-verification.ts @@ -1,5 +1,8 @@ import { bls12_381 as bls } from '@noble/curves/bls12-381' +import { bn254 } from '@kevincharm/noble-bn254-drand' +import type { CHash } from '@noble/curves/abstract/utils' import { sha256 } from '@noble/hashes/sha256' +import { keccak_256 } from '@noble/hashes/sha3' import { ensureBytes } from '@noble/curves/abstract/utils' import {Buffer} from 'buffer' import { @@ -11,7 +14,8 @@ import { G2UnchainedBeacon, isG1G2SwappedBeacon, G1UnchainedBeacon, - isG1Rfc9380 + isG1Rfc9380, + isBn254OnG1 } from './index' type PointG1 = typeof bls.G1.ProjectivePoint.ZERO @@ -46,6 +50,12 @@ async function verifyBeacon(chainInfo: ChainInfo, beacon: RandomnessBeacon, expe return verifySigOnG1(beacon.signature, await unchainedBeaconMessage(beacon), publicKey, 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_') } + if (isBn254OnG1(beacon, chainInfo)) { + return bn254.verifyShortSignature(beacon.signature, await unchainedBeaconMessage(beacon, keccak_256), publicKey, { + DST: 'BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_' + }) + } + console.error(`Beacon type ${chainInfo.schemeID} was not supported or the beacon was not of the purported type`) return false @@ -93,8 +103,8 @@ async function chainedBeaconMessage(beacon: G2ChainedBeacon): Promise { - return sha256(roundBuffer(beacon.round)) +async function unchainedBeaconMessage(beacon: G2UnchainedBeacon | G1UnchainedBeacon, hashFn: CHash = sha256): Promise { + return hashFn(roundBuffer(beacon.round)) } function signatureBuffer(sig: string) { diff --git a/lib/index.ts b/lib/index.ts index faeae63..8c33ee5 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -144,7 +144,7 @@ export type ChainInfo = { } // currently drand supports chained and unchained randomness - read more here: https://drand.love/docs/cryptography/#randomness -export type RandomnessBeacon = G2ChainedBeacon | G2UnchainedBeacon | G1UnchainedBeacon | G1RFC9380Beacon +export type RandomnessBeacon = G2ChainedBeacon | G2UnchainedBeacon | G1UnchainedBeacon | G1RFC9380Beacon | Bn254OnG1Beacon export type G2ChainedBeacon = { round: number @@ -177,6 +177,14 @@ export type G1RFC9380Beacon = { _phantomg19380?: never } +export type Bn254OnG1Beacon = { + round: number + randomness: string + signature: string + // this distinguishes it from the other unchained beacons so the type guard works correctly + _phantombn254ong1?: never +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any export function isChainedBeacon(value: any, info: ChainInfo): value is G2ChainedBeacon { return info.schemeID === 'pedersen-bls-chained' && @@ -214,6 +222,15 @@ export function isG1Rfc9380(value: any, info: ChainInfo): value is G1RFC9380Beac value.round > 0 } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isBn254OnG1(value: any, info: ChainInfo): value is Bn254OnG1Beacon { + return info.schemeID === 'bls-bn254-unchained-on-g1' && + !!value.randomness && + !!value.signature && + value.previous_signature === undefined && + value.round > 0 +} + // exports some default implementations of the above interfaces and other utility functions that could be used with them export { HttpChain, diff --git a/package-lock.json b/package-lock.json index 7256df7..1ee75bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "drand-client", - "version": "1.2.3", + "version": "1.2.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "drand-client", - "version": "1.2.3", + "version": "1.2.6", "license": "(Apache-2.0 OR MIT)", "dependencies": { "@babel/traverse": "^7.23.2", - "@noble/curves": "^1.4.0", + "@kevincharm/noble-bn254-drand": "^0.0.1", + "@noble/curves": "^1.6.0", "buffer": "^6.0.3" }, "devDependencies": { @@ -1228,23 +1229,37 @@ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, + "node_modules/@kevincharm/noble-bn254-drand": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@kevincharm/noble-bn254-drand/-/noble-bn254-drand-0.0.1.tgz", + "integrity": "sha512-HU5Gmst+nxYx8OYKMWuaJZyXh+9/oqJL/L9F3B4My4/IOZZBqIkZIfHQMuPN6KoFHBni08YOw0sRQqEyHVpnGg==", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@noble/curves": "^1.6.0" + } + }, "node_modules/@noble/curves": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", - "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", "dependencies": { - "@noble/hashes": "1.4.0" + "@noble/hashes": "1.5.0" + }, + "engines": { + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", "engines": { - "node": ">= 16" + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" diff --git a/package.json b/package.json index 3eeffe2..d9a62da 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ }, "dependencies": { "@babel/traverse": "^7.23.2", - "@noble/curves": "^1.4.0", + "@kevincharm/noble-bn254-drand": "^0.0.1", + "@noble/curves": "^1.6.0", "buffer": "^6.0.3" }, "repository": { diff --git a/test/beacon-verification.test.ts b/test/beacon-verification.test.ts index be26fce..3358466 100644 --- a/test/beacon-verification.test.ts +++ b/test/beacon-verification.test.ts @@ -207,4 +207,29 @@ describe('verifyBeacon', () => { await expect(verifyBeacon(chainInfo, validBeacon, 2)).resolves.toEqual(false) }) }) + + describe('signatures on BN254 G1', () => { + // TODO: Replace data with evmnet when it's live + const validBeacon = { + round: 10, + signature: + '0fd919ec1490bf01935f348837bd20f49f539c5adfd60ce29648fe16f8dbed8803bf3e8fb4c3cbce5dcb642333d81260a811cc432b2f7a687b9b7c2305ad01ee', + randomness: 'a62a0e81920b40fb361f9106efb34199af1fbdb0c53db51f7acbad73e0d9d0bf', + } + + const chainInfo = { + public_key: + '14018a8127d0cd185f0d0cd168bec330c584581d46f77f41e8ee33ad600588e104961f20efbcec8ce5e176aa182bf141f7059cfb467e3b508bc6f36f227d5cf31c7baa2a010677e813ffca24b4ff5faf3348b2750201c85b126a480118b149f514d3e7aba46a99f40aac6507a2c6d6f3c7a6b38038786831e3e6b5b0adf5894b', + period: 3, + genesis_time: 1726958055, + hash: 'f50064cd5f71a2e2de647940ac7fa2fd9f6128129ada163e971625ec9f082db2', + groupHash: '3a7cd8aa1864ff0cf8837e1bf77dad871aaaa8f046041ccecce74ab4e029f30c', + schemeID: 'bls-bn254-unchained-on-g1', + metadata: { beaconID: 'default' }, + } + + it('should verify a valid signature', async () => { + await expect(verifyBeacon(chainInfo, validBeacon, validBeacon.round)).resolves.toEqual(true) + }) + }) })