Skip to content

Commit eb823b7

Browse files
authored
Merge pull request #1317 from input-output-hk/feat/lw-10571-add-support-to-cip-1854
feat: add support for cip 1854
2 parents 9a7856a + 0788606 commit eb823b7

File tree

20 files changed

+272
-59
lines changed

20 files changed

+272
-59
lines changed

packages/e2e/src/util/createMockKeyAgent.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Bip32PublicKeyHex, SodiumBip32Ed25519 } from '@cardano-sdk/crypto';
22
import { Cardano } from '@cardano-sdk/core';
3-
import { GroupedAddress, KeyAgent, KeyAgentType } from '@cardano-sdk/key-management';
3+
import { GroupedAddress, KeyAgent, KeyAgentType, KeyPurpose } from '@cardano-sdk/key-management';
44

55
const accountIndex = 0;
66
const chainId = Cardano.ChainIds.Preview;
@@ -18,12 +18,14 @@ export const createMockKeyAgent = (deriveAddressesReturn: GroupedAddress[] = [])
1818
derivePublicKey: jest.fn(),
1919
exportRootPrivateKey: jest.fn(),
2020
extendedAccountPublicKey,
21+
purpose: KeyPurpose.STANDARD,
2122
serializableData: {
2223
__typename: KeyAgentType.InMemory,
2324
accountIndex,
2425
chainId,
2526
encryptedRootPrivateKeyBytes: [],
26-
extendedAccountPublicKey
27+
extendedAccountPublicKey,
28+
purpose: KeyPurpose.STANDARD
2729
},
2830
signBlob: jest.fn(),
2931
signTransaction: jest.fn()

