Skip to content

Commit 57286b0

Browse files
authored
Merge pull request #5373 from iron-fish/rahul/import-multisig-account-without-secret
feat: Import multisig hw identity
2 parents 83e49dc + b192ec3 commit 57286b0

File tree

3 files changed

+151
-33
lines changed

3 files changed

+151
-33
lines changed

ironfish/src/rpc/routes/wallet/__fixtures__/importAccount.test.ts.fixture

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@
66
"previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86",
77
"noteCommitment": {
88
"type": "Buffer",
9-
"data": "base64:0KDCZq74l7x/xBpk6pN4GKueDfxqDpBfVVbEBle3piE="
9+
"data": "base64:ZXWDsqog2eRQq6WZ70lqE9cVSUINpf1J8m6T/NS5UTQ="
1010
},
1111
"transactionCommitment": {
1212
"type": "Buffer",
13-
"data": "base64:V+JntG6Mh/CV2y6OUBo0XHM1SsQd2BuBUpVQjfd8UTY="
13+
"data": "base64:iIFwx36ZjBXyt1DsFBf5Av/sNYw4+Z5ZMXV1m0bNLm4="
1414
},
1515
"target": "9282972777491357380673661573939192202192629606981189395159182914949423",
1616
"randomness": "0",
17-
"timestamp": 1726270914810,
17+
"timestamp": 1726274775730,
1818
"graffiti": "0000000000000000000000000000000000000000000000000000000000000000",
1919
"noteSize": 4,
2020
"work": "0"
2121
},
2222
"transactions": [
2323
{
2424
"type": "Buffer",
25-
"data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAyk5bfvKdSTdwi+EqeXx2tuNHJaRs6/OyyTqqtfXiOpeCwrIXEHclFCfWxdYf26U+WL6RmJa2EXCOEU6DWMvSdRv9DjGNEdEs3psBNEoBsPyCxX3g2+2YZOL4q/khIPoKWnEYwtr4Y0LK4CZ2fXcBrwD6RGx/rx6XQI8KCln/DXoY8XogZd5TzPBkEgje6l7AnKSMYJSaj4NxsNHZImLJOcvJsJDKPYaupuA57kTbb9+2mXV4Ww2wX0AzPXgdbtML2WmWSDPhMqNTQ4fSEf+wej8Bdu19SfPHAVcnGM5IlmMnWxLmLpoWJSMqyLHyuIm7U+awFCB2Rnp/ctiDv5Pvw0zXsHnTD1m8FGTtDBXsCLSrcnkAScYq2k9TKSPoebA0pNNqXuK2yCCcuFwGMgno/kepNOjgIQC2NxnzhjxnVdJiw9tTgMgXqSAlXqWpSVP/px2gtax7dFibMtdv8ytLigKMfy9fAAl1L8IVrk2QclO4WHGelqhidGNNRCofwiL43lWwTeX3xFhwLetRp4coJ8xk/zu/IOiLxeL+Pgj3gCsMSgHzwWiLTjWVB0j7zm75Idrgbm+sgS8A6rEnPdlkHvW7aNLYrTHxigi7q6LdHC1m0tzkMPLTqUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwPTtFNObdUhDGdWBuYySM8KCwmFmBaUol9uIxYe/w3Ip9yh0GHfuULUqZHBBCfl/McTVbJl4h86Tb0qRJSoJABQ=="
25+
"data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAKmTHIfoCqB1LPml+WWhSb5/fnq2hk48D9yavNpWu6VeJkv9RrOYq6mpE0ItseXRXF8J6Nl4s4StLeuv4wLiCrAI+n4gmZX8+b+DEUnHSUDaT6iroKN3gPhYrj2ljrGdPO5BtMIu2OAss1FuOkri17p3ABqHUbGr23HVypBuMacMVmaSpbmpctyz3zmAF+bSq24oCCm7tmQUpuK3tc4YH41QYD5vrETduQmdq0Fln5zeSpH40uEA3GegClTHrQ486PHxekbx2I2CMpC/XSZABupohuoZX81a62qm2t9rni07Y4hJtREUiK4Zt2d3+jj6HRZVnn7SeB0J+NEv6CI4vVZfk3K561jIq/tEUdFWexO5U5YZ8ylhFlDkvd4+P20o1544GTAmy9eFOcIjwkn7JBniZHpjcgZY7+H8h/CHHC95CsU8XT3rR/VLaWhy8CvTTx1PGc+X5XK0a66UfI0Pv4bEKjr+ecB4FXk7Y3DPbe4nXTAKnNoh+EbltJGZMhpGC60OQgJ8vURFVmJ/KYtwMwjyD6YFSaFD7FrsjxTIs76GEHuPsNnvEDkBknJk8ngczOqy8j9hu4/uRtBygXZyLRC00SeyfsX+FSK4xWEfKr5pXYBGu9j8LVUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw/ZIbYQski4H101WQ64c3yvUoZtTFl5YQAyAsxoD2Sce86qNzWDY02qg0qQ9OYQHEKC3h5Gyumwk347ivnSLTCg=="
2626
}
2727
]
2828
}

