Skip to content

Commit

Permalink
test: check token data blob signature
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasbrugneaux committed Jun 18, 2024
1 parent fd9663a commit 9b1d661
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 66 deletions.
1 change: 1 addition & 0 deletions packages/sdk/wallets/wallet-ledger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@celo/contractkit": "^8.0.0",
"@ledgerhq/hw-transport-node-hid": "^6.28.5",
"@noble/hashes": "^1.3.3",
"@noble/curves": "^1.4.0",
"patch-package": "^8.0.0",
"ts-node": "^10.9.2",
"web3": "1.10.4"
Expand Down
15 changes: 15 additions & 0 deletions packages/sdk/wallets/wallet-ledger/src/data.ts
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
export default 'AAAAaARDRUxPRx7ON1DaI3+TuOM5xTaYm4l4pDgAAAASAACk7DBFAiEA5rECRg94+fCoIvoG9/5qWh62zl2C6Y+aFuuZrFe4CtcCIEJbRrkL3gqwT/Jj+7L3neazgpVCCTZZ3HX9JXXg5vleAAAAaARjVVNEdl3oFoRYYedaJfyhIrtomLixKCoAAAASAACk7DBFAiEApwQFHNBKXp+V2jq8BMD2y/5AwC9bhPQ2H4hT/vMl/B4CIFalOVtBFGREUKMU/F5vDlJLeQrTn6GQeDertpB2FpMvAAAAaARjRVVS2HY8uidqNzjm3oW0s79f3tbWynMAAAASAACk7DBFAiEAh2UeP1+SI2Ed5SiAjpJF6MkMrVa94gUwjJztyBlzhWMCIHfaOrEsxdxAGx+P+hxuSNO4zcw6KRLfJkkuic1V/CrHAAAAagZiIENFTE/dyb5X9VP+dXUtYWBrlMvX4CZO+AAAABIAAPNwMEUCIQCi62KsBfuNcfX0MriiRZ7a5DKERhtIz7sZ1SqBT7ruhgIgVrfmavyWzxzDW4AQeHn++A4qPjB1pQKoHvNXo8Hf1SMAAABpBmIgY1VTRGJJKmRKWI/ZBCcL7QatUrmr/qGuAAAAEgAA83AwRAIgGDYx4oB/gkYUqLeXqvEZXx9nOxVHzTe2ajyd2wnehxgCICQBe/rBPcXiaQJj3pdoXxroct/hV6r3G2G7y79EOEAPAAAAaQZiIGNFVVL57OMBJHrSziGJSUGDCiRw9Od0ygAAABIAAPNwMEQCIEdcFWP+HxEUoF1sCGVd34QGS0hL5cVUdrWdqVm3bYTgAiBCMA+Rg3Ubc3xla/35wzZesPlbeSMEPcr4uqL+8PeydwAAAGoGYSBDRUxP8ZSv31CwPmm9fQV8GqnhDJlU5MkAAAASAACu8zBFAiEAk/o0FBus2/QCrunFGEyoneQIRaMRC+y5L6Dvar8MU/kCIByJt2ziRhDG3AAbyXBIuJfZQujSHFcSJL3xF0xIlcPdAAAAaQZhIGNVU0SHQGn6HrFtRNYi8uDKJe6hcjabwQAAABIAAK7zMEQCIClrH2xgE3WMbD+hgQ7t5SiAcVG5WiUZ655voqCszKEoAiA/cO8UVgNY891MNJ5yeDk8w47WO0E1DQecrK71LR8g8gAAAGoGYSBjRVVSEMiSpuxDpT5F0LkWtLfTg7G3jA8AAAASAACu8zBFAiEAgpktbB1ZxyAwMJwKTSbZ30n8zgRuW0twbXoZxlsUAswCIHek4l4CIbjVMG2HVr0Ml9/8kA4F9dr69JBMaoSUkdKl'

// How did we get this? By following these steps:
// 1 - get pubkey from https://github.com/blooo-io/app-celo-spender/pull/7/files#diff-e0cf5b28d9b6b600f0af2bc78e8fd30ec675fd731a5da86f0c4283ffc0e40176L75-L83
// 2 - now you've got to trust me
// 3 - run `ASN1_PREFIX=3056301006072a8648ce3d020106052b8104000a034200`
// 4 - remove spaces, colons, 0xs from the pubkey of step 1, I've done that for you :)
// 5 - and store it in key `KEY=04b06cf5d8f7ed71d8bd9b9dc37944a1c6d240f69bb0be3621dddbb6ac0eccd1508bcc2ea46227e43b941e2c6f1b1cd0ae68e54b185e2cabef3455580604bd45b8`
// 5 - finally run `echo $ASN1_PREFIX$KEY | xxd -r -p - | openssl ec -inform der -pubin -pubout`
// 6 - enjoy
export const legacyLedgerPublicKeyHex = [
`-----BEGIN PUBLIC KEY-----`,
`MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEsGz12Pftcdi9m53DeUShxtJA9puwvjYh`,
`3du2rA7M0VCLzC6kYifkO5QeLG8bHNCuaOVLGF4sq+80VVgGBL1FuA==`,
`-----END PUBLIC KEY-----`,
].join('\n')
3 changes: 1 addition & 2 deletions packages/sdk/wallets/wallet-ledger/src/ledger-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { AddressValidation, LedgerWallet } from './ledger-wallet'
import { legacyTokenInfoByAddressAndChainId, tokenInfoByAddressAndChainId } from './tokens'

