Skip to content

Commit

Permalink
SPV Bitcoin HTLC (#308)
Browse files Browse the repository at this point in the history
* SPV Bitcoin HTLC

* Check script size as we decode it. Add test. Other minor fixes.

* Check fee is not less than 1Sat/byte. Tidy fee calc.
  • Loading branch information
Bushstar authored Apr 8, 2021
1 parent a011b9d commit c820ad1
Show file tree
Hide file tree
Showing 20 changed files with 1,426 additions and 222 deletions.
35 changes: 35 additions & 0 deletions src/masternodes/mn_rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// file LICENSE or http://www.opensource.org/licenses/mit-license.php.

#include <masternodes/mn_rpc.h>

#include <base58.h>
#include <policy/settings.h>

extern bool EnsureWalletIsAvailable(bool avoidException); // in rpcwallet.cpp
Expand Down Expand Up @@ -378,6 +380,39 @@ CWallet* GetWallet(const JSONRPCRequest& request) {
return pwallet;
}

CPubKey PublickeyFromString(const std::string &pubkey)
{
if (!IsHex(pubkey) || (pubkey.length() != 66 && pubkey.length() != 130))
{
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + pubkey);
}

return HexToPubKey(pubkey);
}

CScript CreateScriptForHTLC(const JSONRPCRequest& request, uint32_t& blocks, std::vector<unsigned char>& image)
{
CPubKey seller_key = PublickeyFromString(request.params[0].get_str());
CPubKey refund_key = PublickeyFromString(request.params[1].get_str());

{
UniValue timeout;
if (!timeout.read(std::string("[") + request.params[2].get_str() + std::string("]")) || !timeout.isArray() || timeout.size() != 1)
{
throw JSONRPCError(RPC_TYPE_ERROR, "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");
}

return GetScriptForHTLC(seller_key, refund_key, image, blocks);
}

UniValue setgov(const JSONRPCRequest& request) {
CWallet* const pwallet = GetWallet(request);

Expand Down
1 change: 1 addition & 0 deletions src/masternodes/mn_rpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@ std::vector<CTxIn> GetAuthInputsSmart(CWallet* const pwallet, int32_t txVersion,
std::string ScriptToString(CScript const& script);
CAccounts GetAllMineAccounts(CWallet* const pwallet);
CAccounts SelectAccountsByTargetBalances(const CAccounts& accounts, const CBalances& targetBalances, AccountSelectionMode selectionMode);
CScript CreateScriptForHTLC(const JSONRPCRequest& request, uint32_t &blocks, std::vector<unsigned char>& image);

#endif // DEFI_MASTERNODES_MN_RPC_H
3 changes: 1 addition & 2 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,13 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "spv_estimateanchorcost", 0, "feerate" },
{ "spv_rescan", 0, "height" },
{ "spv_gettxconfirmations", 0, "txhash" },
{ "spv_splitutxo", 0, "parts" },
{ "spv_splitutxo", 1, "amount" },
{ "spv_setlastheight", 0, "height" },
{ "spv_listanchors", 0, "minBtcHeight" },
{ "spv_listanchors", 1, "maxBtcHeight" },
{ "spv_listanchors", 2, "minConfs" },
{ "spv_listanchors", 3, "maxConfs" },
{ "spv_sendtoaddress", 1, "amount" },
{ "spv_sendtoaddress", 2, "feerate" },

{ "createpoolpair", 0, "metadata" },
{ "createpoolpair", 1, "inputs" },
Expand Down
24 changes: 24 additions & 0 deletions src/script/standard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,30 @@ CScript GetScriptForWitness(const CScript& redeemscript)
return GetScriptForDestination(WitnessV0ScriptHash(redeemscript));
}

CScript GetScriptForHTLC(const CPubKey& seller, const CPubKey& refund, const std::vector<unsigned char> image, uint32_t timeout)
{
CScript script;

script << OP_IF;
script << OP_SHA256 << image << OP_EQUALVERIFY << ToByteVector(seller);
script << OP_ELSE;

if (timeout <= 16)
{
script << CScript::EncodeOP_N(timeout);
}
else
{
script << CScriptNum(timeout);
}

script << OP_CHECKSEQUENCEVERIFY << OP_DROP << ToByteVector(refund);
script << OP_ENDIF;
script << OP_CHECKSIG;

return script;
}

bool IsValidDestination(const CTxDestination& dest) {
return dest.which() != 0;
}
3 changes: 3 additions & 0 deletions src/script/standard.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey);
/** Generate a multisig script. */
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);

/** Generate a Hash-Timelock Script script. */
CScript GetScriptForHTLC(const CPubKey& seller, const CPubKey& refund, const std::vector<unsigned char> image, uint32_t timeout);

