diff --git a/src/abi/interaction.ts b/src/abi/interaction.ts index 2568dc7dc..e1bedc5f4 100644 --- a/src/abi/interaction.ts +++ b/src/abi/interaction.ts @@ -1,4 +1,4 @@ -import { Account } from "../account"; +import { Account } from "../accounts"; import { Address } from "../address"; import { Compatibility } from "../compatibility"; import { TRANSACTION_VERSION_DEFAULT } from "../constants"; diff --git a/src/account.ts b/src/account.ts deleted file mode 100644 index d371afdc6..000000000 --- a/src/account.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Address } from "./address"; -import { IAccountBalance, IAddress, INonce } from "./interface"; - -/** - * An abstraction representing an account (user or Smart Contract) on the Network. - */ -export class Account { - /** - * The address of the account. - */ - readonly address: IAddress = Address.empty(); - - /** - * The nonce of the account (the account sequence number). - */ - nonce: INonce = 0; - - /** - * The balance of the account. - */ - balance: IAccountBalance = "0"; - - /** - * Creates an account object from an address - */ - constructor(address: IAddress) { - this.address = address; - } - - /** - * Updates account properties (such as nonce, balance). - */ - update(obj: { nonce: INonce; balance: IAccountBalance }) { - this.nonce = obj.nonce; - this.balance = obj.balance; - } - - /** - * Increments (locally) the nonce (the account sequence number). - */ - incrementNonce() { - this.nonce = this.nonce.valueOf() + 1; - } - - /** - * Gets then increments (locally) the nonce (the account sequence number). - */ - getNonceThenIncrement(): INonce { - let nonce = this.nonce; - this.nonce = this.nonce.valueOf() + 1; - return nonce; - } - - /** - * Converts the account to a pretty, plain JavaScript object. - */ - toJSON(): any { - return { - address: this.address.bech32(), - nonce: this.nonce.valueOf(), - balance: this.balance.toString(), - }; - } -} diff --git a/src/accountManagement/accountController.ts b/src/accountManagement/accountController.ts new file mode 100644 index 000000000..91ca55150 --- /dev/null +++ b/src/accountManagement/accountController.ts @@ -0,0 +1,62 @@ +import { IAccount } from "../accounts/interfaces"; +import { Transaction } from "../transaction"; +import { TransactionComputer } from "../transactionComputer"; +import { TransactionsFactoryConfig } from "../transactionsFactories"; +import { AccountTransactionsFactory } from "./accountTransactionsFactory"; +import { SaveKeyValueInput, SetGuardianInput } from "./resources"; + +export class AccountController { + private factory: AccountTransactionsFactory; + private txComputer: TransactionComputer; + + constructor(options: { chainID: string }) { + this.factory = new AccountTransactionsFactory({ + config: new TransactionsFactoryConfig(options), + }); + this.txComputer = new TransactionComputer(); + } + + async createTransactionForSavingKeyValue( + sender: IAccount, + nonce: bigint, + options: SaveKeyValueInput, + ): Promise { + const transaction = this.factory.createTransactionForSavingKeyValue(sender.address, options); + + transaction.nonce = nonce; + transaction.signature = await sender.sign(this.txComputer.computeBytesForSigning(transaction)); + + return transaction; + } + + async createTransactionForSettingGuardian( + sender: IAccount, + nonce: bigint, + options: SetGuardianInput, + ): Promise { + const transaction = this.factory.createTransactionForSettingGuardian(sender.address, options); + + transaction.nonce = nonce; + transaction.signature = await sender.sign(this.txComputer.computeBytesForSigning(transaction)); + + return transaction; + } + + async createTransactionForGuardingAccount(sender: IAccount, nonce: bigint): Promise { + const transaction = this.factory.createTransactionForGuardingAccount(sender.address); + + transaction.nonce = nonce; + transaction.signature = await sender.sign(this.txComputer.computeBytesForSigning(transaction)); + + return transaction; + } + + async createTransactionForUnguardingAccount(sender: IAccount, nonce: bigint): Promise { + const transaction = this.factory.createTransactionForUnguardingAccount(sender.address); + + transaction.nonce = nonce; + transaction.signature = await sender.sign(this.txComputer.computeBytesForSigning(transaction)); + + return transaction; + } +} diff --git a/src/transactionsFactories/accountTransactionsFactory.spec.ts b/src/accountManagement/accountTransactionsFactory.spec.ts similarity index 93% rename from src/transactionsFactories/accountTransactionsFactory.spec.ts rename to src/accountManagement/accountTransactionsFactory.spec.ts index 6dc4ec8ee..78660dd6f 100644 --- a/src/transactionsFactories/accountTransactionsFactory.spec.ts +++ b/src/accountManagement/accountTransactionsFactory.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { Address } from "../address"; -import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; +import { TransactionsFactoryConfig } from "../transactionsFactories"; import { AccountTransactionsFactory } from "./accountTransactionsFactory"; describe("test account transactions factory", function () { @@ -11,8 +11,7 @@ describe("test account transactions factory", function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const keyValuePairs = new Map([[Buffer.from("key0"), Buffer.from("value0")]]); - const transaction = factory.createTransactionForSavingKeyValue({ - sender: sender, + const transaction = factory.createTransactionForSavingKeyValue(sender, { keyValuePairs: keyValuePairs, }); @@ -29,8 +28,7 @@ describe("test account transactions factory", function () { const guardian = Address.fromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const serviceID = "MultiversXTCSService"; - const transaction = factory.createTransactionForSettingGuardian({ - sender: sender, + const transaction = factory.createTransactionForSettingGuardian(sender, { guardianAddress: guardian, serviceID: serviceID, }); @@ -49,9 +47,7 @@ describe("test account transactions factory", function () { it("should create 'Transaction' for guarding account", async function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const transaction = factory.createTransactionForGuardingAccount({ - sender: sender, - }); + const transaction = factory.createTransactionForGuardingAccount(sender); assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); @@ -64,9 +60,7 @@ describe("test account transactions factory", function () { it("should create 'Transaction' for unguarding account", async function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const transaction = factory.createTransactionForUnguardingAccount({ - sender: sender, - }); + const transaction = factory.createTransactionForUnguardingAccount(sender); assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); diff --git a/src/transactionsFactories/accountTransactionsFactory.ts b/src/accountManagement/accountTransactionsFactory.ts similarity index 77% rename from src/transactionsFactories/accountTransactionsFactory.ts rename to src/accountManagement/accountTransactionsFactory.ts index 01784bd21..f9bbc5a16 100644 --- a/src/transactionsFactories/accountTransactionsFactory.ts +++ b/src/accountManagement/accountTransactionsFactory.ts @@ -1,7 +1,8 @@ import { Address } from "../address"; import { IAddress } from "../interface"; import { Transaction } from "../transaction"; -import { TransactionBuilder } from "./transactionBuilder"; +import { TransactionBuilder } from "../transactionsFactories/transactionBuilder"; +import { SaveKeyValueInput, SetGuardianInput } from "./resources"; interface IConfig { chainID: string; @@ -22,10 +23,7 @@ export class AccountTransactionsFactory { this.config = options.config; } - createTransactionForSavingKeyValue(options: { - sender: IAddress; - keyValuePairs: Map; - }): Transaction { + createTransactionForSavingKeyValue(sender: IAddress, options: SaveKeyValueInput): Transaction { const functionName = "SaveKeyValue"; const keyValueParts = this.computeDataPartsForSavingKeyValue(options.keyValuePairs); const dataParts = [functionName, ...keyValueParts]; @@ -33,8 +31,8 @@ export class AccountTransactionsFactory { return new TransactionBuilder({ config: this.config, - sender: options.sender, - receiver: options.sender, + sender: sender, + receiver: sender, dataParts: dataParts, gasLimit: extraGas, addDataMovementGas: true, @@ -63,11 +61,7 @@ export class AccountTransactionsFactory { return dataParts; } - createTransactionForSettingGuardian(options: { - sender: IAddress; - guardianAddress: IAddress; - serviceID: string; - }): Transaction { + createTransactionForSettingGuardian(sender: IAddress, options: SetGuardianInput): Transaction { const dataParts = [ "SetGuardian", Address.fromBech32(options.guardianAddress.bech32()).toHex(), @@ -76,34 +70,34 @@ export class AccountTransactionsFactory { return new TransactionBuilder({ config: this.config, - sender: options.sender, - receiver: options.sender, + sender: sender, + receiver: sender, dataParts: dataParts, gasLimit: this.config.gasLimitSetGuardian, addDataMovementGas: true, }).build(); } - createTransactionForGuardingAccount(options: { sender: IAddress }): Transaction { + createTransactionForGuardingAccount(sender: IAddress): Transaction { const dataParts = ["GuardAccount"]; return new TransactionBuilder({ config: this.config, - sender: options.sender, - receiver: options.sender, + sender: sender, + receiver: sender, dataParts: dataParts, gasLimit: this.config.gasLimitGuardAccount, addDataMovementGas: true, }).build(); } - createTransactionForUnguardingAccount(options: { sender: IAddress }): Transaction { + createTransactionForUnguardingAccount(sender: IAddress): Transaction { const dataParts = ["UnGuardAccount"]; return new TransactionBuilder({ config: this.config, - sender: options.sender, - receiver: options.sender, + sender: sender, + receiver: sender, dataParts: dataParts, gasLimit: this.config.gasLimitUnguardAccount, addDataMovementGas: true, diff --git a/src/accountManagement/index.ts b/src/accountManagement/index.ts new file mode 100644 index 000000000..87098af89 --- /dev/null +++ b/src/accountManagement/index.ts @@ -0,0 +1,2 @@ +export * from "./accountController"; +export * from "./accountTransactionsFactory"; diff --git a/src/accountManagement/resources.ts b/src/accountManagement/resources.ts new file mode 100644 index 000000000..aae540e73 --- /dev/null +++ b/src/accountManagement/resources.ts @@ -0,0 +1,4 @@ +import { IAddress } from "../interface"; + +export type SetGuardianInput = { guardianAddress: IAddress; serviceID: string }; +export type SaveKeyValueInput = { keyValuePairs: Map }; diff --git a/src/accounts/account.ts b/src/accounts/account.ts new file mode 100644 index 000000000..e164a34b2 --- /dev/null +++ b/src/accounts/account.ts @@ -0,0 +1,113 @@ +import { Address } from "../address"; +import { LibraryConfig } from "../config"; +import { IAccountBalance, INonce } from "../interface"; +import { Mnemonic, UserSigner, UserWallet } from "../wallet"; +import { IAccount } from "./interfaces"; + +/** + * An abstraction representing an account (user or Smart Contract) on the Network. + */ +export class Account implements IAccount { + /** + * The address of the account. + */ + readonly address: Address = Address.empty(); + + /** + * The nonce of the account (the account sequence number). + */ + nonce: INonce = 0; + + /** + * @deprecated This will be remove with the next release as not needed anymore. + */ + /** + * The balance of the account. + */ + balance: IAccountBalance = "0"; + + /** + * The signer of the account. + */ + private signer?: UserSigner; + + /** + * Creates an account object from an address + */ + constructor(address: Address, signer?: UserSigner) { + this.address = address; + this.signer = signer; + } + + /** + * @deprecated This will be remove with the next release as not needed anymore. + */ + /** + * Updates account properties (such as nonce, balance). + */ + update(obj: { nonce: INonce; balance: IAccountBalance }) { + this.nonce = obj.nonce; + this.balance = obj.balance; + } + + /** + * Increments (locally) the nonce (the account sequence number). + */ + incrementNonce() { + this.nonce = this.nonce.valueOf() + 1; + } + + /** + * Gets then increments (locally) the nonce (the account sequence number). + */ + getNonceThenIncrement(): INonce { + let nonce = this.nonce; + this.nonce = this.nonce.valueOf() + 1; + return nonce; + } + + /** + * Converts the account to a pretty, plain JavaScript object. + */ + toJSON(): any { + return { + address: this.address.bech32(), + nonce: this.nonce.valueOf(), + balance: this.balance.toString(), + }; + } + + sign(data: Uint8Array): Promise { + if (!this.signer) { + throw new Error("Signer not initialiezed, please provide the signer when account is instantiated"); + } + return this.signer.sign(data); + } + + static newFromPem(path: string, index: number = 0, hrp: string = LibraryConfig.DefaultAddressHrp): Account { + const userSigner = UserSigner.fromPem(path, index); + return new Account(userSigner.getAddress(hrp), userSigner); + } + + static newFromMnemonic( + mnemonic: string, + addressIndex: number = 0, + hrp: string = LibraryConfig.DefaultAddressHrp, + ): Account { + const mnemonicHandler = Mnemonic.fromString(mnemonic); + const secretKey = mnemonicHandler.deriveKey(addressIndex); + const userSigner = new UserSigner(secretKey); + return new Account(userSigner.getAddress(hrp), userSigner); + } + + static newFromKeystore( + filePath: string, + password: string, + addressIndex?: number, + hrp: string = LibraryConfig.DefaultAddressHrp, + ): Account { + const secretKey = UserWallet.loadSecretKey(filePath, password, addressIndex); + const userSigner = new UserSigner(secretKey); + return new Account(userSigner.getAddress(hrp), userSigner); + } +} diff --git a/src/accounts/index.ts b/src/accounts/index.ts new file mode 100644 index 000000000..5b58bd041 --- /dev/null +++ b/src/accounts/index.ts @@ -0,0 +1,2 @@ +export * from "./account"; +export * from "./interfaces"; diff --git a/src/accounts/interfaces.ts b/src/accounts/interfaces.ts new file mode 100644 index 000000000..482b99900 --- /dev/null +++ b/src/accounts/interfaces.ts @@ -0,0 +1,7 @@ +import { Address } from "../address"; + +export interface IAccount { + readonly address: Address; + + sign(data: Uint8Array): Promise; +} diff --git a/src/index.ts b/src/index.ts index 08a84ad38..83fb068d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,8 @@ require("./globals"); export * from "./abi"; -export * from "./account"; +export * from "./accountManagement"; +export * from "./accounts"; export * from "./adapters"; export * from "./address"; export * from "./asyncTimer"; diff --git a/src/testdata/testwallets/alice.json b/src/testdata/testwallets/alice.json index 9e83170cf..18c4ea1e9 100644 --- a/src/testdata/testwallets/alice.json +++ b/src/testdata/testwallets/alice.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "0dc10c02-b59b-4bac-9710-6b2cfa4284ba", "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", diff --git a/src/testdata/testwallets/bob.json b/src/testdata/testwallets/bob.json index 439b394a5..9efb41109 100644 --- a/src/testdata/testwallets/bob.json +++ b/src/testdata/testwallets/bob.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "85fdc8a7-7119-479d-b7fb-ab4413ed038d", "address": "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", "bech32": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", diff --git a/src/testdata/testwallets/carol.json b/src/testdata/testwallets/carol.json index 3614a5ba2..1014a8230 100644 --- a/src/testdata/testwallets/carol.json +++ b/src/testdata/testwallets/carol.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "65894f35-d142-41d2-9335-6ad02e0ed0be", "address": "b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", "bech32": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", diff --git a/src/testdata/testwallets/dan.json b/src/testdata/testwallets/dan.json index 15eb9f793..9b6cf26b6 100644 --- a/src/testdata/testwallets/dan.json +++ b/src/testdata/testwallets/dan.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "fc8b9b89-2227-41ec-afd1-5e6853feb7b2", "address": "b13a017423c366caff8cecfb77a12610a130f4888134122c7937feae0d6d7d17", "bech32": "erd1kyaqzaprcdnv4luvanah0gfxzzsnpaygsy6pytrexll2urtd05ts9vegu7", diff --git a/src/testdata/testwallets/eve.json b/src/testdata/testwallets/eve.json index b95f2582a..507778be8 100644 --- a/src/testdata/testwallets/eve.json +++ b/src/testdata/testwallets/eve.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "e770c455-a23b-4dcd-a7a5-0e22375dc233", "address": "3af8d9c9423b2577c6252722c1d90212a4111f7203f9744f76fcfa1d0a310033", "bech32": "erd18tudnj2z8vjh0339yu3vrkgzz2jpz8mjq0uhgnmklnap6z33qqeszq2yn4", diff --git a/src/testdata/testwallets/frank.json b/src/testdata/testwallets/frank.json index afb4a0f6b..74f69eadd 100644 --- a/src/testdata/testwallets/frank.json +++ b/src/testdata/testwallets/frank.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "df70f3ef-bb40-4afd-8751-77b26b29356d", "address": "b37f5d130beb8885b90ab574a8bfcdd894ca531a7d3d1f3431158d77d6185fbb", "bech32": "erd1kdl46yctawygtwg2k462307dmz2v55c605737dp3zkxh04sct7asqylhyv", diff --git a/src/testdata/testwallets/grace.json b/src/testdata/testwallets/grace.json index 81d640dcf..a438757b7 100644 --- a/src/testdata/testwallets/grace.json +++ b/src/testdata/testwallets/grace.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "9aff338f-f504-403c-86cd-5e623bc81c42", "address": "1e8a8b6b49de5b7be10aaa158a5a6a4abb4b56cc08f524bb5e6cd5f211ad3e13", "bech32": "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede", diff --git a/src/testdata/testwallets/heidi.json b/src/testdata/testwallets/heidi.json index 7608b981f..d9799fdd1 100644 --- a/src/testdata/testwallets/heidi.json +++ b/src/testdata/testwallets/heidi.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "1b55836f-946f-4dc3-946d-3c27e5096873", "address": "6e224118d9068ae626878a1cfbebcb6a95a4715db86d1b51e06a04226cf30fd6", "bech32": "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha", diff --git a/src/testdata/testwallets/ivan.json b/src/testdata/testwallets/ivan.json index ba0f7f34b..5d2b1eec4 100644 --- a/src/testdata/testwallets/ivan.json +++ b/src/testdata/testwallets/ivan.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "0b80d732-d4e6-4145-b5bb-698bdd323b3c", "address": "899451b361a83e89d73b4096d3c90c209b27874c9c7cb01bb08b0bb4dc15693d", "bech32": "erd13x29rvmp4qlgn4emgztd8jgvyzdj0p6vn37tqxas3v9mfhq4dy7shalqrx", diff --git a/src/testdata/testwallets/judy.json b/src/testdata/testwallets/judy.json index 286c9ffd7..6d64175bb 100644 --- a/src/testdata/testwallets/judy.json +++ b/src/testdata/testwallets/judy.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "a3108434-5d2c-4dca-9f94-29e822697e20", "address": "4a101a0f8f95f1218683900801cd971c6028b1597a771b2ed367d1ede09d9d2a", "bech32": "erd1fggp5ru0jhcjrp5rjqyqrnvhr3sz3v2e0fm3ktknvlg7mcyan54qzccnan", diff --git a/src/testdata/testwallets/mallory.json b/src/testdata/testwallets/mallory.json index 1416c2f14..bf9c1490e 100644 --- a/src/testdata/testwallets/mallory.json +++ b/src/testdata/testwallets/mallory.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "6756aa76-5934-4dc6-90c7-c261db98fe44", "address": "1454931ffa758ab35654a5206b28e9dbf1fb8df8f9ced093bb2887eb39f7e7af", "bech32": "erd1z32fx8l6wk9tx4j555sxk28fm0clhr0cl88dpyam9zr7kw0hu7hsx2j524", diff --git a/src/testdata/testwallets/mike.json b/src/testdata/testwallets/mike.json index d46a9e7bf..6a1bbc3a3 100644 --- a/src/testdata/testwallets/mike.json +++ b/src/testdata/testwallets/mike.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "3f6adbc3-1215-4c31-9a61-a049b430e6f7", "address": "e32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", "bech32": "erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa", diff --git a/src/testutils/networkProviders.ts b/src/testutils/networkProviders.ts index 67ed2a4d1..18c8f1b25 100644 --- a/src/testutils/networkProviders.ts +++ b/src/testutils/networkProviders.ts @@ -1,3 +1,4 @@ +import { Query } from "../abi"; import { IAddress } from "../interface"; import { IAccountOnNetwork, @@ -7,7 +8,7 @@ import { ITransactionStatus, } from "../interfaceOfNetwork"; import { ApiNetworkProvider, ProxyNetworkProvider } from "../networkProviders"; -import { Query } from "../smartcontracts/query"; + import { Transaction } from "../transaction"; export function createLocalnetProvider(): INetworkProvider { diff --git a/src/testutils/wallets.ts b/src/testutils/wallets.ts index e332cec0c..c5e77ccb8 100644 --- a/src/testutils/wallets.ts +++ b/src/testutils/wallets.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -import { Account } from "../account"; +import { Account } from "../accounts"; import { Address } from "../address"; import { IAddress } from "../interface"; import { IAccountOnNetwork } from "../interfaceOfNetwork"; diff --git a/src/transactionsFactories/index.ts b/src/transactionsFactories/index.ts index fd9df3521..075c6b33a 100644 --- a/src/transactionsFactories/index.ts +++ b/src/transactionsFactories/index.ts @@ -1,7 +1,7 @@ +export * from "../accountManagement/accountTransactionsFactory"; export * from "./delegationTransactionsFactory"; export * from "./relayedTransactionsFactory"; export * from "./smartContractTransactionsFactory"; export * from "./tokenManagementTransactionsFactory"; export * from "./transactionsFactoryConfig"; export * from "./transferTransactionsFactory"; -export * from "./accountTransactionsFactory"; diff --git a/src/wallet/userVerifier.ts b/src/wallet/userVerifier.ts index ca56585a0..b143c760e 100644 --- a/src/wallet/userVerifier.ts +++ b/src/wallet/userVerifier.ts @@ -1,31 +1,28 @@ +import { Address } from "../address"; import { UserPublicKey } from "./userKeys"; -interface IAddress { - pubkey(): Buffer; -} - /** * ed25519 signature verification */ export class UserVerifier { - publicKey: UserPublicKey; + publicKey: UserPublicKey; - constructor(publicKey: UserPublicKey) { - this.publicKey = publicKey; - } + constructor(publicKey: UserPublicKey) { + this.publicKey = publicKey; + } - static fromAddress(address: IAddress): UserVerifier { - let publicKey = new UserPublicKey(address.pubkey()); - return new UserVerifier(publicKey); - } + static fromAddress(address: Address): UserVerifier { + let publicKey = new UserPublicKey(address.getPublicKey()); + return new UserVerifier(publicKey); + } - /** - * - * @param data the raw data to be verified (e.g. an already-serialized enveloped message) - * @param signature the signature to be verified - * @returns true if the signature is valid, false otherwise - */ - verify(data: Buffer | Uint8Array, signature: Buffer | Uint8Array): boolean { - return this.publicKey.verify(data, signature); - } + /** + * + * @param data the raw data to be verified (e.g. an already-serialized enveloped message) + * @param signature the signature to be verified + * @returns true if the signature is valid, false otherwise + */ + verify(data: Buffer | Uint8Array, signature: Buffer | Uint8Array): boolean { + return this.publicKey.verify(data, signature); + } } diff --git a/src/wallet/userWallet.ts b/src/wallet/userWallet.ts index 743f4ac69..3d505ff60 100644 --- a/src/wallet/userWallet.ts +++ b/src/wallet/userWallet.ts @@ -1,4 +1,7 @@ +import { readFileSync } from "fs"; +import path from "path"; import { Err } from "../errors"; +import { Logger } from "../logger"; import { CipherAlgorithm, Decryptor, EncryptedData, Encryptor, KeyDerivationFunction, Randomness } from "./crypto"; import { ScryptKeyDerivationParams } from "./crypto/derivationParams"; import { Mnemonic } from "./mnemonic"; @@ -77,6 +80,31 @@ export class UserWallet { }); } + static loadSecretKey(filePath: string, password: string, addressIndex?: number): UserSecretKey { + // Load and parse the keystore file + const keyFileJson = readFileSync(path.resolve(filePath), "utf8"); + const keyFileObject = JSON.parse(keyFileJson); + const kind = keyFileObject.kind || UserWalletKind.SecretKey.valueOf(); + + Logger.debug(`UserWallet.loadSecretKey(), kind = ${kind}`); + + let secretKey: UserSecretKey; + + if (kind === UserWalletKind.SecretKey.valueOf()) { + if (addressIndex !== undefined) { + throw new Error("address_index must not be provided when kind == 'secretKey'"); + } + secretKey = UserWallet.decryptSecretKey(keyFileObject, password); + } else if (kind === UserWalletKind.Mnemonic.valueOf()) { + const mnemonic = UserWallet.decryptMnemonic(keyFileObject, password); + secretKey = mnemonic.deriveKey(addressIndex || 0); + } else { + throw new Error(`Unknown kind: ${kind}`); + } + + return secretKey; + } + static decrypt(keyFileObject: any, password: string, addressIndex?: number): UserSecretKey { const kind = keyFileObject.kind || UserWalletKind.SecretKey; diff --git a/src/wallet/users.spec.ts b/src/wallet/users.spec.ts index 976f4ef85..35f0b7632 100644 --- a/src/wallet/users.spec.ts +++ b/src/wallet/users.spec.ts @@ -1,4 +1,5 @@ import { assert } from "chai"; +import path from "path"; import { ErrBadMnemonicEntropy, ErrInvariantFailed } from "../errors"; import { TestMessage } from "./../testutils/message"; import { TestTransaction } from "./../testutils/transaction"; @@ -17,15 +18,17 @@ import { UserSigner } from "./userSigner"; import { UserVerifier } from "./userVerifier"; import { UserWallet } from "./userWallet"; -describe("test user wallets", async () => { +describe("test user wallets", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; - let password: string = await loadPassword(); - const dummyMnemonic = await loadMnemonic(); + let password: string; + let dummyMnemonic: string; before(async function () { alice = await loadTestWallet("alice"); bob = await loadTestWallet("bob"); carol = await loadTestWallet("carol"); + password = await loadPassword(); + dummyMnemonic = await loadMnemonic(); }); it("should generate mnemonic", () => { @@ -74,28 +77,28 @@ describe("test user wallets", async () => { const mnemonic = Mnemonic.fromString(DummyMnemonicOf12Words); assert.equal( - mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), + mnemonic.deriveKey(0).generatePublicKey().toAddress().toBech32(), "erd1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqg4g7na", ); assert.equal( - mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), + mnemonic.deriveKey(1).generatePublicKey().toAddress().toBech32(), "erd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqnts4zs09p", ); assert.equal( - mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), + mnemonic.deriveKey(2).generatePublicKey().toAddress().toBech32(), "erd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksptewtj", ); assert.equal( - mnemonic.deriveKey(0).generatePublicKey().toAddress("test").bech32(), + mnemonic.deriveKey(0).generatePublicKey().toAddress("test").toBech32(), "test1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqc6tnnf", ); assert.equal( - mnemonic.deriveKey(1).generatePublicKey().toAddress("xerd").bech32(), + mnemonic.deriveKey(1).generatePublicKey().toAddress("xerd").toBech32(), "xerd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqntsj4adj4", ); assert.equal( - mnemonic.deriveKey(2).generatePublicKey().toAddress("yerd").bech32(), + mnemonic.deriveKey(2).generatePublicKey().toAddress("yerd").toBech32(), "yerd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksn8p0n5", ); }); @@ -151,9 +154,9 @@ describe("test user wallets", async () => { let carolKeyFile = UserWallet.fromSecretKey({ secretKey: carolSecretKey, password: password }); console.timeEnd("encrypt"); - assert.equal(aliceKeyFile.toJSON().bech32, alice.address.bech32()); - assert.equal(bobKeyFile.toJSON().bech32, bob.address.bech32()); - assert.equal(carolKeyFile.toJSON().bech32, carol.address.bech32()); + assert.equal(aliceKeyFile.toJSON().bech32, alice.address.toBech32()); + assert.equal(bobKeyFile.toJSON().bech32, bob.address.toBech32()); + assert.equal(carolKeyFile.toJSON().bech32, carol.address.toBech32()); console.time("decrypt"); assert.deepEqual(UserWallet.decryptSecretKey(aliceKeyFile.toJSON(), password), aliceSecretKey); @@ -203,7 +206,7 @@ describe("test user wallets", async () => { const secretKey = UserWallet.decryptSecretKey(keyFileObject, password); assert.equal( - secretKey.generatePublicKey().toAddress().bech32(), + secretKey.generatePublicKey().toAddress().toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ); }); @@ -216,15 +219,15 @@ describe("test user wallets", async () => { assert.equal(json.version, 4); assert.equal(json.kind, "mnemonic"); - assert.isUndefined(json.bech32); + assert.isUndefined(json.toBech32); const mnemonic = UserWallet.decryptMnemonic(json, password); const mnemonicText = mnemonic.toString(); assert.equal(mnemonicText, dummyMnemonic); - assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), alice.address.bech32()); - assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), bob.address.bech32()); - assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), carol.address.bech32()); + assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().toBech32(), alice.address.toBech32()); + assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress().toBech32(), bob.address.toBech32()); + assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().toBech32(), carol.address.toBech32()); // With provided randomness, in order to reproduce our test wallets const expectedDummyWallet = await loadTestKeystore("withDummyMnemonic.json"); @@ -241,12 +244,23 @@ describe("test user wallets", async () => { assert.deepEqual(dummyWallet.toJSON(), expectedDummyWallet); }); - it("should loadSecretKey, but without 'kind' field", async function () { + it("should create user wallet from secret key, but without 'kind' field", async function () { const keyFileObject = await loadTestKeystore("withoutKind.json"); const secretKey = UserWallet.decrypt(keyFileObject, password); assert.equal( - secretKey.generatePublicKey().toAddress().bech32(), + secretKey.generatePublicKey().toAddress().toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + }); + + it("should loadSecretKey, but without 'kind' field", async function () { + const testdataPath = path.resolve(__dirname, "..", "testdata/testwallets"); + const keystorePath = path.resolve(testdataPath, "withoutKind.json"); + const secretKey = UserWallet.loadSecretKey(keystorePath, password); + + assert.equal( + secretKey.generatePublicKey().toAddress().toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ); }); @@ -264,15 +278,15 @@ describe("test user wallets", async () => { const keyFileObject = await loadTestKeystore("withDummyMnemonic.json"); assert.equal( - UserWallet.decrypt(keyFileObject, password, 0).generatePublicKey().toAddress().bech32(), + UserWallet.decrypt(keyFileObject, password, 0).generatePublicKey().toAddress().toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ); assert.equal( - UserWallet.decrypt(keyFileObject, password, 1).generatePublicKey().toAddress().bech32(), + UserWallet.decrypt(keyFileObject, password, 1).generatePublicKey().toAddress().toBech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", ); assert.equal( - UserWallet.decrypt(keyFileObject, password, 2).generatePublicKey().toAddress().bech32(), + UserWallet.decrypt(keyFileObject, password, 2).generatePublicKey().toAddress().toBech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", ); }); @@ -468,40 +482,40 @@ describe("test user wallets", async () => { const keyFileObjectWithSecretKey = await loadTestKeystore("withDummySecretKey.json"); assert.equal( - UserSigner.fromWallet(keyFileObjectWithoutKind, password).getAddress().bech32(), + UserSigner.fromWallet(keyFileObjectWithoutKind, password).getAddress().toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ); assert.equal( - UserSigner.fromWallet(keyFileObjectWithMnemonic, password).getAddress().bech32(), + UserSigner.fromWallet(keyFileObjectWithMnemonic, password).getAddress().toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ); assert.equal( - UserSigner.fromWallet(keyFileObjectWithSecretKey, password).getAddress().bech32(), + UserSigner.fromWallet(keyFileObjectWithSecretKey, password).getAddress().toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ); assert.equal( - UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress().bech32(), + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress().toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ); assert.equal( - UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress().bech32(), + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress().toBech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", ); assert.equal( - UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().bech32(), + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().toBech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", ); assert.equal( - UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress("test").bech32(), + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress("test").toBech32(), "test1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ss5hqhtr", ); assert.equal( - UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress("xerd").bech32(), + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress("xerd").toBech32(), "xerd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruq9thc9j", ); assert.equal( - UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress("yerd").bech32(), + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress("yerd").toBech32(), "yerd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaqgh23pp", ); });