Version: next
This documentation is for an unreleased version of Taquito. Features and APIs may change before the final release. View the latest stable version .
Ledger Signer
Written by Roxane Letourneau & Maxwell Ward
The Ledger Signer implements the Signer interface of Taquito, allowing you to sign operation from a Ledger hardware device.
You need to have the Tezos Wallet app installed and opened on your Ledger device when using the Ledger Signer.
You first need to import the desired transport from the LedgerJs library. The Ledger Signer has currently been tested with @ledgerhq/hw-transport-node-hid for Node-based applications and with @ledgerhq/hw-transport-webhid for web applications.
@ledgerhq/hw-transport-webhid is only supported on Chromium based browsers and has to be enabled by a specific configuration flag (chrome://flags/#enable-experimental-web-platform-features).
You can pass an instance of the transport of your choice to your Ledger Signer as follows:
import TransportWebHID from "@ledgerhq/hw-transport-webhid";
import { LedgerSigner } from '@taquito/ledger-signer';
const transport = await TransportWebHID.create();
const ledgerSigner = new LedgerSigner(transport);
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid';
import { LedgerSigner } from '@taquito/ledger-signer';
const transport = await TransportNodeHid.create();
const ledgerSigner = new LedgerSigner(transport);
The constructor of the LedgerSigner class can take three other parameters. If none are specified, the default values are used.
-
path(default:"44'/1729'/0'/0'") UseHDPathTemplateto specify an account index (e.g.,HDPathTemplate(1)="44'/1729'/1'/0'"), or provide a complete custom path. More details about paths. -
prompt(default:true) If true, you will be prompted on your Ledger device to confirm sending your public key. -
derivationType(default:DerivationType.ED25519) Options:ED25519,BIP32_ED25519(tz1),SECP256K1(tz2), orP256(tz3)
import { LedgerSigner, DerivationType, HDPathTemplate } from '@taquito/ledger-signer';
const ledgerSigner = new LedgerSigner(
transport, //required
HDPathTemplate(1), // path optional (equivalent to "44'/1729'/1'/0'")
true, // prompt optional
DerivationType.ED25519 // derivationType optional
);
Usage
import { LedgerSigner } from '@taquito/ledger-signer';
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid';
import { TezosToolkit } from '@taquito/taquito';
const Tezos = new TezosToolkit('https://YOUR_PREFERRED_RPC_URL');
const transport = await TransportNodeHid.create();
const ledgerSigner = new LedgerSigner(transport);
Tezos.setProvider({ signer: ledgerSigner });
//Get the public key and the public key hash from the Ledger
const publicKey = await Tezos.signer.publicKey();
const publicKeyHash = await Tezos.signer.publicKeyHash();
Once configuring the connection like above, you can begin signing operations. You can use your configured Ledger signer with both the Contract API or the Wallet API as normal. If you try the following example, you will be asked on your Ledger device to confirm the transaction before sending it.
const amount = 0.5;
const address = 'tz1h3rQ8wBxFd8L9B3d7Jhaawu6Z568XU3xY';
console.log(`Transferring ${amount} ꜩ to ${address}...`);
try {
const op = await Tezos.contract.transfer({ to: address, amount: amount });
console.log(`Waiting for ${op.hash} to be confirmed...`);
await op.confirmation(1);
console.log(`Operation injected: https://shadownet.tzkt.io/${op.hash}`);
} catch (error) {
console.log(`Error: ${error} ${JSON.stringify(error, null, 2)}`);
}
const amount = 0.5;
const address = 'tz1h3rQ8wBxFd8L9B3d7Jhaawu6Z568XU3xY';
console.log(`Transferring ${amount} ꜩ to ${address}...`);
try {
const op = await Tezos.wallet.transfer({ to: address, amount: amount }).send();
console.log(`Waiting for ${op.opHash} to be confirmed...`);
await op.confirmation(1);
console.log(`Operation injected: https://shadownet.tzkt.io/${op.hash}`);
} catch (error) {
console.log(`Error: ${error} ${JSON.stringify(error, null, 2)}`);
}
Derivation paths, HD wallet & BIP Standards
Derivation paths are related to Hierarchical Deterministic Wallet (HD wallet). HD wallet is a system allowing to derive addresses from a mnemonic phrase combined with a derivation path. Changing one index of the path will allow accessing a different account. We can access a nearly unlimited number of addresses with HD wallet.
Here is the technical specification for the most commonly used HD wallets :
According to BIP44, path is described as follow:
purpose' / coin_type' / account' / change / address_index.
Where purpose is a constant set to 44' and coin_type is set to 1729' for Tezos.
Different Tezos HD paths
All Tezos paths begin with 44'/1729'. Changing any of the remaining indexes (account' / change / address_index) produces a different account. To ensure wallet compatibility, follow established conventions for path structure and index iteration.
Unlike the BIP44 specification which uses 5 indexes, Tezos commonly uses 4. Both tezos-client and Taquito’s LedgerSigner default to 44'/1729'/0'/0'.
Taquito provides HDPathTemplate to simplify path generation by iterating on the account index:
HDPathTemplate(0)→44'/1729'/0'/0'(first address)HDPathTemplate(1)→44'/1729'/1'/0'(second address)HDPathTemplate(2)→44'/1729'/2'/0'(third address)
This template is optional, so you can use any path structure that fits your needs.
Some implementations use 44'/1729'/0'/0'/0' (5 indexes), incrementing either account or address_index for successive addresses.
Some considerations about paths
According to BIP44, “Software should prevent a creation of an account if a previous account does not have a transaction history (meaning none of its addresses have been used before).”
When building an app using the LedgerSigner, you must be careful not to allow users to access an account with a path structure that does not follow any convention. Otherwise, users could have difficulties using their accounts with other wallets that are not compatible with their paths.
As stated before, HD wallets allow you to get a nearly unlimited number of addresses. According to BIP44, wallets should follow an Account discovery algorithm meaning that it is possible that the wallet won’t find an account created with an unconventional path. We can think about how hard it would be for a user who had created an account with a no common path and forgot it to find it again.
More about derivation path here
https://github.com/LedgerHQ/ledger-live-desktop/issues/2559
https://github.com/obsidiansystems/ledger-app-tezos/#importing-the-key-from-the-ledger-device
https://github.com/MyCryptoHQ/MyCrypto/issues/2070
https://medium.com/mycrypto/wtf-is-a-derivation-path-c3493ca2eb52
Live example that iterates from the path 44'/1729'/0'/0' to 44'/1729'/9'/0'
Having your Ledger device connected to your computer and the Tezos Wallet App opened, you can run the following code example. It will scan your Ledger from path 44'/1729'/0'/0' to 44'/1729'/9'/0' to get public key hashes and the balance for revealed accounts. Confirmations will be asked on your Ledger to send the public keys.
Note that this example is not intended to be a complete example of paths scanning but only a rough outline of what is possible to do.
//import { LedgerSigner, DerivationType, HDPathTemplate } from '@taquito/ledger-signer'; //import { TezosToolkit } from '@taquito/taquito'; //import TransportWebHID from "@ledgerhq/hw-transport-webhid"; //const Tezos = new TezosToolkit('https://shadownet.tezos.ecadinfra.com'); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const getAddressInfo = async (transport, index) => { const ledgerSigner = new LedgerSigner( transport, `44'/1729'/${index}'/0'`, true, DerivationType.ED25519 ); Tezos.setProvider({ signer: ledgerSigner }); const pkh = await Tezos.signer.publicKeyHash(); const balance = await Tezos.tz.getBalance(pkh); const managerKey = await Tezos.rpc.getManagerKey(pkh); console.log(`Path index ${index}: ${pkh}`); if (managerKey) { console.log(`Balance: ${balance.toNumber() / 1000000} ꜩ\n`); } else { console.log('Account not revealed.\n'); } }; try { const transport = await TransportWebHID.create(); for (let index = 0; index < 10; index++) { await getAddressInfo(transport, index); await sleep(2000); } } catch (error) { console.log(error); }
A similar example using @ledgerhq/hw-transport-node-hid can be found here. This example directly retrieves the public keys from the Ledger without asking for confirmation on the device.