What I would like is to have the ability to spend the funds from an handle, both with its non-public key or with the non-public key of one other handle that I designate within the coverage.
As I perceive it, it really works like this:
When utilizing the descriptor, it offers an handle the place the miniscript is saved, which tells me how the UTXOs related to that handle may be spent.
Then, if I need to spend the UTXOs related to that handle, I’ve to create a transaction with PSBT the place I have to create the identical descriptor that I made when producing the handle that I defined earlier, and signal it with a non-public key, both that of the handle itself or the opposite one designated within the coverage, thus permitting me to spend the funds from the opposite handle.
That is the reasoning that I’ve, however I believe it’s incorrect. Subsequently, I’ve the next questions:
How do descriptors work? I do know roughly what they’re, however I do not know the way they work programmatically, that’s, tips on how to use them to unlock an output that has a descriptor and a miniscript in it, from a brand new transaction that I create.
I’m attaching a small code snippet that we’re growing. Particularly in CODE 1, I do not perceive how the signersPubKey parameter works:
const wshDescriptor = new Descriptor({
expression: wshExpression,
community,
signersPubKeys: [EMERGENCY_RECOVERY ? emergencyPair.publicKey : unvaultKey]
});
The primary code is tips on how to create an handle with the spending situations that you would be able to see within the coverage POLICY =`or(pk(@emergencyKey),and(pk(@unvaultKey),after(20)))`;
The second code exhibits how we make a transaction that unlocks the funds from the earlier handle.
The CODE 2, we are able to see signersPubKey once more. We’re reaching the conclusion that within the first CODE, when producing the handle, the descriptor that we create with signersPubKey, SignersPubKey does not actually do something, and within the second CODE, when signing the transaction, the descriptor actually makes use of the signersPubKey parameter, however we aren’t certain.
That is what the GitHub web page of the descriptor library that we’re utilizing says about SignersPubKey, however we do not totally perceive it:
“The final half to elucidate from the code block above is that when a descriptor has a number of spending paths, the consumer must set which one to make use of. That is performed by passing the general public keys that shall be used to signal the transaction utilizing the variable signersPubKeys. The corresponding unlocking script (the script witness, on this case) shall be computed later from this info when finalizing the transaction. Word that to be able to check totally different configurations, you'll be able to set EMERGENCY_RECOVERY variable to true or false backwards and forwards.”
Subsequently, how does the SignersPubKey discipline work when creating an handle by the descriptor, and the way does it additionally work when creating the transaction?
CODE 1, to create an handle with miniscript
import { Psbt, networks } from 'bitcoinjs-lib';
import * as ecpair from "ecpair";
import * as secp from "tiny-secp256k1";
const ECPair = ecpair.ECPairFactory(secp);
import * as fs from 'fs';
import * as secp256k1 from '@bitcoinerlab/secp256k1';
import * as descriptors from '@bitcoinerlab/descriptors';
import { compilePolicy } from '@bitcoinerlab/miniscript';
import { generateMnemonic, mnemonicToSeedSync } from 'bip39';
const { Descriptor, BIP32, ECPair2 } = descriptors.DescriptorsFactory(secp256k1);
let unvaultMnemonic;
const EMERGENCY_RECOVERY=true;
const community = networks.testnet;
const POLICY =`or(pk(@emergencyKey),and(pk(@unvaultKey),after(20)))`;
const WSH_ORIGIN_PATH = `/69420'/1'/0'`;
const WSH_KEY_PATH = `/0/0`;
var emergencyPair = ECPair.makeRandom();
console.log(`emergencyPair WIF: ${emergencyPair.toWIF()}`);
unvaultMnemonic = generateMnemonic();
console.log(`unvaultMnemonic: ${unvaultMnemonic}`);
const unvaultMasterNode = BIP32.fromSeed(mnemonicToSeedSync(unvaultMnemonic),community);
console.log(`unvaultMasterNode WIF: ${unvaultMasterNode.toWIF()}`);
const { miniscript, issane } = compilePolicy(POLICY);
console.log(`miniscript: ${miniscript}`);
const unvaultKey = unvaultMasterNode.derivePath(`m${WSH_ORIGIN_PATH}${WSH_KEY_PATH}`).publicKey;
const unvaultKeyBase64 = unvaultKey.toString('base64');
console.log(`unvaultKey BASE 64: ${unvaultKeyBase64}`);
const wshExpression = `wsh(${miniscript
.substitute(
'@unvaultKey',
descriptors.keyExpressionBIP32({
masterNode: unvaultMasterNode,
originPath: WSH_ORIGIN_PATH,
keyPath: WSH_KEY_PATH
})
)
.substitute('@emergencyKey', emergencyPair.publicKey.toString('hex'))})`;
console.log(`wshExpression: ${wshExpression}`);
const wshDescriptor = new Descriptor({
expression: wshExpression,
community,
signersPubKeys: [EMERGENCY_RECOVERY ? emergencyPair.publicKey : unvaultKey]
});
console.log("wshDescriptor: "+wshDescriptor);
const wshAddress = wshDescriptor.getAddress();
console.log(`handle: ${wshAddress}`)
const information = [`adress: ${wshAddress}`,`emergencyPair WIF: ${emergencyPair.toWIF()}`,`unvaultMnemonic: ${unvaultMnemonic}`,`unvaultKey: ${unvaultKeyBase64}`, `wshExpression: ${wshExpression}`];
attempt {
fs.writeFileSync("info_address.txt", information.be a part of('n'));
console.log(`n//////////// Se ha guardado en el fichero los siguientes datos ////////////nn${information.be a part of('n')} `);
} catch (err) {
console.error(err);
}
CODE 2, to create a transaction that spend the stability of the earlier handle
import { Psbt, networks } from 'bitcoinjs-lib';
import * as ecpair from "ecpair";
import * as secp from "tiny-secp256k1";
const ECPair = ecpair.ECPairFactory(secp);
import * as fs from 'fs';
import * as secp256k1 from '@bitcoinerlab/secp256k1';
import * as descriptors from '@bitcoinerlab/descriptors';
const { Descriptor, BIP32, ECPair2 } = descriptors.DescriptorsFactory(secp256k1);
const toAddress="n12kEsK3LnqvYFMTdMvzAXbb8JtF6qcDSP"
const community = networks.testnet;
const EMERGENCY_RECOVERY = true;
const EXPLORER = 'https://blockstream.data/testnet';
// Leer los datos del fichero
const information = fs.readFileSync("info_address.txt", { encoding: 'utf-8' });
// Analizar los datos y extraer los valores
const strains = information.break up('n');
const wshAddress = strains[0].break up(': ')[1];
const emergencyPairWIF = strains[1].break up(': ')[1];
const unvaultMnemonic = strains[2].break up(': ')[1];
const unvaultKeyBase64 = strains[3].break up(': ')[1];
const wshExpression = strains[4].break up(': ')[1];
// Convertir los valores a los tipos originales
const emergencyPair = ECPair.fromWIF(emergencyPairWIF);
const unvaultKey = Buffer.from(unvaultKeyBase64, 'base64');
console.log('wshAddress:', wshAddress);
console.log('emergencyPair:', emergencyPair);
console.log('unvaultMnemonic:', unvaultMnemonic);
console.log('unvaultKey BASE 64:', unvaultKey.toString('base64'));
console.log('wshExpression:', wshExpression);
const unvaultMasterNode = BIP32.fromSeed(mnemonicToSeedSync(unvaultMnemonic),community);
// // Busca el utxo a gastar
const utxo = await (
await fetch(`${EXPLORER}/api/handle/${wshAddress}/utxo`)
).json();
if (utxo?.[0]) {
const txHex = await (
await fetch(`${EXPLORER}/api/tx/${utxo?.[0].txid}/hex`)
).textual content();
const inputValue = utxo[0].worth;
const psbt = new Psbt({ community });
const wshDescriptor = new Descriptor({
expression: wshExpression,
community,
signersPubKeys: [EMERGENCY_RECOVERY ? emergencyPair.publicKey : unvaultKey]
});
wshDescriptor.updatePsbt({ psbt, txHex, vout: utxo[0].vout });
psbt.addOutput({
handle: EMERGENCY_RECOVERY
? toAddress
: 'tb1q4280xax2lt0u5a5s9hd4easuvzalm8v9ege9ge',
worth: inputValue - 1000
});
//Now signal the PSBT
if (EMERGENCY_RECOVERY)
descriptors.signers.signECPair({ psbt, ecpair: emergencyPair });
else descriptors.signers.signBIP32({ psbt, masterNode: unvaultMasterNode });
//Finalize the tx (compute & add the scriptWitness) & push to the blockchain
wshDescriptor.finalizePsbtInput({ index: 0, psbt });
const spendTx = psbt.extractTransaction();
const spendTxPushResult = await (
await fetch(`${EXPLORER}/api/tx`, {
technique: 'POST',
physique: spendTx.toHex()
})
).textual content();
console.log(`Pushing: ${spendTx.toHex()}`);
console.log(`Tx pushed with outcome: ${spendTxPushResult}`);
}