Skip to content

Commit

Permalink
refactor deriveXpub to eliminate repetition from crypto providers
Browse files Browse the repository at this point in the history
  • Loading branch information
refi93 committed Feb 1, 2019
1 parent 08d802d commit 7564a9e
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 98 deletions.
82 changes: 23 additions & 59 deletions app/frontend/wallet/cardano-trezor-crypto-provider.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
const {derivePublic} = require('cardano-crypto.js')
const indexIsHardened = require('./helpers/indexIsHardened')
// eslint-disable-next-line import/no-unresolved
const {default: TrezorConnect} = require('trezor-connect')
const CachedDeriveXpubFactory = require('./helpers/CachedDeriveXpubFactory')

const CardanoTrezorCryptoProvider = (ADALITE_CONFIG, walletState) => {
const state = Object.assign(walletState, {
derivedXpubs: {},
rootHdPassphrase: null,
derivedAddresses: {},
})
Expand All @@ -14,10 +12,29 @@ const CardanoTrezorCryptoProvider = (ADALITE_CONFIG, walletState) => {
throw new Error(`Unsupported derivation scheme: ${state.derivationScheme.type}`)
}

async function trezorDeriveAddress(absDerivationPath, displayConfirmation) {
const deriveXpub = CachedDeriveXpubFactory(state.derivationScheme, async (absDerivationPath) => {
const response = await TrezorConnect.cardanoGetPublicKey({
path: absDerivationPath,
showOnTrezor: false,
})
if (response.error || !response.success) {
throw new Error('public key retrieval from Trezor failed')
}
return Buffer.from(response.payload.publicKey, 'hex')
})

function deriveHdNode(childIndex) {
throw new Error('This operation is not supported on TrezorCryptoProvider!')
}

function sign(message, absDerivationPath) {
throw new Error('Not supported')
}

async function displayAddressForPath(absDerivationPath) {
const response = await TrezorConnect.cardanoGetAddress({
path: absDerivationPath,
showOnTrezor: displayConfirmation,
showOnTrezor: true,
})

if (response.success) {
Expand All @@ -31,59 +48,6 @@ const CardanoTrezorCryptoProvider = (ADALITE_CONFIG, walletState) => {
throw new Error('Trezor operation failed!')
}

function deriveXpub(absDerivationPath) {
const memoKey = JSON.stringify(absDerivationPath)

if (!state.derivedXpubs[memoKey]) {
const deriveHardened =
absDerivationPath.length === 0 ||
indexIsHardened(absDerivationPath[absDerivationPath.length - 1])

/*
* TODO - reset cache if the promise fails, for now it does not matter
* since a failure (e.g. rejection by user) there leads to
* the creation of a fresh wallet instance
*/
state.derivedXpubs[memoKey] = deriveHardened
? deriveXpubHardened(absDerivationPath)
: deriveXpubNonHardened(absDerivationPath)
}

return state.derivedXpubs[memoKey]
}

function deriveXpubHardened(absDerivationPath) {
return TrezorConnect.cardanoGetPublicKey({
path: absDerivationPath,
showOnTrezor: false,
}).then((response) => {
if (response.error || !response.success) {
return Promise.reject(response || 'operation failed')
}
return Buffer.from(response.payload.publicKey, 'hex')
})
}

async function deriveXpubNonHardened(absDerivationPath) {
const lastIndex = absDerivationPath[absDerivationPath.length - 1]
const parentXpub = await deriveXpub(absDerivationPath.slice(0, absDerivationPath.length - 1))

return derivePublic(parentXpub, lastIndex, state.derivationScheme.number)
}

function deriveHdNode(childIndex) {
throw new Error('This operation is not supported on TrezorCryptoProvider!')
}

function sign(message, absDerivationPath) {
throw new Error('Not supported')
}

async function trezorVerifyAddress(address, addressToAbsPathMapper) {
const absDerivationPath = addressToAbsPathMapper(address)
await trezorDeriveAddress(absDerivationPath, true)
}

function prepareInput(input, addressToAbsPathMapper) {
const data = {
prev_hash: input.txHash,
Expand Down Expand Up @@ -161,7 +125,7 @@ const CardanoTrezorCryptoProvider = (ADALITE_CONFIG, walletState) => {
return {
getWalletSecret,
signTx,
trezorVerifyAddress,
displayAddressForPath,
deriveXpub,
_sign: sign,
_deriveHdNode: deriveHdNode,
Expand Down
43 changes: 7 additions & 36 deletions app/frontend/wallet/cardano-wallet-secret-crypto-provider.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
const cbor = require('borc')
const {
blake2b,
sign: signMsg,
derivePublic,
derivePrivate,
xpubToHdPassphrase,
} = require('cardano-crypto.js')
const {blake2b, sign: signMsg, derivePrivate, xpubToHdPassphrase} = require('cardano-crypto.js')

const {TxWitness, SignedTransactionStructured} = require('./transaction')
const HdNode = require('./hd-node')
const {parseTxAux} = require('./helpers/cbor-parsers')
const NamedError = require('../helpers/NamedError')
const {NETWORKS} = require('./constants')
const indexIsHardened = require('./helpers/indexIsHardened')
const CachedDeriveXpubFactory = require('./helpers/CachedDeriveXpubFactory')

const CardanoWalletSecretCryptoProvider = (params, walletState, disableCaching = false) => {
const state = Object.assign(walletState, {
masterHdNode: HdNode({secret: params.walletSecret}),
derivedHdNodes: {},
derivedXpubs: {},
derivedAddresses: {},
network: params.network,
derivationScheme: params.derivationScheme,
})
Expand All @@ -28,31 +20,10 @@ const CardanoWalletSecretCryptoProvider = (params, walletState, disableCaching =
return state.masterHdNode.toBuffer()
}

function deriveXpub(derivationPath) {
const memoKey = JSON.stringify(derivationPath)

if (!state.derivedXpubs[memoKey]) {
const deriveHardened =
derivationPath.length === 0 || indexIsHardened(derivationPath[derivationPath.length - 1])

state.derivedXpubs[memoKey] = deriveHardened
? deriveXpubHardened(derivationPath)
: deriveXpubNonHardened(derivationPath)
}

return state.derivedXpubs[memoKey]
}

function deriveXpubHardened(derivationPath) {
return deriveHdNode(derivationPath).extendedPublicKey
}

function deriveXpubNonHardened(derivationPath) {
const lastIndex = derivationPath[derivationPath.length - 1]
const parentXpub = deriveXpub(derivationPath.slice(0, derivationPath.length - 1))

return derivePublic(parentXpub, lastIndex, state.derivationScheme.number)
}
const deriveXpub = CachedDeriveXpubFactory(
state.derivationScheme,
(derivationPath) => deriveHdNode(derivationPath).extendedPublicKey
)

function getHdPassphrase() {
return xpubToHdPassphrase(state.masterHdNode.extendedPublicKey)
Expand Down Expand Up @@ -118,7 +89,7 @@ const CardanoWalletSecretCryptoProvider = (params, walletState, disableCaching =
const witnesses = await Promise.all(
txAux.inputs.map(async (input) => {
const absoluteDerivationPath = addressToAbsPathMapper(input.utxo.address)
const xpub = deriveHdNode(absoluteDerivationPath).extendedPublicKey
const xpub = await deriveXpub(absoluteDerivationPath)
const protocolMagic = NETWORKS[state.network].protocolMagic

/*
Expand Down
6 changes: 3 additions & 3 deletions app/frontend/wallet/cardano-wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,11 @@ const CardanoWallet = async (options) => {
}

function verifyAddress(addr) {
if (!cryptoProvider.trezorVerifyAddress) {
if (!cryptoProvider.displayAddressForPath) {
throw Error('unsupported operation: verifyAddress')
}

return cryptoProvider.trezorVerifyAddress(addr, getAddressToAbsPathMapper())
const absDerivationPath = getAddressToAbsPathMapper()(addr)
return cryptoProvider.displayAddressForPath(absDerivationPath)
}

async function getNewUtxosFromTxAux(txAux) {
Expand Down
41 changes: 41 additions & 0 deletions app/frontend/wallet/helpers/CachedDeriveXpubFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const indexIsHardened = require('./indexIsHardened')
const {derivePublic: deriveChildXpub} = require('cardano-crypto.js')

function CachedDeriveXpubFactory(derivationScheme, deriveXpubHardenedFn) {
const derivedXpubs = {}

async function deriveXpub(absDerivationPath) {
const memoKey = JSON.stringify(absDerivationPath)

if (!derivedXpubs[memoKey]) {
const deriveHardened =
absDerivationPath.length === 0 || indexIsHardened(absDerivationPath.slice(-1)[0])

/*
* TODO - reset cache if the promise fails, for now it does not matter
* since a failure (e.g. rejection by user) there leads to
* the creation of a fresh wallet and crypto provider instance
*/
derivedXpubs[memoKey] = deriveHardened
? deriveXpubHardenedFn(absDerivationPath)
: deriveXpubNonhardenedFn(absDerivationPath)
}

/*
* the derivedXpubs map stores promises instead of direct results
* to deal with concurrent requests to derive the same xpub
*/
return await derivedXpubs[memoKey]
}

async function deriveXpubNonhardenedFn(derivationPath) {
const lastIndex = derivationPath.slice(-1)[0]
const parentXpub = await deriveXpub(derivationPath.slice(0, -1))

return deriveChildXpub(parentXpub, lastIndex, derivationScheme.number)
}

return deriveXpub
}

module.exports = CachedDeriveXpubFactory

0 comments on commit 7564a9e

Please sign in to comment.