#apdu #ledger #ledger-hardware #protocols #hardware-wallet #encode-decode

no-std ledger-proto

Ledger hardware wallet protocol / APDU definitions

1 unstable release

0.1.0 Jun 16, 2023

#1406 in Hardware support

Download history 72/week @ 2023-12-14 30/week @ 2023-12-21 66/week @ 2023-12-28 77/week @ 2024-01-04 66/week @ 2024-01-11 72/week @ 2024-01-18 87/week @ 2024-01-25 134/week @ 2024-02-01 98/week @ 2024-02-08 32/week @ 2024-02-15 56/week @ 2024-02-22 47/week @ 2024-02-29 23/week @ 2024-03-07 20/week @ 2024-03-14 74/week @ 2024-03-21 33/week @ 2024-03-28

168 downloads per month
Used in 3 crates

Apache-2.0

19KB
327 lines

Ledger Hardware Wallet APDU traits and shared types.

This provides abstractions for encoding and decoding APDUs for to support interaction with Ledger devices.

APDUs must implement [ApduBase] as well as encdec::Encode and encdec::Decode (or encdec::DecodeOwned) for binary serialisation, with commands providing header information via [ApduReq]. encdec::Encode and encdec::Decode can be automatically derived using encdec macros, or manually implemented over existing objects / encodings.

An [ApduStatic] helper is provided to automatically implement [ApduReq] for APDU requests with static headers and a common [ApduError] type is provided to unify serialisation and deserialisation errors across APDU objects.

Examples

Command APDU (no body) using [ApduStatic]:

use ledger_proto::{ApduStatic, ApduError, Encode, DecodeOwned};

/// Application information request APDU
#[derive(Clone, Debug, PartialEq, Encode, DecodeOwned)]
#[encdec(error = "ApduError")]
pub struct AppInfoReq {}

/// Set CLA and INS values for [AppInfoReq]
impl ApduStatic for AppInfoReq {
    /// Application Info GET APDU is class `0xb0`
    const CLA: u8 = 0xb0;
    /// Application Info GET APDU is instruction `0x00`
    const INS: u8 = 0x01;
}

Manual response APDU implementation

use ledger_proto::{ApduStatic, ApduError, Encode, Decode};

/// Example response APDU
#[derive(Clone, Debug, PartialEq)]
pub struct StringResp<'a> {
    pub value: &'a str,
}

/// [Encode] implementation for [StringResp]
impl <'a> Encode for StringResp<'a> {
  type Error = ApduError;

  /// Fetch encoded length
  fn encode_len(&self) -> Result<usize, Self::Error> {
      Ok(1 + self.value.as_bytes().len())
  }

  /// Encode to bytes
  fn encode(&self, buff: &mut [u8]) -> Result<usize, Self::Error> {
    let b = self.value.as_bytes();

    // Check buffer length is valid
    if buff.len() < self.encode_len()?
        || b.len() > u8::MAX as usize {
      return Err(ApduError::InvalidLength);
    }

    // Write value length
    buff[0] = b.len() as u8;

    // Write value
    buff[1..][..b.len()]
        .copy_from_slice(b);

    Ok(1 + b.len())
  }
}

impl <'a> Decode<'a> for StringResp<'a> {
   type Output = Self;
   type Error = ApduError;

    fn decode(buff: &'a [u8]) -> Result<(Self::Output, usize), Self::Error> {
        // Check buffer length
        if buff.len() < 1 {
            return Err(ApduError::InvalidLength);
        }
        let n = buff[0]as usize;
        if n + 1 > buff.len() {
            return Err(ApduError::InvalidLength);
        }

        // Parse string value
        let s = match core::str::from_utf8(&buff[1..][..n]) {
            Ok(v) => v,
            Err(_) => return Err(ApduError::InvalidUtf8),
        };

        // Return object and parsed length
        Ok((Self{ value: s}, n + 1))
   }
}

For more examples, see the shared APDUs provided in the [apdus] module.

Dependencies

~2MB
~49K SLoC