Skip to content

Commit e31f33a

Browse files
authored
added support for new g1-rfc9380 scheme (#68)
* added support for new g1-rfc9380 scheme * bumped package.json version * fixed linter
1 parent 1d6f1b6 commit e31f33a

File tree

5 files changed

+63
-11
lines changed

5 files changed

+63
-11
lines changed

lib/beacon-verification.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
RandomnessBeacon,
1111
G2UnchainedBeacon,
1212
isG1G2SwappedBeacon,
13-
G1UnchainedBeacon
13+
G1UnchainedBeacon,
14+
isG1Rfc9380
1415
} from './index'
1516

1617
async function verifyBeacon(chainInfo: ChainInfo, beacon: RandomnessBeacon): Promise<boolean> {
@@ -32,6 +33,10 @@ async function verifyBeacon(chainInfo: ChainInfo, beacon: RandomnessBeacon): Pro
3233
return verifySigOnG1(beacon.signature, await unchainedBeaconMessage(beacon), publicKey)
3334
}
3435

36+
if (isG1Rfc9380(beacon, chainInfo)) {
37+
return verifySigOnG1(beacon.signature, await unchainedBeaconMessage(beacon), publicKey, 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_')
38+
}
39+
3540
console.error(`Beacon type ${chainInfo.schemeID} was not supported`)
3641
return false
3742

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

52-
async function normP1Hash(point: G1Hex): Promise<PointG1> {
53-
return point instanceof PointG1 ? point : PointG1.hashToCurve(point);
57+
async function normP1Hash(point: G1Hex, domainSeparationTag: string): Promise<PointG1> {
58+
return point instanceof PointG1 ? point : PointG1.hashToCurve(point, {DST: domainSeparationTag});
5459
}
5560

56-
export async function verifySigOnG1(signature: G1Hex, message: G1Hex, publicKey: G2Hex): Promise<boolean> {
61+
export async function verifySigOnG1(
62+
signature: G1Hex,
63+
message: G1Hex,
64+
publicKey: G2Hex,
65+
// default DST is the invalid one used for 'bls-unchained-on-g1' for backwards compat
66+
domainSeparationTag= 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_'
67+
): Promise<boolean> {
5768
const P = normP2(publicKey);
58-
const Hm = await normP1Hash(message);
69+
const Hm = await normP1Hash(message, domainSeparationTag);
5970
const G = PointG2.BASE;
6071
const S = normP1(signature);
6172
const ePHm = pairing(Hm, P.negate(), false);

lib/index.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export type ChainInfo = {
144144
}
145145

146146
// currently drand supports chained and unchained randomness - read more here: https://drand.love/docs/cryptography/#randomness
147-
export type RandomnessBeacon = G2ChainedBeacon | G2UnchainedBeacon | G1UnchainedBeacon
147+
export type RandomnessBeacon = G2ChainedBeacon | G2UnchainedBeacon | G1UnchainedBeacon | G1RFC9380Beacon
148148

149149
export type G2ChainedBeacon = {
150150
round: number
@@ -157,18 +157,26 @@ export type G2UnchainedBeacon = {
157157
round: number
158158
randomness: string
159159
signature: string
160-
// this is needed to distinguish it from the `G1UnchainedBeacon` so the type guard works correctly
160+
// this is needed to distinguish it from the other unchained beacons so the type guard works correctly
161161
_phantomg2?: never
162162
}
163163

164164
export type G1UnchainedBeacon = {
165165
round: number
166166
randomness: string
167167
signature: string
168-
// this distinguishes it from the `G2UnchainedBeacon` so the type guard works correctly
168+
// this distinguishes it from the other unchained beacons so the type guard works correctly
169169
_phantomg1?: never
170170
}
171171

172+
export type G1RFC9380Beacon = {
173+
round: number
174+
randomness: string
175+
signature: string
176+
// this distinguishes it from the other unchained beacons so the type guard works correctly
177+
_phantomg19380?: never
178+
}
179+
172180
// eslint-disable-next-line @typescript-eslint/no-explicit-any
173181
export function isChainedBeacon(value: any, info: ChainInfo): value is G2ChainedBeacon {
174182
return info.schemeID === 'pedersen-bls-chained' &&
@@ -197,5 +205,14 @@ export function isG1G2SwappedBeacon(value: any, info: ChainInfo): value is G1Unc
197205
value.round > 0
198206
}
199207

208+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
209+
export function isG1Rfc9380(value: any, info: ChainInfo): value is G1RFC9380Beacon {
210+
return info.schemeID === 'bls-unchained-g1-rfc9380' &&
211+
!!value.randomness &&
212+
!!value.signature &&
213+
value.previous_signature === undefined &&
214+
value.round > 0
215+
}
216+
200217
// exports some default implementations of the above interfaces and other utility functions that could be used with them
201218
export {HttpChain, HttpChainClient, HttpCachingChain, MultiBeaconNode, FastestNodeClient, roundAt, roundTime}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "drand-client",
3-
"version": "1.1.1",
3+
"version": "1.2.0",
44
"description": "A client to the drand randomness beacon network.",
55
"main": "index.js",
66
"types": "index.d.ts",

test/beacon-verification.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,29 @@ describe('verifyBeacon', () => {
177177
await expect(verifyBeacon(chainInfo, invalidBeacon)).resolves.toEqual(false)
178178
})
179179
})
180+
describe('signatures on G1 with DST', () => {
181+
const validBeacon = {
182+
round: 38,
183+
randomness: 'b2fc21325a24904a6a9e81a6c63f65f6cec3f0b2400df3f4a56b214770e9ccca',
184+
signature: '95c93585c513ebbcb4777ff15599b3140e5ec0295faa0e483f3deadd88fa6d43f0d3703e3a4ce106e8fd6c6987f32126'
185+
}
186+
187+
const chainInfo = {
188+
public_key: '81d320f220ee9c79e60e19dedc838c31e3ab919b15481e9feb52f643628c4f6a13fdc52129493875a818109d767272ca0541cbcdcea9335f2870d781b39b845ba8cbd44fdfe4967781cf72ca5917fc9398bcf97ca0548ed5a709016c4b1ff0f3',
189+
period: 3,
190+
genesis_time: 1687506816,
191+
hash: 'af8b6fc95693b058a3a59efe586eb31c2c352fe00cf40c62a427d87c34f7a235',
192+
groupHash: '02d678e93908888871b70e3f015b396a8c082bc1493ae5479f8743cb5d972b54',
193+
schemeID: 'bls-unchained-g1-rfc9380',
194+
metadata: {'beaconID': 'walkthrough'}
195+
}
196+
197+
it('should verify a valid signature', async () => {
198+
await expect(verifyBeacon(chainInfo, validBeacon)).resolves.toEqual(true)
199+
})
200+
it('should not verify a signature on G1 for the wrong round', async () => {
201+
const invalidBeacon = {...validBeacon, round: 55}
202+
await expect(verifyBeacon(chainInfo, invalidBeacon)).resolves.toEqual(false)
203+
})
204+
})
180205
})

test/index.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@ async function endToEndTest(url: string) {
2323
const chains = await node.chains()
2424
expect(chains).not.toHaveLength(0)
2525

26-
// currently the drand-client does not support the new G1/G2 swapped scheme
2726
let chainToUse
2827
for (const chain of chains) {
2928
const info = await chain.info()
30-
if (info.schemeID === 'pedersen-bls-chained' || info.schemeID === 'pedersen-bls-unchained') {
29+
if (info.schemeID === 'pedersen-bls-chained' || info.schemeID === 'pedersen-bls-unchained' || info.schemeID === 'bls-unchained-on-g1' || info.schemeID === 'bls-unchained-g1-rfc9380') {
3130
chainToUse = chain
3231
break
3332
}

0 commit comments

Comments
 (0)