Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/27989 Ledger tx signature #85

Merged
merged 22 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
87934ce
27989: LedgerKey class extending base Key
sdrug Feb 7, 2022
801d34b
27989: adjusting withWallet middleware to sign tx with ledger if requ…
sdrug Feb 7, 2022
48671a7
27989: Adding await to invokeMiddlewares
sdrug Feb 7, 2022
cf71bd4
27989: Making LedgerKey constructor private
sdrug Feb 7, 2022
bb8bc89
27989: prevent LedgerKey from keeping connection to the ledger open
sdrug Feb 8, 2022
1f127ea
27989: adding secp256k1 to parse the signature
sdrug Feb 8, 2022
5c2a28e
27989: redefining createSignature in LedgerKey to comply with amino e…
sdrug Feb 8, 2022
905e5bd
27979: LedgerKey centralised error handling
sdrug Feb 8, 2022
56b9f9e
27989: LedgerKey signing in legacy amino signing mode
sdrug Feb 9, 2022
8c05d70
27989: signatureImport input data changed straight to buffer
sdrug Feb 9, 2022
96b66f4
27989: Passing signMode straight into createAndSignTx
sdrug Feb 9, 2022
c8242bc
27989: formatting
sdrug Feb 9, 2022
2909c0e
regenerate yarn lock
RodrigoAD Feb 9, 2022
a5ce10b
fix integer validation on set billing
RodrigoAD Feb 9, 2022
b982df7
updated deps
RodrigoAD Feb 9, 2022
b2805af
27989: BIP44 compliant path parsing
sdrug Feb 9, 2022
ab31607
27989: checkForErrors -> assertNoErrors
sdrug Feb 9, 2022
e3c1039
add libusb1 to nix to solve the libudev issue
tateexon Feb 9, 2022
40d7219
27989: Formatting
sdrug Feb 10, 2022
ac1b164
Merge branch 'main' into feature/27989-ledger-support
sdrug Feb 10, 2022
ee092e7
Fix upload test to use yarn instead of executable
tateexon Feb 10, 2022
e1b8903
27989: resolving conflicts in packages versions
sdrug Feb 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ install:
go install github.com/onsi/ginkgo/v2/ginkgo

build_js:
yarn install --frozen-lockfile && yarn bundle
yarn install --frozen-lockfile