packages/e2e/src/util/util.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
timeout
1919
} from 'rxjs';
2020
import { FAST_OPERATION_TIMEOUT_DEFAULT, SYNC_TIMEOUT_DEFAULT } from '../defaults';
21-
import { InMemoryKeyAgent, TransactionSigner } from '@cardano-sdk/key-management';
21+
import { InMemoryKeyAgent, KeyPurpose, TransactionSigner } from '@cardano-sdk/key-management';
2222
import { InitializeTxProps } from '@cardano-sdk/tx-construction';
2323
import { TestWallet, networkInfoProviderFactory } from '../factories';
2424
import { getEnv, walletVariables } from '../environment';
@@ -228,17 +228,20 @@ export const submitCertificate = async (certificate: Cardano.Certificate, wallet
228228
* @param mnemonics The random set of mnemonics.
229229
* @param genesis Network genesis parameters
230230
* @param bip32Ed25519 The Ed25519 cryptography implementation.
231+
* @param purpose The key purpose.
231232
*/
232233
export const createStandaloneKeyAgent = async (
233234
mnemonics: string[],
234235
genesis: Cardano.CompactGenesis,
235-
bip32Ed25519: Crypto.Bip32Ed25519
236+
bip32Ed25519: Crypto.Bip32Ed25519,
237+
purpose?: KeyPurpose
236238
) =>
237239
await InMemoryKeyAgent.fromBip39MnemonicWords(
238240
{
239241
chainId: genesis,
240242
getPassphrase: async () => Buffer.from(''),
241-
mnemonicWords: mnemonics
243+
mnemonicWords: mnemonics,
244+
purpose
242245
},
243246
{ bip32Ed25519, logger }
244247
);

packages/e2e/test/wallet_epoch_0/PersonalWallet/multisignature.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { BaseWallet, FinalizeTxProps } from '@cardano-sdk/wallet';
33
import { Cardano, nativeScriptPolicyId } from '@cardano-sdk/core';
44
import { InitializeTxProps } from '@cardano-sdk/tx-construction';
5-
import { KeyRole, util } from '@cardano-sdk/key-management';
5+
import { KeyPurpose, KeyRole, util } from '@cardano-sdk/key-management';
66
import {
77
bip32Ed25519Factory,
88
burnTokens,
@@ -48,12 +48,14 @@ describe('PersonalWallet/multisignature', () => {
4848
const aliceKeyAgent = await createStandaloneKeyAgent(
4949
env.KEY_MANAGEMENT_PARAMS.mnemonic.split(' '),
5050
genesis,
51-
bip32Ed25519
51+
bip32Ed25519,
52+
KeyPurpose.MULTI_SIG
5253
);
5354
const bobKeyAgent = await createStandaloneKeyAgent(
5455
env.KEY_MANAGEMENT_PARAMS.mnemonic.split(' '),
5556
genesis,
56-
bip32Ed25519
57+
bip32Ed25519,
58+
KeyPurpose.MULTI_SIG
5759
);
5860

5961
const aliceDerivationPath = {

packages/e2e/test/wallet_epoch_0/SharedWallet/utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as Crypto from '@cardano-sdk/crypto';
22
import {
33
AccountKeyDerivationPath,
44
KeyAgent,
5+
KeyPurpose,
56
KeyRole,
67
SignBlobResult,
78
SignDataContext,
@@ -30,7 +31,12 @@ const getKeyAgent = async (
3031
genesisParameters: Cardano.CompactGenesis,
3132
bip32Ed25519: Crypto.Bip32Ed25519
3233
) => {
33-
const keyAgent = await createStandaloneKeyAgent(mnemonics.split(' '), genesisParameters, bip32Ed25519);
34+
const keyAgent = await createStandaloneKeyAgent(
35+
mnemonics.split(' '),
36+
genesisParameters,
37+
bip32Ed25519,
38+
KeyPurpose.MULTI_SIG
39+
);
3440

3541
const pubKey = await keyAgent.derivePublicKey(DERIVATION_PATH);
3642

packages/hardware-ledger/src/LedgerKeyAgent.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
KeyAgentBase,
99
KeyAgentDependencies,
1010
KeyAgentType,
11+
KeyPurpose,
1112
SerializableLedgerKeyAgentData,
1213
SignBlobResult,
1314
SignTransactionContext,
@@ -67,12 +68,14 @@ export interface CreateLedgerKeyAgentProps {
6768
accountIndex?: number;
6869
communicationType: CommunicationType;
6970
deviceConnection?: LedgerConnection | null;
71+
purpose?: KeyPurpose;
7072
}
7173

7274
export interface GetLedgerXpubProps {
7375
deviceConnection?: LedgerConnection;
7476
communicationType: CommunicationType;
7577
accountIndex: number;
78+
purpose: KeyPurpose;
7679
}
7780

7881
export interface CreateLedgerTransportProps {
@@ -434,11 +437,12 @@ export class LedgerKeyAgent extends KeyAgentBase {
434437
static async getXpub({
435438
deviceConnection,
436439
communicationType,
437-
accountIndex
440+
accountIndex,
441+
purpose
438442
}: GetLedgerXpubProps): Promise<Crypto.Bip32PublicKeyHex> {
439443
try {
440444
const recoveredDeviceConnection = await LedgerKeyAgent.checkDeviceConnection(communicationType, deviceConnection);
441-
const derivationPath = `${CardanoKeyConst.PURPOSE}'/${CardanoKeyConst.COIN_TYPE}'/${accountIndex}'`;
445+
const derivationPath = `${purpose}'/${CardanoKeyConst.COIN_TYPE}'/${accountIndex}'`;
442446
const extendedPublicKey = await recoveredDeviceConnection.getExtendedPublicKey({
443447
path: str_to_path(derivationPath) // BIP32Path
444448
});
@@ -468,7 +472,13 @@ export class LedgerKeyAgent extends KeyAgentBase {
468472
* @throws TransportError
469473
*/
470474
static async createWithDevice(
471-
{ chainId, accountIndex = 0, communicationType, deviceConnection }: CreateLedgerKeyAgentProps,
475+
{
476+
chainId,
477+
accountIndex = 0,
478+
communicationType,
479+
deviceConnection,
480+
purpose = KeyPurpose.STANDARD
481+
}: CreateLedgerKeyAgentProps,
472482
dependencies: KeyAgentDependencies
473483
) {
474484
const deviceListPaths = await LedgerKeyAgent.getHidDeviceList(communicationType);
@@ -479,7 +489,8 @@ export class LedgerKeyAgent extends KeyAgentBase {
479489
const extendedAccountPublicKey = await LedgerKeyAgent.getXpub({
480490
accountIndex,
481491
communicationType,
482-
deviceConnection: activeDeviceConnection
492+
deviceConnection: activeDeviceConnection,
493+
purpose
483494
});
484495

485496
return new LedgerKeyAgent(
@@ -488,7 +499,8 @@ export class LedgerKeyAgent extends KeyAgentBase {
488499
chainId,
489500
communicationType,
490501
deviceConnection: activeDeviceConnection,
491-
extendedAccountPublicKey
502+
extendedAccountPublicKey,
503+
purpose
492504
},
493505
dependencies
494506
);
@@ -536,7 +548,10 @@ export class LedgerKeyAgent extends KeyAgentBase {
536548
return TransactionSigningMode.ORDINARY_TRANSACTION;
537549
}
538550

539-
// TODO: Allow additional key paths
551+
// TODO: LW-10571 - Allow additional key paths. This is necessary for multi-signature wallets
552+
// hardware devices inspect the transaction to determine which keys to use for signing,
553+
// however, multi sig transaction do not reference the CIP-1854 directly, but rather the script hash
554+
// so we need to be able to instruct the HW to sign the transaction with arbitrary keys.
540555
async signTransaction(
541556
{ body, hash }: Cardano.TxBodyWithHash,
542557
{ knownAddresses, txInKeyPathMap }: SignTransactionContext

packages/hardware-ledger/test/LedgerKeyAgent.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as Crypto from '@cardano-sdk/crypto';
33
import * as Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano';
44
import { Ada, InvalidDataReason } from '@cardano-foundation/ledgerjs-hw-app-cardano';
55
import { Cardano } from '@cardano-sdk/core';
6-
import { CardanoKeyConst, CommunicationType, util } from '@cardano-sdk/key-management';
6+
import { CardanoKeyConst, CommunicationType, KeyPurpose, util } from '@cardano-sdk/key-management';
77
import { LedgerKeyAgent } from '../src';
88
import { dummyLogger } from 'ts-log';
99
import { poolId, poolParameters, pureAdaTxOut, stakeKeyHash, txIn, txOutWithDatum } from './testData';
@@ -379,7 +379,8 @@ describe('LedgerKeyAgent', () => {
379379
communicationType: CommunicationType.Node,
380380
extendedAccountPublicKey: Crypto.Bip32PublicKeyHex(
381381
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
382-
)
382+
),
383+
purpose: KeyPurpose.STANDARD
383384
},
384385
{
385386
bip32Ed25519: new Crypto.SodiumBip32Ed25519(),

packages/hardware-trezor/src/TrezorKeyAgent.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
KeyAgentBase,
99
KeyAgentDependencies,
1010
KeyAgentType,
11+
KeyPurpose,
1112
SerializableTrezorKeyAgentData,
1213
SignBlobResult,
1314
SignTransactionContext,
@@ -37,12 +38,14 @@ export interface TrezorKeyAgentProps extends Omit<SerializableTrezorKeyAgentData
3738
export interface GetTrezorXpubProps {
3839
accountIndex: number;
3940
communicationType: CommunicationType;
41+
purpose: KeyPurpose;
4042
}
4143

4244
export interface CreateTrezorKeyAgentProps {
4345
chainId: Cardano.ChainId;
4446
accountIndex?: number;
4547
trezorConfig: TrezorConfig;
48+
purpose?: KeyPurpose;
4649
}
4750

4851
export type TrezorConnectInstanceType = typeof TrezorConnectNode | typeof TrezorConnectWeb;
@@ -135,10 +138,14 @@ export class TrezorKeyAgent extends KeyAgentBase {
135138
}
136139
}
137140

138-
static async getXpub({ accountIndex, communicationType }: GetTrezorXpubProps): Promise<Crypto.Bip32PublicKeyHex> {
141+
static async getXpub({
142+
accountIndex,
143+
communicationType,
144+
purpose
145+
}: GetTrezorXpubProps): Promise<Crypto.Bip32PublicKeyHex> {
139146
try {
140147
await TrezorKeyAgent.checkDeviceConnection(communicationType);
141-
const derivationPath = `m/${CardanoKeyConst.PURPOSE}'/${CardanoKeyConst.COIN_TYPE}'/${accountIndex}'`;
148+
const derivationPath = `m/${purpose}'/${CardanoKeyConst.COIN_TYPE}'/${accountIndex}'`;
142149
const trezorConnect = getTrezorConnect(communicationType);
143150
const extendedPublicKey = await trezorConnect.cardanoGetPublicKey({
144151
path: derivationPath,
@@ -154,20 +161,22 @@ export class TrezorKeyAgent extends KeyAgentBase {
154161
}
155162

156163
static async createWithDevice(
157-
{ chainId, accountIndex = 0, trezorConfig }: CreateTrezorKeyAgentProps,
164+
{ chainId, accountIndex = 0, trezorConfig, purpose = KeyPurpose.STANDARD }: CreateTrezorKeyAgentProps,
158165
dependencies: KeyAgentDependencies
159166
) {
160167
const isTrezorInitialized = await TrezorKeyAgent.initializeTrezorTransport(trezorConfig);
161168
const extendedAccountPublicKey = await TrezorKeyAgent.getXpub({
162169
accountIndex,
163-
communicationType: trezorConfig.communicationType
170+
communicationType: trezorConfig.communicationType,
171+
purpose
164172
});
165173
return new TrezorKeyAgent(
166174
{
167175
accountIndex,
168176
chainId,
169177
extendedAccountPublicKey,
170178
isTrezorInitialized,
179+
purpose,
171180
trezorConfig
172181
},
173182
dependencies

packages/key-management/src/InMemoryKeyAgent.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
KeyAgentDependencies,
88
KeyAgentType,
99
KeyPair,
10+
KeyPurpose,
1011
SerializableInMemoryKeyAgentData,
1112
SignBlobResult,
1213
SignTransactionContext,
@@ -37,6 +38,7 @@ export interface FromBip39MnemonicWordsProps {
3738
mnemonic2ndFactorPassphrase?: string;
3839
getPassphrase: GetPassphrase;
3940
accountIndex?: number;
41+
purpose?: KeyPurpose;
4042
}
4143

4244
const getPassphraseRethrowTypedError = async (getPassphrase: GetPassphrase) => {
@@ -60,6 +62,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
6062
const accountKey = await deriveAccountPrivateKey({
6163
accountIndex: this.accountIndex,
6264
bip32Ed25519: this.bip32Ed25519,
65+
purpose: this.purpose,
6366
rootPrivateKey
6467
});
6568

@@ -89,7 +92,8 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
8992
getPassphrase,
9093
mnemonicWords,
9194
mnemonic2ndFactorPassphrase = '',
92-
accountIndex = 0
95+
accountIndex = 0,
96+
purpose = KeyPurpose.STANDARD
9397
}: FromBip39MnemonicWordsProps,
9498
dependencies: KeyAgentDependencies
9599
): Promise<InMemoryKeyAgent> {
@@ -103,6 +107,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
103107
const accountPrivateKey = await deriveAccountPrivateKey({
104108
accountIndex,
105109
bip32Ed25519: dependencies.bip32Ed25519,
110+
purpose,
106111
rootPrivateKey
107112
});
108113

@@ -114,7 +119,8 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
114119
chainId,
115120
encryptedRootPrivateKeyBytes: [...encryptedRootPrivateKey],
116121
extendedAccountPublicKey,
117-
getPassphrase
122+
getPassphrase,
123+
purpose
118124
},
119125
dependencies
120126
);

packages/key-management/src/KeyAgentBase.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
GroupedAddress,
66
KeyAgent,
77
KeyAgentDependencies,
8+
KeyPurpose,
89
SerializableKeyAgentData,
910
SignBlobResult,
1011
SignTransactionContext,
@@ -35,6 +36,10 @@ export abstract class KeyAgentBase implements KeyAgent {
3536
return this.#bip32Ed25519;
3637
}
3738

39+
get purpose(): KeyPurpose {
40+
return this.serializableData.purpose || KeyPurpose.STANDARD;
41+
}
42+
3843
abstract signBlob(derivationPath: AccountKeyDerivationPath, blob: HexBlob): Promise<SignBlobResult>;
3944
abstract exportRootPrivateKey(): Promise<Crypto.Bip32PrivateKeyHex>;
4045
abstract signTransaction(

packages/key-management/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export enum KeyRole {
3838
DRep = 3
3939
}
4040

41+
export enum KeyPurpose {
42+
STANDARD = 1852,
43+
MULTI_SIG = 1854
44+
}
4145
export interface AccountKeyDerivationPath {
4246
role: KeyRole;
4347
index: number;
@@ -93,6 +97,7 @@ export interface SerializableKeyAgentDataBase {
9397
chainId: Cardano.ChainId;
9498
accountIndex: number;
9599
extendedAccountPublicKey: Crypto.Bip32PublicKeyHex;
100+
purpose?: KeyPurpose;
96101
}
97102

98103
export interface SerializableInMemoryKeyAgentData extends SerializableKeyAgentDataBase {
@@ -184,6 +189,7 @@ export interface KeyAgent {
184189
get serializableData(): SerializableKeyAgentData;
185190
get extendedAccountPublicKey(): Crypto.Bip32PublicKeyHex;
186191
get bip32Ed25519(): Crypto.Bip32Ed25519;
192+
get purpose(): KeyPurpose | undefined;
187193

188194
/**
189195
* @throws AuthenticationError

0 commit comments

Comments
 (0)