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

Release v4.1.0 #819

Merged
merged 5 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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ import { avm, pvm, evm } from '@avalabs/avalanchejs';
## Importing Essentials

```ts
import { avm /** X-chain */, pvm /** P-chain */, evm /** C-chain */, utils } from "@avalabs/avalanchejs"
import { avm /** X-chain */, pvm /** P-chain */, evm /** C-chain */, utils, secp256k1 } from "@avalabs/avalanchejs"

// example calls
const exportTx = avm.newExportTx(...) // constructs a new export tx from X
const addValidatorTx = pvm.newAddPermissionlessValidatorTx(...) // constructs a new add validator tx on P
const importTx = evm.newImportTx(...) // constructs a new import tx to C

const publicKeyBytes = utils.hexToBuffer(publicKeyHex)
const signature = utils.signHash(bytes, privateKeyBytes)
const signature = secp256k1.signHash(bytes, privateKeyBytes)
```

Please check out the `examples` folder for more info.
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
"import": "./dist/es/index.js",
"require": "./dist/index.js"
},
"publishConfig": {
"access": "restricted"
},
"files": [
"dist",
"src"
Expand All @@ -34,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 @@ -51,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.

60 changes: 60 additions & 0 deletions src/crypto/bls.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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('generates signature correctly', async () => {
const sk = bls.secretKeyFromBytes(skStr);
expect(bls.sign(msg, sk)).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('generates proof of possession correctly', async () => {
const sk = bls.secretKeyFromBytes(skStr);
const pk = bls.publicKeyFromBytes(pkStr);
const pkBytes = bls.publicKeyToBytes(pk);

expect(bls.signProofOfPossession(pkBytes, sk)).toEqual(hexToBuffer(popStr));
});

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);
});
});
77 changes: 77 additions & 0 deletions src/crypto/bls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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,
});
}

export function sign(msg: Uint8Array | string, sk: SecretKey): Uint8Array {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Will error until a version of @noble/curves is released with https://github.com/paulmillr/noble-curves/pull/117
return bls12_381.sign(msg, sk, {
DST: signatureDST,
});
}

export function signProofOfPossession(
msg: Uint8Array | string,
sk: SecretKey,
): Uint8Array {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Will error until a version of @noble/curves is released with https://github.com/paulmillr/noble-curves/pull/117
return bls12_381.sign(msg, sk, {
DST: proofOfPossessionDST,
});
}
2 changes: 2 additions & 0 deletions src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * as secp256k1 from './secp256k1';
export * as bls from './bls';
28 changes: 16 additions & 12 deletions src/utils/secp256k1.spec.ts → src/crypto/secp256k1.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { sha256 } from '@noble/hashes/sha256';
import { base58check } from './base58';
import { bufferToHex, hexToBuffer } from './buffer';
import * as secp from './secp256k1';
import { base58check } from '../utils/base58';
import { bufferToHex, hexToBuffer } from '../utils/buffer';
import * as secp256k1 from './secp256k1';

describe('secp256k1', function () {
it('works correctly', async () => {
const key = '24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5';
const privKey = base58check.decode(key);
const pubKey = secp.getPublicKey(privKey);
const pubKey = secp256k1.getPublicKey(privKey);

expect(base58check.encode(secp.publicKeyBytesToAddress(pubKey))).toEqual(
'Q4MzFZZDPHRPAHFeDs3NiyyaZDvxHKivf',
);
expect(
base58check.encode(secp256k1.publicKeyBytesToAddress(pubKey)),
).toEqual('Q4MzFZZDPHRPAHFeDs3NiyyaZDvxHKivf');

const tests = [
{
Expand Down Expand Up @@ -52,10 +52,14 @@ describe('secp256k1', function () {
for (const test of tests) {
const hash = sha256(test.msg);

await expect(secp.sign(test.msg, privKey)).resolves.toEqual(test.sig);
await expect(secp.signHash(hash, privKey)).resolves.toEqual(test.sig);
expect(secp.recoverPublicKey(hash, test.sig)).toEqual(pubKey);
expect(secp.verify(test.sig, hash, pubKey)).toEqual(true);
await expect(secp256k1.sign(test.msg, privKey)).resolves.toEqual(
test.sig,
);
await expect(secp256k1.signHash(hash, privKey)).resolves.toEqual(
test.sig,
);
expect(secp256k1.recoverPublicKey(hash, test.sig)).toEqual(pubKey);
expect(secp256k1.verify(test.sig, hash, pubKey)).toEqual(true);
}
});

Expand All @@ -64,7 +68,7 @@ describe('secp256k1', function () {
'04e68acfc0253a10620dff706b0a1b1f1f5833ea3beb3bde2250d5f271f3563606672ebc45e0b7ea2e816ecb70ca03137b1c9476eec63d4632e990020b7b6fba39',
);

const ethAddrKey = bufferToHex(secp.publicKeyToEthAddress(publicKey));
const ethAddrKey = bufferToHex(secp256k1.publicKeyToEthAddress(publicKey));
expect(ethAddrKey).toBe(
'0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'.toLocaleLowerCase(),
);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/secp256k1.ts → src/crypto/secp256k1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ripemd160 } from '@noble/hashes/ripemd160';
import { sha256 } from '@noble/hashes/sha256';
import * as secp from '@noble/secp256k1';
import { Address } from 'micro-eth-signer';
import { concatBytes, hexToBuffer } from './buffer';
import { concatBytes, hexToBuffer } from '../utils/buffer';

export function randomPrivateKey() {
return secp.utils.randomPrivateKey();
Expand Down
20 changes: 8 additions & 12 deletions src/fixtures/vms.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import {
getPublicKey,
hexToBuffer,
publicKeyBytesToAddress,
publicKeyToEthAddress,
} from '../utils';
import { secp256k1 } from '../crypto';
import { hexToBuffer } from '../utils';

export const testPrivateKey1 = hexToBuffer(
'9c0523e7611e62f5dca291ad335e950db076c5ee31c4107355abde0d357bbd29',
);
export const testPublicKey1 = getPublicKey(testPrivateKey1);
export const testAddress1 = publicKeyBytesToAddress(testPublicKey1);
export const testEthAddress1 = publicKeyToEthAddress(testPublicKey1);
export const testPublicKey1 = secp256k1.getPublicKey(testPrivateKey1);
export const testAddress1 = secp256k1.publicKeyBytesToAddress(testPublicKey1);
export const testEthAddress1 = secp256k1.publicKeyToEthAddress(testPublicKey1);

export const testPrivateKey2 = hexToBuffer(
'd11e7aa633eb15682bc2456d399c2a9861c82e0a308dbfd4d3a51ffa972f2b62',
);
export const testPublicKey2 = getPublicKey(testPrivateKey2);
export const testAddress2 = publicKeyBytesToAddress(testPublicKey2);
export const testEthAddress2 = publicKeyToEthAddress(testPublicKey2);
export const testPublicKey2 = secp256k1.getPublicKey(testPrivateKey2);
export const testAddress2 = secp256k1.publicKeyBytesToAddress(testPublicKey2);
export const testEthAddress2 = secp256k1.publicKeyToEthAddress(testPublicKey2);
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export * as networkIDs from './constants/networkIDs';
export * from './serializable';
export { Utxo } from './serializable/avax/utxo';
export * from './signer';
export * from './utils';
export * as utils from './utils';
export * from './vms';
export { Info } from './info/info';
export * from './crypto';
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
Loading
Loading