From 2f6b61892e7cbfdd62ab9451cdcfa0b459120b0c Mon Sep 17 00:00:00 2001 From: Hugh Cunningham Date: Wed, 18 Sep 2024 16:35:38 -0700 Subject: [PATCH] adds ledger flag to participant creation adds dkgGetIDentity method to ledger util module adds '--ledger' flag to 'wallet:multisig:participant:create' command uses 'wallet/multisig/importParticipant' RPC to add the participant identity to the walletDb --- .../wallet/multisig/participant/create.ts | 39 ++++++++++++++++++- ironfish-cli/src/utils/ledger.ts | 36 ++++++++++++----- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/participant/create.ts b/ironfish-cli/src/commands/wallet/multisig/participant/create.ts index 1621686631..2b08ff04f4 100644 --- a/ironfish-cli/src/commands/wallet/multisig/participant/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/participant/create.ts @@ -6,6 +6,7 @@ import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import * as ui from '../../../../ui' +import { Ledger } from '../../../../utils/ledger' export class MultisigIdentityCreate extends IronfishCommand { static description = `Create a multisig participant identity` @@ -16,6 +17,11 @@ 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 { @@ -29,14 +35,27 @@ export class MultisigIdentityCreate extends IronfishCommand { name = await ui.inputPrompt('Enter a name for the identity', true) } + let identity + if (flags.ledger) { + identity = await this.createParticipantWithLedger() + } + let response while (!response) { try { - response = await client.wallet.multisig.createParticipant({ name }) + if (identity) { + response = await client.wallet.multisig.importParticipant({ + name, + identity: identity.toString('hex'), + }) + } else { + 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_ACCOUNT_NAME.toString() || + e.code === RPC_ERROR_CODES.DUPLICATE_IDENTITY_NAME.toString()) ) { this.log() this.log(e.codeMessage) @@ -50,4 +69,20 @@ export class MultisigIdentityCreate extends IronfishCommand { this.log('Identity:') this.log(response.content.identity) } + + async createParticipantWithLedger(): Promise { + const ledger = new Ledger(this.logger) + try { + await ledger.connect(true) + } 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/utils/ledger.ts b/ironfish-cli/src/utils/ledger.ts index a5850907cf..e782f9915d 100644 --- a/ironfish-cli/src/utils/ledger.ts +++ b/ironfish-cli/src/utils/ledger.ts @@ -8,6 +8,7 @@ import IronfishApp, { IronfishKeys, KeyResponse, ResponseAddress, + ResponseIdentity, ResponseProofGenKey, ResponseSign, ResponseViewKey, @@ -45,26 +46,29 @@ export class Ledger { } } - connect = async () => { + connect = async (dkg = false) => { const transport = await TransportNodeHid.create(3000, 3000) if (transport.deviceModel) { this.logger.debug(`${transport.deviceModel.productName} found.`) } - const app = new IronfishApp(transport, false) + const app = new IronfishApp(transport, dkg) - const appInfo = await this.tryInstruction(app.appInfo()) + // TODO: remove this condition if appInfo is available in the DKG app + if (!dkg) { + const appInfo = await this.tryInstruction(app.appInfo()) - this.logger.debug(appInfo.appName ?? 'no app name') - - if (appInfo.appName !== 'Ironfish') { this.logger.debug(appInfo.appName ?? 'no app name') - throw new Error('Please open the Iron Fish app on your ledger device.') - } - if (appInfo.appVersion) { - this.logger.debug(`Ironfish App Version: ${appInfo.appVersion}`) + if (appInfo.appName !== 'Ironfish') { + this.logger.debug(appInfo.appName ?? 'no app name') + throw new Error('Please open the Iron Fish app on your ledger device.') + } + + if (appInfo.appVersion) { + this.logger.debug(`Ironfish App Version: ${appInfo.appVersion}`) + } } this.app = app @@ -152,6 +156,18 @@ export class Ledger { return response.signature } + + dkgGetIdentity = async (index: number): Promise => { + if (!this.app) { + throw new Error('Connect to Ledger first') + } + + this.logger.log('Please approve the request on your ledger device.') + + const response: ResponseIdentity = await this.tryInstruction(this.app.dkgGetIdentity(index)) + + return response.identity + } } function isResponseAddress(response: KeyResponse): response is ResponseAddress {