@@ -22,12 +22,19 @@ const EIP_2645_PATH_LENGTH: usize = 6;
22
22
const PUBLIC_KEY_SIZE : usize = 65 ;
23
23
const SIGNATURE_SIZE : usize = 65 ;
24
24
25
+ /// Ledger app wrapper that implements the [`Signer`] trait.
25
26
#[ derive( Debug ) ]
26
27
pub struct LedgerSigner {
27
- transport : Ledger ,
28
+ app : LedgerStarknetApp ,
28
29
derivation_path : DerivationPath ,
29
30
}
30
31
32
+ /// A handle for communicating with the Ledger Starknet app.
33
+ #[ derive( Debug ) ]
34
+ pub struct LedgerStarknetApp {
35
+ transport : Ledger ,
36
+ }
37
+
31
38
#[ derive( Debug , thiserror:: Error ) ]
32
39
pub enum LedgerError {
33
40
#[ error( "derivation path is empty, not prefixed with m/2645', or is not 6-level long" ) ]
@@ -77,16 +84,14 @@ impl LedgerSigner {
77
84
///
78
85
/// Currently, the Ledger app only enforces the length and the first level of the path.
79
86
pub async fn new ( derivation_path : DerivationPath ) -> Result < Self , LedgerError > {
80
- let transport = Ledger :: init ( ) . await ?;
81
-
82
87
if !matches ! ( derivation_path. iter( ) . next( ) , Some ( & EIP_2645_PURPOSE ) )
83
88
|| derivation_path. len ( ) != EIP_2645_PATH_LENGTH
84
89
{
85
90
return Err ( LedgerError :: InvalidDerivationPath ) ;
86
91
}
87
92
88
93
Ok ( Self {
89
- transport ,
94
+ app : LedgerStarknetApp :: new ( ) . await ? ,
90
95
derivation_path,
91
96
} )
92
97
}
@@ -98,12 +103,57 @@ impl Signer for LedgerSigner {
98
103
type SignError = LedgerError ;
99
104
100
105
async fn get_public_key ( & self ) -> Result < VerifyingKey , Self :: GetPublicKeyError > {
106
+ self . app
107
+ . get_public_key ( self . derivation_path . clone ( ) , false )
108
+ . await
109
+ }
110
+
111
+ async fn sign_hash ( & self , hash : & Felt ) -> Result < Signature , Self :: SignError > {
112
+ self . app . sign_hash ( self . derivation_path . clone ( ) , hash) . await
113
+ }
114
+
115
+ fn is_interactive ( & self ) -> bool {
116
+ true
117
+ }
118
+ }
119
+
120
+ impl LedgerStarknetApp {
121
+ /// Initializes the Starknet Ledger app. Attempts to find and connect to a Ledger device. The
122
+ /// device must be unlocked and have the Starknet app open.
123
+ pub async fn new ( ) -> Result < Self , LedgerError > {
124
+ let transport = Ledger :: init ( ) . await ?;
125
+
126
+ Ok ( Self { transport } )
127
+ }
128
+
129
+ /// Gets a public key from the app for a particular derivation path, with optional on-device
130
+ /// confirmation for extra security.
131
+ ///
132
+ /// The derivation path _must_ follow EIP-2645, i.e. having `2645'` as its "purpose" level as
133
+ /// per BIP-44, as the Ledger app does not allow other paths to be used.
134
+ ///
135
+ /// The path _must_ also be 6-level in length. An example path for Starknet would be:
136
+ ///
137
+ /// `m/2645'/1195502025'/1470455285'/0'/0'/0`
138
+ ///
139
+ /// where:
140
+ ///
141
+ /// - `2645'` is the EIP-2645 prefix
142
+ /// - `1195502025'`, decimal for `0x4741e9c9`, is the 31 lowest bits for `sha256(starknet)`
143
+ /// - `1470455285'`, decimal for `0x57a55df5`, is the 31 lowest bits for `sha256(starkli)`
144
+ ///
145
+ /// Currently, the Ledger app only enforces the length and the first level of the path.
146
+ async fn get_public_key (
147
+ & self ,
148
+ derivation_path : DerivationPath ,
149
+ display : bool ,
150
+ ) -> Result < VerifyingKey , LedgerError > {
101
151
let response = self
102
152
. transport
103
153
. exchange (
104
154
& GetPubKeyCommand {
105
- display : false ,
106
- path : self . derivation_path . clone ( ) ,
155
+ display,
156
+ path : derivation_path,
107
157
}
108
158
. into ( ) ,
109
159
)
@@ -123,13 +173,34 @@ impl Signer for LedgerSigner {
123
173
Ok ( VerifyingKey :: from_scalar ( pubkey_x) )
124
174
}
125
175
126
- async fn sign_hash ( & self , hash : & Felt ) -> Result < Signature , Self :: SignError > {
176
+ /// Requests a signature for a **raw hash** with a certain derivation path. Currently the Ledger
177
+ /// app only supports blind signing raw hashes.
178
+ ///
179
+ /// The derivation path _must_ follow EIP-2645, i.e. having `2645'` as its "purpose" level as
180
+ /// per BIP-44, as the Ledger app does not allow other paths to be used.
181
+ ///
182
+ /// The path _must_ also be 6-level in length. An example path for Starknet would be:
183
+ ///
184
+ /// `m/2645'/1195502025'/1470455285'/0'/0'/0`
185
+ ///
186
+ /// where:
187
+ ///
188
+ /// - `2645'` is the EIP-2645 prefix
189
+ /// - `1195502025'`, decimal for `0x4741e9c9`, is the 31 lowest bits for `sha256(starknet)`
190
+ /// - `1470455285'`, decimal for `0x57a55df5`, is the 31 lowest bits for `sha256(starkli)`
191
+ ///
192
+ /// Currently, the Ledger app only enforces the length and the first level of the path.
193
+ async fn sign_hash (
194
+ & self ,
195
+ derivation_path : DerivationPath ,
196
+ hash : & Felt ,
197
+ ) -> Result < Signature , LedgerError > {
127
198
get_apdu_data (
128
199
& self
129
200
. transport
130
201
. exchange (
131
202
& SignHashCommand1 {
132
- path : self . derivation_path . clone ( ) ,
203
+ path : derivation_path,
133
204
}
134
205
. into ( ) ,
135
206
)
@@ -163,10 +234,6 @@ impl Signer for LedgerSigner {
163
234
164
235
Ok ( signature)
165
236
}
166
-
167
- fn is_interactive ( & self ) -> bool {
168
- true
169
- }
170
237
}
171
238
172
239
impl From < coins_ledger:: LedgerError > for LedgerError {
0 commit comments