Skip to content

Commit

Permalink
Sign messages by Trezor (#10903)
Browse files Browse the repository at this point in the history
  • Loading branch information
spylogsster authored Nov 12, 2021
1 parent 4608fbf commit a375958
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 99 deletions.
6 changes: 5 additions & 1 deletion components/brave_wallet/browser/brave_wallet_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,11 @@ constexpr webui::LocalizedString kLocalizedStrings[] = {
{"braveWalletSignOnDeviceError",
IDS_BRAVE_WALLET_HARDWARE_TRANSACTION_DEVICE_ERROR},
{"braveWalletNoMessageToSignError",
IDS_BRAVE_WALLET_HARDWARE_TRANSACTION_NO_MESSAGE_TO_SIGN_ERROR}};
IDS_BRAVE_WALLET_HARDWARE_TRANSACTION_NO_MESSAGE_TO_SIGN_ERROR},
{"braveWalletProcessMessageError",
IDS_BRAVE_WALLET_HARDWARE_SIGN_MESSAGE_ERROR},
{"braveWalletUnknownKeyringError",
IDS_BRAVE_WALLET_HARDWARE_UNKNOWN_KEYRING_ERROR}};

const char kRopstenSwapBaseAPIURL[] = "https://ropsten.api.0x.org/";
const char kRopstenBuyTokenPercentageFee[] = "0.00875";
Expand Down
12 changes: 11 additions & 1 deletion components/brave_wallet_ui/common/async/hardware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// you can obtain one at http://mozilla.org/MPL/2.0/.

import * as BraveWallet from 'gen/brave/components/brave_wallet/common/brave_wallet.mojom.m.js'
import { SignHardwareTransactionType } from '../hardware_operations'
import { SignHardwareTransactionType, SignHardwareMessageOperationResult } from '../hardware_operations'
import { getLocale } from '../../../common/locale'
import WalletApiProxy from '../../common/wallet_api_proxy'
import LedgerBridgeKeyring from '../../common/ledgerjs/eth_ledger_bridge_keyring'
Expand Down Expand Up @@ -53,3 +53,13 @@ export async function signLedgerTransaction (apiProxy: WalletApiProxy, path: str
}
return { success: result.status }
}

export async function signMessageWithHardwareKeyring (apiProxy: WalletApiProxy, vendor: string, path: string, address: string, message: string): Promise<SignHardwareMessageOperationResult> {
const deviceKeyring = await apiProxy.getKeyringsByType(vendor)
if (deviceKeyring instanceof LedgerBridgeKeyring) {
return deviceKeyring.signPersonalMessage(path, address, message)
} else if (deviceKeyring instanceof TrezorBridgeKeyring) {
return deviceKeyring.signPersonalMessage(path, message)
}
return { success: false, error: getLocale('braveWalletUnknownKeyringError') }
}
10 changes: 9 additions & 1 deletion components/brave_wallet_ui/common/hardware_operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ export interface SignHardwareTransactionType {
success: boolean
error?: string
}

