forked from cerc-io/cosmos-explorer
122 lines
4.7 KiB
TypeScript
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
|
|
}
|
|
}
|