finish ledger signature

This commit is contained in:
liangping
2021-09-10 20:13:31 +08:00
parent 3445a13cdf
commit fe36d08f35
12 changed files with 311 additions and 280 deletions
+93 -164
View File
@@ -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