Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for cip 1854 #1317

Merged
merged 2 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/e2e/src/util/createMockKeyAgent.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,12 +18,14 @@ export const createMockKeyAgent = (deriveAddressesReturn: GroupedAddress[] = [])
derivePublicKey: jest.fn(),
exportRootPrivateKey: jest.fn(),
extendedAccountPublicKey,
purpose: KeyPurpose.STANDARD,
serializableData: {
__typename: KeyAgentType.InMemory,
accountIndex,
chainId,
encryptedRootPrivateKeyBytes: [],
extendedAccountPublicKey
extendedAccountPublicKey,
purpose: KeyPurpose.STANDARD
},
signBlob: jest.fn(),
signTransaction: jest.fn()
Expand Down
9 changes: 6 additions & 3 deletions packages/e2e/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 The key purpose.
*/
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 }
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 = {
Expand Down
8 changes: 7 additions & 1 deletion packages/e2e/test/wallet_epoch_0/SharedWallet/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Crypto from '@cardano-sdk/crypto';
import {
AccountKeyDerivationPath,
KeyAgent,
KeyPurpose,
KeyRole,
SignBlobResult,
SignDataContext,
Expand Down Expand Up @@ -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.MULTI_SIG
);

const pubKey = await keyAgent.derivePublicKey(DERIVATION_PATH);

Expand Down
27 changes: 21 additions & 6 deletions packages/hardware-ledger/src/LedgerKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
KeyAgentBase,
KeyAgentDependencies,
KeyAgentType,
KeyPurpose,
SerializableLedgerKeyAgentData,
SignBlobResult,
SignTransactionContext,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -434,11 +437,12 @@ export class LedgerKeyAgent extends KeyAgentBase {
static async getXpub({
deviceConnection,
communicationType,
accountIndex
accountIndex,
purpose
}: GetLedgerXpubProps): Promise<Crypto.Bip32PublicKeyHex> {
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
});
Expand Down Expand Up @@ -468,7 +472,13 @@ export class LedgerKeyAgent extends KeyAgentBase {
* @throws TransportError
*/
static async createWithDevice(
{ chainId, accountIndex = 0, communicationType, deviceConnection }: CreateLedgerKeyAgentProps,
{
chainId,
accountIndex = 0,
communicationType,
deviceConnection,
purpose = KeyPurpose.STANDARD
}: CreateLedgerKeyAgentProps,
dependencies: KeyAgentDependencies
) {
const deviceListPaths = await LedgerKeyAgent.getHidDeviceList(communicationType);
Expand All @@ -479,7 +489,8 @@ export class LedgerKeyAgent extends KeyAgentBase {
const extendedAccountPublicKey = await LedgerKeyAgent.getXpub({
accountIndex,
communicationType,
deviceConnection: activeDeviceConnection
deviceConnection: activeDeviceConnection,
purpose
});

return new LedgerKeyAgent(
Expand All @@ -488,7 +499,8 @@ export class LedgerKeyAgent extends KeyAgentBase {
chainId,
communicationType,
deviceConnection: activeDeviceConnection,
extendedAccountPublicKey
extendedAccountPublicKey,
purpose
},
dependencies
);
Expand Down Expand Up @@ -536,7 +548,10 @@ export class LedgerKeyAgent extends KeyAgentBase {
return TransactionSigningMode.ORDINARY_TRANSACTION;
}

// TODO: Allow additional key paths
// TODO: LW-10571 - Allow additional key paths. This is necessary for multi-signature wallets
// hardware devices inspect the transaction to determine which keys to use for signing,
// however, multi sig transaction do not reference the CIP-1854 directly, but rather the script hash
// so we need to be able to instruct the HW to sign the transaction with arbitrary keys.
async signTransaction(
{ body, hash }: Cardano.TxBodyWithHash,
{ knownAddresses, txInKeyPathMap }: SignTransactionContext
Expand Down
5 changes: 3 additions & 2 deletions packages/hardware-ledger/test/LedgerKeyAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Crypto from '@cardano-sdk/crypto';
import * as Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano';
import { Ada, InvalidDataReason } from '@cardano-foundation/ledgerjs-hw-app-cardano';
import { Cardano } from '@cardano-sdk/core';
import { CardanoKeyConst, CommunicationType, util } from '@cardano-sdk/key-management';
import { CardanoKeyConst, CommunicationType, KeyPurpose, util } from '@cardano-sdk/key-management';
import { LedgerKeyAgent } from '../src';
import { dummyLogger } from 'ts-log';
import { poolId, poolParameters, pureAdaTxOut, stakeKeyHash, txIn, txOutWithDatum } from './testData';
Expand Down Expand Up @@ -379,7 +379,8 @@ describe('LedgerKeyAgent', () => {
communicationType: CommunicationType.Node,
extendedAccountPublicKey: Crypto.Bip32PublicKeyHex(
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
)
),
purpose: KeyPurpose.STANDARD
},
{
bip32Ed25519: new Crypto.SodiumBip32Ed25519(),
Expand Down
17 changes: 13 additions & 4 deletions packages/hardware-trezor/src/TrezorKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
KeyAgentBase,
KeyAgentDependencies,
KeyAgentType,
KeyPurpose,
SerializableTrezorKeyAgentData,
SignBlobResult,
SignTransactionContext,
Expand Down Expand Up @@ -37,12 +38,14 @@ export interface TrezorKeyAgentProps extends Omit<SerializableTrezorKeyAgentData
export interface GetTrezorXpubProps {
accountIndex: number;
communicationType: CommunicationType;
purpose: KeyPurpose;
}

export interface CreateTrezorKeyAgentProps {
chainId: Cardano.ChainId;
accountIndex?: number;
trezorConfig: TrezorConfig;
purpose?: KeyPurpose;
}

export type TrezorConnectInstanceType = typeof TrezorConnectNode | typeof TrezorConnectWeb;
Expand Down Expand Up @@ -135,10 +138,14 @@ export class TrezorKeyAgent extends KeyAgentBase {
}
}

static async getXpub({ accountIndex, communicationType }: GetTrezorXpubProps): Promise<Crypto.Bip32PublicKeyHex> {
static async getXpub({
accountIndex,
communicationType,
purpose
}: GetTrezorXpubProps): Promise<Crypto.Bip32PublicKeyHex> {
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,
Expand All @@ -154,20 +161,22 @@ export class TrezorKeyAgent extends KeyAgentBase {
}

static async createWithDevice(
{ chainId, accountIndex = 0, trezorConfig }: CreateTrezorKeyAgentProps,
{ chainId, accountIndex = 0, trezorConfig, purpose = KeyPurpose.STANDARD }: 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(
{
accountIndex,
chainId,
extendedAccountPublicKey,
isTrezorInitialized,
purpose,
trezorConfig
},
dependencies
Expand Down
10 changes: 8 additions & 2 deletions packages/key-management/src/InMemoryKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
KeyAgentDependencies,
KeyAgentType,
KeyPair,
KeyPurpose,
SerializableInMemoryKeyAgentData,
SignBlobResult,
SignTransactionContext,
Expand Down Expand Up @@ -37,6 +38,7 @@ export interface FromBip39MnemonicWordsProps {
mnemonic2ndFactorPassphrase?: string;
getPassphrase: GetPassphrase;
accountIndex?: number;
purpose?: KeyPurpose;
}

const getPassphraseRethrowTypedError = async (getPassphrase: GetPassphrase) => {
Expand All @@ -60,6 +62,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
const accountKey = await deriveAccountPrivateKey({
accountIndex: this.accountIndex,
bip32Ed25519: this.bip32Ed25519,
purpose: this.purpose,
rootPrivateKey
});

Expand Down Expand Up @@ -89,7 +92,8 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
getPassphrase,
mnemonicWords,
mnemonic2ndFactorPassphrase = '',
accountIndex = 0
accountIndex = 0,
purpose = KeyPurpose.STANDARD
}: FromBip39MnemonicWordsProps,
dependencies: KeyAgentDependencies
): Promise<InMemoryKeyAgent> {
Expand All @@ -103,6 +107,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
const accountPrivateKey = await deriveAccountPrivateKey({
accountIndex,
bip32Ed25519: dependencies.bip32Ed25519,
purpose,
rootPrivateKey
});

Expand All @@ -114,7 +119,8 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
chainId,
encryptedRootPrivateKeyBytes: [...encryptedRootPrivateKey],
extendedAccountPublicKey,
getPassphrase
getPassphrase,
purpose
},
dependencies
);
Expand Down
5 changes: 5 additions & 0 deletions packages/key-management/src/KeyAgentBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
GroupedAddress,
KeyAgent,
KeyAgentDependencies,
KeyPurpose,
SerializableKeyAgentData,
SignBlobResult,
SignTransactionContext,
Expand Down Expand Up @@ -35,6 +36,10 @@ export abstract class KeyAgentBase implements KeyAgent {
return this.#bip32Ed25519;
}

get purpose(): KeyPurpose {
return this.serializableData.purpose || KeyPurpose.STANDARD;
}

abstract signBlob(derivationPath: AccountKeyDerivationPath, blob: HexBlob): Promise<SignBlobResult>;
abstract exportRootPrivateKey(): Promise<Crypto.Bip32PrivateKeyHex>;
abstract signTransaction(
Expand Down
6 changes: 6 additions & 0 deletions packages/key-management/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export enum KeyRole {
DRep = 3
}

export enum KeyPurpose {
STANDARD = 1852,
MULTI_SIG = 1854
}
export interface AccountKeyDerivationPath {
role: KeyRole;
index: number;
Expand Down Expand Up @@ -93,6 +97,7 @@ export interface SerializableKeyAgentDataBase {
chainId: Cardano.ChainId;
accountIndex: number;
extendedAccountPublicKey: Crypto.Bip32PublicKeyHex;
purpose?: KeyPurpose;
}

export interface SerializableInMemoryKeyAgentData extends SerializableKeyAgentDataBase {
Expand Down Expand Up @@ -184,6 +189,7 @@ export interface KeyAgent {
get serializableData(): SerializableKeyAgentData;
get extendedAccountPublicKey(): Crypto.Bip32PublicKeyHex;
get bip32Ed25519(): Crypto.Bip32Ed25519;
get purpose(): KeyPurpose | undefined;

/**
* @throws AuthenticationError
Expand Down
Loading
Loading