ironfish/src/rpc/routes/wallet/importAccount.test.ts

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44
import { generateKey, LanguageCode, multisig, spendingKeyToWords } from '@ironfish/rust-nodejs'
55
import fs from 'fs'
66
import path from 'path'
7+
import { Assert } from '../../../assert'
78
import { createTrustedDealerKeyPackages, useMinerBlockFixture } from '../../../testUtilities'
89
import { createRouteTest } from '../../../testUtilities/routeTest'
910
import { JsonEncoder } from '../../../wallet'
10-
import { isMultisigSignerImport } from '../../../wallet/exporter'
11+
import {
12+
decodeAccountImport,
13+
isMultisigHardwareSignerImport,
14+
isMultisigSignerImport,
15+
} from '../../../wallet/exporter'
1116
import { AccountFormat, encodeAccountImport } from '../../../wallet/exporter/account'
1217
import { AccountImport } from '../../../wallet/exporter/accountImport'
1318
import { Bech32Encoder } from '../../../wallet/exporter/encoders/bech32'
@@ -322,6 +327,24 @@ describe('Route wallet/importAccount', () => {
322327
expect(response.content.name).not.toBeNull()
323328

324329
await routeTest.client.wallet.removeAccount({ account: testCaseFile })
330+
331+
const account = decodeAccountImport(testCase, {
332+
name: testCaseFile,
333+
})
334+
335+
if (account.multisigKeys && isMultisigHardwareSignerImport(account.multisigKeys)) {
336+
await routeTest.node.wallet.walletDb.deleteMultisigIdentity(
337+
Buffer.from(account.multisigKeys.identity, 'hex'),
338+
)
339+
}
340+
341+
if (account.multisigKeys && isMultisigSignerImport(account.multisigKeys)) {
342+
await routeTest.node.wallet.walletDb.deleteMultisigIdentity(
343+
new multisig.ParticipantSecret(Buffer.from(account.multisigKeys.secret, 'hex'))
344+
.toIdentity()
345+
.serialize(),
346+
)
347+
}
325348
}
326349
})
327350

@@ -442,7 +465,7 @@ describe('Route wallet/importAccount', () => {
442465
expect.assertions(2)
443466
})
444467

