From 8da28862efc77c61a7e8d9fcea3b92b470765fc1 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:25:05 -0700 Subject: [PATCH 01/81] adds session manager to wrap multisig client interaction (#5530) implements two session managers: one for dkg and one for signing moves logic for waiting on multisig broker responses from 'dkg:create' and 'multisig:sign' into session managers waits for confirmation of client connection before returning from connect waits for confirmation of joining session before returning from join session method --- .../commands/wallet/multisig/dkg/create.ts | 134 +++------ .../src/commands/wallet/multisig/sign.ts | 135 +++------- .../src/multisigBroker/sessionManager.ts | 255 ++++++++++++++++++ 3 files changed, 321 insertions(+), 203 deletions(-) create mode 100644 ironfish-cli/src/multisigBroker/sessionManager.ts diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index b53e2c9ace..9223ef956c 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -11,16 +11,16 @@ import { AccountFormat, Assert, encodeAccountImport, - PromiseUtils, RpcClient, } from '@ironfish/sdk' -import { Flags, ux } from '@oclif/core' +import { Flags } from '@oclif/core' import fs from 'fs' import path from 'path' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import { LedgerMultiSigner } from '../../../../ledger' -import { MultisigBrokerUtils, MultisigClient } from '../../../../multisigBroker' +import { MultisigBrokerUtils } from '../../../../multisigBroker' +import { MultisigDkgSessionManager } from '../../../../multisigBroker/sessionManager' import * as ui from '../../../../ui' export class DkgCreateCommand extends IronfishCommand { @@ -99,7 +99,7 @@ export class DkgCreateCommand extends IronfishCommand { accountCreatedAt = statusResponse.content.blockchain.head.sequence } - let multisigClient: MultisigClient | null = null + let sessionManager: MultisigDkgSessionManager | null = null if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { const { hostname, port, sessionId, passphrase } = await MultisigBrokerUtils.parseConnectionOptions({ @@ -110,36 +110,23 @@ export class DkgCreateCommand extends IronfishCommand { passphrase: flags.passphrase, logger: this.logger, }) - - multisigClient = MultisigBrokerUtils.createClient(hostname, port, { + const multisigClient = MultisigBrokerUtils.createClient(hostname, port, { passphrase, tls: flags.tls ?? true, logger: this.logger, }) - multisigClient.start() - - let connectionConfirmed = false - - multisigClient.onConnectedMessage.on(() => { - connectionConfirmed = true - Assert.isNotNull(multisigClient) - multisigClient.onConnectedMessage.clear() - }) + sessionManager = new MultisigDkgSessionManager(multisigClient) + await sessionManager.connect() if (sessionId) { - while (!connectionConfirmed) { - await PromiseUtils.sleep(500) - continue - } - - multisigClient.joinSession(sessionId) + await sessionManager.joinSession(sessionId) } } const { totalParticipants, minSigners } = await ui.retryStep( async () => { return this.getDkgConfig( - multisigClient, + sessionManager, !!ledger, flags.minSigners, flags.totalParticipants, @@ -159,7 +146,7 @@ export class DkgCreateCommand extends IronfishCommand { async () => { return this.performRound1( client, - multisigClient, + sessionManager, participantName, identity, totalParticipants, @@ -175,7 +162,7 @@ export class DkgCreateCommand extends IronfishCommand { async () => { return this.performRound2( client, - multisigClient, + sessionManager, accountName, participantName, round1, @@ -191,7 +178,7 @@ export class DkgCreateCommand extends IronfishCommand { async () => { return this.performRound3( client, - multisigClient, + sessionManager, accountName, participantName, round2Result, @@ -217,7 +204,7 @@ export class DkgCreateCommand extends IronfishCommand { } this.log('Multisig account created successfully using DKG!') - multisigClient?.stop() + sessionManager?.leaveSession() } private async createBackup(ledger: LedgerMultiSigner, accountName: string) { @@ -327,30 +314,13 @@ export class DkgCreateCommand extends IronfishCommand { } async getDkgConfig( - multisigClient: MultisigClient | null, + sessionManager: MultisigDkgSessionManager | null, ledger: boolean, minSigners?: number, totalParticipants?: number, ): Promise<{ totalParticipants: number; minSigners: number }> { - if (multisigClient?.sessionId) { - let totalParticipants = 0 - let minSigners = 0 - let waiting = true - multisigClient.onDkgStatus.on((message) => { - totalParticipants = message.maxSigners - minSigners = message.minSigners - waiting = false - }) - - ux.action.start('Waiting for signer config from server') - while (waiting) { - multisigClient.getDkgStatus() - await PromiseUtils.sleep(3000) - } - multisigClient.onDkgStatus.clear() - ux.action.stop() - - return { totalParticipants, minSigners } + if (sessionManager?.sessionId) { + return sessionManager.getConfig() } if (!totalParticipants) { @@ -383,12 +353,12 @@ export class DkgCreateCommand extends IronfishCommand { ) } - if (multisigClient) { - multisigClient.startDkgSession(totalParticipants, minSigners) + if (sessionManager) { + sessionManager.startSession(totalParticipants, minSigners) this.log('\nStarted new DKG session:') - this.log(`${multisigClient.sessionId}`) + this.log(`${sessionManager.sessionId}`) this.log('\nDKG session connection string:') - this.log(`${multisigClient.connectionString}`) + this.log(`${sessionManager.client.connectionString}`) } return { totalParticipants, minSigners } @@ -428,7 +398,7 @@ export class DkgCreateCommand extends IronfishCommand { async performRound1( client: RpcClient, - multisigClient: MultisigClient | null, + sessionManager: MultisigDkgSessionManager | null, participantName: string, currentIdentity: string, totalParticipants: number, @@ -440,7 +410,7 @@ export class DkgCreateCommand extends IronfishCommand { this.log('\nCollecting Participant Info and Performing Round 1...') let identities: string[] = [currentIdentity] - if (!multisigClient) { + if (!sessionManager) { this.log(`Identity for ${participantName}: \n${currentIdentity} \n`) this.log( @@ -453,21 +423,7 @@ export class DkgCreateCommand extends IronfishCommand { errorOnDuplicate: true, }) } else { - multisigClient.submitDkgIdentity(currentIdentity) - - multisigClient.onDkgStatus.on((message) => { - identities = message.identities - }) - - ux.action.start('Waiting for Identities from server') - while (identities.length < totalParticipants) { - multisigClient.getDkgStatus() - ux.action.status = `${identities.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - multisigClient.onDkgStatus.clear() - ux.action.stop() + identities = await sessionManager.getIdentities(currentIdentity, totalParticipants) } if (ledger) { @@ -520,7 +476,7 @@ export class DkgCreateCommand extends IronfishCommand { async performRound2( client: RpcClient, - multisigClient: MultisigClient | null, + sessionManager: MultisigDkgSessionManager | null, accountName: string, participantName: string, round1Result: { secretPackage: string; publicPackage: string }, @@ -531,7 +487,7 @@ export class DkgCreateCommand extends IronfishCommand { round1PublicPackages: string[] }> { let round1PublicPackages: string[] = [round1Result.publicPackage] - if (!multisigClient) { + if (!sessionManager) { this.log('\n============================================') this.debug(`\nRound 1 Encrypted Secret Package for ${accountName}:`) this.debug(round1Result.secretPackage) @@ -552,20 +508,10 @@ export class DkgCreateCommand extends IronfishCommand { }, ) } else { - multisigClient.submitRound1PublicPackage(round1Result.publicPackage) - multisigClient.onDkgStatus.on((message) => { - round1PublicPackages = message.round1PublicPackages - }) - - ux.action.start('Waiting for Round 1 Public Packages from server') - while (round1PublicPackages.length < totalParticipants) { - multisigClient.getDkgStatus() - ux.action.status = `${round1PublicPackages.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - multisigClient.onDkgStatus.clear() - ux.action.stop() + round1PublicPackages = await sessionManager.getRound1PublicPackages( + round1Result.publicPackage, + totalParticipants, + ) } this.log('\nPerforming DKG Round 2...') @@ -696,7 +642,7 @@ export class DkgCreateCommand extends IronfishCommand { async performRound3( client: RpcClient, - multisigClient: MultisigClient | null, + sessionManager: MultisigDkgSessionManager | null, accountName: string, participantName: string, round2Result: { secretPackage: string; publicPackage: string }, @@ -706,7 +652,7 @@ export class DkgCreateCommand extends IronfishCommand { accountCreatedAt?: number, ): Promise { let round2PublicPackages: string[] = [round2Result.publicPackage] - if (!multisigClient) { + if (!sessionManager) { this.log('\n============================================') this.debug(`\nRound 2 Encrypted Secret Package for ${accountName}:`) this.debug(round2Result.secretPackage) @@ -727,20 +673,10 @@ export class DkgCreateCommand extends IronfishCommand { }, ) } else { - multisigClient.submitRound2PublicPackage(round2Result.publicPackage) - multisigClient.onDkgStatus.on((message) => { - round2PublicPackages = message.round2PublicPackages - }) - - ux.action.start('Waiting for Round 2 Public Packages from server') - while (round2PublicPackages.length < totalParticipants) { - multisigClient.getDkgStatus() - ux.action.status = `${round2PublicPackages.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - multisigClient.onDkgStatus.clear() - ux.action.stop() + round2PublicPackages = await sessionManager.getRound2PublicPackages( + round2Result.publicPackage, + totalParticipants, + ) } if (ledger) { diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index fda01bf01e..0847ecd143 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -4,10 +4,8 @@ import { multisig } from '@ironfish/rust-nodejs' import { - Assert, CurrencyUtils, Identity, - PromiseUtils, RpcClient, Transaction, UnsignedTransaction, @@ -16,7 +14,8 @@ import { Flags, ux } from '@oclif/core' import { IronfishCommand } from '../../../command' import { RemoteFlags } from '../../../flags' import { LedgerMultiSigner } from '../../../ledger' -import { MultisigBrokerUtils, MultisigClient } from '../../../multisigBroker' +import { MultisigBrokerUtils } from '../../../multisigBroker' +import { MultisigSigningSessionManager } from '../../../multisigBroker/sessionManager' import * as ui from '../../../ui' import { renderUnsignedTransactionDetails, watchTransaction } from '../../../utils/transaction' @@ -123,7 +122,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { ) } - let multisigClient: MultisigClient | null = null + let sessionManager: MultisigSigningSessionManager | null = null if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { const { hostname, port, sessionId, passphrase } = await MultisigBrokerUtils.parseConnectionOptions({ @@ -135,33 +134,21 @@ export class SignMultisigTransactionCommand extends IronfishCommand { logger: this.logger, }) - multisigClient = MultisigBrokerUtils.createClient(hostname, port, { + const multisigClient = MultisigBrokerUtils.createClient(hostname, port, { passphrase, tls: flags.tls ?? true, logger: this.logger, }) - multisigClient.start() - - let connectionConfirmed = false - - multisigClient.onConnectedMessage.on(() => { - connectionConfirmed = true - Assert.isNotNull(multisigClient) - multisigClient.onConnectedMessage.clear() - }) + sessionManager = new MultisigSigningSessionManager(multisigClient) + await sessionManager.connect() if (sessionId) { - while (!connectionConfirmed) { - await PromiseUtils.sleep(500) - continue - } - - multisigClient.joinSession(sessionId) + await sessionManager.joinSession(sessionId) } } const { unsignedTransaction, totalParticipants } = await this.getSigningConfig( - multisigClient, + sessionManager, flags.unsignedTransaction, ) @@ -169,7 +156,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { async () => { return this.performCreateSigningCommitment( client, - multisigClient, + sessionManager, multisigAccountName, participant, totalParticipants, @@ -184,7 +171,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { const signingPackage = await ui.retryStep(() => { return this.performAggregateCommitments( client, - multisigClient, + sessionManager, multisigAccountName, commitment, identities, @@ -211,7 +198,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { () => this.performAggregateSignatures( client, - multisigClient, + sessionManager, multisigAccountName, signingPackage, signatureShare, @@ -221,36 +208,15 @@ export class SignMultisigTransactionCommand extends IronfishCommand { ) this.log('Multisignature sign process completed!') - multisigClient?.stop() + sessionManager?.leaveSession() } async getSigningConfig( - multisigClient: MultisigClient | null, + sessionManager: MultisigSigningSessionManager | null, unsignedTransactionFlag?: string, ): Promise<{ unsignedTransaction: UnsignedTransaction; totalParticipants: number }> { - if (multisigClient?.sessionId) { - let totalParticipants = 0 - let unsignedTransactionHex = '' - let waiting = true - multisigClient.onSigningStatus.on((message) => { - totalParticipants = message.numSigners - unsignedTransactionHex = message.unsignedTransaction - waiting = false - }) - - ux.action.start('Waiting for signer config from server') - while (waiting) { - multisigClient.getSigningStatus() - await PromiseUtils.sleep(3000) - } - multisigClient.onSigningStatus.clear() - ux.action.stop() - - const unsignedTransaction = new UnsignedTransaction( - Buffer.from(unsignedTransactionHex, 'hex'), - ) - - return { totalParticipants, unsignedTransaction } + if (sessionManager?.sessionId) { + return sessionManager.getConfig() } const unsignedTransactionInput = @@ -270,12 +236,12 @@ export class SignMultisigTransactionCommand extends IronfishCommand { this.error('Minimum number of participants must be at least 2') } - if (multisigClient) { - multisigClient.startSigningSession(totalParticipants, unsignedTransactionInput) + if (sessionManager) { + sessionManager.startSession(totalParticipants, unsignedTransactionInput) this.log('\nStarted new signing session:') - this.log(`${multisigClient.sessionId}`) + this.log(`${sessionManager.sessionId}`) this.log('\nSigning session connection string:') - this.log(`${multisigClient.connectionString}`) + this.log(`${sessionManager.client.connectionString}`) } return { unsignedTransaction, totalParticipants } @@ -283,14 +249,14 @@ export class SignMultisigTransactionCommand extends IronfishCommand { private async performAggregateSignatures( client: RpcClient, - multisigClient: MultisigClient | null, + sessionManager: MultisigSigningSessionManager | null, accountName: string, signingPackage: string, signatureShare: string, totalParticipants: number, ): Promise { let signatureShares: string[] = [signatureShare] - if (!multisigClient) { + if (!sessionManager) { this.log('\n============================================') this.log('\nSignature Share:') this.log(signatureShare) @@ -309,21 +275,10 @@ export class SignMultisigTransactionCommand extends IronfishCommand { errorOnDuplicate: true, }) } else { - multisigClient.submitSignatureShare(signatureShare) - - multisigClient.onSigningStatus.on((message) => { - signatureShares = message.signatureShares - }) - - ux.action.start('Waiting for Signature Shares from server') - while (signatureShares.length < totalParticipants) { - multisigClient.getSigningStatus() - ux.action.status = `${signatureShares.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - multisigClient.onSigningStatus.clear() - ux.action.stop() + signatureShares = await sessionManager.getSignatureShares( + signatureShare, + totalParticipants, + ) } const broadcast = await ui.confirmPrompt('Do you want to broadcast the transaction?') @@ -418,7 +373,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { private async performAggregateCommitments( client: RpcClient, - multisigClient: MultisigClient | null, + sessionManager: MultisigSigningSessionManager | null, accountName: string, commitment: string, identities: string[], @@ -426,7 +381,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { unsignedTransaction: UnsignedTransaction, ) { let commitments: string[] = [commitment] - if (!multisigClient) { + if (!sessionManager) { this.log('\n============================================') this.log('\nCommitment:') this.log(commitment) @@ -443,21 +398,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { errorOnDuplicate: true, }) } else { - multisigClient.submitSigningCommitment(commitment) - - multisigClient.onSigningStatus.on((message) => { - commitments = message.signingCommitments - }) - - ux.action.start('Waiting for Signing Commitments from server') - while (commitments.length < totalParticipants) { - multisigClient.getSigningStatus() - ux.action.status = `${commitments.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - multisigClient.onSigningStatus.clear() - ux.action.stop() + commitments = await sessionManager.getSigningCommitments(commitment, totalParticipants) } const signingPackageResponse = await client.wallet.multisig.createSigningPackage({ @@ -471,7 +412,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { private async performCreateSigningCommitment( client: RpcClient, - multisigClient: MultisigClient | null, + sessionManager: MultisigSigningSessionManager | null, accountName: string, participant: MultisigParticipant, totalParticipants: number, @@ -479,7 +420,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { ledger: LedgerMultiSigner | undefined, ) { let identities: string[] = [participant.identity] - if (!multisigClient) { + if (!sessionManager) { this.log(`Identity for ${participant.name}: \n${participant.identity} \n`) this.log('Share your participant identity with other signers.') @@ -492,21 +433,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { errorOnDuplicate: true, }) } else { - multisigClient.submitSigningIdentity(participant.identity) - - multisigClient.onSigningStatus.on((message) => { - identities = message.identities - }) - - ux.action.start('Waiting for Identities from server') - while (identities.length < totalParticipants) { - multisigClient.getSigningStatus() - ux.action.status = `${identities.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - multisigClient.onSigningStatus.clear() - ux.action.stop() + identities = await sessionManager.getIdentities(participant.identity, totalParticipants) } const unsignedTransactionHex = unsignedTransaction.serialize().toString('hex') diff --git a/ironfish-cli/src/multisigBroker/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManager.ts new file mode 100644 index 0000000000..f13e878647 --- /dev/null +++ b/ironfish-cli/src/multisigBroker/sessionManager.ts @@ -0,0 +1,255 @@ +/* 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 { PromiseUtils, UnsignedTransaction } from '@ironfish/sdk' +import { ux } from '@oclif/core' +import { MultisigClient } from './clients' + +export class MultisigSessionManager { + client: MultisigClient + sessionId: string | null = null + + constructor(client: MultisigClient) { + this.client = client + } + + async connect(): Promise { + let confirmed = false + + this.client.start() + + this.client.onConnectedMessage.on(() => { + confirmed = true + this.client.onConnectedMessage.clear() + }) + + while (!confirmed) { + await PromiseUtils.sleep(1000) + } + } + + leaveSession(): void { + this.client.stop() + } + + async joinSession(sessionId: string): Promise { + this.client.joinSession(sessionId) + + await this.waitForJoinedSession() + this.sessionId = sessionId + } + + protected async waitForJoinedSession(): Promise { + let confirmed = false + + this.client.onJoinedSession.on(() => { + confirmed = true + this.client.onJoinedSession.clear() + }) + + while (!confirmed) { + await PromiseUtils.sleep(1000) + } + } +} + +export class MultisigDkgSessionManager extends MultisigSessionManager { + startSession(totalParticipants: number, minSigners: number): void { + this.client.startDkgSession(totalParticipants, minSigners) + this.sessionId = this.client.sessionId + } + + async getConfig(): Promise<{ totalParticipants: number; minSigners: number }> { + let totalParticipants = 0 + let minSigners = 0 + let waiting = true + this.client.onDkgStatus.on((message) => { + totalParticipants = message.maxSigners + minSigners = message.minSigners + waiting = false + }) + + ux.action.start('Waiting for signer config from server') + while (waiting) { + this.client.getDkgStatus() + await PromiseUtils.sleep(3000) + } + this.client.onDkgStatus.clear() + ux.action.stop() + + return { totalParticipants, minSigners } + } + + async getIdentities(identity: string, totalParticipants: number): Promise { + this.client.submitDkgIdentity(identity) + + let identities = [identity] + this.client.onDkgStatus.on((message) => { + identities = message.identities + }) + + ux.action.start('Waiting for Identities from server') + while (identities.length < totalParticipants) { + this.client.getDkgStatus() + ux.action.status = `${identities.length}/${totalParticipants}` + await PromiseUtils.sleep(3000) + } + + this.client.onDkgStatus.clear() + ux.action.stop() + + return identities + } + + async getRound1PublicPackages( + round1PublicPackage: string, + totalParticipants: number, + ): Promise { + this.client.submitRound1PublicPackage(round1PublicPackage) + + let round1PublicPackages = [round1PublicPackage] + this.client.onDkgStatus.on((message) => { + round1PublicPackages = message.round1PublicPackages + }) + + ux.action.start('Waiting for Round 1 Public Packages from server') + while (round1PublicPackages.length < totalParticipants) { + this.client.getDkgStatus() + ux.action.status = `${round1PublicPackages.length}/${totalParticipants}` + await PromiseUtils.sleep(3000) + } + + this.client.onDkgStatus.clear() + ux.action.stop() + + return round1PublicPackages + } + + async getRound2PublicPackages( + round2PublicPackage: string, + totalParticipants: number, + ): Promise { + this.client.submitRound2PublicPackage(round2PublicPackage) + + let round2PublicPackages = [round2PublicPackage] + this.client.onDkgStatus.on((message) => { + round2PublicPackages = message.round2PublicPackages + }) + + ux.action.start('Waiting for Round 2 Public Packages from server') + while (round2PublicPackages.length < totalParticipants) { + this.client.getDkgStatus() + ux.action.status = `${round2PublicPackages.length}/${totalParticipants}` + await PromiseUtils.sleep(3000) + } + + this.client.onDkgStatus.clear() + ux.action.stop() + + return round2PublicPackages + } +} + +export class MultisigSigningSessionManager extends MultisigSessionManager { + startSession(numSigners: number, unsignedTransaction: string): void { + this.client.startSigningSession(numSigners, unsignedTransaction) + this.sessionId = this.client.sessionId + } + + async getConfig(): Promise<{ + unsignedTransaction: UnsignedTransaction + totalParticipants: number + }> { + let totalParticipants = 0 + let unsignedTransactionHex = '' + let waiting = true + this.client.onSigningStatus.on((message) => { + totalParticipants = message.numSigners + unsignedTransactionHex = message.unsignedTransaction + waiting = false + }) + + ux.action.start('Waiting for signer config from server') + while (waiting) { + this.client.getSigningStatus() + await PromiseUtils.sleep(3000) + } + this.client.onSigningStatus.clear() + ux.action.stop() + + const unsignedTransaction = new UnsignedTransaction( + Buffer.from(unsignedTransactionHex, 'hex'), + ) + + return { totalParticipants, unsignedTransaction } + } + + async getIdentities(identity: string, numSigners: number): Promise { + this.client.submitSigningIdentity(identity) + + let identities = [identity] + + this.client.onSigningStatus.on((message) => { + identities = message.identities + }) + + ux.action.start('Waiting for Identities from server') + while (identities.length < numSigners) { + this.client.getSigningStatus() + ux.action.status = `${identities.length}/${numSigners}` + await PromiseUtils.sleep(3000) + } + + this.client.onSigningStatus.clear() + ux.action.stop() + + return identities + } + + async getSigningCommitments( + signingCommitment: string, + numSigners: number, + ): Promise { + this.client.submitSigningCommitment(signingCommitment) + + let signingCommitments = [signingCommitment] + + this.client.onSigningStatus.on((message) => { + signingCommitments = message.signingCommitments + }) + + ux.action.start('Waiting for Signing Commitments from server') + while (signingCommitments.length < numSigners) { + this.client.getSigningStatus() + ux.action.status = `${signingCommitments.length}/${numSigners}` + await PromiseUtils.sleep(3000) + } + + this.client.onSigningStatus.clear() + ux.action.stop() + + return signingCommitments + } + + async getSignatureShares(signatureShare: string, numSigners: number): Promise { + this.client.submitSignatureShare(signatureShare) + + let signatureShares = [signatureShare] + + this.client.onSigningStatus.on((message) => { + signatureShares = message.signatureShares + }) + + ux.action.start('Waiting for Signature Shares from server') + while (signatureShares.length < numSigners) { + this.client.getSigningStatus() + ux.action.status = `${signatureShares.length}/${numSigners}` + await PromiseUtils.sleep(3000) + } + + this.client.onSigningStatus.clear() + ux.action.stop() + + return signatureShares + } +} From f07f881500de88aa7cdbd4f685a172262c19aaaf Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:36:28 -0700 Subject: [PATCH 02/81] updates collectStrings to prompt for new item on duplicate (#5538) if a user inputs a duplicate string into a prompt from collectStrings, they will be informed of the duplicate and the prompt will be repeated adds option to allow duplicates updates usage of collectStrings in 'wallet:multisig:dkg:create' and 'wallet:multisig:sign' not to error on duplicates --- .../commands/wallet/multisig/dkg/create.ts | 6 +-- .../src/commands/wallet/multisig/sign.ts | 6 +-- ironfish-cli/src/ui/prompt.ts | 40 +++++++++++-------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index 9223ef956c..db4d601fb9 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -420,7 +420,7 @@ export class DkgCreateCommand extends IronfishCommand { ) identities = await ui.collectStrings('Participant Identity', totalParticipants - 1, { additionalStrings: [currentIdentity], - errorOnDuplicate: true, + logger: this.logger, }) } else { identities = await sessionManager.getIdentities(currentIdentity, totalParticipants) @@ -504,7 +504,7 @@ export class DkgCreateCommand extends IronfishCommand { totalParticipants - 1, { additionalStrings: [round1Result.publicPackage], - errorOnDuplicate: true, + logger: this.logger, }, ) } else { @@ -669,7 +669,7 @@ export class DkgCreateCommand extends IronfishCommand { totalParticipants - 1, { additionalStrings: [round2Result.publicPackage], - errorOnDuplicate: true, + logger: this.logger, }, ) } else { diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index 0847ecd143..fa292daebf 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -272,7 +272,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { signatureShares = await ui.collectStrings('Signature Share', totalParticipants - 1, { additionalStrings: [signatureShare], - errorOnDuplicate: true, + logger: this.logger, }) } else { signatureShares = await sessionManager.getSignatureShares( @@ -395,7 +395,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { commitments = await ui.collectStrings('Commitment', identities.length - 1, { additionalStrings: [commitment], - errorOnDuplicate: true, + logger: this.logger, }) } else { commitments = await sessionManager.getSigningCommitments(commitment, totalParticipants) @@ -430,7 +430,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { identities = await ui.collectStrings('Participant Identity', totalParticipants - 1, { additionalStrings: [participant.identity], - errorOnDuplicate: true, + logger: this.logger, }) } else { identities = await sessionManager.getIdentities(participant.identity, totalParticipants) diff --git a/ironfish-cli/src/ui/prompt.ts b/ironfish-cli/src/ui/prompt.ts index 78c43e5e9a..e00ab50fa8 100644 --- a/ironfish-cli/src/ui/prompt.ts +++ b/ironfish-cli/src/ui/prompt.ts @@ -11,30 +11,36 @@ export async function collectStrings( itemName: string, itemAmount: number, options?: { - additionalStrings: string[] - errorOnDuplicate: boolean + additionalStrings?: string[] + allowDuplicate?: boolean + errorOnDuplicate?: boolean + logger?: Logger }, ): Promise { - const array = [] + const strings = new Set(options?.additionalStrings || []) + const duplicates = [] 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`) + let item + while (!item) { + item = await longPrompt(`${itemName} #${i + 1}`, { required: true }) + + if (strings.has(item)) { + if (options?.allowDuplicate) { + duplicates.push(item) + continue + } else if (options?.errorOnDuplicate) { + throw new Error(`Duplicate ${itemName} found in the list`) + } else { + options?.logger?.log(`Duplicate ${itemName}`) + item = undefined + } + } } + strings.add(item) } - return strings + return [...strings, ...duplicates] } async function _inputPrompt(message: string, options?: { password: boolean }): Promise { From e561275a1d4edd55eb75b6716997addb45c778c0 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:03:09 -0700 Subject: [PATCH 03/81] uses session manager to collect all multisig inputs (#5536) * uses session manager to collect all multisig inputs defines abstract classes and interfaces for session managers to handle either waiting on the broker server for data or waiting on CLI inputs creates uniform session manager interfaces for either case in both signing sessions and dkg sessions removes interface distinction between starting and joining sessions with broker server * restores sessionId and connection string messages to session managers * passes accountName to getIdentities in dkg * updates session managers not to error on duplicate string entry re-prompt instead --- .../commands/wallet/multisig/dkg/create.ts | 185 +++-------- .../src/commands/wallet/multisig/sign.ts | 160 +++------- .../src/multisigBroker/sessionManager.ts | 255 --------------- .../sessionManagers/dkgSessionManager.ts | 300 ++++++++++++++++++ .../multisigBroker/sessionManagers/index.ts | 5 + .../sessionManagers/sessionManager.ts | 89 ++++++ .../sessionManagers/signingSessionManager.ts | 279 ++++++++++++++++ 7 files changed, 751 insertions(+), 522 deletions(-) delete mode 100644 ironfish-cli/src/multisigBroker/sessionManager.ts create mode 100644 ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts create mode 100644 ironfish-cli/src/multisigBroker/sessionManagers/index.ts create mode 100644 ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts create mode 100644 ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index db4d601fb9..5df6e611dc 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -20,7 +20,11 @@ import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import { LedgerMultiSigner } from '../../../../ledger' import { MultisigBrokerUtils } from '../../../../multisigBroker' -import { MultisigDkgSessionManager } from '../../../../multisigBroker/sessionManager' +import { + DkgSessionManager, + MultisigClientDkgSessionManager, + MultisigDkgSessionManager, +} from '../../../../multisigBroker/sessionManagers' import * as ui from '../../../../ui' export class DkgCreateCommand extends IronfishCommand { @@ -99,7 +103,7 @@ export class DkgCreateCommand extends IronfishCommand { accountCreatedAt = statusResponse.content.blockchain.head.sequence } - let sessionManager: MultisigDkgSessionManager | null = null + let sessionManager: DkgSessionManager if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { const { hostname, port, sessionId, passphrase } = await MultisigBrokerUtils.parseConnectionOptions({ @@ -110,31 +114,24 @@ export class DkgCreateCommand extends IronfishCommand { passphrase: flags.passphrase, logger: this.logger, }) - const multisigClient = MultisigBrokerUtils.createClient(hostname, port, { + + sessionManager = new MultisigClientDkgSessionManager({ + hostname, + port, passphrase, + sessionId, tls: flags.tls ?? true, logger: this.logger, }) - sessionManager = new MultisigDkgSessionManager(multisigClient) - await sessionManager.connect() - - if (sessionId) { - await sessionManager.joinSession(sessionId) - } + } else { + sessionManager = new MultisigDkgSessionManager({ logger: this.logger }) } - const { totalParticipants, minSigners } = await ui.retryStep( - async () => { - return this.getDkgConfig( - sessionManager, - !!ledger, - flags.minSigners, - flags.totalParticipants, - ) - }, - this.logger, - true, - ) + const { totalParticipants, minSigners } = await sessionManager.startSession({ + totalParticipants: flags.totalParticipants, + minSigners: flags.minSigners, + ledger: flags.ledger, + }) const { name: participantName, identity } = await this.getOrCreateIdentity( client, @@ -204,7 +201,7 @@ export class DkgCreateCommand extends IronfishCommand { } this.log('Multisig account created successfully using DKG!') - sessionManager?.leaveSession() + sessionManager.endSession() } private async createBackup(ledger: LedgerMultiSigner, accountName: string) { @@ -313,57 +310,6 @@ export class DkgCreateCommand extends IronfishCommand { return name } - async getDkgConfig( - sessionManager: MultisigDkgSessionManager | null, - ledger: boolean, - minSigners?: number, - totalParticipants?: number, - ): Promise<{ totalParticipants: number; minSigners: number }> { - if (sessionManager?.sessionId) { - return sessionManager.getConfig() - } - - if (!totalParticipants) { - 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') - } - - if (!minSigners) { - 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 (sessionManager) { - sessionManager.startSession(totalParticipants, minSigners) - this.log('\nStarted new DKG session:') - this.log(`${sessionManager.sessionId}`) - this.log('\nDKG session connection string:') - this.log(`${sessionManager.client.connectionString}`) - } - - return { totalParticipants, minSigners } - } - async performRound1WithLedger( ledger: LedgerMultiSigner, client: RpcClient, @@ -398,7 +344,7 @@ export class DkgCreateCommand extends IronfishCommand { async performRound1( client: RpcClient, - sessionManager: MultisigDkgSessionManager | null, + sessionManager: DkgSessionManager, participantName: string, currentIdentity: string, totalParticipants: number, @@ -409,22 +355,11 @@ export class DkgCreateCommand extends IronfishCommand { }> { this.log('\nCollecting Participant Info and Performing Round 1...') - let identities: string[] = [currentIdentity] - if (!sessionManager) { - this.log(`Identity for ${participantName}: \n${currentIdentity} \n`) - - this.log( - `\nEnter ${ - totalParticipants - 1 - } identities of all other participants (excluding yours) `, - ) - identities = await ui.collectStrings('Participant Identity', totalParticipants - 1, { - additionalStrings: [currentIdentity], - logger: this.logger, - }) - } else { - identities = await sessionManager.getIdentities(currentIdentity, totalParticipants) - } + const identities = await sessionManager.getIdentities({ + identity: currentIdentity, + totalParticipants, + accountName: participantName, + }) if (ledger) { return await this.performRound1WithLedger( @@ -476,7 +411,7 @@ export class DkgCreateCommand extends IronfishCommand { async performRound2( client: RpcClient, - sessionManager: MultisigDkgSessionManager | null, + sessionManager: DkgSessionManager, accountName: string, participantName: string, round1Result: { secretPackage: string; publicPackage: string }, @@ -486,33 +421,12 @@ export class DkgCreateCommand extends IronfishCommand { round2: { secretPackage: string; publicPackage: string } round1PublicPackages: string[] }> { - let round1PublicPackages: string[] = [round1Result.publicPackage] - if (!sessionManager) { - this.log('\n============================================') - this.debug(`\nRound 1 Encrypted Secret Package for ${accountName}:`) - this.debug(round1Result.secretPackage) - - this.log(`\nRound 1 Public Package for ${accountName}:`) - this.log(round1Result.publicPackage) - this.log('\n============================================') - - this.log('\nShare your Round 1 Public Package with other participants.') - this.log(`\nEnter ${totalParticipants - 1} Round 1 Public Packages (excluding yours) `) - - round1PublicPackages = await ui.collectStrings( - 'Round 1 Public Package', - totalParticipants - 1, - { - additionalStrings: [round1Result.publicPackage], - logger: this.logger, - }, - ) - } else { - round1PublicPackages = await sessionManager.getRound1PublicPackages( - round1Result.publicPackage, - totalParticipants, - ) - } + const round1PublicPackages = await sessionManager.getRound1PublicPackages({ + accountName, + round1PublicPackage: round1Result.publicPackage, + round1SecretPackage: round1Result.secretPackage, + totalParticipants, + }) this.log('\nPerforming DKG Round 2...') @@ -642,7 +556,7 @@ export class DkgCreateCommand extends IronfishCommand { async performRound3( client: RpcClient, - sessionManager: MultisigDkgSessionManager | null, + sessionManager: DkgSessionManager, accountName: string, participantName: string, round2Result: { secretPackage: string; publicPackage: string }, @@ -651,33 +565,12 @@ export class DkgCreateCommand extends IronfishCommand { ledger: LedgerMultiSigner | undefined, accountCreatedAt?: number, ): Promise { - let round2PublicPackages: string[] = [round2Result.publicPackage] - if (!sessionManager) { - this.log('\n============================================') - this.debug(`\nRound 2 Encrypted Secret Package for ${accountName}:`) - this.debug(round2Result.secretPackage) - - this.log(`\nRound 2 Public Package for ${accountName}:`) - this.log(round2Result.publicPackage) - this.log('\n============================================') - - this.log('\nShare your Round 2 Public Package with other participants.') - this.log(`\nEnter ${totalParticipants - 1} Round 2 Public Packages (excluding yours) `) - - round2PublicPackages = await ui.collectStrings( - 'Round 2 Public Package', - totalParticipants - 1, - { - additionalStrings: [round2Result.publicPackage], - logger: this.logger, - }, - ) - } else { - round2PublicPackages = await sessionManager.getRound2PublicPackages( - round2Result.publicPackage, - totalParticipants, - ) - } + const round2PublicPackages = await sessionManager.getRound2PublicPackages({ + accountName, + round2PublicPackage: round2Result.publicPackage, + round2SecretPackage: round2Result.secretPackage, + totalParticipants, + }) if (ledger) { await this.performRound3WithLedger( diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index fa292daebf..f7038d3fbb 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -15,7 +15,11 @@ import { IronfishCommand } from '../../../command' import { RemoteFlags } from '../../../flags' import { LedgerMultiSigner } from '../../../ledger' import { MultisigBrokerUtils } from '../../../multisigBroker' -import { MultisigSigningSessionManager } from '../../../multisigBroker/sessionManager' +import { + MultisigClientSigningSessionManager, + MultisigSigningSessionManager, + SigningSessionManager, +} from '../../../multisigBroker/sessionManagers' import * as ui from '../../../ui' import { renderUnsignedTransactionDetails, watchTransaction } from '../../../utils/transaction' @@ -122,7 +126,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { ) } - let sessionManager: MultisigSigningSessionManager | null = null + let sessionManager: SigningSessionManager if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { const { hostname, port, sessionId, passphrase } = await MultisigBrokerUtils.parseConnectionOptions({ @@ -134,23 +138,21 @@ export class SignMultisigTransactionCommand extends IronfishCommand { logger: this.logger, }) - const multisigClient = MultisigBrokerUtils.createClient(hostname, port, { - passphrase, - tls: flags.tls ?? true, + sessionManager = new MultisigClientSigningSessionManager({ logger: this.logger, + hostname, + port, + passphrase, + sessionId, + tls: flags.tls, }) - sessionManager = new MultisigSigningSessionManager(multisigClient) - await sessionManager.connect() - - if (sessionId) { - await sessionManager.joinSession(sessionId) - } + } else { + sessionManager = new MultisigSigningSessionManager({ logger: this.logger }) } - const { unsignedTransaction, totalParticipants } = await this.getSigningConfig( - sessionManager, - flags.unsignedTransaction, - ) + const { numSigners, unsignedTransaction } = await sessionManager.startSession({ + unsignedTransaction: flags.unsignedTransaction, + }) const { commitment, identities } = await ui.retryStep( async () => { @@ -159,7 +161,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { sessionManager, multisigAccountName, participant, - totalParticipants, + numSigners, unsignedTransaction, ledger, ) @@ -175,7 +177,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { multisigAccountName, commitment, identities, - totalParticipants, + numSigners, unsignedTransaction, ) }, this.logger) @@ -208,78 +210,21 @@ export class SignMultisigTransactionCommand extends IronfishCommand { ) this.log('Multisignature sign process completed!') - sessionManager?.leaveSession() - } - - async getSigningConfig( - sessionManager: MultisigSigningSessionManager | null, - unsignedTransactionFlag?: string, - ): Promise<{ unsignedTransaction: UnsignedTransaction; totalParticipants: number }> { - if (sessionManager?.sessionId) { - return sessionManager.getConfig() - } - - const unsignedTransactionInput = - unsignedTransactionFlag ?? - (await ui.longPrompt('Enter the unsigned transaction', { required: true })) - const unsignedTransaction = new UnsignedTransaction( - Buffer.from(unsignedTransactionInput, 'hex'), - ) - - const totalParticipants = await ui.inputNumberPrompt( - this.logger, - 'Enter the number of participants in signing this transaction', - { required: true, integer: true }, - ) - - if (totalParticipants < 2) { - this.error('Minimum number of participants must be at least 2') - } - - if (sessionManager) { - sessionManager.startSession(totalParticipants, unsignedTransactionInput) - this.log('\nStarted new signing session:') - this.log(`${sessionManager.sessionId}`) - this.log('\nSigning session connection string:') - this.log(`${sessionManager.client.connectionString}`) - } - - return { unsignedTransaction, totalParticipants } + sessionManager.endSession() } private async performAggregateSignatures( client: RpcClient, - sessionManager: MultisigSigningSessionManager | null, + sessionManager: SigningSessionManager, accountName: string, signingPackage: string, signatureShare: string, - totalParticipants: number, + numSigners: number, ): Promise { - let signatureShares: string[] = [signatureShare] - if (!sessionManager) { - this.log('\n============================================') - this.log('\nSignature Share:') - this.log(signatureShare) - this.log('\n============================================') - - this.log('\nShare your signature share with other participants.') - - this.log( - `Enter ${ - totalParticipants - 1 - } signature shares of the participants (excluding your own)`, - ) - - signatureShares = await ui.collectStrings('Signature Share', totalParticipants - 1, { - additionalStrings: [signatureShare], - logger: this.logger, - }) - } else { - signatureShares = await sessionManager.getSignatureShares( - signatureShare, - totalParticipants, - ) - } + const signatureShares = await sessionManager.getSignatureShares({ + signatureShare, + numSigners, + }) const broadcast = await ui.confirmPrompt('Do you want to broadcast the transaction?') const watch = await ui.confirmPrompt('Do you want to watch the transaction?') @@ -373,33 +318,17 @@ export class SignMultisigTransactionCommand extends IronfishCommand { private async performAggregateCommitments( client: RpcClient, - sessionManager: MultisigSigningSessionManager | null, + sessionManager: SigningSessionManager, accountName: string, commitment: string, identities: string[], - totalParticipants: number, + numSigners: number, unsignedTransaction: UnsignedTransaction, ) { - let commitments: string[] = [commitment] - if (!sessionManager) { - this.log('\n============================================') - this.log('\nCommitment:') - this.log(commitment) - this.log('\n============================================') - - this.log('\nShare your commitment with other participants.') - - this.log( - `Enter ${identities.length - 1} commitments of the participants (excluding your own)`, - ) - - commitments = await ui.collectStrings('Commitment', identities.length - 1, { - additionalStrings: [commitment], - logger: this.logger, - }) - } else { - commitments = await sessionManager.getSigningCommitments(commitment, totalParticipants) - } + const commitments = await sessionManager.getSigningCommitments({ + signingCommitment: commitment, + numSigners, + }) const signingPackageResponse = await client.wallet.multisig.createSigningPackage({ account: accountName, @@ -412,29 +341,18 @@ export class SignMultisigTransactionCommand extends IronfishCommand { private async performCreateSigningCommitment( client: RpcClient, - sessionManager: MultisigSigningSessionManager | null, + sessionManager: SigningSessionManager, accountName: string, participant: MultisigParticipant, - totalParticipants: number, + numSigners: number, unsignedTransaction: UnsignedTransaction, ledger: LedgerMultiSigner | undefined, ) { - let identities: string[] = [participant.identity] - if (!sessionManager) { - this.log(`Identity for ${participant.name}: \n${participant.identity} \n`) - this.log('Share your participant identity with other signers.') - - this.log( - `Enter ${totalParticipants - 1} identities of the participants (excluding your own)`, - ) - - identities = await ui.collectStrings('Participant Identity', totalParticipants - 1, { - additionalStrings: [participant.identity], - logger: this.logger, - }) - } else { - identities = await sessionManager.getIdentities(participant.identity, totalParticipants) - } + const identities = await sessionManager.getIdentities({ + accountName, + identity: participant.identity, + numSigners, + }) const unsignedTransactionHex = unsignedTransaction.serialize().toString('hex') diff --git a/ironfish-cli/src/multisigBroker/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManager.ts deleted file mode 100644 index f13e878647..0000000000 --- a/ironfish-cli/src/multisigBroker/sessionManager.ts +++ /dev/null @@ -1,255 +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 { PromiseUtils, UnsignedTransaction } from '@ironfish/sdk' -import { ux } from '@oclif/core' -import { MultisigClient } from './clients' - -export class MultisigSessionManager { - client: MultisigClient - sessionId: string | null = null - - constructor(client: MultisigClient) { - this.client = client - } - - async connect(): Promise { - let confirmed = false - - this.client.start() - - this.client.onConnectedMessage.on(() => { - confirmed = true - this.client.onConnectedMessage.clear() - }) - - while (!confirmed) { - await PromiseUtils.sleep(1000) - } - } - - leaveSession(): void { - this.client.stop() - } - - async joinSession(sessionId: string): Promise { - this.client.joinSession(sessionId) - - await this.waitForJoinedSession() - this.sessionId = sessionId - } - - protected async waitForJoinedSession(): Promise { - let confirmed = false - - this.client.onJoinedSession.on(() => { - confirmed = true - this.client.onJoinedSession.clear() - }) - - while (!confirmed) { - await PromiseUtils.sleep(1000) - } - } -} - -export class MultisigDkgSessionManager extends MultisigSessionManager { - startSession(totalParticipants: number, minSigners: number): void { - this.client.startDkgSession(totalParticipants, minSigners) - this.sessionId = this.client.sessionId - } - - async getConfig(): Promise<{ totalParticipants: number; minSigners: number }> { - let totalParticipants = 0 - let minSigners = 0 - let waiting = true - this.client.onDkgStatus.on((message) => { - totalParticipants = message.maxSigners - minSigners = message.minSigners - waiting = false - }) - - ux.action.start('Waiting for signer config from server') - while (waiting) { - this.client.getDkgStatus() - await PromiseUtils.sleep(3000) - } - this.client.onDkgStatus.clear() - ux.action.stop() - - return { totalParticipants, minSigners } - } - - async getIdentities(identity: string, totalParticipants: number): Promise { - this.client.submitDkgIdentity(identity) - - let identities = [identity] - this.client.onDkgStatus.on((message) => { - identities = message.identities - }) - - ux.action.start('Waiting for Identities from server') - while (identities.length < totalParticipants) { - this.client.getDkgStatus() - ux.action.status = `${identities.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - this.client.onDkgStatus.clear() - ux.action.stop() - - return identities - } - - async getRound1PublicPackages( - round1PublicPackage: string, - totalParticipants: number, - ): Promise { - this.client.submitRound1PublicPackage(round1PublicPackage) - - let round1PublicPackages = [round1PublicPackage] - this.client.onDkgStatus.on((message) => { - round1PublicPackages = message.round1PublicPackages - }) - - ux.action.start('Waiting for Round 1 Public Packages from server') - while (round1PublicPackages.length < totalParticipants) { - this.client.getDkgStatus() - ux.action.status = `${round1PublicPackages.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - this.client.onDkgStatus.clear() - ux.action.stop() - - return round1PublicPackages - } - - async getRound2PublicPackages( - round2PublicPackage: string, - totalParticipants: number, - ): Promise { - this.client.submitRound2PublicPackage(round2PublicPackage) - - let round2PublicPackages = [round2PublicPackage] - this.client.onDkgStatus.on((message) => { - round2PublicPackages = message.round2PublicPackages - }) - - ux.action.start('Waiting for Round 2 Public Packages from server') - while (round2PublicPackages.length < totalParticipants) { - this.client.getDkgStatus() - ux.action.status = `${round2PublicPackages.length}/${totalParticipants}` - await PromiseUtils.sleep(3000) - } - - this.client.onDkgStatus.clear() - ux.action.stop() - - return round2PublicPackages - } -} - -export class MultisigSigningSessionManager extends MultisigSessionManager { - startSession(numSigners: number, unsignedTransaction: string): void { - this.client.startSigningSession(numSigners, unsignedTransaction) - this.sessionId = this.client.sessionId - } - - async getConfig(): Promise<{ - unsignedTransaction: UnsignedTransaction - totalParticipants: number - }> { - let totalParticipants = 0 - let unsignedTransactionHex = '' - let waiting = true - this.client.onSigningStatus.on((message) => { - totalParticipants = message.numSigners - unsignedTransactionHex = message.unsignedTransaction - waiting = false - }) - - ux.action.start('Waiting for signer config from server') - while (waiting) { - this.client.getSigningStatus() - await PromiseUtils.sleep(3000) - } - this.client.onSigningStatus.clear() - ux.action.stop() - - const unsignedTransaction = new UnsignedTransaction( - Buffer.from(unsignedTransactionHex, 'hex'), - ) - - return { totalParticipants, unsignedTransaction } - } - - async getIdentities(identity: string, numSigners: number): Promise { - this.client.submitSigningIdentity(identity) - - let identities = [identity] - - this.client.onSigningStatus.on((message) => { - identities = message.identities - }) - - ux.action.start('Waiting for Identities from server') - while (identities.length < numSigners) { - this.client.getSigningStatus() - ux.action.status = `${identities.length}/${numSigners}` - await PromiseUtils.sleep(3000) - } - - this.client.onSigningStatus.clear() - ux.action.stop() - - return identities - } - - async getSigningCommitments( - signingCommitment: string, - numSigners: number, - ): Promise { - this.client.submitSigningCommitment(signingCommitment) - - let signingCommitments = [signingCommitment] - - this.client.onSigningStatus.on((message) => { - signingCommitments = message.signingCommitments - }) - - ux.action.start('Waiting for Signing Commitments from server') - while (signingCommitments.length < numSigners) { - this.client.getSigningStatus() - ux.action.status = `${signingCommitments.length}/${numSigners}` - await PromiseUtils.sleep(3000) - } - - this.client.onSigningStatus.clear() - ux.action.stop() - - return signingCommitments - } - - async getSignatureShares(signatureShare: string, numSigners: number): Promise { - this.client.submitSignatureShare(signatureShare) - - let signatureShares = [signatureShare] - - this.client.onSigningStatus.on((message) => { - signatureShares = message.signatureShares - }) - - ux.action.start('Waiting for Signature Shares from server') - while (signatureShares.length < numSigners) { - this.client.getSigningStatus() - ux.action.status = `${signatureShares.length}/${numSigners}` - await PromiseUtils.sleep(3000) - } - - this.client.onSigningStatus.clear() - ux.action.stop() - - return signatureShares - } -} diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts new file mode 100644 index 0000000000..91ff07a261 --- /dev/null +++ b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts @@ -0,0 +1,300 @@ +/* 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, PromiseUtils } from '@ironfish/sdk' +import { ux } from '@oclif/core' +import * as ui from '../../ui' +import { MultisigClientSessionManager, MultisigSessionManager } from './sessionManager' + +export interface DkgSessionManager extends MultisigSessionManager { + startSession(options: { + totalParticipants?: number + minSigners?: number + ledger?: boolean + }): Promise<{ totalParticipants: number; minSigners: number }> + getIdentities(options: { + identity: string + totalParticipants: number + accountName?: string + }): Promise + getRound1PublicPackages(options: { + round1PublicPackage: string + totalParticipants: number + accountName?: string + round1SecretPackage?: string + }): Promise + getRound2PublicPackages(options: { + round2PublicPackage: string + totalParticipants: number + accountName?: string + round2SecretPackage?: string + }): Promise +} + +export class MultisigClientDkgSessionManager + extends MultisigClientSessionManager + implements DkgSessionManager +{ + async startSession(options: { + totalParticipants?: number + minSigners?: number + ledger?: boolean + }): Promise<{ totalParticipants: number; minSigners: number }> { + if (this.sessionId) { + await this.joinSession(this.sessionId) + return this.getSessionConfig() + } + + const { totalParticipants, minSigners } = await inputDkgConfig({ + logger: this.logger, + totalParticipants: options.totalParticipants, + minSigners: options.minSigners, + ledger: options.ledger, + }) + + await this.connect() + + this.client.startDkgSession(totalParticipants, minSigners) + this.sessionId = this.client.sessionId + + this.logger.info('\nStarted new DKG session:') + this.logger.info(`${this.sessionId}`) + this.logger.info('\nDKG session connection string:') + this.logger.info(`${this.client.connectionString}`) + + return { totalParticipants, minSigners } + } + + async getSessionConfig(): Promise<{ totalParticipants: number; minSigners: number }> { + let totalParticipants = 0 + let minSigners = 0 + let waiting = true + this.client.onDkgStatus.on((message) => { + totalParticipants = message.maxSigners + minSigners = message.minSigners + waiting = false + }) + + ux.action.start('Waiting for signer config from server') + while (waiting) { + this.client.getDkgStatus() + await PromiseUtils.sleep(3000) + } + this.client.onDkgStatus.clear() + ux.action.stop() + + return { totalParticipants, minSigners } + } + + async getIdentities(options: { + identity: string + totalParticipants: number + }): Promise { + const { identity, totalParticipants } = options + + this.client.submitDkgIdentity(identity) + + let identities = [identity] + this.client.onDkgStatus.on((message) => { + identities = message.identities + }) + + ux.action.start('Waiting for Identities from server') + while (identities.length < totalParticipants) { + this.client.getDkgStatus() + ux.action.status = `${identities.length}/${totalParticipants}` + await PromiseUtils.sleep(3000) + } + + this.client.onDkgStatus.clear() + ux.action.stop() + + return identities + } + + async getRound1PublicPackages(options: { + round1PublicPackage: string + totalParticipants: number + }): Promise { + const { round1PublicPackage, totalParticipants } = options + + this.client.submitRound1PublicPackage(round1PublicPackage) + + let round1PublicPackages = [round1PublicPackage] + this.client.onDkgStatus.on((message) => { + round1PublicPackages = message.round1PublicPackages + }) + + ux.action.start('Waiting for Round 1 Public Packages from server') + while (round1PublicPackages.length < totalParticipants) { + this.client.getDkgStatus() + ux.action.status = `${round1PublicPackages.length}/${totalParticipants}` + await PromiseUtils.sleep(3000) + } + + this.client.onDkgStatus.clear() + ux.action.stop() + + return round1PublicPackages + } + + async getRound2PublicPackages(options: { + round2PublicPackage: string + totalParticipants: number + }): Promise { + const { round2PublicPackage, totalParticipants } = options + + this.client.submitRound2PublicPackage(round2PublicPackage) + + let round2PublicPackages = [round2PublicPackage] + this.client.onDkgStatus.on((message) => { + round2PublicPackages = message.round2PublicPackages + }) + + ux.action.start('Waiting for Round 2 Public Packages from server') + while (round2PublicPackages.length < totalParticipants) { + this.client.getDkgStatus() + ux.action.status = `${round2PublicPackages.length}/${totalParticipants}` + await PromiseUtils.sleep(3000) + } + + this.client.onDkgStatus.clear() + ux.action.stop() + + return round2PublicPackages + } +} + +export class MultisigDkgSessionManager + extends MultisigSessionManager + implements DkgSessionManager +{ + async startSession(options: { + totalParticipants?: number + minSigners?: number + ledger?: boolean + }): Promise<{ totalParticipants: number; minSigners: number }> { + return await inputDkgConfig({ + logger: this.logger, + totalParticipants: options.totalParticipants, + minSigners: options.minSigners, + ledger: options.ledger, + }) + } + + endSession(): void { + return + } + + async getIdentities(options: { + accountName: string + identity: string + totalParticipants: number + }): Promise { + this.logger.info(`Identity for ${options.accountName}:\n${options.identity}\n`) + + this.logger.info( + `\nEnter ${ + options.totalParticipants - 1 + } identities of all other participants (excluding yours) `, + ) + return await ui.collectStrings('Participant Identity', options.totalParticipants - 1, { + additionalStrings: [options.identity], + logger: this.logger, + }) + } + + async getRound1PublicPackages(options: { + accountName: string + round1PublicPackage: string + round1SecretPackage: string + totalParticipants: number + }): Promise { + const { accountName, round1SecretPackage, round1PublicPackage, totalParticipants } = options + + this.logger.info('\n============================================') + this.logger.debug(`\nRound 1 Encrypted Secret Package for ${accountName}:`) + this.logger.debug(round1SecretPackage) + + this.logger.info(`\nRound 1 Public Package for ${accountName}:`) + this.logger.info(round1PublicPackage) + this.logger.info('\n============================================') + + this.logger.info('\nShare your Round 1 Public Package with other participants.') + this.logger.info( + `\nEnter ${totalParticipants - 1} Round 1 Public Packages (excluding yours) `, + ) + + return await ui.collectStrings('Round 1 Public Package', totalParticipants - 1, { + additionalStrings: [round1PublicPackage], + logger: this.logger, + }) + } + + async getRound2PublicPackages(options: { + accountName: string + round2SecretPackage: string + round2PublicPackage: string + totalParticipants: number + }): Promise { + const { accountName, round2SecretPackage, round2PublicPackage, totalParticipants } = options + + this.logger.info('\n============================================') + this.logger.debug(`\nRound 2 Encrypted Secret Package for ${accountName}:`) + this.logger.debug(round2SecretPackage) + + this.logger.info(`\nRound 2 Public Package for ${accountName}:`) + this.logger.info(round2PublicPackage) + this.logger.info('\n============================================') + + this.logger.info('\nShare your Round 2 Public Package with other participants.') + this.logger.info( + `\nEnter ${totalParticipants - 1} Round 2 Public Packages (excluding yours) `, + ) + + return await ui.collectStrings('Round 2 Public Package', totalParticipants - 1, { + additionalStrings: [round2PublicPackage], + logger: this.logger, + }) + } +} + +async function inputDkgConfig(options: { + logger: Logger + totalParticipants?: number + minSigners?: number + ledger?: boolean +}): Promise<{ + totalParticipants: number + minSigners: number +}> { + const totalParticipants = + options.totalParticipants ?? + (await ui.inputNumberPrompt(options.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 (options.ledger && totalParticipants > 4) { + throw new Error('DKG with Ledger supports a maximum of 4 participants') + } + + const minSigners = + options.minSigners ?? + (await ui.inputNumberPrompt(options.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', + ) + } + + return { totalParticipants, minSigners } +} diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/index.ts b/ironfish-cli/src/multisigBroker/sessionManagers/index.ts new file mode 100644 index 0000000000..3cef234fb3 --- /dev/null +++ b/ironfish-cli/src/multisigBroker/sessionManagers/index.ts @@ -0,0 +1,5 @@ +/* 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/. */ +export * from './dkgSessionManager' +export * from './signingSessionManager' diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts new file mode 100644 index 0000000000..c243835146 --- /dev/null +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -0,0 +1,89 @@ +/* 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 { Assert, Logger, PromiseUtils } from '@ironfish/sdk' +import { MultisigClient } from '../clients' +import { MultisigBrokerUtils } from '../utils' + +export abstract class MultisigSessionManager { + sessionId: string | null = null + logger: Logger + + constructor(options: { logger: Logger }) { + this.logger = options.logger + } + + abstract startSession(options: object): Promise + + abstract endSession(): void +} + +export abstract class MultisigClientSessionManager extends MultisigSessionManager { + client: MultisigClient + sessionId: string | null + + constructor(options: { + logger: Logger + hostname: string + port: number + passphrase: string + sessionId?: string + tls?: boolean + }) { + super({ logger: options.logger }) + + this.client = MultisigBrokerUtils.createClient(options.hostname, options.port, { + passphrase: options.passphrase, + tls: options.tls ?? true, + logger: this.logger, + }) + + this.sessionId = options.sessionId ?? null + } + + protected async connect(): Promise { + let confirmed = false + + this.client.start() + + this.client.onConnectedMessage.on(() => { + confirmed = true + }) + + while (!confirmed) { + await PromiseUtils.sleep(1000) + } + + this.client.onConnectedMessage.clear() + } + + async joinSession(sessionId: string): Promise { + await this.connect() + + this.client.joinSession(sessionId) + + await this.waitForJoinedSession() + this.sessionId = sessionId + } + + protected async waitForJoinedSession(): Promise { + Assert.isNotNull(this.client) + let confirmed = false + + this.client.onJoinedSession.on(() => { + confirmed = true + }) + + while (!confirmed) { + await PromiseUtils.sleep(1000) + } + + this.client.onJoinedSession.clear() + } + + endSession(): void { + this.client.stop() + } + + abstract getSessionConfig(): Promise +} diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts new file mode 100644 index 0000000000..9e0de4d2a2 --- /dev/null +++ b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts @@ -0,0 +1,279 @@ +/* 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, PromiseUtils, UnsignedTransaction } from '@ironfish/sdk' +import { ux } from '@oclif/core' +import * as ui from '../../ui' +import { MultisigClientSessionManager, MultisigSessionManager } from './sessionManager' + +export interface SigningSessionManager extends MultisigSessionManager { + startSession(options: { + numSigners?: number + unsignedTransaction?: string + }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> + getIdentities(options: { + identity: string + numSigners: number + accountName?: string + }): Promise + getSigningCommitments(options: { + signingCommitment: string + numSigners: number + }): Promise + getSignatureShares(options: { signatureShare: string; numSigners: number }): Promise +} + +export class MultisigClientSigningSessionManager + extends MultisigClientSessionManager + implements SigningSessionManager +{ + async startSession(options: { + numSigners?: number + unsignedTransaction?: string + }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> { + if (this.sessionId) { + await this.joinSession(this.sessionId) + return this.getSessionConfig() + } + + const { numSigners, unsignedTransaction } = await inputSigningConfig({ + ...options, + logger: this.logger, + }) + + await this.connect() + + this.client.startSigningSession(numSigners, unsignedTransaction) + this.sessionId = this.client.sessionId + + this.logger.info('\nStarted new signing session:') + this.logger.info(`${this.sessionId}`) + this.logger.info('\nSigning session connection string:') + this.logger.info(`${this.client.connectionString}`) + + return { + numSigners, + unsignedTransaction: new UnsignedTransaction(Buffer.from(unsignedTransaction, 'hex')), + } + } + + async getSessionConfig(): Promise<{ + unsignedTransaction: UnsignedTransaction + numSigners: number + }> { + let numSigners = 0 + let unsignedTransactionHex = '' + let waiting = true + this.client.onSigningStatus.on((message) => { + numSigners = message.numSigners + unsignedTransactionHex = message.unsignedTransaction + waiting = false + }) + + ux.action.start('Waiting for signer config from server') + while (waiting) { + this.client.getSigningStatus() + await PromiseUtils.sleep(3000) + } + this.client.onSigningStatus.clear() + ux.action.stop() + + const unsignedTransaction = new UnsignedTransaction( + Buffer.from(unsignedTransactionHex, 'hex'), + ) + + return { numSigners, unsignedTransaction } + } + + async getIdentities(options: { identity: string; numSigners: number }): Promise { + const { identity, numSigners } = options + + this.client.submitSigningIdentity(identity) + + let identities = [identity] + + this.client.onSigningStatus.on((message) => { + identities = message.identities + }) + + ux.action.start('Waiting for Identities from server') + while (identities.length < numSigners) { + this.client.getSigningStatus() + ux.action.status = `${identities.length}/${numSigners}` + await PromiseUtils.sleep(3000) + } + + this.client.onSigningStatus.clear() + ux.action.stop() + + return identities + } + + async getSigningCommitments(options: { + signingCommitment: string + numSigners: number + }): Promise { + const { signingCommitment, numSigners } = options + + this.client.submitSigningCommitment(signingCommitment) + + let signingCommitments = [signingCommitment] + + this.client.onSigningStatus.on((message) => { + signingCommitments = message.signingCommitments + }) + + ux.action.start('Waiting for Signing Commitments from server') + while (signingCommitments.length < numSigners) { + this.client.getSigningStatus() + ux.action.status = `${signingCommitments.length}/${numSigners}` + await PromiseUtils.sleep(3000) + } + + this.client.onSigningStatus.clear() + ux.action.stop() + + return signingCommitments + } + + async getSignatureShares(options: { + signatureShare: string + numSigners: number + }): Promise { + const { signatureShare, numSigners } = options + + this.client.submitSignatureShare(signatureShare) + + let signatureShares = [signatureShare] + + this.client.onSigningStatus.on((message) => { + signatureShares = message.signatureShares + }) + + ux.action.start('Waiting for Signature Shares from server') + while (signatureShares.length < numSigners) { + this.client.getSigningStatus() + ux.action.status = `${signatureShares.length}/${numSigners}` + await PromiseUtils.sleep(3000) + } + + this.client.onSigningStatus.clear() + ux.action.stop() + + return signatureShares + } +} + +export class MultisigSigningSessionManager + extends MultisigSessionManager + implements SigningSessionManager +{ + async startSession(options: { + numSigners?: number + unsignedTransaction?: string + }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> { + const { numSigners, unsignedTransaction } = await inputSigningConfig({ + ...options, + logger: this.logger, + }) + + return { + numSigners, + unsignedTransaction: new UnsignedTransaction(Buffer.from(unsignedTransaction, 'hex')), + } + } + + endSession(): void { + return + } + + async getIdentities(options: { + accountName: string + identity: string + numSigners: number + }): Promise { + const { accountName, identity, numSigners } = options + + this.logger.info(`Identity for ${accountName}: \n${identity} \n`) + this.logger.info('Share your participant identity with other signers.') + + this.logger.info( + `Enter ${numSigners - 1} identities of the participants (excluding your own)`, + ) + + return ui.collectStrings('Participant Identity', numSigners - 1, { + additionalStrings: [identity], + logger: this.logger, + }) + } + + async getSigningCommitments(options: { + signingCommitment: string + numSigners: number + }): Promise { + const { signingCommitment, numSigners } = options + + this.logger.info('\n============================================') + this.logger.info('\nCommitment:') + this.logger.info(signingCommitment) + this.logger.info('\n============================================') + + this.logger.info('\nShare your commitment with other participants.') + + this.logger.info( + `Enter ${numSigners - 1} commitments of the participants (excluding your own)`, + ) + + return ui.collectStrings('Commitment', numSigners - 1, { + additionalStrings: [signingCommitment], + logger: this.logger, + }) + } + + async getSignatureShares(options: { + signatureShare: string + numSigners: number + }): Promise { + const { signatureShare, numSigners } = options + this.logger.info('\n============================================') + this.logger.info('\nSignature Share:') + this.logger.info(signatureShare) + this.logger.info('\n============================================') + + this.logger.info('\nShare your signature share with other participants.') + + this.logger.info( + `Enter ${numSigners - 1} signature shares of the participants (excluding your own)`, + ) + + return ui.collectStrings('Signature Share', numSigners - 1, { + additionalStrings: [signatureShare], + logger: this.logger, + }) + } +} + +async function inputSigningConfig(options: { + logger: Logger + numSigners?: number + unsignedTransaction?: string +}): Promise<{ numSigners: number; unsignedTransaction: string }> { + const unsignedTransaction = + options.unsignedTransaction ?? + (await ui.longPrompt('Enter the unsigned transaction', { required: true })) + + const numSigners = + options.numSigners ?? + (await ui.inputNumberPrompt( + options.logger, + 'Enter the number of participants in signing this transaction', + { required: true, integer: true }, + )) + + if (numSigners < 2) { + throw Error('Minimum number of participants must be at least 2') + } + + return { numSigners, unsignedTransaction } +} From e0d4336201b2e8f500a419e08a0f27427028715d Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:13:41 -0700 Subject: [PATCH 04/81] client cancels retry on error message (#5539) error messages from the multisig broker server include the originating messageId in the error message. the client should cancel retries for a message that results in an error message from the server --- ironfish-cli/src/multisigBroker/clients/client.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 413be1894c..93f35155ae 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -293,6 +293,7 @@ export abstract class MultisigClient { this.logger.debug( `Server sent error ${headerWithError.result.error.message} for id ${headerWithError.result.error.id}`, ) + this.cancelRetry(headerWithError.result.error.id) this.onMultisigBrokerError.emit(headerWithError.result) return } @@ -307,9 +308,7 @@ export abstract class MultisigClient { throw new ServerMessageMalformedError(body.error, header.result.method) } - const retryInterval = this.retries.get(body.result.messageId) - clearInterval(retryInterval) - this.retries.delete(body.result.messageId) + this.cancelRetry(body.result.messageId) break } case 'dkg.status': { @@ -367,6 +366,12 @@ export abstract class MultisigClient { } } + cancelRetry(messageId: number): void { + const retryInterval = this.retries.get(messageId) + clearInterval(retryInterval) + this.retries.delete(messageId) + } + private encryptMessageBody(body: unknown): object { let encrypted = body as object for (const [key, value] of Object.entries(body as object)) { From 7a70f801059d79fe5b5aa82375931ee1e69d70dd Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:34:57 -0700 Subject: [PATCH 05/81] multisig broker server disconnects on invalid message (#5540) * multisig broker server disconnects on invalid message if the broker server receives a message that it cannot parse then it should disconnect from the client that sent the message the server cannot send an 'ack' message to the client if it cannot parse the message and cannot send an error message with the message id of the errant message, so the client may continue retrying the malformed message if the server does not disconnect for now the server does not implement client banning * adds debug log message on parsing error --- ironfish-cli/src/multisigBroker/server.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ironfish-cli/src/multisigBroker/server.ts b/ironfish-cli/src/multisigBroker/server.ts index cfc3be382f..30b0a40a8e 100644 --- a/ironfish-cli/src/multisigBroker/server.ts +++ b/ironfish-cli/src/multisigBroker/server.ts @@ -186,7 +186,14 @@ export class MultisigServer { ) if (parseError) { - this.sendErrorMessage(client, 0, `Error parsing message`) + this.logger.debug( + `Error parsing message from client ${client.id}: ${ErrorUtils.renderError( + parseError, + true, + )}`, + ) + client.close(parseError) + this.clients.delete(client.id) return } From a11a54134eaba4572541cea89921035df0798cfe Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:37:21 -0700 Subject: [PATCH 06/81] adds error codes to multisig broker error messages (#5541) error codes from the server will help the client to determine how to handle the error message Closes IFL-3072 --- ironfish-cli/src/multisigBroker/errors.ts | 7 ++ ironfish-cli/src/multisigBroker/messages.ts | 2 + ironfish-cli/src/multisigBroker/server.ts | 99 +++++++++++++++++---- 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/ironfish-cli/src/multisigBroker/errors.ts b/ironfish-cli/src/multisigBroker/errors.ts index 5f0b8131e8..6fc99a3118 100644 --- a/ironfish-cli/src/multisigBroker/errors.ts +++ b/ironfish-cli/src/multisigBroker/errors.ts @@ -4,6 +4,13 @@ import * as yup from 'yup' import { MultisigServerClient } from './serverClient' +export const MultisigBrokerErrorCodes = { + DUPLICATE_SESSION_ID: 1, + SESSION_ID_NOT_FOUND: 2, + INVALID_DKG_SESSION_ID: 3, + INVALID_SIGNING_SESSION_ID: 4, +} + export class MessageMalformedError extends Error { name = this.constructor.name diff --git a/ironfish-cli/src/multisigBroker/messages.ts b/ironfish-cli/src/multisigBroker/messages.ts index 8775a219e6..18141d5040 100644 --- a/ironfish-cli/src/multisigBroker/messages.ts +++ b/ironfish-cli/src/multisigBroker/messages.ts @@ -15,6 +15,7 @@ export interface MultisigBrokerMessageWithError error: { id: number message: string + code: number } } @@ -99,6 +100,7 @@ export const MultisigBrokerMessageWithErrorSchema: yup.ObjectSchema Date: Mon, 14 Oct 2024 11:39:03 -0700 Subject: [PATCH 07/81] uses ux action to show client connecting (#5542) gives the user feedback that the command is waiting on a connection to the multisig broker server --- .../src/multisigBroker/sessionManagers/sessionManager.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index c243835146..f271f2a5c7 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -2,6 +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 { Assert, Logger, PromiseUtils } from '@ironfish/sdk' +import { ux } from '@oclif/core' import { MultisigClient } from '../clients' import { MultisigBrokerUtils } from '../utils' @@ -44,6 +45,9 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage protected async connect(): Promise { let confirmed = false + ux.action.start( + `Connecting to multisig broker server: ${this.client.hostname}:${this.client.port}`, + ) this.client.start() this.client.onConnectedMessage.on(() => { @@ -55,6 +59,7 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage } this.client.onConnectedMessage.clear() + ux.action.stop() } async joinSession(sessionId: string): Promise { From 8f804ff9bc73a81eff8be7c6a7375904f0bb864d Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:03:15 -0700 Subject: [PATCH 08/81] wait for server to confirm session started (#5543) updates server to send 'joined_session' message to client after starting the session on the server side updates session manager to wait for 'joined_session' message before returning from startSession adds ux status output when waiting for confirmation of joined session --- ironfish-cli/src/multisigBroker/server.ts | 8 ++++++++ .../multisigBroker/sessionManagers/dkgSessionManager.ts | 6 ++++-- .../src/multisigBroker/sessionManagers/sessionManager.ts | 2 ++ .../sessionManagers/signingSessionManager.ts | 6 ++++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ironfish-cli/src/multisigBroker/server.ts b/ironfish-cli/src/multisigBroker/server.ts index bbfbcb64eb..5d3d85d07a 100644 --- a/ironfish-cli/src/multisigBroker/server.ts +++ b/ironfish-cli/src/multisigBroker/server.ts @@ -463,6 +463,10 @@ export class MultisigServer { this.logger.debug(`Client ${client.id} started dkg session ${message.sessionId}`) this.addClientToSession(client, sessionId) + + this.send(client.socket, 'joined_session', message.sessionId, { + challenge: session.challenge, + }) } async handleSigningStartSessionMessage( @@ -507,6 +511,10 @@ export class MultisigServer { this.logger.debug(`Client ${client.id} started signing session ${message.sessionId}`) this.addClientToSession(client, sessionId) + + this.send(client.socket, 'joined_session', message.sessionId, { + challenge: session.challenge, + }) } handleJoinSessionMessage(client: MultisigServerClient, message: MultisigBrokerMessage) { diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts index 91ff07a261..0a4cdad7ce 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts @@ -57,8 +57,10 @@ export class MultisigClientDkgSessionManager this.client.startDkgSession(totalParticipants, minSigners) this.sessionId = this.client.sessionId - this.logger.info('\nStarted new DKG session:') - this.logger.info(`${this.sessionId}`) + this.logger.info(`\nStarted new DKG session: ${this.sessionId}\n`) + + await this.waitForJoinedSession() + this.logger.info('\nDKG session connection string:') this.logger.info(`${this.client.connectionString}`) diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index f271f2a5c7..89cc2c4371 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -75,6 +75,7 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage Assert.isNotNull(this.client) let confirmed = false + ux.action.start(`Waiting to join session: ${this.client.sessionId}`) this.client.onJoinedSession.on(() => { confirmed = true }) @@ -84,6 +85,7 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage } this.client.onJoinedSession.clear() + ux.action.stop() } endSession(): void { diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts index 9e0de4d2a2..630c3eaf32 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts @@ -47,8 +47,10 @@ export class MultisigClientSigningSessionManager this.client.startSigningSession(numSigners, unsignedTransaction) this.sessionId = this.client.sessionId - this.logger.info('\nStarted new signing session:') - this.logger.info(`${this.sessionId}`) + this.logger.info(`\nStarting new signing session: ${this.sessionId}\n`) + + await this.waitForJoinedSession() + this.logger.info('\nSigning session connection string:') this.logger.info(`${this.client.connectionString}`) From 5ced203be41a2c98b9849cd07146bb3e9f257561 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:45:57 -0700 Subject: [PATCH 09/81] updates 'wallet:import' to support ledger multisig (#5544) adds a '--multisig' flag to use with the '--ledger' flag to import a multisig account from the Ironfish DKG Ledger app adds an 'importAccount' method to 'LedgerMultiSigner' to match the function signature in the 'LedgerSingleSigner' class updates 'wallet:multisig:ledger:import' to use the same lgoic hides 'wallet:multisig:ledger:import' and adds a deprecation message --- ironfish-cli/src/commands/wallet/import.ts | 31 +++++++++++- .../commands/wallet/multisig/ledger/import.ts | 48 +++++++++---------- ironfish-cli/src/ledger/ledgerMultiSigner.ts | 22 ++++++++- 3 files changed, 74 insertions(+), 27 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/import.ts b/ironfish-cli/src/commands/wallet/import.ts index c36303fdf5..17339c0711 100644 --- a/ironfish-cli/src/commands/wallet/import.ts +++ b/ironfish-cli/src/commands/wallet/import.ts @@ -5,7 +5,7 @@ import { AccountFormat, encodeAccountImport } from '@ironfish/sdk' import { Args, Flags, ux } from '@oclif/core' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' -import { LedgerError, LedgerSingleSigner } from '../../ledger' +import { LedgerError, LedgerMultiSigner, LedgerSingleSigner } from '../../ledger' import { checkWalletUnlocked, inputPrompt } from '../../ui' import * as ui from '../../ui' import { importFile, importPipe, longPrompt } from '../../ui/longPrompt' @@ -42,6 +42,11 @@ export class ImportCommand extends IronfishCommand { default: false, exclusive: ['path'], }), + multisig: Flags.boolean({ + description: 'Import a view-only multisig account from a ledger device', + default: false, + dependsOn: ['ledger'], + }), } async start(): Promise { @@ -68,6 +73,8 @@ export class ImportCommand extends IronfishCommand { if (blob) { account = blob + } else if (flags.ledger && flags.multisig) { + account = await this.importLedgerMultisig() } else if (flags.ledger) { account = await this.importLedger() } else if (flags.path) { @@ -138,4 +145,26 @@ export class ImportCommand extends IronfishCommand { } } } + + async importLedgerMultisig(): Promise { + try { + const ledger = new LedgerMultiSigner() + + const account = await ui.ledger({ + ledger, + message: 'Import Multisig Wallet', + approval: true, + action: () => ledger.importAccount(), + }) + + return encodeAccountImport(account, AccountFormat.Base64Json) + } catch (e) { + if (e instanceof LedgerError) { + this.logger.error(e.message + '\n') + this.exit(1) + } else { + this.error('Unknown error while importing account from ledger device.') + } + } + } } diff --git a/ironfish-cli/src/commands/wallet/multisig/ledger/import.ts b/ironfish-cli/src/commands/wallet/multisig/ledger/import.ts index b9199e8c6d..c7c82ba242 100644 --- a/ironfish-cli/src/commands/wallet/multisig/ledger/import.ts +++ b/ironfish-cli/src/commands/wallet/multisig/ledger/import.ts @@ -1,16 +1,17 @@ /* 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 { AccountFormat, encodeAccountImport } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' -import { LedgerMultiSigner } from '../../../../ledger' +import { LedgerError, LedgerMultiSigner } from '../../../../ledger' import * as ui from '../../../../ui' import { importAccount } from '../../../../utils' export class MultisigLedgerImport extends IronfishCommand { static description = `import a multisig account from a Ledger device` + static hidden = true static flags = { ...RemoteFlags, @@ -24,6 +25,10 @@ export class MultisigLedgerImport extends IronfishCommand { } async start(): Promise { + this.warn( + `The 'ironfish wallet:multisig:ledger:import' command is deprecated. Use 'ironfish wallet:import --ledger --multisig'`, + ) + const { flags } = await this.parse(MultisigLedgerImport) const client = await this.connectRpc() @@ -31,42 +36,35 @@ export class MultisigLedgerImport extends IronfishCommand { const name = flags.name ?? (await ui.inputPrompt('Enter a name for the account', true)) - const ledger = new LedgerMultiSigner() + let account try { - await ledger.connect() + const ledger = new LedgerMultiSigner() + const accountImport = await ui.ledger({ + ledger, + message: 'Import Wallet', + approval: true, + action: () => ledger.importAccount(), + }) + + account = encodeAccountImport(accountImport, AccountFormat.Base64Json) } catch (e) { - if (e instanceof Error) { - this.error(e.message) + if (e instanceof LedgerError) { + this.logger.error(e.message + '\n') + this.exit(1) } else { - throw e + this.error('Unknown error while importing account from ledger device.') } } - 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), + account, this.logger, name, flags.createdAt, ) this.log() - this.log(`Account ${accountName} imported with public address: ${dkgKeys.publicAddress}`) + this.log(`Account ${accountName} imported`) } } diff --git a/ironfish-cli/src/ledger/ledgerMultiSigner.ts b/ironfish-cli/src/ledger/ledgerMultiSigner.ts index 2057ecf5a1..6595cfd48e 100644 --- a/ironfish-cli/src/ledger/ledgerMultiSigner.ts +++ b/ironfish-cli/src/ledger/ledgerMultiSigner.ts @@ -1,7 +1,7 @@ /* 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 { ACCOUNT_SCHEMA_VERSION, UnsignedTransaction } from '@ironfish/sdk' import { IronfishKeys, KeyResponse, @@ -165,4 +165,24 @@ export class LedgerMultiSigner extends Ledger { dkgRestoreKeys = async (encryptedKeys: string): Promise => { await this.tryInstruction((app) => app.dkgRestoreKeys(encryptedKeys)) } + + importAccount = async () => { + const identity = await this.dkgGetIdentity(0) + const dkgKeys = await this.dkgRetrieveKeys() + const publicKeyPackage = await this.dkgGetPublicPackage() + + const accountImport = { + ...dkgKeys, + name: 'ledger-multisig', + multisigKeys: { + publicKeyPackage: publicKeyPackage.toString('hex'), + identity: identity.toString('hex'), + }, + version: ACCOUNT_SCHEMA_VERSION, + spendingKey: null, + createdAt: null, + } + + return accountImport + } } From cf237e3d577b5de0586a6d574af4d06ca7e1f259 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:18:55 -0700 Subject: [PATCH 10/81] sets passphrase on client when joining session (#5545) instead of requiring a passphrase at the time of client construction, sets the client passphrase only when starting or joining a session moves usage of 'parseConnectionOptions' into session manager for sessions that use the multisig broker server prompts the user for sessionId and passphrase in 'startSession'. this will allow for re-prompting if starting or joining the session fails --- .../commands/wallet/multisig/dkg/create.ts | 20 ++++--------- .../src/commands/wallet/multisig/sign.ts | 20 ++++--------- .../src/multisigBroker/clients/client.ts | 21 ++++++++------ .../src/multisigBroker/clients/tcpClient.ts | 3 +- .../sessionManagers/dkgSessionManager.ts | 18 ++++++++++-- .../sessionManagers/sessionManager.ts | 23 +++++++++++---- .../sessionManagers/signingSessionManager.ts | 18 ++++++++++-- ironfish-cli/src/multisigBroker/utils.ts | 28 ++++--------------- 8 files changed, 80 insertions(+), 71 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index 5df6e611dc..1b2ad1fdfd 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -19,7 +19,6 @@ import path from 'path' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import { LedgerMultiSigner } from '../../../../ledger' -import { MultisigBrokerUtils } from '../../../../multisigBroker' import { DkgSessionManager, MultisigClientDkgSessionManager, @@ -105,21 +104,12 @@ export class DkgCreateCommand extends IronfishCommand { let sessionManager: DkgSessionManager if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { - const { hostname, port, sessionId, passphrase } = - await MultisigBrokerUtils.parseConnectionOptions({ - connection: flags.connection, - hostname: flags.hostname, - port: flags.port, - sessionId: flags.sessionId, - passphrase: flags.passphrase, - logger: this.logger, - }) - sessionManager = new MultisigClientDkgSessionManager({ - hostname, - port, - passphrase, - sessionId, + connection: flags.connection, + hostname: flags.hostname, + port: flags.port, + passphrase: flags.passphrase, + sessionId: flags.sessionId, tls: flags.tls ?? true, logger: this.logger, }) diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index f7038d3fbb..ae237e241b 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -14,7 +14,6 @@ import { Flags, ux } from '@oclif/core' import { IronfishCommand } from '../../../command' import { RemoteFlags } from '../../../flags' import { LedgerMultiSigner } from '../../../ledger' -import { MultisigBrokerUtils } from '../../../multisigBroker' import { MultisigClientSigningSessionManager, MultisigSigningSessionManager, @@ -128,22 +127,13 @@ export class SignMultisigTransactionCommand extends IronfishCommand { let sessionManager: SigningSessionManager if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { - const { hostname, port, sessionId, passphrase } = - await MultisigBrokerUtils.parseConnectionOptions({ - connection: flags.connection, - hostname: flags.hostname, - port: flags.port, - sessionId: flags.sessionId, - passphrase: flags.passphrase, - logger: this.logger, - }) - sessionManager = new MultisigClientSigningSessionManager({ logger: this.logger, - hostname, - port, - passphrase, - sessionId, + connection: flags.connection, + hostname: flags.hostname, + port: flags.port, + passphrase: flags.passphrase, + sessionId: flags.sessionId, tls: flags.tls, }) } else { diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 93f35155ae..65b00cce12 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -63,11 +63,11 @@ export abstract class MultisigClient { readonly onMultisigBrokerError = new Event<[MultisigBrokerMessageWithError]>() sessionId: string | null = null - passphrase: string + passphrase: string | null = null retries: Map = new Map() - constructor(options: { hostname: string; port: number; passphrase: string; logger: Logger }) { + constructor(options: { hostname: string; port: number; logger: Logger }) { this.logger = options.logger this.version = 3 this.hostname = options.hostname @@ -78,8 +78,6 @@ export abstract class MultisigClient { this.connected = false this.connectWarned = false this.connectTimeout = null - - this.passphrase = options.passphrase } get connectionString(): string { @@ -87,7 +85,7 @@ export abstract class MultisigClient { } get key(): xchacha20poly1305.XChaCha20Poly1305Key { - if (!this.sessionId) { + if (!this.sessionId || !this.passphrase) { throw new Error('Client must join a session before encrypting/decrypting messages') } @@ -162,19 +160,26 @@ export abstract class MultisigClient { return this.connected } - joinSession(sessionId: string): void { + joinSession(sessionId: string, passphrase: string): void { this.sessionId = sessionId + this.passphrase = passphrase this.send('join_session', {}) } - startDkgSession(maxSigners: number, minSigners: number): void { + startDkgSession(passphrase: string, maxSigners: number, minSigners: number): void { this.sessionId = uuid() + this.passphrase = passphrase const challenge = this.key.encrypt(Buffer.from('DKG')).toString('hex') this.send('dkg.start_session', { maxSigners, minSigners, challenge }) } - startSigningSession(numSigners: number, unsignedTransaction: string): void { + startSigningSession( + passphrase: string, + numSigners: number, + unsignedTransaction: string, + ): void { this.sessionId = uuid() + this.passphrase = passphrase const challenge = this.key.encrypt(Buffer.from('SIGNING')).toString('hex') this.send('sign.start_session', { numSigners, unsignedTransaction, challenge }) } diff --git a/ironfish-cli/src/multisigBroker/clients/tcpClient.ts b/ironfish-cli/src/multisigBroker/clients/tcpClient.ts index 3de57b0423..df83cd5158 100644 --- a/ironfish-cli/src/multisigBroker/clients/tcpClient.ts +++ b/ironfish-cli/src/multisigBroker/clients/tcpClient.ts @@ -8,11 +8,10 @@ import { MultisigClient } from './client' export class MultisigTcpClient extends MultisigClient { client: net.Socket | null = null - constructor(options: { hostname: string; port: number; passphrase: string; logger: Logger }) { + constructor(options: { hostname: string; port: number; logger: Logger }) { super({ hostname: options.hostname, port: options.port, - passphrase: options.passphrase, logger: options.logger, }) } diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts index 0a4cdad7ce..4794b32e00 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts @@ -40,8 +40,22 @@ export class MultisigClientDkgSessionManager minSigners?: number ledger?: boolean }): Promise<{ totalParticipants: number; minSigners: number }> { + if (!this.sessionId) { + this.sessionId = await ui.inputPrompt( + 'Enter the ID of a multisig session to join, or press enter to start a new session', + false, + ) + } + + if (!this.passphrase) { + this.passphrase = await ui.inputPrompt( + 'Enter the passphrase for the multisig session', + true, + ) + } + if (this.sessionId) { - await this.joinSession(this.sessionId) + await this.joinSession(this.sessionId, this.passphrase) return this.getSessionConfig() } @@ -54,7 +68,7 @@ export class MultisigClientDkgSessionManager await this.connect() - this.client.startDkgSession(totalParticipants, minSigners) + this.client.startDkgSession(this.passphrase, totalParticipants, minSigners) this.sessionId = this.client.sessionId this.logger.info(`\nStarted new DKG session: ${this.sessionId}\n`) diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index 89cc2c4371..5db1f566eb 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -22,24 +22,37 @@ export abstract class MultisigSessionManager { export abstract class MultisigClientSessionManager extends MultisigSessionManager { client: MultisigClient sessionId: string | null + passphrase: string | null constructor(options: { logger: Logger + connection?: string hostname: string port: number - passphrase: string + passphrase?: string sessionId?: string tls?: boolean }) { super({ logger: options.logger }) - this.client = MultisigBrokerUtils.createClient(options.hostname, options.port, { + const { hostname, port, sessionId, passphrase } = + MultisigBrokerUtils.parseConnectionOptions({ + connection: options.connection, + hostname: options.hostname, + port: options.port, + sessionId: options.sessionId, + passphrase: options.passphrase, + logger: this.logger, + }) + + this.client = MultisigBrokerUtils.createClient(hostname, port, { passphrase: options.passphrase, tls: options.tls ?? true, logger: this.logger, }) - this.sessionId = options.sessionId ?? null + this.sessionId = sessionId ?? null + this.passphrase = passphrase ?? null } protected async connect(): Promise { @@ -62,10 +75,10 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage ux.action.stop() } - async joinSession(sessionId: string): Promise { + async joinSession(sessionId: string, passphrase: string): Promise { await this.connect() - this.client.joinSession(sessionId) + this.client.joinSession(sessionId, passphrase) await this.waitForJoinedSession() this.sessionId = sessionId diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts index 630c3eaf32..fa2a098f09 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts @@ -32,8 +32,22 @@ export class MultisigClientSigningSessionManager numSigners?: number unsignedTransaction?: string }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> { + if (!this.sessionId) { + this.sessionId = await ui.inputPrompt( + 'Enter the ID of a multisig session to join, or press enter to start a new session', + false, + ) + } + + if (!this.passphrase) { + this.passphrase = await ui.inputPrompt( + 'Enter the passphrase for the multisig session', + true, + ) + } + if (this.sessionId) { - await this.joinSession(this.sessionId) + await this.joinSession(this.sessionId, this.passphrase) return this.getSessionConfig() } @@ -44,7 +58,7 @@ export class MultisigClientSigningSessionManager await this.connect() - this.client.startSigningSession(numSigners, unsignedTransaction) + this.client.startSigningSession(this.passphrase, numSigners, unsignedTransaction) this.sessionId = this.client.sessionId this.logger.info(`\nStarting new signing session: ${this.sessionId}\n`) diff --git a/ironfish-cli/src/multisigBroker/utils.ts b/ironfish-cli/src/multisigBroker/utils.ts index 7fe8443f45..614ddac045 100644 --- a/ironfish-cli/src/multisigBroker/utils.ts +++ b/ironfish-cli/src/multisigBroker/utils.ts @@ -2,22 +2,21 @@ * 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 { ErrorUtils, Logger } from '@ironfish/sdk' -import * as ui from '../ui' import { MultisigClient, MultisigTcpClient, MultisigTlsClient } from './clients' -async function parseConnectionOptions(options: { +function parseConnectionOptions(options: { connection?: string hostname: string port: number sessionId?: string passphrase?: string logger: Logger -}): Promise<{ +}): { hostname: string port: number - sessionId: string - passphrase: string -}> { + sessionId: string | undefined + passphrase: string | undefined +} { let hostname let port let sessionId @@ -48,19 +47,6 @@ async function parseConnectionOptions(options: { hostname = hostname ?? options.hostname port = port ?? options.port - sessionId = sessionId ?? options.sessionId - if (!sessionId) { - sessionId = await ui.inputPrompt( - 'Enter the ID of a multisig session to join, or press enter to start a new session', - false, - ) - } - - passphrase = passphrase ?? options.passphrase - if (!passphrase) { - passphrase = await ui.inputPrompt('Enter the passphrase for the multisig session', true) - } - return { hostname, port, @@ -72,20 +58,18 @@ async function parseConnectionOptions(options: { function createClient( hostname: string, port: number, - options: { passphrase: string; tls: boolean; logger: Logger }, + options: { passphrase?: string; tls: boolean; logger: Logger }, ): MultisigClient { if (options.tls) { return new MultisigTlsClient({ hostname, port, - passphrase: options.passphrase, logger: options.logger, }) } else { return new MultisigTcpClient({ hostname, port, - passphrase: options.passphrase, logger: options.logger, }) } From d7588a5e8f6628469c3e27c8cb34129376fa9e27 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:41:10 -0700 Subject: [PATCH 11/81] Don't crash when invalid dkg config input is given (#5546) Right now, if you input an invalid number for total participants or min signers, it will end the process by throwing an error. A better experience is to display the error and allow the user to try again with proper input. --- .../sessionManagers/dkgSessionManager.ts | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts index 4794b32e00..b4fd5c71af 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts @@ -284,32 +284,49 @@ async function inputDkgConfig(options: { totalParticipants: number minSigners: number }> { - const totalParticipants = - options.totalParticipants ?? - (await ui.inputNumberPrompt(options.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') - } + let totalParticipants + + // eslint-disable-next-line no-constant-condition + while (true) { + totalParticipants = + options.totalParticipants ?? + (await ui.inputNumberPrompt(options.logger, 'Enter the total number of participants', { + required: true, + integer: true, + })) + + if (totalParticipants < 2) { + options.logger.error('Total number of participants must be at least 2') + continue + } - if (options.ledger && totalParticipants > 4) { - throw new Error('DKG with Ledger supports a maximum of 4 participants') + if (options.ledger && totalParticipants > 4) { + options.logger.error('DKG with Ledger supports a maximum of 4 participants') + continue + } + + break } - const minSigners = - options.minSigners ?? - (await ui.inputNumberPrompt(options.logger, 'Enter the number of minimum signers', { - required: true, - integer: true, - })) + let minSigners - if (minSigners < 2 || minSigners > totalParticipants) { - throw new Error( - 'Minimum number of signers must be between 2 and the total number of participants', - ) + // eslint-disable-next-line no-constant-condition + while (true) { + minSigners = + options.minSigners ?? + (await ui.inputNumberPrompt(options.logger, 'Enter the number of minimum signers', { + required: true, + integer: true, + })) + + if (minSigners < 2 || minSigners > totalParticipants) { + options.logger.error( + 'Minimum number of signers must be between 2 and the total number of participants', + ) + continue + } + + break } return { totalParticipants, minSigners } From 7a8b308e9cbbf3c5dad976a322bb5f7254a0cd3f Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:36:43 -0700 Subject: [PATCH 12/81] retries joining session if passphrase incorrect (#5548) if a user enters the passphrase for a session by the client is unable to decrypt the session challenge string, the client emits a 'SessionDecryptionError'. the 'wallet:multisig:dkg:create' and 'wallet:multisig:sign' commands will now retry the step and prompt the user to re-enter the passphrase adds an event emitter, 'onClientError', to the multisig client. emitting client errors as events provides a way for the command logic to define error handlers to throw/catch the error in the control flow of the command instead of in response to server data --- .../commands/wallet/multisig/dkg/create.ts | 12 +++++---- .../src/commands/wallet/multisig/sign.ts | 8 +++--- .../src/multisigBroker/clients/client.ts | 25 +++++++++---------- ironfish-cli/src/multisigBroker/errors.ts | 10 ++++++++ .../sessionManagers/sessionManager.ts | 20 ++++++++++++++- 5 files changed, 53 insertions(+), 22 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index 1b2ad1fdfd..7245ccc709 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -117,11 +117,13 @@ export class DkgCreateCommand extends IronfishCommand { sessionManager = new MultisigDkgSessionManager({ logger: this.logger }) } - const { totalParticipants, minSigners } = await sessionManager.startSession({ - totalParticipants: flags.totalParticipants, - minSigners: flags.minSigners, - ledger: flags.ledger, - }) + const { totalParticipants, minSigners } = await ui.retryStep(async () => { + return sessionManager.startSession({ + totalParticipants: flags.totalParticipants, + minSigners: flags.minSigners, + ledger: flags.ledger, + }) + }, this.logger) const { name: participantName, identity } = await this.getOrCreateIdentity( client, diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index ae237e241b..639baa6103 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -140,9 +140,11 @@ export class SignMultisigTransactionCommand extends IronfishCommand { sessionManager = new MultisigSigningSessionManager({ logger: this.logger }) } - const { numSigners, unsignedTransaction } = await sessionManager.startSession({ - unsignedTransaction: flags.unsignedTransaction, - }) + const { numSigners, unsignedTransaction } = await ui.retryStep(async () => { + return sessionManager.startSession({ + unsignedTransaction: flags.unsignedTransaction, + }) + }, this.logger) const { commitment, identities } = await ui.retryStep( async () => { diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 65b00cce12..2fa809be61 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -11,7 +11,11 @@ import { YupUtils, } from '@ironfish/sdk' import { v4 as uuid } from 'uuid' -import { ServerMessageMalformedError } from '../errors' +import { + MultisigClientError, + ServerMessageMalformedError, + SessionDecryptionError, +} from '../errors' import { ConnectedMessage, ConnectedMessageSchema, @@ -61,6 +65,7 @@ export abstract class MultisigClient { readonly onConnectedMessage = new Event<[ConnectedMessage]>() readonly onJoinedSession = new Event<[JoinSessionMessage]>() readonly onMultisigBrokerError = new Event<[MultisigBrokerMessageWithError]>() + readonly onClientError = new Event<[MultisigClientError]>() sessionId: string | null = null passphrase: string | null = null @@ -272,9 +277,6 @@ export abstract class MultisigClient { } protected onError = (error: unknown): void => { - if (error instanceof SessionDecryptionError) { - throw error - } this.logger.error(`Error ${ErrorUtils.renderError(error)}`) } @@ -358,10 +360,13 @@ export abstract class MultisigClient { const decrypted = this.decryptMessageBody(body.result) this.onJoinedSession.emit(decrypted) break - } catch { - throw new SessionDecryptionError( - 'Failed to decrypt session challenge. Passphrase is incorrect.', + } catch (e) { + this.onClientError.emit( + new SessionDecryptionError( + 'Failed to decrypt session challenge. Passphrase is incorrect.', + ), ) + break } } @@ -437,9 +442,3 @@ export abstract class MultisigClient { return decrypted } } - -class SessionDecryptionError extends Error { - constructor(message: string) { - super(message) - } -} diff --git a/ironfish-cli/src/multisigBroker/errors.ts b/ironfish-cli/src/multisigBroker/errors.ts index 6fc99a3118..3e8f2cbb35 100644 --- a/ironfish-cli/src/multisigBroker/errors.ts +++ b/ironfish-cli/src/multisigBroker/errors.ts @@ -47,3 +47,13 @@ export class ServerMessageMalformedError extends MessageMalformedError { super('Server', error, method) } } + +export class MultisigClientError extends Error { + name = this.constructor.name +} + +export class SessionDecryptionError extends MultisigClientError { + constructor(message: string) { + super(message) + } +} diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index 5db1f566eb..781c00d158 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -4,6 +4,7 @@ import { Assert, Logger, PromiseUtils } from '@ironfish/sdk' import { ux } from '@oclif/core' import { MultisigClient } from '../clients' +import { SessionDecryptionError } from '../errors' import { MultisigBrokerUtils } from '../utils' export abstract class MultisigSessionManager { @@ -56,6 +57,10 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage } protected async connect(): Promise { + if (this.client.isConnected()) { + return + } + let confirmed = false ux.action.start( @@ -86,14 +91,27 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage protected async waitForJoinedSession(): Promise { Assert.isNotNull(this.client) - let confirmed = false ux.action.start(`Waiting to join session: ${this.client.sessionId}`) + + let confirmed = false this.client.onJoinedSession.on(() => { confirmed = true }) + let clientError: unknown + this.client.onClientError.on((error) => { + clientError = error + }) + while (!confirmed) { + if (clientError) { + if (clientError instanceof SessionDecryptionError) { + this.passphrase = null + } + ux.action.stop() + throw clientError + } await PromiseUtils.sleep(1000) } From dc7940a49c81a3a1b754a34af9391c7282ceae5b Mon Sep 17 00:00:00 2001 From: Derek Guenther Date: Tue, 15 Oct 2024 13:58:56 -0400 Subject: [PATCH 13/81] Remove S3 uploading from Homebrew deployment (#5537) --- .github/workflows/deploy-brew.yml | 9 --------- ironfish-cli/scripts/deploy-brew.sh | 31 +++++++---------------------- 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/.github/workflows/deploy-brew.yml b/.github/workflows/deploy-brew.yml index 989ab68c4a..7d55d8e5cc 100644 --- a/.github/workflows/deploy-brew.yml +++ b/.github/workflows/deploy-brew.yml @@ -30,14 +30,6 @@ jobs: - name: Build Ironfish CLI run: ./ironfish-cli/scripts/build.sh - - name: Deploy Ironfish CLI Brew - run: ./ironfish-cli/scripts/deploy-brew.sh - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - BREW_GITHUB_USERNAME: ${{ secrets.BREW_GITHUB_USERNAME }} - BREW_GITHUB_TOKEN: ${{ secrets.BREW_GITHUB_TOKEN }} - - name: Deploy Ironfish CLI Brew to R2 run: ./ironfish-cli/scripts/deploy-brew.sh env: @@ -46,4 +38,3 @@ jobs: BREW_GITHUB_USERNAME: ${{ secrets.BREW_GITHUB_USERNAME }} BREW_GITHUB_TOKEN: ${{ secrets.BREW_GITHUB_TOKEN }} AWS_DEFAULT_REGION: auto - UPLOAD_TO_R2: true diff --git a/ironfish-cli/scripts/deploy-brew.sh b/ironfish-cli/scripts/deploy-brew.sh index 7f7edb5935..5a29fd02f0 100755 --- a/ironfish-cli/scripts/deploy-brew.sh +++ b/ironfish-cli/scripts/deploy-brew.sh @@ -28,13 +28,7 @@ UPLOAD_HASH=$(shasum -a 256 $SOURCE_PATH | awk '{print $1}') UPLOAD_NAME=ironfish-cli-$GIT_HASH.tar.gz UPLOAD_URL=s3://ironfish-cli/$UPLOAD_NAME - -if [ -z "${UPLOAD_TO_R2-}" ]; -then - PUBLIC_URL=https://ironfish-cli.s3.amazonaws.com/$UPLOAD_NAME -else - PUBLIC_URL=https://releases.ironfish.network/$UPLOAD_NAME -fi +PUBLIC_URL=https://releases.ironfish.network/$UPLOAD_NAME echo "" echo "GIT HASH: $GIT_HASH" @@ -44,25 +38,14 @@ echo "UPLOAD URL: $UPLOAD_URL" echo "PUBLIC URL: $PUBLIC_URL" echo "" -if [ -z "${UPLOAD_TO_R2-}" ]; -then - if aws s3api head-object --bucket ironfish-cli --key $UPLOAD_NAME > /dev/null 2>&1 ; then - echo "Release already uploaded: $PUBLIC_URL" - exit 1 - fi - - echo "Uploading $SOURCE_NAME to $UPLOAD_URL" - aws s3 cp $SOURCE_PATH $UPLOAD_URL -else - if aws s3api head-object --bucket ironfish-cli --endpoint-url https://a93bebf26da4c2fe205f71c896afcf89.r2.cloudflarestorage.com --key $UPLOAD_NAME > /dev/null 2>&1 ; then - echo "Release already uploaded: $PUBLIC_URL" - exit 1 - fi - - echo "Uploading $SOURCE_NAME to $UPLOAD_URL" - aws s3 cp $SOURCE_PATH $UPLOAD_URL --endpoint-url https://a93bebf26da4c2fe205f71c896afcf89.r2.cloudflarestorage.com +if aws s3api head-object --bucket ironfish-cli --endpoint-url https://a93bebf26da4c2fe205f71c896afcf89.r2.cloudflarestorage.com --key $UPLOAD_NAME > /dev/null 2>&1 ; then + echo "Release already uploaded: $PUBLIC_URL" + exit 1 fi +echo "Uploading $SOURCE_NAME to $UPLOAD_URL" +aws s3 cp $SOURCE_PATH $UPLOAD_URL --endpoint-url https://a93bebf26da4c2fe205f71c896afcf89.r2.cloudflarestorage.com + echo "" echo "You are almost finished! To finish the process you need to update update url and sha256 in" echo "https://github.com/iron-fish/homebrew-brew/blob/master/Formula/ironfish.rb" From c7755c49ce25f0d44f48a1c4067ef8518ed80ecc Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:34:29 -0700 Subject: [PATCH 14/81] Clear identities when a user disconnects during the identity collection phase (#5547) * Clear identities when a user disconnects during the identity collection phase * remove extra unneeded checks --- ironfish-cli/src/multisigBroker/server.ts | 24 +++++++++++++++++++ .../src/multisigBroker/serverClient.ts | 1 + 2 files changed, 25 insertions(+) diff --git a/ironfish-cli/src/multisigBroker/server.ts b/ironfish-cli/src/multisigBroker/server.ts index 5d3d85d07a..9f6a0cdb0a 100644 --- a/ironfish-cli/src/multisigBroker/server.ts +++ b/ironfish-cli/src/multisigBroker/server.ts @@ -307,6 +307,28 @@ export class MultisigServer { return } + // If the session is in the collecting identities phase, we can safely + // remove a disconnected client's identity from the list. Otherwise, if a + // client disconnects and then reconnects, they can possibly be counted as + // multiple identities, leaving the session in a bad state. + if (client.identity != null) { + if (isDkgSession(session)) { + if (session.status.round1PublicPackages.length === 0) { + const identIndex = session.status.identities.indexOf(client.identity) + if (identIndex > -1) { + session.status.identities.splice(identIndex, 1) + } + } + } else if (isSigningSession(session)) { + if (session.status.signingCommitments.length === 0) { + const identIndex = session.status.identities.indexOf(client.identity) + if (identIndex > -1) { + session.status.identities.splice(identIndex, 1) + } + } + } + } + client.sessionId = null session.clientIds.delete(client.id) } @@ -567,6 +589,7 @@ export class MultisigServer { } const identity = body.result.identity + client.identity = identity if (!session.status.identities.includes(identity)) { session.status.identities.push(identity) this.sessions.set(message.sessionId, session) @@ -610,6 +633,7 @@ export class MultisigServer { } const identity = body.result.identity + client.identity = identity if (!session.status.identities.includes(identity)) { session.status.identities.push(identity) this.sessions.set(message.sessionId, session) diff --git a/ironfish-cli/src/multisigBroker/serverClient.ts b/ironfish-cli/src/multisigBroker/serverClient.ts index 886f71f284..1d35b3104d 100644 --- a/ironfish-cli/src/multisigBroker/serverClient.ts +++ b/ironfish-cli/src/multisigBroker/serverClient.ts @@ -11,6 +11,7 @@ export class MultisigServerClient { remoteAddress: string messageBuffer: MessageBuffer sessionId: string | null = null + identity: string | null = null private constructor(options: { socket: net.Socket; id: number }) { this.id = options.id From 79c292a39fccadf1bce23c4560a937e056c61738 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:43:46 -0700 Subject: [PATCH 15/81] prompts user to re-enter sessionId if session not found (#5549) listens for server error messages with the error code ofr sessionId not found while waiting for a session. throws an error if the sessionId is not found --- ironfish-cli/src/multisigBroker/errors.ts | 6 ++++++ .../sessionManagers/sessionManager.ts | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ironfish-cli/src/multisigBroker/errors.ts b/ironfish-cli/src/multisigBroker/errors.ts index 3e8f2cbb35..dca8dd35e5 100644 --- a/ironfish-cli/src/multisigBroker/errors.ts +++ b/ironfish-cli/src/multisigBroker/errors.ts @@ -57,3 +57,9 @@ export class SessionDecryptionError extends MultisigClientError { super(message) } } + +export class InvalidSessionError extends MultisigClientError { + constructor(message: string) { + super(message) + } +} diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index 781c00d158..30ef64d9e5 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -4,7 +4,11 @@ import { Assert, Logger, PromiseUtils } from '@ironfish/sdk' import { ux } from '@oclif/core' import { MultisigClient } from '../clients' -import { SessionDecryptionError } from '../errors' +import { + InvalidSessionError, + MultisigBrokerErrorCodes, + SessionDecryptionError, +} from '../errors' import { MultisigBrokerUtils } from '../utils' export abstract class MultisigSessionManager { @@ -104,10 +108,18 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage clientError = error }) + this.client.onMultisigBrokerError.on((errorMessage) => { + if (errorMessage.error.code === MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND) { + clientError = new InvalidSessionError(errorMessage.error.message) + } + }) + while (!confirmed) { if (clientError) { if (clientError instanceof SessionDecryptionError) { this.passphrase = null + } else if (clientError instanceof InvalidSessionError) { + this.sessionId = null } ux.action.stop() throw clientError From 09d36f540b393bb285203804a96e739bab4ffe31 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:11:40 -0700 Subject: [PATCH 16/81] adds connection timeout to multisig broker client (#5550) throws an error if client is unable to connect to multisig broker server within one minute repurposes unused property 'disconnectUntil' into 'tryConnectUntil' --- ironfish-cli/src/multisigBroker/clients/client.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 2fa809be61..5d100bc5f6 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -43,6 +43,7 @@ import { } from '../messages' const RETRY_INTERVAL = 5000 +const CONNECTION_TIMEOUT = 60 * 1000 export abstract class MultisigClient { readonly logger: Logger readonly version: number @@ -57,7 +58,7 @@ export abstract class MultisigClient { private nextMessageId: number private readonly messageBuffer = new MessageBuffer('\n') - private disconnectUntil: number | null = null + private tryConnectUntil: number | null = null readonly onConnected = new Event<[]>() readonly onDkgStatus = new Event<[DkgStatusMessage]>() @@ -120,9 +121,11 @@ export abstract class MultisigClient { return } - if (this.disconnectUntil && this.disconnectUntil > Date.now()) { - this.connectTimeout = setTimeout(() => void this.startConnecting(), 60 * 1000) - return + if (!this.tryConnectUntil) { + this.tryConnectUntil = Date.now() + CONNECTION_TIMEOUT + } else if (Date.now() > this.tryConnectUntil) { + clearTimeout(this.connectTimeout ?? undefined) + throw new Error(`Timed out connecting to server at ${this.hostname}:${this.port}`) } const connected = await this.connect() @@ -143,6 +146,7 @@ export abstract class MultisigClient { return } + this.tryConnectUntil = null this.connectWarned = false this.onConnect() this.onConnected.emit() From 8b24f949ec1e835a745b8026e76ae225de62bcff Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:41:20 -0700 Subject: [PATCH 17/81] encodes passphrase as URI component in connection string (#5551) supports passphrases that contain spaces and other unicode characters that would result in invalid connection strings decodes passphrase when parsing connection string --- ironfish-cli/src/multisigBroker/clients/client.ts | 3 ++- ironfish-cli/src/multisigBroker/utils.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 5d100bc5f6..4f4f384e09 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -87,7 +87,8 @@ export abstract class MultisigClient { } get connectionString(): string { - return `tcp://${this.sessionId}:${this.passphrase}@${this.hostname}:${this.port}` + const passphrase = this.passphrase ? encodeURIComponent(this.passphrase) : '' + return `tcp://${this.sessionId}:${passphrase}@${this.hostname}:${this.port}` } get key(): xchacha20poly1305.XChaCha20Poly1305Key { diff --git a/ironfish-cli/src/multisigBroker/utils.ts b/ironfish-cli/src/multisigBroker/utils.ts index 614ddac045..48ee34150a 100644 --- a/ironfish-cli/src/multisigBroker/utils.ts +++ b/ironfish-cli/src/multisigBroker/utils.ts @@ -34,7 +34,7 @@ function parseConnectionOptions(options: { sessionId = url.username } if (url.password) { - passphrase = url.password + passphrase = decodeURI(url.password) } } catch (e) { if (e instanceof TypeError && e.message.includes('Invalid URL')) { From 6a54f8c4ac8b5caf3e4dee4d133b0c3b32f2b97b Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:47:26 -0700 Subject: [PATCH 18/81] removes unused code from multisigBroker module (#5552) --- ironfish-cli/src/multisigBroker/clients/client.ts | 2 -- ironfish-cli/src/multisigBroker/constants.ts | 9 --------- .../src/multisigBroker/sessionManagers/sessionManager.ts | 1 - ironfish-cli/src/multisigBroker/utils.ts | 2 +- 4 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 ironfish-cli/src/multisigBroker/constants.ts diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 4f4f384e09..4561c3677f 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -46,7 +46,6 @@ const RETRY_INTERVAL = 5000 const CONNECTION_TIMEOUT = 60 * 1000 export abstract class MultisigClient { readonly logger: Logger - readonly version: number readonly hostname: string readonly port: number @@ -75,7 +74,6 @@ export abstract class MultisigClient { constructor(options: { hostname: string; port: number; logger: Logger }) { this.logger = options.logger - this.version = 3 this.hostname = options.hostname this.port = options.port diff --git a/ironfish-cli/src/multisigBroker/constants.ts b/ironfish-cli/src/multisigBroker/constants.ts deleted file mode 100644 index 437f002a98..0000000000 --- a/ironfish-cli/src/multisigBroker/constants.ts +++ /dev/null @@ -1,9 +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/. */ - -export enum DisconnectReason { - BAD_VERSION = 'bad_version', - BANNED = 'banned', - UNKNOWN = 'unknown', -} diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index 30ef64d9e5..2054c136bf 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -51,7 +51,6 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage }) this.client = MultisigBrokerUtils.createClient(hostname, port, { - passphrase: options.passphrase, tls: options.tls ?? true, logger: this.logger, }) diff --git a/ironfish-cli/src/multisigBroker/utils.ts b/ironfish-cli/src/multisigBroker/utils.ts index 48ee34150a..d19a180b37 100644 --- a/ironfish-cli/src/multisigBroker/utils.ts +++ b/ironfish-cli/src/multisigBroker/utils.ts @@ -58,7 +58,7 @@ function parseConnectionOptions(options: { function createClient( hostname: string, port: number, - options: { passphrase?: string; tls: boolean; logger: Logger }, + options: { tls: boolean; logger: Logger }, ): MultisigClient { if (options.tls) { return new MultisigTlsClient({ From 1337046815bd13f57c58c1a6e90af62eee0199ba Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:50:14 -0700 Subject: [PATCH 19/81] clients submit identity when starting or joining session (#5553) modifies multisigBroker messages so that each client submits its identity when it starts or joins a session simplifies the session flow by removing separate messages for submitting identities --- .../commands/wallet/multisig/dkg/create.ts | 13 +- .../src/commands/wallet/multisig/sign.ts | 1 + .../src/multisigBroker/clients/client.ts | 29 ++--- ironfish-cli/src/multisigBroker/messages.ts | 25 ++-- ironfish-cli/src/multisigBroker/server.ts | 114 ++++-------------- .../sessionManagers/dkgSessionManager.ts | 13 +- .../sessionManagers/sessionManager.ts | 4 +- .../sessionManagers/signingSessionManager.ts | 13 +- 8 files changed, 75 insertions(+), 137 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index 7245ccc709..c1b974b4de 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -102,6 +102,12 @@ export class DkgCreateCommand extends IronfishCommand { accountCreatedAt = statusResponse.content.blockchain.head.sequence } + const { name: participantName, identity } = await this.getOrCreateIdentity( + client, + ledger, + accountName, + ) + let sessionManager: DkgSessionManager if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { sessionManager = new MultisigClientDkgSessionManager({ @@ -122,15 +128,10 @@ export class DkgCreateCommand extends IronfishCommand { totalParticipants: flags.totalParticipants, minSigners: flags.minSigners, ledger: flags.ledger, + identity, }) }, this.logger) - const { name: participantName, identity } = await this.getOrCreateIdentity( - client, - ledger, - accountName, - ) - const { round1 } = await ui.retryStep( async () => { return this.performRound1( diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index 639baa6103..191c5b7302 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -143,6 +143,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { const { numSigners, unsignedTransaction } = await ui.retryStep(async () => { return sessionManager.startSession({ unsignedTransaction: flags.unsignedTransaction, + identity: participant.identity, }) }, this.logger) diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 4561c3677f..4aadadcd74 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -23,7 +23,6 @@ import { DkgStartSessionMessage, DkgStatusMessage, DkgStatusSchema, - IdentityMessage, JoinedSessionMessage, JoinedSessionSchema, JoinSessionMessage, @@ -63,7 +62,7 @@ export abstract class MultisigClient { readonly onDkgStatus = new Event<[DkgStatusMessage]>() readonly onSigningStatus = new Event<[SigningStatusMessage]>() readonly onConnectedMessage = new Event<[ConnectedMessage]>() - readonly onJoinedSession = new Event<[JoinSessionMessage]>() + readonly onJoinedSession = new Event<[JoinedSessionMessage]>() readonly onMultisigBrokerError = new Event<[MultisigBrokerMessageWithError]>() readonly onClientError = new Event<[MultisigClientError]>() @@ -168,36 +167,34 @@ export abstract class MultisigClient { return this.connected } - joinSession(sessionId: string, passphrase: string): void { + joinSession(sessionId: string, passphrase: string, identity: string): void { this.sessionId = sessionId this.passphrase = passphrase - this.send('join_session', {}) + this.send('join_session', { identity }) } - startDkgSession(passphrase: string, maxSigners: number, minSigners: number): void { + startDkgSession( + passphrase: string, + maxSigners: number, + minSigners: number, + identity: string, + ): void { this.sessionId = uuid() this.passphrase = passphrase const challenge = this.key.encrypt(Buffer.from('DKG')).toString('hex') - this.send('dkg.start_session', { maxSigners, minSigners, challenge }) + this.send('dkg.start_session', { maxSigners, minSigners, challenge, identity }) } startSigningSession( passphrase: string, numSigners: number, unsignedTransaction: string, + identity: string, ): void { this.sessionId = uuid() this.passphrase = passphrase const challenge = this.key.encrypt(Buffer.from('SIGNING')).toString('hex') - this.send('sign.start_session', { numSigners, unsignedTransaction, challenge }) - } - - submitDkgIdentity(identity: string): void { - this.send('dkg.identity', { identity }) - } - - submitSigningIdentity(identity: string): void { - this.send('sign.identity', { identity }) + this.send('sign.start_session', { numSigners, unsignedTransaction, challenge, identity }) } submitRound1PublicPackage(round1PublicPackage: string): void { @@ -227,8 +224,6 @@ export abstract class MultisigClient { private send(method: 'join_session', body: JoinSessionMessage): void private send(method: 'dkg.start_session', body: DkgStartSessionMessage): void private send(method: 'sign.start_session', body: SigningStartSessionMessage): void - private send(method: 'dkg.identity', body: IdentityMessage): void - private send(method: 'sign.identity', body: IdentityMessage): void private send(method: 'dkg.round1', body: Round1PublicPackageMessage): void private send(method: 'dkg.round2', body: Round2PublicPackageMessage): void private send(method: 'dkg.get_status', body: DkgGetStatusMessage): void diff --git a/ironfish-cli/src/multisigBroker/messages.ts b/ironfish-cli/src/multisigBroker/messages.ts index 18141d5040..c0f472a3c9 100644 --- a/ironfish-cli/src/multisigBroker/messages.ts +++ b/ironfish-cli/src/multisigBroker/messages.ts @@ -27,24 +27,24 @@ export type DkgStartSessionMessage = { minSigners: number maxSigners: number challenge: string + identity: string } export type SigningStartSessionMessage = { numSigners: number unsignedTransaction: string challenge: string + identity: string } -export type JoinSessionMessage = object | undefined +export type JoinSessionMessage = { + identity: string +} export type JoinedSessionMessage = { challenge: string } -export type IdentityMessage = { - identity: string -} - export type Round1PublicPackageMessage = { package: string } @@ -117,6 +117,7 @@ export const DkgStartSessionSchema: yup.ObjectSchema = y minSigners: yup.number().defined(), maxSigners: yup.number().defined(), challenge: yup.string().defined(), + identity: yup.string().defined(), }) .defined() @@ -125,13 +126,15 @@ export const SigningStartSessionSchema: yup.ObjectSchema = yup - .object({}) - .notRequired() - .default(undefined) + .object({ + identity: yup.string().defined(), + }) + .defined() export const JoinedSessionSchema: yup.ObjectSchema = yup .object({ @@ -139,12 +142,6 @@ export const JoinedSessionSchema: yup.ObjectSchema = yup }) .required() -export const IdentitySchema: yup.ObjectSchema = yup - .object({ - identity: yup.string().defined(), - }) - .defined() - export const Round1PublicPackageSchema: yup.ObjectSchema = yup .object({ package: yup.string().defined() }) .defined() diff --git a/ironfish-cli/src/multisigBroker/server.ts b/ironfish-cli/src/multisigBroker/server.ts index 9f6a0cdb0a..2baf8b6fca 100644 --- a/ironfish-cli/src/multisigBroker/server.ts +++ b/ironfish-cli/src/multisigBroker/server.ts @@ -10,8 +10,8 @@ import { DkgGetStatusSchema, DkgStartSessionSchema, DkgStatusMessage, - IdentitySchema, JoinedSessionMessage, + JoinSessionSchema, MultisigBrokerAckMessage, MultisigBrokerMessage, MultisigBrokerMessageSchema, @@ -207,10 +207,7 @@ export class MultisigServer { await this.handleSigningStartSessionMessage(client, message) return } else if (message.method === 'join_session') { - this.handleJoinSessionMessage(client, message) - return - } else if (message.method === 'dkg.identity') { - await this.handleDkgIdentityMessage(client, message) + await this.handleJoinSessionMessage(client, message) return } else if (message.method === 'dkg.round1') { await this.handleRound1PublicPackageMessage(client, message) @@ -221,9 +218,6 @@ export class MultisigServer { } else if (message.method === 'dkg.get_status') { await this.handleDkgGetStatusMessage(client, message) return - } else if (message.method === 'sign.identity') { - await this.handleSigningIdentityMessage(client, message) - return } else if (message.method === 'sign.commitment') { await this.handleSigningCommitmentMessage(client, message) return @@ -472,7 +466,7 @@ export class MultisigServer { status: { maxSigners: body.result.maxSigners, minSigners: body.result.minSigners, - identities: [], + identities: [body.result.identity], round1PublicPackages: [], round2PublicPackages: [], }, @@ -484,6 +478,7 @@ export class MultisigServer { this.logger.debug(`Client ${client.id} started dkg session ${message.sessionId}`) + client.identity = body.result.identity this.addClientToSession(client, sessionId) this.send(client.socket, 'joined_session', message.sessionId, { @@ -520,7 +515,7 @@ export class MultisigServer { status: { numSigners: body.result.numSigners, unsignedTransaction: body.result.unsignedTransaction, - identities: [], + identities: [body.result.identity], signingCommitments: [], signatureShares: [], }, @@ -532,6 +527,7 @@ export class MultisigServer { this.logger.debug(`Client ${client.id} started signing session ${message.sessionId}`) + client.identity = body.result.identity this.addClientToSession(client, sessionId) this.send(client.socket, 'joined_session', message.sessionId, { @@ -539,29 +535,8 @@ export class MultisigServer { }) } - handleJoinSessionMessage(client: MultisigServerClient, message: MultisigBrokerMessage) { - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } - - this.logger.debug(`Client ${client.id} joined session ${message.sessionId}`) - - this.addClientToSession(client, message.sessionId) - - this.send(client.socket, 'joined_session', message.sessionId, { - challenge: session.challenge, - }) - } - - async handleDkgIdentityMessage(client: MultisigServerClient, message: MultisigBrokerMessage) { - const body = await YupUtils.tryValidate(IdentitySchema, message.body) + async handleJoinSessionMessage(client: MultisigServerClient, message: MultisigBrokerMessage) { + const body = await YupUtils.tryValidate(JoinSessionSchema, message.body) if (body.error) { return @@ -578,69 +553,28 @@ export class MultisigServer { return } - if (!isDkgSession(session)) { - this.sendErrorMessage( - client, - message.id, - `Session is not a dkg session: ${message.sessionId}`, - MultisigBrokerErrorCodes.INVALID_DKG_SESSION_ID, - ) - return - } - - const identity = body.result.identity - client.identity = identity - if (!session.status.identities.includes(identity)) { - session.status.identities.push(identity) - this.sessions.set(message.sessionId, session) - - // Broadcast status after collecting all identities - if (session.status.identities.length === session.status.maxSigners) { - this.broadcast('dkg.status', message.sessionId, session.status) - } - } - } - - async handleSigningIdentityMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(IdentitySchema, message.body) - - if (body.error) { - return - } + this.logger.debug(`Client ${client.id} joined session ${message.sessionId}`) - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } + this.addClientToSession(client, message.sessionId) - if (!isSigningSession(session)) { - this.sendErrorMessage( - client, - message.id, - `Session is not a signing session: ${message.sessionId}`, - MultisigBrokerErrorCodes.INVALID_DKG_SESSION_ID, - ) - return - } + this.send(client.socket, 'joined_session', message.sessionId, { + challenge: session.challenge, + }) - const identity = body.result.identity - client.identity = identity - if (!session.status.identities.includes(identity)) { - session.status.identities.push(identity) + client.identity = body.result.identity + if (!session.status.identities.includes(client.identity)) { + session.status.identities.push(client.identity) this.sessions.set(message.sessionId, session) // Broadcast status after collecting all identities - if (session.status.identities.length === session.status.numSigners) { - this.broadcast('sign.status', message.sessionId, session.status) + if (isDkgSession(session)) { + if (session.status.identities.length === session.status.maxSigners) { + this.broadcast('dkg.status', message.sessionId, session.status) + } + } else if (isSigningSession(session)) { + if (session.status.identities.length === session.status.numSigners) { + this.broadcast('sign.status', message.sessionId, session.status) + } } } } diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts index b4fd5c71af..f9c418a040 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts @@ -11,6 +11,7 @@ export interface DkgSessionManager extends MultisigSessionManager { totalParticipants?: number minSigners?: number ledger?: boolean + identity: string }): Promise<{ totalParticipants: number; minSigners: number }> getIdentities(options: { identity: string @@ -39,6 +40,7 @@ export class MultisigClientDkgSessionManager totalParticipants?: number minSigners?: number ledger?: boolean + identity: string }): Promise<{ totalParticipants: number; minSigners: number }> { if (!this.sessionId) { this.sessionId = await ui.inputPrompt( @@ -55,7 +57,7 @@ export class MultisigClientDkgSessionManager } if (this.sessionId) { - await this.joinSession(this.sessionId, this.passphrase) + await this.joinSession(this.sessionId, this.passphrase, options.identity) return this.getSessionConfig() } @@ -68,7 +70,12 @@ export class MultisigClientDkgSessionManager await this.connect() - this.client.startDkgSession(this.passphrase, totalParticipants, minSigners) + this.client.startDkgSession( + this.passphrase, + totalParticipants, + minSigners, + options.identity, + ) this.sessionId = this.client.sessionId this.logger.info(`\nStarted new DKG session: ${this.sessionId}\n`) @@ -108,8 +115,6 @@ export class MultisigClientDkgSessionManager }): Promise { const { identity, totalParticipants } = options - this.client.submitDkgIdentity(identity) - let identities = [identity] this.client.onDkgStatus.on((message) => { identities = message.identities diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index 2054c136bf..2ce7cb4743 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -83,10 +83,10 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage ux.action.stop() } - async joinSession(sessionId: string, passphrase: string): Promise { + async joinSession(sessionId: string, passphrase: string, identity: string): Promise { await this.connect() - this.client.joinSession(sessionId, passphrase) + this.client.joinSession(sessionId, passphrase, identity) await this.waitForJoinedSession() this.sessionId = sessionId diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts index fa2a098f09..53923114af 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts @@ -11,6 +11,7 @@ export interface SigningSessionManager extends MultisigSessionManager { startSession(options: { numSigners?: number unsignedTransaction?: string + identity: string }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> getIdentities(options: { identity: string @@ -31,6 +32,7 @@ export class MultisigClientSigningSessionManager async startSession(options: { numSigners?: number unsignedTransaction?: string + identity: string }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> { if (!this.sessionId) { this.sessionId = await ui.inputPrompt( @@ -47,7 +49,7 @@ export class MultisigClientSigningSessionManager } if (this.sessionId) { - await this.joinSession(this.sessionId, this.passphrase) + await this.joinSession(this.sessionId, this.passphrase, options.identity) return this.getSessionConfig() } @@ -58,7 +60,12 @@ export class MultisigClientSigningSessionManager await this.connect() - this.client.startSigningSession(this.passphrase, numSigners, unsignedTransaction) + this.client.startSigningSession( + this.passphrase, + numSigners, + unsignedTransaction, + options.identity, + ) this.sessionId = this.client.sessionId this.logger.info(`\nStarting new signing session: ${this.sessionId}\n`) @@ -105,8 +112,6 @@ export class MultisigClientSigningSessionManager async getIdentities(options: { identity: string; numSigners: number }): Promise { const { identity, numSigners } = options - this.client.submitSigningIdentity(identity) - let identities = [identity] this.client.onSigningStatus.on((message) => { From 29d3b1084334587072ff32bba3ffff7fc12b5410 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:36:35 -0700 Subject: [PATCH 20/81] adds confirmation prompt to 'multisig:sign' if using server (#5555) * adds confirmation prompt to 'multisig:sign' if using server when running the 'wallet:multisig:sign' command using a broker server there are no prompts or manual steps after the initial setup. this doesn't give the user a chance to confirm the details of the unsigned transaction before the signing process moves forward adds a confirmation prompt if the 'sessionManager' is connected to a server moves the rendering of transaction details and the confirmation prompt out of a 'retryStep' function so that confirmation isn't retried * does not prompt for confirmation if user is using ledger updates prompt message --- .../src/commands/wallet/multisig/sign.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index 191c5b7302..89b9bf2def 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -147,6 +147,18 @@ export class SignMultisigTransactionCommand extends IronfishCommand { }) }, this.logger) + await renderUnsignedTransactionDetails( + client, + unsignedTransaction, + multisigAccountName, + this.logger, + ) + + // Prompt for confirmation before broker automates signing + if (!flags.ledger && sessionManager instanceof MultisigClientSigningSessionManager) { + await ui.confirmOrQuit('Sign this transaction?') + } + const { commitment, identities } = await ui.retryStep( async () => { return this.performCreateSigningCommitment( @@ -349,13 +361,6 @@ export class SignMultisigTransactionCommand extends IronfishCommand { const unsignedTransactionHex = unsignedTransaction.serialize().toString('hex') - await renderUnsignedTransactionDetails( - client, - unsignedTransaction, - accountName, - this.logger, - ) - let commitment if (ledger) { commitment = await this.createSigningCommitmentWithLedger( From fe3a9d5fb88a5522df158da74aadacbb942de506 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:10:03 -0700 Subject: [PATCH 21/81] adds allowed identities set to multisig server sessions (#5554) * adds allowed identities set to multisig server sessions server blocks any client from joining the session if its identity is not in the set of allowed identities sends the (encrypted) list of account identities to the server when starting a signing session. guards against user accidentally joining signing session with the wrong account allowedIdentities is optional for dkg sessions, but there's not a clear way yet of specifying this set * removes allowedIdentities from DKG messages * undoes unnecessary reformatting --- ironfish-cli/src/commands/wallet/multisig/sign.ts | 1 + ironfish-cli/src/multisigBroker/clients/client.ts | 9 ++++++++- ironfish-cli/src/multisigBroker/errors.ts | 7 +++++++ ironfish-cli/src/multisigBroker/messages.ts | 2 ++ ironfish-cli/src/multisigBroker/server.ts | 14 ++++++++++++++ .../sessionManagers/sessionManager.ts | 4 ++++ .../sessionManagers/signingSessionManager.ts | 3 +++ 7 files changed, 39 insertions(+), 1 deletion(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index 89b9bf2def..afc3ad8aef 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -144,6 +144,7 @@ export class SignMultisigTransactionCommand extends IronfishCommand { return sessionManager.startSession({ unsignedTransaction: flags.unsignedTransaction, identity: participant.identity, + allowedIdentities: accountIdentities, }) }, this.logger) diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 4aadadcd74..9a516753be 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -190,11 +190,18 @@ export abstract class MultisigClient { numSigners: number, unsignedTransaction: string, identity: string, + allowedIdentities?: string[], ): void { this.sessionId = uuid() this.passphrase = passphrase const challenge = this.key.encrypt(Buffer.from('SIGNING')).toString('hex') - this.send('sign.start_session', { numSigners, unsignedTransaction, challenge, identity }) + this.send('sign.start_session', { + numSigners, + unsignedTransaction, + challenge, + identity, + allowedIdentities, + }) } submitRound1PublicPackage(round1PublicPackage: string): void { diff --git a/ironfish-cli/src/multisigBroker/errors.ts b/ironfish-cli/src/multisigBroker/errors.ts index dca8dd35e5..dfddc508cf 100644 --- a/ironfish-cli/src/multisigBroker/errors.ts +++ b/ironfish-cli/src/multisigBroker/errors.ts @@ -9,6 +9,7 @@ export const MultisigBrokerErrorCodes = { SESSION_ID_NOT_FOUND: 2, INVALID_DKG_SESSION_ID: 3, INVALID_SIGNING_SESSION_ID: 4, + IDENTITY_NOT_ALLOWED: 5, } export class MessageMalformedError extends Error { @@ -63,3 +64,9 @@ export class InvalidSessionError extends MultisigClientError { super(message) } } + +export class IdentityNotAllowedError extends MultisigClientError { + constructor(message: string) { + super(message) + } +} diff --git a/ironfish-cli/src/multisigBroker/messages.ts b/ironfish-cli/src/multisigBroker/messages.ts index c0f472a3c9..03ed964c1d 100644 --- a/ironfish-cli/src/multisigBroker/messages.ts +++ b/ironfish-cli/src/multisigBroker/messages.ts @@ -35,6 +35,7 @@ export type SigningStartSessionMessage = { unsignedTransaction: string challenge: string identity: string + allowedIdentities?: string[] } export type JoinSessionMessage = { @@ -127,6 +128,7 @@ export const SigningStartSessionSchema: yup.ObjectSchema } interface DkgSession extends MultisigSession { @@ -521,6 +522,9 @@ export class MultisigServer { }, challenge: body.result.challenge, timeout: undefined, + allowedIdentities: body.result.allowedIdentities + ? new Set(body.result.allowedIdentities) + : undefined, } this.sessions.set(sessionId, session) @@ -553,6 +557,16 @@ export class MultisigServer { return } + if (session.allowedIdentities && !session.allowedIdentities.has(body.result.identity)) { + this.sendErrorMessage( + client, + message.id, + 'Identity not allowed to join this session', + MultisigBrokerErrorCodes.IDENTITY_NOT_ALLOWED, + ) + return + } + this.logger.debug(`Client ${client.id} joined session ${message.sessionId}`) this.addClientToSession(client, message.sessionId) diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index 2ce7cb4743..50ee121942 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -5,6 +5,7 @@ import { Assert, Logger, PromiseUtils } from '@ironfish/sdk' import { ux } from '@oclif/core' import { MultisigClient } from '../clients' import { + IdentityNotAllowedError, InvalidSessionError, MultisigBrokerErrorCodes, SessionDecryptionError, @@ -110,6 +111,9 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage this.client.onMultisigBrokerError.on((errorMessage) => { if (errorMessage.error.code === MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND) { clientError = new InvalidSessionError(errorMessage.error.message) + } else if (errorMessage.error.code === MultisigBrokerErrorCodes.IDENTITY_NOT_ALLOWED) { + // Throws error immediately instead of deferring to loop, below + throw new IdentityNotAllowedError(errorMessage.error.message) } }) diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts index 53923114af..a7022bc230 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts @@ -12,6 +12,7 @@ export interface SigningSessionManager extends MultisigSessionManager { numSigners?: number unsignedTransaction?: string identity: string + allowedIdentities?: string[] }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> getIdentities(options: { identity: string @@ -33,6 +34,7 @@ export class MultisigClientSigningSessionManager numSigners?: number unsignedTransaction?: string identity: string + allowedIdentities?: string[] }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> { if (!this.sessionId) { this.sessionId = await ui.inputPrompt( @@ -65,6 +67,7 @@ export class MultisigClientSigningSessionManager numSigners, unsignedTransaction, options.identity, + options.allowedIdentities, ) this.sessionId = this.client.sessionId From 68c8fc189ccf5cb9e4ecb3d45bd9903649469eda Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:13:12 -0700 Subject: [PATCH 22/81] renders fee and expiration in unsigned transaction summary (#5556) when reviewing an unsigned transaction it will be helpful to display the transaction fee and expiration these fields are shown when reviewing the transaction on a ledger device, so we should also display them on the CLI --- ironfish-cli/src/utils/transaction.ts | 14 ++++++++++++++ ironfish/src/primitives/unsignedTransaction.ts | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/ironfish-cli/src/utils/transaction.ts b/ironfish-cli/src/utils/transaction.ts index b87aef8900..22c921d8e6 100644 --- a/ironfish-cli/src/utils/transaction.ts +++ b/ironfish-cli/src/utils/transaction.ts @@ -124,6 +124,8 @@ export async function renderTransactionDetails( await _renderTransactionDetails( client, + transaction.fee(), + transaction.expiration(), transaction.mints, transaction.burns, account, @@ -148,6 +150,8 @@ export async function renderUnsignedTransactionDetails( await _renderTransactionDetails( client, + unsignedTransaction.fee(), + unsignedTransaction.expiration(), unsignedTransaction.mints, unsignedTransaction.burns, account, @@ -158,6 +162,8 @@ export async function renderUnsignedTransactionDetails( async function _renderTransactionDetails( client: RpcClient, + fee: bigint, + expiration: number, mints: MintDescription[], burns: BurnDescription[], account?: string, @@ -169,6 +175,14 @@ async function _renderTransactionDetails( const assetIds = collectAssetIds(mints, burns, notes) const assetLookup = await getAssetVerificationByIds(client, assetIds, account, undefined) + logger.log('') + logger.log('===================') + logger.log('Transaction Summary') + logger.log('===================') + logger.log('') + logger.log(`Fee ${CurrencyUtils.render(fee, true)}`) + logger.log(`Expiration ${expiration}`) + if (mints.length > 0) { logger.log('') logger.log('==================') diff --git a/ironfish/src/primitives/unsignedTransaction.ts b/ironfish/src/primitives/unsignedTransaction.ts index 86a2b50fda..2dcc041c18 100644 --- a/ironfish/src/primitives/unsignedTransaction.ts +++ b/ironfish/src/primitives/unsignedTransaction.ts @@ -176,4 +176,12 @@ export class UnsignedTransaction { this.returnReference() return publicKeyRandomness } + + fee(): bigint { + return this._fee + } + + expiration(): number { + return this._expiration + } } From 8d08c8e1b6722efacde74f6220a45e0970fc324a Mon Sep 17 00:00:00 2001 From: jowparks Date: Thu, 17 Oct 2024 13:34:45 -0700 Subject: [PATCH 23/81] make sapling params public (#5558) --- ironfish-rust/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ironfish-rust/src/lib.rs b/ironfish-rust/src/lib.rs index 352e0fbef8..3503643b99 100644 --- a/ironfish-rust/src/lib.rs +++ b/ironfish-rust/src/lib.rs @@ -49,12 +49,12 @@ pub use ironfish_zkp::primitives::ValueCommitment; // // The values are all loaded from a file in serialized form. pub struct Sapling { - spend_params: groth16::Parameters, - output_params: groth16::Parameters, - mint_params: groth16::Parameters, - spend_verifying_key: groth16::PreparedVerifyingKey, - output_verifying_key: groth16::PreparedVerifyingKey, - mint_verifying_key: groth16::PreparedVerifyingKey, + pub spend_params: groth16::Parameters, + pub output_params: groth16::Parameters, + pub mint_params: groth16::Parameters, + pub spend_verifying_key: groth16::PreparedVerifyingKey, + pub output_verifying_key: groth16::PreparedVerifyingKey, + pub mint_verifying_key: groth16::PreparedVerifyingKey, } impl Sapling { From 6e090879f9875d8203330511ff014de1071dbc7c Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 17 Oct 2024 14:44:06 -0700 Subject: [PATCH 24/81] uses server if user sets 'hostname' or 'port' (#5560) if a user sets the hostname or port for the multisig broker server using flags then they should not need to _also_ set the '--server' flag in order to connect to the server defines constants for default multisig broker hostname and port removes default values for hostname and port flags in 'wallet:multisig:dkg:create' and 'wallet:multisig:sign' and applies defaults only when flags are used. allows flags to be unset to determine whether user has set the flags adds factory functions for session managers --- .../commands/wallet/multisig/dkg/create.ts | 29 +++++++------------ .../src/commands/wallet/multisig/sign.ts | 28 +++++++----------- .../sessionManagers/dkgSessionManager.ts | 23 +++++++++++++++ .../sessionManagers/sessionManager.ts | 13 ++------- .../sessionManagers/signingSessionManager.ts | 23 +++++++++++++++ ironfish-cli/src/multisigBroker/utils.ts | 11 ++++--- 6 files changed, 78 insertions(+), 49 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index c1b974b4de..ba70a3a150 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -20,9 +20,8 @@ import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import { LedgerMultiSigner } from '../../../../ledger' import { + createDkgSessionManager, DkgSessionManager, - MultisigClientDkgSessionManager, - MultisigDkgSessionManager, } from '../../../../multisigBroker/sessionManagers' import * as ui from '../../../../ui' @@ -56,11 +55,9 @@ export class DkgCreateCommand extends IronfishCommand { }), hostname: Flags.string({ description: 'hostname of the multisig broker server to connect to', - default: 'multisig.ironfish.network', }), port: Flags.integer({ description: 'port to connect to on the multisig broker server', - default: 9035, }), sessionId: Flags.string({ description: 'Unique ID for a multisig server session to join', @@ -108,20 +105,16 @@ export class DkgCreateCommand extends IronfishCommand { accountName, ) - let sessionManager: DkgSessionManager - if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { - sessionManager = new MultisigClientDkgSessionManager({ - connection: flags.connection, - hostname: flags.hostname, - port: flags.port, - passphrase: flags.passphrase, - sessionId: flags.sessionId, - tls: flags.tls ?? true, - logger: this.logger, - }) - } else { - sessionManager = new MultisigDkgSessionManager({ logger: this.logger }) - } + const sessionManager = createDkgSessionManager({ + server: flags.server, + connection: flags.connection, + hostname: flags.hostname, + port: flags.port, + passphrase: flags.passphrase, + sessionId: flags.sessionId, + tls: flags.tls, + logger: this.logger, + }) const { totalParticipants, minSigners } = await ui.retryStep(async () => { return sessionManager.startSession({ diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index afc3ad8aef..45c4d4e9f4 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -15,8 +15,8 @@ import { IronfishCommand } from '../../../command' import { RemoteFlags } from '../../../flags' import { LedgerMultiSigner } from '../../../ledger' import { + createSigningSessionManager, MultisigClientSigningSessionManager, - MultisigSigningSessionManager, SigningSessionManager, } from '../../../multisigBroker/sessionManagers' import * as ui from '../../../ui' @@ -57,11 +57,9 @@ export class SignMultisigTransactionCommand extends IronfishCommand { }), hostname: Flags.string({ description: 'hostname of the multisig broker server to connect to', - default: 'multisig.ironfish.network', }), port: Flags.integer({ description: 'port to connect to on the multisig broker server', - default: 9035, }), sessionId: Flags.string({ description: 'Unique ID for a multisig server session to join', @@ -125,20 +123,16 @@ export class SignMultisigTransactionCommand extends IronfishCommand { ) } - let sessionManager: SigningSessionManager - if (flags.server || flags.connection || flags.sessionId || flags.passphrase) { - sessionManager = new MultisigClientSigningSessionManager({ - logger: this.logger, - connection: flags.connection, - hostname: flags.hostname, - port: flags.port, - passphrase: flags.passphrase, - sessionId: flags.sessionId, - tls: flags.tls, - }) - } else { - sessionManager = new MultisigSigningSessionManager({ logger: this.logger }) - } + const sessionManager = createSigningSessionManager({ + logger: this.logger, + server: flags.server, + connection: flags.connection, + hostname: flags.hostname, + port: flags.port, + passphrase: flags.passphrase, + sessionId: flags.sessionId, + tls: flags.tls, + }) const { numSigners, unsignedTransaction } = await ui.retryStep(async () => { return sessionManager.startSession({ diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts index f9c418a040..008e115c44 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts @@ -6,6 +6,29 @@ import { ux } from '@oclif/core' import * as ui from '../../ui' import { MultisigClientSessionManager, MultisigSessionManager } from './sessionManager' +export function createDkgSessionManager(options: { + logger: Logger + server?: boolean + connection?: string + hostname?: string + port?: number + passphrase?: string + sessionId?: string + tls?: boolean +}): DkgSessionManager { + if ( + options.server || + options.connection || + options.hostname || + options.port || + options.sessionId + ) { + return new MultisigClientDkgSessionManager(options) + } else { + return new MultisigDkgSessionManager({ logger: options.logger }) + } +} + export interface DkgSessionManager extends MultisigSessionManager { startSession(options: { totalParticipants?: number diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index 50ee121942..cf6b3b9f69 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -33,8 +33,8 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage constructor(options: { logger: Logger connection?: string - hostname: string - port: number + hostname?: string + port?: number passphrase?: string sessionId?: string tls?: boolean @@ -42,14 +42,7 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage super({ logger: options.logger }) const { hostname, port, sessionId, passphrase } = - MultisigBrokerUtils.parseConnectionOptions({ - connection: options.connection, - hostname: options.hostname, - port: options.port, - sessionId: options.sessionId, - passphrase: options.passphrase, - logger: this.logger, - }) + MultisigBrokerUtils.parseConnectionOptions(options) this.client = MultisigBrokerUtils.createClient(hostname, port, { tls: options.tls ?? true, diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts index a7022bc230..0add8079ef 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts @@ -7,6 +7,29 @@ import { ux } from '@oclif/core' import * as ui from '../../ui' import { MultisigClientSessionManager, MultisigSessionManager } from './sessionManager' +export function createSigningSessionManager(options: { + logger: Logger + server?: boolean + connection?: string + hostname?: string + port?: number + passphrase?: string + sessionId?: string + tls?: boolean +}): SigningSessionManager { + if ( + options.server || + options.connection || + options.hostname || + options.port || + options.sessionId + ) { + return new MultisigClientSigningSessionManager(options) + } else { + return new MultisigSigningSessionManager({ logger: options.logger }) + } +} + export interface SigningSessionManager extends MultisigSessionManager { startSession(options: { numSigners?: number diff --git a/ironfish-cli/src/multisigBroker/utils.ts b/ironfish-cli/src/multisigBroker/utils.ts index d19a180b37..f2fee10c0f 100644 --- a/ironfish-cli/src/multisigBroker/utils.ts +++ b/ironfish-cli/src/multisigBroker/utils.ts @@ -4,10 +4,13 @@ import { ErrorUtils, Logger } from '@ironfish/sdk' import { MultisigClient, MultisigTcpClient, MultisigTlsClient } from './clients' +const DEFAULT_MULTISIG_BROKER_HOSTNAME = 'multisig.ironfish.network' +const DEFAULT_MULTISIG_BROKER_PORT = 9035 + function parseConnectionOptions(options: { connection?: string - hostname: string - port: number + hostname?: string + port?: number sessionId?: string passphrase?: string logger: Logger @@ -44,8 +47,8 @@ function parseConnectionOptions(options: { } } - hostname = hostname ?? options.hostname - port = port ?? options.port + hostname = hostname ?? options.hostname ?? DEFAULT_MULTISIG_BROKER_HOSTNAME + port = port ?? options.port ?? DEFAULT_MULTISIG_BROKER_PORT return { hostname, From 34b9523513d8250b67a7f67db46310cb06698dc3 Mon Sep 17 00:00:00 2001 From: j-s-n <143557528+j-s-n@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:07:41 -0700 Subject: [PATCH 25/81] Wallet Unlock Log: Added Lock Timeout in Hrs, Min, Sec (#5562) * wallet unlock log now uses renderspan w/ hr, min, sec * Update unlock.ts Fixed linting error: added comma after forceMinute * Removed renderSpan options from unlock --- ironfish-cli/src/commands/wallet/unlock.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ironfish-cli/src/commands/wallet/unlock.ts b/ironfish-cli/src/commands/wallet/unlock.ts index 1326df62ff..2071078adc 100644 --- a/ironfish-cli/src/commands/wallet/unlock.ts +++ b/ironfish-cli/src/commands/wallet/unlock.ts @@ -2,6 +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 { DEFAULT_UNLOCK_TIMEOUT_MS, RpcRequestError } from '@ironfish/sdk' +import { TimeUtils } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' @@ -59,7 +60,8 @@ export class UnlockCommand extends IronfishCommand { 'Unlocked the wallet. Call wallet:lock or stop the node to lock the wallet again.', ) } else { - this.log(`Unlocked the wallet for ${timeout}ms`) + const timeoutDuration = TimeUtils.renderSpan(timeout) + this.log(`Unlocked the wallet for ${timeoutDuration}.`) } this.exit(0) From e9bec697d28360c47e0bcc0ac90f7aaa9edeac53 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:55:10 -0700 Subject: [PATCH 26/81] automatically rejoins session when reconnecting to server (#5563) if a client disconnects from the multisig broker server unexpectedly then the server will remove the client and its identity from the session client automatically rejoins the session after automatically reconnecting so that the server adds it and its identity back into the session --- ironfish-cli/src/multisigBroker/clients/client.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index 9a516753be..bf4c2dd7b7 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -68,6 +68,7 @@ export abstract class MultisigClient { sessionId: string | null = null passphrase: string | null = null + identity: string | null = null retries: Map = new Map() @@ -148,6 +149,11 @@ export abstract class MultisigClient { this.connectWarned = false this.onConnect() this.onConnected.emit() + + if (this.sessionId && this.passphrase && this.identity) { + this.logger.debug(`Rejoining session ${this.sessionId}`) + this.joinSession(this.sessionId, this.passphrase, this.identity) + } } stop(): void { @@ -170,6 +176,7 @@ export abstract class MultisigClient { joinSession(sessionId: string, passphrase: string, identity: string): void { this.sessionId = sessionId this.passphrase = passphrase + this.identity = identity this.send('join_session', { identity }) } @@ -181,6 +188,7 @@ export abstract class MultisigClient { ): void { this.sessionId = uuid() this.passphrase = passphrase + this.identity = identity const challenge = this.key.encrypt(Buffer.from('DKG')).toString('hex') this.send('dkg.start_session', { maxSigners, minSigners, challenge, identity }) } @@ -194,6 +202,7 @@ export abstract class MultisigClient { ): void { this.sessionId = uuid() this.passphrase = passphrase + this.identity = identity const challenge = this.key.encrypt(Buffer.from('SIGNING')).toString('hex') this.send('sign.start_session', { numSigners, From 3ae48e24a7e7f7aba9e121212186e9bb23177258 Mon Sep 17 00:00:00 2001 From: jowparks Date: Fri, 18 Oct 2024 13:16:27 -0700 Subject: [PATCH 27/81] hex encode/decode pushed to ironfish-zkp (#5561) --- ironfish-rust/src/errors.rs | 7 ++ ironfish-rust/src/serializing/mod.rs | 109 +---------------------- ironfish-zkp/src/hex.rs | 128 +++++++++++++++++++++++++++ ironfish-zkp/src/lib.rs | 1 + 4 files changed, 137 insertions(+), 108 deletions(-) create mode 100644 ironfish-zkp/src/hex.rs diff --git a/ironfish-rust/src/errors.rs b/ironfish-rust/src/errors.rs index 0de86e44fc..5406264073 100644 --- a/ironfish-rust/src/errors.rs +++ b/ironfish-rust/src/errors.rs @@ -33,6 +33,7 @@ pub enum IronfishErrorKind { FailedXChaCha20Poly1305Decryption, FailedXChaCha20Poly1305Encryption, FailedHkdfExpansion, + HexError, IllegalValue, InconsistentWitness, InvalidAssetIdentifier, @@ -145,3 +146,9 @@ impl From for IronfishError { IronfishError::new_with_source(IronfishErrorKind::FrostLibError, e) } } + +impl From for IronfishError { + fn from(e: ironfish_zkp::hex::HexError) -> IronfishError { + IronfishError::new_with_source(IronfishErrorKind::HexError, e) + } +} diff --git a/ironfish-rust/src/serializing/mod.rs b/ironfish-rust/src/serializing/mod.rs index 17c671c1e7..2e1b76960e 100644 --- a/ironfish-rust/src/serializing/mod.rs +++ b/ironfish-rust/src/serializing/mod.rs @@ -5,6 +5,7 @@ pub mod aead; pub mod fr; use crate::errors::{IronfishError, IronfishErrorKind}; +pub use ironfish_zkp::hex::{bytes_to_hex, hex_to_bytes, hex_to_vec_bytes}; /// Helper functions to convert pairing parts to bytes /// @@ -16,8 +17,6 @@ use group::GroupEncoding; use std::io; -const HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; - pub(crate) fn read_scalar(mut reader: R) -> Result { let mut fr_repr = F::Repr::default(); reader.read_exact(fr_repr.as_mut())?; @@ -43,109 +42,3 @@ pub(crate) fn read_point_unchecked( Option::from(G::from_bytes_unchecked(&point_repr)) .ok_or_else(|| IronfishError::new(IronfishErrorKind::InvalidData)) } - -/// Output the bytes as a hexadecimal String -pub fn bytes_to_hex(bytes: &[u8]) -> String { - let mut hex: Vec = vec![0; bytes.len() * 2]; - - for (i, b) in bytes.iter().enumerate() { - hex[i * 2] = HEX_CHARS[(b >> 4) as usize]; - hex[i * 2 + 1] = HEX_CHARS[(b & 0x0f) as usize]; - } - - unsafe { String::from_utf8_unchecked(hex) } -} - -/// Output the hexadecimal String as bytes -pub fn hex_to_bytes(hex: &str) -> Result<[u8; SIZE], IronfishError> { - if hex.len() != SIZE * 2 { - return Err(IronfishError::new(IronfishErrorKind::InvalidData)); - } - - let mut bytes = [0; SIZE]; - - let hex_iter = hex.as_bytes().chunks_exact(2); - - for (i, hex) in hex_iter.enumerate() { - bytes[i] = hex_to_u8(hex[0])? << 4 | hex_to_u8(hex[1])?; - } - - Ok(bytes) -} - -pub fn hex_to_vec_bytes(hex: &str) -> Result, IronfishError> { - if hex.len() % 2 != 0 { - return Err(IronfishError::new(IronfishErrorKind::InvalidData)); - } - - let mut bytes = Vec::new(); - - let hex_iter = hex.as_bytes().chunks_exact(2); - - for hex in hex_iter { - bytes.push(hex_to_u8(hex[0])? << 4 | hex_to_u8(hex[1])?); - } - - Ok(bytes) -} - -#[inline] -fn hex_to_u8(char: u8) -> Result { - match char { - b'0'..=b'9' => Ok(char - b'0'), - b'a'..=b'f' => Ok(char - b'a' + 10), - b'A'..=b'F' => Ok(char - b'A' + 10), - _ => Err(IronfishError::new(IronfishErrorKind::InvalidData)), - } -} - -#[cfg(test)] -mod test { - use crate::serializing::{bytes_to_hex, hex_to_bytes, hex_to_vec_bytes}; - - #[test] - fn test_hex_to_vec_bytes_valid() { - let hex = "A1B2C3"; - let expected_bytes = vec![161, 178, 195]; - - let result = hex_to_vec_bytes(hex).expect("valid hex"); - - assert_eq!(result, expected_bytes); - } - - #[test] - fn test_hex_to_vec_bytes_invalid_char() { - let hex = "A1B2G3"; - hex_to_vec_bytes(hex).expect_err("invalid hex should throw an error"); - } - - #[test] - fn test_hex_to_vec_bytes_invalid_hex_with_odd_length() { - let hex = "A1B2C"; - hex_to_vec_bytes(hex).expect_err("invalid hex should throw an error"); - } - - #[test] - fn hex_serde() { - const HEX_STRING: &str = "68656C6C6F20776F726C6420616E64207374756666"; - const HEX_LOWER: &str = "68656c6c6f20776f726c6420616e64207374756666"; - const BYTE_LENGTH: usize = HEX_STRING.len() / 2; - // Same as above with the last character removed, which makes the hex - // invalid as the length of a hex string must be divisible by 2 - const INVALID_HEX: &str = "68656C6C6F20776F726C6420616E6420737475666"; - - hex_to_bytes::(INVALID_HEX).expect_err("invalid hex should throw an error"); - - let bytes: [u8; BYTE_LENGTH] = hex_to_bytes(HEX_STRING).expect("converts hex to bytes"); - let lower_bytes: [u8; BYTE_LENGTH] = - hex_to_bytes(HEX_STRING).expect("converts hex to bytes"); - - assert_eq!(bytes, lower_bytes); - - let hex = bytes_to_hex(&bytes); - let lower_hex = bytes_to_hex(&lower_bytes); - - assert_eq!(HEX_LOWER, hex); - assert_eq!(HEX_LOWER, lower_hex); - } -} diff --git a/ironfish-zkp/src/hex.rs b/ironfish-zkp/src/hex.rs new file mode 100644 index 0000000000..14fbc34209 --- /dev/null +++ b/ironfish-zkp/src/hex.rs @@ -0,0 +1,128 @@ +use std::fmt; + +const HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; + +#[derive(Debug)] +pub enum HexError { + ByteLengthMismatch, + InvalidCharacter, + OddStringLength, +} + +impl fmt::Display for HexError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HexError::ByteLengthMismatch => write!(f, "Byte length mismatch"), + HexError::InvalidCharacter => write!(f, "Invalid character"), + HexError::OddStringLength => write!(f, "Odd string length"), + } + } +} + +impl std::error::Error for HexError {} + +/// Output the bytes as a hexadecimal String +pub fn bytes_to_hex(bytes: &[u8]) -> String { + let mut hex: Vec = vec![0; bytes.len() * 2]; + + for (i, b) in bytes.iter().enumerate() { + hex[i * 2] = HEX_CHARS[(b >> 4) as usize]; + hex[i * 2 + 1] = HEX_CHARS[(b & 0x0f) as usize]; + } + + unsafe { String::from_utf8_unchecked(hex) } +} + +/// Output the hexadecimal String as bytes +pub fn hex_to_bytes(hex: &str) -> Result<[u8; SIZE], HexError> { + if hex.len() != SIZE * 2 { + return Err(HexError::ByteLengthMismatch); + } + + let mut bytes = [0; SIZE]; + + let hex_iter = hex.as_bytes().chunks_exact(2); + + for (i, hex) in hex_iter.enumerate() { + bytes[i] = hex_to_u8(hex[0])? << 4 | hex_to_u8(hex[1])?; + } + + Ok(bytes) +} + +pub fn hex_to_vec_bytes(hex: &str) -> Result, HexError> { + if hex.len() % 2 != 0 { + return Err(HexError::OddStringLength); + } + + let mut bytes = Vec::new(); + + let hex_iter = hex.as_bytes().chunks_exact(2); + + for hex in hex_iter { + bytes.push(hex_to_u8(hex[0])? << 4 | hex_to_u8(hex[1])?); + } + + Ok(bytes) +} + +#[inline] +fn hex_to_u8(char: u8) -> Result { + match char { + b'0'..=b'9' => Ok(char - b'0'), + b'a'..=b'f' => Ok(char - b'a' + 10), + b'A'..=b'F' => Ok(char - b'A' + 10), + _ => Err(HexError::InvalidCharacter), + } +} + +#[cfg(test)] +mod test { + use crate::hex::{bytes_to_hex, hex_to_bytes, hex_to_vec_bytes}; + + #[test] + fn test_hex_to_vec_bytes_valid() { + let hex = "A1B2C3"; + let expected_bytes = vec![161, 178, 195]; + + let result = hex_to_vec_bytes(hex).expect("valid hex"); + + assert_eq!(result, expected_bytes); + } + + #[test] + fn test_hex_to_vec_bytes_invalid_char() { + let hex = "A1B2G3"; + hex_to_vec_bytes(hex).expect_err("invalid hex should throw an error"); + } + + #[test] + fn test_hex_to_vec_bytes_invalid_hex_with_odd_length() { + let hex = "A1B2C"; + hex_to_vec_bytes(hex).expect_err("invalid hex should throw an error"); + } + + #[test] + fn hex_serde() { + const HEX_STRING: &str = "68656C6C6F20776F726C6420616E64207374756666"; + const HEX_LOWER: &str = "68656c6c6f20776f726c6420616e64207374756666"; + const BYTE_LENGTH: usize = HEX_STRING.len() / 2; + // Same as above with the last character removed, which makes the hex + // invalid as the length of a hex string must be divisible by 2 + const INVALID_HEX: &str = "68656C6C6F20776F726C6420616E6420737475666"; + + hex_to_bytes::(INVALID_HEX).expect_err("invalid hex should throw an error"); + + let bytes: [u8; BYTE_LENGTH] = hex_to_bytes(HEX_STRING).expect("converts hex to bytes"); + let lower_bytes: [u8; BYTE_LENGTH] = + hex_to_bytes(HEX_STRING).expect("converts hex to bytes"); + + assert_eq!(bytes, lower_bytes); + + let hex = bytes_to_hex(&bytes); + let lower_hex = bytes_to_hex(&lower_bytes); + + assert_eq!(HEX_LOWER, hex); + assert_eq!(HEX_LOWER, lower_hex); + } +} diff --git a/ironfish-zkp/src/lib.rs b/ironfish-zkp/src/lib.rs index 6b68d662f6..3ea6dd5b85 100644 --- a/ironfish-zkp/src/lib.rs +++ b/ironfish-zkp/src/lib.rs @@ -1,5 +1,6 @@ mod circuits; pub mod constants; +pub mod hex; pub mod primitives; pub mod util; From ab452372510a58801363422799746e31061a73cf Mon Sep 17 00:00:00 2001 From: jowparks Date: Fri, 18 Oct 2024 13:37:14 -0700 Subject: [PATCH 28/81] move proof generation key extension to ironfish-zkp (#5559) --- ironfish-rust/src/keys/mod.rs | 8 +- .../src/keys/proof_generation_key.rs | 170 --------------- ironfish-rust/src/transaction/mod.rs | 6 +- ironfish-zkp/src/circuits/mint_asset.rs | 17 +- ironfish-zkp/src/circuits/output.rs | 6 +- ironfish-zkp/src/circuits/spend.rs | 26 +-- ironfish-zkp/src/lib.rs | 3 +- ironfish-zkp/src/primitives/mod.rs | 1 + .../src/primitives/proof_generation_key.rs | 204 ++++++++++++++++++ 9 files changed, 237 insertions(+), 204 deletions(-) delete mode 100644 ironfish-rust/src/keys/proof_generation_key.rs create mode 100644 ironfish-zkp/src/primitives/proof_generation_key.rs diff --git a/ironfish-rust/src/keys/mod.rs b/ironfish-rust/src/keys/mod.rs index 9e4313cda6..24287cf681 100644 --- a/ironfish-rust/src/keys/mod.rs +++ b/ironfish-rust/src/keys/mod.rs @@ -13,6 +13,7 @@ use group::GroupEncoding; use ironfish_zkp::constants::{ CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR, }; +pub use ironfish_zkp::ProofGenerationKey; use jubjub::SubgroupPoint; use rand::prelude::*; @@ -26,8 +27,6 @@ mod view_keys; pub use view_keys::*; mod util; pub use util::*; -pub mod proof_generation_key; -pub use proof_generation_key::*; #[cfg(test)] mod test; @@ -210,10 +209,7 @@ impl SaplingKey { /// Adapter to convert this key to a proof generation key for use in /// sapling functions pub fn sapling_proof_generation_key(&self) -> ProofGenerationKey { - ProofGenerationKey { - ak: self.view_key.authorizing_key, - nsk: self.proof_authorizing_key, - } + ProofGenerationKey::new(self.view_key.authorizing_key, self.proof_authorizing_key) } /// Convert the spending key to another value using a pseudorandom hash diff --git a/ironfish-rust/src/keys/proof_generation_key.rs b/ironfish-rust/src/keys/proof_generation_key.rs deleted file mode 100644 index ac19cf0119..0000000000 --- a/ironfish-rust/src/keys/proof_generation_key.rs +++ /dev/null @@ -1,170 +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/. */ - -use group::GroupEncoding; -pub use ironfish_zkp::ProofGenerationKey; -use jubjub::{Fr, SubgroupPoint}; - -use crate::{ - errors::{IronfishError, IronfishErrorKind}, - serializing::{bytes_to_hex, hex_to_bytes}, -}; - -pub trait ProofGenerationKeySerializable { - fn serialize(&self) -> [u8; 64]; - fn deserialize(bytes: [u8; 64]) -> Result; - fn hex_key(&self) -> String; - fn from_hex(hex_key: &str) -> Result; -} - -impl ProofGenerationKeySerializable for ProofGenerationKey { - fn serialize(&self) -> [u8; 64] { - let mut proof_generation_key_bytes: [u8; 64] = [0; 64]; - proof_generation_key_bytes[0..32].copy_from_slice(&self.ak.to_bytes()); - proof_generation_key_bytes[32..].copy_from_slice(&self.nsk.to_bytes()); - proof_generation_key_bytes - } - - fn deserialize(proof_generation_key_bytes: [u8; 64]) -> Result { - let mut ak_bytes: [u8; 32] = [0; 32]; - let mut nsk_bytes: [u8; 32] = [0; 32]; - - ak_bytes[0..32].copy_from_slice(&proof_generation_key_bytes[0..32]); - nsk_bytes[0..32].copy_from_slice(&proof_generation_key_bytes[32..64]); - - let ak = match SubgroupPoint::from_bytes(&ak_bytes).into() { - Some(ak) => ak, - None => return Err(IronfishError::new(IronfishErrorKind::InvalidAuthorizingKey)), - }; - - let nsk = match Fr::from_bytes(&nsk_bytes).into() { - Some(nsk) => nsk, - None => { - return Err(IronfishError::new( - IronfishErrorKind::InvalidNullifierDerivingKey, - )) - } - }; - - Ok(ProofGenerationKey { ak, nsk }) - } - - fn hex_key(&self) -> String { - let serialized_bytes = self.serialize(); - bytes_to_hex(&serialized_bytes[..]) - } - - fn from_hex(hex_key: &str) -> Result { - let bytes = hex_to_bytes(hex_key)?; - ProofGenerationKey::deserialize(bytes) - } -} - -#[cfg(test)] -mod test { - use crate::errors::IronfishErrorKind; - use ff::Field; - use group::{Group, GroupEncoding}; - use ironfish_zkp::ProofGenerationKey; - - use super::ProofGenerationKeySerializable; - use jubjub; - use rand::{rngs::StdRng, SeedableRng}; - - #[test] - fn test_serialize() { - let mut rng = StdRng::seed_from_u64(0); - - let proof_generation_key = ProofGenerationKey { - ak: jubjub::SubgroupPoint::random(&mut rng), - nsk: jubjub::Fr::random(&mut rng), - }; - - let serialized_bytes = proof_generation_key.serialize(); - - assert_eq!(serialized_bytes.len(), 64); - } - - #[test] - fn test_deserialize_ak_error() { - let mut proof_generation_key_bytes: [u8; 64] = [0; 64]; - proof_generation_key_bytes[0..32].fill(0xFF); - - let result = ProofGenerationKey::deserialize(proof_generation_key_bytes); - - assert!(result.is_err()); - - let err = result.err().unwrap(); - - assert!(matches!(err.kind, IronfishErrorKind::InvalidAuthorizingKey)); - } - - #[test] - fn test_deserialize_nsk_error() { - let mut proof_generation_key_bytes: [u8; 64] = [0; 64]; - // Populate with valid bytes for ak and invalid bytes for nsk - let valid_ak = jubjub::SubgroupPoint::random(&mut StdRng::seed_from_u64(0)); - proof_generation_key_bytes[0..32].copy_from_slice(&valid_ak.to_bytes()); // Assuming these are valid bytes for ak - proof_generation_key_bytes[32..64].fill(0xFF); // Invalid bytes for nsk - - let result = ProofGenerationKey::deserialize(proof_generation_key_bytes); - - assert!(result.is_err()); - - let err = result.err().unwrap(); - - assert!(matches!( - err.kind, - IronfishErrorKind::InvalidNullifierDerivingKey - )); - } - - #[test] - fn test_deserialize() { - let mut rng = StdRng::seed_from_u64(0); - - let proof_generation_key = ProofGenerationKey { - ak: jubjub::SubgroupPoint::random(&mut rng), - nsk: jubjub::Fr::random(&mut rng), - }; - - let serialized_bytes = proof_generation_key.serialize(); - - let deserialized_proof_generation_key = - ProofGenerationKey::deserialize(serialized_bytes).expect("deserialization successful"); - - assert_eq!( - proof_generation_key.ak, - deserialized_proof_generation_key.ak - ); - assert_eq!( - proof_generation_key.nsk, - deserialized_proof_generation_key.nsk - ); - } - - #[test] - fn test_hex() { - let mut rng = StdRng::seed_from_u64(0); - - let proof_generation_key = ProofGenerationKey { - ak: jubjub::SubgroupPoint::random(&mut rng), - nsk: jubjub::Fr::random(&mut rng), - }; - - let hex_key = proof_generation_key.hex_key(); - - let deserialized_proof_generation_key = - ProofGenerationKey::from_hex(&hex_key).expect("deserialization successful"); - - assert_eq!( - proof_generation_key.ak, - deserialized_proof_generation_key.ak - ); - assert_eq!( - proof_generation_key.nsk, - deserialized_proof_generation_key.nsk - ); - } -} diff --git a/ironfish-rust/src/transaction/mod.rs b/ironfish-rust/src/transaction/mod.rs index dbbab6a8e8..dff9699a2a 100644 --- a/ironfish-rust/src/transaction/mod.rs +++ b/ironfish-rust/src/transaction/mod.rs @@ -239,10 +239,8 @@ impl ProposedTransaction { ) -> Result { let public_address = view_key.public_address()?; - let proof_generation_key = ProofGenerationKey { - ak: view_key.authorizing_key, - nsk: proof_authorizing_key, - }; + let proof_generation_key = + ProofGenerationKey::new(view_key.authorizing_key, proof_authorizing_key); // skip adding change notes if this is special case of a miners fee transaction let is_miners_fee = self.outputs.iter().any(|output| output.get_is_miners_fee()); diff --git a/ironfish-zkp/src/circuits/mint_asset.rs b/ironfish-zkp/src/circuits/mint_asset.rs index 92e1a23b13..f9f9de6fc3 100644 --- a/ironfish-zkp/src/circuits/mint_asset.rs +++ b/ironfish-zkp/src/circuits/mint_asset.rs @@ -3,13 +3,15 @@ use bellperson::{ Circuit, }; use ff::PrimeField; -use zcash_primitives::sapling::ProofGenerationKey; use zcash_proofs::{ circuit::ecc, constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, }; -use crate::constants::{proof::PUBLIC_KEY_GENERATOR, CRH_IVK_PERSONALIZATION}; +use crate::{ + constants::{proof::PUBLIC_KEY_GENERATOR, CRH_IVK_PERSONALIZATION}, + ProofGenerationKey, +}; pub struct MintAsset { /// Key required to construct proofs for a particular spending key @@ -122,9 +124,8 @@ mod test { use group::{Curve, Group}; use jubjub::ExtendedPoint; use rand::{rngs::StdRng, SeedableRng}; - use zcash_primitives::sapling::ProofGenerationKey; - use crate::constants::PUBLIC_KEY_GENERATOR; + use crate::{constants::PUBLIC_KEY_GENERATOR, ProofGenerationKey}; use super::MintAsset; @@ -135,10 +136,10 @@ mod test { let mut cs = TestConstraintSystem::new(); - let proof_generation_key = ProofGenerationKey { - ak: jubjub::SubgroupPoint::random(&mut rng), - nsk: jubjub::Fr::random(&mut rng), - }; + let proof_generation_key = ProofGenerationKey::new( + jubjub::SubgroupPoint::random(&mut rng), + jubjub::Fr::random(&mut rng), + ); let incoming_view_key = proof_generation_key.to_viewing_key(); let public_address = *PUBLIC_KEY_GENERATOR * incoming_view_key.ivk().0; let public_address_point = ExtendedPoint::from(public_address).to_affine(); diff --git a/ironfish-zkp/src/circuits/output.rs b/ironfish-zkp/src/circuits/output.rs index 527d1aa88c..d7bb97760a 100644 --- a/ironfish-zkp/src/circuits/output.rs +++ b/ironfish-zkp/src/circuits/output.rs @@ -5,7 +5,6 @@ use bellperson::{gadgets::blake2s, Circuit, ConstraintSystem, SynthesisError}; use group::Curve; use jubjub::SubgroupPoint; -use zcash_primitives::sapling::ProofGenerationKey; use zcash_proofs::{ circuit::{ecc, pedersen_hash}, constants::{ @@ -18,6 +17,7 @@ use crate::{ circuits::util::assert_valid_asset_generator, constants::{proof::PUBLIC_KEY_GENERATOR, ASSET_ID_LENGTH, CRH_IVK_PERSONALIZATION}, primitives::ValueCommitment, + ProofGenerationKey, }; use super::util::expose_value_commitment; @@ -260,9 +260,9 @@ mod test { use group::{Curve, Group}; use rand::rngs::StdRng; use rand::{Rng, RngCore, SeedableRng}; - use zcash_primitives::sapling::ProofGenerationKey; use crate::util::asset_hash_to_point; + use crate::ProofGenerationKey; use crate::{ circuits::output::Output, constants::PUBLIC_KEY_GENERATOR, primitives::ValueCommitment, util::commitment_full_point, @@ -296,7 +296,7 @@ mod test { let esk = jubjub::Fr::random(&mut rng); let ar = jubjub::Fr::random(&mut rng); - let proof_generation_key = ProofGenerationKey { ak, nsk }; + let proof_generation_key = ProofGenerationKey::new(ak, nsk); let viewing_key = proof_generation_key.to_viewing_key(); diff --git a/ironfish-zkp/src/circuits/spend.rs b/ironfish-zkp/src/circuits/spend.rs index 7546711dad..82065ecec6 100644 --- a/ironfish-zkp/src/circuits/spend.rs +++ b/ironfish-zkp/src/circuits/spend.rs @@ -3,6 +3,7 @@ use ff::{Field, PrimeField}; use jubjub::SubgroupPoint; use crate::constants::{CRH_IVK_PERSONALIZATION, PRF_NF_PERSONALIZATION}; +use crate::ProofGenerationKey; use crate::{constants::proof::PUBLIC_KEY_GENERATOR, primitives::ValueCommitment}; use super::util::expose_value_commitment; @@ -11,7 +12,6 @@ use bellperson::gadgets::boolean; use bellperson::gadgets::multipack; use bellperson::gadgets::num; use bellperson::gadgets::Assignment; -use zcash_primitives::sapling::ProofGenerationKey; use zcash_proofs::{ circuit::{ecc, pedersen_hash}, constants::{ @@ -342,15 +342,17 @@ mod test { use ff::{Field, PrimeField, PrimeFieldBits}; use group::{Curve, Group, GroupEncoding}; use rand::{rngs::StdRng, RngCore, SeedableRng}; - use zcash_primitives::sapling::{pedersen_hash, Note, ProofGenerationKey, Rseed}; + use zcash_primitives::sapling::{pedersen_hash, Note, Rseed}; use zcash_primitives::{constants::NULLIFIER_POSITION_GENERATOR, sapling::Nullifier}; use crate::{ circuits::spend::Spend, - constants::PUBLIC_KEY_GENERATOR, - constants::{PRF_NF_PERSONALIZATION, VALUE_COMMITMENT_VALUE_GENERATOR}, + constants::{ + PRF_NF_PERSONALIZATION, PUBLIC_KEY_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR, + }, primitives::ValueCommitment, util::commitment_full_point, + ProofGenerationKey, }; #[test] @@ -367,10 +369,10 @@ mod test { asset_generator: (*VALUE_COMMITMENT_VALUE_GENERATOR).into(), }; - let proof_generation_key = ProofGenerationKey { - ak: jubjub::SubgroupPoint::random(&mut rng), - nsk: jubjub::Fr::random(&mut rng), - }; + let proof_generation_key = ProofGenerationKey::new( + jubjub::SubgroupPoint::random(&mut rng), + jubjub::Fr::random(&mut rng), + ); let viewing_key = proof_generation_key.to_viewing_key(); @@ -524,10 +526,10 @@ mod test { asset_generator: (*VALUE_COMMITMENT_VALUE_GENERATOR).into(), }; - let proof_generation_key = ProofGenerationKey { - ak: jubjub::SubgroupPoint::random(&mut rng), - nsk: jubjub::Fr::random(&mut rng), - }; + let proof_generation_key = ProofGenerationKey::new( + jubjub::SubgroupPoint::random(&mut rng), + jubjub::Fr::random(&mut rng), + ); let viewing_key = proof_generation_key.to_viewing_key(); diff --git a/ironfish-zkp/src/lib.rs b/ironfish-zkp/src/lib.rs index 3ea6dd5b85..b0ddf82de1 100644 --- a/ironfish-zkp/src/lib.rs +++ b/ironfish-zkp/src/lib.rs @@ -6,9 +6,10 @@ pub mod util; pub use zcash_primitives::sapling::{ group_hash::group_hash, pedersen_hash, redjubjub, Diversifier, Note as SaplingNote, Nullifier, - PaymentAddress, ProofGenerationKey, Rseed, ViewingKey, + PaymentAddress, Rseed, ViewingKey, }; +pub use primitives::proof_generation_key::ProofGenerationKey; pub mod proofs { pub use crate::circuits::mint_asset::MintAsset; pub use crate::circuits::{output::Output, spend::Spend}; diff --git a/ironfish-zkp/src/primitives/mod.rs b/ironfish-zkp/src/primitives/mod.rs index 1c9e55b29d..49c82e57d1 100644 --- a/ironfish-zkp/src/primitives/mod.rs +++ b/ironfish-zkp/src/primitives/mod.rs @@ -1,2 +1,3 @@ +pub mod proof_generation_key; mod value_commitment; pub use value_commitment::ValueCommitment; diff --git a/ironfish-zkp/src/primitives/proof_generation_key.rs b/ironfish-zkp/src/primitives/proof_generation_key.rs new file mode 100644 index 0000000000..6df8350751 --- /dev/null +++ b/ironfish-zkp/src/primitives/proof_generation_key.rs @@ -0,0 +1,204 @@ +use group::GroupEncoding; +use jubjub::{Fr, SubgroupPoint}; +use std::error::Error; +use std::fmt; +use std::ops::Deref; +use zcash_primitives::sapling::ProofGenerationKey as ZcashProofGenerationKey; + +use crate::hex::{bytes_to_hex, hex_to_bytes}; + +#[derive(Debug)] +pub enum ProofGenerationKeyError { + InvalidAuthorizingKey, + InvalidLength, + InvalidNullifierDerivingKey, + HexConversionError, +} + +impl fmt::Display for ProofGenerationKeyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProofGenerationKeyError::InvalidAuthorizingKey => write!(f, "Invalid authorizing key"), + ProofGenerationKeyError::InvalidNullifierDerivingKey => { + write!(f, "Invalid nullifier deriving key") + } + ProofGenerationKeyError::HexConversionError => write!(f, "Hex conversion error"), + ProofGenerationKeyError::InvalidLength => write!(f, "Invalid length"), + } + } +} + +impl Error for ProofGenerationKeyError {} + +#[derive(Clone)] +pub struct ProofGenerationKey(ZcashProofGenerationKey); + +impl ProofGenerationKey { + pub fn new(ak: SubgroupPoint, nsk: Fr) -> Self { + ProofGenerationKey(ZcashProofGenerationKey { ak, nsk }) + } + + pub fn to_bytes(&self) -> [u8; 64] { + let mut proof_generation_key_bytes: [u8; 64] = [0; 64]; + proof_generation_key_bytes[0..32].copy_from_slice(&self.0.ak.to_bytes()); + proof_generation_key_bytes[32..].copy_from_slice(&self.0.nsk.to_bytes()); + proof_generation_key_bytes + } + + pub fn from_bytes(proof_generation_key_bytes: &[u8]) -> Result { + if proof_generation_key_bytes.len() != 64 { + return Err(ProofGenerationKeyError::InvalidLength); + } + let mut ak_bytes: [u8; 32] = [0; 32]; + let mut nsk_bytes: [u8; 32] = [0; 32]; + + ak_bytes[0..32].copy_from_slice(&proof_generation_key_bytes[0..32]); + nsk_bytes[0..32].copy_from_slice(&proof_generation_key_bytes[32..64]); + + let ak = match SubgroupPoint::from_bytes(&ak_bytes).into() { + Some(ak) => ak, + None => return Err(ProofGenerationKeyError::InvalidAuthorizingKey), + }; + + let nsk = match Fr::from_bytes(&nsk_bytes).into() { + Some(nsk) => nsk, + None => return Err(ProofGenerationKeyError::InvalidNullifierDerivingKey), + }; + + Ok(ProofGenerationKey(ZcashProofGenerationKey { ak, nsk })) + } + + pub fn hex_key(&self) -> String { + bytes_to_hex(&self.to_bytes()) + } + + pub fn from_hex(hex_key: &str) -> Result { + let bytes: [u8; 64] = + hex_to_bytes(hex_key).map_err(|_| ProofGenerationKeyError::HexConversionError)?; + ProofGenerationKey::from_bytes(&bytes) + } +} + +impl Deref for ProofGenerationKey { + type Target = ZcashProofGenerationKey; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for ProofGenerationKey { + fn from(key: ZcashProofGenerationKey) -> Self { + ProofGenerationKey(key) + } +} + +#[cfg(test)] +mod test { + use ff::Field; + use group::{Group, GroupEncoding}; + + use jubjub; + use rand::{rngs::StdRng, SeedableRng}; + + use crate::primitives::proof_generation_key::{ProofGenerationKey, ProofGenerationKeyError}; + + #[test] + fn test_serialize() { + let mut rng = StdRng::seed_from_u64(0); + + let proof_generation_key = ProofGenerationKey::new( + jubjub::SubgroupPoint::random(&mut rng), + jubjub::Fr::random(&mut rng), + ); + + let serialized_bytes = proof_generation_key.to_bytes(); + + assert_eq!(serialized_bytes.len(), 64); + } + + #[test] + fn test_deserialize_ak_error() { + let mut proof_generation_key_bytes: [u8; 64] = [0; 64]; + proof_generation_key_bytes[0..32].fill(0xFF); + + let result = ProofGenerationKey::from_bytes(&proof_generation_key_bytes); + + assert!(result.is_err()); + + let err = result.err().unwrap(); + + assert!(matches!( + err, + ProofGenerationKeyError::InvalidAuthorizingKey + )); + } + + #[test] + fn test_deserialize_nsk_error() { + let mut proof_generation_key_bytes: [u8; 64] = [0; 64]; + // Populate with valid bytes for ak and invalid bytes for nsk + let valid_ak = jubjub::SubgroupPoint::random(&mut StdRng::seed_from_u64(0)); + proof_generation_key_bytes[0..32].copy_from_slice(&valid_ak.to_bytes()); // Assuming these are valid bytes for ak + proof_generation_key_bytes[32..64].fill(0xFF); // Invalid bytes for nsk + + let result = ProofGenerationKey::from_bytes(&proof_generation_key_bytes); + + assert!(result.is_err()); + + let err = result.err().unwrap(); + + assert!(matches!( + err, + ProofGenerationKeyError::InvalidNullifierDerivingKey + )); + } + + #[test] + fn test_deserialize() { + let mut rng = StdRng::seed_from_u64(0); + + let proof_generation_key = ProofGenerationKey::new( + jubjub::SubgroupPoint::random(&mut rng), + jubjub::Fr::random(&mut rng), + ); + + let serialized_bytes = proof_generation_key.to_bytes(); + + let deserialized_proof_generation_key = + ProofGenerationKey::from_bytes(&serialized_bytes).expect("deserialization successful"); + + assert_eq!( + proof_generation_key.ak, + deserialized_proof_generation_key.ak + ); + assert_eq!( + proof_generation_key.nsk, + deserialized_proof_generation_key.nsk + ); + } + + #[test] + fn test_hex() { + let mut rng = StdRng::seed_from_u64(0); + + let proof_generation_key = ProofGenerationKey::new( + jubjub::SubgroupPoint::random(&mut rng), + jubjub::Fr::random(&mut rng), + ); + + let hex_key = proof_generation_key.hex_key(); + + let deserialized_proof_generation_key = + ProofGenerationKey::from_hex(&hex_key).expect("deserialization successful"); + + assert_eq!( + proof_generation_key.ak, + deserialized_proof_generation_key.ak + ); + assert_eq!( + proof_generation_key.nsk, + deserialized_proof_generation_key.nsk + ); + } +} From 1c66573cb9a63f390be7d7262696f8cb29042157 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:24:53 -0700 Subject: [PATCH 29/81] handles reconnect/rejoin status in session managers (#5564) adds 'onDisconnected' event emitter to multisig broker client uses 'onDisconnected' events in sessionManager to wait from confirmation that client has reconnected to server and rejoined session by using 'waitForConnectedMessage' and 'waitForJoinedSession' we display ux status output to the user _and_ throw an error if the session can't be rejoined updates session managers to conditionally start ux actions in polling loops. this makes it so that the session manager won't clear the ux status output from waiting to reconnect or rejoin a session while polling for session status --- ironfish-cli/src/multisigBroker/clients/client.ts | 3 +++ .../sessionManagers/dkgSessionManager.ts | 12 +++++++++--- .../sessionManagers/sessionManager.ts | 13 +++++++++++-- .../sessionManagers/signingSessionManager.ts | 12 +++++++++--- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts index bf4c2dd7b7..750ae2767b 100644 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ b/ironfish-cli/src/multisigBroker/clients/client.ts @@ -59,6 +59,7 @@ export abstract class MultisigClient { private tryConnectUntil: number | null = null readonly onConnected = new Event<[]>() + readonly onDisconnected = new Event<[]>() readonly onDkgStatus = new Event<[DkgStatusMessage]>() readonly onSigningStatus = new Event<[SigningStatusMessage]>() readonly onConnectedMessage = new Event<[ConnectedMessage]>() @@ -288,6 +289,8 @@ export abstract class MultisigClient { this.logger.warn('Disconnected from server unexpectedly. Reconnecting.') this.connectTimeout = setTimeout(() => void this.startConnecting(), 5000) } + + this.onDisconnected.emit() } protected onError = (error: unknown): void => { diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts index 008e115c44..83c94bd9cd 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts @@ -143,9 +143,11 @@ export class MultisigClientDkgSessionManager identities = message.identities }) - ux.action.start('Waiting for Identities from server') while (identities.length < totalParticipants) { this.client.getDkgStatus() + if (!ux.action.running) { + ux.action.start('Waiting for Identities from server') + } ux.action.status = `${identities.length}/${totalParticipants}` await PromiseUtils.sleep(3000) } @@ -169,9 +171,11 @@ export class MultisigClientDkgSessionManager round1PublicPackages = message.round1PublicPackages }) - ux.action.start('Waiting for Round 1 Public Packages from server') while (round1PublicPackages.length < totalParticipants) { this.client.getDkgStatus() + if (!ux.action.running) { + ux.action.start('Waiting for Round 1 Public Packages from server') + } ux.action.status = `${round1PublicPackages.length}/${totalParticipants}` await PromiseUtils.sleep(3000) } @@ -195,9 +199,11 @@ export class MultisigClientDkgSessionManager round2PublicPackages = message.round2PublicPackages }) - ux.action.start('Waiting for Round 2 Public Packages from server') while (round2PublicPackages.length < totalParticipants) { this.client.getDkgStatus() + if (!ux.action.running) { + ux.action.start('Waiting for Round 2 Public Packages from server') + } ux.action.status = `${round2PublicPackages.length}/${totalParticipants}` await PromiseUtils.sleep(3000) } diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index cf6b3b9f69..4a3c450008 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -58,13 +58,22 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage return } + this.client.start() + + await this.waitForConnectedMessage() + + this.client.onDisconnected.on(async () => { + await this.waitForConnectedMessage() + await this.waitForJoinedSession() + }) + } + + protected async waitForConnectedMessage(): Promise { let confirmed = false ux.action.start( `Connecting to multisig broker server: ${this.client.hostname}:${this.client.port}`, ) - this.client.start() - this.client.onConnectedMessage.on(() => { confirmed = true }) diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts index 0add8079ef..8551c45c03 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts @@ -144,9 +144,11 @@ export class MultisigClientSigningSessionManager identities = message.identities }) - ux.action.start('Waiting for Identities from server') while (identities.length < numSigners) { this.client.getSigningStatus() + if (!ux.action.running) { + ux.action.start('Waiting for Identities from server') + } ux.action.status = `${identities.length}/${numSigners}` await PromiseUtils.sleep(3000) } @@ -171,9 +173,11 @@ export class MultisigClientSigningSessionManager signingCommitments = message.signingCommitments }) - ux.action.start('Waiting for Signing Commitments from server') while (signingCommitments.length < numSigners) { this.client.getSigningStatus() + if (!ux.action.running) { + ux.action.start('Waiting for Signing Commitments from server') + } ux.action.status = `${signingCommitments.length}/${numSigners}` await PromiseUtils.sleep(3000) } @@ -198,9 +202,11 @@ export class MultisigClientSigningSessionManager signatureShares = message.signatureShares }) - ux.action.start('Waiting for Signature Shares from server') while (signatureShares.length < numSigners) { this.client.getSigningStatus() + if (!ux.action.running) { + ux.action.start('Waiting for Signature Shares from server') + } ux.action.status = `${signatureShares.length}/${numSigners}` await PromiseUtils.sleep(3000) } From d0336f046a293b040439963b6493ae3aec71d4ae Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:49:01 -0700 Subject: [PATCH 30/81] use github action for uploading codecov (#5571) We were getting intermittent issues using the original codecov upload process, which has been deprecated for a few years now. The coverage also has been broken for some unknown amount of time. Using the github action is the officially recommended way to upload to codecov now. --- .github/workflows/ci.yml | 13 +++++++++++-- .github/workflows/rust_ci.yml | 4 ++-- package.json | 3 +-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c844928378..f3fbd3e3fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,12 @@ jobs: - name: Upload coverage if: github.repository == 'iron-fish/ironfish' - run: CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} ROOT_PATH=$GITHUB_WORKSPACE/ yarn coverage:upload + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + flags: ironfish + testslow: name: Slow Tests @@ -111,4 +116,8 @@ jobs: - name: Upload coverage if: github.repository == 'iron-fish/ironfish' - run: CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} ROOT_PATH=$GITHUB_WORKSPACE/ yarn coverage:upload + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + flags: ironfish diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index ec1afda9e3..8e1e52ab0e 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -105,7 +105,7 @@ jobs: # Upload code coverage to Codecov - name: Upload to codecov.io - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{secrets.CODECOV_TOKEN}} flags: ironfish-rust @@ -131,7 +131,7 @@ jobs: # Upload code coverage to Codecov - name: Upload to codecov.io - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{secrets.CODECOV_TOKEN}} flags: ironfish-zkp diff --git a/package.json b/package.json index 1693b57fd7..fc94207240 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,7 @@ "test:slow:coverage": "lerna run test:slow --stream -- --testPathIgnorePatterns --collect-coverage", "test:perf:report": "lerna run test:perf:report", "typecheck": "lerna exec -- tsc --noEmit", - "typecheck:changed": "lerna exec --since origin/master --include-dependents -- tsc --noEmit", - "coverage:upload": "lerna exec '\"yarn codecov -t $CODECOV_TOKEN -f ./coverage/clover.xml -F $LERNA_PACKAGE_NAME -p $ROOT_PATH/ --disable=gcov\"'" + "typecheck:changed": "lerna exec --since origin/master --include-dependents -- tsc --noEmit" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "6.19.0", From 1060c3bbb00690b6f4982dd9f051c7e14668be07 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:16:06 -0700 Subject: [PATCH 31/81] clears onDisconnected event handler when ending session (#5573) the handler that we add to the MultisigClient.onDisconnected event assumes that the client will try to reconnect and rejoin a session after a disconnect. however, if the session is ended, the client will not try to reconnect. this results in the CLI displaying an endless action stating that it is waiting to connect after the session ends clears the handler before stopping the client so that the sessionManager won't wait for messages confirming server reconnection and session rejoin --- .../src/multisigBroker/sessionManagers/sessionManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts index 4a3c450008..d3c82fc3c7 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts @@ -137,6 +137,7 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage } endSession(): void { + this.client.onDisconnected.clear() this.client.stop() } From 2a979e36314602d70283b5bd4a2242288ae4b134 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:47:58 -0700 Subject: [PATCH 32/81] replaces multisig broker code with @ironfish/multisig-broker (#5574) adds ironfish-cli dependency on @ironfish/multisig-broker package removes multisigBroker code that was moved to the new package moves sessionManagers to utils/multisig --- ironfish-cli/package.json | 1 + .../commands/wallet/multisig/dkg/create.ts | 4 +- .../src/commands/wallet/multisig/server.ts | 10 +- .../src/commands/wallet/multisig/sign.ts | 4 +- ironfish-cli/src/multisigBroker/README.md | 3 - .../src/multisigBroker/adapters/adapter.ts | 29 - .../src/multisigBroker/adapters/index.ts | 7 - .../src/multisigBroker/adapters/tcpAdapter.ts | 65 -- .../src/multisigBroker/adapters/tlsAdapter.ts | 30 - .../src/multisigBroker/clients/client.ts | 461 ---------- .../src/multisigBroker/clients/index.ts | 7 - .../src/multisigBroker/clients/tcpClient.ts | 65 -- .../src/multisigBroker/clients/tlsClient.ts | 37 - ironfish-cli/src/multisigBroker/errors.ts | 72 -- ironfish-cli/src/multisigBroker/index.ts | 7 - ironfish-cli/src/multisigBroker/messages.ts | 196 ---- ironfish-cli/src/multisigBroker/server.ts | 843 ------------------ .../src/multisigBroker/serverClient.ts | 39 - ironfish-cli/src/multisigBroker/utils.ts | 84 -- ironfish-cli/src/utils/multisig/index.ts | 1 + .../sessionManagers/dkgSessionManager.ts | 2 +- .../multisig}/sessionManagers/index.ts | 0 .../sessionManagers/sessionManager.ts | 10 +- .../sessionManagers/signingSessionManager.ts | 2 +- yarn.lock | 16 + 25 files changed, 34 insertions(+), 1961 deletions(-) delete mode 100644 ironfish-cli/src/multisigBroker/README.md delete mode 100644 ironfish-cli/src/multisigBroker/adapters/adapter.ts delete mode 100644 ironfish-cli/src/multisigBroker/adapters/index.ts delete mode 100644 ironfish-cli/src/multisigBroker/adapters/tcpAdapter.ts delete mode 100644 ironfish-cli/src/multisigBroker/adapters/tlsAdapter.ts delete mode 100644 ironfish-cli/src/multisigBroker/clients/client.ts delete mode 100644 ironfish-cli/src/multisigBroker/clients/index.ts delete mode 100644 ironfish-cli/src/multisigBroker/clients/tcpClient.ts delete mode 100644 ironfish-cli/src/multisigBroker/clients/tlsClient.ts delete mode 100644 ironfish-cli/src/multisigBroker/errors.ts delete mode 100644 ironfish-cli/src/multisigBroker/index.ts delete mode 100644 ironfish-cli/src/multisigBroker/messages.ts delete mode 100644 ironfish-cli/src/multisigBroker/server.ts delete mode 100644 ironfish-cli/src/multisigBroker/serverClient.ts delete mode 100644 ironfish-cli/src/multisigBroker/utils.ts rename ironfish-cli/src/{multisigBroker => utils/multisig}/sessionManagers/dkgSessionManager.ts (99%) rename ironfish-cli/src/{multisigBroker => utils/multisig}/sessionManagers/index.ts (100%) rename ironfish-cli/src/{multisigBroker => utils/multisig}/sessionManagers/sessionManager.ts (97%) rename ironfish-cli/src/{multisigBroker => utils/multisig}/sessionManagers/signingSessionManager.ts (99%) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index 121955e307..be2a6fd850 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -60,6 +60,7 @@ "oclif:version": "oclif readme && git add README.md" }, "dependencies": { + "@ironfish/multisig-broker": "0.1.1", "@ironfish/rust-nodejs": "2.7.0", "@ironfish/sdk": "2.8.1", "@ledgerhq/errors": "6.19.1", diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index ba70a3a150..2610b7c3e9 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -19,11 +19,11 @@ import path from 'path' import { IronfishCommand } from '../../../../command' import { RemoteFlags } from '../../../../flags' import { LedgerMultiSigner } from '../../../../ledger' +import * as ui from '../../../../ui' import { createDkgSessionManager, DkgSessionManager, -} from '../../../../multisigBroker/sessionManagers' -import * as ui from '../../../../ui' +} from '../../../../utils/multisig/sessionManagers' export class DkgCreateCommand extends IronfishCommand { static description = 'Interactive command to create a multisignature account using DKG' diff --git a/ironfish-cli/src/commands/wallet/multisig/server.ts b/ironfish-cli/src/commands/wallet/multisig/server.ts index 1ad674f596..747617b917 100644 --- a/ironfish-cli/src/commands/wallet/multisig/server.ts +++ b/ironfish-cli/src/commands/wallet/multisig/server.ts @@ -2,15 +2,15 @@ * 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 { TlsUtils } from '@ironfish/sdk' -import { Flags } from '@oclif/core' -import { IronfishCommand } from '../../../command' -import { MultisigServer } from '../../../multisigBroker' import { IMultisigBrokerAdapter, + MultisigServer, MultisigTcpAdapter, MultisigTlsAdapter, -} from '../../../multisigBroker/adapters' +} from '@ironfish/multisig-broker' +import { TlsUtils } from '@ironfish/sdk' +import { Flags } from '@oclif/core' +import { IronfishCommand } from '../../../command' export class MultisigServerCommand extends IronfishCommand { static description = 'start a server to broker messages for a multisig session' diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index 45c4d4e9f4..5d2494f798 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -14,12 +14,12 @@ import { Flags, ux } from '@oclif/core' import { IronfishCommand } from '../../../command' import { RemoteFlags } from '../../../flags' import { LedgerMultiSigner } from '../../../ledger' +import * as ui from '../../../ui' import { createSigningSessionManager, MultisigClientSigningSessionManager, SigningSessionManager, -} from '../../../multisigBroker/sessionManagers' -import * as ui from '../../../ui' +} from '../../../utils/multisig/sessionManagers' import { renderUnsignedTransactionDetails, watchTransaction } from '../../../utils/transaction' // todo(patnir): this command does not differentiate between a participant and an account. diff --git a/ironfish-cli/src/multisigBroker/README.md b/ironfish-cli/src/multisigBroker/README.md deleted file mode 100644 index 91f2d80444..0000000000 --- a/ironfish-cli/src/multisigBroker/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Multisig Broker - -This is a server / client that allows multisig participants to broker creating DKG accounts and signing transactions through a trustless server. diff --git a/ironfish-cli/src/multisigBroker/adapters/adapter.ts b/ironfish-cli/src/multisigBroker/adapters/adapter.ts deleted file mode 100644 index 267d6ea528..0000000000 --- a/ironfish-cli/src/multisigBroker/adapters/adapter.ts +++ /dev/null @@ -1,29 +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 { MultisigServer } from '../server' - -/** - * An adapter represents a network transport that accepts connections from - * clients and routes them into the server. - */ -export interface IMultisigBrokerAdapter { - /** - * Called when the adapter is added to a MultisigServer. - */ - attach(server: MultisigServer): void - - /** - * Called when the adapter should start serving requests to the server - * This is when an adapter would normally listen on a port for data and - * create {@link Request } for the routing layer. - * - * For example, when an - * HTTP server starts listening, or an IPC layer opens an IPC socket. - */ - start(): Promise - - /** Called when the adapter should stop serving requests to the server. */ - stop(): Promise -} diff --git a/ironfish-cli/src/multisigBroker/adapters/index.ts b/ironfish-cli/src/multisigBroker/adapters/index.ts deleted file mode 100644 index 436cbdef3f..0000000000 --- a/ironfish-cli/src/multisigBroker/adapters/index.ts +++ /dev/null @@ -1,7 +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/. */ - -export * from './adapter' -export * from './tcpAdapter' -export * from './tlsAdapter' diff --git a/ironfish-cli/src/multisigBroker/adapters/tcpAdapter.ts b/ironfish-cli/src/multisigBroker/adapters/tcpAdapter.ts deleted file mode 100644 index e895ec7d50..0000000000 --- a/ironfish-cli/src/multisigBroker/adapters/tcpAdapter.ts +++ /dev/null @@ -1,65 +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 net from 'net' -import { MultisigServer } from '../server' -import { IMultisigBrokerAdapter } from './adapter' - -export class MultisigTcpAdapter implements IMultisigBrokerAdapter { - server: net.Server | null = null - multisigServer: MultisigServer | null = null - readonly logger: Logger - - readonly host: string - readonly port: number - - started = false - - constructor(options: { logger: Logger; host: string; port: number }) { - this.logger = options.logger - this.host = options.host - this.port = options.port - } - - protected createServer(): net.Server { - this.logger.info(`Hosting Multisig Server via TCP on ${this.host}:${this.port}`) - - return net.createServer((socket) => this.multisigServer?.onConnection(socket)) - } - - start(): Promise { - if (this.started) { - return Promise.resolve() - } - - this.started = true - - return new Promise((resolve, reject) => { - try { - this.server = this.createServer() - this.server.listen(this.port, this.host, () => { - resolve() - }) - } catch (e) { - reject(e) - } - }) - } - - stop(): Promise { - if (!this.started) { - return Promise.resolve() - } - - return new Promise((resolve, reject) => { - this.server?.close((e) => { - return e ? reject(e) : resolve() - }) - }) - } - - attach(server: MultisigServer): void { - this.multisigServer = server - } -} diff --git a/ironfish-cli/src/multisigBroker/adapters/tlsAdapter.ts b/ironfish-cli/src/multisigBroker/adapters/tlsAdapter.ts deleted file mode 100644 index 5669b73d13..0000000000 --- a/ironfish-cli/src/multisigBroker/adapters/tlsAdapter.ts +++ /dev/null @@ -1,30 +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 net from 'net' -import tls from 'tls' -import { MultisigTcpAdapter } from './tcpAdapter' - -export class MultisigTlsAdapter extends MultisigTcpAdapter { - readonly tlsOptions: tls.TlsOptions - - constructor(options: { - logger: Logger - host: string - port: number - tlsOptions: tls.TlsOptions - }) { - super(options) - - this.tlsOptions = options.tlsOptions - } - - protected createServer(): net.Server { - this.logger.info(`Hosting Multisig Server via TLS on ${this.host}:${this.port}`) - - return tls.createServer(this.tlsOptions, (socket) => - this.multisigServer?.onConnection(socket), - ) - } -} diff --git a/ironfish-cli/src/multisigBroker/clients/client.ts b/ironfish-cli/src/multisigBroker/clients/client.ts deleted file mode 100644 index 750ae2767b..0000000000 --- a/ironfish-cli/src/multisigBroker/clients/client.ts +++ /dev/null @@ -1,461 +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 { xchacha20poly1305 } from '@ironfish/rust-nodejs' -import { - ErrorUtils, - Event, - Logger, - MessageBuffer, - SetTimeoutToken, - YupUtils, -} from '@ironfish/sdk' -import { v4 as uuid } from 'uuid' -import { - MultisigClientError, - ServerMessageMalformedError, - SessionDecryptionError, -} from '../errors' -import { - ConnectedMessage, - ConnectedMessageSchema, - DkgGetStatusMessage, - DkgStartSessionMessage, - DkgStatusMessage, - DkgStatusSchema, - JoinedSessionMessage, - JoinedSessionSchema, - JoinSessionMessage, - MultisigBrokerAckSchema, - MultisigBrokerMessage, - MultisigBrokerMessageSchema, - MultisigBrokerMessageWithError, - MultisigBrokerMessageWithErrorSchema, - Round1PublicPackageMessage, - Round2PublicPackageMessage, - SignatureShareMessage, - SigningCommitmentMessage, - SigningGetStatusMessage, - SigningStartSessionMessage, - SigningStatusMessage, - SigningStatusSchema, -} from '../messages' - -const RETRY_INTERVAL = 5000 -const CONNECTION_TIMEOUT = 60 * 1000 -export abstract class MultisigClient { - readonly logger: Logger - readonly hostname: string - readonly port: number - - private started: boolean - private isClosing = false - private connected: boolean - private connectWarned: boolean - private connectTimeout: SetTimeoutToken | null - private nextMessageId: number - private readonly messageBuffer = new MessageBuffer('\n') - - private tryConnectUntil: number | null = null - - readonly onConnected = new Event<[]>() - readonly onDisconnected = new Event<[]>() - readonly onDkgStatus = new Event<[DkgStatusMessage]>() - readonly onSigningStatus = new Event<[SigningStatusMessage]>() - readonly onConnectedMessage = new Event<[ConnectedMessage]>() - readonly onJoinedSession = new Event<[JoinedSessionMessage]>() - readonly onMultisigBrokerError = new Event<[MultisigBrokerMessageWithError]>() - readonly onClientError = new Event<[MultisigClientError]>() - - sessionId: string | null = null - passphrase: string | null = null - identity: string | null = null - - retries: Map = new Map() - - constructor(options: { hostname: string; port: number; logger: Logger }) { - this.logger = options.logger - this.hostname = options.hostname - this.port = options.port - - this.started = false - this.nextMessageId = 0 - this.connected = false - this.connectWarned = false - this.connectTimeout = null - } - - get connectionString(): string { - const passphrase = this.passphrase ? encodeURIComponent(this.passphrase) : '' - return `tcp://${this.sessionId}:${passphrase}@${this.hostname}:${this.port}` - } - - get key(): xchacha20poly1305.XChaCha20Poly1305Key { - if (!this.sessionId || !this.passphrase) { - throw new Error('Client must join a session before encrypting/decrypting messages') - } - - const sessionIdBytes = Buffer.from(this.sessionId) - const salt = sessionIdBytes.subarray(0, 32) - const nonce = sessionIdBytes.subarray(sessionIdBytes.length - 24) - - return xchacha20poly1305.XChaCha20Poly1305Key.fromParts(this.passphrase, salt, nonce) - } - - protected abstract connect(): Promise - protected abstract writeData(data: string): void - protected abstract close(): Promise - - start(): void { - if (this.started) { - return - } - - this.started = true - this.logger.debug('Connecting to server...') - void this.startConnecting() - } - - private async startConnecting(): Promise { - if (this.isClosing) { - return - } - - if (!this.tryConnectUntil) { - this.tryConnectUntil = Date.now() + CONNECTION_TIMEOUT - } else if (Date.now() > this.tryConnectUntil) { - clearTimeout(this.connectTimeout ?? undefined) - throw new Error(`Timed out connecting to server at ${this.hostname}:${this.port}`) - } - - const connected = await this.connect() - .then(() => true) - .catch(() => false) - - if (!this.started) { - return - } - - if (!connected) { - if (!this.connectWarned) { - this.logger.warn(`Failed to connect to server, retrying...`) - this.connectWarned = true - } - - this.connectTimeout = setTimeout(() => void this.startConnecting(), 5000) - return - } - - this.tryConnectUntil = null - this.connectWarned = false - this.onConnect() - this.onConnected.emit() - - if (this.sessionId && this.passphrase && this.identity) { - this.logger.debug(`Rejoining session ${this.sessionId}`) - this.joinSession(this.sessionId, this.passphrase, this.identity) - } - } - - stop(): void { - this.isClosing = true - void this.close() - - if (this.connectTimeout) { - clearTimeout(this.connectTimeout) - } - - for (const retryInterval of this.retries.values()) { - clearInterval(retryInterval) - } - } - - isConnected(): boolean { - return this.connected - } - - joinSession(sessionId: string, passphrase: string, identity: string): void { - this.sessionId = sessionId - this.passphrase = passphrase - this.identity = identity - this.send('join_session', { identity }) - } - - startDkgSession( - passphrase: string, - maxSigners: number, - minSigners: number, - identity: string, - ): void { - this.sessionId = uuid() - this.passphrase = passphrase - this.identity = identity - const challenge = this.key.encrypt(Buffer.from('DKG')).toString('hex') - this.send('dkg.start_session', { maxSigners, minSigners, challenge, identity }) - } - - startSigningSession( - passphrase: string, - numSigners: number, - unsignedTransaction: string, - identity: string, - allowedIdentities?: string[], - ): void { - this.sessionId = uuid() - this.passphrase = passphrase - this.identity = identity - const challenge = this.key.encrypt(Buffer.from('SIGNING')).toString('hex') - this.send('sign.start_session', { - numSigners, - unsignedTransaction, - challenge, - identity, - allowedIdentities, - }) - } - - submitRound1PublicPackage(round1PublicPackage: string): void { - this.send('dkg.round1', { package: round1PublicPackage }) - } - - submitRound2PublicPackage(round2PublicPackage: string): void { - this.send('dkg.round2', { package: round2PublicPackage }) - } - - getDkgStatus(): void { - this.send('dkg.get_status', {}) - } - - submitSigningCommitment(signingCommitment: string): void { - this.send('sign.commitment', { signingCommitment }) - } - - submitSignatureShare(signatureShare: string): void { - this.send('sign.share', { signatureShare }) - } - - getSigningStatus(): void { - this.send('sign.get_status', {}) - } - - private send(method: 'join_session', body: JoinSessionMessage): void - private send(method: 'dkg.start_session', body: DkgStartSessionMessage): void - private send(method: 'sign.start_session', body: SigningStartSessionMessage): void - private send(method: 'dkg.round1', body: Round1PublicPackageMessage): void - private send(method: 'dkg.round2', body: Round2PublicPackageMessage): void - private send(method: 'dkg.get_status', body: DkgGetStatusMessage): void - private send(method: 'sign.commitment', body: SigningCommitmentMessage): void - private send(method: 'sign.share', body: SignatureShareMessage): void - private send(method: 'sign.get_status', body: SigningGetStatusMessage): void - private send(method: string, body?: unknown): void { - if (!this.sessionId) { - throw new Error('Client must join a session before sending messages') - } - - if (!this.connected) { - return - } - - const messageId = this.nextMessageId++ - - const message: MultisigBrokerMessage = { - id: messageId, - method, - sessionId: this.sessionId, - body: this.encryptMessageBody(body), - } - - this.writeData(JSON.stringify(message) + '\n') - - this.retries.set( - messageId, - setInterval(() => { - this.writeData(JSON.stringify(message) + '\n') - }, RETRY_INTERVAL), - ) - } - - protected onConnect(): void { - this.connected = true - - this.logger.debug('Successfully connected to server') - } - - protected onDisconnect = (): void => { - this.connected = false - this.messageBuffer.clear() - - if (!this.isClosing) { - this.logger.warn('Disconnected from server unexpectedly. Reconnecting.') - this.connectTimeout = setTimeout(() => void this.startConnecting(), 5000) - } - - this.onDisconnected.emit() - } - - protected onError = (error: unknown): void => { - this.logger.error(`Error ${ErrorUtils.renderError(error)}`) - } - - protected async onData(data: Buffer): Promise { - this.messageBuffer.write(data) - - for (const message of this.messageBuffer.readMessages()) { - const payload: unknown = JSON.parse(message) - - const header = await YupUtils.tryValidate(MultisigBrokerMessageSchema, payload) - - if (header.error) { - // Try the error message instead. - const headerWithError = await YupUtils.tryValidate( - MultisigBrokerMessageWithErrorSchema, - payload, - ) - if (headerWithError.error) { - throw new ServerMessageMalformedError(header.error) - } - this.logger.debug( - `Server sent error ${headerWithError.result.error.message} for id ${headerWithError.result.error.id}`, - ) - this.cancelRetry(headerWithError.result.error.id) - this.onMultisigBrokerError.emit(headerWithError.result) - return - } - - this.logger.debug(`Server sent ${header.result.method} message`) - - switch (header.result.method) { - case 'ack': { - const body = await YupUtils.tryValidate(MultisigBrokerAckSchema, header.result.body) - - if (body.error) { - throw new ServerMessageMalformedError(body.error, header.result.method) - } - - this.cancelRetry(body.result.messageId) - break - } - case 'dkg.status': { - const body = await YupUtils.tryValidate(DkgStatusSchema, header.result.body) - - if (body.error) { - throw new ServerMessageMalformedError(body.error, header.result.method) - } - - const decrypted = this.decryptMessageBody(body.result) - this.onDkgStatus.emit(decrypted) - break - } - case 'sign.status': { - const body = await YupUtils.tryValidate(SigningStatusSchema, header.result.body) - - if (body.error) { - throw new ServerMessageMalformedError(body.error, header.result.method) - } - - const decrypted = this.decryptMessageBody(body.result) - this.onSigningStatus.emit(decrypted) - break - } - case 'connected': { - const body = await YupUtils.tryValidate(ConnectedMessageSchema, header.result.body) - - if (body.error) { - throw new ServerMessageMalformedError(body.error, header.result.method) - } - - this.onConnectedMessage.emit(body.result) - break - } - case 'joined_session': { - const body = await YupUtils.tryValidate(JoinedSessionSchema, header.result.body) - if (body.error) { - throw new ServerMessageMalformedError(body.error, header.result.method) - } - - try { - const decrypted = this.decryptMessageBody(body.result) - this.onJoinedSession.emit(decrypted) - break - } catch (e) { - this.onClientError.emit( - new SessionDecryptionError( - 'Failed to decrypt session challenge. Passphrase is incorrect.', - ), - ) - break - } - } - - default: - throw new ServerMessageMalformedError(`Invalid message ${header.result.method}`) - } - } - } - - cancelRetry(messageId: number): void { - const retryInterval = this.retries.get(messageId) - clearInterval(retryInterval) - this.retries.delete(messageId) - } - - private encryptMessageBody(body: unknown): object { - let encrypted = body as object - for (const [key, value] of Object.entries(body as object)) { - if (typeof value === 'string') { - encrypted = { - ...encrypted, - [key]: this.key.encrypt(Buffer.from(value)).toString('hex'), - } - } else if (value instanceof Array) { - const encryptedItems = [] - for (const item of value) { - if (typeof item === 'string') { - encryptedItems.push(this.key.encrypt(Buffer.from(item)).toString('hex')) - } else { - encryptedItems.push(item) - } - } - encrypted = { - ...encrypted, - [key]: encryptedItems, - } - } - } - - return encrypted - } - - private decryptMessageBody(body: T): T { - let decrypted = body - for (const [key, value] of Object.entries(body)) { - if (typeof value === 'string') { - decrypted = { - ...decrypted, - [key]: this.key.decrypt(Buffer.from(value, 'hex')).toString(), - } - } else if (value instanceof Array) { - const decryptedItems = [] - for (const item of value) { - if (typeof item === 'string') { - try { - decryptedItems.push(this.key.decrypt(Buffer.from(item, 'hex')).toString()) - } catch { - this.logger.debug( - 'Failed to decrypt submitted session data. Skipping invalid data.', - ) - } - } else { - decryptedItems.push(item) - } - } - decrypted = { - ...decrypted, - [key]: decryptedItems, - } - } - } - - return decrypted - } -} diff --git a/ironfish-cli/src/multisigBroker/clients/index.ts b/ironfish-cli/src/multisigBroker/clients/index.ts deleted file mode 100644 index 5a5d770c3a..0000000000 --- a/ironfish-cli/src/multisigBroker/clients/index.ts +++ /dev/null @@ -1,7 +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/. */ - -export * from './client' -export * from './tcpClient' -export * from './tlsClient' diff --git a/ironfish-cli/src/multisigBroker/clients/tcpClient.ts b/ironfish-cli/src/multisigBroker/clients/tcpClient.ts deleted file mode 100644 index df83cd5158..0000000000 --- a/ironfish-cli/src/multisigBroker/clients/tcpClient.ts +++ /dev/null @@ -1,65 +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 net from 'net' -import { MultisigClient } from './client' - -export class MultisigTcpClient extends MultisigClient { - client: net.Socket | null = null - - constructor(options: { hostname: string; port: number; logger: Logger }) { - super({ - hostname: options.hostname, - port: options.port, - logger: options.logger, - }) - } - - protected onSocketDisconnect = (): void => { - this.client?.off('error', this.onError) - this.client?.off('close', this.onSocketDisconnect) - this.client?.off('data', this.onSocketData) - this.onDisconnect() - } - - protected onSocketData = (data: Buffer): void => { - this.onData(data).catch((e) => this.onError(e)) - } - - protected connect(): Promise { - return new Promise((resolve, reject): void => { - const onConnect = () => { - client.off('connect', onConnect) - client.off('error', onError) - - client.on('error', this.onError) - client.on('close', this.onSocketDisconnect) - - resolve() - } - - const onError = (error: unknown) => { - client.off('connect', onConnect) - client.off('error', onError) - reject(error) - } - - const client = new net.Socket() - client.on('error', onError) - client.on('connect', onConnect) - client.on('data', this.onSocketData) - client.connect({ host: this.hostname, port: this.port }) - this.client = client - }) - } - - protected writeData(data: string): void { - this.client?.write(data) - } - - protected close(): Promise { - this.client?.destroy() - return Promise.resolve() - } -} diff --git a/ironfish-cli/src/multisigBroker/clients/tlsClient.ts b/ironfish-cli/src/multisigBroker/clients/tlsClient.ts deleted file mode 100644 index dc4e163f03..0000000000 --- a/ironfish-cli/src/multisigBroker/clients/tlsClient.ts +++ /dev/null @@ -1,37 +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 tls from 'tls' -import { MultisigTcpClient } from './tcpClient' - -export class MultisigTlsClient extends MultisigTcpClient { - protected connect(): Promise { - return new Promise((resolve, reject): void => { - const onConnect = () => { - client.off('secureConnect', onConnect) - client.off('error', onError) - - client.on('error', this.onError) - client.on('close', this.onSocketDisconnect) - - resolve() - } - - const onError = (error: unknown) => { - client.off('secureConnect', onConnect) - client.off('error', onError) - reject(error) - } - - const client = tls.connect({ - host: this.hostname, - port: this.port, - rejectUnauthorized: false, - }) - client.on('error', onError) - client.on('secureConnect', onConnect) - client.on('data', this.onSocketData) - this.client = client - }) - } -} diff --git a/ironfish-cli/src/multisigBroker/errors.ts b/ironfish-cli/src/multisigBroker/errors.ts deleted file mode 100644 index dfddc508cf..0000000000 --- a/ironfish-cli/src/multisigBroker/errors.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 * as yup from 'yup' -import { MultisigServerClient } from './serverClient' - -export const MultisigBrokerErrorCodes = { - DUPLICATE_SESSION_ID: 1, - SESSION_ID_NOT_FOUND: 2, - INVALID_DKG_SESSION_ID: 3, - INVALID_SIGNING_SESSION_ID: 4, - IDENTITY_NOT_ALLOWED: 5, -} - -export class MessageMalformedError extends Error { - name = this.constructor.name - - constructor(sender: string, error: yup.ValidationError | string, method?: string) { - super() - - if (typeof error === 'string') { - this.message = error - } else { - this.message = `${sender} sent malformed request` - if (method) { - this.message += ` (${method})` - } - this.message += `: ${error.message}` - } - } -} - -export class ClientMessageMalformedError extends MessageMalformedError { - client: MultisigServerClient - - constructor( - client: MultisigServerClient, - error: yup.ValidationError | string, - method?: string, - ) { - super(`Client ${client.id}`, error, method) - this.client = client - } -} - -export class ServerMessageMalformedError extends MessageMalformedError { - constructor(error: yup.ValidationError | string, method?: string) { - super('Server', error, method) - } -} - -export class MultisigClientError extends Error { - name = this.constructor.name -} - -export class SessionDecryptionError extends MultisigClientError { - constructor(message: string) { - super(message) - } -} - -export class InvalidSessionError extends MultisigClientError { - constructor(message: string) { - super(message) - } -} - -export class IdentityNotAllowedError extends MultisigClientError { - constructor(message: string) { - super(message) - } -} diff --git a/ironfish-cli/src/multisigBroker/index.ts b/ironfish-cli/src/multisigBroker/index.ts deleted file mode 100644 index 1344825732..0000000000 --- a/ironfish-cli/src/multisigBroker/index.ts +++ /dev/null @@ -1,7 +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/. */ - -export * from './clients' -export { MultisigServer } from './server' -export * from './utils' diff --git a/ironfish-cli/src/multisigBroker/messages.ts b/ironfish-cli/src/multisigBroker/messages.ts deleted file mode 100644 index 03ed964c1d..0000000000 --- a/ironfish-cli/src/multisigBroker/messages.ts +++ /dev/null @@ -1,196 +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 * as yup from 'yup' - -export type MultisigBrokerMessage = { - id: number - method: string - sessionId: string - body?: unknown -} - -export interface MultisigBrokerMessageWithError - extends Omit { - error: { - id: number - message: string - code: number - } -} - -export type MultisigBrokerAckMessage = { - messageId: number -} - -export type DkgStartSessionMessage = { - minSigners: number - maxSigners: number - challenge: string - identity: string -} - -export type SigningStartSessionMessage = { - numSigners: number - unsignedTransaction: string - challenge: string - identity: string - allowedIdentities?: string[] -} - -export type JoinSessionMessage = { - identity: string -} - -export type JoinedSessionMessage = { - challenge: string -} - -export type Round1PublicPackageMessage = { - package: string -} - -export type Round2PublicPackageMessage = { - package: string -} - -export type SigningCommitmentMessage = { - signingCommitment: string -} - -export type SignatureShareMessage = { - signatureShare: string -} - -export type DkgGetStatusMessage = object | undefined - -export type DkgStatusMessage = { - minSigners: number - maxSigners: number - identities: string[] - round1PublicPackages: string[] - round2PublicPackages: string[] -} - -export type SigningGetStatusMessage = object | undefined - -export type SigningStatusMessage = { - numSigners: number - unsignedTransaction: string - identities: string[] - signingCommitments: string[] - signatureShares: string[] -} - -export type ConnectedMessage = object | undefined - -export const MultisigBrokerMessageSchema: yup.ObjectSchema = yup - .object({ - id: yup.number().required(), - method: yup.string().required(), - sessionId: yup.string().required(), - body: yup.mixed().notRequired(), - }) - .required() - -export const MultisigBrokerMessageWithErrorSchema: yup.ObjectSchema = - yup - .object({ - id: yup.number().required(), - error: yup - .object({ - id: yup.number().required(), - message: yup.string().required(), - code: yup.number().required(), - }) - .required(), - }) - .required() - -export const MultisigBrokerAckSchema: yup.ObjectSchema = yup - .object({ - messageId: yup.number().required(), - }) - .required() - -export const DkgStartSessionSchema: yup.ObjectSchema = yup - .object({ - minSigners: yup.number().defined(), - maxSigners: yup.number().defined(), - challenge: yup.string().defined(), - identity: yup.string().defined(), - }) - .defined() - -export const SigningStartSessionSchema: yup.ObjectSchema = yup - .object({ - numSigners: yup.number().defined(), - unsignedTransaction: yup.string().defined(), - challenge: yup.string().defined(), - identity: yup.string().defined(), - allowedIdentities: yup.array(yup.string().defined()).optional(), - }) - .defined() - -export const JoinSessionSchema: yup.ObjectSchema = yup - .object({ - identity: yup.string().defined(), - }) - .defined() - -export const JoinedSessionSchema: yup.ObjectSchema = yup - .object({ - challenge: yup.string().required(), - }) - .required() - -export const Round1PublicPackageSchema: yup.ObjectSchema = yup - .object({ package: yup.string().defined() }) - .defined() - -export const Round2PublicPackageSchema: yup.ObjectSchema = yup - .object({ package: yup.string().defined() }) - .defined() - -export const SigningCommitmentSchema: yup.ObjectSchema = yup - .object({ signingCommitment: yup.string().defined() }) - .defined() - -export const SignatureShareSchema: yup.ObjectSchema = yup - .object({ signatureShare: yup.string().defined() }) - .defined() - -export const DkgGetStatusSchema: yup.ObjectSchema = yup - .object({}) - .notRequired() - .default(undefined) - -export const DkgStatusSchema: yup.ObjectSchema = yup - .object({ - minSigners: yup.number().defined(), - maxSigners: yup.number().defined(), - identities: yup.array(yup.string().defined()).defined(), - round1PublicPackages: yup.array(yup.string().defined()).defined(), - round2PublicPackages: yup.array(yup.string().defined()).defined(), - }) - .defined() - -export const SigningGetStatusSchema: yup.ObjectSchema = yup - .object({}) - .notRequired() - .default(undefined) - -export const SigningStatusSchema: yup.ObjectSchema = yup - .object({ - numSigners: yup.number().defined(), - unsignedTransaction: yup.string().defined(), - identities: yup.array(yup.string().defined()).defined(), - signingCommitments: yup.array(yup.string().defined()).defined(), - signatureShares: yup.array(yup.string().defined()).defined(), - }) - .defined() - -export const ConnectedMessageSchema: yup.ObjectSchema = yup - .object({}) - .notRequired() - .default(undefined) diff --git a/ironfish-cli/src/multisigBroker/server.ts b/ironfish-cli/src/multisigBroker/server.ts deleted file mode 100644 index adf2970870..0000000000 --- a/ironfish-cli/src/multisigBroker/server.ts +++ /dev/null @@ -1,843 +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 { ErrorUtils, Logger, YupUtils } from '@ironfish/sdk' -import net from 'net' -import { IMultisigBrokerAdapter } from './adapters' -import { ClientMessageMalformedError, MultisigBrokerErrorCodes } from './errors' -import { - ConnectedMessage, - DkgGetStatusSchema, - DkgStartSessionSchema, - DkgStatusMessage, - JoinedSessionMessage, - JoinSessionSchema, - MultisigBrokerAckMessage, - MultisigBrokerMessage, - MultisigBrokerMessageSchema, - MultisigBrokerMessageWithError, - Round1PublicPackageSchema, - Round2PublicPackageSchema, - SignatureShareSchema, - SigningCommitmentSchema, - SigningGetStatusSchema, - SigningStartSessionSchema, - SigningStatusMessage, -} from './messages' -import { MultisigServerClient } from './serverClient' - -enum MultisigSessionType { - DKG = 'DKG', - SIGNING = 'SIGNING', -} - -interface MultisigSession { - id: string - type: MultisigSessionType - clientIds: Set - status: DkgStatus | SigningStatus - challenge: string - timeout: NodeJS.Timeout | undefined - allowedIdentities?: Set -} - -interface DkgSession extends MultisigSession { - type: MultisigSessionType.DKG - status: DkgStatus -} - -interface SigningSession extends MultisigSession { - type: MultisigSessionType.SIGNING - status: SigningStatus -} - -export type DkgStatus = { - minSigners: number - maxSigners: number - identities: string[] - round1PublicPackages: string[] - round2PublicPackages: string[] -} - -export type SigningStatus = { - numSigners: number - unsignedTransaction: string - identities: string[] - signingCommitments: string[] - signatureShares: string[] -} - -export class MultisigServer { - readonly logger: Logger - readonly adapters: IMultisigBrokerAdapter[] = [] - - clients: Map - nextClientId: number - nextMessageId: number - - sessions: Map = new Map() - - private _isRunning = false - private _startPromise: Promise | null = null - private idleSessionTimeout: number - - constructor(options: { logger: Logger; idleSessionTimeout?: number }) { - this.logger = options.logger - - this.clients = new Map() - this.nextClientId = 1 - this.nextMessageId = 1 - this.idleSessionTimeout = options.idleSessionTimeout ?? 600000 - } - - get isRunning(): boolean { - return this._isRunning - } - - /** Starts the MultisigBroker server and tells any attached adapters to start serving requests */ - async start(): Promise { - if (this._isRunning) { - return - } - - this._startPromise = Promise.all(this.adapters.map((a) => a.start())) - this._isRunning = true - await this._startPromise - } - - /** Stops the MultisigBroker server and tells any attached adapters to stop serving requests */ - async stop(): Promise { - if (!this._isRunning) { - return - } - - if (this._startPromise) { - await this._startPromise - } - - for (const session of this.sessions.values()) { - clearTimeout(session.timeout) - } - - await Promise.all(this.adapters.map((a) => a.stop())) - this._isRunning = false - } - - /** Adds an adapter to the MultisigBroker server and starts it if the server has already been started */ - mount(adapter: IMultisigBrokerAdapter): void { - this.adapters.push(adapter) - adapter.attach(this) - - if (this._isRunning) { - let promise: Promise = adapter.start() - - if (this._startPromise) { - // Attach this promise to the start promise chain - // in case we call stop while were still starting up - promise = Promise.all([this._startPromise, promise]) - } - - this._startPromise = promise - } - } - - onConnection(socket: net.Socket): void { - const client = MultisigServerClient.accept(socket, this.nextClientId++) - - socket.on('data', (data: Buffer) => { - this.onData(client, data).catch((e) => this.onError(client, e)) - }) - - socket.on('close', () => this.onDisconnect(client)) - socket.on('error', (e) => this.onError(client, e)) - - this.send(socket, 'connected', '0', {}) - - this.logger.debug(`Client ${client.id} connected: ${client.remoteAddress}`) - this.clients.set(client.id, client) - } - - private onDisconnect(client: MultisigServerClient): void { - this.logger.debug(`Client ${client.id} disconnected (${this.clients.size - 1} total)`) - - this.clients.delete(client.id) - client.close() - client.socket.removeAllListeners('close') - client.socket.removeAllListeners('error') - - if (client.sessionId) { - const sessionId = client.sessionId - - this.removeClientFromSession(client) - - if (!this.isSessionActive(sessionId)) { - this.setSessionTimeout(sessionId) - } - } - } - - private async onData(client: MultisigServerClient, data: Buffer): Promise { - client.messageBuffer.write(data) - - for (const split of client.messageBuffer.readMessages()) { - const payload: unknown = JSON.parse(split) - const { error: parseError, result: message } = await YupUtils.tryValidate( - MultisigBrokerMessageSchema, - payload, - ) - - if (parseError) { - this.logger.debug( - `Error parsing message from client ${client.id}: ${ErrorUtils.renderError( - parseError, - true, - )}`, - ) - client.close(parseError) - this.clients.delete(client.id) - return - } - - this.logger.debug(`Client ${client.id} sent ${message.method} message`) - this.send(client.socket, 'ack', message.sessionId, { messageId: message.id }) - - if (message.method === 'dkg.start_session') { - await this.handleDkgStartSessionMessage(client, message) - return - } else if (message.method === 'sign.start_session') { - await this.handleSigningStartSessionMessage(client, message) - return - } else if (message.method === 'join_session') { - await this.handleJoinSessionMessage(client, message) - return - } else if (message.method === 'dkg.round1') { - await this.handleRound1PublicPackageMessage(client, message) - return - } else if (message.method === 'dkg.round2') { - await this.handleRound2PublicPackageMessage(client, message) - return - } else if (message.method === 'dkg.get_status') { - await this.handleDkgGetStatusMessage(client, message) - return - } else if (message.method === 'sign.commitment') { - await this.handleSigningCommitmentMessage(client, message) - return - } else if (message.method === 'sign.share') { - await this.handleSignatureShareMessage(client, message) - return - } else if (message.method === 'sign.get_status') { - await this.handleSigningGetStatusMessage(client, message) - return - } else { - throw new ClientMessageMalformedError(client, `Invalid message ${message.method}`) - } - } - } - - private onError(client: MultisigServerClient, error: unknown): void { - this.logger.debug( - `Error during handling of data from client ${client.id}: ${ErrorUtils.renderError( - error, - true, - )}`, - ) - - client.socket.removeAllListeners() - client.close() - - this.clients.delete(client.id) - } - - /** - * If a client has the given session ID and is connected, the associated - * session should still be considered active - */ - private isSessionActive(sessionId: string): boolean { - const session = this.sessions.get(sessionId) - if (!session) { - return false - } - - if (session.clientIds.size > 0) { - return true - } - - return false - } - - private setSessionTimeout(sessionId: string): void { - const session = this.sessions.get(sessionId) - if (!session) { - return - } - - session.timeout = setTimeout(() => this.cleanupSession(sessionId), this.idleSessionTimeout) - } - - private cleanupSession(sessionId: string): void { - this.sessions.delete(sessionId) - this.logger.debug(`Session ${sessionId} cleaned up. Active sessions: ${this.sessions.size}`) - } - - private addClientToSession(client: MultisigServerClient, sessionId: string): void { - const session = this.sessions.get(sessionId) - if (!session) { - return - } - - client.sessionId = session.id - session.clientIds.add(client.id) - - clearTimeout(session.timeout) - session.timeout = undefined - } - - private removeClientFromSession(client: MultisigServerClient): void { - if (!client.sessionId) { - return - } - - const session = this.sessions.get(client.sessionId) - if (!session) { - return - } - - // If the session is in the collecting identities phase, we can safely - // remove a disconnected client's identity from the list. Otherwise, if a - // client disconnects and then reconnects, they can possibly be counted as - // multiple identities, leaving the session in a bad state. - if (client.identity != null) { - if (isDkgSession(session)) { - if (session.status.round1PublicPackages.length === 0) { - const identIndex = session.status.identities.indexOf(client.identity) - if (identIndex > -1) { - session.status.identities.splice(identIndex, 1) - } - } - } else if (isSigningSession(session)) { - if (session.status.signingCommitments.length === 0) { - const identIndex = session.status.identities.indexOf(client.identity) - if (identIndex > -1) { - session.status.identities.splice(identIndex, 1) - } - } - } - } - - client.sessionId = null - session.clientIds.delete(client.id) - } - - private broadcast(method: 'dkg.status', sessionId: string, body?: DkgStatusMessage): void - private broadcast(method: 'sign.status', sessionId: string, body?: SigningStatusMessage): void - private broadcast(method: string, sessionId: string, body?: unknown): void { - const message: MultisigBrokerMessage = { - id: this.nextMessageId++, - method, - sessionId, - body, - } - - const serialized = JSON.stringify(message) + '\n' - - this.logger.debug('broadcasting to clients', { - method, - sessionId, - id: message.id, - numClients: this.clients.size, - messageLength: serialized.length, - }) - - let broadcasted = 0 - - const session = this.sessions.get(sessionId) - if (!session) { - this.logger.debug(`Session ${sessionId} does not exist, broadcast failed`) - return - } - - for (const clientId of session.clientIds) { - const client = this.clients.get(clientId) - if (!client) { - this.logger.debug( - `Client ${clientId} does not exist, but session ${sessionId} thinks it does, removing.`, - ) - session.clientIds.delete(clientId) - continue - } - - if (!client.connected) { - continue - } - - client.socket.write(serialized) - broadcasted++ - } - - this.logger.debug('completed broadcast to clients', { - method, - sessionId, - id: message.id, - numClients: broadcasted, - messageLength: serialized.length, - }) - } - - send( - socket: net.Socket, - method: 'dkg.status', - sessionId: string, - body: DkgStatusMessage, - ): void - send( - socket: net.Socket, - method: 'sign.status', - sessionId: string, - body: SigningStatusMessage, - ): void - send(socket: net.Socket, method: 'connected', sessionId: string, body: ConnectedMessage): void - send( - socket: net.Socket, - method: 'joined_session', - sessionId: string, - body: JoinedSessionMessage, - ): void - send( - socket: net.Socket, - method: 'ack', - sessionId: string, - body: MultisigBrokerAckMessage, - ): void - send(socket: net.Socket, method: string, sessionId: string, body?: unknown): void { - const message: MultisigBrokerMessage = { - id: this.nextMessageId++, - method, - sessionId, - body, - } - - const serialized = JSON.stringify(message) + '\n' - socket.write(serialized) - } - - sendErrorMessage( - client: MultisigServerClient, - id: number, - message: string, - code: number, - ): void { - const msg: MultisigBrokerMessageWithError = { - id: this.nextMessageId++, - error: { - id, - message, - code, - }, - } - const serialized = JSON.stringify(msg) + '\n' - client.socket.write(serialized) - } - - async handleDkgStartSessionMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(DkgStartSessionSchema, message.body) - - if (body.error) { - return - } - - const sessionId = message.sessionId - - if (this.sessions.has(sessionId)) { - this.sendErrorMessage( - client, - message.id, - `Duplicate sessionId: ${sessionId}`, - MultisigBrokerErrorCodes.DUPLICATE_SESSION_ID, - ) - return - } - - const session = { - id: sessionId, - type: MultisigSessionType.DKG, - clientIds: new Set(), - status: { - maxSigners: body.result.maxSigners, - minSigners: body.result.minSigners, - identities: [body.result.identity], - round1PublicPackages: [], - round2PublicPackages: [], - }, - challenge: body.result.challenge, - timeout: undefined, - } - - this.sessions.set(sessionId, session) - - this.logger.debug(`Client ${client.id} started dkg session ${message.sessionId}`) - - client.identity = body.result.identity - this.addClientToSession(client, sessionId) - - this.send(client.socket, 'joined_session', message.sessionId, { - challenge: session.challenge, - }) - } - - async handleSigningStartSessionMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(SigningStartSessionSchema, message.body) - - if (body.error) { - return - } - - const sessionId = message.sessionId - - if (this.sessions.has(sessionId)) { - this.sendErrorMessage( - client, - message.id, - `Duplicate sessionId: ${sessionId}`, - MultisigBrokerErrorCodes.DUPLICATE_SESSION_ID, - ) - return - } - - const session = { - id: sessionId, - type: MultisigSessionType.SIGNING, - clientIds: new Set(), - status: { - numSigners: body.result.numSigners, - unsignedTransaction: body.result.unsignedTransaction, - identities: [body.result.identity], - signingCommitments: [], - signatureShares: [], - }, - challenge: body.result.challenge, - timeout: undefined, - allowedIdentities: body.result.allowedIdentities - ? new Set(body.result.allowedIdentities) - : undefined, - } - - this.sessions.set(sessionId, session) - - this.logger.debug(`Client ${client.id} started signing session ${message.sessionId}`) - - client.identity = body.result.identity - this.addClientToSession(client, sessionId) - - this.send(client.socket, 'joined_session', message.sessionId, { - challenge: session.challenge, - }) - } - - async handleJoinSessionMessage(client: MultisigServerClient, message: MultisigBrokerMessage) { - const body = await YupUtils.tryValidate(JoinSessionSchema, message.body) - - if (body.error) { - return - } - - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } - - if (session.allowedIdentities && !session.allowedIdentities.has(body.result.identity)) { - this.sendErrorMessage( - client, - message.id, - 'Identity not allowed to join this session', - MultisigBrokerErrorCodes.IDENTITY_NOT_ALLOWED, - ) - return - } - - this.logger.debug(`Client ${client.id} joined session ${message.sessionId}`) - - this.addClientToSession(client, message.sessionId) - - this.send(client.socket, 'joined_session', message.sessionId, { - challenge: session.challenge, - }) - - client.identity = body.result.identity - if (!session.status.identities.includes(client.identity)) { - session.status.identities.push(client.identity) - this.sessions.set(message.sessionId, session) - - // Broadcast status after collecting all identities - if (isDkgSession(session)) { - if (session.status.identities.length === session.status.maxSigners) { - this.broadcast('dkg.status', message.sessionId, session.status) - } - } else if (isSigningSession(session)) { - if (session.status.identities.length === session.status.numSigners) { - this.broadcast('sign.status', message.sessionId, session.status) - } - } - } - } - - async handleRound1PublicPackageMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(Round1PublicPackageSchema, message.body) - - if (body.error) { - return - } - - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } - - if (!isDkgSession(session)) { - this.sendErrorMessage( - client, - message.id, - `Session is not a dkg session: ${message.sessionId}`, - MultisigBrokerErrorCodes.INVALID_DKG_SESSION_ID, - ) - return - } - - const round1PublicPackage = body.result.package - if (!session.status.round1PublicPackages.includes(round1PublicPackage)) { - session.status.round1PublicPackages.push(round1PublicPackage) - this.sessions.set(message.sessionId, session) - - // Broadcast status after collecting all packages - if (session.status.round1PublicPackages.length === session.status.maxSigners) { - this.broadcast('dkg.status', message.sessionId, session.status) - } - } - } - - async handleRound2PublicPackageMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(Round2PublicPackageSchema, message.body) - - if (body.error) { - return - } - - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } - - if (!isDkgSession(session)) { - this.sendErrorMessage( - client, - message.id, - `Session is not a dkg session: ${message.sessionId}`, - MultisigBrokerErrorCodes.INVALID_DKG_SESSION_ID, - ) - return - } - - const round2PublicPackage = body.result.package - if (!session.status.round2PublicPackages.includes(round2PublicPackage)) { - session.status.round2PublicPackages.push(round2PublicPackage) - this.sessions.set(message.sessionId, session) - - // Broadcast status after collecting all packages - if (session.status.round2PublicPackages.length === session.status.maxSigners) { - this.broadcast('dkg.status', message.sessionId, session.status) - } - } - } - - async handleDkgGetStatusMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(DkgGetStatusSchema, message.body) - - if (body.error) { - return - } - - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } - - if (!isDkgSession(session)) { - this.sendErrorMessage( - client, - message.id, - `Session is not a dkg session: ${message.sessionId}`, - MultisigBrokerErrorCodes.INVALID_DKG_SESSION_ID, - ) - return - } - - this.send(client.socket, 'dkg.status', message.sessionId, session.status) - } - - async handleSigningCommitmentMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(SigningCommitmentSchema, message.body) - - if (body.error) { - return - } - - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } - - if (!isSigningSession(session)) { - this.sendErrorMessage( - client, - message.id, - `Session is not a signing session: ${message.sessionId}`, - MultisigBrokerErrorCodes.INVALID_SIGNING_SESSION_ID, - ) - return - } - - const signingCommitment = body.result.signingCommitment - if (!session.status.signingCommitments.includes(signingCommitment)) { - session.status.signingCommitments.push(signingCommitment) - this.sessions.set(message.sessionId, session) - - // Broadcast status after collecting all signing commitments - if (session.status.signingCommitments.length === session.status.numSigners) { - this.broadcast('sign.status', message.sessionId, session.status) - } - } - } - - async handleSignatureShareMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(SignatureShareSchema, message.body) - - if (body.error) { - return - } - - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } - - if (!isSigningSession(session)) { - this.sendErrorMessage( - client, - message.id, - `Session is not a signing session: ${message.sessionId}`, - MultisigBrokerErrorCodes.INVALID_SIGNING_SESSION_ID, - ) - return - } - - const signatureShare = body.result.signatureShare - if (!session.status.signatureShares.includes(signatureShare)) { - session.status.signatureShares.push(signatureShare) - this.sessions.set(message.sessionId, session) - - // Broadcast status after collecting all signature shares - if (session.status.signatureShares.length === session.status.numSigners) { - this.broadcast('sign.status', message.sessionId, session.status) - } - } - } - - async handleSigningGetStatusMessage( - client: MultisigServerClient, - message: MultisigBrokerMessage, - ) { - const body = await YupUtils.tryValidate(SigningGetStatusSchema, message.body) - - if (body.error) { - return - } - - const session = this.sessions.get(message.sessionId) - if (!session) { - this.sendErrorMessage( - client, - message.id, - `Session not found: ${message.sessionId}`, - MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND, - ) - return - } - - if (!isSigningSession(session)) { - this.sendErrorMessage( - client, - message.id, - `Session is not a signing session: ${message.sessionId}`, - MultisigBrokerErrorCodes.INVALID_SIGNING_SESSION_ID, - ) - return - } - - this.send(client.socket, 'sign.status', message.sessionId, session.status) - } -} - -function isDkgSession(session: MultisigSession): session is DkgSession { - return session.type === MultisigSessionType.DKG -} - -function isSigningSession(session: MultisigSession): session is SigningSession { - return session.type === MultisigSessionType.SIGNING -} diff --git a/ironfish-cli/src/multisigBroker/serverClient.ts b/ironfish-cli/src/multisigBroker/serverClient.ts deleted file mode 100644 index 1d35b3104d..0000000000 --- a/ironfish-cli/src/multisigBroker/serverClient.ts +++ /dev/null @@ -1,39 +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 { Assert, MessageBuffer } from '@ironfish/sdk' -import net from 'net' - -export class MultisigServerClient { - id: number - socket: net.Socket - connected: boolean - remoteAddress: string - messageBuffer: MessageBuffer - sessionId: string | null = null - identity: string | null = null - - private constructor(options: { socket: net.Socket; id: number }) { - this.id = options.id - this.socket = options.socket - this.connected = true - this.messageBuffer = new MessageBuffer('\n') - - Assert.isNotUndefined(this.socket.remoteAddress) - this.remoteAddress = this.socket.remoteAddress - } - - static accept(socket: net.Socket, id: number): MultisigServerClient { - return new MultisigServerClient({ socket, id }) - } - - close(error?: Error): void { - if (!this.connected) { - return - } - - this.messageBuffer.clear() - this.connected = false - this.socket.destroy(error) - } -} diff --git a/ironfish-cli/src/multisigBroker/utils.ts b/ironfish-cli/src/multisigBroker/utils.ts deleted file mode 100644 index f2fee10c0f..0000000000 --- a/ironfish-cli/src/multisigBroker/utils.ts +++ /dev/null @@ -1,84 +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 { ErrorUtils, Logger } from '@ironfish/sdk' -import { MultisigClient, MultisigTcpClient, MultisigTlsClient } from './clients' - -const DEFAULT_MULTISIG_BROKER_HOSTNAME = 'multisig.ironfish.network' -const DEFAULT_MULTISIG_BROKER_PORT = 9035 - -function parseConnectionOptions(options: { - connection?: string - hostname?: string - port?: number - sessionId?: string - passphrase?: string - logger: Logger -}): { - hostname: string - port: number - sessionId: string | undefined - passphrase: string | undefined -} { - let hostname - let port - let sessionId - let passphrase - if (options.connection) { - try { - const url = new URL(options.connection) - if (url.host) { - hostname = url.hostname - } - if (url.port) { - port = Number(url.port) - } - if (url.username) { - sessionId = url.username - } - if (url.password) { - passphrase = decodeURI(url.password) - } - } catch (e) { - if (e instanceof TypeError && e.message.includes('Invalid URL')) { - options.logger.error(ErrorUtils.renderError(e)) - } - throw e - } - } - - hostname = hostname ?? options.hostname ?? DEFAULT_MULTISIG_BROKER_HOSTNAME - port = port ?? options.port ?? DEFAULT_MULTISIG_BROKER_PORT - - return { - hostname, - port, - sessionId, - passphrase, - } -} - -function createClient( - hostname: string, - port: number, - options: { tls: boolean; logger: Logger }, -): MultisigClient { - if (options.tls) { - return new MultisigTlsClient({ - hostname, - port, - logger: options.logger, - }) - } else { - return new MultisigTcpClient({ - hostname, - port, - logger: options.logger, - }) - } -} - -export const MultisigBrokerUtils = { - parseConnectionOptions, - createClient, -} diff --git a/ironfish-cli/src/utils/multisig/index.ts b/ironfish-cli/src/utils/multisig/index.ts index 1e6dfb1fea..d369a03de4 100644 --- a/ironfish-cli/src/utils/multisig/index.ts +++ b/ironfish-cli/src/utils/multisig/index.ts @@ -1,4 +1,5 @@ /* 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/. */ +export * from './sessionManagers' export * from './transaction' diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/utils/multisig/sessionManagers/dkgSessionManager.ts similarity index 99% rename from ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts rename to ironfish-cli/src/utils/multisig/sessionManagers/dkgSessionManager.ts index 83c94bd9cd..0080268fb5 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/dkgSessionManager.ts +++ b/ironfish-cli/src/utils/multisig/sessionManagers/dkgSessionManager.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Logger, PromiseUtils } from '@ironfish/sdk' import { ux } from '@oclif/core' -import * as ui from '../../ui' +import * as ui from '../../../ui' import { MultisigClientSessionManager, MultisigSessionManager } from './sessionManager' export function createDkgSessionManager(options: { diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/index.ts b/ironfish-cli/src/utils/multisig/sessionManagers/index.ts similarity index 100% rename from ironfish-cli/src/multisigBroker/sessionManagers/index.ts rename to ironfish-cli/src/utils/multisig/sessionManagers/index.ts diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts b/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts similarity index 97% rename from ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts rename to ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts index d3c82fc3c7..a6376b18f4 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts @@ -1,16 +1,16 @@ /* 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 { Assert, Logger, PromiseUtils } from '@ironfish/sdk' -import { ux } from '@oclif/core' -import { MultisigClient } from '../clients' import { IdentityNotAllowedError, InvalidSessionError, MultisigBrokerErrorCodes, + MultisigBrokerUtils, + MultisigClient, SessionDecryptionError, -} from '../errors' -import { MultisigBrokerUtils } from '../utils' +} from '@ironfish/multisig-broker' +import { Assert, Logger, PromiseUtils } from '@ironfish/sdk' +import { ux } from '@oclif/core' export abstract class MultisigSessionManager { sessionId: string | null = null diff --git a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/utils/multisig/sessionManagers/signingSessionManager.ts similarity index 99% rename from ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts rename to ironfish-cli/src/utils/multisig/sessionManagers/signingSessionManager.ts index 8551c45c03..1f6d954ee5 100644 --- a/ironfish-cli/src/multisigBroker/sessionManagers/signingSessionManager.ts +++ b/ironfish-cli/src/utils/multisig/sessionManagers/signingSessionManager.ts @@ -4,7 +4,7 @@ import { Logger, PromiseUtils, UnsignedTransaction } from '@ironfish/sdk' import { ux } from '@oclif/core' -import * as ui from '../../ui' +import * as ui from '../../../ui' import { MultisigClientSessionManager, MultisigSessionManager } from './sessionManager' export function createSigningSessionManager(options: { diff --git a/yarn.lock b/yarn.lock index e563687c82..1ba0b223f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1115,6 +1115,17 @@ dependencies: mute-stream "^1.0.0" +"@ironfish/multisig-broker@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@ironfish/multisig-broker/-/multisig-broker-0.1.1.tgz#9c4abfbe09f8199fb13fb672d0e1714203ed9a60" + integrity sha512-v7KaSvmsx3ihlVRi6er8YyLf7zRHB46uP6Rc2/zVvZqSoEeASViN1bpdDYM4YzuVeqTNQBF6Em79eT4tI40IpA== + dependencies: + "@ironfish/rust-nodejs" "^2.7.0" + "@ironfish/sdk" "^2.8.1" + eslint-plugin-simple-import-sort "10.0.0" + uuid "8.3.2" + yup "0.29.3" + "@isaacs/string-locale-compare@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" @@ -5526,6 +5537,11 @@ eslint-plugin-prettier@3.4.0: dependencies: prettier-linter-helpers "^1.0.0" +eslint-plugin-simple-import-sort@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-10.0.0.tgz#cc4ceaa81ba73252427062705b64321946f61351" + integrity sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw== + eslint-plugin-simple-import-sort@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-7.0.0.tgz#a1dad262f46d2184a90095a60c66fef74727f0f8" From ff8f5a67696213ac25854b52a6b0b6cbb21a2406 Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 22 Oct 2024 15:20:03 -0700 Subject: [PATCH 33/81] Ran `cargo vet prune` --- supply-chain/audits.toml | 2 +- supply-chain/config.toml | 12 ---------- supply-chain/imports.lock | 50 ++++++++++++++++++++++++++++----------- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 9237b50784..7f9ec833f9 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -132,6 +132,6 @@ notes = "Fork of the official zcash_proofs owned by Iron Fish" [[trusted.reddsa]] criteria = "safe-to-deploy" -user-id = 6289 # str4d +user-id = 6289 # Jack Grigg (str4d) start = "2021-01-08" end = "2025-09-12" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index a9ba931a8e..0c9cc9a4e6 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -150,10 +150,6 @@ criteria = "safe-to-deploy" version = "1.2.2" criteria = "safe-to-deploy" -[[exemptions.byteorder]] -version = "1.5.0" -criteria = "safe-to-deploy" - [[exemptions.bytes]] version = "1.4.0" criteria = "safe-to-deploy" @@ -786,18 +782,10 @@ criteria = "safe-to-deploy" version = "0.3.2" criteria = "safe-to-deploy" -[[exemptions.tracing]] -version = "0.1.37" -criteria = "safe-to-deploy" - [[exemptions.tracing-attributes]] version = "0.1.23" criteria = "safe-to-deploy" -[[exemptions.tracing-core]] -version = "0.1.30" -criteria = "safe-to-deploy" - [[exemptions.typenum]] version = "1.16.0" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index cc959162f9..b77cff27d8 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -27,6 +27,7 @@ version = "0.3.0" when = "2022-05-10" user-id = 6289 user-login = "str4d" +user-name = "Jack Grigg" [[publisher.unicode-normalization]] version = "0.1.22" @@ -139,6 +140,12 @@ who = "Pat Hickey " criteria = "safe-to-deploy" version = "0.2.0" +[[audits.bytecode-alliance.audits.cipher]] +who = "Andrew Brown " +criteria = "safe-to-deploy" +version = "0.4.4" +notes = "Most unsafe is hidden by `inout` dependency; only remaining unsafe is raw-splitting a slice and an unreachable hint. Older versions of this regularly reach ~150k daily downloads." + [[audits.bytecode-alliance.audits.cobs]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -424,6 +431,13 @@ Additional review comments can be found at https://crrev.com/c/4723145/31 """ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.byteorder]] +who = "danakj " +criteria = "safe-to-deploy" +version = "1.5.0" +notes = "Unsafe review in https://crrev.com/c/5838022" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.document-features]] who = "George Burgess IV " criteria = "safe-to-deploy" @@ -1056,6 +1070,28 @@ criteria = "safe-to-deploy" delta = "0.15.2 -> 0.16.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.tracing]] +who = "Alex Franchuk " +criteria = "safe-to-deploy" +version = "0.1.37" +notes = """ +There's only one unsafe impl, and its purpose is to ensure correct behavior by +creating a non-Send marker type (it has nothing to do with soundness). All +dependencies make sense, and no side-effectful std functions are used. +""" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing-core]] +who = "Alex Franchuk " +criteria = "safe-to-deploy" +version = "0.1.30" +notes = """ +Most unsafe code is in implementing non-std sync primitives. Unsafe impls are +logically correct and justified in comments, and unsafe code is sound and +justified in comments. +""" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.unicode-bidi]] who = "Makoto Kato " criteria = "safe-to-deploy" @@ -1101,20 +1137,6 @@ delta = "0.9.1 -> 0.10.1" notes = "This mainly adapts to API changes between aead 0.4 and aead 0.5." aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" -[[audits.zcash.audits.cipher]] -who = "Daira Hopwood " -criteria = "safe-to-deploy" -delta = "0.3.0 -> 0.4.3" -notes = "Significant rework of (mainly RustCrypto-internal) APIs." -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.cipher]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "0.4.3 -> 0.4.4" -notes = "Adds panics to prevent a block size of zero from causing unsoundness." -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - [[audits.zcash.audits.constant_time_eq]] who = "Jack Grigg " criteria = "safe-to-deploy" From d6078a41c0ab14919600172bf9a44febe2340527 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:23:39 -0700 Subject: [PATCH 34/81] move rpc adapter tests to slow (#5575) --- ironfish/src/rpc/adapters/tests/adapterTest.ts | 12 ++++++------ ...ttp.adapter.test.ts => http.adapter.test.slow.ts} | 2 +- ...{ipc.adapter.test.ts => ipc.adapter.test.slow.ts} | 0 ...{tcp.adapter.test.ts => tcp.adapter.test.slow.ts} | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) rename ironfish/src/rpc/adapters/tests/{http.adapter.test.ts => http.adapter.test.slow.ts} (97%) rename ironfish/src/rpc/adapters/tests/{ipc.adapter.test.ts => ipc.adapter.test.slow.ts} (100%) rename ironfish/src/rpc/adapters/tests/{tcp.adapter.test.ts => tcp.adapter.test.slow.ts} (99%) diff --git a/ironfish/src/rpc/adapters/tests/adapterTest.ts b/ironfish/src/rpc/adapters/tests/adapterTest.ts index 03fdec4c6a..65b618bf99 100644 --- a/ironfish/src/rpc/adapters/tests/adapterTest.ts +++ b/ironfish/src/rpc/adapters/tests/adapterTest.ts @@ -62,7 +62,7 @@ export function createAdapterTest( await adapter.stop() expect(adapter.started).toBe(false) - }, 20000) + }) it('should send and receive message', async () => { router.routes.register('foo/bar', yup.string(), (request) => { @@ -71,7 +71,7 @@ export function createAdapterTest( const response = await client.request('foo/bar', 'hello world').waitForEnd() expect(response.content).toBe('hello world') - }, 20000) + }) it('should stream message', async () => { router.routes.register('foo/bar', yup.object({}), (request) => { @@ -86,7 +86,7 @@ export function createAdapterTest( await response.waitForEnd() expect(response.content).toBe(undefined) - }, 20000) + }) it('should not crash on disconnect while streaming', async () => { const [waitPromise, waitResolve] = PromiseUtils.split() @@ -99,7 +99,7 @@ export function createAdapterTest( expect.assertions(0) await next - }, 20000) + }) it('should handle errors', async () => { router.routes.register('foo/bar', yup.object({}), () => { @@ -114,7 +114,7 @@ export function createAdapterTest( code: 'hello-error', codeMessage: 'hello error', }) - }, 20000) + }) it('should handle request errors', async () => { // Requires this @@ -133,5 +133,5 @@ export function createAdapterTest( // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment codeMessage: expect.stringContaining('this must be defined'), }) - }, 20000) + }) } diff --git a/ironfish/src/rpc/adapters/tests/http.adapter.test.ts b/ironfish/src/rpc/adapters/tests/http.adapter.test.slow.ts similarity index 97% rename from ironfish/src/rpc/adapters/tests/http.adapter.test.ts rename to ironfish/src/rpc/adapters/tests/http.adapter.test.slow.ts index 594acbda6b..711613370d 100644 --- a/ironfish/src/rpc/adapters/tests/http.adapter.test.ts +++ b/ironfish/src/rpc/adapters/tests/http.adapter.test.slow.ts @@ -11,7 +11,7 @@ import { IRpcAdapter } from '../adapter' import { RpcHttpAdapter } from '../httpAdapter' import { createAdapterTest } from './adapterTest' -describe('TlsAdapter', () => { +describe('HttpAdapter', () => { let mitm: ReturnType let client: RpcHttpClient let adapter: RpcHttpAdapter diff --git a/ironfish/src/rpc/adapters/tests/ipc.adapter.test.ts b/ironfish/src/rpc/adapters/tests/ipc.adapter.test.slow.ts similarity index 100% rename from ironfish/src/rpc/adapters/tests/ipc.adapter.test.ts rename to ironfish/src/rpc/adapters/tests/ipc.adapter.test.slow.ts diff --git a/ironfish/src/rpc/adapters/tests/tcp.adapter.test.ts b/ironfish/src/rpc/adapters/tests/tcp.adapter.test.slow.ts similarity index 99% rename from ironfish/src/rpc/adapters/tests/tcp.adapter.test.ts rename to ironfish/src/rpc/adapters/tests/tcp.adapter.test.slow.ts index 887f21fa71..12e07a27b7 100644 --- a/ironfish/src/rpc/adapters/tests/tcp.adapter.test.ts +++ b/ironfish/src/rpc/adapters/tests/tcp.adapter.test.slow.ts @@ -75,7 +75,7 @@ describe('TlsAdapter', () => { const response = await client.request('foo/bar', 'hello world').waitForEnd() expect(response.content).toBe('hello world') - }, 20000) + }) it('should reject when authentication failed', async () => { adapter.enableAuthentication = true @@ -117,5 +117,5 @@ describe('TlsAdapter', () => { code: RPC_ERROR_CODES.UNAUTHENTICATED, codeMessage: expect.stringContaining('Missing authentication token'), }) - }, 20000) + }) }) From 06fc04bb7cdf767b6dca3083145129461344025a Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Wed, 23 Oct 2024 17:19:05 -0400 Subject: [PATCH 35/81] Add createdAt to createAccount RPC (#5569) --- .../src/rpc/routes/wallet/create.test.slow.ts | 57 ++++++++++++++++++- .../src/rpc/routes/wallet/createAccount.ts | 18 +++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/ironfish/src/rpc/routes/wallet/create.test.slow.ts b/ironfish/src/rpc/routes/wallet/create.test.slow.ts index 28b2405d3e..03202e8671 100644 --- a/ironfish/src/rpc/routes/wallet/create.test.slow.ts +++ b/ironfish/src/rpc/routes/wallet/create.test.slow.ts @@ -10,7 +10,7 @@ import { createRouteTest } from '../../../testUtilities/routeTest' import { RPC_ERROR_CODES } from '../../adapters' import { RpcRequestError } from '../../clients/errors' -describe('Route wallet/create', () => { +describe('Route wallet/createAccount', () => { jest.setTimeout(15000) const routeTest = createRouteTest() @@ -20,6 +20,10 @@ describe('Route wallet/create', () => { it('should create an account', async () => { await routeTest.node.wallet.createAccount('existingAccount', { setDefault: true }) + const createdAtHead = { + hash: routeTest.node.chain.head.hash, + sequence: routeTest.node.chain.head.sequence, + } const name = uuid() @@ -35,6 +39,7 @@ describe('Route wallet/create', () => { expect(account).toMatchObject({ name: name, publicAddress: response.content.publicAddress, + createdAt: createdAtHead, }) }) @@ -88,4 +93,54 @@ describe('Route wallet/create', () => { expect(scanSpy).toHaveBeenCalled() }) + + it('should set account createdAt if passed', async () => { + const name = uuid() + const createdAt = 10 + + const response = await routeTest.client.wallet.createAccount({ + name, + createdAt, + }) + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + publicAddress: expect.any(String), + isDefaultAccount: true, + }) + + const account = routeTest.node.wallet.getAccountByName(name) + expect(account).toMatchObject({ + name: name, + publicAddress: response.content.publicAddress, + createdAt: { + hash: Buffer.alloc(32, 0), + sequence: 10, + }, + }) + }) + + it('should set account createdAt to null', async () => { + const name = uuid() + + const response = await routeTest.client.wallet.createAccount({ + name, + createdAt: null, + }) + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + publicAddress: expect.any(String), + isDefaultAccount: true, + }) + + const account = routeTest.node.wallet.getAccountByName(name) + expect(account).toMatchObject({ + name: name, + publicAddress: response.content.publicAddress, + createdAt: null, + }) + }) }) diff --git a/ironfish/src/rpc/routes/wallet/createAccount.ts b/ironfish/src/rpc/routes/wallet/createAccount.ts index 4a9fb5e32a..85b4a565b4 100644 --- a/ironfish/src/rpc/routes/wallet/createAccount.ts +++ b/ironfish/src/rpc/routes/wallet/createAccount.ts @@ -18,7 +18,12 @@ import { AssertHasRpcContext } from '../rpcContext' * Hence, we're adding a new createAccount endpoint and will eventually sunset the create endpoint. */ -export type CreateAccountRequest = { name: string; default?: boolean } +export type CreateAccountRequest = { + name: string + default?: boolean + createdAt?: number | null +} + export type CreateAccountResponse = { name: string publicAddress: string @@ -29,6 +34,7 @@ export const CreateAccountRequestSchema: yup.ObjectSchema .object({ name: yup.string().defined(), default: yup.boolean().optional(), + createdAt: yup.number().optional().nullable(), }) .defined() @@ -46,9 +52,17 @@ routes.register( async (request, context): Promise => { AssertHasRpcContext(request, context, 'wallet') + const createdAt = + typeof request.data.createdAt === 'number' + ? { + hash: Buffer.alloc(32, 0), + sequence: request.data.createdAt, + } + : request.data.createdAt + let account try { - account = await context.wallet.createAccount(request.data.name) + account = await context.wallet.createAccount(request.data.name, { createdAt }) } catch (e) { if (e instanceof DuplicateAccountNameError) { throw new RpcValidationError(e.message, 400, RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME) From e43cb00bd86ff0de465eb92c09867f9549f88aa2 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:35:54 -0700 Subject: [PATCH 36/81] shard unit tests in CI for faster CI runs (#5572) * split main unit tests across 2 processes to increase ci speed * use 3 shards for regular tests and 2 shards for slow tests * explicitly define each shard the strategy.job_total figure can easily change, which would cause pretty big problems with our tests. if we manually define the entire shard piece instead of trying to calculate it, this makes the job more robust to future changes. --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3fbd3e3fe..69c8ef0c10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,9 @@ jobs: test: name: Test runs-on: ubuntu-latest + strategy: + matrix: + shard: [1/3, 2/3, 3/3] steps: - name: Check out Git repository @@ -63,7 +66,7 @@ jobs: run: yarn --non-interactive --frozen-lockfile - name: Run tests - run: yarn test:coverage --maxWorkers=2 --workerIdleMemoryLimit=2000MB + run: yarn test:coverage --maxWorkers=2 --workerIdleMemoryLimit=2000MB --shard=${{ matrix.shard }} - name: Check for missing fixtures run: | @@ -84,6 +87,9 @@ jobs: testslow: name: Slow Tests runs-on: ubuntu-latest + strategy: + matrix: + shard: [1/2, 2/2] steps: - name: Check out Git repository @@ -105,7 +111,7 @@ jobs: run: yarn --non-interactive --frozen-lockfile - name: Run slow tests & coverage - run: yarn test:slow:coverage --maxWorkers=2 --workerIdleMemoryLimit=2000MB + run: yarn test:slow:coverage --maxWorkers=2 --workerIdleMemoryLimit=2000MB --shard=${{ matrix.shard }} - name: Check for missing fixtures run: | From 4fdd27cb05049ab79d8459de8aa55425f4451c9a Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 22 Oct 2024 15:16:39 -0700 Subject: [PATCH 37/81] Change the `ironfish-frost` dependency to use the crate from crates.io --- Cargo.lock | 44 +++++++++++++++++---------------- ironfish-rust-nodejs/Cargo.toml | 2 +- ironfish-rust/Cargo.toml | 2 +- supply-chain/audits.toml | 12 +++++++++ supply-chain/config.toml | 7 ------ 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfb4a048eb..55ea830c0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1524,19 +1524,39 @@ dependencies = [ [[package]] name = "ironfish-frost" version = "0.1.0" -source = "git+https://github.com/iron-fish/ironfish-frost.git?branch=main#d4681df8a5a613fd9e716212aae3be8602acd227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bee779ebf008aa92e1bb90993a47ab730065a8d39f5fa6630cde1ddfcf56a46" dependencies = [ "blake3", "chacha20 0.9.1", "chacha20poly1305 0.10.1", "ed25519-dalek", + "ironfish-reddsa", "rand_chacha", "rand_core", - "reddsa 0.5.1", "siphasher", "x25519-dalek", ] +[[package]] +name = "ironfish-reddsa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb4aecfd3334f0128215ce47f57360d91d68ff9d6fd890e1faca3c79b2bfa28d" +dependencies = [ + "blake2b_simd", + "byteorder", + "frost-rerandomized", + "group 0.13.0", + "hex", + "jubjub 0.10.0", + "pasta_curves 0.5.0", + "rand_core", + "serde", + "thiserror", + "zeroize", +] + [[package]] name = "ironfish-rust-nodejs" version = "0.1.0" @@ -1960,7 +1980,7 @@ dependencies = [ "nonempty", "pasta_curves 0.4.1", "rand", - "reddsa 0.3.0", + "reddsa", "serde", "subtle", "tracing", @@ -2260,24 +2280,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "reddsa" -version = "0.5.1" -source = "git+https://github.com/ZcashFoundation/reddsa.git?rev=ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead#ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead" -dependencies = [ - "blake2b_simd", - "byteorder", - "frost-rerandomized", - "group 0.13.0", - "hex", - "jubjub 0.10.0", - "pasta_curves 0.5.0", - "rand_core", - "serde", - "thiserror", - "zeroize", -] - [[package]] name = "redjubjub" version = "0.5.0" diff --git a/ironfish-rust-nodejs/Cargo.toml b/ironfish-rust-nodejs/Cargo.toml index 69455959fa..1010ac58c5 100644 --- a/ironfish-rust-nodejs/Cargo.toml +++ b/ironfish-rust-nodejs/Cargo.toml @@ -30,7 +30,7 @@ stats = ["ironfish/note-encryption-stats", "jubjub/stats", "dep:signal-hook"] base64 = "0.13.0" fish_hash = "0.3.0" ironfish = { path = "../ironfish-rust" } -ironfish-frost = { git = "https://github.com/iron-fish/ironfish-frost.git", branch = "main" } +ironfish-frost = { version = "0.1.0" } napi = { version = "2.14.4", features = ["napi6"] } napi-derive = "2.14.6" jubjub = { git = "https://github.com/iron-fish/jubjub.git", branch = "blstrs", features = ["multiply-many"] } diff --git a/ironfish-rust/Cargo.toml b/ironfish-rust/Cargo.toml index 2ecfa87512..f68e859d12 100644 --- a/ironfish-rust/Cargo.toml +++ b/ironfish-rust/Cargo.toml @@ -43,7 +43,7 @@ chacha20poly1305 = "0.10.1" crypto_box = { version = "0.9", features = ["std"] } ff = "0.12.0" group = "0.12.0" -ironfish-frost = { git = "https://github.com/iron-fish/ironfish-frost.git", branch = "main" } +ironfish-frost = { version = "0.1.0" } fish_hash = "0.3.0" ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" } jubjub = { git = "https://github.com/iron-fish/jubjub.git", branch = "blstrs", features = ["multiply-many"] } diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 7f9ec833f9..27cd8c2e7d 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -63,6 +63,18 @@ who = "Andrea " criteria = "safe-to-deploy" delta = "1.9.3 -> 2.2.6" +[[audits.ironfish-frost]] +who = "andrea " +criteria = "safe-to-deploy" +version = "0.1.0" +notes = "Crate owned by the Iron Fish organization" + +[[audits.ironfish-reddsa]] +who = "andrea " +criteria = "safe-to-deploy" +version = "0.1.0" +notes = "Crate owned by the Iron Fish organization" + [[audits.jubjub]] who = "Andrea " criteria = "safe-to-deploy" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 0c9cc9a4e6..e9c2ba3663 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -40,9 +40,6 @@ audit-as-crates-io = true [policy."jubjub:0.9.0@git:531157cfa7b81ade207e819ef50c563843b10e30"] audit-as-crates-io = true -[policy.reddsa] -audit-as-crates-io = true - [policy.zcash_address] audit-as-crates-io = true @@ -614,10 +611,6 @@ criteria = "safe-to-deploy" version = "0.8.5" criteria = "safe-to-deploy" -[[exemptions.reddsa]] -version = "0.5.1@git:ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead" -criteria = "safe-to-deploy" - [[exemptions.redjubjub]] version = "0.5.0" criteria = "safe-to-deploy" From 4229f7770ff8c79aefc72c0a4221932cdef24cd4 Mon Sep 17 00:00:00 2001 From: jowparks Date: Thu, 24 Oct 2024 13:01:02 -0700 Subject: [PATCH 38/81] Circuit struct serialization (#5565) * move proof generation key extension to ironfish-zkp * circuit struct serialization * fix review comments, cleanup --- ironfish-zkp/src/circuits/mint_asset.rs | 81 ++++++ ironfish-zkp/src/circuits/output.rs | 185 +++++++++++++- ironfish-zkp/src/circuits/spend.rs | 230 +++++++++++++++++- ironfish-zkp/src/circuits/util.rs | 33 +++ .../src/primitives/proof_generation_key.rs | 7 + .../src/primitives/value_commitment.rs | 59 ++++- 6 files changed, 591 insertions(+), 4 deletions(-) diff --git a/ironfish-zkp/src/circuits/mint_asset.rs b/ironfish-zkp/src/circuits/mint_asset.rs index f9f9de6fc3..b32a0ef276 100644 --- a/ironfish-zkp/src/circuits/mint_asset.rs +++ b/ironfish-zkp/src/circuits/mint_asset.rs @@ -3,6 +3,7 @@ use bellperson::{ Circuit, }; use ff::PrimeField; +use std::io::{Read, Write}; use zcash_proofs::{ circuit::ecc, constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, @@ -12,6 +13,9 @@ use crate::{ constants::{proof::PUBLIC_KEY_GENERATOR, CRH_IVK_PERSONALIZATION}, ProofGenerationKey, }; +use byteorder::{ReadBytesExt, WriteBytesExt}; + +use super::util::FromBytes; pub struct MintAsset { /// Key required to construct proofs for a particular spending key @@ -22,6 +26,39 @@ pub struct MintAsset { pub public_key_randomness: Option, } +impl MintAsset { + pub fn write(&self, mut writer: W) -> std::io::Result<()> { + if let Some(ref proof_generation_key) = self.proof_generation_key { + writer.write_u8(1)?; + writer.write_all(proof_generation_key.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref public_key_randomness) = self.public_key_randomness { + writer.write_u8(1)?; + writer.write_all(public_key_randomness.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + Ok(()) + } + + pub fn read(mut reader: R) -> std::io::Result { + let mut proof_generation_key = None; + if reader.read_u8()? == 1 { + proof_generation_key = Some(ProofGenerationKey::read(&mut reader)?); + } + let mut public_key_randomness = None; + if reader.read_u8()? == 1 { + public_key_randomness = Some(jubjub::Fr::read(&mut reader)?); + } + Ok(MintAsset { + proof_generation_key, + public_key_randomness, + }) + } +} + impl Circuit for MintAsset { fn synthesize>( self, @@ -181,4 +218,48 @@ mod test { // Sanity check assert!(cs.verify(&public_inputs)); } + + #[test] + fn test_mint_asset_read_write() { + // Seed a fixed RNG for determinism in the test + let mut rng = StdRng::seed_from_u64(0); + + // Create a MintAsset instance with random data + let proof_generation_key = ProofGenerationKey::new( + jubjub::SubgroupPoint::random(&mut rng), + jubjub::Fr::random(&mut rng), + ); + let public_key_randomness = jubjub::Fr::random(&mut rng); + + let mint_asset = MintAsset { + proof_generation_key: Some(proof_generation_key.clone()), + public_key_randomness: Some(public_key_randomness), + }; + + let mut buffer = vec![]; + mint_asset.write(&mut buffer).unwrap(); + + let deserialized_mint_asset = MintAsset::read(&buffer[..]).unwrap(); + + assert_eq!( + mint_asset.proof_generation_key.clone().unwrap().ak, + deserialized_mint_asset + .proof_generation_key + .clone() + .unwrap() + .ak + ); + assert_eq!( + mint_asset.proof_generation_key.clone().unwrap().nsk, + deserialized_mint_asset + .proof_generation_key + .clone() + .unwrap() + .nsk + ); + assert_eq!( + mint_asset.public_key_randomness.unwrap(), + deserialized_mint_asset.public_key_randomness.unwrap() + ); + } } diff --git a/ironfish-zkp/src/circuits/output.rs b/ironfish-zkp/src/circuits/output.rs index d7bb97760a..25f7c3fdc5 100644 --- a/ironfish-zkp/src/circuits/output.rs +++ b/ironfish-zkp/src/circuits/output.rs @@ -1,8 +1,11 @@ +use std::io::{Read, Write}; + +use byteorder::{ReadBytesExt, WriteBytesExt}; use ff::PrimeField; use bellperson::{gadgets::blake2s, Circuit, ConstraintSystem, SynthesisError}; -use group::Curve; +use group::{Curve, GroupEncoding}; use jubjub::SubgroupPoint; use zcash_proofs::{ @@ -20,7 +23,7 @@ use crate::{ ProofGenerationKey, }; -use super::util::expose_value_commitment; +use super::util::{expose_value_commitment, FromBytes}; use bellperson::gadgets::boolean; /// This is a circuit instance inspired from ZCash's `Output` circuit in the Sapling protocol @@ -49,6 +52,87 @@ pub struct Output { pub ar: Option, } +impl Output { + pub fn write(&self, mut writer: W) -> std::io::Result<()> { + if let Some(ref value_commitment) = self.value_commitment { + writer.write_u8(1)?; + writer.write_all(value_commitment.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + writer.write_all(&self.asset_id)?; + if let Some(ref payment_address) = self.payment_address { + writer.write_u8(1)?; + writer.write_all(payment_address.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref commitment_randomness) = self.commitment_randomness { + writer.write_u8(1)?; + writer.write_all(commitment_randomness.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref esk) = self.esk { + writer.write_u8(1)?; + writer.write_all(esk.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref proof_generation_key) = self.proof_generation_key { + writer.write_u8(1)?; + writer.write_all(proof_generation_key.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref ar) = self.ar { + writer.write_u8(1)?; + writer.write_all(ar.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + Ok(()) + } + + pub fn read(mut reader: R) -> std::io::Result { + let mut value_commitment = None; + if reader.read_u8()? == 1 { + value_commitment = Some(ValueCommitment::read(&mut reader)?); + } + let mut asset_id = [0u8; ASSET_ID_LENGTH]; + reader.read_exact(&mut asset_id)?; + let mut payment_address = None; + if reader.read_u8()? == 1 { + payment_address = Some(SubgroupPoint::read(&mut reader)?); + } + let mut commitment_randomness = None; + if reader.read_u8()? == 1 { + commitment_randomness = Some(jubjub::Fr::read(&mut reader)?); + } + let mut esk = None; + if reader.read_u8()? == 1 { + esk = Some(jubjub::Fr::read(&mut reader)?); + } + let mut proof_generation_key = None; + if reader.read_u8()? == 1 { + proof_generation_key = Some(ProofGenerationKey::read(&mut reader)?); + } + let mut ar = None; + if reader.read_u8()? == 1 { + ar = Some(jubjub::Fr::read(&mut reader)?); + } + Ok(Output { + value_commitment, + asset_id, + payment_address, + commitment_randomness, + esk, + proof_generation_key, + ar, + }) + } +} + impl Circuit for Output { fn synthesize>( self, @@ -318,6 +402,10 @@ mod test { ar: Some(ar), }; + let mut writer = vec![]; + instance.write(&mut writer).unwrap(); + let _output = Output::read(&writer[..]).unwrap(); + instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); @@ -366,4 +454,97 @@ mod test { } } } + + #[test] + fn test_output_read_write() { + let mut rng = StdRng::seed_from_u64(0); + + for _ in 0..5 { + let mut asset_id = [0u8; 32]; + let asset_generator = loop { + rng.fill(&mut asset_id[..]); + + if let Some(point) = asset_hash_to_point(&asset_id) { + break point; + } + }; + + let value_commitment_randomness = jubjub::Fr::random(&mut rng); + let note_commitment_randomness = jubjub::Fr::random(&mut rng); + let value_commitment = ValueCommitment { + value: rng.next_u64(), + randomness: value_commitment_randomness, + asset_generator, + }; + + let nsk = jubjub::Fr::random(&mut rng); + let ak = jubjub::SubgroupPoint::random(&mut rng); + let esk = jubjub::Fr::random(&mut rng); + let ar = jubjub::Fr::random(&mut rng); + + let proof_generation_key = ProofGenerationKey::new(ak, nsk); + + let viewing_key = proof_generation_key.to_viewing_key(); + + let payment_address = *PUBLIC_KEY_GENERATOR * viewing_key.ivk().0; + + let output = Output { + value_commitment: Some(value_commitment.clone()), + payment_address: Some(payment_address), + commitment_randomness: Some(note_commitment_randomness), + esk: Some(esk), + asset_id, + proof_generation_key: Some(proof_generation_key.clone()), + ar: Some(ar), + }; + + // Ser/de + let mut writer = vec![]; + output.write(&mut writer).unwrap(); + let deserialized_output: Output = Output::read(&writer[..]).unwrap(); + assert_eq!( + output.value_commitment.clone().unwrap().value, + deserialized_output.value_commitment.clone().unwrap().value + ); + assert_eq!( + output.value_commitment.clone().unwrap().randomness, + deserialized_output + .value_commitment + .clone() + .unwrap() + .randomness + ); + assert_eq!( + output.value_commitment.clone().unwrap().asset_generator, + deserialized_output + .value_commitment + .clone() + .unwrap() + .asset_generator + ); + + assert_eq!(output.asset_id, deserialized_output.asset_id); + assert_eq!(output.payment_address, deserialized_output.payment_address); + assert_eq!( + output.commitment_randomness, + deserialized_output.commitment_randomness + ); + assert_eq!(output.esk, deserialized_output.esk); + + assert_eq!( + output.proof_generation_key.clone().unwrap().ak, + deserialized_output.proof_generation_key.clone().unwrap().ak + ); + assert_eq!( + output.proof_generation_key.clone().unwrap().nsk, + deserialized_output + .proof_generation_key + .clone() + .unwrap() + .nsk + ); + + assert_eq!(output.ar, deserialized_output.ar); + } + } } diff --git a/ironfish-zkp/src/circuits/spend.rs b/ironfish-zkp/src/circuits/spend.rs index 82065ecec6..e339f1999f 100644 --- a/ironfish-zkp/src/circuits/spend.rs +++ b/ironfish-zkp/src/circuits/spend.rs @@ -1,12 +1,16 @@ +use std::io::{Read, Write}; + use bellperson::{Circuit, ConstraintSystem, SynthesisError}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::{Field, PrimeField}; +use group::GroupEncoding; use jubjub::SubgroupPoint; use crate::constants::{CRH_IVK_PERSONALIZATION, PRF_NF_PERSONALIZATION}; use crate::ProofGenerationKey; use crate::{constants::proof::PUBLIC_KEY_GENERATOR, primitives::ValueCommitment}; -use super::util::expose_value_commitment; +use super::util::{expose_value_commitment, FromBytes}; use bellperson::gadgets::blake2s; use bellperson::gadgets::boolean; use bellperson::gadgets::multipack; @@ -50,6 +54,117 @@ pub struct Spend { pub sender_address: Option, } +impl Spend { + pub fn write(&self, mut writer: W) -> std::io::Result<()> { + if let Some(ref value_commitment) = self.value_commitment { + writer.write_u8(1)?; + writer.write_all(&value_commitment.to_bytes())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref proof_generation_key) = self.proof_generation_key { + writer.write_u8(1)?; + writer.write_all(proof_generation_key.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref payment_address) = self.payment_address { + writer.write_u8(1)?; + writer.write_all(payment_address.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref commitment_randomness) = self.commitment_randomness { + writer.write_u8(1)?; + writer.write_all(commitment_randomness.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref ar) = self.ar { + writer.write_u8(1)?; + writer.write_all(ar.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + writer.write_all((self.auth_path.len() as u64).to_le_bytes().as_ref())?; + for auth_path in &self.auth_path { + match auth_path { + Some((val, flag)) => { + writer.write_u8(1)?; + writer.write_all(&val.to_bytes_le())?; + writer.write_u8(*flag as u8)?; + } + None => writer.write_u8(0)?, + } + } + if let Some(anchor) = &self.anchor { + writer.write_u8(1)?; + writer.write_all(anchor.to_bytes_le().as_ref())?; + } else { + writer.write_u8(0)?; + } + if let Some(ref sender_address) = self.sender_address { + writer.write_u8(1)?; + writer.write_all(sender_address.to_bytes().as_ref())?; + } else { + writer.write_u8(0)?; + } + Ok(()) + } + + pub fn read(mut reader: R) -> std::io::Result { + let mut value_commitment = None; + if reader.read_u8()? == 1 { + value_commitment = Some(ValueCommitment::read(&mut reader)?); + } + let mut proof_generation_key = None; + if reader.read_u8()? == 1 { + proof_generation_key = Some(ProofGenerationKey::read(&mut reader)?); + } + let mut payment_address = None; + if reader.read_u8()? == 1 { + payment_address = Some(SubgroupPoint::read(&mut reader)?); + } + let mut commitment_randomness = None; + if reader.read_u8()? == 1 { + commitment_randomness = Some(jubjub::Fr::read(&mut reader)?); + } + let mut ar = None; + if reader.read_u8()? == 1 { + ar = Some(jubjub::Fr::read(&mut reader)?); + } + let len = reader.read_u64::().unwrap(); + let mut auth_path = vec![]; + for _ in 0..len { + if reader.read_u8()? == 1 { + let val = blstrs::Scalar::read(&mut reader)?; + let flag = reader.read_u8()? == 1; + auth_path.push(Some((val, flag))); + } else { + auth_path.push(None); + } + } + let mut anchor = None; + if reader.read_u8()? == 1 { + anchor = Some(blstrs::Scalar::read(&mut reader)?); + } + let mut sender_address = None; + if reader.read_u8()? == 1 { + sender_address = Some(SubgroupPoint::read(&mut reader)?); + } + Ok(Spend { + value_commitment, + proof_generation_key, + payment_address, + commitment_randomness, + ar, + auth_path, + anchor, + sender_address, + }) + } +} + impl Circuit for Spend { fn synthesize>( self, @@ -655,4 +770,117 @@ mod test { } } } + + #[test] + fn test_spend_read_write() { + let mut rng = StdRng::seed_from_u64(0); + + let value_commitment = ValueCommitment { + value: rng.next_u64(), + randomness: jubjub::Fr::random(&mut rng), + asset_generator: (*VALUE_COMMITMENT_VALUE_GENERATOR).into(), + }; + + let proof_generation_key = ProofGenerationKey::new( + jubjub::SubgroupPoint::random(&mut rng), + jubjub::Fr::random(&mut rng), + ); + + let viewing_key = proof_generation_key.to_viewing_key(); + + let payment_address = *PUBLIC_KEY_GENERATOR * viewing_key.ivk().0; + + let commitment_randomness = jubjub::Fr::random(&mut rng); + let auth_path = vec![Some((blstrs::Scalar::random(&mut rng), rng.next_u32() % 2 != 0)); 32]; + let ar = jubjub::Fr::random(&mut rng); + + let sender_address = jubjub::SubgroupPoint::random(&mut rng); + + let anchor = blstrs::Scalar::random(&mut rng); + + let spend = Spend { + value_commitment: Some(value_commitment.clone()), + proof_generation_key: Some(proof_generation_key.clone()), + payment_address: Some(payment_address), + commitment_randomness: Some(commitment_randomness), + ar: Some(ar), + auth_path: auth_path.clone(), + anchor: Some(anchor), + sender_address: Some(sender_address), + }; + + let mut buffer = vec![]; + spend.write(&mut buffer).unwrap(); + + let deserialized_spend = Spend::read(&buffer[..]).unwrap(); + assert_eq!( + spend.value_commitment.clone().unwrap().value, + deserialized_spend.value_commitment.clone().unwrap().value + ); + assert_eq!( + spend.value_commitment.clone().unwrap().randomness, + deserialized_spend + .value_commitment + .clone() + .unwrap() + .randomness + ); + assert_eq!( + spend + .value_commitment + .clone() + .unwrap() + .asset_generator + .to_bytes(), + deserialized_spend + .value_commitment + .clone() + .unwrap() + .asset_generator + .to_bytes() + ); + + assert_eq!( + spend.proof_generation_key.clone().unwrap().ak.to_bytes(), + deserialized_spend + .proof_generation_key + .clone() + .unwrap() + .ak + .to_bytes() + ); + assert_eq!( + spend.proof_generation_key.clone().unwrap().nsk, + deserialized_spend.proof_generation_key.clone().unwrap().nsk + ); + + assert_eq!( + spend.payment_address.unwrap().to_bytes(), + deserialized_spend.payment_address.unwrap().to_bytes() + ); + + assert_eq!( + spend.commitment_randomness, + deserialized_spend.commitment_randomness + ); + + assert_eq!(spend.ar, deserialized_spend.ar); + + assert_eq!(spend.auth_path.len(), deserialized_spend.auth_path.len()); + for (ap1, ap2) in spend + .auth_path + .iter() + .zip(deserialized_spend.auth_path.iter()) + { + assert_eq!((*ap1).unwrap().0, (*ap2).unwrap().0); + assert_eq!((*ap1).unwrap().1, (*ap2).unwrap().1); + } + + assert_eq!(spend.anchor, deserialized_spend.anchor); + + assert_eq!( + spend.sender_address.unwrap().to_bytes(), + deserialized_spend.sender_address.unwrap().to_bytes() + ); + } } diff --git a/ironfish-zkp/src/circuits/util.rs b/ironfish-zkp/src/circuits/util.rs index 29d9be4f0a..7cba93e53d 100644 --- a/ironfish-zkp/src/circuits/util.rs +++ b/ironfish-zkp/src/circuits/util.rs @@ -6,6 +6,7 @@ use bellperson::{ ConstraintSystem, SynthesisError, }; use ff::PrimeField; +use group::GroupEncoding; use zcash_proofs::{ circuit::ecc::{self, EdwardsPoint}, constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR, @@ -143,3 +144,35 @@ pub fn assert_valid_asset_generator(reader: R) -> Result; +} + +impl FromBytes for jubjub::SubgroupPoint { + fn read(mut reader: R) -> Result { + let mut bytes = [0u8; 32]; + reader.read_exact(&mut bytes)?; + Option::from(jubjub::SubgroupPoint::from_bytes(&bytes)) + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid point")) + } +} + +impl FromBytes for jubjub::Fr { + fn read(mut reader: R) -> Result { + let mut bytes = [0u8; 32]; + reader.read_exact(&mut bytes)?; + Option::from(jubjub::Fr::from_bytes(&bytes)).ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid field element") + }) + } +} + +impl FromBytes for blstrs::Scalar { + fn read(mut reader: R) -> Result { + let mut bytes = [0u8; 32]; + reader.read_exact(&mut bytes)?; + Option::from(blstrs::Scalar::from_bytes_le(&bytes)) + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid scalar")) + } +} diff --git a/ironfish-zkp/src/primitives/proof_generation_key.rs b/ironfish-zkp/src/primitives/proof_generation_key.rs index 6df8350751..d00c3ee1b2 100644 --- a/ironfish-zkp/src/primitives/proof_generation_key.rs +++ b/ironfish-zkp/src/primitives/proof_generation_key.rs @@ -68,6 +68,13 @@ impl ProofGenerationKey { Ok(ProofGenerationKey(ZcashProofGenerationKey { ak, nsk })) } + pub fn read(mut reader: R) -> Result { + let mut proof_generation_key_bytes: [u8; 64] = [0; 64]; + reader.read_exact(&mut proof_generation_key_bytes)?; + ProofGenerationKey::from_bytes(&proof_generation_key_bytes) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)) + } + pub fn hex_key(&self) -> String { bytes_to_hex(&self.to_bytes()) } diff --git a/ironfish-zkp/src/primitives/value_commitment.rs b/ironfish-zkp/src/primitives/value_commitment.rs index cc38db855c..5937a461e6 100644 --- a/ironfish-zkp/src/primitives/value_commitment.rs +++ b/ironfish-zkp/src/primitives/value_commitment.rs @@ -1,5 +1,8 @@ +use byteorder::{LittleEndian, ReadBytesExt}; use ff::Field; -use group::cofactor::CofactorGroup; +use group::{cofactor::CofactorGroup, GroupEncoding}; + +use jubjub::ExtendedPoint; use rand::thread_rng; use crate::constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR; @@ -26,6 +29,34 @@ impl ValueCommitment { (self.asset_generator.clear_cofactor() * jubjub::Fr::from(self.value)) + (*VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness) } + + pub fn to_bytes(&self) -> [u8; 72] { + let mut res = [0u8; 72]; + res[0..8].copy_from_slice(&self.value.to_le_bytes()); + res[8..40].copy_from_slice(&self.randomness.to_bytes()); + res[40..72].copy_from_slice(&self.asset_generator.to_bytes()); + res + } + + pub fn write(&self, mut writer: W) -> Result<(), std::io::Error> { + writer.write_all(&self.to_bytes())?; + Ok(()) + } + + pub fn read(mut reader: R) -> Result { + let value = reader.read_u64::()?; + let mut randomness_bytes = [0u8; 32]; + reader.read_exact(&mut randomness_bytes)?; + let randomness = jubjub::Fr::from_bytes(&randomness_bytes).unwrap(); + let mut asset_generator = [0u8; 32]; + reader.read_exact(&mut asset_generator)?; + let asset_generator = ExtendedPoint::from_bytes(&asset_generator).unwrap(); + Ok(Self { + value, + randomness, + asset_generator, + }) + } } #[cfg(test)] @@ -187,4 +218,30 @@ mod test { value_commitment_two.randomness ); } + + #[test] + fn test_value_commitment_read_write() { + // Seed a fixed rng for determinism in the test + let mut rng = StdRng::seed_from_u64(0); + + let value_commitment = ValueCommitment { + value: 5, + randomness: jubjub::Fr::random(&mut rng), + asset_generator: jubjub::ExtendedPoint::random(&mut rng), + }; + + // Serialize to bytes + let serialized = value_commitment.to_bytes(); + + // Deserialize from bytes + let deserialized = ValueCommitment::read(&serialized[..]).unwrap(); + + // Assert equality + assert_eq!(value_commitment.value, deserialized.value); + assert_eq!(value_commitment.randomness, deserialized.randomness); + assert_eq!( + value_commitment.asset_generator, + deserialized.asset_generator + ); + } } From aa47db1c721bc3482c72ab8eb6bfa4aff26597ab Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:11:10 -0700 Subject: [PATCH 39/81] shard rust unit tests on ci (#5578) * shard rust unit tests on ci uses nextest as a test runner and llvm-cov as the coverage reporter. neither the default test runner nor cargo tarpaulin support sharding, so opting to use nextest for this purpose. * try removing clean artifact step --- .github/workflows/rust_ci.yml | 41 ++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index 8e1e52ab0e..da72f8757c 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -87,21 +87,34 @@ jobs: ironfish_rust: name: Test ironfish-rust runs-on: ubuntu-latest + strategy: + matrix: + shard: [1/2, 2/2] + steps: - uses: actions/checkout@v4 + - name: install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Cache Rust uses: Swatinem/rust-cache@v2 with: shared-key: base # Run tests to collect code coverage - - name: Run cargo-tarpaulin on ironfish-rust + - name: Run tests run: | - wget -O tarpaulin.tar.gz https://github.com/xd009642/tarpaulin/releases/download/0.22.0/cargo-tarpaulin-0.22.0-travis.tar.gz - tar -xzf tarpaulin.tar.gz - mv cargo-tarpaulin ~/.cargo/bin/ - cargo tarpaulin -p ironfish --release --out Xml --avoid-cfg-tarpaulin --skip-clean --timeout 300 -- --test-threads 1 + cargo llvm-cov nextest \ + --no-clean \ + --codecov \ + --output-path codecov.json \ + --package ironfish \ + --release \ + --partition count:${{ matrix.shard }} # Upload code coverage to Codecov - name: Upload to codecov.io @@ -116,18 +129,26 @@ jobs: steps: - uses: actions/checkout@v4 + - name: install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Cache Rust uses: Swatinem/rust-cache@v2 with: shared-key: zkp # Run tests to collect code coverage - - name: Run cargo-tarpaulin on ironfish-zkp + - name: Run tests run: | - wget -O tarpaulin.tar.gz https://github.com/xd009642/tarpaulin/releases/download/0.22.0/cargo-tarpaulin-0.22.0-travis.tar.gz - tar -xzf tarpaulin.tar.gz - mv cargo-tarpaulin ~/.cargo/bin/ - cargo tarpaulin -p ironfish_zkp --release --out Xml --avoid-cfg-tarpaulin --skip-clean --timeout 300 -- --test-threads 1 + cargo llvm-cov nextest \ + --no-clean \ + --codecov \ + --output-path codecov.json \ + --package ironfish_zkp \ + --release # Upload code coverage to Codecov - name: Upload to codecov.io From 3289f76a449d75344a3e4fa825ffbade96bcc990 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:55:05 -0700 Subject: [PATCH 40/81] split very long running test into smaller tests (#5579) this test sometimes takes over 4 minutes on CI. by splitting it up, we normalize the run time across the test shards, creating a more consistent run time across the CI shards. other upside is that if one of these test fails, it will be easier to see at a glance which one is failing by the name. --- ironfish-rust/src/transaction/tests.rs | 140 ++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 17 deletions(-) diff --git a/ironfish-rust/src/transaction/tests.rs b/ironfish-rust/src/transaction/tests.rs index 5190481263..ca73d6e303 100644 --- a/ironfish-rust/src/transaction/tests.rs +++ b/ironfish-rust/src/transaction/tests.rs @@ -438,7 +438,7 @@ fn test_transaction_value_overflows() { } #[test] -fn test_batch_verify_wrong_params() { +fn test_batch_verify_wrong_spend_params() { let rng = &mut thread_rng(); let wrong_spend_params = @@ -457,6 +457,67 @@ fn test_batch_verify_wrong_params() { ) .unwrap(); + let wrong_spend_vk = bellperson::groth16::prepare_verifying_key(&wrong_spend_params.vk); + + // + // TRANSACTION GENERATION + // + let key = SaplingKey::generate_key(); + let other_key = SaplingKey::generate_key(); + + // Native asset + let in_note = Note::new( + key.public_address(), + 42, + "", + NATIVE_ASSET, + key.public_address(), + ); + let out_note = Note::new( + key.public_address(), + 40, + "", + NATIVE_ASSET, + key.public_address(), + ); + + let witness = make_fake_witness(&in_note); + + // Custom asset + let asset = Asset::new(other_key.public_address(), "Othercoin", "").unwrap(); + + let mut proposed_transaction1 = ProposedTransaction::new(TransactionVersion::latest()); + + proposed_transaction1.add_spend(in_note, &witness).unwrap(); + proposed_transaction1.add_output(out_note).unwrap(); + + let transaction1 = proposed_transaction1 + .post(&key, None, 1) + .expect("should be able to post transaction"); + + let mut proposed_transaction2 = ProposedTransaction::new(TransactionVersion::latest()); + proposed_transaction2.add_mint(asset, 5).unwrap(); + + let transaction2 = proposed_transaction2.post(&other_key, None, 0).unwrap(); + // + // END TRANSACTION CREATION + // + + batch_verify_transactions([&transaction1, &transaction2]) + .expect("Should verify using Sapling params"); + internal_batch_verify_transactions( + [&transaction1, &transaction2], + &wrong_spend_vk, + &SAPLING.output_verifying_key, + &SAPLING.mint_verifying_key, + ) + .expect_err("Should not verify if spend verifying key is wrong"); +} + +#[test] +fn test_batch_verify_wrong_output_params() { + let rng = &mut thread_rng(); + let wrong_output_params = bellperson::groth16::generate_random_parameters::( Output { @@ -472,6 +533,67 @@ fn test_batch_verify_wrong_params() { ) .unwrap(); + let wrong_output_vk = bellperson::groth16::prepare_verifying_key(&wrong_output_params.vk); + + // + // TRANSACTION GENERATION + // + let key = SaplingKey::generate_key(); + let other_key = SaplingKey::generate_key(); + + // Native asset + let in_note = Note::new( + key.public_address(), + 42, + "", + NATIVE_ASSET, + key.public_address(), + ); + let out_note = Note::new( + key.public_address(), + 40, + "", + NATIVE_ASSET, + key.public_address(), + ); + + let witness = make_fake_witness(&in_note); + + // Custom asset + let asset = Asset::new(other_key.public_address(), "Othercoin", "").unwrap(); + + let mut proposed_transaction1 = ProposedTransaction::new(TransactionVersion::latest()); + + proposed_transaction1.add_spend(in_note, &witness).unwrap(); + proposed_transaction1.add_output(out_note).unwrap(); + + let transaction1 = proposed_transaction1 + .post(&key, None, 1) + .expect("should be able to post transaction"); + + let mut proposed_transaction2 = ProposedTransaction::new(TransactionVersion::latest()); + proposed_transaction2.add_mint(asset, 5).unwrap(); + + let transaction2 = proposed_transaction2.post(&other_key, None, 0).unwrap(); + // + // END TRANSACTION CREATION + // + + batch_verify_transactions([&transaction1, &transaction2]) + .expect("Should verify using Sapling params"); + internal_batch_verify_transactions( + [&transaction1, &transaction2], + &SAPLING.spend_verifying_key, + &wrong_output_vk, + &SAPLING.mint_verifying_key, + ) + .expect_err("Should not verify if output verifying key is wrong"); +} + +#[test] +fn test_batch_verify_wrong_mint_params() { + let rng = &mut thread_rng(); + let wrong_mint_params = bellperson::groth16::generate_random_parameters::( MintAsset { proof_generation_key: None, @@ -481,8 +603,6 @@ fn test_batch_verify_wrong_params() { ) .unwrap(); - let wrong_spend_vk = bellperson::groth16::prepare_verifying_key(&wrong_spend_params.vk); - let wrong_output_vk = bellperson::groth16::prepare_verifying_key(&wrong_output_params.vk); let wrong_mint_vk = bellperson::groth16::prepare_verifying_key(&wrong_mint_params.vk); // @@ -552,20 +672,6 @@ fn test_batch_verify_wrong_params() { batch_verify_transactions([&transaction1, &transaction2]) .expect("Should verify using Sapling params"); - internal_batch_verify_transactions( - [&transaction1, &transaction2], - &wrong_spend_vk, - &SAPLING.output_verifying_key, - &SAPLING.mint_verifying_key, - ) - .expect_err("Should not verify if spend verifying key is wrong"); - internal_batch_verify_transactions( - [&transaction1, &transaction2], - &SAPLING.spend_verifying_key, - &wrong_output_vk, - &SAPLING.mint_verifying_key, - ) - .expect_err("Should not verify if output verifying key is wrong"); internal_batch_verify_transactions( [&transaction1, &transaction2], &SAPLING.spend_verifying_key, From ce921658d4c151ac24ecfce95a3aba4463867342 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:36:10 -0700 Subject: [PATCH 41/81] handles errors from full dkg sessions (#5580) upgrades '@ironfish/multisig-broker' catches error messages with the 'DKG_SESSION_FULL' error code and throws an appropriate error in 'waitForJoinedSession' changes 'dkg:create' to ask the user if they want to retry joining the session. gives the user an opportunity to wait before immediately trying to rejoin a full session defines error types that were removed from '@ironfish/multisig-broker' --- ironfish-cli/package.json | 2 +- .../commands/wallet/multisig/dkg/create.ts | 20 +++++++++++-------- .../sessionManagers/sessionManager.ts | 12 +++++++++-- yarn.lock | 8 ++++---- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index be2a6fd850..6fec79d0b0 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -60,7 +60,7 @@ "oclif:version": "oclif readme && git add README.md" }, "dependencies": { - "@ironfish/multisig-broker": "0.1.1", + "@ironfish/multisig-broker": "0.3.0", "@ironfish/rust-nodejs": "2.7.0", "@ironfish/sdk": "2.8.1", "@ledgerhq/errors": "6.19.1", diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index 2610b7c3e9..fd427c156e 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -116,14 +116,18 @@ export class DkgCreateCommand extends IronfishCommand { logger: this.logger, }) - const { totalParticipants, minSigners } = await ui.retryStep(async () => { - return sessionManager.startSession({ - totalParticipants: flags.totalParticipants, - minSigners: flags.minSigners, - ledger: flags.ledger, - identity, - }) - }, this.logger) + const { totalParticipants, minSigners } = await ui.retryStep( + async () => { + return sessionManager.startSession({ + totalParticipants: flags.totalParticipants, + minSigners: flags.minSigners, + ledger: flags.ledger, + identity, + }) + }, + this.logger, + true, + ) const { round1 } = await ui.retryStep( async () => { diff --git a/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts b/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts index a6376b18f4..4cf8457704 100644 --- a/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts @@ -2,8 +2,6 @@ * 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 { - IdentityNotAllowedError, - InvalidSessionError, MultisigBrokerErrorCodes, MultisigBrokerUtils, MultisigClient, @@ -113,6 +111,8 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage this.client.onMultisigBrokerError.on((errorMessage) => { if (errorMessage.error.code === MultisigBrokerErrorCodes.SESSION_ID_NOT_FOUND) { clientError = new InvalidSessionError(errorMessage.error.message) + } else if (errorMessage.error.code === MultisigBrokerErrorCodes.DKG_SESSION_FULL) { + clientError = new DkgSessionFullError(errorMessage.error.message) } else if (errorMessage.error.code === MultisigBrokerErrorCodes.IDENTITY_NOT_ALLOWED) { // Throws error immediately instead of deferring to loop, below throw new IdentityNotAllowedError(errorMessage.error.message) @@ -143,3 +143,11 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage abstract getSessionConfig(): Promise } + +export class MultisigSessionError extends Error { + name = this.constructor.name +} + +export class DkgSessionFullError extends MultisigSessionError {} +export class InvalidSessionError extends MultisigSessionError {} +export class IdentityNotAllowedError extends MultisigSessionError {} diff --git a/yarn.lock b/yarn.lock index 1ba0b223f4..c60966bcc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1115,10 +1115,10 @@ dependencies: mute-stream "^1.0.0" -"@ironfish/multisig-broker@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@ironfish/multisig-broker/-/multisig-broker-0.1.1.tgz#9c4abfbe09f8199fb13fb672d0e1714203ed9a60" - integrity sha512-v7KaSvmsx3ihlVRi6er8YyLf7zRHB46uP6Rc2/zVvZqSoEeASViN1bpdDYM4YzuVeqTNQBF6Em79eT4tI40IpA== +"@ironfish/multisig-broker@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@ironfish/multisig-broker/-/multisig-broker-0.3.0.tgz#6779c9fdae428cd59e46d7e300c82a1bb97d1f1a" + integrity sha512-D2hW7GV7SutnFAb0B1DVM9VV3+BWB31AdsCtQLuMeD9z4MHJJYTzzGFjsQ77sFI4lauVSCfPoPmpVQYd/KdxLA== dependencies: "@ironfish/rust-nodejs" "^2.7.0" "@ironfish/sdk" "^2.8.1" From 92564e4b495bad40c6330d506a25e85915bc2900 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:36:53 -0700 Subject: [PATCH 42/81] update fixture ci job to use the right secret (#5581) --- .github/workflows/ci-regenerate-fixtures.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-regenerate-fixtures.yml b/.github/workflows/ci-regenerate-fixtures.yml index 94ccff899d..dc766b127f 100644 --- a/.github/workflows/ci-regenerate-fixtures.yml +++ b/.github/workflows/ci-regenerate-fixtures.yml @@ -45,4 +45,4 @@ jobs: status: ${{ job.status }} notify_when: 'failure' env: - SLACK_WEBHOOK_URL: ${{ secrets.GITHUB_ACTIONS_SLACK_WEBHOOK }} + SLACK_WEBHOOK_URL: ${{ secrets.GH_ACTIONS_SLACK_WEBHOOK }} From e787230a07089b9db8969171598a49d80dc69f0e Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:12:21 -0700 Subject: [PATCH 43/81] allows multisig commands to disable tls without server flag (#5583) removes dependency of tls flag on server flag in 'wallet:multisig:dkg:create' and 'wallet:multisig:sign' allows user to use flag combinations like '--hostname localhost --no-tls' without needing to also set the '--server' flag --- ironfish-cli/src/commands/wallet/multisig/dkg/create.ts | 1 - ironfish-cli/src/commands/wallet/multisig/sign.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts index fd427c156e..c150ce4854 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/create.ts @@ -67,7 +67,6 @@ export class DkgCreateCommand extends IronfishCommand { }), tls: Flags.boolean({ description: 'connect to the multisig server over TLS', - dependsOn: ['server'], allowNo: true, }), minSigners: Flags.integer({ diff --git a/ironfish-cli/src/commands/wallet/multisig/sign.ts b/ironfish-cli/src/commands/wallet/multisig/sign.ts index 5d2494f798..b9a29622d5 100644 --- a/ironfish-cli/src/commands/wallet/multisig/sign.ts +++ b/ironfish-cli/src/commands/wallet/multisig/sign.ts @@ -69,7 +69,6 @@ export class SignMultisigTransactionCommand extends IronfishCommand { }), tls: Flags.boolean({ description: 'connect to the multisig server over TLS', - dependsOn: ['server'], allowNo: true, }), } From a609c6d0d97aa5dca632247c2bf04868577125cb Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:16:45 -0700 Subject: [PATCH 44/81] cleanup incorrect type dependencies (#5582) @types/tar is already defined in dev dependencies, so remove from dependencies @types/keccak is in dependencies, so move to dev dependencies --- ironfish-cli/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index 6fec79d0b0..9324487c84 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -27,6 +27,7 @@ "@types/blessed": "0.1.17", "@types/cli-progress": "3.11.6", "@types/inquirer": "8.2.5", + "@types/keccak": "3.0.4", "@types/node": "18.11.16", "@types/tar": "6.1.1", "chai": "4.2.0", @@ -69,8 +70,6 @@ "@oclif/plugin-help": "6.2.5", "@oclif/plugin-not-found": "3.2.10", "@oclif/plugin-warn-if-update-available": "3.1.8", - "@types/keccak": "3.0.4", - "@types/tar": "6.1.1", "@zondax/ledger-ironfish": "1.0.0", "@zondax/ledger-js": "1.0.1", "axios": "1.7.2", From 022b41fc5cb986f2a367f67c459a41bb5cc3dae0 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:08:10 -0700 Subject: [PATCH 45/81] dont cache yarn (#5584) --- .github/workflows/perf_test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/perf_test.yml b/.github/workflows/perf_test.yml index 6276ce2729..a3787bf95c 100644 --- a/.github/workflows/perf_test.yml +++ b/.github/workflows/perf_test.yml @@ -25,7 +25,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - cache: 'yarn' - name: Install packages run: yarn --non-interactive --frozen-lockfile From 9b13bd8be11f60dbb0b6478f9687878c94a50b46 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:16:10 -0700 Subject: [PATCH 46/81] allows multisig users to enter connection string for session ID (#5586) when prompting for session ID, tries to parse the user input as a connection string. this allows the user to enter a connection string (e.g., 'tcp://session-id:passphrase@hostname:port') in this prompt instead of only the session ID updates MultisigClientSessionManager to delay instantiating the client until connection time to allow the prompt to change the hostname and/or port --- .../sessionManagers/dkgSessionManager.ts | 5 +- .../sessionManagers/sessionManager.ts | 59 ++++++++++++++++--- .../sessionManagers/signingSessionManager.ts | 5 +- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/ironfish-cli/src/utils/multisig/sessionManagers/dkgSessionManager.ts b/ironfish-cli/src/utils/multisig/sessionManagers/dkgSessionManager.ts index 0080268fb5..4758c15184 100644 --- a/ironfish-cli/src/utils/multisig/sessionManagers/dkgSessionManager.ts +++ b/ironfish-cli/src/utils/multisig/sessionManagers/dkgSessionManager.ts @@ -66,10 +66,7 @@ export class MultisigClientDkgSessionManager identity: string }): Promise<{ totalParticipants: number; minSigners: number }> { if (!this.sessionId) { - this.sessionId = await ui.inputPrompt( - 'Enter the ID of a multisig session to join, or press enter to start a new session', - false, - ) + await this.promptSessionConnection() } if (!this.passphrase) { diff --git a/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts b/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts index 4cf8457704..63784b4857 100644 --- a/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts +++ b/ironfish-cli/src/utils/multisig/sessionManagers/sessionManager.ts @@ -7,8 +7,9 @@ import { MultisigClient, SessionDecryptionError, } from '@ironfish/multisig-broker' -import { Assert, Logger, PromiseUtils } from '@ironfish/sdk' +import { Logger, PromiseUtils } from '@ironfish/sdk' import { ux } from '@oclif/core' +import * as ui from '../../../ui' export abstract class MultisigSessionManager { sessionId: string | null = null @@ -24,9 +25,13 @@ export abstract class MultisigSessionManager { } export abstract class MultisigClientSessionManager extends MultisigSessionManager { - client: MultisigClient + _client: MultisigClient | null = null + + hostname: string + port: number sessionId: string | null passphrase: string | null + tls: boolean constructor(options: { logger: Logger @@ -42,16 +47,54 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage const { hostname, port, sessionId, passphrase } = MultisigBrokerUtils.parseConnectionOptions(options) - this.client = MultisigBrokerUtils.createClient(hostname, port, { - tls: options.tls ?? true, - logger: this.logger, - }) - + this.hostname = hostname + this.port = port this.sessionId = sessionId ?? null this.passphrase = passphrase ?? null + this.tls = options.tls ?? true + } + + async promptSessionConnection(): Promise { + const sessionInput = await ui.inputPrompt( + 'Enter the ID of a multisig session to join, or press enter to start a new session', + false, + ) + + try { + const url = new URL(sessionInput) + this.hostname = url.hostname + this.port = Number(url.port) + this.sessionId = url.username + this.passphrase = decodeURI(url.password) + } catch (e) { + if (e instanceof TypeError && e.message.includes('Invalid URL')) { + this.sessionId = sessionInput + } else { + throw e + } + } + } + + get client(): MultisigClient { + if (!this._client) { + throw new Error('MultisigClient has not been initialized') + } + return this._client + } + + protected createClient() { + if (this._client) { + return + } + this._client = MultisigBrokerUtils.createClient(this.hostname, this.port, { + tls: this.tls, + logger: this.logger, + }) } protected async connect(): Promise { + this.createClient() + if (this.client.isConnected()) { return } @@ -94,8 +137,6 @@ export abstract class MultisigClientSessionManager extends MultisigSessionManage } protected async waitForJoinedSession(): Promise { - Assert.isNotNull(this.client) - ux.action.start(`Waiting to join session: ${this.client.sessionId}`) let confirmed = false diff --git a/ironfish-cli/src/utils/multisig/sessionManagers/signingSessionManager.ts b/ironfish-cli/src/utils/multisig/sessionManagers/signingSessionManager.ts index 1f6d954ee5..324103a4e2 100644 --- a/ironfish-cli/src/utils/multisig/sessionManagers/signingSessionManager.ts +++ b/ironfish-cli/src/utils/multisig/sessionManagers/signingSessionManager.ts @@ -60,10 +60,7 @@ export class MultisigClientSigningSessionManager allowedIdentities?: string[] }): Promise<{ numSigners: number; unsignedTransaction: UnsignedTransaction }> { if (!this.sessionId) { - this.sessionId = await ui.inputPrompt( - 'Enter the ID of a multisig session to join, or press enter to start a new session', - false, - ) + await this.promptSessionConnection() } if (!this.passphrase) { From 0157a9173e4fdff6912e3e0ae6ac5a0d2a1ed232 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:16:31 -0700 Subject: [PATCH 47/81] update axios from 1.7.2 to 1.7.7 (#5585) this resolves some security alerts from github --- ironfish-cli/package.json | 2 +- ironfish/package.json | 2 +- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index 9324487c84..7b66d1b06e 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -72,7 +72,7 @@ "@oclif/plugin-warn-if-update-available": "3.1.8", "@zondax/ledger-ironfish": "1.0.0", "@zondax/ledger-js": "1.0.1", - "axios": "1.7.2", + "axios": "1.7.7", "bech32": "2.0.0", "blessed": "0.1.81", "blru": "0.1.6", diff --git a/ironfish/package.json b/ironfish/package.json index 5dd0c99a02..746debaba9 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -24,7 +24,7 @@ "@fast-csv/format": "4.3.5", "@ironfish/rust-nodejs": "2.7.0", "@napi-rs/blake-hash": "1.3.3", - "axios": "1.7.2", + "axios": "1.7.7", "bech32": "2.0.0", "blru": "0.1.6", "buffer": "6.0.3", diff --git a/package.json b/package.json index fc94207240..1b2667085a 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "typescript": "5.0.4" }, "resolutions": { - "axios": "1.7.2", + "axios": "1.7.7", "ejs": "^3.1.7", "handlebars": "4.7.7", "node-notifier": "8.0.1", diff --git a/yarn.lock b/yarn.lock index c60966bcc5..4aa5f7da6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4213,10 +4213,10 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -axios@1.7.2, axios@^1.0.0: - version "1.7.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" - integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== +axios@1.7.7, axios@^1.0.0: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" From 80f304e32a3411d8ddc49f5eddaf38aa7c28454e Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:32:31 -0700 Subject: [PATCH 48/81] removes multisig:server command (#5587) the multisig:server command has moved to the ironfish-services-cli project now that the AWS multisig servers run from the ironfish-services-cli Docker image we can remove the command from ironfish-cli --- .../src/commands/wallet/multisig/server.ts | 75 ------------------- 1 file changed, 75 deletions(-) delete mode 100644 ironfish-cli/src/commands/wallet/multisig/server.ts diff --git a/ironfish-cli/src/commands/wallet/multisig/server.ts b/ironfish-cli/src/commands/wallet/multisig/server.ts deleted file mode 100644 index 747617b917..0000000000 --- a/ironfish-cli/src/commands/wallet/multisig/server.ts +++ /dev/null @@ -1,75 +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 { - IMultisigBrokerAdapter, - MultisigServer, - MultisigTcpAdapter, - MultisigTlsAdapter, -} from '@ironfish/multisig-broker' -import { TlsUtils } from '@ironfish/sdk' -import { Flags } from '@oclif/core' -import { IronfishCommand } from '../../../command' - -export class MultisigServerCommand extends IronfishCommand { - static description = 'start a server to broker messages for a multisig session' - - static flags = { - host: Flags.string({ - description: 'host address for the multisig server', - default: '::', - }), - port: Flags.integer({ - description: 'port for the multisig server', - default: 9035, - }), - tls: Flags.boolean({ - description: 'enable TLS on the multisig server', - allowNo: true, - default: true, - }), - idleSessionTimeout: Flags.integer({ - description: 'time (in ms) to wait before cleaning up idle session data', - char: 'i', - }), - } - - async start(): Promise { - const { flags } = await this.parse(MultisigServerCommand) - - const server = new MultisigServer({ - logger: this.logger, - idleSessionTimeout: flags.idleSessionTimeout, - }) - - let adapter: IMultisigBrokerAdapter - if (flags.tls) { - const fileSystem = this.sdk.fileSystem - const nodeKeyPath = this.sdk.config.get('tlsKeyPath') - const nodeCertPath = this.sdk.config.get('tlsCertPath') - const tlsOptions = await TlsUtils.getTlsOptions( - fileSystem, - nodeKeyPath, - nodeCertPath, - this.logger, - ) - - adapter = new MultisigTlsAdapter({ - logger: this.logger, - host: flags.host, - port: flags.port, - tlsOptions, - }) - } else { - adapter = new MultisigTcpAdapter({ - logger: this.logger, - host: flags.host, - port: flags.port, - }) - } - - server.mount(adapter) - await server.start() - } -} From 7513b1a93dd96c93119e53c8aedac29492d3b585 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:28:42 -0700 Subject: [PATCH 49/81] ensure artifacts folder exists when deploying rust bindings (#5588) --- .github/workflows/deploy-npm-ironfish-rust-nodejs.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/deploy-npm-ironfish-rust-nodejs.yml b/.github/workflows/deploy-npm-ironfish-rust-nodejs.yml index 9eedb960db..881c78d97d 100644 --- a/.github/workflows/deploy-npm-ironfish-rust-nodejs.yml +++ b/.github/workflows/deploy-npm-ironfish-rust-nodejs.yml @@ -42,6 +42,13 @@ jobs: run: ls -R . shell: bash + - name: Verify artifacts exist + run: | + if [ -z "$(ls -A './artifacts')" ]; then + echo "artifacts folder was empty. ironfish-rust-nodejs artifacts were not uploaded correctly." + exit 1 + fi + - name: Move and publish artifacts run: yarn artifacts From f95986dfa10e9c526da42b1f7ae6fcf0bfb7a7e2 Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 22 Oct 2024 16:04:54 -0700 Subject: [PATCH 50/81] Change the Rust depdendencies to use our newly published crates This commit replaces all the dependencies on our forked crates on `github.com` to the corresponding crates on `crates.io`. Specifically: * `bellperson` -> `ironfish-bellperson` * `jubjub` -> `ironfish-jubjub` * `zcash_primitives` -> `ironfish-primitives` * `zcash_proofs` -> `ironfish-proofs` The following crates were also being consumed from `github.com` and have been restored to the original version on `crates.io` (no need to publish a fork): * `equihash` * `f4jumble` * `zcash_address` * `zcash_encoding` * `zcash_encoding` * `zcash_note_encryption` With this commit, `ironfish-zkp` and `ironfish-rust` have 100% of their dependencies on crates on `crates.io`. --- Cargo.lock | 288 ++++++++---------- ironfish-rust-nodejs/Cargo.toml | 4 +- ironfish-rust-nodejs/src/lib.rs | 2 +- .../src/structs/transaction.rs | 2 +- ironfish-rust/Cargo.toml | 4 +- ironfish-rust/src/assets/asset.rs | 2 +- ironfish-rust/src/assets/asset_identifier.rs | 2 +- ironfish-rust/src/errors.rs | 4 +- ironfish-rust/src/frost_utils/account_keys.rs | 4 +- .../src/frost_utils/split_spender_key.rs | 2 +- ironfish-rust/src/keys/ephemeral.rs | 10 +- ironfish-rust/src/keys/mod.rs | 14 +- ironfish-rust/src/keys/public_address.rs | 2 +- ironfish-rust/src/keys/test.rs | 2 +- ironfish-rust/src/keys/util.rs | 2 +- ironfish-rust/src/keys/view_keys.rs | 10 +- ironfish-rust/src/lib.rs | 2 +- ironfish-rust/src/merkle_note.rs | 2 +- ironfish-rust/src/merkle_note_hash.rs | 2 +- ironfish-rust/src/note.rs | 27 +- ironfish-rust/src/serializing/fr.rs | 4 +- ironfish-rust/src/transaction/mints.rs | 25 +- ironfish-rust/src/transaction/mod.rs | 18 +- ironfish-rust/src/transaction/outputs.rs | 18 +- ironfish-rust/src/transaction/spends.rs | 20 +- ironfish-rust/src/transaction/tests.rs | 31 +- ironfish-rust/src/transaction/unsigned.rs | 4 +- ironfish-rust/src/transaction/utils.rs | 2 +- ironfish-zkp/Cargo.toml | 8 +- ironfish-zkp/src/bin/generate_params.rs | 2 +- ironfish-zkp/src/circuits/mint_asset.rs | 34 +-- ironfish-zkp/src/circuits/output.rs | 60 ++-- ironfish-zkp/src/circuits/spend.rs | 99 +++--- ironfish-zkp/src/circuits/util.rs | 18 +- ironfish-zkp/src/constants.rs | 10 +- ironfish-zkp/src/lib.rs | 2 +- .../src/primitives/proof_generation_key.rs | 20 +- .../src/primitives/value_commitment.rs | 42 +-- ironfish-zkp/src/util.rs | 16 +- supply-chain/audits.toml | 90 ++---- supply-chain/config.toml | 49 +-- supply-chain/imports.lock | 14 +- 42 files changed, 448 insertions(+), 525 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55ea830c0d..4bf0da2e0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,33 +134,6 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" -[[package]] -name = "bellperson" -version = "0.24.1" -source = "git+https://github.com/iron-fish/bellperson.git?branch=blstrs#37b9976bcd96986cbdc71ae09fc455015e3dfac0" -dependencies = [ - "bincode", - "blake2s_simd", - "blstrs", - "byteorder", - "crossbeam-channel", - "digest 0.10.6", - "ec-gpu", - "ec-gpu-gen", - "ff 0.12.1", - "group 0.12.1", - "log", - "memmap2", - "pairing", - "rand", - "rand_core", - "rayon", - "rustversion", - "serde", - "sha2 0.10.6", - "thiserror", -] - [[package]] name = "benchmarks" version = "0.1.0" @@ -180,14 +153,14 @@ dependencies = [ [[package]] name = "bip0039" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0830ae4cc96b0617cc912970c2b17e89456fecbf55e8eed53a956f37ab50c41" +checksum = "e68a5a99c65851e7be249f5cf510c0a136f18c9bca32139576d59bd3f577b043" dependencies = [ - "hmac 0.11.0", - "pbkdf2 0.9.0", + "hmac", + "pbkdf2", "rand", - "sha2 0.9.9", + "sha2 0.10.6", "unicode-normalization", "zeroize", ] @@ -679,16 +652,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto_box" version = "0.9.1" @@ -903,7 +866,8 @@ dependencies = [ [[package]] name = "equihash" version = "0.2.0" -source = "git+https://github.com/iron-fish/librustzcash.git?branch=blstrs#d551820030cb596eafe82226667f32b47164f91b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab579d7cf78477773b03e80bc2f89702ef02d7112c711d54ca93dcdce68533d5" dependencies = [ "blake2b_simd", "byteorder", @@ -976,7 +940,8 @@ checksum = "8ba569491c70ec8471e34aa7e9c0b9e82bb5d2464c0398442d17d3c4af814e5a" [[package]] name = "f4jumble" version = "0.1.0" -source = "git+https://github.com/iron-fish/librustzcash.git?branch=blstrs#d551820030cb596eafe82226667f32b47164f91b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a83e8d7fd0c526af4aad893b7c9fe41e2699ed8a776a6c74aecdeafe05afc75" dependencies = [ "blake2b_simd", ] @@ -1343,17 +1308,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest 0.9.0", + "hmac", ] [[package]] @@ -1495,7 +1450,6 @@ name = "ironfish" version = "0.3.0" dependencies = [ "argon2", - "bellperson", "blake2b_simd", "blake2s_simd", "blake3", @@ -1509,9 +1463,10 @@ dependencies = [ "hex", "hex-literal", "hkdf", + "ironfish-bellperson", "ironfish-frost", + "ironfish-jubjub", "ironfish_zkp", - "jubjub 0.9.0 (git+https://github.com/iron-fish/jubjub.git?branch=blstrs)", "lazy_static", "libc", "rand", @@ -1521,6 +1476,34 @@ dependencies = [ "xxhash-rust", ] +[[package]] +name = "ironfish-bellperson" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2e19b8d7c61fdbdfde5ba86cec85b9facf60b2b5ef9968618c353f8f7f6815" +dependencies = [ + "bincode", + "blake2s_simd", + "blstrs", + "byteorder", + "crossbeam-channel", + "digest 0.10.6", + "ec-gpu", + "ec-gpu-gen", + "ff 0.12.1", + "group 0.12.1", + "log", + "memmap2", + "pairing", + "rand", + "rand_core", + "rayon", + "rustversion", + "serde", + "sha2 0.10.6", + "thiserror", +] + [[package]] name = "ironfish-frost" version = "0.1.0" @@ -1538,6 +1521,77 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "ironfish-jubjub" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ee437e98296799ebdff1f62c14c63bbfb65df3e4e19c3913e7c46b9827b148" +dependencies = [ + "bitvec", + "blst", + "blstrs", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "rand_core", + "subtle", +] + +[[package]] +name = "ironfish-primitives" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b213445520fb92462bd4a79274ad9b31c1ce49248f6eaa7ca3a50e8fe413d3" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "blstrs", + "byteorder", + "chacha20poly1305 0.9.1", + "equihash", + "ff 0.12.1", + "fpe", + "group 0.12.1", + "hex", + "incrementalmerkletree", + "ironfish-jubjub", + "lazy_static", + "memuse", + "nonempty", + "orchard", + "rand", + "rand_core", + "sha2 0.9.9", + "subtle", + "zcash_address", + "zcash_encoding", + "zcash_note_encryption", +] + +[[package]] +name = "ironfish-proofs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea51eb3565333b8c67483a16499452215326df95f92160bd567f16c35f82e29" +dependencies = [ + "blake2b_simd", + "blstrs", + "byteorder", + "directories", + "ff 0.12.1", + "group 0.12.1", + "ironfish-bellperson", + "ironfish-jubjub", + "ironfish-primitives", + "lazy_static", + "rand_core", + "redjubjub", + "tracing", +] + [[package]] name = "ironfish-reddsa" version = "0.1.0" @@ -1565,7 +1619,7 @@ dependencies = [ "fish_hash", "ironfish", "ironfish-frost", - "jubjub 0.9.0 (git+https://github.com/iron-fish/jubjub.git?branch=blstrs)", + "ironfish-jubjub", "napi", "napi-build", "napi-derive", @@ -1578,17 +1632,17 @@ dependencies = [ name = "ironfish_zkp" version = "0.2.0" dependencies = [ - "bellperson", "blake2s_simd", "blstrs", "byteorder", "ff 0.12.1", "group 0.12.1", - "jubjub 0.9.0 (git+https://github.com/iron-fish/jubjub.git?branch=blstrs)", + "ironfish-bellperson", + "ironfish-jubjub", + "ironfish-primitives", + "ironfish-proofs", "lazy_static", "rand", - "zcash_primitives", - "zcash_proofs", ] [[package]] @@ -1638,21 +1692,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "jubjub" -version = "0.9.0" -source = "git+https://github.com/iron-fish/jubjub.git?branch=blstrs#531157cfa7b81ade207e819ef50c563843b10e30" -dependencies = [ - "bitvec", - "blst", - "blstrs", - "ff 0.12.1", - "group 0.12.1", - "lazy_static", - "rand_core", - "subtle", -] - [[package]] name = "jubjub" version = "0.10.0" @@ -1984,7 +2023,7 @@ dependencies = [ "serde", "subtle", "tracing", - "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_note_encryption", ] [[package]] @@ -2004,9 +2043,9 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.3.2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", "rand_core", @@ -2052,16 +2091,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "pbkdf2" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" -dependencies = [ - "crypto-mac", - "password-hash 0.3.2", -] - [[package]] name = "pbkdf2" version = "0.11.0" @@ -2069,6 +2098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.6", + "password-hash 0.4.2", ] [[package]] @@ -2272,7 +2302,7 @@ dependencies = [ "blake2b_simd", "byteorder", "group 0.12.1", - "jubjub 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jubjub 0.9.0", "pasta_curves 0.4.1", "rand_core", "serde", @@ -2289,7 +2319,7 @@ dependencies = [ "blake2b_simd", "byteorder", "digest 0.9.0", - "jubjub 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jubjub 0.9.0", "rand_core", "serde", "thiserror", @@ -2760,9 +2790,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" dependencies = [ "anyhow", - "hmac 0.12.1", + "hmac", "once_cell", - "pbkdf2 0.11.0", + "pbkdf2", "rand", "rustc-hash", "sha2 0.10.6", @@ -3309,7 +3339,8 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.1.0" -source = "git+https://github.com/iron-fish/librustzcash.git?branch=blstrs#d551820030cb596eafe82226667f32b47164f91b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1322a31b757f0087f110cc4a85dc5c6ccf83d0533bac04c4d3d1ce9112cc602" dependencies = [ "bech32", "bs58", @@ -3320,7 +3351,8 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.1.0" -source = "git+https://github.com/iron-fish/librustzcash.git?branch=blstrs#d551820030cb596eafe82226667f32b47164f91b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb61ea88eb539bc0ac2068e5da99411dd4978595b3d7ff6a4b1562ddc8e8710" dependencies = [ "byteorder", "nonempty", @@ -3338,70 +3370,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "git+https://github.com/iron-fish/librustzcash.git?branch=blstrs#d551820030cb596eafe82226667f32b47164f91b" -dependencies = [ - "chacha20 0.8.2", - "chacha20poly1305 0.9.1", - "rand_core", - "subtle", -] - -[[package]] -name = "zcash_primitives" -version = "0.7.0" -source = "git+https://github.com/iron-fish/librustzcash.git?branch=blstrs#d551820030cb596eafe82226667f32b47164f91b" -dependencies = [ - "aes", - "bip0039", - "bitvec", - "blake2b_simd", - "blake2s_simd", - "blstrs", - "byteorder", - "chacha20poly1305 0.9.1", - "equihash", - "ff 0.12.1", - "fpe", - "group 0.12.1", - "hex", - "incrementalmerkletree", - "jubjub 0.9.0 (git+https://github.com/iron-fish/jubjub.git?branch=blstrs)", - "lazy_static", - "memuse", - "nonempty", - "orchard", - "rand", - "rand_core", - "sha2 0.9.9", - "subtle", - "zcash_address", - "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/iron-fish/librustzcash.git?branch=blstrs)", -] - -[[package]] -name = "zcash_proofs" -version = "0.7.1" -source = "git+https://github.com/iron-fish/librustzcash.git?branch=blstrs#d551820030cb596eafe82226667f32b47164f91b" -dependencies = [ - "bellperson", - "blake2b_simd", - "blstrs", - "byteorder", - "directories", - "ff 0.12.1", - "group 0.12.1", - "jubjub 0.9.0 (git+https://github.com/iron-fish/jubjub.git?branch=blstrs)", - "lazy_static", - "rand_core", - "redjubjub", - "tracing", - "zcash_primitives", -] - [[package]] name = "zeroize" version = "1.6.0" diff --git a/ironfish-rust-nodejs/Cargo.toml b/ironfish-rust-nodejs/Cargo.toml index 1010ac58c5..ed7897bb93 100644 --- a/ironfish-rust-nodejs/Cargo.toml +++ b/ironfish-rust-nodejs/Cargo.toml @@ -24,7 +24,7 @@ workspace = true crate-type = ["cdylib"] [features] -stats = ["ironfish/note-encryption-stats", "jubjub/stats", "dep:signal-hook"] +stats = ["ironfish/note-encryption-stats", "ironfish-jubjub/stats", "dep:signal-hook"] [dependencies] base64 = "0.13.0" @@ -33,7 +33,7 @@ ironfish = { path = "../ironfish-rust" } ironfish-frost = { version = "0.1.0" } napi = { version = "2.14.4", features = ["napi6"] } napi-derive = "2.14.6" -jubjub = { git = "https://github.com/iron-fish/jubjub.git", branch = "blstrs", features = ["multiply-many"] } +ironfish-jubjub = { version = "0.1.0", features = ["multiply-many"] } rand = "0.8.5" num_cpus = "1.16.0" signal-hook = { version = "0.3.17", optional = true, default-features = false, features = ["iterator"] } diff --git a/ironfish-rust-nodejs/src/lib.rs b/ironfish-rust-nodejs/src/lib.rs index 31cdc60cc5..fcb8f96712 100644 --- a/ironfish-rust-nodejs/src/lib.rs +++ b/ironfish-rust-nodejs/src/lib.rs @@ -268,7 +268,7 @@ pub fn randomize_pk( let view_key = ViewKey::from_hex(&view_key_string).map_err(to_napi_err)?; let public_key_randomness = - jubjub::Fr::from_hex(&public_key_randomness_string).map_err(to_napi_err)?; + ironfish_jubjub::Fr::from_hex(&public_key_randomness_string).map_err(to_napi_err)?; let public_key = generate_randomized_public_key(view_key, public_key_randomness).map_err(to_napi_err)?; diff --git a/ironfish-rust-nodejs/src/structs/transaction.rs b/ironfish-rust-nodejs/src/structs/transaction.rs index 17070949cd..d1361df679 100644 --- a/ironfish-rust-nodejs/src/structs/transaction.rs +++ b/ironfish-rust-nodejs/src/structs/transaction.rs @@ -336,7 +336,7 @@ impl NativeTransaction { let view_key = ViewKey::from_hex(&view_key_str).map_err(to_napi_err)?; let outgoing_view_key = OutgoingViewKey::from_hex(&outgoing_view_key_str).map_err(to_napi_err)?; - let proof_authorizing_key = jubjub::Fr::from_hex(&proof_authorizing_key_str) + let proof_authorizing_key = ironfish_jubjub::Fr::from_hex(&proof_authorizing_key_str) .map_err(|_| to_napi_err("PublicKeyPackage authorizing key hex to bytes failed"))?; let change_address = match change_goes_to { diff --git a/ironfish-rust/Cargo.toml b/ironfish-rust/Cargo.toml index f68e859d12..88ebb8c1c8 100644 --- a/ironfish-rust/Cargo.toml +++ b/ironfish-rust/Cargo.toml @@ -33,7 +33,7 @@ name = "ironfish" path = "src/lib.rs" [dependencies] -bellperson = { git = "https://github.com/iron-fish/bellperson.git", branch = "blstrs", features = ["groth16"] } +ironfish-bellperson = { version = "0.1.0", features = ["groth16"] } blake2b_simd = "1.0.0" blake2s_simd = "1.0.0" blake3 = "1.5.0" @@ -46,7 +46,7 @@ group = "0.12.0" ironfish-frost = { version = "0.1.0" } fish_hash = "0.3.0" ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" } -jubjub = { git = "https://github.com/iron-fish/jubjub.git", branch = "blstrs", features = ["multiply-many"] } +ironfish-jubjub = { version = "0.1.0", features = ["multiply-many"] } lazy_static = "1.4.0" libc = "0.2.126" # sub-dependency that needs a pinned version until a new release of cpufeatures: https://github.com/RustCrypto/utils/pull/789 rand = "0.8.5" diff --git a/ironfish-rust/src/assets/asset.rs b/ironfish-rust/src/assets/asset.rs index f1d4605435..81405df2fa 100644 --- a/ironfish-rust/src/assets/asset.rs +++ b/ironfish-rust/src/assets/asset.rs @@ -8,8 +8,8 @@ use crate::{ PublicAddress, }; use byteorder::{ReadBytesExt, WriteBytesExt}; +use ironfish_jubjub::{ExtendedPoint, SubgroupPoint}; use ironfish_zkp::constants::{ASSET_ID_LENGTH, ASSET_ID_PERSONALIZATION, GH_FIRST_BLOCK}; -use jubjub::{ExtendedPoint, SubgroupPoint}; use std::io; use super::asset_identifier::AssetIdentifier; diff --git a/ironfish-rust/src/assets/asset_identifier.rs b/ironfish-rust/src/assets/asset_identifier.rs index c34e0805fe..23a8b66ac8 100644 --- a/ironfish-rust/src/assets/asset_identifier.rs +++ b/ironfish-rust/src/assets/asset_identifier.rs @@ -3,8 +3,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::errors::{IronfishError, IronfishErrorKind}; use group::cofactor::CofactorGroup; +use ironfish_jubjub::{ExtendedPoint, SubgroupPoint}; use ironfish_zkp::{constants::ASSET_ID_LENGTH, util::asset_hash_to_point}; -use jubjub::{ExtendedPoint, SubgroupPoint}; use std::io; pub const NATIVE_ASSET: AssetIdentifier = AssetIdentifier([ diff --git a/ironfish-rust/src/errors.rs b/ironfish-rust/src/errors.rs index 5406264073..5d8c5cc8f9 100644 --- a/ironfish-rust/src/errors.rs +++ b/ironfish-rust/src/errors.rs @@ -129,8 +129,8 @@ impl From for IronfishError { } } -impl From for IronfishError { - fn from(e: bellperson::SynthesisError) -> IronfishError { +impl From for IronfishError { + fn from(e: ironfish_bellperson::SynthesisError) -> IronfishError { IronfishError::new_with_source(IronfishErrorKind::BellpersonSynthesis, e) } } diff --git a/ironfish-rust/src/frost_utils/account_keys.rs b/ironfish-rust/src/frost_utils/account_keys.rs index b40b872844..a257cb1c34 100644 --- a/ironfish-rust/src/frost_utils/account_keys.rs +++ b/ironfish-rust/src/frost_utils/account_keys.rs @@ -8,12 +8,12 @@ use crate::{ }; use group::GroupEncoding; use ironfish_frost::frost::VerifyingKey; +use ironfish_jubjub::SubgroupPoint; use ironfish_zkp::constants::PROOF_GENERATION_KEY_GENERATOR; -use jubjub::SubgroupPoint; pub struct MultisigAccountKeys { /// Equivalent to [`crate::keys::SaplingKey::proof_authorizing_key`] - pub proof_authorizing_key: jubjub::Fr, + pub proof_authorizing_key: ironfish_jubjub::Fr, /// Equivalent to [`crate::keys::SaplingKey::outgoing_viewing_key`] pub outgoing_viewing_key: OutgoingViewKey, /// Equivalent to [`crate::keys::SaplingKey::view_key`] diff --git a/ironfish-rust/src/frost_utils/split_spender_key.rs b/ironfish-rust/src/frost_utils/split_spender_key.rs index 7bee66eaf7..3e576c2ace 100644 --- a/ironfish-rust/src/frost_utils/split_spender_key.rs +++ b/ironfish-rust/src/frost_utils/split_spender_key.rs @@ -20,7 +20,7 @@ pub struct TrustedDealerKeyPackages { pub view_key: ViewKey, pub incoming_view_key: IncomingViewKey, pub outgoing_view_key: OutgoingViewKey, - pub proof_authorizing_key: jubjub::Fr, + pub proof_authorizing_key: ironfish_jubjub::Fr, pub key_packages: HashMap, } diff --git a/ironfish-rust/src/keys/ephemeral.rs b/ironfish-rust/src/keys/ephemeral.rs index 11823390ad..dd106d0d70 100644 --- a/ironfish-rust/src/keys/ephemeral.rs +++ b/ironfish-rust/src/keys/ephemeral.rs @@ -11,13 +11,13 @@ use rand::thread_rng; /// [`crate::keys::shared_secret`] #[derive(Default)] pub struct EphemeralKeyPair { - secret: jubjub::Fr, - public: jubjub::SubgroupPoint, + secret: ironfish_jubjub::Fr, + public: ironfish_jubjub::SubgroupPoint, } impl EphemeralKeyPair { pub fn new() -> Self { - let secret = jubjub::Fr::random(thread_rng()); + let secret = ironfish_jubjub::Fr::random(thread_rng()); Self { secret, @@ -25,11 +25,11 @@ impl EphemeralKeyPair { } } - pub fn secret(&self) -> &jubjub::Fr { + pub fn secret(&self) -> &ironfish_jubjub::Fr { &self.secret } - pub fn public(&self) -> &jubjub::SubgroupPoint { + pub fn public(&self) -> &ironfish_jubjub::SubgroupPoint { &self.public } } diff --git a/ironfish-rust/src/keys/mod.rs b/ironfish-rust/src/keys/mod.rs index 24287cf681..92c1e7cd9c 100644 --- a/ironfish-rust/src/keys/mod.rs +++ b/ironfish-rust/src/keys/mod.rs @@ -10,11 +10,11 @@ use bip39::Mnemonic; use blake2b_simd::Params as Blake2b; use blake2s_simd::Params as Blake2s; use group::GroupEncoding; +use ironfish_jubjub::SubgroupPoint; use ironfish_zkp::constants::{ CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR, }; pub use ironfish_zkp::ProofGenerationKey; -use jubjub::SubgroupPoint; use rand::prelude::*; use std::io; @@ -50,12 +50,12 @@ pub struct SaplingKey { /// Part of the expanded form of the spending key, generally referred to as /// `ask` in the literature. Derived from spending key using a seeded /// pseudorandom hash function. Used to construct authorizing_key. - pub(crate) spend_authorizing_key: jubjub::Fr, + pub(crate) spend_authorizing_key: ironfish_jubjub::Fr, /// Part of the expanded form of the spending key, generally referred to as /// `nsk` in the literature. Derived from spending key using a seeded /// pseudorandom hash function. Used to construct nullifier_deriving_key - pub(crate) proof_authorizing_key: jubjub::Fr, + pub(crate) proof_authorizing_key: ironfish_jubjub::Fr, /// Part of the expanded form of the spending key, as well as being used /// directly in the full viewing key. Generally referred to as @@ -80,14 +80,14 @@ impl SaplingKey { /// Construct a new key from an array of bytes pub fn new(spending_key: [u8; SPEND_KEY_SIZE]) -> Result { let spend_authorizing_key = - jubjub::Fr::from_bytes_wide(&Self::convert_key(spending_key, 0)); + ironfish_jubjub::Fr::from_bytes_wide(&Self::convert_key(spending_key, 0)); - if spend_authorizing_key == jubjub::Fr::zero() { + if spend_authorizing_key == ironfish_jubjub::Fr::zero() { return Err(IronfishError::new(IronfishErrorKind::IllegalValue)); } let proof_authorizing_key = - jubjub::Fr::from_bytes_wide(&Self::convert_key(spending_key, 1)); + ironfish_jubjub::Fr::from_bytes_wide(&Self::convert_key(spending_key, 1)); let mut outgoing_viewing_key = [0; SPEND_KEY_SIZE]; outgoing_viewing_key[0..SPEND_KEY_SIZE] @@ -243,7 +243,7 @@ impl SaplingKey { pub fn hash_viewing_key( authorizing_key: &SubgroupPoint, nullifier_deriving_key: &SubgroupPoint, - ) -> Result { + ) -> Result { let mut view_key_contents = [0; 64]; view_key_contents[0..32].copy_from_slice(&authorizing_key.to_bytes()); view_key_contents[32..64].copy_from_slice(&nullifier_deriving_key.to_bytes()); diff --git a/ironfish-rust/src/keys/public_address.rs b/ironfish-rust/src/keys/public_address.rs index 4b9ea5f887..5e759c3ae3 100644 --- a/ironfish-rust/src/keys/public_address.rs +++ b/ironfish-rust/src/keys/public_address.rs @@ -7,8 +7,8 @@ use crate::{ serializing::{bytes_to_hex, hex_to_bytes}, }; use group::GroupEncoding; +use ironfish_jubjub::SubgroupPoint; use ironfish_zkp::constants::PUBLIC_KEY_GENERATOR; -use jubjub::SubgroupPoint; use std::io; diff --git a/ironfish-rust/src/keys/test.rs b/ironfish-rust/src/keys/test.rs index 997785307a..fd46fa2f09 100644 --- a/ironfish-rust/src/keys/test.rs +++ b/ironfish-rust/src/keys/test.rs @@ -6,7 +6,7 @@ use crate::keys::{ephemeral::EphemeralKeyPair, PUBLIC_ADDRESS_SIZE}; use super::{shared_secret, PublicAddress, SaplingKey}; use group::Curve; -use jubjub::ExtendedPoint; +use ironfish_jubjub::ExtendedPoint; #[test] fn test_key_generation_and_construction() { diff --git a/ironfish-rust/src/keys/util.rs b/ironfish-rust/src/keys/util.rs index 369b518e8a..9250a91475 100644 --- a/ironfish-rust/src/keys/util.rs +++ b/ironfish-rust/src/keys/util.rs @@ -3,8 +3,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use group::GroupEncoding; +use ironfish_jubjub::Fr; use ironfish_zkp::{constants::SPENDING_KEY_GENERATOR, redjubjub}; -use jubjub::Fr; use crate::{errors::IronfishError, ViewKey}; diff --git a/ironfish-rust/src/keys/view_keys.rs b/ironfish-rust/src/keys/view_keys.rs index 68b9f26e3e..1ce0168f2e 100644 --- a/ironfish-rust/src/keys/view_keys.rs +++ b/ironfish-rust/src/keys/view_keys.rs @@ -21,7 +21,7 @@ use crate::{ use bip39::{Language, Mnemonic}; use blake2b_simd::Params as Blake2b; use group::GroupEncoding; -use jubjub::SubgroupPoint; +use ironfish_jubjub::SubgroupPoint; use std::io; @@ -32,7 +32,7 @@ const DIFFIE_HELLMAN_PERSONALIZATION: &[u8; 16] = b"Iron Fish shared"; /// Referred to as `ivk` in the literature. #[derive(Clone)] pub struct IncomingViewKey { - pub(crate) view_key: jubjub::Fr, + pub(crate) view_key: ironfish_jubjub::Fr, } impl IncomingViewKey { @@ -110,11 +110,11 @@ pub struct ViewKey { /// Part of the full viewing key. Generally referred to as /// `ak` in the literature. Derived from spend_authorizing_key using scalar /// multiplication in Sapling. Used to construct incoming viewing key. - pub authorizing_key: jubjub::SubgroupPoint, + pub authorizing_key: ironfish_jubjub::SubgroupPoint, /// Part of the full viewing key. Generally referred to as /// `nk` in the literature. Derived from proof_authorizing_key using scalar /// multiplication. Used to construct incoming viewing key. - pub nullifier_deriving_key: jubjub::SubgroupPoint, + pub nullifier_deriving_key: ironfish_jubjub::SubgroupPoint, } impl ViewKey { @@ -241,7 +241,7 @@ impl OutgoingViewKey { /// The resulting key can be used in any symmetric cipher #[must_use] pub(crate) fn shared_secret( - secret_key: &jubjub::Fr, + secret_key: &ironfish_jubjub::Fr, other_public_key: &SubgroupPoint, reference_public_key: &SubgroupPoint, ) -> [u8; 32] { diff --git a/ironfish-rust/src/lib.rs b/ironfish-rust/src/lib.rs index 3503643b99..45068042ab 100644 --- a/ironfish-rust/src/lib.rs +++ b/ironfish-rust/src/lib.rs @@ -1,8 +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/. */ -use bellperson::groth16; use blstrs::Bls12; +use ironfish_bellperson::groth16; pub mod assets; pub mod errors; diff --git a/ironfish-rust/src/merkle_note.rs b/ironfish-rust/src/merkle_note.rs index be1d763d60..fb1b873544 100644 --- a/ironfish-rust/src/merkle_note.rs +++ b/ironfish-rust/src/merkle_note.rs @@ -22,8 +22,8 @@ use blake2b_simd::Params as Blake2b; use blstrs::Scalar; use ff::PrimeField; use group::GroupEncoding; +use ironfish_jubjub::{ExtendedPoint, SubgroupPoint}; use ironfish_zkp::primitives::ValueCommitment; -use jubjub::{ExtendedPoint, SubgroupPoint}; use std::{convert::TryInto, io}; diff --git a/ironfish-rust/src/merkle_note_hash.rs b/ironfish-rust/src/merkle_note_hash.rs index 1de35e8b5b..139a5a1306 100644 --- a/ironfish-rust/src/merkle_note_hash.rs +++ b/ironfish-rust/src/merkle_note_hash.rs @@ -11,8 +11,8 @@ use super::serializing::read_scalar; use blstrs::Scalar; use ff::{PrimeField, PrimeFieldBits}; use group::Curve; +use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::pedersen_hash::{pedersen_hash, Personalization}; -use jubjub::ExtendedPoint; use std::io; diff --git a/ironfish-rust/src/note.rs b/ironfish-rust/src/note.rs index 3a87c09ce4..f4b7b9846a 100644 --- a/ironfish-rust/src/note.rs +++ b/ironfish-rust/src/note.rs @@ -19,12 +19,12 @@ use blstrs::Scalar; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::{Field, PrimeField}; use group::{Curve, GroupEncoding}; +use ironfish_jubjub::SubgroupPoint; use ironfish_zkp::{ constants::{ASSET_ID_LENGTH, NULLIFIER_POSITION_GENERATOR, PRF_NF_PERSONALIZATION}, util::commitment_full_point, Nullifier, }; -use jubjub::SubgroupPoint; use rand::thread_rng; use std::{fmt, io, io::Read}; pub const ENCRYPTED_NOTE_SIZE: usize = @@ -99,7 +99,7 @@ pub struct Note { /// This helps create zero knowledge around the note, /// allowing the owner to prove they have the note without revealing /// anything else about it. - pub(crate) randomness: jubjub::Fr, + pub(crate) randomness: ironfish_jubjub::Fr, /// Arbitrary note the spender can supply when constructing a spend so the /// receiver has some record from whence it came. @@ -120,7 +120,7 @@ impl Note { asset_id: AssetIdentifier, sender: PublicAddress, ) -> Self { - let randomness: jubjub::Fr = jubjub::Fr::random(thread_rng()); + let randomness: ironfish_jubjub::Fr = ironfish_jubjub::Fr::random(thread_rng()); Self { owner, @@ -142,7 +142,7 @@ impl Note { let asset_id = AssetIdentifier::read(&mut reader)?; let value = reader.read_u64::()?; - let randomness: jubjub::Fr = read_scalar(&mut reader)?; + let randomness: ironfish_jubjub::Fr = read_scalar(&mut reader)?; let mut memo = Memo::default(); reader.read_exact(&mut memo.0)?; @@ -244,7 +244,7 @@ impl Note { self.owner } - pub fn asset_generator(&self) -> jubjub::ExtendedPoint { + pub fn asset_generator(&self) -> ironfish_jubjub::ExtendedPoint { self.asset_id.asset_generator() } @@ -286,7 +286,7 @@ impl Note { } /// Computes the note commitment, returning the full point. - fn commitment_full_point(&self) -> jubjub::SubgroupPoint { + fn commitment_full_point(&self) -> ironfish_jubjub::SubgroupPoint { commitment_full_point( self.asset_generator(), self.value, @@ -304,7 +304,7 @@ impl Note { pub fn nullifier(&self, view_key: &ViewKey, position: u64) -> Nullifier { // Compute rho = cm + position.G let rho = self.commitment_full_point() - + (*NULLIFIER_POSITION_GENERATOR * jubjub::Fr::from(position)); + + (*NULLIFIER_POSITION_GENERATOR * ironfish_jubjub::Fr::from(position)); // Compute nf = BLAKE2s(nk | rho) Nullifier::from_slice( @@ -335,7 +335,7 @@ impl Note { pub(crate) fn commitment_point(&self) -> Scalar { // The commitment is in the prime order subgroup, so mapping the // commitment to the u-coordinate is an injective encoding. - jubjub::ExtendedPoint::from(self.commitment_full_point()) + ironfish_jubjub::ExtendedPoint::from(self.commitment_full_point()) .to_affine() .get_u() } @@ -352,7 +352,16 @@ impl Note { fn decrypt_note_parts( shared_secret: &[u8; 32], encrypted_bytes: &[u8; ENCRYPTED_NOTE_SIZE + aead::MAC_SIZE], - ) -> Result<(jubjub::Fr, AssetIdentifier, u64, Memo, PublicAddress), IronfishError> { + ) -> Result< + ( + ironfish_jubjub::Fr, + AssetIdentifier, + u64, + Memo, + PublicAddress, + ), + IronfishError, + > { let plaintext_bytes: [u8; ENCRYPTED_NOTE_SIZE] = aead::decrypt(shared_secret, encrypted_bytes)?; let mut reader = &plaintext_bytes[..]; diff --git a/ironfish-rust/src/serializing/fr.rs b/ironfish-rust/src/serializing/fr.rs index 19cc103b38..acbcf4dcca 100644 --- a/ironfish-rust/src/serializing/fr.rs +++ b/ironfish-rust/src/serializing/fr.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::errors::{IronfishError, IronfishErrorKind}; -use jubjub::Fr; +use ironfish_jubjub::Fr; use super::{bytes_to_hex, hex_to_bytes}; @@ -88,7 +88,7 @@ mod test { fn test_hex() { let mut rng = StdRng::seed_from_u64(0); - let fr = jubjub::Fr::random(&mut rng); + let fr = ironfish_jubjub::Fr::random(&mut rng); let hex_key = fr.hex_key(); diff --git a/ironfish-rust/src/transaction/mints.rs b/ironfish-rust/src/transaction/mints.rs index c207ec3fde..2638fec204 100644 --- a/ironfish-rust/src/transaction/mints.rs +++ b/ironfish-rust/src/transaction/mints.rs @@ -4,18 +4,18 @@ use std::io; -use bellperson::groth16; use blstrs::{Bls12, Scalar}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::Field; use group::{Curve, GroupEncoding}; +use ironfish_bellperson::groth16; +use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::{ constants::SPENDING_KEY_GENERATOR, proofs::MintAsset, redjubjub::{self, Signature}, ProofGenerationKey, }; -use jubjub::ExtendedPoint; use rand::thread_rng; use crate::{ @@ -61,7 +61,7 @@ impl MintBuilder { &self, proof_generation_key: &ProofGenerationKey, public_address: &PublicAddress, - public_key_randomness: &jubjub::Fr, + public_key_randomness: &ironfish_jubjub::Fr, randomized_public_key: &redjubjub::PublicKey, ) -> Result { let circuit = MintAsset { @@ -105,7 +105,7 @@ impl MintBuilder { pub struct UnsignedMintDescription { /// Used to add randomness to signature generation. Referred to as `ar` in /// the literature. - public_key_randomness: jubjub::Fr, + public_key_randomness: ironfish_jubjub::Fr, /// Proof and public parameters for a user action to issue supply for an /// asset. @@ -391,7 +391,7 @@ mod test { let value = 5; - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -428,7 +428,10 @@ mod test { .is_err()); let other_randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) - .randomize(jubjub::Fr::random(thread_rng()), *SPENDING_KEY_GENERATOR); + .randomize( + ironfish_jubjub::Fr::random(thread_rng()), + *SPENDING_KEY_GENERATOR, + ); assert!(verify_mint_proof( &description.proof, @@ -451,7 +454,7 @@ mod test { let asset = Asset::new(creator, name, metadata).unwrap(); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -560,7 +563,7 @@ mod test { key: &SaplingKey, mint: &MintBuilder, ) -> MintDescription { - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -656,7 +659,7 @@ mod test { let value = 5; - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -686,13 +689,13 @@ mod test { let public_address = key.public_address(); let asset = Asset::new(public_address, "name", "").expect("should be able to create asset"); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); let value = random(); let builder = MintBuilder::new(asset, value); // create a random private key and sign random message as placeholder - let private_key = redjubjub::PrivateKey(jubjub::Fr::random(thread_rng())); + let private_key = redjubjub::PrivateKey(ironfish_jubjub::Fr::random(thread_rng())); let public_key = redjubjub::PublicKey::from_private(&private_key, *SPENDING_KEY_GENERATOR); let msg = [0u8; 32]; let signature = private_key.sign(&msg, &mut thread_rng(), *SPENDING_KEY_GENERATOR); diff --git a/ironfish-rust/src/transaction/mod.rs b/ironfish-rust/src/transaction/mod.rs index dff9699a2a..a3e04b049a 100644 --- a/ironfish-rust/src/transaction/mod.rs +++ b/ironfish-rust/src/transaction/mod.rs @@ -23,11 +23,11 @@ use crate::{ use rand::{rngs::OsRng, thread_rng}; -use bellperson::groth16::{verify_proofs_batch, PreparedVerifyingKey}; use blake2b_simd::Params as Blake2b; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use group::GroupEncoding; -use jubjub::ExtendedPoint; +use ironfish_bellperson::groth16::{verify_proofs_batch, PreparedVerifyingKey}; +use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::{ constants::{ @@ -116,7 +116,7 @@ pub struct ProposedTransaction { // allows us to verify the sender address is valid and stored in the notes // Used to add randomness to signature generation without leaking the // key. Referred to as `ar` in the literature. - public_key_randomness: jubjub::Fr, + public_key_randomness: ironfish_jubjub::Fr, // NOTE: If adding fields here, you may need to add fields to // signature hash method, and also to Transaction. } @@ -131,7 +131,7 @@ impl ProposedTransaction { burns: vec![], value_balances: ValueBalances::new(), expiration: 0, - public_key_randomness: jubjub::Fr::random(thread_rng()), + public_key_randomness: ironfish_jubjub::Fr::random(thread_rng()), } } @@ -231,7 +231,7 @@ impl ProposedTransaction { pub fn build( &mut self, - proof_authorizing_key: jubjub::Fr, + proof_authorizing_key: ironfish_jubjub::Fr, view_key: ViewKey, outgoing_view_key: OutgoingViewKey, intended_transaction_fee: i64, @@ -480,7 +480,7 @@ impl ProposedTransaction { ) -> Result<(redjubjub::PrivateKey, redjubjub::PublicKey), IronfishError> { // A "private key" manufactured from a bunch of randomness added for each // spend and output. - let mut binding_signature_key = jubjub::Fr::zero(); + let mut binding_signature_key = ironfish_jubjub::Fr::zero(); // A "public key" manufactured from a combination of the values of each // description and the same randomness as above @@ -777,7 +777,7 @@ fn fee_to_point(value: i64) -> Result { None => return Err(IronfishError::new(IronfishErrorKind::IllegalValue)), }; - let mut value_balance = *NATIVE_VALUE_COMMITMENT_GENERATOR * jubjub::Fr::from(abs); + let mut value_balance = *NATIVE_VALUE_COMMITMENT_GENERATOR * ironfish_jubjub::Fr::from(abs); if is_negative { value_balance = -value_balance; @@ -802,12 +802,12 @@ fn calculate_value_balance( for mint in mints { let mint_generator = mint.asset.value_commitment_generator(); - value_balance_point += mint_generator * jubjub::Fr::from(mint.value); + value_balance_point += mint_generator * ironfish_jubjub::Fr::from(mint.value); } for burn in burns { let burn_generator = burn.asset_id.value_commitment_generator(); - value_balance_point -= burn_generator * jubjub::Fr::from(burn.value); + value_balance_point -= burn_generator * ironfish_jubjub::Fr::from(burn.value); } Ok(value_balance_point) diff --git a/ironfish-rust/src/transaction/outputs.rs b/ironfish-rust/src/transaction/outputs.rs index d7f1690277..7e7202adae 100644 --- a/ironfish-rust/src/transaction/outputs.rs +++ b/ironfish-rust/src/transaction/outputs.rs @@ -11,12 +11,12 @@ use crate::{ OutgoingViewKey, }; -use bellperson::groth16; use blstrs::{Bls12, Scalar}; use ff::Field; use group::Curve; +use ironfish_bellperson::groth16; +use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::{primitives::ValueCommitment, proofs::Output, redjubjub, ProofGenerationKey}; -use jubjub::ExtendedPoint; use rand::thread_rng; use std::io; @@ -83,7 +83,7 @@ impl OutputBuilder { &self, proof_generation_key: &ProofGenerationKey, outgoing_view_key: &OutgoingViewKey, - public_key_randomness: &jubjub::Fr, + public_key_randomness: &ironfish_jubjub::Fr, randomized_public_key: &redjubjub::PublicKey, ) -> Result { let diffie_hellman_keys = EphemeralKeyPair::new(); @@ -232,8 +232,8 @@ mod test { }; use ff::{Field, PrimeField}; use group::Curve; + use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::{constants::SPENDING_KEY_GENERATOR, redjubjub}; - use jubjub::ExtendedPoint; use rand::thread_rng; #[test] @@ -241,7 +241,7 @@ mod test { /// set will use the hard-coded note encryption keys fn test_output_miners_fee() { let spender_key = SaplingKey::generate_key(); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(spender_key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -276,7 +276,7 @@ mod test { fn test_output_not_miners_fee() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(spender_key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -310,12 +310,12 @@ mod test { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(spender_key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); - let other_public_key_randomness = jubjub::Fr::random(thread_rng()); + let other_public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let other_randomized_public_key = redjubjub::PublicKey(receiver_key.view_key.authorizing_key.into()) .randomize(other_public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -385,7 +385,7 @@ mod test { fn test_output_round_trip() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(spender_key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index 583858d2e2..70280257aa 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -13,12 +13,13 @@ use crate::{ ViewKey, }; -use bellperson::gadgets::multipack; -use bellperson::groth16; use blstrs::{Bls12, Scalar}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::{Field, PrimeField}; use group::{Curve, GroupEncoding}; +use ironfish_bellperson::gadgets::multipack; +use ironfish_bellperson::groth16; +use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::{ constants::SPENDING_KEY_GENERATOR, primitives::ValueCommitment, @@ -26,7 +27,6 @@ use ironfish_zkp::{ redjubjub::{self, Signature}, Nullifier, ProofGenerationKey, }; -use jubjub::ExtendedPoint; use rand::thread_rng; use std::io; @@ -93,7 +93,7 @@ impl SpendBuilder { &self, proof_generation_key: &ProofGenerationKey, view_key: &ViewKey, - public_key_randomness: &jubjub::Fr, + public_key_randomness: &ironfish_jubjub::Fr, randomized_public_key: &redjubjub::PublicKey, ) -> Result { let value_commitment_point = self.value_commitment_point(); @@ -149,7 +149,7 @@ impl SpendBuilder { pub struct UnsignedSpendDescription { /// Used to add randomness to signature generation without leaking the /// key. Referred to as `ar` in the literature. - public_key_randomness: jubjub::Fr, + public_key_randomness: ironfish_jubjub::Fr, /// Proof and public parameters for a user action to spend tokens. pub(crate) description: SpendDescription, @@ -424,11 +424,11 @@ mod test { let public_address = key.public_address(); let sender_key = SaplingKey::generate_key(); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); - let other_public_key_randomness = jubjub::Fr::random(thread_rng()); + let other_public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let other_randomized_public_key = redjubjub::PublicKey(sender_key.view_key.authorizing_key.into()) .randomize(other_public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -525,7 +525,7 @@ mod test { let spend = SpendBuilder::new(note, &witness); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); @@ -601,13 +601,13 @@ mod test { sender_key.public_address(), ); let witness = make_fake_witness(¬e); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); let builder = SpendBuilder::new(note, &witness); // create a random private key and sign random message as placeholder - let private_key = PrivateKey(jubjub::Fr::random(thread_rng())); + let private_key = PrivateKey(ironfish_jubjub::Fr::random(thread_rng())); let public_key = PublicKey::from_private(&private_key, *SPENDING_KEY_GENERATOR); let msg = [0u8; 32]; let signature = private_key.sign(&msg, &mut thread_rng(), *SPENDING_KEY_GENERATOR); diff --git a/ironfish-rust/src/transaction/tests.rs b/ironfish-rust/src/transaction/tests.rs index ca73d6e303..9c69edcc05 100644 --- a/ironfish-rust/src/transaction/tests.rs +++ b/ironfish-rust/src/transaction/tests.rs @@ -442,7 +442,7 @@ fn test_batch_verify_wrong_spend_params() { let rng = &mut thread_rng(); let wrong_spend_params = - bellperson::groth16::generate_random_parameters::( + ironfish_bellperson::groth16::generate_random_parameters::( Spend { value_commitment: None, proof_generation_key: None, @@ -457,7 +457,8 @@ fn test_batch_verify_wrong_spend_params() { ) .unwrap(); - let wrong_spend_vk = bellperson::groth16::prepare_verifying_key(&wrong_spend_params.vk); + let wrong_spend_vk = + ironfish_bellperson::groth16::prepare_verifying_key(&wrong_spend_params.vk); // // TRANSACTION GENERATION @@ -519,7 +520,7 @@ fn test_batch_verify_wrong_output_params() { let rng = &mut thread_rng(); let wrong_output_params = - bellperson::groth16::generate_random_parameters::( + ironfish_bellperson::groth16::generate_random_parameters::( Output { value_commitment: None, payment_address: None, @@ -533,7 +534,8 @@ fn test_batch_verify_wrong_output_params() { ) .unwrap(); - let wrong_output_vk = bellperson::groth16::prepare_verifying_key(&wrong_output_params.vk); + let wrong_output_vk = + ironfish_bellperson::groth16::prepare_verifying_key(&wrong_output_params.vk); // // TRANSACTION GENERATION @@ -594,16 +596,17 @@ fn test_batch_verify_wrong_output_params() { fn test_batch_verify_wrong_mint_params() { let rng = &mut thread_rng(); - let wrong_mint_params = bellperson::groth16::generate_random_parameters::( - MintAsset { - proof_generation_key: None, - public_key_randomness: None, - }, - rng, - ) - .unwrap(); + let wrong_mint_params = + ironfish_bellperson::groth16::generate_random_parameters::( + MintAsset { + proof_generation_key: None, + public_key_randomness: None, + }, + rng, + ) + .unwrap(); - let wrong_mint_vk = bellperson::groth16::prepare_verifying_key(&wrong_mint_params.vk); + let wrong_mint_vk = ironfish_bellperson::groth16::prepare_verifying_key(&wrong_mint_params.vk); // // TRANSACTION GENERATION @@ -686,7 +689,7 @@ fn test_batch_verify() { let key = SaplingKey::generate_key(); let other_key = SaplingKey::generate_key(); - let public_key_randomness = jubjub::Fr::random(thread_rng()); + let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); let other_randomized_public_key = redjubjub::PublicKey(other_key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); diff --git a/ironfish-rust/src/transaction/unsigned.rs b/ironfish-rust/src/transaction/unsigned.rs index eae47bf51e..694cb630b5 100644 --- a/ironfish-rust/src/transaction/unsigned.rs +++ b/ironfish-rust/src/transaction/unsigned.rs @@ -68,7 +68,7 @@ pub struct UnsignedTransaction { pub(crate) randomized_public_key: redjubjub::PublicKey, // TODO: Verify if this is actually okay to store on the unsigned transaction - pub(crate) public_key_randomness: jubjub::Fr, + pub(crate) public_key_randomness: ironfish_jubjub::Fr, /// The balance of total spends - outputs, which is the amount that the miner gets to keep pub(crate) fee: i64, @@ -319,7 +319,7 @@ impl UnsignedTransaction { } // Exposes the public key package for use in round two of FROST multisig protocol - pub fn public_key_randomness(&self) -> jubjub::Fr { + pub fn public_key_randomness(&self) -> ironfish_jubjub::Fr { self.public_key_randomness } diff --git a/ironfish-rust/src/transaction/utils.rs b/ironfish-rust/src/transaction/utils.rs index 20f07c2680..879f30d620 100644 --- a/ironfish-rust/src/transaction/utils.rs +++ b/ironfish-rust/src/transaction/utils.rs @@ -1,8 +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/. */ -use bellperson::groth16; use blstrs::Bls12; +use ironfish_bellperson::groth16; use crate::{ errors::{IronfishError, IronfishErrorKind}, diff --git a/ironfish-zkp/Cargo.toml b/ironfish-zkp/Cargo.toml index 3aebfc2204..23046ea248 100644 --- a/ironfish-zkp/Cargo.toml +++ b/ironfish-zkp/Cargo.toml @@ -21,14 +21,14 @@ workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bellperson = { git = "https://github.com/iron-fish/bellperson.git", branch = "blstrs", features = ["groth16"] } +ironfish-bellperson = { version = "0.1.0", features = ["groth16"] } blake2s_simd = "1.0.0" blstrs = { version = "0.6.0", features = ["portable"] } byteorder = "1.4.3" ff = "0.12.0" group = "0.12.0" -jubjub = { git = "https://github.com/iron-fish/jubjub.git", branch = "blstrs" } +ironfish-jubjub = { version = "0.1.0" } lazy_static = "1.4.0" rand = "0.8.5" -zcash_primitives = { git = "https://github.com/iron-fish/librustzcash.git", branch = "blstrs", package = "zcash_primitives" } -zcash_proofs = { git = "https://github.com/iron-fish/librustzcash.git", branch = "blstrs", package = "zcash_proofs" } +ironfish-primitives = { version = "0.1.0" } +ironfish-proofs = { version = "0.1.0" } diff --git a/ironfish-zkp/src/bin/generate_params.rs b/ironfish-zkp/src/bin/generate_params.rs index 7c2aa011f4..d1ae0c640d 100644 --- a/ironfish-zkp/src/bin/generate_params.rs +++ b/ironfish-zkp/src/bin/generate_params.rs @@ -1,5 +1,5 @@ -use bellperson::{groth16, Circuit}; use blstrs::Bls12; +use ironfish_bellperson::{groth16, Circuit}; use ironfish_zkp::{ constants::ASSET_ID_LENGTH, proofs::{MintAsset, Output, Spend}, diff --git a/ironfish-zkp/src/circuits/mint_asset.rs b/ironfish-zkp/src/circuits/mint_asset.rs index b32a0ef276..948ed8e8c9 100644 --- a/ironfish-zkp/src/circuits/mint_asset.rs +++ b/ironfish-zkp/src/circuits/mint_asset.rs @@ -1,13 +1,13 @@ -use bellperson::{ +use ff::PrimeField; +use ironfish_bellperson::{ gadgets::{blake2s, boolean}, Circuit, }; -use ff::PrimeField; -use std::io::{Read, Write}; -use zcash_proofs::{ +use ironfish_proofs::{ circuit::ecc, constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, }; +use std::io::{Read, Write}; use crate::{ constants::{proof::PUBLIC_KEY_GENERATOR, CRH_IVK_PERSONALIZATION}, @@ -23,7 +23,7 @@ pub struct MintAsset { /// Used to add randomness to signature generation without leaking the /// key. Referred to as `ar` in the literature. - pub public_key_randomness: Option, + pub public_key_randomness: Option, } impl MintAsset { @@ -50,7 +50,7 @@ impl MintAsset { } let mut public_key_randomness = None; if reader.read_u8()? == 1 { - public_key_randomness = Some(jubjub::Fr::read(&mut reader)?); + public_key_randomness = Some(ironfish_jubjub::Fr::read(&mut reader)?); } Ok(MintAsset { proof_generation_key, @@ -60,10 +60,10 @@ impl MintAsset { } impl Circuit for MintAsset { - fn synthesize>( + fn synthesize>( self, cs: &mut CS, - ) -> Result<(), bellperson::SynthesisError> { + ) -> Result<(), ironfish_bellperson::SynthesisError> { // Prover witnesses ak (ensures that it's on the curve) let ak = ecc::EdwardsPoint::witness( cs.namespace(|| "ak"), @@ -139,7 +139,7 @@ impl Circuit for MintAsset { )?; // drop_5 to ensure it's in the field - ivk.truncate(jubjub::Fr::CAPACITY as usize); + ivk.truncate(ironfish_jubjub::Fr::CAPACITY as usize); // Compute owner public address let owner_public_address = ecc::fixed_base_multiplication( @@ -156,10 +156,10 @@ impl Circuit for MintAsset { #[cfg(test)] mod test { - use bellperson::{gadgets::test::TestConstraintSystem, Circuit, ConstraintSystem}; use ff::Field; use group::{Curve, Group}; - use jubjub::ExtendedPoint; + use ironfish_bellperson::{gadgets::test::TestConstraintSystem, Circuit, ConstraintSystem}; + use ironfish_jubjub::ExtendedPoint; use rand::{rngs::StdRng, SeedableRng}; use crate::{constants::PUBLIC_KEY_GENERATOR, ProofGenerationKey}; @@ -174,14 +174,14 @@ mod test { let mut cs = TestConstraintSystem::new(); let proof_generation_key = ProofGenerationKey::new( - jubjub::SubgroupPoint::random(&mut rng), - jubjub::Fr::random(&mut rng), + ironfish_jubjub::SubgroupPoint::random(&mut rng), + ironfish_jubjub::Fr::random(&mut rng), ); let incoming_view_key = proof_generation_key.to_viewing_key(); let public_address = *PUBLIC_KEY_GENERATOR * incoming_view_key.ivk().0; let public_address_point = ExtendedPoint::from(public_address).to_affine(); - let public_key_randomness = jubjub::Fr::random(&mut rng); + let public_key_randomness = ironfish_jubjub::Fr::random(&mut rng); let randomized_public_key = ExtendedPoint::from(incoming_view_key.rk(public_key_randomness)).to_affine(); @@ -226,10 +226,10 @@ mod test { // Create a MintAsset instance with random data let proof_generation_key = ProofGenerationKey::new( - jubjub::SubgroupPoint::random(&mut rng), - jubjub::Fr::random(&mut rng), + ironfish_jubjub::SubgroupPoint::random(&mut rng), + ironfish_jubjub::Fr::random(&mut rng), ); - let public_key_randomness = jubjub::Fr::random(&mut rng); + let public_key_randomness = ironfish_jubjub::Fr::random(&mut rng); let mint_asset = MintAsset { proof_generation_key: Some(proof_generation_key.clone()), diff --git a/ironfish-zkp/src/circuits/output.rs b/ironfish-zkp/src/circuits/output.rs index 25f7c3fdc5..8565ac1e6a 100644 --- a/ironfish-zkp/src/circuits/output.rs +++ b/ironfish-zkp/src/circuits/output.rs @@ -3,12 +3,12 @@ use std::io::{Read, Write}; use byteorder::{ReadBytesExt, WriteBytesExt}; use ff::PrimeField; -use bellperson::{gadgets::blake2s, Circuit, ConstraintSystem, SynthesisError}; +use ironfish_bellperson::{gadgets::blake2s, Circuit, ConstraintSystem, SynthesisError}; use group::{Curve, GroupEncoding}; -use jubjub::SubgroupPoint; +use ironfish_jubjub::SubgroupPoint; -use zcash_proofs::{ +use ironfish_proofs::{ circuit::{ecc, pedersen_hash}, constants::{ NOTE_COMMITMENT_RANDOMNESS_GENERATOR, PROOF_GENERATION_KEY_GENERATOR, @@ -24,7 +24,7 @@ use crate::{ }; use super::util::{expose_value_commitment, FromBytes}; -use bellperson::gadgets::boolean; +use ironfish_bellperson::gadgets::boolean; /// This is a circuit instance inspired from ZCash's `Output` circuit in the Sapling protocol /// https://github.com/zcash/librustzcash/blob/main/zcash_proofs/src/circuit/sapling.rs#L57-L70 @@ -39,17 +39,17 @@ pub struct Output { pub payment_address: Option, /// The randomness used to hide the note commitment data - pub commitment_randomness: Option, + pub commitment_randomness: Option, /// The ephemeral secret key for DH with recipient - pub esk: Option, + pub esk: Option, /// Key required to construct proofs for spending notes /// for a particular spending key pub proof_generation_key: Option, /// Re-randomization of the public key - pub ar: Option, + pub ar: Option, } impl Output { @@ -107,11 +107,11 @@ impl Output { } let mut commitment_randomness = None; if reader.read_u8()? == 1 { - commitment_randomness = Some(jubjub::Fr::read(&mut reader)?); + commitment_randomness = Some(ironfish_jubjub::Fr::read(&mut reader)?); } let mut esk = None; if reader.read_u8()? == 1 { - esk = Some(jubjub::Fr::read(&mut reader)?); + esk = Some(ironfish_jubjub::Fr::read(&mut reader)?); } let mut proof_generation_key = None; if reader.read_u8()? == 1 { @@ -119,7 +119,7 @@ impl Output { } let mut ar = None; if reader.read_u8()? == 1 { - ar = Some(jubjub::Fr::read(&mut reader)?); + ar = Some(ironfish_jubjub::Fr::read(&mut reader)?); } Ok(Output { value_commitment, @@ -212,7 +212,7 @@ impl Circuit for Output { )?; // drop_5 to ensure it's in the field - ivk.truncate(jubjub::Fr::CAPACITY as usize); + ivk.truncate(ironfish_jubjub::Fr::CAPACITY as usize); // Compute pk_d let pk_d_sender = ecc::fixed_base_multiplication( @@ -272,7 +272,7 @@ impl Circuit for Output { let pk_d = self .payment_address .as_ref() - .map(|e| jubjub::ExtendedPoint::from(*e).to_affine()); + .map(|e| ironfish_jubjub::ExtendedPoint::from(*e).to_affine()); // Witness the v-coordinate, encoded as little // endian bits (to match the representation) @@ -339,9 +339,9 @@ impl Circuit for Output { #[cfg(test)] mod test { - use bellperson::{gadgets::test::*, Circuit, ConstraintSystem}; use ff::Field; use group::{Curve, Group}; + use ironfish_bellperson::{gadgets::test::*, Circuit, ConstraintSystem}; use rand::rngs::StdRng; use rand::{Rng, RngCore, SeedableRng}; @@ -367,18 +367,18 @@ mod test { } }; - let value_commitment_randomness = jubjub::Fr::random(&mut rng); - let note_commitment_randomness = jubjub::Fr::random(&mut rng); + let value_commitment_randomness = ironfish_jubjub::Fr::random(&mut rng); + let note_commitment_randomness = ironfish_jubjub::Fr::random(&mut rng); let value_commitment = ValueCommitment { value: rng.next_u64(), randomness: value_commitment_randomness, asset_generator, }; - let nsk = jubjub::Fr::random(&mut rng); - let ak = jubjub::SubgroupPoint::random(&mut rng); - let esk = jubjub::Fr::random(&mut rng); - let ar = jubjub::Fr::random(&mut rng); + let nsk = ironfish_jubjub::Fr::random(&mut rng); + let ak = ironfish_jubjub::SubgroupPoint::random(&mut rng); + let esk = ironfish_jubjub::Fr::random(&mut rng); + let ar = ironfish_jubjub::Fr::random(&mut rng); let proof_generation_key = ProofGenerationKey::new(ak, nsk); @@ -389,7 +389,7 @@ mod test { let sender_address = payment_address; { - let rk = jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine(); + let rk = ironfish_jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine(); let mut cs = TestConstraintSystem::new(); let instance = Output { @@ -422,13 +422,15 @@ mod test { note_commitment_randomness, sender_address, ); - let expected_cmu = jubjub::ExtendedPoint::from(commitment).to_affine().get_u(); + let expected_cmu = ironfish_jubjub::ExtendedPoint::from(commitment) + .to_affine() + .get_u(); let expected_value_commitment = - jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine(); + ironfish_jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine(); let expected_epk = - jubjub::ExtendedPoint::from(*PUBLIC_KEY_GENERATOR * esk).to_affine(); + ironfish_jubjub::ExtendedPoint::from(*PUBLIC_KEY_GENERATOR * esk).to_affine(); assert_eq!(cs.num_inputs(), 8); assert_eq!(cs.get_input(0, "ONE"), blstrs::Scalar::one()); @@ -469,18 +471,18 @@ mod test { } }; - let value_commitment_randomness = jubjub::Fr::random(&mut rng); - let note_commitment_randomness = jubjub::Fr::random(&mut rng); + let value_commitment_randomness = ironfish_jubjub::Fr::random(&mut rng); + let note_commitment_randomness = ironfish_jubjub::Fr::random(&mut rng); let value_commitment = ValueCommitment { value: rng.next_u64(), randomness: value_commitment_randomness, asset_generator, }; - let nsk = jubjub::Fr::random(&mut rng); - let ak = jubjub::SubgroupPoint::random(&mut rng); - let esk = jubjub::Fr::random(&mut rng); - let ar = jubjub::Fr::random(&mut rng); + let nsk = ironfish_jubjub::Fr::random(&mut rng); + let ak = ironfish_jubjub::SubgroupPoint::random(&mut rng); + let esk = ironfish_jubjub::Fr::random(&mut rng); + let ar = ironfish_jubjub::Fr::random(&mut rng); let proof_generation_key = ProofGenerationKey::new(ak, nsk); diff --git a/ironfish-zkp/src/circuits/spend.rs b/ironfish-zkp/src/circuits/spend.rs index e339f1999f..1ad6fa3002 100644 --- a/ironfish-zkp/src/circuits/spend.rs +++ b/ironfish-zkp/src/circuits/spend.rs @@ -1,22 +1,22 @@ use std::io::{Read, Write}; -use bellperson::{Circuit, ConstraintSystem, SynthesisError}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::{Field, PrimeField}; use group::GroupEncoding; -use jubjub::SubgroupPoint; +use ironfish_bellperson::{Circuit, ConstraintSystem, SynthesisError}; +use ironfish_jubjub::SubgroupPoint; use crate::constants::{CRH_IVK_PERSONALIZATION, PRF_NF_PERSONALIZATION}; use crate::ProofGenerationKey; use crate::{constants::proof::PUBLIC_KEY_GENERATOR, primitives::ValueCommitment}; use super::util::{expose_value_commitment, FromBytes}; -use bellperson::gadgets::blake2s; -use bellperson::gadgets::boolean; -use bellperson::gadgets::multipack; -use bellperson::gadgets::num; -use bellperson::gadgets::Assignment; -use zcash_proofs::{ +use ironfish_bellperson::gadgets::blake2s; +use ironfish_bellperson::gadgets::boolean; +use ironfish_bellperson::gadgets::multipack; +use ironfish_bellperson::gadgets::num; +use ironfish_bellperson::gadgets::Assignment; +use ironfish_proofs::{ circuit::{ecc, pedersen_hash}, constants::{ NOTE_COMMITMENT_RANDOMNESS_GENERATOR, NULLIFIER_POSITION_GENERATOR, @@ -38,10 +38,10 @@ pub struct Spend { pub payment_address: Option, /// The randomness of the note commitment - pub commitment_randomness: Option, + pub commitment_randomness: Option, /// Re-randomization of the public key - pub ar: Option, + pub ar: Option, /// The authentication path of the commitment in the tree pub auth_path: Vec>, @@ -127,11 +127,11 @@ impl Spend { } let mut commitment_randomness = None; if reader.read_u8()? == 1 { - commitment_randomness = Some(jubjub::Fr::read(&mut reader)?); + commitment_randomness = Some(ironfish_jubjub::Fr::read(&mut reader)?); } let mut ar = None; if reader.read_u8()? == 1 { - ar = Some(jubjub::Fr::read(&mut reader)?); + ar = Some(ironfish_jubjub::Fr::read(&mut reader)?); } let len = reader.read_u64::().unwrap(); let mut auth_path = vec![]; @@ -248,7 +248,7 @@ impl Circuit for Spend { )?; // drop_5 to ensure it's in the field - ivk.truncate(jubjub::Fr::CAPACITY as usize); + ivk.truncate(ironfish_jubjub::Fr::CAPACITY as usize); // Compute pk_d let pk_d = ecc::fixed_base_multiplication( @@ -298,7 +298,8 @@ impl Circuit for Spend { // add sender address to note contents so correct note commitment can be calculated let sender_address = ecc::EdwardsPoint::witness( cs.namespace(|| "sender_address"), - self.sender_address.map(jubjub::ExtendedPoint::from), + self.sender_address + .map(ironfish_jubjub::ExtendedPoint::from), )?; // Place sender_address (pk_d) in the note @@ -449,16 +450,16 @@ impl Circuit for Spend { #[cfg(test)] mod test { - use bellperson::{ - gadgets::{multipack, test::*}, - Circuit, ConstraintSystem, - }; use blake2s_simd::Params as Blake2sParams; use ff::{Field, PrimeField, PrimeFieldBits}; use group::{Curve, Group, GroupEncoding}; + use ironfish_bellperson::{ + gadgets::{multipack, test::*}, + Circuit, ConstraintSystem, + }; + use ironfish_primitives::sapling::{pedersen_hash, Note, Rseed}; + use ironfish_primitives::{constants::NULLIFIER_POSITION_GENERATOR, sapling::Nullifier}; use rand::{rngs::StdRng, RngCore, SeedableRng}; - use zcash_primitives::sapling::{pedersen_hash, Note, Rseed}; - use zcash_primitives::{constants::NULLIFIER_POSITION_GENERATOR, sapling::Nullifier}; use crate::{ circuits::spend::Spend, @@ -480,28 +481,28 @@ mod test { for _ in 0..5 { let value_commitment = ValueCommitment { value: rng.next_u64(), - randomness: jubjub::Fr::random(&mut rng), + randomness: ironfish_jubjub::Fr::random(&mut rng), asset_generator: (*VALUE_COMMITMENT_VALUE_GENERATOR).into(), }; let proof_generation_key = ProofGenerationKey::new( - jubjub::SubgroupPoint::random(&mut rng), - jubjub::Fr::random(&mut rng), + ironfish_jubjub::SubgroupPoint::random(&mut rng), + ironfish_jubjub::Fr::random(&mut rng), ); let viewing_key = proof_generation_key.to_viewing_key(); let payment_address = *PUBLIC_KEY_GENERATOR * viewing_key.ivk().0; - let commitment_randomness = jubjub::Fr::random(&mut rng); + let commitment_randomness = ironfish_jubjub::Fr::random(&mut rng); let auth_path = vec![Some((blstrs::Scalar::random(&mut rng), rng.next_u32() % 2 != 0)); tree_depth]; - let ar = jubjub::Fr::random(&mut rng); + let ar = ironfish_jubjub::Fr::random(&mut rng); { - let rk = jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine(); + let rk = ironfish_jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine(); let expected_value_commitment = - jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine(); + ironfish_jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine(); let note = Note { value: value_commitment.value, g_d: *PUBLIC_KEY_GENERATOR, @@ -517,7 +518,9 @@ mod test { note.rcm(), payment_address, ); - let cmu = jubjub::ExtendedPoint::from(commitment).to_affine().get_u(); + let cmu = ironfish_jubjub::ExtendedPoint::from(commitment) + .to_affine() + .get_u(); let mut cur = cmu; @@ -534,7 +537,7 @@ mod test { let lhs = lhs.to_le_bits(); let rhs = rhs.to_le_bits(); - cur = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( + cur = ironfish_jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( pedersen_hash::Personalization::MerkleTree(i), lhs.iter() .by_vals() @@ -549,7 +552,8 @@ mod test { } } - let rho = commitment + (*NULLIFIER_POSITION_GENERATOR * jubjub::Fr::from(position)); + let rho = commitment + + (*NULLIFIER_POSITION_GENERATOR * ironfish_jubjub::Fr::from(position)); // Compute nf = BLAKE2s(nk | rho) let expected_nf = Nullifier::from_slice( @@ -637,28 +641,28 @@ mod test { for i in 0..5 { let value_commitment = ValueCommitment { value: i, - randomness: jubjub::Fr::from(1000 * (i + 1)), + randomness: ironfish_jubjub::Fr::from(1000 * (i + 1)), asset_generator: (*VALUE_COMMITMENT_VALUE_GENERATOR).into(), }; let proof_generation_key = ProofGenerationKey::new( - jubjub::SubgroupPoint::random(&mut rng), - jubjub::Fr::random(&mut rng), + ironfish_jubjub::SubgroupPoint::random(&mut rng), + ironfish_jubjub::Fr::random(&mut rng), ); let viewing_key = proof_generation_key.to_viewing_key(); let payment_address = *PUBLIC_KEY_GENERATOR * viewing_key.ivk().0; - let commitment_randomness = jubjub::Fr::random(&mut rng); + let commitment_randomness = ironfish_jubjub::Fr::random(&mut rng); let auth_path = vec![Some((blstrs::Scalar::random(&mut rng), rng.next_u32() % 2 != 0)); tree_depth]; - let ar = jubjub::Fr::random(&mut rng); + let ar = ironfish_jubjub::Fr::random(&mut rng); { - let rk = jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine(); + let rk = ironfish_jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine(); let expected_value_commitment = - jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine(); + ironfish_jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine(); assert_eq!( expected_value_commitment.get_u(), blstrs::Scalar::from_str_vartime(expected_commitment_us[i as usize]).unwrap() @@ -677,7 +681,9 @@ mod test { commitment_randomness, payment_address, ); - let cmu = jubjub::ExtendedPoint::from(commitment).to_affine().get_u(); + let cmu = ironfish_jubjub::ExtendedPoint::from(commitment) + .to_affine() + .get_u(); let mut cur = cmu; @@ -694,7 +700,7 @@ mod test { let lhs = lhs.to_le_bits(); let rhs = rhs.to_le_bits(); - cur = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( + cur = ironfish_jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( pedersen_hash::Personalization::MerkleTree(i), lhs.iter() .by_vals() @@ -709,7 +715,8 @@ mod test { } } - let rho = commitment + (*NULLIFIER_POSITION_GENERATOR * jubjub::Fr::from(position)); + let rho = commitment + + (*NULLIFIER_POSITION_GENERATOR * ironfish_jubjub::Fr::from(position)); // Compute nf = BLAKE2s(nk | rho) let expected_nf = Nullifier::from_slice( @@ -777,24 +784,24 @@ mod test { let value_commitment = ValueCommitment { value: rng.next_u64(), - randomness: jubjub::Fr::random(&mut rng), + randomness: ironfish_jubjub::Fr::random(&mut rng), asset_generator: (*VALUE_COMMITMENT_VALUE_GENERATOR).into(), }; let proof_generation_key = ProofGenerationKey::new( - jubjub::SubgroupPoint::random(&mut rng), - jubjub::Fr::random(&mut rng), + ironfish_jubjub::SubgroupPoint::random(&mut rng), + ironfish_jubjub::Fr::random(&mut rng), ); let viewing_key = proof_generation_key.to_viewing_key(); let payment_address = *PUBLIC_KEY_GENERATOR * viewing_key.ivk().0; - let commitment_randomness = jubjub::Fr::random(&mut rng); + let commitment_randomness = ironfish_jubjub::Fr::random(&mut rng); let auth_path = vec![Some((blstrs::Scalar::random(&mut rng), rng.next_u32() % 2 != 0)); 32]; - let ar = jubjub::Fr::random(&mut rng); + let ar = ironfish_jubjub::Fr::random(&mut rng); - let sender_address = jubjub::SubgroupPoint::random(&mut rng); + let sender_address = ironfish_jubjub::SubgroupPoint::random(&mut rng); let anchor = blstrs::Scalar::random(&mut rng); diff --git a/ironfish-zkp/src/circuits/util.rs b/ironfish-zkp/src/circuits/util.rs index 7cba93e53d..04f67d60a2 100644 --- a/ironfish-zkp/src/circuits/util.rs +++ b/ironfish-zkp/src/circuits/util.rs @@ -1,13 +1,13 @@ -use bellperson::{ +use ff::PrimeField; +use group::GroupEncoding; +use ironfish_bellperson::{ gadgets::{ blake2s, boolean::{self, AllocatedBit, Boolean}, }, ConstraintSystem, SynthesisError, }; -use ff::PrimeField; -use group::GroupEncoding; -use zcash_proofs::{ +use ironfish_proofs::{ circuit::ecc::{self, EdwardsPoint}, constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR, }; @@ -110,7 +110,7 @@ where Ok(value_bits) } -pub fn assert_valid_asset_generator>( +pub fn assert_valid_asset_generator>( mut cs: CS, asset_id: &[u8; ASSET_ID_LENGTH], asset_generator_repr: &[Boolean], @@ -149,20 +149,20 @@ pub(crate) trait FromBytes: Sized { fn read(reader: R) -> Result; } -impl FromBytes for jubjub::SubgroupPoint { +impl FromBytes for ironfish_jubjub::SubgroupPoint { fn read(mut reader: R) -> Result { let mut bytes = [0u8; 32]; reader.read_exact(&mut bytes)?; - Option::from(jubjub::SubgroupPoint::from_bytes(&bytes)) + Option::from(ironfish_jubjub::SubgroupPoint::from_bytes(&bytes)) .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid point")) } } -impl FromBytes for jubjub::Fr { +impl FromBytes for ironfish_jubjub::Fr { fn read(mut reader: R) -> Result { let mut bytes = [0u8; 32]; reader.read_exact(&mut bytes)?; - Option::from(jubjub::Fr::from_bytes(&bytes)).ok_or_else(|| { + Option::from(ironfish_jubjub::Fr::from_bytes(&bytes)).ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid field element") }) } diff --git a/ironfish-zkp/src/constants.rs b/ironfish-zkp/src/constants.rs index f6a1e8616e..d8663b773d 100644 --- a/ironfish-zkp/src/constants.rs +++ b/ironfish-zkp/src/constants.rs @@ -1,12 +1,12 @@ -use jubjub::SubgroupPoint; -use lazy_static::lazy_static; -pub use zcash_primitives::constants::{ +use ironfish_jubjub::SubgroupPoint; +pub use ironfish_primitives::constants::{ CRH_IVK_PERSONALIZATION, GH_FIRST_BLOCK, NOTE_COMMITMENT_RANDOMNESS_GENERATOR, NULLIFIER_POSITION_GENERATOR, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR, }; +use lazy_static::lazy_static; -pub use zcash_proofs::circuit::sapling::TREE_DEPTH; +pub use ironfish_proofs::circuit::sapling::TREE_DEPTH; /// Length in bytes of the asset identifier pub const ASSET_ID_LENGTH: usize = 32; @@ -57,8 +57,8 @@ lazy_static! { } pub mod proof { + use ironfish_proofs::constants::{generate_circuit_generator, FixedGeneratorOwned}; use lazy_static::lazy_static; - use zcash_proofs::constants::{generate_circuit_generator, FixedGeneratorOwned}; lazy_static! { pub static ref PUBLIC_KEY_GENERATOR: FixedGeneratorOwned = diff --git a/ironfish-zkp/src/lib.rs b/ironfish-zkp/src/lib.rs index b0ddf82de1..ffe7549b07 100644 --- a/ironfish-zkp/src/lib.rs +++ b/ironfish-zkp/src/lib.rs @@ -4,7 +4,7 @@ pub mod hex; pub mod primitives; pub mod util; -pub use zcash_primitives::sapling::{ +pub use ironfish_primitives::sapling::{ group_hash::group_hash, pedersen_hash, redjubjub, Diversifier, Note as SaplingNote, Nullifier, PaymentAddress, Rseed, ViewingKey, }; diff --git a/ironfish-zkp/src/primitives/proof_generation_key.rs b/ironfish-zkp/src/primitives/proof_generation_key.rs index d00c3ee1b2..a46f045ab0 100644 --- a/ironfish-zkp/src/primitives/proof_generation_key.rs +++ b/ironfish-zkp/src/primitives/proof_generation_key.rs @@ -1,9 +1,9 @@ use group::GroupEncoding; -use jubjub::{Fr, SubgroupPoint}; +use ironfish_jubjub::{Fr, SubgroupPoint}; +use ironfish_primitives::sapling::ProofGenerationKey as ZcashProofGenerationKey; use std::error::Error; use std::fmt; use std::ops::Deref; -use zcash_primitives::sapling::ProofGenerationKey as ZcashProofGenerationKey; use crate::hex::{bytes_to_hex, hex_to_bytes}; @@ -105,7 +105,7 @@ mod test { use ff::Field; use group::{Group, GroupEncoding}; - use jubjub; + use ironfish_jubjub; use rand::{rngs::StdRng, SeedableRng}; use crate::primitives::proof_generation_key::{ProofGenerationKey, ProofGenerationKeyError}; @@ -115,8 +115,8 @@ mod test { let mut rng = StdRng::seed_from_u64(0); let proof_generation_key = ProofGenerationKey::new( - jubjub::SubgroupPoint::random(&mut rng), - jubjub::Fr::random(&mut rng), + ironfish_jubjub::SubgroupPoint::random(&mut rng), + ironfish_jubjub::Fr::random(&mut rng), ); let serialized_bytes = proof_generation_key.to_bytes(); @@ -145,7 +145,7 @@ mod test { fn test_deserialize_nsk_error() { let mut proof_generation_key_bytes: [u8; 64] = [0; 64]; // Populate with valid bytes for ak and invalid bytes for nsk - let valid_ak = jubjub::SubgroupPoint::random(&mut StdRng::seed_from_u64(0)); + let valid_ak = ironfish_jubjub::SubgroupPoint::random(&mut StdRng::seed_from_u64(0)); proof_generation_key_bytes[0..32].copy_from_slice(&valid_ak.to_bytes()); // Assuming these are valid bytes for ak proof_generation_key_bytes[32..64].fill(0xFF); // Invalid bytes for nsk @@ -166,8 +166,8 @@ mod test { let mut rng = StdRng::seed_from_u64(0); let proof_generation_key = ProofGenerationKey::new( - jubjub::SubgroupPoint::random(&mut rng), - jubjub::Fr::random(&mut rng), + ironfish_jubjub::SubgroupPoint::random(&mut rng), + ironfish_jubjub::Fr::random(&mut rng), ); let serialized_bytes = proof_generation_key.to_bytes(); @@ -190,8 +190,8 @@ mod test { let mut rng = StdRng::seed_from_u64(0); let proof_generation_key = ProofGenerationKey::new( - jubjub::SubgroupPoint::random(&mut rng), - jubjub::Fr::random(&mut rng), + ironfish_jubjub::SubgroupPoint::random(&mut rng), + ironfish_jubjub::Fr::random(&mut rng), ); let hex_key = proof_generation_key.hex_key(); diff --git a/ironfish-zkp/src/primitives/value_commitment.rs b/ironfish-zkp/src/primitives/value_commitment.rs index 5937a461e6..dd48c406c3 100644 --- a/ironfish-zkp/src/primitives/value_commitment.rs +++ b/ironfish-zkp/src/primitives/value_commitment.rs @@ -2,7 +2,7 @@ use byteorder::{LittleEndian, ReadBytesExt}; use ff::Field; use group::{cofactor::CofactorGroup, GroupEncoding}; -use jubjub::ExtendedPoint; +use ironfish_jubjub::ExtendedPoint; use rand::thread_rng; use crate::constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR; @@ -12,21 +12,21 @@ use crate::constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR; #[derive(Clone)] pub struct ValueCommitment { pub value: u64, - pub randomness: jubjub::Fr, - pub asset_generator: jubjub::ExtendedPoint, + pub randomness: ironfish_jubjub::Fr, + pub asset_generator: ironfish_jubjub::ExtendedPoint, } impl ValueCommitment { - pub fn new(value: u64, asset_generator: jubjub::ExtendedPoint) -> Self { + pub fn new(value: u64, asset_generator: ironfish_jubjub::ExtendedPoint) -> Self { Self { value, - randomness: jubjub::Fr::random(thread_rng()), + randomness: ironfish_jubjub::Fr::random(thread_rng()), asset_generator, } } - pub fn commitment(&self) -> jubjub::SubgroupPoint { - (self.asset_generator.clear_cofactor() * jubjub::Fr::from(self.value)) + pub fn commitment(&self) -> ironfish_jubjub::SubgroupPoint { + (self.asset_generator.clear_cofactor() * ironfish_jubjub::Fr::from(self.value)) + (*VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness) } @@ -47,7 +47,7 @@ impl ValueCommitment { let value = reader.read_u64::()?; let mut randomness_bytes = [0u8; 32]; reader.read_exact(&mut randomness_bytes)?; - let randomness = jubjub::Fr::from_bytes(&randomness_bytes).unwrap(); + let randomness = ironfish_jubjub::Fr::from_bytes(&randomness_bytes).unwrap(); let mut asset_generator = [0u8; 32]; reader.read_exact(&mut asset_generator)?; let asset_generator = ExtendedPoint::from_bytes(&asset_generator).unwrap(); @@ -74,8 +74,8 @@ mod test { let value_commitment = ValueCommitment { value: 5, - randomness: jubjub::Fr::random(&mut rng), - asset_generator: jubjub::ExtendedPoint::random(&mut rng), + randomness: ironfish_jubjub::Fr::random(&mut rng), + asset_generator: ironfish_jubjub::ExtendedPoint::random(&mut rng), }; let commitment = value_commitment.commitment(); @@ -91,7 +91,7 @@ mod test { #[test] fn test_value_commitment_new() { - let generator = jubjub::ExtendedPoint::random(thread_rng()); + let generator = ironfish_jubjub::ExtendedPoint::random(thread_rng()); let value = 5; let value_commitment = ValueCommitment::new(value, generator); @@ -105,9 +105,9 @@ mod test { // Seed a fixed rng for determinism in the test let mut rng = StdRng::seed_from_u64(0); - let randomness = jubjub::Fr::random(&mut rng); + let randomness = ironfish_jubjub::Fr::random(&mut rng); - let asset_generator_one = jubjub::ExtendedPoint::random(&mut rng); + let asset_generator_one = ironfish_jubjub::ExtendedPoint::random(&mut rng); let value_commitment_one = ValueCommitment { value: 5, @@ -117,7 +117,7 @@ mod test { let commitment_one = value_commitment_one.commitment(); - let asset_generator_two = jubjub::ExtendedPoint::random(&mut rng); + let asset_generator_two = ironfish_jubjub::ExtendedPoint::random(&mut rng); let value_commitment_two = ValueCommitment { value: 5, @@ -145,9 +145,9 @@ mod test { // Seed a fixed rng for determinism in the test let mut rng = StdRng::seed_from_u64(0); - let randomness_one = jubjub::Fr::random(&mut rng); + let randomness_one = ironfish_jubjub::Fr::random(&mut rng); - let asset_generator = jubjub::ExtendedPoint::random(&mut rng); + let asset_generator = ironfish_jubjub::ExtendedPoint::random(&mut rng); let value_commitment_one = ValueCommitment { value: 5, @@ -157,7 +157,7 @@ mod test { let commitment_one = value_commitment_one.commitment(); - let randomness_two = jubjub::Fr::random(&mut rng); + let randomness_two = ironfish_jubjub::Fr::random(&mut rng); let value_commitment_two = ValueCommitment { value: 5, @@ -184,8 +184,8 @@ mod test { let value_one = 5; - let randomness = jubjub::Fr::random(&mut rng); - let asset_generator = jubjub::ExtendedPoint::random(&mut rng); + let randomness = ironfish_jubjub::Fr::random(&mut rng); + let asset_generator = ironfish_jubjub::ExtendedPoint::random(&mut rng); let value_commitment_one = ValueCommitment { value: value_one, @@ -226,8 +226,8 @@ mod test { let value_commitment = ValueCommitment { value: 5, - randomness: jubjub::Fr::random(&mut rng), - asset_generator: jubjub::ExtendedPoint::random(&mut rng), + randomness: ironfish_jubjub::Fr::random(&mut rng), + asset_generator: ironfish_jubjub::ExtendedPoint::random(&mut rng), }; // Serialize to bytes diff --git a/ironfish-zkp/src/util.rs b/ironfish-zkp/src/util.rs index 09d2df3d68..dc939fd423 100644 --- a/ironfish-zkp/src/util.rs +++ b/ironfish-zkp/src/util.rs @@ -3,7 +3,7 @@ use std::io::Write; use byteorder::{LittleEndian, WriteBytesExt}; use ff::PrimeField; use group::{cofactor::CofactorGroup, Group, GroupEncoding}; -use zcash_primitives::{ +use ironfish_primitives::{ constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR, sapling::pedersen_hash::{pedersen_hash, Personalization}, }; @@ -12,12 +12,12 @@ use crate::constants::VALUE_COMMITMENT_GENERATOR_PERSONALIZATION; /// Computes the note commitment with sender address, returning the full point. pub fn commitment_full_point( - asset_generator: jubjub::ExtendedPoint, + asset_generator: ironfish_jubjub::ExtendedPoint, value: u64, - pk_d: jubjub::SubgroupPoint, - rcm: jubjub::Fr, - sender_address: jubjub::SubgroupPoint, -) -> jubjub::SubgroupPoint { + pk_d: ironfish_jubjub::SubgroupPoint, + rcm: ironfish_jubjub::Fr, + sender_address: ironfish_jubjub::SubgroupPoint, +) -> ironfish_jubjub::SubgroupPoint { // Calculate the note contents, as bytes let mut note_contents = vec![]; @@ -56,7 +56,7 @@ pub fn commitment_full_point( /// This is a lightly modified group_hash function, for use with the asset identifier/generator flow #[allow(clippy::assertions_on_constants)] -pub fn asset_hash_to_point(tag: &[u8]) -> Option { +pub fn asset_hash_to_point(tag: &[u8]) -> Option { assert_eq!(VALUE_COMMITMENT_GENERATOR_PERSONALIZATION.len(), 8); // Check to see that scalar field is 255 bits @@ -69,7 +69,7 @@ pub fn asset_hash_to_point(tag: &[u8]) -> Option { .update(tag) .finalize(); - let p = jubjub::ExtendedPoint::from_bytes(h.as_array()); + let p = ironfish_jubjub::ExtendedPoint::from_bytes(h.as_array()); if p.is_some().into() { let p = p.unwrap(); diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 27cd8c2e7d..b6ca798d9f 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -1,43 +1,22 @@ # cargo-vet audits file -[audits] -reddsa = [] - [[audits.arrayvec]] who = "Andrea " criteria = "safe-to-deploy" delta = "0.7.2 -> 0.7.4" -[[audits.bellman]] -who = "Andrea " -criteria = "safe-to-deploy" -delta = "0.13.1 -> 0.13.1@git:1cc52ca33e6db14233f1cbc0c9c5b7c822b229ec" -notes = "Fork of the official bellperson owned by Iron Fish" - -[[audits.bellperson]] -who = "Andrea " +[[audits.bip0039]] +who = "andrea " criteria = "safe-to-deploy" -delta = "0.24.1 -> 0.24.1@git:37b9976bcd96986cbdc71ae09fc455015e3dfac0" -notes = "Fork of the official bellperson owned by Iron Fish" +delta = "0.10.1 -> 0.11.0" +notes = "Main change is how langauges are handled (previous version: through an enum, new version: through a marker type)" [[audits.crypto_box]] who = "Andrea " criteria = "safe-to-deploy" version = "0.9.1" -[[audits.equihash]] -who = "Andrea " -criteria = "safe-to-deploy" -delta = "0.2.0 -> 0.2.0@git:d551820030cb596eafe82226667f32b47164f91b" -notes = "Fork of the official equihash owned by Iron Fish" - -[[audits.f4jumble]] -who = "Andrea " -criteria = "safe-to-deploy" -delta = "0.1.0 -> 0.1.0@git:d551820030cb596eafe82226667f32b47164f91b" -notes = "Fork of the official f4jumble owned by Iron Fish" - [[audits.frost-core]] who = "Andrea " criteria = "safe-to-deploy" @@ -63,25 +42,42 @@ who = "Andrea " criteria = "safe-to-deploy" delta = "1.9.3 -> 2.2.6" -[[audits.ironfish-frost]] +[[audits.ironfish-bellperson]] who = "andrea " criteria = "safe-to-deploy" version = "0.1.0" -notes = "Crate owned by the Iron Fish organization" +notes = "Fork of the official bellperson owned by Iron Fish" -[[audits.ironfish-reddsa]] +[[audits.ironfish-frost]] who = "andrea " criteria = "safe-to-deploy" version = "0.1.0" notes = "Crate owned by the Iron Fish organization" -[[audits.jubjub]] -who = "Andrea " +[[audits.ironfish-jubjub]] +who = "andrea " criteria = "safe-to-deploy" -delta = "0.9.0 -> 0.9.0@git:531157cfa7b81ade207e819ef50c563843b10e30" -importable = false +version = "0.1.0" notes = "Fork of the official jubjub owned by Iron Fish" +[[audits.ironfish-primitives]] +who = "andrea " +criteria = "safe-to-deploy" +version = "0.1.0" +notes = "Fork of the official zcash_primitives owned by Iron Fish" + +[[audits.ironfish-proofs]] +who = "andrea " +criteria = "safe-to-deploy" +version = "0.1.0" +notes = "Fork of the official zcash_proofs owned by Iron Fish" + +[[audits.ironfish-reddsa]] +who = "andrea " +criteria = "safe-to-deploy" +version = "0.1.0" +notes = "Crate owned by the Iron Fish organization" + [[audits.mio]] who = "Andrea " criteria = "safe-to-deploy" @@ -112,36 +108,6 @@ who = "Andrea " criteria = "safe-to-deploy" version = "1.0.0" -[[audits.zcash_address]] -who = "Andrea " -criteria = "safe-to-deploy" -delta = "0.1.0 -> 0.1.0@git:d551820030cb596eafe82226667f32b47164f91b" -notes = "Fork of the official zcash_address owned by Iron Fish" - -[[audits.zcash_encoding]] -who = "Andrea " -criteria = "safe-to-deploy" -delta = "0.1.0 -> 0.1.0@git:d551820030cb596eafe82226667f32b47164f91b" -notes = "Fork of the official zcash_encoding owned by Iron Fish" - -[[audits.zcash_note_encryption]] -who = "Andrea " -criteria = "safe-to-deploy" -delta = "0.1.0 -> 0.1.0@git:d551820030cb596eafe82226667f32b47164f91b" -notes = "Fork of the official zcash_note_encryption owned by Iron Fish" - -[[audits.zcash_primitives]] -who = "Andrea " -criteria = "safe-to-deploy" -delta = "0.7.0 -> 0.7.0@git:d551820030cb596eafe82226667f32b47164f91b" -notes = "Fork of the official zcash_primitives owned by Iron Fish" - -[[audits.zcash_proofs]] -who = "Andrea " -criteria = "safe-to-deploy" -delta = "0.7.1 -> 0.7.1@git:d551820030cb596eafe82226667f32b47164f91b" -notes = "Fork of the official zcash_proofs owned by Iron Fish" - [[trusted.reddsa]] criteria = "safe-to-deploy" user-id = 6289 # Jack Grigg (str4d) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index e9c2ba3663..fce27b73e8 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -2,7 +2,7 @@ # cargo-vet config file [cargo-vet] -version = "0.9" +version = "0.10" [imports.bytecode-alliance] url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml" @@ -22,39 +22,12 @@ url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" [imports.zcash] url = "https://raw.githubusercontent.com/zcash/rust-ecosystem/main/supply-chain/audits.toml" -[policy.bellperson] -audit-as-crates-io = true - -[policy.equihash] -audit-as-crates-io = true - -[policy.f4jumble] -audit-as-crates-io = true - [policy.ironfish] audit-as-crates-io = true [policy.ironfish_zkp] audit-as-crates-io = true -[policy."jubjub:0.9.0@git:531157cfa7b81ade207e819ef50c563843b10e30"] -audit-as-crates-io = true - -[policy.zcash_address] -audit-as-crates-io = true - -[policy.zcash_encoding] -audit-as-crates-io = true - -[policy."zcash_note_encryption:0.1.0@git:d551820030cb596eafe82226667f32b47164f91b"] -audit-as-crates-io = true - -[policy.zcash_primitives] -audit-as-crates-io = true - -[policy.zcash_proofs] -audit-as-crates-io = true - [[exemptions.aead]] version = "0.4.3" criteria = "safe-to-deploy" @@ -87,10 +60,6 @@ criteria = "safe-to-deploy" version = "0.8.1" criteria = "safe-to-deploy" -[[exemptions.bellperson]] -version = "0.24.1" -criteria = "safe-to-deploy" - [[exemptions.bincode]] version = "1.3.3" criteria = "safe-to-deploy" @@ -227,10 +196,6 @@ criteria = "safe-to-deploy" version = "0.8.14" criteria = "safe-to-deploy" -[[exemptions.crypto-mac]] -version = "0.11.1" -criteria = "safe-to-deploy" - [[exemptions.crypto_secretbox]] version = "0.1.1" criteria = "safe-to-deploy" @@ -387,10 +352,6 @@ criteria = "safe-to-deploy" version = "0.12.4" criteria = "safe-to-deploy" -[[exemptions.hmac]] -version = "0.11.0" -criteria = "safe-to-deploy" - [[exemptions.http]] version = "0.2.9" criteria = "safe-to-deploy" @@ -943,14 +904,6 @@ criteria = "safe-to-deploy" version = "0.1.0" criteria = "safe-to-deploy" -[[exemptions.zcash_primitives]] -version = "0.7.0" -criteria = "safe-to-deploy" - -[[exemptions.zcash_proofs]] -version = "0.7.1" -criteria = "safe-to-deploy" - [[exemptions.zeroize]] version = "1.6.0" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index b77cff27d8..06a5fee8c1 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -497,6 +497,12 @@ criteria = "safe-to-deploy" delta = "0.1.0 -> 0.1.1" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" +[[audits.google.audits.password-hash]] +who = "Joshua Liebow-Feeser " +criteria = "safe-to-deploy" +delta = "0.3.2 -> 0.4.2" +aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.pbkdf2]] who = "Joshua Liebow-Feeser " criteria = "safe-to-deploy" @@ -756,7 +762,7 @@ who = "Henri Sivonen " criteria = "safe-to-deploy" user-id = 4484 # Henri Sivonen (hsivonen) start = "2019-02-26" -end = "2024-08-28" +end = "2025-10-23" notes = "I, Henri Sivonen, wrote encoding_rs for Gecko and have reviewed contributions by others. There are two caveats to the certification: 1) The crate does things that are documented to be UB but that do not appear to actually be UB due to integer types differing from the general rule; https://github.com/hsivonen/encoding_rs/issues/79 . 2) It would be prudent to re-review the code that reinterprets buffers of integers as SIMD vectors; see https://github.com/hsivonen/encoding_rs/issues/87 ." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" @@ -1123,6 +1129,12 @@ criteria = "safe-to-deploy" delta = "0.5.1 -> 0.5.2" aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" +[[audits.zcash.audits.bip0039]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "0.9.0 -> 0.10.1" +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + [[audits.zcash.audits.bls12_381]] who = "Sean Bowe " criteria = "safe-to-deploy" From 380f88841c4b8f739d365f79216768381a56af35 Mon Sep 17 00:00:00 2001 From: jowparks Date: Mon, 28 Oct 2024 13:44:18 -0700 Subject: [PATCH 51/81] add default account label to account list (#5589) --- ironfish-cli/src/commands/wallet/index.ts | 5 ++++- ironfish/src/rpc/routes/wallet/serializers.ts | 1 + ironfish/src/rpc/routes/wallet/types.ts | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ironfish-cli/src/commands/wallet/index.ts b/ironfish-cli/src/commands/wallet/index.ts index 92ad0dc869..37f6653122 100644 --- a/ironfish-cli/src/commands/wallet/index.ts +++ b/ironfish-cli/src/commands/wallet/index.ts @@ -44,6 +44,10 @@ export class AccountsCommand extends IronfishCommand { header: 'Account', minWidth: 11, }, + default: { + get: (row) => (row.default ? chalk.green('✓') : ''), + header: 'Default', + }, viewOnly: { get: (row) => (row.viewOnly ? chalk.green('✓') : ''), header: 'View Only', @@ -73,7 +77,6 @@ export class AccountsCommand extends IronfishCommand { { ...flags, printLine: this.log.bind(this), - 'no-header': flags['no-header'] ?? !flags.extended, }, ) diff --git a/ironfish/src/rpc/routes/wallet/serializers.ts b/ironfish/src/rpc/routes/wallet/serializers.ts index bb2c27b627..67ea68bba4 100644 --- a/ironfish/src/rpc/routes/wallet/serializers.ts +++ b/ironfish/src/rpc/routes/wallet/serializers.ts @@ -170,5 +170,6 @@ export async function serializeRpcAccountStatus( : null, scanningEnabled: account.scanningEnabled, viewOnly: !account.isSpendingAccount(), + default: wallet.getDefaultAccount()?.id === account.id, } } diff --git a/ironfish/src/rpc/routes/wallet/types.ts b/ironfish/src/rpc/routes/wallet/types.ts index 33ef60a55b..a692840702 100644 --- a/ironfish/src/rpc/routes/wallet/types.ts +++ b/ironfish/src/rpc/routes/wallet/types.ts @@ -172,6 +172,7 @@ export type RpcAccountStatus = { } | null scanningEnabled: boolean viewOnly: boolean + default: boolean } export const RpcAccountStatusSchema: yup.ObjectSchema = yup @@ -188,5 +189,6 @@ export const RpcAccountStatusSchema: yup.ObjectSchema = yup .defined(), scanningEnabled: yup.boolean().defined(), viewOnly: yup.boolean().defined(), + default: yup.boolean().defined(), }) .defined() From 3148ccf207170550c83170ab870801fd8f87ba58 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:08:47 -0700 Subject: [PATCH 52/81] conditionally logs scan starts and ends (#5591) only logs scan starts and ends in debug output if the start and end sequences differ reduces noise from debug output when running a synced node where each block produces a new scan with equal start and end sequences --- ironfish/src/wallet/scanner/walletScanner.ts | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.ts b/ironfish/src/wallet/scanner/walletScanner.ts index 64e74126cd..feb56da343 100644 --- a/ironfish/src/wallet/scanner/walletScanner.ts +++ b/ironfish/src/wallet/scanner/walletScanner.ts @@ -105,9 +105,12 @@ export class WalletScanner { this.state = scan unlock() - this.logger.debug( - `Scan starting from block ${scan.start.sequence} to ${scan.start.sequence}`, - ) + const logScanState = scan.start.sequence !== scan.end.sequence + if (logScanState) { + this.logger.debug( + `Scan starting from block ${scan.start.sequence} to ${scan.end.sequence}`, + ) + } decryptor.start(scan.abortController) @@ -136,11 +139,13 @@ export class WalletScanner { await decryptor.flush() })() .then(() => { - this.logger.debug( - `Finished scanning for transactions after ${Math.floor( - (Date.now() - scan.startedAt) / 1000, - )} seconds`, - ) + if (logScanState) { + this.logger.debug( + `Finished scanning for transactions after ${Math.floor( + (Date.now() - scan.startedAt) / 1000, + )} seconds`, + ) + } }) .finally(() => { decryptor.stop() From 1d6161dcd9b8e00fbfe4f033b8618079acfd829d Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:18:28 -0700 Subject: [PATCH 53/81] displays resolved file path on export path conflicts (#5592) instead of displaying the user-entered path (e.g., '.'), displays the fully-qualified path resolved by the file system --- ironfish-cli/src/commands/wallet/export.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish-cli/src/commands/wallet/export.ts b/ironfish-cli/src/commands/wallet/export.ts index 5a891d9c3a..ce36f52b2f 100644 --- a/ironfish-cli/src/commands/wallet/export.ts +++ b/ironfish-cli/src/commands/wallet/export.ts @@ -82,7 +82,7 @@ export class ExportCommand extends IronfishCommand { if (fs.existsSync(resolved)) { await confirmOrQuit( - `There is already an account backup at ${exportPath}` + + `There is already an account backup at ${resolved}` + `\n\nOverwrite the account backup with new file?`, ) } From 4704b1a8cce278848511ec08291f0ce26abcd697 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:44:56 -0700 Subject: [PATCH 54/81] update ws library to latest (#5593) --- ironfish/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ironfish/package.json b/ironfish/package.json index 746debaba9..cc4894ecf8 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -48,7 +48,7 @@ "sqlite": "4.0.23", "sqlite3": "5.1.6", "uuid": "8.3.2", - "ws": "8.12.1", + "ws": "8.18.0", "yup": "0.29.3" }, "scripts": { diff --git a/yarn.lock b/yarn.lock index 4aa5f7da6e..db06ca37c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10685,10 +10685,10 @@ write-pkg@^4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" -ws@8.12.1: - version "8.12.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f" - integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" From 569de29e90a04e66bf40cdae1d93c6b49715932d Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Tue, 29 Oct 2024 12:26:06 -0400 Subject: [PATCH 55/81] Export multisig from SDK (#5493) --- ironfish/src/index.ts | 1 + ironfish/src/{ => wallet}/multisig.test.slow.ts | 6 +++--- ironfish/src/wallet/multisig.ts | 9 +++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) rename ironfish/src/{ => wallet}/multisig.test.slow.ts (97%) create mode 100644 ironfish/src/wallet/multisig.ts diff --git a/ironfish/src/index.ts b/ironfish/src/index.ts index a8da92f77e..0dd1a6a20d 100644 --- a/ironfish/src/index.ts +++ b/ironfish/src/index.ts @@ -27,5 +27,6 @@ export * from './network' export * from './package' export * from './platform' export * from './primitives' +export * from './wallet/multisig' export { getFeeRate } from './memPool' export * as devUtils from './devUtils' diff --git a/ironfish/src/multisig.test.slow.ts b/ironfish/src/wallet/multisig.test.slow.ts similarity index 97% rename from ironfish/src/multisig.test.slow.ts rename to ironfish/src/wallet/multisig.test.slow.ts index c21d7dad40..84c4f3a016 100644 --- a/ironfish/src/multisig.test.slow.ts +++ b/ironfish/src/wallet/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 '../devUtils' +import { Note, RawTransaction } from '../primitives' +import { Transaction, TransactionVersion } from '../primitives/transaction' describe('multisig', () => { describe('dkg', () => { diff --git a/ironfish/src/wallet/multisig.ts b/ironfish/src/wallet/multisig.ts new file mode 100644 index 0000000000..9d4d14c39b --- /dev/null +++ b/ironfish/src/wallet/multisig.ts @@ -0,0 +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 { multisig } from '@ironfish/rust-nodejs' + +const Multisig = multisig + +export default Multisig From 78d5ad0a440dc64041ffa7e7f48ac6a1f8d1b7d6 Mon Sep 17 00:00:00 2001 From: jowparks Date: Tue, 29 Oct 2024 10:12:12 -0700 Subject: [PATCH 56/81] standardizes currency output information for currency prompting, for verified and unverified (#5594) --- ironfish-cli/src/commands/wallet/burn.ts | 5 +- .../src/commands/wallet/chainport/send.ts | 5 +- ironfish-cli/src/commands/wallet/mint.ts | 5 +- ironfish-cli/src/commands/wallet/send.ts | 5 +- ironfish-cli/src/utils/currency.ts | 46 +++++++------------ ironfish-cli/src/utils/fees.ts | 2 +- 6 files changed, 26 insertions(+), 42 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/burn.ts b/ironfish-cli/src/commands/wallet/burn.ts index 1ee14c9777..7ebc9d8f7b 100644 --- a/ironfish-cli/src/commands/wallet/burn.ts +++ b/ironfish-cli/src/commands/wallet/burn.ts @@ -151,11 +151,10 @@ This will destroy tokens and decrease supply for a given asset.` amount = await promptCurrency({ client: client, required: true, - text: 'Enter the amount of the custom asset to burn', + text: 'Enter the amount to burn', minimum: 1n, logger: this.logger, - assetId: assetId, - assetVerification: assetData.verification, + assetData, balance: { account, confirmations: flags.confirmations, diff --git a/ironfish-cli/src/commands/wallet/chainport/send.ts b/ironfish-cli/src/commands/wallet/chainport/send.ts index 93595ba3e9..bd677ad90d 100644 --- a/ironfish-cli/src/commands/wallet/chainport/send.ts +++ b/ironfish-cli/src/commands/wallet/chainport/send.ts @@ -296,11 +296,10 @@ export class BridgeCommand extends IronfishCommand { amount = await promptCurrency({ client: client, required: true, - text: 'Enter the amount in the major denomination', + text: 'Enter the amount', minimum: 1n, logger: this.logger, - assetId: assetId, - assetVerification: assetData.verification, + assetData, balance: { account: from, }, diff --git a/ironfish-cli/src/commands/wallet/mint.ts b/ironfish-cli/src/commands/wallet/mint.ts index d2b5d8a294..cba11b74a8 100644 --- a/ironfish-cli/src/commands/wallet/mint.ts +++ b/ironfish-cli/src/commands/wallet/mint.ts @@ -209,11 +209,10 @@ This will create tokens and increase supply for a given asset.` amount = await promptCurrency({ client: client, required: true, - text: 'Enter the amount', + text: 'Enter the amount to mint', minimum: 0n, logger: this.logger, - assetId: assetId, - assetVerification: assetData?.verification, + assetData, }) } diff --git a/ironfish-cli/src/commands/wallet/send.ts b/ironfish-cli/src/commands/wallet/send.ts index 50d3875b89..e7cc1a26f2 100644 --- a/ironfish-cli/src/commands/wallet/send.ts +++ b/ironfish-cli/src/commands/wallet/send.ts @@ -172,11 +172,10 @@ export class Send extends IronfishCommand { amount = await promptCurrency({ client: client, required: true, - text: 'Enter the amount in the major denomination', + text: 'Enter the amount', minimum: 1n, logger: this.logger, - assetId: assetId, - assetVerification: assetData.verification, + assetData, balance: { account: from, confirmations: flags.confirmations, diff --git a/ironfish-cli/src/utils/currency.ts b/ironfish-cli/src/utils/currency.ts index 67740de52a..9d32fe0fba 100644 --- a/ironfish-cli/src/utils/currency.ts +++ b/ironfish-cli/src/utils/currency.ts @@ -3,54 +3,46 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Asset } from '@ironfish/rust-nodejs' -import { Assert, CurrencyUtils, Logger, RpcAssetVerification, RpcClient } from '@ironfish/sdk' +import { Assert, CurrencyUtils, Logger, RpcAsset, RpcClient } from '@ironfish/sdk' import { inputPrompt } from '../ui' +import { renderAssetWithVerificationStatus } from './asset' /** * This prompts the user to enter an amount of currency in the major * denomination and returns the value in the minor denomination */ -export async function promptCurrency(options: { - client: Pick - text: string - logger: Logger - required: true - minimum?: bigint - assetId?: string - assetVerification?: RpcAssetVerification - balance?: { - account?: string - confirmations?: number - } -}): Promise - export async function promptCurrency(options: { client: Pick text: string logger: Logger required?: boolean minimum?: bigint - assetId?: string - assetVerification?: RpcAssetVerification + assetData?: RpcAsset balance?: { account?: string confirmations?: number } -}): Promise { +}): Promise { let text = options.text + if (options.assetData) { + const assetName = Buffer.from(options.assetData.name, 'hex').toString('utf-8') + text += ` in ${renderAssetWithVerificationStatus(assetName, { + verification: options.assetData.verification, + })}` + } if (options.balance) { const balance = await options.client.wallet.getAccountBalance({ account: options.balance.account, - assetId: options.assetId ?? Asset.nativeId().toString('hex'), + assetId: options.assetData?.id ?? Asset.nativeId().toString('hex'), confirmations: options.balance.confirmations, }) const renderedAvailable = CurrencyUtils.render( balance.content.available, false, - options.assetId, - options.assetVerification, + options.assetData?.id, + options.assetData?.verification, ) text += ` (balance ${renderedAvailable})` } @@ -59,14 +51,10 @@ export async function promptCurrency(options: { while (true) { const input = await inputPrompt(text, options.required) - if (!input) { - return null - } - const [amount, error] = CurrencyUtils.tryMajorToMinor( input, - options.assetId, - options.assetVerification, + options.assetData?.id, + options.assetData?.verification, ) if (error) { @@ -80,8 +68,8 @@ export async function promptCurrency(options: { const renderedMinimum = CurrencyUtils.render( options.minimum, false, - options.assetId, - options.assetVerification, + options.assetData?.id, + options.assetData?.verification, ) options.logger.error(`Error: Minimum is ${renderedMinimum}`) continue diff --git a/ironfish-cli/src/utils/fees.ts b/ironfish-cli/src/utils/fees.ts index fce9b08088..601dc58525 100644 --- a/ironfish-cli/src/utils/fees.ts +++ b/ironfish-cli/src/utils/fees.ts @@ -75,7 +75,7 @@ export async function selectFee(options: { const fee = await promptCurrency({ client: options.client, required: true, - text: 'Enter the fee amount in $IRON', + text: 'Enter the fee in $IRON', logger: options.logger, balance: { account: options.account, From 41813ec5f52cb9d407644d3fbd940d8f3125230b Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 28 Oct 2024 17:49:36 -0700 Subject: [PATCH 57/81] Mark a doc comment block as `text` instead of `ignore` This way, it won't show up in the output of `cargo test` --- ironfish-rust/src/frost_utils/account_keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish-rust/src/frost_utils/account_keys.rs b/ironfish-rust/src/frost_utils/account_keys.rs index a257cb1c34..cb51e299f6 100644 --- a/ironfish-rust/src/frost_utils/account_keys.rs +++ b/ironfish-rust/src/frost_utils/account_keys.rs @@ -26,7 +26,7 @@ pub struct MultisigAccountKeys { /// Derives the account keys for a multisig account, realizing the following key hierarchy: /// -/// ```ignore +/// ```text /// ak ─┐ /// ├─ ivk ── pk /// gsk ── nsk ── nk ─┘ From 8692bb2a5724f5804a1939d396506de0e6b07cc3 Mon Sep 17 00:00:00 2001 From: andrea Date: Wed, 30 Oct 2024 11:24:53 -0700 Subject: [PATCH 58/81] Fix an undeclared crate name error when the `stats` feature is enabled This was missed during the transition from `jubjub` to `ironfish_jubjub` --- ironfish-rust-nodejs/src/stats.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish-rust-nodejs/src/stats.rs b/ironfish-rust-nodejs/src/stats.rs index 3b52f5b567..7fb3a1bd86 100644 --- a/ironfish-rust-nodejs/src/stats.rs +++ b/ironfish-rust-nodejs/src/stats.rs @@ -13,7 +13,7 @@ use std::sync::Once; use std::thread; fn print_stats(colors: bool) { - let ecpm_stats = jubjub::stats(); + let ecpm_stats = ironfish_jubjub::stats(); let note_stats = ironfish::merkle_note::stats::get(); // Write the stats in a buffer first, then write the buffer to stderr. The goal of the buffer From 721203f9eaaf5477c37b9b8d050cdf0b80957f83 Mon Sep 17 00:00:00 2001 From: andrea Date: Wed, 30 Oct 2024 11:26:07 -0700 Subject: [PATCH 59/81] Run `clippy` with `--all-features` --- .github/workflows/rust_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index da72f8757c..eba37dec79 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -62,7 +62,7 @@ jobs: - name: "Clippy check on ironfish-rust" run: | - cargo clippy --all-targets -- -D warnings + cargo clippy --all-targets --all-features -- -D warnings cargo_vet: name: Vet Dependencies From fd381cf5be1886fdceb988a6dfe9f7264b591bf1 Mon Sep 17 00:00:00 2001 From: Derek Guenther Date: Wed, 30 Oct 2024 15:11:40 -0400 Subject: [PATCH 60/81] Use destination token symbol on bridge confirmation (#5596) --- .../src/commands/wallet/chainport/send.ts | 61 +++++++++++++------ ironfish-cli/src/utils/chainport/requests.ts | 9 ++- ironfish-cli/src/utils/chainport/types.ts | 5 ++ 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/chainport/send.ts b/ironfish-cli/src/commands/wallet/chainport/send.ts index bd677ad90d..b74437911b 100644 --- a/ironfish-cli/src/commands/wallet/chainport/send.ts +++ b/ironfish-cli/src/commands/wallet/chainport/send.ts @@ -20,8 +20,8 @@ import { HexFlag, IronFlag, RemoteFlags, ValueFlag } from '../../../flags' import * as ui from '../../../ui' import { ChainportBridgeTransaction, - ChainportNetwork, ChainportToken, + ChainportTokenWithNetwork, fetchChainportBridgeTransaction, fetchChainportTokenPaths, fetchChainportTokens, @@ -107,13 +107,13 @@ export class BridgeCommand extends IronfishCommand { } } - const { targetNetwork, from, to, amount, asset, assetData, expiration } = + const { targetToken, from, to, amount, asset, assetData, expiration } = await this.getAndValidateInputs(client, networkId) const rawTransaction = await this.constructBridgeTransaction( client, networkId, - targetNetwork, + targetToken, from, to, amount, @@ -275,7 +275,7 @@ export class BridgeCommand extends IronfishCommand { assetData.verification.status = 'verified' } - const targetNetwork = await this.selectNetwork(networkId, asset) + const targetToken = await this.selectTokenWithNetwork(networkId, asset) let amount if (flags.amount) { @@ -305,13 +305,13 @@ export class BridgeCommand extends IronfishCommand { }, }) } - return { targetNetwork, from, to, amount, asset, assetData, expiration } + return { targetToken, from, to, amount, asset, assetData, expiration } } private async constructBridgeTransaction( client: RpcClient, networkId: number, - network: ChainportNetwork, + tokenWithNetwork: ChainportTokenWithNetwork, from: string, to: string, amount: bigint, @@ -326,7 +326,7 @@ export class BridgeCommand extends IronfishCommand { networkId, amount, asset.web3_address, - network.chainport_network_id, + tokenWithNetwork.network.chainport_network_id, to, ) ux.action.stop() @@ -365,7 +365,15 @@ export class BridgeCommand extends IronfishCommand { rawTransaction = RawTransactionSerde.deserialize(bytes) } - this.displayTransactionSummary(txn, rawTransaction, from, to, assetData, network) + this.displayTransactionSummary( + txn, + rawTransaction, + from, + to, + asset, + assetData, + tokenWithNetwork, + ) return rawTransaction } @@ -375,14 +383,24 @@ export class BridgeCommand extends IronfishCommand { raw: RawTransaction, from: string, to: string, - assetData: RpcAsset, - network: ChainportNetwork, + sourceToken: ChainportToken, + sourceAsset: RpcAsset, + targetTokenWithNetwork: ChainportTokenWithNetwork, ) { const bridgeAmount = CurrencyUtils.render( BigInt(txn.bridge_output.amount) - BigInt(txn.bridge_fee.source_token_fee_amount ?? 0), true, - assetData.id, - assetData.verification, + 'bridge amount id', + { + // Note we're using Chainport's token decimals here in case the verified asset decimals + // don't match. We enforce through the API that the verified asset decimals are the same + // as chainport's decimals, so it's unlikely this will happen. + decimals: sourceToken.decimals, + // The outputs are given in source tokens, so we use the source token's decimals, but display + // using the target token's symbol. There could be rounding involved if the decimals don't match + // on either end, hence why this is an estimate. + symbol: targetTokenWithNetwork.token.symbol, + }, ) const ironfishNetworkFee = CurrencyUtils.render(raw.fee, true) @@ -406,8 +424,11 @@ export class BridgeCommand extends IronfishCommand { chainportFee = CurrencyUtils.render( BigInt(txn.bridge_fee.source_token_fee_amount ?? 0), true, - assetData.id, - assetData.verification, + 'chainport fee id', + { + decimals: sourceToken.decimals, + symbol: sourceAsset.verification.symbol, + }, ) } @@ -416,13 +437,13 @@ export class BridgeCommand extends IronfishCommand { From ${from} To ${to} - Target Network ${network.label} + Target Network ${targetTokenWithNetwork.network.label} Estimated Amount Received ${bridgeAmount} Fees: Chainport Fee ${chainportFee} Target Network Fee ${targetNetworkFee} - Ironfish Network Fee ${ironfishNetworkFee} + Iron Fish Network Fee ${ironfishNetworkFee} Outputs ${raw.outputs.length} Spends ${raw.spends.length} @@ -431,10 +452,10 @@ export class BridgeCommand extends IronfishCommand { this.logger.log(summary) } - private async selectNetwork( + private async selectTokenWithNetwork( networkId: number, asset: ChainportToken, - ): Promise { + ): Promise { ux.action.start('Fetching available networks') const networks = await fetchChainportTokenPaths(networkId, asset.id) ux.action.stop() @@ -444,14 +465,14 @@ export class BridgeCommand extends IronfishCommand { } const result = await inquirer.prompt<{ - selection: ChainportNetwork + selection: ChainportTokenWithNetwork }>([ { name: 'selection', message: `Select the network you would like to bridge ${asset.symbol} to`, type: 'list', choices: networks.map((network) => ({ - name: network.label, + name: network.network.label, value: network, })), }, diff --git a/ironfish-cli/src/utils/chainport/requests.ts b/ironfish-cli/src/utils/chainport/requests.ts index 558e0836c5..8a43f087e8 100644 --- a/ironfish-cli/src/utils/chainport/requests.ts +++ b/ironfish-cli/src/utils/chainport/requests.ts @@ -7,6 +7,7 @@ import { ChainportBridgeTransaction, ChainportNetwork, ChainportToken, + ChainportTokenWithNetwork, ChainportTransactionStatus, } from './types' @@ -42,10 +43,12 @@ export const fetchChainportTokens = async (networkId: number): Promise => { +): Promise => { const config = getConfig(networkId) - const url = new URL(`/bridges/tokens/${tokenId}/networks`, config.endpoint).toString() - return (await makeChainportRequest<{ data: ChainportNetwork[] }>(url)).data + const url = new URL(`/bridges/tokens/${tokenId}/networks`, config.endpoint) + url.searchParams.append('with_tokens', true.toString()) + return (await makeChainportRequest<{ data: ChainportTokenWithNetwork[] }>(url.toString())) + .data } export const fetchChainportBridgeTransaction = async ( diff --git a/ironfish-cli/src/utils/chainport/types.ts b/ironfish-cli/src/utils/chainport/types.ts index 7aaaee1cfc..af3b71beba 100644 --- a/ironfish-cli/src/utils/chainport/types.ts +++ b/ironfish-cli/src/utils/chainport/types.ts @@ -46,6 +46,11 @@ export type ChainportToken = { is_lifi: boolean } +export type ChainportTokenWithNetwork = { + network: ChainportNetwork + token: ChainportToken +} + export type ChainportTransactionStatus = | Record // empty object | { From 56dfee231a88801f01b0659a65110bef8fe16604 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:24:25 -0700 Subject: [PATCH 61/81] validates the point of failure for signing tx with wrong key (#5601) adds a test that signs a transaction with a different key than the key used to generate proofs for the transaction the test confirms that signing the transaction with the wrong key succeeds, but the transaction does not verify --- ironfish-rust/src/transaction/tests.rs | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/ironfish-rust/src/transaction/tests.rs b/ironfish-rust/src/transaction/tests.rs index 9c69edcc05..ce466b2581 100644 --- a/ironfish-rust/src/transaction/tests.rs +++ b/ironfish-rust/src/transaction/tests.rs @@ -812,6 +812,58 @@ fn test_sign_simple() { verify_transaction(&signed_transaction).expect("should be able to verify transaction"); } +#[test] +fn test_sign_key_mismatch_failure() { + let spender_key = SaplingKey::generate_key(); + let receiver_key = SaplingKey::generate_key(); + let sender_key = SaplingKey::generate_key(); + + let in_note = Note::new( + spender_key.public_address(), + 42, + "", + NATIVE_ASSET, + sender_key.public_address(), + ); + let out_note = Note::new( + receiver_key.public_address(), + 40, + "", + NATIVE_ASSET, + spender_key.public_address(), + ); + let witness = make_fake_witness(&in_note); + + // create transaction, add spend and output + let mut transaction = ProposedTransaction::new(TransactionVersion::latest()); + transaction + .add_spend(in_note, &witness) + .expect("should be able to add a spend"); + transaction + .add_output(out_note) + .expect("should be able to add an output"); + + // build transaction, generate proofs + let unsigned_transaction = transaction + .build( + spender_key.proof_authorizing_key, + spender_key.view_key().clone(), + spender_key.outgoing_view_key().clone(), + 1, + Some(spender_key.public_address()), + ) + .expect("should be able to build unsigned transaction"); + + // sign with different, mismatched key + let signer_key = SaplingKey::generate_key(); + let signed_transaction = unsigned_transaction + .sign(&signer_key) + .expect("should be able to sign transaction"); + + // verify transaction + verify_transaction(&signed_transaction).expect_err("should not be able to verify transaction"); +} + #[test] fn test_aggregate_signature_shares() { let spender_key = SaplingKey::generate_key(); From 92ed24330ccb8784e4406cf3cd1b73d1ef41a198 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:34:59 -0700 Subject: [PATCH 62/81] fix streamed csv output for wallet:transactions (#5605) this was printing the headers for every line because it was not checking the no-headers table option --- ironfish-cli/src/commands/wallet/transactions.ts | 12 ++++++------ ironfish-cli/src/ui/table.ts | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/transactions.ts b/ironfish-cli/src/commands/wallet/transactions.ts index 07fdedbdb7..8ac0550357 100644 --- a/ironfish-cli/src/commands/wallet/transactions.ts +++ b/ironfish-cli/src/commands/wallet/transactions.ts @@ -13,12 +13,12 @@ import { import { Flags } from '@oclif/core' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' -import { checkWalletUnlocked, table, TableColumns, TableFlags } from '../../ui' +import * as ui from '../../ui' import { getAssetsByIDs, useAccount } from '../../utils' import { extractChainportDataFromTransaction } from '../../utils/chainport' import { Format, TableCols } from '../../utils/table' -const { sort: _, ...tableFlags } = TableFlags +const { sort: _, ...tableFlags } = ui.TableFlags export class TransactionsCommand extends IronfishCommand { static description = `list the account's transactions` @@ -64,7 +64,7 @@ export class TransactionsCommand extends IronfishCommand { : Format.cli const client = await this.connectRpc() - await checkWalletUnlocked(client) + await ui.checkWalletUnlocked(client) const account = await useAccount(client, flags.account) @@ -114,7 +114,7 @@ export class TransactionsCommand extends IronfishCommand { transactionRows = this.getTransactionRows(assetLookup, transaction, format) } - table(transactionRows, columns, { + ui.table(transactionRows, columns, { printLine: this.log.bind(this), ...flags, 'no-header': !showHeader, @@ -254,8 +254,8 @@ export class TransactionsCommand extends IronfishCommand { extended: boolean, notes: boolean, format: Format, - ): TableColumns> { - let columns: TableColumns> = { + ): ui.TableColumns> { + let columns: ui.TableColumns> = { timestamp: TableCols.timestamp({ streaming: true, }), diff --git a/ironfish-cli/src/ui/table.ts b/ironfish-cli/src/ui/table.ts index a94d45d06f..ef33408905 100644 --- a/ironfish-cli/src/ui/table.ts +++ b/ironfish-cli/src/ui/table.ts @@ -146,7 +146,9 @@ class Table> { } columnHeaders.push(sanitizeCsvValue(column.header)) } - this.options.printLine(columnHeaders.join(',')) + if (!this.options['no-header']) { + this.options.printLine(columnHeaders.join(',')) + } for (const row of rows) { const rowValues = [] From 61c7085464f39c362fae66dc93ed53a6b37e3510 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:16:17 -0700 Subject: [PATCH 63/81] logs warning if bootstrap node on different network (#5606) * logs warning if bootstrap node on different network adds onMessage event handler to log a warning message if a bootstrap node is on a different network than the user's node outputs the warning without requiring verbose output and specifies that the peer with the network mismatch is a bootstrap node updates 'Peer.dispose' to use 'clearAfter' on 'onMessage' so that this second handler will still fire to log the warning even after disconnecting from the bootstrap node * uses renderNetworkName to log network names in warning, if available --- ironfish/src/network/peerNetwork.ts | 20 +++++++++++++++++++- ironfish/src/network/peers/peer.ts | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ironfish/src/network/peerNetwork.ts b/ironfish/src/network/peerNetwork.ts index 3982882587..c7a4460ed9 100644 --- a/ironfish/src/network/peerNetwork.ts +++ b/ironfish/src/network/peerNetwork.ts @@ -17,6 +17,7 @@ import { import { PeerStore } from '../fileStores/peerStore' import { createRootLogger, Logger } from '../logger' import { MetricsMonitor } from '../metrics' +import { renderNetworkName } from '../networks' import { FullNode } from '../node' import { IronfishPKG } from '../package' import { Platform } from '../platform' @@ -37,6 +38,7 @@ import { GetBlockTransactionsResponse, } from './messages/getBlockTransactions' import { GetCompactBlockRequest, GetCompactBlockResponse } from './messages/getCompactBlock' +import { IdentifyMessage } from './messages/identify' import { NetworkMessage } from './messages/networkMessage' import { NewBlockHashesMessage } from './messages/newBlockHashes' import { NewCompactBlockMessage } from './messages/newCompactBlock' @@ -368,11 +370,27 @@ export class PeerNetwork { // it's running on the default ironfish websocket port const port = url.port ? url.port : DEFAULT_WEBSOCKET_PORT - this.peerManager.connectToWebSocketAddress({ + const bootstrapNodePeer = this.peerManager.connectToWebSocketAddress({ host: url.hostname, port, whitelist: true, }) + + const warnNetworkMismatch = (message: NetworkMessage) => { + if (message instanceof IdentifyMessage) { + if (message.networkId !== this.networkId) { + this.logger.warn( + `Bootstrap node ${node} is on ${renderNetworkName( + message.networkId, + )} while we are on ${renderNetworkName(this.networkId)}`, + ) + } + + bootstrapNodePeer?.onMessage.off(warnNetworkMismatch) + } + } + + bootstrapNodePeer?.onMessage.on(warnNetworkMismatch) } } diff --git a/ironfish/src/network/peers/peer.ts b/ironfish/src/network/peers/peer.ts index 30d06784b5..5889f58516 100644 --- a/ironfish/src/network/peers/peer.ts +++ b/ironfish/src/network/peers/peer.ts @@ -662,7 +662,7 @@ export class Peer { */ dispose(): void { this.onStateChanged.clearAfter() - this.onMessage.clear() + this.onMessage.clearAfter() this.onBanned.clear() this.disposeMessageMeter() } From 29ed1d0c2d7a9a7be0d626905c1e245cf3ffbf90 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:38:02 -0700 Subject: [PATCH 64/81] adds command to display status for single account (#5595) 'wallet:account' displays the status information available in 'wallet:accounts --extended' for a single account accepted as an arg defaults to the wallet's default account Closes IFL-2651 --- ironfish-cli/src/commands/wallet/account.ts | 70 +++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 ironfish-cli/src/commands/wallet/account.ts diff --git a/ironfish-cli/src/commands/wallet/account.ts b/ironfish-cli/src/commands/wallet/account.ts new file mode 100644 index 0000000000..8048a75b57 --- /dev/null +++ b/ironfish-cli/src/commands/wallet/account.ts @@ -0,0 +1,70 @@ +/* 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 { RpcRequestError } from '@ironfish/sdk' +import { Args } from '@oclif/core' +import chalk from 'chalk' +import { IronfishCommand } from '../../command' +import { JsonFlags, RemoteFlags } from '../../flags' +import * as ui from '../../ui' + +export class AccountsCommand extends IronfishCommand { + static description = `display status for a wallet account` + static enableJsonFlag = true + + static flags = { + ...RemoteFlags, + ...JsonFlags, + } + + static args = { + account: Args.string({ + description: 'name of the account to display status for', + }), + } + + async start(): Promise { + const { args } = await this.parse(AccountsCommand) + + const client = await this.connectRpc() + await ui.checkWalletUnlocked(client) + + const account = + args.account ?? (await client.wallet.getDefaultAccount()).content.account?.name + + if (!account) { + this.log( + 'There is currently no account being used.\n' + + ' * Create an account: "ironfish wallet:create"\n' + + ' * List all accounts: "ironfish wallet:accounts"\n' + + ' * Use an existing account: "ironfish wallet:use "', + ) + return {} + } + + try { + const response = await client.wallet.getAccountStatus({ account }) + + const status: Record = { + Account: response.content.account.name, + Default: response.content.account.default ? chalk.green('✓') : '', + 'View Only': response.content.account.viewOnly ? chalk.green('✓') : '', + 'In Chain': response.content.account.head?.inChain ? chalk.green('✓') : '', + Scanning: response.content.account.scanningEnabled ? chalk.green('✓') : '', + Sequence: response.content.account.head?.sequence, + Head: response.content.account.head?.hash, + } + + this.log(ui.card(status)) + + return status + } catch (e) { + if (e instanceof RpcRequestError && e.codeMessage.includes('No account with name')) { + this.log(e.codeMessage) + return {} + } else { + throw e + } + } + } +} From e94d81955e345459036d2b75245eb1b81ff58163 Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 28 Oct 2024 17:49:11 -0700 Subject: [PATCH 65/81] Feature-gate all code in ironfish-rust that requires Sapling parameters This commit adds a new `transaction-proofs` Cargo feature (enabled by default) to the `ironfish-rust` crate. When this feature is disabled, all the structs and methods that create or verify zero-knowledge proofs are also disabled, hence making the Sapling parameters not needed. This way, developers have an option to use some of the `ironfish-rust` features (like transaction decryption) without having to deploy the Sapling parameters, which are several MBs in size. When `transaction-proofs` is disabled, all the transaction builder structs (like `ProposedTransaction`, `SpendBuilder, `OutputBuilder`, `MintBuilder`, ...) and related methods are unavailable, because those structs/methods all generate proofs. This commit also cleans up and reorganizes some code, in particular: * renamed the `ironfish::transaction::utils` module to `ironfish::transaction::verify`, because this module only contains methods for verification; * moved some of the transaction verification methods from `ironfish::transaction` to `ironfish::transaction::verify`; * moved `ProposedTransaction` to a dedicated submodule; --- ironfish-rust/Cargo.toml | 3 + ironfish-rust/src/lib.rs | 18 +- ironfish-rust/src/merkle_note.rs | 22 +- ironfish-rust/src/transaction/mints.rs | 40 +- ironfish-rust/src/transaction/mod.rs | 657 +--------------------- ironfish-rust/src/transaction/outputs.rs | 23 +- ironfish-rust/src/transaction/proposed.rs | 501 +++++++++++++++++ ironfish-rust/src/transaction/spends.rs | 47 +- ironfish-rust/src/transaction/tests.rs | 69 ++- ironfish-rust/src/transaction/utils.rs | 52 -- ironfish-rust/src/transaction/verify.rs | 189 +++++++ 11 files changed, 850 insertions(+), 771 deletions(-) create mode 100644 ironfish-rust/src/transaction/proposed.rs delete mode 100644 ironfish-rust/src/transaction/utils.rs create mode 100644 ironfish-rust/src/transaction/verify.rs diff --git a/ironfish-rust/Cargo.toml b/ironfish-rust/Cargo.toml index 88ebb8c1c8..f06f63c5ac 100644 --- a/ironfish-rust/Cargo.toml +++ b/ironfish-rust/Cargo.toml @@ -24,9 +24,12 @@ workspace = true workspace = true [features] +default = ["transaction-proofs"] + benchmark = [] download-params = ["dep:reqwest"] note-encryption-stats = [] +transaction-proofs = [] [lib] name = "ironfish" diff --git a/ironfish-rust/src/lib.rs b/ironfish-rust/src/lib.rs index 45068042ab..b8f26ba1dd 100644 --- a/ironfish-rust/src/lib.rs +++ b/ironfish-rust/src/lib.rs @@ -14,7 +14,6 @@ pub mod mining; pub mod nacl; pub mod note; pub mod rolling_filter; -pub mod sapling_bls12; pub mod serializing; pub mod signal_catcher; pub mod transaction; @@ -22,6 +21,12 @@ pub mod util; pub mod witness; pub mod xchacha20poly1305; +#[cfg(any(test, feature = "benchmark"))] +pub mod test_util; + +#[cfg(feature = "transaction-proofs")] +pub mod sapling_bls12; + pub use { ironfish_frost::frost, ironfish_frost::participant, @@ -29,17 +34,15 @@ pub use { merkle_note::MerkleNote, merkle_note_hash::MerkleNoteHash, note::Note, - transaction::{ - outputs::OutputDescription, spends::SpendDescription, ProposedTransaction, Transaction, - }, + transaction::{outputs::OutputDescription, spends::SpendDescription, Transaction}, }; -#[cfg(any(test, feature = "benchmark"))] -pub mod test_util; - #[cfg(feature = "benchmark")] pub use ironfish_zkp::primitives::ValueCommitment; +#[cfg(feature = "transaction-proofs")] +pub use transaction::ProposedTransaction; + // The main entry-point to the sapling API. Construct this with loaded parameters, and then call // methods on it to do the actual work. // @@ -57,6 +60,7 @@ pub struct Sapling { pub mint_verifying_key: groth16::PreparedVerifyingKey, } +#[cfg(feature = "transaction-proofs")] impl Sapling { /// Initialize a Sapling instance and prepare for proving. Load the parameters from files /// at a known location (`$OUT_DIR/sapling_params`). diff --git a/ironfish-rust/src/merkle_note.rs b/ironfish-rust/src/merkle_note.rs index fb1b873544..a9aec78617 100644 --- a/ironfish-rust/src/merkle_note.rs +++ b/ironfish-rust/src/merkle_note.rs @@ -2,31 +2,29 @@ * 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/. */ +//! Implement a merkle note to store all the values that need to go into a merkle tree. +//! A tree containing these values can serve as a snapshot of the entire chain. + use crate::{ errors::IronfishError, keys::EphemeralKeyPair, - serializing::{read_point, read_point_unchecked}, -}; - -/// Implement a merkle note to store all the values that need to go into a merkle tree. -/// A tree containing these values can serve as a snapshot of the entire chain. -use super::{ keys::{shared_secret, IncomingViewKey, OutgoingViewKey, PublicAddress}, note::{Note, ENCRYPTED_NOTE_SIZE}, serializing::{aead, read_scalar}, - witness::{WitnessNode, WitnessTrait}, + serializing::{read_point, read_point_unchecked}, MerkleNoteHash, }; - use blake2b_simd::Params as Blake2b; use blstrs::Scalar; use ff::PrimeField; use group::GroupEncoding; use ironfish_jubjub::{ExtendedPoint, SubgroupPoint}; use ironfish_zkp::primitives::ValueCommitment; - use std::{convert::TryInto, io}; +#[cfg(feature = "transaction-proofs")] +use crate::witness::{WitnessNode, WitnessTrait}; + pub const ENCRYPTED_SHARED_KEY_SIZE: usize = 64; pub const NOTE_ENCRYPTION_KEY_SIZE: usize = ENCRYPTED_SHARED_KEY_SIZE + aead::MAC_SIZE; @@ -108,6 +106,7 @@ impl MerkleNote { /// Helper function to instantiate a MerkleNote with pre-set /// note_encryption_keys. Should only be used for miners fee transactions. + #[cfg(feature = "transaction-proofs")] pub(crate) fn new_for_miners_fee( note: &Note, value_commitment: &ValueCommitment, @@ -283,6 +282,7 @@ impl MerkleNote { } } +#[cfg(feature = "transaction-proofs")] pub(crate) fn sapling_auth_path(witness: &dyn WitnessTrait) -> Vec> { let mut auth_path = vec![]; for element in &witness.get_auth_path() { @@ -302,6 +302,7 @@ pub(crate) fn sapling_auth_path(witness: &dyn WitnessTrait) -> Vec u64 { let mut pos = 0; for (i, element) in witness.get_auth_path().iter().enumerate() { @@ -460,9 +461,10 @@ mod test { ); } - #[test] /// Test to confirm that creating a [`MerkleNote`] via new_for_miners_note() /// does use the hard-coded miners fee note encryption keys + #[test] + #[cfg(feature = "transaction-proofs")] fn test_new_miners_fee_key() { let receiver_key = SaplingKey::generate_key(); let sender_key: SaplingKey = SaplingKey::generate_key(); diff --git a/ironfish-rust/src/transaction/mints.rs b/ironfish-rust/src/transaction/mints.rs index 2638fec204..3f09c3d8b9 100644 --- a/ironfish-rust/src/transaction/mints.rs +++ b/ironfish-rust/src/transaction/mints.rs @@ -2,8 +2,13 @@ * 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/. */ -use std::io; - +use crate::{ + assets::asset::Asset, + errors::{IronfishError, IronfishErrorKind}, + serializing::read_scalar, + transaction::TransactionVersion, + PublicAddress, SaplingKey, +}; use blstrs::{Bls12, Scalar}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::Field; @@ -12,25 +17,19 @@ use ironfish_bellperson::groth16; use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::{ constants::SPENDING_KEY_GENERATOR, - proofs::MintAsset, redjubjub::{self, Signature}, - ProofGenerationKey, }; use rand::thread_rng; +use std::io; -use crate::{ - assets::asset::Asset, - errors::{IronfishError, IronfishErrorKind}, - sapling_bls12::SAPLING, - serializing::read_scalar, - transaction::TransactionVersion, - PublicAddress, SaplingKey, -}; - -use super::utils::verify_mint_proof; +#[cfg(feature = "transaction-proofs")] +use crate::{sapling_bls12::SAPLING, transaction::verify::verify_mint_proof}; +#[cfg(feature = "transaction-proofs")] +use ironfish_zkp::{proofs::MintAsset, ProofGenerationKey}; /// Parameters used to build a circuit that verifies an asset can be minted with /// a given key +#[cfg(feature = "transaction-proofs")] pub struct MintBuilder { /// Asset to be minted pub asset: Asset, @@ -43,6 +42,7 @@ pub struct MintBuilder { pub transfer_ownership_to: Option, } +#[cfg(feature = "transaction-proofs")] impl MintBuilder { pub fn new(asset: Asset, value: u64) -> Self { Self { @@ -362,25 +362,25 @@ impl MintDescription { } #[cfg(test)] +#[cfg(feature = "transaction-proofs")] mod test { - use ff::Field; - use ironfish_zkp::{constants::SPENDING_KEY_GENERATOR, redjubjub}; - use rand::{random, thread_rng}; - use crate::{ assets::asset::Asset, errors::IronfishErrorKind, transaction::{ mints::{MintBuilder, MintDescription}, - utils::verify_mint_proof, + verify::verify_mint_proof, TransactionVersion, }, PublicAddress, SaplingKey, }; + use ff::Field; + use ironfish_zkp::{constants::SPENDING_KEY_GENERATOR, redjubjub}; + use rand::{random, thread_rng}; - #[test] /// Test that we can create a builder with a valid asset and proof /// generation key + #[test] fn test_mint_builder() { let key = SaplingKey::generate_key(); let creator = key.public_address(); diff --git a/ironfish-rust/src/transaction/mod.rs b/ironfish-rust/src/transaction/mod.rs index a3e04b049a..1b71a3d202 100644 --- a/ironfish-rust/src/transaction/mod.rs +++ b/ironfish-rust/src/transaction/mod.rs @@ -2,52 +2,27 @@ * 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/. */ -use blstrs::Bls12; -use ff::Field; -use outputs::OutputBuilder; -use spends::{SpendBuilder, UnsignedSpendDescription}; -use value_balances::ValueBalances; - use crate::{ - assets::{ - asset::Asset, - asset_identifier::{AssetIdentifier, NATIVE_ASSET}, - }, - errors::{IronfishError, IronfishErrorKind}, - keys::{PublicAddress, SaplingKey}, - note::Note, - sapling_bls12::SAPLING, - witness::WitnessTrait, - OutgoingViewKey, OutputDescription, SpendDescription, ViewKey, + errors::IronfishError, + transaction::{burns::BurnDescription, mints::MintDescription}, + OutputDescription, SpendDescription, }; - -use rand::{rngs::OsRng, thread_rng}; - use blake2b_simd::Params as Blake2b; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use group::GroupEncoding; -use ironfish_bellperson::groth16::{verify_proofs_batch, PreparedVerifyingKey}; -use ironfish_jubjub::ExtendedPoint; - -use ironfish_zkp::{ - constants::{ - NATIVE_VALUE_COMMITMENT_GENERATOR, SPENDING_KEY_GENERATOR, - VALUE_COMMITMENT_RANDOMNESS_GENERATOR, - }, - redjubjub::{self, PrivateKey, PublicKey, Signature}, - ProofGenerationKey, -}; - +use ironfish_zkp::redjubjub::{self, Signature}; use std::{ io::{self, Write}, - iter, slice::Iter, }; -use self::{ - burns::{BurnBuilder, BurnDescription}, - mints::{MintBuilder, MintDescription, UnsignedMintDescription}, - unsigned::UnsignedTransaction, +#[cfg(feature = "transaction-proofs")] +use crate::errors::IronfishErrorKind; +#[cfg(feature = "transaction-proofs")] +use ironfish_jubjub::ExtendedPoint; +#[cfg(feature = "transaction-proofs")] +use ironfish_zkp::constants::{ + NATIVE_VALUE_COMMITMENT_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR, }; pub mod burns; @@ -56,15 +31,27 @@ pub mod outputs; pub mod spends; pub mod unsigned; -mod utils; -mod value_balances; mod version; +#[cfg(feature = "transaction-proofs")] +mod proposed; +#[cfg(feature = "transaction-proofs")] +mod value_balances; +#[cfg(feature = "transaction-proofs")] +mod verify; + #[cfg(test)] mod tests; pub use version::TransactionVersion; +#[cfg(feature = "transaction-proofs")] +pub use proposed::ProposedTransaction; +#[cfg(feature = "transaction-proofs")] +pub use verify::batch_verify_transactions; +#[cfg(feature = "transaction-proofs")] +pub use verify::verify_transaction; + const SIGNATURE_HASH_PERSONALIZATION: &[u8; 8] = b"IFsighsh"; const TRANSACTION_SIGNATURE_VERSION: &[u8; 1] = &[0]; pub const TRANSACTION_SIGNATURE_SIZE: usize = 64; @@ -72,466 +59,6 @@ pub const TRANSACTION_PUBLIC_KEY_SIZE: usize = 32; pub const TRANSACTION_EXPIRATION_SIZE: usize = 4; pub const TRANSACTION_FEE_SIZE: usize = 8; -/// A collection of spend and output proofs that can be signed and verified. -/// In general, all the spent values should add up to all the output values. -/// -/// The Transaction is used while the spends and outputs are being constructed, -/// and contains working state that is used to create the transaction information. -/// -/// The Transaction, below, contains the serializable version, without any -/// secret keys or state not needed for verifying. -pub struct ProposedTransaction { - /// The transaction serialization version. This can be incremented when - /// changes need to be made to the transaction format - version: TransactionVersion, - - /// Builders for the proofs of the individual spends with all values required to calculate - /// the signatures. - spends: Vec, - - /// Builders for proofs of the individual outputs with values required to calculate - /// signatures. Note: This is commonly referred to as - /// `outputs` in the literature. - outputs: Vec, - - /// Builders for proofs of the individual mints with all values required to - /// calculate the signatures. - mints: Vec, - - /// Descriptions containing the assets and value commitments to be burned. - /// We do not need to use a builder here since we only need to handle - /// balancing and effects are handled by outputs. - burns: Vec, - - /// The balance of all the spends minus all the outputs. The difference - /// is the fee paid to the miner for mining the transaction. - value_balances: ValueBalances, - - /// This is the sequence in the chain the transaction will expire at and be - /// removed from the mempool. A value of 0 indicates the transaction will - /// not expire. - expiration: u32, - - // randomness used for the transaction to calculate the randomized ak, which - // allows us to verify the sender address is valid and stored in the notes - // Used to add randomness to signature generation without leaking the - // key. Referred to as `ar` in the literature. - public_key_randomness: ironfish_jubjub::Fr, - // NOTE: If adding fields here, you may need to add fields to - // signature hash method, and also to Transaction. -} - -impl ProposedTransaction { - pub fn new(version: TransactionVersion) -> Self { - Self { - version, - spends: vec![], - outputs: vec![], - mints: vec![], - burns: vec![], - value_balances: ValueBalances::new(), - expiration: 0, - public_key_randomness: ironfish_jubjub::Fr::random(thread_rng()), - } - } - - /// Spend the note owned by spender_key at the given witness location. - pub fn add_spend( - &mut self, - note: Note, - witness: &dyn WitnessTrait, - ) -> Result<(), IronfishError> { - self.value_balances - .add(note.asset_id(), note.value().try_into()?)?; - - self.spends.push(SpendBuilder::new(note, witness)); - - Ok(()) - } - - /// Create a proof of a new note owned by the recipient in this - /// transaction. - pub fn add_output(&mut self, note: Note) -> Result<(), IronfishError> { - self.value_balances - .subtract(note.asset_id(), note.value().try_into()?)?; - - self.outputs.push(OutputBuilder::new(note)); - - Ok(()) - } - - pub fn add_mint(&mut self, asset: Asset, value: u64) -> Result<(), IronfishError> { - self.value_balances.add(asset.id(), value.try_into()?)?; - - self.mints.push(MintBuilder::new(asset, value)); - - Ok(()) - } - - pub fn add_mint_with_new_owner( - &mut self, - asset: Asset, - value: u64, - new_owner: PublicAddress, - ) -> Result<(), IronfishError> { - self.value_balances.add(asset.id(), value.try_into()?)?; - - let mut mint_builder = MintBuilder::new(asset, value); - mint_builder.transfer_ownership_to(new_owner); - self.mints.push(mint_builder); - - Ok(()) - } - - pub fn add_burn(&mut self, asset_id: AssetIdentifier, value: u64) -> Result<(), IronfishError> { - self.value_balances.subtract(&asset_id, value.try_into()?)?; - - self.burns.push(BurnBuilder::new(asset_id, value)); - - Ok(()) - } - - fn add_change_notes( - &mut self, - change_goes_to: Option, - public_address: PublicAddress, - intended_transaction_fee: i64, - ) -> Result<(), IronfishError> { - let mut change_notes = vec![]; - - for (asset_id, value) in self.value_balances.iter() { - let is_native_asset = asset_id == &NATIVE_ASSET; - - let change_amount = match is_native_asset { - true => *value - intended_transaction_fee, - false => *value, - }; - - if change_amount < 0 { - return Err(IronfishError::new(IronfishErrorKind::InvalidBalance)); - } - if change_amount > 0 { - let change_address = change_goes_to.unwrap_or(public_address); - let change_note = Note::new( - change_address, - change_amount as u64, // we checked it was positive - "", - *asset_id, - public_address, - ); - - change_notes.push(change_note); - } - } - for change_note in change_notes { - self.add_output(change_note)?; - } - Ok(()) - } - - pub fn build( - &mut self, - proof_authorizing_key: ironfish_jubjub::Fr, - view_key: ViewKey, - outgoing_view_key: OutgoingViewKey, - intended_transaction_fee: i64, - change_goes_to: Option, - ) -> Result { - let public_address = view_key.public_address()?; - - let proof_generation_key = - ProofGenerationKey::new(view_key.authorizing_key, proof_authorizing_key); - - // skip adding change notes if this is special case of a miners fee transaction - let is_miners_fee = self.outputs.iter().any(|output| output.get_is_miners_fee()); - if !is_miners_fee { - self.add_change_notes(change_goes_to, public_address, intended_transaction_fee)?; - } - - // The public key after randomization has been applied. This is used - // during signature verification. Referred to as `rk` in the literature - // Calculated from the authorizing key and the public_key_randomness. - let randomized_public_key = redjubjub::PublicKey(view_key.authorizing_key.into()) - .randomize(self.public_key_randomness, *SPENDING_KEY_GENERATOR); - - let mut unsigned_spends = Vec::with_capacity(self.spends.len()); - for spend in &self.spends { - unsigned_spends.push(spend.build( - &proof_generation_key, - &view_key, - &self.public_key_randomness, - &randomized_public_key, - )?); - } - - let mut output_descriptions = Vec::with_capacity(self.outputs.len()); - for output in &self.outputs { - output_descriptions.push(output.build( - &proof_generation_key, - &outgoing_view_key, - &self.public_key_randomness, - &randomized_public_key, - )?); - } - - let mut unsigned_mints = Vec::with_capacity(self.mints.len()); - for mint in &self.mints { - unsigned_mints.push(mint.build( - &proof_generation_key, - &public_address, - &self.public_key_randomness, - &randomized_public_key, - )?); - } - - let mut burn_descriptions = Vec::with_capacity(self.burns.len()); - for burn in &self.burns { - burn_descriptions.push(burn.build()); - } - - let data_to_sign = self.transaction_signature_hash( - &unsigned_spends, - &output_descriptions, - &unsigned_mints, - &burn_descriptions, - &randomized_public_key, - )?; - - // Create and verify binding signature keys - let (binding_signature_private_key, binding_signature_public_key) = - self.binding_signature_keys(&unsigned_mints, &burn_descriptions)?; - - let binding_signature = self.binding_signature( - &binding_signature_private_key, - &binding_signature_public_key, - &data_to_sign, - )?; - - Ok(UnsignedTransaction { - burns: burn_descriptions, - mints: unsigned_mints, - outputs: output_descriptions, - spends: unsigned_spends, - version: self.version, - fee: intended_transaction_fee, - binding_signature, - randomized_public_key, - public_key_randomness: self.public_key_randomness, - expiration: self.expiration, - }) - } - - /// Post the transaction. This performs a bit of validation, and signs - /// the spends with a signature that proves the spends are part of this - /// transaction. - /// - /// Transaction fee is the amount the spender wants to send to the miner - /// for mining this transaction. This has to be non-negative; sane miners - /// wouldn't accept a transaction that takes money away from them. - /// - /// sum(spends) + sum(mints) - sum(outputs) - sum(burns) - intended_transaction_fee - change = 0 - /// aka: self.value_balance - intended_transaction_fee - change = 0 - pub fn post( - &mut self, - spender_key: &SaplingKey, - change_goes_to: Option, - intended_transaction_fee: u64, - ) -> Result { - let i64_fee = i64::try_from(intended_transaction_fee)?; - - let unsigned = self.build( - spender_key.proof_authorizing_key, - spender_key.view_key().clone(), - spender_key.outgoing_view_key().clone(), - i64_fee, - change_goes_to, - )?; - unsigned.sign(spender_key) - } - - /// Special case for posting a miners fee transaction. Miner fee transactions - /// are unique in that they generate currency. They do not have any spends - /// or change and therefore have a negative transaction fee. In normal use, - /// a miner would not accept such a transaction unless it was explicitly set - /// as the miners fee. - pub fn post_miners_fee( - &mut self, - spender_key: &SaplingKey, - ) -> Result { - if !self.spends.is_empty() - || self.outputs.len() != 1 - || !self.mints.is_empty() - || !self.burns.is_empty() - { - return Err(IronfishError::new( - IronfishErrorKind::InvalidMinersFeeTransaction, - )); - } - self.post_miners_fee_unchecked(spender_key) - } - - /// Do not call this directly -- see post_miners_fee. - pub fn post_miners_fee_unchecked( - &mut self, - spender_key: &SaplingKey, - ) -> Result { - // Set note_encryption_keys to a constant value on the outputs - for output in &mut self.outputs { - output.set_is_miners_fee(); - } - let unsigned = self.build( - spender_key.proof_authorizing_key, - spender_key.view_key().clone(), - spender_key.outgoing_view_key().clone(), - *self.value_balances.fee(), - None, - )?; - unsigned.sign(spender_key) - } - - /// Get the expiration sequence for this transaction - pub fn expiration(&self) -> u32 { - self.expiration - } - - /// Set the sequence to expire the transaction from the mempool. - pub fn set_expiration(&mut self, sequence: u32) { - self.expiration = sequence; - } - - /// Calculate a hash of the transaction data. This hash is what gets signed - /// by the private keys to verify that the transaction actually happened. - /// - /// This is called during final posting of the transaction - /// - fn transaction_signature_hash( - &self, - spends: &[UnsignedSpendDescription], - outputs: &[OutputDescription], - mints: &[UnsignedMintDescription], - burns: &[BurnDescription], - randomized_public_key: &PublicKey, - ) -> Result<[u8; 32], IronfishError> { - let mut hasher = Blake2b::new() - .hash_length(32) - .personal(SIGNATURE_HASH_PERSONALIZATION) - .to_state(); - - hasher.update(TRANSACTION_SIGNATURE_VERSION); - self.version.write(&mut hasher)?; - hasher.write_u32::(self.expiration)?; - hasher.write_i64::(*self.value_balances.fee())?; - - hasher.write_all(&randomized_public_key.0.to_bytes())?; - - for spend in spends { - spend.description.serialize_signature_fields(&mut hasher)?; - } - - for output in outputs { - output.serialize_signature_fields(&mut hasher)?; - } - - for mint in mints { - mint.description - .serialize_signature_fields(&mut hasher, self.version)?; - } - - for burn in burns { - burn.serialize_signature_fields(&mut hasher)?; - } - - let mut hash_result = [0; 32]; - hash_result[..].clone_from_slice(hasher.finalize().as_ref()); - Ok(hash_result) - } - - /// The binding signature ties up all the randomness generated with the - /// transaction and uses it as a private key to sign all the values - /// that were calculated as part of the transaction. This function - /// performs the calculation and sets the value on this struct. - fn binding_signature( - &self, - private_key: &PrivateKey, - public_key: &PublicKey, - transaction_signature_hash: &[u8; 32], - ) -> Result { - // NOTE: The initial versions of the RedDSA specification and the redjubjub crate (that - // we're using here) require the public key bytes to be prefixed to the message. The latest - // version of the spec and the crate add the public key bytes automatically. Therefore, if - // in the future we upgrade to a newer version of redjubjub, `data_to_be_signed` will have - // to equal `transaction_signature_hash` - let mut data_to_be_signed = [0u8; TRANSACTION_SIGNATURE_SIZE]; - data_to_be_signed[..TRANSACTION_PUBLIC_KEY_SIZE].copy_from_slice(&public_key.0.to_bytes()); - data_to_be_signed[TRANSACTION_PUBLIC_KEY_SIZE..] - .copy_from_slice(transaction_signature_hash); - - Ok(private_key.sign( - &data_to_be_signed, - &mut OsRng, - *VALUE_COMMITMENT_RANDOMNESS_GENERATOR, - )) - } - - fn binding_signature_keys( - &self, - mints: &[UnsignedMintDescription], - burns: &[BurnDescription], - ) -> Result<(redjubjub::PrivateKey, redjubjub::PublicKey), IronfishError> { - // A "private key" manufactured from a bunch of randomness added for each - // spend and output. - let mut binding_signature_key = ironfish_jubjub::Fr::zero(); - - // A "public key" manufactured from a combination of the values of each - // description and the same randomness as above - let mut binding_verification_key = ExtendedPoint::identity(); - - for spend in &self.spends { - binding_signature_key += spend.value_commitment.randomness; - binding_verification_key += spend.value_commitment_point(); - } - - for output in &self.outputs { - binding_signature_key -= output.value_commitment.randomness; - binding_verification_key -= output.value_commitment_point(); - } - - let private_key = PrivateKey(binding_signature_key); - let public_key = - PublicKey::from_private(&private_key, *VALUE_COMMITMENT_RANDOMNESS_GENERATOR); - - let value_balance = - self.calculate_value_balance(&binding_verification_key, mints, burns)?; - - // Confirm that the public key derived from the binding signature key matches - // the final value balance point. The binding verification key is how verifiers - // check the consistency of the values in a transaction. - if value_balance != public_key.0 { - return Err(IronfishError::new(IronfishErrorKind::InvalidBalance)); - } - - Ok((private_key, public_key)) - } - - /// Small wrapper around [`calculate_value_balance`] to handle [`UnsignedMintDescription`] - fn calculate_value_balance( - &self, - binding_verification_key: &ExtendedPoint, - mints: &[UnsignedMintDescription], - burns: &[BurnDescription], - ) -> Result { - let mints_descriptions: Vec = - mints.iter().map(|m| m.description.clone()).collect(); - - calculate_value_balance( - binding_verification_key, - *self.value_balances.fee(), - &mints_descriptions, - burns, - ) - } -} - /// A transaction that has been published and can be read by anyone, not storing /// any of the working data or private keys used in creating the proofs. /// @@ -743,6 +270,7 @@ impl Transaction { /// Confirm that this transaction was signed by the values it contains. /// Called from the public verify function. + #[cfg(feature = "transaction-proofs")] fn verify_binding_signature( &self, binding_verification_key: &ExtendedPoint, @@ -768,6 +296,7 @@ impl Transaction { /// Convert the integer value to a point on the Jubjub curve, accounting for /// negative values +#[cfg(feature = "transaction-proofs")] fn fee_to_point(value: i64) -> Result { // Can only construct edwards point on positive numbers, so need to // add and possibly negate later @@ -790,6 +319,7 @@ fn fee_to_point(value: i64) -> Result { /// /// Does not confirm that the transactions add up to zero. The calculation /// for fees and change happens elsewhere. +#[cfg(feature = "transaction-proofs")] fn calculate_value_balance( binding_verification_key: &ExtendedPoint, fee: i64, @@ -812,134 +342,3 @@ fn calculate_value_balance( Ok(value_balance_point) } - -/// A convenience wrapper method around [`batch_verify_transactions`] for single -/// transactions -pub fn verify_transaction(transaction: &Transaction) -> Result<(), IronfishError> { - batch_verify_transactions(iter::once(transaction)) -} - -fn internal_batch_verify_transactions<'a>( - transactions: impl IntoIterator, - spend_verifying_key: &PreparedVerifyingKey, - output_verifying_key: &PreparedVerifyingKey, - mint_verifying_key: &PreparedVerifyingKey, -) -> Result<(), IronfishError> { - let mut spend_proofs = vec![]; - let mut spend_public_inputs = vec![]; - - let mut output_proofs = vec![]; - let mut output_public_inputs = vec![]; - - let mut mint_proofs = vec![]; - let mut mint_public_inputs = vec![]; - - for transaction in transactions { - // Context to accumulate a signature of all the spends and outputs and - // guarantee they are part of this transaction, unmodified. - let mut binding_verification_key = ExtendedPoint::identity(); - - let hash_to_verify_signature = transaction.transaction_signature_hash()?; - - for spend in transaction.spends.iter() { - spend.partial_verify()?; - - spend_proofs.push(&spend.proof); - spend_public_inputs.push( - spend - .public_inputs(transaction.randomized_public_key()) - .to_vec(), - ); - - binding_verification_key += spend.value_commitment; - - spend.verify_signature( - &hash_to_verify_signature, - transaction.randomized_public_key(), - )?; - } - - for output in transaction.outputs.iter() { - output.partial_verify()?; - - output_proofs.push(&output.proof); - output_public_inputs.push( - output - .public_inputs(transaction.randomized_public_key()) - .to_vec(), - ); - - binding_verification_key -= output.merkle_note.value_commitment; - } - - for mint in transaction.mints.iter() { - mint.partial_verify()?; - - mint_proofs.push(&mint.proof); - mint_public_inputs.push( - mint.public_inputs(transaction.randomized_public_key()) - .to_vec(), - ); - - mint.verify_signature( - &hash_to_verify_signature, - transaction.randomized_public_key(), - )?; - } - - transaction.verify_binding_signature(&binding_verification_key)?; - } - - if !spend_proofs.is_empty() - && !verify_proofs_batch( - spend_verifying_key, - &mut OsRng, - &spend_proofs[..], - &spend_public_inputs[..], - )? - { - return Err(IronfishError::new(IronfishErrorKind::InvalidSpendProof)); - } - if !output_proofs.is_empty() - && !verify_proofs_batch( - output_verifying_key, - &mut OsRng, - &output_proofs[..], - &output_public_inputs[..], - )? - { - return Err(IronfishError::new(IronfishErrorKind::InvalidOutputProof)); - } - if !mint_proofs.is_empty() - && !verify_proofs_batch( - mint_verifying_key, - &mut OsRng, - &mint_proofs[..], - &mint_public_inputs[..], - )? - { - return Err(IronfishError::new(IronfishErrorKind::InvalidOutputProof)); - } - - Ok(()) -} - -/// Validate the transaction. Confirms that: -/// * Each of the spend proofs has the inputs it says it has -/// * Each of the output proofs has the inputs it says it has -/// * Each of the mint proofs has the inputs it says it has -/// * Each of the spend proofs was signed by the owner -/// * Each of the mint proofs was signed by the owner -/// * The entire transaction was signed with a binding signature -/// containing those proofs (and only those proofs) -/// -pub fn batch_verify_transactions<'a>( - transactions: impl IntoIterator, -) -> Result<(), IronfishError> { - internal_batch_verify_transactions( - transactions, - &SAPLING.spend_verifying_key, - &SAPLING.output_verifying_key, - &SAPLING.mint_verifying_key, - ) -} diff --git a/ironfish-rust/src/transaction/outputs.rs b/ironfish-rust/src/transaction/outputs.rs index 7e7202adae..ddd3cfc53f 100644 --- a/ironfish-rust/src/transaction/outputs.rs +++ b/ironfish-rust/src/transaction/outputs.rs @@ -4,28 +4,29 @@ use crate::{ errors::{IronfishError, IronfishErrorKind}, - keys::EphemeralKeyPair, merkle_note::MerkleNote, - note::Note, - sapling_bls12::SAPLING, - OutgoingViewKey, }; - use blstrs::{Bls12, Scalar}; use ff::Field; use group::Curve; use ironfish_bellperson::groth16; use ironfish_jubjub::ExtendedPoint; -use ironfish_zkp::{primitives::ValueCommitment, proofs::Output, redjubjub, ProofGenerationKey}; -use rand::thread_rng; - +use ironfish_zkp::redjubjub; use std::io; -use super::utils::verify_output_proof; +#[cfg(feature = "transaction-proofs")] +use super::verify::verify_output_proof; +#[cfg(feature = "transaction-proofs")] +use crate::{keys::EphemeralKeyPair, note::Note, sapling_bls12::SAPLING, OutgoingViewKey}; +#[cfg(feature = "transaction-proofs")] +use ironfish_zkp::{primitives::ValueCommitment, proofs::Output, ProofGenerationKey}; +#[cfg(feature = "transaction-proofs")] +use rand::thread_rng; /// Parameters used when constructing proof that a new note exists. The owner /// of this note is the recipient of funds in a transaction. The note is signed /// with the owners public key so only they can read it. +#[cfg(feature = "transaction-proofs")] pub struct OutputBuilder { pub(crate) note: Note, @@ -42,6 +43,7 @@ pub struct OutputBuilder { pub const PROOF_SIZE: u32 = 192; +#[cfg(feature = "transaction-proofs")] impl OutputBuilder { /// Create a new [`OutputBuilder`] attempting to create a note. pub(crate) fn new(note: Note) -> Self { @@ -223,12 +225,13 @@ impl OutputDescription { } #[cfg(test)] +#[cfg(feature = "transaction-proofs")] mod test { use super::{OutputBuilder, OutputDescription}; use crate::{ assets::asset_identifier::NATIVE_ASSET, keys::SaplingKey, merkle_note::NOTE_ENCRYPTION_MINER_KEYS, note::Note, - transaction::utils::verify_output_proof, + transaction::verify::verify_output_proof, }; use ff::{Field, PrimeField}; use group::Curve; diff --git a/ironfish-rust/src/transaction/proposed.rs b/ironfish-rust/src/transaction/proposed.rs new file mode 100644 index 0000000000..49a7b3cc48 --- /dev/null +++ b/ironfish-rust/src/transaction/proposed.rs @@ -0,0 +1,501 @@ +/* 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/. */ + +use crate::{ + assets::{ + asset::Asset, + asset_identifier::{AssetIdentifier, NATIVE_ASSET}, + }, + errors::{IronfishError, IronfishErrorKind}, + keys::{PublicAddress, SaplingKey}, + note::Note, + transaction::{ + burns::{BurnBuilder, BurnDescription}, + calculate_value_balance, + mints::MintDescription, + mints::{MintBuilder, UnsignedMintDescription}, + outputs::{OutputBuilder, OutputDescription}, + spends::{SpendBuilder, UnsignedSpendDescription}, + unsigned::UnsignedTransaction, + value_balances::ValueBalances, + Transaction, TransactionVersion, SIGNATURE_HASH_PERSONALIZATION, + TRANSACTION_PUBLIC_KEY_SIZE, TRANSACTION_SIGNATURE_SIZE, TRANSACTION_SIGNATURE_VERSION, + }, + witness::WitnessTrait, + OutgoingViewKey, ViewKey, +}; +use blake2b_simd::Params as Blake2b; +use byteorder::{LittleEndian, WriteBytesExt}; +use ff::Field; +use group::GroupEncoding; +use ironfish_jubjub::ExtendedPoint; +use ironfish_zkp::{ + constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, + redjubjub::{self, Signature}, + ProofGenerationKey, +}; +use rand::{rngs::OsRng, thread_rng}; +use std::io::Write; + +/// A collection of spend and output proofs that can be signed and verified. +/// In general, all the spent values should add up to all the output values. +/// +/// The Transaction is used while the spends and outputs are being constructed, +/// and contains working state that is used to create the transaction information. +/// +/// The Transaction, below, contains the serializable version, without any +/// secret keys or state not needed for verifying. +pub struct ProposedTransaction { + /// The transaction serialization version. This can be incremented when + /// changes need to be made to the transaction format + pub(super) version: TransactionVersion, + + /// Builders for the proofs of the individual spends with all values required to calculate + /// the signatures. + pub(super) spends: Vec, + + /// Builders for proofs of the individual outputs with values required to calculate + /// signatures. Note: This is commonly referred to as + /// `outputs` in the literature. + pub(super) outputs: Vec, + + /// Builders for proofs of the individual mints with all values required to + /// calculate the signatures. + pub(super) mints: Vec, + + /// Descriptions containing the assets and value commitments to be burned. + /// We do not need to use a builder here since we only need to handle + /// balancing and effects are handled by outputs. + pub(super) burns: Vec, + + /// The balance of all the spends minus all the outputs. The difference + /// is the fee paid to the miner for mining the transaction. + pub(super) value_balances: ValueBalances, + + /// This is the sequence in the chain the transaction will expire at and be + /// removed from the mempool. A value of 0 indicates the transaction will + /// not expire. + pub(super) expiration: u32, + + // randomness used for the transaction to calculate the randomized ak, which + // allows us to verify the sender address is valid and stored in the notes + // Used to add randomness to signature generation without leaking the + // key. Referred to as `ar` in the literature. + pub(super) public_key_randomness: ironfish_jubjub::Fr, + // NOTE: If adding fields here, you may need to add fields to + // signature hash method, and also to Transaction. +} + +impl ProposedTransaction { + pub fn new(version: TransactionVersion) -> Self { + Self { + version, + spends: vec![], + outputs: vec![], + mints: vec![], + burns: vec![], + value_balances: ValueBalances::new(), + expiration: 0, + public_key_randomness: ironfish_jubjub::Fr::random(thread_rng()), + } + } + + /// Spend the note owned by spender_key at the given witness location. + pub fn add_spend( + &mut self, + note: Note, + witness: &dyn WitnessTrait, + ) -> Result<(), IronfishError> { + self.value_balances + .add(note.asset_id(), note.value().try_into()?)?; + + self.spends.push(SpendBuilder::new(note, witness)); + + Ok(()) + } + + /// Create a proof of a new note owned by the recipient in this + /// transaction. + pub fn add_output(&mut self, note: Note) -> Result<(), IronfishError> { + self.value_balances + .subtract(note.asset_id(), note.value().try_into()?)?; + + self.outputs.push(OutputBuilder::new(note)); + + Ok(()) + } + + pub fn add_mint(&mut self, asset: Asset, value: u64) -> Result<(), IronfishError> { + self.value_balances.add(asset.id(), value.try_into()?)?; + + self.mints.push(MintBuilder::new(asset, value)); + + Ok(()) + } + + pub fn add_mint_with_new_owner( + &mut self, + asset: Asset, + value: u64, + new_owner: PublicAddress, + ) -> Result<(), IronfishError> { + self.value_balances.add(asset.id(), value.try_into()?)?; + + let mut mint_builder = MintBuilder::new(asset, value); + mint_builder.transfer_ownership_to(new_owner); + self.mints.push(mint_builder); + + Ok(()) + } + + pub fn add_burn(&mut self, asset_id: AssetIdentifier, value: u64) -> Result<(), IronfishError> { + self.value_balances.subtract(&asset_id, value.try_into()?)?; + + self.burns.push(BurnBuilder::new(asset_id, value)); + + Ok(()) + } + + pub(super) fn add_change_notes( + &mut self, + change_goes_to: Option, + public_address: PublicAddress, + intended_transaction_fee: i64, + ) -> Result<(), IronfishError> { + let mut change_notes = vec![]; + + for (asset_id, value) in self.value_balances.iter() { + let is_native_asset = asset_id == &NATIVE_ASSET; + + let change_amount = match is_native_asset { + true => *value - intended_transaction_fee, + false => *value, + }; + + if change_amount < 0 { + return Err(IronfishError::new(IronfishErrorKind::InvalidBalance)); + } + if change_amount > 0 { + let change_address = change_goes_to.unwrap_or(public_address); + let change_note = Note::new( + change_address, + change_amount as u64, // we checked it was positive + "", + *asset_id, + public_address, + ); + + change_notes.push(change_note); + } + } + for change_note in change_notes { + self.add_output(change_note)?; + } + Ok(()) + } + + pub fn build( + &mut self, + proof_authorizing_key: ironfish_jubjub::Fr, + view_key: ViewKey, + outgoing_view_key: OutgoingViewKey, + intended_transaction_fee: i64, + change_goes_to: Option, + ) -> Result { + let public_address = view_key.public_address()?; + + let proof_generation_key = + ProofGenerationKey::new(view_key.authorizing_key, proof_authorizing_key); + + // skip adding change notes if this is special case of a miners fee transaction + let is_miners_fee = self.outputs.iter().any(|output| output.get_is_miners_fee()); + if !is_miners_fee { + self.add_change_notes(change_goes_to, public_address, intended_transaction_fee)?; + } + + // The public key after randomization has been applied. This is used + // during signature verification. Referred to as `rk` in the literature + // Calculated from the authorizing key and the public_key_randomness. + let randomized_public_key = redjubjub::PublicKey(view_key.authorizing_key.into()) + .randomize(self.public_key_randomness, *SPENDING_KEY_GENERATOR); + + let mut unsigned_spends = Vec::with_capacity(self.spends.len()); + for spend in &self.spends { + unsigned_spends.push(spend.build( + &proof_generation_key, + &view_key, + &self.public_key_randomness, + &randomized_public_key, + )?); + } + + let mut output_descriptions = Vec::with_capacity(self.outputs.len()); + for output in &self.outputs { + output_descriptions.push(output.build( + &proof_generation_key, + &outgoing_view_key, + &self.public_key_randomness, + &randomized_public_key, + )?); + } + + let mut unsigned_mints = Vec::with_capacity(self.mints.len()); + for mint in &self.mints { + unsigned_mints.push(mint.build( + &proof_generation_key, + &public_address, + &self.public_key_randomness, + &randomized_public_key, + )?); + } + + let mut burn_descriptions = Vec::with_capacity(self.burns.len()); + for burn in &self.burns { + burn_descriptions.push(burn.build()); + } + + let data_to_sign = self.transaction_signature_hash( + &unsigned_spends, + &output_descriptions, + &unsigned_mints, + &burn_descriptions, + &randomized_public_key, + )?; + + // Create and verify binding signature keys + let (binding_signature_private_key, binding_signature_public_key) = + self.binding_signature_keys(&unsigned_mints, &burn_descriptions)?; + + let binding_signature = self.binding_signature( + &binding_signature_private_key, + &binding_signature_public_key, + &data_to_sign, + )?; + + Ok(UnsignedTransaction { + burns: burn_descriptions, + mints: unsigned_mints, + outputs: output_descriptions, + spends: unsigned_spends, + version: self.version, + fee: intended_transaction_fee, + binding_signature, + randomized_public_key, + public_key_randomness: self.public_key_randomness, + expiration: self.expiration, + }) + } + + /// Post the transaction. This performs a bit of validation, and signs + /// the spends with a signature that proves the spends are part of this + /// transaction. + /// + /// Transaction fee is the amount the spender wants to send to the miner + /// for mining this transaction. This has to be non-negative; sane miners + /// wouldn't accept a transaction that takes money away from them. + /// + /// sum(spends) + sum(mints) - sum(outputs) - sum(burns) - intended_transaction_fee - change = 0 + /// aka: self.value_balance - intended_transaction_fee - change = 0 + pub fn post( + &mut self, + spender_key: &SaplingKey, + change_goes_to: Option, + intended_transaction_fee: u64, + ) -> Result { + let i64_fee = i64::try_from(intended_transaction_fee)?; + + let unsigned = self.build( + spender_key.proof_authorizing_key, + spender_key.view_key().clone(), + spender_key.outgoing_view_key().clone(), + i64_fee, + change_goes_to, + )?; + unsigned.sign(spender_key) + } + + /// Special case for posting a miners fee transaction. Miner fee transactions + /// are unique in that they generate currency. They do not have any spends + /// or change and therefore have a negative transaction fee. In normal use, + /// a miner would not accept such a transaction unless it was explicitly set + /// as the miners fee. + pub fn post_miners_fee( + &mut self, + spender_key: &SaplingKey, + ) -> Result { + if !self.spends.is_empty() + || self.outputs.len() != 1 + || !self.mints.is_empty() + || !self.burns.is_empty() + { + return Err(IronfishError::new( + IronfishErrorKind::InvalidMinersFeeTransaction, + )); + } + self.post_miners_fee_unchecked(spender_key) + } + + /// Do not call this directly -- see post_miners_fee. + pub fn post_miners_fee_unchecked( + &mut self, + spender_key: &SaplingKey, + ) -> Result { + // Set note_encryption_keys to a constant value on the outputs + for output in &mut self.outputs { + output.set_is_miners_fee(); + } + let unsigned = self.build( + spender_key.proof_authorizing_key, + spender_key.view_key().clone(), + spender_key.outgoing_view_key().clone(), + *self.value_balances.fee(), + None, + )?; + unsigned.sign(spender_key) + } + + /// Get the expiration sequence for this transaction + pub fn expiration(&self) -> u32 { + self.expiration + } + + /// Set the sequence to expire the transaction from the mempool. + pub fn set_expiration(&mut self, sequence: u32) { + self.expiration = sequence; + } + + /// Calculate a hash of the transaction data. This hash is what gets signed + /// by the private keys to verify that the transaction actually happened. + /// + /// This is called during final posting of the transaction + /// + fn transaction_signature_hash( + &self, + spends: &[UnsignedSpendDescription], + outputs: &[OutputDescription], + mints: &[UnsignedMintDescription], + burns: &[BurnDescription], + randomized_public_key: &redjubjub::PublicKey, + ) -> Result<[u8; 32], IronfishError> { + let mut hasher = Blake2b::new() + .hash_length(32) + .personal(SIGNATURE_HASH_PERSONALIZATION) + .to_state(); + + hasher.update(TRANSACTION_SIGNATURE_VERSION); + self.version.write(&mut hasher)?; + hasher.write_u32::(self.expiration)?; + hasher.write_i64::(*self.value_balances.fee())?; + + hasher.write_all(&randomized_public_key.0.to_bytes())?; + + for spend in spends { + spend.description.serialize_signature_fields(&mut hasher)?; + } + + for output in outputs { + output.serialize_signature_fields(&mut hasher)?; + } + + for mint in mints { + mint.description + .serialize_signature_fields(&mut hasher, self.version)?; + } + + for burn in burns { + burn.serialize_signature_fields(&mut hasher)?; + } + + let mut hash_result = [0; 32]; + hash_result[..].clone_from_slice(hasher.finalize().as_ref()); + Ok(hash_result) + } + + /// The binding signature ties up all the randomness generated with the + /// transaction and uses it as a private key to sign all the values + /// that were calculated as part of the transaction. This function + /// performs the calculation and sets the value on this struct. + fn binding_signature( + &self, + private_key: &redjubjub::PrivateKey, + public_key: &redjubjub::PublicKey, + transaction_signature_hash: &[u8; 32], + ) -> Result { + // NOTE: The initial versions of the RedDSA specification and the redjubjub crate (that + // we're using here) require the public key bytes to be prefixed to the message. The latest + // version of the spec and the crate add the public key bytes automatically. Therefore, if + // in the future we upgrade to a newer version of redjubjub, `data_to_be_signed` will have + // to equal `transaction_signature_hash` + let mut data_to_be_signed = [0u8; TRANSACTION_SIGNATURE_SIZE]; + data_to_be_signed[..TRANSACTION_PUBLIC_KEY_SIZE].copy_from_slice(&public_key.0.to_bytes()); + data_to_be_signed[TRANSACTION_PUBLIC_KEY_SIZE..] + .copy_from_slice(transaction_signature_hash); + + Ok(private_key.sign( + &data_to_be_signed, + &mut OsRng, + *VALUE_COMMITMENT_RANDOMNESS_GENERATOR, + )) + } + + fn binding_signature_keys( + &self, + mints: &[UnsignedMintDescription], + burns: &[BurnDescription], + ) -> Result<(redjubjub::PrivateKey, redjubjub::PublicKey), IronfishError> { + // A "private key" manufactured from a bunch of randomness added for each + // spend and output. + let mut binding_signature_key = ironfish_jubjub::Fr::zero(); + + // A "public key" manufactured from a combination of the values of each + // description and the same randomness as above + let mut binding_verification_key = ExtendedPoint::identity(); + + for spend in &self.spends { + binding_signature_key += spend.value_commitment.randomness; + binding_verification_key += spend.value_commitment_point(); + } + + for output in &self.outputs { + binding_signature_key -= output.value_commitment.randomness; + binding_verification_key -= output.value_commitment_point(); + } + + let private_key = redjubjub::PrivateKey(binding_signature_key); + let public_key = redjubjub::PublicKey::from_private( + &private_key, + *VALUE_COMMITMENT_RANDOMNESS_GENERATOR, + ); + + let value_balance = + self.calculate_value_balance(&binding_verification_key, mints, burns)?; + + // Confirm that the public key derived from the binding signature key matches + // the final value balance point. The binding verification key is how verifiers + // check the consistency of the values in a transaction. + if value_balance != public_key.0 { + return Err(IronfishError::new(IronfishErrorKind::InvalidBalance)); + } + + Ok((private_key, public_key)) + } + + /// Small wrapper around [`calculate_value_balance`] to handle [`UnsignedMintDescription`] + fn calculate_value_balance( + &self, + binding_verification_key: &ExtendedPoint, + mints: &[UnsignedMintDescription], + burns: &[BurnDescription], + ) -> Result { + let mints_descriptions: Vec = + mints.iter().map(|m| m.description.clone()).collect(); + + calculate_value_balance( + binding_verification_key, + *self.value_balances.fee(), + &mints_descriptions, + burns, + ) + } +} diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index 70280257aa..7acc7e58ec 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -5,37 +5,41 @@ use crate::{ errors::{IronfishError, IronfishErrorKind}, keys::SaplingKey, - merkle_note::{position as witness_position, sapling_auth_path}, - note::Note, - sapling_bls12::SAPLING, serializing::{read_point, read_scalar}, - witness::WitnessTrait, - ViewKey, + transaction::TRANSACTION_PUBLIC_KEY_SIZE, }; - use blstrs::{Bls12, Scalar}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::{Field, PrimeField}; use group::{Curve, GroupEncoding}; -use ironfish_bellperson::gadgets::multipack; -use ironfish_bellperson::groth16; +use ironfish_bellperson::{gadgets::multipack, groth16}; use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::{ constants::SPENDING_KEY_GENERATOR, - primitives::ValueCommitment, - proofs::Spend, redjubjub::{self, Signature}, - Nullifier, ProofGenerationKey, + Nullifier, }; use rand::thread_rng; use std::io; -use super::{utils::verify_spend_proof, TRANSACTION_PUBLIC_KEY_SIZE}; +#[cfg(feature = "transaction-proofs")] +use crate::transaction::verify::verify_spend_proof; +#[cfg(feature = "transaction-proofs")] +use crate::{ + merkle_note::{position as witness_position, sapling_auth_path}, + note::Note, + sapling_bls12::SAPLING, + witness::WitnessTrait, + ViewKey, +}; +#[cfg(feature = "transaction-proofs")] +use ironfish_zkp::{primitives::ValueCommitment, proofs::Spend, ProofGenerationKey}; /// Parameters used when constructing proof that the spender owns a note with /// a given value. /// /// Contains all the working values needed to construct the proof. +#[cfg(feature = "transaction-proofs")] pub struct SpendBuilder { pub(crate) note: Note, @@ -56,6 +60,7 @@ pub struct SpendBuilder { pub(crate) auth_path: Vec>, } +#[cfg(feature = "transaction-proofs")] impl SpendBuilder { /// Create a new [`SpendBuilder`] attempting to spend a note at a given /// location in the merkle tree. @@ -405,18 +410,20 @@ fn serialize_signature_fields( } #[cfg(test)] +#[cfg(feature = "transaction-proofs")] mod test { - use super::{SpendBuilder, SpendDescription}; - use crate::assets::asset_identifier::NATIVE_ASSET; - use crate::transaction::utils::verify_spend_proof; - use crate::{keys::SaplingKey, note::Note, test_util::make_fake_witness}; + use crate::{ + assets::asset_identifier::NATIVE_ASSET, keys::SaplingKey, note::Note, + test_util::make_fake_witness, transaction::verify::verify_spend_proof, + }; use ff::Field; use group::Curve; - use ironfish_zkp::constants::SPENDING_KEY_GENERATOR; - use ironfish_zkp::redjubjub::{self, PrivateKey, PublicKey}; - use rand::prelude::*; - use rand::{thread_rng, Rng}; + use ironfish_zkp::{ + constants::SPENDING_KEY_GENERATOR, + redjubjub::{self, PrivateKey, PublicKey}, + }; + use rand::{random, thread_rng, Rng}; #[test] fn test_spend_builder() { diff --git a/ironfish-rust/src/transaction/tests.rs b/ironfish-rust/src/transaction/tests.rs index ce466b2581..50837ca839 100644 --- a/ironfish-rust/src/transaction/tests.rs +++ b/ironfish-rust/src/transaction/tests.rs @@ -2,44 +2,52 @@ * 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/. */ -use std::collections::{BTreeMap, HashMap}; +use crate::{ + errors::{IronfishError, IronfishErrorKind}, + transaction::Transaction, +}; -#[cfg(test)] -use super::internal_batch_verify_transactions; -use super::{ProposedTransaction, Transaction, TRANSACTION_PUBLIC_KEY_SIZE}; -use crate::frost_utils::account_keys::derive_account_keys; -use crate::test_util::create_multisig_identities; -use crate::transaction::tests::split_spender_key::split_spender_key; +#[cfg(feature = "transaction-proofs")] use crate::{ assets::{asset::Asset, asset_identifier::NATIVE_ASSET}, - errors::{IronfishError, IronfishErrorKind}, - frost_utils::split_spender_key, + frost_utils::{account_keys::derive_account_keys, split_spender_key::split_spender_key}, keys::SaplingKey, merkle_note::NOTE_ENCRYPTION_MINER_KEYS, note::Note, sapling_bls12::SAPLING, - test_util::make_fake_witness, + test_util::{create_multisig_identities, make_fake_witness}, transaction::{ - batch_verify_transactions, verify_transaction, TransactionVersion, - TRANSACTION_EXPIRATION_SIZE, TRANSACTION_FEE_SIZE, TRANSACTION_SIGNATURE_SIZE, + verify::batch_verify_transactions, verify::internal_batch_verify_transactions, + verify_transaction, ProposedTransaction, TransactionVersion, TRANSACTION_EXPIRATION_SIZE, + TRANSACTION_FEE_SIZE, TRANSACTION_PUBLIC_KEY_SIZE, TRANSACTION_SIGNATURE_SIZE, }, }; +#[cfg(feature = "transaction-proofs")] use ff::Field; +#[cfg(feature = "transaction-proofs")] use group::GroupEncoding; -use ironfish_frost::dkg::{round1 as round1_dkg, round2 as round2_dkg, round3 as round3_dkg}; -use ironfish_frost::participant::Secret; +#[cfg(feature = "transaction-proofs")] use ironfish_frost::{ + dkg::{ + round1::round1 as dkg_round1, round2::round2 as dkg_round2, round3::round3 as dkg_round3, + }, frost::{round2, round2::SignatureShare, Identifier, Randomizer}, nonces::deterministic_signing_nonces, + participant::Secret, }; +#[cfg(feature = "transaction-proofs")] use ironfish_zkp::{ constants::{ASSET_ID_LENGTH, SPENDING_KEY_GENERATOR, TREE_DEPTH}, proofs::{MintAsset, Output, Spend}, redjubjub::{self, Signature}, }; +#[cfg(feature = "transaction-proofs")] use rand::thread_rng; +#[cfg(feature = "transaction-proofs")] +use std::collections::{BTreeMap, HashMap}; #[test] +#[cfg(feature = "transaction-proofs")] fn test_transaction() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); @@ -163,6 +171,7 @@ fn test_transaction() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_transaction_simple() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); @@ -211,6 +220,7 @@ fn test_transaction_simple() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_proposed_transaction_build() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); @@ -266,6 +276,7 @@ fn test_proposed_transaction_build() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_miners_fee() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); @@ -294,6 +305,7 @@ fn test_miners_fee() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_transaction_signature() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); @@ -333,6 +345,7 @@ fn test_transaction_signature() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_transaction_created_with_version_1() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); @@ -406,6 +419,7 @@ fn test_transaction_version_is_checked() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_transaction_value_overflows() { let key = SaplingKey::generate_key(); @@ -438,6 +452,7 @@ fn test_transaction_value_overflows() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_batch_verify_wrong_spend_params() { let rng = &mut thread_rng(); @@ -516,6 +531,7 @@ fn test_batch_verify_wrong_spend_params() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_batch_verify_wrong_output_params() { let rng = &mut thread_rng(); @@ -593,6 +609,7 @@ fn test_batch_verify_wrong_output_params() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_batch_verify_wrong_mint_params() { let rng = &mut thread_rng(); @@ -685,6 +702,7 @@ fn test_batch_verify_wrong_mint_params() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_batch_verify() { let key = SaplingKey::generate_key(); let other_key = SaplingKey::generate_key(); @@ -762,6 +780,7 @@ fn test_batch_verify() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_sign_simple() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); @@ -813,6 +832,7 @@ fn test_sign_simple() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_sign_key_mismatch_failure() { let spender_key = SaplingKey::generate_key(); let receiver_key = SaplingKey::generate_key(); @@ -865,6 +885,7 @@ fn test_sign_key_mismatch_failure() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_aggregate_signature_shares() { let spender_key = SaplingKey::generate_key(); @@ -1003,6 +1024,7 @@ fn test_aggregate_signature_shares() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_add_signature_by_building_transaction() { let spender_key = SaplingKey::generate_key(); @@ -1079,6 +1101,7 @@ fn test_add_signature_by_building_transaction() { } #[test] +#[cfg(feature = "transaction-proofs")] fn test_dkg_signing() { let secret1 = Secret::random(thread_rng()); let secret2 = Secret::random(thread_rng()); @@ -1088,7 +1111,7 @@ fn test_dkg_signing() { let identity3 = secret3.to_identity(); let identities = &[identity1.clone(), identity2.clone(), identity3.clone()]; - let (round1_secret_package_1, package1) = round1_dkg::round1( + let (round1_secret_package_1, package1) = dkg_round1( &identity1, 2, [&identity1, &identity2, &identity3], @@ -1096,7 +1119,7 @@ fn test_dkg_signing() { ) .expect("round 1 failed"); - let (round1_secret_package_2, package2) = round1_dkg::round1( + let (round1_secret_package_2, package2) = dkg_round1( &identity2, 2, [&identity1, &identity2, &identity3], @@ -1104,7 +1127,7 @@ fn test_dkg_signing() { ) .expect("round 1 failed"); - let (round1_secret_package_3, package3) = round1_dkg::round1( + let (round1_secret_package_3, package3) = dkg_round1( &identity3, 2, [&identity1, &identity2, &identity3], @@ -1112,7 +1135,7 @@ fn test_dkg_signing() { ) .expect("round 1 failed"); - let (encrypted_secret_package_1, round2_public_packages_1) = round2_dkg::round2( + let (encrypted_secret_package_1, round2_public_packages_1) = dkg_round2( &secret1, &round1_secret_package_1, [&package1, &package2, &package3], @@ -1120,7 +1143,7 @@ fn test_dkg_signing() { ) .expect("round 2 failed"); - let (encrypted_secret_package_2, round2_public_packages_2) = round2_dkg::round2( + let (encrypted_secret_package_2, round2_public_packages_2) = dkg_round2( &secret2, &round1_secret_package_2, [&package1, &package2, &package3], @@ -1128,7 +1151,7 @@ fn test_dkg_signing() { ) .expect("round 2 failed"); - let (encrypted_secret_package_3, round2_public_packages_3) = round2_dkg::round2( + let (encrypted_secret_package_3, round2_public_packages_3) = dkg_round2( &secret3, &round1_secret_package_3, [&package1, &package2, &package3], @@ -1136,7 +1159,7 @@ fn test_dkg_signing() { ) .expect("round 2 failed"); - let (key_package_1, public_key_package, group_secret_key) = round3_dkg::round3( + let (key_package_1, public_key_package, group_secret_key) = dkg_round3( &secret1, &encrypted_secret_package_1, [&package1, &package2, &package3], @@ -1144,7 +1167,7 @@ fn test_dkg_signing() { ) .expect("round 3 failed"); - let (key_package_2, _, _) = round3_dkg::round3( + let (key_package_2, _, _) = dkg_round3( &secret2, &encrypted_secret_package_2, [&package1, &package2, &package3], @@ -1152,7 +1175,7 @@ fn test_dkg_signing() { ) .expect("round 3 failed"); - let (key_package_3, _, _) = round3_dkg::round3( + let (key_package_3, _, _) = dkg_round3( &secret3, &encrypted_secret_package_3, [&package1, &package2, &package3], diff --git a/ironfish-rust/src/transaction/utils.rs b/ironfish-rust/src/transaction/utils.rs deleted file mode 100644 index 879f30d620..0000000000 --- a/ironfish-rust/src/transaction/utils.rs +++ /dev/null @@ -1,52 +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/. */ -use blstrs::Bls12; -use ironfish_bellperson::groth16; - -use crate::{ - errors::{IronfishError, IronfishErrorKind}, - sapling_bls12::SAPLING, -}; - -/// Helper function for verifying spend proof internally. Note that this is not -/// called by verifiers as part of transaction verification. See -/// [`super::batch_verify_transactions`] -pub(crate) fn verify_spend_proof( - proof: &groth16::Proof, - inputs: &[blstrs::Scalar], -) -> Result<(), IronfishError> { - if !groth16::verify_proof(&SAPLING.spend_verifying_key, proof, inputs)? { - return Err(IronfishError::new(IronfishErrorKind::InvalidSpendProof)); - } - - Ok(()) -} - -/// Helper function for verifying output proof internally. Note that this is not -/// called by verifiers as part of transaction verification. See -/// [`super::batch_verify_transactions`] -pub(crate) fn verify_output_proof( - proof: &groth16::Proof, - inputs: &[blstrs::Scalar], -) -> Result<(), IronfishError> { - if !groth16::verify_proof(&SAPLING.output_verifying_key, proof, inputs)? { - return Err(IronfishError::new(IronfishErrorKind::InvalidOutputProof)); - } - - Ok(()) -} - -/// Helper function for verifying mint proof internally. Note that this is not -/// called by verifiers as part of transaction verification. See -/// [`super::batch_verify_transactions`] -pub(crate) fn verify_mint_proof( - proof: &groth16::Proof, - inputs: &[blstrs::Scalar], -) -> Result<(), IronfishError> { - if !groth16::verify_proof(&SAPLING.mint_verifying_key, proof, inputs)? { - return Err(IronfishError::new(IronfishErrorKind::InvalidMintProof)); - } - - Ok(()) -} diff --git a/ironfish-rust/src/transaction/verify.rs b/ironfish-rust/src/transaction/verify.rs new file mode 100644 index 0000000000..003764148e --- /dev/null +++ b/ironfish-rust/src/transaction/verify.rs @@ -0,0 +1,189 @@ +/* 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/. */ + +use crate::{ + errors::{IronfishError, IronfishErrorKind}, + sapling_bls12::SAPLING, + transaction::Transaction, +}; +use blstrs::Bls12; +use ironfish_bellperson::groth16::{self, verify_proofs_batch, PreparedVerifyingKey}; +use ironfish_jubjub::ExtendedPoint; +use rand::rngs::OsRng; + +/// Helper function for verifying spend proof internally. Note that this is not +/// called by verifiers as part of transaction verification. See +/// [`super::batch_verify_transactions`] +pub(crate) fn verify_spend_proof( + proof: &groth16::Proof, + inputs: &[blstrs::Scalar], +) -> Result<(), IronfishError> { + if !groth16::verify_proof(&SAPLING.spend_verifying_key, proof, inputs)? { + return Err(IronfishError::new(IronfishErrorKind::InvalidSpendProof)); + } + + Ok(()) +} + +/// Helper function for verifying output proof internally. Note that this is not +/// called by verifiers as part of transaction verification. See +/// [`super::batch_verify_transactions`] +pub(crate) fn verify_output_proof( + proof: &groth16::Proof, + inputs: &[blstrs::Scalar], +) -> Result<(), IronfishError> { + if !groth16::verify_proof(&SAPLING.output_verifying_key, proof, inputs)? { + return Err(IronfishError::new(IronfishErrorKind::InvalidOutputProof)); + } + + Ok(()) +} + +/// Helper function for verifying mint proof internally. Note that this is not +/// called by verifiers as part of transaction verification. See +/// [`super::batch_verify_transactions`] +pub(crate) fn verify_mint_proof( + proof: &groth16::Proof, + inputs: &[blstrs::Scalar], +) -> Result<(), IronfishError> { + if !groth16::verify_proof(&SAPLING.mint_verifying_key, proof, inputs)? { + return Err(IronfishError::new(IronfishErrorKind::InvalidMintProof)); + } + + Ok(()) +} + +/// A convenience wrapper method around [`batch_verify_transactions`] for single +/// transactions +#[cfg(feature = "transaction-proofs")] +pub fn verify_transaction(transaction: &Transaction) -> Result<(), IronfishError> { + batch_verify_transactions(std::iter::once(transaction)) +} + +#[cfg(feature = "transaction-proofs")] +pub(super) fn internal_batch_verify_transactions<'a>( + transactions: impl IntoIterator, + spend_verifying_key: &PreparedVerifyingKey, + output_verifying_key: &PreparedVerifyingKey, + mint_verifying_key: &PreparedVerifyingKey, +) -> Result<(), IronfishError> { + let mut spend_proofs = vec![]; + let mut spend_public_inputs = vec![]; + + let mut output_proofs = vec![]; + let mut output_public_inputs = vec![]; + + let mut mint_proofs = vec![]; + let mut mint_public_inputs = vec![]; + + for transaction in transactions { + // Context to accumulate a signature of all the spends and outputs and + // guarantee they are part of this transaction, unmodified. + let mut binding_verification_key = ExtendedPoint::identity(); + + let hash_to_verify_signature = transaction.transaction_signature_hash()?; + + for spend in transaction.spends.iter() { + spend.partial_verify()?; + + spend_proofs.push(&spend.proof); + spend_public_inputs.push( + spend + .public_inputs(transaction.randomized_public_key()) + .to_vec(), + ); + + binding_verification_key += spend.value_commitment; + + spend.verify_signature( + &hash_to_verify_signature, + transaction.randomized_public_key(), + )?; + } + + for output in transaction.outputs.iter() { + output.partial_verify()?; + + output_proofs.push(&output.proof); + output_public_inputs.push( + output + .public_inputs(transaction.randomized_public_key()) + .to_vec(), + ); + + binding_verification_key -= output.merkle_note.value_commitment; + } + + for mint in transaction.mints.iter() { + mint.partial_verify()?; + + mint_proofs.push(&mint.proof); + mint_public_inputs.push( + mint.public_inputs(transaction.randomized_public_key()) + .to_vec(), + ); + + mint.verify_signature( + &hash_to_verify_signature, + transaction.randomized_public_key(), + )?; + } + + transaction.verify_binding_signature(&binding_verification_key)?; + } + + if !spend_proofs.is_empty() + && !verify_proofs_batch( + spend_verifying_key, + &mut OsRng, + &spend_proofs[..], + &spend_public_inputs[..], + )? + { + return Err(IronfishError::new(IronfishErrorKind::InvalidSpendProof)); + } + if !output_proofs.is_empty() + && !verify_proofs_batch( + output_verifying_key, + &mut OsRng, + &output_proofs[..], + &output_public_inputs[..], + )? + { + return Err(IronfishError::new(IronfishErrorKind::InvalidOutputProof)); + } + if !mint_proofs.is_empty() + && !verify_proofs_batch( + mint_verifying_key, + &mut OsRng, + &mint_proofs[..], + &mint_public_inputs[..], + )? + { + return Err(IronfishError::new(IronfishErrorKind::InvalidOutputProof)); + } + + Ok(()) +} + +/// Validate the transaction. Confirms that: +/// * Each of the spend proofs has the inputs it says it has +/// * Each of the output proofs has the inputs it says it has +/// * Each of the mint proofs has the inputs it says it has +/// * Each of the spend proofs was signed by the owner +/// * Each of the mint proofs was signed by the owner +/// * The entire transaction was signed with a binding signature +/// containing those proofs (and only those proofs) +/// +#[cfg(feature = "transaction-proofs")] +pub fn batch_verify_transactions<'a>( + transactions: impl IntoIterator, +) -> Result<(), IronfishError> { + internal_batch_verify_transactions( + transactions, + &SAPLING.spend_verifying_key, + &SAPLING.output_verifying_key, + &SAPLING.mint_verifying_key, + ) +} From 63d3de261ffb16169068e6412aae5f6435492433 Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 31 Oct 2024 14:13:56 -0700 Subject: [PATCH 66/81] Rust CI: run tests with `--no-default-features` and `--all-features` --- .github/workflows/rust_ci.yml | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index eba37dec79..ab5b265a08 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -123,6 +123,58 @@ jobs: token: ${{secrets.CODECOV_TOKEN}} flags: ironfish-rust + ironfish_rust_no_default_features: + name: Test ironfish-rust (no default features) + runs-on: ubuntu-latest + strategy: + matrix: + shard: [1/2, 2/2] + + steps: + - uses: actions/checkout@v4 + + - name: Install nextest + uses: taiki-e/install-action@nextest + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + shared-key: base + + - name: Run tests + run: | + cargo nextest run \ + --package ironfish \ + --release \ + --no-default-features \ + --partition count:${{ matrix.shard }} + + ironfish_rust_all_features: + name: Test ironfish-rust (all features) + runs-on: ubuntu-latest + strategy: + matrix: + shard: [1/2, 2/2] + + steps: + - uses: actions/checkout@v4 + + - name: Install nextest + uses: taiki-e/install-action@nextest + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + shared-key: base + + - name: Run tests (all features) + run: | + cargo nextest run \ + --package ironfish \ + --release \ + --all-features \ + --partition count:${{ matrix.shard }} + ironfish_zkp: name: Test ironfish-zkp runs-on: ubuntu-latest From 8350e0bce3ed3fa0e7fafc83cd936977014e2e94 Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 31 Oct 2024 14:42:55 -0700 Subject: [PATCH 67/81] Rust CI: run `cargo check-all-features` instead of `cargo check` --- .github/workflows/rust_ci.yml | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index ab5b265a08..8e3c12006c 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -41,14 +41,6 @@ jobs: save-if: false shared-key: base - - name: Install Cargo tools - run: | - rustup component add rustfmt clippy - - - name: Check that cargo lockfile is up to date - run: | - cargo check --locked --all-targets - # Note: ironfish-zkp is does not need this due to different licensing - name: Check for license headers for ironfish-rust run: ./ci/lintHeaders.sh ./ironfish-rust/src *.rs @@ -64,6 +56,26 @@ jobs: run: | cargo clippy --all-targets --all-features -- -D warnings + cargo_check: + name: Check Rust + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + save-if: false + shared-key: base + + - name: Install cargo-all-features + run: | + cargo install --locked cargo-all-features + + - name: Check that cargo lockfile is up to date + run: | + cargo check-all-features --locked --all-targets + cargo_vet: name: Vet Dependencies runs-on: ubuntu-latest From 57ca7ee3fa1c3ccd1cbc35cfcab9a35b858c363b Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 31 Oct 2024 15:31:28 -0700 Subject: [PATCH 68/81] Make the Sapling parameters optional for documentation builds This is to allow our documentation to build on platforms like docs.rs, which need to build the documentation for our crates, but don't have access to the Sapling parameters. This also slightly improves the experience for developers who use this crate for the first time: if they use the crate without the Sapling parameters, they'll now get a helpful warning instead of a panic. --- ironfish-rust/build.rs | 19 +++++++-- ironfish-rust/src/lib.rs | 71 +++------------------------------ ironfish-rust/src/sapling.rs | 76 ++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 69 deletions(-) create mode 100644 ironfish-rust/src/sapling.rs diff --git a/ironfish-rust/build.rs b/ironfish-rust/build.rs index 0ebb63ba0b..2a3274c74a 100644 --- a/ironfish-rust/build.rs +++ b/ironfish-rust/build.rs @@ -83,9 +83,22 @@ fn verify_integrity(checksum_path: &Path, files_dir: &Path) { let expected_hash = hex::decode(parts[0]) .unwrap_or_else(|_| panic!("{}: invalid syntax", checksum_path.display())); - let path = files_dir.join(parts[1]); - let mut file = fs::File::open(&path) - .unwrap_or_else(|err| panic!("failed to open {}: {}", path.display(), err)); + let file_name = parts[1]; + let path = files_dir.join(file_name); + let mut file = match fs::File::open(&path) { + Ok(file) => file, + Err(err) if err.kind() == io::ErrorKind::NotFound => { + println!( + "cargo:warning=could not find `{file_name}`. \ + Consider enabling the `download-params` feature \ + so that this file can be downloaded and verified \ + automatically" + ); + continue; + } + Err(err) => panic!("failed to open {}: {}", path.display(), err), + }; + let mut hasher = Sha512::new(); io::copy(&mut file, &mut hasher) .unwrap_or_else(|err| panic!("failed to read {}: {}", path.display(), err)); diff --git a/ironfish-rust/src/lib.rs b/ironfish-rust/src/lib.rs index b8f26ba1dd..637ec7d520 100644 --- a/ironfish-rust/src/lib.rs +++ b/ironfish-rust/src/lib.rs @@ -1,8 +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/. */ -use blstrs::Bls12; -use ironfish_bellperson::groth16; + +#[cfg(feature = "transaction-proofs")] +mod sapling; pub mod assets; pub mod errors; @@ -39,69 +40,7 @@ pub use { #[cfg(feature = "benchmark")] pub use ironfish_zkp::primitives::ValueCommitment; - #[cfg(feature = "transaction-proofs")] -pub use transaction::ProposedTransaction; - -// The main entry-point to the sapling API. Construct this with loaded parameters, and then call -// methods on it to do the actual work. -// -// spend and output are two arithmetic circuits for use in zksnark calculations provided by bellperson. -// Though the *_params have a verifying key on them, they are not the prepared verifying keys, -// so we store the prepared keys separately at the time of loading the params. -// -// The values are all loaded from a file in serialized form. -pub struct Sapling { - pub spend_params: groth16::Parameters, - pub output_params: groth16::Parameters, - pub mint_params: groth16::Parameters, - pub spend_verifying_key: groth16::PreparedVerifyingKey, - pub output_verifying_key: groth16::PreparedVerifyingKey, - pub mint_verifying_key: groth16::PreparedVerifyingKey, -} - +pub use sapling::Sapling; #[cfg(feature = "transaction-proofs")] -impl Sapling { - /// Initialize a Sapling instance and prepare for proving. Load the parameters from files - /// at a known location (`$OUT_DIR/sapling_params`). - pub fn load() -> Self { - let spend_bytes = include_bytes!(concat!( - env!("OUT_DIR"), - "/sapling_params/sapling-spend.params" - )); - let output_bytes = include_bytes!(concat!( - env!("OUT_DIR"), - "/sapling_params/sapling-output.params" - )); - let mint_bytes = include_bytes!(concat!( - env!("OUT_DIR"), - "/sapling_params/sapling-mint.params" - )); - - let spend_params = Sapling::load_params(&spend_bytes[..]); - let output_params = Sapling::load_params(&output_bytes[..]); - let mint_params = Sapling::load_params(&mint_bytes[..]); - - let spend_vk = groth16::prepare_verifying_key(&spend_params.vk); - let output_vk = groth16::prepare_verifying_key(&output_params.vk); - let mint_vk = groth16::prepare_verifying_key(&mint_params.vk); - - Sapling { - spend_verifying_key: spend_vk, - output_verifying_key: output_vk, - mint_verifying_key: mint_vk, - spend_params, - output_params, - mint_params, - } - } - - /// Load sapling parameters from a provided filename. The parameters are huge and take a - /// couple seconds to load. They primarily contain the "toxic waste" for a specific sapling - /// curve. - /// - /// NOTE: If this is stupidly slow for you, try compiling in --release mode - fn load_params(bytes: &[u8]) -> groth16::Parameters { - groth16::Parameters::read(bytes, false).unwrap() - } -} +pub use transaction::ProposedTransaction; diff --git a/ironfish-rust/src/sapling.rs b/ironfish-rust/src/sapling.rs new file mode 100644 index 0000000000..958fcaccb8 --- /dev/null +++ b/ironfish-rust/src/sapling.rs @@ -0,0 +1,76 @@ +/* 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/. */ + +use blstrs::Bls12; +use ironfish_bellperson::groth16; + +#[cfg(not(doc))] +macro_rules! include_params { + ( $name:literal ) => { + include_bytes!(concat!(env!("OUT_DIR"), "/sapling_params/", $name)) + }; +} + +// When building documentation (especially on docs.rs), it's quite possible that the parameter +// files won't be available, so don't even attempt to include them. This will also speed up +// documentation builds. +#[cfg(doc)] +macro_rules! include_params { + ( $name:literal ) => { + b"" + }; +} + +static SAPLING_SPEND_PARAMS: &[u8] = include_params!("sapling-spend.params"); +static SAPLING_OUTPUT_PARAMS: &[u8] = include_params!("sapling-output.params"); +static SAPLING_MINT_PARAMS: &[u8] = include_params!("sapling-mint.params"); + +// The main entry-point to the sapling API. Construct this with loaded parameters, and then call +// methods on it to do the actual work. +// +// spend and output are two arithmetic circuits for use in zksnark calculations provided by bellperson. +// Though the *_params have a verifying key on them, they are not the prepared verifying keys, +// so we store the prepared keys separately at the time of loading the params. +// +// The values are all loaded from a file in serialized form. +pub struct Sapling { + pub spend_params: groth16::Parameters, + pub output_params: groth16::Parameters, + pub mint_params: groth16::Parameters, + pub spend_verifying_key: groth16::PreparedVerifyingKey, + pub output_verifying_key: groth16::PreparedVerifyingKey, + pub mint_verifying_key: groth16::PreparedVerifyingKey, +} + +impl Sapling { + /// Initialize a Sapling instance and prepare for proving. Load the parameters from files + /// at a known location (`$OUT_DIR/sapling_params`). + pub fn load() -> Self { + let spend_params = Sapling::load_params(SAPLING_SPEND_PARAMS); + let output_params = Sapling::load_params(SAPLING_OUTPUT_PARAMS); + let mint_params = Sapling::load_params(SAPLING_MINT_PARAMS); + + let spend_verifying_key = groth16::prepare_verifying_key(&spend_params.vk); + let output_verifying_key = groth16::prepare_verifying_key(&output_params.vk); + let mint_verifying_key = groth16::prepare_verifying_key(&mint_params.vk); + + Sapling { + spend_verifying_key, + output_verifying_key, + mint_verifying_key, + spend_params, + output_params, + mint_params, + } + } + + /// Load sapling parameters from a provided filename. The parameters are huge and take a + /// couple seconds to load. They primarily contain the "toxic waste" for a specific sapling + /// curve. + /// + /// NOTE: If this is stupidly slow for you, try compiling in --release mode + fn load_params(bytes: &[u8]) -> groth16::Parameters { + groth16::Parameters::read(bytes, false).unwrap() + } +} From 4596b89e07577292f184d237029ab1281ae3b041 Mon Sep 17 00:00:00 2001 From: jowparks Date: Fri, 1 Nov 2024 14:43:24 -0700 Subject: [PATCH 69/81] - adds default limit for table output to 50 (#5611) - prints message at end of table if limit is reached - updates commands to handle limit --- .../src/commands/mempool/transactions.ts | 15 +-- ironfish-cli/src/commands/peers/banned.ts | 7 +- ironfish-cli/src/commands/wallet/assets.ts | 120 +++++++++--------- .../src/commands/wallet/notes/index.ts | 120 +++++++++--------- .../src/commands/wallet/transactions.ts | 22 ++-- ironfish-cli/src/ui/table.ts | 13 +- 6 files changed, 150 insertions(+), 147 deletions(-) diff --git a/ironfish-cli/src/commands/mempool/transactions.ts b/ironfish-cli/src/commands/mempool/transactions.ts index 70f3878cea..0c3e5c8dbd 100644 --- a/ironfish-cli/src/commands/mempool/transactions.ts +++ b/ironfish-cli/src/commands/mempool/transactions.ts @@ -131,6 +131,9 @@ export class TransactionsCommand extends IronfishCommand { const transactions: GetMempoolTransactionResponse[] = [] for await (const transaction of response.contentStream()) { + if (transactions.length >= flags.limit) { + break + } transactions.push(transaction) } @@ -208,22 +211,16 @@ function renderTable( let result = '' - const limit = flags.csv ? 0 : flags.show - table(getRows(response, limit), columns, { + table(getRows(response), columns, { printLine: (line) => (result += `${String(line)}\n`), ...flags, }) - if (limit > 0 && response.length > limit) { - result += ` ... ${response.length - limit} more rows\n` - } - return result } -function getRows(response: GetMempoolTransactionResponse[], limit: number): TransactionRow[] { - const transactions = limit > 0 ? response.slice(0, limit) : response - return transactions.map(({ serializedTransaction, position, expiresIn }) => { +function getRows(response: GetMempoolTransactionResponse[]): TransactionRow[] { + return response.map(({ serializedTransaction, position, expiresIn }) => { const transaction = new Transaction(Buffer.from(serializedTransaction, 'hex')) return { diff --git a/ironfish-cli/src/commands/peers/banned.ts b/ironfish-cli/src/commands/peers/banned.ts index 9344a6efe9..110afc5ade 100644 --- a/ironfish-cli/src/commands/peers/banned.ts +++ b/ironfish-cli/src/commands/peers/banned.ts @@ -33,7 +33,7 @@ export class BannedCommand extends IronfishCommand { if (!flags.follow) { await this.sdk.client.connect() const response = await this.sdk.client.peer.getBannedPeers() - this.log(renderTable(response.content)) + this.log(renderTable(response.content, flags.limit)) this.exit(0) } @@ -59,14 +59,14 @@ export class BannedCommand extends IronfishCommand { for await (const value of response.contentStream()) { text.clearBaseLine(0) - text.setContent(renderTable(value)) + text.setContent(renderTable(value, flags.limit)) screen.render() } } } } -function renderTable(content: GetBannedPeersResponse): string { +function renderTable(content: GetBannedPeersResponse, limit: number): string { const columns: TableColumns = { identity: { minWidth: 45, @@ -87,6 +87,7 @@ function renderTable(content: GetBannedPeersResponse): string { let result = '' table(content.peers, columns, { + limit, printLine: (line) => (result += `${String(line)}\n`), }) diff --git a/ironfish-cli/src/commands/wallet/assets.ts b/ironfish-cli/src/commands/wallet/assets.ts index 32adc0de6a..28a5ab9b6d 100644 --- a/ironfish-cli/src/commands/wallet/assets.ts +++ b/ironfish-cli/src/commands/wallet/assets.ts @@ -52,68 +52,68 @@ export class AssetsCommand extends IronfishCommand { const assetNameWidth = flags.extended ? MAX_ASSET_NAME_COLUMN_WIDTH : MIN_ASSET_NAME_COLUMN_WIDTH - let showHeader = !flags['no-header'] - + const assets = [] for await (const asset of response.contentStream()) { - table( - [asset], - { - name: TableCols.fixedWidth({ - header: 'Name', - width: assetNameWidth, - get: (row) => - renderAssetWithVerificationStatus( - BufferUtils.toHuman(Buffer.from(row.name, 'hex')), - { - verification: row.verification, - outputType: flags.output, - }, - ), - }), - id: { - header: 'ID', - minWidth: ASSET_ID_LENGTH + 1, - get: (row) => row.id, - }, - metadata: TableCols.fixedWidth({ - header: 'Metadata', - width: assetMetadataWidth, - get: (row) => BufferUtils.toHuman(Buffer.from(row.metadata, 'hex')), - }), - createdTransactionHash: { - header: 'Created Transaction Hash', - get: (row) => row.createdTransactionHash, - }, - supply: { - header: 'Supply', - minWidth: 16, - get: (row) => row.supply ?? 'NULL', - }, - creator: { - header: 'Creator', - minWidth: PUBLIC_ADDRESS_LENGTH + 1, - get: (row) => - row.id === Asset.nativeId().toString('hex') - ? BufferUtils.toHuman(Buffer.from(row.creator, 'hex')) - : row.creator, - }, - owner: { - header: 'Owner', - minWidth: PUBLIC_ADDRESS_LENGTH + 1, - get: (row) => - row.id === Asset.nativeId().toString('hex') - ? BufferUtils.toHuman(Buffer.from(row.owner, 'hex')) - : row.owner, - }, + assets.push(asset) + if (assets.length >= flags.limit) { + break + } + } + table( + assets, + { + name: TableCols.fixedWidth({ + header: 'Name', + width: assetNameWidth, + get: (row) => + renderAssetWithVerificationStatus( + BufferUtils.toHuman(Buffer.from(row.name, 'hex')), + { + verification: row.verification, + outputType: flags.output, + }, + ), + }), + id: { + header: 'ID', + minWidth: ASSET_ID_LENGTH + 1, + get: (row) => row.id, }, - { - printLine: this.log.bind(this), - ...flags, - 'no-header': !showHeader, + metadata: TableCols.fixedWidth({ + header: 'Metadata', + width: assetMetadataWidth, + get: (row) => BufferUtils.toHuman(Buffer.from(row.metadata, 'hex')), + }), + createdTransactionHash: { + header: 'Created Transaction Hash', + get: (row) => row.createdTransactionHash, }, - ) - - showHeader = false - } + supply: { + header: 'Supply', + minWidth: 16, + get: (row) => row.supply ?? 'NULL', + }, + creator: { + header: 'Creator', + minWidth: PUBLIC_ADDRESS_LENGTH + 1, + get: (row) => + row.id === Asset.nativeId().toString('hex') + ? BufferUtils.toHuman(Buffer.from(row.creator, 'hex')) + : row.creator, + }, + owner: { + header: 'Owner', + minWidth: PUBLIC_ADDRESS_LENGTH + 1, + get: (row) => + row.id === Asset.nativeId().toString('hex') + ? BufferUtils.toHuman(Buffer.from(row.owner, 'hex')) + : row.owner, + }, + }, + { + printLine: this.log.bind(this), + ...flags, + }, + ) } } diff --git a/ironfish-cli/src/commands/wallet/notes/index.ts b/ironfish-cli/src/commands/wallet/notes/index.ts index e1ac874f94..076cbd5581 100644 --- a/ironfish-cli/src/commands/wallet/notes/index.ts +++ b/ironfish-cli/src/commands/wallet/notes/index.ts @@ -1,7 +1,7 @@ /* 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 { CurrencyUtils, RpcAsset } from '@ironfish/sdk' +import { CurrencyUtils, RpcAsset, RpcWalletNote } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../command' import { RemoteFlags } from '../../../flags' @@ -33,74 +33,74 @@ export class NotesCommand extends IronfishCommand { const account = await useAccount(client, flags.account) const response = client.wallet.getAccountNotesStream({ account }) - - let showHeader = !flags['no-header'] - + const notes: RpcWalletNote[] = [] for await (const note of response.contentStream()) { + if (notes.length >= flags.limit) { + break + } if (!assetLookup.has(note.assetId)) { assetLookup.set( note.assetId, (await client.wallet.getAsset({ id: note.assetId, account })).content, ) } - - ui.table( - [note], - { - memo: { - header: 'Memo', - // Maximum memo length is 32 bytes - minWidth: 33, - get: (row) => row.memo, - }, - sender: { - header: 'Sender', - get: (row) => row.sender, - }, - transactionHash: { - header: 'From Transaction', - get: (row) => row.transactionHash, - }, - isSpent: { - header: 'Spent', - get: (row) => { - if (row.spent === undefined) { - return '-' - } else { - return row.spent ? `✔` : `x` - } - }, - }, - ...TableCols.asset({ extended: flags.extended }), - value: { - header: 'Amount', - get: (row) => - CurrencyUtils.render( - row.value, - false, - row.assetId, - assetLookup.get(row.assetId)?.verification, - ), - minWidth: 16, - }, - noteHash: { - header: 'Note Hash', - get: (row) => row.noteHash, + notes.push(note) + } + ui.table( + notes, + { + memo: { + header: 'Memo', + // Maximum memo length is 32 bytes + minWidth: 33, + get: (row) => row.memo, + }, + sender: { + header: 'Sender', + get: (row) => row.sender, + }, + transactionHash: { + header: 'From Transaction', + get: (row) => row.transactionHash, + }, + isSpent: { + header: 'Spent', + get: (row) => { + if (row.spent === undefined) { + return '-' + } else { + return row.spent ? `✔` : `x` + } }, - nullifier: { - header: 'Nullifier', - get: (row) => { - if (row.nullifier === null) { - return '-' - } else { - return row.nullifier - } - }, + }, + ...TableCols.asset({ extended: flags.extended }), + value: { + header: 'Amount', + get: (row) => + CurrencyUtils.render( + row.value, + false, + row.assetId, + assetLookup.get(row.assetId)?.verification, + ), + minWidth: 16, + }, + noteHash: { + header: 'Note Hash', + get: (row) => row.noteHash, + }, + nullifier: { + header: 'Nullifier', + get: (row) => { + if (row.nullifier === null) { + return '-' + } else { + return row.nullifier + } }, }, - { ...flags, 'no-header': !showHeader }, - ) - showHeader = false - } + }, + flags, + ) } } diff --git a/ironfish-cli/src/commands/wallet/transactions.ts b/ironfish-cli/src/commands/wallet/transactions.ts index 8ac0550357..d38eb61e3b 100644 --- a/ironfish-cli/src/commands/wallet/transactions.ts +++ b/ironfish-cli/src/commands/wallet/transactions.ts @@ -38,9 +38,6 @@ export class TransactionsCommand extends IronfishCommand { char: 's', description: 'Block sequence to get transactions for', }), - limit: Flags.integer({ - description: 'Number of latest transactions to get details for', - }), offset: Flags.integer({ description: 'Number of latest transactions to skip', }), @@ -82,11 +79,13 @@ export class TransactionsCommand extends IronfishCommand { const columns = this.getColumns(flags.extended, flags.notes, format) - let showHeader = !flags['no-header'] let hasTransactions = false + let transactionRows: PartialRecursive[] = [] for await (const transaction of response.contentStream()) { - let transactionRows: PartialRecursive[] + if (transactionRows.length >= flags.limit) { + break + } if (flags.notes) { Assert.isNotUndefined(transaction.notes) const assetLookup = await getAssetsByIDs( @@ -113,17 +112,14 @@ export class TransactionsCommand extends IronfishCommand { ) transactionRows = this.getTransactionRows(assetLookup, transaction, format) } - - ui.table(transactionRows, columns, { - printLine: this.log.bind(this), - ...flags, - 'no-header': !showHeader, - }) - - showHeader = false hasTransactions = true } + ui.table(transactionRows, columns, { + printLine: this.log.bind(this), + ...flags, + }) + if (!hasTransactions) { this.log('No transactions found') } diff --git a/ironfish-cli/src/ui/table.ts b/ironfish-cli/src/ui/table.ts index ef33408905..2112e70b97 100644 --- a/ironfish-cli/src/ui/table.ts +++ b/ironfish-cli/src/ui/table.ts @@ -30,6 +30,7 @@ export interface TableOptions { output?: string printLine?(this: void, s: unknown): unknown sort?: string + limit?: number } export const TableFlags = { @@ -51,6 +52,10 @@ export const TableFlags = { sort: Flags.string({ description: "property to sort by (prepend '-' for descending)", }), + limit: Flags.integer({ + description: 'the number of rows to display, 0 will show all rows', + default: 50, + }), } export function table>( @@ -82,6 +87,7 @@ class Table> { output: options.csv ? 'csv' : options.output, printLine: options.printLine ?? ux.stdout.bind(ux), sort: options.sort, + limit: options.limit ?? 50, } } @@ -168,7 +174,6 @@ class Table> { for (const column of this.columns) { column.width = maxColumnLength(column, rows) } - if (!this.options['no-header']) { // Print headers const columnHeaders = [] @@ -199,7 +204,8 @@ class Table> { } // Print rows - for (const row of rows) { + const slicedRows = this.options.limit ? rows.slice(0, this.options.limit) : rows + for (const row of slicedRows) { const rowValues = [] for (const [key, value] of Object.entries(row)) { const column = this.columns.find((c) => c.key === key) @@ -210,6 +216,9 @@ class Table> { } this.options.printLine(` ${rowValues.join(' ')}`) } + if (this.options.limit && this.options.limit <= 0 && rows.length >= this.options.limit) { + this.options.printLine(`...\n[see more rows by using --limit flag]`) + } } } From d5f886c594183a615d8241d69a086cd615f2d220 Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Fri, 1 Nov 2024 18:17:11 -0400 Subject: [PATCH 70/81] Fix tests + schema for wallet/create endpoint (#5603) --- .../src/rpc/routes/wallet/create.test.slow.ts | 68 ++------ ironfish/src/rpc/routes/wallet/create.ts | 22 ++- .../routes/wallet/createAccount.test.slow.ts | 146 ++++++++++++++++++ 3 files changed, 178 insertions(+), 58 deletions(-) create mode 100644 ironfish/src/rpc/routes/wallet/createAccount.test.slow.ts diff --git a/ironfish/src/rpc/routes/wallet/create.test.slow.ts b/ironfish/src/rpc/routes/wallet/create.test.slow.ts index 03202e8671..a297e267e8 100644 --- a/ironfish/src/rpc/routes/wallet/create.test.slow.ts +++ b/ironfish/src/rpc/routes/wallet/create.test.slow.ts @@ -9,8 +9,9 @@ import { v4 as uuid } from 'uuid' import { createRouteTest } from '../../../testUtilities/routeTest' import { RPC_ERROR_CODES } from '../../adapters' import { RpcRequestError } from '../../clients/errors' +import { CreateAccountResponse } from './createAccount' -describe('Route wallet/createAccount', () => { +describe('Route wallet/create', () => { jest.setTimeout(15000) const routeTest = createRouteTest() @@ -27,7 +28,8 @@ describe('Route wallet/createAccount', () => { const name = uuid() - const response = await routeTest.client.wallet.createAccount({ name }) + const response = await routeTest.client.request(`wallet/create`, { name }).waitForEnd() + expect(response.status).toBe(200) expect(response.content).toMatchObject({ name: name, @@ -38,7 +40,7 @@ describe('Route wallet/createAccount', () => { const account = routeTest.node.wallet.getAccountByName(name) expect(account).toMatchObject({ name: name, - publicAddress: response.content.publicAddress, + publicAddress: (response.content as CreateAccountResponse).publicAddress, createdAt: createdAtHead, }) }) @@ -48,7 +50,8 @@ describe('Route wallet/createAccount', () => { const name = uuid() - const response = await routeTest.client.wallet.createAccount({ name }) + const response = await routeTest.client.request(`wallet/create`, { name }).waitForEnd() + expect(response.content).toMatchObject({ name: name, publicAddress: expect.any(String), @@ -64,13 +67,13 @@ describe('Route wallet/createAccount', () => { try { expect.assertions(2) - await routeTest.client.wallet.createAccount({ name: name }) + await routeTest.client.request(`wallet/create`, { name }).waitForEnd() } catch (e: unknown) { if (!(e instanceof RpcRequestError)) { throw e } expect(e.status).toBe(400) - expect(e.code).toBe(RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME) + expect(e.code).toBe(RPC_ERROR_CODES.ACCOUNT_EXISTS) } }) @@ -83,7 +86,8 @@ describe('Route wallet/createAccount', () => { const name = uuid() - const response = await routeTest.client.wallet.createAccount({ name }) + const response = await routeTest.client.request(`wallet/create`, { name }).waitForEnd() + expect(response.status).toBe(200) expect(response.content).toMatchObject({ name: name, @@ -93,54 +97,4 @@ describe('Route wallet/createAccount', () => { expect(scanSpy).toHaveBeenCalled() }) - - it('should set account createdAt if passed', async () => { - const name = uuid() - const createdAt = 10 - - const response = await routeTest.client.wallet.createAccount({ - name, - createdAt, - }) - - expect(response.status).toBe(200) - expect(response.content).toMatchObject({ - name: name, - publicAddress: expect.any(String), - isDefaultAccount: true, - }) - - const account = routeTest.node.wallet.getAccountByName(name) - expect(account).toMatchObject({ - name: name, - publicAddress: response.content.publicAddress, - createdAt: { - hash: Buffer.alloc(32, 0), - sequence: 10, - }, - }) - }) - - it('should set account createdAt to null', async () => { - const name = uuid() - - const response = await routeTest.client.wallet.createAccount({ - name, - createdAt: null, - }) - - expect(response.status).toBe(200) - expect(response.content).toMatchObject({ - name: name, - publicAddress: expect.any(String), - isDefaultAccount: true, - }) - - const account = routeTest.node.wallet.getAccountByName(name) - expect(account).toMatchObject({ - name: name, - publicAddress: response.content.publicAddress, - createdAt: null, - }) - }) }) diff --git a/ironfish/src/rpc/routes/wallet/create.ts b/ironfish/src/rpc/routes/wallet/create.ts index 18738ebd4f..da31100979 100644 --- a/ironfish/src/rpc/routes/wallet/create.ts +++ b/ironfish/src/rpc/routes/wallet/create.ts @@ -8,11 +8,31 @@ * is the verbObject naming convention. For example, `POST /wallet/burnAsset` burns an asset. */ +import * as yup from 'yup' import { RPC_ERROR_CODES, RpcValidationError } from '../../adapters' import { ApiNamespace } from '../namespaces' import { routes } from '../router' import { AssertHasRpcContext } from '../rpcContext' -import { CreateAccountRequestSchema, CreateAccountResponse } from '../wallet' + +type CreateAccountRequest = { + name: string + default?: boolean + createdAt?: number | null +} + +type CreateAccountResponse = { + name: string + publicAddress: string + isDefaultAccount: boolean +} + +const CreateAccountRequestSchema: yup.ObjectSchema = yup + .object({ + name: yup.string().defined(), + default: yup.boolean().optional(), + createdAt: yup.number().optional().nullable(), + }) + .defined() routes.register( `${ApiNamespace.wallet}/create`, diff --git a/ironfish/src/rpc/routes/wallet/createAccount.test.slow.ts b/ironfish/src/rpc/routes/wallet/createAccount.test.slow.ts new file mode 100644 index 0000000000..03202e8671 --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/createAccount.test.slow.ts @@ -0,0 +1,146 @@ +/* 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/. */ + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { v4 as uuid } from 'uuid' +import { createRouteTest } from '../../../testUtilities/routeTest' +import { RPC_ERROR_CODES } from '../../adapters' +import { RpcRequestError } from '../../clients/errors' + +describe('Route wallet/createAccount', () => { + jest.setTimeout(15000) + const routeTest = createRouteTest() + + beforeEach(() => { + jest.spyOn(routeTest.node.wallet, 'scan').mockReturnValue(Promise.resolve(null)) + }) + + it('should create an account', async () => { + await routeTest.node.wallet.createAccount('existingAccount', { setDefault: true }) + const createdAtHead = { + hash: routeTest.node.chain.head.hash, + sequence: routeTest.node.chain.head.sequence, + } + + const name = uuid() + + const response = await routeTest.client.wallet.createAccount({ name }) + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + publicAddress: expect.any(String), + isDefaultAccount: false, + }) + + const account = routeTest.node.wallet.getAccountByName(name) + expect(account).toMatchObject({ + name: name, + publicAddress: response.content.publicAddress, + createdAt: createdAtHead, + }) + }) + + it('should set the account as default', async () => { + await routeTest.node.wallet.setDefaultAccount(null) + + const name = uuid() + + const response = await routeTest.client.wallet.createAccount({ name }) + expect(response.content).toMatchObject({ + name: name, + publicAddress: expect.any(String), + isDefaultAccount: true, + }) + expect(routeTest.node.wallet.getDefaultAccount()?.name).toBe(name) + }) + + it('should fail if name already exists', async () => { + const name = uuid() + + await routeTest.node.wallet.createAccount(name) + + try { + expect.assertions(2) + await routeTest.client.wallet.createAccount({ name: name }) + } catch (e: unknown) { + if (!(e instanceof RpcRequestError)) { + throw e + } + expect(e.status).toBe(400) + expect(e.code).toBe(RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME) + } + }) + + it('should start scanning transactions for the new account', async () => { + const scanSpy = jest + .spyOn(routeTest.node.wallet, 'scan') + .mockReturnValue(Promise.resolve(null)) + + await routeTest.node.wallet.createAccount('existingAccount', { setDefault: true }) + + const name = uuid() + + const response = await routeTest.client.wallet.createAccount({ name }) + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + publicAddress: expect.any(String), + isDefaultAccount: false, + }) + + expect(scanSpy).toHaveBeenCalled() + }) + + it('should set account createdAt if passed', async () => { + const name = uuid() + const createdAt = 10 + + const response = await routeTest.client.wallet.createAccount({ + name, + createdAt, + }) + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + publicAddress: expect.any(String), + isDefaultAccount: true, + }) + + const account = routeTest.node.wallet.getAccountByName(name) + expect(account).toMatchObject({ + name: name, + publicAddress: response.content.publicAddress, + createdAt: { + hash: Buffer.alloc(32, 0), + sequence: 10, + }, + }) + }) + + it('should set account createdAt to null', async () => { + const name = uuid() + + const response = await routeTest.client.wallet.createAccount({ + name, + createdAt: null, + }) + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + publicAddress: expect.any(String), + isDefaultAccount: true, + }) + + const account = routeTest.node.wallet.getAccountByName(name) + expect(account).toMatchObject({ + name: name, + publicAddress: response.content.publicAddress, + createdAt: null, + }) + }) +}) From 4fd96110ab3518f480292ec2b6bbfdb94c7aed23 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Fri, 1 Nov 2024 15:20:47 -0700 Subject: [PATCH 71/81] Move transactions list command to index (#5607) --- .../{transactions.ts => transactions/index.ts} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename ironfish-cli/src/commands/wallet/{transactions.ts => transactions/index.ts} (96%) diff --git a/ironfish-cli/src/commands/wallet/transactions.ts b/ironfish-cli/src/commands/wallet/transactions/index.ts similarity index 96% rename from ironfish-cli/src/commands/wallet/transactions.ts rename to ironfish-cli/src/commands/wallet/transactions/index.ts index d38eb61e3b..9b4576c32e 100644 --- a/ironfish-cli/src/commands/wallet/transactions.ts +++ b/ironfish-cli/src/commands/wallet/transactions/index.ts @@ -11,12 +11,12 @@ import { TransactionType, } from '@ironfish/sdk' import { Flags } from '@oclif/core' -import { IronfishCommand } from '../../command' -import { RemoteFlags } from '../../flags' -import * as ui from '../../ui' -import { getAssetsByIDs, useAccount } from '../../utils' -import { extractChainportDataFromTransaction } from '../../utils/chainport' -import { Format, TableCols } from '../../utils/table' +import { IronfishCommand } from '../../../command' +import { RemoteFlags } from '../../../flags' +import * as ui from '../../../ui' +import { getAssetsByIDs, useAccount } from '../../../utils' +import { extractChainportDataFromTransaction } from '../../../utils/chainport' +import { Format, TableCols } from '../../../utils/table' const { sort: _, ...tableFlags } = ui.TableFlags export class TransactionsCommand extends IronfishCommand { From 71d24f22cf2ae2b3a96695273463d0a5103e46f8 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Fri, 1 Nov 2024 15:28:11 -0700 Subject: [PATCH 72/81] Make these account tests not slow (#5612) --- .../rpc/routes/wallet/{create.test.slow.ts => create.test.ts} | 1 - .../wallet/{createAccount.test.slow.ts => createAccount.test.ts} | 1 - 2 files changed, 2 deletions(-) rename ironfish/src/rpc/routes/wallet/{create.test.slow.ts => create.test.ts} (99%) rename ironfish/src/rpc/routes/wallet/{createAccount.test.slow.ts => createAccount.test.ts} (99%) diff --git a/ironfish/src/rpc/routes/wallet/create.test.slow.ts b/ironfish/src/rpc/routes/wallet/create.test.ts similarity index 99% rename from ironfish/src/rpc/routes/wallet/create.test.slow.ts rename to ironfish/src/rpc/routes/wallet/create.test.ts index a297e267e8..aaaadc2c04 100644 --- a/ironfish/src/rpc/routes/wallet/create.test.slow.ts +++ b/ironfish/src/rpc/routes/wallet/create.test.ts @@ -12,7 +12,6 @@ import { RpcRequestError } from '../../clients/errors' import { CreateAccountResponse } from './createAccount' describe('Route wallet/create', () => { - jest.setTimeout(15000) const routeTest = createRouteTest() beforeEach(() => { diff --git a/ironfish/src/rpc/routes/wallet/createAccount.test.slow.ts b/ironfish/src/rpc/routes/wallet/createAccount.test.ts similarity index 99% rename from ironfish/src/rpc/routes/wallet/createAccount.test.slow.ts rename to ironfish/src/rpc/routes/wallet/createAccount.test.ts index 03202e8671..ad41a417f6 100644 --- a/ironfish/src/rpc/routes/wallet/createAccount.test.slow.ts +++ b/ironfish/src/rpc/routes/wallet/createAccount.test.ts @@ -11,7 +11,6 @@ import { RPC_ERROR_CODES } from '../../adapters' import { RpcRequestError } from '../../clients/errors' describe('Route wallet/createAccount', () => { - jest.setTimeout(15000) const routeTest = createRouteTest() beforeEach(() => { From 5e0e0cf88d3edcf37d61b387afb97f56d44cf49e Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 4 Nov 2024 15:13:00 -0800 Subject: [PATCH 73/81] Enable the `unused_qualifications` lint for our Rust crates --- ironfish-rust-nodejs/src/lib.rs | 4 +- ironfish-rust-nodejs/src/nacl.rs | 2 +- ironfish-rust/src/keys/public_address.rs | 2 +- ironfish-rust/src/keys/view_keys.rs | 4 +- ironfish-rust/src/lib.rs | 2 + ironfish-rust/src/note.rs | 5 ++- ironfish-rust/src/transaction/mints.rs | 2 +- ironfish-rust/src/transaction/mod.rs | 2 +- ironfish-rust/src/transaction/spends.rs | 32 +++++++------- ironfish-rust/src/transaction/unsigned.rs | 2 +- ironfish-rust/src/witness.rs | 4 +- ironfish-zkp/src/circuits/util.rs | 4 +- ironfish-zkp/src/lib.rs | 2 + .../src/primitives/value_commitment.rs | 44 +++++++++---------- 14 files changed, 59 insertions(+), 52 deletions(-) diff --git a/ironfish-rust-nodejs/src/lib.rs b/ironfish-rust-nodejs/src/lib.rs index fcb8f96712..b1efd4eb28 100644 --- a/ironfish-rust-nodejs/src/lib.rs +++ b/ironfish-rust-nodejs/src/lib.rs @@ -2,6 +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/. */ +#![warn(unused_qualifications)] + use std::fmt::Display; use std::num::NonZeroUsize; @@ -31,7 +33,7 @@ pub mod xchacha20poly1305; #[cfg(feature = "stats")] pub mod stats; -fn to_napi_err(err: impl Display) -> napi::Error { +fn to_napi_err(err: impl Display) -> Error { Error::from_reason(err.to_string()) } diff --git a/ironfish-rust-nodejs/src/nacl.rs b/ironfish-rust-nodejs/src/nacl.rs index 91ff5ee9c0..bed94daac1 100644 --- a/ironfish-rust-nodejs/src/nacl.rs +++ b/ironfish-rust-nodejs/src/nacl.rs @@ -37,7 +37,7 @@ impl BoxKeyPair { } #[napi(factory)] - pub fn from_hex(secret_hex: String) -> napi::Result { + pub fn from_hex(secret_hex: String) -> Result { let bytes: [u8; nacl::KEY_LENGTH] = hex_to_bytes(&secret_hex).map_err(|_| to_napi_err("Unable to decode secret key"))?; diff --git a/ironfish-rust/src/keys/public_address.rs b/ironfish-rust/src/keys/public_address.rs index 5e759c3ae3..b2b9285af2 100644 --- a/ironfish-rust/src/keys/public_address.rs +++ b/ironfish-rust/src/keys/public_address.rs @@ -94,7 +94,7 @@ impl std::fmt::Debug for PublicAddress { } } -impl std::cmp::PartialEq for PublicAddress { +impl PartialEq for PublicAddress { fn eq(&self, other: &Self) -> bool { self.hex_public_address() == other.hex_public_address() } diff --git a/ironfish-rust/src/keys/view_keys.rs b/ironfish-rust/src/keys/view_keys.rs index 1ce0168f2e..0a039381a2 100644 --- a/ironfish-rust/src/keys/view_keys.rs +++ b/ironfish-rust/src/keys/view_keys.rs @@ -110,11 +110,11 @@ pub struct ViewKey { /// Part of the full viewing key. Generally referred to as /// `ak` in the literature. Derived from spend_authorizing_key using scalar /// multiplication in Sapling. Used to construct incoming viewing key. - pub authorizing_key: ironfish_jubjub::SubgroupPoint, + pub authorizing_key: SubgroupPoint, /// Part of the full viewing key. Generally referred to as /// `nk` in the literature. Derived from proof_authorizing_key using scalar /// multiplication. Used to construct incoming viewing key. - pub nullifier_deriving_key: ironfish_jubjub::SubgroupPoint, + pub nullifier_deriving_key: SubgroupPoint, } impl ViewKey { diff --git a/ironfish-rust/src/lib.rs b/ironfish-rust/src/lib.rs index 637ec7d520..2e1c478c3c 100644 --- a/ironfish-rust/src/lib.rs +++ b/ironfish-rust/src/lib.rs @@ -2,6 +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/. */ +#![warn(unused_qualifications)] + #[cfg(feature = "transaction-proofs")] mod sapling; diff --git a/ironfish-rust/src/note.rs b/ironfish-rust/src/note.rs index f4b7b9846a..c0d1718b83 100644 --- a/ironfish-rust/src/note.rs +++ b/ironfish-rust/src/note.rs @@ -27,6 +27,7 @@ use ironfish_zkp::{ }; use rand::thread_rng; use std::{fmt, io, io::Read}; + pub const ENCRYPTED_NOTE_SIZE: usize = SCALAR_SIZE + MEMO_SIZE + AMOUNT_VALUE_SIZE + ASSET_ID_LENGTH + PUBLIC_ADDRESS_SIZE; // 8 value @@ -136,7 +137,7 @@ impl Note { /// /// You probably don't want to use this unless you are transmitting /// across nodejs threads in memory. - pub fn read(mut reader: R) -> Result { + pub fn read(mut reader: R) -> Result { let owner = PublicAddress::read(&mut reader)?; let asset_id = AssetIdentifier::read(&mut reader)?; @@ -286,7 +287,7 @@ impl Note { } /// Computes the note commitment, returning the full point. - fn commitment_full_point(&self) -> ironfish_jubjub::SubgroupPoint { + fn commitment_full_point(&self) -> SubgroupPoint { commitment_full_point( self.asset_generator(), self.value, diff --git a/ironfish-rust/src/transaction/mints.rs b/ironfish-rust/src/transaction/mints.rs index 3f09c3d8b9..96a0ea9250 100644 --- a/ironfish-rust/src/transaction/mints.rs +++ b/ironfish-rust/src/transaction/mints.rs @@ -207,7 +207,7 @@ pub struct MintDescription { /// Signature of the creator authorizing the mint action. This value is /// calculated after the transaction is signed since the value is dependent /// on the binding signature key - pub authorizing_signature: redjubjub::Signature, + pub authorizing_signature: Signature, } impl MintDescription { diff --git a/ironfish-rust/src/transaction/mod.rs b/ironfish-rust/src/transaction/mod.rs index 1b71a3d202..e4dc239f86 100644 --- a/ironfish-rust/src/transaction/mod.rs +++ b/ironfish-rust/src/transaction/mod.rs @@ -153,7 +153,7 @@ impl Transaction { /// Store the bytes of this transaction in the given writer. This is used /// to serialize transactions to file or network - pub fn write(&self, mut writer: W) -> Result<(), IronfishError> { + pub fn write(&self, mut writer: W) -> Result<(), IronfishError> { self.version.write(&mut writer)?; writer.write_u64::(self.spends.len() as u64)?; writer.write_u64::(self.outputs.len() as u64)?; diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index 7acc7e58ec..b56412704c 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -13,7 +13,7 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::{Field, PrimeField}; use group::{Curve, GroupEncoding}; use ironfish_bellperson::{gadgets::multipack, groth16}; -use ironfish_jubjub::ExtendedPoint; +use ironfish_jubjub::{ExtendedPoint, Fr}; use ironfish_zkp::{ constants::SPENDING_KEY_GENERATOR, redjubjub::{self, Signature}, @@ -98,7 +98,7 @@ impl SpendBuilder { &self, proof_generation_key: &ProofGenerationKey, view_key: &ViewKey, - public_key_randomness: &ironfish_jubjub::Fr, + public_key_randomness: &Fr, randomized_public_key: &redjubjub::PublicKey, ) -> Result { let value_commitment_point = self.value_commitment_point(); @@ -154,7 +154,7 @@ impl SpendBuilder { pub struct UnsignedSpendDescription { /// Used to add randomness to signature generation without leaking the /// key. Referred to as `ar` in the literature. - public_key_randomness: ironfish_jubjub::Fr, + public_key_randomness: Fr, /// Proof and public parameters for a user action to spend tokens. pub(crate) description: SpendDescription, @@ -254,7 +254,7 @@ pub struct SpendDescription { /// key that incorporates calculations from all the spends and outputs /// in that transaction. It's optional because it is calculated after /// construction. - pub(crate) authorizing_signature: redjubjub::Signature, + pub(crate) authorizing_signature: Signature, } impl SpendDescription { @@ -419,9 +419,10 @@ mod test { }; use ff::Field; use group::Curve; + use ironfish_jubjub::Fr; use ironfish_zkp::{ constants::SPENDING_KEY_GENERATOR, - redjubjub::{self, PrivateKey, PublicKey}, + redjubjub::{PrivateKey, PublicKey}, }; use rand::{random, thread_rng, Rng}; @@ -431,14 +432,13 @@ mod test { let public_address = key.public_address(); let sender_key = SaplingKey::generate_key(); - let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); - let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) + let public_key_randomness = Fr::random(thread_rng()); + let randomized_public_key = PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); - let other_public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); - let other_randomized_public_key = - redjubjub::PublicKey(sender_key.view_key.authorizing_key.into()) - .randomize(other_public_key_randomness, *SPENDING_KEY_GENERATOR); + let other_public_key_randomness = Fr::random(thread_rng()); + let other_randomized_public_key = PublicKey(sender_key.view_key.authorizing_key.into()) + .randomize(other_public_key_randomness, *SPENDING_KEY_GENERATOR); let note_randomness = random(); @@ -532,8 +532,8 @@ mod test { let spend = SpendBuilder::new(note, &witness); - let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); - let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) + let public_key_randomness = Fr::random(thread_rng()); + let randomized_public_key = PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); // signature comes from transaction, normally @@ -608,13 +608,13 @@ mod test { sender_key.public_address(), ); let witness = make_fake_witness(¬e); - let public_key_randomness = ironfish_jubjub::Fr::random(thread_rng()); - let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into()) + let public_key_randomness = Fr::random(thread_rng()); + let randomized_public_key = PublicKey(key.view_key.authorizing_key.into()) .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); let builder = SpendBuilder::new(note, &witness); // create a random private key and sign random message as placeholder - let private_key = PrivateKey(ironfish_jubjub::Fr::random(thread_rng())); + let private_key = PrivateKey(Fr::random(thread_rng())); let public_key = PublicKey::from_private(&private_key, *SPENDING_KEY_GENERATOR); let msg = [0u8; 32]; let signature = private_key.sign(&msg, &mut thread_rng(), *SPENDING_KEY_GENERATOR); diff --git a/ironfish-rust/src/transaction/unsigned.rs b/ironfish-rust/src/transaction/unsigned.rs index 694cb630b5..076cc46d72 100644 --- a/ironfish-rust/src/transaction/unsigned.rs +++ b/ironfish-rust/src/transaction/unsigned.rs @@ -127,7 +127,7 @@ impl UnsignedTransaction { /// Store the bytes of this transaction in the given writer. This is used /// to serialize transactions to file or network - pub fn write(&self, mut writer: W) -> Result<(), IronfishError> { + pub fn write(&self, mut writer: W) -> Result<(), IronfishError> { self.version.write(&mut writer)?; writer.write_u64::(self.spends.len() as u64)?; writer.write_u64::(self.outputs.len() as u64)?; diff --git a/ironfish-rust/src/witness.rs b/ironfish-rust/src/witness.rs index 97844563b3..06445337e1 100644 --- a/ironfish-rust/src/witness.rs +++ b/ironfish-rust/src/witness.rs @@ -5,14 +5,14 @@ use blstrs::Scalar; use super::MerkleNoteHash; -use std::fmt::{self, Debug}; +use std::fmt; /// Witness to a specific node in an authentication path. /// /// The Left/Right is the Hash of THIS node, but the MerkleHash at node.0 is /// the hash of the SIBLING node. #[derive(PartialEq, Eq, Debug, Clone)] -pub enum WitnessNode { +pub enum WitnessNode { Left(H), Right(H), } diff --git a/ironfish-zkp/src/circuits/util.rs b/ironfish-zkp/src/circuits/util.rs index 04f67d60a2..3e8ffe8e4e 100644 --- a/ironfish-zkp/src/circuits/util.rs +++ b/ironfish-zkp/src/circuits/util.rs @@ -56,7 +56,7 @@ pub fn expose_value_commitment( mut cs: CS, asset_generator: EdwardsPoint, value_commitment: Option, -) -> Result, SynthesisError> +) -> Result, SynthesisError> where CS: ConstraintSystem, { @@ -110,7 +110,7 @@ where Ok(value_bits) } -pub fn assert_valid_asset_generator>( +pub fn assert_valid_asset_generator>( mut cs: CS, asset_id: &[u8; ASSET_ID_LENGTH], asset_generator_repr: &[Boolean], diff --git a/ironfish-zkp/src/lib.rs b/ironfish-zkp/src/lib.rs index ffe7549b07..158ada162e 100644 --- a/ironfish-zkp/src/lib.rs +++ b/ironfish-zkp/src/lib.rs @@ -1,3 +1,5 @@ +#![warn(unused_qualifications)] + mod circuits; pub mod constants; pub mod hex; diff --git a/ironfish-zkp/src/primitives/value_commitment.rs b/ironfish-zkp/src/primitives/value_commitment.rs index dd48c406c3..43aad09e7a 100644 --- a/ironfish-zkp/src/primitives/value_commitment.rs +++ b/ironfish-zkp/src/primitives/value_commitment.rs @@ -2,7 +2,7 @@ use byteorder::{LittleEndian, ReadBytesExt}; use ff::Field; use group::{cofactor::CofactorGroup, GroupEncoding}; -use ironfish_jubjub::ExtendedPoint; +use ironfish_jubjub::{ExtendedPoint, Fr}; use rand::thread_rng; use crate::constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR; @@ -12,21 +12,21 @@ use crate::constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR; #[derive(Clone)] pub struct ValueCommitment { pub value: u64, - pub randomness: ironfish_jubjub::Fr, - pub asset_generator: ironfish_jubjub::ExtendedPoint, + pub randomness: Fr, + pub asset_generator: ExtendedPoint, } impl ValueCommitment { - pub fn new(value: u64, asset_generator: ironfish_jubjub::ExtendedPoint) -> Self { + pub fn new(value: u64, asset_generator: ExtendedPoint) -> Self { Self { value, - randomness: ironfish_jubjub::Fr::random(thread_rng()), + randomness: Fr::random(thread_rng()), asset_generator, } } pub fn commitment(&self) -> ironfish_jubjub::SubgroupPoint { - (self.asset_generator.clear_cofactor() * ironfish_jubjub::Fr::from(self.value)) + (self.asset_generator.clear_cofactor() * Fr::from(self.value)) + (*VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness) } @@ -47,7 +47,7 @@ impl ValueCommitment { let value = reader.read_u64::()?; let mut randomness_bytes = [0u8; 32]; reader.read_exact(&mut randomness_bytes)?; - let randomness = ironfish_jubjub::Fr::from_bytes(&randomness_bytes).unwrap(); + let randomness = Fr::from_bytes(&randomness_bytes).unwrap(); let mut asset_generator = [0u8; 32]; reader.read_exact(&mut asset_generator)?; let asset_generator = ExtendedPoint::from_bytes(&asset_generator).unwrap(); @@ -61,12 +61,12 @@ impl ValueCommitment { #[cfg(test)] mod test { + use crate::primitives::ValueCommitment; use ff::Field; use group::{Group, GroupEncoding}; + use ironfish_jubjub::{ExtendedPoint, Fr}; use rand::{rngs::StdRng, thread_rng, SeedableRng}; - use crate::primitives::ValueCommitment; - #[test] fn test_value_commitment() { // Seed a fixed rng for determinism in the test @@ -74,8 +74,8 @@ mod test { let value_commitment = ValueCommitment { value: 5, - randomness: ironfish_jubjub::Fr::random(&mut rng), - asset_generator: ironfish_jubjub::ExtendedPoint::random(&mut rng), + randomness: Fr::random(&mut rng), + asset_generator: ExtendedPoint::random(&mut rng), }; let commitment = value_commitment.commitment(); @@ -91,7 +91,7 @@ mod test { #[test] fn test_value_commitment_new() { - let generator = ironfish_jubjub::ExtendedPoint::random(thread_rng()); + let generator = ExtendedPoint::random(thread_rng()); let value = 5; let value_commitment = ValueCommitment::new(value, generator); @@ -105,9 +105,9 @@ mod test { // Seed a fixed rng for determinism in the test let mut rng = StdRng::seed_from_u64(0); - let randomness = ironfish_jubjub::Fr::random(&mut rng); + let randomness = Fr::random(&mut rng); - let asset_generator_one = ironfish_jubjub::ExtendedPoint::random(&mut rng); + let asset_generator_one = ExtendedPoint::random(&mut rng); let value_commitment_one = ValueCommitment { value: 5, @@ -117,7 +117,7 @@ mod test { let commitment_one = value_commitment_one.commitment(); - let asset_generator_two = ironfish_jubjub::ExtendedPoint::random(&mut rng); + let asset_generator_two = ExtendedPoint::random(&mut rng); let value_commitment_two = ValueCommitment { value: 5, @@ -145,9 +145,9 @@ mod test { // Seed a fixed rng for determinism in the test let mut rng = StdRng::seed_from_u64(0); - let randomness_one = ironfish_jubjub::Fr::random(&mut rng); + let randomness_one = Fr::random(&mut rng); - let asset_generator = ironfish_jubjub::ExtendedPoint::random(&mut rng); + let asset_generator = ExtendedPoint::random(&mut rng); let value_commitment_one = ValueCommitment { value: 5, @@ -157,7 +157,7 @@ mod test { let commitment_one = value_commitment_one.commitment(); - let randomness_two = ironfish_jubjub::Fr::random(&mut rng); + let randomness_two = Fr::random(&mut rng); let value_commitment_two = ValueCommitment { value: 5, @@ -184,8 +184,8 @@ mod test { let value_one = 5; - let randomness = ironfish_jubjub::Fr::random(&mut rng); - let asset_generator = ironfish_jubjub::ExtendedPoint::random(&mut rng); + let randomness = Fr::random(&mut rng); + let asset_generator = ExtendedPoint::random(&mut rng); let value_commitment_one = ValueCommitment { value: value_one, @@ -226,8 +226,8 @@ mod test { let value_commitment = ValueCommitment { value: 5, - randomness: ironfish_jubjub::Fr::random(&mut rng), - asset_generator: ironfish_jubjub::ExtendedPoint::random(&mut rng), + randomness: Fr::random(&mut rng), + asset_generator: ExtendedPoint::random(&mut rng), }; // Serialize to bytes From 2d5884a99dbff8da6a08e78bb205314d3903752a Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 4 Nov 2024 15:19:31 -0800 Subject: [PATCH 74/81] Enable the `unreachable_pub` lint for our Rust crates --- ironfish-rust-nodejs/src/lib.rs | 1 + ironfish-rust/src/lib.rs | 1 + ironfish-rust/src/mining/thread.rs | 18 +++++++++--------- .../src/transaction/value_balances.rs | 16 ++++++++++------ ironfish-zkp/src/circuits/mod.rs | 8 ++++---- ironfish-zkp/src/circuits/util.rs | 6 +++--- ironfish-zkp/src/lib.rs | 1 + 7 files changed, 29 insertions(+), 22 deletions(-) diff --git a/ironfish-rust-nodejs/src/lib.rs b/ironfish-rust-nodejs/src/lib.rs index b1efd4eb28..ff82a1cf71 100644 --- a/ironfish-rust-nodejs/src/lib.rs +++ b/ironfish-rust-nodejs/src/lib.rs @@ -2,6 +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/. */ +#![warn(unreachable_pub)] #![warn(unused_qualifications)] use std::fmt::Display; diff --git a/ironfish-rust/src/lib.rs b/ironfish-rust/src/lib.rs index 2e1c478c3c..3cb4f7040e 100644 --- a/ironfish-rust/src/lib.rs +++ b/ironfish-rust/src/lib.rs @@ -2,6 +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/. */ +#![warn(unreachable_pub)] #![warn(unused_qualifications)] #[cfg(feature = "transaction-proofs")] diff --git a/ironfish-rust/src/mining/thread.rs b/ironfish-rust/src/mining/thread.rs index ec217ee520..9099af34da 100644 --- a/ironfish-rust/src/mining/thread.rs +++ b/ironfish-rust/src/mining/thread.rs @@ -11,7 +11,7 @@ use super::mine; use fish_hash::Context; #[derive(Debug)] -pub(crate) enum Command { +pub(super) enum Command { NewWork( Vec, // header bytes Vec, // target @@ -23,17 +23,17 @@ pub(crate) enum Command { Pause, } -pub struct FishHashOptions { - pub enabled: bool, - pub full_context: bool, +pub(super) struct FishHashOptions { + pub(super) enabled: bool, + pub(super) full_context: bool, } -pub(crate) struct Thread { +pub(super) struct Thread { command_channel: Sender, } impl Thread { - pub(crate) fn new( + pub(super) fn new( id: u64, block_found_channel: Sender<(u64, u32)>, hash_rate_channel: Sender, @@ -76,7 +76,7 @@ impl Thread { } } - pub(crate) fn new_work( + pub(super) fn new_work( &self, header_bytes: Vec, target: Vec, @@ -93,11 +93,11 @@ impl Thread { )) } - pub(crate) fn pause(&self) -> Result<(), SendError> { + pub(super) fn pause(&self) -> Result<(), SendError> { self.command_channel.send(Command::Pause) } - pub(crate) fn stop(&self) -> Result<(), SendError> { + pub(super) fn stop(&self) -> Result<(), SendError> { self.command_channel.send(Command::Stop) } } diff --git a/ironfish-rust/src/transaction/value_balances.rs b/ironfish-rust/src/transaction/value_balances.rs index 035ef3dcea..6d73a0758e 100644 --- a/ironfish-rust/src/transaction/value_balances.rs +++ b/ironfish-rust/src/transaction/value_balances.rs @@ -8,12 +8,12 @@ use crate::{ errors::{IronfishError, IronfishErrorKind}, }; -pub struct ValueBalances { +pub(super) struct ValueBalances { values: HashMap, } impl ValueBalances { - pub fn new() -> Self { + pub(super) fn new() -> Self { let mut hash_map = HashMap::default(); hash_map.insert(NATIVE_ASSET, 0); @@ -21,7 +21,11 @@ impl ValueBalances { ValueBalances { values: hash_map } } - pub fn add(&mut self, asset_id: &AssetIdentifier, value: i64) -> Result<(), IronfishError> { + pub(super) fn add( + &mut self, + asset_id: &AssetIdentifier, + value: i64, + ) -> Result<(), IronfishError> { let current_value = self.values.entry(*asset_id).or_insert(0); let new_value = current_value .checked_add(value) @@ -32,7 +36,7 @@ impl ValueBalances { Ok(()) } - pub fn subtract( + pub(super) fn subtract( &mut self, asset_id: &AssetIdentifier, value: i64, @@ -47,11 +51,11 @@ impl ValueBalances { Ok(()) } - pub fn iter(&self) -> hash_map::Iter { + pub(super) fn iter(&self) -> hash_map::Iter { self.values.iter() } - pub fn fee(&self) -> &i64 { + pub(super) fn fee(&self) -> &i64 { self.values.get(&NATIVE_ASSET).unwrap() } } diff --git a/ironfish-zkp/src/circuits/mod.rs b/ironfish-zkp/src/circuits/mod.rs index 0ef6fe21c7..5c50c6e493 100644 --- a/ironfish-zkp/src/circuits/mod.rs +++ b/ironfish-zkp/src/circuits/mod.rs @@ -1,4 +1,4 @@ -pub mod mint_asset; -pub mod output; -pub mod spend; -pub mod util; +pub(crate) mod mint_asset; +pub(crate) mod output; +pub(crate) mod spend; +pub(crate) mod util; diff --git a/ironfish-zkp/src/circuits/util.rs b/ironfish-zkp/src/circuits/util.rs index 3e8ffe8e4e..488ad6a33f 100644 --- a/ironfish-zkp/src/circuits/util.rs +++ b/ironfish-zkp/src/circuits/util.rs @@ -17,7 +17,7 @@ use crate::{ primitives::ValueCommitment, }; -pub fn slice_into_boolean_vec_le>( +fn slice_into_boolean_vec_le>( mut cs: CS, value: Option<&[u8]>, byte_length: u32, @@ -52,7 +52,7 @@ pub fn slice_into_boolean_vec_le( +pub(crate) fn expose_value_commitment( mut cs: CS, asset_generator: EdwardsPoint, value_commitment: Option, @@ -110,7 +110,7 @@ where Ok(value_bits) } -pub fn assert_valid_asset_generator>( +pub(crate) fn assert_valid_asset_generator>( mut cs: CS, asset_id: &[u8; ASSET_ID_LENGTH], asset_generator_repr: &[Boolean], diff --git a/ironfish-zkp/src/lib.rs b/ironfish-zkp/src/lib.rs index 158ada162e..a9e1407ca9 100644 --- a/ironfish-zkp/src/lib.rs +++ b/ironfish-zkp/src/lib.rs @@ -1,3 +1,4 @@ +#![warn(unreachable_pub)] #![warn(unused_qualifications)] mod circuits; From 652eae5971b122da7729f71e67f6f60c0c51a52a Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 4 Nov 2024 15:24:19 -0800 Subject: [PATCH 75/81] Enable lints for unused code/dependencies in our Rust crates Also forbid the use of `println!`, `eprintln!`, `dbg!` and similar invocations, which should not be allowed in a library. --- ironfish-rust-nodejs/src/lib.rs | 5 +++++ ironfish-rust/src/lib.rs | 5 +++++ ironfish-rust/src/mining/thread.rs | 3 +-- ironfish-zkp/src/lib.rs | 5 +++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ironfish-rust-nodejs/src/lib.rs b/ironfish-rust-nodejs/src/lib.rs index ff82a1cf71..720fc0cd63 100644 --- a/ironfish-rust-nodejs/src/lib.rs +++ b/ironfish-rust-nodejs/src/lib.rs @@ -2,7 +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/. */ +#![warn(clippy::dbg_macro)] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] #![warn(unreachable_pub)] +#![warn(unused_crate_dependencies)] +#![warn(unused_macro_rules)] #![warn(unused_qualifications)] use std::fmt::Display; diff --git a/ironfish-rust/src/lib.rs b/ironfish-rust/src/lib.rs index 3cb4f7040e..778938325b 100644 --- a/ironfish-rust/src/lib.rs +++ b/ironfish-rust/src/lib.rs @@ -2,7 +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/. */ +#![warn(clippy::dbg_macro)] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] #![warn(unreachable_pub)] +#![warn(unused_crate_dependencies)] +#![warn(unused_macro_rules)] #![warn(unused_qualifications)] #[cfg(feature = "transaction-proofs")] diff --git a/ironfish-rust/src/mining/thread.rs b/ironfish-rust/src/mining/thread.rs index 9099af34da..45e2a783d4 100644 --- a/ironfish-rust/src/mining/thread.rs +++ b/ironfish-rust/src/mining/thread.rs @@ -192,8 +192,7 @@ fn process_commands( } if remaining_search_space < default_batch_size { - // miner has exhausted it's search space, stop mining - println!("Search space exhausted, no longer mining this block."); + // miner has exhausted its search space, stop mining break; } batch_start += batch_size + step_size as u64 - (batch_size % step_size as u64); diff --git a/ironfish-zkp/src/lib.rs b/ironfish-zkp/src/lib.rs index a9e1407ca9..2890e68c65 100644 --- a/ironfish-zkp/src/lib.rs +++ b/ironfish-zkp/src/lib.rs @@ -1,4 +1,9 @@ +#![warn(clippy::dbg_macro)] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] #![warn(unreachable_pub)] +#![warn(unused_crate_dependencies)] +#![warn(unused_macro_rules)] #![warn(unused_qualifications)] mod circuits; From 72683db4f32d50c72aa8f7afb740a404fb7c7abe Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 4 Nov 2024 17:26:57 -0800 Subject: [PATCH 76/81] Move the signal handler from `ironfish-rust` to `ironfish-rust-nodejs` This removes the `libc` dependency from `ironfish-rust`, which is not supported in generic WASM environments. In addition to that: * Removed the `triggerSegfault` function, which does not appear to be used anywhere. * Replaced the call to `libc::exit()` with `std::process::abort()`. The two are not exactly equivalent, but the latter is better suited for the task. If we want equivalence, we can use `std::process::exit()` instead. * Corrected the definition of `display_trace`: the argument to signal is `void (*sighandler_t)(int)`, but our `display_trace` was not accepting an int argument. This could lead to stack corruption on some platforms (on x86-64, the first argument is passed on a register, so no stack corruption can occur there, but other platforms may be affected). * Changed the `STACK_TRACE` variable from being a `static mut` to a stack-local variable. This simplifies the implementation and also avoids potential issues with multi-threading. * Protected the `backtrace_symbols_fd` symbol from being defined on platforms where it's not supported. --- Cargo.lock | 2 +- ironfish-rust-nodejs/Cargo.toml | 5 ++- ironfish-rust-nodejs/src/signal_catcher.rs | 38 +++++++++++----- ironfish-rust/Cargo.toml | 1 - ironfish-rust/src/lib.rs | 1 - ironfish-rust/src/signal_catcher.rs | 50 ---------------------- 6 files changed, 31 insertions(+), 66 deletions(-) delete mode 100644 ironfish-rust/src/signal_catcher.rs diff --git a/Cargo.lock b/Cargo.lock index 4bf0da2e0d..e80aef1b43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1468,7 +1468,6 @@ dependencies = [ "ironfish-jubjub", "ironfish_zkp", "lazy_static", - "libc", "rand", "reqwest", "sha2 0.10.6", @@ -1620,6 +1619,7 @@ dependencies = [ "ironfish", "ironfish-frost", "ironfish-jubjub", + "libc", "napi", "napi-build", "napi-derive", diff --git a/ironfish-rust-nodejs/Cargo.toml b/ironfish-rust-nodejs/Cargo.toml index ed7897bb93..c6784e6fdf 100644 --- a/ironfish-rust-nodejs/Cargo.toml +++ b/ironfish-rust-nodejs/Cargo.toml @@ -31,11 +31,12 @@ base64 = "0.13.0" fish_hash = "0.3.0" ironfish = { path = "../ironfish-rust" } ironfish-frost = { version = "0.1.0" } +ironfish-jubjub = { version = "0.1.0", features = ["multiply-many"] } +libc = "0.2.150" napi = { version = "2.14.4", features = ["napi6"] } napi-derive = "2.14.6" -ironfish-jubjub = { version = "0.1.0", features = ["multiply-many"] } -rand = "0.8.5" num_cpus = "1.16.0" +rand = "0.8.5" signal-hook = { version = "0.3.17", optional = true, default-features = false, features = ["iterator"] } [build-dependencies] diff --git a/ironfish-rust-nodejs/src/signal_catcher.rs b/ironfish-rust-nodejs/src/signal_catcher.rs index 8c80029d4f..4889e771b7 100644 --- a/ironfish-rust-nodejs/src/signal_catcher.rs +++ b/ironfish-rust-nodejs/src/signal_catcher.rs @@ -2,19 +2,35 @@ * 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/. */ -use ironfish::signal_catcher::{init_signal_handler, trigger_segfault}; use napi_derive::napi; -/// # Safety -/// This is unsafe, it calls libc functions -#[napi(js_name = "initSignalHandler")] -pub unsafe fn native_init_signal_handler() { - init_signal_handler() +extern "C" { + #[cfg(all(unix, not(target_env = "musl"), not(target_os = "android")))] + fn backtrace_symbols_fd(buffer: *const *mut libc::c_void, size: libc::c_int, fd: libc::c_int); } -/// # Safety -/// This is unsafe, it intentionally crashes -#[napi(js_name = "triggerSegfault")] -pub unsafe fn native_trigger_segfault() { - trigger_segfault() +fn display_trace(_signal: libc::c_int) { + #[cfg(all(unix, not(target_env = "musl"), not(target_os = "android")))] + unsafe { + const MAX_FRAMES: usize = 256; + let mut stack_trace = [std::ptr::null_mut(); MAX_FRAMES]; + let depth = libc::backtrace(stack_trace.as_mut_ptr(), MAX_FRAMES as i32); + backtrace_symbols_fd(stack_trace.as_ptr(), depth, libc::STDERR_FILENO); + } + + std::process::abort(); +} + +#[napi(js_name = "initSignalHandler")] +pub fn init_signal_handler() { + #[cfg(unix)] + unsafe { + libc::signal(libc::SIGSEGV, display_trace as usize); + // Rust may throw one of these in place of a SIGSEGV when the platform does not have a + // native implementation for `abort()`, or sometimes when encountering internal errors, + // see: https://doc.rust-lang.org/std/intrinsics/fn.abort.html + libc::signal(libc::SIGBUS, display_trace as usize); + libc::signal(libc::SIGILL, display_trace as usize); + libc::signal(libc::SIGTRAP, display_trace as usize); + } } diff --git a/ironfish-rust/Cargo.toml b/ironfish-rust/Cargo.toml index f06f63c5ac..dea688afc2 100644 --- a/ironfish-rust/Cargo.toml +++ b/ironfish-rust/Cargo.toml @@ -51,7 +51,6 @@ fish_hash = "0.3.0" ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" } ironfish-jubjub = { version = "0.1.0", features = ["multiply-many"] } lazy_static = "1.4.0" -libc = "0.2.126" # sub-dependency that needs a pinned version until a new release of cpufeatures: https://github.com/RustCrypto/utils/pull/789 rand = "0.8.5" tiny-bip39 = "1.0" xxhash-rust = { version = "0.8.5", features = ["xxh3"] } diff --git a/ironfish-rust/src/lib.rs b/ironfish-rust/src/lib.rs index 778938325b..57ab7f54ed 100644 --- a/ironfish-rust/src/lib.rs +++ b/ironfish-rust/src/lib.rs @@ -24,7 +24,6 @@ pub mod nacl; pub mod note; pub mod rolling_filter; pub mod serializing; -pub mod signal_catcher; pub mod transaction; pub mod util; pub mod witness; diff --git a/ironfish-rust/src/signal_catcher.rs b/ironfish-rust/src/signal_catcher.rs deleted file mode 100644 index fb5613f444..0000000000 --- a/ironfish-rust/src/signal_catcher.rs +++ /dev/null @@ -1,50 +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/. */ - -extern "C" { - // This is present in libc on unix, but not on linux - fn backtrace_symbols_fd(buffer: *const *mut libc::c_void, size: libc::c_int, fd: libc::c_int); -} - -/// # Safety -/// This is unsafe, it calls libc functions -#[cfg(any(all(unix, target_env = "musl"), target_os = "android"))] -unsafe fn display_trace() { - libc::exit(libc::EXIT_FAILURE); -} - -/// # Safety -/// This is unsafe, it calls libc functions -#[cfg(all(unix, not(target_env = "musl"), not(target_os = "android")))] -unsafe fn display_trace() { - const MAX_FRAMES: usize = 256; - static mut STACK_TRACE: [*mut libc::c_void; MAX_FRAMES] = [std::ptr::null_mut(); MAX_FRAMES]; - let depth = libc::backtrace(STACK_TRACE.as_mut_ptr(), MAX_FRAMES as i32); - backtrace_symbols_fd(STACK_TRACE.as_ptr(), depth, 2); - libc::exit(libc::EXIT_FAILURE); -} - -/// # Safety -/// This is unsafe, it calls libc functions -#[cfg(unix)] -pub unsafe fn init_signal_handler() { - libc::signal(libc::SIGSEGV, display_trace as usize); - // Rust in release mode will throw one of these in place of a SIGSEGV, not - // sure why it differs based on system - libc::signal(libc::SIGTRAP, display_trace as usize); - libc::signal(libc::SIGILL, display_trace as usize); -} - -/// # Safety -/// This is unsafe, it calls libc functions -#[cfg(not(unix))] -pub unsafe fn init_signal_handler() { - return; -} - -/// # Safety -/// This is unsafe, it intentionally crashes -pub unsafe fn trigger_segfault() { - std::ptr::null_mut::().write(42); -} From 4707d40536b9b0325aba9e31f223c6cf57210c23 Mon Sep 17 00:00:00 2001 From: andrea Date: Wed, 6 Nov 2024 09:15:57 -0800 Subject: [PATCH 77/81] Make it easier to construct an `IronfishError` from an `IronfishErrorKind` --- ironfish-rust/src/errors.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ironfish-rust/src/errors.rs b/ironfish-rust/src/errors.rs index 5d8c5cc8f9..65baa72bfa 100644 --- a/ironfish-rust/src/errors.rs +++ b/ironfish-rust/src/errors.rs @@ -111,6 +111,12 @@ impl fmt::Display for IronfishError { } } +impl From for IronfishError { + fn from(kind: IronfishErrorKind) -> Self { + Self::new(kind) + } +} + impl From for IronfishError { fn from(e: io::Error) -> IronfishError { IronfishError::new_with_source(IronfishErrorKind::Io, e) From f5271e3ac9dffee99684c0c4ab747ae516231cfb Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Wed, 6 Nov 2024 12:45:24 -0500 Subject: [PATCH 78/81] Pass in account head to wallet/createAccount (#5619) --- ironfish/src/rpc/routes/wallet/create.test.ts | 2 +- .../rpc/routes/wallet/createAccount.test.ts | 39 ++++++++- .../src/rpc/routes/wallet/createAccount.ts | 27 +++++-- .../src/testUtilities/fixtures/account.ts | 4 +- ironfish/src/wallet/wallet.ts | 81 +++++++++++++------ 5 files changed, 117 insertions(+), 36 deletions(-) diff --git a/ironfish/src/rpc/routes/wallet/create.test.ts b/ironfish/src/rpc/routes/wallet/create.test.ts index aaaadc2c04..749240dee3 100644 --- a/ironfish/src/rpc/routes/wallet/create.test.ts +++ b/ironfish/src/rpc/routes/wallet/create.test.ts @@ -21,7 +21,7 @@ describe('Route wallet/create', () => { it('should create an account', async () => { await routeTest.node.wallet.createAccount('existingAccount', { setDefault: true }) const createdAtHead = { - hash: routeTest.node.chain.head.hash, + hash: Buffer.alloc(32, 0), sequence: routeTest.node.chain.head.sequence, } diff --git a/ironfish/src/rpc/routes/wallet/createAccount.test.ts b/ironfish/src/rpc/routes/wallet/createAccount.test.ts index ad41a417f6..a05c279b7b 100644 --- a/ironfish/src/rpc/routes/wallet/createAccount.test.ts +++ b/ironfish/src/rpc/routes/wallet/createAccount.test.ts @@ -5,6 +5,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { blake3 } from '@napi-rs/blake-hash' import { v4 as uuid } from 'uuid' import { createRouteTest } from '../../../testUtilities/routeTest' import { RPC_ERROR_CODES } from '../../adapters' @@ -20,7 +21,7 @@ describe('Route wallet/createAccount', () => { it('should create an account', async () => { await routeTest.node.wallet.createAccount('existingAccount', { setDefault: true }) const createdAtHead = { - hash: routeTest.node.chain.head.hash, + hash: Buffer.alloc(32, 0), sequence: routeTest.node.chain.head.sequence, } @@ -118,6 +119,9 @@ describe('Route wallet/createAccount', () => { sequence: 10, }, }) + + const head = await account?.getHead() + expect(head).toBeNull() }) it('should set account createdAt to null', async () => { @@ -142,4 +146,37 @@ describe('Route wallet/createAccount', () => { createdAt: null, }) }) + + it('should set account head if passed', async () => { + const name = uuid() + + const response = await routeTest.client.wallet.createAccount({ + name, + createdAt: null, + head: { + hash: blake3('test').toString('hex'), + sequence: 10, + }, + }) + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + publicAddress: expect.any(String), + isDefaultAccount: true, + }) + + const account = routeTest.node.wallet.getAccountByName(name) + expect(account).toMatchObject({ + name: name, + publicAddress: response.content.publicAddress, + createdAt: null, + }) + + const head = await account?.getHead() + expect(head).toMatchObject({ + hash: blake3('test'), + sequence: 10, + }) + }) }) diff --git a/ironfish/src/rpc/routes/wallet/createAccount.ts b/ironfish/src/rpc/routes/wallet/createAccount.ts index 85b4a565b4..13aa69331d 100644 --- a/ironfish/src/rpc/routes/wallet/createAccount.ts +++ b/ironfish/src/rpc/routes/wallet/createAccount.ts @@ -22,6 +22,10 @@ export type CreateAccountRequest = { name: string default?: boolean createdAt?: number | null + head?: { + hash: string + sequence: number + } } export type CreateAccountResponse = { @@ -35,6 +39,13 @@ export const CreateAccountRequestSchema: yup.ObjectSchema name: yup.string().defined(), default: yup.boolean().optional(), createdAt: yup.number().optional().nullable(), + head: yup + .object({ + hash: yup.string().defined(), + sequence: yup.number().defined(), + }) + .optional() + .default(undefined), }) .defined() @@ -52,17 +63,17 @@ routes.register( async (request, context): Promise => { AssertHasRpcContext(request, context, 'wallet') - const createdAt = - typeof request.data.createdAt === 'number' - ? { - hash: Buffer.alloc(32, 0), - sequence: request.data.createdAt, - } - : request.data.createdAt + const head = request.data.head && { + hash: Buffer.from(request.data.head.hash, 'hex'), + sequence: request.data.head.sequence, + } let account try { - account = await context.wallet.createAccount(request.data.name, { createdAt }) + account = await context.wallet.createAccount(request.data.name, { + createdAt: request.data.createdAt, + head, + }) } catch (e) { if (e instanceof DuplicateAccountNameError) { throw new RpcValidationError(e.message, 400, RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME) diff --git a/ironfish/src/testUtilities/fixtures/account.ts b/ironfish/src/testUtilities/fixtures/account.ts index c99de7a6f3..b626f59601 100644 --- a/ironfish/src/testUtilities/fixtures/account.ts +++ b/ironfish/src/testUtilities/fixtures/account.ts @@ -11,7 +11,7 @@ import { FixtureGenerate, useFixture } from './fixture' export function useAccountFixture( wallet: Wallet, generate: FixtureGenerate | string = 'test', - options?: { createdAt?: HeadValue | null; setDefault?: boolean }, + options?: Parameters[1], ): Promise { if (typeof generate === 'string') { const name = generate @@ -61,7 +61,7 @@ export async function useAccountAndAddFundsFixture( wallet: Wallet, chain: Blockchain, generate: FixtureGenerate | string = 'test', - options?: { createdAt?: HeadValue | null; setDefault?: boolean }, + options?: Parameters[1], ): Promise { const account = await useAccountFixture(wallet, generate, options) const block = await useMinerBlockFixture(chain, undefined, account) diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index b37c278120..001f5590f5 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -1355,7 +1355,7 @@ export class Wallet { async createAccount( name: string, - options: { createdAt?: HeadValue | null; setDefault?: boolean } = { + options: { setDefault?: boolean; createdAt?: number | null; head?: HeadValue | null } = { setDefault: false, }, ): Promise { @@ -1369,15 +1369,12 @@ export class Wallet { const key = generateKey() - let createdAt: HeadValue | null = null - if (options.createdAt !== undefined) { - createdAt = options.createdAt - } else if (this.nodeClient) { - try { - createdAt = await this.getChainHead() - } catch { - this.logger.warn('Failed to fetch chain head from node client') - } + const createdAt = await this.createdAtWithDefault(options.createdAt) + let accountHead: HeadValue | null + if (options.head === undefined) { + accountHead = createdAt && (await this.accountHeadAtSequence(createdAt.sequence)) + } else { + accountHead = options.head } const account = new Account({ @@ -1413,7 +1410,7 @@ export class Wallet { await this.walletDb.setAccount(account, tx) } - await account.updateHead(createdAt, tx) + await account.updateHead(accountHead, tx) }) this.accountById.set(account.id, account) @@ -1425,6 +1422,52 @@ export class Wallet { return account } + /* + * Use createdAt if provided, otherwise use the current chain head + */ + private async createdAtWithDefault(createdAt?: number | null): Promise { + if (createdAt === null) { + return null + } + + if (createdAt === undefined) { + try { + const sequence = (await this.getChainHead()).sequence + return { + sequence, + hash: Buffer.alloc(32, 0), + } + } catch { + this.logger.warn('Failed to fetch chain head from node client') + return null + } + } + + return { + sequence: createdAt, + hash: Buffer.alloc(32, 0), + } + } + + /* + * Try to get the block hash from the chain with createdAt sequence + * Otherwise, return null + */ + private async accountHeadAtSequence(sequence: number): Promise { + try { + const previousBlock = await this.chainGetBlock({ sequence }) + return previousBlock + ? { + hash: Buffer.from(previousBlock.block.hash, 'hex'), + sequence: previousBlock.block.sequence, + } + : null + } catch { + this.logger.warn(`Failed to fetch block ${sequence} from node client`) + return null + } + } + async skipRescan(account: Account, tx?: IDatabaseTransaction): Promise { const { hash, sequence } = await this.getChainHead() await account.updateHead({ hash, sequence }, tx) @@ -1544,20 +1587,10 @@ export class Wallet { } } - if (createdAt !== null) { - const previousBlock = await this.chainGetBlock({ sequence: createdAt.sequence - 1 }) + const accountHead = + createdAt && (await this.accountHeadAtSequence(createdAt.sequence - 1)) - const head = previousBlock - ? { - hash: Buffer.from(previousBlock.block.hash, 'hex'), - sequence: previousBlock.block.sequence, - } - : null - - await account.updateHead(head, tx) - } else { - await account.updateHead(null, tx) - } + await account.updateHead(accountHead, tx) }) this.accountById.set(account.id, account) From d04e25a73fded37b9c5e57d065043f08ff938b77 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:00:44 -0700 Subject: [PATCH 79/81] Add lint for rust binding changes (#5626) * Add lint for rust binding changes * check in rust binding changes --- .github/workflows/ci.yml | 10 ++++++++++ ironfish-rust-nodejs/index.d.ts | 9 --------- ironfish-rust-nodejs/index.js | 3 +-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69c8ef0c10..dd24ed63fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,16 @@ jobs: - name: Lint run: yarn lint + - name: Build Rust bindings + run: yarn build + + - name: Check for Rust binding changes + run: | + if [[ $(git status | grep index.) ]]; then + echo "Rust bindings have outstanding updates, please run 'yarn build' and check them in." + exit 1 + fi + test: name: Test runs-on: ubuntu-latest diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index a53f89fa9e..0d8d8d5bd9 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -29,16 +29,7 @@ export interface BoxedMessage { } export declare function boxMessage(plaintext: string, senderSecretKey: Uint8Array, recipientPublicKey: string): BoxedMessage export declare function unboxMessage(boxedMessage: string, nonce: string, senderPublicKey: string, recipientSecretKey: Uint8Array): string -/** - * # Safety - * This is unsafe, it calls libc functions - */ export declare function initSignalHandler(): void -/** - * # Safety - * This is unsafe, it intentionally crashes - */ -export declare function triggerSegfault(): void export const ASSET_ID_LENGTH: number export const ASSET_METADATA_LENGTH: number export const ASSET_NAME_LENGTH: number diff --git a/ironfish-rust-nodejs/index.js b/ironfish-rust-nodejs/index.js index e4d1006a92..648b4ab451 100644 --- a/ironfish-rust-nodejs/index.js +++ b/ironfish-rust-nodejs/index.js @@ -252,7 +252,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { FishHashContext, deserializePublicPackage, deserializeRound2CombinedPublicPackage, KEY_LENGTH, NONCE_LENGTH, BoxKeyPair, randomBytes, boxMessage, unboxMessage, RollingFilter, initSignalHandler, triggerSegfault, ASSET_ID_LENGTH, ASSET_METADATA_LENGTH, ASSET_NAME_LENGTH, ASSET_LENGTH, Asset, NOTE_ENCRYPTION_KEY_LENGTH, MAC_LENGTH, ENCRYPTED_NOTE_PLAINTEXT_LENGTH, ENCRYPTED_NOTE_LENGTH, NoteEncrypted, PUBLIC_ADDRESS_LENGTH, RANDOMNESS_LENGTH, MEMO_LENGTH, AMOUNT_VALUE_LENGTH, DECRYPTED_NOTE_LENGTH, Note, PROOF_LENGTH, TRANSACTION_SIGNATURE_LENGTH, TRANSACTION_PUBLIC_KEY_RANDOMNESS_LENGTH, TRANSACTION_EXPIRATION_LENGTH, TRANSACTION_FEE_LENGTH, LATEST_TRANSACTION_VERSION, TransactionPosted, Transaction, verifyTransactions, UnsignedTransaction, LanguageCode, generateKey, spendingKeyToWords, wordsToSpendingKey, generatePublicAddressFromIncomingViewKey, generateKeyFromPrivateKey, initializeSapling, FoundBlockResult, ThreadPoolHandler, isValidPublicAddress, CpuCount, getCpuCount, generateRandomizedPublicKey, multisig, xchacha20poly1305 } = nativeBinding +const { FishHashContext, deserializePublicPackage, deserializeRound2CombinedPublicPackage, KEY_LENGTH, NONCE_LENGTH, BoxKeyPair, randomBytes, boxMessage, unboxMessage, RollingFilter, initSignalHandler, ASSET_ID_LENGTH, ASSET_METADATA_LENGTH, ASSET_NAME_LENGTH, ASSET_LENGTH, Asset, NOTE_ENCRYPTION_KEY_LENGTH, MAC_LENGTH, ENCRYPTED_NOTE_PLAINTEXT_LENGTH, ENCRYPTED_NOTE_LENGTH, NoteEncrypted, PUBLIC_ADDRESS_LENGTH, RANDOMNESS_LENGTH, MEMO_LENGTH, AMOUNT_VALUE_LENGTH, DECRYPTED_NOTE_LENGTH, Note, PROOF_LENGTH, TRANSACTION_SIGNATURE_LENGTH, TRANSACTION_PUBLIC_KEY_RANDOMNESS_LENGTH, TRANSACTION_EXPIRATION_LENGTH, TRANSACTION_FEE_LENGTH, LATEST_TRANSACTION_VERSION, TransactionPosted, Transaction, verifyTransactions, UnsignedTransaction, LanguageCode, generateKey, spendingKeyToWords, wordsToSpendingKey, generatePublicAddressFromIncomingViewKey, generateKeyFromPrivateKey, initializeSapling, FoundBlockResult, ThreadPoolHandler, isValidPublicAddress, CpuCount, getCpuCount, generateRandomizedPublicKey, multisig, xchacha20poly1305 } = nativeBinding module.exports.FishHashContext = FishHashContext module.exports.deserializePublicPackage = deserializePublicPackage @@ -265,7 +265,6 @@ module.exports.boxMessage = boxMessage module.exports.unboxMessage = unboxMessage module.exports.RollingFilter = RollingFilter module.exports.initSignalHandler = initSignalHandler -module.exports.triggerSegfault = triggerSegfault module.exports.ASSET_ID_LENGTH = ASSET_ID_LENGTH module.exports.ASSET_METADATA_LENGTH = ASSET_METADATA_LENGTH module.exports.ASSET_NAME_LENGTH = ASSET_NAME_LENGTH From 42ff1c844e80e77e4f3843334a09247e58dc49a9 Mon Sep 17 00:00:00 2001 From: andrea Date: Wed, 6 Nov 2024 09:25:34 -0800 Subject: [PATCH 80/81] Implement the `Debug` trait for `Transaction` --- ironfish-rust/src/transaction/burns.rs | 2 +- ironfish-rust/src/transaction/mints.rs | 2 +- ironfish-rust/src/transaction/mod.rs | 2 +- ironfish-rust/src/transaction/outputs.rs | 2 +- ironfish-rust/src/transaction/spends.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ironfish-rust/src/transaction/burns.rs b/ironfish-rust/src/transaction/burns.rs index 366394a262..5595347761 100644 --- a/ironfish-rust/src/transaction/burns.rs +++ b/ironfish-rust/src/transaction/burns.rs @@ -32,7 +32,7 @@ impl BurnBuilder { /// This description represents an action to decrease the supply of an existing /// asset on Iron Fish -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct BurnDescription { /// Identifier for the Asset which is being burned pub asset_id: AssetIdentifier, diff --git a/ironfish-rust/src/transaction/mints.rs b/ironfish-rust/src/transaction/mints.rs index 96a0ea9250..7fd1503622 100644 --- a/ironfish-rust/src/transaction/mints.rs +++ b/ironfish-rust/src/transaction/mints.rs @@ -185,7 +185,7 @@ impl UnsignedMintDescription { /// This description represents an action to increase the supply of an existing /// asset on Iron Fish -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MintDescription { /// Proof that the mint was valid for the provided creator and asset pub proof: groth16::Proof, diff --git a/ironfish-rust/src/transaction/mod.rs b/ironfish-rust/src/transaction/mod.rs index e4dc239f86..7ef627e4a7 100644 --- a/ironfish-rust/src/transaction/mod.rs +++ b/ironfish-rust/src/transaction/mod.rs @@ -63,7 +63,7 @@ pub const TRANSACTION_FEE_SIZE: usize = 8; /// any of the working data or private keys used in creating the proofs. /// /// This is the serializable form of a transaction. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Transaction { /// The transaction serialization version. This can be incremented when /// changes need to be made to the transaction format diff --git a/ironfish-rust/src/transaction/outputs.rs b/ironfish-rust/src/transaction/outputs.rs index ddd3cfc53f..008c8a9cb3 100644 --- a/ironfish-rust/src/transaction/outputs.rs +++ b/ironfish-rust/src/transaction/outputs.rs @@ -131,7 +131,7 @@ impl OutputBuilder { /// /// This is the variation of an Output that gets serialized to bytes and can /// be loaded from bytes. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct OutputDescription { /// Proof that the output circuit was valid and successful pub(crate) proof: groth16::Proof, diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index b56412704c..e0741e1b19 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -225,7 +225,7 @@ impl UnsignedSpendDescription { /// The publicly visible value of a spent note. These get serialized to prove /// that the owner once had access to these values. It also publishes the /// nullifier so that they can't pretend they still have access to them. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct SpendDescription { /// Proof that the spend was valid and successful for the provided owner /// and note. From 0f680509cd0818e2aec1be6860472aefebbc6f5a Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Thu, 7 Nov 2024 12:12:11 -0500 Subject: [PATCH 81/81] Version bump to 2.9.0 (#5613) --- ironfish-cli/package.json | 6 +++--- ironfish-rust-nodejs/npm/darwin-arm64/package.json | 2 +- ironfish-rust-nodejs/npm/darwin-x64/package.json | 2 +- ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json | 2 +- ironfish-rust-nodejs/npm/linux-arm64-musl/package.json | 2 +- ironfish-rust-nodejs/npm/linux-x64-gnu/package.json | 2 +- ironfish-rust-nodejs/npm/linux-x64-musl/package.json | 2 +- ironfish-rust-nodejs/npm/win32-x64-msvc/package.json | 2 +- ironfish-rust-nodejs/package.json | 2 +- ironfish/package.json | 4 ++-- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index 7b66d1b06e..5c2198ac39 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -1,6 +1,6 @@ { "name": "ironfish", - "version": "2.8.1", + "version": "2.9.0", "description": "CLI for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -62,8 +62,8 @@ }, "dependencies": { "@ironfish/multisig-broker": "0.3.0", - "@ironfish/rust-nodejs": "2.7.0", - "@ironfish/sdk": "2.8.1", + "@ironfish/rust-nodejs": "2.8.0", + "@ironfish/sdk": "2.9.0", "@ledgerhq/errors": "6.19.1", "@ledgerhq/hw-transport-node-hid": "6.29.5", "@oclif/core": "4.0.11", diff --git a/ironfish-rust-nodejs/npm/darwin-arm64/package.json b/ironfish-rust-nodejs/npm/darwin-arm64/package.json index 0c09d5c874..5d99096c7b 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.8.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..24ff1dd74d 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.8.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..ec73406c28 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.8.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..b9f8fabc34 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.8.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..e20a64a84e 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.8.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..229c711a77 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.8.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..258a070237 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.8.0", "os": [ "win32" ], diff --git a/ironfish-rust-nodejs/package.json b/ironfish-rust-nodejs/package.json index 11b3fa9a29..9ab2d913c5 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.8.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/package.json b/ironfish/package.json index cc4894ecf8..5bcbba5d7b 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/sdk", - "version": "2.8.1", + "version": "2.9.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.8.0", "@napi-rs/blake-hash": "1.3.3", "axios": "1.7.7", "bech32": "2.0.0",