import { PubKey , PubKeySecp256k1 } from "../crypto" ; import { Context } from "./context" ; import { AccAddress } from "../common/address" ; import { WalletProvider , Key } from "./walletProvider" ; import { Buffer } from "buffer/" ; const TransportWebUSB = require ( "@ledgerhq/hw-transport-webusb" ) . default ; const TransportU2F = require ( "@ledgerhq/hw-transport-u2f" ) . default ; const TransportHID = require ( "@ledgerhq/hw-transport-node-hid" ) . default ; const CosmosApp = require ( "ledger-cosmos-js" ) . default ; /**
 * This wallet provider provides a basic client interface to communicate with a Tendermint/Cosmos App running in a Ledger Nano S/X
 * Note: WebUSB support requires Cosmos app >= 1.5.3
 */ export class LedgerWalletProvider implements WalletProvider { private app : any ; private path : number [ ] | undefined ; private key : { pubKey : PubKey ; address : Uint8Array ; } | undefined ; constructor ( public readonly transport , public readonly bech32PrefixAccAddr , private readonly account = 0 , private readonly index = 0 ) { } async enable ( context ) { let transport ; switch ( this . transport ) { case "WebUSB" : transport = await TransportWebUSB . create ( ) ; break ; case "U2F" : transport = await TransportU2F . create ( ) ; break ; case "HID" : transport = await TransportHID . create ( ) ; break ; default : throw new Error ( "Unsupported transport" ) ; } this . app = new CosmosApp ( transport ) ; let response = await this . app . getVersion ( ) ; if ( response . error_message !== "No errors" ) { throw new Error ( `[${ response . error_message }] ${ response . error_message }` ) ; } console . log ( "Response received!" ) ; console . log ( `App Version ${ response . major }.${ response . minor }.${ response . patch }` ) ; console . log ( `Device Locked: ${ response . device_locked }` ) ; console . log ( `Test mode: ${ response . test_mode }` ) ; console . log ( "Full response:" ) ; console . log ( response ) ; this . path = context . get ( "bip44" ) . path ( this . account , this . index ) ; response = await this . app . getAddressAndPubKey ( this . path , context . get ( "bech32Config" ) . bech32PrefixAccAddr ) ; if ( response . error_message !== "No errors" ) { throw new Error ( `[${ response . error_message }] ${ response . error_message }` ) ; } this . key = { address : AccAddress . fromBech32 ( this . bech32PrefixAccAddr , response . bech32_address ) . toBytes ( ) , pubKey : new PubKeySecp256k1 ( response . compressed_pk ) } ; return Promise . resolve ( ) ; } getKeys ( context ) { if ( ! this . app || ! this . path || ! this . key ) { throw new Error ( "Not approved" ) ; } const bech32Address = new AccAddress ( this . key ! . address , this . bech32PrefixAccAddr ) . toBech32 ( ) ; const key = { address : this . key . address , algo : "secp256k1" , pubKey : this . key . pubKey . serialize ( ) , bech32Address } ; return Promise . resolve ( [ key ] ) ; } public async sign ( context , bech32Address , message ) { if ( ! this . app || ! this . path || ! this . key ) { throw new Error ( "Not signed in" ) ; } if ( new AccAddress ( this . key ! . address , this . bech32PrefixAccAddr ) . toBech32 ( ) !== bech32Address ) { throw new Error ( `Unknown address: ${ bech32Address }` ) ; } const response = await this . app . sign ( this . path , message ) ; if ( response . error_message !== "No errors" ) { throw new Error ( `[${ response . error_message }] ${ response . error_message }` ) ; } let signature = response . signature ; if ( signature [ 0 ] !== 0x30 ) { throw new Error ( "Ledger assertion failed: Expected a signature header of 0x30" ) ; } let rOffset = 4 ; let rLen = signature [ 3 ] ; const sLen = signature [ 4 + rLen + 1 ] ; let sOffset = signature . length - sLen ; if ( rLen === 33 ) { rOffset ++ ; rLen -- ; } if ( sLen === 33 ) { sOffset ++ ; } const sigR = signature . slice ( rOffset , rOffset + rLen ) ; const sigS = signature . slice ( sOffset ) ; signature = Buffer . concat ( [ sigR , sigS ] ) ; if ( signature . length !== 64 ) { throw new Error ( `Ledger assertion failed: incorrect signature length ${ signature . length }` ) ; } return Promise . resolve ( new Uint8Array ( signature ) ) ; } }