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

fix: 943 certificate.ts verify explicits encoding chain from JSON/str… #962

Merged
merged 5 commits into from
Jun 6, 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
6 changes: 3 additions & 3 deletions apps/sdk-hardhat-integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
"deploy-testnet": "npx hardhat run scripts/deploy.ts --network vechain_testnet"
},
"dependencies": {
"@vechain/sdk-core": "1.0.0-beta.14",
"@vechain/sdk-hardhat-plugin": "1.0.0-beta.14",
"@vechain/sdk-logging": "1.0.0-beta.14",
"@vechain/sdk-core": "1.0.0-beta.15",
"@vechain/sdk-hardhat-plugin": "1.0.0-beta.15",
"@vechain/sdk-logging": "1.0.0-beta.15",
"@openzeppelin/contracts": "^5.0.2"
},
"devDependencies": {
Expand Down
216 changes: 108 additions & 108 deletions apps/sdk-hardhat-integration/yarn.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/sdk-nextjs-integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"test:watch": "jest --watch"
},
"dependencies": {
"@vechain/sdk-core": "1.0.0-beta.14",
"@vechain/sdk-network": "1.0.0-beta.14",
"@vechain/sdk-core": "1.0.0-beta.15",
"@vechain/sdk-network": "1.0.0-beta.15",
"install": "^0.13.0",
"next": "14",
"react": "^18",
Expand Down
451 changes: 215 additions & 236 deletions apps/sdk-nextjs-integration/yarn.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/sdk-node-integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"test": "jest"
},
"dependencies": {
"@vechain/sdk-network": "1.0.0-beta.14",
"@vechain/sdk-network": "1.0.0-beta.15",
"axios": "^1.6.8",
"typescript": "^5.3.3"
},
Expand Down
664 changes: 334 additions & 330 deletions apps/sdk-node-integration/yarn.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "docs",
"version": "1.0.0-beta.14",
"version": "1.0.0-beta.15",
"description": "Documentation for the SDK with examples",
"author": "vechain Foundation",
"license": "MIT",
Expand All @@ -20,7 +20,7 @@
"test:examples:solo": "(yarn start-thor-solo && yarn test:examples && yarn stop-thor-solo) || yarn stop-thor-solo"
},
"dependencies": {
"@vechain/sdk-core": "1.0.0-beta.14",
"@vechain/sdk-core": "1.0.0-beta.15",
"typescript": "^5.4.4"
},
"devDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vechain/sdk-core",
"version": "1.0.0-beta.14",
"version": "1.0.0-beta.15",
"description": "Includes modules for fundamental operations like hashing and cryptography",
"author": "vechain Foundation",
"license": "MIT",
Expand Down Expand Up @@ -39,8 +39,8 @@
"@scure/bip32": "^1.4.0",
"@scure/bip39": "^1.3.0",
"@types/elliptic": "^6.4.18",
"@vechain/sdk-errors": "1.0.0-beta.14",
"@vechain/sdk-logging": "1.0.0-beta.14",
"@vechain/sdk-errors": "1.0.0-beta.15",
"@vechain/sdk-logging": "1.0.0-beta.15",
"bignumber.js": "^9.1.2",
"blakejs": "^1.2.1",
"elliptic": "^6.5.5",
Expand Down
91 changes: 70 additions & 21 deletions packages/core/src/certificate/certificate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as utils from '@noble/curves/abstract/utils';
import fastJsonStableStringify from 'fast-json-stable-stringify';
import { Hex, Hex0x } from '../utils';
import { addressUtils } from '../address';
import { assert, buildError, CERTIFICATE } from '@vechain/sdk-errors';
import { blake2b256 } from '../hash';
import { hexToBytes } from '@noble/curves/abstract/utils';
import { secp256k1 } from '../secp256k1';
import { type Certificate } from './types';

Expand Down Expand Up @@ -33,7 +33,7 @@ function encode(cert: Certificate): string {
}

