Skip to content

Commit

Permalink
Support EIP-1559 for Ledger and Lattice (#532)
Browse files Browse the repository at this point in the history
  • Loading branch information
mholtzman authored Aug 11, 2021
1 parent 62026f4 commit 2f1c6b8
Show file tree
Hide file tree
Showing 9 changed files with 458 additions and 50 deletions.
2 changes: 1 addition & 1 deletion hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ task('send-tx', 'send a test transaction')
.then(accounts => ({
value: utils.parseEther(amount || '.0002').toHexString(),
from: accounts[0],
to: '0x5837ec9a54f71B6be9a304115CcDE7a07b666438',
to: '0xf2C1E45B6611bC4378c3502789957A57e0390B79',
data: '0x',
gasLimit: '0x5208',
}))
Expand Down
2 changes: 1 addition & 1 deletion main/accounts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ class Accounts extends EventEmitter {
if (!signer) return cb(new Error('No signer'))

const data = currentAccount.requests[handlerId].data
cb(null, signerCompatibility(data, signer.type))
cb(null, signerCompatibility(data, signer.summary()))
}

close () {
Expand Down
3 changes: 2 additions & 1 deletion main/signers/Signer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ class Signer extends EventEmitter {
type: this.type,
addresses: this.addresses,
status: this.status,
liveAddressesFound: this.liveAddressesFound || 0
liveAddressesFound: this.liveAddressesFound || 0,
appVersion: this.appVersion || { major: 0, minor: 0, patch: 0 }
}
}

Expand Down
53 changes: 36 additions & 17 deletions main/signers/lattice/Lattice/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const utils = require('web3-utils')
const { padToEven, stripHexPrefix, addHexPrefix } = require('ethereumjs-util')
const { Client } = require('gridplus-sdk')
const { promisify } = require('util')
const { sign, londonToLegacy } = require('../../../transaction')
const { sign, signerCompatibility, londonToLegacy } = require('../../../transaction')

const store = require('../../../store')
const Signer = require('../../Signer')
Expand Down Expand Up @@ -69,6 +69,7 @@ class Lattice extends Signer {
baseUrl,
privKey
})

