The messaging format of the app is compatible with the APDU protocol. The P1
field is reserved for future use and must be set to 0
in all messages. The P2
field is used as a protocol version identifier; the current version is 1
, while version 0
is still supported. No other value must be used.
The main commands use CLA = 0xE1
.
CLA | INS | COMMAND NAME | DESCRIPTION |
---|---|---|---|
E1 | 00 | GET_EXTENDED_PUBKEY | Return (and optionally show on screen) extended pubkey |
E1 | 02 | REGISTER_WALLET | Register a wallet policy on the device (with user's approval) |
E1 | 03 | GET_WALLET_ADDRESS | Return and show on screen an address for a registered or default wallet |
E1 | 04 | SIGN_PSBT | Sign a PSBT with a registered or default wallet |
E1 | 05 | GET_MASTER_FINGERPRINT | Return the fingerprint of the master public key |
E1 | 10 | SIGN_MESSAGE | Sign a message with a key from a BIP32 path (Bitcoin Message Signing) |
E1 | 11 | SIGN_WITHDRAWAL | Signs a Withdrawal message. The message being signed is the hash of the Acre Withdrawal transaction. |
E1 | 12 | SIGN_ERC4361_MESSAGE | Signs an Ethereum Sign-In message (ERC-4361) in Bitcoin format. |
The CLA = 0xF8
is used for framework-specific (rather than app-specific) APDUs; at this time, only one command is present.
CLA | INS | COMMAND NAME | DESCRIPTION |
---|---|---|---|
F8 | 01 | CONTINUE | Respond to an interruption and continue processing a command |
The CONTINUE
command is sent as a response to a client command from the Hardware Wallet; the format and content on the response depends on the client command, and is documented below for each client command.
Several commands are executed via an interactive protocol that requires multiple rounds. At any time after receiving the command and before returning the commands final response (which is status word 0x9000
in case of success), the Hardware Wallet can respond with a special status word SW_INTERRUPTED_EXECUTION
(0xE000
), containing a request for the client in the response data. The first byte of the response is the client command code, identified what kind of request the Hardware Wallet is asking the client to perform. The client must comply with the request and send a special CONTINUE command CLA = 0xF8
and INS = 0x01
, with the appropriate response.
The specs for the client commands are detailed below.
The Acre app uses a language similar to output script descriptors in order to represent the wallets that can be used to sign transactions. Wallet policies need to be registered on the device, with an interactive process that requires user's approval.
See here for detailed information on the wallet policy language.
In order to use a wallet policy that is not one of the default ones, the policy must first be registered on the wallet, which is a protocol that requires explicit approval from the user.
A wallet policy is initiated using the REGISTER_WALLET
command. The screen of the hardware wallet will ask the user to inspect the wallet descriptor template, followed by each of the keys of the cosigners that are part of the wallet policy.
Once the user approves, the REGISTER_WALLET
returns to the client a 32-byte HMAC-SHA256. This will be provided to any future command that makes use of the wallet policy; therefore, the HMAC should be permanently stored on the client. In case of loss of the HMAC, the registration flow must be repeated from scratch.
SW | SW name | Description |
---|---|---|
0x6985 | SW_DENY |
Rejected by user |
0x6A80 | SW_INCORECT_DATA |
Data is incorrect |
0x6A86 | SW_WRONG_P1P2 |
Either P1 or P2 is incorrect |
0x6A87 | SW_WRONG_DATA_LENGTH |
Lc or minimum APDU length is incorrect |
0x6D00 | SW_INS_NOT_SUPPORTED |
No command exists with INS |
0x6E00 | SW_CLA_NOT_SUPPORTED |
Bad CLA used for this application |
0xB000 | SW_WRONG_RESPONSE_LENGTH |
Wrong response length (buffer size problem) |
0xB007 | SW_BAD_STATE |
Abrted because unexpected state reached |
0xB008 | SW_SIGNATURE_FAIL |
Invalid signature or HMAC |
0xE000 | SW_INTERRUPTED_EXECUTION |
The command is interrupted, and requires the client's response |
0x9000 | SW_OK |
Success |
Returns an extended public key at the given derivation path, serialized as per BIP-32.
Command
CLA | INS |
---|---|
E1 | 00 |
Input data
Length | Name | Description |
---|---|---|
1 |
display |
0 or 1 |
1 |
n |
Number of derivation steps (maximum 8) |
4 |
bip32_path[0] |
First derivation step (big endian) |
4 |
bip32_path[1] |
Second derivation step (big endian) |
... | ||
4 |
bip32_path[n-1] |
n -th derivation step (big endian) |
Output data
Length | Description |
---|---|
<variable> |
The full serialized extended public key as per BIP-32 |
This command returns the extended public key for the given BIP 32 path.
The paths defined in BIP-44, BIP-48, BIP-49, BIP-84 and BIP-86, either in full or are at the deepest hardened level (excluding change
and address_index
), are considered standard.
If the display
parameter is 0
and the path is not standard, an error is returned.
If the display
parameter is 1
, the result is also shown on the secure screen for verification. The UX flow shows on the device screen the exact path and the complete serialized extended pubkey as defined in BIP-32 for that path. If the path is not standard, an additional warning is shown to the user.
Registers a wallet policy on the device, after validating it with the user.
Command
CLA | INS |
---|---|
E1 | 02 |
Input data
Length | Name | Description |
---|---|---|
<variable> |
policy_length |
The length of the policy (unsigned varint) |
policy_length |
policy |
The serialized wallet policy |
The policy
is serialized as described here. At this time, no policy can be longer than 252 bytes, therefore the policy_length
field is always encoded as 1 byte.
Output data
Length | Description |
---|---|
32 |
The wallet_id |
32 |
The hmac for this wallet |
This command allows to register a wallet policy on the device. The wallet's name, descriptor template and each of the keys information is shown to the user.
After user's validation is completed successfully, the application returns the wallet_id
(sha256 of the wallet serialization), and the hmac
for this wallet.
GET_PREIMAGE
must know and respond for the full serialized wallet policy whose sha256 hash is wallet_id
; moreover, it must know and respond for the sha256 hash of its descriptor template.
The client must respond to the GET_PREIMAGE
, GET_MERKLE_LEAF_PROOF
and GET_MERKLE_LEAF_INDEX
queries related to the Merkle tree of the list of keys information.
The GET_MORE_ELEMENTS
command must be handled.
Get a receive or change a address for a registered or default wallet, after validating it with the user using the trusted screen.
Command
CLA | INS |
---|---|
E1 | 03 |
Input data
Length | Name | Description |
---|---|---|
1 |
display |
0 or 1 |
32 |
wallet_id |
The id of the wallet |
32 |
wallet_hmac |
The hmac of a registered wallet, or exactly 32 0 bytes |
1 |
change |
0 for a receive address, 1 for a change address |
4 |
address_index |
The desired address index (big-endian) |
Output data
Length | Description |
---|---|
The wallet address for the given change/address_index |
For a registered wallet, the hmac must be correct. Once that is validated, this command computes the address of the wallet for the given change
and address_index
choice.
For a default wallet, hmac
must be equal to 32 bytes 0
.
If the display
parameter is 1
, the resulting wallet address is also shown on the secure screen, and only returns successfully after the user confirms it. If the display
parameter is 0
, the result is silently returned.
GET_PREIMAGE
must know and respond for the full serialized wallet policy whose sha256 hash is wallet_id
; moreover, it must know and respond for the sha256 hash of its descriptor template.
The client must respond to the GET_PREIMAGE
, GET_MERKLE_LEAF_PROOF
and GET_MERKLE_LEAF_INDEX
queries related to the Merkle tree of the list of keys information.
The GET_MORE_ELEMENTS
command must be handled.
Given a PSBTv2 and a registered wallet (or a standard one), sign all the inputs that are owned by that wallet.
Command
CLA | INS |
---|---|
E1 | 04 |
Input data
Length | Name | Description |
---|---|---|
<var> |
global_map_size |
The number of key/value pairs of the global map of the psbt |
32 |
global_map_keys_root |
The Merkle root of the keys of the global map |
32 |
global_map_vals_root |
The Merkle root of the values of the global map |
<var> |
n_inputs |
The number of inputs of the psbt |
32 |
inputs_maps_root |
The Merkle root of the vector of Merkleized map commitments for the input maps |
<var> |
n_outputs |
The number of outputs of the psbt |
32 |
outputs_maps_root |
The Merkle root of the vector of Merkleized map commitments for the output maps |
32 |
wallet_id |
The id of the wallet |
32 |
wallet_hmac |
The hmac of a registered wallet, or exactly 32 0 bytes |
Output data
No output data; the signature are returned using the YIELD client command.
Using the information in the PSBT and the wallet description, this command verifies what inputs are internal and what outputs match the pattern for a change address. After validating all the external outputs and the transaction fee with the user, it signs each of the internal inputs; each signature is sent to the client using the YIELD command, in the format described below. If multiple key placeholders of the wallet policy are internal, the process is repeated for each of them.
The results yielded via the YIELD command respect the following format: <input_index> <pubkey_augm_len> <pubkey_augm> <signature>
, where:
input_index
is a Bitcoin style varint, the index input of the input being signed (starting from 0);pubkey_augm_len
is an unsigned byte equal to the length ofpubkey_augm
;pubkey_augm
is thepubkey
used for signing for legacy, segwit or taproot script path spends (a compressed pubkey if non-taproot, a 32-byte x-only pubkey if taproot); for taproot script path spends, it is the concatenation of thex-only
pubkey and the 32-byte tapleaf hash as defined in BIP-0341;signature
is the returned signature, possibly concatenated with the sighash byte (as it would be pushed on the stack).
If P2
is 0
(version 0
of the protocol), pubkey_augm_len
and pubkey_augm
are omitted in the YIELD messages.
For a registered wallet, the hmac must be correct.
For a default wallet, hmac
must be equal to 32 bytes 0
.
GET_PREIMAGE
must know and respond for the full serialized wallet policy whose sha256 hash is wallet_id
; moreover, it must know and respond for the sha256 hash of its descriptor template.
The client must respond to the GET_PREIMAGE
, GET_MERKLE_LEAF_PROOF
and GET_MERKLE_LEAF_INDEX
queries for all the Merkle trees in the input, including each of the Merkle trees for keys and values of the Merkleized map commitments of each of the inputs/outputs maps of the psbt.
The GET_MORE_ELEMENTS
command must be handled.
The YIELD
command must be processed in order to receive the signatures.
Returns the fingerprint of the master public key, as defined in BIP-0032#Key identifiers.
Command
CLA | INS |
---|---|
E1 | 05 |
Input data
No input data.
Output data
Length | Description |
---|---|
4 |
The master key fingerprint |
The fingerprint is necessary to fill the key origin information for some PSBT fields, or to create wallet descriptors.
User interaction is not required for this command.
Signs a message, according to the standard Bitcoin Message Signing.
The device shows on its secure screen the BIP-32 path used for signing, and the SHA256 hash of the message; the hash should be verified by the user using an external tool if the client is untrusted.
Command
CLA | INS |
---|---|
E1 | 10 |
Input data
Length | Name | Description |
---|---|---|
1 |
n |
Number of derivation steps (maximum 8) |
4 |
bip32_path[0] |
First derivation step (big endian) |
4 |
bip32_path[1] |
Second derivation step (big endian) |
... | ||
4 |
bip32_path[n-1] |
n -th derivation step (big endian) |
<var> |
msg_length |
The byte length of the message to sign (Bitcoin-style varint) |
32 |
msg_merkle_root |
The Merkle root of the message, split in 64-byte chunks |
The message to be signed is split into ceil(msg_length/64)
chunks of 64 bytes (except the last chunk that could be smaller); msg_merkle_root
is the root of the Merkle tree of the corresponding list of chunks.
The theoretical maximum valid length of the message is 232-1 = 4 294 967 295 bytes.
Output data
Length | Description |
---|---|
65 |
The returned signature, encoded in the standard Bitcoin message signing format |
The signature is returned as a 65-byte binary string (1 byte equal to 32 or 33, followed by r
and s
, each of them represented as a 32-byte big-endian integer).
The digest being signed is the double-SHA256 of the message, after prefixing the message with:
- the magic string
"\x18Bitcoin Signed Message:\n"
(equal to18426974636f696e205369676e6564204d6573736167653a0a
in hexadecimal) - the length of the message, encoded as a Bitcoin-style variable length integer.
The client must respond to the GET_PREIMAGE
, GET_MERKLE_LEAF_PROOF
and GET_MERKLE_LEAF_INDEX
queries for the Merkle tree of the list of chunks in the message.
This section documents the commands that the Hardware Wallet can request to the client when returning with a SW_INTERRUPTED_EXECUTION
status word.
Signs a Withdrawal message. The message being signed is the hash of the Acre Withdrawal transaction.
The device shows on its secure screen the value of stBTC being withdrawn and the address derived from the output script present in the transaction.
If the address derived from the output scipt does not match with the one derived from the derivation path, the application returns the status word SW_INCORECT_DATA
.
Command
CLA | INS |
---|---|
E1 | 11 |
Input data
Length | Name | Description |
---|---|---|
1 |
n |
Number of derivation steps (maximum 8) |
4 |
bip32_path[0] |
First derivation step (big endian) |
4 |
bip32_path[1] |
Second derivation step (big endian) |
... | ||
4 |
bip32_path[n-1] |
n -th derivation step (big endian) |
1 |
n_chunks |
The total number of data chunks |
32 |
merkle_root |
The Merkle root of the Withdrawal data, split in 64-byte chunks |
The message to be signed is split into 5 + ceil(smart_contract_data.length/64)
chunks of 64 bytes; merkle_root
is the root of the Merkle tree of the corresponding list of chunks.
The data is organized into chunks as follows:
Length | Chunk Number | Content Description |
---|---|---|
60 | 0 | to[20] + gasToken[20] + refundReceiver[20] |
64 | 1 | value[32] + safeTxGas[32] |
64 | 2 | baseGas[32] + gasPrice[32] |
33 | 3 | nonce[32] + operation[1] |
4 | 4 | smart_contract_data[0:4] (the selector of the smart contract data) |
64 | 5 to n | Remaining smart_contract_data split into 64-byte chunks |
Output data
Length | Description |
---|---|
65 |
The returned signature, encoded in the standard Bitcoin message signing format |
The signature is returned as a 65-byte binary string (1 byte equal to 32 or 33, followed by r
and s
, each of them represented as a 32-byte big-endian integer).
The digest being signed is the double-SHA256 of the Withdrawal transaction hash, after prefixing the message with:
- the magic string
"\x18Bitcoin Signed Message:\n"
(equal to18426974636f696e205369676e6564204d6573736167653a0a
in hexadecimal) - the length of the hash, encoded as a Bitcoin-style variable length integer.
The client must respond to the GET_PREIMAGE
, GET_MERKLE_LEAF_PROOF
and GET_MERKLE_LEAF_INDEX
queries for the Merkle tree of the list of chunks in the Withdrawal data.
Signs an Ethereum Sign-In message (ERC-4361) in Bitcoin format.
The device shows on its secure screen the following information:
- Domain
- Address
- URI
- Version
- Nonce
- Issued At
- Expiration Time
The user should verify this information carefully before approving the signature.
Command
CLA | INS |
---|---|
E1 | 12 |
Input data
Length | Name | Description |
---|---|---|
1 |
n |
Number of derivation steps (maximum 8) |
4 |
bip32_path[0] |
First derivation step (big endian) |
4 |
bip32_path[1] |
Second derivation step (big endian) |
... | ||
4 |
bip32_path[n-1] |
n -th derivation step (big endian) |
<var> |
msg_length |
The byte length of the message to sign (Bitcoin-style varint) |
32 |
msg_merkle_root |
The Merkle root of the message, split in 64-byte chunks |
The message to be signed is split into ceil(msg_length/64)
chunks of 64 bytes (except the last chunk that could be smaller); msg_merkle_root
is the root of the Merkle tree of the corresponding list of chunks.
Output data
Length | Description |
---|---|
65 |
The returned signature, encoded in the standard Bitcoin message signing format |
The signature is returned as a 65-byte binary string (1 byte equal to 32 or 33, followed by r
and s
, each of them represented as a 32-byte big-endian integer).
The digest being signed is the double-SHA256 of the ERC-4361 message, after prefixing the message with:
- the magic string
"\x18Bitcoin Signed Message:\n"
(equal to18426974636f696e205369676e6564204d6573736167653a0a
in hexadecimal) - the length of the message, encoded as a Bitcoin-style variable length integer.
Note: The message is restricted to maximum 128 character lines.
The client must respond to the GET_PREIMAGE
, GET_MERKLE_LEAF_PROOF
and GET_MERKLE_LEAF_INDEX
queries for the Merkle tree of the list of chunks in the message.
This section documents the commands that the Hardware Wallet can request to the client when returning with a SW_INTERRUPTED_EXECUTION
status word.
CMD | COMMAND NAME | DESCRIPTION |
---|---|---|
10 | YIELD | Receive some elements during command execution |
40 | GET_PREIMAGE | Return the preimage corresponding to the given sha256 hash |
41 | GET_MERKLE_LEAF_PROOF | Returns the Merkle proof for a given leaf |
42 | GET_MERKLE_LEAF_INDEX | Returns the index of a leaf in a Merkle tree |
A0 | GET_MORE_ELEMENTS | Receive more data that could not fit in the previous responses |
Command code: 0x10
The YIELD
client command is sent to the client to communicate some result during the execution of a command. Currently only used during SIGN_PSBT
in order to communicate each of the signatures. The format of the attached message is documented for each command that uses YIELD
.
The client must respond with an empty message.
Command code: 0x40
The GET_PREIMAGE
command requests the client to reveal a SHA-256 preimage.
The request contains:
1
byte: must equal 0, reserved for future usage. (The client should abort if non-zero);32
bytes: a sha-256 hash.
The response must contain:
<var>
: the length of the preimage, encoded as a Bitcoin-style varint;1
byte: a 1-byte unsigned integerb
, the length of the prefix of the pre-image that is part of the response;b
bytes: corresponding to the firstb
bytes of the preimage.
If the pre-image is too long to be contained in a single response, the client should choose b
to be as large as possible; subsequent bytes are enqueued as single-byte elements that the Hardware Wallet will request with one ore more GET_MORE_ELEMENTS
requests.
Command code: 0x41
The GET_MERKLE_LEAF_PROOF
command requests the hash of a given leaf of a Merkle tree, together with the Merkle proof.
The request contains:
32
bytes: the Merkle root hash;<var>
bytes: the tree sizen
, encoded as a Bitcoin-style varint;<var>
bytes: the leaf indexi
, encoded as a Bitcoin-style varint.
The client must respond with:
32
bytes: the hash of the leaf with indexi
in the requested Merkle tree;1
byte: the length of the Merkle proof;1
byte: the amountp
of hashes of the proof that are contained in the response;32 * p
bytes: the concatenation of the firstp
hashes in the Merkle proof.
If the proof is too long to be contained in a single response, the client should choose p
to be as large as possible; subsequent bytes are enqueued as 32-byte elements that the Hardware Wallet will request with one or more GET_MORE_ELEMENTS
requests.
Command code: 0x42
The GET_MERKLE_LEAF_INDEX
requests the index of a leaf with a certain hash. if multiple leafs have the same hash, the client could respond with either.
The request contains:
32
bytes: the Merkle root hash;32
bytes: the leaf hash.
The response contains:
1
byte:1
if the leaf is found,0
if matching leaf exists;<var>
: the index of the leaf, encoded as a Bitcoin-style varint.
Command code: 0xA0
The GET_MORE_ELEMENTS
command requests the client to return more elements that were enqueued by previous client commands (like GET_PREIMAGE
and GET_MERKLE_LEAF_PROOF
).
All of the elements in the queue must all be byte strings of the same length; the command fails otherwise. The client should return as many elements as it is possible to fit in the response, while leaving the remaining ones (if any) in the queue.
The request is empty.
The response contains:
1
byte: the numbern
of returned element;1
byte: the sizes
of each returned element;n * s
bytes: the concatenation of then
returned elements.
Some of the client commands are used to allow the client to reveal some information that is not known to the hardware wallet. This approach allows to create protocols that work with an amount of data that is too large to fit in a single APDU, or even in the limited RAM of a device like a Ledger Nano S.
In designing the interactive protocol, care is taken to avoid security risks associated with a malicious, possibly compromised client.
All the current commands use a commit-and-reveal approach: the APDU that starts the protocol (first message) commits to all the relevant data (for example, the entirety of the PSBT), by using hashes and/or Merkle trees. Any time the client is asked to reveal some committed information, the app does not consider it trusted:
- If a preimage is asked via
GET_PREIMAGE
, the hash is computed to validate that the correct preimage is returned by the client. - If a Merkle proof is asked via
GET_MERKLE_LEAF_PROOF
, the proof is verified. - If the index of a leaf is asked
GET_MERKLE_LEAF_INDEX
, the proof for that element is requested viaGET_MERKLE_LEAF_PROOF
and the proof verified, even if the leaf value is known.
Care needs to be taken in designing protocols, as the client might lie by omission (for example, fail to reveal that a leaf of a Merkle tree is present during a call to GET_MERKLE_LEAF_INDEX
).