Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bls): validate proof of possession #815

Merged
merged 23 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading