forked from ElementsProject/elements
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathelements.cpp
More file actions
2035 lines (1788 loc) · 102 KB
/
Copy pathelements.cpp
File metadata and controls
2035 lines (1788 loc) · 102 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2009-2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <assetsdir.h>
#include <block_proof.h>
#include <core_io.h>
#include <deploymentstatus.h>
#include <dynafed.h>
#include <issuance.h>
#include <key_io.h>
#include <mainchainrpc.h>
#include <node/context.h>
#include <node/kernel_notifications.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/generic.hpp>
#include <script/pegins.h>
#include <secp256k1.h>
#include <util/check.h>
#include <util/signalinterrupt.h>
#include <util/moneystr.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
#include <wallet/receive.h>
#include <wallet/rpc/util.h>
#include <wallet/spend.h>
#include <wallet/wallet.h>
// forward declarations
namespace wallet {
UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose, bool ignore_blind_fail);
RPCHelpMan signrawtransactionwithwallet();
}
// ELEMENTS commands
namespace {
static secp256k1_context *secp256k1_ctx;
class CSecp256k1Init {
public:
CSecp256k1Init() {
secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
}
~CSecp256k1Init() {
secp256k1_context_destroy(secp256k1_ctx);
}
};
static CSecp256k1Init instance_of_csecp256k1;
}
namespace wallet {
RPCHelpMan signblock()
{
return RPCHelpMan{"signblock",
"\nSigns a block proposal, checking that it would be accepted first. Errors if it cannot sign the block. Note that this call adds the witnessScript to your wallet for signing purposes! This function is intended for QA and testing.\n",
{
{"blockhex", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded block from getnewblockhex"},
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded witness script. Required for dynamic federation blocks. Argument is \"\" when the block is P2WPKH."},
},
RPCResult{
RPCResult::Type::ARR, "", "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "pubkey", "signature's pubkey"},
{RPCResult::Type::STR_HEX, "sig", "the signature itself"},
}},
},
},
RPCExamples{
HelpExampleCli("signblock", "0000002018c6f2f913f9902aeab...5ca501f77be96de63f609010000000000000000015100000000")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
if (!g_signed_blocks) {
throw JSONRPCError(RPC_MISC_ERROR, "Signed blocks are not active for this network.");
}
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
CBlock block;
if (!DecodeHexBlk(block, request.params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");
LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
if (!spk_man) {
throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command");
}
{
LOCK(cs_main);
uint256 hash = block.GetHash();
if (pwallet->chain().hasBlocks(hash, 0, std::nullopt))
throw JSONRPCError(RPC_VERIFY_ERROR, "already have block");
CBlockIndex* const pindexPrev = wallet->chain().getTip();
// TestBlockValidity only supports blocks built on the current Tip
if (block.hashPrevBlock != pindexPrev->GetBlockHash())
throw JSONRPCError(RPC_VERIFY_ERROR, "proposal was not based on our best chain");
BlockValidationState state;
if (!wallet->chain().testBlockValidity(state, Params(), block, pindexPrev, false, true) || !state.IsValid()) {
std::string strRejectReason = state.GetRejectReason();
if (strRejectReason.empty())
throw JSONRPCError(RPC_VERIFY_ERROR, state.IsInvalid() ? "Block proposal was invalid" : "Error checking block proposal");
throw JSONRPCError(RPC_VERIFY_ERROR, strRejectReason);
}
}
// Expose SignatureData internals in return value in lieu of "Partially Signed Bitcoin Blocks"
SignatureData block_sigs;
if (block.m_dynafed_params.IsNull()) {
GenericSignScript(*spk_man, block.GetBlockHeader(), block.proof.challenge, block_sigs, SCRIPT_NO_SIGHASH_BYTE /* additional_flags */);
} else {
if (request.params[1].isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Signing dynamic blocks requires the witnessScript argument");
}
std::vector<unsigned char> witness_bytes(ParseHex(request.params[1].get_str()));
// Note that we're adding the signblockscript to the wallet so it can actually
// satisfy witness program scriptpubkeys
if (!witness_bytes.empty()) {
spk_man->AddCScript(CScript(witness_bytes.begin(), witness_bytes.end()));
}
GenericSignScript(*spk_man, block.GetBlockHeader(), block.m_dynafed_params.m_current.m_signblockscript, block_sigs, SCRIPT_VERIFY_NONE /* additional_flags */);
}
// Error if sig data didn't "grow"
if (!block_sigs.complete && block_sigs.signatures.empty()) {
throw JSONRPCError(RPC_VERIFY_ERROR, "Could not sign the block.");
}
UniValue ret(UniValue::VARR);
for (const auto& signature : block_sigs.signatures) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("pubkey", HexStr(signature.second.first));
obj.pushKV("sig", HexStr(MakeByteSpan(signature.second.second)));
ret.push_back(obj);
}
return ret;
},
};
}
RPCHelpMan getpeginaddress()
{
return RPCHelpMan{"getpeginaddress",
"\nReturns information needed for claimpegin to move coins to the sidechain.\n"
"The user should send coins from their Bitcoin wallet to the mainchain_address returned.\n",
{},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "mainchain_address", "mainchain deposit address to send bitcoin to"},
{RPCResult::Type::STR_HEX, "claim_script", "claim script committed to by the mainchain address. This may be required in `claimpegin` to retrieve pegged-in funds\n"},
{RPCResult::Type::STR_AMOUNT, "pegin_min_amount", /*optional=*/true, "Minimum peg-in amount in " + CURRENCY_UNIT},
{RPCResult::Type::NUM, "pegin_min_height", /*optional=*/true, "Minimum block height for peg-in amount rule"},
{RPCResult::Type::BOOL, "pegin_min_active", /*optional=*/true, "Whether the peg-in minimum height rule is active at the current tip"},
{RPCResult::Type::STR_AMOUNT, "pegin_subsidy_threshold", /*optional=*/true, "Peg-in subsidy threshold amount"},
{RPCResult::Type::NUM, "pegin_subsidy_height", /*optional=*/true, "Block height at which peg-in subsidy activates"},
{RPCResult::Type::BOOL, "pegin_subsidy_active", /*optional=*/true, "Whether peg-in subsidy is active at the current tip"},
},
},
RPCExamples{
HelpExampleCli("getpeginaddress", "")
+ HelpExampleRpc("getpeginaddress", "")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
if (pwallet->chain().isInitialBlockDownload()) {
throw JSONRPCError(RPC_WALLET_ERROR, "This action cannot be completed during initial sync or reindexing.");
}
LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
if (!spk_man) {
throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command");
}
if (!pwallet->IsLocked()) {
pwallet->TopUpKeyPool();
}
// Use native witness destination
auto dest = pwallet->GetNewDestination(OutputType::BECH32, "");
if (!dest) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(dest).original);
}
CScript dest_script = GetScriptForDestination(*dest);
// Also add raw scripts to index to recognize later.
spk_man->AddCScript(dest_script);
// Get P2CH deposit address on mainchain from most recent fedpegscript.
const CChainParams& chainparams = Params();
const Consensus::Params& consensus = chainparams.GetConsensus();
const auto& fedpegscripts = GetValidFedpegScripts(pwallet->chain().getTip(), consensus, true /* nextblock_validation */);
if (fedpegscripts.empty()) {
std::string message = "No valid fedpegscripts.";
if (!g_con_elementsmode) {
message += " Not running in Elements mode, check your 'chain' param.";
}
throw JSONRPCError(RPC_INTERNAL_ERROR, message);
}
CTxDestination mainchain_dest(WitnessV0ScriptHash(calculate_contract(fedpegscripts.front().second, dest_script)));
// P2SH-wrapped is the only valid choice for non-dynafed chains but still an
// option for dynafed-enabled ones as well
VersionBitsCache versionbitscache;
if (!DeploymentActiveAfter(pwallet->chain().getTip(), consensus, Consensus::DEPLOYMENT_DYNA_FED, versionbitscache) ||
fedpegscripts.front().first.IsPayToScriptHash()) {
mainchain_dest = ScriptHash(GetScriptForDestination(mainchain_dest));
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("mainchain_address", EncodeParentDestination(mainchain_dest));
ret.pushKV("claim_script", HexStr(dest_script));
PeginMinimum pegin_minimum = Params().GetPeginMinimum();
if (pegin_minimum.amount > 0) {
ret.pushKV("pegin_min_amount", FormatMoney(pegin_minimum.amount));
}
if (pegin_minimum.height < std::numeric_limits<int>::max()) {
ret.pushKV("pegin_min_height", pegin_minimum.height);
ret.pushKV("pegin_min_active", wallet->chain().getTip()->nHeight >= pegin_minimum.height);
}
PeginSubsidy pegin_subsidy = Params().GetPeginSubsidy();
if (pegin_subsidy.threshold > 0) {
ret.pushKV("pegin_subsidy_threshold", FormatMoney(pegin_subsidy.threshold));
}
if (pegin_subsidy.height < std::numeric_limits<int>::max()) {
ret.pushKV("pegin_subsidy_height", pegin_subsidy.height);
ret.pushKV("pegin_subsidy_active", wallet->chain().getTip()->nHeight >= pegin_subsidy.height);
}
return ret;
},
};
}
//! Derive BIP32 tweak from master xpub to child pubkey.
bool DerivePubTweak(const std::vector<uint32_t>& vPath, const CPubKey& keyMaster, const ChainCode &ccMaster, std::vector<unsigned char>& tweakSum)
{
tweakSum.clear();
tweakSum.resize(32);
std::vector<unsigned char> tweak;
CPubKey keyParent = keyMaster;
CPubKey keyChild;
ChainCode ccChild;
ChainCode ccParent = ccMaster;
for (unsigned int i = 0; i < vPath.size(); i++) {
if ((vPath[i] >> 31) != 0) {
return false;
}
if (!keyParent.Derive(keyChild, ccChild, vPath[i], ccParent, &tweak)) return false;
CHECK_NONFATAL(tweak.size() == 32);
ccParent = ccChild;
keyParent = keyChild;
if (i == 0) {
tweakSum = tweak;
} else {
bool ret = secp256k1_ec_seckey_tweak_add(secp256k1_ctx, tweakSum.data(), tweak.data());
if (!ret) {
return false;
}
}
}
return true;
}
// For general cryptographic design of peg-out authorization scheme, see: https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/whitelist/whitelist.md
RPCHelpMan initpegoutwallet()
{
return RPCHelpMan{"initpegoutwallet",
"\nThis call is for Liquid network initialization on the Liquid wallet. The wallet generates a new Liquid pegout authorization key (PAK) and stores it in the Liquid wallet. It then combines this with the `bitcoin_descriptor` to finally create a PAK entry for the network. This allows the user to send Liquid coins directly to a secure offline Bitcoin wallet at the derived path from the bitcoin_descriptor using the `sendtomainchain` command. Losing the Liquid PAK or offline Bitcoin root key will result in the inability to pegout funds, so immediate backup upon initialization is required.\n" +
wallet::HELP_REQUIRING_PASSPHRASE,
{
{"bitcoin_descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin descriptor that includes a single extended pubkey. Must be one of the following: pkh(<xpub>), sh(wpkh(<xpub>)), or wpkh(<xpub>). This is used as the destination chain for the Bitcoin destination wallet. The derivation path from the xpub is given by the descriptor, typically `0/k`, reflecting the external chain of the wallet. DEPRECATED: If a plain xpub is given, pkh(<xpub>) is assumed, with the `0/k` derivation from that xpub. See link for more details on script descriptors: https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md"},
{"bip32_counter", RPCArg::Type::NUM , RPCArg::Default{0}, "The `k` in `0/k` to be set as the next address to derive from the `bitcoin_descriptor`. This will be stored in the wallet and incremented on each successful `sendtomainchain` invocation."},
{"liquid_pak", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The Liquid wallet pubkey in hex to be used as the Liquid PAK for pegout authorization. The private key must be in the wallet if argument is given. If this argument is not provided one will be generated and stored in the wallet automatically and returned."}
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "pakentry", "PAK entry to be used at network initialization time in the form of: `pak=<bitcoin_pak>:<liquid_pak>`"},
{RPCResult::Type::STR_HEX, "liquid_pak", "Liquid PAK pubkey, which is stored in the local Liquid wallet. This can be used in subsequent calls to `initpegoutwallet` to avoid generating a new `liquid_pak`"},
{RPCResult::Type::STR, "liquid_pak_address", "corresponding address for `liquid_pak`. Useful for `dumpprivkey` for wallet backup or transfer"},
{RPCResult::Type::ARR_FIXED, "address_lookahead", "the three next Bitcoin addresses the wallet will use for `sendtomainchain` based on `bip32_counter`",
{RPCResult{RPCResult::Type::STR, "", ""}}},
}
},
RPCExamples{
HelpExampleCli("initpegoutwallet", "sh(wpkh(tpubDAY5hwtonH4NE8zY46ZMFf6B6F3fqMis7cwfNihXXpAg6XzBZNoHAdAzAZx2peoU8nTWFqvUncXwJ9qgE5VxcnUKxdut8F6mptVmKjfiwDQ/0/*))")
+ HelpExampleRpc("initpegoutwallet", "sh(wpkh(tpubDAY5hwtonH4NE8zY46ZMFf6B6F3fqMis7cwfNihXXpAg6XzBZNoHAdAzAZx2peoU8nTWFqvUncXwJ9qgE5VxcnUKxdut8F6mptVmKjfiwDQ/0/*))")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
if (!spk_man) {
throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command");
}
// Check that network cares about PAK
if (!Params().GetEnforcePak()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "PAK enforcement is not enabled on this network.");
}
if (!pwallet->IsLocked())
pwallet->TopUpKeyPool();
// Generate a new key that is added to wallet or set from argument
CPubKey online_pubkey;
if (request.params.size() < 3) {
std::string error;
if (!pwallet->GetOnlinePakKey(online_pubkey, error)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error);
}
} else {
online_pubkey = CPubKey(ParseHex(request.params[2].get_str()));
if (!online_pubkey.IsFullyValid()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Given liquid_pak is not valid.");
}
if (!spk_man->HaveKey(online_pubkey.GetID())) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: liquid_pak could not be found in wallet");
}
}
// Parse offline counter
int counter = 0;
if (request.params.size() > 1) {
counter = request.params[1].getInt<int>();
if (counter < 0 || counter > 1000000000) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "bip32_counter must be between 0 and 1,000,000,000, inclusive.");
}
}
std::string bitcoin_desc = request.params[0].get_str();
std::string xpub_str;
// First check for naked xpub, and impute it as pkh(<xpub>/0/*) for backwards compat
CExtPubKey xpub = DecodeExtPubKey(bitcoin_desc);
if (xpub.pubkey.IsFullyValid()) {
bitcoin_desc = "pkh(" + bitcoin_desc + "/0/*)";
}
FlatSigningProvider provider;
std::string error;
auto descs = Parse(bitcoin_desc, provider, error); // don't require checksum
if (descs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
}
auto& desc = descs.at(0);
if (!desc->IsRange()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_descriptor must be a ranged descriptor.");
}
// Check if we can actually generate addresses(catches hardened derivation steps etc) before
// writing to cache
UniValue address_list(UniValue::VARR);
for (int i = counter; i < counter+3; i++) {
std::vector<CScript> scripts;
if (!desc->Expand(i, provider, scripts, provider)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not generate lookahead addresses with descriptor. Are there hardened derivations after the xpub?");
}
CTxDestination destination;
ExtractDestination(scripts[0], destination);
address_list.push_back(EncodeParentDestination(destination));
}
// For our manual pattern matching, we don't want the checksum part.
auto checksum_char = bitcoin_desc.find('#');
if (checksum_char != std::string::npos) {
bitcoin_desc = bitcoin_desc.substr(0, checksum_char);
}
// Three acceptable descriptors:
if (bitcoin_desc.substr(0, 8) == "sh(wpkh("
&& bitcoin_desc.substr(bitcoin_desc.size()-2, 2) == "))") {
xpub_str = bitcoin_desc.substr(8, bitcoin_desc.size()-2);
} else if (bitcoin_desc.substr(0, 5) == "wpkh("
&& bitcoin_desc.substr(bitcoin_desc.size()-1, 1) == ")") {
xpub_str = bitcoin_desc.substr(5, bitcoin_desc.size()-1);
} else if (bitcoin_desc.substr(0, 4) == "pkh("
&& bitcoin_desc.substr(bitcoin_desc.size()-1, 1) == ")") {
xpub_str = bitcoin_desc.substr(4, bitcoin_desc.size()-1);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_descriptor is not of any type supported: pkh(<xpub>), sh(wpkh(<xpub>)), wpkh(<xpub>), or <xpub>.");
}
// Strip off leading key origin
if (xpub_str.find(']') != std::string::npos) {
xpub_str = xpub_str.substr(xpub_str.find(']') + 1, std::string::npos);
}
// Strip off following range
xpub_str = xpub_str.substr(0, xpub_str.find('/'));
xpub = DecodeExtPubKey(xpub_str);
if (!xpub.pubkey.IsFullyValid()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_descriptor does not contain a valid extended pubkey for this network.");
}
// Parse master pubkey
CPubKey masterpub = xpub.pubkey;
secp256k1_pubkey masterpub_secp;
int ret = secp256k1_ec_pubkey_parse(secp256k1_ctx, &masterpub_secp, masterpub.begin(), masterpub.size());
if (ret != 1) {
throw JSONRPCError(RPC_WALLET_ERROR, "bitcoin_descriptor could not be parsed.");
}
// Store the keys and metadata
if (!pwallet->SetOnlinePubKey(online_pubkey) ||
!pwallet->SetOfflineXPubKey(xpub) ||
!pwallet->SetOfflineCounter(counter) ||
!pwallet->SetOfflineDescriptor(bitcoin_desc)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Failure to initialize pegout wallet.");
}
// Negate the pubkey
ret = secp256k1_ec_pubkey_negate(secp256k1_ctx, &masterpub_secp);
std::vector<unsigned char> negatedpubkeybytes;
negatedpubkeybytes.resize(33);
size_t len = 33;
ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx, &negatedpubkeybytes[0], &len, &masterpub_secp, SECP256K1_EC_COMPRESSED);
CHECK_NONFATAL(ret == 1);
CHECK_NONFATAL(len == 33);
CHECK_NONFATAL(negatedpubkeybytes.size() == 33);
UniValue pak(UniValue::VOBJ);
pak.pushKV("pakentry", "pak=" + HexStr(MakeByteSpan(negatedpubkeybytes)) + ":" + HexStr(online_pubkey));
pak.pushKV("liquid_pak", HexStr(online_pubkey));
pak.pushKV("liquid_pak_address", EncodeDestination(PKHash(online_pubkey)));
pak.pushKV("address_lookahead", address_list);
return pak;
},
};
}
RPCHelpMan sendtomainchain_base()
{
return RPCHelpMan{"sendtomainchain",
"\nSends sidechain funds to the given mainchain address, through the federated peg-in mechanism\n"
+ wallet::HELP_REQUIRING_PASSPHRASE,
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination address on Bitcoin mainchain"},
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount being sent to Bitcoin mainchain"},
{"subtractfeefromamount", RPCArg::Type::BOOL, RPCArg::Default{false}, "The fee will be deducted from the amount being pegged-out."},
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."},
},
{
RPCResult{"if verbose is not set or set to false",
RPCResult::Type::STR_HEX, "txid", "Transaction ID of the resulting sidechain transaction",
},
RPCResult{"if verbose is set to true",
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
{RPCResult::Type::STR, "fee reason", /*optional=*/true, "The transaction fee reason."}
},
},
},
RPCExamples{
HelpExampleCli("sendtomainchain", "\"mgWEy4vBJSHt3mC8C2SEWJQitifb4qeZQq\" 0.1")
+ HelpExampleRpc("sendtomainchain", "\"mgWEy4vBJSHt3mC8C2SEWJQitifb4qeZQq\" 0.1")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(*pwallet);
std::string error_str;
CTxDestination parent_address = DecodeParentDestination(request.params[0].get_str(), error_str);
if (!IsValidDestination(parent_address))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid Bitcoin address: %s", error_str));
CAmount nAmount = AmountFromValue(request.params[1]);
if (nAmount <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
bool subtract_fee = false;
if (request.params.size() > 2) {
subtract_fee = request.params[2].get_bool();
}
// Parse Bitcoin address for destination, embed script
CScript mainchain_script(GetScriptForDestination(parent_address));
uint256 genesisBlockHash = Params().ParentGenesisBlockHash();
// Asset type is implicit, no need to add to script
NullData nulldata;
nulldata << std::vector<unsigned char>(genesisBlockHash.begin(), genesisBlockHash.end());
nulldata << std::vector<unsigned char>(mainchain_script.begin(), mainchain_script.end());
CTxDestination address(nulldata);
std::vector<CRecipient> recipients;
CRecipient recipient = {address, nAmount, Params().GetConsensus().pegged_asset, CPubKey(), subtract_fee};
recipients.push_back(recipient);
EnsureWalletIsUnlocked(*pwallet);
bool verbose = request.params[3].isNull() ? false: request.params[3].get_bool();
mapValue_t mapValue;
CCoinControl no_coin_control; // This is a deprecated API
return SendMoney(*pwallet, no_coin_control, recipients, std::move(mapValue), verbose, true /* ignore_blind_fail */);
},
};
}
// ELEMENTS: Copied from script/descriptor.cpp
typedef std::vector<uint32_t> KeyPath;
/** Split a string on every instance of sep, returning a vector. */
std::vector<Span<const char>> Split(const Span<const char>& sp, char sep)
{
std::vector<Span<const char>> ret;
auto it = sp.begin();
auto start = it;
while (it != sp.end()) {
if (*it == sep) {
ret.emplace_back(start, it);
start = it + 1;
}
++it;
}
ret.emplace_back(start, it);
return ret;
}
/** Parse a key path, being passed a split list of elements (the first element is ignored). */
bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out)
{
for (size_t i = 1; i < split.size(); ++i) {
Span<const char> elem = split[i];
bool hardened = false;
if (elem.size() > 0 && (elem[elem.size() - 1] == '\'' || elem[elem.size() - 1] == 'h')) {
elem = elem.first(elem.size() - 1);
hardened = true;
}
uint32_t p;
if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p) || p > 0x7FFFFFFFUL) return false;
out.push_back(p | (((uint32_t)hardened) << 31));
}
return true;
}
////////////////////////////
RPCHelpMan sendtomainchain_pak()
{
return RPCHelpMan{"sendtomainchain",
"\nSends Liquid funds to the Bitcoin mainchain, through the federated withdraw mechanism. The wallet internally generates the returned `bitcoin_address` via `bitcoin_descriptor` and `bip32_counter` previously set in `initpegoutwallet`. The counter will be incremented upon successful send, avoiding address re-use.\n"
+ wallet::HELP_REQUIRING_PASSPHRASE,
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be \"\". Only for non-PAK `sendtomainchain` compatibility."},
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount being sent to `bitcoin_address`."},
{"subtractfeefromamount", RPCArg::Type::BOOL, RPCArg::Default{false}, "The fee will be deducted from the amount being pegged-out."},
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "bitcoin_address", "destination address on Bitcoin mainchain"},
{RPCResult::Type::STR_HEX, "txid", "transaction ID of the resulting Liquid transaction"},
{RPCResult::Type::STR, "fee_reason", /*optional=*/true, "If verbose is set to true, the Liquid transaction fee reason"},
{RPCResult::Type::STR, "bitcoin_descriptor", "xpubkey of the child destination address"},
{RPCResult::Type::STR, "bip32_counter", "derivation counter for the `bitcoin_descriptor`"},
},
},
RPCExamples{
HelpExampleCli("sendtomainchain", "\"\" 0.1")
+ HelpExampleRpc("sendtomainchain", "\"\" 0.1")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(*pwallet);
if (!request.params[0].get_str().empty()) {
throw JSONRPCError(RPC_TYPE_ERROR, "`address` argument must be \"\" for PAK-enabled networks as the address is generated automatically.");
}
//amount
CAmount nAmount = AmountFromValue(request.params[1]);
if (nAmount < 100000)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount for send, must send more than 0.00100000 BTC");
bool subtract_fee = false;
if (request.params.size() > 2) {
subtract_fee = request.params[2].get_bool();
}
CPAKList paklist = GetActivePAKList(pwallet->chain().getTip(), Params().GetConsensus());
if (paklist.IsReject()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pegout freeze is under effect to aid a pak transition to a new list. Please consult the network operator.");
}
// Fetch pegout key data
int counter = pwallet->offline_counter;
CExtPubKey& xpub = pwallet->offline_xpub;
CPubKey& onlinepubkey = pwallet->online_key;
if (counter < 0) {
throw JSONRPCError(RPC_WALLET_ERROR, "Pegout authorization for this wallet has not been set. Please call `initpegoutwallet` with the appropriate arguments first.");
}
FlatSigningProvider provider;
std::string error;
auto descriptors = Parse(pwallet->offline_desc, provider, error);
LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
if (!spk_man) {
throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command");
}
// If descriptor not previously set, generate it
if (descriptors.empty()) {
std::string offline_desc = "pkh(" + EncodeExtPubKey(xpub) + "0/*)";
if (!pwallet->SetOfflineDescriptor(offline_desc)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Couldn't set wallet descriptor for peg-outs.");
}
descriptors = Parse(pwallet->offline_desc, provider, error);
if (descriptors.empty()) {
throw JSONRPCError(RPC_WALLET_ERROR, "descriptor still null. This is a bug in elementsd.");
}
}
auto& descriptor = descriptors.at(0);
std::string desc_str = pwallet->offline_desc;
std::string xpub_str = EncodeExtPubKey(xpub);
// TODO: More properly expose key parsing functionality
// Strip last parenths(up to 2) and "/*" to let ParseKeyPath do its thing
desc_str.erase(std::remove(desc_str.begin(), desc_str.end(), ')'), desc_str.end());
desc_str = desc_str.substr(0, desc_str.size()-2);
// Since we know there are no key origin data, directly call inner parsing functions
Span<const char> span(desc_str.data(), desc_str.size());
auto split = Split(span, '/');
KeyPath key_path;
if (!ParseKeyPath(split, key_path)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Stored keypath in descriptor cannot be parsed.");
}
key_path.push_back(counter);
secp256k1_pubkey onlinepubkey_secp;
if (secp256k1_ec_pubkey_parse(secp256k1_ctx, &onlinepubkey_secp, onlinepubkey.begin(), onlinepubkey.size()) != 1) {
throw JSONRPCError(RPC_TYPE_ERROR, "Pubkey is invalid");
}
// Get index of given online key
int whitelistindex=-1;
std::vector<secp256k1_pubkey> pak_online = paklist.OnlineKeys();
for (unsigned int i=0; i<pak_online.size(); i++) {
if (memcmp((void *)&pak_online[i], (void *)&onlinepubkey_secp, sizeof(secp256k1_pubkey)) == 0) {
whitelistindex = i;
break;
}
}
if (whitelistindex == -1)
throw JSONRPCError(RPC_WALLET_ERROR, "Given online key is not in Pegout Authorization Key List");
// Parse master pubkey
CPubKey masterpub = xpub.pubkey;
secp256k1_pubkey masterpub_secp;
int ret = secp256k1_ec_pubkey_parse(secp256k1_ctx, &masterpub_secp, masterpub.begin(), masterpub.size());
if (ret != 1) {
throw JSONRPCError(RPC_WALLET_ERROR, "Master pubkey could not be parsed.");
}
secp256k1_pubkey btcpub_secp;
memcpy(&btcpub_secp, &masterpub_secp, sizeof(secp256k1_pubkey));
// Negate master pubkey
ret = secp256k1_ec_pubkey_negate(secp256k1_ctx, &masterpub_secp);
// Make sure negated master pubkey is in PAK list at same index as online_pubkey
if (memcmp((void *)&paklist.OfflineKeys()[whitelistindex], (void *)&masterpub_secp, sizeof(secp256k1_pubkey)) != 0) {
throw JSONRPCError(RPC_WALLET_ERROR, "Given bitcoin_descriptor cannot be found in same entry as known liquid_pak");
}
// Get online PAK
CKey masterOnlineKey;
if (!spk_man->GetKey(onlinepubkey.GetID(), masterOnlineKey))
throw JSONRPCError(RPC_WALLET_ERROR, "Given online key is in master set but not in wallet");
// Tweak offline pubkey by tweakSum aka sumkey to get bitcoin key
std::vector<unsigned char> tweakSum;
if (!DerivePubTweak(key_path, xpub.pubkey, xpub.chaincode, tweakSum)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not create xpub tweak to generate proof.");
}
ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx, &btcpub_secp, tweakSum.data());
CHECK_NONFATAL(ret);
std::vector<unsigned char> btcpubkeybytes;
btcpubkeybytes.resize(33);
size_t btclen = 33;
ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx, &btcpubkeybytes[0], &btclen, &btcpub_secp, SECP256K1_EC_COMPRESSED);
CHECK_NONFATAL(ret == 1);
CHECK_NONFATAL(btclen == 33);
CHECK_NONFATAL(btcpubkeybytes.size() == 33);
//Create, verify whitelist proof
secp256k1_whitelist_signature sig;
if(secp256k1_whitelist_sign(secp256k1_ctx, &sig, &paklist.OnlineKeys()[0], &paklist.OfflineKeys()[0], paklist.size(), &btcpub_secp, UCharCast(masterOnlineKey.begin()), &tweakSum[0], whitelistindex) != 1) {
throw JSONRPCError(RPC_WALLET_ERROR, "Pegout authorization proof signing failed");
}
if (secp256k1_whitelist_verify(secp256k1_ctx, &sig, &paklist.OnlineKeys()[0], &paklist.OfflineKeys()[0], paklist.size(), &btcpub_secp) != 1) {
throw JSONRPCError(RPC_WALLET_ERROR, "Pegout authorization proof was created and signed but is invalid");
}
//Serialize
const size_t expectedOutputSize = 1 + 32 * (1 + paklist.size());
CHECK_NONFATAL(1 + 32 * (1 + 256) >= expectedOutputSize);
unsigned char output[1 + 32 * (1 + 256)];
size_t outlen = expectedOutputSize;
secp256k1_whitelist_signature_serialize(secp256k1_ctx, output, &outlen, &sig);
CHECK_NONFATAL(outlen == expectedOutputSize);
std::vector<unsigned char> whitelistproof(output, output + expectedOutputSize / sizeof(unsigned char));
// Derive the end address in mainchain
std::vector<CScript> scripts;
if (!descriptor->Expand(counter, provider, scripts, provider)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not generate mainchain destination with descriptor. This is a bug.");
}
CHECK_NONFATAL(scripts.size() == 1);
CScript mainchain_script = scripts[0];
CTxDestination bitcoin_address;
ExtractDestination(mainchain_script, bitcoin_address);
uint256 genesisBlockHash = Params().ParentGenesisBlockHash();
NullData nulldata;
nulldata << std::vector<unsigned char>(genesisBlockHash.begin(), genesisBlockHash.end());
nulldata << std::vector<unsigned char>(mainchain_script.begin(), mainchain_script.end());
nulldata << btcpubkeybytes;
nulldata << whitelistproof;
CTxDestination address(nulldata);
CHECK_NONFATAL(GetScriptForDestination(nulldata).IsPegoutScript(genesisBlockHash));
std::vector<CRecipient> recipients;
CRecipient recipient = {address, nAmount, Params().GetConsensus().pegged_asset, CPubKey(), subtract_fee};
recipients.push_back(recipient);
if (!ScriptHasValidPAKProof(GetScriptForDestination(nulldata), Params().ParentGenesisBlockHash(), paklist)) {
throw JSONRPCError(RPC_TYPE_ERROR, "Resulting scriptPubKey is non-standard. Ensure pak=reject is not set");
}
bool verbose = request.params[3].isNull() ? false: request.params[3].get_bool();
mapValue_t mapValue;
CCoinControl no_coin_control; // This is a deprecated API
UniValue tx_hash_hex = SendMoney(*pwallet, no_coin_control, recipients, std::move(mapValue), verbose, true /* ignore_blind_fail */);
pwallet->SetOfflineCounter(counter+1);
std::stringstream ss;
ss << counter;
UniValue obj(UniValue::VOBJ);
if (!verbose) {
obj.pushKV("txid", tx_hash_hex);
} else {
obj.pushKV("txid", tx_hash_hex["txid"]);
obj.pushKV("fee_reason", tx_hash_hex["fee_reason"]);
}
obj.pushKV("bitcoin_address", EncodeParentDestination(bitcoin_address));
obj.pushKV("bip32_counter", ss.str());
obj.pushKV("bitcoin_descriptor", pwallet->offline_desc);
return obj;
},
};
}
// We only expose the appropriate peg-out method type per network
RPCHelpMan sendtomainchain()
{
if (Params().GetEnforcePak()) {
return sendtomainchain_pak();
} else {
return sendtomainchain_base();
}
}
extern UniValue signrawtransaction(const JSONRPCRequest& request);
extern UniValue sendrawtransaction(const JSONRPCRequest& request);
template <typename T_tx_ref, typename T_merkle_block>
static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef, T_merkle_block& merkleBlock)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
std::vector<unsigned char> txData = ParseHex(request.params[0].get_str());
std::vector<unsigned char> txOutProofData = ParseHex(request.params[1].get_str());
std::set<CScript> claim_scripts;
if (request.params.size() > 2) {
const std::string claim_script = request.params[2].get_str();
if (!IsHex(claim_script)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script is not hex.");
}
// If given manually, no need for it to be a witness script
std::vector<unsigned char> witnessBytes(ParseHex(claim_script));
CScript witness_script(witnessBytes.begin(), witnessBytes.end());
claim_scripts.insert(std::move(witness_script));
}
else {
// Look for known wpkh address in wallet
for (std::map<CTxDestination, CAddressBookData>::const_iterator iter = pwallet->m_address_book.begin(); iter != pwallet->m_address_book.end(); ++iter) {
CScript dest_script = GetScriptForDestination(iter->first);
claim_scripts.insert(std::move(dest_script));
}
}
// Make the tx
CMutableTransaction mtx;
// Construct peg-in input
CreatePegInInput(mtx, 0, txBTCRef, merkleBlock, claim_scripts, txData, txOutProofData, wallet->chain().getTip());
// Get value for peg-in output
CAmount value = 0;
if (!GetAmountFromParentChainPegin(value, *txBTCRef, mtx.vin[0].prevout.n)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to peg-in must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
}
const PeginMinimum pegin_minimum = Params().GetPeginMinimum();
if (pwallet->chain().getTip()->nHeight >= pegin_minimum.height && value < pegin_minimum.amount) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Peg-in amount (%d) is lower than the minimum peg-in amount for this chain (%d).", FormatMoney(value), FormatMoney(pegin_minimum.amount)));
}
const PeginSubsidy pegin_subsidy = Params().GetPeginSubsidy();
bool subsidy_required = pwallet->chain().getTip()->nHeight >= pegin_subsidy.height && value < pegin_subsidy.threshold;
if (subsidy_required && !gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain) && request.params[3].isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Bitcoin transaction fee rate must be supplied, because validatepegin is off and this peg-in requires a burn subsidy.");
}
CAmount fee = 0;
uint32_t parent_vsize = 0;
CFeeRate feerate = CFeeRate{0};
if (gArgs.GetBoolArg("-validatepegin", false) && subsidy_required) {
std::string txid = txBTCRef->GetHash().ToString();
std::string blockhash = merkleBlock.header.GetHash().ToString();
UniValue params(UniValue::VARR);
params.push_back(txid);
params.push_back(2);
params.push_back(blockhash);
UniValue result = CallMainChainRPC("getrawtransaction", params);
if (result["error"].isStr()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, result["error"]["message"].get_str());
} else {
parent_vsize = result["result"]["vsize"].getInt<uint64_t>();
if (result["result"]["fee"].isNum()) {
fee = static_cast<CAmount>(std::round(result["result"]["fee"].get_real() * COIN));
} else if (result["result"]["fee"].isObject()) {
std::string asset = Params().GetConsensus().parent_pegged_asset.GetHex();
if (result["result"]["fee"][asset].isNum()) {
fee = static_cast<CAmount>(std::round(result["result"]["fee"][asset].get_real() * COIN));
} else {
throw JSONRPCError(RPC_MISC_ERROR, "No fee result for the parent pegged asset.");
}
} else {
throw JSONRPCError(RPC_MISC_ERROR, "Fee result is not a number or object.");
}
// when parent feerate is less than 1 sat/vb, use 1 sat/vb for the calculation
feerate = std::max(CFeeRate{fee, parent_vsize}, CFeeRate{1000});
}
} else if (!request.params[3].isNull()) {
// manual feerate, specified in sats/vb but CFeeRate takes sats/Kvb
CAmount satsperk = static_cast<CAmount>(std::round(request.params[3].get_real() * 1000));
feerate = std::max(CFeeRate{satsperk}, CFeeRate{1000});
}
// Manually construct peg-in transaction, sign it, and send it off.
// Decrement the output value as much as needed given the total vsize to
// pay the fees.
if (!pwallet->IsLocked())
pwallet->TopUpKeyPool();
// Generate a new key that is added to wallet
auto wpkhash = pwallet->GetNewDestination(OutputType::BECH32, "");
if (!wpkhash) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(wpkhash).original);
}
// add a wallet output for the peg-in value
mtx.vout.emplace_back(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(*wpkhash));
if (subsidy_required) {
// add an op_return for the peg-in fee subsidy
mtx.vout.emplace_back(Params().GetConsensus().pegged_asset, 0, CScript() << OP_RETURN);
}
// add a fee output
mtx.vout.emplace_back(Params().GetConsensus().pegged_asset, 0, CScript());
// Estimate fee for transaction, decrement fee output (including witness data)
unsigned int nBytes = GetVirtualTransactionSize(CTransaction(mtx)) + (1 + 1 + 72 + 1 + 33) / WITNESS_SCALE_FACTOR;
CCoinControl coin_control;
FeeCalculation feeCalc;
CAmount nFeeNeeded = GetMinimumFee(*pwallet, nBytes, coin_control, &feeCalc);
if (nFeeNeeded == CAmount{0} && feeCalc.reason == FeeReason::FALLBACK) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
}
if (subsidy_required) {
CHECK_NONFATAL(mtx.vout.size() == 3);
// calculate the subsidy as the amount required to spend the P2WSH output
const auto& fedpegscripts = GetValidFedpegScripts(pwallet->chain().getTip(), Params().GetConsensus(), true /* nextblock_validation */);
int t = 0;
int n = 0;
CHECK_NONFATAL(fedpegscripts.size() > 0);
CHECK_NONFATAL(ParseFedPegQuorum(fedpegscripts[0].second, t, n));
// P2WSH input is 41 bytes: txid (32) + vout (4) + scriptsig len (1) + sequence (4)
// the witness to spend is `t` signatures + the script size
unsigned int weight = WITNESS_SCALE_FACTOR * (32 + 4 + 1 + 4) + (t * 72 + fedpegscripts[0].second.size());
unsigned int vbytes = (weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
CAmount subsidy = feerate.GetFee(vbytes);
CAmount value = mtx.vout[0].nValue.GetAmount() - nFeeNeeded - subsidy;
mtx.vout[0].nValue = value;
mtx.vout[1].nValue = subsidy;
mtx.vout[2].nValue = nFeeNeeded;
if (IsDust(mtx.vout[0], pwallet->chain().relayDustFee())) {
throw JSONRPCError(RPC_WALLET_ERROR, "Peg-in transaction would create dust output.");
}
} else {
CHECK_NONFATAL(mtx.vout.size() == 2);
mtx.vout[0].nValue = mtx.vout[0].nValue.GetAmount() - nFeeNeeded;
mtx.vout[1].nValue = nFeeNeeded;
}
UniValue ret(UniValue::VOBJ);
// Return hex
std::string strHex = EncodeHexTx(CTransaction(mtx));
ret.pushKV("hex", strHex);
// Additional block lee-way to avoid bitcoin block races
if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) {
unsigned int required_depth = Params().GetConsensus().pegin_min_depth + 2;
std::vector<uint256> txHashes;
std::vector<unsigned int> txIndices;
merkleBlock.txn.ExtractMatches(txHashes, txIndices);
if (txIndices[0] == 0) {
required_depth = std::max(required_depth, (unsigned int)COINBASE_MATURITY+2);
}
ret.pushKV("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(), required_depth, merkleBlock.txn.GetNumTransactions()));
}
return ret;
}
RPCHelpMan createrawpegin()
{
return RPCHelpMan{"createrawpegin",
"\nCreates a raw transaction to claim coins from the main chain by creating a peg-in transaction with the necessary metadata after the corresponding Bitcoin transaction.\n"
"Note that this call will not sign the transaction.\n"
"If a transaction is not relayed it may require manual addition to a functionary mempool in order for it to be mined.\n",
{
{"bitcoin_tx", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The raw bitcoin transaction (in hex) depositing bitcoin to the mainchain_address generated by getpeginaddress"},
{"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A rawtxoutproof (in hex) generated by the mainchain daemon's `gettxoutproof` containing a proof of only bitcoin_tx"},
{"claim_script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The witness program generated by getpeginaddress. Only needed if not in wallet."},
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "The fee rate of the Bitcoin transaction in sats/vb, only necessary when validatepegin=0."},
},
RPCResult{