@@ -1537,8 +1544,9 @@
receivers: receiverAddresses,
amounts: receiverAmounts,
scriptWitness,
- tr: taprootCommitment,
- signerSecretKey
+ signerSecretKey,
+ userScript: hex.decode(script),
+ userControlBlock: taproot.TaprootControlBlock.decode(hex.decode(controlBlockHex))
})
console.log(txHex)
btcOperator.broadcastTx(txHex).then(txid => {
@@ -1735,21 +1743,29 @@
})
function checkBalance() {
- const wif = getRef('private_key_input').value.trim()
- if (!wif)
- return notify(`Please enter sender's private key to check balance`)
+ let address;
+ const hasProvidedPrivateKey = !!getRef('private_key_input')
+ if (hasProvidedPrivateKey) {
+ const wif = getRef('private_key_input').value.trim()
+ if (!wif)
+ return notify(`Please enter sender's private key to check balance`)
+ address = getTaprootAddress(wif).tr.address
+ } else {
+ address = getRef('taproot_sender_input').value.trim()
+ }
getRef('sender_balance_container').classList.remove('hidden')
renderElem(getRef('sender_balance_container'), html`
Loading balance...
`)
- const { tr: { address } } = getTaprootAddress(wif)
btcOperator.getBalance(address).then(balance => {
renderElem(getRef('sender_balance_container'), html`
-
-
Sender address
-
${address}
-
+ ${hasProvidedPrivateKey ? html`
+
+
Sender address
+
${address}
+
+ `: ''}
Balance: ${getConvertedAmount(balance, true)}
@@ -2076,7 +2092,7 @@
const { privkey } = coinjs.wif2privkey(wif)
const privKey_arrayform = hex.decode(privkey)
const pubS = secp256k1_schnorr.getPublicKey(privKey_arrayform);
- const tr = btc.p2tr(pubS);
+ const tr = taproot.p2tr(pubS);
return {
tr,
wif
@@ -2114,45 +2130,66 @@
* @param {number} fee in BTC
*/
async function createTx(params = {}) {
- let { senderPrivateKey, receivers = [], amounts = [], fee = 0, isTaprootScriptPath = false, tr, witness, signerSecretKey } = params
return new Promise(async (resolve, reject) => {
+ let {
+ senderAddress,
+ senderPrivateKey,
+ receivers = [],
+ amounts = [],
+ fee = 0,
+ isTaprootScriptPath = false,
+ scriptWitness,
+ userScript,
+ signerSecretKey,
+ userControlBlock,
+ } = params
try {
- if (!tr)
+ let tr
+ let address, script
+ let opts = {};
+ if (isTaprootScriptPath) {
+ address = senderAddress
+ script = hex.decode(coinjs.addressDecode(senderAddress).outstring)
+ opts = { allowUnknownInputs: true }
+ } else {
tr = getTaprootAddress(senderPrivateKey).tr
- const { address, script } = tr
- const opts = {};
+ address = tr.address
+ script = tr.script
+ }
const tx = new taproot.Transaction(opts);
const totalAmount = amounts.reduce((total, amount) => total + amount, 0)
const amountInSat = btcOperator.util.BTC_to_Sat(totalAmount)
- // check if sender has enough balance
- const senderBalance = await btcOperator.getBalance(address)
- const feeRate = await btcOperator.getFeeRate();
+ const [senderBalance, feeRate] = await Promise.all([btcOperator.getBalance(address), btcOperator.getFeeRate()]);
let calculatedFee = 0;
- const { input_size } = await addUTXOs(tx, tr, [address], btcOperator.util.BTC_to_Sat(totalAmount), feeRate)
+ const { input_size, consumedUtxoInputs } = await addUTXOs(tx, tr, [address], btcOperator.util.BTC_to_Sat(totalAmount), feeRate)
calculatedFee += input_size
// add receivers
receivers.forEach((receiver, i) => {
tx.addOutputAddress(receiver, BigInt(btcOperator.util.BTC_to_Sat(amounts[i])))
calculatedFee += _sizePerOutput(receiver)
})
- calculatedFee += _sizePerOutput(address)
+ calculatedFee += _sizePerOutput(address) // add change output
calculatedFee = parseFloat((calculatedFee * feeRate).toFixed(8)) // convert to sat
fee = fee || calculatedFee; // if fee is not provided, pass calculated fee
// add change address
const changeAmount = senderBalance - (totalAmount + fee)
- if (changeAmount < 0)
- return reject(`Insufficient balance. Required: ${getConvertedAmount(totalAmount + fee, true)}, Available: ${getConvertedAmount(btcOperator.util.Sat_to_BTC(senderBalance), true)}`)
+ // if (changeAmount < 0)
+ // return resolve({
+ // txHex: null,
+ // fee: calculatedFee
+ // })
tx.addOutputAddress(address, BigInt(btcOperator.util.BTC_to_Sat(changeAmount)));
if (isTaprootScriptPath) {
- // TODO: check this
- tx.witness = witness;
- tx.sign(signerSecretKey, undefined, new Uint8Array(32));
+ if (signerSecretKey) {
+ const hash = tx.preimageWitnessV1(0, [script], 0, consumedUtxoInputs, "", userScript, userControlBlock.version)
+ const sig = secp.schnorr.signSync(hash, signerSecretKey, new Uint8Array(32))
+ }
+ tx.inputs.forEach(input => input.finalScriptWitness = scriptWitness)
} else {
const privKey = coinjs.wif2privkey(senderPrivateKey).privkey;
- const privKey_arrayForm = hex.decode(privKey);
- tx.sign(privKey_arrayForm, undefined, new Uint8Array(32));
+ tx.sign(hex.decode(privKey), undefined, new Uint8Array(32));
+ tx.finalize()
}
- tx.finalize()
resolve({ txHex: tx.hex, fee })
} catch (e) {
reject(e)
@@ -2166,11 +2203,13 @@
rec_args.n = 0;
rec_args.input_size = 0;
rec_args.input_amount = 0;
+ rec_args.consumedUtxoInputs = [];
}
if (required_amount <= 0)
return resolve({
input_size: rec_args.input_size,
input_amount: rec_args.input_amount,
+ consumedUtxoInputs: rec_args.consumedUtxoInputs,
change_amount: required_amount * -1 //required_amount will be -ve of change_amount
});
else if (rec_args.n >= senders.length)
@@ -2185,15 +2224,25 @@
if (!confirmations) //ignore unconfirmed utxo
continue;
// changes for taproot
- const input = { txid: tx_hash_big_endian, index: tx_output_n, script: tr.script, amount: BigInt(value) }
- tx.addInput({ ...input, ...tr, witnessUtxo: { script: input.script, amount: input.amount } });
-
- //TODO: verify this
- // tx.addInput({ ...input, ...taprootCommitment, witnessUtxo: { script: taprootCommitment.script, amount: input.amount }});
-
+ const inputScript = tr ? tr.script : hex.decode(coinjs.addressDecode(addr).outstring);
+ let input = {
+ txid: tx_hash_big_endian,
+ index: tx_output_n,
+ script: inputScript,
+ amount: BigInt(value),
+ witnessUtxo: {
+ script: inputScript,
+ amount: BigInt(value)
+ }
+ }
+ if (tr)
+ input = { ...input, ...tr }
+ console.log(input)
+ tx.addInput(input);
//update track values
rec_args.input_size += size_per_input; // Adjust input size calculation
rec_args.input_amount += value;
+ rec_args.consumedUtxoInputs.push(value);
required_amount -= value;
if (fee_rate) //automatic fee calculation (dynamic)
required_amount += size_per_input * fee_rate;
@@ -2283,6 +2332,62 @@
secretKey,
};
};
+ // generate new private key
+ internalKeyPair = keyPairFromSecret(
+ '1229101a0fcf2104e8808dab35661134aa5903867d44deb73ce1c7e4eb925be8'
+ );
+
+ preimage = hashmini.sha256(
+ hex.decode('107661134f21fc7c02223d50ab9eb3600bc3ffc3712423a1e47bb1f9a9dbf55f')
+ );
+
+ aliceKeyPair = keyPairFromSecret(
+ '2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90'
+ );
+
+ bobKeyPair = keyPairFromSecret(
+ '81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9'
+ );
+
+ scriptAlice = new Uint8Array([
+ 0x02,
+ 144,
+ 0x00,
+ btc.OP.CHECKSEQUENCEVERIFY,
+ btc.OP.DROP,
+ 0x20,
+ ...aliceKeyPair.schnorrPublicKey,
+ 0xac,
+ ]);
+
+ scriptBob = new Uint8Array([
+ btc.OP.SHA256,
+ 0x20,
+ ...preimage,
+ btc.OP.EQUALVERIFY,
+ 0x20,
+ ...bobKeyPair.schnorrPublicKey,
+ 0xac,
+ ]);
+
+ taprootTree = btc.taprootListToTree([
+ {
+ script: scriptAlice,
+ leafVersion: 0xc0,
+ },
+ {
+ script: scriptBob,
+ leafVersion: 0xc0,
+ },
+ ]);
+
+
+ taprootCommitment = btc.p2tr(
+ internalKeyPair.schnorrPublicKey,
+ taprootTree,
+ undefined,
+ true
+ );