forked from LaconicNetwork/cosmos-explorer
finish ledger signature
This commit is contained in:
+93
-164
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user