Thursday, November 21, 2024
HomeBitcoinhash time locked contract - Easy methods to signal P2WSH PSBT with...

hash time locked contract – Easy methods to signal P2WSH PSBT with sats-connect?


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?

RELATED ARTICLES

Most Popular

Recent Comments