|
|
@ -1,5 +1,6 @@ |
|
|
|
// @flow
|
|
|
|
|
|
|
|
import logger from 'logger' |
|
|
|
import type { AccountRaw, OperationRaw } from '@ledgerhq/live-common/lib/types' |
|
|
|
import Btc from '@ledgerhq/hw-app-btc' |
|
|
|
import { Observable } from 'rxjs' |
|
|
@ -55,6 +56,84 @@ const cmd: Command<Input, Result> = createCommand( |
|
|
|
}), |
|
|
|
) |
|
|
|
|
|
|
|
async function signTransaction({ |
|
|
|
hwApp, |
|
|
|
currencyId, |
|
|
|
transaction, |
|
|
|
sigHashType, |
|
|
|
supportsSegwit, |
|
|
|
isSegwit, |
|
|
|
hasTimestamp, |
|
|
|
}: { |
|
|
|
hwApp: Btc, |
|
|
|
currencyId: string, |
|
|
|
transaction: *, |
|
|
|
sigHashType: number, |
|
|
|
supportsSegwit: boolean, |
|
|
|
isSegwit: boolean, |
|
|
|
hasTimestamp: boolean, |
|
|
|
}) { |
|
|
|
const additionals = [] |
|
|
|
if (currencyId === 'bitcoin_cash' || currencyId === 'bitcoin_gold') additionals.push('bip143') |
|
|
|
const rawInputs = transaction.getInputs() |
|
|
|
|
|
|
|
const inputs = await Promise.all( |
|
|
|
rawInputs.map(async input => { |
|
|
|
const rawPreviousTransaction = await input.getPreviousTransaction() |
|
|
|
const hexPreviousTransaction = Buffer.from(rawPreviousTransaction).toString('hex') |
|
|
|
const previousTransaction = hwApp.splitTransaction( |
|
|
|
hexPreviousTransaction, |
|
|
|
supportsSegwit, |
|
|
|
hasTimestamp, |
|
|
|
) |
|
|
|
const outputIndex = input.getPreviousOutputIndex() |
|
|
|
const sequence = input.getSequence() |
|
|
|
return [ |
|
|
|
previousTransaction, |
|
|
|
outputIndex, |
|
|
|
undefined, // we don't use that TODO: document
|
|
|
|
sequence, // 0xffffffff,
|
|
|
|
] |
|
|
|
}), |
|
|
|
) |
|
|
|
|
|
|
|
const associatedKeysets = rawInputs.map(input => { |
|
|
|
const derivationPaths = input.getDerivationPath() |
|
|
|
return derivationPaths[0].toString() |
|
|
|
}) |
|
|
|
|
|
|
|
const outputs = transaction.getOutputs() |
|
|
|
|
|
|
|
const output = outputs.find(output => { |
|
|
|
const derivationPath = output.getDerivationPath() |
|
|
|
if (derivationPath.isNull()) { |
|
|
|
return false |
|
|
|
} |
|
|
|
const strDerivationPath = derivationPath.toString() |
|
|
|
const derivationArr = strDerivationPath.split('/') |
|
|
|
return derivationArr[derivationArr.length - 2] === '1' |
|
|
|
}) |
|
|
|
|
|
|
|
const changePath = output ? output.getDerivationPath().toString() : undefined |
|
|
|
const outputScriptHex = Buffer.from(transaction.serializeOutputs()).toString('hex') |
|
|
|
const lockTime = transaction.getLockTime() |
|
|
|
const initialTimestamp = hasTimestamp ? transaction.getTimestamp() : undefined |
|
|
|
|
|
|
|
const signedTransaction = await hwApp.createPaymentTransactionNew( |
|
|
|
inputs, |
|
|
|
associatedKeysets, |
|
|
|
changePath, |
|
|
|
outputScriptHex, |
|
|
|
lockTime, |
|
|
|
sigHashType, |
|
|
|
isSegwit, |
|
|
|
initialTimestamp, |
|
|
|
additionals, |
|
|
|
) |
|
|
|
|
|
|
|
return signedTransaction |
|
|
|
} |
|
|
|
|
|
|
|
export async function doSignAndBroadcast({ |
|
|
|
account, |
|
|
|
transaction, |
|
|
@ -74,7 +153,7 @@ export async function doSignAndBroadcast({ |
|
|
|
}): Promise<void> { |
|
|
|
let njsAccount |
|
|
|
|
|
|
|
const signedTransaction: ?string = await withDevice(deviceId)(async transport => { |
|
|
|
const signedTransaction = await withDevice(deviceId)(async transport => { |
|
|
|
const hwApp = new Btc(transport) |
|
|
|
|
|
|
|
const WALLET_IDENTIFIER = await getWalletIdentifier({ |
|
|
@ -103,21 +182,22 @@ export async function doSignAndBroadcast({ |
|
|
|
|
|
|
|
const builded = await transactionBuilder.build() |
|
|
|
if (isCancelled()) return null |
|
|
|
const sigHashType = core.helpers.bytesToHex( |
|
|
|
const sigHashType = Buffer.from( |
|
|
|
njsWalletCurrency.bitcoinLikeNetworkParameters.SigHash, |
|
|
|
) |
|
|
|
).toString('hex') |
|
|
|
|
|
|
|
const hasTimestamp = njsWalletCurrency.bitcoinLikeNetworkParameters.UsesTimestampedTransaction |
|
|
|
const hasTimestamp = !!njsWalletCurrency.bitcoinLikeNetworkParameters.UsesTimestampedTransaction |
|
|
|
// TODO: const timestampDelay = njsWalletCurrency.bitcoinLikeNetworkParameters.TimestampDelay
|
|
|
|
|
|
|
|
const currency = getCryptoCurrencyById(account.currencyId) |
|
|
|
|
|
|
|
return core.signTransaction({ |
|
|
|
return signTransaction({ |
|
|
|
hwApp, |
|
|
|
currencyId: account.currencyId, |
|
|
|
transaction: builded, |
|
|
|
sigHashType: parseInt(sigHashType, 16).toString(), |
|
|
|
sigHashType: parseInt(sigHashType, 16), |
|
|
|
supportsSegwit: !!currency.supportsSegwit, |
|
|
|
isSegwit: account.isSegwit, |
|
|
|
isSegwit: !!account.isSegwit, |
|
|
|
hasTimestamp, |
|
|
|
}) |
|
|
|
}) |
|
|
@ -125,7 +205,12 @@ export async function doSignAndBroadcast({ |
|
|
|
if (!signedTransaction || isCancelled() || !njsAccount) return |
|
|
|
onSigned() |
|
|
|
|
|
|
|
const txHash = await njsAccount.asBitcoinLikeAccount().broadcastRawTransaction(signedTransaction) |
|
|
|
logger.log(signedTransaction) |
|
|
|
|
|
|
|
const txHash = await njsAccount |
|
|
|
.asBitcoinLikeAccount() |
|
|
|
.broadcastRawTransaction(Array.from(Buffer.from(signedTransaction, 'hex'))) |
|
|
|
|
|
|
|
// NB we don't check isCancelled() because the broadcast is not cancellable now!
|
|
|
|
onOperationBroadcasted({ |
|
|
|
id: txHash, |
|
|
|