I’ve created a funded P2WSH script, which is an HTLC. I now wish to create a PSBT which can be signed by one other pockets to withdraw the funds.
The issue is that the PSBT signing is throwing the next bitcoinlib-js error: Enter script would not have pubKey
. I’m not certain why, or the way to add this pubkey to the PSBT appropriately.
The next is a particulars breakdown of the steps I’m taking to create, fund and (failing to) redeem the HTLC.
Handle: tb1pnz86gezf5dzmja9q86z5agnmgjj29f00nxjj9jx0fsss6kkyh03sjkqhpd
Public Key: 59fff87c1bb3f75d34ea9d1588b72d0df43540695671c7a5ad3ec6a71d44bd79
Script to create the HTLC:
import bitcoin from 'bitcoinjs-lib';
import crypto from 'crypto';
operate createHTLC(secret, lockduration, recipientPubKey, senderPubKey, networkType) {
const community = networkType === 'testnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin;
const secretHash = crypto.createHash('sha256').replace(Buffer.from(secret, 'utf-8')).digest();
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,
secretHash,
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,
]);
// Calculate the P2WSH handle and scriptPubKey
const redeemScriptHash = bitcoin.crypto.sha256(redeemScript);
const scriptPubKey = bitcoin.script.compile([
bitcoin.opcodes.OP_0, // Witness version 0
redeemScriptHash
]);
const p2wshAddress = bitcoin.funds.p2wsh({
redeem: { output: redeemScript, community },
community
}).handle;
console.log('nCreated an HTLC Script!');
console.log('-------------------------------------------------');
console.log('P2WSH Bitcoin Deposit Handle for HTLC:', p2wshAddress);
console.log('Witness Script Hex:', redeemScript.toString('hex'));
console.log('Redeem Block Quantity:', lockduration);
console.log('Secret (for spending):', secret);
console.log('SHA256(Secret) (for HTLC creation):', secretHash.toString('hex'));
console.log('ScriptPubKey Hex:', scriptPubKey.toString('hex'));
console.log('-------------------------------------------------');
// To fund the HTLC, ship BTC to the p2wsh.handle
// Redeeming the HTLC would contain making a transaction that spends from this handle
// utilizing the supplied witnessScript, which might be included within the transaction's witness area
}
// Instance utilization
createHTLC(
'mysecret',
1, // locktime in blocks
"59fff87c1bb3f75d34ea9d1588b72d0df43540695671c7a5ad3ec6a71d44bd79",
"59fff87c1bb3f75d34ea9d1588b72d0df43540695671c7a5ad3ec6a71d44bd79",
'testnet'
);
This efficiently creates the P2WSH transaction and provides the next output to the display screen
Created an HTLC Script!
-------------------------------------------------
P2WSH Bitcoin Deposit Handle for HTLC: tb1q5cyw034kcvsp6fsfx9skeq93vhvuqghmjc9y8xdhjll8m9thrn9q5mv0nr
Witness Script Hex: 63a820652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd08876a914e399056c4ca63571aca44fc2d11b3fdac69a37e06751b17576a914e399056c4ca63571aca44fc2d11b3fdac69a37e06888ac
Redeem Block Quantity: 1
Secret (for spending): mysecret
SHA256(Secret) (for HTLC creation): 652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0
ScriptPubKey Hex: 0020a608e7c6b6c3201d260931616c80b165d9c022fb960a4399b797fe7d95771cca
-------------------------------------------------
Then I fund the script by way of xverse by sending bitcoin on to
tb1q5cyw034kcvsp6fsfx9skeq93vhvuqghmjc9y8xdhjll8m9thrn9q5mv0nr
https://mempool.area/testnet/tx/be9cc0e300d1c01b7fdbeeff1c99acc0fb8a7d9e8d025547b7bfc9635dedcbb3
The ScriptPubKey on mempool.area appears to match mine
0020a608e7c6b6c3201d260931616c80b165d9c022fb960a4399b797fe7d95771cca
Then it is time to create the PSBT. I am unable to instantly see any points with how I’m creating this PSBT. Do I would like so as to add the general public key someplace?:
import * as bitcoin from 'bitcoinjs-lib';
import crypto from 'crypto';
import * as tinysecp256k1 from 'tiny-secp256k1';
// Initialize ECC library
import * as bitcoinjs from "bitcoinjs-lib";
import * as ecc from "tiny-secp256k1";
bitcoin.initEccLib(ecc);
operate createSpendPSBT(secret, lockduration, scriptPubKeyHex, htlcTxId, htlcOutputIndex, refundAmount, recipientPubKey, senderPubKey, recipientAddress, networkType) {
const community = networkType === 'testnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin;
const secretHash = crypto.createHash('sha256').replace(Buffer.from(secret, 'utf-8')).digest();
// Recreate the HTLC script utilizing the supplied 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,
secretHash,
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, // Essential for OP_CHECKLOCKTIMEVERIFY
witnessUtxo: {
script: scriptPubKey,
worth: refundAmount,
},
witnessScript: redeemScript,
})
.addOutput({
handle: recipientAddress,
worth: refundAmount - 1000, // Subtract a nominal payment
})
.setVersion(2)
.setLocktime(lockduration);
console.log("PSBT to be signed:", psbt.toBase64());
}
// Instance utilization (Fill within the precise values)
createSpendPSBT(
"mysecret",
0,
"0020a608e7c6b6c3201d260931616c80b165d9c022fb960a4399b797fe7d95771cca",
"be9cc0e300d1c01b7fdbeeff1c99acc0fb8a7d9e8d025547b7bfc9635dedcbb3",
0,
1000,
"59fff87c1bb3f75d34ea9d1588b72d0df43540695671c7a5ad3ec6a71d44bd79",
"59fff87c1bb3f75d34ea9d1588b72d0df43540695671c7a5ad3ec6a71d44bd79",
"tb1pnz86gezf5dzmja9q86z5agnmgjj29f00nxjj9jx0fsss6kkyh03sjkqhpd",
"testnet",
);
//createSpendPSBT(secret, lockduration, scriptPubKey, htlcTxId, htlcOutputIndex, refundAmount, recipientPubKey, senderPubKey, recipientAddress, networkType)
This provides me the next PSBT:
cHNidP8BAF4CAAAAAbPL7V1jyb+3R1UCjZ59ivvArJkc/+7bfxvA0QDjwJy+AAAAAAD+////AQAAAAAAAAAAIlEgmI+kZEmjRbl0oD6FTqJ7RKSipe+ZpSLIz0whDVrEu+MAAAAAAAEBK+gDAAAAAAAAIgAgpgjnxrbDIB0mCTFhbICxZdnAIvuWCkOZt5f+fZV3HMoBBVljqCBlLH3Gh9mMmIkwTtLkCMdLYR6GpAyqUcS0Px3VkTxc0Ih2qRTjmQVsTKY1caykT8LRGz/axpo34GcAsXV2qRTjmQVsTKY1caykT8LRGz/axpo34GiIrAAA
Once I decode it, so I can examine the contents, I get the next
{
"tx": {
"txid": "a1eaefe490f5d3be11fbd6a5afeffcff20a9e92cfde3363484168c9f5769c57a",
"hash": "a1eaefe490f5d3be11fbd6a5afeffcff20a9e92cfde3363484168c9f5769c57a",
"model": 2,
"measurement": 94,
"vsize": 94,
"weight": 376,
"locktime": 0,
"vin": [
{
"txid": "be9cc0e300d1c01b7fdbeeff1c99acc0fb8a7d9e8d025547b7bfc9635dedcbb3",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967294
}
],
"vout": [
{
"value": 0.00000000,
"n": 0,
"scriptPubKey": {
"asm": "1 988fa46449a345b974a03e854ea27b44a4a2a5ef99a522c8cf4c210d5ac4bbe3",
"desc": "rawtr(988fa46449a345b974a03e854ea27b44a4a2a5ef99a522c8cf4c210d5ac4bbe3)#4xpnet5r",
"hex": "5120988fa46449a345b974a03e854ea27b44a4a2a5ef99a522c8cf4c210d5ac4bbe3",
"address": "tb1pnz86gezf5dzmja9q86z5agnmgjj29f00nxjj9jx0fsss6kkyh03sjkqhpd",
"type": "witness_v1_taproot"
}
}
]
},
"global_xpubs": [
],
"psbt_version": 0,
"proprietary": [
],
"unknown": {
},
"inputs": [
{
"witness_utxo": {
"amount": 0.00001000,
"scriptPubKey": {
"asm": "0 a608e7c6b6c3201d260931616c80b165d9c022fb960a4399b797fe7d95771cca",
"desc": "addr(tb1q5cyw034kcvsp6fsfx9skeq93vhvuqghmjc9y8xdhjll8m9thrn9q5mv0nr)#wjcfmgw8",
"hex": "0020a608e7c6b6c3201d260931616c80b165d9c022fb960a4399b797fe7d95771cca",
"address": "tb1q5cyw034kcvsp6fsfx9skeq93vhvuqghmjc9y8xdhjll8m9thrn9q5mv0nr",
"type": "witness_v0_scripthash"
}
},
"witness_script": {
"asm": "OP_IF OP_SHA256 652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0 OP_EQUALVERIFY OP_DUP OP_HASH160 e399056c4ca63571aca44fc2d11b3fdac69a37e0 OP_ELSE 0 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 e399056c4ca63571aca44fc2d11b3fdac69a37e0 OP_ENDIF OP_EQUALVERIFY OP_CHECKSIG",
"hex": "63a820652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd08876a914e399056c4ca63571aca44fc2d11b3fdac69a37e06700b17576a914e399056c4ca63571aca44fc2d11b3fdac69a37e06888ac",
"type": "nonstandard"
}
}
],
"outputs": [
{
}
],
"payment": 0.00001000
}
Once I take a look at the inputs a part of the PSBT, I can see that there’s some enter in there with my HTLC funds, and a scriptPubKey. Is that this not the general public key that the error is complaining would not exist?
From there I attempted to signal it anyway utilizing sats-connect:
const signPsbtOptions = {
payload: {
community: {
kind: 'Testnet' // Change to 'Regtest' or 'Mainnet' as essential
},
psbtBase64: `cHNidP8BAF4CAAAAAbPL7V1jyb+3R1UCjZ59ivvArJkc/+7bfxvA0QDjwJy+AAAAAAD+////AQAAAAAAAAAAIlEgmI+kZEmjRbl0oD6FTqJ7RKSipe+ZpSLIz0whDVrEu+MAAAAAAAEBK+gDAAAAAAAAIgAgpgjnxrbDIB0mCTFhbICxZdnAIvuWCkOZt5f+fZV3HMoBBVljqCBlLH3Gh9mMmIkwTtLkCMdLYR6GpAyqUcS0Px3VkTxc0Ih2qRTjmQVsTKY1caykT8LRGz/axpo34GcAsXV2qRTjmQVsTKY1caykT8LRGz/axpo34GiIrAAA`,
broadcast: false, // Set to true if you wish to broadcast after signing
inputsToSign: [
{
address: "tb1pnz86gezf5dzmja9q86z5agnmgjj29f00nxjj9jx0fsss6kkyh03sjkqhpd", //should this be the address of signer or the address of the input?
signingIndexes: [0] // Assuming you wish to signal the primary enter
}
],
},
onFinish: (response) => {
console.log('Signed PSBT:', response.psbtBase64);
// Right here, you possibly can add extra code to deal with the signed PSBT
},
onCancel: () => alert('Signing canceled'),
};
attempt {
await signTransaction(signPsbtOptions);
} catch (error) {
console.error('Error signing PSBT:', error);
alert('Did not signal PSBT.');
}
and I used to be met with the next error
Enter script would not have pubKey
My Query
Why would not the enter script have a public key, and is not this the aim of scriptPubKey? How do I present the general public key appropriately to signal this psbt?