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: 1113 ThorId implemented #1203

Merged
merged 18 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
804b9c7
feat: 1110 remove Quantity class and add HexInt class
lucanicoladebiasi Aug 8, 2024
9424bc1
feat: 1110 remove Quantity class and add HexInt class
lucanicoladebiasi Aug 8, 2024
0582481
Merge branch 'refs/heads/main' into HexInt
lucanicoladebiasi Aug 8, 2024
52b6358
feat: 1110 HexInt class documented
lucanicoladebiasi Aug 8, 2024
0825ddd
feat: 1110 HexInt class tested
lucanicoladebiasi Aug 8, 2024
bfe0202
feat: 1110 HexUInt class provided
lucanicoladebiasi Aug 9, 2024
5d73584
Merge branch 'refs/heads/main' into 1110-class-HexUInt
lucanicoladebiasi Aug 9, 2024
b9c481e
feat: 1113 utils/hex removed
lucanicoladebiasi Aug 9, 2024
43422b1
feat: 1113 utils/hex removed
lucanicoladebiasi Aug 9, 2024
2bd7ec2
Merge branch 'refs/heads/main' into 1113-package-unit-and-quantity-ex…
lucanicoladebiasi Aug 9, 2024
535347a
Merge branch 'refs/heads/main' into 1113-package-unit-and-quantity-ex…
lucanicoladebiasi Aug 9, 2024
30441c3
feat: 1115 Sha256.ts class implemented
lucanicoladebiasi Aug 10, 2024
634d1fc
Merge branch 'refs/heads/main' into 1113-package-unit-and-quantity-ex…
lucanicoladebiasi Aug 14, 2024
62dce5c
Merge branch 'main' into 1113-package-unit-and-quantity-expressions
lucanicoladebiasi Aug 27, 2024
72f66f4
feat: 1113 ThorId implemented
lucanicoladebiasi Aug 27, 2024
52c28ae
Merge branch 'main' into 1113-package-unit-and-quantity-expressions
lucanicoladebiasi Aug 27, 2024
fc0fbce
feat: 1113 ThorId implemented
lucanicoladebiasi Aug 27, 2024
d38a0da
Merge branch 'main' into 1113-package-unit-and-quantity-expressions
lucanicoladebiasi Aug 27, 2024
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
5 changes: 5 additions & 0 deletions docs/diagrams/architecture/vcdm.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ classDiagram
class Txt {
+Txt of(bigint|number|string|Uint8Array exp)$
}
class ThorId {
+boolean isValid0x(string exp)
+ThorID of(bigint|number|string|Uint8Array|HexInt exp)$
}
class VeChainDataModel{
<<interface>>
+bigint bi
Expand All @@ -89,6 +93,7 @@ classDiagram
HexUInt <|-- Keccak256
HexUInt <|-- Quantity
HexUInt <|-- Sha256
HexUInt <|-- ThorId
String <|-- Txt
Txt <|-- Revision
Txt <|-- Mnemonic
Expand Down
27 changes: 15 additions & 12 deletions packages/core/src/vcdm/Mnemonic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,19 @@ class Mnemonic implements VeChainDataModel<Mnemonic> {
* [BIP39 Mnemonic Words](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)
* and a derivation path as in the examples.
*
* Secure audit function.
* - {@link bip32.HDKey}(https://github.com/paulmillr/scure-bip32)
* - {@link HDNode}
*
* @example `m/0` (default)
* @example `m/0/2`
* @example `m/0/2/4/6`
*
*
* @param {string[]} words - The set of words used for mnemonic generation.
* @param {string} [path='m/0'] - The derivation path from the current node.
*
* @returns {Uint8Array} - The derived private key as a Uint8Array.
*
* @throws {InvalidHDNode}
*
* @remarks Security auditable method, depends on
* * {@link HDNode}.
*/
public static toPrivateKey(
words: string[],
Expand All @@ -167,18 +166,18 @@ class Mnemonic implements VeChainDataModel<Mnemonic> {
* [BIP39 Mnemonic Words](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)
* phrase using the specified wordlist size and random generator.
*
* Secure audit function.
* - [bip39](https://github.com/paulmillr/scure-bip39)
* - `randomGenerator` - **Must provide a cryptographic secure source of entropy
* else any secure audit certification related with this software is invalid.**
*
* @param {WordlistSizeType} wordlistSize The number of words to generate the mnemonic.
* @param {function} [randomGenerator] The random generator function used to generate the entropy.
*
* @returns {Mnemonic} The generated mnemonic.
*
* @throws {InvalidDataType} If the number of words is not valid.
* @remarks This method is a wrapper around the `generateMnemonic` function from the `bip39` package.
*
* @remarks Security auditable method, depends on
* * [entropyToMnemonic](https://github.com/paulmillr/scure-bip39);
* * [generateMnemonic](https://github.com/paulmillr/scure-bip39);
* * `randomGenerator` - **Must provide a cryptographic secure source of entropy
* else any secure audit certification related with this software is invalid.**
*/
public static of(
wordlistSize: WordlistSizeType = 12,
Expand Down Expand Up @@ -209,9 +208,13 @@ class Mnemonic implements VeChainDataModel<Mnemonic> {

/**
* Check if the given mnemonic words are valid.
*
* @param {string | string[]} words The mnemonic words to check.
*
* @returns {boolean} true if the words are valid, false otherwise.
* @remarks This method is a wrapper around the `validateMnemonic` function from the `bip39` package.
*
* @remarks Security auditable method, depends on
* * [validateMnemonic](https://github.com/paulmillr/scure-bip39).
*/
public static isValid(words: string | string[]): boolean {
const wordsToValidate = Array.isArray(words) ? words.join(' ') : words;
Expand Down
56 changes: 36 additions & 20 deletions packages/core/src/vcdm/ThorId.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
import { Hex } from './Hex';
import { HexUInt } from './HexUInt';
import { InvalidDataType } from '@vechain/sdk-errors';

/**
* Represents a ThorId.
* @experiemntal
* The ThorId class represents a Thor ID value, which is a hexadecimal positive integer having 64 digits.
*
* @extends HexInt
*/
class ThorId extends Hex {
class ThorId extends HexUInt {
/**
* Number of digits to represent a Thor ID value.
*
* @remarks The `0x` prefix is excluded.
*
* @type {number}
*/
private static readonly DIGITS = 64;

/**
* Constructs a ThorId object with the provided hexadecimal value.
*
* @param {Hex} hex - The hexadecimal value representing the ThorId.
* @param {HexUInt} huint - The hexadecimal value representing the ThorId.
*
* @throws {InvalidDataType} - If the provided value is not a valid ThorId expression.
* @experiemntal
*/
protected constructor(hex: Hex) {
if (ThorId.isValid(hex.digits)) {
super(Hex.POSITIVE, hex.digits);
protected constructor(huint: HexUInt) {
if (ThorId.isValid(huint.digits)) {
super(Hex.POSITIVE, huint.digits);
} else {
throw new InvalidDataType(
'ThorId.constructor',
'not a ThorId expression',
{ hex }
{ hex: huint }
);
}
}
Expand All @@ -37,11 +40,11 @@ class ThorId extends Hex {
* Check if the given expression is a valid ThorId.
*
* @param {string} exp - The expression to be validated.
*
* @return {boolean} Returns true if the expression is a valid ThorId, false otherwise.
* @experimental
*/
public static isValid(exp: string): boolean {
return Hex.isValid(exp) && Hex.REGEX_HEX_PREFIX.test(exp)
return Hex.isValid(exp) && HexUInt.REGEX_HEXUINT_PREFIX.test(exp)
? exp.length === ThorId.DIGITS + 2
: exp.length === ThorId.DIGITS;
}
Expand All @@ -50,11 +53,11 @@ class ThorId extends Hex {
* Determines whether the given string is a valid hex number prefixed with '0x'.
*
* @param {string} exp - The hex number to be checked.
* @returns {boolean} - True if the hex number is valid, false otherwise.
* @experimental
*
* @returns {boolean} - True if the hex number is valid, false otherwise.
*/
public static isValid0x(exp: string): boolean {
return Hex.REGEX_HEX_PREFIX.test(exp) && ThorId.isValid(exp);
return HexUInt.REGEX_HEXUINT_PREFIX.test(exp) && ThorId.isValid(exp);
}

/**
Expand All @@ -65,16 +68,29 @@ class ThorId extends Hex {
* - bigint: A BigInteger value that represents the ThorId.
* - number: A number value that represents the ThorId.
* - string: A string value that represents the ThorId.
* - Hex: A Hex object that represents the ThorId.
* - HexUInt: A HexUInt object that represents the ThorId.
* - Uint8Array: A Uint8Array object that represents the ThorId.
*
* @returns {ThorId} - A new ThorId object created from the given expression.
* @experimntal
*
* @throws {InvalidDataType} If the given expression is not a valid hexadecimal positive integer expression.
*/
public static of(exp: bigint | number | string | Hex | Uint8Array): ThorId {
if (exp instanceof Hex) {
return new ThorId(exp.fit(this.DIGITS));
public static of(
exp: bigint | number | string | Uint8Array | HexUInt
): ThorId {
try {
if (exp instanceof Hex) {
return new ThorId(exp.fit(this.DIGITS));
}
return new ThorId(HexUInt.of(exp).fit(ThorId.DIGITS));
} catch (e) {
throw new InvalidDataType(
'ThorId.of',
'not a ThorId expression',
{ exp: `${exp}` }, // Needed to serialize bigint values.
e
);
}
return new ThorId(Hex.of(exp).fit(ThorId.DIGITS));
}
}

Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/vcdm/Txt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ class Txt extends String implements VeChainDataModel<Txt> {
* Converts the current Txt string to a BigInt.
*
* @returns {bigint} The BigInt representation of the Txt string.
* @throws {InvalidOperation} If the conversion to BigInt fails because this Txt string doesn't represent an integer.
*
* @throws {InvalidOperation} If the conversion to BigInt fails because this Txt string doesn't represent an integer.
*/
get bi(): bigint {
try {
Expand Down Expand Up @@ -84,6 +85,7 @@ class Txt extends String implements VeChainDataModel<Txt> {
* Converts the current Txt string to a number.
*
* @returns {number} The numeric value of the Txt string.
*
* @throws {InvalidOperation} If the conversion to number fails because this Txt string doesn't represent a decimal number.
*/
get n(): number {
Expand All @@ -94,6 +96,7 @@ class Txt extends String implements VeChainDataModel<Txt> {
* Compares the current instance to another instance of Txt.
*
* @param {Txt} that - The instance to compare with.
*
* @return {number} - A negative number if the current instance is less than the specified instance,
* zero if they are equal, or a positive number if the current instance is greater.
*/
Expand All @@ -105,7 +108,8 @@ class Txt extends String implements VeChainDataModel<Txt> {
* Checks if the current Txt object is equal to the given Txt object.
*
* @param {Txt} that - The Txt object to compare with.
* @return {boolean} - True if the objects are equal, false otherwise.
*
* @return {boolean} - True if the objects are equal, false otherwise.
*/
public isEqual(that: Txt): boolean {
return this.compareTo(that) === 0;
Expand All @@ -128,6 +132,7 @@ class Txt extends String implements VeChainDataModel<Txt> {
* * {@link number} is represented as a {@link NFC} encoded string expressing the value in base 10;
* * {@link string} is encoded as {@link NFC} string;
* * {@link Uint8Array} is {@link NFC} decoded to a string.
*
* @returns {Txt} - A new Txt instance.
*/
public static of(exp: bigint | number | string | Uint8Array): Txt {
Expand Down
115 changes: 115 additions & 0 deletions packages/core/tests/vcdm/ThorId.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { describe, expect, test } from '@jest/globals';
import { HexUInt, ThorId } from '../../src';
import { InvalidDataType } from '@vechain/sdk-errors';

const ThorIdFixture = {
invalid: {
short: '0x271f7db20141001975f71deb8fca90d6b22b8d6610d',
noHex: '0xInvalidThorID'
},
valid: {
bytes: '0x271f7db20141001975f71deb8fca90d6b22b8d6610dfb5a3e0bbeaf78b5a4891',
number: '0x00000000000000000000000000000000000000000000000000000000000000ff' // This safely casts to number.
}
};

/**
* Test ThorId class.
* @group unit/vcdm
*/
describe('ThorId class tests.', () => {
describe('Construction tests', () => {
test('Return a ThorId instance if the passed argument is an array of bytes', () => {
const exp = HexUInt.of(ThorIdFixture.valid.bytes);
const tid = ThorId.of(exp.bytes);
expect(tid).toBeInstanceOf(ThorId);
expect(tid.isEqual(exp)).toBe(true);
});

test('Return a ThorId instance if the passed argument is a bigint', () => {
const exp = HexUInt.of(ThorIdFixture.valid.bytes);
const tid = ThorId.of(exp.bi);
expect(tid).toBeInstanceOf(ThorId);
expect(tid.isEqual(exp)).toBe(true);
});

test('Return a ThorId instance if the passed argument is a number', () => {
const exp = HexUInt.of(ThorIdFixture.valid.number);
const tid = ThorId.of(exp.n); // This is a safe number cast.
expect(tid).toBeInstanceOf(ThorId);
expect(tid.isEqual(exp)).toBe(true);
});

test('Return a ThorId instance if the passed argument is a `0x` prefixed string', () => {
const exp = HexUInt.of(ThorIdFixture.valid.bytes);
const tid = ThorId.of(exp.toString());
expect(tid).toBeInstanceOf(ThorId);
expect(tid.isEqual(exp)).toBe(true);
});

test('Return a ThorId instance if the passed argument is a not prefixed string', () => {
const exp = HexUInt.of(ThorIdFixture.valid.bytes);
const tid = ThorId.of(exp.digits);
expect(tid).toBeInstanceOf(ThorId);
expect(tid.isEqual(exp)).toBe(true);
});

test('Throw an error if the passed argument is a negative bigint', () => {
expect(() => ThorId.of(-1)).toThrow(InvalidDataType);
});

test('Throw an error if the passed argument is a negative number', () => {
expect(() => ThorId.of(-1n)).toThrow(InvalidDataType);
});
});

describe('isValid method tests', () => {
test('Return false for no hex expression', () => {
expect(ThorId.isValid(ThorIdFixture.invalid.noHex)).toBe(false);
});

test('Return false for short expression', () => {
expect(ThorId.isValid(ThorIdFixture.invalid.short)).toBe(false);
});

test('Return true for valid `0x` prefixed expression', () => {
expect(ThorId.isValid(ThorIdFixture.valid.bytes)).toBe(true);
});

test('Return true for valid `not prefixed expression', () => {
expect(
ThorId.isValid(HexUInt.of(ThorIdFixture.valid.bytes).digits)
).toBe(true);
});
});

describe('isValid0x method tests', () => {
test('Return false for no hex expression', () => {
expect(ThorId.isValid0x(ThorIdFixture.invalid.noHex)).toBe(false);
});

test('Return false for short expression', () => {
expect(ThorId.isValid0x(ThorIdFixture.invalid.short)).toBe(false);
});

test('Return true for valid `0x` prefixed expression', () => {
expect(ThorId.isValid0x(ThorIdFixture.valid.bytes)).toBe(true);
});

test('Return false for valid `not prefixed expression', () => {
expect(
ThorId.isValid0x(HexUInt.of(ThorIdFixture.valid.bytes).digits)
).toBe(false);
});
});

test('digits property should return 64 characters', () => {
const tid = ThorId.of(0);
expect(tid.digits.length).toBe(64);
});

test('toString method should return 66 characters', () => {
const tid = ThorId.of(0);
expect(tid.toString().length).toBe(66);
});
});