@@ -52,8 +52,10 @@ const (
52
52
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
53
53
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
54
54
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
55
+ ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification
55
56
56
57
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
58
+ ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data
57
59
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
58
60
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
59
61
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
@@ -170,6 +172,24 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
170
172
return w .ledgerSign (path , tx , chainID )
171
173
}
172
174
175
+ // SignTypedMessage implements usbwallet.driver, sending the message to the Ledger and
176
+ // waiting for the user to sign or deny the transaction.
177
+ //
178
+ // Note: this was introduced in the ledger 1.5.0 firmware
179
+ func (w * ledgerDriver ) SignTypedMessage (path accounts.DerivationPath , domainHash []byte , messageHash []byte ) ([]byte , error ) {
180
+ // If the Ethereum app doesn't run, abort
181
+ if w .offline () {
182
+ return nil , accounts .ErrWalletClosed
183
+ }
184
+ // Ensure the wallet is capable of signing the given transaction
185
+ if w .version [0 ] < 1 && w .version [1 ] < 5 {
186
+ //lint:ignore ST1005 brand name displayed on the console
187
+ return nil , fmt .Errorf ("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)" , w .version [0 ], w .version [1 ], w .version [2 ])
188
+ }
189
+ // All infos gathered and metadata checks out, request signing
190
+ return w .ledgerSignTypedMessage (path , domainHash , messageHash )
191
+ }
192
+
173
193
// ledgerVersion retrieves the current version of the Ethereum wallet app running
174
194
// on the Ledger wallet.
175
195
//
@@ -367,6 +387,68 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
367
387
return sender , signed , nil
368
388
}
369
389
390
+ // ledgerSignTypedMessage sends the transaction to the Ledger wallet, and waits for the user
391
+ // to confirm or deny the transaction.
392
+ //
393
+ // The signing protocol is defined as follows:
394
+ //
395
+ // CLA | INS | P1 | P2 | Lc | Le
396
+ // ----+-----+----+-----------------------------+-----+---
397
+ // E0 | 0C | 00 | implementation version : 00 | variable | variable
398
+ //
399
+ // Where the input is:
400
+ //
401
+ // Description | Length
402
+ // -------------------------------------------------+----------
403
+ // Number of BIP 32 derivations to perform (max 10) | 1 byte
404
+ // First derivation index (big endian) | 4 bytes
405
+ // ... | 4 bytes
406
+ // Last derivation index (big endian) | 4 bytes
407
+ // domain hash | 32 bytes
408
+ // message hash | 32 bytes
409
+ //
410
+ //
411
+ //
412
+ // And the output data is:
413
+ //
414
+ // Description | Length
415
+ // ------------+---------
416
+ // signature V | 1 byte
417
+ // signature R | 32 bytes
418
+ // signature S | 32 bytes
419
+ func (w * ledgerDriver ) ledgerSignTypedMessage (derivationPath []uint32 , domainHash []byte , messageHash []byte ) ([]byte , error ) {
420
+ // Flatten the derivation path into the Ledger request
421
+ path := make ([]byte , 1 + 4 * len (derivationPath ))
422
+ path [0 ] = byte (len (derivationPath ))
423
+ for i , component := range derivationPath {
424
+ binary .BigEndian .PutUint32 (path [1 + 4 * i :], component )
425
+ }
426
+ // Create the 712 message
427
+ payload := append (path , domainHash ... )
428
+ payload = append (payload , messageHash ... )
429
+
430
+ // Send the request and wait for the response
431
+ var (
432
+ op = ledgerP1InitTypedMessageData
433
+ reply []byte
434
+ err error
435
+ )
436
+
437
+ // Send the message over, ensuring it's processed correctly
438
+ reply , err = w .ledgerExchange (ledgerOpSignTypedMessage , op , 0 , payload )
439
+
440
+ if err != nil {
441
+ return nil , err
442
+ }
443
+
444
+ // Extract the Ethereum signature and do a sanity validation
445
+ if len (reply ) != crypto .SignatureLength {
446
+ return nil , errors .New ("reply lacks signature" )
447
+ }
448
+ signature := append (reply [1 :], reply [0 ])
449
+ return signature , nil
450
+ }
451
+
370
452
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
371
453
// message and retrieving the response.
372
454
//
0 commit comments