diff --git a/packages/massa-web3/src/experimental/basicElements/address.ts b/packages/massa-web3/src/experimental/basicElements/address.ts index beca7c34..0188cbc3 100644 --- a/packages/massa-web3/src/experimental/basicElements/address.ts +++ b/packages/massa-web3/src/experimental/basicElements/address.ts @@ -1,14 +1,16 @@ import Base58 from '../crypto/base58' import Serializer from '../crypto/interfaces/serializer' -import { Version } from '../crypto/interfaces/versioner' -import { checkPrefix } from './internal' +import { Version, Versioner } from '../crypto/interfaces/versioner' +import VarintVersioner from '../crypto/varintVersioner' import { PublicKey } from './keys' import varint from 'varint' -const ADDRESS_PREFIX = 'A' -const ADDRESS_USER_PREFIX = 'U' -const ADDRESS_CONTRACT_PREFIX = 'S' +const ADDRESS_USER_PREFIX = 'AU' +const ADDRESS_CONTRACT_PREFIX = 'AS' const UNDERLYING_HASH_LEN = 32 + +const DEFAULT_VERSION = Version.V0 + /** * A class representing an address. * @@ -17,37 +19,31 @@ const UNDERLYING_HASH_LEN = 32 * * @privateRemarks * Interfaces are used to make the code more modular. To add a new version, you simply need to: - * - extend the `initFromVersion` method: - * - Add a new case in the switch statement with the new algorithms to use. - * - Add a new default version matching the last version. + * - Change the DEFAULT_VERSION version matching the last version. * - check the `fromPublicKey` method to potentially adapt how an address is derived from a public key. * - Voila! The code will automatically handle the new version. */ export class Address { private bytes: Uint8Array + public version = DEFAULT_VERSION + public isEOA = false + protected constructor( - public serializer: Serializer, - public version: Version, - public isEOA: boolean + public serializer: Serializer = new Base58(), + public versioner: Versioner = new VarintVersioner() ) {} - /** - * Initialize a new address object from a version. - * - * @param version - The version of the address. - * - * @returns A new address instance. - * - * @throws If the version is not supported. - */ - protected static initFromVersion(version: Version = Version.V0): Address { - switch (version) { - case Version.V0: - return new Address(new Base58(), version, false) - default: - throw new Error(`unsupported version: ${version}`) + private getPrefix(str: string): string { + const expected = [ADDRESS_USER_PREFIX, ADDRESS_CONTRACT_PREFIX] + for (let prefix of expected) { + if (str.startsWith(prefix)) { + return prefix + } } + throw new Error( + `invalid address prefix: one of ${expected.join(' or ')} was expected.` + ) } /** @@ -60,29 +56,24 @@ export class Address { * @throws If the address string is invalid. */ public static fromString(str: string): Address { - const address = Address.initFromVersion() + const address = new Address() try { - const prefix = checkPrefix( - str, - ADDRESS_PREFIX + ADDRESS_USER_PREFIX, - ADDRESS_PREFIX + ADDRESS_CONTRACT_PREFIX - ) + const prefix = address.getPrefix(str) - address.isEOA = str[ADDRESS_PREFIX.length] === ADDRESS_USER_PREFIX - const versionedHash = address.serializer.deserialize( + address.isEOA = prefix === ADDRESS_USER_PREFIX + const versionedBytes = address.serializer.deserialize( str.slice(prefix.length) ) address.bytes = Uint8Array.from([ ...varint.encode(address.isEOA ? 0 : 1), - ...versionedHash, + ...versionedBytes, ]) address.version = address.getVersion() } catch (e) { throw new Error(`invalid address string: ${e.message}`) } - return address } @@ -107,8 +98,12 @@ export class Address { if (!this.bytes) { throw new Error('address bytes is not initialized') } - varint.decode(this.bytes) - return varint.decode(this.bytes, varint.decode.bytes) + + const { version } = this.versioner.extract( + // skip address type bytes + this.bytes.slice(varint.encodingLength(this.getType())) + ) + return version } /** @@ -118,13 +113,16 @@ export class Address { * * @returns A new address object. */ - public static fromPublicKey(publicKey: PublicKey): Address { - const address = Address.initFromVersion() - const hash = publicKey.hasher.hash(publicKey.toBytes()) + public static fromPublicKey( + publicKey: PublicKey, + version = DEFAULT_VERSION + ): Address { + const address = new Address() + const rawBytes = publicKey.hasher.hash(publicKey.toBytes()) + address.version = version address.bytes = Uint8Array.from([ 0 /* EOA*/, - ...varint.encode(address.version), - ...hash, + ...address.versioner.attach(version, rawBytes), ]) address.isEOA = true return address @@ -138,7 +136,7 @@ export class Address { * @returns A new address object. */ public static fromBytes(bytes: Uint8Array): Address { - const address = Address.initFromVersion() + const address = new Address() address.bytes = bytes address.isEOA = address.getType() === 0 address.version = address.getVersion() @@ -165,20 +163,22 @@ export class Address { * @returns The serialized address string. */ toString(): string { - // decode version - varint.decode(this.bytes) - const versionedBytes = this.bytes.slice(varint.decode.bytes) - return `${ADDRESS_PREFIX}${ + // skip address type bytes + const versionedBytes = this.bytes.slice( + varint.encodingLength(this.getType()) + ) + return `${ this.isEOA ? ADDRESS_USER_PREFIX : ADDRESS_CONTRACT_PREFIX }${this.serializer.serialize(versionedBytes)}` } /** - * Get byte length of address in binary format . + * Get address in binary format from a bytes buffer. + * The address should be the first element in the buffer. * - * @returns The address length in bytes. + * @returns The address in bytes format. */ - static getByteLength(data: Uint8Array): number { + static extractFromBuffer(data: Uint8Array): Uint8Array { // addr type varint.decode(data) let addrByteLen = varint.decode.bytes @@ -187,6 +187,6 @@ export class Address { addrByteLen += varint.decode.bytes addrByteLen += UNDERLYING_HASH_LEN - return addrByteLen + return data.slice(0, addrByteLen) } } diff --git a/packages/massa-web3/src/experimental/basicElements/operationManager.ts b/packages/massa-web3/src/experimental/basicElements/operationManager.ts index 4693a09f..f4a1ca25 100644 --- a/packages/massa-web3/src/experimental/basicElements/operationManager.ts +++ b/packages/massa-web3/src/experimental/basicElements/operationManager.ts @@ -164,11 +164,9 @@ export class OperationManager { switch (operationDetails.type) { case OperationType.Transaction: { - const addrLen = Address.getByteLength(data.slice(offset)) - const recipientAddress = Address.fromBytes( - data.slice(offset, offset + addrLen) - ) - offset += addrLen + const addrBytes = Address.extractFromBuffer(data.slice(offset)) + const recipientAddress = Address.fromBytes(addrBytes) + offset += addrBytes.length return { ...operationDetails, recipientAddress, diff --git a/packages/massa-web3/test/experimental/unit/address.spec.ts b/packages/massa-web3/test/experimental/unit/address.spec.ts index 40702aec..b09ea3e1 100644 --- a/packages/massa-web3/test/experimental/unit/address.spec.ts +++ b/packages/massa-web3/test/experimental/unit/address.spec.ts @@ -57,9 +57,9 @@ describe('Address tests', () => { test('getByteLength returns correct length', () => { const address = Address.fromPublicKey(account.publicKey) - const bytes = address.toBytes() - const byteLength = Address.getByteLength(bytes) + const buffer = Uint8Array.from([...address.toBytes(), 1, 2, 3, 4]) + const addressBytes = Address.extractFromBuffer(buffer) - expect(byteLength).toBe(bytes.length) + expect(addressBytes).toStrictEqual(address.toBytes()) }) })