cosmos-explorer/src/libs/client/EthereumLedgerSigner.ts

122 lines
4.7 KiB
TypeScript

import { AccountData, 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 { fromBech32, fromHex, toBech32, toHex } from "@cosmjs/encoding";
import TransportWebBLE from '@ledgerhq/hw-transport-web-ble'
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
import { HdPath, keccak256, pathToString } from "@cosmjs/crypto";
import { ethToEvmos } from '@tharsis/address-converter'
import eth from "@tharsis/proto/dist/proto/ethermint/crypto/v1/ethsecp256k1/keys";
import { Secp256k1 } from "@cosmjs/crypto"
import EthCrypto from "eth-crypto"
import { MessageTypeProperty, SignTypedDataVersion, TypedDataUtils, TypedMessage, MessageTypes } from "@metamask/eth-sig-util";
import { EIPToSign } from "@tharsis/transactions";
// import { recoverPublicKey } from "@metamask/eth-sig-util/dist/utils";
// 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 implements OfflineAminoSigner{
app: Eth
hdpath: string = "44'/60'/0'/0/0"
prefix: string = "evmos"
static async create(protocol: string, hdpath: HdPath, 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 = pathToString(hdpath).replace('m/', '').replace('/60/', "/60'/")
instance.app = new Eth(t, scrambleKey, loadConfig)
return instance
})
}
public async getAccounts(): Promise<readonly AccountData[]> {
return this.app.getAddress(this.hdpath).then(x => {
const x1: AccountData = {
pubkey: fromHex(EthCrypto.publicKey.compress(x.publicKey)),
address: x.address,
algo: "secp256k1" // should be 'ethsecp256k1'
}
const x2: AccountData = {
pubkey: fromHex(EthCrypto.publicKey.compress(x.publicKey)),
address: toBech32(this.prefix, fromBech32(ethToEvmos(x.address)).data),
algo: "secp256k1" // should be 'ethsecp256k1'
}
return [x2, x1]
})
};
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,
})
}
}
public 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!')
})
};
// async showAddress(path?: HdPath): Promise<AddressAndPubkey> {
// return new Promise((r, j) => { })
// }
// async signTransaction(rawTxHex: string, resolution?: LedgerEthTransactionResolution) {
// return this.app.signPersonalMessage(this.hdpath, rawTxHex)
// }
async sign712( eipToSign: EIPToSign ) {
/// sign typed struct
const types = eipToSign.types as Record<string, MessageTypeProperty[]>
const domainSeparatorHex = TypedDataUtils.hashStruct('EIP712Domain', eipToSign.domain, types, SignTypedDataVersion.V4).toString('hex')
// console.log('hex1: ', domainSeparatorHex)
const hashStructMessageHex = toHex(keccak256(TypedDataUtils.encodeData('Tx', eipToSign.message as Record<string, unknown>, types, SignTypedDataVersion.V4)))
// console.log('hex2:', hashStructMessageHex)
const signature = await this.app.signEIP712HashedMessage(this.hdpath, domainSeparatorHex, hashStructMessageHex)
let v: string = (signature.v - 27).toString(16);
if (v.length < 2) {
v = "0" + v;
}
const sig = "0x"+signature.r + signature.s + v
return sig
}
}