Skip to content

Commit

Permalink
adds default multisig broker server (#5520)
Browse files Browse the repository at this point in the history
* adds default multisig broker server

changes '--server' to boolean flag in 'wallet:multisig:sign' and
'wallet:multisig:dkg:create'

adds '--hostname' and '--port' flags in place of previous '--server' flag to
supply server hostname and port

defaults 'hostname' to 'multisig.ironfish.network' and 'port' to 9035

adds support for connection strings with '--connection' flag

adds util function to parse all connection options from flags

prints connection string to console after starting session

* allows sessionId and passphrase flags without server flag

if a users sets the sessionId or passphrase flag in 'multisig:sign' or
'dkg:create', the commands will now interpret these flags as intent to use a
broker server
  • Loading branch information
hughy authored Oct 9, 2024
1 parent 2176e9c commit 04d5cdb
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 61 deletions.
47 changes: 28 additions & 19 deletions ironfish-cli/src/commands/wallet/multisig/dkg/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,26 @@ export class DkgCreateCommand extends IronfishCommand {
description:
"Block sequence to begin scanning from for the created account. Uses node's chain head by default",
}),
server: Flags.string({
description: "multisig server to connect to. formatted as '<host>:<port>'",
server: Flags.boolean({
description: 'connect to a multisig broker server',
}),
connection: Flags.string({
char: 'c',
description: 'connection string for a multisig server session',
}),
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',
dependsOn: ['server'],
}),
passphrase: Flags.string({
description: 'Passphrase to join the multisig server session',
dependsOn: ['server'],
}),
tls: Flags.boolean({
description: 'connect to the multisig server over TLS',
Expand Down Expand Up @@ -90,21 +100,18 @@ export class DkgCreateCommand extends IronfishCommand {
}

let multisigClient: MultisigClient | null = null
if (flags.server) {
let sessionId = flags.sessionId
if (!sessionId && !flags.totalParticipants && !flags.minSigners) {
sessionId = await ui.inputPrompt(
'Enter the ID of a multisig session to join, or press enter to start a new session',
false,
)
}

let passphrase = flags.passphrase
if (!passphrase) {
passphrase = await ui.inputPrompt('Enter the passphrase for the multisig session', true)
}

multisigClient = await MultisigBrokerUtils.createClient(flags.server, {
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,
})

multisigClient = MultisigBrokerUtils.createClient(hostname, port, {
passphrase,
tls: flags.tls ?? true,
logger: this.logger,
Expand Down Expand Up @@ -380,6 +387,8 @@ export class DkgCreateCommand extends IronfishCommand {
multisigClient.startDkgSession(totalParticipants, minSigners)
this.log('\nStarted new DKG session:')
this.log(`${multisigClient.sessionId}`)
this.log('\nDKG session connection string:')
this.log(`${multisigClient.connectionString}`)
}

return { totalParticipants, minSigners }
Expand Down
45 changes: 27 additions & 18 deletions ironfish-cli/src/commands/wallet/multisig/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,26 @@ export class SignMultisigTransactionCommand extends IronfishCommand {
default: false,
description: 'Perform operation with a ledger device',
}),
server: Flags.string({
description: "multisig server to connect to. formatted as '<host>:<port>'",
server: Flags.boolean({
description: 'connect to a multisig broker server',
}),
connection: Flags.string({
char: 'c',
description: 'connection string for a multisig server session',
}),
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',
dependsOn: ['server'],
}),
passphrase: Flags.string({
description: 'Passphrase to join the multisig server session',
dependsOn: ['server'],
}),
tls: Flags.boolean({
description: 'connect to the multisig server over TLS',
Expand Down Expand Up @@ -114,21 +124,18 @@ export class SignMultisigTransactionCommand extends IronfishCommand {
}

let multisigClient: MultisigClient | null = null
if (flags.server) {
let sessionId = flags.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,
)
}

let passphrase = flags.passphrase
if (!passphrase) {
passphrase = await ui.inputPrompt('Enter the passphrase for the multisig session', true)
}
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,
})

multisigClient = await MultisigBrokerUtils.createClient(flags.server, {
multisigClient = MultisigBrokerUtils.createClient(hostname, port, {
passphrase,
tls: flags.tls ?? true,
logger: this.logger,
Expand Down Expand Up @@ -267,6 +274,8 @@ export class SignMultisigTransactionCommand extends IronfishCommand {
multisigClient.startSigningSession(totalParticipants, unsignedTransactionInput)
this.log('\nStarted new signing session:')
this.log(`${multisigClient.sessionId}`)
this.log('\nSigning session connection string:')
this.log(`${multisigClient.connectionString}`)
}

return { unsignedTransaction, totalParticipants }
Expand Down
10 changes: 9 additions & 1 deletion ironfish-cli/src/multisigBroker/clients/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const RETRY_INTERVAL = 5000
export abstract class MultisigClient {
readonly logger: Logger
readonly version: number
readonly hostname: string
readonly port: number

private started: boolean
private isClosing = false
Expand All @@ -65,9 +67,11 @@ export abstract class MultisigClient {

retries: Map<number, NodeJS.Timer> = new Map()

constructor(options: { passphrase: string; logger: Logger }) {
constructor(options: { hostname: string; port: number; passphrase: string; logger: Logger }) {
this.logger = options.logger
this.version = 3
this.hostname = options.hostname
this.port = options.port

this.started = false
this.nextMessageId = 0
Expand All @@ -78,6 +82,10 @@ export abstract class MultisigClient {
this.passphrase = options.passphrase
}

get connectionString(): string {
return `tcp://${this.sessionId}:${this.passphrase}@${this.hostname}:${this.port}`
}

get key(): xchacha20poly1305.XChaCha20Poly1305Key {
if (!this.sessionId) {
throw new Error('Client must join a session before encrypting/decrypting messages')
Expand Down
16 changes: 8 additions & 8 deletions ironfish-cli/src/multisigBroker/clients/tcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import net from 'net'
import { MultisigClient } from './client'

export class MultisigTcpClient extends MultisigClient {
readonly host: string
readonly port: number

client: net.Socket | null = null

constructor(options: { host: string; port: number; passphrase: string; logger: Logger }) {
super({ passphrase: options.passphrase, logger: options.logger })
this.host = options.host
this.port = options.port
constructor(options: { hostname: string; port: number; passphrase: string; logger: Logger }) {
super({
hostname: options.hostname,
port: options.port,
passphrase: options.passphrase,
logger: options.logger,
})
}

protected onSocketDisconnect = (): void => {
Expand Down Expand Up @@ -50,7 +50,7 @@ export class MultisigTcpClient extends MultisigClient {
client.on('error', onError)
client.on('connect', onConnect)
client.on('data', this.onSocketData)
client.connect({ host: this.host, port: this.port })
client.connect({ host: this.hostname, port: this.port })
this.client = client
})
}
Expand Down
2 changes: 1 addition & 1 deletion ironfish-cli/src/multisigBroker/clients/tlsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class MultisigTlsClient extends MultisigTcpClient {
}

const client = tls.connect({
host: this.host,
host: this.hostname,
port: this.port,
rejectUnauthorized: false,
})
Expand Down
85 changes: 71 additions & 14 deletions ironfish-cli/src/multisigBroker/utils.ts
Original file line number Diff line number Diff line change
@@ -1,33 +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, parseUrl } from '@ironfish/sdk'
import dns from 'dns'
import { ErrorUtils, Logger } from '@ironfish/sdk'
import * as ui from '../ui'
import { MultisigClient, MultisigTcpClient, MultisigTlsClient } from './clients'

async function createClient(
serverAddress: string,
options: { passphrase: string; tls: boolean; logger: Logger },
): Promise<MultisigClient> {
const parsed = parseUrl(serverAddress)
async function parseConnectionOptions(options: {
connection?: string
hostname: string
port: number
sessionId?: string
passphrase?: string
logger: Logger
}): Promise<{
hostname: string
port: number
sessionId: string
passphrase: string
}> {
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 = url.password
}
} catch (e) {
if (e instanceof TypeError && e.message.includes('Invalid URL')) {
options.logger.error(ErrorUtils.renderError(e))
}
throw e
}
}

Assert.isNotNull(parsed.hostname)
Assert.isNotNull(parsed.port)
hostname = hostname ?? options.hostname
port = port ?? options.port

const resolved = await dns.promises.lookup(parsed.hostname)
const host = resolved.address
const port = parsed.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,
sessionId,
passphrase,
}
}

function createClient(
hostname: string,
port: number,
options: { passphrase: string; tls: boolean; logger: Logger },
): MultisigClient {
if (options.tls) {
return new MultisigTlsClient({
host,
hostname,
port,
passphrase: options.passphrase,
logger: options.logger,
})
} else {
return new MultisigTcpClient({
host,
hostname,
port,
passphrase: options.passphrase,
logger: options.logger,
Expand All @@ -36,5 +92,6 @@ async function createClient(
}

export const MultisigBrokerUtils = {
parseConnectionOptions,
createClient,
}

0 comments on commit 04d5cdb

Please sign in to comment.