/**
* Verifies the validity of a certificate.
* Matches a certificate against a given address and signature.
*
* This method is insensitive to the case representation of the signer's address.
*
Expand All @@ -42,9 +42,67 @@ function encode(cert: Certificate): string {
*
* Secure audit function.
* - {@link blake2b256};
* - {@link certificate.encode};
* - {@link secp256k1.recover}.
*
* @param {Uint8Array} cert - The certificate to match. computed from the certificate without the `signature` property.
* @param {string} address - The address to match against, optionally prefixed with `0x`.
* @param {string} signature - The signature to verify expressed in hexadecimal form, optionally prefixed with `0x`.
*
* @returns {void} - No return value.
*
* @throws CertificateInvalidSignatureFormatError - If the certificate signature's is not a valid hexadecimal expression prefixed with `0x`.
* @throws CertificateNotSignedError - If the certificate is not signed.
* @throws CertificateInvalidSignerError - If the certificate's signature's doesn't match with the signer;s public key.
*
*/
function match(cert: Uint8Array, address: string, signature: string): void {
// Invalid hexadecimal as signature.
assert(
'certificate.match',
Hex0x.isValid(signature, true, true),
CERTIFICATE.CERTIFICATE_INVALID_SIGNATURE_FORMAT,
'Verification failed: signature format is invalid.',
{ signature }
);
try {
// The `encode` method could throw `InvalidAddressError`.
const signingHash = blake2b256(cert, 'buffer');
const signingPublicKey = secp256k1.recover(
signingHash,
hexToBytes(Hex.canon(signature))
);
const signingAddress = addressUtils.fromPublicKey(signingPublicKey);
// Signature does not match with the signer's public key.
assert(
'certificate.match',
signingAddress.toLowerCase() === address.toLowerCase(),
CERTIFICATE.CERTIFICATE_INVALID_SIGNER,
"Verification failed: signature does not correspond to the signer's public key.",
{ pubKey: signingPublicKey, cert }
);
} catch (e) {
throw buildError(
'certificate.match',
CERTIFICATE.CERTIFICATE_INVALID_SIGNER,
(e as Error).message,
{ address, certificate: cert, signature },
e
);
}
}