445-
it('should not import multisig account with duplicate identity name', async () => {
468+
it('should not import multisig account with secret with the same identity name', async () => {
446469
const name = 'duplicateIdentityNameTest'
447470

448471
const {
@@ -527,4 +550,96 @@ describe('Route wallet/importAccount', () => {
527550

528551
expect.assertions(7)
529552
})
553+
554+
it('should not import hardware multisig account with same identity name', async () => {
555+
const name = 'duplicateIdentityNameTest'
556+
557+
const {
558+
dealer: trustedDealerPackages,
559+
secrets,
560+
identities,
561+
} = createTrustedDealerKeyPackages()
562+
563+
const identity = identities[0]
564+
const nextIdentity = identities[1]
565+
566+
await routeTest.node.wallet.walletDb.putMultisigIdentity(Buffer.from(identity, 'hex'), {
567+
secret: secrets[0].serialize(),
568+
name,
569+
})
570+
571+
const account: AccountImport = {
572+
version: 1,
573+
name,
574+
viewKey: trustedDealerPackages.viewKey,
575+
incomingViewKey: trustedDealerPackages.incomingViewKey,
576+
outgoingViewKey: trustedDealerPackages.outgoingViewKey,
577+
publicAddress: trustedDealerPackages.publicAddress,
578+
proofAuthorizingKey: trustedDealerPackages.proofAuthorizingKey,
579+
spendingKey: null,
580+
createdAt: null,
581+
multisigKeys: {
582+
publicKeyPackage: trustedDealerPackages.publicKeyPackage,
583+
identity: nextIdentity,
584+
},
585+
}
586+
587+
try {
588+
await routeTest.client.wallet.importAccount({
589+
account: new JsonEncoder().encode(account),
590+
name,
591+
rescan: false,
592+
})
593+
} catch (e: unknown) {
594+
if (!(e instanceof RpcRequestError)) {
595+
throw e
596+
}
597+
598+
expect(e.status).toBe(400)
599+
expect(e.code).toBe(RPC_ERROR_CODES.DUPLICATE_IDENTITY_NAME)
600+
}
601+
602+
expect.assertions(2)
603+
})
604+
605+
it('should not modify existing identity if a new one is being imported with a different name', async () => {
606+
const { dealer: trustedDealerPackages, identities } = createTrustedDealerKeyPackages()
607+
608+
const identity = identities[0]
609+
610+
await routeTest.node.wallet.walletDb.putMultisigIdentity(Buffer.from(identity, 'hex'), {
611+
name: 'existingIdentity',
612+
})
613+
614+
const account: AccountImport = {
615+
version: 1,
616+
name: 'newIdentity',
617+
viewKey: trustedDealerPackages.viewKey,
618+
incomingViewKey: trustedDealerPackages.incomingViewKey,
619+
outgoingViewKey: trustedDealerPackages.outgoingViewKey,
620+
publicAddress: trustedDealerPackages.publicAddress,
621+
proofAuthorizingKey: trustedDealerPackages.proofAuthorizingKey,
622+
spendingKey: null,
623+
createdAt: null,
624+
multisigKeys: {
625+
publicKeyPackage: trustedDealerPackages.publicKeyPackage,
626+
identity: identity,
627+
},
628+
}
629+
630+
const response = await routeTest.client.wallet.importAccount({
631+
account: new JsonEncoder().encode(account),
632+
name: 'newIdentity',
633+
rescan: false,
634+
})
635+
636+
expect(response.status).toBe(200)
637+
expect(response.content.name).toEqual('newIdentity')
638+
639+
const existingIdentity = await routeTest.wallet.walletDb.getMultisigIdentity(
640+
Buffer.from(identity, 'hex'),
641+
)
642+
Assert.isNotUndefined(existingIdentity)
643+
expect(existingIdentity.name).toEqual('existingIdentity')
644+
})
530645
})

ironfish/src/wallet/wallet.ts

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ import {
5454
} from './errors'
5555
import { isMultisigSignerImport } from './exporter'
5656
import { AccountImport, validateAccountImport } from './exporter/accountImport'
57-
import { isMultisigSignerTrustedDealerImport } from './exporter/multisig'
57+
import {
58+
isMultisigHardwareSignerImport,
59+
isMultisigSignerTrustedDealerImport,
60+
} from './exporter/multisig'
5861
import { MintAssetOptions } from './interfaces/mintAssetOptions'
5962
import { ScanState } from './scanner/scanState'
6063
import { WalletScanner } from './scanner/walletScanner'
@@ -1418,29 +1421,31 @@ export class Wallet {
14181421
): Promise<Account> {
14191422
let multisigKeys = accountValue.multisigKeys
14201423
let secret: Buffer | undefined
1424+
let identity: Buffer | undefined
14211425
const name = accountValue.name
14221426

1423-
if (
1424-
accountValue.multisigKeys &&
1425-
isMultisigSignerTrustedDealerImport(accountValue.multisigKeys)
1426-
) {
1427-
const multisigIdentity = await this.walletDb.getMultisigIdentity(
1428-
Buffer.from(accountValue.multisigKeys.identity, 'hex'),
1429-
)
1430-
if (!multisigIdentity || !multisigIdentity.secret) {
1431-
throw new Error('Cannot import identity without a corresponding multisig secret')
1432-
}
1427+
if (accountValue.multisigKeys) {
1428+
if (isMultisigSignerTrustedDealerImport(accountValue.multisigKeys)) {
1429+
const multisigIdentity = await this.walletDb.getMultisigIdentity(
1430+
Buffer.from(accountValue.multisigKeys.identity, 'hex'),
1431+
)
1432+
if (!multisigIdentity || !multisigIdentity.secret) {
1433+
throw new Error('Cannot import identity without a corresponding multisig secret')
1434+
}
14331435

1434-
multisigKeys = {
1435-
keyPackage: accountValue.multisigKeys.keyPackage,
1436-
publicKeyPackage: accountValue.multisigKeys.publicKeyPackage,
1437-
secret: multisigIdentity.secret.toString('hex'),
1436+
multisigKeys = {
1437+
keyPackage: accountValue.multisigKeys.keyPackage,
1438+
publicKeyPackage: accountValue.multisigKeys.publicKeyPackage,
1439+
secret: multisigIdentity.secret.toString('hex'),
1440+
}
1441+
secret = multisigIdentity.secret
1442+
identity = Buffer.from(accountValue.multisigKeys.identity, 'hex')
1443+
} else if (isMultisigSignerImport(accountValue.multisigKeys)) {
1444+
secret = Buffer.from(accountValue.multisigKeys.secret, 'hex')
1445+
identity = new multisig.ParticipantSecret(secret).toIdentity().serialize()
1446+
} else if (isMultisigHardwareSignerImport(accountValue.multisigKeys)) {
1447+
identity = Buffer.from(accountValue.multisigKeys.identity, 'hex')
14381448
}
1439-
secret = multisigIdentity.secret
1440-
}
1441-
1442-
if (accountValue.multisigKeys && isMultisigSignerImport(accountValue.multisigKeys)) {
1443-
secret = Buffer.from(accountValue.multisigKeys.secret, 'hex')
14441449
}
14451450

14461451
if (name && this.getAccountByName(name)) {
@@ -1500,23 +1505,21 @@ export class Wallet {
15001505
await this.walletDb.setAccount(account, tx)
15011506
}
15021507

1503-
if (secret) {
1504-
const identitySerialized = new multisig.ParticipantSecret(secret)
1505-
.toIdentity()
1506-
.serialize()
1507-
const multisigIdentity = await this.walletDb.getMultisigIdentity(identitySerialized, tx)
1508+
if (identity) {
1509+
const existingIdentity = await this.walletDb.getMultisigIdentity(identity, tx)
15081510

1509-
if (!multisigIdentity) {
1511+
if (!existingIdentity) {
15101512
const duplicateSecret = await this.walletDb.getMultisigSecretByName(
15111513
accountValue.name,
15121514
tx,
15131515
)
1516+
15141517
if (duplicateSecret) {
15151518
throw new DuplicateIdentityNameError(accountValue.name)
15161519
}
15171520

15181521
await this.walletDb.putMultisigIdentity(
1519-
identitySerialized,
1522+
identity,
15201523
{
15211524
name: account.name,
15221525
secret,

0 commit comments

Comments
 (0)