Commit ec6f4da7 authored by Alain Mebsout's avatar Alain Mebsout

Merge remote-tracking branch 'obsidian/v2.2.4' into merge_obsidian_2_2_4

parents 85dfb2fc 8fca2176
......@@ -5,6 +5,7 @@ obj
src/u2f_crypto_data.h
src/glyphs.h
src/glyphs.c
src/delegates.h
#ide
*.code-workspace
......
# APDU
An APDU is sent by a client to the ledger hardware. This message tells
the ledger some operation to run. Most APDU messages will be
accompanied by an Accept / Deny prompt on the Ledger. Once the user
hits “Accept” the Ledger will issue a response to the Tezos client.
The basic format of the APDU request follows.
| Field | Length | Description |
|-------|--------|-------------------------------------------------------------------------|
| CLA | 1 byte | Instruction class (always 0x80) |
| INS | 1 byte | Instruction code (0x00-0x0f) |
| P1 | 1 byte | User-defined 1-byte parameter |
| P2 | 1 byte | Derivation type (0=ED25519, 1=SECP256K1, 2=SECP256R1, 3=BIPS32_ED25519) |
| LC | 1 byte | Length of CDATA |
| CDATA | <LC> | Payload containing instruction arguments |
Each APDU has a header of 5 bytes followed by some data. The format of
the data will depend on which instruction is being used.
## Example
Here is an example of an APDU message from the ledger-app-tezos tests:
> 0x8001000011048000002c800006c18000000080000000
This parses as:
| Field | Value |
|-------|--------------------------------------|
| CLA | 0x80 |
| INS | 0x01 |
| P1 | 0x00 |
| P2 | 0x00 |
| LC | 0x11 (17) |
| CDATA | 0x048000002c800006c18000000080000000 |
0x01 is the first instruction and is used for "authorize baking". This
APDU tells the ledger to save the choice of curve and derivation path
to memory so that future operations know which of these to use when
baking. Note that this instruction is only recognized by the baking
app, and not the wallet app.
A list of more instructions follows.
## APDU instructions in use by Tezos Ledger apps
| Instruction | Code | App | Prompt | Short description |
|---------------------------------|------|-----|--------|--------------------------------------------------|
| `INS_VERSION` | 0x00 | WB | No | Get version information for the ledger |
| `INS_AUTHORIZE_BAKING` | 0x01 | B | Yes | Authorize baking |
| `INS_GET_PUBLIC_KEY` | 0x02 | WB | No | Get the ledger’s internal public key |
| `INS_PROMPT_PUBLIC_KEY` | 0x03 | WB | Yes | Prompt for the ledger’s internal public key |
| `INS_SIGN` | 0x04 | WB | Yes | Sign a message with the ledger’s key |
| `INS_SIGN_UNSAFE` | 0x05 | W | Yes | Sign a message with the ledger’s key (no hash) |
| `INS_RESET` | 0x06 | B | Yes | Reset high water mark block level |
| `INS_QUERY_AUTH_KEY` | 0x07 | B | No | Get auth key |
| `INS_QUERY_MAIN_HWM` | 0x08 | B | No | Get current high water mark |
| `INS_GIT` | 0x09 | WB | No | Get the commit hash |
| `INS_SETUP` | 0x0a | B | Yes | Setup a baking address |
| `INS_QUERY_ALL_HWM` | 0x0b | B | No | Get all high water mark information |
| `INS_DEAUTHORIZE` | 0x0c | B | No | Deauthorize baking |
| `INS_QUERY_AUTH_KEY_WITH_CURVE` | 0x0d | B | No | Get auth key and curve |
| `INS_HMAC` | 0x0e | B | No | Get the HMAC of a message |
| `INS_SIGN_WITH_HASH` | 0x0f | WB | Yes | Sign a message with the ledger’s key (with hash) |
- B = Baking app, W = Wallet app
## Signing operations
There are 3 APDUs that deal with signing things. They use the Ledger’s
private key to sign messages sent. They are:
| Instruction | Code | App | Parsing | Send hash |
|----------------------|------|-----|---------|-----------|
| `INS_SIGN` | 0x04 | WB | Yes | No |
| `INS_SIGN_UNSAFE` | 0x05 | W | No | No |
| `INS_SIGN_WITH_HASH` | 0x0f | WB | Yes | Yes |
The main difference between `INS_SIGN` and `INS_SIGN_UNSAFE` is that
`INS_SIGN_UNSAFE` skips the parsing step which shows what operation is
included in the APDU data. This is unsafe, because the user doesn’t
see what operation they are actually signing. When this happens, we
tell the user “Unrecognized: Sign Hash” so that they can make
appropriate external steps to verify this hash.
### Parsing operations
Each Tezos block that is received through `INS_SIGN` is parsed and the
results are shown on the Ledger’s display. At many points, this
parsing may fail, and the Tezos app will fall back in this case to
“Unrecognized: Sign Hash” mode.
Parsing some Tezos blocks are particularly difficult. Contract
“originations” contain Michelson data that could be too big to display
and transactions can contain “parameters” which can be any valid
Michelson data. Currently, only a small subset of parameters are
parsed, and no contract originations can be parsed.
There is not enough resources on the Ledger Nano S to parse any
arbitrary operation, but we can match to a predefined template.
Currently, the Tezos app matches to templates provided for specific
Manager.tz operations, which are part of the migration to Babylon. In
the Babylon migration, implicit contracts are converted to originated
contracts. We support Babylon to make sure those contracts are still
accessible via Ledger signing. More details on the migration are
available at
[migration_004_to_005.md](https://gitlab.com/cryptiumlabs/tezos/blob/master/specs/migration_004_to_005.md).
There are four Michelson operations currently supported in the Ledger.
They are:
- set delegate
- remove delegate
- transfer implicit to contract
- transfer contract to contract
Each of these comes with its own Michelson sequence, each beginning
with `DROP ; NIL operation` and ending with `CONS`. From there, we
match like this:
![Michelson manager.tz ops graph](michelson_ops.png)
#### Manager.tz parsing limitations
There are some limitations for Michelson parsing that should be noted.
- Arguments passed to Manager.tz contract must match exactly those
described in the migration document. Any variations will be
rejected.
- All endpoints other than “do” are rejected.
- Amount transferred must be 0.
- “contract-to-contract” requires that you use:
- the default endpoint for your destination contract
- the parameters must be of type unit
#*******************************************************************************
# Ledger App
# (c) 2017 Ledger
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#*******************************************************************************
# Version
APPVERSION_M=0
APPVERSION_N=5
APPVERSION_P=1
APPVERSION_D=20191014
# Enable PRINTF (with specific runtime on Ledger)
DEBUG = 0
# Enable PRINTF_STACK_SIZE (with modified Bolos)
......@@ -52,13 +29,26 @@ APPNAME = "Tezos"
KIND=tezos
DEFINES+= TEZOS_APP
endif
APP_LOAD_PARAMS=--appFlags 0 --curve ed25519 --curve secp256k1 --curve prime256r1 --path "44'/1729'" $(COMMON_LOAD_PARAMS)
APP_LOAD_PARAMS= --appFlags 0 --curve ed25519 --curve secp256k1 --curve prime256r1 --path "44'/1729'" $(COMMON_LOAD_PARAMS)
GIT_DESCRIBE ?= $(shell git describe --tags --abbrev=8 --always --long --dirty 2>/dev/null)
VERSION_TAG ?= $(shell echo "$(GIT_DESCRIBE)" | cut -f1 -d-)
APPVERSION_M=0
APPVERSION_N=6
APPVERSION_P=0
APPVERSION_D=20191201
APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)-$(APPVERSION_D)
# Only warn about version tags if specified/inferred
ifeq ($(VERSION_TAG),)
$(warning VERSION_TAG not checked)
else
ifneq (v$(APPVERSION), $(VERSION_TAG))
$(warning Version-Tag Mismatch: v$(APPVERSION) version and $(VERSION_TAG) tag)
endif
endif
COMMIT ?= $(shell echo "$(GIT_DESCRIBE)" | awk -F'-g' '{print $$2}' | sed 's/-dirty/*/')
ifeq ($(COMMIT),)
$(warning COMMIT not specified and could not be determined with git from "$(GIT_DESCRIBE)")
......@@ -92,9 +82,11 @@ watch-log:
DEFINES += OS_IO_SEPROXYHAL
DEFINES += HAVE_BAGL HAVE_SPRINTF
DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=6 IO_HID_EP_LENGTH=64 HAVE_USB_APDU
DEFINES += VERSION=\"$(APPVERSION)\" COMMIT=\"$(COMMIT)\" \
APPVERSION_M=$(APPVERSION_M) APPVERSION_N=$(APPVERSION_N) \
APPVERSION_P=$(APPVERSION_P) APPVERSION_D=\"$(APPVERSION_D)\"
DEFINES += HAVE_LEGACY_PID
DEFINES += VERSION=\"$(APPVERSION)\" APPVERSION_M=$(APPVERSION_M)
DEFINES += COMMIT=\"$(COMMIT)\" APPVERSION_N=$(APPVERSION_N) APPVERSION_P=$(APPVERSION_P)
# DEFINES += _Static_assert\(...\)=
DEFINES += APPVERSION_D=\"$(APPVERSION_D)\"
# U2F
# Note: using U2F causes 148 bytes on the stack
......@@ -108,6 +100,7 @@ DEFINES += HAVE_BOLOS_APP_STACK_CANARY
DEFINES += UNUSED\(x\)=\(void\)x
ifeq ($(TARGET_NAME),TARGET_NANOX)
APP_LOAD_PARAMS += --appFlags 0x240 # with BLE support
DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300
DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000
DEFINES += HAVE_BLE_APDU # basic ledger apdu transport over BLE
......@@ -135,12 +128,13 @@ endif
ifneq ($(DEBUG),0)
DEFINES += DUNE_DEBUG
ifeq ($(TARGET_NAME),TARGET_NANOX)
DEFINES += HAVE_PRINTF PRINTF=mcu_usb_printf
else
DEFINES += HAVE_PRINTF PRINTF=screen_printf
endif
DEFINES += DUNE_DEBUG
else
DEFINES += PRINTF\(...\)=
endif
......@@ -169,6 +163,7 @@ ifneq ($(BOLOS_ENV),)
$(info BOLOS_ENV=$(BOLOS_ENV))
CLANGPATH := $(BOLOS_ENV)/clang-arm-fropi/bin/
GCCPATH := $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/bin/
CFLAGS += -idirafter $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/arm-none-eabi/include
else
$(info BOLOS_ENV is not set: falling back to CLANGPATH and GCCPATH)
endif
......@@ -210,3 +205,8 @@ dep/%.d: %.c Makefile
listvariants:
@echo VARIANTS APP tezos_wallet tezos_baking
# Generate delegates from baker list
src/delegates.h: tools/gen-delegates.sh tools/BakersRegistryCoreUnfilteredData.json
./tools/gen-delegates.sh ./tools/BakersRegistryCoreUnfilteredData.json
dep/to_string.d: src/delegates.h
......@@ -90,7 +90,7 @@ restore your key. If you lose your Ledger device or destroy it somehow, you can
new one and set it up with the seed phrase from your old one, hence restoring
your tokens.
Consequently, is is extremely important that you keep your seed phrase written
Consequently, it is extremely important that you keep your seed phrase written
down somewhere safe. Losing it can mean you lose control of your account should
you, for example, lose your Ledger device. Keeping it somewhere a hacker could find it
(such as in a file on your internet-connected computer) means your private key
......@@ -120,12 +120,12 @@ technique is that your dun be stored in the passphrase-protected and
deniable account, and that you delegate them to a baking account. This
way, the baking account won't actually store the vast majority of the dun.
### Ledger Nano S firmware update
### Ledger device firmware update
To use this app, you must be sure to have [up-to-date
firmware](https://support.ledgerwallet.com/hc/en-us/articles/360002731113)
on the Ledger device. This code was tested with version
1.5.5. Please use [Ledger
1.6.0. Please use [Ledger
Live](https://www.ledger.com/pages/ledger-live) to do this.
<!--
......@@ -143,7 +143,7 @@ If you've used Ledger Live for app installation, you can skip ahead to [Register
You should follow the instructions in [`BUILDING.md`](BUILDING.md), and
[`INSTALL_linux.md`](INSTALL_linux.md) or [`INSTALL_macosx.md`](INSTALL_macosx.md).
## Registering the Ledger Nano S with the node
## Registering the Ledger device with the node
For the remainder of this document, we assume you have a Dune node running and
`dune-client` installed. Also, Docker has some issues working with the Ledger device,
......@@ -196,7 +196,14 @@ encryption system, is indicated by a root key hash, the Dune-specific base58
encoding of the hash of the public key at `44'/1729'` on that Ledger Nano S. Because
all Dune paths start with this, in `dune-client` commands it is implied.
### Importing the key from the Ledger Nano S
Beginning in version 0.5.0, there is also support for a `ed25519-bip32` derivation
method, which was made available in V1.5.5 of the Nano firmware. The existing `ed25519`
operation was purposefully not changed to preserve backwards compatibility. If you do
nothing, expect no changes. However, it is recommended that all new accounts use the `bip25519`
command instead of the legacy `ed25519`. After it is imported, the address can be treated
the same as any other.
### Importing the key from the Ledger device
This section must be done regardless of whether you're going to be baking or
only using the Dune Wallet application.
......@@ -207,7 +214,7 @@ Please run, with a Dune app open on your device, in whatever mode.
$ dune-client list connected ledgers
```
The output of this command includes three Dune addresses derived from the secret
The output of this command includes four Dune addresses derived from the secret
stored on the device, via different signing curves and BIP32 paths.
```
......@@ -217,19 +224,20 @@ running on Ledger Nano S at [0003:000b:00].
To use keys at BIP32 path m/44'/1729'/0'/0' (default Dune key path), use one
of:
dune-client import secret key my_ledger "ledger://major-squirrel-thick-hedgehog/ed25519/0'/0'"
dune-client import secret key my_ledger "ledger://major-squirrel-thick-hedgehog/secp256k1/0'/0'"
dune-client import secret key my_ledger "ledger://major-squirrel-thick-hedgehog/P-256/0'/0'"
dune-client import secret key my_ledger "ledger://major-squirrel-thick-hedgehog/bip25519/0h/0h"
dune-client import secret key my_ledger "ledger://major-squirrel-thick-hedgehog/ed25519/0h/0h"
dune-client import secret key my_ledger "ledger://major-squirrel-thick-hedgehog/secp256k1/0h/0h"
dune-client import secret key my_ledger "ledger://major-squirrel-thick-hedgehog/P-256/0h/0h"
```
These show you how to import keys with a specific signing curve (e.g. `ed25519`) and derivation path (e.g. `/0'/0'`). The
These show you how to import keys with a specific signing curve (e.g. `bip25519`) and derivation path (e.g. `/0'/0'`). The
animal-based name (e.g. `major-squirrel-thick-hedgehog`) is a unique identifier for your
Ledger device enabling the client to distinguish different Ledger devices. This is combined with
a derivation path (e.g. `/0'/0'`) to indicate one of the possible keys on the Ledger Nano S. Your *root* key is the full identifier without the derivation path (e.g. `major-squirrel-thick-hedgehog/ed25519` by itself) but you should not use the root key directly\*.
a derivation path (e.g. `/0'/0'`) to indicate one of the possible keys on the Ledger device. Your *root* key is the full identifier without the derivation path (e.g. `major-squirrel-thick-hedgehog/bip25519` by itself) but you should not use the root key directly\*.
\* *NOTE:* If you have used your root key in the past and need to import it, you can do so by simply running one of the commands but without the last derivation portion. From the example above, you would import your root key by running `dune-client import secret key my_ledger "ledger://major-squirrel-thick-hedgehog/ed25519"`. You should avoid using your root key.
The Ledger Nano S does not currently support non-hardened path components. All
The Ledger device does not currently support non-hardened path components. All
components of all paths must be hardened, which is indicated by following them
with a `'` character. This character may need to be escaped from the shell
through backslashes `\` or double-quotes `"`.
......@@ -249,10 +257,10 @@ three things:
The `dune-client import secret key` operation copies only the public key; it
says "import secret key" to indicate that the Ledger hardware wallet's secret key will be
considered available for signing from them on, but it does not leave the Ledger Nano S.
considered available for signing from them on, but it does not leave the Ledger device.
This sends a BIP32 path to the device. You then need to click a button on the
Ledger device, and the Ledger Nano S then sends the public key back to the computer.
Ledger device and it sends the public key back to the computer.
After you perform this step, if you run the `list known addresses` command, you
should see the key you chose in the list:
......@@ -281,7 +289,7 @@ set up purposes.
The sign command for the Wallet Mode prompts every time for transactions
and other "unsafe" operations, with the generic prompt saying "Sign?" We hope to
eventually display more transaction details along with this. When block headers
and endorsements are sent to the Ledger Nano S, they are rejected silently as if the
and endorsements are sent to the Ledger device, they are rejected silently as if the
user rejected them.
### Faucet (testnet only)
......@@ -319,7 +327,7 @@ $ dune-client get balance for <your-name>
### Transfer
Now transfer the balance to the account whose key resides on your Ledger Nano S:
Now transfer the balance to the account whose key resides on your Ledger device:
```
$ dune-client transfer 66000 from <your-name> to ledger_<...>_ed_0_0
......@@ -369,7 +377,7 @@ someone.
If you want to delegate dun controlled by a Ledger Nano S account to another account to
bake, that requires the Wallet App. This is distinct from registering the Ledger Nano S
account itself to bake, which is also called "delegation," and which is covered
in the section on the baking app below.
in the section on the baking application below.
To delegate dun controlled by a Ledger device to someone else,
you must first originate an account. Please read more
......@@ -388,7 +396,7 @@ $ dune-client originate account <NEW> for <MGR> transferring <QTY> from <SRC> --
* `QTY` is the initial amount of dun to give to the originated account.
* `SRC` is the account where you'll be getting the dun from.
Subsequently, every transaction made with `<NEW>` will require the Ledger harware wallet mentioned in `<MGR>`
Subsequently, every transaction made with `<NEW>` will require the Ledger hardware wallet mentioned in `<MGR>`
to sign it. This is done with the wallet application, and includes setting a delegate with:
```
......@@ -486,7 +494,7 @@ operation.
$ dune-baker-004-Pt24m4xi run with local node ~/.dune-node ledger_<...>_ed_0_0
```
This won't actually be able bake successfully yet until you run the rest of
This won't actually be able to bake successfully yet until you run the rest of
these setup steps. This will run indefinitely, so you might want to do it in
a dedicated terminal or in a `tmux` or `screen` session.
......@@ -508,7 +516,7 @@ You need to run a specific command to authorize a key for baking. Once a key is
authorized for baking, the user will not have to approve this command again. If
a key is not authorized for baking, signing endorsements and block headers with
that key will be rejected. This authorization data is persisted across runs of
the application, but not across app installations. Only one key can be authorized for baking per Ledger hardware wallet at a
the application, but not across application installations. Only one key can be authorized for baking per Ledger hardware wallet at a
time.
In order to authorize a public key for baking, use the APDU for setting up the ledger device to bake:
......@@ -517,16 +525,16 @@ In order to authorize a public key for baking, use the APDU for setting up the l
$ dune-client setup ledger to bake for <ALIAS>
```
This only authorizes the key for baking on the Ledger Nano S, but does
This only authorizes the key for baking on the Ledger device, but does
not inform the blockchain of your intention to bake. This might
be necessary if you re-install the app, or if you have a different
be necessary if you reinstall the app, or if you have a different
paired Ledger device that you are using to bake for the first time.
### Registering as a Delegate
*Note: The ledger device will not sign this operation unless you have already setup the device to bake using the command in the previous section.*
In order to bake from the Ledger Nano S account you need to register the key as a
In order to bake from the Ledger device account you need to register the key as a
delegate. This is formally done by delegating the account to itself. As a
non-originated account, an account directly stored on the Ledger device can only
delegate to itself.
......@@ -556,7 +564,7 @@ have been baked so far -- is displayed on the device's screen, and is also
persisted between runs of the device.
The sign operation will be sent to the hardware wallet by the baking daemon when
configured to bake with a Ledger Nano S key. The Ledger device uses the first byte of the
configured to bake with a Ledger device key. The Ledger device uses the first byte of the
information to be signed -- the magic number -- to tell whether it is a block
header (which is verified with the High Watermark), an endorsement (which is
not), or some other operation (which it will reject, unless it is a
......@@ -617,7 +625,7 @@ Alternatively, you can also set the High Watermark to the level of the most rece
$ dune-client set ledger high watermark for "ledger://<tz...>/" to <HWM>
```
The later will require the correct URL for the Ledger device acquired from:
The latter will require the correct URL for the Ledger device acquired from:
```
$ dune-client list connected ledgers
......@@ -628,7 +636,7 @@ $ dune-client list connected ledgers
### Display Debug Logs
If you are worried about bugs, you should configure your system to display debug logs. Add the
following line to `~/.bashrc` and to `~/.bash_profile`, or set the equivalent environnment
following line to `~/.bashrc` and to `~/.bash_profile`, or set the equivalent environment
variable in whatever system you use to launch your daemons:
```
......@@ -663,7 +671,7 @@ ledgers at a time. Two Ledger devices of different seeds are fine and are fully
and the computer will automatically determine which one to send information to.
If you have one running the baking app, it is bad for security to also have the wallet app
plugged in simultaneously. Plug the wallet app in as-needed, removing the baking app, at a time
plugged in simultaneously. Plug the wallet application in as-needed, removing the baking app, at a time
when you are not going to be needed for endorsement or baking. Alternatively, use a different
computer for wallet transactions.
......@@ -694,16 +702,52 @@ to connect to. Please ensure that you are running a node. `ps aux | grep dune-no
the process information for the current node. If it displays nothing, or just displays a `grep`
command, then there is no node running on your machine.
### Ledger Nano S App Crashes
### Ledger Application Crashes
If the Ledger Nano S app crashes when you load it, there are two primary causes:
If the Ledger application crashes when you load it, there are two primary causes:
* Quitting the `dune-client` process before the device responds. Even if you meant to cancel
the operation in question, cancel it from the device before pressing Ctrl-C, otherwise you
might have to restart the Ledger Nano S.
* Out of date firmware: If the Ledger Nano S app doesn't work at all, make sure you are running firmware
might have to restart the Ledger device.
* Out of date firmware: If the Ledger application doesn't work at all, make sure you are running firmware
version 1.5.5.
### Error "Unexpected sequence number (expected 0, got 191)" on macOS
If `dune-client` on macOS intermittently fails with an error that looks like
```
client.signer.ledger: APDU level error: Unexpected sequence number (expected 0, got 191)
```
then your installation of `dune-client` was built with an older version of HIDAPI that doesn't work well with macOS (see [#30](https://github.com/obsidiansystems/ledger-app-tezos/issues/30)).
To fix this you need to get the yet-unreleased fixes from the [HIDAPI library](https://github.com/signal11/hidapi) and rebuild `tezos-client`.
If you got HIDAPI from Homebrew, you can update to the `master` branch of HIDAPI like this:
```shell
$ brew install hidapi --HEAD
```
Then start a full rebuild of `dune-client` with HIDAPI's `master` branch:
```shell
$ brew unlink hidapi # remove the current one
$ brew install autoconf automake libtool # Just keep installing stuff until the following command succeeds:
$ brew install hidapi --HEAD
```
Finally, rebuild `ocaml-hidapi` with Tezos. In the `tezos` repository:
```shell
$ opam reinstall hidapi
$ make all build-test
$ ./tezos-client list connected ledgers # should now work consistently
```
Note that you may still see warnings similar to `Unexpected sequence number (expected 0, got 191)` even after this update. The reason is that there is a separate, more cosmetic, issue in `tezos-client` itself which has already been fixed but may not be in your branch yet (see the [merge request](https://gitlab.com/tezos/tezos/merge_requests/600)).
### Contact Us
You can email us at [email protected] and come to our channel on Discord.
The Exciting World of Ledger C
------------------------------
Knowing C will help you in this adventure. But not as much as it should. There are some fun twists when it comes to Ledger C. Explore them below. Memorize them. There *will* be a quiz...
### Exceptions
C doesn't have them. So you don't have to think about bracketing, exception safety, RAII, try/catch, all that.
Well not on the Ledger. You have exceptions! Which means you also have out-of-band code paths, and you now have to worry about exception safety.
You can `THROW` a `uint16_t` like this `THROW(0x9000)`.
Handling exceptions looks like this.
```c
volatile int something = 0;
BEGIN_TRY {
TRY {
//do something;
}
CATCH(EXC_PARSE_ERROR) {
//do something on parse error
}
CATCH_OTHER(e) {
THROW(e);
}
FINALLY { }
}
END_TRY;
```
Exceptions that make it all the way to the top of the application are caught and returned as status codes from the APDU.
#### Gotchas
1. If a variable will be accessed both outside and inside the `BEGIN_TRY`/`END_TRY` block it must be `volatile`. The compiler doesn't expect these shenanigans and will optimize incorrectly if you don't.
2. Do not `return` in the `TRY` block. It will cause the Ledger to crash. Instead use a `volatile` variable to capture the result you want to `return` at the end.
3. Don't try to leave out blocks like `CATCH_OTHER(e)` and `FINALLY`. I don't know if that will work right and it's not worth the risk.
#### Implications
1. If you have some global state and an exception is thrown then, unless you do something about it, that global state will remain. That might be a *very bad thing*. As long as you use globals our way (see Globals Our Way) you should be safe.
### Globals Our Way
`static const` globals are fine. `static` non-const are not fine for two reasons:
1. If you try to initialize them (which you would want to do!) then the app will crash. For example `static int my_bool = 3;` crashes whenever you try to read or write `my_bool`...
2. Instead of getting initialized to 0 like the C standard says, they are initialized to `0xA5`. Yes this can cause the compiler to incorrectly optimize your code.
So just don't use `static` non-const globals. Instead we have `globals.h` which defines a large `struct` wher you can put your globals. At the beginning of the application we `memset(&global, 0, sizeof(global))` to clear it all to zeros.
Anything inside of `global.apdu` will get cleared when an exception gets to the top of the app (see Exceptions). To benefit from this behavior you should never return an error code via the in-band way of sending bytes back. All errors should be sent via `THROW`.
### Relocation
When we said `static const` globals were fine, we meant that they were possible. There is
a major gotcha, however: if you initialize a `static const` value with a pointer to another
`static` or `static const` value, the pointers might be incorrect and require relocation.
For example:
```
static const char important_string[] = "Important!";
static const char **important_string_ptrs = { important_string, NULL };
const char *str1 = important_string_ptrs[0];
const char *str2 = important_string;
```
`str` will now have the wrong value. `str2` will not. The reason `str1`
has the wrong value is that the linker gets confused with a reference
from one `static const` variable to another `static` variable on this
platform. To resolve, you can use the `PIC` macro, which will fix broken
pointers but never break a good pointer. Because of this, you can use
it liberally and not have to worry about breaking anything:
```
static const char important_string[] = "Important!";
static const char **important_string_ptrs = { important_string, NULL };
const char *str1 = PIC(important_string_ptrs[0]); // necessary use of PIC
const char *str2 = PIC(important_string); // unnecessary but harmless use of PIC
```
Many of the UI functions call `PIC` for you, so just because a UI function
accepts a data structure, doesn't mean that data structure is valid.
### Dynamic Allocation
Nope. Don't even try. No `malloc`/`calloc`/`free`. Use globals (see Globals).
......@@ -20,8 +20,14 @@ for arg in "[email protected]"; do
echo
set -x
appFlag="0x00"
if [ $target == "nano_x" ]; then
appFlag="0x240"
fi
python -m ledgerblue.loadApp \
--appFlags 0x00 \
--appFlags "$appFlag" \
--dataSize "${nvram_size:?manifest file is missing field}" \
--tlv \
--curve ed25519 \
......
#include "apdu.h"
size_t handle_apdu_error(uint8_t __attribute__((unused)) instruction) {
THROW(EXC_INVALID_INS);
}