mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Add sendtomainchain RPC call which is used when -pak_enforce is enabled
This commit is contained in:
parent
567de67afc
commit
3cfe2d49ef
1 changed files with 175 additions and 1 deletions
|
|
@ -5150,7 +5150,181 @@ UniValue sendtomainchain_base(const JSONRPCRequest& request)
|
|||
|
||||
UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
||||
{
|
||||
return NullUniValue;
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
|
||||
throw std::runtime_error(
|
||||
"sendtomainchain "" amount ( subtractfeefromamount ) \n"
|
||||
"\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"
|
||||
+ HelpRequiringPassphrase(pwallet) +
|
||||
"\nArguments:\n"
|
||||
"1. \"address\" (string, required) Must be \"\". Only for non-PAK `sendtomainchain` compatibility.\n"
|
||||
"2. \"amount\" (numeric, required) The amount being sent to `bitcoin_address`.\n"
|
||||
"3. \"subtractfeefromamount\" (boolean, optional, default=false) The fee will be deducted from the amount being pegged-out.\n"
|
||||
"\nResult:\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
"\"bitcoin_address\" (string) The destination address on Bitcoin mainchain."
|
||||
"\"txid\" (string) Transaction ID of the resulting Liquid transaction\n"
|
||||
"\"bitcoin_xpub\" (string) The xpubkey of the child destination address.\n"
|
||||
"\"derivation_path\" (string) The derivation path in text that leads to `bitcoin_address` from the `bitcoin_xpub`.\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("sendtomainchain", "\"\" 0.1")
|
||||
+ HelpExampleRpc("sendtomainchain", "\"\" 0.1")
|
||||
);
|
||||
|
||||
LOCK2(cs_main, 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.0001 BTC");
|
||||
|
||||
bool subtract_fee = false;
|
||||
if (request.params.size() > 2) {
|
||||
subtract_fee = request.params[1].get_bool();
|
||||
}
|
||||
|
||||
CPAKList paklist = g_paklist_blockchain;
|
||||
if (g_paklist_config) {
|
||||
paklist = *g_paklist_config;
|
||||
}
|
||||
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.");
|
||||
}
|
||||
|
||||
std::vector<uint32_t> vPath;
|
||||
vPath.push_back(0);
|
||||
vPath.push_back((uint32_t)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_xpub cannot be found in same entry as known liquid_pak");
|
||||
}
|
||||
|
||||
// Get online PAK
|
||||
CKey masterOnlineKey;
|
||||
if (!pwallet->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(vPath, 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());
|
||||
assert(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);
|
||||
assert(ret == 1);
|
||||
assert(btclen == 33);
|
||||
assert(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, masterOnlineKey.begin(), &tweakSum[0], whitelistindex, NULL, NULL) != 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());
|
||||
assert(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);
|
||||
assert(outlen == expectedOutputSize);
|
||||
std::vector<unsigned char> whitelistproof(output, output + expectedOutputSize / sizeof(unsigned char));
|
||||
|
||||
// Bitcoin address
|
||||
CTxDestination bitcoin_address = DeriveBitcoinOfflineAddress(xpub, counter);
|
||||
CScript scriptPubKeyMainchain(GetScriptForDestination(bitcoin_address));
|
||||
|
||||
uint256 genesisBlockHash = Params().ParentGenesisBlockHash();
|
||||
NullData nulldata;
|
||||
nulldata << std::vector<unsigned char>(genesisBlockHash.begin(), genesisBlockHash.end());
|
||||
nulldata << std::vector<unsigned char>(scriptPubKeyMainchain.begin(), scriptPubKeyMainchain.end());
|
||||
nulldata << btcpubkeybytes;
|
||||
nulldata << whitelistproof;
|
||||
CTxDestination address(nulldata);
|
||||
assert(GetScriptForDestination(nulldata).IsPegoutScript(genesisBlockHash));
|
||||
|
||||
txnouttype txntype;
|
||||
if (!IsStandard(GetScriptForDestination(nulldata), txntype)) {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Resulting scriptPubKey is non-standard. Ensure pak=reject is not set");
|
||||
}
|
||||
|
||||
mapValue_t mapValue;
|
||||
CCoinControl no_coin_control; // This is a deprecated API
|
||||
CTransactionRef tx = SendMoney(pwallet, address, nAmount, subtract_fee, no_coin_control, std::move(mapValue), {});
|
||||
|
||||
pwallet->SetOfflineCounter(counter+1);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << counter;
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("txid", tx->GetHash().GetHex()));
|
||||
obj.push_back(Pair("bitcoin_address", EncodeDestination(bitcoin_address)));
|
||||
obj.push_back(Pair("derivation_path", "/0/"+ss.str()));
|
||||
obj.push_back(Pair("bitcoin_xpub", EncodeExtPubKey(xpub)));
|
||||
return obj;
|
||||
}
|
||||
|
||||
// We only expose the appropriate peg-out method type per network
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue