import { ensureLeading0x , trimLeading0x } from '@celo/base/lib/address' ; import { RLPEncodedTx , Signer } from '@celo/connect' ; import { EIP712TypedData , structHash } from '@celo/utils/lib/sign-typed-data-utils' ; import { TransportStatusError } from '@ledgerhq/errors' ; import debugFactory from 'debug' ; import * as ethUtil from 'ethereumjs-util' ; import { transportErrorFriendlyMessage } from './ledger-utils' ; import { AddressValidation } from './ledger-wallet' ; import { compareLedgerAppVersions , tokenInfoByAddressAndChainId } from './tokens' ; const debug = debugFactory ( 'kit:wallet:ledger' ) ; const CELO_APP_ACCEPTS_CONTRACT_DATA_FROM_VERSION = '1.0.2' ; /**
 * Signs the EVM transaction with a Ledger device
 */ export class LedgerSigner implements Signer { private ledger : any ; private derivationPath : string ; private validated : boolean = false ; private ledgerAddressValidation : AddressValidation ; private appConfiguration : { arbitraryDataEnabled : number ; version : string ; } ; constructor ( ledger , derivationPath , ledgerAddressValidation , appConfiguration = { arbitraryDataEnabled : 0 , version : '0.0.0' } ) { this . ledger = ledger ; this . derivationPath = derivationPath ; this . ledgerAddressValidation = ledgerAddressValidation ; this . appConfiguration = appConfiguration ; } getNativeKey ( ) { return this . derivationPath ; } async signTransaction ( addToV , encodedTx ) { try { const validatedDerivationPath = await this . getValidatedDerivationPath ( ) ; await this . checkForKnownToken ( encodedTx ) ; const signature = await this . ledger ! . signTransaction ( validatedDerivationPath , trimLeading0x ( encodedTx . rlpEncode ) ) ; const rv = parseInt ( signature . v , 16 ) ; if ( rv !== addToV && ( rv & addToV ) !== rv ) { addToV += 1 ; } signature . v = addToV . toString ( 10 ) ; return { v : signature . v , r : ethUtil . toBuffer ( ensureLeading0x ( signature . r ) ) as Buffer , s : ethUtil . toBuffer ( ensureLeading0x ( signature . s ) ) as Buffer } ; } catch ( error ) { if ( error instanceof TransportStatusError ) { if ( error . statusCode === 27264 && error . statusText === 'INCORRECT_DATA' ) { debug ( 'Possible invalid feeCurrency field' ) ; throw new Error ( 'ledger-signer@signTransaction: Incorrect Data. Verify that the feeCurrency is a valid one' ) ; } else { transportErrorFriendlyMessage ( error ) ; } } throw error ; } } async signPersonalMessage ( data ) { try { const signature = await this . ledger ! . signPersonalMessage ( await this . getValidatedDerivationPath ( ) , trimLeading0x ( data ) ) ; return { v : signature . v , r : ethUtil . toBuffer ( ensureLeading0x ( signature . r ) ) as Buffer , s : ethUtil . toBuffer ( ensureLeading0x ( signature . s ) ) as Buffer } ; } catch ( error ) { if ( error instanceof TransportStatusError ) { transportErrorFriendlyMessage ( error ) ; } throw error ; } } async signTypedData ( typedData ) { try { const domainSeparator = structHash ( 'EIP712Domain' , typedData . domain , typedData . types ) ; const hashStructMessage = structHash ( typedData . primaryType , typedData . message , typedData . types ) ; const sig = await this . ledger ! . signEIP712HashedMessage ( await this . getValidatedDerivationPath ( ) , domainSeparator , hashStructMessage ) ; return { v : parseInt ( sig . v , 10 ) , r : ethUtil . toBuffer ( ensureLeading0x ( sig . r ) ) as Buffer , s : ethUtil . toBuffer ( ensureLeading0x ( sig . s ) ) as Buffer } ; } catch ( error ) { if ( error instanceof TransportStatusError ) { transportErrorFriendlyMessage ( error ) ; } throw error ; } } private async getValidatedDerivationPath ( ) { if ( this . validationRequired ( ) ) { await this . ledger ! . getAddress ( this . derivationPath , true ) ; this . validated = true ; } return this . derivationPath ; } private validationRequired ( ) { switch ( this . ledgerAddressValidation ) { case AddressValidation . never : { return false ; } case AddressValidation . everyTransaction : { return true ; } case AddressValidation . firstTransactionPerAddress : { return ! this . validated ; } case AddressValidation . initializationOnly : { return false ; } default : { throw new Error ( 'ledger-signer@validationRequired: invalid ledgerValidation value' ) ; } } } param rlpEncoded private async checkForKnownToken ( rlpEncoded ) { if ( compareLedgerAppVersions ( this . appConfiguration . version , CELO_APP_ACCEPTS_CONTRACT_DATA_FROM_VERSION ) >= 0 ) { const tokenInfo = tokenInfoByAddressAndChainId ( rlpEncoded . transaction . to ! , rlpEncoded . transaction . chainId ! ) ; if ( tokenInfo ) { await this . ledger ! . provideERC20TokenInformation ( tokenInfo ) ; } if ( rlpEncoded . transaction . feeCurrency && rlpEncoded . transaction . feeCurrency !== '0x' ) { const feeTokenInfo = tokenInfoByAddressAndChainId ( rlpEncoded . transaction . feeCurrency ! , rlpEncoded . transaction . chainId ! ) ; if ( feeTokenInfo ) { await this . ledger ! . provideERC20TokenInformation ( feeTokenInfo ) ; } } } } decrypt ( _ciphertext ) { throw new Error ( 'Decryption operation is not supported on this signer' ) ; return Promise . resolve ( _ciphertext ) ; } computeSharedSecret ( _publicKey ) { throw new Error ( 'Not implemented' ) ; return Promise . resolve ( Buffer . from ( [ ] ) ) ; } }