Skip to content

Commit 3cfe2d4

Browse files
committed
Add sendtomainchain RPC call which is used when -pak_enforce is enabled
1 parent 567de67 commit 3cfe2d4

File tree

1 file changed

+175
-1
lines changed

1 file changed

+175
-1
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5150,7 +5150,181 @@ UniValue sendtomainchain_base(const JSONRPCRequest& request)
51505150

51515151
UniValue sendtomainchain_pak(const JSONRPCRequest& request)
51525152
{
5153-
return NullUniValue;
5153+
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
5154+
CWallet* const pwallet = wallet.get();
5155+
5156+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
5157+
return NullUniValue;
5158+
5159+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
5160+
throw std::runtime_error(
5161+
"sendtomainchain "" amount ( subtractfeefromamount ) \n"
5162+
"\nSends Liquid funds to the Bitcoin mainchain, through the federated withdraw mechanism. The wallet internally generates the returned `bitcoin_address` via `bitcoin_xpub` and `bip32_counter` previously set in `initpegoutwallet`. The counter will be incremented upon successful send, avoiding address re-use.\n"
5163+
+ HelpRequiringPassphrase(pwallet) +
5164+
"\nArguments:\n"
5165+
"1. \"address\" (string, required) Must be \"\". Only for non-PAK `sendtomainchain` compatibility.\n"
5166+
"2. \"amount\" (numeric, required) The amount being sent to `bitcoin_address`.\n"
5167+
"3. \"subtractfeefromamount\" (boolean, optional, default=false) The fee will be deducted from the amount being pegged-out.\n"
5168+
"\nResult:\n"
5169+
"\nResult:\n"
5170+
"{\n"
5171+
"\"bitcoin_address\" (string) The destination address on Bitcoin mainchain."
5172+
"\"txid\" (string) Transaction ID of the resulting Liquid transaction\n"
5173+
"\"bitcoin_xpub\" (string) The xpubkey of the child destination address.\n"
5174+
"\"derivation_path\" (string) The derivation path in text that leads to `bitcoin_address` from the `bitcoin_xpub`.\n"
5175+
"}\n"
5176+
"\nExamples:\n"
5177+
+ HelpExampleCli("sendtomainchain", "\"\" 0.1")
5178+
+ HelpExampleRpc("sendtomainchain", "\"\" 0.1")
5179+
);
5180+
5181+
LOCK2(cs_main, pwallet->cs_wallet);
5182+
5183+
EnsureWalletIsUnlocked(pwallet);
5184+
5185+
if (!request.params[0].get_str().empty()) {
5186+
throw JSONRPCError(RPC_TYPE_ERROR, "`address` argument must be \"\" for PAK-enabled networks as the address is generated automatically.");
5187+
}
5188+
5189+
//amount
5190+
CAmount nAmount = AmountFromValue(request.params[1]);
5191+
if (nAmount < 100000)
5192+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount for send, must send more than 0.0001 BTC");
5193+
5194+
bool subtract_fee = false;
5195+
if (request.params.size() > 2) {
5196+
subtract_fee = request.params[1].get_bool();
5197+
}
5198+
5199+
CPAKList paklist = g_paklist_blockchain;
5200+
if (g_paklist_config) {
5201+
paklist = *g_paklist_config;
5202+
}
5203+
if (paklist.IsReject()) {
5204+
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.");
5205+
}
5206+
5207+
// Fetch pegout key data
5208+
int counter = pwallet->offline_counter;
5209+
CExtPubKey& xpub = pwallet->offline_xpub;
5210+
CPubKey& onlinepubkey = pwallet->online_key;
5211+
5212+
if (counter < 0) {
5213+
throw JSONRPCError(RPC_WALLET_ERROR, "Pegout authorization for this wallet has not been set. Please call `initpegoutwallet` with the appropriate arguments first.");
5214+
}
5215+
5216+
std::vector<uint32_t> vPath;
5217+
vPath.push_back(0);
5218+
vPath.push_back((uint32_t)counter);
5219+
5220+
secp256k1_pubkey onlinepubkey_secp;
5221+
if (secp256k1_ec_pubkey_parse(secp256k1_ctx, &onlinepubkey_secp, onlinepubkey.begin(), onlinepubkey.size()) != 1) {
5222+
throw JSONRPCError(RPC_TYPE_ERROR, "Pubkey is invalid");
5223+
}
5224+
5225+
// Get index of given online key
5226+
int whitelistindex=-1;
5227+
std::vector<secp256k1_pubkey> pak_online = paklist.OnlineKeys();
5228+
for (unsigned int i=0; i<pak_online.size(); i++) {
5229+
if (memcmp((void *)&pak_online[i], (void *)&onlinepubkey_secp, sizeof(secp256k1_pubkey)) == 0)
5230+
whitelistindex = i;
5231+
break;
5232+
}
5233+
if (whitelistindex == -1)
5234+
throw JSONRPCError(RPC_WALLET_ERROR, "Given online key is not in Pegout Authorization Key List");
5235+
5236+
// Parse master pubkey
5237+
CPubKey masterpub = xpub.pubkey;
5238+
secp256k1_pubkey masterpub_secp;
5239+
int ret = secp256k1_ec_pubkey_parse(secp256k1_ctx, &masterpub_secp, masterpub.begin(), masterpub.size());
5240+
if (ret != 1) {
5241+
throw JSONRPCError(RPC_WALLET_ERROR, "Master pubkey could not be parsed.");
5242+
}
5243+
5244+
secp256k1_pubkey btcpub_secp;
5245+
memcpy(&btcpub_secp, &masterpub_secp, sizeof(secp256k1_pubkey));
5246+
5247+
// Negate master pubkey
5248+
ret = secp256k1_ec_pubkey_negate(secp256k1_ctx, &masterpub_secp);
5249+
5250+
// Make sure negated master pubkey is in PAK list at same index as online_pubkey
5251+
if (memcmp((void *)&paklist.OfflineKeys()[whitelistindex], (void *)&masterpub_secp, sizeof(secp256k1_pubkey)) != 0) {
5252+
throw JSONRPCError(RPC_WALLET_ERROR, "Given bitcoin_xpub cannot be found in same entry as known liquid_pak");
5253+
}
5254+
5255+
// Get online PAK
5256+
CKey masterOnlineKey;
5257+
if (!pwallet->GetKey(onlinepubkey.GetID(), masterOnlineKey))
5258+
throw JSONRPCError(RPC_WALLET_ERROR, "Given online key is in master set but not in wallet");
5259+
5260+
// Tweak offline pubkey by tweakSum aka sumkey to get bitcoin key
5261+
std::vector<unsigned char> tweakSum;
5262+
if (!DerivePubTweak(vPath, xpub.pubkey, xpub.chaincode, tweakSum)) {
5263+
throw JSONRPCError(RPC_WALLET_ERROR, "Could not create xpub tweak to generate proof.");
5264+
}
5265+
ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx, &btcpub_secp, tweakSum.data());
5266+
assert(ret);
5267+
5268+
std::vector<unsigned char> btcpubkeybytes;
5269+
btcpubkeybytes.resize(33);
5270+
size_t btclen = 33;
5271+
ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx, &btcpubkeybytes[0], &btclen, &btcpub_secp, SECP256K1_EC_COMPRESSED);
5272+
assert(ret == 1);
5273+
assert(btclen == 33);
5274+
assert(btcpubkeybytes.size() == 33);
5275+
5276+
//Create, verify whitelist proof
5277+
secp256k1_whitelist_signature sig;
5278+
if(secp256k1_whitelist_sign(secp256k1_ctx, &sig, &paklist.OnlineKeys()[0], &paklist.OfflineKeys()[0], paklist.size(), &btcpub_secp, masterOnlineKey.begin(), &tweakSum[0], whitelistindex, NULL, NULL) != 1) {
5279+
throw JSONRPCError(RPC_WALLET_ERROR, "Pegout authorization proof signing failed");
5280+
}
5281+
5282+
if (secp256k1_whitelist_verify(secp256k1_ctx, &sig, &paklist.OnlineKeys()[0], &paklist.OfflineKeys()[0], paklist.size(), &btcpub_secp) != 1) {
5283+
throw JSONRPCError(RPC_WALLET_ERROR, "Pegout authorization proof was created and signed but is invalid");
5284+
}
5285+
5286+
//Serialize
5287+
const size_t expectedOutputSize = 1 + 32 * (1 + paklist.size());
5288+
assert(1 + 32 * (1 + 256) >= expectedOutputSize);
5289+
unsigned char output[1 + 32 * (1 + 256)];
5290+
size_t outlen = expectedOutputSize;
5291+
secp256k1_whitelist_signature_serialize(secp256k1_ctx, output, &outlen, &sig);
5292+
assert(outlen == expectedOutputSize);
5293+
std::vector<unsigned char> whitelistproof(output, output + expectedOutputSize / sizeof(unsigned char));
5294+
5295+
// Bitcoin address
5296+
CTxDestination bitcoin_address = DeriveBitcoinOfflineAddress(xpub, counter);
5297+
CScript scriptPubKeyMainchain(GetScriptForDestination(bitcoin_address));
5298+
5299+
uint256 genesisBlockHash = Params().ParentGenesisBlockHash();
5300+
NullData nulldata;
5301+
nulldata << std::vector<unsigned char>(genesisBlockHash.begin(), genesisBlockHash.end());
5302+
nulldata << std::vector<unsigned char>(scriptPubKeyMainchain.begin(), scriptPubKeyMainchain.end());
5303+
nulldata << btcpubkeybytes;
5304+
nulldata << whitelistproof;
5305+
CTxDestination address(nulldata);
5306+
assert(GetScriptForDestination(nulldata).IsPegoutScript(genesisBlockHash));
5307+
5308+
txnouttype txntype;
5309+
if (!IsStandard(GetScriptForDestination(nulldata), txntype)) {
5310+
throw JSONRPCError(RPC_TYPE_ERROR, "Resulting scriptPubKey is non-standard. Ensure pak=reject is not set");
5311+
}
5312+
5313+
mapValue_t mapValue;
5314+
CCoinControl no_coin_control; // This is a deprecated API
5315+
CTransactionRef tx = SendMoney(pwallet, address, nAmount, subtract_fee, no_coin_control, std::move(mapValue), {});
5316+
5317+
pwallet->SetOfflineCounter(counter+1);
5318+
5319+
std::stringstream ss;
5320+
ss << counter;
5321+
5322+
UniValue obj(UniValue::VOBJ);
5323+
obj.push_back(Pair("txid", tx->GetHash().GetHex()));
5324+
obj.push_back(Pair("bitcoin_address", EncodeDestination(bitcoin_address)));
5325+
obj.push_back(Pair("derivation_path", "/0/"+ss.str()));
5326+
obj.push_back(Pair("bitcoin_xpub", EncodeExtPubKey(xpub)));
5327+
return obj;
51545328
}
51555329

51565330
// We only expose the appropriate peg-out method type per network

0 commit comments

Comments
 (0)