Skip to content

Commit

Permalink
added support for new g1-rfc9380 scheme (#68)
Browse files Browse the repository at this point in the history
* added support for new g1-rfc9380 scheme

* bumped package.json version

* fixed linter
  • Loading branch information
CluEleSsUK authored Jul 3, 2023
1 parent 1d6f1b6 commit e31f33a
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 11 deletions.
21 changes: 16 additions & 5 deletions lib/beacon-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
RandomnessBeacon,
G2UnchainedBeacon,
isG1G2SwappedBeacon,
G1UnchainedBeacon
G1UnchainedBeacon,
isG1Rfc9380
} from './index'

async function verifyBeacon(chainInfo: ChainInfo, beacon: RandomnessBeacon): Promise<boolean> {
Expand All @@ -32,6 +33,10 @@ async function verifyBeacon(chainInfo: ChainInfo, beacon: RandomnessBeacon): Pro
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`)
return false

Expand All @@ -49,13 +54,19 @@ function normP2(point: G2Hex): PointG2 {
return point instanceof PointG2 ? point : PointG2.fromHex(point);
}

async function normP1Hash(point: G1Hex): Promise<PointG1> {
return point instanceof PointG1 ? point : PointG1.hashToCurve(point);
async function normP1Hash(point: G1Hex, domainSeparationTag: string): Promise<PointG1> {
return point instanceof PointG1 ? point : PointG1.hashToCurve(point, {DST: domainSeparationTag});
}

export async function verifySigOnG1(signature: G1Hex, message: G1Hex, publicKey: G2Hex): Promise<boolean> {
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 = await normP1Hash(message);
const Hm = await normP1Hash(message, domainSeparationTag);
const G = PointG2.BASE;
const S = normP1(signature);
const ePHm = pairing(Hm, P.negate(), false);
Expand Down
23 changes: 20 additions & 3 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
export type RandomnessBeacon = G2ChainedBeacon | G2UnchainedBeacon | G1UnchainedBeacon | G1RFC9380Beacon

export type G2ChainedBeacon = {
round: number
Expand All @@ -157,18 +157,26 @@ export type G2UnchainedBeacon = {
round: number
randomness: string
signature: string
// this is needed to distinguish it from the `G1UnchainedBeacon` so the type guard works correctly
// this is needed to distinguish it from the other unchained beacons so the type guard works correctly
_phantomg2?: never
}

export type G1UnchainedBeacon = {
round: number
randomness: string
signature: string
// this distinguishes it from the `G2UnchainedBeacon` so the type guard works correctly
// this distinguishes it from the other unchained beacons so the type guard works correctly
_phantomg1?: never
}

export type G1RFC9380Beacon = {
round: number
randomness: string
signature: string
// this distinguishes it from the other unchained beacons so the type guard works correctly
_phantomg19380?: 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' &&
Expand Down Expand Up @@ -197,5 +205,14 @@ export function isG1G2SwappedBeacon(value: any, info: ChainInfo): value is G1Unc
value.round > 0
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isG1Rfc9380(value: any, info: ChainInfo): value is G1RFC9380Beacon {
return info.schemeID === 'bls-unchained-g1-rfc9380' &&
!!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, HttpChainClient, HttpCachingChain, MultiBeaconNode, FastestNodeClient, roundAt, roundTime}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "drand-client",
"version": "1.1.1",
"version": "1.2.0",
"description": "A client to the drand randomness beacon network.",
"main": "index.js",
"types": "index.d.ts",
Expand Down
25 changes: 25 additions & 0 deletions test/beacon-verification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,29 @@ describe('verifyBeacon', () => {
await expect(verifyBeacon(chainInfo, invalidBeacon)).resolves.toEqual(false)
})
})
describe('signatures on G1 with DST', () => {
const validBeacon = {
round: 38,
randomness: 'b2fc21325a24904a6a9e81a6c63f65f6cec3f0b2400df3f4a56b214770e9ccca',
signature: '95c93585c513ebbcb4777ff15599b3140e5ec0295faa0e483f3deadd88fa6d43f0d3703e3a4ce106e8fd6c6987f32126'
}

const chainInfo = {
public_key: '81d320f220ee9c79e60e19dedc838c31e3ab919b15481e9feb52f643628c4f6a13fdc52129493875a818109d767272ca0541cbcdcea9335f2870d781b39b845ba8cbd44fdfe4967781cf72ca5917fc9398bcf97ca0548ed5a709016c4b1ff0f3',
period: 3,
genesis_time: 1687506816,
hash: 'af8b6fc95693b058a3a59efe586eb31c2c352fe00cf40c62a427d87c34f7a235',
groupHash: '02d678e93908888871b70e3f015b396a8c082bc1493ae5479f8743cb5d972b54',
schemeID: 'bls-unchained-g1-rfc9380',
metadata: {'beaconID': 'walkthrough'}
}

it('should verify a valid signature', async () => {
await expect(verifyBeacon(chainInfo, validBeacon)).resolves.toEqual(true)
})
it('should not verify a signature on G1 for the wrong round', async () => {
const invalidBeacon = {...validBeacon, round: 55}
await expect(verifyBeacon(chainInfo, invalidBeacon)).resolves.toEqual(false)
})
})
})
3 changes: 1 addition & 2 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ async function endToEndTest(url: string) {
const chains = await node.chains()
expect(chains).not.toHaveLength(0)

// currently the drand-client does not support the new G1/G2 swapped scheme
let chainToUse
for (const chain of chains) {
const info = await chain.info()
if (info.schemeID === 'pedersen-bls-chained' || info.schemeID === 'pedersen-bls-unchained') {
if (info.schemeID === 'pedersen-bls-chained' || info.schemeID === 'pedersen-bls-unchained' || info.schemeID === 'bls-unchained-on-g1' || info.schemeID === 'bls-unchained-g1-rfc9380') {
chainToUse = chain
break
}
Expand Down

0 comments on commit e31f33a

Please sign in to comment.