import { Graphviz } from 'graphviz-react' import QRCode from 'qrcode.react'
import { Image, Appear, Invert } from 'mdx-deck' import Button from "react-bootstrap/Button";
import customTheme from './theme' export const theme = customTheme
<Image size='cover' style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', }} src="https://user-images.githubusercontent.com/35975617/63715451-00799000-c801-11e9-888b-1281574186db.png">
bit.ly/2L23QaF
- KeepKey, v6.1.0+
- Chrome or Brave
- Node.js 10+
- These slides!
- github.com/keepkeyjon/teach-hdwallet
- Lesson
- Hands-on Exercises
- Follow along
$ git clone github.com/keepkeyjon/teach-hdwallet
$ cd teach-hdwallet
$ yarn
$ yarn start
⬅️ ➡️ arrow keys to navigate
option-p for presenter mode, hidden notes.
Hands-on exercises need v6.1.0+ firmware:
<Graphviz dot={digraph D { "dApp A" -> "Ledger"; "dApp C" -> "Ledger"; "dApp B" -> "Ledger"; "dApp B" -> "Trezor"; "dApp C" -> "Trezor"; "dApp D" -> "KeepKey"; }
} options={{ height: 1000, width: 1000 }} />
- Every vendor has their own libraries
- Fragmented wallet support
<Graphviz dot={digraph D { "dApp A" -> "Common API"; "dApp B" -> "Common API"; "dApp C" -> "Common API"; "Common API" -> "Ledger"; "Common API" -> "Trezor"; "Common API" -> "KeepKey"; }
} options={{ height: 1000, width: 1000 }} />
- Unified api
<Graphviz dot={digraph D { rankdir="LR"; hdwalletcore [label="hdwallet-core", shape=box]; subgraph cluster_1 { label = "KeepKey"; hdwalletkeepkey [label="hdwallet-keepkey", shape=box]; hdwalletkeepkeywebusb [label="hdwallet-keepkey-webusb", shape=box, fillcolor="aquamarine", style=filled]; hdwalletkeepkeywebusb -> hdwalletkeepkey; hdwalletkeepkeynodewebusb [label="hdwallet-keepkey-nodewebusb", shape=box, fillcolor="darkseagreen1", style=filled]; hdwalletkeepkeynodewebusb -> hdwalletkeepkey; hdwalletkeepkeychromeusb [label="hdwallet-keepkey-chromeusb", shape=box, fillcolor="bisque", style=filled]; hdwalletkeepkeychromeusb -> hdwalletkeepkey; } hdwalletkeepkey -> hdwalletcore; subgraph cluster_2 { label = "Trezor" hdwallettrezor [label="hdwallet-trezor", shape=box]; hdwallettrezorconnect [label="hdwallet-trezor-connect", shape=box, fillcolor="aquamarine", style=filled]; hdwallettrezorconnect -> hdwallettrezor; } hdwallettrezor -> hdwalletcore; subgraph cluster_3 { label = "Ledger"; hdwalletledger [label="hdwallet-ledger", shape=box]; hdwalletledgerwebusb [label="hdwallet-ledger-webusb", shape=box, fillcolor="aquamarine", style=filled]; hdwalletledgerwebusb -> hdwalletledger; hdwalletledgeru2f [label="hdwallet-ledger-u2f", shape=box, fillcolor="aquamarine", style=filled]; hdwalletledgeru2f -> hdwalletledger; } hdwalletledger -> hdwalletcore; }
} options={{ height: 1000, width: 1000 }} />
<Graphviz dot={digraph D { rankdir="LR"; subgraph cluster_0 { label = "Legend"; package [label="package", shape=box]; webpackage [label="web package", shape=box, fillcolor="aquamarine", style=filled]; nodepackage [label="node package", shape=box, fillcolor="darkseagreen1", style=filled]; chromepackage [label="chrome package", shape=box, fillcolor="bisque", style=filled]; } }
} options={{ height: 1000, width: 1000 }} />
<iframe src="https://codesandbox.io/embed/github/keepkeyjon/teach-hdwallet/tree/master/exercises/01-pair-device?autoresize=1&expanddevtools=1&fontsize=14&module=%2Fsrc%2FWallet.tsx" title="Pair Device" allow="usb" style="width:100%; height:850px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"> </iframe>
- Users should try pairing a KeepKey, or a Trezor
- May run into issues with
- Old Firmware
- Old Trezor Bridge versions
- Browsers without WebUSB support
- Coin support varies
- Devices
- Firmware Versions
- Support common subset
// ETH / ETC / Ropsten / Kovan / etc.
if (supportsETH(wallet)) {
addr = await wallet.ethGetAddress({ ... })
}
// Bitcoin / Litecoin / Dash / Doge / etc.
if (supportsBTC(wallet)) {
addr = await wallet.btcGetAddress({ ... })
}
- Standard for derivation paths in HD Wallets
- Notation
m/44'/0'/0'/0/0
[0x8000002c, 0x80000000, 0x80000000, 0, 0]
- BIP44 / BIP49 / BIP84
- BIP44
m/44'/0'/0'/0/0
m/purpose'/coin'/account'/change/index
- Problem
- No clear standard for ETH
- Every wallet does it differently 😭
Vendor | Path |
---|---|
KeepKey | m/44'/60'/x'/0/0 |
Trezor | m/44'/60'/0'/0/x |
Ledger | m/44'/60'/0'/x |
Ledger | m/44'/60'/x'/0/0 |
Coinomi | m/44'/60'/x'/0 |
Solution: Ask HDWallet!
wallet.ethGetAccountPaths({
coin: 'Ethereum',
accountIdx: 1
})
['🐓','🥚'].sort()
let addressNList =
bip32ToAddressNList(`m/44'/60'/0'/0/0`)
let address = await wallet.ethGetAddress({
coin: 'Ethereum',
addressNList,
showDisplay: true
address: ???
})
TrezorConnect api requires that we provide the address that we are expecting that the user see when it is shown on their device.
This prevents surprises, but it also means we need to have the address before we can show it on the display.
Solution: Ask twice!
let addressNList =
bip32ToAddressNList(`m/44'/60'/0'/0/0`)
address = await wallet.ethGetAddress({
coin: 'Ethereum', addressNList,
showDisplay: false, ...msg })
renderQRCode(address)
address = await wallet.ethGetAddress({
coin: 'Ethereum', addressNList,
showDisplay: true, address, ...msg })
<iframe src="https://codesandbox.io/embed/github/keepkeyjon/teach-hdwallet/tree/master/exercises/02-show-address?autoresize=1&expanddevtools=1&fontsize=14&module=%2Fsrc%2FWallet.tsx" title="Show Address" allow="usb" style="width:100%; height:850px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"> </iframe>
- <span style={{ color: '#f0c808' }}>transfer(<span style={{ color: '#1ea4da' }}>address,<span style={{ color: '#dd1c1a' }}>uint256)
- <span style={{ fontSize: 16, color: 'white' }}>0x<span style={{ fontSize: 16, color: '#f0c808' }}>a9059cbb<span style={{ fontSize: 16, color: '#1ea4da' }}>000000000000000000000073d0385F4d8E00C5e6504C6030F47BF6212736A8<span style={{ fontSize: 16, color: '#dd1c1a' }}>00000000000000000000000000000000000000000000487A9A304539440000
let sig = await wallet.ethSignTx({
addressNList,
nonce: '0x01',
gasPrice: "0x1dcd65000",
gasLimit: "0x5622",
value: '0x',
to: '0xc770EEfAd204B5180dF6a14Ee197D99d808ee52d',
chainId: 1,
data: '0xa9059cbb' +
'000000000000000000000000' +
'73d0385F4d8E00C5e6504C6030F47BF6212736A8' +
'0000000000000000000000000000000000000000000000' +
'487A9A304539440000'
})
🔫 🐠 🛢️
<iframe src="https://codesandbox.io/embed/github/keepkeyjon/teach-hdwallet/tree/master/exercises/03-sign-erc20?autoresize=1&expanddevtools=1&fontsize=14&module=%2Fsrc%2FWallet.tsx" title="Show Address" allow="usb" style="width:100%; height:850px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"> </iframe>signDigest(hash(message), key)
signDigest(hash("\x19Ethereum Signed Message:\n" + message), key)
"Personal" message signing gets prefixed with a string to distinguish it from transaction signing. This helps protect users from signing malicious payloads that take all their funds, or other funny business.
let res = await wallet.ethSignMessage({
addressNList: bip32ToAddressNList("m/44'/60'/0'/0/0"),
message: 'Hello World'
})
let res = await wallet.ethVerifyMessage({
address: '0x3f2329C9ADFbcCd9A84f52c906E936A42dA18CB8',
message: 'Hello World',
signature: '0x29f7212ecc1c76cea81174af267b67506' +
'f754ea8c73f144afa900a0d85b24b2131' +
'9621aeb062903e856352f383057101908' +
'69c3ce5a1425d65ef4fa558d0fc251b'
})
KeepKey doesn't have font support for emojis, so you won't be able to see them when you sign them (you'll see a sadface instead)... But plain old ASCII will work, up to a size limit.
if (!supportsBTC(wallet))
return
if (!wallet.btcSupportsCoin('Litecoin'))
return
// Same "Ask Twice" pattern as on ETH
let address = await wallet.btcGetAddress({
addressNList,
coin: 'Litecoin',
showDisplay: false,
scriptType: BTCInputScriptType.SpendWitness, // p2wpkh, bech32
})
Alternatively, fetch the account's xpub using wallet.getPublicKeys(...)
,
and derive the address yourself, then make the request with showDisplay: true
.
- Much more involved than for ETH signing
- No UTXO selection in HDWallet
- Need to provide full prevTx for legacy UTXOs
- Allows devices to verify amounts add up
- Ideas
- Add Ledger support
- Add UI for BIP39 Passphrases
- Add Sign & Verify message support