import * as core from "@shapeshiftoss/hdwallet-core" ; import _ from "lodash" ; import { handleError } from "./utils" ; import * as Btc from "./bitcoin" ; import * as Eth from "./ethereum" ; import { TrezorTransport } from "./transport" ; export function isTrezor ( wallet ) { return _ . isObject ( wallet ) && ( wallet as any ) . _isTrezor ; } function describeETHPath ( path ) { let pathStr = core . addressNListToBIP32 ( path ) ; let unknown = { verbose : pathStr , coin : "Ethereum" , isKnown : false , } ; if ( path . length != 5 ) return unknown ; if ( path [ 0 ] != 0x80000000 + 44 ) return unknown ; if ( path [ 1 ] != 0x80000000 + core . slip44ByCoin ( "Ethereum" ) ) return unknown ; if ( path [ 2 ] !== 0x80000000 ) return unknown ; if ( path [ 3 ] != 0 ) return unknown ; if ( ( path [ 4 ] & 0x80000000 ) !== 0 ) return unknown ; let accountIdx = path [ 4 ] & 0x7fffffff ; return { verbose : `Ethereum Account #${ accountIdx }` , coin : "Ethereum" , accountIdx , wholeAccount : true , isKnown : true , isPrefork : false , } ; } function describeUTXOPath ( path , coin , scriptType ? ) { let pathStr = core . addressNListToBIP32 ( path ) ; let unknown = { verbose : pathStr , coin , scriptType , isKnown : false , } ; if ( ! Btc . btcSupportsCoin ( coin ) ) return unknown ; if ( ! Btc . btcSupportsScriptType ( coin , scriptType ) ) return unknown ; if ( path . length !== 3 && path . length !== 5 ) return unknown ; if ( ( path [ 0 ] & 0x80000000 ) >>> 0 !== 0x80000000 ) return unknown ; let purpose = path [ 0 ] & 0x7fffffff ; if ( ! [ 44 , 49 , 84 ] . includes ( purpose ) ) return unknown ; if ( purpose === 44 && scriptType !== core . BTCInputScriptType . SpendAddress ) return unknown ; if ( purpose === 49 && scriptType !== core . BTCInputScriptType . SpendP2SHWitness ) return unknown ; if ( purpose === 84 && scriptType !== core . BTCInputScriptType . SpendWitness ) return unknown ; const slip44 = core . slip44ByCoin ( coin ) ; if ( slip44 == undefined || path [ 1 ] !== 0x80000000 + slip44 ) return unknown ; let wholeAccount = path . length === 3 ; let script = scriptType ? ( { [ core . BTCInputScriptType . SpendAddress ] : " (Legacy)" , [ core . BTCInputScriptType . SpendP2SHWitness ] : "" , [ core . BTCInputScriptType . SpendWitness ] : " (Segwit Native)" , } as Partial < Record < core . BTCInputScriptType , string > > ) [ scriptType ] ?? "" : "" ; switch ( coin ) { case "Bitcoin" : case "Litecoin" : case "BitcoinGold" : case "Testnet" : break ; default : script = "" ; } let accountIdx = path [ 2 ] & 0x7fffffff ; if ( wholeAccount ) { return { verbose : `${ coin } Account #${ accountIdx }${ script }` , scriptType , coin , accountIdx , wholeAccount : true , isKnown : true , isPrefork : false , } ; } else { let change = path [ 3 ] === 1 ? "Change " : "" ; let addressIdx = path [ 4 ] ; return { verbose : `${ coin } Account #${ accountIdx }, ${ change }Address #${ addressIdx }${ script }` , coin , scriptType , accountIdx , addressIdx , isChange : path [ 3 ] === 1 , wholeAccount : false , isKnown : true , isPrefork : false , } ; } } export class TrezorHDWalletInfo implements core . HDWalletInfo , core . BTCWalletInfo , core . ETHWalletInfo { readonly _supportsBTCInfo = true ; readonly _supportsETHInfo = true ; public getVendor ( ) { return "Trezor" ; } public async btcSupportsCoin ( coin ) { return Btc . btcSupportsCoin ( coin ) ; } public async btcSupportsScriptType ( coin , scriptType ) { return Btc . btcSupportsScriptType ( coin , scriptType ) ; } public async btcSupportsSecureTransfer ( ) { return Btc . btcSupportsSecureTransfer ( ) ; } public btcSupportsNativeShapeShift ( ) { return Btc . btcSupportsNativeShapeShift ( ) ; } public btcGetAccountPaths ( msg ) { return Btc . btcGetAccountPaths ( msg ) ; } public btcIsSameAccount ( msg ) { return Btc . btcIsSameAccount ( msg ) ; } public async ethSupportsNetwork ( chain_id ) { return Eth . ethSupportsNetwork ( chain_id ) ; } public async ethSupportsSecureTransfer ( ) { return Eth . ethSupportsSecureTransfer ( ) ; } public ethSupportsNativeShapeShift ( ) { return Eth . ethSupportsNativeShapeShift ( ) ; } public async ethSupportsEIP1559 ( ) { return await Eth . ethSupportsEIP1559 ( ) ; } public ethGetAccountPaths ( msg ) { return Eth . ethGetAccountPaths ( msg ) ; } public hasOnDevicePinEntry ( ) { return true ; } public hasOnDevicePassphrase ( ) { return true ; } public hasOnDeviceDisplay ( ) { return true ; } public hasOnDeviceRecovery ( ) { return true ; } public hasNativeShapeShift ( srcCoin , dstCoin ) { return false ; } public supportsOfflineSigning ( ) { return true ; } public supportsBroadcast ( ) { return false ; } public describePath ( msg ) { switch ( msg . coin ) { case "Ethereum" : return describeETHPath ( msg . path ) ; default : return describeUTXOPath ( msg . path , msg . coin , msg . scriptType ) ; } } public btcNextAccountPath ( msg ) { let description = describeUTXOPath ( msg . addressNList , msg . coin , msg . scriptType ) ; if ( ! description . isKnown ) { return undefined ; } let addressNList = msg . addressNList ; if ( addressNList [ 0 ] === 0x80000000 + 44 || addressNList [ 0 ] === 0x80000000 + 49 || addressNList [ 0 ] === 0x80000000 + 84 ) { addressNList [ 2 ] += 1 ; return { ... msg , addressNList , } ; } return undefined ; } public ethNextAccountPath ( msg ) { let addressNList = msg . hardenedPath . concat ( msg . relPath ) ; let description = describeETHPath ( addressNList ) ; if ( ! description . isKnown ) { return undefined ; } if ( addressNList [ 0 ] === 0x80000000 + 44 ) { addressNList [ 4 ] += 1 ; return { ... msg , addressNList , hardenedPath : core . hardenedPath ( addressNList ) , relPath : core . relativePath ( addressNList ) , } ; } return undefined ; } } export class TrezorHDWallet implements core . HDWallet , core . BTCWallet , core . ETHWallet { readonly _supportsETHInfo = true ; readonly _supportsBTCInfo = true ; readonly _supportsBTC = true ; readonly _supportsETH = true ; readonly _supportsKavaInfo = true ; readonly _supportsTerraInfo = true ; readonly _isTrezor = true ; transport : TrezorTransport ; featuresCache : any ; info : TrezorHDWalletInfo & core . HDWalletInfo ; constructor ( transport ) { this . transport = transport ; this . info = new TrezorHDWalletInfo ( ) ; } public async initialize ( ) { return ; } public async isInitialized ( ) { let features = await this . getFeatures ( true ) ; return features . initialized ; } public async getDeviceID ( ) { const { device : { deviceID : transportId } , } = this . transport as any ; if ( transportId ) return transportId ; let features = await this . getFeatures ( true ) ; return features . device_id ; } public async getFirmwareVersion ( ) { let features = await this . getFeatures ( true ) ; return `v${ features . major_version }.${ features . minor_version }.${ features . patch_version }` ; } public getVendor ( ) { return "Trezor" ; } public async getModel ( ) { let features = await this . getFeatures ( true ) ; return "Trezor " + features . model ; } public async getLabel ( ) { let features = await this . getFeatures ( true ) ; return typeof features . label === "string" ? features . label : "" ; } public async getFeatures ( cached = false ) { if ( cached && this . featuresCache ) return this . featuresCache ; let res = await this . transport . call ( "getFeatures" , { } ) ; handleError ( this . transport , res , "Could not get Trezor features" ) ; this . cacheFeatures ( res . payload ) ; return res . payload ; } public cacheFeatures ( features ) { this . featuresCache = features ; } public async getPublicKeys ( msg ) { if ( ! msg . length ) return [ ] ; let res = await this . transport . call ( "getPublicKey" , { bundle : msg . map ( ( request ) => { return { path : request . addressNList , coin : request . coin || "Bitcoin" , crossChain : true , } ; } ) , } ) ; handleError ( this . transport , res , "Could not load xpubs from Trezor" ) ; return ( res . payload as Array < { xpubSegwit ? : string ; xpub ? : string ; } > ) . map ( ( result , i ) => { const scriptType = msg [ i ] . scriptType ; switch ( scriptType ) { case core . BTCInputScriptType . SpendP2SHWitness : case core . BTCInputScriptType . SpendWitness : { const xpub = result . xpubSegwit ; if ( ! xpub ) throw new Error ( "unable to get public key" ) ; return { xpub , } ; } case core . BTCInputScriptType . SpendAddress : default : { const xpub = result . xpub ; if ( ! xpub ) throw new Error ( "unable to get public key" ) ; return { xpub , } ; } } } ) ; } public async isLocked ( ) { const features = await this . getFeatures ( false ) ; if ( features . pin_protection && ! features . pin_cached ) return true ; if ( features . passphrase_protection && ! features . passphrase_cached ) return true ; return false ; } public async clearSession ( ) { } public async sendPin ( pin ) { await this . transport . call ( "uiResponse" , { type : "ui-receive_pin" , payload : pin , } ) ; } public async sendPassphrase ( passphrase ) { await this . transport . call ( "uiResponse" , { type : "ui-receive_passphrase" , payload : { value : passphrase , save : true , } , } ) ; } public async sendCharacter ( charater ) { throw new Error ( "Trezor does not suport chiphered recovery" ) ; } public async sendWord ( word ) { throw new Error ( "Trezor does not yet support recoverDevice" ) ; } public async ping ( msg ) { return { msg : msg . msg } ; } public async wipe ( ) { let res = await this . transport . call ( "wipeDevice" , { } ) ; handleError ( this . transport , res , "Could not wipe Trezor" ) ; } public async reset ( msg ) { let res = await this . transport . call ( "resetDevice" , { strength : msg . entropy , label : msg . label , pinProtection : msg . pin , passphraseProtection : msg . passphrase , } ) ; handleError ( this . transport , res , "Could not reset Trezor" ) ; } public async cancel ( ) { await this . transport . cancel ( ) ; } public async recover ( msg ) { throw new Error ( "TrezorConnect does not expose Core.RecoverDevice... yet?" ) ; } public async loadDevice ( msg ) { let res = await this . transport . call ( "loadDevice" , { mnemonic : msg . mnemonic , pin : msg . pin , passphraseProtection : msg . passphrase , label : msg . label , } ) ; handleError ( this . transport , res , "Could not load seed into Trezor" ) ; } public hasOnDevicePinEntry ( ) { return this . transport . hasPopup ; } public hasOnDevicePassphrase ( ) { return this . transport . hasPopup ; } public hasOnDeviceDisplay ( ) { return true ; } public hasOnDeviceRecovery ( ) { return this . transport . hasPopup ; } public hasNativeShapeShift ( srcCoin , dstCoin ) { return false ; } public supportsOfflineSigning ( ) { return true ; } public supportsBroadcast ( ) { return false ; } public async btcSupportsCoin ( coin ) { return this . info . btcSupportsCoin ( coin ) ; } public async btcSupportsScriptType ( coin , scriptType ) { return this . info . btcSupportsScriptType ( coin , scriptType ) ; } public async btcGetAddress ( msg ) { return Btc . btcGetAddress ( this . transport , msg ) ; } public async btcSignTx ( msg ) { return Btc . btcSignTx ( this , this . transport , msg ) ; } public async btcSupportsSecureTransfer ( ) { return this . info . btcSupportsSecureTransfer ( ) ; } public btcSupportsNativeShapeShift ( ) { return this . info . btcSupportsNativeShapeShift ( ) ; } public async btcSignMessage ( msg ) { return Btc . btcSignMessage ( this . transport , msg ) ; } public async btcVerifyMessage ( msg ) { return Btc . btcVerifyMessage ( this . transport , msg ) ; } public btcGetAccountPaths ( msg ) { return Btc . btcGetAccountPaths ( msg ) ; } public btcIsSameAccount ( msg ) { return this . info . btcIsSameAccount ( msg ) ; } public async ethSignTx ( msg ) { return Eth . ethSignTx ( this , this . transport , msg ) ; } public async ethGetAddress ( msg ) { return Eth . ethGetAddress ( this . transport , msg ) ; } public async ethSignMessage ( msg ) { return Eth . ethSignMessage ( this . transport , msg ) ; } public async ethVerifyMessage ( msg ) { return Eth . ethVerifyMessage ( this . transport , msg ) ; } public async ethSupportsNetwork ( chain_id ) { return this . info . ethSupportsNetwork ( chain_id ) ; } public async ethSupportsSecureTransfer ( ) { return this . info . ethSupportsSecureTransfer ( ) ; } public ethSupportsNativeShapeShift ( ) { return this . info . ethSupportsNativeShapeShift ( ) ; } public async ethSupportsEIP1559 ( ) { return await this . info . ethSupportsEIP1559 ( ) ; } public ethGetAccountPaths ( msg ) { return this . info . ethGetAccountPaths ( msg ) ; } public describePath ( msg ) { return this . info . describePath ( msg ) ; } public disconnect ( ) { return this . transport . disconnect ( ) ; } public btcNextAccountPath ( msg ) { return this . info . btcNextAccountPath ( msg ) ; } public ethNextAccountPath ( msg ) { return this . info . ethNextAccountPath ( msg ) ; } } export function info ( ) { return new TrezorHDWalletInfo ( ) ; } export function create ( transport ) { return new TrezorHDWallet ( transport ) ; }