accounts: eip-712 signing for ledger (#22378)
* accounts: eip-712 signing for ledger * address review comments
This commit is contained in:
parent
eaccdba4ab
commit
aab35600bc
@ -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.
|
||||||
//
|
//
|
||||||
|
@ -185,6 +185,10 @@ func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
|
|||||||
return w.trezorSign(path, tx, chainID)
|
return w.trezorSign(path, tx, chainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *trezorDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) {
|
||||||
|
return nil, accounts.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
// trezorDerive sends a derivation request to the Trezor device and returns the
|
// trezorDerive sends a derivation request to the Trezor device and returns the
|
||||||
// Ethereum address located on that path.
|
// Ethereum address located on that path.
|
||||||
func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) {
|
func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) {
|
||||||
|
@ -67,6 +67,8 @@ type driver interface {
|
|||||||
// SignTx sends the transaction to the USB device and waits for the user to confirm
|
// SignTx sends the transaction to the USB device and waits for the user to confirm
|
||||||
// or deny the transaction.
|
// or deny the transaction.
|
||||||
SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error)
|
SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error)
|
||||||
|
|
||||||
|
SignTypedMessage(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wallet represents the common functionality shared by all USB hardware
|
// wallet represents the common functionality shared by all USB hardware
|
||||||
@ -524,7 +526,46 @@ func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error)
|
|||||||
|
|
||||||
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
return w.signHash(account, crypto.Keccak256(data))
|
|
||||||
|
// Unless we are doing 712 signing, simply dispatch to signHash
|
||||||
|
if !(mimeType == accounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) {
|
||||||
|
return w.signHash(account, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch to 712 signing if the mimetype is TypedData and the format matches
|
||||||
|
w.stateLock.RLock() // Comms have own mutex, this is for the state fields
|
||||||
|
defer w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
// If the wallet is closed, abort
|
||||||
|
if w.device == nil {
|
||||||
|
return nil, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
// Make sure the requested account is contained within
|
||||||
|
path, ok := w.paths[account.Address]
|
||||||
|
if !ok {
|
||||||
|
return nil, accounts.ErrUnknownAccount
|
||||||
|
}
|
||||||
|
// All infos gathered and metadata checks out, request signing
|
||||||
|
<-w.commsLock
|
||||||
|
defer func() { w.commsLock <- struct{}{} }()
|
||||||
|
|
||||||
|
// Ensure the device isn't screwed with while user confirmation is pending
|
||||||
|
// TODO(karalabe): remove if hotplug lands on Windows
|
||||||
|
w.hub.commsLock.Lock()
|
||||||
|
w.hub.commsPend++
|
||||||
|
w.hub.commsLock.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
w.hub.commsLock.Lock()
|
||||||
|
w.hub.commsPend--
|
||||||
|
w.hub.commsLock.Unlock()
|
||||||
|
}()
|
||||||
|
// Sign the transaction
|
||||||
|
signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return signature, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given
|
// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
|
Loading…
Reference in New Issue
Block a user