I’ve created an HTLC with the next Bitcoin script utilizing bitcoinlib-js:
const redeemScript = bitcoin.script.compile([
bitcoin.opcodes.OP_IF,
bitcoin.opcodes.OP_SHA256,
secretHashBuffer,
bitcoin.opcodes.OP_EQUALVERIFY,
bitcoin.opcodes.OP_DUP,
bitcoin.opcodes.OP_HASH160,
recipientHash, // Hashed recipient public key
bitcoin.opcodes.OP_ELSE,
bitcoin.script.number.encode(lockduration),
bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
bitcoin.opcodes.OP_DROP,
bitcoin.opcodes.OP_DUP,
bitcoin.opcodes.OP_HASH160,
senderHash, // Hashed sender public key
bitcoin.opcodes.OP_ENDIF,
bitcoin.opcodes.OP_EQUALVERIFY,
bitcoin.opcodes.OP_CHECKSIG,
]);
I’m able to unlock the funds with within the OP_IF department through the use of the proper secret, and spending from the recipient public key. Nonetheless, I’m encountering a problem when trying to redeem funds by way of the OP_ELSE department of the script utilizing the timelock lockduration
and broadcasting utilizing the sender public key.
The particular error I get is:
error code: -26
error message:
non-mandatory-script-verify-flag (OP_IF/NOTIF argument have to be minimal)
I imagine the error stems from how I’m encoding my values. I’m constructing a PSBT which I then signal with xverse and convert right into a uncooked transaction utilizing bitcoin-cli finalizepsbt cHNidP8BAFMCAAAAAa/...
Firstly I create the PSBT
import * as bitcoin from 'bitcoinjs-lib';
import crypto from "crypto";
export perform createRefundPSBT(
secretHash: string,
lockduration: quantity,
scriptPubKeyHex: string,
htlcTxId: string,
htlcOutputIndex: quantity,
refundAmount: quantity,
recipientPubKey : string,
senderPubKey : string,
recipientAddress : string,
networkType : string
) {
const community =
networkType === "testnet"
? bitcoin.networks.testnet
: bitcoin.networks.bitcoin;
const secretHashBuffer = Buffer.from(secretHash, "hex");
// Recreate the HTLC script utilizing the offered secret
const recipientHash = bitcoin.crypto.hash160(
Buffer.from(recipientPubKey, "hex")
);
const senderHash = bitcoin.crypto.hash160(Buffer.from(senderPubKey, "hex"));
const redeemScript = bitcoin.script.compile([
bitcoin.opcodes.OP_IF,
bitcoin.opcodes.OP_SHA256,
secretHashBuffer,
bitcoin.opcodes.OP_EQUALVERIFY,
bitcoin.opcodes.OP_DUP,
bitcoin.opcodes.OP_HASH160,
recipientHash, // Hashed recipient public key
bitcoin.opcodes.OP_ELSE,
bitcoin.script.number.encode(lockduration),
bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
bitcoin.opcodes.OP_DROP,
bitcoin.opcodes.OP_DUP,
bitcoin.opcodes.OP_HASH160,
senderHash, // Hashed sender public key
bitcoin.opcodes.OP_ENDIF,
bitcoin.opcodes.OP_EQUALVERIFY,
bitcoin.opcodes.OP_CHECKSIG,
]);
const scriptPubKey = Buffer.from(scriptPubKeyHex, "hex");
console.log("Creating PSBT");
// Create a PSBT
const psbt = new bitcoin.Psbt({ community: community })
.addInput({
hash: htlcTxId,
index: htlcOutputIndex,
sequence: 0xfffffffe, // Vital for OP_CHECKLOCKTIMEVERIFY
witnessUtxo: {
script: scriptPubKey,
worth: refundAmount,
},
witnessScript: redeemScript,
})
.addOutput({
tackle: recipientAddress,
worth: refundAmount - 3000, // Subtract a nominal payment
})
.setVersion(2)
.setLocktime(lockduration);
console.log("PSBT to be signed:", psbt.toBase64());
return psbt.toBase64();
}
Then I signal it with xverse pockets utilizing a entrance finish and try and finalize the PSBT.
// This script is for refund
import * as bitcoin from 'bitcoinjs-lib';
import varuint from 'varuint-bitcoin';
perform witnessStackToScriptWitness(witness: any) {
let buffer = Buffer.allocUnsafe(0);
perform writeSlice(slice: string) {
buffer = Buffer.concat([buffer, Buffer.from(slice)]);
}
perform writeVarInt(i: quantity) {
const varintLen = varuint.encodingLength(i);
const currentLen = buffer.size;
buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
varuint.encode(i, buffer, currentLen);
}
perform writeVarSlice(slice: string) {
writeVarInt(slice.size);
writeSlice(slice);
}
perform writeVector(vector: any) {
writeVarInt(vector.size);
vector.forEach(writeVarSlice);
}
writeVector(witness);
return buffer;
}
perform getFinalScriptsSpecial(inputIndex: quantity, enter: any, script: any, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean) {
if (!enter.partialSig || enter.partialSig.size === 0) {
throw new Error(`Can't finalize enter #${inputIndex}: Lacking partial signatures`);
}
if (!enter.witnessUtxo) {
throw new Error(`Can't finalize enter #${inputIndex}: Lacking witness UTXO`);
}
if (!script) {
throw new Error(`Can't finalize enter #${inputIndex}: Lacking script`);
}
const signature = enter.partialSig[0].signature;
const pubkey = enter.partialSig[0].pubkey;
const finalScriptWitness = witnessStackToScriptWitness([
signature,
pubkey,
Buffer.from([0]), // Byte array for OP_FALSE or 0, is that this right?
//Buffer.alloc(0), // Or is that this right?
script
]);
return {
finalScriptWitness: finalScriptWitness
};
}
export perform prepareFinalRefundPSBT(
witnessHexWithdraw: string,
psbtBase64: string,
): string | null {
let psbt: any;
attempt {
psbt = bitcoin.Psbt.fromBase64(psbtBase64);
psbt.isSegwit = true;
psbt.isP2SH = false;
psbt.isP2WSH = true;
psbt.finalizeInput(0, getFinalScriptsSpecial);
//including witness script hex again in from bitcoin-cli decodepsbt <psbtBase64> as a result of psbt.finalizeInput removes it for some purpose
psbt.knowledge.inputs[0].witnessScript = Buffer.from(witnessHexWithdraw, 'hex');
} catch (e) {
console.error(`Error finalizing PSBT: ${e}`);
course of.exit(1);
}
return psbt.toBase64();
}
This offers me a finalized PSBT which I convert right into a uncooked transaction. Right here is the uncooked transaction decoded:
{
"txid": "d773911f5e0c2bd4590794676e7c95bd2e33d8156c22e64c80e8c5bf4cfcbece",
"hash": "5f1025270b4f2b298d2a90647c2640cc222815d0df3e2d38b72b9e37b04c14ff",
"model": 2,
"measurement": 285,
"vsize": 134,
"weight": 534,
"locktime": 1,
"vin": [
{
"txid": "fa69f9cee0b3fffb419084ccdaa19dbc319ca23965d3e6a9444210449b92a194",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"3045022100cf0ca0b7cbdda5e0c6733d508031c160cb93c7f4e9fcc6bafed4e50e7c23dd6e02200bc9a300ed5d6ec1202de946a59b4fdbb07136d5ffc43f8f5fe1e8fae6d291cd01",
"0269bf7a1301185625758c324221cf6614e4a4104f90f83f633a8790f62919aa0a",
"00",
"63a820ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb8876a914a5102a75c05993aa082ca365b5ba7f49bed517586751b17576a914a5102a75c05993aa082ca365b5ba7f49bed517586888ac"
],
"sequence": 4294967294
}
],
"vout": [
{
"value": 0.00007000,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 836676150f0f5892416d8bd9bc5e923b494677f3 OP_EQUAL",
"desc": "addr(2N5E1FYfiQgM5U8aTvU1btZYpiTVPE9ReaK)#00u0x08a",
"hex": "a914836676150f0f5892416d8bd9bc5e923b494677f387",
"address": "2N5E1FYfiQgM5U8aTvU1btZYpiTVPE9ReaK",
"type": "scripthash"
}
}
]
}
Then I attempt to broadcast the transaction
bitcoin-cli sendrawtransaction 0200000000010194a1929b44104244a9e6d36539a29c31bc9da1dacc849041fbffb3e0cef969fa0000000000feffffff01581b00000000000017a914836676150f0f5892416d8bd9bc5e923b494677f38704483045022100cf0ca0b7cbdda5e0c6733d508031c160cb93c7f4e9fcc6bafed4e50e7c23dd6e02200bc9a300ed5d6ec1202de946a59b4fdbb07136d5ffc43f8f5fe1e8fae6d291cd01210269bf7a1301185625758c324221cf6614e4a4104f90f83f633a8790f62919aa0a01005963a820ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb8876a914a5102a75c05993aa082ca365b5ba7f49bed517586751b17576a914a5102a75c05993aa082ca365b5ba7f49bed517586888ac01000000
And I’m met with the next error:
error code: -26
error message:
non-mandatory-script-verify-flag (OP_IF/NOTIF argument have to be minimal)
Might or not it’s this a part of the txinwitness
that isn’t minimal?
"00",
Ought to it as a substitute be
"0",
Or maybe even
"",
Or is there another purpose my OP_IF/NOTIF
argument shouldn’t be minimal?