Skip to content

Commit f5447c9

Browse files
laanwjCryptoCentric
authored andcommitted
Merge bitcoin#8883: Add all standard TXO types to bitcoin-tx
1 parent 1514332 commit f5447c9

24 files changed

+477
-31
lines changed

src/absolute-tx.cpp

Lines changed: 164 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +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", _("Add raw script output to TX"));
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."));
8391
strUsage += HelpMessageOpt("sign=SIGHASH-FLAGS", _("Add zero or more signatures to transaction") + ". " +
8492
_("This command requires JSON registers:") +
8593
_("prevtxs=JSON object") + ", " +
@@ -168,6 +176,14 @@ static void RegisterLoad(const std::string& strInput)
168176
RegisterSetJson(key, valStr);
169177
}
170178

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+
171187
static void MutateTxVersion(CMutableTransaction& tx, const std::string& cmdVal)
172188
{
173189
int64_t newVersion = atoi64(cmdVal);
@@ -221,25 +237,18 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu
221237

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

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

237247
// extract and validate ADDRESS
238-
std::string strAddr = strInput.substr(pos + 1, std::string::npos);
248+
std::string strAddr = vStrInputParts[1];
239249
CBitcoinAddress addr(strAddr);
240250
if (!addr.IsValid())
241251
throw std::runtime_error("invalid TX output address");
242-
243252
// build standard output script via GetScriptForDestination()
244253
CScript scriptPubKey = GetScriptForDestination(addr.Get());
245254

@@ -248,6 +257,114 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn
248257
tx.vout.push_back(txout);
249258
}
250259

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

261378
if (pos != std::string::npos) {
262-
// extract and validate VALUE
263-
std::string strValue = strInput.substr(0, pos);
264-
if (!ParseMoney(strValue, value))
265-
throw std::runtime_error("invalid TX output value");
379+
// Extract and validate VALUE
380+
value = ExtractAndValidateValue(strInput.substr(0, pos));
266381
}
267382

