Skip to main content

alloy_signer_ledger/
types.rs

1//! Helpers for interacting with the Ethereum Ledger App.
2//!
3//! [Official Docs](https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.adoc)
4
5#![allow(clippy::upper_case_acronyms)]
6
7use alloy_primitives::hex;
8use std::fmt;
9use thiserror::Error;
10
11#[derive(Clone, Debug)]
12/// Ledger wallet type
13pub enum DerivationType {
14    /// Ledger Live-generated HD path
15    LedgerLive(usize),
16    /// Legacy generated HD Path
17    Legacy(usize),
18    /// Any other path
19    Other(String),
20}
21
22impl fmt::Display for DerivationType {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
24        match self {
25            Self::Legacy(index) => write!(f, "m/44'/60'/0'/{index}"),
26            Self::LedgerLive(index) => write!(f, "m/44'/60'/{index}'/0/0"),
27            Self::Other(inner) => f.write_str(inner),
28        }
29    }
30}
31
32/// Error when using the Ledger transport.
33#[derive(Debug, Error)]
34pub enum LedgerError {
35    /// Underlying Ledger transport error.
36    #[error(transparent)]
37    LedgerError(#[from] coins_ledger::errors::LedgerError),
38    /// Device response was unexpectedly empty.
39    #[error("received an unexpected empty response")]
40    UnexpectedNullResponse,
41    /// [`hex`](mod@hex) error.
42    #[error(transparent)]
43    HexError(#[from] hex::FromHexError),
44    /// [`semver`] error.
45    #[error(transparent)]
46    SemVerError(#[from] semver::Error),
47    /// Signature Error
48    #[error(transparent)]
49    SignatureError(#[from] alloy_primitives::SignatureError),
50    /// Thrown when trying to sign using EIP-712 with an incompatible Ledger Ethereum app.
51    #[error("Ledger Ethereum app requires at least version {0}")]
52    UnsupportedAppVersion(&'static str),
53    /// Got a response, but it didn't contain as much data as expected
54    #[error("bad response; got {got} bytes, expected {expected}")]
55    ShortResponse {
56        /// Number of bytes received.
57        got: usize,
58        /// Number of bytes expected.
59        expected: usize,
60    },
61}
62
63pub(crate) const P1_FIRST_0: u8 = 0x00;
64pub(crate) const P1_FIRST_1: u8 = 0x01;
65
66#[repr(u8)]
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68#[expect(non_camel_case_types)]
69#[allow(dead_code)] // Some variants are only used with certain features.
70pub(crate) enum INS {
71    GET_PUBLIC_KEY = 0x02,
72    SIGN = 0x04,
73    GET_APP_CONFIGURATION = 0x06,
74    SIGN_PERSONAL_MESSAGE = 0x08,
75    SIGN_ETH_EIP_712 = 0x0C,
76    SIGN_EIP7702_AUTHORIZATION = 0x34,
77}
78
79impl fmt::Display for INS {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self {
82            Self::GET_PUBLIC_KEY => write!(f, "GET_PUBLIC_KEY"),
83            Self::SIGN => write!(f, "SIGN"),
84            Self::GET_APP_CONFIGURATION => write!(f, "GET_APP_CONFIGURATION"),
85            Self::SIGN_PERSONAL_MESSAGE => write!(f, "SIGN_PERSONAL_MESSAGE"),
86            Self::SIGN_ETH_EIP_712 => write!(f, "SIGN_ETH_EIP_712"),
87            Self::SIGN_EIP7702_AUTHORIZATION => write!(f, "SIGN_EIP7702_AUTHORIZATION"),
88        }
89    }
90}
91
92#[repr(u8)]
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94#[expect(non_camel_case_types)]
95pub(crate) enum P1 {
96    NON_CONFIRM = 0x00,
97    MORE = 0x80,
98}
99
100#[repr(u8)]
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102#[expect(non_camel_case_types)]
103pub(crate) enum P2 {
104    NO_CHAINCODE = 0x00,
105}