Skip to content

Commit

Permalink
adds ledger option for creating signing commitment (#5398)
Browse files Browse the repository at this point in the history
* adds ledger option for creating signing commitment

implements dkgGetCommitments on Ledger util class

adds '--ledger' flag to 'wallet:multisig:commitment:create'

adds multisig account selector to 'commitment:create', fetches the identity for
the selected account

adds 'hash' method to UnsignedTransaction primitive

* adds ledger flag for creating signature shares (#5399)

implements dkgSign on Ledger util class to obtain a signature share from the
device

adds '--ledger' flag to 'wallet:multisig:signature:create' command

adds selector to command to choose multisig account if one is not provided

adds 'publicKeyRandomness' method to UnsignedTransaction primitive to extract
the randomness from the transaction (needed for signing)

adds 'serialize' napi method to NativeSignatureShare to support constructing the
signature share from its raw parts and then printing it
  • Loading branch information
hughy authored and patnir committed Sep 24, 2024
1 parent bd8d566 commit e1ff7d0
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 6 deletions.
64 changes: 61 additions & 3 deletions ironfish-cli/src/commands/wallet/multisig/commitment/create.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* 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 { UnsignedTransaction } from '@ironfish/sdk'
import { multisig } from '@ironfish/rust-nodejs'
import { RpcClient, UnsignedTransaction } from '@ironfish/sdk'
import { Flags } from '@oclif/core'
import { IronfishCommand } from '../../../../command'
import { RemoteFlags } from '../../../../flags'
import * as ui from '../../../../ui'
import { Ledger } from '../../../../utils/ledger'
import { MultisigTransactionJson } from '../../../../utils/multisig'
import { renderUnsignedTransactionDetails } from '../../../../utils/transaction'

Expand Down Expand Up @@ -36,6 +38,10 @@ 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<void> {
Expand All @@ -47,6 +53,11 @@ 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(
Expand Down Expand Up @@ -77,14 +88,24 @@ export class CreateSigningCommitmentCommand extends IronfishCommand {
await renderUnsignedTransactionDetails(
client,
unsignedTransaction,
flags.account,
participantName,
this.logger,
)

await ui.confirmOrQuit('Confirm signing commitment creation', flags.confirm)

if (flags.ledger) {
await this.createSigningCommitmentWithLedger(
client,
participantName,
unsignedTransaction.hash(),
identities,
)
return
}

const response = await client.wallet.multisig.createSigningCommitment({
account: flags.account,
account: participantName,
unsignedTransaction: unsignedTransactionInput,
signers: identities.map((identity) => ({ identity })),
})
Expand All @@ -96,4 +117,41 @@ 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,
transactionHash: Buffer,
signers: string[],
): Promise<void> {
const ledger = new Ledger(this.logger)
try {
await ledger.connect(true)
} 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 rawCommitments = await ledger.dkgGetCommitments(transactionHash.toString('hex'))

const sigingCommitment = multisig.SigningCommitment.fromRaw(
identity,
rawCommitments,
transactionHash,
signers,
)

this.log('\nCommitment:\n')
this.log(sigingCommitment.serialize().toString('hex'))

this.log()
this.log('Next step:')
this.log('Send the commitment to the multisig account coordinator.')
}
}
68 changes: 65 additions & 3 deletions ironfish-cli/src/commands/wallet/multisig/signature/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
* 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 { UnsignedTransaction } from '@ironfish/sdk'
import { RpcClient, UnsignedTransaction } from '@ironfish/sdk'
import { Flags } from '@oclif/core'
import { IronfishCommand } from '../../../../command'
import { RemoteFlags } from '../../../../flags'
import * as ui from '../../../../ui'
import { Ledger } from '../../../../utils/ledger'
import { MultisigTransactionJson } from '../../../../utils/multisig'
import { renderUnsignedTransactionDetails } from '../../../../utils/transaction'

Expand All @@ -30,6 +31,10 @@ 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<void> {
Expand All @@ -41,6 +46,11 @@ 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')
Expand All @@ -56,16 +66,26 @@ export class CreateSignatureShareCommand extends IronfishCommand {
await renderUnsignedTransactionDetails(
client,
unsignedTransaction,
flags.account,
participantName,
this.logger,
)

if (!flags.confirm) {
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: flags.account,
account: participantName,
signingPackage: signingPackageString,
})

Expand Down Expand Up @@ -93,4 +113,46 @@ export class CreateSignatureShareCommand extends IronfishCommand {
this.log(signer.toString('hex'))
}
}

async createSignatureShareWithLedger(
client: RpcClient,
participantName: string,
unsignedTransaction: UnsignedTransaction,
frostSigningPackage: string,
): Promise<void> {
const ledger = new Ledger(this.logger)
try {
await ledger.connect(true)
} 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 frostSignatureShare = await ledger.dkgSign(
unsignedTransaction.publicKeyRandomness(),
frostSigningPackage,
unsignedTransaction.hash().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.',
)
}
}
28 changes: 28 additions & 0 deletions ironfish-cli/src/utils/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,34 @@ export class Ledger {

return response.publicPackage
}

dkgGetCommitments = async (transactionHash: string): Promise<Buffer> => {
if (!this.app) {
throw new Error('Connect to Ledger first')
}

const { commitments } = await this.tryInstruction(
this.app.dkgGetCommitments(transactionHash),
)

return commitments
}

dkgSign = async (
randomness: string,
frostSigningPackage: string,
transactionHash: string,
): Promise<Buffer> => {
if (!this.app) {
throw new Error('Connect to Ledger first')
}

const { signature } = await this.tryInstruction(
this.app.dkgSign(randomness, frostSigningPackage, transactionHash),
)

return signature
}
}

function isResponseAddress(response: KeyResponse): response is ResponseAddress {
Expand Down
1 change: 1 addition & 0 deletions ironfish-rust-nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ export namespace multisig {
static fromFrost(frostSignatureShare: Buffer, identity: Buffer): NativeSignatureShare
identity(): Buffer
frostSignatureShare(): Buffer
serialize(): Buffer
}
export class ParticipantSecret {
constructor(jsBytes: Buffer)
Expand Down
5 changes: 5 additions & 0 deletions ironfish-rust-nodejs/src/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ impl NativeSignatureShare {
.as_slice(),
)
}

#[napi]
pub fn serialize(&self) -> Buffer {
Buffer::from(self.signature_share.serialize().as_slice())
}
}

#[napi(namespace = "multisig")]
Expand Down
12 changes: 12 additions & 0 deletions ironfish/src/primitives/unsignedTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,16 @@ 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
}
}

0 comments on commit e1ff7d0

Please sign in to comment.