export interface SignatureVRS {
v: number
r: string
s: string
}
export type HardwareOperationResult = {
success: boolean
error?: string
Expand All @@ -14,3 +18,7 @@ export type HardwareOperationResult = {
export type SignHardwareTransactionOperationResult = HardwareOperationResult & {
payload?: EthereumSignedTx
}

export type SignHardwareMessageOperationResult = HardwareOperationResult & {
payload?: string
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
} from '../../components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/types'

import {
LEDGER_HARDWARE_VENDOR,
SignatureVRS
LEDGER_HARDWARE_VENDOR
} from '../../constants/types'
import { SignatureVRS } from '../hardware_operations'

class MockApp {
signature: SignatureVRS
Expand Down Expand Up @@ -108,21 +108,15 @@ test('Sign personal message successfully', () => {
const ledgerHardwareKeyring = new LedgerBridgeKeyring()
ledgerHardwareKeyring.app = new MockApp()
ledgerHardwareKeyring.app.signature = { v: 1, r: 'b68983', s: 'r68983' }
ledgerHardwareKeyring._recoverAddressFromSignature = (message: string, signature: string) => {
return '0x111'
}
return expect(ledgerHardwareKeyring.signPersonalMessage(
'm/44\'/60\'/0\'/0/0', '0x111', 'message'))
.resolves.toStrictEqual('0xb68983r68983-26')
.resolves.toStrictEqual({ payload: '0xb68983r68983-26', success: true })
})

test('Sign personal message failed', () => {
const ledgerHardwareKeyring = new LedgerBridgeKeyring()
const ledgerHardwareKeyring = createLedgerKeyring()
ledgerHardwareKeyring.app = new MockApp()
ledgerHardwareKeyring._recoverAddressFromSignature = (message: string, signature: string) => {
return '0x111'
}
return expect(ledgerHardwareKeyring.signPersonalMessage(
'm/44\'/60\'/0\'/0/0', '0x111', 'message'))
.rejects.toThrow()
.resolves.toMatchObject({ success: false })
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */

const { EventEmitter } = require('events')

import Eth from '@ledgerhq/hw-app-eth'
import TransportWebHID from '@ledgerhq/hw-transport-webhid'
import { LEDGER_HARDWARE_VENDOR } from 'gen/brave/components/brave_wallet/common/brave_wallet.mojom.m.js'
import {
LedgerDerivationPaths
} from '../../components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/types'

import {
LEDGER_HARDWARE_VENDOR, SignatureVRS
} from '../../constants/types'

import Eth from '@ledgerhq/hw-app-eth'
import TransportWebHID from '@ledgerhq/hw-transport-webhid'
import { getLocale } from '../../../common/locale'
import { hardwareDeviceIdFromAddress } from '../hardwareDeviceIdFromAddress'
import { SignHardwareTransactionOperationResult } from '../../common/hardware_operations'
import { SignatureVRS, SignHardwareMessageOperationResult, SignHardwareTransactionOperationResult } from '../../common/hardware_operations'

export default class LedgerBridgeKeyring extends EventEmitter {
constructor () {
Expand Down Expand Up @@ -70,27 +65,24 @@ export default class LedgerBridgeKeyring extends EventEmitter {
return { success: true, payload: signed }
}

signPersonalMessage = async (path: string, address: string, message: string) => {
return new Promise(async (resolve, reject) => {
try {
if (!this.isUnlocked() && !(await this.unlock())) {
return new Error(getLocale('braveWalletUnlockError'))
}
return this.app.signPersonalMessage(path,
Buffer.from(message)).then((result: SignatureVRS) => {
const signature = this._createMessageSignature(result, message, address)
if (!signature) {
return reject(new Error(getLocale('braveWalletLedgerValidationError')))
}
resolve(signature)
}).catch(reject)
} catch (e) {
reject(e)
signPersonalMessage = async (path: string, address: string, message: string): Promise<SignHardwareMessageOperationResult> => {
if (!this.isUnlocked() && !(await this.unlock())) {
return { success: false, error: getLocale('braveWalletUnlockError') }
}
try {
const data = await this.app.signPersonalMessage(path,
Buffer.from(message))
const signature = this.createMessageSignature(data)
if (!signature) {
return { success: false, error: getLocale('braveWalletLedgerValidationError') }
}
})
return { success: true, payload: signature }
} catch (e) {
return { success: false, error: e.message }
}
}

_createMessageSignature = (result: SignatureVRS, message: string, address: string) => {
private readonly createMessageSignature = (result: SignatureVRS) => {
let v = (result.v - 27).toString()
if (v.length < 2) {
v = `0${v}`
Expand All @@ -100,7 +92,7 @@ export default class LedgerBridgeKeyring extends EventEmitter {
}

/* PRIVATE METHODS */
_getPathForIndex = (index: number, scheme: string) => {
private readonly getPathForIndex = (index: number, scheme: string) => {
if (scheme === LedgerDerivationPaths.LedgerLive) {
return `m/44'/60'/${index}'/0/0`
} else if (scheme === LedgerDerivationPaths.Legacy) {
Expand All @@ -120,7 +112,7 @@ export default class LedgerBridgeKeyring extends EventEmitter {
_getAccounts = async (from: number, to: number, scheme: string) => {
const accounts = []
for (let i = from; i <= to; i++) {
const path = this._getPathForIndex(i, scheme)
const path = this.getPathForIndex(i, scheme)
const address = await this._getAddress(path)
accounts.push({
address: address.address,
Expand Down
49 changes: 29 additions & 20 deletions components/brave_wallet_ui/common/trezor/trezor-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,31 @@
import { loadTimeData } from '../../../common/loadTimeData'
import { Unsuccessful, EthereumSignTransaction, CommonParams, Success } from 'trezor-connect'
import { HDNodeResponse } from 'trezor-connect/lib/typescript'
import { EthereumSignedTx } from 'trezor-connect/lib/typescript/networks/ethereum'
import { EthereumSignedTx, EthereumSignMessage } from 'trezor-connect/lib/typescript/networks/ethereum'
import { MessageSignature } from 'trezor-connect/lib/typescript/trezor/protobuf'
export const kTrezorBridgeUrl = loadTimeData.getString('braveWalletTrezorBridgeUrl')

export enum TrezorCommand {
Unlock = 'trezor-unlock',
GetAccounts = 'trezor-get-accounts',
SignTransaction = 'trezor-sign-treansaction'
SignTransaction = 'trezor-sign-treansaction',
SignMessage = 'trezor-sign-message'
}
export type CommandMessage = {
command: TrezorCommand
id: string
origin: string
}
export type TrezorAccountPath = {
path: string
}
export type GetAccountsCommand = CommandMessage & {
command: TrezorCommand.GetAccounts
paths: TrezorAccountPath[]
}
export type UnlockCommand = CommandMessage & {
command: TrezorCommand.Unlock
}
export type UnlockResponse = CommandMessage & {
result: Boolean
error?: Unsuccessful
}

export type SignTransactionCommandPayload = CommonParams & EthereumSignTransaction

export type SignTransactionCommand = CommandMessage & {
command: TrezorCommand.SignTransaction
payload: SignTransactionCommandPayload
export type TrezorAccountPath = {
path: string
}

export type SignTransactionResponse = Unsuccessful | Success<EthereumSignedTx>

export type TrezorAccount = {
publicKey: string
serializedPath: string
Expand All @@ -55,12 +43,33 @@ export type TrezorGetPublicKeyResponse = Unsuccessful | Success<HDNodeResponse[]
export type GetAccountsResponsePayload = CommandMessage & {
payload: TrezorGetPublicKeyResponse
}
export type GetAccountsCommand = CommandMessage & {
command: TrezorCommand.GetAccounts
paths: TrezorAccountPath[]
}

export type SignTransactionCommandPayload = CommonParams & EthereumSignTransaction
export type SignTransactionCommand = CommandMessage & {
command: TrezorCommand.SignTransaction
payload: SignTransactionCommandPayload
}
export type SignTransactionResponse = Unsuccessful | Success<EthereumSignedTx>
export type SignTransactionResponsePayload = CommandMessage & {
payload: SignTransactionResponse
}
export type TrezorFrameCommand = GetAccountsCommand | UnlockCommand | SignTransactionCommand
export type TrezorFrameResponse = UnlockResponse | GetAccountsResponsePayload | SignTransactionResponsePayload

export type SignMessageCommandPayload = CommonParams & EthereumSignMessage
export type SignMessageCommand = CommandMessage & {
command: TrezorCommand.SignMessage
payload: SignMessageCommandPayload
}
export type SignMessageResponse = Unsuccessful | Success<MessageSignature>
export type SignMessageResponsePayload = CommandMessage & {
payload: SignMessageResponse
}

export type TrezorFrameCommand = GetAccountsCommand | UnlockCommand | SignTransactionCommand | SignMessageCommand
export type TrezorFrameResponse = UnlockResponse | GetAccountsResponsePayload | SignTransactionResponsePayload | SignMessageResponsePayload

// Trezor library is loaded inside the chrome-untrusted webui page
// and communication is going through posting messages between parent window
Expand Down
33 changes: 30 additions & 3 deletions components/brave_wallet_ui/common/trezor/trezor_bridge_keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import {
TrezorAccount,
SignTransactionCommandPayload,
TrezorFrameCommand,
SignTransactionResponsePayload
SignTransactionResponsePayload,
SignMessageCommandPayload,
SignMessageResponsePayload
} from '../../common/trezor/trezor-messages'
import { sendTrezorCommand } from '../../common/trezor/trezor-bridge-transport'
import { getLocale } from '../../../common/locale'
import { hardwareDeviceIdFromAddress } from '../hardwareDeviceIdFromAddress'
import { SignHardwareTransactionOperationResult } from '../../common/hardware_operations'
import { SignHardwareMessageOperationResult, SignHardwareTransactionOperationResult } from '../../common/hardware_operations'

export default class TrezorBridgeKeyring extends EventEmitter {
constructor () {
Expand Down Expand Up @@ -78,6 +80,27 @@ export default class TrezorBridgeKeyring extends EventEmitter {
return { success: true, payload: data.payload.payload }
}

signPersonalMessage = async (path: string, message: string): Promise<SignHardwareMessageOperationResult> => {
if (!this.isUnlocked() && !(await this.unlock())) {
return { success: false, error: getLocale('braveWalletUnlockError') }
}
const data = await this.sendTrezorCommand<SignMessageResponsePayload>({
command: TrezorCommand.SignMessage,
// @ts-expect-error
id: crypto.randomUUID(),
payload: this.prepareSignMessagePayload(path, message),
origin: window.origin
})
if (!data) {
return { success: false, error: getLocale('braveWalletProcessMessageError') }
}
if (!data.payload.success) {
const unsuccess = data.payload
return { success: false, error: unsuccess.payload.error, code: unsuccess.payload.code }
}
return { success: true, payload: data.payload.payload.signature }
}

isUnlocked = () => {
return this.unlocked_
}
Expand All @@ -88,7 +111,7 @@ export default class TrezorBridgeKeyring extends EventEmitter {
id: crypto.randomUUID(),
origin: window.origin,
command: TrezorCommand.Unlock
})
})
if (!data) {
return false
}
Expand Down Expand Up @@ -158,6 +181,10 @@ export default class TrezorBridgeKeyring extends EventEmitter {
}
}

private readonly prepareSignMessagePayload = (path: string, message: string): SignMessageCommandPayload => {
return { path: path, message: message }
}

private readonly publicKeyToAddress = (key: string) => {
const buffer = Buffer.from(key, 'hex')
const address = publicToAddress(buffer, true).toString('hex')
Expand Down
6 changes: 0 additions & 6 deletions components/brave_wallet_ui/constants/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ export interface AssetOptionType {
logo: string
}

export interface SignatureVRS {
v: number
r: string
s: string
}

export interface UserAssetOptionType {
asset: AssetOptionType
assetBalance: number
Expand Down
Loading

0 comments on commit a375958

Please sign in to comment.