const debug = debugFactory('kit:wallet:ledger')
const CELO_APP_ACCEPTS_CONTRACT_DATA_FROM_VERSION = '1.0.2'

/**
* Signs the EVM transaction with a Ledger device
Expand Down Expand Up @@ -171,7 +170,7 @@ export class LedgerSigner implements Signer {
const version = new SemVer(this.appConfiguration.version)
if (meetsVersionRequirements(version, { minimum: LedgerWallet.MIN_VERSION_TOKEN_DATA })) {
const getTokenInfo = meetsVersionRequirements(version, {
minimum: LedgerWallet.MIN_VERSION_EIP159,
minimum: LedgerWallet.MIN_VERSION_EIP1559,
})
? tokenInfoByAddressAndChainId
: legacyTokenInfoByAddressAndChainId
Expand Down
74 changes: 60 additions & 14 deletions packages/sdk/wallets/wallet-ledger/src/ledger-wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import {
import * as ethUtil from '@ethereumjs/util'
import Ledger from '@ledgerhq/hw-app-eth'
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
import { VerifyPublicKeyInput, createVerify } from 'crypto'
import { readFileSync } from 'fs'
import { dirname, join } from 'path'
import Web3 from 'web3'
import { legacyLedgerPublicKeyHex } from './data'
import { meetsVersionRequirements } from './ledger-utils'
import { AddressValidation, LedgerWallet } from './ledger-wallet'

// Update this variable when testing using a physical device
Expand Down Expand Up @@ -116,7 +121,7 @@ interface ILedger {
}

const mockLedgerImplementation = (mockForceValidation: () => void, version: string): ILedger => {
return {
const _ledger = {
getAddress: async (derivationPath: string, forceValidation?: boolean) => {
if (forceValidation) {
mockForceValidation()
Expand Down Expand Up @@ -197,15 +202,55 @@ const mockLedgerImplementation = (mockForceValidation: () => void, version: stri
starkv2Supported: 1,
}
},
provideERC20TokenInformation: async (_token) => {
return true
provideERC20TokenInformation: async (tokenData: string) => {
let pubkey: VerifyPublicKeyInput
const version = (await _ledger.getAppConfiguration()).version
if (
meetsVersionRequirements(version, {
minimum: LedgerWallet.MIN_VERSION_EIP1559,
})
) {
// verify with new pubkey
const pubDir = dirname(require.resolve('@celo/ledger-token-signer'))
pubkey = { key: readFileSync(join(pubDir, 'pubkey.pem')).toString() }
} else {
// verify with oldpubkey
pubkey = { key: legacyLedgerPublicKeyHex }
}

const verify = createVerify('sha256')
const tokenDataBuf = Buffer.from(tokenData, 'hex')
const BASE_DATA_LENGTH =
20 + // contract address, 20 bytes
4 + // decimals, uint32, 4 bytes
4 // chainId, uint32, 4 bytes
// first byte of data is the ticker length, so we add that to base data length
const dataLen = BASE_DATA_LENGTH + tokenDataBuf.readInt8(0)
// start at 1 since the first byte was just informative
const data = tokenDataBuf.slice(1, dataLen + 1)
verify.update(data)
verify.end()
// read from end of data til the end
const signature = tokenDataBuf.slice(dataLen + 1)
const verified = verify.verify(pubkey, signature)

if (!verified) {
throw new Error('couldnt verify data sent to MockLedger')
}
return verified
},
}
return _ledger
}

function mockLedger(wallet: LedgerWallet, mockForceValidation: () => void, version = '1.2.0') {
function mockLedger(
wallet: LedgerWallet,
mockForceValidation: () => void,
version = LedgerWallet.MIN_VERSION_EIP1559
) {
jest
.spyOn<any, any>(wallet, 'generateNewLedger')
.mockClear()
.mockImplementation((_transport: any): ILedger => {
return mockLedgerImplementation(mockForceValidation, version)
})
Expand Down Expand Up @@ -394,7 +439,7 @@ describe('LedgerWallet class', () => {
mockForceValidation = jest.fn((): void => {
// do nothing
})
mockLedger(wallet, mockForceValidation, '1.0.0')
mockLedger(wallet, mockForceValidation, LedgerWallet.MIN_VERSION_TOKEN_DATA)
await wallet.init()

expect(
Expand All @@ -418,7 +463,7 @@ describe('LedgerWallet class', () => {
mockForceValidation = jest.fn((): void => {
// do nothing
})
mockLedger(wallet, mockForceValidation, '1.0.0')
mockLedger(wallet, mockForceValidation, LedgerWallet.MIN_VERSION_TOKEN_DATA)
await wallet.init()
const warnSpy = jest.spyOn(console, 'warn')
// setup complete
Expand Down Expand Up @@ -522,6 +567,7 @@ describe('LedgerWallet class', () => {

describe('[celo-legacy]', () => {
beforeEach(async () => {
const kit = newKit('https://alfajores-forno.celo-testnet.org')
celoTransaction = {
from: knownAddress,
to: otherAddress,
Expand All @@ -530,7 +576,7 @@ describe('LedgerWallet class', () => {
nonce: 0,
gas: 99,
gasPrice: 99,
feeCurrency: '0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73',
feeCurrency: (await kit.contracts.getStableToken(StableToken.cUSD)).address,
}
})
describe('with old ledger app', () => {
Expand All @@ -546,7 +592,7 @@ describe('LedgerWallet class', () => {
mockForceValidation = jest.fn((): void => {
// do nothing
})
mockLedger(wallet, mockForceValidation, '1.0.0')
mockLedger(wallet, mockForceValidation, LedgerWallet.MIN_VERSION_TOKEN_DATA)
await wallet.init()
})

Expand Down Expand Up @@ -576,23 +622,23 @@ describe('LedgerWallet class', () => {
mockForceValidation = jest.fn((): void => {
// do nothing
})
mockLedger(wallet, mockForceValidation, '1.0.0')
mockLedger(wallet, mockForceValidation, LedgerWallet.MIN_VERSION_TOKEN_DATA)
await wallet.init()
const warnSpy = jest.spyOn(console, 'warn')
// setup complete

await expect(wallet.signTransaction(celoTransaction)).resolves
.toMatchInlineSnapshot(`
{
"raw": "0xf87f80636394d8763cba276a3738e6de85b4b3bf5fded6d6ca73808094588e4b68193001e4d10928660ab4165b813717c0880de0b6b3a76400008083015e09a09d8307331534bc7839f055dcaab64d991057da094b021466c734f26b08c3e1f4a02332ec85ff2889e1a05590a97fe0c429fadb2c4260af4dea2b6b69a26c4d60e0",
"raw": "0xf87f80636394874069fa1eb16d44d622f2e0ca25eea172369bc1808094588e4b68193001e4d10928660ab4165b813717c0880de0b6b3a76400008083015e09a024c4b1d027c50d2e847d371cd902d3e22c9fa10fcbd59e9c5a854282afed34daa0686180d75830ea223c3ed1ca12613d029bc3613b4b5b51a724b33067491c2398",
"tx": {
"feeCurrency": "0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73",
"feeCurrency": "0x874069fa1eb16d44d622f2e0ca25eea172369bc1",
"gas": "0x63",
"hash": "0xc0467a86cae1f1a526899e450764c747777dc146f6c021edbe9021c13e1e189b",
"hash": "0x302b12585b4a17317e9affcbe0abd0cd8cd39ef402108625085485b1c1d92d71",
"input": "0x",
"nonce": "0",
"r": "0x9d8307331534bc7839f055dcaab64d991057da094b021466c734f26b08c3e1f4",
"s": "0x2332ec85ff2889e1a05590a97fe0c429fadb2c4260af4dea2b6b69a26c4d60e0",
"r": "0x24c4b1d027c50d2e847d371cd902d3e22c9fa10fcbd59e9c5a854282afed34da",
"s": "0x686180d75830ea223c3ed1ca12613d029bc3613b4b5b51a724b33067491c2398",
"to": "0x588e4b68193001e4d10928660ab4165b813717c0",
"v": "0x015e09",
"value": "0x0de0b6b3a7640000",
Expand Down
10 changes: 5 additions & 5 deletions packages/sdk/wallets/wallet-ledger/src/ledger-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const debug = debugFactory('kit:wallet:ledger')
export class LedgerWallet extends RemoteWallet<LedgerSigner> implements ReadOnlyWallet {
static MIN_VERSION_SUPPORTED = '1.0.0'
static MIN_VERSION_TOKEN_DATA = '1.0.2'
static MIN_VERSION_EIP159 = '1.2.0'
static MIN_VERSION_EIP1559 = '1.2.0'
ledger: Ledger | undefined

/**
Expand Down Expand Up @@ -104,10 +104,10 @@ export class LedgerWallet extends RemoteWallet<LedgerSigner> implements ReadOnly
const version = new SemVer(deviceApp.version)

// if the app is of minimum version it doesnt matter if chain is cel2 or not
if (meetsVersionRequirements(version, { minimum: LedgerWallet.MIN_VERSION_EIP159 })) {
if (meetsVersionRequirements(version, { minimum: LedgerWallet.MIN_VERSION_EIP1559 })) {
if (txParams.gasPrice && txParams.feeCurrency && txParams.feeCurrency !== '0x') {
throw new Error(
`celo ledger app above ${LedgerWallet.MIN_VERSION_EIP159} cannot serialize legacy celo transactions. Replace "gasPrice" with "maxFeePerGas".`
`celo ledger app above ${LedgerWallet.MIN_VERSION_EIP1559} cannot serialize legacy celo transactions. Replace "gasPrice" with "maxFeePerGas".`
)
}
if (txParams.gasPrice) {
Expand All @@ -129,12 +129,12 @@ export class LedgerWallet extends RemoteWallet<LedgerSigner> implements ReadOnly
// but if not celo as layer 2 and as layer 1 are different
} else if (this.isCel2) {
throw new Error(
`celo ledger app version must be at least ${LedgerWallet.MIN_VERSION_EIP159} to sign transactions supported on celo after the L2 upgrade`
`celo ledger app version must be at least ${LedgerWallet.MIN_VERSION_EIP1559} to sign transactions supported on celo after the L2 upgrade`
)
} else {
// the l1 legacy case
console.warn(
`Upgrade your celo ledger app to at least ${LedgerWallet.MIN_VERSION_EIP159} before cel2 transition`
`Upgrade your celo ledger app to at least ${LedgerWallet.MIN_VERSION_EIP1559} before cel2 transition`
)
if (!txParams.gasPrice) {
// this version of app only supports legacy so must have gasPrice
Expand Down
82 changes: 37 additions & 45 deletions packages/sdk/wallets/wallet-ledger/src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,50 +41,42 @@ function generateContractKey(contract: Address, chainId: number): string {
return [normalizeAddressWith0x(contract), chainId].join('-')
}

// this internal get() will lazy load and cache the data from the erc20 data blob
const get: (data: string) => API = (() => {
let cache: API
return (data) => {
if (cache) {
return cache
const get = (data: string): API => {
const buf = Buffer.from(data, 'base64')
const byContract: { [id: string]: TokenInfo } = {}
const entries: TokenInfo[] = []
let i = 0
while (i < buf.length) {
const length = buf.readUInt32BE(i)
i += 4
const item = buf.slice(i, i + length)
let j = 0
const tickerLength = item.readUInt8(j)
j += 1
const ticker = item.slice(j, j + tickerLength).toString('ascii')
j += tickerLength
const contractAddress: string = normalizeAddressWith0x(item.slice(j, j + 20).toString('hex'))
j += 20
const decimals = item.readUInt32BE(j)
j += 4
const chainId = item.readUInt32BE(j)
j += 4
const signature = item.slice(j)
const entry: TokenInfo = {
ticker,
contractAddress,
decimals,
chainId,
signature,
data: item,
}
const buf = Buffer.from(data, 'base64')
const byContract: { [id: string]: TokenInfo } = {}
const entries: TokenInfo[] = []
let i = 0
while (i < buf.length) {
const length = buf.readUInt32BE(i)
i += 4
const item = buf.slice(i, i + length)
let j = 0
const tickerLength = item.readUInt8(j)
j += 1
const ticker = item.slice(j, j + tickerLength).toString('ascii')
j += tickerLength
const contractAddress: string = normalizeAddressWith0x(item.slice(j, j + 20).toString('hex'))
j += 20
const decimals = item.readUInt32BE(j)
j += 4
const chainId = item.readUInt32BE(j)
j += 4
const signature = item.slice(j)
const entry: TokenInfo = {
ticker,
contractAddress,
decimals,
chainId,
signature,
data: item,
}
entries.push(entry)
byContract[generateContractKey(contractAddress, chainId)] = entry
i += length
}
const api = {
list: () => entries,
byContractKey: (id: string) => byContract[id],
}
cache = api
return api
entries.push(entry)
byContract[generateContractKey(contractAddress, chainId)] = entry
i += length
}
})()
const api = {
list: () => entries,
byContractKey: (id: string) => byContract[id],
}
return api
}

0 comments on commit 9b1d661

Please sign in to comment.