From fe36d08f35eb3fa4734c46abc92d0950d591db24 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 10 Sep 2021 20:13:31 +0800 Subject: [PATCH] finish ledger signature --- src/lang/locales/en.json | 1 + src/libs/data/data.js | 257 +++++++----------- src/views/OperationDelegateComponent.vue | 33 ++- src/views/OperationRedelegateComponent.vue | 33 ++- src/views/OperationTransferComponent.vue | 33 ++- src/views/OperationUnbondComponent.vue | 33 ++- src/views/OperationVoteComponent.vue | 33 ++- .../OperationWithdrawCommissionComponent.vue | 33 ++- src/views/OperationWithdrawComponent.vue | 35 ++- src/views/UserAccountDetail.vue | 4 +- src/views/UserAccountImportAddress.vue | 87 +++++- src/views/UserAccounts.vue | 9 +- 12 files changed, 311 insertions(+), 280 deletions(-) diff --git a/src/lang/locales/en.json b/src/lang/locales/en.json index c1473758..502146e9 100644 --- a/src/lang/locales/en.json +++ b/src/lang/locales/en.json @@ -31,6 +31,7 @@ "proposal_description": "Description", "proposal_content": "Content", "proposal_total_deposit": "Total Deposit", + "voting_time": "Voting Time", "btn_vote": "Vote", "btn_detail": "Detail", diff --git a/src/libs/data/data.js b/src/libs/data/data.js index 864532d5..87cb1079 100644 --- a/src/libs/data/data.js +++ b/src/libs/data/data.js @@ -1,151 +1,23 @@ import { Bech32, fromBase64, fromHex, toHex, } from '@cosmjs/encoding' -import { sha256 } from '@cosmjs/crypto' +import { sha256, stringToPath } from '@cosmjs/crypto' // ledger import TransportWebBLE from '@ledgerhq/hw-transport-web-ble' import TransportWebUSB from '@ledgerhq/hw-transport-webusb' import { SigningStargateClient } from '@cosmjs/stargate' -import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx' -// import Cosmos from '@ledgerhq/hw-app-cosmos' import CosmosApp from 'ledger-cosmos-js' import { LedgerSigner } from '@cosmjs/ledger-amino' -import { - makeAuthInfoBytes, makeSignBytes, Registry, SignMode, -} from '@cosmjs/proto-signing' -import { encodeSecp256k1Pubkey, encodeSecp256k1Signature, encodePubkey } from '@cosmjs/amino' -import { Uint53 } from '@cosmjs/math' import dayjs from 'dayjs' import duration from 'dayjs/plugin/duration' import relativeTime from 'dayjs/plugin/relativeTime' import localeData from 'dayjs/plugin/localeData' -const COSMOS_PATH = [44, 118, 0, 0, 0] - dayjs.extend(localeData) dayjs.extend(duration) dayjs.extend(relativeTime) -export async function connectLedger(transport = 'usb') { - const trans = await transport === 'usb' ? TransportWebUSB.create() : TransportWebBLE.create() - return new CosmosApp(trans) -} - -/* eslint-enable */ -function unharden(hdPath) { - return hdPath.map(n => (n.isHardened() ? n.toNumber() - 2 ** 31 : n.toNumber())) -} - -function makeSignDoc(bodyBytes, authInfoBytes, chainId, accountNumber) { - return { - bodyBytes, - authInfoBytes, - chainId, - accountNumber: Uint53.fromString(String(accountNumber)), - } -} - -export async function signDirect(signer, signerAddress, messages, fee, memo, { accountNumber, sequence, chainId }) { - // const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress) - // if (!accountFromSigner) { - // throw new Error("Failed to retrieve account from signer"); - // } - const accountFromSigner = await signer.getAddressAndPubKey(COSMOS_PATH, 'cosmos') - if (!accountFromSigner) { - throw new Error('Failed to retrieve account from signer') - } - const { pubkey } = accountFromSigner - const txBodyEncodeObject = { - typeUrl: '/cosmos.tx.v1beta1.TxBody', - value: { - messages, - memo, - }, - } - const txBodyBytes = new Registry().encode(txBodyEncodeObject) - const gasLimit = Uint53.fromString(String(fee.gas)) - const authInfoBytes = makeAuthInfoBytes([pubkey], fee.amount, gasLimit, sequence) - const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber) - // const { signature, signed } = await signer.signDirect(signerAddress, signDoc) - const signBytes = makeSignBytes(signDoc) - const hashedMessage = sha256(signBytes) - const signature = await signer.sign(unharden(COSMOS_PATH), hashedMessage) - const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]) - const stdSignature = encodeSecp256k1Signature(pubkey, signatureBytes) - - return TxRaw.fromPartial({ - bodyBytes: txBodyBytes, - authInfoBytes, - signatures: [fromBase64(stdSignature)], - }) -} - -export async function signAmino(signerAddress, messages, fee, memo, { accountNumber, sequence, chainId }) { - const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress) - if (!accountFromSigner) { - throw new Error('Failed to retrieve account from signer') - } - const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey)) - const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON - const msgs = messages.map(msg => this.aminoTypes.toAmino(msg)) - const signDoc = makeSignDoc(msgs, fee, chainId, memo, accountNumber, sequence) - const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc) - const signedTxBody = { - messages: signed.msgs.map(msg => this.aminoTypes.fromAmino(msg)), - memo: signed.memo, - } - const signedTxBodyEncodeObject = { - typeUrl: '/cosmos.tx.v1beta1.TxBody', - value: signedTxBody, - } - const signedTxBodyBytes = this.registry.encode(signedTxBodyEncodeObject) - const signedGasLimit = Number(signed.fee.gas) - const signedSequence = Number(signed.sequence) - const signedAuthInfoBytes = makeAuthInfoBytes([pubkey], signed.fee.amount, signedGasLimit, signedSequence, signMode) - return TxRaw.fromPartial({ - bodyBytes: signedTxBodyBytes, - authInfoBytes: signedAuthInfoBytes, - signatures: [fromBase64(signature.signature)], - }) -} - -export async function sign(device, chainId, signerAddress, messages, fee, memo, signerData) { - let transport - let signer - switch (device) { - case 'ledgerBle': - transport = await TransportWebBLE.create() - signer = new LedgerSigner(transport) - break - case 'ledgerUSB': - transport = await TransportWebUSB.create() - signer = new LedgerSigner(transport) - break - case 'keplr': - default: - if (!window.getOfflineSigner || !window.keplr) { - throw new Error('Please install keplr extension') - } - await window.keplr.enable(chainId) - // signer = window.getOfflineSigner(chainId) - signer = window.getOfflineSignerOnlyAmino(chainId) - } - - // Ensure the address has some tokens to spend - const client = await SigningStargateClient.offline(signer) - return client.sign(signerAddress, messages, fee, memo, signerData) - // return signDirect(signer, signerAddress, messages, fee, memo, signerData) -} - -export async function getLedgerAddress(transport = 'blu') { - const trans = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create() - - trans.setDebugMode(true) - const cosmos = new CosmosApp(trans) - return cosmos.getAddressAndPubKey(COSMOS_PATH, 'cosmos') -} - export function getLocalObject(name) { const text = localStorage.getItem(name) if (text) { @@ -175,6 +47,98 @@ export function setLocalTxHistory(newTx) { return localStorage.setItem('txHistory', JSON.stringify([newTx])) } +export async function connectLedger(transport = 'usb') { + const trans = await transport === 'usb' ? TransportWebUSB.create() : TransportWebBLE.create() + return new CosmosApp(trans) +} + +export function operatorAddressToAccount(operAddress) { + const { prefix, data } = Bech32.decode(operAddress) + if (prefix === 'iva') { // handle special cases + return Bech32.encode('iaa', data) + } + if (prefix === 'crocncl') { // handle special cases + return Bech32.encode('cro', data) + } + return Bech32.encode(prefix.replace('valoper', ''), data) +} + +// TODO, not tested +export function pubkeyToAccountAddress(pubkey, prefix) { + return Bech32.encode(prefix, pubkey, 40) +} + +export function addressDecode(address) { + return Bech32.decode(address) +} + +export function addressEnCode(prefix, pubkey) { + return Bech32.encode(prefix, pubkey) +} + +export function consensusPubkeyToHexAddress(consensusPubkey) { + let raw = null + if (typeof consensusPubkey === 'object') { + raw = toHex(fromBase64(consensusPubkey.value)) + } else { + raw = toHex(Bech32.decode(consensusPubkey).data).toUpperCase().replace('1624DE6420', '') + } + const address = toHex(sha256(fromHex(raw))).slice(0, 40).toUpperCase() + return address +} + +function toSignAddress(addr) { + const { data } = addressDecode(addr) + return addressEnCode('cosmos', data) +} + +function getHdPath(address) { + let hdPath = "m/44'/118/0'/0/0" + Object.values(getLocalAccounts()).forEach(item => { + const curr = item.address.find(i => i.addr === address) + if (curr && curr.hdpath) { + hdPath = curr.hdpath + } + }) + // return [44, 118, 0, 0, 0] + // m/0'/1/2'/2/1000000000 + return stringToPath(hdPath) +} + +export async function sign(device, chainId, signerAddress, messages, fee, memo, signerData) { + let transport + let signer + switch (device) { + case 'ledgerBle': + transport = await TransportWebBLE.create() + signer = new LedgerSigner(transport, { hdPaths: [getHdPath(signerAddress)] }) + break + case 'ledgerUSB': + transport = await TransportWebUSB.create() + signer = new LedgerSigner(transport, { hdPaths: [getHdPath(signerAddress)] }) + break + case 'keplr': + default: + if (!window.getOfflineSigner || !window.keplr) { + throw new Error('Please install keplr extension') + } + await window.keplr.enable(chainId) + // signer = window.getOfflineSigner(chainId) + signer = window.getOfflineSignerOnlyAmino(chainId) + } + + // Ensure the address has some tokens to spend + const client = await SigningStargateClient.offline(signer) + return client.signAmino(toSignAddress(signerAddress), messages, fee, memo, signerData) + // return signDirect(signer, signerAddress, messages, fee, memo, signerData) +} + +export async function getLedgerAddress(transport = 'blu', hdPath = "m/44'/118/0'/0/0") { + const trans = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create() + const signer = new LedgerSigner(trans, { hdPaths: [stringToPath(hdPath)] }) + return signer.getAccounts() +} + export function toDuration(value) { return dayjs.duration(value).humanize() } @@ -293,41 +257,6 @@ export function tokenFormatter(tokens) { return formatToken(tokens) } -export function operatorAddressToAccount(operAddress) { - const { prefix, data } = Bech32.decode(operAddress) - if (prefix === 'iva') { // handle special cases - return Bech32.encode('iaa', data) - } - if (prefix === 'crocncl') { // handle special cases - return Bech32.encode('cro', data) - } - return Bech32.encode(prefix.replace('valoper', ''), data) -} - -// TODO, not tested -export function pubkeyToAccountAddress(pubkey, prefix) { - return Bech32.encode(prefix, pubkey, 40) -} - -export function addressDecode(address) { - return Bech32.decode(address) -} - -export function addressEnCode(prefix, pubkey) { - return Bech32.encode(prefix, pubkey) -} - -export function consensusPubkeyToHexAddress(consensusPubkey) { - let raw = null - if (typeof consensusPubkey === 'object') { - raw = toHex(fromBase64(consensusPubkey.value)) - } else { - raw = toHex(Bech32.decode(consensusPubkey).data).toUpperCase().replace('1624DE6420', '') - } - const address = toHex(sha256(fromHex(raw))).slice(0, 40).toUpperCase() - return address -} - export function getCachedValidators(chainName) { const locals = localStorage.getItem(`validators-${chainName}`) return locals diff --git a/src/views/OperationDelegateComponent.vue b/src/views/OperationDelegateComponent.vue index 93b72259..2567a473 100644 --- a/src/views/OperationDelegateComponent.vue +++ b/src/views/OperationDelegateComponent.vue @@ -102,13 +102,20 @@ label="Fee" label-for="Fee" > - - + + + {{ errors[0] }} + + @@ -120,9 +127,9 @@ {{ item.denom }} - - {{ errors[0] }} - + {{ errors[0] }} + + @@ -171,12 +178,11 @@ > Keplr - + {{ errors[0] }} @@ -262,7 +267,7 @@ export default { balance: [], delegations: [], memo: '', - fee: 800, + fee: '800', feeDenom: '', wallet: 'keplr', error: null, diff --git a/src/views/OperationRedelegateComponent.vue b/src/views/OperationRedelegateComponent.vue index d2897ce8..e84fdcc6 100644 --- a/src/views/OperationRedelegateComponent.vue +++ b/src/views/OperationRedelegateComponent.vue @@ -117,13 +117,20 @@ label="Fee" label-for="Fee" > - - + + + {{ errors[0] }} + + @@ -135,9 +142,9 @@ {{ item.denom }} - - {{ errors[0] }} - + {{ errors[0] }} + + @@ -186,12 +193,11 @@ > Keplr - + {{ errors[0] }} @@ -278,7 +283,7 @@ export default { balance: [], delegations: [], memo: '', - fee: 800, + fee: '800', feeDenom: '', wallet: 'keplr', error: null, diff --git a/src/views/OperationTransferComponent.vue b/src/views/OperationTransferComponent.vue index b08269da..6c35fc5f 100644 --- a/src/views/OperationTransferComponent.vue +++ b/src/views/OperationTransferComponent.vue @@ -129,13 +129,20 @@ label="Fee" label-for="Fee" > - - + + + {{ errors[0] }} + + @@ -147,9 +154,9 @@ {{ item.denom }} - - {{ errors[0] }} - + {{ errors[0] }} + + @@ -198,12 +205,11 @@ > Keplr - + {{ errors[0] }} @@ -283,7 +288,7 @@ export default { amount: null, memo: '', recipient: null, - fee: 800, + fee: '800', feeDenom: '', wallet: 'keplr', error: null, diff --git a/src/views/OperationUnbondComponent.vue b/src/views/OperationUnbondComponent.vue index c1a4e681..d911de1f 100644 --- a/src/views/OperationUnbondComponent.vue +++ b/src/views/OperationUnbondComponent.vue @@ -102,13 +102,20 @@ label="Fee" label-for="Fee" > - - + + + {{ errors[0] }} + + @@ -120,9 +127,9 @@ {{ item.denom }} - - {{ errors[0] }} - + {{ errors[0] }} + + @@ -171,12 +178,11 @@ > Keplr - + {{ errors[0] }} @@ -262,7 +267,7 @@ export default { balance: [], delegations: [], memo: '', - fee: 800, + fee: '800', feeDenom: '', wallet: 'keplr', error: null, diff --git a/src/views/OperationVoteComponent.vue b/src/views/OperationVoteComponent.vue index 328d0f93..dfdbde32 100644 --- a/src/views/OperationVoteComponent.vue +++ b/src/views/OperationVoteComponent.vue @@ -92,13 +92,20 @@ label="Fee" label-for="Fee" > - - + + + {{ errors[0] }} + + @@ -110,9 +117,9 @@ {{ item.denom }} - - {{ errors[0] }} - + {{ errors[0] }} + + @@ -161,12 +168,11 @@ > Keplr - + {{ errors[0] }} @@ -248,7 +253,7 @@ export default { selectedChain: '', balance: [], memo: '', - fee: null, + fee: '', feeDenom: '', wallet: 'keplr', error: null, diff --git a/src/views/OperationWithdrawCommissionComponent.vue b/src/views/OperationWithdrawCommissionComponent.vue index e59b2bb4..2d2011d0 100644 --- a/src/views/OperationWithdrawCommissionComponent.vue +++ b/src/views/OperationWithdrawCommissionComponent.vue @@ -44,13 +44,20 @@ label="Fee" label-for="Fee" > - - + + + {{ errors[0] }} + + @@ -62,9 +69,9 @@ {{ item.denom }} - - {{ errors[0] }} - + {{ errors[0] }} + + @@ -113,12 +120,11 @@ > Keplr - + {{ errors[0] }} @@ -199,7 +204,7 @@ export default { balance: [], delegations: [], memo: '', - fee: 800, + fee: '800', feeDenom: '', wallet: 'keplr', error: null, diff --git a/src/views/OperationWithdrawComponent.vue b/src/views/OperationWithdrawComponent.vue index 83fc8b67..92172145 100644 --- a/src/views/OperationWithdrawComponent.vue +++ b/src/views/OperationWithdrawComponent.vue @@ -44,13 +44,20 @@ label="Fee" label-for="Fee" > - - + + + {{ errors[0] }} + + @@ -62,9 +69,9 @@ {{ item.denom }} - - {{ errors[0] }} - + {{ errors[0] }} + + @@ -113,12 +120,11 @@ > Keplr - + {{ errors[0] }} @@ -154,7 +159,7 @@ import { required, email, url, between, alpha, integer, password, min, digits, alphaDash, length, } from '@validations' import { - formatToken, getLocalAccounts, getLocalChains, setLocalTxHistory, sign, timeIn, + formatToken, getLocalAccounts, getLocalChains, sign, timeIn, setLocalTxHistory, } from '@/libs/data' import chainAPI from '@/libs/fetch' import ToastificationContent from '@core/components/toastification/ToastificationContent.vue' @@ -195,7 +200,7 @@ export default { balance: [], delegations: [], memo: '', - fee: 800, + fee: '800', feeDenom: '', wallet: 'keplr', error: null, diff --git a/src/views/UserAccountDetail.vue b/src/views/UserAccountDetail.vue index 2a78133a..691c810b 100644 --- a/src/views/UserAccountDetail.vue +++ b/src/views/UserAccountDetail.vue @@ -100,7 +100,9 @@ - + Delegation
diff --git a/src/views/UserAccountImportAddress.vue b/src/views/UserAccountImportAddress.vue index bb1c935c..27962cb0 100644 --- a/src/views/UserAccountImportAddress.vue +++ b/src/views/UserAccountImportAddress.vue @@ -79,6 +79,29 @@ + + + + + {{ errors[0] }} + + + @@ -113,6 +136,29 @@ + + + + + {{ errors[0] }} + + + { - const { logo, addr_prefix } = this.chains[x] - const addr = addressEnCode(addr_prefix, data) - return { chain: x, addr, logo } - }) + if (this.accounts && this.accounts.address) { + const { data } = addressDecode(this.accounts.address) + return this.selected.map(x => { + const { logo, addr_prefix } = this.chains[x] + const addr = addressEnCode(addr_prefix, data) + return { + chain: x, addr, logo, hdpath: this.hdpath, + } + }) + } + return [] }, }, created() { @@ -271,11 +323,18 @@ export default { } }, methods: { + formatPubkey(v) { + if (typeof (v) === 'string') { + return v + } + if (v) { + return toHex(v) + } + return '' + }, async connect() { const transport = this.device === 'ledger' ? 'usb' : 'bluetooth' - return getLedgerAddress(transport).catch(e => { - this.debug = e - }) + return getLedgerAddress(transport, this.hdpath) }, async cennectKeplr() { if (!window.getOfflineSigner || !window.keplr) { @@ -338,12 +397,12 @@ export default { case 'ledger2': await this.connect().then(accounts => { if (accounts) { - this.accounts = { - address: accounts.bech32_address, - pubkey: accounts.compressed_pk, - } + // eslint-disable-next-line prefer-destructuring + this.accounts = accounts[0] ok = true } + }).catch(e => { + this.debug = e }) break default: diff --git a/src/views/UserAccounts.vue b/src/views/UserAccounts.vue index 15f94d36..0cf3996b 100644 --- a/src/views/UserAccounts.vue +++ b/src/views/UserAccounts.vue @@ -254,9 +254,14 @@ export default { return 0 }, removeAddress(v) { - Object.values(this.accounts).forEach(item => { + Object.keys(this.accounts).forEach(key => { + const item = this.accounts[key] const newAddrs = item.address.filter(a => a.addr !== v) - this.$set(item, 'address', newAddrs) + if (newAddrs.length > 0) { + this.$set(item, 'address', newAddrs) + } else { + delete this.accounts[key] + } }) localStorage.setItem('accounts', JSON.stringify(this.accounts)) },