diff --git a/src/libs/client/EthereumLedgerSigner.ts b/src/libs/client/EthereumLedgerSigner.ts index d8414331..08d014f0 100644 --- a/src/libs/client/EthereumLedgerSigner.ts +++ b/src/libs/client/EthereumLedgerSigner.ts @@ -1,118 +1,107 @@ +import { + AccountData, AminoSignResponse, OfflineAminoSigner, Pubkey, StdSignature, StdSignDoc, +} from '@cosmjs/amino' +import Transport from '@ledgerhq/hw-transport' -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 Eth from '@ledgerhq/hw-app-eth' +import { 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 { + 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 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; -// } +import EthCrypto from 'eth-crypto' +import { + MessageTypeProperty, SignTypedDataVersion, TypedDataUtils, +} from '@metamask/eth-sig-util' +import { EIPToSign } from '@tharsis/transactions' +function goEthAddress(address) { + return `0x${toHex(fromBech32(address).data)}` +} -export class EthereumLedgerSigner implements OfflineAminoSigner{ +function toPubkey(keyBytes: Uint8Array): Pubkey { + return { + type: 'ethermint.crypto.v1.ethsecp256k1.PubKey', + value: new eth.ethermint.crypto.v1.ethsecp256k1.PubKey({ + key: keyBytes, + }), + } +} + +export default 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 { - let transport: Promise = protocol === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create() + hdpath = "44'/60'/0'/0/0" + + prefix = 'evmos' + + static async create(protocol: string, hdpath: HdPath, loadConfig?: LoadConfig, scrambleKey = 'w0w'): Promise { + const transport: Promise = protocol === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create() return transport.then(t => { - let instance = new EthereumLedgerSigner() + const 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 { 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' + 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' + algo: 'secp256k1', // should be 'ethsecp256k1' } return [x2, x1] - }).catch(e=> { - if(e.toString().indexOf('0x6b0c')> 0) { + }).catch(e => { + if (e.toString().indexOf('0x6b0c') > 0) { throw new Error('Please unlock your Ledger first') - } - if(e.toString().indexOf('0x6e00')> 0) { + } + if (e.toString().indexOf('0x6e00') > 0) { throw new Error('Please open Ethereum app on the Ledger!') - } - if(e.toString().indexOf('0x6511')> 0) { + } + if (e.toString().indexOf('0x6511') > 0) { throw new Error('Please open Ethereum app on the Ledger!') } throw e }) - }; - - 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 { - const ethAddr = this.goEthAddress(signerAddress) + const ethAddr = 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'] + pub_key: toPubkey(acc.pubkey), + signature: result.s, } const output: AminoSignResponse = { signed: signDoc, - signature + signature, } return output }) } throw new Error('Account Does not exists!') }) - }; + } - // async showAddress(path?: HdPath): Promise { - // return new Promise((r, j) => { }) - // } - - // async signTransaction(rawTxHex: string, resolution?: LedgerEthTransactionResolution) { - // return this.app.signPersonalMessage(this.hdpath, rawTxHex) - // } - - async sign712( eipToSign: EIPToSign ) { - + async sign712(eipToSign: EIPToSign) { /// sign typed struct const types = eipToSign.types as Record const domainSeparatorHex = TypedDataUtils.hashStruct('EIP712Domain', eipToSign.domain, types, SignTypedDataVersion.V4).toString('hex') @@ -120,11 +109,11 @@ export class EthereumLedgerSigner implements OfflineAminoSigner{ const hashStructMessageHex = toHex(keccak256(TypedDataUtils.encodeData('Tx', eipToSign.message as Record, 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); + let v: string = (signature.v - 27).toString(16) if (v.length < 2) { - v = "0" + v; + v = `0${v}` } - const sig = "0x"+signature.r + signature.s + v + const sig = `0x${signature.r}${signature.s}${v}` return sig } diff --git a/src/libs/client/PingWalletClient.ts b/src/libs/client/PingWalletClient.ts index f9542f27..49e74808 100644 --- a/src/libs/client/PingWalletClient.ts +++ b/src/libs/client/PingWalletClient.ts @@ -1,260 +1,222 @@ import { - AminoTypes, - SignerData, - SigningStargateClient, - defaultRegistryTypes, -} from '@cosmjs/stargate'; -import { Registry, EncodeObject, TxBodyEncodeObject, makeAuthInfoBytes, GeneratedType } from '@cosmjs/proto-signing'; -import { LedgerSigner } from '@cosmjs/ledger-amino'; -import { EthereumLedgerSigner } from './EthereumLedgerSigner'; -import TransportWebUSB from '@ledgerhq/hw-transport-webusb'; + AminoTypes, + SignerData, + SigningStargateClient, + AminoConverters, + createAuthzAminoConverters, + createBankAminoConverters, + createDistributionAminoConverters, + createFreegrantAminoConverters, + createGovAminoConverters, + createIbcAminoConverters, + createStakingAminoConverters, +} from '@cosmjs/stargate' +import { + EncodeObject, TxBodyEncodeObject, makeAuthInfoBytes, +} from '@cosmjs/proto-signing' +import { LedgerSigner } from '@cosmjs/ledger-amino' +import TransportWebUSB from '@ledgerhq/hw-transport-webusb' import TransportWebBLE from '@ledgerhq/hw-transport-web-ble' -import { makeSignDoc, OfflineAminoSigner, Pubkey, pubkeyType, StdFee } from "@cosmjs/amino"; -import { TxBody, TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { - generateMessageWithMultipleTransactions, - MSG_WITHDRAW_DELEGATOR_REWARD_TYPES, - IBC_MSG_TRANSFER_TYPES, - generateTypes, - generateFee, - createEIP712, -} from "@tharsis/eip712" + StdFee, +} from '@cosmjs/amino' +import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx' import { - createTxRawEIP712, signatureToWeb3Extension, Chain, - createMessageSend, createTxMsgWithdrawDelegatorReward, Fee, MsgWithdrawDelegatorRewardParams, - createTxMsgMultipleWithdrawDelegatorReward, - MsgMultipleWithdrawDelegatorRewardParams, - EIPToSign, + generateMessageWithMultipleTransactions, + generateTypes, + generateFee, + createEIP712, +} from '@tharsis/eip712' +import { + createTxRawEIP712, signatureToWeb3Extension, Chain, } from '@tharsis/transactions' -import { - createTransactionWithMultipleMessages, - createMsgWithdrawDelegatorReward, - createIBCMsgTransfer -} from '@tharsis/proto' -import { fromBase64, fromBech32, fromHex, toBase64, toHex } from '@cosmjs/encoding'; -//import { generateEndpointBroadcast, generatePostBodyBroadcast } from '@tharsis/provider' -import { keccak256, Secp256k1 } from "@cosmjs/crypto" -import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing'; -import { MessageTypeProperty, MessageTypes, SignTypedDataVersion, TypedDataUtils, TypedMessage, } from '@metamask/eth-sig-util' -import MetaMaskSigner from './MetaMaskSigner.js' -import * as eth from '@tharsis/proto/dist/proto/ethermint/crypto/v1/ethsecp256k1/keys' // /ethermint/crypto/v1/ethsecp256k1/keys' import { - AminoConverters, - createAuthzAminoConverters, - createBankAminoConverters, - createDistributionAminoConverters, - createFreegrantAminoConverters, - createGovAminoConverters, - createIbcAminoConverters, - createStakingAminoConverters, -} from "@cosmjs/stargate" -import { Int53 } from '@cosmjs/math'; -import { Any } from 'cosmjs-types/google/protobuf/any'; -import { legacyToBuffer, recoverPublicKey } from '@metamask/eth-sig-util/dist/utils'; -import { utils } from 'ethers' -import { signatureToPubkey } from '@hanchon/signature-to-pubkey' -import { defaultMessageAdapter } from './MessageAdapter'; + createTransactionWithMultipleMessages, +} from '@tharsis/proto' +import { + fromBase64, fromBech32, fromHex, toBase64, +} from '@cosmjs/encoding' +// import { generateEndpointBroadcast, generatePostBodyBroadcast } from '@tharsis/provider' +import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing' +import * as eth from '@tharsis/proto/dist/proto/ethermint/crypto/v1/ethsecp256k1/keys' // /ethermint/crypto/v1/ethsecp256k1/keys' +import { PubKey } from 'cosmjs-types/cosmos/crypto/secp256k1/keys' +import { Int53 } from '@cosmjs/math' +import { Any } from 'cosmjs-types/google/protobuf/any' +import EthereumLedgerSigner from './EthereumLedgerSigner' +import { defaultMessageAdapter } from './MessageAdapter' export interface TypedDataField { name: string; type: string; -}; - -export declare type SigningClient = SigningStargateClient | SigningEthermintClient; - -export async function getSigningClient(device, hdpath): Promise { - let ledgerAppName = 'Cosmos' - let coinType = Number(hdpath[1]) - switch (coinType) { - case 60: - return new SigningEthermintClient(await EthereumLedgerSigner.create(device, hdpath)) // 'Ethereum' - case 529: - ledgerAppName = 'Secret' // 'Secret' - break - case 852: - ledgerAppName = 'Desmos' // 'Desmos' - break - case 118: - default: - } - const transport = await (device === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create()) - const signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName }) - return SigningStargateClient.offline(signer) } function createDefaultTypes(prefix: string): AminoConverters { - return { - ...createAuthzAminoConverters(), - ...createBankAminoConverters(), - ...createDistributionAminoConverters(), - ...createGovAminoConverters(), - ...createStakingAminoConverters(prefix), - ...createIbcAminoConverters(), - ...createFreegrantAminoConverters(), - // ...createVestingAminoConverters(), - }; + return { + ...createAuthzAminoConverters(), + ...createBankAminoConverters(), + ...createDistributionAminoConverters(), + ...createGovAminoConverters(), + ...createStakingAminoConverters(prefix), + ...createIbcAminoConverters(), + ...createFreegrantAminoConverters(), + // ...createVestingAminoConverters(), + } } function extractChainId(chainId: string) { - const start = chainId.indexOf('_') - const end = chainId.indexOf('-') - if (end > start && start > 0) { - return Number(chainId.substring(start + 1, end)) - } - return 0 -} -export class SigningEthermintClient { - readonly signer: EthereumLedgerSigner - aminoTypes: AminoTypes - constructor(signer: EthereumLedgerSigner) { - this.signer = signer - this.aminoTypes = new AminoTypes(createDefaultTypes("")) - } - - // async signEVM(signerAddress: string, messages: readonly EncodeObject[], stdfee: StdFee, memo: string, explicitSignerData?: SignerData): Promise { - // const chain: Chain = { - // chainId: extractChainId(explicitSignerData.chainId), - // cosmosChainId: explicitSignerData.chainId - // } - // this.signer.prefix = fromBech32(signerAddress).prefix - // const account = await this.signer.getAccounts() - - // const sender = { - // accountAddress: signerAddress, - // sequence: explicitSignerData.sequence, - // accountNumber: explicitSignerData.accountNumber, - // pubkey: toBase64(account[0].pubkey), - // } - // const fee = { - // amount: stdfee.amount[0].amount, - // denom: stdfee.amount[0].denom, - // gas: stdfee.gas, - // } - // const validatorAddresses = messages.map(x => x.value.validatorAddress) - - // let param: MsgMultipleWithdrawDelegatorRewardParams = { - // validatorAddresses - // } - - // const msgs = createTxMsgMultipleWithdrawDelegatorReward(chain, sender, fee, memo, param) - // const sig = await this.signer.sign712(msgs.eipToSign) - - // let extension = signatureToWeb3Extension(chain, sender, sig) - - // // Create the txRaw - // let prototx = createTxRawEIP712(msgs.legacyAmino.body, msgs.legacyAmino.authInfo, extension) - // return Promise.resolve(prototx.message.serializeBinary()) - // } - - async sign(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo: string, explicitSignerData?: SignerData): Promise { - - const chain: Chain = { - chainId: extractChainId(explicitSignerData.chainId), - cosmosChainId: explicitSignerData.chainId - } - - this.signer.prefix = fromBech32(signerAddress).prefix - const account = await this.signer.getAccounts() - - const acc = account.find(x => x.address === signerAddress) - if(!acc) { - throw new Error('The signer address dose not exsits in Ledger!') - } - const sender = { - accountAddress: signerAddress, - sequence: explicitSignerData.sequence, - accountNumber: explicitSignerData.accountNumber, - pubkey: toBase64(account[0].pubkey), - } - - let fees = generateFee(fee.amount[0].amount, fee.amount[0].denom, fee.gas, signerAddress) - - const msgs = messages.map(x => this.aminoTypes.toAmino(x)) - const tx = generateMessageWithMultipleTransactions( - sender.accountNumber.toString(), - sender.sequence.toString(), - explicitSignerData.chainId, - memo, - fees, - msgs, - ) - - const types = generateTypes(defaultMessageAdapter[messages[0].typeUrl].getTypes()) - const eip = createEIP712(types, chain.chainId, tx ) - const sig = await this.signer.sign712(eip) - - const rawTx = makeRawTxEvmos(sender, messages, memo, fee, sig, chain) - - return Promise.resolve(rawTx) - } + const start = chainId.indexOf('_') + const end = chainId.indexOf('-') + if (end > start && start > 0) { + return Number(chainId.substring(start + 1, end)) + } + return 0 } function makeRawTxEvmos(sender, messages, memo, fee, signature, chain): Uint8Array { - /// evmos style - ///* - const protoMsgs = messages.map(x => { - const adapter = defaultMessageAdapter[x.typeUrl] - return adapter.toProto(x) - }) + /// evmos style + /// * + const protoMsgs = messages.map(x => { + const adapter = defaultMessageAdapter[x.typeUrl] + return adapter.toProto(x) + }) - const evmos = createTransactionWithMultipleMessages( - protoMsgs, - memo, - fee.amount[0].amount, - fee.amount[0].denom, - Number(fee.gas), - 'ethsecp256', - sender.pubkey, - sender.sequence, - sender.accountNumber, - chain.cosmosChainId + const evmos = createTransactionWithMultipleMessages( + protoMsgs, + memo, + fee.amount[0].amount, + fee.amount[0].denom, + Number(fee.gas), + 'ethsecp256', + sender.pubkey, + sender.sequence, + sender.accountNumber, + chain.cosmosChainId, + ) + + const extension = signatureToWeb3Extension(chain, sender, signature) + + // Create the txRaw + const prototx = createTxRawEIP712(evmos.legacyAmino.body, evmos.legacyAmino.authInfo, extension) + return prototx.message.serializeBinary() + /// end of EVMOS style */ +} + +export class SigningEthermintClient { + readonly signer: EthereumLedgerSigner + + aminoTypes: AminoTypes + + constructor(signer: EthereumLedgerSigner) { + this.signer = signer + this.aminoTypes = new AminoTypes(createDefaultTypes('')) + } + + async sign(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo: string, explicitSignerData?: SignerData): Promise { + const chain: Chain = { + chainId: extractChainId(explicitSignerData.chainId), + cosmosChainId: explicitSignerData.chainId, + } + + this.signer.prefix = fromBech32(signerAddress).prefix + const account = await this.signer.getAccounts() + + const acc = account.find(x => x.address === signerAddress) + if (!acc) { + throw new Error('The signer address dose not exsits in Ledger!') + } + const sender = { + accountAddress: signerAddress, + sequence: explicitSignerData.sequence, + accountNumber: explicitSignerData.accountNumber, + pubkey: toBase64(account[0].pubkey), + } + + const fees = generateFee(fee.amount[0].amount, fee.amount[0].denom, fee.gas, signerAddress) + + const msgs = messages.map(x => this.aminoTypes.toAmino(x)) + const tx = generateMessageWithMultipleTransactions( + sender.accountNumber.toString(), + sender.sequence.toString(), + explicitSignerData.chainId, + memo, + fees, + msgs, ) - let extension = signatureToWeb3Extension(chain, sender, signature) + const types = generateTypes(defaultMessageAdapter[messages[0].typeUrl].getTypes()) + const eip = createEIP712(types, chain.chainId, tx) + const sig = await this.signer.sign712(eip) - // Create the txRaw - let prototx = createTxRawEIP712(evmos.legacyAmino.body, evmos.legacyAmino.authInfo, extension) - return prototx.message.serializeBinary() - /// end of EVMOS style */ + const rawTx = makeRawTxEvmos(sender, messages, memo, fee, sig, chain) + + return Promise.resolve(rawTx) + } +} +export function encodePubkey(pubkey: string): Any { +// const value = new eth.ethermint.crypto.v1.ethsecp256k1.PubKey({ key: fromBase64(pubkey) }) +// return Any.fromPartial({ +// typeUrl: '/ethermint.crypto.v1.ethsecp256k1.PubKey', +// value: value.serializeBinary(), +// }) + return Any.fromPartial({ + typeUrl: '/ethermint.crypto.v1.ethsecp256k1.PubKey', + value: PubKey.encode({ + key: fromBase64(pubkey), + }).finish(), + }) } function makeRawTx(sender, messages, memo, fee, signature, registry): TxRaw { - const pubkey = encodePubkey(sender.pubkey); + const pubkey = encodePubkey(sender.pubkey) - const signedTxBody = { - messages, - memo, - }; - const signedTxBodyEncodeObject: TxBodyEncodeObject = { - typeUrl: "/cosmos.tx.v1beta1.TxBody", - value: signedTxBody, - }; - const signedTxBodyBytes = registry.encode(signedTxBodyEncodeObject); - const signedGasLimit = Int53.fromString(fee.gas).toNumber(); - const signedSequence = sender.sequence; - const signedAuthInfoBytes = makeAuthInfoBytes( - [{ pubkey, sequence: sender.sequence }], - fee.amount, - signedGasLimit, - SignMode.SIGN_MODE_LEGACY_AMINO_JSON, - ); - const rawTx = TxRaw.fromPartial({ - bodyBytes: signedTxBodyBytes, - authInfoBytes: signedAuthInfoBytes, - signatures: [fromHex(signature)], - }); + const signedTxBody = { + messages, + memo, + } + const signedTxBodyEncodeObject: TxBodyEncodeObject = { + typeUrl: '/cosmos.tx.v1beta1.TxBody', + value: signedTxBody, + } + const signedTxBodyBytes = registry.encode(signedTxBodyEncodeObject) + const signedGasLimit = Int53.fromString(fee.gas).toNumber() + // const signedSequence = sender.sequence + const signedAuthInfoBytes = makeAuthInfoBytes( + [{ pubkey, sequence: sender.sequence }], + fee.amount, + signedGasLimit, + SignMode.SIGN_MODE_LEGACY_AMINO_JSON, + ) + const rawTx = TxRaw.fromPartial({ + bodyBytes: signedTxBodyBytes, + authInfoBytes: signedAuthInfoBytes, + signatures: [fromHex(signature)], + }) - return rawTx -} - -export function encodePubkey(pubkey: string): Any { - const value = new eth.ethermint.crypto.v1.ethsecp256k1.PubKey({ key: fromBase64(pubkey) }) - return Any.fromPartial({ - typeUrl: "/ethermint.crypto.v1.ethsecp256k1.PubKey", - value: value.serializeBinary(), - }); + return rawTx } // export function createAnyMessage(messages: readonly EncodeObject[]) { // return messages.map(x => x.) // } + +export declare type SigningClient = SigningStargateClient | SigningEthermintClient; + +export async function getSigningClient(device, hdpath): Promise { + let ledgerAppName = 'Cosmos' + const coinType = Number(hdpath[1]) + switch (coinType) { + case 60: + return new SigningEthermintClient(await EthereumLedgerSigner.create(device, hdpath)) // 'Ethereum' + case 529: + ledgerAppName = 'Secret' // 'Secret' + break + case 852: + ledgerAppName = 'Desmos' // 'Desmos' + break + case 118: + default: + } + const transport = await (device === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create()) + const signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName }) + return SigningStargateClient.offline(signer) +}