From 1512954e15f2ec2b17c6101553d346ae58f5741a Mon Sep 17 00:00:00 2001 From: Alejo Acosta Date: Tue, 18 Jun 2024 12:59:07 -0300 Subject: [PATCH 1/4] move 'deserialize' to child classes and eliminate cyclic dependencies --- src/wallet/hdwallet.ts | 29 +++---- src/wallet/qi-hdwallet.ts | 156 ++++++++++++++++++------------------ src/wallet/quai-hdwallet.ts | 21 ++++- 3 files changed, 108 insertions(+), 98 deletions(-) diff --git a/src/wallet/hdwallet.ts b/src/wallet/hdwallet.ts index f0e036da..93bc7499 100644 --- a/src/wallet/hdwallet.ts +++ b/src/wallet/hdwallet.ts @@ -7,8 +7,6 @@ import { getZoneForAddress, isQiAddress } from '../utils/index.js'; import { Zone } from '../constants/index.js'; import { TransactionRequest, Provider, TransactionResponse } from '../providers/index.js'; import { AllowedCoinType } from '../constants/index.js'; -import { QiHDWallet } from './qi-hdwallet.js'; -import { QuaiHDWallet } from './quai-hdwallet.js'; export interface NeuteredAddressInfo { pubKey: string; @@ -283,31 +281,24 @@ export abstract class AbstractHDWallet { }; } - static async deserialize( - this: new (root: HDNodeWallet, provider?: Provider) => T, - serialized: SerializedHDWallet, - ): Promise { - // validate the version and coinType + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public static async deserialize(_serialized: SerializedHDWallet): Promise { + throw new Error('deserialize method must be implemented in the subclass'); + } + + // This method is used to validate the version and coinType of the serialized wallet. + protected static validateSerializedWallet(serialized: SerializedHDWallet): void { if (serialized.version !== (this as any)._version) { throw new Error(`Invalid version ${serialized.version} for wallet (expected ${(this as any)._version})`); } if (serialized.coinType !== (this as any)._coinType) { throw new Error(`Invalid coinType ${serialized.coinType} for wallet (expected ${(this as any)._coinType})`); } - // create the wallet instance - const mnemonic = Mnemonic.fromPhrase(serialized.phrase); - const path = (this as any).parentPath(serialized.coinType); - const root = HDNodeWallet.fromMnemonic(mnemonic, path); - const wallet = new this(root); - - // import the addresses - wallet.importSerializedAddresses(wallet._addresses, serialized.addresses); - - return wallet as T; } - // This method is used to import addresses from a serialized wallet. - // It validates the addresses and adds them to the wallet. + // This method is used to import addresses from a serialized wallet into the addresses map. + // Before adding the addresses, a validation is performed to ensure the address, public key and zone + // match the expected values. protected importSerializedAddresses( addressMap: Map, addresses: NeuteredAddressInfo[], diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts index d7a03acb..d453b35a 100644 --- a/src/wallet/qi-hdwallet.ts +++ b/src/wallet/qi-hdwallet.ts @@ -1,7 +1,5 @@ - - import { AbstractHDWallet, NeuteredAddressInfo, SerializedHDWallet } from './hdwallet'; -import { HDNodeWallet } from "./hdnodewallet"; +import { HDNodeWallet } from './hdnodewallet'; import { QiTransactionRequest, Provider, TransactionResponse } from '../providers/index.js'; import { computeAddress } from '../address/index.js'; import { getBytes, hexlify } from '../utils/index.js'; @@ -13,6 +11,7 @@ import { musigCrypto } from '../crypto/index.js'; import { Outpoint } from '../transaction/utxo.js'; import { getZoneForAddress } from '../utils/index.js'; import { AllowedCoinType, Zone } from '../constants/index.js'; +import { Mnemonic } from './mnemonic.js'; type OutpointInfo = { outpoint: Outpoint; @@ -21,15 +20,14 @@ type OutpointInfo = { account?: number; }; -interface SerializedQiHDWallet extends SerializedHDWallet{ - outpoints: OutpointInfo[]; - changeAddresses: NeuteredAddressInfo[]; - gapAddresses: NeuteredAddressInfo[]; - gapChangeAddresses: NeuteredAddressInfo[]; +interface SerializedQiHDWallet extends SerializedHDWallet { + outpoints: OutpointInfo[]; + changeAddresses: NeuteredAddressInfo[]; + gapAddresses: NeuteredAddressInfo[]; + gapChangeAddresses: NeuteredAddressInfo[]; } export class QiHDWallet extends AbstractHDWallet { - protected static _version: number = 1; protected static _GAP_LIMIT: number = 20; @@ -319,71 +317,77 @@ export class QiHDWallet extends AbstractHDWallet { return gapAddresses; } - public getGapChangeAddressesForZone(zone: Zone): NeuteredAddressInfo[] { - this.validateZone(zone); - const gapChangeAddresses = this._gapChangeAddresses.filter((addressInfo) => addressInfo.zone === zone); - return gapChangeAddresses; - } - - public async signMessage(address: string, message: string | Uint8Array): Promise { - const addrNode = this._getHDNodeForAddress(address); - const privKey = addrNode.privateKey; - const digest = keccak_256(message); - const signature = schnorr.sign(digest, getBytes(privKey)); - return hexlify(signature); - } - - public async serialize(): Promise { - const hdwalletSerialized = await super.serialize(); - return { - outpoints: this._outpoints, - changeAddresses: Array.from(this._changeAddresses.values()), - gapAddresses: this._gapAddresses, - gapChangeAddresses: this._gapChangeAddresses, - ...hdwalletSerialized, - }; - } - - public static async deserialize(serialized: SerializedQiHDWallet): Promise { - const wallet = await super.deserialize(serialized) as QiHDWallet; - // import the change addresses - wallet.importSerializedAddresses(wallet._changeAddresses, serialized.changeAddresses); - - // import the gap addresses, verifying they exist in the wallet - for (const gapAddressInfo of serialized.gapAddresses) { - const gapAddress = gapAddressInfo.address; - if (!wallet._addresses.has(gapAddress)) { - throw new Error(`Address ${gapAddress} not found in wallet`); - } - wallet._gapAddresses.push(gapAddressInfo); - - } - // import the gap change addresses, verifying they exist in the wallet - for (const gapChangeAddressInfo of serialized.gapChangeAddresses) { - const gapChangeAddress = gapChangeAddressInfo.address; - if (!wallet._changeAddresses.has(gapChangeAddress)) { - throw new Error(`Address ${gapChangeAddress} not found in wallet`); - } - wallet._gapChangeAddresses.push(gapChangeAddressInfo); - } - - // validate the outpoints and import them - for (const outpointInfo of serialized.outpoints) { - // check the zone is valid - wallet.validateZone(outpointInfo.zone); - // check the outpoint address is known to the wallet - if (!wallet._addresses.has(outpointInfo.address)) { - throw new Error(`Address ${outpointInfo.address} not found in wallet`); - } - const outpoint = outpointInfo.outpoint; - // TODO: implement a more robust check for Outpoint - // check the Outpoint fields are not empty - if (outpoint.Txhash == null || outpoint.Index == null || outpoint.Denomination == null) { - throw new Error(`Invalid Outpoint: ${JSON.stringify(outpoint)} `); - } - wallet._outpoints.push(outpointInfo); - } - return wallet; - - } + public getGapChangeAddressesForZone(zone: Zone): NeuteredAddressInfo[] { + this.validateZone(zone); + const gapChangeAddresses = this._gapChangeAddresses.filter((addressInfo) => addressInfo.zone === zone); + return gapChangeAddresses; + } + + public async signMessage(address: string, message: string | Uint8Array): Promise { + const addrNode = this._getHDNodeForAddress(address); + const privKey = addrNode.privateKey; + const digest = keccak_256(message); + const signature = schnorr.sign(digest, getBytes(privKey)); + return hexlify(signature); + } + + public async serialize(): Promise { + const hdwalletSerialized = await super.serialize(); + return { + outpoints: this._outpoints, + changeAddresses: Array.from(this._changeAddresses.values()), + gapAddresses: this._gapAddresses, + gapChangeAddresses: this._gapChangeAddresses, + ...hdwalletSerialized, + }; + } + + public static async deserialize(serialized: SerializedQiHDWallet): Promise { + super.validateSerializedWallet(serialized); + // create the wallet instance + const mnemonic = Mnemonic.fromPhrase(serialized.phrase); + const path = (this as any).parentPath(serialized.coinType); + const root = HDNodeWallet.fromMnemonic(mnemonic, path); + const wallet = new this(root); + + // import the addresses + wallet.importSerializedAddresses(wallet._addresses, serialized.addresses); + // import the change addresses + wallet.importSerializedAddresses(wallet._changeAddresses, serialized.changeAddresses); + + // import the gap addresses, verifying they already exist in the wallet + for (const gapAddressInfo of serialized.gapAddresses) { + const gapAddress = gapAddressInfo.address; + if (!wallet._addresses.has(gapAddress)) { + throw new Error(`Address ${gapAddress} not found in wallet`); + } + wallet._gapAddresses.push(gapAddressInfo); + } + // import the gap change addresses, verifying they already exist in the wallet + for (const gapChangeAddressInfo of serialized.gapChangeAddresses) { + const gapChangeAddress = gapChangeAddressInfo.address; + if (!wallet._changeAddresses.has(gapChangeAddress)) { + throw new Error(`Address ${gapChangeAddress} not found in wallet`); + } + wallet._gapChangeAddresses.push(gapChangeAddressInfo); + } + + // validate the outpoints and import them + for (const outpointInfo of serialized.outpoints) { + // check the zone is valid + wallet.validateZone(outpointInfo.zone); + // check the outpoint address is known to the wallet + if (!wallet._addresses.has(outpointInfo.address)) { + throw new Error(`Address ${outpointInfo.address} not found in wallet`); + } + const outpoint = outpointInfo.outpoint; + // TODO: implement a more robust check for Outpoint + // check the Outpoint fields are not empty + if (outpoint.Txhash == null || outpoint.Index == null || outpoint.Denomination == null) { + throw new Error(`Invalid Outpoint: ${JSON.stringify(outpoint)} `); + } + wallet._outpoints.push(outpointInfo); + } + return wallet; + } } diff --git a/src/wallet/quai-hdwallet.ts b/src/wallet/quai-hdwallet.ts index d2199da0..cd08456d 100644 --- a/src/wallet/quai-hdwallet.ts +++ b/src/wallet/quai-hdwallet.ts @@ -3,11 +3,12 @@ import { HDNodeWallet } from './hdnodewallet.js'; import { QuaiTransactionRequest, Provider, TransactionResponse } from '../providers/index.js'; import { resolveAddress } from '../address/index.js'; import { AllowedCoinType } from '../constants/index.js'; +import { SerializedHDWallet } from './hdwallet.js'; +import { Mnemonic } from './mnemonic.js'; export class QuaiHDWallet extends AbstractHDWallet { - protected static _version: number = 1; - + protected static _coinType: AllowedCoinType = 994; private constructor(root: HDNodeWallet, provider?: Provider) { @@ -31,8 +32,22 @@ export class QuaiHDWallet extends AbstractHDWallet { return await fromNodeConnected.sendTransaction(tx); } - public async signMessage(address: string, message: string | Uint8Array): Promise { + public async signMessage(address: string, message: string | Uint8Array): Promise { const addrNode = this._getHDNodeForAddress(address); return await addrNode.signMessage(message); } + + public static async deserialize(serialized: SerializedHDWallet): Promise { + super.validateSerializedWallet(serialized); + // create the wallet instance + const mnemonic = Mnemonic.fromPhrase(serialized.phrase); + const path = (this as any).parentPath(serialized.coinType); + const root = HDNodeWallet.fromMnemonic(mnemonic, path); + const wallet = new this(root); + + // import the addresses + wallet.importSerializedAddresses(wallet._addresses, serialized.addresses); + + return wallet; + } } From 56632479f1b8926ce0ce141677221f513d8e34aa Mon Sep 17 00:00:00 2001 From: Alejo Acosta Date: Tue, 18 Jun 2024 13:10:59 -0300 Subject: [PATCH 2/4] add method to validate imported outpoints --- src/wallet/qi-hdwallet.ts | 40 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts index d453b35a..bb9ff964 100644 --- a/src/wallet/qi-hdwallet.ts +++ b/src/wallet/qi-hdwallet.ts @@ -79,10 +79,8 @@ export class QiHDWallet extends AbstractHDWallet { } public importOutpoints(outpoints: OutpointInfo[]): void { - outpoints.forEach((outpoint) => { - this.validateZone(outpoint.zone); - this._outpoints.push(outpoint); - }); + this.validateOutpointInfo(outpoints); + this._outpoints.push(...outpoints); } public getOutpoints(zone: Zone): OutpointInfo[] { @@ -373,21 +371,27 @@ export class QiHDWallet extends AbstractHDWallet { } // validate the outpoints and import them - for (const outpointInfo of serialized.outpoints) { - // check the zone is valid - wallet.validateZone(outpointInfo.zone); - // check the outpoint address is known to the wallet - if (!wallet._addresses.has(outpointInfo.address)) { - throw new Error(`Address ${outpointInfo.address} not found in wallet`); + wallet.validateOutpointInfo(serialized.outpoints); + wallet._outpoints.push(...serialized.outpoints); + return wallet; + } + + private validateOutpointInfo(outpointInfo: OutpointInfo[]): void { + outpointInfo.forEach((info) => { + // validate zone + this.validateZone(info.zone); + // validate address + if (!this._addresses.has(info.address)) { + throw new Error(`Address ${info.address} not found in wallet`); } - const outpoint = outpointInfo.outpoint; - // TODO: implement a more robust check for Outpoint - // check the Outpoint fields are not empty - if (outpoint.Txhash == null || outpoint.Index == null || outpoint.Denomination == null) { - throw new Error(`Invalid Outpoint: ${JSON.stringify(outpoint)} `); + // validate account + if (info.account && !this._accounts.has(info.account)) { + throw new Error(`Account ${info.account} not found in wallet`); } - wallet._outpoints.push(outpointInfo); - } - return wallet; + // validate Outpoint + if (info.outpoint.Txhash == null || info.outpoint.Index == null || info.outpoint.Denomination == null) { + throw new Error(`Invalid Outpoint: ${JSON.stringify(info)} `); + } + }); } } From 340907a5f4153c638ea6efba4cb42af3a6a159a0 Mon Sep 17 00:00:00 2001 From: Alejo Acosta Date: Tue, 18 Jun 2024 13:20:27 -0300 Subject: [PATCH 3/4] add new method 'signTypedData' --- src/wallet/hdwallet.ts | 15 +++++++++++++-- src/wallet/quai-hdwallet.ts | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/wallet/hdwallet.ts b/src/wallet/hdwallet.ts index 93bc7499..c36b6896 100644 --- a/src/wallet/hdwallet.ts +++ b/src/wallet/hdwallet.ts @@ -251,8 +251,19 @@ export abstract class AbstractHDWallet { } } - // Returns the HD node that derives the address. - // If the address is not found in the wallet, an error is thrown. + /** + * Derives and returns the Hierarchical Deterministic (HD) node wallet associated with a given address. + * + * This method fetches the account and address information from the wallet's internal storage, derives the + * appropriate change node based on whether the address is a change address, and further derives the final HD node + * using the address index. + * + * @param {string} addr - The address for which to derive the HD node. + * + * @returns {HDNodeWallet} - The derived HD node wallet corresponding to the given address. + * @throws {Error} If the given address is not known to the wallet. + * @throws {Error} If the account associated with the address is not found. + */ protected _getHDNodeForAddress(addr: string): HDNodeWallet { const addressInfo = this._addresses.get(addr); if (!addressInfo) { diff --git a/src/wallet/quai-hdwallet.ts b/src/wallet/quai-hdwallet.ts index cd08456d..b7758252 100644 --- a/src/wallet/quai-hdwallet.ts +++ b/src/wallet/quai-hdwallet.ts @@ -5,6 +5,7 @@ import { resolveAddress } from '../address/index.js'; import { AllowedCoinType } from '../constants/index.js'; import { SerializedHDWallet } from './hdwallet.js'; import { Mnemonic } from './mnemonic.js'; +import { TypedDataDomain, TypedDataField } from '../hash/index.js'; export class QuaiHDWallet extends AbstractHDWallet { protected static _version: number = 1; @@ -50,4 +51,26 @@ export class QuaiHDWallet extends AbstractHDWallet { return wallet; } + + /** + * Signs typed data using the private key associated with the given address. + * + * @param {string} address - The address for which the typed data is to be signed. + * @param {TypedDataDomain} domain - The domain information of the typed data, defining the scope of the signature. + * @param {Record} types - The types of the data to be signed, mapping each data type name + * to its fields. + * @param {Record} value - The actual data to be signed. + * + * @returns {Promise} A promise that resolves to the signed data in string format. + * @throws {Error} If the address does not correspond to a valid HD node or if signing fails. + */ + public async signTypedData( + address: string, + domain: TypedDataDomain, + types: Record>, + value: Record, + ): Promise { + const addrNode = this._getHDNodeForAddress(address); + return addrNode.signTypedData(domain, types, value); + } } From 3e4c9b9c910710007376b066f1e1000fe102cf9f Mon Sep 17 00:00:00 2001 From: Alejo Acosta Date: Tue, 18 Jun 2024 15:43:16 -0300 Subject: [PATCH 4/4] add JSDOC comments to new methods --- src/wallet/hdwallet.ts | 48 +++++++++++++++++++++++++++++++++---- src/wallet/qi-hdwallet.ts | 42 ++++++++++++++++++++++++++++++-- src/wallet/quai-hdwallet.ts | 17 +++++++++++++ 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/wallet/hdwallet.ts b/src/wallet/hdwallet.ts index c36b6896..3b272a73 100644 --- a/src/wallet/hdwallet.ts +++ b/src/wallet/hdwallet.ts @@ -280,9 +280,24 @@ export abstract class AbstractHDWallet { return changeNode.deriveChild(addressInfo.index); } + /** + * Abstract method to sign a message using the private key associated with the given address. + * + * @param {string} address - The address for which the message is to be signed. + * @param {string | Uint8Array} message - The message to be signed, either as a string or Uint8Array. + * + * @returns {Promise} A promise that resolves to the signature of the message in hexadecimal string format. + * @throws {Error} If the method is not implemented in the subclass. + */ abstract signMessage(address: string, message: string | Uint8Array): Promise; - public async serialize(): Promise { + /** + * Serializes the HD wallet state into a format suitable for storage or transmission. + * + * @returns {SerializedHDWallet} An object representing the serialized state of the HD wallet, including version, + * mnemonic phrase, coin type, and addresses. + */ + public serialize(): SerializedHDWallet { const addresses = Array.from(this._addresses.values()); return { version: (this.constructor as any)._version, @@ -292,12 +307,28 @@ export abstract class AbstractHDWallet { }; } + /** + * Deserializes a serialized HD wallet object and reconstructs the wallet instance. This method must be implemented + * in the subclass. + * + * @param {SerializedHDWallet} _serialized - The serialized object representing the state of an HD wallet. + * + * @returns {AbstractHDWallet} An instance of AbstractHDWallet. + * @throws {Error} This method must be implemented in the subclass. + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars public static async deserialize(_serialized: SerializedHDWallet): Promise { throw new Error('deserialize method must be implemented in the subclass'); } - // This method is used to validate the version and coinType of the serialized wallet. + /** + * Validates the version and coinType of the serialized wallet. + * + * @param {SerializedHDWallet} serialized - The serialized wallet data to be validated. + * @throws {Error} If the version or coinType of the serialized wallet does not match the expected values. + * @protected + * @static + */ protected static validateSerializedWallet(serialized: SerializedHDWallet): void { if (serialized.version !== (this as any)._version) { throw new Error(`Invalid version ${serialized.version} for wallet (expected ${(this as any)._version})`); @@ -307,9 +338,16 @@ export abstract class AbstractHDWallet { } } - // This method is used to import addresses from a serialized wallet into the addresses map. - // Before adding the addresses, a validation is performed to ensure the address, public key and zone - // match the expected values. + /** + * Imports addresses from a serialized wallet into the addresses map. Before adding the addresses, a validation is + * performed to ensure the address, public key, and zone match the expected values. + * + * @param {Map} addressMap - The map where the addresses will be imported. + * @param {NeuteredAddressInfo[]} addresses - The array of addresses to be imported, each containing account, index, + * change, address, pubKey, and zone information. + * @throws {Error} If there is a mismatch between the expected and actual address, public key, or zone. + * @protected + */ protected importSerializedAddresses( addressMap: Map, addresses: NeuteredAddressInfo[], diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts index bb9ff964..f33f66c4 100644 --- a/src/wallet/qi-hdwallet.ts +++ b/src/wallet/qi-hdwallet.ts @@ -321,6 +321,15 @@ export class QiHDWallet extends AbstractHDWallet { return gapChangeAddresses; } + /** + * Signs a message using the private key associated with the given address. + * + * @param {string} address - The address for which the message is to be signed. + * @param {string | Uint8Array} message - The message to be signed, either as a string or Uint8Array. + * + * @returns {Promise} A promise that resolves to the signature of the message in hexadecimal string format. + * @throws {Error} If the address does not correspond to a valid HD node or if signing fails. + */ public async signMessage(address: string, message: string | Uint8Array): Promise { const addrNode = this._getHDNodeForAddress(address); const privKey = addrNode.privateKey; @@ -329,8 +338,14 @@ export class QiHDWallet extends AbstractHDWallet { return hexlify(signature); } - public async serialize(): Promise { - const hdwalletSerialized = await super.serialize(); + /** + * Serializes the HD wallet state into a format suitable for storage or transmission. + * + * @returns {SerializedQiHDWallet} An object representing the serialized state of the HD wallet, including + * outpoints, change addresses, gap addresses, and other inherited properties. + */ + public serialize(): SerializedQiHDWallet { + const hdwalletSerialized = super.serialize(); return { outpoints: this._outpoints, changeAddresses: Array.from(this._changeAddresses.values()), @@ -340,6 +355,15 @@ export class QiHDWallet extends AbstractHDWallet { }; } + /** + * Deserializes a serialized QiHDWallet object and reconstructs the wallet instance. + * + * @param {SerializedQiHDWallet} serialized - The serialized object representing the state of a QiHDWallet. + * + * @returns {Promise} A promise that resolves to a reconstructed QiHDWallet instance. + * @throws {Error} If the serialized data is invalid or if any addresses in the gap addresses or gap change + * addresses do not exist in the wallet. + */ public static async deserialize(serialized: SerializedQiHDWallet): Promise { super.validateSerializedWallet(serialized); // create the wallet instance @@ -376,6 +400,20 @@ export class QiHDWallet extends AbstractHDWallet { return wallet; } + /** + * Validates an array of OutpointInfo objects. + * + * This method checks the validity of each OutpointInfo object by performing the following validations: + * + * - Validates the zone using the `validateZone` method. + * - Checks if the address exists in the wallet. + * - Checks if the account (if provided) exists in the wallet. + * - Validates the Outpoint by ensuring that `Txhash`, `Index`, and `Denomination` are not null. + * + * @private + * @param {OutpointInfo[]} outpointInfo - An array of OutpointInfo objects to be validated. + * @throws {Error} If any of the validations fail, an error is thrown with a descriptive message. + */ private validateOutpointInfo(outpointInfo: OutpointInfo[]): void { outpointInfo.forEach((info) => { // validate zone diff --git a/src/wallet/quai-hdwallet.ts b/src/wallet/quai-hdwallet.ts index b7758252..75a22541 100644 --- a/src/wallet/quai-hdwallet.ts +++ b/src/wallet/quai-hdwallet.ts @@ -38,6 +38,23 @@ export class QuaiHDWallet extends AbstractHDWallet { return await addrNode.signMessage(message); } + /** + * Deserializes the given serialized HD wallet data into an instance of QuaiHDWallet. + * + * This method performs the following steps: + * + * - Validates the serialized wallet data. + * - Creates a new wallet instance using the mnemonic phrase and derivation path. + * - Imports the addresses into the wallet instance. + * + * @async + * @param {SerializedHDWallet} serialized - The serialized wallet data to be deserialized. + * + * @returns {Promise} A promise that resolves to an instance of QuaiHDWallet. + * @throws {Error} If validation of the serialized wallet data fails or if deserialization fails. + * @public + * @static + */ public static async deserialize(serialized: SerializedHDWallet): Promise { super.validateSerializedWallet(serialized); // create the wallet instance