|
|
@ -52,8 +52,10 @@ const ( |
|
|
|
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
|
|
|
|
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
|
|
|
|
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
|
|
|
|
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
|
|
|
|
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
|
|
|
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
|
|
|
|
|
|
|
ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification
|
|
|
|
|
|
|
|
|
|
|
|
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
|
|
|
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
|
|
|
|
|
|
|
ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data
|
|
|
|
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
|
|
|
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
|
|
|
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
|
|
|
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
|
|
|
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
|
|
|
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 |
|
|
|
return w.ledgerSign(path, tx, chainID) |
|
|
|
return w.ledgerSign(path, tx, chainID) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SignTypedMessage implements usbwallet.driver, sending the message to the Ledger and
|
|
|
|
|
|
|
|
// waiting for the user to sign or deny the transaction.
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Note: this was introduced in the ledger 1.5.0 firmware
|
|
|
|
|
|
|
|
func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { |
|
|
|
|
|
|
|
// If the Ethereum app doesn't run, abort
|
|
|
|
|
|
|
|
if w.offline() { |
|
|
|
|
|
|
|
return nil, accounts.ErrWalletClosed |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Ensure the wallet is capable of signing the given transaction
|
|
|
|
|
|
|
|
if w.version[0] < 1 && w.version[1] < 5 { |
|
|
|
|
|
|
|
//lint:ignore ST1005 brand name displayed on the console
|
|
|
|
|
|
|
|
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]) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// All infos gathered and metadata checks out, request signing
|
|
|
|
|
|
|
|
return w.ledgerSignTypedMessage(path, domainHash, messageHash) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ledgerVersion retrieves the current version of the Ethereum wallet app running
|
|
|
|
// ledgerVersion retrieves the current version of the Ethereum wallet app running
|
|
|
|
// on the Ledger wallet.
|
|
|
|
// on the Ledger wallet.
|
|
|
|
//
|
|
|
|
//
|
|
|
@ -367,6 +387,68 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction |
|
|
|
return sender, signed, nil |
|
|
|
return sender, signed, nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ledgerSignTypedMessage sends the transaction to the Ledger wallet, and waits for the user
|
|
|
|
|
|
|
|
// to confirm or deny the transaction.
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// The signing protocol is defined as follows:
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// CLA | INS | P1 | P2 | Lc | Le
|
|
|
|
|
|
|
|
// ----+-----+----+-----------------------------+-----+---
|
|
|
|
|
|
|
|
// E0 | 0C | 00 | implementation version : 00 | variable | variable
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Where the input is:
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Description | Length
|
|
|
|
|
|
|
|
// -------------------------------------------------+----------
|
|
|
|
|
|
|
|
// Number of BIP 32 derivations to perform (max 10) | 1 byte
|
|
|
|
|
|
|
|
// First derivation index (big endian) | 4 bytes
|
|
|
|
|
|
|
|
// ... | 4 bytes
|
|
|
|
|
|
|
|
// Last derivation index (big endian) | 4 bytes
|
|
|
|
|
|
|
|
// domain hash | 32 bytes
|
|
|
|
|
|
|
|
// message hash | 32 bytes
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// And the output data is:
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Description | Length
|
|
|
|
|
|
|
|
// ------------+---------
|
|
|
|
|
|
|
|
// signature V | 1 byte
|
|
|
|
|
|
|
|
// signature R | 32 bytes
|
|
|
|
|
|
|
|
// signature S | 32 bytes
|
|
|
|
|
|
|
|
func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) { |
|
|
|
|
|
|
|
// Flatten the derivation path into the Ledger request
|
|
|
|
|
|
|
|
path := make([]byte, 1+4*len(derivationPath)) |
|
|
|
|
|
|
|
path[0] = byte(len(derivationPath)) |
|
|
|
|
|
|
|
for i, component := range derivationPath { |
|
|
|
|
|
|
|
binary.BigEndian.PutUint32(path[1+4*i:], component) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Create the 712 message
|
|
|
|
|
|
|
|
payload := append(path, domainHash...) |
|
|
|
|
|
|
|
payload = append(payload, messageHash...) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Send the request and wait for the response
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
|
|
|
op = ledgerP1InitTypedMessageData |
|
|
|
|
|
|
|
reply []byte |
|
|
|
|
|
|
|
err error |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Send the message over, ensuring it's processed correctly
|
|
|
|
|
|
|
|
reply, err = w.ledgerExchange(ledgerOpSignTypedMessage, op, 0, payload) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Extract the Ethereum signature and do a sanity validation
|
|
|
|
|
|
|
|
if len(reply) != crypto.SignatureLength { |
|
|
|
|
|
|
|
return nil, errors.New("reply lacks signature") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
signature := append(reply[1:], reply[0]) |
|
|
|
|
|
|
|
return signature, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
|
|
|
|
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
|
|
|
|
// message and retrieving the response.
|
|
|
|
// message and retrieving the response.
|
|
|
|
//
|
|
|
|
//
|
|
|
|