This commit is contained in:
Scott Sadler
2018-04-16 22:57:35 -05:00
parent 3205a566d4
commit 0cb91a8d20
30 changed files with 1002 additions and 289 deletions

View File

@@ -1,5 +1,8 @@
#include <cryptoconditions.h>
#include "hash.h"
#include "main.h"
#include "chain.h"
#include "streams.h"
#include "script/cc.h"
#include "cc/eval.h"
@@ -137,3 +140,146 @@ bool GetOpReturnHash(CScript script, uint256 &hash)
hash = uint256(vHash);
return true;
}
/*
* Crypto-Condition EVAL method that verifies a payout against a transaction
* notarised on another chain.
*
* IN: params - condition params
* IN: importTx - Payout transaction on value chain (KMD)
* IN: nIn - index of input of stake
*
* importTx: Spends stakeTx with payouts from asset chain
*
* in 0: Spends Stake TX and contains ImportPayout CC
* out 0: OP_RETURN MomProof, disputeTx
* out 1-: arbitrary payouts
*
* disputeTx: Spends sessionTx.0 (opener on asset chain)
*
* in 0: spends sessionTx.0
* in 1-: anything
* out 0: OP_RETURN hash of payouts
* out 1-: anything
*/
bool Eval::ImportPayout(const std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn)
{
if (importTx.vout.size() == 0) return Invalid("no-vouts");
// load data from vout[0]
MoMProof proof;
CTransaction disputeTx;
{
std::vector<unsigned char> vopret;
GetOpReturnData(importTx.vout[0].scriptPubKey, vopret);
if (!E_UNMARSHAL(vopret, ss >> proof; ss >> disputeTx))
return Invalid("invalid-payload");
}
// Check disputeTx.0 shows correct payouts
{
uint256 givenPayoutsHash;
GetOpReturnHash(disputeTx.vout[0].scriptPubKey, givenPayoutsHash);
std::vector<CTxOut> payouts(importTx.vout.begin() + 1, importTx.vout.end());
if (givenPayoutsHash != SerializeHash(payouts))
return Invalid("wrong-payouts");
}
// Check disputeTx spends sessionTx.0
// condition ImportPayout params is session ID from other chain
{
uint256 sessionHash;
if (!E_UNMARSHAL(params, ss >> sessionHash))
return Invalid("malformed-params");
if (disputeTx.vin[0].prevout != COutPoint(sessionHash, 0))
return Invalid("wrong-session");
}
// Check disputeTx solves momproof from vout[0]
{
NotarisationData data;
if (!GetNotarisationData(proof.notarisationHash, data))
return Invalid("coudnt-load-mom");
if (data.MoM != proof.branch.Exec(disputeTx.GetHash()))
return Invalid("mom-check-fail");
}
return Valid();
}
/*
* Crypto-Condition EVAL method that resolves a dispute of a session
*
* IN: vm - AppVM virtual machine to verify states
* IN: params - condition params
* IN: disputeTx - transaction attempting to resolve dispute
* IN: nIn - index of input of dispute tx
*
* disputeTx: attempt to resolve a dispute
*
* in 0: Spends Session TX first output, reveals DisputeHeader
* out 0: OP_RETURN hash of payouts
*/
bool Eval::DisputePayout(AppVM &vm, std::vector<uint8_t> params, const CTransaction &disputeTx, unsigned int nIn)
{
if (disputeTx.vout.size() == 0) return Invalid("no-vouts");
// get payouts hash
uint256 payoutHash;
if (!GetOpReturnHash(disputeTx.vout[0].scriptPubKey, payoutHash))
return Invalid("invalid-payout-hash");
// load params
uint16_t waitBlocks;
std::vector<uint8_t> vmParams;
if (!E_UNMARSHAL(params, ss >> VARINT(waitBlocks); ss >> vmParams))
return Invalid("malformed-params");
// ensure that enough time has passed
{
CTransaction sessionTx;
CBlockIndex sessionBlock;
// if unconformed its too soon
if (!GetTxConfirmed(disputeTx.vin[0].prevout.hash, sessionTx, sessionBlock))
return Error("couldnt-get-parent");
if (GetCurrentHeight() < sessionBlock.nHeight + waitBlocks)
return Invalid("dispute-too-soon"); // Not yet
}
// get spends
std::vector<CTransaction> spends;
if (!GetSpendsConfirmed(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<spends.size(); i++)
{
std::vector<unsigned char> vmState;
if (spends[i].vout.size() == 0) continue;
if (!GetOpReturnData(spends[i].vout[0].scriptPubKey, vmState)) continue;
auto out = vm.evaluate(vmParams, vmState);
uint256 resultHash = SerializeHash(out.second);
if (out.first > maxLength) {
maxLength = out.first;
bestPayout = resultHash;
}
// The below means that if for any reason there is a draw, the first dispute wins
else if (out.first == maxLength) {
if (bestPayout != payoutHash) {
fprintf(stderr, "WARNING: VM has multiple solutions of same length\n");
bestPayout = resultHash;
}
}
}
if (maxLength == -1) return Invalid("no-evidence");
return bestPayout == payoutHash ? Valid() : Invalid("wrong-payout");
}

View File

@@ -3,7 +3,6 @@
#include "cc/eval.h"
#include "pubkey.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "cryptoconditions/include/cryptoconditions.h"
@@ -11,25 +10,18 @@
class MoMProof
{
public:
int nIndex;
std::vector<uint256> branch;
MerkleBranch branch;
uint256 notarisationHash;
MoMProof() {}
MoMProof(int i, std::vector<uint256> b, uint256 n) : notarisationHash(n), nIndex(i), branch(b) {}
uint256 Exec(uint256 hash) const { return CBlock::CheckMerkleBranch(hash, branch, nIndex); }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(VARINT(nIndex));
READWRITE(branch);
READWRITE(notarisationHash);
}
};
class BetProtocol
{
protected:

View File

@@ -1,84 +0,0 @@
#include <cryptoconditions.h>
#include "hash.h"
#include "chain.h"
#include "version.h"
#include "script/cc.h"
#include "cc/eval.h"
#include "cc/betprotocol.h"
#include "primitives/transaction.h"
/*
* Crypto-Condition EVAL method that resolves a dispute of a session
*
* IN: vm - AppVM virtual machine to verify states
* IN: params - condition params
* IN: disputeTx - transaction attempting to resolve dispute
* IN: nIn - index of input of dispute tx
*
* disputeTx: attempt to resolve a dispute
*
* in 0: Spends Session TX first output, reveals DisputeHeader
* out 0: OP_RETURN hash of payouts
*/
bool Eval::DisputePayout(AppVM &vm, std::vector<uint8_t> params, const CTransaction &disputeTx, unsigned int nIn)
{
if (disputeTx.vout.size() == 0) return Invalid("no-vouts");
// get payouts hash
uint256 payoutHash;
if (!GetOpReturnHash(disputeTx.vout[0].scriptPubKey, payoutHash))
return Invalid("invalid-payout-hash");
// load params
uint16_t waitBlocks;
std::vector<uint8_t> vmParams;
if (!E_UNMARSHAL(params, ss >> VARINT(waitBlocks); ss >> vmParams))
return Invalid("malformed-params");
// ensure that enough time has passed
{
CTransaction sessionTx;
CBlockIndex sessionBlock;
// if unconformed its too soon
if (!GetTxConfirmed(disputeTx.vin[0].prevout.hash, sessionTx, sessionBlock))
return Error("couldnt-get-parent");
if (GetCurrentHeight() < sessionBlock.nHeight + waitBlocks)
return Invalid("dispute-too-soon"); // Not yet
}
// get spends
std::vector<CTransaction> spends;
if (!GetSpendsConfirmed(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<spends.size(); i++)
{
std::vector<unsigned char> vmState;
if (spends[i].vout.size() == 0) continue;
if (!GetOpReturnData(spends[i].vout[0].scriptPubKey, vmState)) continue;
auto out = vm.evaluate(vmParams, vmState);
uint256 resultHash = SerializeHash(out.second);
if (out.first > maxLength) {
maxLength = out.first;
bestPayout = resultHash;
}
// The below means that if for any reason there is a draw, the first dispute wins
else if (out.first == maxLength) {
if (bestPayout != payoutHash) {
fprintf(stderr, "WARNING: VM has multiple solutions of same length\n");
bestPayout = resultHash;
}
}
}
if (maxLength == -1) return Invalid("no-evidence");
return bestPayout == payoutHash ? Valid() : Invalid("wrong-payout");
}

View File

@@ -49,6 +49,10 @@ bool Eval::Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn)
return ImportPayout(vparams, txTo, nIn);
}
if (ecode == EVAL_IMPORTCOIN) {
return ImportCoin(vparams, txTo, nIn);
}
return Invalid("invalid-code");
}
@@ -143,6 +147,12 @@ bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t t
}
uint32_t Eval::GetCurrentLedgerID() const
{
return -1; // TODO
}
/*
* Get MoM from a notarisation tx hash
*/
@@ -157,6 +167,11 @@ bool Eval::GetNotarisationData(const uint256 notaryHash, NotarisationData &data)
return true;
}
bool Eval::GetNotarisationData(int notarisationHeight, NotarisationData &data, bool verifyCanonical) const
{
return false;
}
/*
* Notarisation data, ie, OP_RETURN payload in notarisation transactions
@@ -202,4 +217,26 @@ std::string EvalToStr(EvalCode c)
char s[10];
sprintf(s, "0x%x", c);
return std::string(s);
}
uint256 SafeCheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex)
{
if (nIndex == -1)
return uint256();
for (auto it(vMerkleBranch.begin()); it != vMerkleBranch.end(); ++it)
{
if (nIndex & 1) {
if (*it == hash) {
// non canonical. hash may be equal to node but never on the right.
return uint256();
}
hash = Hash(BEGIN(*it), END(*it), BEGIN(hash), END(hash));
}
else
hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it));
nIndex >>= 1;
}
return hash;
}

View File

@@ -20,8 +20,10 @@
* a possible code is EVAL_BITCOIN_SCRIPT, where the entire binary
* after the code is interpreted as a bitcoin script.
*/
#define FOREACH_EVAL(EVAL) \
EVAL(EVAL_IMPORTPAYOUT, 0xe1)
#define FOREACH_EVAL(EVAL) \
EVAL(EVAL_IMPORTPAYOUT, 0xe1) \
EVAL(EVAL_IMPORTCOIN, 0xe2)
typedef uint8_t EvalCode;
@@ -54,6 +56,11 @@ public:
*/
bool ImportPayout(std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn);
/*
* Import coin from another chain with same symbol
*/
bool ImportCoin(std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn);
/*
* IO functions
*/
@@ -64,7 +71,10 @@ public:
virtual bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const;
virtual int32_t GetNotaries(uint8_t pubkeys[64][33], int32_t height, uint32_t timestamp) const;
virtual bool GetNotarisationData(uint256 notarisationHash, NotarisationData &data) const;
virtual bool GetNotarisationData(int notarisationHeight, NotarisationData &data,
bool verifyCanonical) const;
virtual bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const;
virtual uint32_t GetCurrentLedgerID() const;
};
@@ -87,7 +97,6 @@ public:
evaluate(std::vector<unsigned char> header, std::vector<unsigned char> body) = 0;
};
/*
* Data from notarisation OP_RETURN
*/
@@ -99,6 +108,8 @@ public:
char symbol[64];
uint256 MoM;
uint32_t MoMDepth;
uint256 MoMoM;
uint32_t MoMoMDepth;
bool Parse(CScript scriptPubKey);
};
@@ -140,4 +151,30 @@ bool DeserializeF(const std::vector<unsigned char> vIn, T f)
}
/*
* Merkle stuff
*/
uint256 SafeCheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex);
class MerkleBranch
{
public:
int nIndex;
std::vector<uint256> branch;
MerkleBranch() {}
MerkleBranch(int i, std::vector<uint256> b) : nIndex(i), branch(b) {}
uint256 Exec(uint256 hash) const { return SafeCheckMerkleBranch(hash, branch, nIndex); }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(VARINT(nIndex));
READWRITE(branch);
}
};
#endif /* CC_EVAL_H */

