-
Notifications
You must be signed in to change notification settings - Fork 63
feat: support all Trezor derivation types #1650
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| # Trezor Master Key Generation Types | ||
|
|
||
| This package now supports multiple master key generation algorithms for Trezor hardware wallets, allowing compatibility with different wallet implementations and enabling seamless integration with various existing wallet types. | ||
|
|
||
| ## Supported Master Key Generation Types | ||
|
|
||
| ### 1. ICARUS | ||
| - Standard CIP-3 wallet compatibility mode | ||
|
|
||
| ### 2. ICARUS_TREZOR | ||
| - CIP-3 variant with 24-word mnemonic compatibility | ||
| - This is Trezor's internal default (SDK passes no `derivationType`) | ||
|
|
||
| ### 3. LEDGER | ||
| - Ledger hardware wallet compatibility mode | ||
| - Use this if your wallet was originally created on a Ledger device | ||
| - Enables access to Ledger-generated accounts on Trezor hardware | ||
|
|
||
| ## Key Concept | ||
|
|
||
| All types use the same derivation path but different master key generation algorithms from the mnemonic. The `derivationType` tells Trezor which algorithm to use. | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Importing Types and Constants | ||
|
|
||
| All types and constants are fully exportable from their respective packages: | ||
|
|
||
| ```typescript | ||
| // Core constants | ||
| import { | ||
| HD_WALLET_CIP_ID, | ||
| Cardano | ||
| } from '@cardano-sdk/core'; | ||
|
|
||
| // Key management types | ||
| import { | ||
| TrezorConfig, | ||
| KeyPurpose, | ||
| MasterKeyGeneration | ||
| } from '@cardano-sdk/key-management'; | ||
|
|
||
| // Hardware Trezor implementation | ||
| import { TrezorKeyAgent } from '@cardano-sdk/hardware-trezor'; | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ### Basic Configuration | ||
|
|
||
| ```typescript | ||
| import { TrezorConfig, MasterKeyGeneration } from '@cardano-sdk/key-management'; | ||
|
|
||
| const trezorConfig: TrezorConfig = { | ||
| communicationType: 'web', | ||
| manifest: { | ||
| email: '[email protected]', | ||
| appUrl: 'https://myapp.com' | ||
| }, | ||
| // Use CIP-3 master key generation | ||
| derivationType: 'ICARUS' | ||
| }; | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ### Creating a Trezor Key Agent | ||
|
|
||
| ```typescript | ||
| import { TrezorKeyAgent } from '@cardano-sdk/hardware-trezor'; | ||
| import { Cardano } from '@cardano-sdk/core'; | ||
| import { createBip32Ed25519 } from '@cardano-sdk/crypto'; | ||
|
|
||
| const dependencies = { | ||
| logger: console, // or your preferred logger | ||
| bip32Ed25519: await createBip32Ed25519() | ||
| }; | ||
|
|
||
| const keyAgent = await TrezorKeyAgent.createWithDevice({ | ||
| chainId: Cardano.ChainIds.Mainnet, | ||
| accountIndex: 0, | ||
| trezorConfig: { | ||
| communicationType: 'web', | ||
| manifest: { | ||
| email: '[email protected]', | ||
| appUrl: 'https://myapp.com' | ||
| }, | ||
| derivationType: 'ICARUS' | ||
| } | ||
| }, dependencies); | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ## Discovery & UX Guidelines | ||
|
|
||
| ### Automatic Discovery Pattern | ||
|
|
||
| When pairing a Trezor device, follow this discovery pattern to find existing accounts: | ||
|
|
||
| 1. **Try ICARUS first** - Most common for software wallets | ||
| 2. **Try ICARUS_TREZOR** - If balance not found and user has 24-word mnemonic | ||
| 3. **Try LEDGER** - If user confirms wallet was originally created on Ledger device | ||
|
|
||
| This is why many wallet UIs show a "Derivation type override" dropdown - to help users discover their existing accounts. | ||
|
|
||
| ### Manual Selection During Onboarding | ||
|
|
||
| For applications that prefer explicit user control, consider exposing derivation type selection during the onboarding process: | ||
|
|
||
| ```typescript | ||
| // Example onboarding flow | ||
| const derivationTypeOptions = [ | ||
| { value: 'ICARUS', label: 'Software Wallet', description: 'Most common for wallets created with software applications' }, | ||
| { value: 'ICARUS_TREZOR', label: 'Trezor Default', description: 'Trezor\'s internal default (24-word mnemonic compatible)' }, | ||
| { value: 'LEDGER', label: 'Ledger Hardware Wallet', description: 'For wallets originally created on Ledger devices' } | ||
| ]; | ||
|
|
||
| // Let user select during onboarding | ||
| const selectedDerivationType = await showDerivationTypeSelection(derivationTypeOptions); | ||
|
|
||
| const keyAgent = await TrezorKeyAgent.createWithDevice({ | ||
| chainId: Cardano.ChainIds.Mainnet, | ||
| trezorConfig: { | ||
| communicationType: 'web', | ||
| manifest: { email: '[email protected]', appUrl: 'https://myapp.com' }, | ||
| derivationType: selectedDerivationType | ||
| } | ||
| }, dependencies); | ||
| ``` | ||
|
|
||
| **Benefits of Manual Selection:** | ||
| - **User Control**: Users explicitly choose their derivation type | ||
| - **Multi-Wallet Support**: Users can create multiple wallets with different derivation types | ||
| - **Transparency**: Clear understanding of which derivation type is being used | ||
| - **No Guessing**: Eliminates the need for automatic discovery patterns | ||
|
|
||
| **When to Use Manual Selection:** | ||
| - Applications that prioritize user control and transparency | ||
| - Multi-wallet applications where users might have accounts with different derivation types | ||
| - Enterprise applications where explicit configuration is preferred | ||
| - When users have used multiple wallet types and need to access different accounts | ||
|
|
||
| ## Implementation Details | ||
|
|
||
| When a non-default derivation type is specified, the SDK sets the appropriate `derivationType` in the `cardanoSignTransaction` call. For the default type, no `derivationType` is sent to Trezor. | ||
|
|
||
| ## Backward Compatibility | ||
|
|
||
| Existing wallets without a `derivationType` configuration will continue to work as before. No changes are required for existing users. | ||
|
|
||
|
|
||
|
|
||
| ## References | ||
|
|
||
| - **[CIP-1852](https://cips.cardano.org/cip/CIP-1852)**: HD paths and role meanings | ||
| - **[CIP-3](https://cips.cardano.org/cip/CIP-3)**: Master key generation and 24-word compatibility note | ||
| - **[Trezor Forum](https://forum.trezor.io/t/cardano-ada-transaction-signing-error/10466)**: Community discussion on derivation types | ||
| - **[Cardano Stack Exchange](https://cardano.stackexchange.com/questions/5977/how-does-ledger-generate-the-public-keys)**: Ledger key generation details | ||
| - **[Cardano Foundation](https://cardano-foundation.github.io/cardano-wallet/concepts/master-key-generation)**: Master key generation background | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,17 +46,75 @@ const createWallet = async (keyAgent: KeyAgent) => { | |
| const getAddress = async (wallet: ObservableWallet) => (await firstValueFrom(wallet.addresses$))[0].address; | ||
|
|
||
| describe('TrezorKeyAgent+BaseWallet', () => { | ||
| test('creating and restoring TrezorKeyAgent wallet', async () => { | ||
| const keyAgentDependencies = { bip32Ed25519: await Crypto.SodiumBip32Ed25519.create(), logger }; | ||
| let keyAgentDependencies: { bip32Ed25519: Crypto.Bip32Ed25519; logger: typeof logger }; | ||
|
|
||
| const TEST_APP_URL = 'https://your.application.com'; | ||
| const TEST_EMAIL = '[email protected]'; | ||
|
|
||
| beforeAll(async () => { | ||
| keyAgentDependencies = { bip32Ed25519: await Crypto.SodiumBip32Ed25519.create(), logger }; | ||
| }); | ||
|
|
||
| test('creating and restoring TrezorKeyAgent wallet with default derivation type', async () => { | ||
| const freshKeyAgent = await TrezorKeyAgent.createWithDevice( | ||
| { | ||
| chainId: Cardano.ChainIds.Preprod, | ||
| trezorConfig: { | ||
| communicationType: CommunicationType.Node, | ||
| manifest: { | ||
| appUrl: TEST_APP_URL, | ||
| email: TEST_EMAIL | ||
| } | ||
| // No derivationType specified - uses Trezor's default (ICARUS_TREZOR) | ||
| } | ||
| }, | ||
| keyAgentDependencies | ||
| ); | ||
| const freshWallet = await createWallet(freshKeyAgent); | ||
|
|
||
| const restoredKeyAgent = await restoreKeyAgent(freshKeyAgent.serializableData, keyAgentDependencies); | ||
| const restoredWallet = await createWallet(restoredKeyAgent); | ||
|
|
||
| expect(await getAddress(freshWallet)).toEqual(await getAddress(restoredWallet)); | ||
| freshWallet.shutdown(); | ||
| restoredWallet.shutdown(); | ||
| }); | ||
|
|
||
| test('creating TrezorKeyAgent wallet with ICARUS derivation type', async () => { | ||
| const freshKeyAgent = await TrezorKeyAgent.createWithDevice( | ||
| { | ||
| chainId: Cardano.ChainIds.Preprod, | ||
| trezorConfig: { | ||
| communicationType: CommunicationType.Node, | ||
| derivationType: 'ICARUS', | ||
| manifest: { | ||
| appUrl: TEST_APP_URL, | ||
| email: TEST_EMAIL | ||
| } | ||
| } | ||
| }, | ||
| keyAgentDependencies | ||
| ); | ||
| const freshWallet = await createWallet(freshKeyAgent); | ||
|
|
||
| const restoredKeyAgent = await restoreKeyAgent(freshKeyAgent.serializableData, keyAgentDependencies); | ||
| const restoredWallet = await createWallet(restoredKeyAgent); | ||
|
|
||
| expect(await getAddress(freshWallet)).toEqual(await getAddress(restoredWallet)); | ||
| freshWallet.shutdown(); | ||
| restoredWallet.shutdown(); | ||
| }); | ||
|
|
||
| test('creating TrezorKeyAgent wallet with LEDGER derivation type', async () => { | ||
| const freshKeyAgent = await TrezorKeyAgent.createWithDevice( | ||
| { | ||
| chainId: Cardano.ChainIds.Preprod, | ||
| trezorConfig: { | ||
| communicationType: CommunicationType.Node, | ||
| derivationType: 'LEDGER', | ||
| manifest: { | ||
| appUrl: 'https://your.application.com', | ||
| email: '[email protected]' | ||
| appUrl: TEST_APP_URL, | ||
| email: TEST_EMAIL | ||
| } | ||
| } | ||
| }, | ||
|
|
@@ -71,4 +129,71 @@ describe('TrezorKeyAgent+BaseWallet', () => { | |
| freshWallet.shutdown(); | ||
| restoredWallet.shutdown(); | ||
| }); | ||
|
|
||
| test('different derivation types produce different addresses', async () => { | ||
| const defaultKeyAgent = await TrezorKeyAgent.createWithDevice( | ||
| { | ||
| chainId: Cardano.ChainIds.Preprod, | ||
| trezorConfig: { | ||
| communicationType: CommunicationType.Node, | ||
| manifest: { | ||
| appUrl: TEST_APP_URL, | ||
| email: TEST_EMAIL | ||
| } | ||
| } | ||
| }, | ||
| keyAgentDependencies | ||
| ); | ||
|
|
||
| const icarusKeyAgent = await TrezorKeyAgent.createWithDevice( | ||
| { | ||
| chainId: Cardano.ChainIds.Preprod, | ||
| trezorConfig: { | ||
| communicationType: CommunicationType.Node, | ||
| derivationType: 'ICARUS', | ||
| manifest: { | ||
| appUrl: TEST_APP_URL, | ||
| email: TEST_EMAIL | ||
| } | ||
| } | ||
| }, | ||
| keyAgentDependencies | ||
| ); | ||
|
|
||
| const defaultWallet = await createWallet(defaultKeyAgent); | ||
| const icarusWallet = await createWallet(icarusKeyAgent); | ||
|
|
||
| const defaultAddress = await getAddress(defaultWallet); | ||
| const icarusAddress = await getAddress(icarusWallet); | ||
|
|
||
| // Different derivation types should produce different addresses | ||
| expect(defaultAddress).not.toEqual(icarusAddress); | ||
|
|
||
| defaultWallet.shutdown(); | ||
| icarusWallet.shutdown(); | ||
| }); | ||
|
|
||
| test('backward compatibility - existing wallets without derivation type continue to work', async () => { | ||
| // Simulate an existing wallet configuration (no derivationType) | ||
| const existingWalletConfig = { | ||
| chainId: Cardano.ChainIds.Preprod, | ||
| trezorConfig: { | ||
| communicationType: CommunicationType.Node, | ||
| manifest: { | ||
| appUrl: 'https://your.application.com', | ||
| email: '[email protected]' | ||
| } | ||
| // No derivationType - this is how existing wallets were configured | ||
| } | ||
| }; | ||
|
|
||
| const keyAgent = await TrezorKeyAgent.createWithDevice(existingWalletConfig, keyAgentDependencies); | ||
| const wallet = await createWallet(keyAgent); | ||
|
|
||
| // Should work exactly as before | ||
| expect(await getAddress(wallet)).toBeDefined(); | ||
| expect(keyAgent.trezorConfig.derivationType).toBeUndefined(); | ||
|
|
||
| wallet.shutdown(); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.