Skip to content

Commit

Permalink
feat(bls): validate proof of possession (#815)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhrubabasu authored Feb 20, 2024
1 parent 0e81005 commit 273b8ca
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 22 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"prepare": "husky install"
},
"dependencies": {
"@noble/curves": "1.3.0",
"@noble/hashes": "1.3.3",
"@noble/secp256k1": "2.0.0",
"@scure/base": "1.1.5",
Expand All @@ -48,8 +49,8 @@
"@semantic-release/git": "10.0.1",
"@semantic-release/github": "9.2.6",
"@semantic-release/npm": "11.0.2",
"@types/node": "20.11.10",
"@types/jest": "29.5.11",
"@types/node": "20.11.10",
"@typescript-eslint/eslint-plugin": "6.18.1",
"@typescript-eslint/parser": "6.18.1",
"commitizen": "4.3.0",
Expand Down
2 changes: 0 additions & 2 deletions src/constants/bls.ts

This file was deleted.

47 changes: 47 additions & 0 deletions src/crypto/bls.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { stringToBytes } from '@scure/base';
import { hexToBuffer } from '../utils/buffer';
import * as bls from './bls';

const msg = stringToBytes('utf8', 'test');
const skStr =
'233428aaadf8a5d11ebba263d97b85a286750540f4abd04f109321e07b746277';
const pkStr =
'adf6062df01fc18456140f7126567a84834d85b2af70454a7aacad932b92d0d7d0dab897d2f9bf46021511969f5b62f8';
const popStr =
'98e8d8e33a51ecdcbcca2166370d99fdc02134e8e84ca34327cd2ec4412eb3b39619050a0146cba5d5948cb43c32a7f00f5df841700e3937d58b64e6f74493891b2a70402111841f69e9fc73236beb79f2e63e9a7caa55b724c61a139969ff57';
const sigStr =
'9254acb2bfe4638daef4424b07f7a03987245c8945e634a7fca3302a2bb45e0aa9d2f8f5198e37d41aa65f8ab81efa4608d23ab55ccf06122f9718b37d42e0274297966191e3de2852f3a328727fe0dcced453c943405205b0f23038b7409e66';

describe('bls', () => {
it('serializes correctly', async () => {
const sk = bls.secretKeyFromBytes(skStr);
expect(bls.secretKeyToBytes(sk)).toEqual(hexToBuffer(skStr));

const pk = bls.publicKeyFromBytes(pkStr);
expect(bls.publicKeyToBytes(pk)).toEqual(hexToBuffer(pkStr));

const pk2 = bls.publicKeyFromBytes(hexToBuffer(pkStr));
expect(bls.publicKeyToBytes(pk2)).toEqual(hexToBuffer(pkStr));

const pop = bls.signatureFromBytes(hexToBuffer(popStr));
expect(bls.signatureToBytes(pop)).toEqual(hexToBuffer(popStr));

const sig = bls.signatureFromBytes(hexToBuffer(sigStr));
expect(bls.signatureToBytes(sig)).toEqual(hexToBuffer(sigStr));
});

it('verifies signature correctly', async () => {
const pk = bls.publicKeyFromBytes(pkStr);
const sig = bls.signatureFromBytes(hexToBuffer(sigStr));

expect(bls.verify(pk, sig, msg)).toEqual(true);
});

it('verifies proof of possession correctly', async () => {
const pk = bls.publicKeyFromBytes(pkStr);
const pop = bls.signatureFromBytes(hexToBuffer(popStr));
const pkBytes = bls.publicKeyToBytes(pk);

expect(bls.verifyProofOfPossession(pk, pop, pkBytes)).toEqual(true);
});
});
58 changes: 58 additions & 0 deletions src/crypto/bls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { bls12_381 } from '@noble/curves/bls12-381';
import type { ProjPointType } from '@noble/curves/abstract/weierstrass';
import { hexToBuffer } from '../utils/buffer';

export type PublicKey = ProjPointType<bigint>;
export type SecretKey = bigint;
export type Signature = ProjPointType<typeof bls12_381.fields.Fp2.ZERO>;
export type Message = ProjPointType<typeof bls12_381.fields.Fp2.ZERO>;

export const PUBLIC_KEY_LENGTH = 48;
export const SIGNATURE_LENGTH = 96;

const signatureDST = 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_';
const proofOfPossessionDST = 'BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_';

export function secretKeyFromBytes(skBytes: Uint8Array | string): SecretKey {
return bls12_381.G1.normPrivateKeyToScalar(skBytes);
}

export function secretKeyToBytes(sk: SecretKey): Uint8Array {
return hexToBuffer(sk.toString(16));
}

export function publicKeyFromBytes(pkBytes: Uint8Array | string): PublicKey {
return bls12_381.G1.ProjectivePoint.fromHex(pkBytes);
}

export function publicKeyToBytes(pk: PublicKey): Uint8Array {
return pk.toRawBytes();
}

export function signatureFromBytes(sigBytes: Uint8Array): Signature {
return bls12_381.Signature.fromHex(sigBytes);
}

export function signatureToBytes(sig: Signature): Uint8Array {
return sig.toRawBytes();
}

export function verify(
pk: PublicKey,
sig: Signature,
msg: Uint8Array | string | Message,
): boolean {
return bls12_381.verify(sig, msg, pk, {
DST: signatureDST,
});
}

export function verifyProofOfPossession(
pk: PublicKey,
sig: Signature,
msg: Uint8Array | string | Message,
): boolean {
return bls12_381.verify(sig, msg, pk, {
DST: proofOfPossessionDST,
});
}
1 change: 1 addition & 0 deletions src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * as secp256k1 from './secp256k1';
export * as bls from './bls';
20 changes: 11 additions & 9 deletions src/serializable/pvm/proofOfPossession.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ProofOfPossession } from './proofOfPossession';

