Skip to content

Commit 61a1534

Browse files
committed
Add all transaction output types to bitcoin-tx.
This commit enhances bitcoin-tx so all remaining standard TXO types can be created: - Pay to Pub Key - Multi-sig - bare multi-sig - multi-sig in Pay To Script Hash - multi-sig in Pay to Witness Script Hash - multi-sig in Pay to Witness Script Hash, wrapped in P2SH - Pay to Witness Pub Key Hash - Pay to Witness Pub Key Hash, wrapped in P2SH - Pay to Witness Script Hash - Pay to Witness Script Hash, wrapped in P2SH
1 parent 1814b08 commit 61a1534

File tree

1 file changed

+160
-37
lines changed

1 file changed

+160
-37
lines changed

src/bitcoin-tx.cpp

Lines changed: 160 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,16 @@ static int AppInitRawTx(int argc, char* argv[])
7878
strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N"));
7979
strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N"));
8080
strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX"));
81+
strUsage += HelpMessageOpt("outpubkey=VALUE:PUBKEY[:FLAGS]", _("Add pay-to-pubkey output to TX") + ". " +
82+
_("Optionally add the \"W\" flag to produce a pay-to-witness-pubkey-hash output") + ". " +
83+
_("Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash."));
8184
strUsage += HelpMessageOpt("outdata=[VALUE:]DATA", _("Add data-based output to TX"));
82-
strUsage += HelpMessageOpt("outscript=VALUE:SCRIPT(:\"SEGWIT\")(:\"P2SH\")", _("Add raw script output to TX") + ". " +
83-
_("Optionally add the \"SEGWIT\" flag to produce a segwit output") + ". " +
84-
_("Optionally add the \"P2SH\" flag to wrap the script in a P2SH output."));
85+
strUsage += HelpMessageOpt("outscript=VALUE:SCRIPT[:FLAGS]", _("Add raw script output to TX") + ". " +
86+
_("Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output") + ". " +
87+
_("Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash."));
88+
strUsage += HelpMessageOpt("outmultisig=VALUE:REQUIRED:PUBKEYS:PUBKEY1:PUBKEY2:....[:FLAGS]", _("Add Pay To n-of-m Multi-sig output to TX. n = REQUIRED, m = PUBKEYS") + ". " +
89+
_("Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output") + ". " +
90+
_("Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash."));
8591
strUsage += HelpMessageOpt("sign=SIGHASH-FLAGS", _("Add zero or more signatures to transaction") + ". " +
8692
_("This command requires JSON registers:") +
8793
_("prevtxs=JSON object") + ", " +
@@ -170,6 +176,14 @@ static void RegisterLoad(const std::string& strInput)
170176
RegisterSetJson(key, valStr);
171177
}
172178

179+
static CAmount ExtractAndValidateValue(const std::string& strValue)
180+
{
181+
CAmount value;
182+
if (!ParseMoney(strValue, value))
183+
throw std::runtime_error("invalid TX output value");
184+
return value;
185+
}
186+
173187
static void MutateTxVersion(CMutableTransaction& tx, const std::string& cmdVal)
174188
{
175189
int64_t newVersion = atoi64(cmdVal);
@@ -224,25 +238,18 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu
224238

225239
static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strInput)
226240
{
227-
// separate VALUE:ADDRESS in string
228-
size_t pos = strInput.find(':');
229-
if ((pos == std::string::npos) ||
230-
(pos == 0) ||
231-
(pos == (strInput.size() - 1)))
232-
throw std::runtime_error("TX output missing separator");
241+
// Separate into VALUE:ADDRESS
242+
std::vector<std::string> vStrInputParts;
243+
boost::split(vStrInputParts, strInput, boost::is_any_of(":"));
233244

234-
// extract and validate VALUE
235-
std::string strValue = strInput.substr(0, pos);
236-
CAmount value;
237-
if (!ParseMoney(strValue, value))
238-
throw std::runtime_error("invalid TX output value");
245+
// Extract and validate VALUE
246+
CAmount value = ExtractAndValidateValue(vStrInputParts[0]);
239247

240248
// extract and validate ADDRESS
241-
std::string strAddr = strInput.substr(pos + 1, std::string::npos);
249+
std::string strAddr = vStrInputParts[1];
242250
CBitcoinAddress addr(strAddr);
243251
if (!addr.IsValid())
244252
throw std::runtime_error("invalid TX output address");
245-
246253
// build standard output script via GetScriptForDestination()
247254
CScript scriptPubKey = GetScriptForDestination(addr.Get());
248255

@@ -251,6 +258,114 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn
251258
tx.vout.push_back(txout);
252259
}
253260

