From 8ed229b3fe612df2a9f4ab45c1eaf5cbdb060664 Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Tue, 7 Nov 2023 16:30:02 +0530 Subject: [PATCH] Adding script-path fee editing --- index.html | 124 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 76 insertions(+), 48 deletions(-) diff --git a/index.html b/index.html index 27f22ba..8e565f5 100644 --- a/index.html +++ b/index.html @@ -501,7 +501,7 @@ .then(() => { selectedCurrency = localStorage.getItem('taproot-wallet-currency') || 'btc' setTimeout(() => { - document.getElementById('currency_selector').value = selectedCurrency + getRef('currency_selector').value = selectedCurrency }, 100) }) .catch(e => { @@ -2105,15 +2105,13 @@ Show password - -
- ${finalScriptWitness.length > 3 ? html` - - ` : html` - - `}

*The generated signature will be used as one of the solutions.

+ ${finalScriptWitness.length > 3 ? html` + + ` : html` + + `} ` : ''}

@@ -2124,7 +2122,7 @@

- +
`) @@ -2174,12 +2172,12 @@
- +
`) } - document.getElementById('new_fee').querySelector('.currency-symbol').innerHTML = currencyIcons[selectedCurrency] + getRef('new_fee').querySelector('.currency-symbol').innerHTML = currencyIcons[selectedCurrency] openPopup('increase_fee_popup') } catch (e) { console.error(e) @@ -2187,39 +2185,54 @@ buttonLoader(button, false) } } - async function increaseFee() { - buttonLoader(document.getElementById('increase_fee'), true) - const newFee = parseFloat((parseFloat(document.getElementById('new_fee').value.trim()) / (globalExchangeRate[selectedCurrency] || 1)).toFixed(8)) - const privateKeys = [] - document.querySelectorAll('.increase-fee-sender').forEach(sender => { - const address = sender.querySelector('.sender__address').textContent.trim() - const privateKey = sender.querySelector('.sender__private-key').value.trim() - if (!btcOperator.verifyKey(address, privateKey)) - return notify(`Invalid private key for address ${address}`, 'error') - if (privateKey) { - privateKeys.push(privateKey) - } - }) - console.log(changingFeeOf, newFee, privateKeys) + async function increaseFee(type = 'key-path') { try { - let signedTxHex = await taprootEditFee({ - txId: changingFeeOf, - new_fee: newFee, - private_keys: privateKeys - }) - btcOperator.broadcastTx(signedTxHex).then(txId => { + buttonLoader(getRef('increase_fee'), true) + const newFee = parseFloat((parseFloat(getRef('new_fee').value.trim()) / (globalExchangeRate[selectedCurrency] || 1)).toFixed(8)) + let txHex + if (type === 'key-path') { + const privateKeys = [] + document.querySelectorAll('.increase-fee-sender').forEach(sender => { + const address = sender.querySelector('.sender__address').textContent.trim() + const privateKey = sender.querySelector('.sender__private-key').value.trim() + if (!btcOperator.verifyKey(address, privateKey)) + return notify(`Invalid private key for address ${address}`, 'error') + if (privateKey) { + privateKeys.push(privateKey) + } + }) + txHex = await taprootEditFee({ + txId: changingFeeOf, + newFee: newFee, + privateKeys + }) + } else { + const providedSolutions = [...document.querySelectorAll('.solution-input')].map(input => input.value.trim()) + const signaturePlace = parseInt(getRef('signature_place_input').value.trim()) + const signerPrivateKey = getRef('signer_input') ? getRef('signer_input').value.trim() : null; + const editParams = { + txId: changingFeeOf, + newFee, + providedSolutions, + signaturePlace, + } + if (signerPrivateKey) + editParams.signerSecretKey = hex.decode(coinjs.wif2privkey(signerPrivateKey).privkey) + txHex = await taprootEditFee(editParams) + } + btcOperator.broadcastTx(txHex).then(txId => { console.log(txId) closePopup() showTransactionResult('success', txId) }).catch(e => { notify(e, 'error') }).finally(_ => { - buttonLoader(document.getElementById('increase_fee'), false) + buttonLoader(getRef('increase_fee'), false) changingFeeOf = null }) } catch (err) { notify(e, 'error') - buttonLoader(document.getElementById('increase_fee'), false) + buttonLoader(getRef('increase_fee'), false) } } router.addRoute('create', state => { @@ -2487,29 +2500,27 @@ }) } - const taprootEditFee = function ({ txId, new_fee, private_keys, change_only = true }) { + const taprootEditFee = function ({ txId, newFee, privateKeys, changeOnly = true, signerSecretKey, providedSolutions, signaturePlace }) { return new Promise((resolve, reject) => { - if (!Array.isArray(private_keys)) - private_keys = [private_keys]; getTaprootTx(txId).then(({ tx, rawTx }) => { const isScriptPath = tx.inputs[0].finalScriptWitness && tx.inputs[0].finalScriptWitness.length > 1; const parsedTx = parseTransaction(rawTx) - if (parsedTx.fee >= new_fee) + if (parsedTx.fee >= newFee) return reject("Fees can only be increased"); //editable addresses in output values (for fee increase) - parsedTx edit_output_address = new Set(); - if (change_only === true) //allow only change values (ie, sender address) to be edited to inc fee + const edit_output_address = new Set(); + if (changeOnly === true) //allow only change values (ie, sender address) to be edited to inc fee parsedTx.inputs.forEach(inp => edit_output_address.add(inp.address)); - else if (change_only === false) //allow all output values to be edited + else if (changeOnly === false) //allow all output values to be edited parsedTx.outputs.forEach(out => edit_output_address.add(out.address)); - else if (typeof change_only == 'string') // allow only given receiver id output to be edited - edit_output_address.add(change_only); - else if (Array.isArray(change_only)) //allow only given set of receiver id outputs to be edited - change_only.forEach(id => edit_output_address.add(id)); + else if (typeof changeOnly == 'string') // allow only given receiver id output to be edited + edit_output_address.add(changeOnly); + else if (Array.isArray(changeOnly)) //allow only given set of receiver id outputs to be edited + changeOnly.forEach(id => edit_output_address.add(id)); //edit output values to increase fee - let inc_fee = btcOperator.util.BTC_to_Sat(new_fee - parsedTx.fee); + let inc_fee = btcOperator.util.BTC_to_Sat(newFee - parsedTx.fee); if (inc_fee < MIN_FEE_UPDATE) return reject(`Insufficient additional fee. Minimum increment: ${MIN_FEE_UPDATE}`); for (let i = tx.outs.length - 1; i >= 0 && inc_fee > 0; i--) //reduce in reverse order @@ -2527,19 +2538,36 @@ } } if (inc_fee > 0) { - let max_possible_fee = btcOperator.util.BTC_to_Sat(new_fee) - inc_fee; //in satoshi + let max_possible_fee = btcOperator.util.BTC_to_Sat(newFee) - inc_fee; //in satoshi return reject(`Insufficient output values to increase fee. Maximum fee possible: ${btcOperator.util.Sat_to_BTC(max_possible_fee)}`); } tx.outs = tx.outs.filter(o => o.value >= DUST_AMT); //remove all output with value less than DUST amount if (isScriptPath) { - + tx.inputs.forEach((input, index) => { + // remove every element from the finalScriptWitness except the last two + const scriptWitness = input.finalScriptWitness.slice(-2) + const [userScript, controlBlock] = scriptWitness; + const controlBlockVersion = taproot.TaprootControlBlock.decode(controlBlock).version + const providedSolutions = scriptWitness.slice(0, -1) + if (providedSolutions && providedSolutions.length) + scriptWitness.unshift(...providedSolutions) + if (signerSecretKey) { + const { amount } = consumedUtxos[index] + const hash = tx.preimageWitnessV1(index, [script], 0, [amount], undefined, userScript, controlBlockVersion) + const sig = secp.schnorr.signSync(hash, signerSecretKey, new Uint8Array(32)) + scriptWitness.splice((signaturePlace - 1), 0, sig) + } + input.finalScriptWitness = scriptWitness + }) } else { //remove existing signatures and reset the scripts + if (!Array.isArray(privateKeys)) + privateKeys = [privateKeys]; let wif_keys = []; for (let i in tx.ins) { const addr = parsedTx.inputs[i].address; //find the correct key for addr - const privKey = private_keys.find(pk => btcOperator.verifyKey(addr, pk)); + const privKey = privateKeys.find(pk => btcOperator.verifyKey(addr, pk)); if (!privKey) return reject(`Private key missing for ${addr}`); }