Closed codemaster101 closed 1 year ago
Does this code give you any errors or are you looking for the next steps in how to sign?
In case it's useful to anybody out there, I think I managed to get something working which does what @codemaster101 was asking about.
The trick is to implement SignerAsync
. This type has 2 fields:
publicKey
: the public key bytes signing tx inputsfunction sign(hash, lowerR)
: callback being fed the hash
value to sign and a lowerR
boolean. Not sure what this boolean is used for tbh, in my experience it was always set to undefined
🤷 With the SignerAsync
interface implemented, you can then call pbst.signInputAsync(inputIndex, yourAsyncSigner)
, and your own sign
method will be called.
In the following snippet I'm constructing a transaction, then implementing sign
as an async prompt
to get the signature from stdin and combine it.
const bitcoin = require("bitcoinjs-lib");
const prompts = require("prompts");
const ecc = require("tiny-secp256k1");
const ecpair = require("ecpair");
const fetch = require("node-fetch");
async function run() {
const hash = "6a94d6b2d27a3df8036ecf713af6418ef9e8bf5daa7ee170fa74d67a07d4ffae";
const index = 0;
const amount = 501;
const publicKey = "040fc05389d5f98143cb037f7707e008f8154a5951b106c141e80af36184ca88c8e951bdfe7ee7f0e50afd42ab2f120ed1fbaaa5f8e5a528bdfabbd63276349da6";
const destination = "2Mv28PpCuEynr6rU9rqNJ5VW3znGZFfAU7Y";
// This is needed to derive the compressed format of the public key
const ECPair = ecpair.ECPairFactory(ecc);
const pair = ECPair.fromPublicKey(Buffer.from(publicKey, "hex"));
// Get the transaction info from blockcypher API
let resp = await fetch(
`https://api.blockcypher.com/v1/btc/test3/txs/${hash}?limit=50&includeHex=true`
);
let respJson = await resp.json();
const pbst = new bitcoin.Psbt({ network: bitcoin.networks.testnet });
pbst.addInput({
hash: hash,
index: index,
nonWitnessUtxo: Buffer.from(respJson.hex, "hex"),
redeemScript: bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({
pubkey: pair.publicKey,
network: bitcoin.networks.testnet,
}),
}).redeem.output,
});
pbst.addOutput({
script: bitcoin.address.toOutputScript(
destination,
bitcoin.networks.testnet
),
value: amount,
});
// This is really the crux of it!
const myAsyncSigner = {
publicKey: pair.publicKey,
sign: (hash, lowerR) => {
return new Promise((resolve, rejects) => {
const signPrompt = [
{
type: "confirm",
name: "sigPrompt",
message: `Please go sign the following hash: ${hash.toString(
"hex"
)}\nReady to continue?`,
},
{
type: "text",
name: "sig",
message: "sig (R + S, hex-encoded):",
},
];
prompts(signPrompt)
.then(function (vals) {
let signatureBuf = Buffer.from(vals.sig, "hex");
resolve(signatureBuf);
})
.catch(function (reason) {
rejects(reason);
});
});
},
});
await pbst.signInputAsync(0, myAsyncSigner);
pbst.finalizeAllInputs();
return pbst.extractTransaction().toHex();
}
run().then((res) => console.log(res));
Executing the script above I get:
$ node demo.js
✔ Please go sign the following hash: c5fd0d738920d4715e25d31432dfb72531d6a6dbc8c3d6b1ef2132249b4c5ead
Ready to continue? … yes
✔ sig (R + S, hex-encoded): … 683c75e1aa6c55a2f108693e7f04d183eeafe6be88b61dc3901b9e02479cee3943739ad0ab3e3d0f7ce1ad7fc686d506f07c035b7078c1899c09da2922437a34
02000000000101aeffd4077ad674fa70e17eaa5dbfe8f98e41f63a71cf6e03f83d7ad2b2d6946a00000000171600140be1390164efb9ff52f51e50269aabc646c8d0f7ffffffff01f50100000000000017a9141e6e4cca6c1e388e775412acc2dff967b510c40f87024730440220683c75e1aa6c55a2f108693e7f04d183eeafe6be88b61dc3901b9e02479cee39022043739ad0ab3e3d0f7ce1ad7fc686d506f07c035b7078c1899c09da2922437a340121020fc05389d5f98143cb037f7707e008f8154a5951b106c141e80af36184ca88c800000000
The signature that I fed into my script (683c...7a34
) was obtained on a remote signer which does bare ECDSA signing, somewhere else.
This is a great explanation of the SignerAsync interface!
I think this answers OPs question, so closing.
Hi, I was going through some code to create transactions for bitcoin and use a serialized hash of the unsigned transaction to create a ECDSA digital signature and use that to sign the actual transaction but I think I am a little confused here as I have not been able to find any examples of doing so.
I have created a new address say: bc1qjkg99q654997uz5lxanjugzz2hfwum7ph9fcvw, with the compressed public key as 039b219ff489f9f5d3c674602e2280cc803f068f07db7295c7d2a2f9d51844cc45 (in hexadecimal representation).
Script used is
P2WPKH
. Say I want to transfer funds to 3HmEiQfaghizrNNn3tt5ydzvejLeYMzbD1, with some amount of BTC. Now I want to create an unsigned transaction so that I can generate a digital signature and then sign the transaction.This is how I proceeded: First I created a payment object like so:
Now I maybe going wrong here...
I then proceeded to create an input data object like so (maybe I am missing scriptPubKey)?!
I then proceed to do something like
Can someone guide me on how to proceed or where to change the logic?
Also if I understand correctly all unspent inputs (if I have multiple) would need to be signed separately, so do digital signatures need to be created separately and then sign the inputs one by one? How to create a hash of the an unsigned transaction from PSBT?
Also please tell me if this does not make sense at all. I think I might be a little confused on how I should use digital signatures here.