/**
* Generate a pay-to-witness script for the given redeem script. If the redeem
* script is P2PK or P2PKH, this returns a P2WPKH script, otherwise it returns a
Expand Down
81 changes: 72 additions & 9 deletions src/spv/bitcoin/BRTransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@

#include "BRTransaction.h"
#include "BRKey.h"
#include "BRAddress.h"
#include "BRArray.h"
#include <stdlib.h>
#include <inttypes.h>
#include <limits.h>
#include <time.h>

#define TX_VERSION 0x00000001
#include <util/strencodings.h>

#define TX_LOCKTIME 0x00000000
#define SIGHASH_ALL 0x01 // default, sign all of the outputs
#define SIGHASH_NONE 0x02 // sign none of the outputs, I don't care where the bitcoins go
Expand Down Expand Up @@ -351,12 +351,12 @@ static size_t _BRTransactionData(const BRTransaction *tx, uint8_t *data, size_t
}

// returns a newly allocated empty transaction that must be freed by calling BRTransactionFree()
BRTransaction *BRTransactionNew(void)
BRTransaction *BRTransactionNew(uint32_t version)
{
BRTransaction *tx = (BRTransaction *)calloc(1, sizeof(*tx));

assert(tx != NULL);
tx->version = TX_VERSION;
tx->version = version;
array_new(tx->inputs, 1);
array_new(tx->outputs, 2);
tx->lockTime = TX_LOCKTIME;
Expand Down Expand Up @@ -585,6 +585,28 @@ size_t BRTransactionSize(const BRTransaction *tx)
return size + witSize;
}

size_t BRTransactionHTLCSize(const BRTransaction *tx, const size_t sigSize)
{
BRTxInput *input;
size_t size;

size = (tx) ? 8 + BRVarIntSize(tx->inCount) + BRVarIntSize(tx->outCount) : 0;

for (size_t i = 0; i < tx->inCount; i++)
{
input = &tx->inputs[i];

size += sigSize + TX_HTLC_INPUT_NOSIG;
}

for (size_t i = 0; i < tx->outCount; i++)
{
size += sizeof(uint64_t) + BRVarIntSize(tx->outputs[i].scriptLen) + tx->outputs[i].scriptLen;
}

return size;
}

// virtual transaction size as defined by BIP141: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
size_t BRTransactionVSize(const BRTransaction *tx)
{
Expand Down Expand Up @@ -637,7 +659,7 @@ int BRTransactionIsSigned(const BRTransaction *tx)
// adds signatures to any inputs with NULL signatures that can be signed with any keys
// forkId is 0 for bitcoin, 0x40 for b-cash, 0x4f for b-gold
// returns true if tx is signed
int BRTransactionSign(BRTransaction *tx, int forkId, BRKey keys[], size_t keysCount)
int BRTransactionSign(BRTransaction *tx, int forkId, BRKey keys[], size_t keysCount, HTLCScriptType htlcType, const uint8_t* seed, const uint8_t *redeemScript)
{
UInt160 pkh[keysCount];
size_t i, j;
Expand All @@ -651,17 +673,28 @@ int BRTransactionSign(BRTransaction *tx, int forkId, BRKey keys[], size_t keysCo

for (i = 0; tx && i < tx->inCount; i++) {
BRTxInput *input = &tx->inputs[i];
const uint8_t *hash = BRScriptPKH(input->script, input->scriptLen);
const uint8_t *hash;
UInt160 hash160 = UINT160_ZERO;

if (htlcType == ScriptTypeNone)
{
hash = BRScriptPKH(input->script, input->scriptLen);
}
else
{
hash160 = BRHTLCScriptPKH(input->script, input->scriptLen, htlcType);
hash = &hash160.u8[0];
}

j = 0;
while (j < keysCount && (! hash || ! UInt160Eq(pkh[j], UInt160Get(hash)))) j++;
if (j >= keysCount) continue;

const uint8_t *elems[BRScriptElements(NULL, 0, input->script, input->scriptLen)];
size_t elemsCount = BRScriptElements(elems, sizeof(elems)/sizeof(*elems), input->script, input->scriptLen);
uint8_t pubKey[BRKeyPubKey(&keys[j], NULL, 0)];
size_t pkLen = BRKeyPubKey(&keys[j], pubKey, sizeof(pubKey));
uint8_t sig[73], script[1 + sizeof(sig) + 1 + sizeof(pubKey)];
uint8_t sig[73], script[1 + sizeof(sig) + 1 + (seed ? seed[0] + (htlcType == ScriptTypeSeller ? 1 /* OP_1 */ : 0) + 1 /* pushdata */ + redeemScript[0]: sizeof(pubKey))];
size_t sigLen, scriptLen;
UInt256 md = UINT256_ZERO;

