Source file ledgerwallet_tezos.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
open Rresult
open Ledgerwallet
module Version = struct
type app_class = Tezos | TezBake
let pp_app_class ppf = function
| Tezos -> Format.pp_print_string ppf "Tezos Wallet"
| TezBake -> Format.pp_print_string ppf "Tezos Baking"
let class_of_int = function
| 0 -> Tezos
| 1 -> TezBake
| _ -> invalid_arg "class_of_int"
type t = {app_class : app_class; major : int; minor : int; patch : int}
let pp ppf {app_class; major; minor; patch} =
Format.fprintf ppf "%a %d.%d.%d" pp_app_class app_class major minor patch
let create ~app_class ~major ~minor ~patch = {app_class; major; minor; patch}
type Status.t += Tezos_impossible_to_read_version
let () =
Status.register_string_f (function
| Tezos_impossible_to_read_version -> Some "Impossible to read version"
| _ -> None)
let read cs =
try
let app_class = class_of_int (Cstruct.get_uint8 cs 0) in
let major = Cstruct.get_uint8 cs 1 in
let minor = Cstruct.get_uint8 cs 2 in
let patch = Cstruct.get_uint8 cs 3 in
R.ok (create ~app_class ~major ~minor ~patch)
with _ ->
Transport.app_error
~msg:"Version.read"
(R.error Tezos_impossible_to_read_version)
end
type ins =
| Version
| Git_commit
| Authorize_baking
| Get_public_key
| Prompt_public_key
| Sign
| Sign_unsafe
| Reset_high_watermark
| Query_high_watermark
| Get_authorized_key
| Setup
| Query_all_high_watermarks
| Deauthorize_baking
| Get_authorized_path_and_curve
| Make_deterministic_nonce
| Sign_with_hash
let int_of_ins = function
| Version -> 0x00
| Authorize_baking -> 0x01
| Get_public_key -> 0x02
| Prompt_public_key -> 0x03
| Sign -> 0x04
| Sign_unsafe -> 0x05
| Reset_high_watermark -> 0x06
| Query_high_watermark -> 0x08
| Git_commit -> 0x09
| Get_authorized_key -> 0x07
| Setup -> 0x0A
| Query_all_high_watermarks -> 0x0B
| Deauthorize_baking -> 0x0C
| Get_authorized_path_and_curve -> 0x0D
| Make_deterministic_nonce -> 0x0E
| Sign_with_hash -> 0x0F
type curve = Ed25519 | Secp256k1 | Secp256r1 | Bip32_ed25519
let pp_curve ppf = function
| Ed25519 -> Format.pp_print_string ppf "ed25519"
| Secp256k1 -> Format.pp_print_string ppf "secp256k1"
| Secp256r1 -> Format.pp_print_string ppf "P-256"
| Bip32_ed25519 -> Format.pp_print_string ppf "bip25519"
let pp_curve_short ppf = function
| Ed25519 -> Format.pp_print_string ppf "ed"
| Secp256k1 -> Format.pp_print_string ppf "secp"
| Secp256r1 -> Format.pp_print_string ppf "p2"
| Bip32_ed25519 -> Format.pp_print_string ppf "bip25519"
let curve_of_string str =
match String.lowercase_ascii str with
| "ed" | "ed25519" -> Some Ed25519
| "bip25519" | "bip32-ed25519" -> Some Bip32_ed25519
| "secp256k1" -> Some Secp256k1
| "p256" | "p-256" | "secp256r1" -> Some Secp256r1
| _ -> None
let int_of_curve = function
| Ed25519 -> 0x00
| Secp256k1 -> 0x01
| Secp256r1 -> 0x02
| Bip32_ed25519 -> 0x03
let curve_of_int = function
| 0x00 -> Some Ed25519
| 0x01 -> Some Secp256k1
| 0x02 -> Some Secp256r1
| 0x03 -> Some Bip32_ed25519
| _ -> None
type Status.t += Tezos_invalid_curve_code of int | Payload_too_big of int
let () =
Status.register_string_f (function
| Tezos_invalid_curve_code curve_code ->
Some ("Unrecognized curve code: " ^ string_of_int curve_code)
| Payload_too_big size ->
Some (Printf.sprintf "Payload too big: %d bytes" size)
| _ -> None)
let () =
Status.register_help_suggestor_f (function
| Status.Conditions_of_use_not_satisfied ->
Some
"Either you rejected the operation or you waited long enough to \
respond that the device rejected it for you."
| Status.Incorrect_class ->
Some
"A Tezos application wasn't found on the device. Is the Tezos \
Wallet or Tezos Baking application open on the device? Is the \
device busy talking to another process?"
| Status.Security_status_unsatisfied ->
Some
"The operation was automatically rejected for security reasons. If \
baking, you may need to setup the device or reset the high-water \
mark."
| _ -> None)
let wrap_ins cmd =
Apdu.create_cmd ~cmd ~cla_of_cmd:(fun _ -> 0x80) ~ins_of_cmd:int_of_ins
let get_version ?pp ?buf h =
let apdu = Apdu.create (wrap_ins Version) in
Transport.apdu ~msg:"get_version" ?pp ?buf h apdu >>= Version.read
let get_git_commit ?pp ?buf h =
let apdu = Apdu.create (wrap_ins Git_commit) in
Transport.apdu ~msg:"get_git_commit" ?pp ?buf h apdu >>| Cstruct.to_string
let read_path_with_length buf =
let length = Cstruct.get_uint8 buf 0 in
let rec go acc path =
if Cstruct.length path = 0 || List.length acc = length then List.rev acc
else go (Cstruct.BE.get_uint32 path 0 :: acc) (Cstruct.shift path 4)
in
go [] (Cstruct.shift buf 1)
let get_authorized_key ?pp ?buf h =
let apdu = Apdu.create (wrap_ins Get_authorized_key) in
Transport.apdu ~msg:"get_authorized_key" ?pp ?buf h apdu >>| fun path ->
read_path_with_length path
let get_authorized_path_and_curve ?pp ?buf h =
let apdu = Apdu.create (wrap_ins Get_authorized_path_and_curve) in
Transport.apdu ~msg:"get_authorized_path_and_curve" ?pp ?buf h apdu
>>= fun payload ->
let curve_code = Cstruct.get_uint8 payload 0 in
match curve_of_int curve_code with
| None ->
Transport.app_error
~msg:"get_authorized_path_and_curve"
(R.error (Tezos_invalid_curve_code curve_code))
| Some curve ->
let path_components = read_path_with_length (Cstruct.shift payload 1) in
R.ok (path_components, curve)
let write_path cs path =
ListLabels.fold_left path ~init:cs ~f:(fun cs i ->
Cstruct.BE.set_uint32 cs 0 i ;
Cstruct.shift cs 4)
let get_public_key_like cmd ?pp ?buf h curve path =
let nb_derivations = List.length path in
if nb_derivations > 10 then invalid_arg "get_public_key: max 10 derivations" ;
let lc = 1 + (4 * nb_derivations) in
let data_init = Cstruct.create lc in
Cstruct.set_uint8 data_init 0 nb_derivations ;
let data = Cstruct.shift data_init 1 in
let _data = write_path data path in
let msg = "get_public_key" in
let apdu =
Apdu.create ~p2:(int_of_curve curve) ~lc ~data:data_init (wrap_ins cmd)
in
Transport.apdu ~msg ?pp ?buf h apdu >>| fun addr ->
let keylen = Cstruct.get_uint8 addr 0 in
Cstruct.sub addr 1 keylen
let get_public_key ?(prompt = true) =
let cmd = if prompt then Prompt_public_key else Get_public_key in
get_public_key_like cmd
let authorize_baking = get_public_key_like Authorize_baking
let setup_baking ?pp ?buf h ~main_chain_id ~main_hwm ~test_hwm curve path =
let nb_derivations = List.length path in
if nb_derivations > 10 then
invalid_arg "Ledgerwallet_tezos.setup: max 10 derivations" ;
let lc =
(3 * 4) + 1 + (4 * nb_derivations)
in
let data_init = Cstruct.create lc in
assert (String.length main_chain_id = 4) ;
for ith = 0 to 3 do
Cstruct.set_uint8 data_init ith (int_of_char main_chain_id.[ith])
done ;
Cstruct.BE.set_uint32 data_init 4 main_hwm ;
Cstruct.BE.set_uint32 data_init 8 test_hwm ;
Cstruct.set_uint8 data_init 12 nb_derivations ;
let (_ : Cstruct.t) =
let data = Cstruct.shift data_init (12 + 1) in
write_path data path
in
let msg = "setup" in
let apdu =
Apdu.create ~p2:(int_of_curve curve) ~lc ~data:data_init (wrap_ins Setup)
in
Transport.apdu ~msg ?pp ?buf h apdu >>| fun addr ->
let keylen = Cstruct.get_uint8 addr 0 in
Cstruct.sub addr 1 keylen
let deauthorize_baking ?pp ?buf h =
let apdu = Apdu.create (wrap_ins Deauthorize_baking) in
Transport.apdu ~msg:"deauthorize_baking" ?pp ?buf h apdu >>| fun _ -> ()
let get_high_watermark ?pp ?buf h =
let apdu = Apdu.create (wrap_ins Query_high_watermark) in
Transport.apdu ~msg:"get_high_watermark" ?pp ?buf h apdu >>| fun data ->
let has_migrated_to_tenderbake = Cstruct.length data >= 8 in
if has_migrated_to_tenderbake then
(Cstruct.BE.get_uint32 data 0, Some (Cstruct.BE.get_uint32 data 4))
else (Cstruct.BE.get_uint32 data 0, None)
let get_all_high_watermarks ?pp ?buf h =
let apdu = Apdu.create (wrap_ins Query_all_high_watermarks) in
Transport.apdu ~msg:"get_high_watermark" ?pp ?buf h apdu >>| fun data ->
let has_migrated_to_tenderbake = Cstruct.length data >= 20 in
if has_migrated_to_tenderbake then
let main_hwm = Cstruct.BE.get_uint32 data 0 in
let main_hwm_round = Cstruct.BE.get_uint32 data 4 in
let test_hwm = Cstruct.BE.get_uint32 data 8 in
let test_hwm_round = Cstruct.BE.get_uint32 data 12 in
let chain_id = Cstruct.copy data 16 4 in
( `Main_hwm (main_hwm, Some main_hwm_round),
`Test_hwm (test_hwm, Some test_hwm_round),
`Chain_id chain_id )
else
let main_hwm = Cstruct.BE.get_uint32 data 0 in
let test_hwm = Cstruct.BE.get_uint32 data 4 in
let chain_id = Cstruct.copy data 8 4 in
(`Main_hwm (main_hwm, None), `Test_hwm (test_hwm, None), `Chain_id chain_id)
let set_high_watermark ?pp ?buf h hwm =
let data = Cstruct.create 4 in
Cstruct.BE.set_uint32 data 0 hwm ;
let apdu = Apdu.create ~lc:4 ~data (wrap_ins Reset_high_watermark) in
Transport.apdu ~msg:"set_high_watermark" ?pp ?buf h apdu >>| ignore
let sign ?pp ?buf ?(hash_on_ledger = true) h curve path payload =
let nb_derivations = List.length path in
if nb_derivations > 10 then invalid_arg "get_public_key: max 10 derivations" ;
let lc = 1 + (4 * nb_derivations) in
let data_init = Cstruct.create lc in
Cstruct.set_uint8 data_init 0 nb_derivations ;
let data = Cstruct.shift data_init 1 in
let _data = write_path data path in
let cmd = wrap_ins (if hash_on_ledger then Sign else Sign_unsafe) in
let msg = "sign" in
let apdu = Apdu.create ~p2:(int_of_curve curve) ~lc ~data:data_init cmd in
let _addr = Transport.apdu ~msg ?pp ?buf h apdu in
Transport.write_payload ~mark_last:true ?pp ?buf ~msg ~cmd h ~p1:0x01 payload
let get_deterministic_nonce ?pp ?buf h curve path payload =
let nb_derivations = List.length path in
if nb_derivations > 10 then
invalid_arg "get_deterministic_nonce: max 10 derivations" ;
let path_data =
let lc = 1 + (4 * nb_derivations) in
let data = Cstruct.create lc in
Cstruct.set_uint8 data 0 nb_derivations ;
let _ = write_path (Cstruct.shift data 1) path in
data
in
let data = Cstruct.append path_data payload in
let cmd = wrap_ins Make_deterministic_nonce in
let lc = Cstruct.length data in
if lc >= Apdu.max_data_length then
Transport.app_error ~msg:"get_deterministic_nonce"
@@ R.error (Payload_too_big (Cstruct.length payload))
else
let apdu = Apdu.create ~p2:(int_of_curve curve) ~lc ~data cmd in
let msg = "make-deterministic-nonce" in
Transport.apdu ~msg ?pp ?buf h apdu
let sign_and_hash ?pp ?buf h curve path payload =
let nb_derivations = List.length path in
if nb_derivations > 10 then invalid_arg "get_public_key: max 10 derivations" ;
let lc = 1 + (4 * nb_derivations) in
let data_init = Cstruct.create lc in
Cstruct.set_uint8 data_init 0 nb_derivations ;
let data = Cstruct.shift data_init 1 in
let _data = write_path data path in
let cmd = wrap_ins Sign_with_hash in
let msg = "sign-with-hash" in
let apdu = Apdu.create ~p2:(int_of_curve curve) ~lc ~data:data_init cmd in
let _addr = Transport.apdu ~msg ?pp ?buf h apdu in
Transport.write_payload ~mark_last:true ?pp ?buf ~msg ~cmd h ~p1:0x01 payload
>>= fun bytes ->
let hash, signature = Cstruct.split bytes 32 in
R.return (hash, signature)