/**
* Verifies the validity of a certificate.
*
* This method is insensitive to the case representation of the signer's address.
*
* [EIP/ERC-55: Mixed-case checksum address encoding](https://eips.ethereum.org/EIPS/eip-55).
* is supported.
*
* Secure audit function.
* - {@link certificate.encode};
* - {@link match}.
*
* @param {Certificate} cert - The certificate to verify.
*
* @returns {void} - No return value.
Expand All @@ -54,12 +112,13 @@ function encode(cert: Certificate): string {
* @throws CertificateInvalidSignerError - If the certificate's signature's doesn't match with the signer;s public key.
*
* @remark This methods {@link certificate.encode} the `cert` instance
* to extract its signer 's address and compare it with the address derioved from the public key recovered from the
* to extract its signer 's address and compare it with the address computed from the public key recovered from the
* certificate using the
* [BLAKE2](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2)
* hash of its JSON encoded representation.
*
* @see {encode}
* @see {match}
*/
function verify(cert: Certificate): void {
// No signature.
Expand All @@ -78,24 +137,14 @@ function verify(cert: Certificate): void {
'Verification failed: signature format is invalid.',
{ cert }
);
// Encode the certificate without the signature and get signing hash.
try {
// The `encode` method could throw `InvalidAddressError`.
const encoded = encode({ ...cert, signature: undefined });
const signingHash = blake2b256(encoded);
const pubKey = secp256k1.recover(
signingHash,
utils.hexToBytes(Hex.canon(cert.signature as string))
);
// Signature does not match with the signer's public key.
assert(
'certificate.verify',
addressUtils.fromPublicKey(pubKey).toLowerCase() ===
cert.signer.toLowerCase(),
CERTIFICATE.CERTIFICATE_INVALID_SIGNER,
"Verification failed: signature does not correspond to the signer's public key.",
{ pubKey, cert }
// Encode the certificate without the signature.
const encoded = new TextEncoder().encode(
certificate
.encode({ ...cert, signature: undefined })
.normalize('NFC')
);
match(new Uint8Array(encoded), cert.signer, cert.signature as string);
} catch (e) {
throw buildError(
'certificate.verify',
Expand All @@ -110,4 +159,4 @@ function verify(cert: Certificate): void {
/**
* Exposes the certificate encoding and verification functions.
*/
export const certificate = { encode, verify };
export const certificate = { encode, match, verify };
52 changes: 42 additions & 10 deletions packages/core/tests/certificate/certificate.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from '@jest/globals';
import { certificate } from '../../src';
import { cert, cert2, invalidSignature, sig, sig2 } from './fixture';
import { cert1, cert2, invalidSignature, sig1, sig2 } from './fixture';
import {
CertificateInvalidSignatureFormatError,
CertificateInvalidSignerError,
Expand All @@ -15,13 +15,13 @@ import {
describe('certificate', () => {
describe('encode', () => {
test('consistent between two certificates - before signature', () => {
expect(certificate.encode(cert)).toStrictEqual(
expect(certificate.encode(cert1)).toStrictEqual(
certificate.encode(cert2)
);
});

test('consistent between two certificates - after signature', () => {
expect(certificate.encode({ ...cert, signature: sig })).toEqual(
expect(certificate.encode({ ...cert1, signature: sig1 })).toEqual(
certificate.encode({
...cert2,
signature: sig2
Expand All @@ -30,42 +30,74 @@ describe('certificate', () => {
});
});

describe('match', () => {
const cert = new TextEncoder().encode(
certificate
.encode({ ...cert1, signature: undefined })
.normalize('NFC')
);

test('valid - because signature', () => {
expect(() => {
certificate.match(cert, cert1.signer, sig1);
}).not.toThrowError();
});

test('valid - because signature - uppercase', () => {
expect(() => {
certificate.match(cert, cert1.signer, sig1.toUpperCase());
}).not.toThrowError();
});

test('invalid - because signer address', () => {
expect(() => {
certificate.match(cert, '0x', sig1);
}).toThrowError(CertificateInvalidSignerError);
});

test('invalid - because invalid signature format', () => {
expect(() => {
certificate.match(cert, cert1.signer, invalidSignature);
}).toThrowError(CertificateInvalidSignatureFormatError);
});
});

describe('verify', () => {
test('valid - because signature', () => {
expect(() => {
certificate.verify({ ...cert, signature: sig });
certificate.verify({ ...cert1, signature: sig1 });
}).not.toThrowError();
});

test('valid - because signature - uppercase', () => {
expect(() => {
certificate.verify({
...cert,
signature: sig.toUpperCase()
...cert1,
signature: sig1.toUpperCase()
});
}).not.toThrowError();
});

test('invalid - because signer address', () => {
expect(() => {
certificate.verify({
...cert,
signature: sig,
...cert1,
signature: sig1,
signer: '0x'
});
}).toThrowError(CertificateInvalidSignerError);
});

test('invalid - because missing signature', () => {
expect(() => {
certificate.verify({ ...cert, signer: '0x' });
certificate.verify({ ...cert1, signer: '0x' });
}).toThrowError(CertificateNotSignedError);
});

test('invalid - because invalid signature format', () => {
expect(() => {
certificate.verify({
...cert,
...cert1,
signature: invalidSignature,
signer: '0x'
});
Expand Down
12 changes: 6 additions & 6 deletions packages/core/tests/certificate/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const privKey = utils.hexToBytes(
/**
* Certificate n.1 to be used for testing, mostly for encoding and verify functions
*/
const cert = {
const cert1 = {
purpose: 'identification',
payload: {
type: 'text',
Expand All @@ -36,7 +36,7 @@ const cert2 = {
domain: 'localhost',
timestamp: 1545035330,
purpose: 'identification',
signer: cert.signer,
signer: cert1.signer,
payload: {
content: 'fyi',
type: 'text'
Expand All @@ -46,8 +46,8 @@ const cert2 = {
/**
* Signature of Certificate n.1
*/
const sig = Hex0x.of(
secp256k1.sign(blake2b256(certificate.encode(cert)), privKey)
const sig1 = Hex0x.of(
secp256k1.sign(blake2b256(certificate.encode(cert1)), privKey)
);

/**
Expand All @@ -62,6 +62,6 @@ const sig2 = Hex0x.of(
*/
const invalidSignature =
'0xBAD' +
Hex.of(secp256k1.sign(blake2b256(certificate.encode(cert)), privKey));
Hex.of(secp256k1.sign(blake2b256(certificate.encode(cert1)), privKey));

export { privKey, cert, cert2, sig, sig2, invalidSignature };
export { privKey, cert1, cert2, sig1, sig2, invalidSignature };
4 changes: 2 additions & 2 deletions packages/core/tests/hash/hash.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { InvalidDataReturnTypeError } from '@vechain/sdk-errors';
import { Buffer } from 'buffer';
import { ZERO_BYTES } from '../../src';

import { cert } from '../certificate/fixture';
import { cert1 } from '../certificate/fixture';
import { bytesToHex } from '@noble/ciphers/utils';

/**
Expand All @@ -14,7 +14,7 @@ import { bytesToHex } from '@noble/ciphers/utils';
*/
describe('Hash', () => {
test('thordevkit', () => {
const json = JSON.stringify(cert);
const json = JSON.stringify(cert1);
console.log(json);
// const blake_dev_dir = ThorDevKit.blake2b256(json);
// console.log('dir', blake_dev_dir);
Expand Down
2 changes: 1 addition & 1 deletion packages/errors/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vechain/sdk-errors",
"version": "1.0.0-beta.14",
"version": "1.0.0-beta.15",
"description": "This module is dedicated to managing and customizing errors within the SDK",
"author": "vechain Foundation",
"license": "MIT",
Expand Down
6 changes: 3 additions & 3 deletions packages/ethers-adapter/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vechain/sdk-ethers-adapter",
"version": "1.0.0-beta.14",
"version": "1.0.0-beta.15",
"description": "This module serves as a crucial bridge between the standard Ethereum tooling provided by Hardhat and the unique features of the vechain thor blockchain",
"author": "vechain Foundation",
"license": "MIT",
Expand Down Expand Up @@ -34,8 +34,8 @@
"test": "rm -rf ./coverage && jest --coverage --coverageDirectory=coverage --group=integration --group=unit"
},
"dependencies": {
"@vechain/sdk-core": "1.0.0-beta.14",
"@vechain/sdk-network": "1.0.0-beta.14"
"@vechain/sdk-core": "1.0.0-beta.15",
"@vechain/sdk-network": "1.0.0-beta.15"
},
"devDependencies": {
"@nomicfoundation/hardhat-ethers": "^3.0.6"
Expand Down
Loading