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) { | ||||||
|  | 
 | ||||||
|  | 	// 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)) | 		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