this.status = 'disconnected'
this.update()
}
Expand Down Expand Up @@ -100,6 +101,12 @@ class Lattice extends Signer {
this.paired = await clientConnect(this.deviceId)

if (this.paired) {
this.appVersion = {
major: this.client.fwVersion[2],
minor: this.client.fwVersion[1],
patch: this.client.fwVersion[0],
}

if (this.addresses.length === 0) {
this.status = 'addresses'
this.update()
Expand Down Expand Up @@ -311,25 +318,37 @@ class Lattice extends Signer {
}
}

_createTransaction (index, chainId, tx) {
const { value, to, data, ...txJson } = tx.toJSON()

const unsignedTx = {
to,
value,
data,
chainId,
nonce: utils.hexToNumber(txJson.nonce),
gasLimit: utils.hexToNumber(txJson.gasLimit),
useEIP155: true,
signerPath: [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, index]
}

const optionalFields = ['gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']

optionalFields.forEach(field => {
if (txJson[field]) {
unsignedTx[field] = utils.hexToNumber(txJson[field])
}
})

return unsignedTx
}

async signTransaction (index, rawTx, cb) {
// as of 08-05-2021 Lattice doesn't support EIP-1559 transactions
const latticeTx = londonToLegacy(rawTx)
const compatibility = signerCompatibility(rawTx, this.summary())
const latticeTx = compatibility.compatible ? { ...rawTx } : londonToLegacy(rawTx)

sign(latticeTx, tx => {
const { value, to, data, ...txJson } = tx.toJSON()

const unsignedTx = {
to,
value,
data,
chainId: latticeTx.chainId,
nonce: utils.hexToNumber(txJson.nonce),
gasPrice: utils.hexToNumber(txJson.gasPrice),
gasLimit: utils.hexToNumber(txJson.gasLimit),
useEIP155: true,
signerPath: [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, index]
}

const unsignedTx = this._createTransaction(index, latticeTx.chainId, tx)
const signOpts = { currency: 'ETH', data: unsignedTx }
const clientSign = promisify(this.client.sign).bind(this.client)

Expand Down
27 changes: 23 additions & 4 deletions main/signers/ledger/Ledger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const Eth = require('@ledgerhq/hw-app-eth').default
const HID = require('node-hid')
const TransportNodeHid = require('@ledgerhq/hw-transport-node-hid').default

const { sign, londonToLegacy } = require('../../../transaction')
const { sign, signerCompatibility, londonToLegacy } = require('../../../transaction')
const store = require('../../../store')
const Signer = require('../../Signer')

Expand Down Expand Up @@ -213,6 +213,11 @@ class Ledger extends Signer {
this.deviceStatus()
}
this.status = 'ok'

const version = (await this._getAppConfiguration()).version
const [major, minor, patch] = (version || '1.6.1').split('.')
this.appVersion = { major, minor, patch }

if (!this.addresses.length) {
this.status = 'loading'
this.deriveAddresses()
Expand Down Expand Up @@ -307,12 +312,14 @@ class Ledger extends Signer {
const eth = await this.getDevice()
const signerPath = this.getPath(index)

// as of 08-05-2021 Ledger doesn't support EIP-1559 transactions
const ledgerTx = londonToLegacy(rawTx)
const compatibility = signerCompatibility(rawTx, this.summary())
const ledgerTx = compatibility.compatible ? { ...rawTx } : londonToLegacy(rawTx)

const signedTx = await sign(ledgerTx, tx => {
// legacy transactions aren't RLP encoded before they're returned
const message = tx.getMessageToSign(false)
const rawTxHex = rlp.encode(message).toString('hex')
const legacyMessage = message[0] !== parseInt(tx.type)
const rawTxHex = legacyMessage ? rlp.encode(message).toString('hex') : message.toString('hex')

return eth.signTransaction(signerPath, rawTxHex)
})
Expand Down Expand Up @@ -360,6 +367,18 @@ class Ledger extends Signer {
}
}

async _getAppConfiguration () {
try {
const eth = await this.getDevice()
const result = await eth.getAppConfiguration()
await this.releaseDevice()
return result
} catch (err) {
await this.releaseDevice()
throw err
}
}

async _deriveLiveAddresses () {
let addresses = []
this.status = 'Deriving Live Addresses'
Expand Down
61 changes: 45 additions & 16 deletions main/transaction/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import { BN, addHexPrefix, stripHexPrefix, bnToHex } from 'ethereumjs-util'
import { JsonTx, Transaction, TransactionFactory, TxData } from '@ethereumjs/tx'
import { JsonTx, TransactionFactory, TxData } from '@ethereumjs/tx'
import Common from '@ethereumjs/common'

import chainConfig from '../chains/config'

const londonHardforkSigners = ['seed', 'ring']
const londonHardforkSigners: SignerCompatibilityByVersion = {
seed: () => true,
ring: () => true,
ledger: version => version.major >= 2 || (version.major >= 1 && version.minor >= 9),
lattice: version => version.major >= 1 || version.minor >= 11
}

type SignerCompatibilityByVersion = {
[key: string]: (version: AppVersion) => boolean
}

interface Signature {
v: string,
r: string,
s: string
}

interface AppVersion {
major: number,
minor: number,
patch: number
}

export interface RawTransaction {
chainId: string,
type: string
}

export interface TransactionData extends JsonTx {
warning?: string,
maxFee: string
warning?: string
}

export interface SignerCompatibility {
Expand All @@ -28,16 +42,29 @@ export interface SignerCompatibility {
compatible: boolean
}

export interface SignerSummary {
id: string,
name: string,
type: string,
addresses: string[],
status: string,
liveAddressesFound: number,
appVersion: AppVersion
}

function toBN (hexStr: string) {
return new BN(stripHexPrefix(hexStr), 'hex')
}

function signerCompatibility (txData: TransactionData, signer: string): SignerCompatibility {
return txData.type === '0x2' ? ({
signer, tx: 'london', compatible: londonHardforkSigners.includes(signer)
}) : ({
signer, tx: 'legacy', compatible: true
})
function signerCompatibility (txData: TransactionData, signer: SignerSummary): SignerCompatibility {
if (typeSupportsBaseFee(txData.type)) {
const compatible = (signer.type in londonHardforkSigners) && londonHardforkSigners[signer.type](signer.appVersion)
return { signer: signer.type, tx: 'london', compatible }
}

return {
signer: signer.type, tx: 'legacy', compatible: true
}
}

function londonToLegacy (txData: TransactionData): TransactionData {
Expand All @@ -50,12 +77,16 @@ function londonToLegacy (txData: TransactionData): TransactionData {
return txData
}

function typeSupportsBaseFee (type: string | undefined) {
return parseInt(type || '0') === 2
}

function usesBaseFee (rawTx: RawTransaction) {
return parseInt(rawTx.type) === 2
return typeSupportsBaseFee(rawTx.type)
}

async function populate (rawTx: RawTransaction, chainConfig: Common, gas: any): Promise<TransactionData> {
const txData: TransactionData = { ...rawTx, maxFee: '' }
function populate (rawTx: RawTransaction, chainConfig: Common, gas: any): TransactionData {
const txData: TransactionData = { ...rawTx }

if (chainConfig.isActivatedEIP(1559)) {
txData.type = '0x2'
Expand All @@ -67,14 +98,12 @@ async function populate (rawTx: RawTransaction, chainConfig: Common, gas: any):

txData.maxPriorityFeePerGas = bnToHex(maxPriorityFee)
txData.maxFeePerGas = bnToHex(maxFee)
txData.maxFee = txData.maxFeePerGas
} else {
txData.type = '0x0'

const gasPrice = toBN(gas.price.levels.fast)

txData.gasPrice = bnToHex(gasPrice)
txData.maxFee = bnToHex(toBN(<string>txData.gasLimit).mul(gasPrice))
}

return txData
Expand All @@ -97,7 +126,7 @@ async function sign (rawTx: RawTransaction, signingFn: (tx: TxData) => Promise<S
return signingFn(tx).then(sig => {
const signature = hexifySignature(sig)

return Transaction.fromTxData(
return TransactionFactory.fromTxData(
{
...rawTx,
...signature
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
"extract-zip": "2.0.1",
"fs-extra": "10.0.0",
"get-pixels": "3.3.3",
"gridplus-sdk": "0.7.27",
"gridplus-sdk": "0.8.0",
"hdkey": "2.0.1",
"ipfs-core": "0.9.1",
"ipfs-http-client": "51.0.1",
Expand Down
Loading

0 comments on commit 2f1c6b8

Please sign in to comment.