diff --git a/src/Makefile.am b/src/Makefile.am index 092015600..07150d0c6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -163,7 +163,6 @@ BITCOIN_CORE_H = \ init.h \ key.h \ keystore.h \ - komodo_cc.h \ leveldbwrapper.h \ limitedmap.h \ main.h \ @@ -253,6 +252,7 @@ libbitcoin_server_a_SOURCES = \ cc/eval.cpp \ cc/importpayout.cpp \ cc/disputepayout.cpp \ + cc/betprotocol.cpp \ chain.cpp \ checkpoints.cpp \ deprecation.cpp \ diff --git a/src/Makefile.ktest.include b/src/Makefile.ktest.include index 3ae3d9be8..cef80b0d1 100644 --- a/src/Makefile.ktest.include +++ b/src/Makefile.ktest.include @@ -5,7 +5,8 @@ bin_PROGRAMS += komodo-test # tool for generating our public parameters komodo_test_SOURCES = \ test-komodo/main.cpp \ - test-komodo/test_cryptoconditions.cpp + test-komodo/test_cryptoconditions.cpp \ + test-komodo/test_bet.cpp komodo_test_CPPFLAGS = $(komodod_CPPFLAGS) diff --git a/src/cc/betprotocol.cpp b/src/cc/betprotocol.cpp index 024b4c001..92ec961ee 100644 --- a/src/cc/betprotocol.cpp +++ b/src/cc/betprotocol.cpp @@ -1,117 +1,79 @@ #include +#include "streams.h" +#include "komodo_cc.h" +#include "cc/eval.h" +#include "cc/betprotocol.h" #include "primitives/transaction.h" -class DisputeHeader -{ -public: - int waitBlocks; - std::vector vmParams; - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { - READWRITE(VARINT(waitBlocks)); - READWRITE(vmParams); - } -}; - - -static CScript CCPubKey(const CC *cond) -{ - unsigned char buf[1000]; - size_t len = cc_conditionBinary(cond, buf); - return CScript() << std::vector(buf, buf+len) << OP_CHECKCRYPTOCONDITION; -} - - -static CScript CCSig(const CC *cond) -{ - unsigned char buf[1000]; - size_t len = cc_fulfillmentBinary(cond, buf, 1000); - auto ffill = std::vector(buf, buf+len); - ffill.push_back(SIGHASH_ALL); - return CScript() << ffill; -} - - static unsigned char* CopyPubKey(CPubKey pkIn) { - auto *pk = malloc(33); + unsigned char* pk = (unsigned char*) malloc(33); memcpy(pk, pkIn.begin(), 33); // TODO: compressed? return pk; } -class PokerProtocol +CC* CCNewThreshold(int t, std::vector v) { -public: - CAmount MINFEE = 1; - std::vector players; - DisputeHeader disputeHeader; - - // on PANGEA - CC* MakeDisputeCond(); - CMutableTransaction MakeSessionTx(CTxIn dealerInput); - CMutableTransaction MakeDisputeTx(uint256 signedSessionTxHash); - CMutableTransaction MakePostEvidenceTx(uint256 signedSessionTxHash, - CPubKey playerKey, std::vector state); - - // on KMD - CC* MakePayoutCond(); - CMutableTransaction MakeStakeTx(CAmount totalPayout, std::vector playerInputs, - uint256 signedSessionTx); - CMutableTransaction MakeAgreePayoutTx(std::vector payouts, - CC *signedPayoutCond, uint256 signedStakeTxHash); - CMutableTransaction MakeImportPayoutTx(std::vector payouts, - CC *signedPayoutCond, CTransaction signedDisputeTx); -} - - -CC* PokerProtocol::MakeDisputeCond() -{ - CC *disputePoker = cond->subconditions[0] = cc_new(CC_Eval); - char err[1000]; - std::vector headerData; - disputeHeader >> CDataStream(headerData, SER_DISK, PROTOCOL_VERSION); - disputePoker->paramsBin = malloc(headerData.size()); - memcpy(disputePoker->paramsBin, headerData.data()); - disputePoker.paramsBinLength = headerData.size(); - - CC *spendSig = cond->subconditions[1] = cc_new(CC_Threshold); - spendSig->threshold = 1; - spendSig->size = players.size() + 1; - spendSig->subconditions = malloc(spendSig->size, sizeof(CC*)); - - for (int i=0; isubconditions[i] = cc_new(CC_Secp256k1); - sub->publicKey = CopyPubKey(players[i]); - } - CC *cond = cc_new(CC_Threshold); - cond->threshold = 2; - cond->size = 2; - cond->subconditions = calloc(2, sizeof(CC*)); - cond->subconditions[0] = disputePoker; - cond->subconditions[1] = spendSig; + cond->threshold = t; + cond->size = v.size(); + cond->subconditions = (CC**) calloc(v.size(), sizeof(CC*)); + memcpy(cond->subconditions, v.data(), v.size() * sizeof(CC*)); return cond; } -CMutableTransaction PokerProtocol::MakeSessionTx(CTxIn dealerInput) +CC* CCNewSecp256k1(CPubKey k) +{ + CC *cond = cc_new(CC_Secp256k1); + cond->publicKey = CopyPubKey(k); + return cond; +} + + +CC* CCNewEval(std::string method, std::vector paramsBin) +{ + CC *cond = cc_new(CC_Eval); + strcpy(cond->method, method.data()); + cond->paramsBin = (unsigned char*) malloc(paramsBin.size()); + memcpy(cond->paramsBin, paramsBin.data(), paramsBin.size()); + cond->paramsBinLength = paramsBin.size(); + return cond; +} + + +std::vector BetProtocol::PlayerConditions() +{ + std::vector subs; + for (int i=0; ipublicKey = CopyPubKey(players[i]); + CC *cond = CCNewSecp256k1(players[i]); mtx.vout.push_back(CTxOut(MINFEE, CCPubKey(cond))); cc_free(cond); } @@ -119,12 +81,12 @@ CMutableTransaction PokerProtocol::MakeSessionTx(CTxIn dealerInput) } -CMutableTransaction PokerProtocol::MakeDisputeTx(uint256 signedSessionTxHash, CC *signedDisputeCond, uint256 vmResultHash) +CMutableTransaction BetProtocol::MakeDisputeTx(uint256 signedSessionTxHash, uint256 vmResultHash) { CMutableTransaction mtx; CC *disputeCond = MakeDisputeCond(); - mtx.vin.push_back(CTxIn(signedSessionTxHash, 0, CCSig(signedDisputeCond))); + mtx.vin.push_back(CTxIn(signedSessionTxHash, 0, CScript())); std::vector result(vmResultHash.begin(), vmResultHash.begin()+32); mtx.vout.push_back(CTxOut(0, CScript() << OP_RETURN << result)); @@ -132,69 +94,29 @@ CMutableTransaction PokerProtocol::MakeDisputeTx(uint256 signedSessionTxHash, CC } -CMutableTransaction PokerProtocol::MakePostEvidenceTx(uint256 signedSessionTxHash, +CMutableTransaction BetProtocol::MakePostEvidenceTx(uint256 signedSessionTxHash, int playerIdx, std::vector state) { CMutableTransaction mtx; - CC *cond = cc_new(CC_Secp256k1); - cond->publicKey = CopyPubKey(players[i]); - mtx.vin.push_back(CTxIn(signedSessionTxHash, playerIdx+1, CCSig(cond))); - + mtx.vin.push_back(CTxIn(signedSessionTxHash, playerIdx+1, CScript())); mtx.vout.push_back(CTxOut(0, CScript() << OP_RETURN << state)); return mtx; } -CC* CCNewThreshold(int t, std::vector v) + +CC* BetProtocol::MakePayoutCond(uint256 signedSessionTxHash) { - CC *cond = cc_new(CC_Threshold); - cond->threshold = t; - cond->size = v.size(); - cond->subconditions = calloc(1, sizeof(CC*)); - memcpy(cond->subconditions, v.data(), v.size() * sizeof(CC*)); - return cond; -} - - -CCNewSecp256k1(CPubKey &k) -{ - cond = cc_new(CC_Secp256k1); - cond->publicKey = CopyPubKey(players[i]); - return cond; -} - - -CCNewEval(char *method, unsigned char* paramsBin, size_t len) -{ - CC *cond = cc_new(CC_Eval); - strcpy(cond->method, method); - cond->paramsBin = malloc(32); - memcpy(cond->paramsBin, bin, len); - cond->paramsBinLength = len; - return cond; -} - - -CC* MakePayoutCond(uint256 signedSessionTx) -{ - CC* agree; - { - // TODO: 2/3 majority - std::vector subs; - for (int i=0; i vHash(signedSessionTxHash.begin(), signedSessionTxHash.end()); + CC *importEval = CCNewEval("ImportPayout", vHash); - std::vector subs; - for (int i=0; i playerInputs, - uint256 signedSessionTx) +CMutableTransaction BetProtocol::MakeStakeTx(CAmount totalPayout, uint256 signedSessionTxHash) { CMutableTransaction mtx; - mtx.vin = playerInputs; - CC *payoutCond = MakePayoutCond(signedSessionTx);push + CC *payoutCond = MakePayoutCond(signedSessionTxHash); mtx.vout.push_back(CTxOut(totalPayout, CCPubKey(payoutCond))); cc_free(payoutCond); @@ -217,24 +137,23 @@ CMutableTransaction PokerProtocol::MakeStakeTx(CAmount totalPayout, std::vector< } -CMutableTransaction PokerProtocol::MakeAgreePayoutTx(std::vector payouts, - CC *signedPayoutCond, uint256 signedStakeTxHash) +CMutableTransaction BetProtocol::MakeAgreePayoutTx(std::vector payouts, + uint256 signedStakeTxHash) { CMutableTransaction mtx; - mtx.vin.push_back(CTxIn(signedStakeTxHash, 0, CCSig(signedPayoutCond))); - mtx.vouts = payouts; + mtx.vin.push_back(CTxIn(signedStakeTxHash, 0, CScript())); + mtx.vout = payouts; return mtx; } -CMutableTransaction PokerProtocol::MakeImportPayoutTx(std::vector payouts, - CC *signedPayoutCond, CTransaction signedDisputeTx, uint256 signedStakeTxHash) +CMutableTransaction BetProtocol::MakeImportPayoutTx(std::vector payouts, + CTransaction signedDisputeTx, uint256 signedStakeTxHash, MoMProof momProof) { - std::vector vDisputeTx; - signedDisputeTx >> CDataStream(vDisputeTx, SER_DISK, PROTOCOL_VERSION); CMutableTransaction mtx; - mtx.vin.push_back(CTxInput(signedStakeTxHash, 0, CCSig(signedPayoutCond))); + mtx.vin.push_back(CTxIn(signedStakeTxHash, 0, CScript())); mtx.vout = payouts; - CMutableTransaction.vout.insert(0, CTxOutput(0, CScript() << OP_RETURN << "PROOF HERE")); - CMutableTransaction.vout.insert(1, CTxOutput(0, CScript() << OP_RETURN << vDisputeTx)); + mtx.vout.insert(mtx.vout.begin(), CTxOut(0, CScript() << OP_RETURN << CheckSerialize(momProof))); + mtx.vout.insert(mtx.vout.begin()+1, CTxOut(0, CScript() << OP_RETURN << CheckSerialize(signedDisputeTx))); + return mtx; } diff --git a/src/cc/betprotocol.h b/src/cc/betprotocol.h new file mode 100644 index 000000000..dcbb6ec4e --- /dev/null +++ b/src/cc/betprotocol.h @@ -0,0 +1,85 @@ +#ifndef BETPROTOCOL_H +#define BETPROTOCOL_H + +#include "pubkey.h" +#include "primitives/transaction.h" +#include "cryptoconditions/include/cryptoconditions.h" + + +#define ExecMerkle CBlock::CheckMerkleBranch + + +class MoMProof +{ +public: + int nIndex; + std::vector branch; + uint256 notarisationHash; + + MoMProof() {} + MoMProof(int i, std::vector b, uint256 n) : notarisationHash(n), nIndex(i), branch(b) {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(VARINT(nIndex)); + READWRITE(branch); + READWRITE(notarisationHash); + } +}; + + +class DisputeHeader +{ +public: + int waitBlocks; + std::vector vmParams; + + DisputeHeader() {} + DisputeHeader(int w, std::vector vmp) : waitBlocks(w), vmParams(vmp) {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(VARINT(waitBlocks)); + READWRITE(vmParams); + } +}; + + +class BetProtocol +{ +protected: + char* disputeFunc = (char*) "DisputeBet"; + std::vector playerConditions(); +public: + CAmount MINFEE = 1; + std::vector players; + DisputeHeader disputeHeader; + + // Utility + BetProtocol(std::vector ps, DisputeHeader dh) : players(ps), disputeHeader(dh) {} + std::vector PlayerConditions(); + + // on PANGEA + CC* MakeDisputeCond(); + CMutableTransaction MakeSessionTx(); + CMutableTransaction MakeDisputeTx(uint256 signedSessionTxHash, uint256 vmResultHash); + CMutableTransaction MakePostEvidenceTx(uint256 signedSessionTxHash, + int playerIndex, std::vector state); + + // on KMD + CC* MakePayoutCond(uint256 signedSessionTxHash); + CMutableTransaction MakeStakeTx(CAmount totalPayout, uint256 signedSessionTx); + CMutableTransaction MakeAgreePayoutTx(std::vector payouts, uint256 signedStakeTxHash); + CMutableTransaction MakeImportPayoutTx(std::vector payouts, + CTransaction signedDisputeTx, uint256 signedStakeTxHash, MoMProof momProof); +}; + + +CC* CCNewSecp256k1(CPubKey k); + + +#endif /* BETPROTOCOL_H */ diff --git a/src/cc/disputepayout.cpp b/src/cc/disputepayout.cpp index 36a5e1723..ea34ed23c 100644 --- a/src/cc/disputepayout.cpp +++ b/src/cc/disputepayout.cpp @@ -1,34 +1,27 @@ -#include "primitives/transaction.h" -#include "streams.h" +#include + +#include "hash.h" #include "chain.h" -#include "main.h" +#include "version.h" +#include "komodo_cc.h" #include "cc/eval.h" -#include "cryptoconditions/include/cryptoconditions.h" +#include "cc/betprotocol.h" +#include "primitives/transaction.h" -bool GetSpends(uint256 hash, std::vector> &spends) +class DisputeHeader; + + +static bool GetOpReturnHash(CScript script, uint256 &hash) { - // NOT IMPLEMENTED - return false; + std::vector vHash; + GetOpReturnData(script, vHash); + if (vHash.size() != 32) return false; + memcpy(hash.begin(), vHash.data(), 32); + return true; } -class DisputeHeader -{ -public: - int waitBlocks; - std::vector vmHeader; - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { - READWRITE(VARINT(waitBlocks)); - READWRITE(vmHeader); - } -}; - - /* * Crypto-Condition EVAL method that resolves a dispute of a session * @@ -42,48 +35,51 @@ public: * in 0: Spends Session TX first output, reveals DisputeHeader * out 0: OP_RETURN hash of payouts */ -bool DisputePayout(AppVM &vm, const CC *cond, const CTransaction *disputeTx, int nIn) +bool Eval::DisputePayout(AppVM &vm, const CC *cond, const CTransaction &disputeTx, unsigned int nIn) { - // TODO: Error messages! - if (disputeTx->vout.size() < 2) return 0; + if (disputeTx.vout.size() == 0) return Invalid("no-vouts"); // get payouts hash - std::vector vPayoutHash; uint256 payoutHash; - if (!GetOpReturnData(disputeTx->vout[0].scriptPubKey, vPayoutHash)) return 0; - memcpy(payoutHash.begin(), vPayoutHash.data(), 32); + if (!GetOpReturnHash(disputeTx.vout[0].scriptPubKey, payoutHash)) + return Invalid("invalid-payout-hash"); // load dispute header DisputeHeader disputeHeader; std::vector headerData(cond->paramsBin, cond->paramsBin+cond->paramsBinLength); - CDataStream(headerData, SER_DISK, PROTOCOL_VERSION) >> disputeHeader; - // TODO: exception? end of stream? + if (!CheckDeserialize(headerData, disputeHeader)) + return Invalid("invalid-dispute-header"); // ensure that enough time has passed CTransaction sessionTx; uint256 sessionBlockHash; - if (!GetTransaction(disputeTx->vin[0].prevout.hash, sessionTx, sessionBlockHash, false)) - return false; // wth? TODO: log TODO: MUST be upsteam of disputeTx, how to ensure? - // below does this by looking up block in blockindex - // what if GetTransaction returns from mempool, maybe theres no block? - CBlockIndex* sessionBlockIndex = mapBlockIndex[sessionBlockHash]; - if (chainActive.Height() < sessionBlockIndex->nHeight + disputeHeader.waitBlocks) - return false; // Not yet + CBlockIndex sessionBlock; + + if (!GetTx(disputeTx.vin[0].prevout.hash, sessionTx, sessionBlockHash, false)) + return Error("couldnt-get-parent"); + // TODO: This may not be an error, if both txs are to go into the same block... + // Probably change it to Invalid + if (!GetBlock(sessionBlockHash, sessionBlock)) + return Error("couldnt-get-block"); + + if (GetCurrentHeight() < sessionBlock.nHeight + disputeHeader.waitBlocks) + return Invalid("dispute-too-soon"); // Not yet // get spends - std::vector> spends; - if (!GetSpends(disputeTx->vin[0].prevout.hash, spends)) return 0; + std::vector spends; + if (!GetSpends(disputeTx.vin[0].prevout.hash, spends)) + return Error("couldnt-get-spends"); // verify result from VM int maxLength = -1; uint256 bestPayout; for (int i=1; i vmBody; - if (!GetOpReturnData(spends[i]->vout[0].scriptPubKey, vmBody)) continue; - auto out = vm.evaluate(disputeHeader.vmHeader, vmBody); + std::vector vmState; + if (!spends[i].vout.size() > 0) continue; + if (!GetOpReturnData(spends[i].vout[0].scriptPubKey, vmState)) continue; + auto out = vm.evaluate(disputeHeader.vmParams, vmState); uint256 resultHash = SerializeHash(out.second); if (out.first > maxLength) { maxLength = out.first; @@ -98,5 +94,7 @@ bool DisputePayout(AppVM &vm, const CC *cond, const CTransaction *disputeTx, int } } - return bestPayout == payoutHash; + if (maxLength == -1) return Invalid("no-evidence"); + + return bestPayout == payoutHash ? Valid() : Invalid("wrong-payout"); } diff --git a/src/cc/eval.cpp b/src/cc/eval.cpp index f61a7c241..589a362c0 100644 --- a/src/cc/eval.cpp +++ b/src/cc/eval.cpp @@ -1,56 +1,151 @@ +#include +#include + #include "primitives/transaction.h" #include "komodo_cc.h" #include "cc/eval.h" -#include +#include "main.h" +#include "chain.h" + + +Eval* EVAL_TEST = 0; + + +bool RunCCEval(const CC *cond, const CTransaction &tx, unsigned int nIn) +{ + Eval eval_; + Eval *eval = EVAL_TEST; + if (!eval) eval = &eval_; + + bool out = eval->Dispatch(cond, tx, nIn); + assert(eval->state.IsValid() == out); + + if (eval->state.IsValid()) return true; + + std::string lvl = eval->state.IsInvalid() ? "Invalid" : "Error!"; + fprintf(stderr, "CC Eval %s %s: %s in tx %s\n", lvl.data(), cond->method, + eval->state.GetRejectReason().data(), tx.GetHash().GetHex().data()); + return false; +} /* * Test the validity of an Eval node */ -bool EvalConditionValidity(const CC *cond, const CTransaction *txTo, int nIn) +bool Eval::Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn) { if (strcmp(cond->method, "TestEval") == 0) { - return cond->paramsBinLength == 8 && - memcmp(cond->paramsBin, "TestEval", 8) == 0; + bool valid = cond->paramsBinLength == 8 && memcmp(cond->paramsBin, "TestEval", 8) == 0; + return valid ? Valid() : Invalid("testing"); } if (strcmp(cond->method, "ImportPayout") == 0) { - return CheckImportPayout(cond, txTo, nIn); + return ImportPayout(cond, txTo, nIn); } /* Example of how you might call DisputePayout - if (strcmp(ASSETCHAINS_SYMBOL, "PANGEA") == 0) { if (strcmp(cond->method, "DisputePoker") == 0) { return DisputePayout(PokerVM(), cond, txTo, nIn); } } - */ - fprintf(stderr, "no defined behaviour for method: %s\n", cond->method); - return 0; + return Invalid("no-such-method"); } - -bool GetPushData(const CScript &sig, std::vector &data) +bool Eval::GetSpends(uint256 hash, std::vector &spends) const { - opcodetype opcode; - auto pc = sig.begin(); - if (sig.GetOp(pc, opcode, data)) return opcode > OP_0 && opcode <= OP_PUSHDATA4; + // NOT IMPLEMENTED return false; } -bool GetOpReturnData(const CScript &sig, std::vector &data) +bool Eval::GetTx(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow) const { - auto pc = sig.begin(); - opcodetype opcode; - if (sig.GetOp2(pc, opcode, NULL)) - if (opcode == OP_RETURN) - if (sig.GetOp(pc, opcode, data)) - return opcode > OP_0 && opcode <= OP_PUSHDATA4; + return GetTransaction(hash, txOut, hashBlock, fAllowSlow); +} + + +unsigned int Eval::GetCurrentHeight() const +{ + return chainActive.Height(); +} + + +bool Eval::GetBlock(uint256 hash, CBlockIndex& blockIdx) const +{ + auto r = mapBlockIndex.find(hash); + if (r != mapBlockIndex.end()) { + blockIdx = *r->second; + return true; + } return false; } + +extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp); + + +bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const +{ + if (tx.vin.size() < 11) return false; + + uint8_t notaries[64][33]; + uint8_t seenNotaries[64]; + int nNotaries = komodo_notaries(notaries, height, timestamp); + char pk[33]; + + BOOST_FOREACH(const CTxIn &txIn, tx.vin) + { + // Get notary pubkey + CTransaction tx; + uint256 hashBlock; + if (!GetTx(txIn.prevout.hash, tx, hashBlock, false)) return false; + if (tx.vout.size() < txIn.prevout.n) return false; + const unsigned char *script = tx.vout[txIn.prevout.n].scriptPubKey.data(); + if (script[0] != 33) return false; + memcpy(pk, script+1, 33); + return true; + + // Check it's a notary + for (int i=0; i opret; + if (!GetOpReturnData(notarisationTx.vout[0].scriptPubKey, opret)) return 0; + if (opret.size() < 36) return 0; // In reality it is more than 36, but at the moment I + // only know where it is relative to the end, and this + // is enough to prevent a memory fault. In the case that + // the assumption about the presence of a MoM at this + // offset fails, we will return random other data that is + // not more likely to generate a false positive. + memcpy(mom.begin(), opret.data()+opret.size()-36, 32); + return 1; +} diff --git a/src/cc/eval.h b/src/cc/eval.h index aadb2e85f..2060d76ca 100644 --- a/src/cc/eval.h +++ b/src/cc/eval.h @@ -1,18 +1,56 @@ #ifndef CC_EVAL_H #define CC_EVAL_H -#include "cryptoconditions/include/cryptoconditions.h" +#include + +#include "chain.h" +#include "streams.h" +#include "version.h" +#include "consensus/validation.h" #include "primitives/transaction.h" -/* - * Test validity of a CC_Eval node - */ -bool EvalConditionValidity(const CC *cond, const CTransaction *tx, int nIn); -/* - * Test an ImportPayout CC Eval condition - */ -bool CheckImportPayout(const CC *cond, const CTransaction *payoutTx, int nIn); +class AppVM; + + +class Eval +{ +public: + CValidationState state; + + bool Invalid(std::string s) { return state.Invalid(false, 0, s); } + bool Error(std::string s) { return state.Error(s); } + bool Valid() { return true; } + + /* + * Test validity of a CC_Eval node + */ + virtual bool Dispatch(const CC *cond, const CTransaction &tx, unsigned int nIn); + + /* + * Dispute a payout using a VM + */ + bool DisputePayout(AppVM &vm, const CC *cond, const CTransaction &disputeTx, unsigned int nIn); + + /* + * Test an ImportPayout CC Eval condition + */ + bool ImportPayout(const CC *cond, const CTransaction &payoutTx, unsigned int nIn); + + /* + * IO functions + */ + virtual bool GetTx(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow) const; + virtual unsigned int GetCurrentHeight() const; + virtual bool GetSpends(uint256 hash, std::vector &spends) const; + virtual bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const; + virtual bool GetMoM(uint256 notarisationHash, uint256& MoM) const; + virtual bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const; +}; + + +bool RunCCEval(const CC *cond, const CTransaction &tx, unsigned int nIn); + /* * Virtual machine to use in the case of on-chain app evaluation @@ -30,20 +68,36 @@ public: evaluate(std::vector header, std::vector body) = 0; }; -/* - * Test a DisputePayout CC Eval condition, using a provided AppVM - */ -bool DisputePayout(AppVM &vm, const CC *cond, const CTransaction *disputeTx, int nIn); /* - * Get PUSHDATA from a script + * Serialisation boilerplate */ -bool GetPushData(const CScript &sig, std::vector &data); +template +std::vector CheckSerialize(T &in); +template +bool CheckDeserialize(std::vector vIn, T &out); -/* - * Get OP_RETURN data from a script - */ -bool GetOpReturnData(const CScript &sig, std::vector &data); + + +template +std::vector CheckSerialize(T &in) +{ + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << in; + return std::vector(ss.begin(), ss.end()); +} + + +template +bool CheckDeserialize(std::vector vIn, T &out) +{ + CDataStream ss(vIn, SER_NETWORK, PROTOCOL_VERSION); + try { + ss >> out; + if (ss.eof()) return true; + } catch(...) {} + return false; +} #endif /* CC_EVAL_H */ diff --git a/src/cc/importpayout.cpp b/src/cc/importpayout.cpp index f05abb943..5251f1dfd 100644 --- a/src/cc/importpayout.cpp +++ b/src/cc/importpayout.cpp @@ -1,78 +1,11 @@ -#include "primitives/transaction.h" -#include "streams.h" -#include "chain.h" +#include + #include "main.h" +#include "chain.h" +#include "streams.h" #include "cc/eval.h" -#include "cc/importpayout.h" -#include "cryptoconditions/include/cryptoconditions.h" - - -extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp); - -bool DerefNotaryPubkey(const COutPoint &prevout, char *pk33) -{ - CTransaction tx; - uint256 hashBlock; - if (!GetTransaction(prevout.hash, tx, hashBlock, false)) return false; - if (tx.vout.size() < prevout.n) return false; - const unsigned char *script = tx.vout[prevout.n].scriptPubKey.data(); - if (script[0] != 33) return false; - memcpy(pk33, script+1, 33); - return true; -} - -bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) -{ - if (tx.vin.size() < 11) return false; - - uint8_t notaries[64][33]; - uint8_t seenNotaries[64]; - int nNotaries = komodo_notaries(notaries, height, timestamp); - char *pk; - - BOOST_FOREACH(const CTxIn &txIn, tx.vin) - { - if (!DerefNotaryPubkey(txIn.prevout, pk)) return false; - - for (int i=0; inHeight, blockindex->nTime)) { - return false; - } - if (!notarisationTx.vout.size() < 1) return 0; - std::vector opret; - if (!GetOpReturnData(notarisationTx.vout[0].scriptPubKey, opret)) return 0; - if (opret.size() < 36) return 0; // In reality it is more than 36, but at the moment I - // only know where it is relative to the end, and this - // is enough to prevent a memory fault. In the case that - // the assumption about the presence of a MoM at this - // offset fails, we will return random other data that is - // not more likely to generate a false positive. - memcpy(mom.begin(), opret.data()+opret.size()-36, 32); - return 1; -} - -#define ExecMerkle CBlock::CheckMerkleBranch +#include "cc/betprotocol.h" +#include "primitives/transaction.h" /* @@ -97,54 +30,51 @@ bool GetMoM(const uint256 notaryHash, uint256 &mom) * out 0: OP_RETURN hash of payouts * out 1-: anything */ -bool CheckImportPayout(const CC *cond, const CTransaction *payoutTx, int nIn) +bool Eval::ImportPayout(const CC *cond, const CTransaction &payoutTx, unsigned int nIn) { // TODO: Error messages! - if (payoutTx->vin.size() != 1) return 0; - if (payoutTx->vout.size() < 2) return 0; - - // Get hash of payouts - std::vector payouts(payoutTx->vout.begin() + 2, payoutTx->vout.end()); - uint256 payoutsHash = SerializeHash(payouts); - std::vector vPayoutsHash(payoutsHash.begin(), payoutsHash.end()); + if (payoutTx.vout.size() < 2) return Invalid("need-2-vouts"); // load disputeTx from vout[1] CTransaction disputeTx; - { - std::vector exportData; - if (!GetOpReturnData(payoutTx->vout[1].scriptPubKey, exportData)) return 0; - CDataStream(exportData, SER_DISK, PROTOCOL_VERSION) >> disputeTx; - // TODO: end of stream? exception? - } + std::vector exportData; + GetOpReturnData(payoutTx.vout[1].scriptPubKey, exportData); + if (!CheckDeserialize(exportData, disputeTx)) + return Invalid("invalid-dispute-tx"); - // Check disputeTx.0 is vPayoutsHash - std::vector exportPayoutsHash; - if (!GetOpReturnData(disputeTx.vout[0].scriptPubKey, exportPayoutsHash)) return 0; - if (exportPayoutsHash != vPayoutsHash) return 0; + // Check disputeTx.0 shows correct payouts + { + std::vector payouts(payoutTx.vout.begin() + 2, payoutTx.vout.end()); + uint256 payoutsHash = SerializeHash(payouts); + std::vector vPayoutsHash(payoutsHash.begin(), payoutsHash.end()); + + if (disputeTx.vout[0].scriptPubKey != CScript() << OP_RETURN << vPayoutsHash) + return Invalid("wrong-payouts"); + } // Check disputeTx spends sessionTx.0 // condition ImportPayout params is session ID from other chain { - if (cond->paramsBinLength != 32) return 0; + if (cond->paramsBinLength != 32) return Invalid("malformed-params"); COutPoint prevout = disputeTx.vin[0].prevout; if (memcmp(prevout.hash.begin(), cond->paramsBin, 32) != 0 || - prevout.n != 0) return 0; + prevout.n != 0) return Invalid("wrong-session"); } // Check disputeTx solves momproof from vout[0] { - std::vector vchMomProof; - if (!GetOpReturnData(payoutTx->vout[0].scriptPubKey, vchMomProof)) return 0; + std::vector vProof; + GetOpReturnData(payoutTx.vout[0].scriptPubKey, vProof); + MoMProof proof; + if (!CheckDeserialize(vProof, proof)) + return Invalid("invalid-mom-proof-payload"); - MoMProof momProof; - CDataStream(vchMomProof, SER_DISK, PROTOCOL_VERSION) >> momProof; - - uint256 mom; - if (!GetMoM(momProof.notarisationHash, mom)) return 0; + uint256 MoM; + if (!GetMoM(proof.notarisationHash, MoM)) return Invalid("coudnt-load-mom"); - uint256 proofResult = ExecMerkle(disputeTx.GetHash(), momProof.branch, momProof.nIndex); - if (proofResult != mom) return 0; + if (MoM != ExecMerkle(disputeTx.GetHash(), proof.branch, proof.nIndex)) + return Invalid("mom-check-fail"); } - return 1; + return Valid(); } diff --git a/src/cc/importpayout.h b/src/cc/importpayout.h deleted file mode 100644 index 3ae094e80..000000000 --- a/src/cc/importpayout.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef KOMODO_TXPROOF_H -#define KOMODO_TXPROOF_H - - -class MoMProof -{ -public: - int nIndex; - std::vector branch; - uint256 notarisationHash; - - MoMProof() {} - MoMProof(int i, std::vector b, uint256 n) : notarisationHash(n), nIndex(i), branch(b) {} - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { - READWRITE(VARINT(nIndex)); - READWRITE(branch); - READWRITE(notarisationHash); - } -}; - - -#endif /* KOMODO_TXPROOF_H */ diff --git a/src/cryptoconditions/include/cryptoconditions.h b/src/cryptoconditions/include/cryptoconditions.h index 439fe3f4a..a192b06c2 100644 --- a/src/cryptoconditions/include/cryptoconditions.h +++ b/src/cryptoconditions/include/cryptoconditions.h @@ -96,6 +96,7 @@ char* cc_typeName(const CC *cond); enum CCTypeId cc_typeId(const CC *cond); unsigned long cc_getCost(const CC *cond); uint32_t cc_typeMask(const CC *cond); +int cc_isAnon(const CC *cond); void cc_free(struct CC *cond); #ifdef __cplusplus diff --git a/src/cryptoconditions/src/cryptoconditions.c b/src/cryptoconditions/src/cryptoconditions.c index 622f09530..6a6513b59 100644 --- a/src/cryptoconditions/src/cryptoconditions.c +++ b/src/cryptoconditions/src/cryptoconditions.c @@ -284,7 +284,7 @@ char *cc_typeName(const CC *cond) { CC *cc_new(int typeId) { CC *cond = calloc(1, sizeof(CC)); - cond->type = CCTypeRegistry[typeId]; + cond->type = typeId == CC_Anon ? &CC_AnonType : CCTypeRegistry[typeId]; return cond; } diff --git a/src/cryptoconditions/src/threshold.c b/src/cryptoconditions/src/threshold.c index da8a3027a..f4684cd7b 100644 --- a/src/cryptoconditions/src/threshold.c +++ b/src/cryptoconditions/src/threshold.c @@ -130,15 +130,19 @@ static CC *thresholdFromFulfillment(const Fulfillment_t *ffill) { static Fulfillment_t *thresholdToFulfillment(const CC *cond) { CC *sub; Fulfillment_t *fulfillment; + + // Make a copy of subconditions so we can leave original order alone + CC** subconditions = malloc(cond->size*sizeof(CC*)); + memcpy(subconditions, cond->subconditions, cond->size*sizeof(CC*)); - qsort(cond->subconditions, cond->size, sizeof(CC*), cmpConditionCost); + qsort(subconditions, cond->size, sizeof(CC*), cmpConditionCost); ThresholdFulfillment_t *tf = calloc(1, sizeof(ThresholdFulfillment_t)); int needed = cond->threshold; for (int i=0; isize; i++) { - sub = cond->subconditions[i]; + sub = subconditions[i]; if (needed && (fulfillment = asnFulfillmentNew(sub))) { asn_set_add(&tf->subfulfillments, fulfillment); needed--; @@ -147,6 +151,8 @@ static Fulfillment_t *thresholdToFulfillment(const CC *cond) { } } + free(subconditions); + if (needed) { ASN_STRUCT_FREE(asn_DEF_ThresholdFulfillment, tf); return NULL; @@ -200,7 +206,7 @@ static void thresholdToJSON(const CC *cond, cJSON *params) { static int thresholdIsFulfilled(const CC *cond) { int nFulfilled = 0; - for (int i=0; ithreshold; i++) { + for (int i=0; isize; i++) { if (cc_isFulfilled(cond->subconditions[i])) { nFulfilled++; } diff --git a/src/komodo_cc.cpp b/src/komodo_cc.cpp index 81459fe2b..e7b166eba 100644 --- a/src/komodo_cc.cpp +++ b/src/komodo_cc.cpp @@ -30,3 +30,72 @@ bool IsSignedCryptoCondition(const CC *cond) if (IsSignedCryptoCondition(cond->subconditions[i])) return true; return false; } + + + +CScript CCPubKey(const CC *cond) +{ + unsigned char buf[1000]; + size_t len = cc_conditionBinary(cond, buf); + return CScript() << std::vector(buf, buf+len) << OP_CHECKCRYPTOCONDITION; +} + + +CScript CCSig(const CC *cond) +{ + unsigned char buf[1000]; + size_t len = cc_fulfillmentBinary(cond, buf, 1000); + auto ffill = std::vector(buf, buf+len); + ffill.push_back(1); // SIGHASH_ALL + return CScript() << ffill; +} + + +std::string CCShowStructure(CC *cond) +{ + std::string out; + if (cc_isAnon(cond)) { + out = "A" + std::to_string(cc_typeId(cond)); + } + else if (cc_typeId(cond) == CC_Threshold) { + out += "(" + std::to_string(cond->threshold) + " of "; + for (int i=0; isize; i++) { + out += CCShowStructure(cond->subconditions[i]); + if (i < cond->size - 1) out += ","; + } + out += ")"; + } + else { + out = std::to_string(cc_typeId(cond)); + } + return out; +} + + +CC* CCPrune(CC *cond) +{ + std::vector ffillBin; + GetPushData(CCSig(cond), ffillBin); + return cc_readFulfillmentBinary(ffillBin.data(), ffillBin.size()-1); +} + + +bool GetPushData(const CScript &sig, std::vector &data) +{ + opcodetype opcode; + auto pc = sig.begin(); + if (sig.GetOp(pc, opcode, data)) return opcode > OP_0 && opcode <= OP_PUSHDATA4; + return false; +} + + +bool GetOpReturnData(const CScript &sig, std::vector &data) +{ + auto pc = sig.begin(); + opcodetype opcode; + if (sig.GetOp2(pc, opcode, NULL)) + if (opcode == OP_RETURN) + if (sig.GetOp(pc, opcode, data)) + return opcode > OP_0 && opcode <= OP_PUSHDATA4; + return false; +} diff --git a/src/komodo_cc.h b/src/komodo_cc.h index 62e584a94..78cb4401a 100644 --- a/src/komodo_cc.h +++ b/src/komodo_cc.h @@ -1,6 +1,7 @@ #ifndef KOMODO_CC_H #define KOMODO_CC_H +#include "script/script.h" #include "cryptoconditions/include/cryptoconditions.h" @@ -31,4 +32,42 @@ bool IsSupportedCryptoCondition(const CC *cond); bool IsSignedCryptoCondition(const CC *cond); +/* + * Turn a condition into a scriptPubKey + */ +CScript CCPubKey(const CC *cond); + + +/* + * Turn a condition into a scriptSig + * + * Note: This will fail in undefined ways if the condition is missing signatures + */ +CScript CCSig(const CC *cond); + + +/* + * Produces a string showing the structure of a CC condition + */ +std::string CCShowStructure(CC *cond); + + +/* + * Take a signed CC, encode it, and decode it again. This has the effect + * of removing branches unneccesary for fulfillment. + */ +CC* CCPrune(CC *cond); + + +/* + * Get PUSHDATA from a script + */ +bool GetPushData(const CScript &sig, std::vector &data); + +/* + * Get OP_RETURN data from a script + */ +bool GetOpReturnData(const CScript &sig, std::vector &data); + + #endif /* KOMODO_CC_H */ diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 30d2c0aab..f353d549d 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -8,7 +8,7 @@ #include "chainparams.h" #include "checkpoints.h" #include "consensus/validation.h" -#include "cc/importpayout.h" +#include "cc/betprotocol.h" #include "main.h" #include "primitives/transaction.h" #include "rpcserver.h" diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 851904eb3..31a7fdbd0 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1310,18 +1310,22 @@ int TransactionSignatureChecker::CheckCryptoCondition( } catch (logic_error ex) { return 0; } + + VerifyEval eval = [] (CC *cond, void *checker) { + return ((TransactionSignatureChecker*)checker)->CheckEvalCondition(cond); + }; + int out = cc_verify(cond, (const unsigned char*)&sighash, 32, 0, - condBin.data(), condBin.size(), GetCCEval(), (void*)this); + condBin.data(), condBin.size(), eval, (void*)this); cc_free(cond); return out; } -VerifyEval TransactionSignatureChecker::GetCCEval() const { - return [] (CC *cond, void *checker) { - fprintf(stderr, "Cannot check crypto-condition Eval outside of server\n"); - return 0; - }; +int TransactionSignatureChecker::CheckEvalCondition(const CC *cond) const +{ + fprintf(stderr, "Cannot check crypto-condition Eval outside of server\n"); + return 0; } diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 57051f797..46c0818d2 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -142,13 +142,12 @@ public: class TransactionSignatureChecker : public BaseSignatureChecker { -private: +protected: const CTransaction* txTo; unsigned int nIn; const CAmount amount; const PrecomputedTransactionData* txdata; -protected: virtual bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; public: @@ -161,7 +160,7 @@ public: const std::vector& ffillBin, const CScript& scriptCode, uint32_t consensusBranchId) const; - virtual VerifyEval GetCCEval() const; + virtual int CheckEvalCondition(const CC *cond) const; }; class MutableTransactionSignatureChecker : public TransactionSignatureChecker diff --git a/src/script/serverchecker.cpp b/src/script/serverchecker.cpp index 852895305..0baaee9f5 100644 --- a/src/script/serverchecker.cpp +++ b/src/script/serverchecker.cpp @@ -100,14 +100,7 @@ bool ServerTransactionSignatureChecker::VerifySignature(const std::vectorCheckEvalCondition(cond); - }; -} - -int ServerTransactionSignatureChecker::CheckEvalCondition(CC *cond) const -{ - return EvalConditionValidity(cond, txTo, nIn); + return RunCCEval(cond, *txTo, nIn); } diff --git a/src/script/serverchecker.h b/src/script/serverchecker.h index c680da7ce..107586689 100644 --- a/src/script/serverchecker.h +++ b/src/script/serverchecker.h @@ -16,15 +16,12 @@ class ServerTransactionSignatureChecker : public TransactionSignatureChecker { private: bool store; - const CTransaction* txTo; - unsigned int nIn; public: - ServerTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amount, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amount, txdataIn), store(storeIn) {} + ServerTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nIn, const CAmount& amount, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nIn, amount, txdataIn), store(storeIn) {} bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; - int CheckEvalCondition(CC *cond) const; - VerifyEval GetCCEval() const; + int CheckEvalCondition(const CC *cond) const; }; #endif // BITCOIN_SCRIPT_SERVERCHECKER_H diff --git a/src/test-komodo/main.cpp b/src/test-komodo/main.cpp index 2a83e47fa..527d7666e 100644 --- a/src/test-komodo/main.cpp +++ b/src/test-komodo/main.cpp @@ -1,12 +1,14 @@ #include "key.h" +#include "chainparams.h" #include "gtest/gtest.h" #include "crypto/common.h" int main(int argc, char **argv) { - assert(init_and_check_sodium() != -1); - ECC_Start(); + assert(init_and_check_sodium() != -1); + ECC_Start(); + SelectParams(CBaseChainParams::REGTEST); - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/test-komodo/test_bet.cpp b/src/test-komodo/test_bet.cpp new file mode 100644 index 000000000..d0072ad38 --- /dev/null +++ b/src/test-komodo/test_bet.cpp @@ -0,0 +1,596 @@ +#include +#include + +#include "cc/betprotocol.h" +#include "cc/eval.h" +#include "base58.h" +#include "key.h" +#include "main.h" +#include "komodo_cc.h" +#include "primitives/transaction.h" +#include "script/interpreter.h" +#include "script/serverchecker.h" + +#include "testutils.h" + + + +static std::vector playerSecrets; +static std::vector players; + +static int Dealer = 0, Player1 = 1, Player2 = 2; + + +int CCSign(CMutableTransaction &tx, unsigned int nIn, CC *cond, std::vector keyIds) { + PrecomputedTransactionData txdata(tx); + uint256 sighash = SignatureHash(CCPubKey(cond), tx, nIn, SIGHASH_ALL, 0, 0, &txdata); + int nSigned = 0; + for (int i=0; i> evaluate( + std::vector header, std::vector body) + { + std::vector outs; + if (memcmp(header.data(), "BetHeader", 9)) { + printf("Wrong VM header\n"); + return std::make_pair(0, outs); + } + outs.push_back(CTxOut(2, CScript() << OP_RETURN << body.size())); + return std::make_pair(body.size(), outs); + } +}; + + +class EvalMock : public Eval +{ +public: + uint256 MoM; + int currentHeight; + std::map txs; + std::map blocks; + std::map> spends; + + bool Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn) + { + if (strcmp(cond->method, "DisputeBet") == 0) { + MockVM vm; + return DisputePayout(vm, cond, txTo, nIn); + } + if (strcmp(cond->method, "ImportPayout") == 0) { + return ImportPayout(cond, txTo, nIn); + } + return Invalid("invalid-method"); + } + + bool GetSpends(uint256 hash, std::vector &spendsOut) const + { + auto r = spends.find(hash); + if (r != spends.end()) { + spendsOut = r->second; + return true; + } + return false; + } + + bool GetTx(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow) const + { + auto r = txs.find(hash); + if (r != txs.end()) { + txOut = r->second; + hashBlock = hash; + return true; + } + return false; + } + + unsigned int GetCurrentHeight() const { return currentHeight; } + + bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const + { + auto r = blocks.find(hash); + if (r == blocks.end()) return false; + blockIdx = r->second; + return true; + } + + bool GetMoM(uint256 notarisationHash, uint256& _MoM) const + { + if (notarisationHash == NotarisationHash()) { + _MoM = MoM; + return true; + } + return false; + } + + static uint256 NotarisationHash() + { + uint256 h; + h.begin()[0] = 123; + return h; + } +}; + + +extern Eval* EVAL_TEST; + + +/* + * Generates example data that we will test with and shows how to call BetProtocol. + */ +class ExampleBet +{ +public: + BetProtocol bet; + CAmount totalPayout; + + ExampleBet() : bet(BetProtocol(players, DisputeHeader(2, VCH("BetHeader", 9)))), totalPayout(100) {} + ~ExampleBet() {}; + + CTransaction SessionTx() + { + return CTransaction(bet.MakeSessionTx()); + } + + CC* DisputeCond() + { + return bet.MakeDisputeCond(); + } + + CC* PayoutCond() + { + return bet.MakePayoutCond(SessionTx().GetHash()); + } + + CTransaction StakeTx() + { + return CTransaction(bet.MakeStakeTx(totalPayout, SessionTx().GetHash())); + } + + std::vector PlayerState(int playerIdx) + { + std::vector state; + for (int i=0; i Payouts(int playerIdx) + { + return MockVM().evaluate(bet.disputeHeader.vmParams, PlayerState(playerIdx)).second; + } + + CMutableTransaction DisputeTx(int playerIdx) + { + return bet.MakeDisputeTx(SessionTx().GetHash(), SerializeHash(Payouts(playerIdx))); + } + + CMutableTransaction PostEvidenceTx(int playerIdx) + { + return bet.MakePostEvidenceTx(SessionTx().GetHash(), playerIdx, PlayerState(playerIdx)); + } + + CMutableTransaction AgreePayoutTx() + { + std::vector v; + return bet.MakeAgreePayoutTx(v, uint256()); + } + + MoMProof GetMoMProof() + { + int nIndex = 5; + std::vector vBranch; + vBranch.resize(3); + return MoMProof(nIndex, vBranch, EvalMock::NotarisationHash()); + } + + CMutableTransaction ImportPayoutTx() + { + CMutableTransaction disputeTx = DisputeTx(Player2); + return bet.MakeImportPayoutTx(Payouts(Player2), disputeTx, uint256(), GetMoMProof()); + } + + EvalMock SetEvalMock(int currentHeight) + { + EvalMock eval; + CTransaction sessionTx = SessionTx(); + + eval.txs[sessionTx.GetHash()] = sessionTx; + + CBlockIndex sessionBlock; + sessionBlock.nHeight = 10; + eval.blocks[sessionTx.GetHash()] = sessionBlock; + + std::vector sessionSpends; + sessionSpends.push_back(CTransaction(PostEvidenceTx(Dealer))); + sessionSpends.push_back(CTransaction()); // Invalid, should be ignored + sessionSpends.push_back(CTransaction(PostEvidenceTx(Player2))); + eval.spends[sessionTx.GetHash()] = sessionSpends; + + eval.currentHeight = currentHeight; + + MoMProof proof = GetMoMProof(); + eval.MoM = ExecMerkle(DisputeTx(Player2).GetHash(), proof.branch, proof.nIndex); + + EVAL_TEST = &eval; + return eval; + } +}; + + +ExampleBet ebet; + + +class TestBet : public ::testing::Test { +protected: + static void SetUpTestCase() { + // Make playerSecrets + CBitcoinSecret vchSecret; + auto addKey = [&] (std::string k) { vchSecret.SetString(k); playerSecrets.push_back(vchSecret.GetKey()); }; + addKey("UwFBKf4d6wC3yqdnk3LoGrFjy7gwxrWerBT8jTFamrBbem8wSw9L"); + addKey("Up6GpWwrmx2VpqF8rD3snJXToKT56Dzc8YSoL24osXnfNdCucaMR"); + addKey("UxEHwki3A95PSHHVRzE2N67eHTeoUcqLkovxp6yDPVViv54skF8c"); + // Make playerpubkeys + for (int i=0; isubconditions[0]->paramsBin, 11)); + for (int i=0; isubconditions[1]->subconditions[i])); +} + + +TEST_F(TestBet, testSignDisputeCond) +{ + // Only one key needed to dispute + CMutableTransaction disputeTx = ebet.DisputeTx(Player1); + CC *disputeCond = ebet.DisputeCond(); + EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1})); + + EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[0])); + EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[1])); + EXPECT_EQ(0, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[0])); + EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[1])); + EXPECT_EQ(0, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[2])); + EXPECT_EQ(1, cc_isFulfilled(disputeCond)); +} + + +TEST_F(TestBet, testDispute) +{ + EvalMock eval = ebet.SetEvalMock(12); + + // Only one key needed to dispute + CMutableTransaction disputeTx = ebet.DisputeTx(Player2); + CC *disputeCond = ebet.DisputeCond(); + EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2})); + + // Success + EXPECT_TRUE(TestCC(disputeTx, 0, disputeCond)); + + // Set result hash to some rubbish and check false + uint256 rubbishHash; + std::vector rubbish(rubbishHash.begin(), rubbishHash.end()); + disputeTx.vout[0].scriptPubKey = CScript() << OP_RETURN << rubbish; + EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2})); + EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond)); + EXPECT_EQ("wrong-payout", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testDisputeInvalidOutput) +{ + EvalMock eval = ebet.SetEvalMock(11); + + // Only one key needed to dispute + CMutableTransaction disputeTx = ebet.DisputeTx(Dealer); + CC *disputeCond = ebet.DisputeCond(); + + // invalid payout hash + std::vector invalidHash = {0,1,2}; + disputeTx.vout[0].scriptPubKey = CScript() << OP_RETURN << invalidHash; + ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1})); + EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond)); + EXPECT_EQ("invalid-payout-hash", eval.state.GetRejectReason()); + + // no vout at all + disputeTx.vout.resize(0); + ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1})); + EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond)); + EXPECT_EQ("no-vouts", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testDisputeEarly) +{ + EvalMock eval = ebet.SetEvalMock(11); + + // Only one key needed to dispute + CMutableTransaction disputeTx = ebet.DisputeTx(Dealer); + CC *disputeCond = ebet.DisputeCond(); + EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1})); + + EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond)); + EXPECT_EQ("dispute-too-soon", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testDisputeInvalidParams) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction disputeTx = ebet.DisputeTx(Player2); + CC *disputeCond = ebet.DisputeCond(); + CC *evalCond = disputeCond->subconditions[0]; + + // too long + evalCond->paramsBin = (unsigned char*) realloc(evalCond->paramsBin, ++evalCond->paramsBinLength); + ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2})); + EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond)); + EXPECT_EQ("invalid-dispute-header", eval.state.GetRejectReason()); + + // too short + eval.state = CValidationState(); + evalCond->paramsBinLength = 1; + ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2})); + EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond)); + EXPECT_EQ("invalid-dispute-header", eval.state.GetRejectReason()); + + // is fine + eval.state = CValidationState(); + evalCond->paramsBinLength = 11; + ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2})); + EXPECT_TRUE(TestCC(disputeTx, 0, disputeCond)); +} + + +TEST_F(TestBet, testDisputeInvalidEvidence) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction disputeTx = ebet.DisputeTx(Player2); + CC *disputeCond = ebet.DisputeCond(); + CCSign(disputeTx, 0, disputeCond, {Player2}); + + CMutableTransaction mtx; + + mtx.vout.resize(1); + mtx.vout[0].scriptPubKey = CScript(); + eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx); + ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond)); + + mtx.vout[0].scriptPubKey << OP_RETURN; + eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx); + ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond)); + + mtx.vout[0].scriptPubKey = CScript() << 0; + eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx); + ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond)); + + eval.spends[ebet.SessionTx().GetHash()].resize(1); + eval.spends[ebet.SessionTx().GetHash()][0] = CTransaction(); + ASSERT_FALSE(TestCC(disputeTx, 0, disputeCond)); + EXPECT_EQ("no-evidence", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testMakeStakeTx) +{ + CTransaction stakeTx = ebet.StakeTx(); + EXPECT_EQ(0, stakeTx.vin.size()); + EXPECT_EQ(1, stakeTx.vout.size()); + EXPECT_EQ(ebet.totalPayout, stakeTx.vout[0].nValue); + EXPECT_EQ(CCPubKey(ebet.PayoutCond()), stakeTx.vout[0].scriptPubKey); +} + + +TEST_F(TestBet, testMakePayoutCond) +{ + CC *payoutCond = ebet.PayoutCond(); + EXPECT_EQ("(1 of (3 of 5,5,5),(2 of (1 of 5,5,5),15))", CCShowStructure(payoutCond)); + EXPECT_EQ(0, memcmp(payoutCond->subconditions[1]->subconditions[1]->paramsBin, + ebet.SessionTx().GetHash().begin(), 32)); +} + + +TEST_F(TestBet, testSignPayout) +{ + + CMutableTransaction payoutTx = ebet.AgreePayoutTx(); + CC *payoutCond = ebet.PayoutCond(); + + EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0])); + EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[1])); + EXPECT_EQ(0, cc_isFulfilled(payoutCond)); + + EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player1})); + EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0])); + EXPECT_EQ(1, cc_isFulfilled(payoutCond->subconditions[1])); + EXPECT_EQ(1, cc_isFulfilled(payoutCond)); + + EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player2})); + EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0])); + + EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Dealer})); + EXPECT_EQ(1, cc_isFulfilled(payoutCond->subconditions[0])); +} + + +TEST_F(TestBet, testAgreePayout) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction payoutTx = ebet.AgreePayoutTx(); + CC *payoutCond = ebet.PayoutCond(); + + EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Dealer})); + EXPECT_FALSE(TestCC(payoutTx, 0, payoutCond)); + EXPECT_EQ("(1 of (2 of (1 of 5,A5,A5),15),A2)", + CCShowStructure(CCPrune(payoutCond))); + + EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player1})); + EXPECT_FALSE(TestCC(payoutTx, 0, payoutCond)); + EXPECT_EQ("(1 of (2 of (1 of 5,A5,A5),15),A2)", + CCShowStructure(CCPrune(payoutCond))); + + EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player2})); + EXPECT_TRUE( TestCC(payoutTx, 0, payoutCond)); + EXPECT_EQ("(1 of (3 of 5,5,5),A2)", + CCShowStructure(CCPrune(payoutCond))); +} + + +TEST_F(TestBet, testImportPayout) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction importTx = ebet.ImportPayoutTx(); + CC *payoutCond = ebet.PayoutCond(); + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + EXPECT_TRUE(TestCC(importTx, 0, payoutCond)); +} + + +TEST_F(TestBet, testImportPayoutFewVouts) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction importTx = ebet.ImportPayoutTx(); + importTx.vout.resize(1); + CC *payoutCond = ebet.PayoutCond(); + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + EXPECT_FALSE(TestCC(importTx, 0, payoutCond)); + EXPECT_EQ("need-2-vouts", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testImportPayoutInvalidDisputeTx) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction importTx = ebet.ImportPayoutTx(); + importTx.vout[1].scriptPubKey.pop_back(); + CC *payoutCond = ebet.PayoutCond(); + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + EXPECT_FALSE(TestCC(importTx, 0, payoutCond)); + EXPECT_EQ("invalid-dispute-tx", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testImportPayoutWrongPayouts) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction importTx = ebet.ImportPayoutTx(); + importTx.vout[2].nValue = 7; + CC *payoutCond = ebet.PayoutCond(); + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + ASSERT_FALSE(TestCC(importTx, 0, payoutCond)); + EXPECT_EQ("wrong-payouts", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testImportPayoutMangleSessionId) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction importTx = ebet.ImportPayoutTx(); + CC *payoutCond = ebet.PayoutCond(); + payoutCond->subconditions[1]->subconditions[1]->paramsBinLength = 31; + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + ASSERT_FALSE(TestCC(importTx, 0, payoutCond)); + EXPECT_EQ("malformed-params", eval.state.GetRejectReason()); + + payoutCond = ebet.PayoutCond(); + memset(payoutCond->subconditions[1]->subconditions[1]->paramsBin, 1, 32); + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + ASSERT_FALSE(TestCC(importTx, 0, payoutCond)); + EXPECT_EQ("wrong-session", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testImportPayoutInvalidProofPayload) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction importTx = ebet.ImportPayoutTx(); + importTx.vout[0].scriptPubKey.pop_back(); + CC *payoutCond = ebet.PayoutCond(); + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + EXPECT_FALSE(TestCC(importTx, 0, payoutCond)); + EXPECT_EQ("invalid-mom-proof-payload", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testImportPayoutInvalidNotarisationHash) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction importTx = ebet.ImportPayoutTx(); + MoMProof proof = ebet.GetMoMProof(); + proof.notarisationHash = uint256(); + importTx.vout[0].scriptPubKey = CScript() << OP_RETURN << CheckSerialize(proof); + CC *payoutCond = ebet.PayoutCond(); + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + EXPECT_FALSE(TestCC(importTx, 0, payoutCond)); + EXPECT_EQ("coudnt-load-mom", eval.state.GetRejectReason()); +} + + +TEST_F(TestBet, testImportPayoutMomFail) +{ + EvalMock eval = ebet.SetEvalMock(12); + + CMutableTransaction importTx = ebet.ImportPayoutTx(); + MoMProof proof = ebet.GetMoMProof(); + proof.nIndex ^= 1; + importTx.vout[0].scriptPubKey = CScript() << OP_RETURN << CheckSerialize(proof); + CC *payoutCond = ebet.PayoutCond(); + EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2})); + EXPECT_FALSE(TestCC(importTx, 0, payoutCond)); + EXPECT_EQ("mom-check-fail", eval.state.GetRejectReason()); +} diff --git a/src/test-komodo/test_cryptoconditions.cpp b/src/test-komodo/test_cryptoconditions.cpp index 9ab4b676c..f22d50be3 100644 --- a/src/test-komodo/test_cryptoconditions.cpp +++ b/src/test-komodo/test_cryptoconditions.cpp @@ -8,60 +8,35 @@ #include "script/interpreter.h" #include "script/serverchecker.h" +#include "testutils.h" -#define VCH(a,b) std::vector(a, a + b) + +CKey notaryKey; std::string pubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47"; std::string secret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU"; -CKey notaryKey; - - -char ccjsonerr[1000] = "\0"; -#define CCFromJson(o,s) \ - o = cc_conditionFromJSONString(s, ccjsonerr); \ - if (!o) FAIL() << "bad json: " << ccjsonerr; - - -CScript CCPubKey(const CC *cond) { - unsigned char buf[1000]; - size_t len = cc_conditionBinary(cond, buf); - return CScript() << VCH(buf, len) << OP_CHECKCRYPTOCONDITION; -} - - -CScript CCSig(const CC *cond) { - unsigned char buf[1000]; - size_t len = cc_fulfillmentBinary(cond, buf, 1000); - auto ffill = VCH(buf, len); - ffill.push_back(SIGHASH_ALL); - return CScript() << ffill; -} - - -void CCSign(CMutableTransaction &tx, CC *cond) { - tx.vin.resize(1); - PrecomputedTransactionData txdata(tx); - uint256 sighash = SignatureHash(CCPubKey(cond), tx, 0, SIGHASH_ALL, 0, 0, &txdata); - - int out = cc_signTreeSecp256k1Msg32(cond, notaryKey.begin(), sighash.begin()); - tx.vin[0].scriptSig = CCSig(cond); -} class CCTest : public ::testing::Test { +public: + void CCSign(CMutableTransaction &tx, CC *cond) { + tx.vin.resize(1); + PrecomputedTransactionData txdata(tx); + uint256 sighash = SignatureHash(CCPubKey(cond), tx, 0, SIGHASH_ALL, 0, 0, &txdata); + + int out = cc_signTreeSecp256k1Msg32(cond, notaryKey.begin(), sighash.begin()); + tx.vin[0].scriptSig = CCSig(cond); + } protected: - static void SetUpTestCase() { - SelectParams(CBaseChainParams::REGTEST); + virtual void SetUp() { + // enable CC + ASSETCHAINS_CC = 1; // Notary key CBitcoinSecret vchSecret; // this returns false due to network prefix mismatch but works anyway vchSecret.SetString(secret); notaryKey = vchSecret.GetKey(); } - virtual void SetUp() { - // enable CC - ASSETCHAINS_CC = 1; - } }; diff --git a/src/test-komodo/testutils.h b/src/test-komodo/testutils.h new file mode 100644 index 000000000..7c38526a3 --- /dev/null +++ b/src/test-komodo/testutils.h @@ -0,0 +1,15 @@ +#ifndef TESTUTILS_H +#define TESTUTILS_H + +#include "komodo_cc.h" + + +#define VCH(a,b) std::vector(a, a + b) + +static char ccjsonerr[1000] = "\0"; +#define CCFromJson(o,s) \ + o = cc_conditionFromJSONString(s, ccjsonerr); \ + if (!o) FAIL() << "bad json: " << ccjsonerr; + + +#endif /* TESTUTILS_H */