147
src/cc/importcoin.cpp Normal file
View File

@@ -0,0 +1,147 @@
#include "cc/importcoin.h"
#include "hash.h"
#include "script/cc.h"
#include "primitives/transaction.h"
/*
* Generate ImportCoin transaction.
*
* Contains an empty OP_RETURN as first output; this is critical for preventing a double
* import. If it doesn't contain this it's invalid. The empty OP_RETURN will hang around
* in the UTXO set and the transaction will be detected as a duplicate.
*/
CTransaction MakeImportCoinTransaction(const MomoProof proof, const CTransaction burnTx, const std::vector<CTxOut> payouts)
{
std::vector<uint8_t> payload =
E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << proof; ss << burnTx);
CMutableTransaction mtx;
mtx.vin.resize(1);
mtx.vin[0].scriptSig << payload;
mtx.vin[0].prevout.n = 10e8;
mtx.vout = payouts;
return CTransaction(mtx);
}
CTxOut MakeBurnOutput(CAmount value, int targetChain, const std::vector<CTxOut> payouts)
{
std::vector<uint8_t> opret = E_MARSHAL(ss << VARINT(targetChain); ss << SerializeHash(payouts));
return CTxOut(value, CScript() << OP_RETURN << opret);
}
/*
* CC Eval method for import coin.
*
* This method has to control *every* parameter of the ImportCoin transaction, so that the legal
* importTx for a valid burnTx is 1:1. There can be no two legal importTx transactions for a burnTx
* on another chain.
*/
bool Eval::ImportCoin(const std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn)
{
if (importTx.vout.size() == 0) return Invalid("no-vouts");
// params
MomoProof proof;
CTransaction burnTx;
if (!E_UNMARSHAL(params, ss >> proof; ss >> burnTx))
return Invalid("invalid-params");
// Control all aspects of this transaction
// It must not be at all malleable
if (MakeImportCoinTransaction(proof, burnTx, importTx.vout).GetHash() != importTx.GetHash())
return Invalid("non-canonical");
// burn params
uint32_t chain; // todo
uint256 payoutsHash;
std::vector<uint8_t> burnOpret;
if (burnTx.vout.size() == 0) return Invalid("invalid-burn-outputs");
GetOpReturnData(burnTx.vout[0].scriptPubKey, burnOpret);
if (!E_UNMARSHAL(burnOpret, ss >> VARINT(chain); ss >> payoutsHash))
return Invalid("invalid-burn-params");
// check chain
if (chain != GetCurrentLedgerID())
return Invalid("importcoin-wrong-chain");
// check burn amount
{
uint64_t burnAmount = burnTx.vout[0].nValue;
if (burnAmount == 0)
return Invalid("invalid-burn-amount");
uint64_t totalOut = 0;
for (int i=0; i<importTx.vout.size(); i++)
totalOut += importTx.vout[i].nValue;
if (totalOut > burnAmount)
return Invalid("payout-too-high");
}
// Check burntx shows correct outputs hash
if (payoutsHash != SerializeHash(importTx.vout))
return Invalid("wrong-payouts");
// Check proof confirms existance of burnTx
{
NotarisationData data;
if (!GetNotarisationData(proof.notarisationHeight, data, true))
return Invalid("coudnt-load-momom");
if (data.MoMoM != proof.branch.Exec(burnTx.GetHash()))
return Invalid("momom-check-fail");
}
return Valid();
}
/*
* Required by main
*/
CAmount GetCoinImportValue(const CTransaction &tx)
{
CScript scriptSig = tx.vin[0].scriptSig;
auto pc = scriptSig.begin();
opcodetype opcode;
std::vector<uint8_t> evalScript;
if (!scriptSig.GetOp(pc, opcode, evalScript))
return false;
if (pc != scriptSig.end())
return false;
EvalCode code;
MomoProof proof;
CTransaction burnTx;
if (!E_UNMARSHAL(evalScript, ss >> proof; ss >> burnTx))
return 0;
return burnTx.vout.size() ? burnTx.vout[0].nValue : 0;
}
/*
* CoinImport is different enough from normal script execution that it's not worth
* making all the mods neccesary in the interpreter to do the dispatch correctly.
*/
bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& checker, CValidationState &state)
{
auto pc = scriptSig.begin();
opcodetype opcode;
std::vector<uint8_t> evalScript;
auto f = [&] () {
if (!scriptSig.GetOp(pc, opcode, evalScript))
return false;
if (pc != scriptSig.end())
return false;
if (evalScript.size() == 0)
return false;
if (evalScript.begin()[0] != EVAL_IMPORTCOIN)
return false;
// Ok, all looks good so far...
CC *cond = CCNewEval(evalScript);
bool out = checker.CheckEvalCondition(cond);
cc_free(cond);
return out;
};
return f() ? true : state.Invalid(false, 0, "invalid-coin-import");
}

