You will need a Linux kernel with libcomposite
, dummy_hcd
and configfs
.
Either compile your own kernel or use linux-zen
.
modprobe libcomposite
,modprobe dummy_hcd
,modprobe configfs
make wallera-linux
sudo ./wallera-linux
Here are some LedgerJS-based examples one can use to fiddle with the implementation.
Each packet is 64 bytes in size, as specified by the HID report descriptor.
Example packet:
[38 190 5 0 0 0 5 85 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
|___| | |_| |_| |________________________________________________________________________________________________________________|
| | | | |-> Upper-level data packet
| | | |-> Upper-level data packet length
| | |-> Packet index
| |-> Tag
|-> Channel ID
The maximum amount of data each upper-level data packet can hold is 64-7 = 57 bytes.
An hypothetical Go struct representing a HID frame is the following:
type HIDFrame struct {
ChannelID uint16
Tag uint8
PacketIndex uint16
DataLength uint16
Data [57]byte
}
A follow-up packet is defined as follows:
type HIDFrameNext struct {
ChannelID uint16
Tag uint8
PacketIndex uint16
Data [59]byte
}
The upper-level data packet framing is implementation-dependent, typically APDU is used.
PSA: hypothesis ahead!
Each packet can hold maximum of 57 bytes, times max(uint16)
= 57 * 65535 = 3735495 bytes is the maximum amount of bytes we can send over this framing.
AFAIS Ledger add a channel identification number but doesn't really like being connected to multiple parties at the same time, AKA only one program running on the host can access the Ledger.
This greatly simplifies how we reason about the system
If we assume 57 bytes per packet received, DataLength
should decrease for each packet we receive.
When DataLength =< 57
it means the packet we just received is the last one and we can now pass the resulting data slice to the upper-level layer.
Short summary:
- handle error codes correctly when commands do not return properly (check what the client code expects)
- design an extensible
crypto
package
Most (if not all) cryptocurrencies use the same cryptographic primitives (variations of secp256k1, BIP-44 and BIP-39).
We should design an interface for the crypto
package which is extensible and usable in both development sessions (so through wallera-linux
) and on secure hardware (ARM TrustZone, USB Armory Mk.II).
Ideally we should design the secret derivation algorithm in a way that "derives secrets from secrets".
Given a "transformation box", a constant diversifier is fed to it and the resulting 256 bits of entropy gets used in the BIP-39 derivation algorithm.
For the USB Armory Mk.II we could use the integrated AES secret key in the following way:
- let
div
be the constant diversifier - let
aes(div)
be the result of AES encryption ofdiv
with the i.MX6 secret, fused AES key - the entropy from which the BIP-39 mnemonic is generated is the result of
SHA256(aes(div))
On wallera-linux
we could simply embed a byte slice at compile-time, so that everybody uses the same test keys.
APDU packet schema is here
The signature session is weird.
They can create three kinds of session:
init
: contains derivation path used to sign the blob, only one packet of this kind will ever be observedadd
: contains beginning of data blob to sign, multiple packets of this kind can be observedlast
: contains remaining part of the data to be signed
Instead of using a single HID session and send all the data over, the Cosmos app needs this three-session approach because potentially a Cosmos signature client could beam over megabytes of data to be signed.
Signature payload is chunked in chunks of 255 bytes in size, so excluding init
sessions which are always less than 57 bytes in length:
add
sessions beam always 255 bytes of datalast
sessions beam always less than 255 bytes of data
For each session, we have to pass data to the Cosmos app from the usb
layer, let it process and respond back with OK/Error, otherwise the signature flow fails.
This means we have to build some sort of SignatureSession
object which should persist across session handling calls: a state machine on top of another state machine.
An example signature request, with a payload of 355 bytes in length will work as follows:
- a
init
session is created, which consists of 1HIDFrame
frame; Cosmos app will elaborate this by initializing aSignatureSession
internally, storing the derivation path in it - a
add
session is created, which consists in 1HIDFrame
frame, 4HIDFrameNext
frames; Cosmos app will add the resulting data bytes in abytes.Buffer
- a
last
session is created, which consists in 1HIDFrame
frame, 1HIDFrameNext
frame; Cosmos app will append the resulting data bytes in thebytes.Buffer
created previously, sign the 355 bytes and send back a signature