build_contracts:
./scripts/build-contracts.sh
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ require (
github.com/satori/go.uuid v1.2.0
github.com/smartcontractkit/chainlink v1.0.1-0.20220114200720-95a78cb9fc2b
github.com/smartcontractkit/chainlink-relay v0.0.0-20220208185145-f90ff8f9d79a
github.com/smartcontractkit/helmenv v1.0.32
github.com/smartcontractkit/integrations-framework v1.0.46
github.com/smartcontractkit/helmenv v1.0.35
github.com/smartcontractkit/integrations-framework v1.0.47
github.com/smartcontractkit/libocr v0.0.0-20220121130134-5d2b1d5f424b
github.com/smartcontractkit/terra.go v1.0.3-0.20220108002221-62b39252ee16
github.com/stretchr/testify v1.7.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,8 @@ github.com/smartcontractkit/helmenv v1.0.20/go.mod h1:oFDq/QeOcHMJ8Cl+UEOA3tAmYM
github.com/smartcontractkit/helmenv v1.0.27/go.mod h1:ef0doolSZf8ckqaWMIK2M+EPXdIKYVzttd6EXaCgCK4=
github.com/smartcontractkit/helmenv v1.0.32 h1:CX8bcmZOsAPbhwdUbaD4AJxqgJV6R1CfgEb0YjT8CAc=
github.com/smartcontractkit/helmenv v1.0.32/go.mod h1:ef0doolSZf8ckqaWMIK2M+EPXdIKYVzttd6EXaCgCK4=
github.com/smartcontractkit/helmenv v1.0.35 h1:WwSRfWK5VKE9NdarlbjG+fq31YEhoxvImOSgSJmwQEw=
github.com/smartcontractkit/helmenv v1.0.35/go.mod h1:pkScfFRZM9UhMAqeG+Bfxyy7YjLWh8pY6vmNGdTpKJs=
github.com/smartcontractkit/integrations-framework v1.0.21/go.mod h1:mceWG89OYgDkqIVD64Ocp2pn1utqoHAC4ycVV33JOTo=
github.com/smartcontractkit/integrations-framework v1.0.24/go.mod h1:yFgbE88F6+om0jo5mFskgKEQBnxqfYg6H1OL2dCIekk=
github.com/smartcontractkit/integrations-framework v1.0.44 h1:/2lAuWN5ELKFb36NIM++49ktXKLHJMfppB1cA78mn0c=
Expand All @@ -2399,6 +2401,8 @@ github.com/smartcontractkit/integrations-framework v1.0.45 h1:HP3f6sYgz4n1i1fqBM
github.com/smartcontractkit/integrations-framework v1.0.45/go.mod h1:p4uumn3/C/96UNFzKQZcThJ0uhyUPUbXW0wkzO2PsGc=
github.com/smartcontractkit/integrations-framework v1.0.46 h1:0v6H3K/EO7sfyqeHD/cgY6cxotOUUllgoi3BJn3gG1U=
github.com/smartcontractkit/integrations-framework v1.0.46/go.mod h1:80yVYzfn5jz2ubPbDEuD7Kw0a55cS+o7tZlTHQu4VQc=
github.com/smartcontractkit/integrations-framework v1.0.47 h1:cqTmqIEPRLXpfLrvhlOHl1nNZ9wFQuM/uPeazrvnDYw=
github.com/smartcontractkit/integrations-framework v1.0.47/go.mod h1:vxO+yFQqnDh2Y707zensXk6JxJuRWrclJ/GdS/nS7jE=
github.com/smartcontractkit/libocr v0.0.0-20201203233047-5d9b24f0cbb5/go.mod h1:bfdSuLnBWCkafDvPGsQ1V6nrXhg046gh227MKi4zkpc=
github.com/smartcontractkit/libocr v0.0.0-20211117215336-6c9726817b2d/go.mod h1:nq3crM3wVqnyMlM/4ZydTuJ/WyCapAsOt7P94oRgSPg=
github.com/smartcontractkit/libocr v0.0.0-20211202172717-e8b0536a572e/go.mod h1:nq3crM3wVqnyMlM/4ZydTuJ/WyCapAsOt7P94oRgSPg=
Expand Down
2 changes: 1 addition & 1 deletion packages-ts/gauntlet-terra-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"dependencies": {
"@chainlink/gauntlet-core": "0.0.7",
"@chainlink/gauntlet-terra": "*",
"@terra-money/terra.js": "^3.0.4",
"@terra-money/terra.js": "^3.0.6",
"ajv": "^8.6.3"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const instructionToCommand = (instruction: AbstractInstruction<any, any>)
instruction.validateInput(commandInput)
const input = await instruction.makeContractInput(commandInput)
const abstractCommand = await makeAbstractCommand(id, this.flags, this.args, input)
abstractCommand.invokeMiddlewares(abstractCommand, abstractCommand.middlewares)
await abstractCommand.invokeMiddlewares(abstractCommand, abstractCommand.middlewares)
return abstractCommand.execute()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ const validateInput = (input: CommandInput): boolean => {
}

try {
observationPayment = BN(input.observationPaymentGjuels)
transmissionPayment = BN(input.transmissionPaymentGjuels) // parse as integers
observationPayment = new BN(input.observationPaymentGjuels)
transmissionPayment = new BN(input.transmissionPaymentGjuels) // parse as integers
} catch {
throw new Error(
`observationPaymentGjuels=${input.observationPaymentGjuels} and ` +
Expand Down
7 changes: 5 additions & 2 deletions packages-ts/gauntlet-terra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
"bundle": "yarn build && pkg ."
},
"dependencies": {
"@terra-money/terra.js": "^3.0.4",
"@chainlink/gauntlet-core": "0.0.7"
"@chainlink/gauntlet-core": "0.0.7",
"@ledgerhq/hw-transport-node-hid": "^6.20.0",
"@terra-money/ledger-terra-js": "^1.2.1",
"@terra-money/terra.js": "^3.0.6",
"secp256k1": "^4.0.3"
}
}
11 changes: 11 additions & 0 deletions packages-ts/gauntlet-terra/src/commands/internal/terra.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Result, WriteCommand } from '@chainlink/gauntlet-core'
import { logger } from '@chainlink/gauntlet-core/dist/utils'
import { MsgStoreCode } from '@terra-money/terra.js'
import { SignMode } from '@terra-money/terra.proto/cosmos/tx/signing/v1beta1/signing'

import { withProvider, withWallet, withCodeIds, withNetwork } from '../middlewares'
import {
Expand All @@ -12,6 +13,7 @@ import {
Wallet,
} from '@terra-money/terra.js'
import { TransactionResponse } from '../types'
import { LedgerKey } from '../ledgerKey'

type CodeIds = Record<string, number>

Expand Down Expand Up @@ -60,6 +62,9 @@ export default abstract class TerraCommand extends WriteCommand<TransactionRespo

const tx = await this.wallet.createAndSignTx({
msgs: [msg],
...(this.wallet.key instanceof LedgerKey && {
sdrug marked this conversation as resolved.
Show resolved Hide resolved
signMode: SignMode.SIGN_MODE_LEGACY_AMINO_JSON,
}),
})

const res = await this.provider.tx.broadcast(tx)
Expand All @@ -78,6 +83,9 @@ export default abstract class TerraCommand extends WriteCommand<TransactionRespo
const instantiateTx = await this.wallet.createAndSignTx({
msgs: [instantiate],
memo: 'Instantiating',
...(this.wallet.key instanceof LedgerKey && {
signMode: SignMode.SIGN_MODE_LEGACY_AMINO_JSON,
}),
})
logger.loading(`Deploying contract...`)
const res = await this.provider.tx.broadcast(instantiateTx)
Expand All @@ -91,6 +99,9 @@ export default abstract class TerraCommand extends WriteCommand<TransactionRespo
const tx = await this.wallet.createAndSignTx({
msgs: [code],
memo: `Storing ${contractName}`,
...(this.wallet.key instanceof LedgerKey && {
signMode: SignMode.SIGN_MODE_LEGACY_AMINO_JSON,
}),
})

logger.loading(`Uploading ${contractName} contract code...`)
Expand Down
83 changes: 83 additions & 0 deletions packages-ts/gauntlet-terra/src/commands/ledgerKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Key, SimplePublicKey, SignatureV2, SignDoc, SignerInfo, ModeInfo } from '@terra-money/terra.js'
import LedgerTerraConnector, { ERROR_CODE, CommonResponse } from '@terra-money/ledger-terra-js'
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
import { logger } from '@chainlink/gauntlet-core/dist/utils'
import { signatureImport } from 'secp256k1'

const BIP44_REGEX = /^(44)\'\s*\/\s*(\d+)\'\s*\/\s*([0,1]+)\'\s*\/\s*(\d+)\s*\/\s*(\d+)$/

export class LedgerKey extends Key {
private path: Array<number>

private constructor(path: Array<number>) {
super()
this.path = path
}

public async initialize() {
const { ledgerConnector, terminateConnection } = await this.connectToLedger()

const response = await ledgerConnector.getPublicKey(this.path)
this.assertNoErrors(response)

this.publicKey = new SimplePublicKey(Buffer.from(response.compressed_pk.data).toString('base64'))
await terminateConnection()
}

public static async create(path: string): Promise<LedgerKey> {
const pathArr = this.bip44PathtoArray(path)
const ledgerKey = new LedgerKey(pathArr)
await ledgerKey.initialize()

return ledgerKey
}

public async sign(payload: Buffer): Promise<Buffer> {
const { ledgerConnector, terminateConnection } = await this.connectToLedger()
try {
logger.info('Approve tx on your Ledger device.')
const response = await ledgerConnector.sign(this.path, payload)
this.assertNoErrors(response)

const signature = signatureImport(Buffer.from(response.signature.data))
return Buffer.from(signature)
} catch (e) {
logger.error(`LedgerKey sign failed: ${e.message}`)
throw e
} finally {
await terminateConnection()
}
}

private async connectToLedger() {
const transport = await TransportNodeHid.create()
const ledgerConnector = new LedgerTerraConnector(transport)
const response = await ledgerConnector.initialize()
this.assertNoErrors(response)

return {
ledgerConnector,
terminateConnection: transport.close.bind(transport),
}
}

private static bip44PathtoArray(path: string): Array<number> {
sdrug marked this conversation as resolved.
Show resolved Hide resolved
const match = BIP44_REGEX.exec(path)
if (!match) throw new Error('Invalid BIP44 path!')

return match.slice(1).map(Number)
}

private assertNoErrors(response: CommonResponse) {
if (!response) return

const { error_message: ledgerError, return_code: returnCode, device_locked: isLocked } = response

if (returnCode === ERROR_CODE.NoError) return

if (isLocked) {
throw new Error('Is Ledger unlocked and Terra app open?')
}
throw new Error(ledgerError)
}
}
27 changes: 17 additions & 10 deletions packages-ts/gauntlet-terra/src/commands/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { LCDClient, MnemonicKey } from '@terra-money/terra.js'
import { LCDClient, Key, MnemonicKey } from '@terra-money/terra.js'
import { LedgerKey } from './ledgerKey'
import { Middleware, Next } from '@chainlink/gauntlet-core'
import { assertions, io, logger } from '@chainlink/gauntlet-core/dist/utils'
import TerraCommand from './internal/terra'
import path from 'path'
import { existsSync } from 'fs'
import { BIP44_LUNA_PATH } from '../lib/constants'

const isValidURL = (a) => true
export const withProvider: Middleware = (c: TerraCommand, next: Next) => {
Expand All @@ -20,17 +22,22 @@ export const withProvider: Middleware = (c: TerraCommand, next: Next) => {
return next()
}

export const withWallet: Middleware = (c: TerraCommand, next: Next) => {
const mnemonic = process.env.MNEMONIC
assertions.assert(!!mnemonic, `Missing MNEMONIC, please add one`)
export const withWallet: Middleware = async (c: TerraCommand, next: Next) => {
let key: Key
if (c.flags.withLedger || !!process.env.WITH_LEDGER) {
const path = c.flags.ledgerPath || BIP44_LUNA_PATH
key = await LedgerKey.create(path)
} else {
const mnemonic = process.env.MNEMONIC
assertions.assert(!!mnemonic, `Missing MNEMONIC, please add one`)

const mk = new MnemonicKey({
mnemonic: mnemonic,
})
key = new MnemonicKey({
mnemonic: mnemonic,
})
}

const wallet = c.provider.wallet(mk)
c.wallet = wallet
logger.debug(`Operator address is ${wallet.key.accAddress}`)
c.wallet = c.provider.wallet(key)
logger.info(`Operator address is ${c.wallet.key.accAddress}`)
return next()
}

Expand Down
1 change: 1 addition & 0 deletions packages-ts/gauntlet-terra/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const ADDRESS_ZERO = 'terra000000000000000000000000000000000000000'
export const BIP44_LUNA_PATH = "44'/330'/0'/0/0"
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pkgs.mkShell {
# Keep this nodejs version in sync with the version in .tool-versions please
nodejs-14_x
(yarn.override { nodejs = nodejs-14_x; })
libusb1
];
RUST_BACKTRACE = "1";
GOROOT="${pkgs.go_1_17}/share/go";
Expand Down
12 changes: 1 addition & 11 deletions tests/e2e/smoke/gauntlet_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package smoke_test

import (
"fmt"
"math/big"
"os"
"os/exec"
"path/filepath"

. "github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -63,19 +61,11 @@ var _ = Describe("Terra Gauntlet @gauntlet", func() {
})
By("Setup Gauntlet", func() {
networkDirPath = filepath.Join(utils.ProjectRoot, "./packages-ts/gauntlet-terra-contracts/networks")
_, err := exec.LookPath("yarn")
Expect(err).ShouldNot(HaveOccurred())

cwd, _ := os.Getwd()
err = os.Chdir(filepath.Join(cwd + "../../../.."))
Expect(err).ShouldNot(HaveOccurred())

gauntletBin := fmt.Sprintf(
"%s%s",
filepath.Join(cwd, "../../../bin/gauntlet-terra-"),
gauntlet.GetOsVersion(),
)
g, err = gauntlet.NewGauntlet(gauntletBin)
g, err = gauntlet.NewGauntlet()
Expect(err).ShouldNot(HaveOccurred())

terraNodeUrl, err := e.Charts.Connections("localterra").LocalURLByPort("lcd", environment.HTTP)
Expand Down
Loading