33
src/cc/importcoin.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef CC_IMPORTCOIN_H
#define CC_IMPORTCOIN_H
#include "cc/eval.h"
#include "primitives/transaction.h"
#include "script/interpreter.h"
#include <cryptoconditions.h>
class MomoProof
{
public:
MerkleBranch branch;
int notarisationHeight;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(branch);
READWRITE(notarisationHeight);
}
};
CAmount GetCoinImportValue(const CTransaction &tx);
CTransaction MakeImportCoinTransaction(const MomoProof proof,
const CTransaction burnTx, const std::vector<CTxOut> payouts);
CTxOut MakeBurnOutput(CAmount value, int targetChain, const std::vector<CTxOut> payouts);
bool VerifyCoinImport(const CScript& scriptSig,
TransactionSignatureChecker& checker, CValidationState &state);
#endif /* CC_IMPORTCOIN_H */

View File

@@ -1,76 +0,0 @@
#include <cryptoconditions.h>
#include "main.h"
#include "chain.h"
#include "streams.h"
#include "cc/eval.h"
#include "cc/betprotocol.h"
#include "primitives/transaction.h"
/*
* Crypto-Condition EVAL method that verifies a payout against a transaction
* notarised on another chain.
*
* IN: params - condition params
* IN: importTx - Payout transaction on value chain (KMD)
* IN: nIn - index of input of stake
*
* importTx: Spends stakeTx with payouts from asset chain
*
* in 0: Spends Stake TX and contains ImportPayout CC
* out 0: OP_RETURN MomProof, disputeTx
* out 1-: arbitrary payouts
*
* disputeTx: Spends sessionTx.0 (opener on asset chain)
*
* in 0: spends sessionTx.0
* in 1-: anything
* out 0: OP_RETURN hash of payouts
* out 1-: anything
*/
bool Eval::ImportPayout(const std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn)
{
if (importTx.vout.size() == 0) return Invalid("no-vouts");
// load data from vout[0]
MoMProof proof;
CTransaction disputeTx;
{
std::vector<unsigned char> vopret;
GetOpReturnData(importTx.vout[0].scriptPubKey, vopret);
if (!E_UNMARSHAL(vopret, ss >> proof; ss >> disputeTx))
return Invalid("invalid-payload");
}
// Check disputeTx.0 shows correct payouts
{
uint256 givenPayoutsHash;
GetOpReturnHash(disputeTx.vout[0].scriptPubKey, givenPayoutsHash);
std::vector<CTxOut> payouts(importTx.vout.begin() + 1, importTx.vout.end());
if (givenPayoutsHash != SerializeHash(payouts))
return Invalid("wrong-payouts");
}
// Check disputeTx spends sessionTx.0
// condition ImportPayout params is session ID from other chain
{
uint256 sessionHash;
if (!E_UNMARSHAL(params, ss >> sessionHash))
return Invalid("malformed-params");
if (disputeTx.vin[0].prevout != COutPoint(sessionHash, 0))
return Invalid("wrong-session");
}
// Check disputeTx solves momproof from vout[0]
{
NotarisationData data;
if (!GetNotarisationData(proof.notarisationHash, data))
return Invalid("coudnt-load-mom");
if (data.MoM != proof.Exec(disputeTx.GetHash()))
return Invalid("mom-check-fail");
}
return Valid();
}