diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 68e11b4c2ba0f..cff7fbdaa4fda 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -83,6 +83,7 @@ 'rpcnamedargs.py', 'listsinceblock.py', 'p2p-leaktests.py', + 'htlc.py' ] ZMQ_SCRIPTS = [ diff --git a/qa/rpc-tests/htlc.py b/qa/rpc-tests/htlc.py new file mode 100755 index 0000000000000..0de0c1a548576 --- /dev/null +++ b/qa/rpc-tests/htlc.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2016 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test usage of HTLC transactions with RPC +# + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from test_framework.mininode import sha256, ripemd160 + +class HTLCTest(BitcoinTestFramework): + BUYER = 0 + SELLER = 1 + + def __init__(self): + super().__init__() + self.num_nodes = 2 + self.setup_clean_chain = False + + def setup_network(self): + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir) + connect_nodes_bi(self.nodes, 0, 1) + self.is_network_split = False + self.sync_all() + + def activateCSV(self): + # activation should happen at block height 432 (3 periods) + min_activation_height = 432 + height = self.nodes[0].getblockcount() + assert(height < 432) + self.nodes[0].generate(432-height) + assert(get_bip9_status(self.nodes[0], 'csv')['status'] == 'active') + sync_blocks(self.nodes) + + def run_test(self): + # Activate checksequenceverify + self.activateCSV() + + # The buyer wishes to purchase the preimage of "254e38932fdb9fc27f82aac2a5cc6d789664832383e3cf3298f8c120812712db" + image = "254e38932fdb9fc27f82aac2a5cc6d789664832383e3cf3298f8c120812712db" + # The seller wishes to sell the preimage + preimage = "696c6c756d696e617469" + + assert_equal(image, bytes_to_hex_str(sha256(hex_str_to_bytes(preimage)))) + + self.run_tests_with_preimage_and_image(preimage, image) + + image = "5bb50b07a120dba7f1aae9623825071bc1fe4b40" + preimage = "ffffffffff" + + assert_equal(image, bytes_to_hex_str(ripemd160(hex_str_to_bytes(preimage)))) + + self.run_tests_with_preimage_and_image(preimage, image) + + def run_tests_with_preimage_and_image(self, preimage, image): + self.test_refund(image, 10) + self.test_refund(image, 100) + + assert_equal(False, self.test_sell(image, 10)) + self.nodes[self.SELLER].importpreimage(preimage) + assert(self.test_sell(image, 10)) + + def test_refund(self, image, num_blocks): + (seller_spending_tx, buyer_refund_tx) = self.fund_htlc(image, num_blocks) + + # The buyer signs the refund transaction + buyer_sign = self.nodes[self.BUYER].signrawtransaction(buyer_refund_tx) + assert_equal(buyer_sign["complete"], True) + + # The buyer should not be able to spend the funds yet + assert(self.expect_cannot_send(self.BUYER, buyer_sign["hex"])) + + # After appearing in num_blocks number of blocks, the buyer can. + self.nodes[self.BUYER].generate(num_blocks-1) + sync_blocks(self.nodes) + + self.nodes[self.BUYER].sendrawtransaction(buyer_sign["hex"]) + + self.nodes[self.BUYER].generate(1) + sync_blocks(self.nodes) + + def test_sell(self, image, num_blocks): + (seller_spending_tx, buyer_refund_tx) = self.fund_htlc(image, num_blocks) + + # The seller signs the spending transaction + seller_sign = self.nodes[self.SELLER].signrawtransaction(seller_spending_tx) + + if seller_sign["complete"] == False: + return False + + self.nodes[self.SELLER].sendrawtransaction(seller_sign["hex"]) + + return True + + def fund_htlc(self, image, num_blocks): + buyer_addr = self.nodes[self.BUYER].getnewaddress("") + buyer_pubkey = self.nodes[self.BUYER].validateaddress(buyer_addr)["pubkey"] + seller_addr = self.nodes[self.SELLER].getnewaddress("") + seller_pubkey = self.nodes[self.SELLER].validateaddress(seller_addr)["pubkey"] + + # Create the HTLC transaction + htlc = self.nodes[self.BUYER].createhtlc(seller_pubkey, buyer_pubkey, image, str(num_blocks)) + + # Import into wallets + self.nodes[self.BUYER].importaddress(htlc["redeemScript"], "", False, True) + self.nodes[self.SELLER].importaddress(htlc["redeemScript"], "", False, True) + + # Buyer sends the funds + self.nodes[self.BUYER].sendtoaddress(htlc["address"], 10) + self.nodes[self.BUYER].generate(1) + sync_blocks(self.nodes) + + funding_tx = False + + for tx in self.nodes[self.SELLER].listtransactions("*", 500, 0, True): + if tx["address"] == htlc["address"]: + funding_tx = tx + + assert(funding_tx != False) + + seller_spending_tx = self.nodes[self.SELLER].createrawtransaction( + [{"txid": funding_tx["txid"], "vout": funding_tx["vout"]}], + {seller_addr: 9.99} + ) + buyer_refund_tx = self.nodes[self.BUYER].createrawtransaction( + [{"txid": funding_tx["txid"], "vout": funding_tx["vout"], "sequence": num_blocks}], + {buyer_addr: 9.99} + ) + + # TODO: why isn't this already version 2? or shouldn't the signer figure + # out that it's necessary? + seller_spending_tx = "02" + seller_spending_tx[2:] + buyer_refund_tx = "02" + buyer_refund_tx[2:] + + return (seller_spending_tx, buyer_refund_tx) + + def expect_cannot_send(self, i, tx): + exception_triggered = False + + try: + self.nodes[i].sendrawtransaction(tx) + except JSONRPCException: + exception_triggered = True + + return exception_triggered + +if __name__ == '__main__': + HTLCTest().main() + diff --git a/src/keystore.cpp b/src/keystore.cpp index b17567e99b72e..f2154a91ce165 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -66,6 +66,33 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) return false; } +bool CBasicKeyStore::GetPreimage( + const std::vector& image, + std::vector& preimage +) const +{ + LOCK(cs_KeyStore); + + PreimageMap::const_iterator it = mapPreimages.find(image); + if (it != mapPreimages.end()) { + preimage = it->second; + + return true; + } + return false; +} + +bool CBasicKeyStore::AddPreimage( + const std::vector& image, + const std::vector& preimage +) +{ + LOCK(cs_KeyStore); + + mapPreimages[image] = preimage; + return true; +} + static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) { //TODO: Use Solver to extract this? diff --git a/src/keystore.h b/src/keystore.h index d9290722e1b1a..1f2692e068d39 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -39,6 +39,17 @@ class CKeyStore virtual bool HaveCScript(const CScriptID &hash) const =0; virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const =0; + //! Support for HTLC preimages + virtual bool GetPreimage( + const std::vector& image, + std::vector& preimage + ) const =0; + + virtual bool AddPreimage( + const std::vector& image, + const std::vector& preimage + ) =0; + //! Support for Watch-only addresses virtual bool AddWatchOnly(const CScript &dest) =0; virtual bool RemoveWatchOnly(const CScript &dest) =0; @@ -50,6 +61,7 @@ typedef std::map KeyMap; typedef std::map WatchKeyMap; typedef std::map ScriptMap; typedef std::set WatchOnlySet; +typedef std::map, std::vector> PreimageMap; /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore @@ -59,6 +71,7 @@ class CBasicKeyStore : public CKeyStore WatchKeyMap mapWatchKeys; ScriptMap mapScripts; WatchOnlySet setWatchOnly; + PreimageMap mapPreimages; public: bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); @@ -102,6 +115,16 @@ class CBasicKeyStore : public CKeyStore virtual bool HaveCScript(const CScriptID &hash) const; virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const; + bool GetPreimage( + const std::vector& image, + std::vector& preimage + ) const; + + bool AddPreimage( + const std::vector& image, + const std::vector& preimage + ); + virtual bool AddWatchOnly(const CScript &dest); virtual bool RemoveWatchOnly(const CScript &dest); virtual bool HaveWatchOnly(const CScript &dest) const; diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index ec398f6627f19..32b09cb6110a1 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -46,6 +46,8 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool w return false; if (m < 1 || m > n) return false; + } else if (whichType == TX_HTLC) { + return false; } else if (whichType == TX_NULL_DATA && (!fAcceptDatacarrier || scriptPubKey.size() > nMaxDatacarrierBytes)) return false; diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index c84eab7d23866..08f874c48573e 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -224,6 +224,42 @@ UniValue validateaddress(const JSONRPCRequest& request) return ret; } +void _publickey_from_string(const std::string &ks, CPubKey &out) +{ +#ifdef ENABLE_WALLET + // Case 1: Bitcoin address and we have full public key: + CBitcoinAddress address(ks); + if (pwalletMain && address.IsValid()) + { + CKeyID keyID; + if (!address.GetKeyID(keyID)) + throw runtime_error( + strprintf("%s does not refer to a key",ks)); + CPubKey vchPubKey; + if (!pwalletMain->GetPubKey(keyID, vchPubKey)) + throw runtime_error( + strprintf("no full public key for address %s",ks)); + if (!vchPubKey.IsFullyValid()) + throw runtime_error(" Invalid public key: "+ks); + out = vchPubKey; + } + + // Case 2: hex public key + else +#endif + if (IsHex(ks)) + { + CPubKey vchPubKey(ParseHex(ks)); + if (!vchPubKey.IsFullyValid()) + throw runtime_error(" Invalid public key: "+ks); + out = vchPubKey; + } + else + { + throw runtime_error(" Invalid public key: "+ks); + } +} + /** * Used by addmultisigaddress / createmultisig: */ @@ -246,38 +282,7 @@ CScript _createmultisig_redeemScript(const UniValue& params) for (unsigned int i = 0; i < keys.size(); i++) { const std::string& ks = keys[i].get_str(); -#ifdef ENABLE_WALLET - // Case 1: Bitcoin address and we have full public key: - CBitcoinAddress address(ks); - if (pwalletMain && address.IsValid()) - { - CKeyID keyID; - if (!address.GetKeyID(keyID)) - throw runtime_error( - strprintf("%s does not refer to a key",ks)); - CPubKey vchPubKey; - if (!pwalletMain->GetPubKey(keyID, vchPubKey)) - throw runtime_error( - strprintf("no full public key for address %s",ks)); - if (!vchPubKey.IsFullyValid()) - throw runtime_error(" Invalid public key: "+ks); - pubkeys[i] = vchPubKey; - } - - // Case 2: hex public key - else -#endif - if (IsHex(ks)) - { - CPubKey vchPubKey(ParseHex(ks)); - if (!vchPubKey.IsFullyValid()) - throw runtime_error(" Invalid public key: "+ks); - pubkeys[i] = vchPubKey; - } - else - { - throw runtime_error(" Invalid public key: "+ks); - } + _publickey_from_string(ks, pubkeys[i]); } CScript result = GetScriptForMultisig(nRequired, pubkeys); @@ -331,6 +336,89 @@ UniValue createmultisig(const JSONRPCRequest& request) return result; } + +UniValue createhtlc(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 4) { + string msg = "createhtlc seller_key refund_key hash timeout_type timeout\n" + "\nCreates an address whose funds can be unlocked with a preimage or as a refund\n" + "It returns a json object with the address and redeemScript.\n" + + "\nArguments:\n" + "1. seller_key (string, required) The public key of the possessor of the preimage.\n" + "2. refund_key (string, required) The public key of the recipient of the refund.\n" + "3. hash (string, required) SHA256 or RIPEMD160 hash of the preimage.\n" + "4. timeout (string, required) Timeout of the contract (denominated in blocks) relative to its placement in the blockchain\n" + + "\nResult:" + "{\n" + " \"address\":\"htlcaddress\" (string) The value of the new HTLC address.\n" + " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n" + "}\n" + + "\nExamples:\n" + "\nPay someone for the preimage of 254e38932fdb9fc27f82aac2a5cc6d789664832383e3cf3298f8c120812712db\n" + + HelpExampleCli("createhtlc", "0333ffc4d18c7b2adbd1df49f5486030b0b70449c421189c2c0f8981d0da9669af 0333ffc4d18c7b2adbd1df49f5486030b0b70449c421189c2c0f8981d0da9669af 254e38932fdb9fc27f82aac2a5cc6d789664832383e3cf3298f8c120812712db 10") + + "\nAs a json rpc call\n" + + HelpExampleRpc("createhtlc", "0333ffc4d18c7b2adbd1df49f5486030b0b70449c421189c2c0f8981d0da9669af, 0333ffc4d18c7b2adbd1df49f5486030b0b70449c421189c2c0f8981d0da9669af, 254e38932fdb9fc27f82aac2a5cc6d789664832383e3cf3298f8c120812712db, 10") + ; + + throw runtime_error(msg); + } + + CPubKey seller_key; + CPubKey refund_key; + _publickey_from_string(request.params[0].get_str(), seller_key); + _publickey_from_string(request.params[1].get_str(), refund_key); + + std::string hs = request.params[2].get_str(); + std::vector image; + opcodetype hasher; + if (IsHex(hs)) { + image = ParseHex(hs); + + if (image.size() == 32) { + hasher = OP_SHA256; + } else if (image.size() == 20) { + hasher = OP_RIPEMD160; + } else { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid hash image length, 32 (SHA256) and 20 (RIPEMD160) accepted"); + } + } else { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid hash image"); + } + + uint32_t blocks; + // This will make request.params[3] forward compatible with '10h'/'3d' style timeout + // values if it is extended in the future. + { + // FIXME: This duplicates the functionality of `ParseNonRFCJSONValue` in + // `client`, so perhaps it should just be added to univalue itself? + UniValue timeout; + if (!timeout.read(std::string("[")+request.params[3].get_str()+std::string("]")) || + !timeout.isArray() || timeout.size()!=1) + throw runtime_error(string("Error parsing JSON:")+request.params[3].get_str()); + + blocks = timeout[0].get_int(); + } + + if (blocks >= CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid block denominated relative timeout"); + } + + CScript inner = GetScriptForHTLC(seller_key, refund_key, image, + blocks, hasher, OP_CHECKSEQUENCEVERIFY); + + CScriptID innerID(inner); + CBitcoinAddress address(innerID); + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("address", address.ToString())); + result.push_back(Pair("redeemScript", HexStr(inner.begin(), inner.end()))); + + return result; +} + UniValue verifymessage(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) @@ -516,6 +604,7 @@ static const CRPCCommand commands[] = { "control", "getmemoryinfo", &getmemoryinfo, true, {} }, { "util", "validateaddress", &validateaddress, true, {"address"} }, /* uses wallet if enabled */ { "util", "createmultisig", &createmultisig, true, {"nrequired","keys"} }, + { "util", "createhtlc", &createhtlc, true, {"seller_key","refund_key","hash","timeout"} }, { "util", "verifymessage", &verifymessage, true, {"address","signature","message"} }, { "util", "signmessagewithprivkey", &signmessagewithprivkey, true, {"privkey","message"} }, diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 608a8de8f57ee..5a74e5ac62176 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -125,6 +125,19 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& break; } + case TX_HTLC: + { + // Only consider HTLC's "mine" if we own ALL the keys + // involved. + vector keys; + keys.push_back(vSolutions[1]); + keys.push_back(vSolutions[3]); + if (HaveKeys(keys, keystore) == keys.size()) { + return ISMINE_SPENDABLE; + } + break; + } + case TX_MULTISIG: { // Only consider transactions "mine" if we own ALL the diff --git a/src/script/script.h b/src/script/script.h index 654dff4625ddc..0049f21d95933 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -179,6 +179,10 @@ enum opcodetype // template matching params + OP_BLOB20 = 0xf7, + OP_BLOB32 = 0xf8, + OP_U32INT = 0xf9, + OP_SMALLINTEGER = 0xfa, OP_PUBKEYS = 0xfb, OP_PUBKEYHASH = 0xfd, diff --git a/src/script/sign.cpp b/src/script/sign.cpp index b008df259178d..692ec6872ef21 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -108,6 +108,30 @@ static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptP ret.push_back(valtype()); // workaround CHECKMULTISIG bug return (SignN(vSolutions, creator, scriptPubKey, ret, sigversion)); + case TX_HTLC: + { + std::vector image(vSolutions[0]); + std::vector preimage; + + if (creator.KeyStore().GetPreimage(image, preimage)) { + keyID = CPubKey(vSolutions[1]).GetID(); + if (!Sign1(keyID, creator, scriptPubKey, ret, sigversion)) { + return false; + } + + ret.push_back(preimage); + ret.push_back({1}); + } else { + keyID = CPubKey(vSolutions[3]).GetID(); + if (!Sign1(keyID, creator, scriptPubKey, ret, sigversion)) { + return false; + } + + ret.push_back(valtype()); + } + return true; + } + case TX_WITNESS_V0_KEYHASH: ret.push_back(vSolutions[0]); return true; @@ -312,6 +336,7 @@ static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignature switch (txType) { case TX_NONSTANDARD: + case TX_HTLC: case TX_NULL_DATA: // Don't know anything about this, assume bigger one is correct: if (sigs1.script.size() >= sigs2.script.size()) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 4b9bec9aa18a4..094df24b679d4 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -27,6 +27,7 @@ const char* GetTxnOutputType(txnouttype t) { case TX_NONSTANDARD: return "nonstandard"; case TX_PUBKEY: return "pubkey"; + case TX_HTLC: return "htlc"; case TX_PUBKEYHASH: return "pubkeyhash"; case TX_SCRIPTHASH: return "scripthash"; case TX_MULTISIG: return "multisig"; @@ -54,6 +55,29 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector accepted_hashers[] = { + make_pair(OP_SHA256, OP_BLOB32), + make_pair(OP_RIPEMD160, OP_BLOB20) + }; + const opcodetype accepted_timeout_ops[] = {OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY}; + + BOOST_FOREACH(auto hasher, accepted_hashers) { + BOOST_FOREACH(opcodetype timeout_op, accepted_timeout_ops) { + mTemplates.insert(make_pair(TX_HTLC, CScript() + << OP_IF + << hasher.first << hasher.second << OP_EQUALVERIFY << OP_PUBKEY + << OP_ELSE + << OP_U32INT << timeout_op << OP_DROP << OP_PUBKEY + << OP_ENDIF + << OP_CHECKSIG + )); + } + } + } } vSolutionsRet.clear(); @@ -166,6 +190,33 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector std::numeric_limits::max()) { + break; + } + + vSolutionsRet.push_back(vch1); + } + else if (opcode2 == OP_BLOB32) + { + if (vch1.size() != sizeof(uint256)) + break; + vSolutionsRet.push_back(vch1); + } + else if (opcode2 == OP_BLOB20) + { + if (vch1.size() != sizeof(uint160)) + break; + vSolutionsRet.push_back(vch1); + } else if (opcode1 != opcode2 || vch1 != vch2) { // Others must match exactly @@ -237,6 +288,28 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, vecto if (addressRet.empty()) return false; } + else if (typeRet == TX_HTLC) + { + // Seller + { + CPubKey pubKey(vSolutions[1]); + if (pubKey.IsValid()) { + CTxDestination address = pubKey.GetID(); + addressRet.push_back(address); + } + } + // Refund + { + CPubKey pubKey(vSolutions[3]); + if (pubKey.IsValid()) { + CTxDestination address = pubKey.GetID(); + addressRet.push_back(address); + } + } + + if (addressRet.empty()) + return false; + } else { nRequiredRet = 1; @@ -301,6 +374,32 @@ CScript GetScriptForMultisig(int nRequired, const std::vector& keys) return script; } +CScript GetScriptForHTLC(const CPubKey& seller, + const CPubKey& refund, + const std::vector image, + uint32_t timeout, + opcodetype hasher_type, + opcodetype timeout_type) +{ + CScript script; + + script << OP_IF; + script << hasher_type << image << OP_EQUALVERIFY << ToByteVector(seller); + script << OP_ELSE; + + if (timeout <= 16) { + script << CScript::EncodeOP_N(timeout); + } else { + script << CScriptNum(timeout); + } + + script << timeout_type << OP_DROP << ToByteVector(refund); + script << OP_ENDIF; + script << OP_CHECKSIG; + + return script; +} + CScript GetScriptForWitness(const CScript& redeemscript) { CScript ret; diff --git a/src/script/standard.h b/src/script/standard.h index 097e0c3748c34..ac023d66663c0 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -45,6 +45,7 @@ static const unsigned int MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH; enum txnouttype { TX_NONSTANDARD, + TX_HTLC, // 'standard' transaction types: TX_PUBKEY, TX_PUBKEYHASH, @@ -79,6 +80,12 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std:: CScript GetScriptForDestination(const CTxDestination& dest); CScript GetScriptForRawPubKey(const CPubKey& pubkey); CScript GetScriptForMultisig(int nRequired, const std::vector& keys); +CScript GetScriptForHTLC(const CPubKey& seller, + const CPubKey& refund, + const std::vector image, + uint32_t timeout, + opcodetype hasher_type, + opcodetype timeout_type); CScript GetScriptForWitness(const CScript& redeemscript); #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 20a3cbda1e78b..f06eaa1277f4c 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -640,6 +640,49 @@ UniValue dumpwallet(const JSONRPCRequest& request) return NullUniValue; } +UniValue importpreimage(const JSONRPCRequest& request) +{ + if (!EnsureWalletIsAvailable(request.fHelp)) + return NullUniValue; + + if (request.fHelp || request.params.size() != 1) + throw runtime_error( + "importpreimage \"data\"\n" + "\nImports a preimage for use in HTLC transactions. Only remains in memory.\n" + "\nArguments:\n" + "1. \"data\" (string, required) The preimage of a SHA256 or RIPEMD160 hash\n" + "\nExamples:\n" + + HelpExampleCli("importpreimage", "\"mylittlesecret\"") + + HelpExampleRpc("importpreimage", "\"mylittlesecret\"") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + vector preimage = ParseHexV(request.params[0], "preimage"); + + // SHA256 + { + std::vector vch(32); + CSHA256 hash; + hash.Write(preimage.data(), preimage.size()); + hash.Finalize(vch.data()); + + pwalletMain->AddPreimage(vch, preimage); + } + + // RIPEMD160 + { + std::vector vch(20); + CRIPEMD160 hash; + hash.Write(preimage.data(), preimage.size()); + hash.Finalize(vch.data()); + + pwalletMain->AddPreimage(vch, preimage); + } + + return NullUniValue; +} + UniValue ProcessImport(const UniValue& data, const int64_t timestamp) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d785c95ae0acd..6f3ffa67524d2 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2989,6 +2989,7 @@ extern UniValue importprivkey(const JSONRPCRequest& request); extern UniValue importaddress(const JSONRPCRequest& request); extern UniValue importpubkey(const JSONRPCRequest& request); extern UniValue dumpwallet(const JSONRPCRequest& request); +extern UniValue importpreimage(const JSONRPCRequest& request); extern UniValue importwallet(const JSONRPCRequest& request); extern UniValue importprunedfunds(const JSONRPCRequest& request); extern UniValue removeprunedfunds(const JSONRPCRequest& request); @@ -3006,6 +3007,7 @@ static const CRPCCommand commands[] = { "wallet", "bumpfee", &bumpfee, true, {"txid", "options"} }, { "wallet", "dumpprivkey", &dumpprivkey, true, {"address"} }, { "wallet", "dumpwallet", &dumpwallet, true, {"filename"} }, + { "wallet", "importpreimage", &importpreimage, true, {"preimage"} }, { "wallet", "encryptwallet", &encryptwallet, true, {"passphrase"} }, { "wallet", "getaccountaddress", &getaccountaddress, true, {"account"} }, { "wallet", "getaccount", &getaccount, true, {"address"} },