This is a key management application for the Handshake Protocol. With support for both the Ledger Nano S and Ledger Nano X, it allows users to create extended public keys, addresses, and signatures for valid Handshake transactions. It can be used with the hsd-ledger client library to interact with wallet software.
This document serves as an overview of the relevant technical and licensing details for this application. For more general information on developing for Ledger personal security devices please read the official Ledger developer documentation here.
For a walk-through of a Ledger application, check out the nanos-app-sia project. The Nebulous Inc. developers have done a wonderful job of documenting both the high-level architecture and low-level implementation details of Nano S app development.
To load the app on your Ledger Nanos S without using Docker:
- Follow the setup instructions here.
- Run
make load
in the root of this git repo. - Follow the terminal and on-device instructions (this process takes a while).
Once installation is complete, install the client library.
Note: macOS and Windows are not supported. If you are not running Linux, please follow the Docker instructions below.
If you are running Linux, the only dependency is Docker. If you are running macOS or Windows you will also need to setup a Python virtualenv and install developer tools provided by Ledger. Detailed instructions for each host environment are below.
Once installation is complete, install the client library.
Note: all example commands begin with the $ symbol. This symbol signifies a shell command prompt. The command prompt in your shell may be different. Do not copy the $ symbol when using the commands in your shell.
First, be sure to have Docker installed and running on your machine.
Clone the git repo:
$ git clone https://github.com/handshake-org/ledger-app-hns.git
$ cd ledger-app-hns
- Connect the Ledger Nano S to your machine via USB.
- Unlock the device.
- Navigate to the device's main menu.
If your device is running firmware v1.6.0, run:
$ docker build --build-arg CACHE_BUST="$(date)" -f Dockerfile.build -t ledger-app-hns-build .
$ docker run --rm --privileged ledger-app-hns-build make load
If your device is running firmware v1.5.5, run:
$ docker build --build-arg GIT_REF="nanos-1553" --build-arg CACHE_BUST="$(date)" -f Dockerfile.build -t ledger-app-hns-build .
$ docker run --rm --privileged ledger-app-hns-build make load
Note: the above
docker run
command uses the--privileged
flag, which gives the docker container full access to your host machine. If you know the location of your connected Ledger Nano S you can >replace the--privileged
tag with--device=/path/to/usb/device
.
- Follow the terminal and on-device instructions (this process takes a while).
If you are using a Nano X, run:
$ docker build --build-arg GIT_NAME="nanox-secure-sdk" --build-arg GIT_REF="1.2.4-5.1" \
--build-arg CACHE_BUST="$(date)" -f Dockerfile.build -t ledger-app-hns-build .
$ docker run --rm --privileged ledger-app-hns-build make load
First, be sure to have the following development dependencies installed on your machine:
- Docker
- Python 2.7 development environment (
python2
,python2-dev
,virtualenv
) make
gcc
g++
git
Next, create a directory to store the Nano S Secure SDK and the Python
virtual environment for the Python Loader tool. The SDK is used in
ledger-app-hns
to interact with the device's operating system and crypto
libraries. The loader tool loads compiled applications onto your Ledger Nano S.
It does not matter where you store the SDK or the virtual environment.
For simplicity, this guide stores them together in a directory called
ledger-tools
.
To begin, open a terminal shell and run:
$ mkdir ledger-tools
$ cd ledger-tools
Before installing the SDK, you will need to know what firmware version is
running on your Ledger Nano S. If you do not know how to check your firmware
version, follow the instructions here. This application supports
v1.6.0 and
v1.5.5`.
Clone the git repo:
$ git clone https://github.com/ledgerhq/nanos-secure-sdk.git
If your device is running firmware v1.6.0 checkout the nanos-1612
branch:
$ git checkout nanos-1612
If your device is running firmware v1.5.5 checkout the nanos-1553
branch:
$ git checkout nanos-1553
Set the BOLOS_SDK
environment variable to the absolute path
of the SDK repo:
$ cd nanos-secure-sdk
$ export BOLOS_SDK=`pwd`
$ cd ..
Now, you will need to install the Python loader tool. This process
will involve: navigating back to the root of the ledger-tools
directory,
creating and activating a Python virtual environment, and installing the
ledgerblue
loader tools.
Run:
$ virtualenv ledger
$ source ledger/bin/activate
$ pip install ledgerblue
$ pip install Pillow
We are ready to install the application! This process will involve: cloning
the git repo into the ledger-tools
directory, checking out the branch that
corresponds to your device's firmware version, compiling the application, and
loading it onto your device. The last two steps are triggered by one
make
command.
Clone the git repo:
$ git clone https://github.com/handshake-org/ledger-app-hns.git
$ cd ledger-app-hns
- Start Docker.
- Connect the Ledger Nano S to your machine via USB.
- Unlock the device.
- Navigate to the device's main menu.
If your device is running firmware v1.6.0, run:
$ make docker-load
If your device is running firmware v1.5.5, run:
$ GIT_REF="nanos-1553" make docker-load
- Follow the terminal and on-device instructions (this process takes a while).
To build both Nano S and Nano X apps using Docker:
$ make docker-build-all
This will run the build in a docker container and copy the final builds to
/bin/hns-nanos.elf
and /bin/hns-nanox.elf
These two binaries can be executed inside the Speculos emulator. For example, to run the built Nano S app in the emulator from docker:
docker run -v \
/path/to/ledger-app-hns/bin:/speculos/apps \
--publish 5900:5900 \
-it \
ledgerhq/speculos \
--display headless \
--vnc-port 5900 \
--vnc-password=xyz \
apps/hns-nanos.elf \
--model nanos
The command is explained in detail in the Speculos docs. Just note the path
to the /bin
directory and model
argument. The emulated device can be
accessed using a VNC client. On OSX, the --vnc-password
argument is required
and will be requested by the VNC client.
A suite of tests have been added to the client library. They include
device tests with mocked transaction data, and end-to-end tests involving an
active, hsd
node. All tests require a Ledger Nano S configured with the
following test seed:
abandon abandon abandon abandon abandon abandon
abandon abandon abandon abandon abandon about
This application interacts with a computer host through the APDU communication
protocol. This specification describes the APDU command interface for
ledger-app-hns
.
The basic structure of an APDU command consists of a 5 byte header followed by a variable amount of command input data. The header for an HNS Ledger application command takes the following structure:
NOTE: For the remainder of this document all lengths are represented in bytes. All data is represented as little-endian bytestrings except where noted.
Field | Len | Purpose |
---|---|---|
CLA | 1 | Instruction class - the type of command (always 0xe0) |
INS | 1 | Instruction code - the specific command |
P1 | 1 | Instruction param #1 |
P2 | 1 | Instruction param #2 |
LC | 1 | Length of command input data |
*The above description is unique to this application. Specifically, the APDU protocol allows for a larger LC field. A more general description of the APDU message protocol can be found here.
This command returns the application's version number.
CLA | INS | P1 | P2 | LC |
---|---|---|---|---|
0xe0 | 0x40 | 0x00 | 0x00 | 0x00 |
None
Field | Len |
---|---|
major version | 1 |
minor version | 1 |
patch version | 1 |
This command returns a public key for the given BIP32 path. Using the APDU parameter fields it can also return a Bech32 encoded address for the public key, or the details needed reconstruct the extended public key at the specified level in the HD tree.
The first instruction param (P1) can be used to require on-device confirmation by setting the least significant bit. The next two lowest bits are used to signify the network. The network flag is only used for xpub confirmation. Address generation will parse the network from the derivation path. If an unknown coin type is passed during address generation, an error will be returned.
The second instruction param indicates which, if any, additional details to return, i.e. extended public details and/or address. If confirmation is turned on, and an address is generated, the address will be displayed on screen. The next precedence will be given to extended public key details. Otherwise, the public key will be displayed for confirmation.
NOTE: an on-device warning will be displayed for non-hardened derivation at the BIP44 account level or above. It will also be displayed for derivations past the address index level.
CLA | INS | P1 | P2 | LC |
---|---|---|---|---|
0xe0 | 0x42 | *var | **var | var |
* P1:
- 0x00 = No confimation
- 0x01 = Require confirmation
0x06 is used as a mask to check second and third least significant bits.
- 0x00 = Mainnet
- 0x02 = Testnet
- 0x04 = Regtest
- 0x06 = Simnet
** P2:
- 0x00 = Public key only
- 0x01 = Public key + chain code + parent fingerprint
- 0x02 = Public key + address
- 0x03 = Public key + chain code + parent fingerprint + address
Field | Len |
---|---|
# of derivations (max 5) | 1 |
First derivation index (big-endian) | 4 |
... | 4 |
Last derivation index (big-endian) | 4 |
Field | Len |
---|---|
public key | 33 |
chain code length | 1 |
chain code | var |
parent fingerprint length | 1 |
parent fingerprint (big-endian) | var |
address length | 1 |
address | var |
This command handles the entire input signature creation process. It operates in two modes: parse and sign. When engaged in parse mode, transaction details are sent to the device where they are parsed, cached, and prepared for signing. Once all tx details have been parsed, the user can send signature requests for each input. The first signature request for a particular tx requires on-device confirmation of the txid.
Both modes may require multiple message exchanges between the client and the device. The first instruction param (P1) indicates if a message is the initial one. An initial parse message clears any cached transaction details from memory and restarts the signing process. The initial message in a signature request should pass the path of the signing key, the sighash type, the input, and the first 182 bytes of the input script (including the varint script size). If the entire script does not fit into the first message, additional messages will be necessary to send the rest of the script. The subsequent messages should only include the remaining script bytes.
The second instruction param (P2) indicates the operation mode.
NOTE: Signature requests for non-standard BIP44 address paths will be rejected.
CLA | INS | P1 | P2 | LC |
---|---|---|---|---|
0xe0 | 0x44 | *var | 0x00 | var |
* P1:
- 0x01 = Initial message
- 0x00 = Following message
NOTE: The transaction details should be sent in packets of up to 255 bytes. This is because the APDU command data length is represented as a uint8_t.
Field | Len |
---|---|
version | 4 |
locktime | var |
# of inputs | 1 |
# of outputs | 1 |
*change flag | 1 |
change index? | 1 |
change version? | 1 |
**change path? | var |
***inputs | var |
****outputs | var |
* change flag:
- 0x00 = No address.
- 0x01 = P2PKH change address. Address info provided.
- 0x02 = P2SH change address. No info provided. On-device confirmation required.
** A BIP32 derivation path of the transaction's change address. Only one change address is allowed per transaction. If no change address path is provided, only one output is allowed in the transaction. See serialization format above. Non-standard BIP44 address paths will be rejected. If serialized redeem script, an on-device warning will be displayed.
*** Input serialization for parse mode
Field | Len |
---|---|
prevout | 36 |
sequence | 4 |
value | 8 |
**** Output serialization
Field | Len |
---|---|
value | 8 |
*****address | var |
******covenant | var |
***** Address serialization
Field | Len |
---|---|
version | 1 |
hash length | 1 |
hash | var |
****** Covenant serialization
Field | Len |
---|---|
type | 1 |
# of items | var |
items? | var |
name? | var |
None
CLA | INS | P1 | P2 | LC |
---|---|---|---|---|
0xe0 | 0x44 | *var | 0x01 | var |
* P1:
- 0x01 = Initial signature request (on-device txid confirmation required)
- 0x00 = Additional signature request
Field | Len |
---|---|
*signing key path | var |
sighash type | 4 |
**input | var |
* See serialization format above. Non-standard BIP44 address paths will be rejected.
** Input serialization for sign mode
Field | Len |
---|---|
prevout | 36 |
value | 8 |
sequence | 4 |
script length | var |
script | var |
NOTE: If the size of the input data is larger than the APDU buffer size, the script must be split into smaller packet sizes and sent in multiple messages. Subsequent messages should only send the remaining script bytes. All other input data i.e., the path, sighash type, prevout, value, sequence, and script length, should not be resent.
Field | Len |
---|---|
signature | var |
NOTE: The application keeps track of the number of script bytes it has parsed and will return a SUCCESS status word, without any response data, if it successfully parsed the input data, but is expecting more bytes. After parsing all script bytes, the signature will be generated and returned.
If you contribute code to this project, you are implicitly allowing your code
to be distributed under the MIT license. You are also implicitly verifying that
all code is your original work. </legalese>
- Copyright (c) 2018, Boyma Fahnbulleh (MIT License).
Parts of this software are based on ledger-app-btc, blue-app-nano, nanos-app-sia, hnsd, and ledger-app-eth-dockerized.
- Copyright (c) 2016, Ledger (Apache License).
- Copyright (c) 2018, Mart Roosmaa (Apache License).
- Copyright (c) 2018, Nebulous Inc. (MIT License).
- Copyright (c) 2018, Christopher Jeffrey (MIT License).
- No License
See LICENSE for more info.