Expand Down Expand Up @@ -689,6 +722,36 @@ int BRTransactionSign(BRTransaction *tx, int forkId, BRKey keys[], size_t keysCo
BRTxInputSetSignature(input, script, scriptLen);
BRTxInputSetWitness(input, script, 0);
}
else if (elemsCount == 12 && *elems[0] == OP_IF && *elems[1] == OP_SHA256 && *elems[3] == OP_EQUALVERIFY && // HTLC
*elems[5] == OP_ELSE && *elems[7] == OP_CHECKSEQUENCEVERIFY && *elems[8] == OP_DROP &&
*elems[10] == OP_ENDIF && *elems[11] == OP_CHECKSIG)
{
uint8_t data[_BRTransactionData(tx, NULL, 0, i, forkId | SIGHASH_ALL)];
size_t dataLen = _BRTransactionData(tx, data, sizeof(data), i, forkId | SIGHASH_ALL);

BRSHA256_2(&md, data, dataLen);
sigLen = BRKeySign(&keys[j], sig, sizeof(sig) - 1, md);
sig[sigLen++] = forkId | SIGHASH_ALL;

// Add signature
scriptLen = BRScriptPushData(script, sizeof(script), sig, sigLen);

// Add seed
scriptLen += BRScriptPushData(&script[scriptLen], sizeof(script) - scriptLen, &seed[1], seed[0]);

if (htlcType == ScriptTypeSeller)
{
// Add OP_1 after seed
script[scriptLen] = OP_1;
++scriptLen;
}

// Add redeemscript
scriptLen += BRScriptPushData(&script[scriptLen], sizeof(script) - scriptLen, &redeemScript[1], redeemScript[0]);

BRTxInputSetSignature(input, script, scriptLen);
BRTxInputSetWitness(input, script, 0);
}
else { // pay-to-pubkey
uint8_t data[_BRTransactionData(tx, NULL, 0, i, forkId | SIGHASH_ALL)];
size_t dataLen = _BRTransactionData(tx, data, sizeof(data), i, forkId | SIGHASH_ALL);
Expand All @@ -706,7 +769,7 @@ int BRTransactionSign(BRTransaction *tx, int forkId, BRKey keys[], size_t keysCo
uint8_t data[BRTransactionSerialize(tx, NULL, 0)];
size_t len = BRTransactionSerialize(tx, data, sizeof(data));
BRTransaction *t = BRTransactionParse(data, len);

if (t) tx->txHash = t->txHash, tx->wtxHash = t->wtxHash;
if (t) BRTransactionFree(t);
return 1;
Expand Down
11 changes: 9 additions & 2 deletions src/spv/bitcoin/BRTransaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#define BRTransaction_h

#include "BRKey.h"
#include "BRAddress.h"
#include "BRInt.h"

#include <stddef.h>
Expand All @@ -36,9 +37,12 @@
extern "C" {
#endif

#define TX_VERSION 0x00000001
#define TX_VERSION_V2 0x00000002
#define TX_FEE_PER_KB 1000ULL // standard tx fee per kb of tx size (defid 0.12 default min-relay fee-rate)
#define TX_OUTPUT_SIZE 34 // estimated size for a typical transaction output
#define TX_INPUT_SIZE 148 // estimated size for a typical compact pubkey transaction input
#define TX_HTLC_INPUT_NOSIG 42 // approx. size of input without signature
#define TX_MIN_OUTPUT_AMOUNT (TX_FEE_PER_KB*3*(TX_OUTPUT_SIZE + TX_INPUT_SIZE)/1000) //no txout can be below this amount
#define TX_MAX_SIZE 100000 // no tx can be larger than this size in bytes
#define TX_UNCONFIRMED INT32_MAX // block height indicating transaction is unconfirmed
Expand Down Expand Up @@ -96,7 +100,7 @@ struct BRTransactionStruct {
typedef struct BRTransactionStruct BRTransaction;

// returns a newly allocated empty transaction that must be freed by calling BRTransactionFree()
BRTransaction *BRTransactionNew(void);
BRTransaction *BRTransactionNew(uint32_t version = TX_VERSION);

// returns a deep copy of tx and that must be freed by calling BRTransactionFree()
BRTransaction *BRTransactionCopy(const BRTransaction *tx);
Expand All @@ -123,6 +127,9 @@ void BRTransactionShuffleOutputs(BRTransaction *tx);
// size in bytes if signed, or estimated size assuming compact pubkey sigs
size_t BRTransactionSize(const BRTransaction *tx);

// Calculate size of HTLC transaction
size_t BRTransactionHTLCSize(const BRTransaction *tx, const size_t sigSize);

// virtual transaction size as defined by BIP141: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
size_t BRTransactionVSize(const BRTransaction *tx);

Expand All @@ -135,7 +142,7 @@ int BRTransactionIsSigned(const BRTransaction *tx);
// adds signatures to any inputs with NULL signatures that can be signed with any keys
// forkId is 0 for bitcoin, 0x40 for b-cash, 0x4f for b-gold
// returns true if tx is signed
int BRTransactionSign(BRTransaction *tx, int forkId, BRKey keys[], size_t keysCount);
int BRTransactionSign(BRTransaction *tx, int forkId, BRKey keys[], size_t keysCount, HTLCScriptType htlcType = ScriptTypeNone, const uint8_t* seed = nullptr, const uint8_t* redeemScript = nullptr);

// true if tx meets IsStandard() rules: https://bitcoin.org/en/developer-guide#standard-transactions
int BRTransactionIsStandard(const BRTransaction *tx);
Expand Down
Loading

0 comments on commit c820ad1

Please sign in to comment.