261+
static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& strInput)
262+
{
263+
// Separate into VALUE:PUBKEY[:FLAGS]
264+
std::vector<std::string> vStrInputParts;
265+
boost::split(vStrInputParts, strInput, boost::is_any_of(":"));
266+
267+
// Extract and validate VALUE
268+
CAmount value = ExtractAndValidateValue(vStrInputParts[0]);
269+
270+
// Extract and validate PUBKEY
271+
CPubKey pubkey(ParseHex(vStrInputParts[1]));
272+
if (!pubkey.IsFullyValid())
273+
throw std::runtime_error("invalid TX output pubkey");
274+
CScript scriptPubKey = GetScriptForRawPubKey(pubkey);
275+
CBitcoinAddress addr(scriptPubKey);
276+
277+
// Extract and validate FLAGS
278+
bool bSegWit = false;
279+
bool bScriptHash = false;
280+
if (vStrInputParts.size() == 3) {
281+
std::string flags = vStrInputParts[2];
282+
bSegWit = (flags.find("W") != std::string::npos);
283+
bScriptHash = (flags.find("S") != std::string::npos);
284+
}
285+
286+
if (bSegWit) {
287+
// Call GetScriptForWitness() to build a P2WSH scriptPubKey
288+
scriptPubKey = GetScriptForWitness(scriptPubKey);
289+
}
290+
if (bScriptHash) {
291+
// Get the address for the redeem script, then call
292+
// GetScriptForDestination() to construct a P2SH scriptPubKey.
293+
CBitcoinAddress redeemScriptAddr(scriptPubKey);
294+
scriptPubKey = GetScriptForDestination(redeemScriptAddr.Get());
295+
}
296+
297+
// construct TxOut, append to transaction output list
298+
CTxOut txout(value, scriptPubKey);
299+
tx.vout.push_back(txout);
300+
}
301+
302+
static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& strInput)
303+
{
304+
// Separate into VALUE:REQUIRED:NUMKEYS:PUBKEY1:PUBKEY2:....[:FLAGS]
305+
std::vector<std::string> vStrInputParts;
306+
boost::split(vStrInputParts, strInput, boost::is_any_of(":"));
307+
308+
// Check that there are enough parameters
309+
if (vStrInputParts.size()<3)
310+
throw std::runtime_error("Not enough multisig parameters");
311+
312+
// Extract and validate VALUE
313+
CAmount value = ExtractAndValidateValue(vStrInputParts[0]);
314+
315+
// Extract REQUIRED
316+
uint32_t required = stoul(vStrInputParts[1]);
317+
318+
// Extract NUMKEYS
319+
uint32_t numkeys = stoul(vStrInputParts[2]);
320+
321+
// Validate there are the correct number of pubkeys
322+
if (vStrInputParts.size() < numkeys + 3)
323+
throw std::runtime_error("incorrect number of multisig pubkeys");
324+
325+
if (required < 1 || required > 20 || numkeys < 1 || numkeys > 20 || numkeys < required)
326+
throw std::runtime_error("multisig parameter mismatch. Required " \
327+
+ std::to_string(required) + " of " + std::to_string(numkeys) + "signatures.");
328+
329+
// extract and validate PUBKEYs
330+
std::vector<CPubKey> pubkeys;
331+
for(int pos = 1; pos <= int(numkeys); pos++) {
332+
CPubKey pubkey(ParseHex(vStrInputParts[pos + 2]));
333+
if (!pubkey.IsFullyValid())
334+
throw std::runtime_error("invalid TX output pubkey");
335+
pubkeys.push_back(pubkey);
336+
}
337+
338+
// Extract FLAGS
339+
bool bSegWit = false;
340+
bool bScriptHash = false;
341+
if (vStrInputParts.size() == numkeys + 4) {
342+
std::string flags = vStrInputParts.back();
343+
bSegWit = (flags.find("W") != std::string::npos);
344+
bScriptHash = (flags.find("S") != std::string::npos);
345+
}
346+
else if (vStrInputParts.size() > numkeys + 4) {
347+
// Validate that there were no more parameters passed
348+
throw std::runtime_error("Too many parameters");
349+
}
350+
351+
CScript scriptPubKey = GetScriptForMultisig(required, pubkeys);
352+
353+
if (bSegWit) {
354+
// Call GetScriptForWitness() to build a P2WSH scriptPubKey
355+
scriptPubKey = GetScriptForWitness(scriptPubKey);
356+
}
357+
if (bScriptHash) {
358+
// Get the address for the redeem script, then call
359+
// GetScriptForDestination() to construct a P2SH scriptPubKey.
360+
CBitcoinAddress addr(scriptPubKey);
361+
scriptPubKey = GetScriptForDestination(addr.Get());
362+
}
363+
364+
// construct TxOut, append to transaction output list
365+
CTxOut txout(value, scriptPubKey);
366+
tx.vout.push_back(txout);
367+
}
368+
254369
static void MutateTxAddOutData(CMutableTransaction& tx, const std::string& strInput)
255370
{
256371
CAmount value = 0;
@@ -262,10 +377,8 @@ static void MutateTxAddOutData(CMutableTransaction& tx, const std::string& strIn
262377
throw std::runtime_error("TX output value not specified");
263378

264379
if (pos != std::string::npos) {
265-
// extract and validate VALUE
266-
std::string strValue = strInput.substr(0, pos);
267-
if (!ParseMoney(strValue, value))
268-
throw std::runtime_error("invalid TX output value");
380+
// Extract and validate VALUE
381+
value = ExtractAndValidateValue(strInput.substr(0, pos));
269382
}
270383

271384
// extract and validate DATA
@@ -282,26 +395,32 @@ static void MutateTxAddOutData(CMutableTransaction& tx, const std::string& strIn
282395

283396
static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& strInput)
284397
{
285-
// separate VALUE:SCRIPT(:SEGWIT)(:P2SH)
286-
std::vector<std::string> vStrInput;
287-
boost::split(vStrInput, strInput, boost::is_any_of(":"));
288-
if (vStrInput.size() < 2)
289-
throw srd::runtime_error("TX output missing separator");
290-
291-
// extract and validate VALUE
292-
std::string strValue = vStrInput[0];
293-
CAmount value;
294-
if (!ParseMoney(strValue, value))
295-
throw std::runtime_error("invalid TX output value");
398+
// separate VALUE:SCRIPT[:FLAGS]
399+
std::vector<std::string> vStrInputParts;
400+
boost::split(vStrInputParts, strInput, boost::is_any_of(":"));
401+
if (vStrInputParts.size() < 2)
402+
throw std::runtime_error("TX output missing separator");
403+
404+
// Extract and validate VALUE
405+
CAmount value = ExtractAndValidateValue(vStrInputParts[0]);
296406

297407
// extract and validate script
298-
std::string strScript = vStrInput[1];
299-
CScript scriptPubKey = ParseScript(strScript); // throws on err
408+
std::string strScript = vStrInputParts[1];
409+
CScript scriptPubKey = ParseScript(strScript);
410+
411+
// Extract FLAGS
412+
bool bSegWit = false;
413+
bool bScriptHash = false;
414+
if (vStrInputParts.size() == 3) {
415+
std::string flags = vStrInputParts.back();
416+
bSegWit = (flags.find("W") != std::string::npos);
417+
bScriptHash = (flags.find("S") != std::string::npos);
418+
}
300419

301-
if (std::find(vStrInput.begin(), vStrInput.end(), "SEGWIT") != vStrInput.end()) {
420+
if (bSegWit) {
302421
scriptPubKey = GetScriptForWitness(scriptPubKey);
303422
}
304-
if (std::find(vStrInput.begin(), vStrInput.end(), "P2SH") != vStrInput.end()) {
423+
if (bScriptHash) {
305424
CBitcoinAddress addr(scriptPubKey);
306425
scriptPubKey = GetScriptForDestination(addr.Get());
307426
}
@@ -548,10 +667,14 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command,
548667
MutateTxDelOutput(tx, commandVal);
549668
else if (command == "outaddr")
550669
MutateTxAddOutAddr(tx, commandVal);
551-
else if (command == "outdata")
552-
MutateTxAddOutData(tx, commandVal);
670+
else if (command == "outpubkey")
671+
MutateTxAddOutPubKey(tx, commandVal);
672+
else if (command == "outmultisig")
673+
MutateTxAddOutMultiSig(tx, commandVal);
553674
else if (command == "outscript")
554675
MutateTxAddOutScript(tx, commandVal);
676+
else if (command == "outdata")
677+
MutateTxAddOutData(tx, commandVal);
555678

556679
else if (command == "sign") {
557680
if (!ecc) { ecc.reset(new Secp256k1Init()); }

0 commit comments

Comments
 (0)