@@ -29,19 +29,18 @@ use serialization::Encode;
2929use utils:: ensure;
3030use wallet_types:: hw_data:: LedgerFullInfo ;
3131
32- use ledger_lib:: Exchange ;
32+ use ledger_lib:: { Device , Exchange } ;
33+ use ledger_proto:: StatusCode ;
3334use mintlayer_ledger_messages:: {
34- decode_all as ledger_decode_all, encode as ledger_encode, AddrType , Amount as LAmount ,
35- Bip32Path as LedgerBip32Path , CoinType , GetPublicKeyRespones , GetVersionRespones ,
36- InputAdditionalInfoReq , Ins , MsgSignature , OutputValue as LOutputValue , P1SignTx , PubKeyP1 ,
37- PublicKeyReq , SignMessageReq , SignTxReq , Signature as LedgerSignature , TxInput as LTxInput ,
38- TxInputReq , TxMetadataReq , TxOutput as LTxOutput , TxOutputReq , APDU_CLASS , H256 as LH256 ,
39- P1_APP_NAME , P1_GET_VERSION , P1_SIGN_NEXT , P1_SIGN_START , P2_DONE , P2_SIGN_MORE ,
35+ decode_all as ledger_decode_all, encode as ledger_encode, AddrType , Amount as LAmount , Apdu ,
36+ Bip32Path as LedgerBip32Path , CoinType , GetPublicKeyRespones , InputAdditionalInfoReq , Ins ,
37+ MsgSignature , OutputValue as LOutputValue , P1SignTx , PubKeyP1 , PublicKeyReq , SignMessageReq ,
38+ SignTxReq , Signature as LedgerSignature , TxInput as LTxInput , TxInputReq , TxMetadataReq ,
39+ TxOutput as LTxOutput , TxOutputReq , APDU_CLASS , H256 as LH256 , P1_SIGN_NEXT , P1_SIGN_START ,
40+ P2_DONE , P2_SIGN_MORE ,
4041} ;
4142
42- const MAX_ADPU_LEN : usize = ( u8:: MAX - 5 ) as usize ; // 4 bytes for the header + 1 for len
4343const TIMEOUT_DUR : Duration = Duration :: from_secs ( 100 ) ;
44- const OK_RESPONSE : u16 = 0x9000 ;
4544const TX_VERSION : u8 = 1 ;
4645
4746struct SignatureResult {
@@ -55,13 +54,16 @@ pub fn ok_response(mut resp: Vec<u8>) -> SignerResult<Vec<u8>> {
5554 let ( _, status_code) = resp. split_last_chunk ( ) . ok_or ( LedgerError :: InvalidResponse ) ?;
5655 let response_status = u16:: from_be_bytes ( * status_code) ;
5756
58- ensure ! (
59- response_status == OK_RESPONSE ,
60- LedgerError :: ErrorResponse ( response_status)
61- ) ;
57+ let code = StatusCode :: try_from ( response_status)
58+ . map_err ( |_| LedgerError :: ErrorResponse ( format ! ( "Unknown error: {response_status}" ) ) ) ?;
6259
63- resp. truncate ( resp. len ( ) - size_of_val ( & response_status) ) ;
64- Ok ( resp)
60+ match code {
61+ StatusCode :: Ok => {
62+ resp. truncate ( resp. len ( ) - size_of_val ( & response_status) ) ;
63+ Ok ( resp)
64+ }
65+ err => Err ( LedgerError :: ErrorResponse ( err. to_string ( ) ) . into ( ) ) ,
66+ }
6567}
6668
6769/// Send a message to the Ledger and check the response status code is ok
@@ -84,26 +86,30 @@ async fn send_chunked<L: Exchange>(
8486 message : & [ u8 ] ,
8587) -> Result < Vec < u8 > , SignerError > {
8688 let mut msg_buf = vec ! [ ] ;
87- let mut chunks = message . chunks ( MAX_ADPU_LEN ) . peekable ( ) ;
89+ let chunks = Apdu :: new_chunks ( ins , p1 , message ) ;
8890 let mut resp = vec ! [ ] ;
89- while let Some ( chunk) = chunks. next ( ) {
91+ for chunk in chunks {
9092 msg_buf. clear ( ) ;
93+ msg_buf. reserve ( chunk. bytes_count ( ) ) ;
94+ chunk. write_bytes ( & mut msg_buf) ;
9195
92- let p2 = if chunks. peek ( ) . is_some ( ) {
93- P2_SIGN_MORE
94- } else {
95- P2_DONE
96- } ;
97-
98- msg_buf. extend ( [ APDU_CLASS , ins, p1, p2] ) ;
99- msg_buf. push ( chunk. len ( ) as u8 ) ;
100- msg_buf. extend ( chunk) ;
10196 resp = exchange_message ( ledger, & msg_buf) . await ?;
10297 }
10398
10499 Ok ( resp)
105100}
106101
102+ async fn send_chunked_expect_empty_ok_response < L : Exchange > (
103+ ledger : & mut L ,
104+ ins : u8 ,
105+ p1 : u8 ,
106+ message : & [ u8 ] ,
107+ ) -> Result < ( ) , SignerError > {
108+ let resp = send_chunked ( ledger, ins, p1, message) . await ?;
109+ ensure ! ( resp. is_empty( ) , LedgerError :: InvalidResponse ) ;
110+ Ok ( ( ) )
111+ }
112+
107113pub async fn sign_challenge < L : Exchange > (
108114 ledger : & mut L ,
109115 coin : CoinType ,
@@ -117,7 +123,13 @@ pub async fn sign_challenge<L: Exchange>(
117123 path,
118124 } ;
119125
120- send_chunked ( ledger, Ins :: SIGN_MSG , P1_SIGN_START , & ledger_encode ( req) ) . await ?;
126+ send_chunked_expect_empty_ok_response (
127+ ledger,
128+ Ins :: SIGN_MSG ,
129+ P1_SIGN_START ,
130+ & ledger_encode ( req) ,
131+ )
132+ . await ?;
121133
122134 let resp = send_chunked ( ledger, Ins :: SIGN_MSG , P1_SIGN_NEXT , message) . await ?;
123135
@@ -126,43 +138,48 @@ pub async fn sign_challenge<L: Exchange>(
126138 Ok ( sig. signature . to_vec ( ) )
127139}
128140
129- pub async fn get_app_name < L : Exchange > ( ledger : & mut L ) -> Result < Vec < u8 > , ledger_lib:: Error > {
130- let msg_buf = [ APDU_CLASS , Ins :: APP_NAME , P1_APP_NAME , P2_DONE ] ;
131- ledger. exchange ( & msg_buf, Duration :: from_millis ( 500 ) ) . await
132- }
133-
134- async fn get_app_version < L : Exchange > ( ledger : & mut L ) -> Result < Vec < u8 > , ledger_lib:: Error > {
135- let msg_buf = [ APDU_CLASS , Ins :: GET_VERSION , P1_GET_VERSION , P2_DONE ] ;
136- ledger. exchange ( & msg_buf, Duration :: from_millis ( 500 ) ) . await
137- }
138-
139- pub async fn check_current_app < L : Exchange > ( ledger : & mut L ) -> SignerResult < LedgerFullInfo > {
140- let resp = get_app_name ( ledger)
141+ pub async fn check_current_app < L : Exchange + Device + Send > (
142+ ledger : & mut L ,
143+ ) -> SignerResult < LedgerFullInfo > {
144+ let info = ledger
145+ . app_info ( TIMEOUT_DUR )
141146 . await
142147 . map_err ( |err| LedgerError :: DeviceError ( err. to_string ( ) ) ) ?;
143- let resp = ok_response ( resp ) ? ;
144- let name = String :: from_utf8 ( resp ) . map_err ( |_| LedgerError :: InvalidResponse ) ? ;
148+ let name = info . name ;
149+ let app_version = info . version ;
145150
146151 ensure ! (
147152 name == "mintlayer-app" ,
148153 LedgerError :: DifferentActiveApp ( name)
149154 ) ;
150155
151- let resp = get_app_version ( ledger)
152- . await
153- . map_err ( |err| LedgerError :: DeviceError ( err. to_string ( ) ) ) ?;
154- let ver = ok_response ( resp) ?;
155- let app_version_resp: GetVersionRespones =
156- ledger_decode_all ( & ver) . ok_or ( LedgerError :: InvalidResponse ) ?;
157- let app_version = common:: primitives:: semver:: SemVer {
158- major : app_version_resp. major ,
159- minor : app_version_resp. minor ,
160- patch : app_version_resp. patch as u16 ,
161- } ;
162-
163156 Ok ( LedgerFullInfo { app_version } )
164157}
165158
159+ pub async fn get_extended_public_key_raw < L : Exchange > (
160+ ledger : & mut L ,
161+ coin_type : CoinType ,
162+ derivation_path : & DerivationPath ,
163+ ) -> Result < Vec < u8 > , ledger_lib:: Error > {
164+ let path = LedgerBip32Path (
165+ derivation_path. as_slice ( ) . iter ( ) . map ( |c| c. into_encoded_index ( ) ) . collect ( ) ,
166+ ) ;
167+ let req = PublicKeyReq { coin_type, path } ;
168+ let encoded_req = ledger_encode ( req) ;
169+
170+ let apdu = Apdu :: new_with_data (
171+ Ins :: PUB_KEY ,
172+ PubKeyP1 :: NoDisplayAddress . into ( ) ,
173+ & encoded_req,
174+ )
175+ . expect ( "ok size" ) ;
176+
177+ let mut msg_buf = Vec :: with_capacity ( apdu. bytes_count ( ) ) ;
178+ apdu. write_bytes ( & mut msg_buf) ;
179+
180+ ledger. exchange ( & msg_buf, TIMEOUT_DUR ) . await
181+ }
182+
166183pub async fn get_extended_public_key < L : Exchange > (
167184 ledger : & mut L ,
168185 coin_type : CoinType ,
@@ -206,10 +223,16 @@ pub async fn sign_tx<L: Exchange>(
206223 num_inputs : inputs. len ( ) as u32 ,
207224 num_outputs : outputs. len ( ) as u32 ,
208225 } ) ;
209- send_chunked ( ledger, Ins :: SIGN_TX , P1SignTx :: Metadata . into ( ) , & metadata) . await ?;
226+ send_chunked_expect_empty_ok_response (
227+ ledger,
228+ Ins :: SIGN_TX ,
229+ P1SignTx :: Metadata . into ( ) ,
230+ & metadata,
231+ )
232+ . await ?;
210233
211234 for inp in inputs {
212- send_chunked (
235+ send_chunked_expect_empty_ok_response (
213236 ledger,
214237 Ins :: SIGN_TX ,
215238 P1SignTx :: Input . into ( ) ,
@@ -219,7 +242,7 @@ pub async fn sign_tx<L: Exchange>(
219242 }
220243
221244 for info in input_additional_infos {
222- send_chunked (
245+ send_chunked_expect_empty_ok_response (
223246 ledger,
224247 Ins :: SIGN_TX ,
225248 P1SignTx :: InputAdditionalInfo . into ( ) ,
@@ -228,16 +251,27 @@ pub async fn sign_tx<L: Exchange>(
228251 . await ?;
229252 }
230253
231- // the response from the last output will have the first signature returned
232254 let mut resp = vec ! [ ] ;
233- for o in outputs {
234- resp = send_chunked (
235- ledger,
236- Ins :: SIGN_TX ,
237- P1SignTx :: Output . into ( ) ,
238- & ledger_encode ( SignTxReq :: Output ( o) ) ,
239- )
240- . await ?;
255+ let num_outputs = outputs. len ( ) ;
256+ for ( idx, o) in outputs. into_iter ( ) . enumerate ( ) {
257+ if idx < num_outputs - 1 {
258+ send_chunked_expect_empty_ok_response (
259+ ledger,
260+ Ins :: SIGN_TX ,
261+ P1SignTx :: Output . into ( ) ,
262+ & ledger_encode ( SignTxReq :: Output ( o) ) ,
263+ )
264+ . await ?;
265+ } else {
266+ // the response from the last output will have the first signature returned
267+ resp = send_chunked (
268+ ledger,
269+ Ins :: SIGN_TX ,
270+ P1SignTx :: Output . into ( ) ,
271+ & ledger_encode ( SignTxReq :: Output ( o) ) ,
272+ )
273+ . await ?;
274+ }
241275 }
242276
243277 let mut signatures: BTreeMap < _ , Vec < _ > > = BTreeMap :: new ( ) ;
0 commit comments