268383
// extract and validate DATA
@@ -279,21 +394,35 @@ static void MutateTxAddOutData(CMutableTransaction& tx, const std::string& strIn
279394

280395
static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& strInput)
281396
{
282-
// separate VALUE:SCRIPT in string
283-
size_t pos = strInput.find(':');
284-
if ((pos == std::string::npos) ||
285-
(pos == 0))
397+
// separate VALUE:SCRIPT[:FLAGS]
398+
std::vector<std::string> vStrInputParts;
399+
boost::split(vStrInputParts, strInput, boost::is_any_of(":"));
400+
if (vStrInputParts.size() < 2)
286401
throw std::runtime_error("TX output missing separator");
287402

288-
// extract and validate VALUE
289-
std::string strValue = strInput.substr(0, pos);
290-
CAmount value;
291-
if (!ParseMoney(strValue, value))
292-
throw std::runtime_error("invalid TX output value");
403+
// Extract and validate VALUE
404+
CAmount value = ExtractAndValidateValue(vStrInputParts[0]);
293405

294406
// extract and validate script
295-
std::string strScript = strInput.substr(pos + 1, std::string::npos);
296-
CScript scriptPubKey = ParseScript(strScript); // throws on err
407+
std::string strScript = vStrInputParts[1];
408+
CScript scriptPubKey = ParseScript(strScript);
409+
410+
// Extract FLAGS
411+
bool bSegWit = false;
412+
bool bScriptHash = false;
413+
if (vStrInputParts.size() == 3) {
414+
std::string flags = vStrInputParts.back();
415+
bSegWit = (flags.find("W") != std::string::npos);
416+
bScriptHash = (flags.find("S") != std::string::npos);
417+
}
418+
419+
if (bSegWit) {
420+
scriptPubKey = GetScriptForWitness(scriptPubKey);
421+
}
422+
if (bScriptHash) {
423+
CBitcoinAddress addr(scriptPubKey);
424+
scriptPubKey = GetScriptForDestination(addr.Get());
425+
}
297426

298427
// construct TxOut, append to transaction output list
299428
CTxOut txout(value, scriptPubKey);
@@ -522,10 +651,14 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command,
522651
MutateTxDelOutput(tx, commandVal);
523652
else if (command == "outaddr")
524653
MutateTxAddOutAddr(tx, commandVal);
525-
else if (command == "outdata")
526-
MutateTxAddOutData(tx, commandVal);
654+
else if (command == "outpubkey")
655+
MutateTxAddOutPubKey(tx, commandVal);
656+
else if (command == "outmultisig")
657+
MutateTxAddOutMultiSig(tx, commandVal);
527658
else if (command == "outscript")
528659
MutateTxAddOutScript(tx, commandVal);
660+
else if (command == "outdata")
661+
MutateTxAddOutData(tx, commandVal);
529662

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

src/test/data/bitcoin-util-test.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,26 @@
102102
"output_cmp": "txcreate2.json",
103103
"description": "Creates a new transaction with a single empty output script (output in json)"
104104
},
105+
{ "exec": "./absolute-tx",
106+
"args": ["-create", "outscript=0:OP_DROP", "nversion=1"],
107+
"output_cmp": "txcreatescript1.hex",
108+
"description": "Create a new transaction with a single output script (OP_DROP)"
109+
},
110+
{ "exec": "./absolute-tx",
111+
"args": ["-json", "-create", "outscript=0:OP_DROP", "nversion=1"],
112+
"output_cmp": "txcreatescript1.json",
113+
"description": "Create a new transaction with a single output script (OP_DROP) (output as json)"
114+
},
115+
{ "exec": "./absolute-tx",
116+
"args": ["-create", "outscript=0:OP_DROP:S", "nversion=1"],
117+
"output_cmp": "txcreatescript2.hex",
118+
"description": "Create a new transaction with a single output script (OP_DROP) in a P2SH"
119+
},
120+
{ "exec": "./absolute-tx",
121+
"args": ["-json", "-create", "outscript=0:OP_DROP:S", "nversion=1"],
122+
"output_cmp": "txcreatescript2.json",
123+
"description": "Create a new transaction with a single output script (OP_DROP) in a P2SH (output as json)"
124+
},
105125
{ "exec": "./absolute-tx",
106126
"args": ["02000000000100000000000000000000000000"],
107127
"output_cmp": "txcreate2.hex",
@@ -146,6 +166,18 @@
146166
"output_cmp": "txcreatesignv2.hex",
147167
"description": "Creates a new transaction with a single input and a single output, and then signs the transaction"
148168
},
169+
{ "exec": "./absolute-tx",
170+
"args":
171+
["-create", "outpubkey=0:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397", "nversion=1"],
172+
"output_cmp": "txcreateoutpubkey1.hex",
173+
"description": "Creates a new transaction with a single pay-to-pubkey output"
174+
},
175+
{ "exec": "./absolute-tx",
176+
"args":
177+
["-json", "-create", "outpubkey=0:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397", "nversion=1"],
178+
"output_cmp": "txcreateoutpubkey1.json",
179+
"description": "Creates a new transaction with a single pay-to-pubkey output (output as json)"
180+
},
149181
{ "exec": "./absolute-tx",
150182
"args":
151183
["-create",
@@ -231,5 +263,25 @@
231263
"in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0:1"],
232264
"output_cmp": "txcreatedata_seq1.json",
233265
"description": "Adds a new input with sequence number to a transaction (output in json)"
266+
},
267+
{ "exec": "./absolute-tx",
268+
"args": ["-create", "outmultisig=1:2:3:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397:021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d:02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485", "nversion=1"],
269+
"output_cmp": "txcreatemultisig1.hex",
270+
"description": "Creates a new transaction with a single 2-of-3 multisig output"
271+
},
272+
{ "exec": "./absolute-tx",
273+
"args": ["-json", "-create", "outmultisig=1:2:3:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397:021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d:02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485", "nversion=1"],
274+
"output_cmp": "txcreatemultisig1.json",
275+
"description": "Creates a new transaction with a single 2-of-3 multisig output (output in json)"
276+
},
277+
{ "exec": "./absolute-tx",
278+
"args": ["-create", "outmultisig=1:2:3:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397:021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d:02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485:S", "nversion=1"],
279+
"output_cmp": "txcreatemultisig2.hex",
280+
"description": "Creates a new transaction with a single 2-of-3 multisig in a P2SH output"
281+
},
282+
{ "exec": "./absolute-tx",
283+
"args": ["-json", "-create", "outmultisig=1:2:3:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397:021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d:02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485:S", "nversion=1"],
284+
"output_cmp": "txcreatemultisig2.json",
285+
"description": "Creates a new transaction with a single 2-of-3 multisig in a P2SH output (output in json)"
234286
}
235287
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
01000000000100e1f5050000000069522102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff39721021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d2102df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb48553ae00000000
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"txid": "0d1d4edfc217d9db3ab6a9298f26a52eae3c52f55a6cb8ccbc14f7c727572894",
3+
"hash": "0d1d4edfc217d9db3ab6a9298f26a52eae3c52f55a6cb8ccbc14f7c727572894",
4+
"version": 1,
5+
"locktime": 0,
6+
"vin": [
7+
],
8+
"vout": [
9+
{
10+
"value": 1.00,
11+
"n": 0,
12+
"scriptPubKey": {
13+
"asm": "2 02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397 021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d 02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485 3 OP_CHECKMULTISIG",
14+
"hex": "522102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff39721021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d2102df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb48553ae",
15+
"reqSigs": 2,
16+
"type": "multisig",
17+
"addresses": [
18+
"1FoG2386FG2tAJS9acMuiDsKy67aGg9MKz",
19+
"1FXtz9KU8JNmQDyHdiEm5HDiALuP3zdHvV",
20+
"14LuavcBbXZYJ6Tsz3cAUQj9SuQoL2xCQX"
21+
]
22+
}
23+
}
24+
],
25+
"hex": "01000000000100e1f5050000000069522102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff39721021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d2102df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb48553ae00000000"
26+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
01000000000100e1f5050000000017a9141c6fbaf46d64221e80cbae182c33ddf81b9294ac8700000000
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"txid": "0d861f278a3b7bce7cb5a88d71e6e6a903336f95ad5a2c29b295b63835b6eee3",
3+
"hash": "0d861f278a3b7bce7cb5a88d71e6e6a903336f95ad5a2c29b295b63835b6eee3",
4+
"version": 1,
5+
"locktime": 0,
6+
"vin": [
7+
],
8+
"vout": [
9+
{
10+
"value": 1.00,
11+
"n": 0,
12+
"scriptPubKey": {
13+
"asm": "OP_HASH160 1c6fbaf46d64221e80cbae182c33ddf81b9294ac OP_EQUAL",
14+
"hex": "a9141c6fbaf46d64221e80cbae182c33ddf81b9294ac87",
15+
"reqSigs": 1,
16+
"type": "scripthash",
17+
"addresses": [
18+
"34HNh57oBCRKkxNyjTuWAJkTbuGh6jg2Ms"
19+
]
20+
}
21+
}
22+
],
23+
"hex": "01000000000100e1f5050000000017a9141c6fbaf46d64221e80cbae182c33ddf81b9294ac8700000000"
24+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
01000000000100e1f50500000000220020e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f0500000000

0 commit comments

Comments
 (0)