diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index cfff8cf806..0e143eec4c 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -1,6 +1,6 @@ { "name": "ironfish", - "version": "2.7.0", + "version": "2.6.0", "description": "CLI for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -59,8 +59,8 @@ "oclif:version": "oclif readme && git add README.md" }, "dependencies": { - "@ironfish/rust-nodejs": "2.7.0", - "@ironfish/sdk": "2.7.0", + "@ironfish/rust-nodejs": "2.6.0", + "@ironfish/sdk": "2.6.0", "@ledgerhq/hw-transport-node-hid": "6.29.1", "@oclif/core": "4.0.11", "@oclif/plugin-help": "6.2.5", @@ -69,8 +69,6 @@ "@types/keccak": "3.0.4", "@types/tar": "6.1.1", "@zondax/ledger-ironfish": "0.1.2", - "@zondax/ledger-ironfish-dkg": "npm:@zondax/ledger-ironfish@0.4.0", - "@zondax/ledger-js": "^1.0.1", "axios": "1.7.2", "bech32": "2.0.0", "blessed": "0.1.81", diff --git a/ironfish-cli/src/commands/wallet/chainport/send.ts b/ironfish-cli/src/commands/wallet/chainport/send.ts index bcce482551..879839afc2 100644 --- a/ironfish-cli/src/commands/wallet/chainport/send.ts +++ b/ironfish-cli/src/commands/wallet/chainport/send.ts @@ -2,10 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { Asset } from '@ironfish/rust-nodejs' import { CreateTransactionRequest, CurrencyUtils, - MAINNET, RawTransaction, RawTransactionSerde, RpcAsset, @@ -84,8 +84,8 @@ export class BridgeCommand extends IronfishCommand { const networkId = (await client.chain.getNetworkInfo()).content.networkId - if (networkId !== TESTNET.id && networkId !== MAINNET.id) { - this.error(`Chainport transactions are only available on testnet and mainnet.`) + if (networkId !== TESTNET.id) { + this.error(`Chainport transactions are only available on testnet.`) } if (!flags.offline) { @@ -184,31 +184,22 @@ export class BridgeCommand extends IronfishCommand { const tokens = await fetchChainportVerifiedTokens(networkId) - const tokenNames = tokens.map( - (t, index) => `${index + 1}. ${t.name} (${t.symbol}) - ${t.web3_address}`, - ) - - if (!assetId) { + if (assetId == null) { const asset = await ui.assetPrompt(client, from, { action: 'send', showNativeAsset: true, showNonCreatorAsset: true, - showSingleAssetChoice: true, + showSingleAssetChoice: false, filter: (asset) => { return tokens.some((t) => t.web3_address === asset.id) }, }) - if (!asset) { - this.logger.error( - `No supported Chainport asset found for this account. Here are the supported tokens: \n\n${tokenNames.join( - '\n', - )}\n`, - ) - this.exit(1) - } + assetId = asset?.id - assetId = asset.id + if (!assetId) { + assetId = Asset.nativeId().toString('hex') + } } const asset: ChainportVerifiedToken | undefined = tokens.find( @@ -216,12 +207,15 @@ export class BridgeCommand extends IronfishCommand { ) if (!asset) { - this.logger.error( - `Asset ${assetId} not supported by Chainport. Here are the supported tokens: \n\n${tokenNames.join( + const names = tokens.map( + (t, index) => `${index + 1}. ${t.name} (${t.symbol}) - ${t.web3_address}`, + ) + + this.error( + `Asset ${assetId} not supported by Chainport. Here are the supported tokens: \n\n${names.join( '\n', )}\n`, ) - this.exit(1) } const targetNetworks = asset.target_networks diff --git a/ironfish-cli/src/commands/wallet/decrypt.ts b/ironfish-cli/src/commands/wallet/decrypt.ts index 8e512cecc7..177fc07ef6 100644 --- a/ironfish-cli/src/commands/wallet/decrypt.ts +++ b/ironfish-cli/src/commands/wallet/decrypt.ts @@ -8,6 +8,8 @@ import { RemoteFlags } from '../../flags' import { inputPrompt } from '../../ui' export class DecryptCommand extends IronfishCommand { + static hidden = true + static description = 'decrypt accounts in the wallet' static flags = { diff --git a/ironfish-cli/src/commands/wallet/encrypt.ts b/ironfish-cli/src/commands/wallet/encrypt.ts index 018febb7ba..2573ceb8c1 100644 --- a/ironfish-cli/src/commands/wallet/encrypt.ts +++ b/ironfish-cli/src/commands/wallet/encrypt.ts @@ -8,6 +8,8 @@ import { RemoteFlags } from '../../flags' import { inputPrompt } from '../../ui' export class EncryptCommand extends IronfishCommand { + static hidden = true + static description = 'encrypt accounts in the wallet' static flags = { diff --git a/ironfish-cli/src/commands/wallet/import.ts b/ironfish-cli/src/commands/wallet/import.ts index 7c8e54badf..4ff92863f9 100644 --- a/ironfish-cli/src/commands/wallet/import.ts +++ b/ironfish-cli/src/commands/wallet/import.ts @@ -1,14 +1,18 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { AccountFormat, encodeAccountImport } from '@ironfish/sdk' +import { + AccountFormat, + encodeAccountImport, + RPC_ERROR_CODES, + RpcRequestError, +} from '@ironfish/sdk' import { Args, Flags, ux } from '@oclif/core' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' import { checkWalletUnlocked, inputPrompt } from '../../ui' import { importFile, importPipe, longPrompt } from '../../ui/longPrompt' -import { importAccount } from '../../utils' -import { Ledger, LedgerError } from '../../utils/ledger' +import { Ledger } from '../../utils/ledger' export class ImportCommand extends IronfishCommand { static description = `import an account` @@ -98,15 +102,49 @@ export class ImportCommand extends IronfishCommand { flags.name = name } - const { name, isDefaultAccount } = await importAccount( - client, - account, - this.logger, - flags.name, - flags.createdAt, - flags.rescan, - ) + let result + + while (!result) { + try { + result = await client.wallet.importAccount({ + account, + rescan: flags.rescan, + name: flags.name, + createdAt: flags.createdAt, + }) + } catch (e) { + if ( + e instanceof RpcRequestError && + (e.code === RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME.toString() || + e.code === RPC_ERROR_CODES.IMPORT_ACCOUNT_NAME_REQUIRED.toString() || + e.code === RPC_ERROR_CODES.DUPLICATE_IDENTITY_NAME.toString()) + ) { + const message = 'Enter a name for the account' + + if (e.code === RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME.toString()) { + this.log() + this.log(e.codeMessage) + } + + if (e.code === RPC_ERROR_CODES.DUPLICATE_IDENTITY_NAME.toString()) { + this.log() + this.log(e.codeMessage) + } + + const name = await inputPrompt(message, true) + if (name === flags.name) { + this.error(`Entered the same name: '${name}'`) + } + + flags.name = name + continue + } + + throw e + } + } + const { name, isDefaultAccount } = result.content this.log(`Account ${name} imported.`) if (isDefaultAccount) { @@ -123,9 +161,8 @@ export class ImportCommand extends IronfishCommand { const account = await ledger.importAccount() return encodeAccountImport(account, AccountFormat.Base64Json) } catch (e) { - if (e instanceof LedgerError) { - this.logger.error(e.message + '\n') - this.exit(1) + if (e instanceof Error) { + this.error(e.message) } else { this.error('Unknown error while importing account from ledger device.') } diff --git a/ironfish-cli/src/commands/wallet/lock.ts b/ironfish-cli/src/commands/wallet/lock.ts index 763f8c8f0c..3fc5ce43a1 100644 --- a/ironfish-cli/src/commands/wallet/lock.ts +++ b/ironfish-cli/src/commands/wallet/lock.ts @@ -6,6 +6,8 @@ import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' export class LockCommand extends IronfishCommand { + static hidden = true + static description = 'lock accounts in the wallet' static flags = { diff --git a/ironfish-cli/src/commands/wallet/mint.ts b/ironfish-cli/src/commands/wallet/mint.ts index bcea04220d..9981524248 100644 --- a/ironfish-cli/src/commands/wallet/mint.ts +++ b/ironfish-cli/src/commands/wallet/mint.ts @@ -23,7 +23,6 @@ import { promptCurrency } from '../../utils/currency' import { promptExpiration } from '../../utils/expiration' import { getExplorer } from '../../utils/explorer' import { selectFee } from '../../utils/fees' -import { sendTransactionWithLedger } from '../../utils/ledger' import { watchTransaction } from '../../utils/transaction' export class Mint extends IronfishCommand { @@ -97,22 +96,12 @@ This will create tokens and increase supply for a given asset.` transferOwnershipTo: Flags.string({ description: 'The public address of the account to transfer ownership of this asset to.', }), - transferTo: Flags.string({ - description: 'transfer all newly minted coins to this public address', - }), - transferToMemo: Flags.string({ - description: 'The memo of transfer when using transferTo', - }), unsignedTransaction: Flags.boolean({ default: false, description: 'Return a serialized UnsignedTransaction. Use it to create a transaction and build proofs but not post to the network', exclusive: ['rawTransaction'], }), - ledger: Flags.boolean({ - default: false, - description: 'Mint a transaction using a Ledger device', - }), } async start(): Promise { @@ -151,7 +140,7 @@ This will create tokens and increase supply for a given asset.` name = await ui.inputPrompt('Enter the name for the new asset', true) } - if (metadata == null) { + if (!metadata) { metadata = await ui.inputPrompt('Enter metadata for the new asset') } @@ -224,12 +213,6 @@ This will create tokens and increase supply for a given asset.` } } - if (flags.transferTo) { - if (!isValidPublicAddress(flags.transferTo)) { - this.error('transferTo must be a valid public address') - } - } - let expiration = flags.expiration if ((flags.rawTransaction || flags.unsignedTransaction) && expiration === undefined) { expiration = await promptExpiration({ logger: this.logger, client: client }) @@ -240,34 +223,25 @@ This will create tokens and increase supply for a given asset.` this.exit(1) } - const mint = { - // Only provide the asset id if we are not minting an asset for the first time - ...(assetData != null ? { assetId } : {}), - name: name, - metadata: metadata, - value: CurrencyUtils.encode(amount), - transferOwnershipTo: flags.transferOwnershipTo, - } - const params: CreateTransactionRequest = { account, outputs: [], - mints: [mint], + mints: [ + { + // Only provide the asset id if we are not minting an asset for the first time + ...(assetData != null ? { assetId } : {}), + name, + metadata, + value: CurrencyUtils.encode(amount), + transferOwnershipTo: flags.transferOwnershipTo, + }, + ], fee: flags.fee ? CurrencyUtils.encode(flags.fee) : null, feeRate: flags.feeRate ? CurrencyUtils.encode(flags.feeRate) : null, expiration: expiration, confirmations: flags.confirmations, } - if (flags.transferTo) { - params.outputs.push({ - publicAddress: flags.transferTo, - amount: mint.value, - assetId: assetId, - memo: flags.transferToMemo, - }) - } - let raw: RawTransaction if (params.fee === null && params.feeRate === null) { raw = await selectFee({ @@ -308,23 +282,10 @@ This will create tokens and increase supply for a given asset.` name, metadata, flags.transferOwnershipTo, - flags.transferTo, flags.confirm, assetData, ) - if (flags.ledger) { - await sendTransactionWithLedger( - client, - raw, - account, - flags.watch, - flags.confirm, - this.logger, - ) - this.exit(0) - } - ux.action.start('Sending the transaction') const response = await client.wallet.postTransaction({ @@ -363,7 +324,6 @@ This will create tokens and increase supply for a given asset.` Value: renderedValue, Fee: renderedFee, Hash: transaction.hash().toString('hex'), - 'Transfered To': flags.transferTo ? flags.transferTo : undefined, }), ) @@ -396,7 +356,6 @@ This will create tokens and increase supply for a given asset.` name?: string, metadata?: string, transferOwnershipTo?: string, - transferTo?: string, confirm?: boolean, assetData?: RpcAsset, ): Promise { @@ -417,17 +376,6 @@ This will create tokens and increase supply for a given asset.` `Fee: ${renderedFee}`, ] - if (transferTo) { - confirmMessage.push( - `\nAll ${CurrencyUtils.render( - amount, - false, - assetId, - assetData?.verification, - )} of this asset will be transferred to ${transferTo}.`, - ) - } - if (transferOwnershipTo) { confirmMessage.push( `Ownership of this asset will be transferred to ${transferOwnershipTo}. The current account will no longer have any permission to mint or modify this asset. This cannot be undone.`, diff --git a/ironfish-cli/src/commands/wallet/multisig/account/participants.ts b/ironfish-cli/src/commands/wallet/multisig/account/participants.ts index 3896f54087..1c7cba014f 100644 --- a/ironfish-cli/src/commands/wallet/multisig/account/participants.ts +++ b/ironfish-cli/src/commands/wallet/multisig/account/participants.ts @@ -23,29 +23,11 @@ export class MultisigAccountParticipants extends IronfishCommand { const client = await this.connectRpc() await ui.checkWalletUnlocked(client) - const accountIdentities = ( - await client.wallet.multisig.getAccountIdentities({ - account: flags.account, - }) - ).content.identities + const response = await client.wallet.multisig.getAccountIdentities({ + account: flags.account, + }) - const participants = (await client.wallet.multisig.getIdentities()).content.identities - - const matchingIdentities = participants.filter((identity) => - accountIdentities.includes(identity.identity), - ) - - let participant: string | undefined - if (matchingIdentities.length === 1) { - participant = matchingIdentities[0].identity - this.log(`Your identity:\n${participant}`) - this.log('\nOther participating identities:') - } - - for (const identity of accountIdentities) { - if (participant && participant === identity) { - continue - } + for (const identity of response.content.identities) { this.log(identity) } } diff --git a/ironfish-cli/src/commands/wallet/multisig/commitment/create.ts b/ironfish-cli/src/commands/wallet/multisig/commitment/create.ts index a78872b9fa..8f3a3614c1 100644 --- a/ironfish-cli/src/commands/wallet/multisig/commitment/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/commitment/create.ts @@ -1,13 +1,11 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { multisig } from '@ironfish/rust-nodejs' -import { RpcClient, UnsignedTransaction } from '@ironfish/sdk' +import { UnsignedTransaction } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import * as ui from '../../../../ui' -import { LedgerDkg } from '../../../../utils/ledger' import { MultisigTransactionJson } from '../../../../utils/multisig' import { renderUnsignedTransactionDetails } from '../../../../utils/transaction' @@ -38,10 +36,6 @@ export class CreateSigningCommitmentCommand extends IronfishCommand { path: Flags.string({ description: 'Path to a JSON file containing multisig transaction data', }), - ledger: Flags.boolean({ - default: false, - description: 'Create signing commitment using a Ledger device', - }), } async start(): Promise { @@ -53,11 +47,6 @@ export class CreateSigningCommitmentCommand extends IronfishCommand { const client = await this.connectRpc() await ui.checkWalletUnlocked(client) - let participantName = flags.account - if (!participantName) { - participantName = await ui.multisigSecretPrompt(client) - } - let identities = options.identity if (!identities || identities.length < 2) { const input = await ui.longPrompt( @@ -88,24 +77,14 @@ export class CreateSigningCommitmentCommand extends IronfishCommand { await renderUnsignedTransactionDetails( client, unsignedTransaction, - participantName, + flags.account, this.logger, ) await ui.confirmOrQuit('Confirm signing commitment creation', flags.confirm) - if (flags.ledger) { - await this.createSigningCommitmentWithLedger( - client, - participantName, - unsignedTransaction, - identities, - ) - return - } - const response = await client.wallet.multisig.createSigningCommitment({ - account: participantName, + account: flags.account, unsignedTransaction: unsignedTransactionInput, signers: identities.map((identity) => ({ identity })), }) @@ -117,45 +96,4 @@ export class CreateSigningCommitmentCommand extends IronfishCommand { this.log('Next step:') this.log('Send the commitment to the multisig account coordinator.') } - - async createSigningCommitmentWithLedger( - client: RpcClient, - participantName: string, - unsignedTransaction: UnsignedTransaction, - signers: string[], - ): Promise { - const ledger = new LedgerDkg(this.logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - - const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) - const identity = identityResponse.content.identity - - const transactionHash = await ledger.reviewTransaction( - unsignedTransaction.serialize().toString('hex'), - ) - - const rawCommitments = await ledger.dkgGetCommitments(transactionHash.toString('hex')) - - const signingCommitment = multisig.SigningCommitment.fromRaw( - identity, - rawCommitments, - transactionHash, - signers, - ) - - this.log('\nCommitment:\n') - this.log(signingCommitment.serialize().toString('hex')) - - this.log() - this.log('Next step:') - this.log('Send the commitment to the multisig account coordinator.') - } } diff --git a/ironfish-cli/src/commands/wallet/multisig/dealer/create.ts b/ironfish-cli/src/commands/wallet/multisig/dealer/create.ts index e56e8eeda0..095ebc0ed7 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dealer/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dealer/create.ts @@ -1,7 +1,8 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { ACCOUNT_SCHEMA_VERSION, AccountImport, JsonEncoder, RpcClient } from '@ironfish/sdk' +import { ACCOUNT_SCHEMA_VERSION, JsonEncoder, RpcClient } from '@ironfish/sdk' +import { AccountImport } from '@ironfish/sdk/src/wallet/exporter' import { Flags, ux } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts deleted file mode 100644 index 12377e2ead..0000000000 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ /dev/null @@ -1,570 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import { - deserializePublicPackage, - deserializeRound2CombinedPublicPackage, -} from '@ironfish/rust-nodejs' -import { - ACCOUNT_SCHEMA_VERSION, - AccountFormat, - Assert, - encodeAccountImport, - RpcClient, -} from '@ironfish/sdk' -import { Flags } from '@oclif/core' -import { IronfishCommand } from '../../../../command' -import { RemoteFlags } from '../../../../flags' -import * as ui from '../../../../ui' -import { LedgerDkg } from '../../../../utils/ledger' - -export class DkgCreateCommand extends IronfishCommand { - static description = 'Interactive command to create a multisignature account using DKG' - - static flags = { - ...RemoteFlags, - participant: Flags.string({ - char: 'n', - description: 'The name of the secret to use for encryption during DKG', - }), - name: Flags.string({ - char: 'a', - description: 'The name to set for multisig account to be created', - }), - ledger: Flags.boolean({ - default: false, - description: 'Perform operation with a ledger device', - }), - createdAt: Flags.integer({ - description: - "Block sequence to begin scanning from for the created account. Uses node's chain head by default", - }), - } - - async start(): Promise { - const { flags } = await this.parse(DkgCreateCommand) - const client = await this.connectRpc() - await ui.checkWalletUnlocked(client) - - let ledger: LedgerDkg | undefined = undefined - - if (flags.ledger) { - ledger = new LedgerDkg(this.logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - } - - const accountName = await this.getAccountName(client, flags.name) - - let accountCreatedAt = flags.createdAt - if (!accountCreatedAt) { - const statusResponse = await client.node.getStatus() - accountCreatedAt = statusResponse.content.blockchain.head.sequence - } - - const { name: participantName, identity } = ledger - ? await ui.retryStep( - () => { - Assert.isNotUndefined(ledger) - return this.getIdentityFromLedger(ledger, client, flags.participant) - }, - this.logger, - true, - ) - : await this.getParticipant(client, flags.participant) - - this.log(`Identity for ${participantName}: \n${identity} \n`) - - const { round1, totalParticipants } = await ui.retryStep( - async () => { - return this.performRound1(client, participantName, identity, ledger) - }, - this.logger, - true, - ) - - this.log('\n============================================') - this.log('\nRound 1 Encrypted Secret Package:') - this.log(round1.secretPackage) - - this.log('\nRound 1 Public Package:') - this.log(round1.publicPackage) - this.log('\n============================================') - - this.log('\nShare your Round 1 Public Package with other participants.') - - const { round2: round2Result, round1PublicPackages } = await ui.retryStep( - async () => { - return this.performRound2(client, participantName, round1, totalParticipants, ledger) - }, - this.logger, - true, - ) - - this.log('\n============================================') - this.log('\nRound 2 Encrypted Secret Package:') - this.log(round2Result.secretPackage) - - this.log('\nRound 2 Public Package:') - this.log(round2Result.publicPackage) - this.log('\n============================================') - this.log('\nShare your Round 2 Public Package with other participants.') - - await ui.retryStep( - async () => { - return this.performRound3( - client, - accountName, - participantName, - round2Result, - round1PublicPackages, - totalParticipants, - ledger, - accountCreatedAt, - ) - }, - this.logger, - true, - ) - - this.log('Multisig account created successfully using DKG!') - } - - private async getParticipant(client: RpcClient, participantName?: string) { - const identities = (await client.wallet.multisig.getIdentities()).content.identities - - if (participantName) { - const foundIdentity = identities.find((i) => i.name === participantName) - if (!foundIdentity) { - throw new Error(`Participant with name ${participantName} not found`) - } - - return { - name: foundIdentity.name, - identity: foundIdentity.identity, - } - } - - const name = await ui.inputPrompt('Enter the name of the participant', true) - const foundIdentity = identities.find((i) => i.name === name) - - if (foundIdentity) { - this.log('Found an identity with the same name') - - return { - ...foundIdentity, - } - } - - const identity = (await client.wallet.multisig.createParticipant({ name })).content.identity - - return { - name, - identity, - } - } - - private async getAccountName(client: RpcClient, accountName?: string) { - let name: string - if (accountName) { - name = accountName - } else { - name = await ui.inputPrompt('Enter a name for the new multisig account', true) - } - - const accounts = (await client.wallet.getAccounts()).content.accounts - - if (accounts.find((a) => a === name)) { - this.log('An account with the same name already exists') - name = await ui.inputPrompt('Enter a new name for the account', true) - } - - return name - } - - async getIdentityFromLedger( - ledger: LedgerDkg, - client: RpcClient, - name?: string, - ): Promise<{ - name: string - identity: string - }> { - // TODO(hughy): support multiple identities using index - const identity = await ledger.dkgGetIdentity(0) - - const allIdentities = (await client.wallet.multisig.getIdentities()).content.identities - - const foundIdentity = allIdentities.find((i) => i.identity === identity.toString('hex')) - - if (foundIdentity) { - this.log(`Identity already exists with name: ${foundIdentity.name}`) - - return { - name: foundIdentity.name, - identity: identity.toString('hex'), - } - } - - name = await ui.inputPrompt('Enter a name for the identity', true) - - while (allIdentities.find((i) => i.name === name)) { - this.log('An identity with the same name already exists') - name = await ui.inputPrompt('Enter a new name for the identity', true) - } - - await client.wallet.multisig.importParticipant({ - name, - identity: identity.toString('hex'), - }) - - return { - name, - identity: identity.toString('hex'), - } - } - - async createParticipant( - client: RpcClient, - name: string, - ): Promise<{ - name: string - identity: string - }> { - const identity = (await client.wallet.multisig.createParticipant({ name })).content.identity - return { - name, - identity, - } - } - - async performRound1WithLedger( - ledger: LedgerDkg, - client: RpcClient, - participantName: string, - identities: string[], - minSigners: number, - ): Promise<{ - round1: { secretPackage: string; publicPackage: string } - }> { - const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) - const identity = identityResponse.content.identity - - if (!identities.includes(identity)) { - identities.push(identity) - } - - // TODO(hughy): determine how to handle multiple identities using index - const { publicPackage, secretPackage } = await ledger.dkgRound1(0, identities, minSigners) - - return { - round1: { - secretPackage: secretPackage.toString('hex'), - publicPackage: publicPackage.toString('hex'), - }, - } - } - - async performRound1( - client: RpcClient, - participantName: string, - currentIdentity: string, - ledger: LedgerDkg | undefined, - ): Promise<{ - round1: { secretPackage: string; publicPackage: string } - totalParticipants: number - }> { - this.log('\nCollecting Participant Info and Performing Round 1...') - - const totalParticipants = await ui.inputNumberPrompt( - this.logger, - 'Enter the total number of participants', - { required: true, integer: true }, - ) - - if (totalParticipants < 2) { - throw new Error('Total number of participants must be at least 2') - } - - if (ledger && totalParticipants > 4) { - throw new Error('DKG with Ledger supports a maximum of 4 participants') - } - - this.log( - `\nEnter ${ - totalParticipants - 1 - } identities of all other participants (excluding yours) `, - ) - const identities = await ui.collectStrings('Participant Identity', totalParticipants - 1, { - additionalStrings: [currentIdentity], - errorOnDuplicate: true, - }) - - const minSigners = await ui.inputNumberPrompt( - this.logger, - 'Enter the number of minimum signers', - { required: true, integer: true }, - ) - - if (minSigners < 2 || minSigners > totalParticipants) { - throw new Error( - 'Minimum number of signers must be between 2 and the total number of participants', - ) - } - - if (ledger) { - const result = await this.performRound1WithLedger( - ledger, - client, - participantName, - identities, - minSigners, - ) - - return { - ...result, - totalParticipants, - } - } - - this.log('\nPerforming DKG Round 1...') - const response = await client.wallet.multisig.dkg.round1({ - participantName, - participants: identities.map((identity) => ({ identity })), - minSigners, - }) - - return { - round1: { - secretPackage: response.content.round1SecretPackage, - publicPackage: response.content.round1PublicPackage, - }, - totalParticipants, - } - } - - async performRound2WithLedger( - ledger: LedgerDkg, - round1PublicPackages: string[], - round1SecretPackage: string, - ): Promise<{ - round2: { secretPackage: string; publicPackage: string } - }> { - // TODO(hughy): determine how to handle multiple identities using index - const { publicPackage, secretPackage } = await ledger.dkgRound2( - 0, - round1PublicPackages, - round1SecretPackage, - ) - - return { - round2: { - secretPackage: secretPackage.toString('hex'), - publicPackage: publicPackage.toString('hex'), - }, - } - } - - async performRound2( - client: RpcClient, - participantName: string, - round1Result: { secretPackage: string; publicPackage: string }, - totalParticipants: number, - ledger: LedgerDkg | undefined, - ): Promise<{ - round2: { secretPackage: string; publicPackage: string } - round1PublicPackages: string[] - }> { - this.log(`\nEnter ${totalParticipants - 1} Round 1 Public Packages (excluding yours) `) - - const round1PublicPackages = await ui.collectStrings( - 'Round 1 Public Package', - totalParticipants - 1, - { - additionalStrings: [round1Result.publicPackage], - errorOnDuplicate: true, - }, - ) - - this.log('\nPerforming DKG Round 2...') - - if (ledger) { - const result = await this.performRound2WithLedger( - ledger, - round1PublicPackages, - round1Result.secretPackage, - ) - return { - ...result, - round1PublicPackages, - } - } - - const response = await client.wallet.multisig.dkg.round2({ - participantName, - round1SecretPackage: round1Result.secretPackage, - round1PublicPackages, - }) - - return { - round2: { - secretPackage: response.content.round2SecretPackage, - publicPackage: response.content.round2PublicPackage, - }, - round1PublicPackages, - } - } - - async performRound3WithLedger( - ledger: LedgerDkg, - client: RpcClient, - accountName: string, - participantName: string, - round1PublicPackagesStr: string[], - round2PublicPackagesStr: string[], - round2SecretPackage: string, - accountCreatedAt?: number, - ): Promise { - const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) - const identity = identityResponse.content.identity - - // Sort packages by identity - const round1PublicPackages = round1PublicPackagesStr - .map(deserializePublicPackage) - .sort((a, b) => a.identity.localeCompare(b.identity)) - - // Filter out packages not intended for participant and sort by sender identity - const round2CombinedPublicPackages = round2PublicPackagesStr.map( - deserializeRound2CombinedPublicPackage, - ) - const round2PublicPackages = round2CombinedPublicPackages - .flatMap((combined) => - combined.packages.filter((pkg) => pkg.recipientIdentity === identity), - ) - .sort((a, b) => a.senderIdentity.localeCompare(b.senderIdentity)) - - // Extract raw parts from round1 and round2 public packages - const participants = [] - const round1FrostPackages = [] - const gskBytes = [] - for (const pkg of round1PublicPackages) { - // Exclude participant's own identity and round1 public package - if (pkg.identity !== identity) { - participants.push(pkg.identity) - round1FrostPackages.push(pkg.frostPackage) - } - - gskBytes.push(pkg.groupSecretKeyShardEncrypted) - } - - const round2FrostPackages = round2PublicPackages.map((pkg) => pkg.frostPackage) - - // Perform round3 with Ledger - await ledger.dkgRound3( - 0, - participants, - round1FrostPackages, - round2FrostPackages, - round2SecretPackage, - gskBytes, - ) - - // Retrieve all multisig account keys and publicKeyPackage - const dkgKeys = await ledger.dkgRetrieveKeys() - - const publicKeyPackage = await ledger.dkgGetPublicPackage() - - const accountImport = { - ...dkgKeys, - multisigKeys: { - publicKeyPackage: publicKeyPackage.toString('hex'), - identity, - }, - version: ACCOUNT_SCHEMA_VERSION, - name: accountName, - createdAt: null, - spendingKey: null, - } - - // Import multisig account - const response = await client.wallet.importAccount({ - account: encodeAccountImport(accountImport, AccountFormat.Base64Json), - createdAt: accountCreatedAt, - }) - - this.log() - this.log( - `Account ${response.content.name} imported with public address: ${dkgKeys.publicAddress}`, - ) - - this.log() - this.log('Creating an encrypted backup of multisig keys from your Ledger device...') - this.log() - - const encryptedKeys = await ledger.dkgBackupKeys() - - this.log() - this.log('Encrypted Ledger Multisig Backup:') - this.log(encryptedKeys.toString('hex')) - this.log() - this.log('Please save the encrypted keys shown above.') - this.log( - 'Use `ironfish wallet:multisig:ledger:restore` if you need to restore the keys to your Ledger.', - ) - } - - async performRound3( - client: RpcClient, - accountName: string, - participantName: string, - round2Result: { secretPackage: string; publicPackage: string }, - round1PublicPackages: string[], - totalParticipants: number, - ledger: LedgerDkg | undefined, - accountCreatedAt?: number, - ): Promise { - this.log(`\nEnter ${totalParticipants - 1} Round 2 Public Packages (excluding yours) `) - - const round2PublicPackages = await ui.collectStrings( - 'Round 2 Public Package', - totalParticipants - 1, - { - additionalStrings: [round2Result.publicPackage], - errorOnDuplicate: true, - }, - ) - - if (ledger) { - await this.performRound3WithLedger( - ledger, - client, - accountName, - participantName, - round1PublicPackages, - round2PublicPackages, - round2Result.secretPackage, - accountCreatedAt, - ) - return - } - - const response = await client.wallet.multisig.dkg.round3({ - participantName: participantName, - accountName: accountName, - round2SecretPackage: round2Result.secretPackage, - round1PublicPackages, - round2PublicPackages, - }) - - this.log(`Account Name: ${response.content.name}`) - this.log(`Public Address: ${response.content.publicAddress}`) - } -} diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/round1.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/round1.ts index 8b8c9d195c..b26085cf0c 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/round1.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/round1.ts @@ -1,12 +1,11 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { RpcClient } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import * as ui from '../../../../ui' -import { LedgerDkg } from '../../../../utils/ledger' +import { Ledger } from '../../../../utils/ledger' export class DkgRound1Command extends IronfishCommand { static description = 'Perform round1 of the DKG protocol for multisig account creation' @@ -72,7 +71,7 @@ export class DkgRound1Command extends IronfishCommand { } if (flags.ledger) { - await this.performRound1WithLedger(client, participantName, identities, minSigners) + await this.performRound1WithLedger() return } @@ -94,13 +93,8 @@ export class DkgRound1Command extends IronfishCommand { this.log('Send the round 1 public package to each participant') } - async performRound1WithLedger( - client: RpcClient, - participantName: string, - identities: string[], - minSigners: number, - ): Promise { - const ledger = new LedgerDkg(this.logger) + async performRound1WithLedger(): Promise { + const ledger = new Ledger(this.logger) try { await ledger.connect() } catch (e) { @@ -110,26 +104,5 @@ export class DkgRound1Command extends IronfishCommand { throw e } } - - const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) - const identity = identityResponse.content.identity - - if (!identities.includes(identity)) { - identities.push(identity) - } - - // TODO(hughy): determine how to handle multiple identities using index - const { publicPackage, secretPackage } = await ledger.dkgRound1(0, identities, minSigners) - - this.log('\nRound 1 Encrypted Secret Package:\n') - this.log(secretPackage.toString('hex')) - this.log() - - this.log('\nRound 1 Public Package:\n') - this.log(publicPackage.toString('hex')) - this.log() - - this.log('Next step:') - this.log('Send the round 1 public package to each participant') } } diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/round2.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/round2.ts index f7f569bcf0..d2b5b4027b 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/round2.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/round2.ts @@ -5,7 +5,7 @@ import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import * as ui from '../../../../ui' -import { LedgerDkg } from '../../../../utils/ledger' +import { Ledger } from '../../../../utils/ledger' export class DkgRound2Command extends IronfishCommand { static description = 'Perform round2 of the DKG protocol for multisig account creation' @@ -70,7 +70,7 @@ export class DkgRound2Command extends IronfishCommand { round1PublicPackages = round1PublicPackages.map((i) => i.trim()) if (flags.ledger) { - await this.performRound2WithLedger(round1PublicPackages, round1SecretPackage) + await this.performRound2WithLedger() return } @@ -93,11 +93,8 @@ export class DkgRound2Command extends IronfishCommand { this.log('Send the round 2 public package to each participant') } - async performRound2WithLedger( - round1PublicPackages: string[], - round1SecretPackage: string, - ): Promise { - const ledger = new LedgerDkg(this.logger) + async performRound2WithLedger(): Promise { + const ledger = new Ledger(this.logger) try { await ledger.connect() } catch (e) { @@ -107,24 +104,5 @@ export class DkgRound2Command extends IronfishCommand { throw e } } - - // TODO(hughy): determine how to handle multiple identities using index - const { publicPackage, secretPackage } = await ledger.dkgRound2( - 0, - round1PublicPackages, - round1SecretPackage, - ) - - this.log('\nRound 2 Encrypted Secret Package:\n') - this.log(secretPackage.toString('hex')) - this.log() - - this.log('\nRound 2 Public Package:\n') - this.log(publicPackage.toString('hex')) - this.log() - - this.log() - this.log('Next step:') - this.log('Send the round 2 public package to each participant') } } diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/round3.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/round3.ts index 7d4bc8083a..12034755fd 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/round3.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/round3.ts @@ -1,22 +1,11 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { - deserializePublicPackage, - deserializeRound2CombinedPublicPackage, -} from '@ironfish/rust-nodejs' -import { - ACCOUNT_SCHEMA_VERSION, - AccountFormat, - encodeAccountImport, - RpcClient, -} from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import * as ui from '../../../../ui' -import { importAccount } from '../../../../utils' -import { LedgerDkg } from '../../../../utils/ledger' +import { Ledger } from '../../../../utils/ledger' export class DkgRound3Command extends IronfishCommand { static description = 'Perform round3 of the DKG protocol for multisig account creation' @@ -53,10 +42,6 @@ export class DkgRound3Command extends IronfishCommand { description: 'Perform operation with a ledger device', hidden: true, }), - createdAt: Flags.integer({ - description: - "Block sequence to begin scanning from for the created account. Uses node's chain head by default.", - }), } async start(): Promise { @@ -121,21 +106,8 @@ export class DkgRound3Command extends IronfishCommand { } round2PublicPackages = round2PublicPackages.map((i) => i.trim()) - let accountCreatedAt = flags.createdAt - if (!accountCreatedAt) { - const statusResponse = await client.node.getStatus() - accountCreatedAt = statusResponse.content.blockchain.head.sequence - } - if (flags.ledger) { - await this.performRound3WithLedger( - client, - participantName, - round1PublicPackages, - round2PublicPackages, - round2SecretPackage, - accountCreatedAt, - ) + await this.performRound3WithLedger() return } @@ -145,7 +117,6 @@ export class DkgRound3Command extends IronfishCommand { round2SecretPackage, round1PublicPackages, round2PublicPackages, - accountCreatedAt, }) this.log() @@ -154,15 +125,8 @@ export class DkgRound3Command extends IronfishCommand { ) } - async performRound3WithLedger( - client: RpcClient, - participantName: string, - round1PublicPackagesStr: string[], - round2PublicPackagesStr: string[], - round2SecretPackage: string, - accountCreatedAt?: number, - ): Promise { - const ledger = new LedgerDkg(this.logger) + async performRound3WithLedger(): Promise { + const ledger = new Ledger(this.logger) try { await ledger.connect() } catch (e) { @@ -172,93 +136,5 @@ export class DkgRound3Command extends IronfishCommand { throw e } } - - const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) - const identity = identityResponse.content.identity - - // Sort packages by identity - const round1PublicPackages = round1PublicPackagesStr - .map(deserializePublicPackage) - .sort((a, b) => a.identity.localeCompare(b.identity)) - - // Filter out packages not intended for participant and sort by sender identity - const round2CombinedPublicPackages = round2PublicPackagesStr.map( - deserializeRound2CombinedPublicPackage, - ) - const round2PublicPackages = round2CombinedPublicPackages - .flatMap((combined) => - combined.packages.filter((pkg) => pkg.recipientIdentity === identity), - ) - .sort((a, b) => a.senderIdentity.localeCompare(b.senderIdentity)) - - // Extract raw parts from round1 and round2 public packages - const participants = [] - const round1FrostPackages = [] - const gskBytes = [] - for (const pkg of round1PublicPackages) { - // Exclude participant's own identity and round1 public package - if (pkg.identity !== identity) { - participants.push(pkg.identity) - round1FrostPackages.push(pkg.frostPackage) - } - - gskBytes.push(pkg.groupSecretKeyShardEncrypted) - } - - const round2FrostPackages = round2PublicPackages.map((pkg) => pkg.frostPackage) - - // Perform round3 with Ledger - await ledger.dkgRound3( - 0, - participants, - round1FrostPackages, - round2FrostPackages, - round2SecretPackage, - gskBytes, - ) - - // Retrieve all multisig account keys and publicKeyPackage - const dkgKeys = await ledger.dkgRetrieveKeys() - - const publicKeyPackage = await ledger.dkgGetPublicPackage() - - const accountImport = { - ...dkgKeys, - multisigKeys: { - publicKeyPackage: publicKeyPackage.toString('hex'), - identity, - }, - version: ACCOUNT_SCHEMA_VERSION, - name: participantName, - spendingKey: null, - createdAt: null, - } - - // Import multisig account - const { name } = await importAccount( - client, - encodeAccountImport(accountImport, AccountFormat.Base64Json), - this.logger, - participantName, - accountCreatedAt, - ) - - this.log() - this.log(`Account ${name} imported with public address: ${dkgKeys.publicAddress}`) - - this.log() - this.log('Creating an encrypted backup of multisig keys from your Ledger device...') - this.log() - - const encryptedKeys = await ledger.dkgBackupKeys() - - this.log() - this.log('Encrypted Ledger Multisig Backup:') - this.log(encryptedKeys.toString('hex')) - this.log() - this.log('Please save the encrypted keys shown above.') - this.log( - 'Use `ironfish wallet:multisig:ledger:restore` if you need to restore the keys to your Ledger.', - ) } } diff --git a/ironfish-cli/src/commands/wallet/multisig/ledger/backup.ts b/ironfish-cli/src/commands/wallet/multisig/ledger/backup.ts deleted file mode 100644 index a2b26435b5..0000000000 --- a/ironfish-cli/src/commands/wallet/multisig/ledger/backup.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { IronfishCommand } from '../../../../command' -import { LedgerDkg } from '../../../../utils/ledger' - -export class MultisigLedgerBackup extends IronfishCommand { - static description = `show encrypted multisig keys from a Ledger device` - - async start(): Promise { - const ledger = new LedgerDkg(this.logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - - const encryptedKeys = await ledger.dkgBackupKeys() - - this.log() - this.log('Encrypted Ledger Multisig Backup:') - this.log(encryptedKeys.toString('hex')) - this.log() - this.log('Please save the encrypted keys shown above.') - this.log( - 'Use `ironfish wallet:multisig:ledger:restore` if you need to restore the keys to your Ledger.', - ) - } -} diff --git a/ironfish-cli/src/commands/wallet/multisig/ledger/import.ts b/ironfish-cli/src/commands/wallet/multisig/ledger/import.ts deleted file mode 100644 index 1641e382c5..0000000000 --- a/ironfish-cli/src/commands/wallet/multisig/ledger/import.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { ACCOUNT_SCHEMA_VERSION, AccountFormat, encodeAccountImport } from '@ironfish/sdk' -import { Flags } from '@oclif/core' -import { IronfishCommand } from '../../../../command' -import { RemoteFlags } from '../../../../flags' -import * as ui from '../../../../ui' -import { importAccount } from '../../../../utils' -import { LedgerDkg } from '../../../../utils/ledger' - -export class MultisigLedgerImport extends IronfishCommand { - static description = `import a multisig account from a Ledger device` - - static flags = { - ...RemoteFlags, - name: Flags.string({ - description: 'Name to use for the account', - char: 'n', - }), - createdAt: Flags.integer({ - description: 'Block sequence to begin scanning from for the imported account', - }), - } - - async start(): Promise { - const { flags } = await this.parse(MultisigLedgerImport) - - const client = await this.connectRpc() - await ui.checkWalletUnlocked(client) - - const name = flags.name ?? (await ui.inputPrompt('Enter a name for the account', true)) - - const ledger = new LedgerDkg(this.logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - - const identity = await ledger.dkgGetIdentity(0) - const dkgKeys = await ledger.dkgRetrieveKeys() - const publicKeyPackage = await ledger.dkgGetPublicPackage() - - const accountImport = { - ...dkgKeys, - multisigKeys: { - publicKeyPackage: publicKeyPackage.toString('hex'), - identity: identity.toString('hex'), - }, - version: ACCOUNT_SCHEMA_VERSION, - name, - spendingKey: null, - createdAt: null, - } - - const { name: accountName } = await importAccount( - client, - encodeAccountImport(accountImport, AccountFormat.Base64Json), - this.logger, - name, - flags.createdAt, - ) - - this.log() - this.log(`Account ${accountName} imported with public address: ${dkgKeys.publicAddress}`) - } -} diff --git a/ironfish-cli/src/commands/wallet/multisig/ledger/restore.ts b/ironfish-cli/src/commands/wallet/multisig/ledger/restore.ts deleted file mode 100644 index cad4c91e16..0000000000 --- a/ironfish-cli/src/commands/wallet/multisig/ledger/restore.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Args } from '@oclif/core' -import { IronfishCommand } from '../../../../command' -import * as ui from '../../../../ui' -import { LedgerDkg } from '../../../../utils/ledger' - -export class MultisigLedgerRestore extends IronfishCommand { - static description = `restore encrypted multisig keys to a Ledger device` - - static args = { - backup: Args.string({ - required: false, - description: 'Encrypted multisig key backup from your Ledger device', - }), - } - - async start(): Promise { - const { args } = await this.parse(MultisigLedgerRestore) - - let encryptedKeys = args.backup - if (!encryptedKeys) { - encryptedKeys = await ui.longPrompt( - 'Enter the encrypted multisig key backup to restore to your Ledger device', - ) - } - - const ledger = new LedgerDkg(this.logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - - await ledger.dkgRestoreKeys(encryptedKeys) - - this.log() - this.log('Encrypted multisig key backup restored to Ledger.') - } -} diff --git a/ironfish-cli/src/commands/wallet/multisig/participant/create.ts b/ironfish-cli/src/commands/wallet/multisig/participant/create.ts index 3630fcb43c..1621686631 100644 --- a/ironfish-cli/src/commands/wallet/multisig/participant/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/participant/create.ts @@ -6,7 +6,6 @@ import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import * as ui from '../../../../ui' -import { LedgerDkg } from '../../../../utils/ledger' export class MultisigIdentityCreate extends IronfishCommand { static description = `Create a multisig participant identity` @@ -17,11 +16,6 @@ export class MultisigIdentityCreate extends IronfishCommand { char: 'n', description: 'Name to associate with the identity', }), - ledger: Flags.boolean({ - default: false, - description: 'Perform operation with a ledger device', - hidden: true, - }), } async start(): Promise { @@ -35,27 +29,14 @@ export class MultisigIdentityCreate extends IronfishCommand { name = await ui.inputPrompt('Enter a name for the identity', true) } - let identity - if (flags.ledger) { - identity = await this.getIdentityFromLedger() - } - let response while (!response) { try { - if (identity) { - response = await client.wallet.multisig.importParticipant({ - name, - identity: identity.toString('hex'), - }) - } else { - response = await client.wallet.multisig.createParticipant({ name }) - } + response = await client.wallet.multisig.createParticipant({ name }) } catch (e) { if ( e instanceof RpcRequestError && - (e.code === RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME.toString() || - e.code === RPC_ERROR_CODES.DUPLICATE_IDENTITY_NAME.toString()) + e.code === RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME.toString() ) { this.log() this.log(e.codeMessage) @@ -69,20 +50,4 @@ export class MultisigIdentityCreate extends IronfishCommand { this.log('Identity:') this.log(response.content.identity) } - - async getIdentityFromLedger(): Promise { - const ledger = new LedgerDkg(this.logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - - // TODO(hughy): support multiple identities using index - return ledger.dkgGetIdentity(0) - } } diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts deleted file mode 100644 index 4f75574b05..0000000000 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ /dev/null @@ -1,389 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import { multisig } from '@ironfish/rust-nodejs' -import { - CurrencyUtils, - Identity, - RpcClient, - Transaction, - UnsignedTransaction, -} from '@ironfish/sdk' -import { Flags, ux } from '@oclif/core' -import { IronfishCommand } from '../../../command' -import { RemoteFlags } from '../../../flags' -import * as ui from '../../../ui' -import { LedgerDkg } from '../../../utils/ledger' -import { renderUnsignedTransactionDetails, watchTransaction } from '../../../utils/transaction' - -// todo(patnir): this command does not differentiate between a participant and an account. -// there is a possibility that the account and participant have different names. - -type MultisigParticipant = { - name: string - identity: Identity - hasSecret: boolean -} - -export class SignMultisigTransactionCommand extends IronfishCommand { - static description = 'Interactive command sign a transaction with a multisig account' - - static flags = { - ...RemoteFlags, - unsignedTransaction: Flags.string({ - char: 'u', - description: 'The unsigned transaction that needs to be signed', - }), - account: Flags.string({ - char: 'a', - description: 'Name of the account to use for signing the transaction', - }), - ledger: Flags.boolean({ - default: false, - description: 'Perform operation with a ledger device', - }), - } - - async start(): Promise { - const { flags } = await this.parse(SignMultisigTransactionCommand) - const client = await this.connectRpc() - await ui.checkWalletUnlocked(client) - - let ledger: LedgerDkg | undefined = undefined - - if (flags.ledger) { - ledger = new LedgerDkg(this.logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - } - - let multisigAccountName: string - if (!flags.account) { - multisigAccountName = await ui.accountPrompt(client) - } else { - multisigAccountName = flags.account - const account = (await client.wallet.getAccounts()).content.accounts.find( - (a) => a === multisigAccountName, - ) - if (!account) { - this.error(`Account ${multisigAccountName} not found`) - } - } - - const accountIdentities = ( - await client.wallet.multisig.getAccountIdentities({ account: multisigAccountName }) - ).content.identities - const participants = (await client.wallet.multisig.getIdentities()).content.identities - - const matchingIdentities = participants.filter((identity) => - accountIdentities.includes(identity.identity), - ) - - if (matchingIdentities.length === 0) { - this.error(`No matching identities found for account ${multisigAccountName}`) - } - - let participant: MultisigParticipant - - if (matchingIdentities.length === 1) { - participant = matchingIdentities[0] - } else { - participant = await ui.listPrompt( - 'Select identity for signing', - matchingIdentities, - (i) => i.name, - ) - } - - const unsignedTransactionInput = - flags.unsignedTransaction ?? - (await ui.longPrompt('Enter the unsigned transaction', { required: true })) - const unsignedTransaction = new UnsignedTransaction( - Buffer.from(unsignedTransactionInput, 'hex'), - ) - await renderUnsignedTransactionDetails( - client, - unsignedTransaction, - multisigAccountName, - this.logger, - ) - - const { commitment, identities } = await ui.retryStep( - async () => { - return this.performCreateSigningCommitment( - client, - multisigAccountName, - participant, - unsignedTransaction, - unsignedTransactionInput, - ledger, - ) - }, - this.logger, - true, - ) - - this.log('\n============================================') - this.log('\nCommitment:') - this.log(commitment) - this.log('\n============================================') - - this.log('\nShare your commitment with other participants.') - - const signingPackage = await ui.retryStep(() => { - return this.performAggregateCommitments( - client, - multisigAccountName, - commitment, - identities, - unsignedTransaction, - ) - }, this.logger) - - this.log('\n============================================') - this.log('\nSigning Package:') - this.log(signingPackage) - this.log('\n============================================') - - const signatureShare = await ui.retryStep( - () => - this.performCreateSignatureShare( - client, - multisigAccountName, - participant, - signingPackage, - unsignedTransaction, - ledger, - ), - this.logger, - true, - ) - - this.log('\n============================================') - this.log('\nSignature Share:') - this.log(signatureShare) - this.log('\n============================================') - - this.log('\nShare your signature share with other participants.') - - await ui.retryStep( - () => - this.performAggregateSignatures( - client, - multisigAccountName, - signingPackage, - signatureShare, - identities.length, - ), - this.logger, - ) - - this.log('Mutlisignature sign process completed!') - } - - private async performAggregateSignatures( - client: RpcClient, - accountName: string, - signingPackage: string, - signatureShare: string, - totalParticipants: number, - ): Promise { - this.log( - `Enter ${ - totalParticipants - 1 - } signature shares of the participants (excluding your own)`, - ) - - const signatureShares = await ui.collectStrings('Signature Share', totalParticipants - 1, { - additionalStrings: [signatureShare], - errorOnDuplicate: true, - }) - - const broadcast = await ui.confirmPrompt('Do you want to broadcast the transaction?') - const watch = await ui.confirmPrompt('Do you want to watch the transaction?') - - ux.action.start('Signing the multisig transaction') - - const response = await client.wallet.multisig.aggregateSignatureShares({ - account: accountName, - broadcast, - signingPackage, - signatureShares, - }) - - const bytes = Buffer.from(response.content.transaction, 'hex') - const transaction = new Transaction(bytes) - - ux.action.stop() - - if (broadcast && response.content.accepted === false) { - this.warn( - `Transaction '${transaction.hash().toString('hex')}' was not accepted into the mempool`, - ) - } - - if (broadcast && response.content.broadcasted === false) { - this.warn(`Transaction '${transaction.hash().toString('hex')}' failed to broadcast`) - } - - this.log(`Transaction: ${response.content.transaction}`) - this.log(`Hash: ${transaction.hash().toString('hex')}`) - this.log(`Fee: ${CurrencyUtils.render(transaction.fee(), true)}`) - - if (watch) { - this.log('') - - await watchTransaction({ - client, - logger: this.logger, - account: accountName, - hash: transaction.hash().toString('hex'), - }) - } - } - - private async performCreateSignatureShare( - client: RpcClient, - accountName: string, - identity: MultisigParticipant, - signingPackageString: string, - unsignedTransaction: UnsignedTransaction, - ledger: LedgerDkg | undefined, - ): Promise { - let signatureShare: string - - const signingPackage = new multisig.SigningPackage(Buffer.from(signingPackageString, 'hex')) - - if (ledger) { - const frostSignatureShare = await ledger.dkgSign( - unsignedTransaction.publicKeyRandomness(), - signingPackage.frostSigningPackage().toString('hex'), - unsignedTransaction.hash().toString('hex'), - ) - - signatureShare = multisig.SignatureShare.fromFrost( - frostSignatureShare, - Buffer.from(identity.identity, 'hex'), - ) - .serialize() - .toString('hex') - } else { - signatureShare = ( - await client.wallet.multisig.createSignatureShare({ - account: accountName, - signingPackage: signingPackageString, - }) - ).content.signatureShare - } - - return signatureShare - } - - private async performAggregateCommitments( - client: RpcClient, - accountName: string, - commitment: string, - identities: string[], - unsignedTransaction: UnsignedTransaction, - ) { - this.log( - `Enter ${identities.length - 1} commitments of the participants (excluding your own)`, - ) - - const commitments = await ui.collectStrings('Commitment', identities.length - 1, { - additionalStrings: [commitment], - errorOnDuplicate: true, - }) - - const signingPackageResponse = await client.wallet.multisig.createSigningPackage({ - account: accountName, - unsignedTransaction: unsignedTransaction.serialize().toString('hex'), - commitments, - }) - - return signingPackageResponse.content.signingPackage - } - - private async performCreateSigningCommitment( - client: RpcClient, - accountName: string, - participant: MultisigParticipant, - unsignedTransaction: UnsignedTransaction, - unsignedTransactionInput: string, - ledger: LedgerDkg | undefined, - ) { - this.log(`Identity for ${participant.name}: \n${participant.identity} \n`) - this.log('Share your participant identity with other signers.') - - const input = await ui.inputPrompt( - 'Enter the number of participants in signing this transaction', - true, - ) - const totalParticipants = parseInt(input) - - if (totalParticipants < 2) { - this.error('Minimum number of participants must be at least 2') - } - - this.log( - `Enter ${totalParticipants - 1} identities of the participants (excluding your own)`, - ) - - const identities = await ui.collectStrings('Participant Identity', totalParticipants - 1, { - additionalStrings: [participant.identity], - errorOnDuplicate: true, - }) - - let commitment - - if (ledger) { - await ledger.reviewTransaction(unsignedTransaction.serialize().toString('hex')) - - commitment = await this.createSigningCommitmentWithLedger( - ledger, - participant, - unsignedTransaction.hash(), - identities, - ) - } else { - commitment = ( - await client.wallet.multisig.createSigningCommitment({ - account: accountName, - unsignedTransaction: unsignedTransactionInput, - signers: identities.map((identity) => ({ identity })), - }) - ).content.commitment - } - - return { - commitment, - identities, - } - } - - async createSigningCommitmentWithLedger( - ledger: LedgerDkg, - participant: MultisigParticipant, - transactionHash: Buffer, - signers: string[], - ): Promise { - const rawCommitments = await ledger.dkgGetCommitments(transactionHash.toString('hex')) - - const sigingCommitment = multisig.SigningCommitment.fromRaw( - participant.identity, - rawCommitments, - transactionHash, - signers, - ) - - return sigingCommitment.serialize().toString('hex') - } -} diff --git a/ironfish-cli/src/commands/wallet/multisig/signature/create.ts b/ironfish-cli/src/commands/wallet/multisig/signature/create.ts index db52317a67..a156964794 100644 --- a/ironfish-cli/src/commands/wallet/multisig/signature/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/signature/create.ts @@ -2,12 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { multisig } from '@ironfish/rust-nodejs' -import { RpcClient, UnsignedTransaction } from '@ironfish/sdk' +import { UnsignedTransaction } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import * as ui from '../../../../ui' -import { LedgerDkg } from '../../../../utils/ledger' import { MultisigTransactionJson } from '../../../../utils/multisig' import { renderUnsignedTransactionDetails } from '../../../../utils/transaction' @@ -31,10 +30,6 @@ export class CreateSignatureShareCommand extends IronfishCommand { path: Flags.string({ description: 'Path to a JSON file containing multisig transaction data', }), - ledger: Flags.boolean({ - default: false, - description: 'Create signature share using a Ledger device', - }), } async start(): Promise { @@ -46,11 +41,6 @@ export class CreateSignatureShareCommand extends IronfishCommand { const client = await this.connectRpc() await ui.checkWalletUnlocked(client) - let participantName = flags.account - if (!participantName) { - participantName = await ui.multisigSecretPrompt(client) - } - let signingPackageString = options.signingPackage if (!signingPackageString) { signingPackageString = await ui.longPrompt('Enter the signing package') @@ -66,7 +56,7 @@ export class CreateSignatureShareCommand extends IronfishCommand { await renderUnsignedTransactionDetails( client, unsignedTransaction, - participantName, + flags.account, this.logger, ) @@ -74,18 +64,8 @@ export class CreateSignatureShareCommand extends IronfishCommand { await ui.confirmOrQuit('Confirm new signature share creation') } - if (flags.ledger) { - await this.createSignatureShareWithLedger( - client, - participantName, - unsignedTransaction, - signingPackage.frostSigningPackage().toString('hex'), - ) - return - } - const signatureShareResponse = await client.wallet.multisig.createSignatureShare({ - account: participantName, + account: flags.account, signingPackage: signingPackageString, }) @@ -113,50 +93,4 @@ export class CreateSignatureShareCommand extends IronfishCommand { this.log(signer.toString('hex')) } } - - async createSignatureShareWithLedger( - client: RpcClient, - participantName: string, - unsignedTransaction: UnsignedTransaction, - frostSigningPackage: string, - ): Promise { - const ledger = new LedgerDkg(this.logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - - const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) - const identity = identityResponse.content.identity - - const transactionHash = await ledger.reviewTransaction( - unsignedTransaction.serialize().toString('hex'), - ) - - const frostSignatureShare = await ledger.dkgSign( - unsignedTransaction.publicKeyRandomness(), - frostSigningPackage, - transactionHash.toString('hex'), - ) - - const signatureShare = multisig.SignatureShare.fromFrost( - frostSignatureShare, - Buffer.from(identity, 'hex'), - ) - - this.log() - this.log('Signature Share:') - this.log(signatureShare.serialize().toString('hex')) - - this.log() - this.log('Next step:') - this.log( - 'Send the signature to the coordinator. They will aggregate the signatures from all participants and sign the transaction.', - ) - } } diff --git a/ironfish-cli/src/commands/wallet/send.ts b/ironfish-cli/src/commands/wallet/send.ts index 4c7efe5231..66107f8355 100644 --- a/ironfish-cli/src/commands/wallet/send.ts +++ b/ironfish-cli/src/commands/wallet/send.ts @@ -8,6 +8,7 @@ import { isValidPublicAddress, RawTransaction, RawTransactionSerde, + RpcClient, TimeUtils, Transaction, } from '@ironfish/sdk' @@ -20,7 +21,7 @@ import { promptCurrency } from '../../utils/currency' import { promptExpiration } from '../../utils/expiration' import { getExplorer } from '../../utils/explorer' import { selectFee } from '../../utils/fees' -import { sendTransactionWithLedger } from '../../utils/ledger' +import { Ledger } from '../../utils/ledger' import { getSpendPostTimeInMs, updateSpendPostTimeInMs } from '../../utils/spendPostTime' import { displayTransactionSummary, @@ -258,14 +259,7 @@ export class Send extends IronfishCommand { } if (flags.ledger) { - await sendTransactionWithLedger( - client, - raw, - from, - flags.watch, - flags.confirm, - this.logger, - ) + await this.sendTransactionWithLedger(client, raw, from, flags.watch, flags.confirm) this.exit(0) } @@ -357,4 +351,83 @@ export class Send extends IronfishCommand { }) } } + + private async sendTransactionWithLedger( + client: RpcClient, + raw: RawTransaction, + from: string | undefined, + watch: boolean, + confirm: boolean, + ): Promise { + const ledger = new Ledger(this.logger) + try { + await ledger.connect() + } catch (e) { + if (e instanceof Error) { + this.error(e.message) + } else { + throw e + } + } + + const publicKey = (await client.wallet.getAccountPublicKey({ account: from })).content + .publicKey + + const ledgerPublicKey = await ledger.getPublicAddress() + + if (publicKey !== ledgerPublicKey) { + this.error( + `The public key on the ledger device does not match the public key of the account '${from}'`, + ) + } + + const buildTransactionResponse = await client.wallet.buildTransaction({ + account: from, + rawTransaction: RawTransactionSerde.serialize(raw).toString('hex'), + }) + + const unsignedTransaction = buildTransactionResponse.content.unsignedTransaction + + const signature = (await ledger.sign(unsignedTransaction)).toString('hex') + + this.log(`\nSignature: ${signature}`) + + const addSignatureResponse = await client.wallet.addSignature({ + unsignedTransaction, + signature, + }) + + const signedTransaction = addSignatureResponse.content.transaction + const bytes = Buffer.from(signedTransaction, 'hex') + + const transaction = new Transaction(bytes) + + this.log(`\nSigned Transaction: ${signedTransaction}`) + this.log(`\nHash: ${transaction.hash().toString('hex')}`) + this.log(`Fee: ${CurrencyUtils.render(transaction.fee(), true)}`) + + await ui.confirmOrQuit('', confirm) + + const addTransactionResponse = await client.wallet.addTransaction({ + transaction: signedTransaction, + broadcast: true, + }) + + if (addTransactionResponse.content.accepted === false) { + this.error( + `Transaction '${transaction.hash().toString('hex')}' was not accepted into the mempool`, + ) + } + + if (watch) { + this.log('') + + await watchTransaction({ + client, + logger: this.logger, + account: from, + hash: transaction.hash().toString('hex'), + }) + } + } } diff --git a/ironfish-cli/src/commands/wallet/unlock.ts b/ironfish-cli/src/commands/wallet/unlock.ts index 1326df62ff..bf874f11c6 100644 --- a/ironfish-cli/src/commands/wallet/unlock.ts +++ b/ironfish-cli/src/commands/wallet/unlock.ts @@ -8,6 +8,8 @@ import { RemoteFlags } from '../../flags' import { inputPrompt } from '../../ui' export class UnlockCommand extends IronfishCommand { + static hidden = true + static description = 'unlock accounts in the wallet' static flags = { diff --git a/ironfish-cli/src/ui/index.ts b/ironfish-cli/src/ui/index.ts index 25752ab000..9ae9ad0931 100644 --- a/ironfish-cli/src/ui/index.ts +++ b/ironfish-cli/src/ui/index.ts @@ -8,6 +8,5 @@ export * from './longPrompt' export * from './progressBar' export * from './prompt' export * from './prompts' -export * from './retry' export * from './table' export * from './wallet' diff --git a/ironfish-cli/src/ui/prompt.ts b/ironfish-cli/src/ui/prompt.ts index 5685bfdd05..f670e28044 100644 --- a/ironfish-cli/src/ui/prompt.ts +++ b/ironfish-cli/src/ui/prompt.ts @@ -2,40 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Logger } from '@ironfish/sdk' import { ux } from '@oclif/core' import inquirer from 'inquirer' -import { longPrompt } from './longPrompt' - -export async function collectStrings( - itemName: string, - itemAmount: number, - options?: { - additionalStrings: string[] - errorOnDuplicate: boolean - }, -): Promise { - const array = [] - - for (let i = 0; i < itemAmount; i++) { - const input = await longPrompt(`${itemName} #${i + 1}`, { required: true }) - array.push(input) - } - - const additionalStrings = options?.additionalStrings || [] - - const strings = [...array, ...additionalStrings] - - if (options?.errorOnDuplicate) { - const withoutDuplicates = [...new Set(strings)] - - if (withoutDuplicates.length !== strings.length) { - throw new Error(`Duplicate ${itemName} found in the list`) - } - } - - return strings -} async function _inputPrompt(message: string, options?: { password: boolean }): Promise { const result: { prompt: string } = await inquirer.prompt({ @@ -46,47 +14,6 @@ async function _inputPrompt(message: string, options?: { password: boolean }): P return result.prompt.trim() } -export async function inputNumberPrompt( - logger: Logger, - message: string, - options: { - required?: boolean - integer?: boolean - }, -): Promise { - const validateNumber = (input: string): number => { - const num = Number(input) - - if (isNaN(num)) { - throw new Error('Input must be a number') - } - - if (options.integer && num % 1 !== 0) { - throw new Error('Input must be an integer') - } - - return num - } - - if (options.required) { - // eslint-disable-next-line no-constant-condition - while (true) { - try { - const userInput = await _inputPrompt(message) - return validateNumber(userInput) - } catch (e) { - if (e instanceof Error) { - logger.error(e.message) - } else { - logger.error('An error occurred. Please try again.') - } - } - } - } - - return validateNumber(await _inputPrompt(message)) -} - export async function inputPrompt( message: string, required: boolean = false, diff --git a/ironfish-cli/src/ui/prompts.ts b/ironfish-cli/src/ui/prompts.ts index fff33c80ce..8c2fead622 100644 --- a/ironfish-cli/src/ui/prompts.ts +++ b/ironfish-cli/src/ui/prompts.ts @@ -73,11 +73,6 @@ export async function assetPrompt( ) } - const filter = options.filter - if (filter) { - balances = balances.filter((balance) => filter(assetLookup[balance.assetId])) - } - if (balances.length === 0) { return undefined } @@ -90,6 +85,11 @@ export async function assetPrompt( } } + const filter = options.filter + if (filter) { + balances = balances.filter((balance) => filter(assetLookup[balance.assetId])) + } + // Show verified assets at top of the list balances = balances.sort((asset1, asset2) => { const verified1 = assetLookup[asset1.assetId].verification.status === 'verified' diff --git a/ironfish-cli/src/ui/retry.ts b/ironfish-cli/src/ui/retry.ts deleted file mode 100644 index da206c5fb8..0000000000 --- a/ironfish-cli/src/ui/retry.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Logger } from '@ironfish/sdk' -import { confirmPrompt } from './prompt' - -export async function retryStep( - stepFunction: () => Promise, - logger: Logger, - askToRetry: boolean = false, - maxRetries: number = 10, -): Promise { - // eslint-disable-next-line no-constant-condition - let retries = 0 - while (retries < maxRetries) { - try { - const result = await stepFunction() - return result - } catch (error) { - logger.log(`An Error Occurred: ${(error as Error).message}`) - if (askToRetry) { - const continueResponse = await confirmPrompt('Do you want to retry this step?') - if (!continueResponse) { - throw new Error('User chose to not continue') - } - } - } - retries++ - } - - throw new Error('Max retries reached') -} diff --git a/ironfish-cli/src/utils/account.ts b/ironfish-cli/src/utils/account.ts index 2f0f20dbb4..a867d86707 100644 --- a/ironfish-cli/src/utils/account.ts +++ b/ironfish-cli/src/utils/account.ts @@ -2,15 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { - ImportResponse, - Logger, - RPC_ERROR_CODES, - RpcClient, - RpcRequestError, -} from '@ironfish/sdk' +import { RpcClient } from '@ironfish/sdk' import * as ui from '../ui' -import { inputPrompt } from '../ui' export async function useAccount( client: RpcClient, @@ -34,57 +27,3 @@ export async function useAccount( return ui.accountPrompt(client, message) } - -export async function importAccount( - client: RpcClient, - account: string, - logger: Logger, - accountName?: string, - createdAt?: number, - rescan?: boolean, -): Promise { - let name = accountName - - let result - while (!result) { - try { - result = await client.wallet.importAccount({ - account, - name, - rescan, - createdAt, - }) - } catch (e) { - if ( - e instanceof RpcRequestError && - (e.code === RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME.toString() || - e.code === RPC_ERROR_CODES.IMPORT_ACCOUNT_NAME_REQUIRED.toString() || - e.code === RPC_ERROR_CODES.DUPLICATE_IDENTITY_NAME.toString()) - ) { - const message = 'Enter a name for the account' - - if (e.code === RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME.toString()) { - logger.info('') - logger.info(e.codeMessage) - } - - if (e.code === RPC_ERROR_CODES.DUPLICATE_IDENTITY_NAME.toString()) { - logger.info('') - logger.info(e.codeMessage) - } - - const inputName = await inputPrompt(message, true) - if (inputName === name) { - throw new Error(`Entered the same name: '${name}'`) - } - - name = inputName - continue - } - - throw e - } - } - - return result.content -} diff --git a/ironfish-cli/src/utils/chainport/config.ts b/ironfish-cli/src/utils/chainport/config.ts index b8405e283f..015d0556a9 100644 --- a/ironfish-cli/src/utils/chainport/config.ts +++ b/ironfish-cli/src/utils/chainport/config.ts @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { MAINNET, TESTNET } from '@ironfish/sdk' +import { TESTNET } from '@ironfish/sdk' const config = { [TESTNET.id]: { @@ -17,18 +17,7 @@ const config = { '06102d319ab7e77b914a1bd135577f3e266fd82a3e537a02db281421ed8b3d13', ]), }, - [MAINNET.id]: { - chainportId: 22, - endpoint: 'https://api.chainport.io', - outgoingAddresses: new Set([ - '576ffdcc27e11d81f5180d3dc5690294941170d492b2d9503c39130b1f180405', - '7ac2d6a59e19e66e590d014af013cd5611dc146e631fa2aedf0ee3ed1237eebe', - ]), - incomingAddresses: new Set([ - '1216302193e8f1ad020f458b54a163039403d803e98673c6a85e59b5f4a1a900', - ]), - }, -} +} // MAINNET support to follow export const isNetworkSupportedByChainport = (networkId: number) => { return !!config[networkId] diff --git a/ironfish-cli/src/utils/chainport/requests.ts b/ironfish-cli/src/utils/chainport/requests.ts index 11124fe83c..d538ab338b 100644 --- a/ironfish-cli/src/utils/chainport/requests.ts +++ b/ironfish-cli/src/utils/chainport/requests.ts @@ -1,7 +1,6 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { MAINNET } from '@ironfish/sdk' import axios from 'axios' import { getConfig } from './config' import { @@ -38,12 +37,7 @@ export const fetchChainportVerifiedTokens = async ( networkId: number, ): Promise => { const config = getConfig(networkId) - let url - if (networkId === MAINNET.id) { - url = `${config.endpoint}/token/list?network_name=IRONFISH` - } else { - url = `${config.endpoint}/token_list?network_name=IRONFISH` - } + const url = `${config.endpoint}/token/list?network_name=IRONFISH` return (await makeChainportRequest<{ verified_tokens: ChainportVerifiedToken[] }>(url)) .verified_tokens diff --git a/ironfish-cli/src/utils/ledger.ts b/ironfish-cli/src/utils/ledger.ts index 106051fab3..fe472383ec 100644 --- a/ironfish-cli/src/utils/ledger.ts +++ b/ironfish-cli/src/utils/ledger.ts @@ -1,20 +1,9 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { - ACCOUNT_SCHEMA_VERSION, - AccountImport, - Assert, - createRootLogger, - CurrencyUtils, - Logger, - RawTransaction, - RawTransactionSerde, - RpcClient, - Transaction, -} from '@ironfish/sdk' +import { createRootLogger, Logger } from '@ironfish/sdk' +import { AccountImport } from '@ironfish/sdk/src/wallet/exporter' import TransportNodeHid from '@ledgerhq/hw-transport-node-hid' -import { Errors, ux } from '@oclif/core' import IronfishApp, { IronfishKeys, ResponseAddress, @@ -22,248 +11,6 @@ import IronfishApp, { ResponseSign, ResponseViewKey, } from '@zondax/ledger-ironfish' -import { - default as IronfishDkgApp, - KeyResponse, - ResponseAddress as ResponseAddressDkg, - ResponseDkgRound1, - ResponseDkgRound2, - ResponseIdentity, - ResponseProofGenKey as ResponseProofGenKeyDkg, - ResponseViewKey as ResponseViewKeyDkg, -} from '@zondax/ledger-ironfish-dkg' -import { ResponseError } from '@zondax/ledger-js' -import * as ui from '../ui' -import { watchTransaction } from './transaction' - -export class LedgerDkg { - app: IronfishDkgApp | undefined - logger: Logger - PATH = "m/44'/1338'/0" - - constructor(logger?: Logger) { - this.app = undefined - this.logger = logger ? logger : createRootLogger() - } - - tryInstruction = async (instruction: (app: IronfishDkgApp) => Promise) => { - await this.refreshConnection() - Assert.isNotUndefined(this.app, 'Unable to establish connection with Ledger device') - - try { - return await instruction(this.app) - } catch (error: unknown) { - if (isResponseError(error)) { - this.logger.debug(`Ledger ResponseError returnCode: ${error.returnCode.toString(16)}`) - if (error.returnCode === LedgerDeviceLockedError.returnCode) { - throw new LedgerDeviceLockedError('Please unlock your Ledger device.') - } else if (LedgerAppUnavailableError.returnCodes.includes(error.returnCode)) { - throw new LedgerAppUnavailableError() - } - - throw new LedgerError(error.errorMessage) - } - - throw error - } - } - - connect = async () => { - const transport = await TransportNodeHid.create(3000) - - transport.on('disconnect', async () => { - await transport.close() - this.app = undefined - }) - - if (transport.deviceModel) { - this.logger.debug(`${transport.deviceModel.productName} found.`) - } - - const app = new IronfishDkgApp(transport, true) - - // If the app isn't open or the device is locked, this will throw an error. - await app.getVersion() - - this.app = app - - return { app, PATH: this.PATH } - } - - private refreshConnection = async () => { - if (!this.app) { - await this.connect() - } - } - - dkgGetIdentity = async (index: number): Promise => { - this.logger.log('Retrieving identity from ledger device.') - - const response: ResponseIdentity = await this.tryInstruction((app) => - app.dkgGetIdentity(index, false), - ) - - return response.identity - } - - dkgRound1 = async ( - index: number, - identities: string[], - minSigners: number, - ): Promise => { - this.logger.log('Please approve the request on your ledger device.') - - return this.tryInstruction((app) => app.dkgRound1(index, identities, minSigners)) - } - - dkgRound2 = async ( - index: number, - round1PublicPackages: string[], - round1SecretPackage: string, - ): Promise => { - this.logger.log('Please approve the request on your ledger device.') - - return this.tryInstruction((app) => - app.dkgRound2(index, round1PublicPackages, round1SecretPackage), - ) - } - - dkgRound3 = async ( - index: number, - participants: string[], - round1PublicPackages: string[], - round2PublicPackages: string[], - round2SecretPackage: string, - gskBytes: string[], - ): Promise => { - this.logger.log('Please approve the request on your ledger device.') - - return this.tryInstruction((app) => - app.dkgRound3Min( - index, - participants, - round1PublicPackages, - round2PublicPackages, - round2SecretPackage, - gskBytes, - ), - ) - } - - dkgRetrieveKeys = async (): Promise<{ - publicAddress: string - viewKey: string - incomingViewKey: string - outgoingViewKey: string - proofAuthorizingKey: string - }> => { - const responseAddress: KeyResponse = await this.tryInstruction((app) => - app.dkgRetrieveKeys(IronfishKeys.PublicAddress), - ) - - if (!isResponseAddress(responseAddress)) { - throw new Error(`No public address returned.`) - } - - const responseViewKey = await this.tryInstruction((app) => - app.dkgRetrieveKeys(IronfishKeys.ViewKey), - ) - - if (!isResponseViewKey(responseViewKey)) { - throw new Error(`No view key returned.`) - } - - const responsePGK: KeyResponse = await this.tryInstruction((app) => - app.dkgRetrieveKeys(IronfishKeys.ProofGenerationKey), - ) - - if (!isResponseProofGenKey(responsePGK)) { - throw new Error(`No proof authorizing key returned.`) - } - - return { - publicAddress: responseAddress.publicAddress.toString('hex'), - viewKey: responseViewKey.viewKey.toString('hex'), - incomingViewKey: responseViewKey.ivk.toString('hex'), - outgoingViewKey: responseViewKey.ovk.toString('hex'), - proofAuthorizingKey: responsePGK.nsk.toString('hex'), - } - } - - dkgGetPublicPackage = async (): Promise => { - if (!this.app) { - throw new Error('Connect to Ledger first') - } - - const response = await this.tryInstruction((app) => app.dkgGetPublicPackage()) - - return response.publicPackage - } - - reviewTransaction = async (transaction: string): Promise => { - if (!this.app) { - throw new Error('Connect to Ledger first') - } - - this.logger.info( - 'Please review and approve the outputs of this transaction on your ledger device.', - ) - - const { hash } = await this.tryInstruction((app) => app.reviewTransaction(transaction)) - - return hash - } - - dkgGetCommitments = async (transactionHash: string): Promise => { - if (!this.app) { - throw new Error('Connect to Ledger first') - } - - const { commitments } = await this.tryInstruction((app) => - app.dkgGetCommitments(transactionHash), - ) - - return commitments - } - - dkgSign = async ( - randomness: string, - frostSigningPackage: string, - transactionHash: string, - ): Promise => { - if (!this.app) { - throw new Error('Connect to Ledger first') - } - - const { signature } = await this.tryInstruction((app) => - app.dkgSign(randomness, frostSigningPackage, transactionHash), - ) - - return signature - } - - dkgBackupKeys = async (): Promise => { - if (!this.app) { - throw new Error('Connect to Ledger first') - } - - this.logger.log('Please approve the request on your ledger device.') - - const { encryptedKeys } = await this.tryInstruction((app) => app.dkgBackupKeys()) - - return encryptedKeys - } - - dkgRestoreKeys = async (encryptedKeys: string): Promise => { - if (!this.app) { - throw new Error('Connect to Ledger first') - } - - this.logger.log('Please approve the request on your ledger device.') - - await this.tryInstruction((app) => app.dkgRestoreKeys(encryptedKeys)) - } -} export class Ledger { app: IronfishApp | undefined @@ -276,7 +23,7 @@ export class Ledger { } connect = async () => { - const transport = await TransportNodeHid.create(3000) + const transport = await TransportNodeHid.create(3000, 3000) if (transport.deviceModel) { this.logger.debug(`${transport.deviceModel.productName} found.`) @@ -296,10 +43,10 @@ export class Ledger { // https://github.com/LedgerHQ/ledger-live/blob/173bb3c84cc855f83ab8dc49362bc381afecc31e/libs/ledgerjs/packages/errors/src/index.ts#L263 // https://github.com/Zondax/ledger-ironfish/blob/bf43a4b8d403d15138699ee3bb1a3d6dfdb428bc/docs/APDUSPEC.md?plain=1#L25 if (appInfo.returnCode === 0x5515) { - throw new LedgerError('Please unlock your Ledger device.') + throw new Error('Please unlock your Ledger device.') } - throw new LedgerError('Please open the Iron Fish app on your ledger device.') + throw new Error('Please open the Iron Fish app on your ledger device.') } if (appInfo.appVersion) { @@ -374,7 +121,7 @@ export class Ledger { } const accountImport: AccountImport = { - version: ACCOUNT_SCHEMA_VERSION, + version: 4, // ACCOUNT_SCHEMA_VERSION as of 2024-05 name: 'ledger', viewKey: responseViewKey.viewKey.toString('hex'), incomingViewKey: responseViewKey.ivk.toString('hex'), @@ -413,121 +160,3 @@ export class Ledger { return response.signature } } - -function isResponseAddress(response: KeyResponse): response is ResponseAddressDkg { - return 'publicAddress' in response -} - -function isResponseViewKey(response: KeyResponse): response is ResponseViewKeyDkg { - return 'viewKey' in response -} - -function isResponseProofGenKey(response: KeyResponse): response is ResponseProofGenKeyDkg { - return 'ak' in response -} - -function isResponseError(error: unknown): error is ResponseError { - return 'errorMessage' in (error as object) && 'returnCode' in (error as object) -} - -export class LedgerError extends Error { - name = this.constructor.name -} - -export class LedgerDeviceLockedError extends LedgerError { - static returnCode = 0x5515 -} - -export class LedgerAppUnavailableError extends LedgerError { - static returnCodes = [ - 0x6d00, // Instruction not supported - 0xffff, // Unknown transport error - 0x6f00, // Technical error - ] - - constructor() { - super( - `Unable to connect to Ironfish app on Ledger. Please check that the device is unlocked and the app is open.`, - ) - } -} - -export async function sendTransactionWithLedger( - client: RpcClient, - raw: RawTransaction, - from: string | undefined, - watch: boolean, - confirm: boolean, - logger?: Logger, -): Promise { - const ledger = new Ledger(logger) - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - Errors.error(e.message) - } else { - throw e - } - } - - const publicKey = (await client.wallet.getAccountPublicKey({ account: from })).content - .publicKey - - const ledgerPublicKey = await ledger.getPublicAddress() - - if (publicKey !== ledgerPublicKey) { - Errors.error( - `The public key on the ledger device does not match the public key of the account '${from}'`, - ) - } - - const buildTransactionResponse = await client.wallet.buildTransaction({ - account: from, - rawTransaction: RawTransactionSerde.serialize(raw).toString('hex'), - }) - - const unsignedTransaction = buildTransactionResponse.content.unsignedTransaction - - const signature = (await ledger.sign(unsignedTransaction)).toString('hex') - - ux.stdout(`\nSignature: ${signature}`) - - const addSignatureResponse = await client.wallet.addSignature({ - unsignedTransaction, - signature, - }) - - const signedTransaction = addSignatureResponse.content.transaction - const bytes = Buffer.from(signedTransaction, 'hex') - - const transaction = new Transaction(bytes) - - ux.stdout(`\nSigned Transaction: ${signedTransaction}`) - ux.stdout(`\nHash: ${transaction.hash().toString('hex')}`) - ux.stdout(`Fee: ${CurrencyUtils.render(transaction.fee(), true)}`) - - await ui.confirmOrQuit('Would you like to broadcast this transaction?', confirm) - - const addTransactionResponse = await client.wallet.addTransaction({ - transaction: signedTransaction, - broadcast: true, - }) - - if (addTransactionResponse.content.accepted === false) { - Errors.error( - `Transaction '${transaction.hash().toString('hex')}' was not accepted into the mempool`, - ) - } - - if (watch) { - ux.stdout('') - - await watchTransaction({ - client, - logger, - account: from, - hash: transaction.hash().toString('hex'), - }) - } -} diff --git a/ironfish-cli/src/utils/transaction.ts b/ironfish-cli/src/utils/transaction.ts index b87aef8900..b60e4ddccf 100644 --- a/ironfish-cli/src/utils/transaction.ts +++ b/ironfish-cli/src/utils/transaction.ts @@ -5,13 +5,11 @@ import { Asset } from '@ironfish/rust-nodejs' import { assetMetadataWithDefaults, - BurnDescription, createRootLogger, CurrencyUtils, GetTransactionNotesResponse, GetUnsignedTransactionNotesResponse, Logger, - MintDescription, PromiseUtils, RawTransaction, RpcAsset, @@ -21,9 +19,11 @@ import { TransactionStatus, UnsignedTransaction, } from '@ironfish/sdk' +import { BurnDescription } from '@ironfish/sdk/src/primitives/burnDescription' +import { MintDescription } from '@ironfish/sdk/src/primitives/mintDescription' import { ux } from '@oclif/core' import { ProgressBar, ProgressBarPresets } from '../ui' -import { getAssetVerificationByIds } from './asset' +import { getAssetsByIDs, getAssetVerificationByIds } from './asset' export class TransactionTimer { private progressBar: ProgressBar | undefined @@ -167,7 +167,7 @@ async function _renderTransactionDetails( logger = logger ?? createRootLogger() const assetIds = collectAssetIds(mints, burns, notes) - const assetLookup = await getAssetVerificationByIds(client, assetIds, account, undefined) + const assetLookup = await getAssetsByIDs(client, assetIds, account, undefined) if (mints.length > 0) { logger.log('') @@ -185,7 +185,7 @@ async function _renderTransactionDetails( mint.value, false, mint.asset.id().toString('hex'), - assetLookup[mint.asset.id().toString('hex')], + assetLookup[mint.asset.id().toString('hex')].verification, ) logger.log(`Asset ID: ${mint.asset.id().toString('hex')}`) logger.log(`Name: ${mint.asset.name().toString('utf8')}`) @@ -218,7 +218,7 @@ async function _renderTransactionDetails( burn.value, false, burn.assetId.toString('hex'), - assetLookup[burn.assetId.toString('hex')], + assetLookup[burn.assetId.toString('hex')].verification, ) logger.log(`Asset ID: ${burn.assetId.toString('hex')}`) logger.log(`Amount: ${renderedAmount}`) @@ -243,20 +243,13 @@ async function _renderTransactionDetails( } logger.log('') - const verifiedAssetMetadata = assetLookup[note.assetId] - const renderedAmount = CurrencyUtils.render( note.value, true, note.assetId, - verifiedAssetMetadata, + assetLookup[note.assetId].verification, ) logger.log(`Amount: ${renderedAmount}`) - - if (verifiedAssetMetadata?.symbol) { - logger.log(`Asset ID: ${note.assetId}`) - } - logger.log(`Memo: ${note.memo}`) logger.log(`Recipient: ${note.owner}`) logger.log(`Sender: ${note.sender}`) @@ -274,20 +267,13 @@ async function _renderTransactionDetails( } logger.log('') - const verifiedAssetMetadata = assetLookup[note.assetId] - const renderedAmount = CurrencyUtils.render( note.value, true, note.assetId, - verifiedAssetMetadata, + assetLookup[note.assetId].verification, ) logger.log(`Amount: ${renderedAmount}`) - - if (verifiedAssetMetadata?.symbol) { - logger.log(`Asset ID: ${note.assetId}`) - } - logger.log(`Memo: ${note.memo}`) logger.log(`Recipient: ${note.owner}`) logger.log(`Sender: ${note.sender}`) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index a53f89fa9e..eb4ed16352 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -337,7 +337,6 @@ export namespace multisig { static fromFrost(frostSignatureShare: Buffer, identity: Buffer): NativeSignatureShare identity(): Buffer frostSignatureShare(): Buffer - serialize(): Buffer } export class ParticipantSecret { constructor(jsBytes: Buffer) diff --git a/ironfish-rust-nodejs/npm/darwin-arm64/package.json b/ironfish-rust-nodejs/npm/darwin-arm64/package.json index 0c09d5c874..eeddf59ab5 100644 --- a/ironfish-rust-nodejs/npm/darwin-arm64/package.json +++ b/ironfish-rust-nodejs/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-darwin-arm64", - "version": "2.7.0", + "version": "2.6.0", "os": [ "darwin" ], diff --git a/ironfish-rust-nodejs/npm/darwin-x64/package.json b/ironfish-rust-nodejs/npm/darwin-x64/package.json index 7485c7d570..d34397a1f7 100644 --- a/ironfish-rust-nodejs/npm/darwin-x64/package.json +++ b/ironfish-rust-nodejs/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-darwin-x64", - "version": "2.7.0", + "version": "2.6.0", "os": [ "darwin" ], diff --git a/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json b/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json index 2a6630ddcf..9a3ab7c0f6 100644 --- a/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json +++ b/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-arm64-gnu", - "version": "2.7.0", + "version": "2.6.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json b/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json index e8e3dd548e..fabe279e18 100644 --- a/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json +++ b/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-arm64-musl", - "version": "2.7.0", + "version": "2.6.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json b/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json index 3df3885f9a..0041a8bae1 100644 --- a/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json +++ b/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-x64-gnu", - "version": "2.7.0", + "version": "2.6.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-x64-musl/package.json b/ironfish-rust-nodejs/npm/linux-x64-musl/package.json index 8fe9f2d953..0f10be1936 100644 --- a/ironfish-rust-nodejs/npm/linux-x64-musl/package.json +++ b/ironfish-rust-nodejs/npm/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-x64-musl", - "version": "2.7.0", + "version": "2.6.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json b/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json index f3809d4733..2a7e344884 100644 --- a/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json +++ b/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-win32-x64-msvc", - "version": "2.7.0", + "version": "2.6.0", "os": [ "win32" ], diff --git a/ironfish-rust-nodejs/package.json b/ironfish-rust-nodejs/package.json index 11b3fa9a29..4b61d3bad9 100644 --- a/ironfish-rust-nodejs/package.json +++ b/ironfish-rust-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs", - "version": "2.7.0", + "version": "2.6.0", "description": "Node.js bindings for Rust code required by the Iron Fish SDK", "main": "index.js", "types": "index.d.ts", diff --git a/ironfish-rust-nodejs/src/multisig.rs b/ironfish-rust-nodejs/src/multisig.rs index 52f8ca989f..dde0171e5f 100644 --- a/ironfish-rust-nodejs/src/multisig.rs +++ b/ironfish-rust-nodejs/src/multisig.rs @@ -190,11 +190,6 @@ impl NativeSignatureShare { .as_slice(), ) } - - #[napi] - pub fn serialize(&self) -> Buffer { - Buffer::from(self.signature_share.serialize().as_slice()) - } } #[napi(namespace = "multisig")] diff --git a/ironfish/package.json b/ironfish/package.json index 8c68235061..2e3e59653b 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/sdk", - "version": "2.7.0", + "version": "2.6.0", "description": "SDK for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -22,7 +22,7 @@ "dependencies": { "@ethersproject/bignumber": "5.7.0", "@fast-csv/format": "4.3.5", - "@ironfish/rust-nodejs": "2.7.0", + "@ironfish/rust-nodejs": "2.6.0", "@napi-rs/blake-hash": "1.3.3", "axios": "1.7.2", "bech32": "2.0.0", diff --git a/ironfish/src/assets/assetsVerificationApi.test.ts b/ironfish/src/assets/assetsVerificationApi.test.ts index ffc87745f2..bf177144bc 100644 --- a/ironfish/src/assets/assetsVerificationApi.test.ts +++ b/ironfish/src/assets/assetsVerificationApi.test.ts @@ -3,10 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import nock from 'nock' import { NodeFileProvider } from '../fileSystems' -import { - AssetsVerificationApi, - getDefaultAssetVerificationEndpoint, -} from './assetsVerificationApi' +import { AssetsVerificationApi } from './assetsVerificationApi' const assetData1 = { identifier: '0123', @@ -242,26 +239,3 @@ describe('Assets Verification API Client', () => { }) }) }) - -describe('getDefaultAssetVerificationEndpoint', () => { - it('returns the testnet url with the testnet id', () => { - expect(getDefaultAssetVerificationEndpoint(0)).toEqual( - 'https://testnet.api.ironfish.network/assets/verified_metadata', - ) - }) - - it('returns the regular url with any other id', () => { - expect(getDefaultAssetVerificationEndpoint(1)).toEqual( - 'https://api.ironfish.network/assets/verified_metadata', - ) - expect(getDefaultAssetVerificationEndpoint(10)).toEqual( - 'https://api.ironfish.network/assets/verified_metadata', - ) - }) - - it('returns the regular url with no id', () => { - expect(getDefaultAssetVerificationEndpoint()).toEqual( - 'https://api.ironfish.network/assets/verified_metadata', - ) - }) -}) diff --git a/ironfish/src/assets/assetsVerificationApi.ts b/ironfish/src/assets/assetsVerificationApi.ts index 32835f8b35..491e980ea7 100644 --- a/ironfish/src/assets/assetsVerificationApi.ts +++ b/ironfish/src/assets/assetsVerificationApi.ts @@ -79,8 +79,8 @@ export class AssetsVerificationApi { readonly url: string - constructor(options: { files: FileSystem; url: string; timeout?: number }) { - this.url = options.url + constructor(options: { files: FileSystem; url?: string; timeout?: number }) { + this.url = options?.url || 'https://api.ironfish.network/assets/verified_metadata' this.timeout = options?.timeout ?? 30 * 1000 // 30 seconds this.adapter = isFileUrl(this.url) ? axiosFileAdapter(options.files) @@ -130,14 +130,6 @@ export class AssetsVerificationApi { } } -export function getDefaultAssetVerificationEndpoint(networkId?: number): string { - if (networkId === 0) { - return 'https://testnet.api.ironfish.network/assets/verified_metadata' - } - - return 'https://api.ironfish.network/assets/verified_metadata' -} - const isFileUrl = (url: string): boolean => { const parsedUrl = new URL(url) return parsedUrl.protocol === 'file:' diff --git a/ironfish/src/assets/assetsVerifier.test.ts b/ironfish/src/assets/assetsVerifier.test.ts index 8eb23e6363..83b182d16a 100644 --- a/ironfish/src/assets/assetsVerifier.test.ts +++ b/ironfish/src/assets/assetsVerifier.test.ts @@ -7,8 +7,6 @@ import { VerifiedAssetsCacheStore } from '../fileStores/verifiedAssets' import { NodeFileProvider } from '../fileSystems' import { AssetsVerifier } from './assetsVerifier' -const apiUrl = 'https://example.com/endpoint' - /* eslint-disable jest/no-standalone-expect */ /* eslint-disable @typescript-eslint/no-explicit-any */ const assetData1 = { @@ -60,7 +58,7 @@ describe('AssetsVerifier', () => { }) it('does not refresh when not started', () => { - const assetsVerifier = new AssetsVerifier({ files, apiUrl }) + const assetsVerifier = new AssetsVerifier({ files }) const refresh = jest.spyOn(assetsVerifier as any, 'refresh') jest.runOnlyPendingTimers() @@ -193,7 +191,7 @@ describe('AssetsVerifier', () => { describe('verify', () => { it("returns 'unknown' when not started", () => { - const assetsVerifier = new AssetsVerifier({ files, apiUrl }) + const assetsVerifier = new AssetsVerifier({ files }) expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'unknown' }) expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unknown' }) diff --git a/ironfish/src/assets/assetsVerifier.ts b/ironfish/src/assets/assetsVerifier.ts index d7e08c4735..9f40b6f9a0 100644 --- a/ironfish/src/assets/assetsVerifier.ts +++ b/ironfish/src/assets/assetsVerifier.ts @@ -35,13 +35,13 @@ export class AssetsVerifier { constructor(options: { files: FileSystem - apiUrl: string + apiUrl?: string cache?: VerifiedAssetsCacheStore logger?: Logger }) { - this.logger = options.logger ?? createRootLogger() - this.api = new AssetsVerificationApi({ url: options.apiUrl, files: options.files }) - this.cache = options.cache + this.logger = options?.logger ?? createRootLogger() + this.api = new AssetsVerificationApi({ url: options?.apiUrl, files: options.files }) + this.cache = options?.cache this.started = false if (this.cache?.config?.apiUrl === this.api.url) { diff --git a/ironfish/src/devUtils/index.ts b/ironfish/src/devUtils/index.ts deleted file mode 100644 index 951be1475c..0000000000 --- a/ironfish/src/devUtils/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -// The devUtils module contains exportable utilities for testing and -// development. This module must NOT import any test libraries, fixtures, mocks, -// or other dev dependencies. -export * from './witness' diff --git a/ironfish/src/index.ts b/ironfish/src/index.ts index a8da92f77e..1ee9e55d5b 100644 --- a/ironfish/src/index.ts +++ b/ironfish/src/index.ts @@ -28,4 +28,3 @@ export * from './package' export * from './platform' export * from './primitives' export { getFeeRate } from './memPool' -export * as devUtils from './devUtils' diff --git a/ironfish/src/multisig.test.slow.ts b/ironfish/src/multisig.test.slow.ts index c21d7dad40..f07eaa92c6 100644 --- a/ironfish/src/multisig.test.slow.ts +++ b/ironfish/src/multisig.test.slow.ts @@ -3,9 +3,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Asset, multisig, Note as NativeNote, verifyTransactions } from '@ironfish/rust-nodejs' -import { makeFakeWitness } from './devUtils' import { Note, RawTransaction } from './primitives' import { Transaction, TransactionVersion } from './primitives/transaction' +import { makeFakeWitness } from './testUtilities' describe('multisig', () => { describe('dkg', () => { diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index 34e411e9a9..bdd70abd05 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { BoxKeyPair, FishHashContext } from '@ironfish/rust-nodejs' import { v4 as uuid } from 'uuid' -import { AssetsVerifier, getDefaultAssetVerificationEndpoint } from './assets' +import { AssetsVerifier } from './assets' import { Blockchain } from './blockchain' import { BlockHasher } from './blockHasher' import { @@ -223,6 +223,16 @@ export class FullNode { const peerStore = new PeerStore(files, dataDir) await peerStore.load() + const verifiedAssetsCache = new VerifiedAssetsCacheStore(files, dataDir) + await verifiedAssetsCache.load() + + const assetsVerifier = new AssetsVerifier({ + files, + apiUrl: config.get('assetVerificationApi'), + cache: verifiedAssetsCache, + logger, + }) + const numWorkers = calculateWorkers(config.get('nodeWorkers'), config.get('nodeWorkersMax')) const workerPool = new WorkerPool({ logger, metrics, numWorkers }) @@ -239,17 +249,6 @@ export class FullNode { const network = new Network(networkDefinition) - const verifiedAssetsCache = new VerifiedAssetsCacheStore(files, dataDir) - await verifiedAssetsCache.load() - - const assetsVerifier = new AssetsVerifier({ - files, - apiUrl: - config.get('assetVerificationApi') || getDefaultAssetVerificationEndpoint(network.id), - cache: verifiedAssetsCache, - logger, - }) - if (!config.isSet('bootstrapNodes')) { config.setOverride('bootstrapNodes', network.bootstrapNodes) } diff --git a/ironfish/src/primitives/index.ts b/ironfish/src/primitives/index.ts index 50e79ca522..658cf72313 100644 --- a/ironfish/src/primitives/index.ts +++ b/ironfish/src/primitives/index.ts @@ -10,5 +10,3 @@ export { Target } from './target' export { Transaction } from './transaction' export { RawTransaction, RawTransactionSerde } from './rawTransaction' export { UnsignedTransaction } from './unsignedTransaction' -export { MintDescription } from './mintDescription' -export { BurnDescription } from './burnDescription' diff --git a/ironfish/src/primitives/rawTransaction.test.slow.ts b/ironfish/src/primitives/rawTransaction.test.slow.ts index faf298d2b6..cf7e19128f 100644 --- a/ironfish/src/primitives/rawTransaction.test.slow.ts +++ b/ironfish/src/primitives/rawTransaction.test.slow.ts @@ -2,8 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Asset, generateKey, Note as NativeNote } from '@ironfish/rust-nodejs' -import { makeFakeWitness } from '../devUtils' -import { useAccountFixture, useTxFixture } from '../testUtilities' +import { makeFakeWitness, useAccountFixture, useTxFixture } from '../testUtilities' import { createNodeTest } from '../testUtilities/nodeTest' import { SpendingAccount } from '../wallet' import { Note } from './note' diff --git a/ironfish/src/primitives/rawTransaction.test.ts b/ironfish/src/primitives/rawTransaction.test.ts index bf08438d47..e29ebc9ee4 100644 --- a/ironfish/src/primitives/rawTransaction.test.ts +++ b/ironfish/src/primitives/rawTransaction.test.ts @@ -4,8 +4,8 @@ import { Asset, generateKey, Note as NativeNote } from '@ironfish/rust-nodejs' import { BufferMap } from 'buffer-map' import { Assert } from '../assert' -import { makeFakeWitness } from '../devUtils' import { IsNoteWitnessEqual } from '../merkletree/witness' +import { makeFakeWitness } from '../testUtilities' import { useAccountFixture, useMinerBlockFixture, diff --git a/ironfish/src/primitives/unsignedTransaction.ts b/ironfish/src/primitives/unsignedTransaction.ts index 86a2b50fda..df644f472b 100644 --- a/ironfish/src/primitives/unsignedTransaction.ts +++ b/ironfish/src/primitives/unsignedTransaction.ts @@ -164,16 +164,4 @@ export class UnsignedTransaction { return result } - - hash(): Buffer { - const hash = this.takeReference().hash() - this.returnReference() - return hash - } - - publicKeyRandomness(): string { - const publicKeyRandomness = this.takeReference().publicKeyRandomness() - this.returnReference() - return publicKeyRandomness - } } diff --git a/ironfish/src/rpc/adapters/errors.ts b/ironfish/src/rpc/adapters/errors.ts index 31898515de..5835d90c0d 100644 --- a/ironfish/src/rpc/adapters/errors.ts +++ b/ironfish/src/rpc/adapters/errors.ts @@ -11,7 +11,6 @@ export enum RPC_ERROR_CODES { INSUFFICIENT_BALANCE = 'insufficient-balance', UNAUTHENTICATED = 'unauthenticated', NOT_FOUND = 'not-found', - IDENTITY_NOT_FOUND = 'identity-not-found', DUPLICATE_ACCOUNT_NAME = 'duplicate-account-name', DUPLICATE_IDENTITY_NAME = 'duplicate-identity-name', IMPORT_ACCOUNT_NAME_REQUIRED = 'import-account-name-required', diff --git a/ironfish/src/rpc/routes/wallet/multisig/dkg/round3.test.ts b/ironfish/src/rpc/routes/wallet/multisig/dkg/round3.test.ts index f6d3068881..9fbba85f09 100644 --- a/ironfish/src/rpc/routes/wallet/multisig/dkg/round3.test.ts +++ b/ironfish/src/rpc/routes/wallet/multisig/dkg/round3.test.ts @@ -52,8 +52,6 @@ describe('Route multisig/dkg/round3', () => { ), ) - const accountCreatedAt = 2 - // Perform DKG round 3 const round3Responses = await Promise.all( participantNames.map((participantName, index) => @@ -63,7 +61,6 @@ describe('Route multisig/dkg/round3', () => { round2SecretPackage: round2Packages[index].content.round2SecretPackage, round1PublicPackages: round1Packages.map((pkg) => pkg.content.round1PublicPackage), round2PublicPackages: round2Packages.map((pkg) => pkg.content.round2PublicPackage), - accountCreatedAt, }), ), ) @@ -101,13 +98,6 @@ describe('Route multisig/dkg/round3', () => { .sort() expect(knownIdentities).toStrictEqual(expectedIdentities) } - - // Check that all imported accounts have createdAt sequence set - for (const accountName of accountNames) { - const account = routeTest.wallet.getAccountByName(accountName) - Assert.isNotNull(account) - expect(account.createdAt?.sequence).toEqual(accountCreatedAt) - } }) it('should fail if not all round 1 packages are passed as an input', async () => { diff --git a/ironfish/src/rpc/routes/wallet/multisig/dkg/round3.ts b/ironfish/src/rpc/routes/wallet/multisig/dkg/round3.ts index 448ec7fe44..0912b324db 100644 --- a/ironfish/src/rpc/routes/wallet/multisig/dkg/round3.ts +++ b/ironfish/src/rpc/routes/wallet/multisig/dkg/round3.ts @@ -16,7 +16,6 @@ export type DkgRound3Request = { round1PublicPackages: Array round2PublicPackages: Array accountName?: string - accountCreatedAt?: number } export type DkgRound3Response = { @@ -31,7 +30,6 @@ export const DkgRound3RequestSchema: yup.ObjectSchema = yup round1PublicPackages: yup.array().of(yup.string().defined()).defined(), round2PublicPackages: yup.array().of(yup.string().defined()).defined(), accountName: yup.string().optional(), - accountCreatedAt: yup.number().optional(), }) .defined() @@ -94,9 +92,7 @@ routes.register( }, } - const account = await node.wallet.importAccount(accountImport, { - createdAt: request.data.accountCreatedAt, - }) + const account = await node.wallet.importAccount(accountImport) await node.wallet.skipRescan(account) request.end({ diff --git a/ironfish/src/rpc/routes/wallet/multisig/getIdentity.ts b/ironfish/src/rpc/routes/wallet/multisig/getIdentity.ts index 5c98a4f83f..881b559970 100644 --- a/ironfish/src/rpc/routes/wallet/multisig/getIdentity.ts +++ b/ironfish/src/rpc/routes/wallet/multisig/getIdentity.ts @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' -import { RPC_ERROR_CODES, RpcValidationError } from '../../../adapters/errors' +import { RpcValidationError } from '../../../adapters/errors' import { ApiNamespace } from '../../namespaces' import { routes } from '../../router' import { AssertHasRpcContext } from '../../rpcContext' @@ -37,11 +37,7 @@ routes.register( const identity = await context.wallet.walletDb.getMultisigIdentityByName(name) if (identity === undefined) { - throw new RpcValidationError( - `No identity found with name ${name}`, - 404, - RPC_ERROR_CODES.IDENTITY_NOT_FOUND, - ) + throw new RpcValidationError(`No identity found with name ${name}`, 404) } request.end({ identity: identity.toString('hex') }) diff --git a/ironfish/src/rpc/routes/wallet/multisig/importParticipant.ts b/ironfish/src/rpc/routes/wallet/multisig/importParticipant.ts index 19b1f48909..0e1de10590 100644 --- a/ironfish/src/rpc/routes/wallet/multisig/importParticipant.ts +++ b/ironfish/src/rpc/routes/wallet/multisig/importParticipant.ts @@ -45,12 +45,13 @@ routes.register