diff --git a/packages/cardano-services/test/StakePool/TypeormStakePoolProvider/util.test.ts b/packages/cardano-services/test/StakePool/TypeormStakePoolProvider/util.test.ts index acce639583d..c2fd5c21fd8 100644 --- a/packages/cardano-services/test/StakePool/TypeormStakePoolProvider/util.test.ts +++ b/packages/cardano-services/test/StakePool/TypeormStakePoolProvider/util.test.ts @@ -4,7 +4,7 @@ import { validateFuzzyOptions, withTextFilter } from '../../../src/StakePool/Typ describe('TypeormStakePoolProvider utils', () => { describe('validateFuzzyOptions', () => { it('throws if value is not a valid JSON encoded string', () => - expect(() => validateFuzzyOptions('test')).toThrow('Unexpected token e in JSON at position 1')); + expect(() => validateFuzzyOptions('test')).toThrow(/Unexpected token/)); it('throws if value is not an object', () => expect(() => validateFuzzyOptions('"test"')).toThrow('must be an object')); it('throws without threshold', () => diff --git a/packages/e2e/src/factories.ts b/packages/e2e/src/factories.ts index ab6899c0796..97e95745aa8 100644 --- a/packages/e2e/src/factories.ts +++ b/packages/e2e/src/factories.ts @@ -204,7 +204,8 @@ keyManagementFactory.register('inMemory', async (params: any): Promise Buffer.from(params.passphrase), - mnemonicWords + mnemonicWords, + purpose: params.purpose }, dependencies ) @@ -220,7 +221,8 @@ keyManagementFactory.register('ledger', async (params: any): Promise Buffer.from(''), - mnemonicWords: mnemonicArray + mnemonicWords: mnemonicArray, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), diff --git a/packages/e2e/src/util/createMockKeyAgent.ts b/packages/e2e/src/util/createMockKeyAgent.ts index 3df1ed1595e..5c98aca4551 100644 --- a/packages/e2e/src/util/createMockKeyAgent.ts +++ b/packages/e2e/src/util/createMockKeyAgent.ts @@ -1,6 +1,6 @@ import { Bip32PublicKeyHex, SodiumBip32Ed25519 } from '@cardano-sdk/crypto'; import { Cardano } from '@cardano-sdk/core'; -import { GroupedAddress, KeyAgent, KeyAgentType } from '@cardano-sdk/key-management'; +import { GroupedAddress, KeyAgent, KeyAgentType, KeyPurpose } from '@cardano-sdk/key-management'; const accountIndex = 0; const chainId = Cardano.ChainIds.Preview; @@ -18,12 +18,16 @@ export const createMockKeyAgent = (deriveAddressesReturn: GroupedAddress[] = []) derivePublicKey: jest.fn(), exportRootPrivateKey: jest.fn(), extendedAccountPublicKey, + get purpose(): KeyPurpose { + return KeyPurpose.STANDARD; + }, serializableData: { __typename: KeyAgentType.InMemory, accountIndex, chainId, encryptedRootPrivateKeyBytes: [], - extendedAccountPublicKey + extendedAccountPublicKey, + purpose: KeyPurpose.STANDARD }, signBlob: jest.fn(), signTransaction: jest.fn() diff --git a/packages/e2e/src/util/util.ts b/packages/e2e/src/util/util.ts index b363f2aebf4..ee98d49338b 100644 --- a/packages/e2e/src/util/util.ts +++ b/packages/e2e/src/util/util.ts @@ -18,7 +18,7 @@ import { timeout } from 'rxjs'; import { FAST_OPERATION_TIMEOUT_DEFAULT, SYNC_TIMEOUT_DEFAULT } from '../defaults'; -import { InMemoryKeyAgent, TransactionSigner } from '@cardano-sdk/key-management'; +import { InMemoryKeyAgent, KeyPurpose, TransactionSigner } from '@cardano-sdk/key-management'; import { InitializeTxProps } from '@cardano-sdk/tx-construction'; import { TestWallet, networkInfoProviderFactory } from '../factories'; import { getEnv, walletVariables } from '../environment'; @@ -228,17 +228,20 @@ export const submitCertificate = async (certificate: Cardano.Certificate, wallet * @param mnemonics The random set of mnemonics. * @param genesis Network genesis parameters * @param bip32Ed25519 The Ed25519 cryptography implementation. + * @param purpose they key derivation purpose of either 1852 or 1854 */ export const createStandaloneKeyAgent = async ( mnemonics: string[], genesis: Cardano.CompactGenesis, - bip32Ed25519: Crypto.Bip32Ed25519 + bip32Ed25519: Crypto.Bip32Ed25519, + purpose: KeyPurpose ) => await InMemoryKeyAgent.fromBip39MnemonicWords( { chainId: genesis, getPassphrase: async () => Buffer.from(''), - mnemonicWords: mnemonics + mnemonicWords: mnemonics, + purpose }, { bip32Ed25519, logger } ); diff --git a/packages/e2e/test/long-running/multisig-wallet/MultiSigWallet.ts b/packages/e2e/test/long-running/multisig-wallet/MultiSigWallet.ts index 7b4b1809cea..142ec708882 100644 --- a/packages/e2e/test/long-running/multisig-wallet/MultiSigWallet.ts +++ b/packages/e2e/test/long-running/multisig-wallet/MultiSigWallet.ts @@ -4,6 +4,7 @@ import { AddressType, GroupedAddress, InMemoryKeyAgent, + KeyPurpose, KeyRole } from '@cardano-sdk/key-management'; import { @@ -215,7 +216,7 @@ export class MultiSigWallet { body: multiSigTx.getTransaction().body, hash: multiSigTx.getTransaction().id }, - { knownAddresses: [this.#address], txInKeyPathMap: {} }, + { knownAddresses: [this.#address], purpose: KeyPurpose.MULTI_SIG, txInKeyPathMap: {} }, { additionalKeyPaths: [DERIVATION_PATH] } ); diff --git a/packages/e2e/test/long-running/multisig-wallet/multisig-delegation-rewards.test.ts b/packages/e2e/test/long-running/multisig-wallet/multisig-delegation-rewards.test.ts index 93d51150bf9..183013cd1a6 100644 --- a/packages/e2e/test/long-running/multisig-wallet/multisig-delegation-rewards.test.ts +++ b/packages/e2e/test/long-running/multisig-wallet/multisig-delegation-rewards.test.ts @@ -1,7 +1,7 @@ import * as Crypto from '@cardano-sdk/crypto'; import { BaseWallet } from '@cardano-sdk/wallet'; import { Cardano, EraSummary, StakePoolProvider, createSlotEpochCalc } from '@cardano-sdk/core'; -import { InMemoryKeyAgent, KeyRole } from '@cardano-sdk/key-management'; +import { InMemoryKeyAgent, KeyPurpose, KeyRole } from '@cardano-sdk/key-management'; import { MultiSigTx } from './MultiSigTx'; import { MultiSigWallet } from './MultiSigWallet'; import { Observable, filter, firstValueFrom, map, take } from 'rxjs'; @@ -63,7 +63,7 @@ const fundMultiSigWallet = async (sendingWallet: BaseWallet, address: Cardano.Pa const getKeyAgent = async (mnemonics: string, faucetWallet: BaseWallet, bip32Ed25519: Crypto.Bip32Ed25519) => { const genesis = await firstValueFrom(faucetWallet.genesisParameters$); - const keyAgent = await createStandaloneKeyAgent(mnemonics.split(' '), genesis, bip32Ed25519); + const keyAgent = await createStandaloneKeyAgent(mnemonics.split(' '), genesis, bip32Ed25519, KeyPurpose.STANDARD); const pubKey = await keyAgent.derivePublicKey(DERIVATION_PATH); diff --git a/packages/e2e/test/wallet_epoch_0/PersonalWallet/handle.test.ts b/packages/e2e/test/wallet_epoch_0/PersonalWallet/handle.test.ts index 144bf7dbcb2..59285c8806d 100644 --- a/packages/e2e/test/wallet_epoch_0/PersonalWallet/handle.test.ts +++ b/packages/e2e/test/wallet_epoch_0/PersonalWallet/handle.test.ts @@ -1,7 +1,7 @@ /* eslint-disable sonarjs/no-duplicate-string */ import { BaseWallet } from '@cardano-sdk/wallet'; import { Cardano, metadatum } from '@cardano-sdk/core'; -import { KeyAgent, TransactionSigner } from '@cardano-sdk/key-management'; +import { KeyAgent, KeyPurpose, TransactionSigner } from '@cardano-sdk/key-management'; import { bip32Ed25519Factory, burnTokens, @@ -50,7 +50,8 @@ describe('Ada handle', () => { keyAgent = await createStandaloneKeyAgent( env.KEY_MANAGEMENT_PARAMS.mnemonic.split(' '), await firstValueFrom(wallet.genesisParameters$), - await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger) + await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger), + KeyPurpose.STANDARD ); ({ policyScript, policySigner, policyId } = await createHandlePolicy(keyAgent)); const handleProviderPolicyId = await getHandlePolicyId( diff --git a/packages/e2e/test/wallet_epoch_0/PersonalWallet/mint.test.ts b/packages/e2e/test/wallet_epoch_0/PersonalWallet/mint.test.ts index 17834da408d..9071031ca2d 100644 --- a/packages/e2e/test/wallet_epoch_0/PersonalWallet/mint.test.ts +++ b/packages/e2e/test/wallet_epoch_0/PersonalWallet/mint.test.ts @@ -1,7 +1,7 @@ import { BaseWallet, FinalizeTxProps } from '@cardano-sdk/wallet'; import { Cardano, nativeScriptPolicyId } from '@cardano-sdk/core'; import { InitializeTxProps } from '@cardano-sdk/tx-construction'; -import { KeyRole, util } from '@cardano-sdk/key-management'; +import { KeyPurpose, KeyRole, util } from '@cardano-sdk/key-management'; import { bip32Ed25519Factory, burnTokens, @@ -40,7 +40,8 @@ describe('PersonalWallet/mint', () => { const aliceKeyAgent = await createStandaloneKeyAgent( env.KEY_MANAGEMENT_PARAMS.mnemonic.split(' '), genesis, - await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger) + await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger), + KeyPurpose.STANDARD ); const derivationPath = { diff --git a/packages/e2e/test/wallet_epoch_0/PersonalWallet/multiAddress.test.ts b/packages/e2e/test/wallet_epoch_0/PersonalWallet/multiAddress.test.ts index d6aac295a7b..85b071d9312 100644 --- a/packages/e2e/test/wallet_epoch_0/PersonalWallet/multiAddress.test.ts +++ b/packages/e2e/test/wallet_epoch_0/PersonalWallet/multiAddress.test.ts @@ -1,5 +1,5 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { AddressType, GroupedAddress, util } from '@cardano-sdk/key-management'; +import { AddressType, GroupedAddress, KeyPurpose, util } from '@cardano-sdk/key-management'; import { BaseWallet } from '@cardano-sdk/wallet'; import { Cardano } from '@cardano-sdk/core'; import { @@ -43,7 +43,8 @@ describe('PersonalWallet/multiAddress', () => { const multiAddressKeyAgent = await createStandaloneKeyAgent( mnemonics, genesis, - await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger) + await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger), + KeyPurpose.STANDARD ); let txBuilder = wallet.createTxBuilder(); diff --git a/packages/e2e/test/wallet_epoch_0/PersonalWallet/multisignature.test.ts b/packages/e2e/test/wallet_epoch_0/PersonalWallet/multisignature.test.ts index 160a6a35c93..fd98a035643 100644 --- a/packages/e2e/test/wallet_epoch_0/PersonalWallet/multisignature.test.ts +++ b/packages/e2e/test/wallet_epoch_0/PersonalWallet/multisignature.test.ts @@ -2,7 +2,7 @@ import { BaseWallet, FinalizeTxProps } from '@cardano-sdk/wallet'; import { Cardano, nativeScriptPolicyId } from '@cardano-sdk/core'; import { InitializeTxProps } from '@cardano-sdk/tx-construction'; -import { KeyRole, util } from '@cardano-sdk/key-management'; +import { KeyPurpose, KeyRole, util } from '@cardano-sdk/key-management'; import { bip32Ed25519Factory, burnTokens, @@ -48,12 +48,14 @@ describe('PersonalWallet/multisignature', () => { const aliceKeyAgent = await createStandaloneKeyAgent( env.KEY_MANAGEMENT_PARAMS.mnemonic.split(' '), genesis, - bip32Ed25519 + bip32Ed25519, + KeyPurpose.MULTI_SIG ); const bobKeyAgent = await createStandaloneKeyAgent( env.KEY_MANAGEMENT_PARAMS.mnemonic.split(' '), genesis, - bip32Ed25519 + bip32Ed25519, + KeyPurpose.MULTI_SIG ); const aliceDerivationPath = { diff --git a/packages/e2e/test/wallet_epoch_0/PersonalWallet/nft.test.ts b/packages/e2e/test/wallet_epoch_0/PersonalWallet/nft.test.ts index 107c7973964..963fc5126fb 100644 --- a/packages/e2e/test/wallet_epoch_0/PersonalWallet/nft.test.ts +++ b/packages/e2e/test/wallet_epoch_0/PersonalWallet/nft.test.ts @@ -2,7 +2,7 @@ import { Asset, Cardano, metadatum, nativeScriptPolicyId } from '@cardano-sdk/core'; import { Assets, BaseWallet, FinalizeTxProps } from '@cardano-sdk/wallet'; import { InitializeTxProps } from '@cardano-sdk/tx-construction'; -import { KeyRole, TransactionSigner, util } from '@cardano-sdk/key-management'; +import { KeyPurpose, KeyRole, TransactionSigner, util } from '@cardano-sdk/key-management'; import { bip32Ed25519Factory, burnTokens, @@ -61,7 +61,8 @@ describe('PersonalWallet.assets/nft', () => { const keyAgent = await createStandaloneKeyAgent( env.KEY_MANAGEMENT_PARAMS.mnemonic.split(' '), genesis, - await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger) + await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger), + KeyPurpose.STANDARD ); const derivationPath = { diff --git a/packages/e2e/test/wallet_epoch_0/SharedWallet/utils.ts b/packages/e2e/test/wallet_epoch_0/SharedWallet/utils.ts index 855ceb875ac..a3071f19a5d 100644 --- a/packages/e2e/test/wallet_epoch_0/SharedWallet/utils.ts +++ b/packages/e2e/test/wallet_epoch_0/SharedWallet/utils.ts @@ -2,6 +2,7 @@ import * as Crypto from '@cardano-sdk/crypto'; import { AccountKeyDerivationPath, KeyAgent, + KeyPurpose, KeyRole, SignBlobResult, SignDataContext, @@ -30,7 +31,12 @@ const getKeyAgent = async ( genesisParameters: Cardano.CompactGenesis, bip32Ed25519: Crypto.Bip32Ed25519 ) => { - const keyAgent = await createStandaloneKeyAgent(mnemonics.split(' '), genesisParameters, bip32Ed25519); + const keyAgent = await createStandaloneKeyAgent( + mnemonics.split(' '), + genesisParameters, + bip32Ed25519, + KeyPurpose.STANDARD + ); const pubKey = await keyAgent.derivePublicKey(DERIVATION_PATH); diff --git a/packages/e2e/test/web-extension/extension/ui.ts b/packages/e2e/test/web-extension/extension/ui.ts index b1ab310b086..4c1e9367954 100644 --- a/packages/e2e/test/web-extension/extension/ui.ts +++ b/packages/e2e/test/web-extension/extension/ui.ts @@ -31,6 +31,7 @@ import { Cardano } from '@cardano-sdk/core'; import { CommunicationType, InMemoryKeyAgent, + KeyPurpose, SerializableInMemoryKeyAgentData, emip3encrypt } from '@cardano-sdk/key-management'; @@ -304,7 +305,8 @@ const createWalletIfNotExistsAndActivate = async (accountIndex: number) => { accountIndex, chainId: env.KEY_MANAGEMENT_PARAMS.chainId, getPassphrase: async () => passphrase, - mnemonicWords + mnemonicWords, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519, logger } ); diff --git a/packages/hardware-ledger/src/LedgerKeyAgent.ts b/packages/hardware-ledger/src/LedgerKeyAgent.ts index 99113244721..69f827f5b23 100644 --- a/packages/hardware-ledger/src/LedgerKeyAgent.ts +++ b/packages/hardware-ledger/src/LedgerKeyAgent.ts @@ -8,6 +8,7 @@ import { KeyAgentBase, KeyAgentDependencies, KeyAgentType, + KeyPurpose, SerializableLedgerKeyAgentData, SignBlobResult, SignTransactionContext, @@ -67,12 +68,14 @@ export interface CreateLedgerKeyAgentProps { accountIndex?: number; communicationType: CommunicationType; deviceConnection?: LedgerConnection | null; + purpose: KeyPurpose; } export interface GetLedgerXpubProps { deviceConnection?: LedgerConnection; communicationType: CommunicationType; accountIndex: number; + purpose: KeyPurpose; } export interface CreateLedgerTransportProps { @@ -434,11 +437,12 @@ export class LedgerKeyAgent extends KeyAgentBase { static async getXpub({ deviceConnection, communicationType, - accountIndex + accountIndex, + purpose }: GetLedgerXpubProps): Promise { try { const recoveredDeviceConnection = await LedgerKeyAgent.checkDeviceConnection(communicationType, deviceConnection); - const derivationPath = `${CardanoKeyConst.PURPOSE}'/${CardanoKeyConst.COIN_TYPE}'/${accountIndex}'`; + const derivationPath = `${purpose}'/${CardanoKeyConst.COIN_TYPE}'/${accountIndex}'`; const extendedPublicKey = await recoveredDeviceConnection.getExtendedPublicKey({ path: str_to_path(derivationPath) // BIP32Path }); @@ -468,7 +472,7 @@ export class LedgerKeyAgent extends KeyAgentBase { * @throws TransportError */ static async createWithDevice( - { chainId, accountIndex = 0, communicationType, deviceConnection }: CreateLedgerKeyAgentProps, + { chainId, accountIndex = 0, communicationType, deviceConnection, purpose }: CreateLedgerKeyAgentProps, dependencies: KeyAgentDependencies ) { const deviceListPaths = await LedgerKeyAgent.getHidDeviceList(communicationType); @@ -479,7 +483,8 @@ export class LedgerKeyAgent extends KeyAgentBase { const extendedAccountPublicKey = await LedgerKeyAgent.getXpub({ accountIndex, communicationType, - deviceConnection: activeDeviceConnection + deviceConnection: activeDeviceConnection, + purpose }); return new LedgerKeyAgent( @@ -488,7 +493,8 @@ export class LedgerKeyAgent extends KeyAgentBase { chainId, communicationType, deviceConnection: activeDeviceConnection, - extendedAccountPublicKey + extendedAccountPublicKey, + purpose }, dependencies ); @@ -539,7 +545,7 @@ export class LedgerKeyAgent extends KeyAgentBase { // TODO: Allow additional key paths async signTransaction( { body, hash }: Cardano.TxBodyWithHash, - { knownAddresses, txInKeyPathMap }: SignTransactionContext + { knownAddresses, txInKeyPathMap, purpose }: SignTransactionContext ): Promise { try { const ledgerTxData = await toLedgerTx(body, { @@ -547,6 +553,7 @@ export class LedgerKeyAgent extends KeyAgentBase { chainId: this.chainId, dRepPublicKey: await this.derivePublicKey(util.DREP_KEY_DERIVATION_PATH), knownAddresses, + purpose, txInKeyPathMap }); diff --git a/packages/hardware-ledger/src/transformers/txIn.ts b/packages/hardware-ledger/src/transformers/txIn.ts index e64cab74d2e..6f8870830cd 100644 --- a/packages/hardware-ledger/src/transformers/txIn.ts +++ b/packages/hardware-ledger/src/transformers/txIn.ts @@ -6,14 +6,18 @@ import { TxInId, util } from '@cardano-sdk/key-management'; const resolveKeyPath = ( txIn: Cardano.TxIn, - { accountIndex, txInKeyPathMap }: LedgerTxTransformerContext + { accountIndex, txInKeyPathMap, purpose }: LedgerTxTransformerContext ): Ledger.BIP32Path | null => { - const utxoKeyPath = txInKeyPathMap[TxInId(txIn)]; - if (utxoKeyPath) { - return util.accountKeyDerivationPathToBip32Path(accountIndex, utxoKeyPath); - } + const txInKeyPath = txInKeyPathMap[TxInId(txIn)]; - return null; + if (!txInKeyPath || txInKeyPath.role === undefined || txInKeyPath.index === undefined) return null; + + const utxoKeyPath = { + ...txInKeyPath, + purpose + }; + + return util.accountKeyDerivationPathToBip32Path(accountIndex, utxoKeyPath); }; export const toTxIn: Transform = (txIn, context) => ({ diff --git a/packages/hardware-ledger/src/transformers/withdrawals.ts b/packages/hardware-ledger/src/transformers/withdrawals.ts index 9df233b2a62..ab782300707 100644 --- a/packages/hardware-ledger/src/transformers/withdrawals.ts +++ b/packages/hardware-ledger/src/transformers/withdrawals.ts @@ -1,13 +1,16 @@ import * as Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano'; import { Cardano } from '@cardano-sdk/core'; -import { CardanoKeyConst, GroupedAddress, util } from '@cardano-sdk/key-management'; +import { CardanoKeyConst, GroupedAddress, KeyPurpose, util } from '@cardano-sdk/key-management'; import { InvalidArgumentError, Transform, areNumbersEqualInConstantTime } from '@cardano-sdk/util'; import { LedgerTxTransformerContext } from '../types'; -const resolveKeyPath = ( - rewardAddress: Cardano.RewardAddress, - knownAddresses: GroupedAddress[] | undefined -): Ledger.BIP32Path | null => { +interface ResolveKeyPathParams { + rewardAddress: Cardano.RewardAddress; + knownAddresses: GroupedAddress[] | undefined; + purpose: KeyPurpose; +} + +const resolveKeyPath = ({ rewardAddress, knownAddresses, purpose }: ResolveKeyPathParams): Ledger.BIP32Path | null => { if (!knownAddresses) return null; const knownAddress = knownAddresses.find( @@ -16,7 +19,7 @@ const resolveKeyPath = ( if (knownAddress && knownAddress.stakeKeyDerivationPath) { return [ - util.harden(CardanoKeyConst.PURPOSE), + util.harden(purpose), util.harden(CardanoKeyConst.COIN_TYPE), util.harden(knownAddress.accountIndex), knownAddress.stakeKeyDerivationPath.role, @@ -35,11 +38,16 @@ export const toWithdrawal: Transform { const txId = '0000000000000000000000000000000000000000000000000000000000000000' as unknown as Cardano.TransactionId; const noAddressesOptions = { knownAddresses: [], + purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} }; @@ -379,7 +380,8 @@ describe('LedgerKeyAgent', () => { communicationType: CommunicationType.Node, extendedAccountPublicKey: Crypto.Bip32PublicKeyHex( '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - ) + ), + purpose: KeyPurpose.STANDARD }, { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), diff --git a/packages/hardware-ledger/test/testData.ts b/packages/hardware-ledger/test/testData.ts index a8a876f7d99..74f47cdeb30 100644 --- a/packages/hardware-ledger/test/testData.ts +++ b/packages/hardware-ledger/test/testData.ts @@ -1,5 +1,5 @@ import * as Crypto from '@cardano-sdk/crypto'; -import { AddressType, KeyRole } from '@cardano-sdk/key-management'; +import { AddressType, KeyPurpose, KeyRole } from '@cardano-sdk/key-management'; import { Base64Blob, HexBlob } from '@cardano-sdk/util'; import { Cardano, Serialization } from '@cardano-sdk/core'; import { LedgerTxTransformerContext } from '../src'; @@ -357,6 +357,7 @@ export const CONTEXT_WITH_KNOWN_ADDRESSES: LedgerTxTransformerContext = { type: AddressType.Internal } ], + purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} }; @@ -367,6 +368,7 @@ export const CONTEXT_WITHOUT_KNOWN_ADDRESSES: LedgerTxTransformerContext = { networkMagic: 999 }, knownAddresses: [], + purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} }; diff --git a/packages/hardware-ledger/test/transformers/certificates.test.ts b/packages/hardware-ledger/test/transformers/certificates.test.ts index 73e1591783a..e45d0faec4e 100644 --- a/packages/hardware-ledger/test/transformers/certificates.test.ts +++ b/packages/hardware-ledger/test/transformers/certificates.test.ts @@ -5,6 +5,7 @@ import { AddressType, CardanoKeyConst, GroupedAddress, + KeyPurpose, KeyRole, TxInId, TxInKeyPathMap, @@ -81,6 +82,7 @@ const mockContext: LedgerTxTransformerContext = { createGroupedAddress(address1, ownRewardAccount, AddressType.External, 0, stakeKeyPath), createGroupedAddress(address2, ownRewardAccount, AddressType.External, 1, stakeKeyPath) ], + purpose: KeyPurpose.STANDARD, sender: undefined, txInKeyPathMap: createTxInKeyPathMapMock([ createGroupedAddress(address1, ownRewardAccount, AddressType.External, 0, stakeKeyPath), diff --git a/packages/hardware-trezor/src/TrezorKeyAgent.ts b/packages/hardware-trezor/src/TrezorKeyAgent.ts index 0c18a1a2067..271a9909d5d 100644 --- a/packages/hardware-trezor/src/TrezorKeyAgent.ts +++ b/packages/hardware-trezor/src/TrezorKeyAgent.ts @@ -8,6 +8,7 @@ import { KeyAgentBase, KeyAgentDependencies, KeyAgentType, + KeyPurpose, SerializableTrezorKeyAgentData, SignBlobResult, SignTransactionContext, @@ -37,12 +38,14 @@ export interface TrezorKeyAgentProps extends Omit { + static async getXpub({ + accountIndex, + communicationType, + purpose + }: GetTrezorXpubProps): Promise { try { await TrezorKeyAgent.checkDeviceConnection(communicationType); - const derivationPath = `m/${CardanoKeyConst.PURPOSE}'/${CardanoKeyConst.COIN_TYPE}'/${accountIndex}'`; + const derivationPath = `m/${purpose}'/${CardanoKeyConst.COIN_TYPE}'/${accountIndex}'`; const trezorConnect = getTrezorConnect(communicationType); const extendedPublicKey = await trezorConnect.cardanoGetPublicKey({ path: derivationPath, @@ -154,13 +161,14 @@ export class TrezorKeyAgent extends KeyAgentBase { } static async createWithDevice( - { chainId, accountIndex = 0, trezorConfig }: CreateTrezorKeyAgentProps, + { chainId, accountIndex = 0, trezorConfig, purpose }: CreateTrezorKeyAgentProps, dependencies: KeyAgentDependencies ) { const isTrezorInitialized = await TrezorKeyAgent.initializeTrezorTransport(trezorConfig); const extendedAccountPublicKey = await TrezorKeyAgent.getXpub({ accountIndex, - communicationType: trezorConfig.communicationType + communicationType: trezorConfig.communicationType, + purpose }); return new TrezorKeyAgent( { @@ -168,6 +176,7 @@ export class TrezorKeyAgent extends KeyAgentBase { chainId, extendedAccountPublicKey, isTrezorInitialized, + purpose, trezorConfig }, dependencies @@ -208,7 +217,7 @@ export class TrezorKeyAgent extends KeyAgentBase { async signTransaction( { body, hash }: Cardano.TxBodyWithHash, - { knownAddresses, txInKeyPathMap }: SignTransactionContext + { knownAddresses, txInKeyPathMap, purpose }: SignTransactionContext ): Promise { try { await this.isTrezorInitialized; @@ -216,6 +225,7 @@ export class TrezorKeyAgent extends KeyAgentBase { accountIndex: this.accountIndex, chainId: this.chainId, knownAddresses, + purpose, txInKeyPathMap }); diff --git a/packages/hardware-trezor/src/transformers/keyPaths.ts b/packages/hardware-trezor/src/transformers/keyPaths.ts index 3b2ab27991e..372b4b966da 100644 --- a/packages/hardware-trezor/src/transformers/keyPaths.ts +++ b/packages/hardware-trezor/src/transformers/keyPaths.ts @@ -9,10 +9,16 @@ export const resolvePaymentKeyPathForTxIn = ( context?: TrezorTxTransformerContext ): BIP32Path | undefined => { if (!context) return; - const txInKeyPath = context?.txInKeyPathMap[TxInId(txIn)]; - if (txInKeyPath) { - return util.accountKeyDerivationPathToBip32Path(context.accountIndex, txInKeyPath); - } + + const txInKeyPath = context.txInKeyPathMap[TxInId(txIn)]; + + if (!txInKeyPath || txInKeyPath.role === undefined || txInKeyPath.index === undefined) return; + const utxoKeyPath = { + ...txInKeyPath, + purpose: context.purpose + }; + + return util.accountKeyDerivationPathToBip32Path(context.accountIndex, utxoKeyPath); }; export const resolveStakeKeyPath = ( diff --git a/packages/hardware-trezor/test/testData.ts b/packages/hardware-trezor/test/testData.ts index 7726bf407a4..63a206166b3 100644 --- a/packages/hardware-trezor/test/testData.ts +++ b/packages/hardware-trezor/test/testData.ts @@ -1,5 +1,5 @@ import * as Crypto from '@cardano-sdk/crypto'; -import { AddressType, GroupedAddress, KeyRole } from '@cardano-sdk/key-management'; +import { AddressType, GroupedAddress, KeyPurpose, KeyRole } from '@cardano-sdk/key-management'; import { Cardano } from '@cardano-sdk/core'; import { HexBlob } from '@cardano-sdk/util'; import { TrezorTxTransformerContext } from '../src'; @@ -152,7 +152,10 @@ export const knownAddress: GroupedAddress = { type: AddressType.Internal }; -export const knownAddressPaymentKeyPath = { index: knownAddress.index, role: Number(knownAddress.type) }; +export const knownAddressPaymentKeyPath = { + index: knownAddress.index, + role: Number(knownAddress.type) +}; export const knownAddressWithoutStakingPath: GroupedAddress = { accountIndex: 0, @@ -170,6 +173,7 @@ export const contextWithKnownAddresses: TrezorTxTransformerContext = { networkMagic: 999 }, knownAddresses: [knownAddress], + purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} }; @@ -180,6 +184,7 @@ export const contextWithKnownAddressesWithoutStakingCredentials: TrezorTxTransfo networkMagic: 999 }, knownAddresses: [knownAddressWithoutStakingPath], + purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} }; @@ -190,6 +195,7 @@ export const contextWithoutKnownAddresses: TrezorTxTransformerContext = { networkMagic: 999 }, knownAddresses: [], + purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} }; diff --git a/packages/key-management/src/InMemoryKeyAgent.ts b/packages/key-management/src/InMemoryKeyAgent.ts index b7fd927cb34..b5cab2f9dd3 100644 --- a/packages/key-management/src/InMemoryKeyAgent.ts +++ b/packages/key-management/src/InMemoryKeyAgent.ts @@ -7,6 +7,7 @@ import { KeyAgentDependencies, KeyAgentType, KeyPair, + KeyPurpose, SerializableInMemoryKeyAgentData, SignBlobResult, SignTransactionContext, @@ -37,6 +38,7 @@ export interface FromBip39MnemonicWordsProps { mnemonic2ndFactorPassphrase?: string; getPassphrase: GetPassphrase; accountIndex?: number; + purpose: KeyPurpose; } const getPassphraseRethrowTypedError = async (getPassphrase: GetPassphrase) => { @@ -55,11 +57,15 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent { this.#getPassphrase = getPassphrase; } - async signBlob({ index, role: type }: AccountKeyDerivationPath, blob: HexBlob): Promise { + async signBlob( + { index, role: type, purpose }: AccountKeyDerivationPath & { purpose: KeyPurpose }, + blob: HexBlob + ): Promise { const rootPrivateKey = await this.#decryptRootPrivateKey(); const accountKey = await deriveAccountPrivateKey({ accountIndex: this.accountIndex, bip32Ed25519: this.bip32Ed25519, + purpose, rootPrivateKey }); @@ -89,7 +95,8 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent { getPassphrase, mnemonicWords, mnemonic2ndFactorPassphrase = '', - accountIndex = 0 + accountIndex = 0, + purpose = KeyPurpose.STANDARD }: FromBip39MnemonicWordsProps, dependencies: KeyAgentDependencies ): Promise { @@ -103,6 +110,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent { const accountPrivateKey = await deriveAccountPrivateKey({ accountIndex, bip32Ed25519: dependencies.bip32Ed25519, + purpose, rootPrivateKey }); @@ -114,7 +122,8 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent { chainId, encryptedRootPrivateKeyBytes: [...encryptedRootPrivateKey], extendedAccountPublicKey, - getPassphrase + getPassphrase, + purpose }, dependencies ); @@ -122,7 +131,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent { async signTransaction( { body, hash }: Cardano.TxBodyWithHash, - { txInKeyPathMap, knownAddresses }: SignTransactionContext, + { txInKeyPathMap, knownAddresses, purpose }: SignTransactionContext, { additionalKeyPaths = [] }: SignTransactionOptions = {} ): Promise { // Possible optimization is casting strings to OpaqueString types directly and skipping validation @@ -139,7 +148,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent { return new Map( await Promise.all( keyPaths.map(async ({ role, index }) => { - const { publicKey, signature } = await this.signBlob({ index, role }, blob); + const { publicKey, signature } = await this.signBlob({ index, purpose, role }, blob); return [publicKey, signature] as const; }) ) diff --git a/packages/key-management/src/KeyAgentBase.ts b/packages/key-management/src/KeyAgentBase.ts index 8e761a3afed..b468c3f0f43 100644 --- a/packages/key-management/src/KeyAgentBase.ts +++ b/packages/key-management/src/KeyAgentBase.ts @@ -5,6 +5,7 @@ import { GroupedAddress, KeyAgent, KeyAgentDependencies, + KeyPurpose, SerializableKeyAgentData, SignBlobResult, SignTransactionContext, @@ -18,6 +19,7 @@ export abstract class KeyAgentBase implements KeyAgent { readonly #serializableData: SerializableKeyAgentData; readonly #bip32Ed25519: Crypto.Bip32Ed25519; readonly #account: Bip32Account; + readonly #purpose: KeyPurpose; get serializableData(): SerializableKeyAgentData { return this.#serializableData; @@ -34,6 +36,9 @@ export abstract class KeyAgentBase implements KeyAgent { get bip32Ed25519(): Crypto.Bip32Ed25519 { return this.#bip32Ed25519; } + get purpose(): KeyPurpose { + return this.#purpose; + } abstract signBlob(derivationPath: AccountKeyDerivationPath, blob: HexBlob): Promise; abstract exportRootPrivateKey(): Promise; diff --git a/packages/key-management/src/cip8/cip30signData.ts b/packages/key-management/src/cip8/cip30signData.ts index 194026964c2..60e77dc9bf4 100644 --- a/packages/key-management/src/cip8/cip30signData.ts +++ b/packages/key-management/src/cip8/cip30signData.ts @@ -1,5 +1,5 @@ import * as Crypto from '@cardano-sdk/crypto'; -import { AccountKeyDerivationPath, GroupedAddress, KeyRole, MessageSender } from '../types'; +import { AccountKeyDerivationPath, GroupedAddress, KeyPurpose, KeyRole, MessageSender } from '../types'; import { AlgorithmId, CBORValue, @@ -65,7 +65,9 @@ const getDerivationPath = async ( if (!knownRewardAddress) throw new Cip30DataSignError(Cip30DataSignErrorCode.ProofGeneration, 'Unknown reward address'); - return knownRewardAddress.stakeKeyDerivationPath || STAKE_KEY_DERIVATION_PATH; + const { role, index } = knownRewardAddress.stakeKeyDerivationPath || STAKE_KEY_DERIVATION_PATH; + + return { index, purpose: KeyPurpose.STANDARD, role }; } const knownAddress = knownAddresses.find(({ address }) => address === signWith); @@ -74,7 +76,7 @@ const getDerivationPath = async ( throw new Cip30DataSignError(Cip30DataSignErrorCode.ProofGeneration, 'Unknown address'); } - return { index: knownAddress.index, role: knownAddress.type as number as KeyRole }; + return { index: knownAddress.index, purpose: KeyPurpose.STANDARD, role: knownAddress.type as number as KeyRole }; }; const createSigStructureHeaders = (addressBytes: Uint8Array) => { diff --git a/packages/key-management/src/types.ts b/packages/key-management/src/types.ts index 5494130eb7c..f38cbfc0750 100644 --- a/packages/key-management/src/types.ts +++ b/packages/key-management/src/types.ts @@ -38,10 +38,16 @@ export enum KeyRole { DRep = 3 } +export enum KeyPurpose { + STANDARD = 1852, + MULTI_SIG = 1854 +} + export interface AccountKeyDerivationPath { role: KeyRole; index: number; } + /** Internal = change address & External = receipt address */ export enum AddressType { /** Change address */ @@ -93,6 +99,7 @@ export interface SerializableKeyAgentDataBase { chainId: Cardano.ChainId; accountIndex: number; extendedAccountPublicKey: Crypto.Bip32PublicKeyHex; + purpose: KeyPurpose; } export interface SerializableInMemoryKeyAgentData extends SerializableKeyAgentDataBase { @@ -167,6 +174,7 @@ export interface SignTransactionContext { txInKeyPathMap: TxInKeyPathMap; knownAddresses: GroupedAddress[]; handleResolutions?: HandleResolution[]; + purpose: KeyPurpose; dRepPublicKey?: Crypto.Ed25519PublicKeyHex; sender?: MessageSender; } @@ -181,6 +189,7 @@ export interface SignDataContext { export interface KeyAgent { get chainId(): Cardano.ChainId; get accountIndex(): number; + get purpose(): KeyPurpose; get serializableData(): SerializableKeyAgentData; get extendedAccountPublicKey(): Crypto.Bip32PublicKeyHex; get bip32Ed25519(): Crypto.Bip32Ed25519; @@ -207,7 +216,6 @@ export interface KeyAgent { * * @param paymentKeyDerivationPath The payment key derivation path. * @param stakeKeyDerivationIndex The stake key index. This field is optional. If not provided it defaults to index 0. - * @param pure If set to true, the key agent will derive a new address without mutating its internal state. */ deriveAddress( paymentKeyDerivationPath: AccountAddressDerivationPath, @@ -224,6 +232,7 @@ export type AsyncKeyAgent = Pick; getExtendedAccountPublicKey(): Promise; getAccountIndex(): Promise; + getKeyPurpose(): Promise; } & Shutdown; export type WitnessOptions = SignTransactionOptions; diff --git a/packages/key-management/src/util/KeyAgentTransactionSigner.ts b/packages/key-management/src/util/KeyAgentTransactionSigner.ts index c8ea903cd6c..9a8177897eb 100644 --- a/packages/key-management/src/util/KeyAgentTransactionSigner.ts +++ b/packages/key-management/src/util/KeyAgentTransactionSigner.ts @@ -32,6 +32,7 @@ export class KeyAgentTransactionSigner implements TransactionSigner { tx, { knownAddresses: [], + purpose: this.#keyAgent.purpose, txInKeyPathMap: {} }, { diff --git a/packages/key-management/src/util/createAsyncKeyAgent.ts b/packages/key-management/src/util/createAsyncKeyAgent.ts index 71a3f5e2470..2a5e70adc13 100644 --- a/packages/key-management/src/util/createAsyncKeyAgent.ts +++ b/packages/key-management/src/util/createAsyncKeyAgent.ts @@ -9,6 +9,7 @@ export const createAsyncKeyAgent = (keyAgent: KeyAgent, onShutdown?: () => void) getBip32Ed25519: () => Promise.resolve(keyAgent.bip32Ed25519), getChainId: () => Promise.resolve(keyAgent.chainId), getExtendedAccountPublicKey: () => Promise.resolve(keyAgent.extendedAccountPublicKey), + getKeyPurpose: () => Promise.resolve(keyAgent.serializableData.purpose), shutdown() { onShutdown?.(); }, diff --git a/packages/key-management/src/util/key.ts b/packages/key-management/src/util/key.ts index 9e1b8ebb47c..81c9789208d 100644 --- a/packages/key-management/src/util/key.ts +++ b/packages/key-management/src/util/key.ts @@ -1,16 +1,28 @@ import * as Crypto from '@cardano-sdk/crypto'; -import { AccountKeyDerivationPath, CardanoKeyConst, Ed25519KeyPair, GroupedAddress, KeyPair, KeyRole } from '../types'; +import { + AccountKeyDerivationPath, + CardanoKeyConst, + Ed25519KeyPair, + GroupedAddress, + KeyPair, + KeyPurpose, + KeyRole +} from '../types'; import { BIP32Path } from '@cardano-sdk/crypto'; export const harden = (num: number): number => 0x80_00_00_00 + num; -export const STAKE_KEY_DERIVATION_PATH: AccountKeyDerivationPath = { +export type AccountKeyDerivationPathWithPurpose = AccountKeyDerivationPath & { purpose: KeyPurpose }; + +export const STAKE_KEY_DERIVATION_PATH: AccountKeyDerivationPathWithPurpose = { index: 0, + purpose: KeyPurpose.STANDARD, role: KeyRole.Stake }; -export const DREP_KEY_DERIVATION_PATH: AccountKeyDerivationPath = { +export const DREP_KEY_DERIVATION_PATH: AccountKeyDerivationPathWithPurpose = { index: 0, + purpose: KeyPurpose.STANDARD, role: KeyRole.DRep }; @@ -26,15 +38,17 @@ export interface DeriveAccountPrivateKeyProps { rootPrivateKey: Crypto.Bip32PrivateKeyHex; accountIndex: number; bip32Ed25519: Crypto.Bip32Ed25519; + purpose: KeyPurpose; } export const deriveAccountPrivateKey = async ({ rootPrivateKey, accountIndex, - bip32Ed25519 + bip32Ed25519, + purpose }: DeriveAccountPrivateKeyProps): Promise => await bip32Ed25519.derivePrivateKey(rootPrivateKey, [ - harden(CardanoKeyConst.PURPOSE), + harden(purpose), harden(CardanoKeyConst.COIN_TYPE), harden(accountIndex) ]); @@ -42,18 +56,20 @@ export const deriveAccountPrivateKey = async ({ // TODO: test /** * Constructs the hardened derivation path for the specified - * account key of an HD wallet as specified in CIP 1852 + * account key of an HD wallet as specified in CIP 1852 or CIP 1854 * https://cips.cardano.org/cips/cip1852/ + * https://cips.cardano.org/cips/cip1854 */ export const accountKeyDerivationPathToBip32Path = ( accountIndex: number, - { index, role }: AccountKeyDerivationPath -): BIP32Path => [harden(CardanoKeyConst.PURPOSE), harden(CardanoKeyConst.COIN_TYPE), harden(accountIndex), role, index]; + { index, role, purpose }: AccountKeyDerivationPathWithPurpose +): BIP32Path => [harden(purpose), harden(CardanoKeyConst.COIN_TYPE), harden(accountIndex), role, index]; /** * Constructs the hardened derivation path of the payment key for the - * given grouped address of an HD wallet as specified in CIP 1852 + * given grouped address of an HD wallet as specified in CIP 1852 or CIP 1854 * https://cips.cardano.org/cips/cip1852/ + * https://cips.cardano.org/cips/cip1854/ */ export const paymentKeyPathFromGroupedAddress = (address: GroupedAddress): BIP32Path => [ harden(CardanoKeyConst.PURPOSE), diff --git a/packages/key-management/test/Bip32Account.test.ts b/packages/key-management/test/Bip32Account.test.ts index 59dffb70469..a576f5dd1c3 100644 --- a/packages/key-management/test/Bip32Account.test.ts +++ b/packages/key-management/test/Bip32Account.test.ts @@ -1,40 +1,63 @@ import * as Crypto from '@cardano-sdk/crypto'; -import { AccountKeyDerivationPath, AddressType, Bip32Account, InMemoryKeyAgent, KeyRole, util } from '../src'; +import { + AccountKeyDerivationPath, + AddressType, + Bip32Account, + InMemoryKeyAgent, + KeyPurpose, + KeyRole, + util +} from '../src'; import { Cardano } from '@cardano-sdk/core'; import { HexBlob } from '@cardano-sdk/util'; import { dummyLogger } from 'ts-log'; +const initializeKeyAgents = async (accountIndex: number, testnetChainId: Cardano.ChainId, purpose: KeyPurpose) => { + const mnemonicWords = util.generateMnemonicWords(); + const getPassphrase = jest.fn().mockResolvedValue(Buffer.from('password')); + const keyAgentDependencies = { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), logger: dummyLogger }; + const testnetKeyAgent = await InMemoryKeyAgent.fromBip39MnemonicWords( + { + accountIndex, + chainId: testnetChainId, + getPassphrase, + mnemonicWords, + purpose + }, + keyAgentDependencies + ); + const mainnetKeyAgent = await InMemoryKeyAgent.fromBip39MnemonicWords( + { + accountIndex, + chainId: Cardano.ChainIds.Mainnet, + getPassphrase, + mnemonicWords, + purpose + }, + keyAgentDependencies + ); + + const testnetAccount = new Bip32Account(testnetKeyAgent.serializableData); + const mainnetAccount = new Bip32Account(mainnetKeyAgent.serializableData); + + return { mainnetAccount, testnetAccount }; +}; + describe('Bip32Account', () => { const accountIndex = 1; const testnetChainId = Cardano.ChainIds.Preview; let testnetAccount: Bip32Account; let mainnetAccount: Bip32Account; + let multiSigTestnetAccount: Bip32Account; + let multiSigMainnetAccount: Bip32Account; beforeEach(async () => { - const mnemonicWords = util.generateMnemonicWords(); - const getPassphrase = jest.fn().mockResolvedValue(Buffer.from('password')); - const keyAgentDependencies = { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), logger: dummyLogger }; - const testnetKeyAgent = await InMemoryKeyAgent.fromBip39MnemonicWords( - { - accountIndex, - chainId: testnetChainId, - getPassphrase, - mnemonicWords - }, - keyAgentDependencies - ); - const mainnetKeyAgent = await InMemoryKeyAgent.fromBip39MnemonicWords( - { - accountIndex, - chainId: Cardano.ChainIds.Mainnet, - getPassphrase, - mnemonicWords - }, - keyAgentDependencies - ); - - testnetAccount = new Bip32Account(testnetKeyAgent.serializableData); - mainnetAccount = new Bip32Account(mainnetKeyAgent.serializableData); + const standardKeyAgents = await initializeKeyAgents(accountIndex, testnetChainId, KeyPurpose.STANDARD); + const multiSigKeyAgents = await initializeKeyAgents(accountIndex, testnetChainId, KeyPurpose.MULTI_SIG); + testnetAccount = standardKeyAgents.testnetAccount; + mainnetAccount = standardKeyAgents.mainnetAccount; + multiSigTestnetAccount = multiSigKeyAgents.testnetAccount; + multiSigMainnetAccount = multiSigKeyAgents.mainnetAccount; }); it('derivePublicKey resolves with ed25519 public key', async () => { @@ -42,7 +65,9 @@ describe('Bip32Account', () => { await testnetAccount.derivePublicKey({ index: 0, role: KeyRole.DRep }), await testnetAccount.derivePublicKey({ index: 1, role: KeyRole.External }), await mainnetAccount.derivePublicKey({ index: 2, role: KeyRole.Internal }), - await mainnetAccount.derivePublicKey({ index: 3, role: KeyRole.Stake }) + await mainnetAccount.derivePublicKey({ index: 3, role: KeyRole.Stake }), + await multiSigTestnetAccount.derivePublicKey({ index: 0, role: KeyRole.External }), + await multiSigMainnetAccount.derivePublicKey({ index: 1, role: KeyRole.Stake }) ]; for (const key of derivedKeys) { const hexKey = key.hex(); diff --git a/packages/key-management/test/InMemoryKeyAgent.test.ts b/packages/key-management/test/InMemoryKeyAgent.test.ts index a965d1b1b91..62eb708a9e1 100644 --- a/packages/key-management/test/InMemoryKeyAgent.test.ts +++ b/packages/key-management/test/InMemoryKeyAgent.test.ts @@ -1,5 +1,13 @@ import * as Crypto from '@cardano-sdk/crypto'; -import { AddressType, GroupedAddress, InMemoryKeyAgent, KeyRole, SerializableInMemoryKeyAgentData, util } from '../src'; +import { + AddressType, + GroupedAddress, + InMemoryKeyAgent, + KeyPurpose, + KeyRole, + SerializableInMemoryKeyAgentData, + util +} from '../src'; import { Cardano } from '@cardano-sdk/core'; import { HexBlob } from '@cardano-sdk/util'; import { dummyLogger } from 'ts-log'; @@ -20,7 +28,8 @@ describe('InMemoryKeyAgent', () => { { chainId: Cardano.ChainIds.Preview, getPassphrase, - mnemonicWords + mnemonicWords, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519, logger: dummyLogger } ); @@ -46,7 +55,8 @@ describe('InMemoryKeyAgent', () => { chainId: Cardano.ChainIds.Preview, getPassphrase, mnemonic2ndFactorPassphrase: 'passphrase', - mnemonicWords + mnemonicWords, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519, logger: dummyLogger } ); @@ -77,7 +87,10 @@ describe('InMemoryKeyAgent', () => { }); test('signBlob', async () => { - const { publicKey, signature } = await keyAgent.signBlob({ index: 0, role: KeyRole.Internal }, HexBlob('abc123')); + const { publicKey, signature } = await keyAgent.signBlob( + { index: 0, purpose: KeyPurpose.STANDARD, role: KeyRole.Internal }, + HexBlob('abc123') + ); expect(typeof publicKey).toBe('string'); expect(typeof signature).toBe('string'); }); @@ -97,6 +110,7 @@ describe('InMemoryKeyAgent', () => { }, { knownAddresses, + purpose: KeyPurpose.STANDARD, txInKeyPathMap } ); @@ -158,10 +172,12 @@ describe('InMemoryKeyAgent', () => { await util.deriveAccountPrivateKey({ accountIndex: 0, bip32Ed25519, + purpose: KeyPurpose.STANDARD, rootPrivateKey: Crypto.Bip32PrivateKeyHex(yoroiRootPrivateKeyHex) }) ), - getPassphrase + getPassphrase, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519, logger: dummyLogger } ); @@ -174,7 +190,8 @@ describe('InMemoryKeyAgent', () => { { chainId: Cardano.ChainIds.Preview, getPassphrase, - mnemonicWords: yoroiMnemonic + mnemonicWords: yoroiMnemonic, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519, logger: dummyLogger } ); @@ -192,7 +209,8 @@ describe('InMemoryKeyAgent', () => { { chainId: Cardano.ChainIds.Preview, getPassphrase, - mnemonicWords: michaelMnemonic + mnemonicWords: michaelMnemonic, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519, logger: dummyLogger } ); @@ -203,7 +221,7 @@ describe('InMemoryKeyAgent', () => { body: { fee: 10n, inputs: [], outputs: [], validityInterval: {} }, hash: Cardano.TransactionId('0000000000000000000000000000000000000000000000000000000000000000') }, - { knownAddresses: [], txInKeyPathMap: {} } + { knownAddresses: [], purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} } ); expect( signature.has(Crypto.Ed25519PublicKeyHex('0b1c96fad4179d7910bd9485ac28c4c11368c83d18d01b29d4cf84d8ff6a06c4')) @@ -255,11 +273,13 @@ describe('InMemoryKeyAgent', () => { await util.deriveAccountPrivateKey({ accountIndex: 0, bip32Ed25519, + purpose: KeyPurpose.STANDARD, rootPrivateKey: Crypto.Bip32PrivateKeyHex(daedalusRootPrivateKeyHex) }) ), // daedelus enforces min length of 10 - getPassphrase: jest.fn().mockResolvedValue(Buffer.from('nMmys*X002')) + getPassphrase: jest.fn().mockResolvedValue(Buffer.from('nMmys*X002')), + purpose: KeyPurpose.STANDARD }, { bip32Ed25519, logger: dummyLogger } ); @@ -278,7 +298,8 @@ describe('InMemoryKeyAgent', () => { { chainId: Cardano.ChainIds.Preview, getPassphrase, - mnemonicWords: daedelusMnemonic24 + mnemonicWords: daedelusMnemonic24, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519, logger: dummyLogger } ); diff --git a/packages/key-management/test/KeyAgentBase.test.ts b/packages/key-management/test/KeyAgentBase.test.ts index b30ac3dfc9f..9f93f8c1943 100644 --- a/packages/key-management/test/KeyAgentBase.test.ts +++ b/packages/key-management/test/KeyAgentBase.test.ts @@ -1,6 +1,5 @@ -/* eslint-disable sonarjs/no-duplicate-string */ import * as Crypto from '@cardano-sdk/crypto'; -import { AddressType, KeyAgentBase, KeyAgentType, KeyRole, SerializableInMemoryKeyAgentData } from '../src'; +import { AddressType, KeyAgentBase, KeyAgentType, KeyPurpose, KeyRole, SerializableInMemoryKeyAgentData } from '../src'; import { Cardano } from '@cardano-sdk/core'; import { dummyLogger } from 'ts-log'; @@ -35,13 +34,11 @@ describe('KeyAgentBase', () => { extendedAccountPublicKey: Crypto.Bip32PublicKeyHex( // eslint-disable-next-line max-len 'fc5ab25e830b67c47d0a17411bf7fdabf711a597fb6cf04102734b0a2934ceaaa65ff5e7c52498d52c07b8ddfcd436fc2b4d2775e2984a49d0c79f65ceee4779' - ) + ), + purpose: KeyPurpose.STANDARD }); }); - // eslint-disable-next-line max-len - // extpubkey: return ''; - test('deriveAddress derives a new address', async () => { const index = 1; const type = AddressType.External; diff --git a/packages/key-management/test/mocks/testKeyAgent.ts b/packages/key-management/test/mocks/testKeyAgent.ts index b2f7ba00741..4bd9f5535b9 100644 --- a/packages/key-management/test/mocks/testKeyAgent.ts +++ b/packages/key-management/test/mocks/testKeyAgent.ts @@ -1,5 +1,5 @@ import { Cardano } from '@cardano-sdk/core'; -import { InMemoryKeyAgent, KeyAgentDependencies, util } from '../../src'; +import { InMemoryKeyAgent, KeyAgentDependencies, KeyPurpose, util } from '../../src'; import { mockKeyAgentDependencies } from './mockKeyAgentDependencies'; export const getPassphrase = jest.fn(async () => Buffer.from('password')); @@ -9,7 +9,8 @@ export const testKeyAgent = async (dependencies: KeyAgentDependencies | undefine { chainId: Cardano.ChainIds.Preview, getPassphrase, - mnemonicWords: util.generateMnemonicWords() + mnemonicWords: util.generateMnemonicWords(), + purpose: KeyPurpose.STANDARD }, dependencies ); diff --git a/packages/key-management/test/util/createWitnesser.test.ts b/packages/key-management/test/util/createWitnesser.test.ts index c4ec60b3c2d..297de1a60c0 100644 --- a/packages/key-management/test/util/createWitnesser.test.ts +++ b/packages/key-management/test/util/createWitnesser.test.ts @@ -1,4 +1,4 @@ -import { AsyncKeyAgent, SignBlobResult, Witnesser, util } from '../../src'; +import { AsyncKeyAgent, KeyPurpose, SignBlobResult, Witnesser, util } from '../../src'; import { Cardano, Serialization } from '@cardano-sdk/core'; import { HexBlob } from '@cardano-sdk/util'; @@ -15,7 +15,7 @@ describe('createBip32Ed25519Witnesser', () => { }); it('signBlob is unchanged', async () => { - const keyDerivationPath = { index: 0, role: 0 }; + const keyDerivationPath = { index: 0, purpose: KeyPurpose.STANDARD, role: 0 }; const blob = HexBlob('abc123'); const result = {} as SignBlobResult; asyncKeyAgent.signBlob.mockResolvedValueOnce(result); @@ -39,7 +39,7 @@ describe('createBip32Ed25519Witnesser', () => { hash: Cardano.TransactionId('3643bb5fe745ba0532977f82ecf54699963c97adef2626f7c780225d218e9ba6') }; - const options = { knownAddresses: [], txInKeyPathMap: {} }; + const options = { knownAddresses: [], purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} }; const result = new Map(); asyncKeyAgent.signTransaction.mockResolvedValueOnce(result); diff --git a/packages/key-management/test/util/key.test.ts b/packages/key-management/test/util/key.test.ts index bf63184d041..6f5922a3dee 100644 --- a/packages/key-management/test/util/key.test.ts +++ b/packages/key-management/test/util/key.test.ts @@ -1,4 +1,4 @@ -import { AddressType, GroupedAddress, KeyRole } from '../../src'; +import { AddressType, GroupedAddress, KeyPurpose, KeyRole } from '../../src'; import { Cardano } from '@cardano-sdk/core'; import { paymentKeyPathFromGroupedAddress, stakeKeyPathFromGroupedAddress } from '../../src/util'; @@ -12,6 +12,7 @@ export const rewardAccount = Cardano.RewardAccount(rewardKey); const stakeKeyDerivationPath = { index: 0, + purpose: KeyPurpose.STANDARD, role: KeyRole.Stake }; diff --git a/packages/key-management/test/util/ownSignaturePaths.test.ts b/packages/key-management/test/util/ownSignaturePaths.test.ts index 55f4b8b3b22..a0f77d8d5e4 100644 --- a/packages/key-management/test/util/ownSignaturePaths.test.ts +++ b/packages/key-management/test/util/ownSignaturePaths.test.ts @@ -1,6 +1,6 @@ /* eslint-disable sonarjs/no-duplicate-string */ import * as Crypto from '@cardano-sdk/crypto'; -import { AccountKeyDerivationPath, AddressType, GroupedAddress, KeyRole, TxInId, util } from '../../src'; +import { AccountKeyDerivationPath, AddressType, GroupedAddress, KeyPurpose, KeyRole, TxInId, util } from '../../src'; import { Cardano } from '@cardano-sdk/core'; export const stakeKeyPath = { @@ -77,9 +77,18 @@ describe('KeyManagement.util.ownSignaturePaths', () => { expect( util.ownSignatureKeyPaths(txBody, knownAddresses, { - [TxInId(inputs[0])]: { index: knownAddresses[0].index, role: Number(knownAddresses[0].type) }, - [TxInId(inputs[1])]: { index: knownAddresses[1].index, role: Number(knownAddresses[1].type) }, - [TxInId(inputs[2])]: { index: knownAddresses[0].index, role: Number(knownAddresses[0].type) } + [TxInId(inputs[0])]: { + index: knownAddresses[0].index, + role: Number(knownAddresses[0].type) + }, + [TxInId(inputs[1])]: { + index: knownAddresses[1].index, + role: Number(knownAddresses[1].type) + }, + [TxInId(inputs[2])]: { + index: knownAddresses[0].index, + role: Number(knownAddresses[0].type) + } }) ).toEqual([ { @@ -443,7 +452,9 @@ describe('KeyManagement.util.ownSignaturePaths', () => { inputs: [{}, {}, {}] } as Cardano.TxBody; - expect(util.ownSignatureKeyPaths(txBody, knownAddresses, {})).toEqual([ + const addresses = util.ownSignatureKeyPaths(txBody, knownAddresses, {}); + + expect(addresses).toEqual([ { index: 0, role: KeyRole.Stake }, { index: 1, role: KeyRole.Stake }, { index: 2, role: KeyRole.Stake }, @@ -511,7 +522,7 @@ describe('KeyManagement.util.ownSignaturePaths', () => { } as Cardano.TxBody; expect(util.ownSignatureKeyPaths(txBody, [knownAddress1], {}, dRepKeyHash)).toEqual([ - { index: 0, role: KeyRole.DRep } + { index: 0, purpose: KeyPurpose.STANDARD, role: KeyRole.DRep } ]); }); @@ -530,7 +541,7 @@ describe('KeyManagement.util.ownSignaturePaths', () => { } as Cardano.TxBody; expect(util.ownSignatureKeyPaths(txBody, [knownAddress1], {}, dRepKeyHash)).toEqual([ - { index: 0, role: KeyRole.DRep } + { index: 0, purpose: KeyPurpose.STANDARD, role: KeyRole.DRep } ]); }); @@ -550,7 +561,7 @@ describe('KeyManagement.util.ownSignaturePaths', () => { } as Cardano.TxBody; expect(util.ownSignatureKeyPaths(txBody, [knownAddress1], {}, dRepKeyHash)).toEqual([ - { index: 0, role: KeyRole.DRep } + { index: 0, purpose: KeyPurpose.STANDARD, role: KeyRole.DRep } ]); }); @@ -571,7 +582,7 @@ describe('KeyManagement.util.ownSignaturePaths', () => { } as Cardano.TxBody; expect(util.ownSignatureKeyPaths(txBody, [knownAddress1], {}, dRepKeyHash)).toEqual([ - { index: 0, role: KeyRole.DRep } + { index: 0, purpose: KeyPurpose.STANDARD, role: KeyRole.DRep } ]); }); }); diff --git a/packages/key-management/test/util/stubSignTransaction.test.ts b/packages/key-management/test/util/stubSignTransaction.test.ts index 95cd8c34900..4802c1e5b0f 100644 --- a/packages/key-management/test/util/stubSignTransaction.test.ts +++ b/packages/key-management/test/util/stubSignTransaction.test.ts @@ -1,7 +1,7 @@ import { Cardano } from '@cardano-sdk/core'; import { Ed25519PublicKey, Ed25519PublicKeyHex } from '@cardano-sdk/crypto'; -import { GroupedAddress, util } from '../../src'; +import { GroupedAddress, KeyPurpose, util } from '../../src'; jest.mock('../../src/util/ownSignatureKeyPaths'); const { ownSignatureKeyPaths } = jest.requireMock('../../src/util/ownSignatureKeyPaths'); @@ -17,7 +17,7 @@ describe('KeyManagement.util.stubSignTransaction', () => { expect( ( await util.stubSignTransaction({ - context: { dRepPublicKey, knownAddresses, txInKeyPathMap }, + context: { dRepPublicKey, knownAddresses, purpose: KeyPurpose.STANDARD, txInKeyPathMap }, txBody }) ).size @@ -25,7 +25,7 @@ describe('KeyManagement.util.stubSignTransaction', () => { expect( ( await util.stubSignTransaction({ - context: { dRepPublicKey, knownAddresses, txInKeyPathMap }, + context: { dRepPublicKey, knownAddresses, purpose: KeyPurpose.STANDARD, txInKeyPathMap }, txBody }) ).size diff --git a/packages/tx-construction/src/tx-builder/TxBuilder.ts b/packages/tx-construction/src/tx-builder/TxBuilder.ts index 1126f4374af..3cbfd7549df 100644 --- a/packages/tx-construction/src/tx-builder/TxBuilder.ts +++ b/packages/tx-construction/src/tx-builder/TxBuilder.ts @@ -3,6 +3,7 @@ import * as Crypto from '@cardano-sdk/crypto'; import { AddressType, Bip32Account, + KeyPurpose, SignTransactionOptions, TransactionSigner, WitnessedTx, @@ -332,6 +333,9 @@ export class GenericTxBuilder implements TxBuilder { const extraSigners = this.partialExtraSigners && [...this.partialExtraSigners]; const partialSigningOptions = this.partialSigningOptions && { ...this.partialSigningOptions, extraSigners }; + // TODO: Not sure about this. What is the best way to pass purpose to TxBuilder? + const purpose = KeyPurpose.STANDARD; + if (this.partialAuxiliaryData) { this.partialTxBody.auxiliaryDataHash = Cardano.computeAuxiliaryDataHash(this.partialAuxiliaryData); } @@ -429,6 +433,7 @@ export class GenericTxBuilder implements TxBuilder { signingContext: { handleResolutions: this.#handleResolutions, knownAddresses: ownAddresses, + purpose, txInKeyPathMap: await util.createTxInKeyPathMap(body, ownAddresses, this.#dependencies.inputResolver) }, signingOptions: { ...partialSigningOptions, extraSigners }, diff --git a/packages/tx-construction/src/tx-builder/initializeTx.ts b/packages/tx-construction/src/tx-builder/initializeTx.ts index 077f1d223a9..b58bf16f58f 100644 --- a/packages/tx-construction/src/tx-builder/initializeTx.ts +++ b/packages/tx-construction/src/tx-builder/initializeTx.ts @@ -1,13 +1,12 @@ -import { StaticChangeAddressResolver, roundRobinRandomImprove } from '@cardano-sdk/input-selection'; - import { Cardano, Serialization } from '@cardano-sdk/core'; import { GreedyTxEvaluator } from './GreedyTxEvaluator'; import { InitializeTxProps, InitializeTxResult } from '../types'; +import { KeyPurpose, util } from '@cardano-sdk/key-management'; import { RedeemersByType, defaultSelectionConstraints } from '../input-selection'; +import { StaticChangeAddressResolver, roundRobinRandomImprove } from '@cardano-sdk/input-selection'; import { TxBuilderDependencies } from './types'; import { createPreInputSelectionTxBody, includeChangeAndInputs } from '../createTransactionInternals'; import { ensureValidityInterval } from '../ensureValidityInterval'; -import { util } from '@cardano-sdk/key-management'; export const initializeTx = async ( props: InitializeTxProps, @@ -88,6 +87,7 @@ export const initializeTx = async ( dRepPublicKey, handleResolutions: props.handleResolutions ?? [], knownAddresses: addresses, + purpose: KeyPurpose.STANDARD, txInKeyPathMap: await util.createTxInKeyPathMap(unwitnessedTx.body, addresses, inputResolver) }; diff --git a/packages/tx-construction/test/tx-builder/TxBuilder.bootstrap.test.ts b/packages/tx-construction/test/tx-builder/TxBuilder.bootstrap.test.ts index ef102c1569c..c90cd173a5d 100644 --- a/packages/tx-construction/test/tx-builder/TxBuilder.bootstrap.test.ts +++ b/packages/tx-construction/test/tx-builder/TxBuilder.bootstrap.test.ts @@ -1,4 +1,4 @@ -import { AddressType, Bip32Account, InMemoryKeyAgent, util } from '@cardano-sdk/key-management'; +import { AddressType, Bip32Account, InMemoryKeyAgent, KeyPurpose, util } from '@cardano-sdk/key-management'; import { Cardano } from '@cardano-sdk/core'; import { GenericTxBuilder, OutputValidation, TxBuilderProviders } from '../../src'; import { SodiumBip32Ed25519 } from '@cardano-sdk/crypto'; @@ -35,7 +35,8 @@ describe.each([ { chainId: Cardano.ChainIds.Preview, getPassphrase: async () => Buffer.from([]), - mnemonicWords: util.generateMnemonicWords() + mnemonicWords: util.generateMnemonicWords(), + purpose: KeyPurpose.STANDARD }, { bip32Ed25519: new SodiumBip32Ed25519(), logger } ) diff --git a/packages/tx-construction/test/tx-builder/TxBuilder.test.ts b/packages/tx-construction/test/tx-builder/TxBuilder.test.ts index 6b42ad3a369..a7e432e660f 100644 --- a/packages/tx-construction/test/tx-builder/TxBuilder.test.ts +++ b/packages/tx-construction/test/tx-builder/TxBuilder.test.ts @@ -7,6 +7,7 @@ import { Bip32Account, GroupedAddress, InMemoryKeyAgent, + KeyPurpose, SignTransactionOptions, TransactionSigner, util @@ -75,7 +76,8 @@ describe.each([ { chainId: Cardano.ChainIds.Preprod, getPassphrase: async () => Buffer.from('passphrase'), - mnemonicWords: util.generateMnemonicWords() + mnemonicWords: util.generateMnemonicWords(), + purpose: KeyPurpose.STANDARD }, { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), logger: dummyLogger } ); diff --git a/packages/tx-construction/test/tx-builder/TxBuilderDelegatePortfolio.test.ts b/packages/tx-construction/test/tx-builder/TxBuilderDelegatePortfolio.test.ts index f4eef6e2260..c3752371bd4 100644 --- a/packages/tx-construction/test/tx-builder/TxBuilderDelegatePortfolio.test.ts +++ b/packages/tx-construction/test/tx-builder/TxBuilderDelegatePortfolio.test.ts @@ -1,6 +1,13 @@ /* eslint-disable sonarjs/no-duplicate-string */ import * as Crypto from '@cardano-sdk/crypto'; -import { AddressType, Bip32Account, GroupedAddress, InMemoryKeyAgent, util } from '@cardano-sdk/key-management'; +import { + AddressType, + Bip32Account, + GroupedAddress, + InMemoryKeyAgent, + KeyPurpose, + util +} from '@cardano-sdk/key-management'; import { Cardano } from '@cardano-sdk/core'; import { GenericTxBuilder, @@ -144,7 +151,8 @@ describe('TxBuilder/delegatePortfolio', () => { { chainId: Cardano.ChainIds.Preprod, getPassphrase: async () => Buffer.from('passphrase'), - mnemonicWords: util.generateMnemonicWords() + mnemonicWords: util.generateMnemonicWords(), + purpose: KeyPurpose.STANDARD }, { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), logger: dummyLogger } ); diff --git a/packages/tx-construction/test/tx-builder/TxBuilderPlutusScripts.test.ts b/packages/tx-construction/test/tx-builder/TxBuilderPlutusScripts.test.ts index 52672e73777..c1b4869117f 100644 --- a/packages/tx-construction/test/tx-builder/TxBuilderPlutusScripts.test.ts +++ b/packages/tx-construction/test/tx-builder/TxBuilderPlutusScripts.test.ts @@ -1,6 +1,6 @@ /* eslint-disable sonarjs/no-duplicate-string */ import * as Crypto from '@cardano-sdk/crypto'; -import { AddressType, Bip32Account, InMemoryKeyAgent, util } from '@cardano-sdk/key-management'; +import { AddressType, Bip32Account, InMemoryKeyAgent, KeyPurpose, util } from '@cardano-sdk/key-management'; import { Cardano, coalesceValueQuantities } from '@cardano-sdk/core'; import { DatumResolver, @@ -199,7 +199,8 @@ describe('TxBuilder/plutusScripts', () => { { chainId: Cardano.ChainIds.Preprod, getPassphrase: async () => Buffer.from('passphrase'), - mnemonicWords: util.generateMnemonicWords() + mnemonicWords: util.generateMnemonicWords(), + purpose: KeyPurpose.STANDARD }, { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), logger: dummyLogger } ); diff --git a/packages/wallet/src/Wallets/BaseWallet.ts b/packages/wallet/src/Wallets/BaseWallet.ts index ef6a7febea9..420b62b697f 100644 --- a/packages/wallet/src/Wallets/BaseWallet.ts +++ b/packages/wallet/src/Wallets/BaseWallet.ts @@ -87,7 +87,15 @@ import { tap, throwError } from 'rxjs'; -import { Bip32Account, GroupedAddress, WitnessedTx, Witnesser, cip8, util } from '@cardano-sdk/key-management'; +import { + Bip32Account, + GroupedAddress, + KeyPurpose, + WitnessedTx, + Witnesser, + cip8, + util +} from '@cardano-sdk/key-management'; import { ChangeAddressResolver, InputSelector, roundRobinRandomImprove } from '@cardano-sdk/input-selection'; import { Cip30DataSignature } from '@cardano-sdk/dapp-connector'; import { Ed25519PublicKey, Ed25519PublicKeyHex } from '@cardano-sdk/crypto'; @@ -601,6 +609,7 @@ export class BaseWallet implements ObservableWallet { ...signingContext, dRepPublicKey, knownAddresses, + purpose: signingContext?.purpose ?? KeyPurpose.STANDARD, txInKeyPathMap: await util.createTxInKeyPathMap(tx.body, knownAddresses, this.util) }; diff --git a/packages/wallet/test/hardware/ledger/LedgerKeyAgent.integration.test.ts b/packages/wallet/test/hardware/ledger/LedgerKeyAgent.integration.test.ts index 84ebd7167ad..4a2ad4d6a0d 100644 --- a/packages/wallet/test/hardware/ledger/LedgerKeyAgent.integration.test.ts +++ b/packages/wallet/test/hardware/ledger/LedgerKeyAgent.integration.test.ts @@ -1,6 +1,5 @@ import * as Crypto from '@cardano-sdk/crypto'; - -import { Bip32Account, CommunicationType, KeyAgent, util } from '@cardano-sdk/key-management'; +import { Bip32Account, CommunicationType, KeyAgent, KeyPurpose, util } from '@cardano-sdk/key-management'; import { Cardano } from '@cardano-sdk/core'; import { LedgerKeyAgent } from '@cardano-sdk/hardware-ledger'; import { ObservableWallet, createPersonalWallet, restoreKeyAgent } from '../../../src'; @@ -52,7 +51,8 @@ describe('LedgerKeyAgent+BaseWallet', () => { const freshKeyAgent = await LedgerKeyAgent.createWithDevice( { chainId: Cardano.ChainIds.Preprod, - communicationType: CommunicationType.Node + communicationType: CommunicationType.Node, + purpose: KeyPurpose.STANDARD }, keyAgentDependencies ); diff --git a/packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts b/packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts index 1591769e689..c0248152839 100644 --- a/packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts +++ b/packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts @@ -4,6 +4,7 @@ import { AddressType, Bip32Account, CommunicationType, + KeyPurpose, SerializableLedgerKeyAgentData, util } from '@cardano-sdk/key-management'; @@ -77,7 +78,8 @@ describe('LedgerKeyAgent', () => { ledgerKeyAgent = await LedgerKeyAgent.createWithDevice( { chainId: Cardano.ChainIds.Preprod, - communicationType: CommunicationType.Node + communicationType: CommunicationType.Node, + purpose: KeyPurpose.STANDARD }, { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), logger } ); @@ -93,7 +95,8 @@ describe('LedgerKeyAgent', () => { accountIndex: 5, chainId: Cardano.ChainIds.Preprod, communicationType: CommunicationType.Node, - deviceConnection: ledgerKeyAgent.deviceConnection + deviceConnection: ledgerKeyAgent.deviceConnection, + purpose: KeyPurpose.STANDARD }, mockKeyAgentDependencies() ); @@ -206,7 +209,11 @@ describe('LedgerKeyAgent', () => { ...txInternals, hash: 'non-matching' as unknown as Cardano.TransactionId }, - { knownAddresses: await firstValueFrom(wallet.addresses$), txInKeyPathMap: {} } + { + knownAddresses: await firstValueFrom(wallet.addresses$), + purpose: KeyPurpose.STANDARD, + txInKeyPathMap: {} + } ) ).rejects.toThrow(); }); diff --git a/packages/wallet/test/hardware/trezor/TrezorKeyAgent.integration.test.ts b/packages/wallet/test/hardware/trezor/TrezorKeyAgent.integration.test.ts index ee53d944c7c..4aa4a5e5f9f 100644 --- a/packages/wallet/test/hardware/trezor/TrezorKeyAgent.integration.test.ts +++ b/packages/wallet/test/hardware/trezor/TrezorKeyAgent.integration.test.ts @@ -1,5 +1,5 @@ import * as Crypto from '@cardano-sdk/crypto'; -import { Bip32Account, CommunicationType, KeyAgent, util } from '@cardano-sdk/key-management'; +import { Bip32Account, CommunicationType, KeyAgent, KeyPurpose, util } from '@cardano-sdk/key-management'; import { Cardano } from '@cardano-sdk/core'; import { ObservableWallet, createPersonalWallet, restoreKeyAgent } from '../../../src'; import { TrezorKeyAgent } from '@cardano-sdk/hardware-trezor'; @@ -51,6 +51,7 @@ describe('TrezorKeyAgent+BaseWallet', () => { const freshKeyAgent = await TrezorKeyAgent.createWithDevice( { chainId: Cardano.ChainIds.Preprod, + purpose: KeyPurpose.STANDARD, trezorConfig: { communicationType: CommunicationType.Node, manifest: { diff --git a/packages/wallet/test/hardware/trezor/TrezorKeyAgent.test.ts b/packages/wallet/test/hardware/trezor/TrezorKeyAgent.test.ts index 2917020fba9..349ea6d2e98 100644 --- a/packages/wallet/test/hardware/trezor/TrezorKeyAgent.test.ts +++ b/packages/wallet/test/hardware/trezor/TrezorKeyAgent.test.ts @@ -3,6 +3,7 @@ import { AddressType, Bip32Account, CommunicationType, + KeyPurpose, SerializableTrezorKeyAgentData, util } from '@cardano-sdk/key-management'; @@ -36,6 +37,7 @@ describe('TrezorKeyAgent', () => { trezorKeyAgent = await TrezorKeyAgent.createWithDevice( { chainId: Cardano.ChainIds.Preprod, + purpose: KeyPurpose.STANDARD, trezorConfig }, { bip32Ed25519: new Crypto.SodiumBip32Ed25519(), logger } @@ -300,7 +302,7 @@ describe('TrezorKeyAgent', () => { body: txInternals.body, hash: 'non-matching' as unknown as Cardano.TransactionId }, - { knownAddresses: await firstValueFrom(wallet.addresses$), txInKeyPathMap: {} } + { knownAddresses: await firstValueFrom(wallet.addresses$), purpose: KeyPurpose.STANDARD, txInKeyPathMap: {} } ) ).rejects.toThrow(); }); @@ -441,6 +443,7 @@ describe('TrezorKeyAgent', () => { { accountIndex: 5, chainId: Cardano.ChainIds.Preprod, + purpose: KeyPurpose.STANDARD, trezorConfig }, mockKeyAgentDependencies() diff --git a/packages/wallet/test/services/KeyAgent/restoreKeyAgent.test.ts b/packages/wallet/test/services/KeyAgent/restoreKeyAgent.test.ts index 94c046608c6..bc4bb676c69 100644 --- a/packages/wallet/test/services/KeyAgent/restoreKeyAgent.test.ts +++ b/packages/wallet/test/services/KeyAgent/restoreKeyAgent.test.ts @@ -5,6 +5,7 @@ import { GetPassphrase, KeyAgentDependencies, KeyAgentType, + KeyPurpose, SerializableInMemoryKeyAgentData, SerializableLedgerKeyAgentData, SerializableTrezorKeyAgentData, @@ -41,7 +42,8 @@ describe('KeyManagement/restoreKeyAgent', () => { accountIndex: 0, chainId: Cardano.ChainIds.Preview, encryptedRootPrivateKeyBytes, - extendedAccountPublicKey + extendedAccountPublicKey, + purpose: KeyPurpose.STANDARD }; // eslint-disable-next-line unicorn/consistent-function-scoping const getPassphrase: GetPassphrase = async () => Buffer.from('password'); @@ -76,7 +78,8 @@ describe('KeyManagement/restoreKeyAgent', () => { extendedAccountPublicKey: Crypto.Bip32PublicKeyHex( // eslint-disable-next-line max-len 'fc5ab25e830b67c47d0a17411bf7fdabf711a597fb6cf04102734b0a2934ceaaa65ff5e7c52498d52c07b8ddfcd436fc2b4d2775e2984a49d0c79f65ceee4779' - ) + ), + purpose: KeyPurpose.STANDARD }; it('can restore key manager from valid data', async () => { @@ -94,6 +97,7 @@ describe('KeyManagement/restoreKeyAgent', () => { // eslint-disable-next-line max-len 'fc5ab25e830b67c47d0a17411bf7fdabf711a597fb6cf04102734b0a2934ceaaa65ff5e7c52498d52c07b8ddfcd436fc2b4d2775e2984a49d0c79f65ceee4779' ), + purpose: KeyPurpose.STANDARD, trezorConfig: { communicationType: CommunicationType.Node, manifest: { diff --git a/packages/wallet/test/util.ts b/packages/wallet/test/util.ts index 4ffe4bd3e20..7f8fa2b9f64 100644 --- a/packages/wallet/test/util.ts +++ b/packages/wallet/test/util.ts @@ -1,6 +1,6 @@ import * as Crypto from '@cardano-sdk/crypto'; import { Cardano, Serialization, TxCBOR } from '@cardano-sdk/core'; -import { GroupedAddress, InMemoryKeyAgent, WitnessedTx, util } from '@cardano-sdk/key-management'; +import { GroupedAddress, InMemoryKeyAgent, KeyPurpose, WitnessedTx, util } from '@cardano-sdk/key-management'; import { HexBlob } from '@cardano-sdk/util'; import { Observable, catchError, filter, firstValueFrom, throwError, timeout } from 'rxjs'; import { ObservableWallet, OutgoingTx, WalletUtil } from '../src'; @@ -74,7 +74,8 @@ export const createAsyncKeyAgent = async () => { chainId: Cardano.ChainIds.Preview, getPassphrase: async () => Buffer.from([]), - mnemonicWords: util.generateMnemonicWords() + mnemonicWords: util.generateMnemonicWords(), + purpose: KeyPurpose.STANDARD }, { bip32Ed25519: new SodiumBip32Ed25519(), @@ -103,6 +104,7 @@ export const signTx = async ({ ), { knownAddresses, + purpose: KeyPurpose.STANDARD, txInKeyPathMap: await util.createTxInKeyPathMap(tx.body, knownAddresses, walletUtil) } ); diff --git a/packages/web-extension/src/keyAgent/util.ts b/packages/web-extension/src/keyAgent/util.ts index 03adbf6d9de..05995409a8a 100644 --- a/packages/web-extension/src/keyAgent/util.ts +++ b/packages/web-extension/src/keyAgent/util.ts @@ -10,6 +10,7 @@ export const keyAgentProperties: RemoteApiProperties = { getBip32Ed25519: RemoteApiPropertyType.MethodReturningPromise, getChainId: RemoteApiPropertyType.MethodReturningPromise, getExtendedAccountPublicKey: RemoteApiPropertyType.MethodReturningPromise, + getKeyPurpose: RemoteApiPropertyType.MethodReturningPromise, signBlob: RemoteApiPropertyType.MethodReturningPromise, signTransaction: RemoteApiPropertyType.MethodReturningPromise }; diff --git a/packages/web-extension/src/walletManager/SigningCoordinator/SigningCoordinator.ts b/packages/web-extension/src/walletManager/SigningCoordinator/SigningCoordinator.ts index 6e8f16b0934..280ea6ca2da 100644 --- a/packages/web-extension/src/walletManager/SigningCoordinator/SigningCoordinator.ts +++ b/packages/web-extension/src/walletManager/SigningCoordinator/SigningCoordinator.ts @@ -143,6 +143,8 @@ export class SigningCoordinator reject(new errors.AuthenticationError(reason)) @@ -164,7 +166,8 @@ export class SigningCoordinator passphrase + getPassphrase: async () => passphrase, + purpose }) ); clearPassphrase(passphrase); @@ -190,12 +193,14 @@ export class SigningCoordinator throwMaybeWrappedWithNoRejectError(error, options)), diff --git a/packages/web-extension/src/walletManager/SigningCoordinator/types.ts b/packages/web-extension/src/walletManager/SigningCoordinator/types.ts index 2b41ca06dde..9f05d7a2917 100644 --- a/packages/web-extension/src/walletManager/SigningCoordinator/types.ts +++ b/packages/web-extension/src/walletManager/SigningCoordinator/types.ts @@ -1,5 +1,6 @@ import { AccountKeyDerivationPath, + KeyPurpose, SignBlobResult, SignDataContext, SignTransactionContext, @@ -14,6 +15,7 @@ export type RequestContext; accountIndex: number; chainId: Cardano.ChainId; + purpose: KeyPurpose; }; export type RequestBase = { diff --git a/packages/web-extension/src/walletManager/util.ts b/packages/web-extension/src/walletManager/util.ts index efee2940dba..bd976576771 100644 --- a/packages/web-extension/src/walletManager/util.ts +++ b/packages/web-extension/src/walletManager/util.ts @@ -2,6 +2,7 @@ import * as Crypto from '@cardano-sdk/crypto'; import { AccountKeyDerivationPath, AsyncKeyAgent, + KeyPurpose, SignDataContext, SignTransactionContext, TransactionSigner, @@ -283,6 +284,7 @@ const createScriptWitness = async { - signBlob: async (derivationPath: AccountKeyDerivationPath, blob: HexBlob, context: SignDataContext) => + signBlob: async ( + derivationPath: AccountKeyDerivationPath & { purpose: KeyPurpose }, + blob: HexBlob, + context: SignDataContext + ) => await signingCoordinatorApi.signData( { blob, @@ -315,6 +321,7 @@ export const buildBip32Witnesser = { const requestContext = { accountIndex: 0, chainId: Cardano.ChainIds.Preprod, + purpose: KeyPurpose.STANDARD, wallet }; let passphrase: Uint8Array; @@ -69,7 +71,11 @@ describe('SigningCoordinator', () => { // eslint-disable-next-line max-len '84a60081825820260aed6e7a24044b1254a87a509468a649f522a4e54e830ac10f27ea7b5ec61f01018383581d70b429738bd6cc58b5c7932d001aa2bd05cfea47020a556c8c753d44361a004c4b40582007845f8f3841996e3d8157954e2f5e2fb90465f27112fc5fe9056d916fae245b82583900b1814238b0d287a8a46ce7348c6ad79ab8995b0e6d46010e2d9e1c68042f1946335c498d2e7556c5c647c4649c6a69d2b645cd1428a339ba1a0463676982583900b1814238b0d287a8a46ce7348c6ad79ab8995b0e6d46010e2d9e1c68042f1946335c498d2e7556c5c647c4649c6a69d2b645cd1428a339ba821a00177a6ea2581c648823ffdad1610b4162f4dbc87bd47f6f9cf45d772ddef661eff198a5447742544319271044774554481a0031f9194577444f47451a0056898d4577555344431a000fc589467753484942411a000103c2581c659ab0b5658687c2e74cd10dba8244015b713bf503b90557769d77a7a14a57696e675269646572731a02269552021a0002e665031a01353f84081a013531740b58204107eada931c72a600a6e3305bd22c7aeb9ada7c3f6823b155f4db85de36a69aa20081825820e686ade5bc97372f271fd2abc06cfd96c24b3d9170f9459de1d8e3dd8fd385575840653324a9dddad004f05a8ac99fa2d1811af5f00543591407fb5206cfe9ac91bb1412404323fa517e0e189684cd3592e7f74862e3f16afbc262519abec958180c0481d8799fd8799fd8799fd8799f581cb1814238b0d287a8a46ce7348c6ad79ab8995b0e6d46010e2d9e1c68ffd8799fd8799fd8799f581c042f1946335c498d2e7556c5c647c4649c6a69d2b645cd1428a339baffffffff581cb1814238b0d287a8a46ce7348c6ad79ab8995b0e6d46010e2d9e1c681b000001863784a12ed8799fd8799f4040ffd8799f581c648823ffdad1610b4162f4dbc87bd47f6f9cf45d772ddef661eff1984577444f4745ffffffd8799fd87980190c8efffff5f6' ); - const signContext: SignTransactionContext = { knownAddresses: [], txInKeyPathMap: {} }; + const signContext: SignTransactionContext = { + knownAddresses: [], + purpose: KeyPurpose.STANDARD, + txInKeyPathMap: {} + }; it('rejects given invalid tx cbor', async () => { await expect(