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 {
}