add importing evmos address from ledger

This commit is contained in:
liangping
2022-05-24 15:17:25 +08:00
parent 858ca2920c
commit 37d4ff511c
10 changed files with 414 additions and 73 deletions
+86
View File
@@ -0,0 +1,86 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { AminoMsg } from "@cosmjs/amino";
import { EncodeObject } from "@cosmjs/proto-signing";
export interface AminoConverter {
readonly aminoType: string;
readonly toAmino: (value: any) => any;
readonly fromAmino: (value: any) => any;
}
/** A map from protobuf type URL to the AminoConverter implementation if supported on chain */
export type AminoConverters = Record<string, AminoConverter | "not_supported_by_chain">;
function isAminoConverter(
converter: [string, AminoConverter | "not_supported_by_chain"],
): converter is [string, AminoConverter] {
return typeof converter[1] !== "string";
}
/**
* A map from Stargate message types as used in the messages's `Any` type
* to Amino types.
*/
export class AminoTypes {
// The map type here ensures uniqueness of the protobuf type URL in the key.
// There is no uniqueness guarantee of the Amino type identifier in the type
// system or constructor. Instead it's the user's responsibility to ensure
// there is no overlap when fromAmino is called.
private readonly register: Record<string, AminoConverter | "not_supported_by_chain">;
public constructor(types: AminoConverters) {
this.register = types;
}
public toAmino({ typeUrl, value }: EncodeObject): AminoMsg {
const converter = this.register[typeUrl];
if (converter === "not_supported_by_chain") {
throw new Error(
`The message type '${typeUrl}' cannot be signed using the Amino JSON sign mode because this is not supported by chain.`,
);
}
if (!converter) {
throw new Error(
`Type URL '${typeUrl}' does not exist in the Amino message type register. ` +
"If you need support for this message type, you can pass in additional entries to the AminoTypes constructor. " +
"If you think this message type should be included by default, please open an issue at https://github.com/cosmos/cosmjs/issues.",
);
}
return {
type: converter.aminoType,
value: converter.toAmino(value),
};
}
public fromAmino({ type, value }: AminoMsg): EncodeObject {
const matches = Object.entries(this.register)
.filter(isAminoConverter)
.filter(([_typeUrl, { aminoType }]) => aminoType === type);
switch (matches.length) {
case 0: {
throw new Error(
`Amino type identifier '${type}' does not exist in the Amino message type register. ` +
"If you need support for this message type, you can pass in additional entries to the AminoTypes constructor. " +
"If you think this message type should be included by default, please open an issue at https://github.com/cosmos/cosmjs/issues.",
);
}
case 1: {
const [typeUrl, converter] = matches[0];
return {
typeUrl: typeUrl,
value: converter.fromAmino(value),
};
}
default:
throw new Error(
`Multiple types are registered with Amino type identifier '${type}': '` +
matches
.map(([key, _value]) => key)
.sort()
.join("', '") +
"'. Thus fromAmino cannot be performed.",
);
}
}
}
+73 -24
View File
@@ -1,48 +1,97 @@
import { AccountData, AminoSignResponse, encodeSecp256k1Signature, OfflineAminoSigner, StdSignature, StdSignDoc } from "@cosmjs/amino";
import { AminoSignResponse, OfflineAminoSigner, Pubkey, StdSignature, StdSignDoc } from "@cosmjs/amino";
import { AddressAndPubkey } from "@cosmjs/ledger-amino";
import Transport from "@ledgerhq/hw-transport";
import Eth from "@ledgerhq/hw-app-eth";
import { LedgerEthTransactionResolution, LoadConfig } from "@ledgerhq/hw-app-eth/lib/services/types";
import { toHex } from "@cosmjs/encoding";
import { fromBech32, toHex } from "@cosmjs/encoding";
import TransportWebBLE from '@ledgerhq/hw-transport-web-ble'
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
import { HdPath } from "@cosmjs/crypto";
import { ethToCosmos } from '@tharsis/address-converter'
import eth from "@tharsis/proto/dist/proto/ethermint/crypto/v1/ethsecp256k1/keys";
class EthereumLedgerSigner implements OfflineAminoSigner {
export type Algo = "secp256k1" | "ed25519" | "sr25519" | "ethsecp256k1";
export interface AccountData {
/** A printable address (typically bech32 encoded) */
readonly address: string;
readonly algo: Algo;
readonly pubkey: Uint8Array;
}
export class EthereumLedgerSigner {
app: Eth
hdpath: string
constructor(trasport: Transport, hdpath: string, scrambleKey?: string, loadConfig?: LoadConfig) {
this.app = new Eth(trasport, scrambleKey, loadConfig)
static async create(protocol: string, hdpath: string, scrambleKey = "w0w", loadConfig?: LoadConfig): Promise<EthereumLedgerSigner> {
let transport: Promise<Transport> = protocol === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create()
return transport.then(t => {
let instance = new EthereumLedgerSigner()
instance.hdpath = hdpath
instance.app = new Eth(t, scrambleKey, loadConfig)
return instance
})
}
async getAccounts(): Promise<readonly AccountData[]> {
return this.app.getAddress(this.hdpath).then(x => {
const x2: AccountData = {
console.log('eth:', this.app, this.hdpath)
return this.app.getAddress("44'/60'/0'/0/0").then(x => {
const x1: AccountData = {
pubkey: new TextEncoder().encode(x.publicKey),
address: x.address,
algo: "secp256k1"
algo: "ethsecp256k1" // should be 'ethsecp256k1'
}
return [x2]
const x2: AccountData = {
pubkey: new TextEncoder().encode(x.publicKey),
address: ethToCosmos(x.address),
algo: "ethsecp256k1" // // should be 'ethsecp256k1'
}
return [x1, x2]
})
};
async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
return this.getAccounts().then(list => {
const acc = list.find(x => x.address === signerAddress)
if (acc) {
const messageHex: string = toHex(new TextEncoder().encode(JSON.stringify(signDoc)))
this.app.signPersonalMessage(this.hdpath, messageHex).then(r => {
const signature: StdSignature = encodeSecp256k1Signature(acc.pubkey, new TextEncoder().encode(r['s']))
const output: AminoSignResponse = {
signed: signDoc,
signature
}
console.log(r)
return output
goEthAddress(address) {
return `0x${toHex(fromBech32(address).data)}`
}
toPubkey(keyBytes: Uint8Array): Pubkey {
return {
"type": "ethermint.crypto.v1.ethsecp256k1.PubKey",
value: new eth.ethermint.crypto.v1.ethsecp256k1.PubKey({
key: keyBytes,
})
}
}
async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
const ethAddr = this.goEthAddress(signerAddress)
return this.getAccounts().then(list => {
const acc = list.find(x => x.address === ethAddr)
if (acc) {
const messageHex: string = toHex(new TextEncoder().encode(JSON.stringify(signDoc)))
this.app.signPersonalMessage(this.hdpath, messageHex).then(result => {
const signature: StdSignature = {
pub_key: this.toPubkey(acc.pubkey),
signature: result['s']
}
const output: AminoSignResponse = {
signed: signDoc,
signature
}
console.log(result, output)
return output
})
}
throw new Error('Account Does not exists!')
})
};
signTransaction(rawTxHex: string, resolution: LedgerEthTransactionResolution) {
return this.app.signTransaction(this.hdpath, rawTxHex, resolution )
async showAddress(path?: HdPath): Promise<AddressAndPubkey> {
return new Promise((r, j) => { })
}
async signTransaction(rawTxHex: string, resolution: LedgerEthTransactionResolution) {
return this.app.signTransaction(this.hdpath, rawTxHex, resolution)
}
}
+28
View File
@@ -0,0 +1,28 @@
import { AccountData, encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino, StdFee } from "@cosmjs/amino"
import { EncodeObject, encodePubkey, GeneratedType, isOfflineDirectSigner, makeAuthInfoBytes, OfflineSigner, Registry, TxBodyEncodeObject } from "@cosmjs/proto-signing";
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'
import Eth from "@ledgerhq/hw-app-eth";
import Transport from '@ledgerhq/hw-transport'
import { Int53 } from "@cosmjs/math";
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
import { fromBase64 } from "@cosmjs/encoding";
import { SigningStargateClient } from '@cosmjs/stargate'
import { assert } from "@cosmjs/utils";
import { AminoTypes } from "./aminotypes";
export interface SignerData {
readonly accountNumber: number;
readonly sequence: number;
readonly chainId: string;
}
export interface ClientOptions {
readonly registry?: Registry;
readonly aminoTypes?: AminoTypes;
readonly prefix?: string;
}
export default class PingWalletClient extends SigningStargateClient {
}
+25
View File
@@ -2,6 +2,7 @@
import { SigningStargateClient } from '@cosmjs/stargate'
// import { MsgTransfer } from './msg-transfer'
// import { createMessageSend } from '@tharsis/transactions'
import { AminoTypes } from './aminotypes'
import { MsgSwapExactAmountIn } from '../msg'
@@ -25,6 +26,30 @@ export default class PingWalletClient extends SigningStargateClient {
return instance
}
// async signEthermintTransaction(signerAddress, messages, fee, memo, { accountNumber, sequence, chainId }) {
// // const msgs = messages.map(msg => this.aminoTypes.toAmino(msg))
// const tosign = createMessageSend(chainId, signerAddress, fee, memo, null)
// const { signature, signed } = await this.signer.signAmino(tosign.legacyAmino)
// // const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc)
// const signedTxBody = {
// messages,
// memo: signed.memo,
// }
// const signedTxBodyEncodeObject = {
// typeUrl: '/cosmos.tx.v1beta1.TxBody',
// value: signedTxBody,
// }
// const signedTxBodyBytes = this.registry.encode(signedTxBodyEncodeObject)
// const signedGasLimit = math_1.Int53.fromString(signed.fee.gas).toNumber()
// const signedSequence = math_1.Int53.fromString(signed.sequence).toNumber()
// const signedAuthInfoBytes = proto_signing_1.makeAuthInfoBytes([{ pubkey: signature.pub_key, sequence: signedSequence }], signed.fee.amount, signedGasLimit, signMode)
// return tx_5.TxRaw.fromPartial({
// bodyBytes: signedTxBodyBytes,
// authInfoBytes: signedAuthInfoBytes,
// signatures: [encoding_1.fromBase64(signature.signature)],
// })
// }
async signAmino2(signerAddress, messages, fee, memo, { accountNumber, sequence, chainId }) {
const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress)
if (!accountFromSigner) {
+33 -24
View File
@@ -1,5 +1,5 @@
import {
Bech32, fromBase64, fromBech32, fromHex, toHex,
Bech32, fromBase64, fromBech32, fromHex, toBech32, toHex,
} from '@cosmjs/encoding'
import { sha256, stringToPath } from '@cosmjs/crypto'
// ledger
@@ -7,6 +7,7 @@ import TransportWebBLE from '@ledgerhq/hw-transport-web-ble'
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
import CosmosApp from 'ledger-cosmos-js'
import { LedgerSigner } from '@cosmjs/ledger-amino'
import { ethToEvmos } from '@tharsis/address-converter'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
@@ -17,6 +18,7 @@ import localeData from 'dayjs/plugin/localeData'
import { $themeColors } from '@themeConfig'
// import { SigningStargateClient } from '@cosmjs/stargate'
import PingWalletClient from './data/signing'
import { EthereumLedgerSigner } from './client/EthereumLedgerSigner.ts'
dayjs.extend(localeData)
dayjs.extend(duration)
@@ -74,12 +76,19 @@ export function pubkeyToAccountAddress(pubkey, prefix) {
return Bech32.encode(prefix, pubkey, 40)
}
export function toETHAddress(cosmosAddress) {
return `0x${toHex(fromBech32(cosmosAddress).data)}`
}
export function addressDecode(address) {
return Bech32.decode(address)
if (address.startsWith('0x')) {
return fromBech32(ethToEvmos(address))
}
return fromBech32(address)
}
export function addressEnCode(prefix, pubkey) {
return Bech32.encode(prefix, pubkey)
return toBech32(prefix, pubkey)
}
export function getUserCurrency() {
@@ -175,10 +184,6 @@ export function consensusPubkeyToHexAddress(consensusPubkey) {
return address
}
export function toETHAddress(cosmosAddress) {
return `0x${toHex(fromBech32(cosmosAddress).data)}`
}
function toSignAddress(addr) {
const { data } = addressDecode(addr)
return addressEnCode('cosmos', data)
@@ -192,39 +197,44 @@ function getHdPath(address) {
hdPath = curr.hdpath
}
})
// return [44, 118, 0, 0, 0]
// m/0'/1/2'/2/1000000000
return stringToPath(hdPath)
}
function getLedgerAppName(coinType) {
async function getLedgerAppName(coinType, device, hdpath) {
let ledgerAppName = 'Cosmos'
switch (coinType) {
case 60:
return 'Ethereum'
return EthereumLedgerSigner.create(device, hdpath) // 'Ethereum'
case 529:
return 'Secret'
ledgerAppName = 'Secret' // 'Secret'
break
case 852:
return 'Desmos'
ledgerAppName = 'Desmos' // 'Desmos'
break
case 118:
default:
return 'Cosmos'
}
const transport = await (device === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create())
return new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName })
}
export async function sign(device, chainId, signerAddress, messages, fee, memo, signerData) {
let transport
// let transport
let signer
const hdpath = getHdPath(signerAddress)
const coinType = Number(hdpath[1])
const ledgerName = getLedgerAppName(coinType)
// const ledgerName = getLedgerAppName(coinType)
switch (device) {
case 'ledgerBle':
transport = await TransportWebBLE.create()
signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
// transport = await TransportWebBLE.create()
// signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
signer = await getLedgerAppName(coinType, device, hdpath)
break
case 'ledgerUSB':
transport = await TransportWebUSB.create()
signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
// transport = await TransportWebUSB.create()
// signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
signer = await getLedgerAppName(coinType, device, hdpath)
break
case 'keplr':
default:
@@ -236,8 +246,6 @@ export async function sign(device, chainId, signerAddress, messages, fee, memo,
signer = window.getOfflineSignerOnlyAmino(chainId)
}
// if (signer) return signAmino(signer, signerAddress, messages, fee, memo, signerData)
// Ensure the address has some tokens to spend
const client = await PingWalletClient.offline(signer)
// const client = await SigningStargateClient.offline(signer)
@@ -246,11 +254,12 @@ export async function sign(device, chainId, signerAddress, messages, fee, memo,
}
export async function getLedgerAddress(transport = 'blu', hdPath = "m/44'/118/0'/0/0") {
const trans = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create()
const protocol = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create()
// extract Cointype from from HDPath
const coinType = Number(stringToPath(hdPath)[1])
const ledgerName = getLedgerAppName(coinType)
const signer = new LedgerSigner(trans, { hdPaths: [stringToPath(hdPath)], ledgerAppName: ledgerName })
// const ledgerName = getLedgerAppName(coinType)
// const signer = new LedgerSigner(trans, { hdPaths: [stringToPath(hdPath)], ledgerAppName: ledgerName })
const signer = await getLedgerAppName(coinType, protocol, hdPath)
return signer.getAccounts()
}