const pubkey = new Uint8Array([
const publicKey = new Uint8Array([
0x85, 0x02, 0x5b, 0xca, 0x6a, 0x30, 0x2d, 0xc6, 0x13, 0x38, 0xff, 0x49, 0xc8,
0xba, 0xa5, 0x72, 0xde, 0xd3, 0xe8, 0x6f, 0x37, 0x59, 0x30, 0x4c, 0x7f, 0x61,
0x8a, 0x2a, 0x25, 0x93, 0xc1, 0x87, 0xe0, 0x80, 0xa3, 0xcf, 0xde, 0xc9, 0x50,
Expand All @@ -20,27 +20,29 @@ const signature = new Uint8Array([

describe('proofOfPossession', function () {
it('can init', () => {
const proof = new ProofOfPossession(pubkey, signature);
expect(proof instanceof ProofOfPossession).toBe(true);
const pop = new ProofOfPossession(publicKey, signature);
expect(pop instanceof ProofOfPossession).toBe(true);
});

it('throws for invalid pubkey', () => {
expect(() => {
const invalidPub = pubkey.slice(0, pubkey.length - 2);
new ProofOfPossession(invalidPub, signature);
const popBytes = new Uint8Array([...publicKey, ...signature]);
popBytes[2] = 0x00;
ProofOfPossession.fromBytes(popBytes);
}).toThrow();
});

it('throws for invalid signature', () => {
expect(() => {
const invalidSig = signature.slice(0, signature.length - 2);
new ProofOfPossession(pubkey, invalidSig);
const popBytes = new Uint8Array([...publicKey, ...signature]);
popBytes[64] = 0x00;
ProofOfPossession.fromBytes(popBytes);
}).toThrow();
});

it('can call toString', () => {
const proof = new ProofOfPossession(pubkey, signature);
const pop = new ProofOfPossession(publicKey, signature);
const expected = `0x85025bca6a302dc61338ff49c8baa572ded3e86f3759304c7f618a2a2593c187e080a3cfdec95040309ad1f1589530678b1d6133d17e3483220ad960b6fde11e4e1214a8ce21ef616227e5d5eef070d7500e6f7d4452c5a760620cc06795cbe218e072eba76d94788d9d01176ce4ecadfb96b47f942281894ddfadd1c1743f7f549f1d07d59d55655927f72bc6bf7c12`;
expect(proof.toString()).toEqual(expected);
expect(pop.toString()).toEqual(expected);
});
});
23 changes: 14 additions & 9 deletions src/serializable/pvm/proofOfPossession.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { serializable } from '../common/types';
import { bufferToHex, concatBytes } from '../../utils/buffer';
import { BLS_PUBKEY_LENGTH, BLS_SIGNATURE_LENGTH } from '../../constants/bls';
import { bls } from '../../crypto';
import { TypeSymbols } from '../constants';

/**
Expand All @@ -14,19 +14,24 @@ export class ProofOfPossession {
public readonly publicKey: Uint8Array,
public readonly signature: Uint8Array,
) {
if (publicKey.length !== BLS_PUBKEY_LENGTH)
throw new Error(`public key must be ${BLS_PUBKEY_LENGTH} bytes`);
if (signature.length !== BLS_SIGNATURE_LENGTH)
throw new Error(`signature must be ${BLS_SIGNATURE_LENGTH} bytes`);
const pk = bls.publicKeyFromBytes(publicKey);
const sig = bls.signatureFromBytes(signature);

pk.assertValidity();
sig.assertValidity();

if (!bls.verifyProofOfPossession(pk, sig, bls.publicKeyToBytes(pk))) {
throw new Error(`Invalid proof of possession`);
}
}

static fromBytes(bytes: Uint8Array): [ProofOfPossession, Uint8Array] {
const pubkey = bytes.slice(0, BLS_PUBKEY_LENGTH);
const pubkey = bytes.slice(0, bls.PUBLIC_KEY_LENGTH);
const signature = bytes.slice(
BLS_PUBKEY_LENGTH,
BLS_PUBKEY_LENGTH + BLS_SIGNATURE_LENGTH,
bls.PUBLIC_KEY_LENGTH,
bls.PUBLIC_KEY_LENGTH + bls.SIGNATURE_LENGTH,
);
const rest = bytes.slice(BLS_PUBKEY_LENGTH + BLS_SIGNATURE_LENGTH);
const rest = bytes.slice(bls.PUBLIC_KEY_LENGTH + bls.SIGNATURE_LENGTH);
return [new ProofOfPossession(pubkey, signature), rest];
}

Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@
dependencies:
"@noble/hashes" "1.3.2"

"@noble/curves@~1.3.0":
"@noble/curves@1.3.0", "@noble/curves@~1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e"
integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==
Expand Down

0 comments on commit 273b8ca

Please sign in to comment.