// Copyright (c) 2016-2020 The Hush developers // Distributed under the GPLv3 software license, see the accompanying // file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html /****************************************************************************** * Copyright © 2014-2019 The SuperNET Developers. * * * * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * SuperNET software, including this file may be copied, modified, propagated * * or distributed except according to the terms contained in the LICENSE file * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ #include "cc/eval.h" #include "cc/utils.h" #include "importcoin.h" #include "crosschain.h" #include "primitives/transaction.h" #include "cc/CCinclude.h" #include #include "cc/CCtokens.h" #include "key_io.h" #define CODA_BURN_ADDRESS "KPrrRoPfHOnNpZZQ6laHXdQDkSQDkVHaN0V+LizLlHxz7NaA59sBAAAA" /* * CC Eval method for import coin. * * This method should control every parameter of the ImportCoin transaction, since it has no signature * to protect it from malleability. ##### 0xffffffff is a special CCid for single chain/dual daemon imports */ extern std::string ASSETCHAINS_SELFIMPORT; extern uint16_t ASSETCHAINS_CODAPORT,ASSETCHAINS_BEAMPORT; extern uint8_t ASSETCHAINS_OVERRIDE_PUBKEY33[33]; extern uint256 KOMODO_EARLYTXID; // utilities from gateways.cpp uint256 BitcoinGetProofMerkleRoot(const std::vector &proofData, std::vector &txids); uint256 GatewaysReverseScan(uint256 &txid, int32_t height, uint256 reforacletxid, uint256 batontxid); int32_t GatewaysCointxidExists(struct CCcontract_info *cp, uint256 cointxid); uint8_t DecodeImportGatewayBindOpRet(char *burnaddr,const CScript &scriptPubKey,std::string &coin,uint256 &oracletxid,uint8_t &M,uint8_t &N,std::vector &importgatewaypubkeys,uint8_t &taddr,uint8_t &prefix,uint8_t &prefix2,uint8_t &wiftype); int64_t ImportGatewayVerify(char *refburnaddr,uint256 oracletxid,int32_t claimvout,std::string refcoin,uint256 burntxid,const std::string deposithex,std::vectorproof,uint256 merkleroot,CPubKey destpub,uint8_t taddr,uint8_t prefix,uint8_t prefix2); char *nonportable_path(char *str); char *portable_path(char *str); void *loadfile(char *fname,uint8_t **bufp,long *lenp,long *allocsizep); void *filestr(long *allocsizep,char *_fname); cJSON* CodaRPC(char **retstr,char const *arg0,char const *arg1,char const *arg2,char const *arg3,char const *arg4,char const *arg5) { char cmdstr[5000],fname[256],*jsonstr; long fsize; cJSON *retjson=NULL; sprintf(fname,"/tmp/coda.%s",arg0); sprintf(cmdstr,"coda.exe client %s %s %s %s %s %s > %s 2>&1",arg0,arg1,arg2,arg3,arg4,arg5,fname); *retstr = 0; if (system(cmdstr)<0) return (retjson); if ( (jsonstr=(char *)filestr(&fsize,fname)) != 0 ) { jsonstr[strlen(jsonstr)-1]='\0'; if ( (strncmp(jsonstr,"Merkle List of transactions:",28)!=0) || (retjson= cJSON_Parse(jsonstr+29)) == 0) *retstr=jsonstr; else free(jsonstr); } return(retjson); } // makes source tx for self import tx CMutableTransaction MakeSelfImportSourceTx(CTxDestination &dest, int64_t amount) { const int64_t txfee = 10000; int64_t inputs, change; CPubKey myPubKey = Mypubkey(); struct CCcontract_info *cpDummy, C; cpDummy = CCinit(&C, EVAL_TOKENS); // this is just for FinalizeCCTx to work CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); if (AddNormalinputs(mtx, myPubKey, 2 * txfee, 4) == 0) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "MakeSelfImportSourceTx() warning: cannot find normal inputs for txfee" << std::endl); } CScript scriptPubKey = GetScriptForDestination(dest); mtx.vout.push_back(CTxOut(txfee, scriptPubKey)); //make opret with 'burned' amount: FinalizeCCTx(0, cpDummy, mtx, myPubKey, txfee, CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)EVAL_IMPORTCOIN << (uint8_t)'A' << amount)); return mtx; } // make sure vin is signed by pubkey33 bool CheckVinPubKey(const CTransaction &sourcetx, int32_t i, uint8_t pubkey33[33]) { CTransaction vintx; uint256 blockHash; char destaddr[64], pkaddr[64]; if (i < 0 || i >= sourcetx.vin.size()) return false; if( !myGetTransaction(sourcetx.vin[i].prevout.hash, vintx, blockHash) ) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckVinPubKey() could not load vintx" << sourcetx.vin[i].prevout.hash.GetHex() << std::endl); return false; } if( sourcetx.vin[i].prevout.n < vintx.vout.size() && Getscriptaddress(destaddr, vintx.vout[sourcetx.vin[i].prevout.n].scriptPubKey) != 0 ) { pubkey2addr(pkaddr, pubkey33); if (strcmp(pkaddr, destaddr) == 0) { return true; } LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckVinPubKey() mismatched vin[" << i << "].prevout.n=" << sourcetx.vin[i].prevout.n << " -> destaddr=" << destaddr << " vs pkaddr=" << pkaddr << std::endl); } return false; } // ac_import=PUBKEY support: // prepare a tx for creating import tx and quasi-burn tx int32_t GetSelfimportProof(const CMutableTransaction sourceMtx, CMutableTransaction &templateMtx, ImportProof &proofNull) // find burnTx with hash from "other" daemon { MerkleBranch newBranch; CMutableTransaction tmpmtx; //CTransaction sourcetx; tmpmtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); /* if (!E_UNMARSHAL(ParseHex(rawsourcetx), ss >> sourcetx)) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: could not unmarshal source tx" << std::endl); return(-1); } if (sourcetx.vout.size() == 0) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: vout size is 0" << std::endl); return -1; } */ /*if (ivout < 0) { // "ivout < 0" means "find" // try to find vout CPubKey myPubkey = Mypubkey(); ivout = 0; // skip change: if (sourcetx.vout[ivout].scriptPubKey == (CScript() << ParseHex(HexStr(myPubkey)) << OP_CHECKSIG)) ivout++; } if (ivout >= sourcetx.vout.size()) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: needed vout not found" << std::endl); return -1; } */ int32_t ivout = 0; // LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "GetSelfimportProof: using vout[" << ivout << "] of the passed rawtx" << std::endl); CScript scriptPubKey = sourceMtx.vout[ivout].scriptPubKey; //mtx is template for import tx templateMtx = sourceMtx; templateMtx.fOverwintered = tmpmtx.fOverwintered; //malleability fix for burn tx: //mtx.nExpiryHeight = tmpmtx.nExpiryHeight; templateMtx.nExpiryHeight = sourceMtx.nExpiryHeight; templateMtx.nVersionGroupId = tmpmtx.nVersionGroupId; templateMtx.nVersion = tmpmtx.nVersion; templateMtx.vout.clear(); templateMtx.vout.resize(1); uint8_t evalCode, funcId; int64_t burnAmount; vscript_t vopret; if( !GetOpReturnData(sourceMtx.vout.back().scriptPubKey, vopret) || !E_UNMARSHAL(vopret, ss >> evalCode; ss >> funcId; ss >> burnAmount)) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof() could not unmarshal source tx opret" << std::endl); return -1; } templateMtx.vout[0].nValue = burnAmount; templateMtx.vout[0].scriptPubKey = scriptPubKey; // not sure we need this now as we create sourcetx ourselves: /*if (sourcetx.GetHash() != sourcetxid) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: passed source txid incorrect" << std::endl); return(-1); }*/ // check ac_pubkey: if (!CheckVinPubKey(sourceMtx, 0, ASSETCHAINS_OVERRIDE_PUBKEY33)) { return -1; } proofNull = ImportProof(std::make_pair(sourceMtx.GetHash(), newBranch)); return 0; } // make import tx with burntx and dual daemon std::string MakeCodaImportTx(uint64_t txfee, std::string receipt, std::string srcaddr, std::vector vouts) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()),burntx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); CPubKey mypk; uint256 codaburntxid; std::vector dummyproof; int32_t i,numvouts,n,m; std::string coin,error; struct CCcontract_info *cp, C; cJSON *result,*tmp,*tmp1; unsigned char hash[SHA256_DIGEST_LENGTH+1]; char out[SHA256_DIGEST_LENGTH*2+1],*retstr,*destaddr,*receiver; TxProof txProof; uint64_t amount; cp = CCinit(&C, EVAL_GATEWAYS); if (txfee == 0) txfee = 10000; mypk = pubkey2pk(Mypubkey()); SHA256_CTX sha256; SHA256_Init(&sha256); SHA256_Update(&sha256, receipt.c_str(), receipt.size()); SHA256_Final(hash, &sha256); for(i = 0; i < SHA256_DIGEST_LENGTH; i++) { sprintf(out + (i * 2), "%02x", hash[i]); } out[65]='\0'; LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "MakeCodaImportTx: hash=" << out << std::endl); codaburntxid.SetHex(out); LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "MakeCodaImportTx: receipt=" << receipt << " codaburntxid=" << codaburntxid.GetHex().data() << " amount=" << (double)amount / COIN << std::endl); result=CodaRPC(&retstr,"prove-payment","-address",srcaddr.c_str(),"-receipt-chain-hash",receipt.c_str(),""); if (result==0) { if (retstr!=0) { CCerror=std::string("CodaRPC: ")+retstr; free(retstr); } return(""); } else { if ((tmp=jobj(jitem(jarray(&n,result,(char *)"payments"),0),(char *)"payload"))!=0 && (destaddr=jstr(jobj(tmp,(char *)"common"),(char *)"memo"))!=0 && (receiver=jstr(jitem(jarray(&m,tmp,(char *)"body"),1),(char *)"receiver"))!=0 && (amount=j64bits(jitem(jarray(&m,tmp,(char *)"body"),1),(char *)"amount"))!=0) { LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "MakeCodaImportTx: receiver=" << receiver << " destaddr=" << destaddr << " amount=" << amount << std::endl); if (strcmp(receiver,CODA_BURN_ADDRESS)!=0) { CCerror="MakeCodaImportTx: invalid burn address, coins do not go to predefined burn address - "; CCerror+=CODA_BURN_ADDRESS; LOGSTREAM("importcoin", CCLOG_INFO, stream << CCerror << std::endl); free(result); return(""); } CTxDestination dest = DecodeDestination(destaddr); CScript scriptPubKey = GetScriptForDestination(dest); if (vouts[0]!=CTxOut(amount*COIN,scriptPubKey)) { CCerror="MakeCodaImportTx: invalid destination address, burnTx memo!=importTx destination"; LOGSTREAM("importcoin", CCLOG_INFO, stream << CCerror << std::endl); free(result); return(""); } if (amount*COIN!=vouts[0].nValue) { CCerror="MakeCodaImportTx: invalid amount, burnTx amount!=importTx amount"; LOGSTREAM("importcoin", CCLOG_INFO, stream << CCerror << std::endl); free(result); return(""); } burntx.vin.push_back(CTxIn(codaburntxid,0,CScript())); burntx.vout.push_back(MakeBurnOutput(amount*COIN,0xffffffff,"CODA",vouts,dummyproof,srcaddr,receipt)); return HexStr(E_MARSHAL(ss << MakeImportCoinTransaction(txProof,burntx,vouts))); } else { CCerror="MakeCodaImportTx: invalid Coda burn tx"; LOGSTREAM("importcoin", CCLOG_INFO, stream << CCerror << std::endl); free(result); return(""); } } CCerror="MakeCodaImportTx: error fetching Coda tx"; LOGSTREAM("importcoin", CCLOG_INFO, stream << CCerror << std::endl); free(result); return(""); } // use proof from the above functions to validate the import int32_t CheckBEAMimport(TxProof proof,std::vector rawproof,CTransaction burnTx,std::vector payouts) { // check with dual-BEAM daemon via ASSETCHAINS_BEAMPORT for validity of burnTx return(-1); } int32_t CheckCODAimport(CTransaction importTx,CTransaction burnTx,std::vector payouts,std::string srcaddr,std::string receipt) { cJSON *result,*tmp,*tmp1; char *retstr,out[SHA256_DIGEST_LENGTH*2+1]; unsigned char hash[SHA256_DIGEST_LENGTH+1]; int i,n,m; SHA256_CTX sha256; uint256 codaburntxid; char *destaddr,*receiver; uint64_t amount; // check with dual-CODA daemon via ASSETCHAINS_CODAPORT for validity of burnTx SHA256_Init(&sha256); SHA256_Update(&sha256, receipt.c_str(), receipt.size()); SHA256_Final(hash, &sha256); for(i = 0; i < SHA256_DIGEST_LENGTH; i++) { sprintf(out + (i * 2), "%02x", hash[i]); } out[65]='\0'; codaburntxid.SetHex(out); result=CodaRPC(&retstr,"prove-payment","-address",srcaddr.c_str(),"-receipt-chain-hash",receipt.c_str(),""); if (result==0) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "CodaRPC error: " << retstr << std::endl); free(retstr); return (-1); } else { if ((tmp=jobj(jitem(jarray(&n,result,(char *)"payments"),0),(char *)"payload"))==0 || (destaddr=jstr(jobj(tmp,(char *)"common"),(char *)"memo"))==0 || (receiver=jstr(jitem(jarray(&m,tmp,(char *)"body"),1),(char *)"receiver"))==0 || (amount=j64bits(jitem(jarray(&m,tmp,(char *)"body"),1),(char *)"amount"))==0) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "Invalid Coda burn tx" << jprint(result,1) << std::endl); free(result); return (-1); } CTxDestination dest = DecodeDestination(destaddr); CScript scriptPubKey = GetScriptForDestination(dest); if (payouts[0]!=CTxOut(amount*COIN,scriptPubKey)); { LOGSTREAM("importcoin", CCLOG_INFO, stream << "Destination address in burn tx does not match destination in import tx" << std::endl); free(result); return (-1); } if (strcmp(receiver,CODA_BURN_ADDRESS)!=0) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "Invalid burn address " << jstr(tmp1,(char *)"receiver") << std::endl); free(result); return (-1); } if (amount*COIN!=payouts[0].nValue) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "Burn amount and import amount not matching, " << j64bits(tmp,(char *)"amount") << " - " << payouts[0].nValue/COIN << std::endl); free(result); return (-1); } if (burnTx.vin[0].prevout.hash!=codaburntxid || importTx.vin[0].prevout.hash!=burnTx.GetHash()) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "Invalid import/burn tx vin" << std::endl); free(result); return (-1); } free(result); } return(0); } int32_t CheckGATEWAYimport(CTransaction importTx,CTransaction burnTx,std::string refcoin,std::vector proof, uint256 bindtxid,std::vector publishers,std::vector txids,uint256 burntxid,int32_t height,int32_t burnvout,std::string rawburntx,CPubKey destpub, int64_t amount) { CTransaction oracletx,bindtx,regtx; int32_t i,m,n=0,numvouts; uint8_t M,N,taddr,prefix,prefix2,wiftype; uint256 txid,oracletxid,tmporacletxid,merkleroot,mhash,hashBlock; std::string name,desc,format,coin; std::vector vouts; CPubKey regpk; std::vector pubkeys,tmppubkeys,tmppublishers; char markeraddr[64],deposit[64],destaddr[64],tmpdest[64]; int64_t datafee; std::vector > unspentOutputs; // ASSETCHAINS_SELFIMPORT is coin if (KOMODO_EARLYTXID!=zeroid && bindtxid!=KOMODO_EARLYTXID) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport invalid import gateway. On this chain only valid import gateway is " << KOMODO_EARLYTXID.GetHex() << std::endl); return(-1); } // check for valid burn from external coin blockchain and if valid return(0); if (myGetTransaction(bindtxid, bindtx, hashBlock) == 0 || (numvouts = bindtx.vout.size()) <= 0) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport cant find bindtxid=" << bindtxid.GetHex() << std::endl); return(-1); } else if (DecodeImportGatewayBindOpRet(deposit,bindtx.vout[numvouts - 1].scriptPubKey,coin,oracletxid,M,N,tmppubkeys,taddr,prefix,prefix2,wiftype) != 'B') { LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckGATEWAYimport invalid bind tx. bindtxid=" << bindtxid.GetHex() << std::endl); return(-1); } else if (refcoin!=coin) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport invalid coin " << refcoin << "!=" << coin << std::endl); return(-1); } else if ( N == 0 || N > 15 || M > N ) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport invalid N or M " << std::endl); return(-1); } else if (tmppubkeys.size()!=N) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport not enough pubkeys for given N " << std::endl); return(-1); } else if (komodo_txnotarizedconfirmed(bindtxid) == false) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport bindtx not yet confirmed/notarized" << std::endl); return(-1); } else if (myGetTransaction(oracletxid, oracletx, hashBlock) == 0 || (numvouts = oracletx.vout.size()) <= 0) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport cant find oracletxid=" << oracletxid.GetHex() << std::endl); return(-1); } else if (DecodeOraclesCreateOpRet(oracletx.vout[numvouts - 1].scriptPubKey,name,desc,format) != 'C') { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport invalid oracle tx. oracletxid=" << oracletxid.GetHex() << std::endl); return(-1); } else if (name!=refcoin || format!="Ihh") { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport invalid oracle name or format tx. oracletxid=" << oracletxid.GetHex() << " name=" << name << " format=" << format << std::endl); return(-1); } CCtxidaddr(markeraddr,oracletxid); SetCCunspents(unspentOutputs,markeraddr,false); for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) { txid = it->first.txhash; if ( myGetTransaction(txid,regtx,hashBlock) != 0 && regtx.vout.size() > 0 && DecodeOraclesOpRet(regtx.vout[regtx.vout.size()-1].scriptPubKey,tmporacletxid,regpk,datafee) == 'R' && oracletxid == tmporacletxid ) { pubkeys.push_back(regpk); n++; } } if (pubkeys.size()!=tmppubkeys.size()) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport different number of bind and oracle pubkeys " << tmppubkeys.size() << "!=" << pubkeys.size() << std::endl); return(-1); } merkleroot = zeroid; for (i = m = 0; i < n; i++) { if ((mhash = CCOraclesReverseScan("importgateway-1",txid, height, oracletxid, OraclesBatontxid(oracletxid, pubkeys[i]))) != zeroid) { if (merkleroot == zeroid) merkleroot = mhash, m = 1; else if (mhash == merkleroot) m ++; tmppublishers.push_back(pubkeys[i]); } } if (publishers.size()!=tmppublishers.size()) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport different number of publishers for burtx in oracle" << std::endl); return(-1); } else if (merkleroot == zeroid || m < n / 2) // none or less than half oracle nodes sent merkleroot { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport couldnt find merkleroot for block height=" << height << "coin=" << refcoin.c_str() << " oracleid=" << oracletxid.GetHex() << " m=" << m << " vs n=" << n << std::endl ); return(-1); } else if ( ImportGatewayVerify(deposit,oracletxid,burnvout,refcoin,burntxid,rawburntx,proof,merkleroot,destpub,taddr,prefix,prefix2) != amount ) { CCerror = strprintf("burntxid didnt validate !"); LOGSTREAM("importgateway",CCLOG_INFO, stream << CCerror << std::endl); return(-1); } else if (importTx.vout[0].nValue!=amount) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport import amount different than in burntx" << std::endl); return(-1); } Getscriptaddress(destaddr,importTx.vout[0].scriptPubKey); Getscriptaddress(tmpdest,CScript() << ParseHex(HexStr(destpub)) << OP_CHECKSIG); if (strcmp(destaddr,tmpdest)!=0) { LOGSTREAM("importgateway", CCLOG_INFO, stream << "CheckGATEWAYimport import coins destination different than in burntx" << std::endl); return(-1); } return(0); } int32_t CheckPUBKEYimport(TxProof proof,std::vector rawproof,CTransaction burnTx,std::vector payouts) { // if burnTx has ASSETCHAINS_PUBKEY vin, it is valid return(0); LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "proof txid=" << proof.first.GetHex() << std::endl); uint256 sourcetxid = proof.first, hashBlock; CTransaction sourcetx; if (!myGetTransaction(sourcetxid, sourcetx, hashBlock)) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "could not load source txid=" << sourcetxid.GetHex() << std::endl); return -1; } if (sourcetx.vout.size() == 0) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "no vouts in source txid=" << sourcetxid.GetHex() << std::endl); return -1; } // might be malleable: if (burnTx.nExpiryHeight != sourcetx.nExpiryHeight) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "burntx nExpiryHeight incorrect for source txid=" << sourcetxid.GetHex() << std::endl); return -1; } //ac_pubkey check: if (!CheckVinPubKey(sourcetx, 0, ASSETCHAINS_OVERRIDE_PUBKEY33)) { return -1; } // get source tx opret: std::vector vopret; uint8_t evalCode, funcId; int64_t amount; if (!GetOpReturnData(sourcetx.vout.back().scriptPubKey, vopret) || vopret.size() == 0 || !E_UNMARSHAL(vopret, ss >> evalCode; ss >> funcId; ss >> amount) || evalCode != EVAL_IMPORTCOIN || funcId != 'A') { LOGSTREAM("importcoin", CCLOG_INFO, stream << "none or incorrect opret to validate in source txid=" << sourcetxid.GetHex() << std::endl); return -1; } LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "importTx amount=" << payouts[0].nValue << " burnTx amount=" << burnTx.vout[0].nValue << " opret amount=" << amount << " source txid=" << sourcetxid.GetHex() << std::endl); // amount malleability check with the opret from the source tx: if (payouts[0].nValue != amount) { // assume that burntx amount is checked in the common code in Eval::ImportCoin() LOGSTREAM("importcoin", CCLOG_INFO, stream << "importTx amount != amount in the opret of source txid=" << sourcetxid.GetHex() << std::endl); return -1; } return(0); } bool CheckMigration(Eval *eval, const CTransaction &importTx, const CTransaction &burnTx, std::vector & payouts, const ImportProof &proof, const std::vector &rawproof) { vscript_t vimportOpret; if (!GetOpReturnData(importTx.vout.back().scriptPubKey, vimportOpret) || vimportOpret.empty()) return eval->Invalid("invalid-import-tx-no-opret"); uint256 tokenid = zeroid; if (vimportOpret.begin()[0] == EVAL_TOKENS) { // for tokens (new opret with tokens) struct CCcontract_info *cpTokens, CCtokens_info; std::vector> oprets; uint8_t evalCodeInOpret; std::vector voutTokenPubkeys; vscript_t vnonfungibleOpret; cpTokens = CCinit(&CCtokens_info, EVAL_TOKENS); if (DecodeTokenOpRet(importTx.vout.back().scriptPubKey, evalCodeInOpret, tokenid, voutTokenPubkeys, oprets) == 0) return eval->Invalid("cannot-decode-import-tx-token-opret"); uint8_t nonfungibleEvalCode = EVAL_TOKENS; // init to no non-fungibles GetOpretBlob(oprets, OPRETID_NONFUNGIBLEDATA, vnonfungibleOpret); if (!vnonfungibleOpret.empty()) nonfungibleEvalCode = vnonfungibleOpret.begin()[0]; // check if burn tx at least has cc evaltoken vins (we cannot get cc input) bool hasTokenVin = false; for (auto vin : burnTx.vin) if (cpTokens->ismyvin(vin.scriptSig)) hasTokenVin = true; if (!hasTokenVin) return eval->Invalid("burn-tx-has-no-token-vins"); // calc outputs for burn tx CAmount ccBurnOutputs = 0; for (auto v : burnTx.vout) if (v.scriptPubKey.IsPayToCryptoCondition() && CTxOut(v.nValue, v.scriptPubKey) == MakeTokensCC1vout(nonfungibleEvalCode, v.nValue, pubkey2pk(ParseHex(CC_BURNPUBKEY)))) // burned to dead pubkey ccBurnOutputs += v.nValue; // calc outputs for import tx CAmount ccImportOutputs = 0; for (auto v : importTx.vout) if (v.scriptPubKey.IsPayToCryptoCondition() && !IsTokenMarkerVout(v)) // should not be marker here ccImportOutputs += v.nValue; if (ccBurnOutputs != ccImportOutputs) return eval->Invalid("token-cc-burned-output-not-equal-cc-imported-output"); } else if (vimportOpret.begin()[0] != EVAL_IMPORTCOIN) { return eval->Invalid("import-tx-incorrect-opret-eval"); } // for tokens check burn, import, tokenbase tx if (!tokenid.IsNull()) { std::string sourceSymbol; CTransaction tokenbaseTx; if (!E_UNMARSHAL(rawproof, ss >> sourceSymbol; ss >> tokenbaseTx)) return eval->Invalid("cannot-unmarshal-rawproof-for-tokens"); uint256 sourceTokenId; std::vector> oprets; uint8_t evalCodeInOpret; std::vector voutTokenPubkeys; if (burnTx.vout.size() > 0 && DecodeTokenOpRet(burnTx.vout.back().scriptPubKey, evalCodeInOpret, sourceTokenId, voutTokenPubkeys, oprets) == 0) return eval->Invalid("cannot-decode-burn-tx-token-opret"); if (sourceTokenId != tokenbaseTx.GetHash()) // check tokenid in burn tx opret maches the passed tokenbase tx (to prevent cheating by importing user) return eval->Invalid("incorrect-token-creation-tx-passed"); std::vector> opretsSrc; vscript_t vorigpubkeySrc; std::string nameSrc, descSrc; if (DecodeTokenCreateOpRet(tokenbaseTx.vout.back().scriptPubKey, vorigpubkeySrc, nameSrc, descSrc, opretsSrc) == 0) return eval->Invalid("cannot-decode-token-creation-tx"); std::vector> opretsImport; vscript_t vorigpubkeyImport; std::string nameImport, descImport; if (importTx.vout.size() == 0 || DecodeTokenCreateOpRet(importTx.vout.back().scriptPubKey, vorigpubkeySrc, nameSrc, descSrc, opretsImport) == 0) return eval->Invalid("cannot-decode-token-import-tx"); // check that name,pubkey,description in import tx correspond ones in token creation tx in the source chain: if (vorigpubkeySrc != vorigpubkeyImport || nameSrc != nameImport || descSrc != descImport) return eval->Invalid("import-tx-token-params-incorrect"); } // Check burntx shows correct outputs hash // if (payoutsHash != SerializeHash(payouts)) // done in ImportCoin // return eval->Invalid("wrong-payouts"); TxProof merkleBranchProof; std::vector notaryTxids; // Check proof confirms existance of burnTx if (proof.IsMerkleBranch(merkleBranchProof)) { uint256 target = merkleBranchProof.second.Exec(burnTx.GetHash()); LOGSTREAM("importcoin", CCLOG_DEBUG2, stream << "Eval::ImportCoin() momom target=" << target.GetHex() << " merkleBranchProof.first=" << merkleBranchProof.first.GetHex() << std::endl); if (!CheckMoMoM(merkleBranchProof.first, target)) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "MoMoM check failed for importtx=" << importTx.GetHash().GetHex() << std::endl); return eval->Invalid("momom-check-fail"); } } else if (proof.IsNotaryTxids(notaryTxids)) { if (!CheckNotariesApproval(burnTx.GetHash(), notaryTxids)) { return eval->Invalid("notaries-approval-check-fail"); } } else { return eval->Invalid("invalid-import-proof"); } /* if (vimportOpret.begin()[0] == EVAL_TOKENS) return eval->Invalid("test-invalid-tokens-are-good!!"); else return eval->Invalid("test-invalid-coins-are-good!!"); */ return true; } bool Eval::ImportCoin(const std::vector params, const CTransaction &importTx, unsigned int nIn) { ImportProof proof; CTransaction burnTx; std::vector payouts; CAmount txfee = 10000, amount; int32_t height, burnvout; std::vector publishers; uint32_t targetCcid; std::string targetSymbol, srcaddr, destaddr, receipt, rawburntx; uint256 payoutsHash, bindtxid, burntxid; std::vector rawproof; std::vector txids; CPubKey destpub; LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "Validating import tx..., txid=" << importTx.GetHash().GetHex() << std::endl); if (strcmp(SMART_CHAIN_SYMBOL, "CFEKDIMXY6") == 0 && chainActive.Height() <= 44693) return true; if (importTx.vout.size() < 2) return Invalid("too-few-vouts"); // params if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) return Invalid("invalid-params"); // Control all aspects of this transaction // It should not be at all malleable if (ASSETCHAINS_SELFIMPORT!="PEGSCC" && MakeImportCoinTransaction(proof, burnTx, payouts, importTx.nExpiryHeight).GetHash() != importTx.GetHash()) // ExistsImportTombstone prevents from duplication return Invalid("non-canonical"); // burn params if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCcid, payoutsHash, rawproof)) return Invalid("invalid-burn-tx"); if (burnTx.vout.size() == 0) return Invalid("invalid-burn-tx-no-vouts"); // check burned normal amount >= import amount && burned amount <= import amount + txfee (extra txfee is for miners and relaying, see GetImportCoinValue() func) CAmount burnAmount = burnTx.vout.back().nValue; if (burnAmount == 0) return Invalid("invalid-burn-amount"); CAmount totalOut = 0; for (auto v : importTx.vout) if (!v.scriptPubKey.IsPayToCryptoCondition()) totalOut += v.nValue; if (totalOut > burnAmount || totalOut < burnAmount - txfee) return Invalid("payout-too-high-or-too-low"); // Check burntx shows correct outputs hash if (payoutsHash != SerializeHash(payouts)) return Invalid("wrong-payouts"); if (targetCcid < HUSH_FIRSTFUNGIBLEID) return Invalid("chain-not-fungible"); if ( targetCcid != 0xffffffff ) { if (targetCcid != GetAssetchainsCC() || targetSymbol != GetAssetchainsSymbol()) return Invalid("importcoin-wrong-chain"); if (!CheckMigration(this, importTx, burnTx, payouts, proof, rawproof)) return false; // eval->Invalid() is called in the func } else { TxProof merkleBranchProof; if (!proof.IsMerkleBranch(merkleBranchProof)) return Invalid("invalid-import-proof-for-0xFFFFFFFF"); if ( targetSymbol == "BEAM" ) { if ( ASSETCHAINS_BEAMPORT == 0 ) return Invalid("BEAM-import-without-port"); else if ( CheckBEAMimport(merkleBranchProof,rawproof,burnTx,payouts) < 0 ) return Invalid("BEAM-import-failure"); } else if ( targetSymbol == "CODA" ) { if ( ASSETCHAINS_CODAPORT == 0 ) return Invalid("CODA-import-without-port"); else if ( UnmarshalBurnTx(burnTx,srcaddr,receipt)==0 || CheckCODAimport(importTx,burnTx,payouts,srcaddr,receipt) < 0 ) return Invalid("CODA-import-failure"); } else if ( targetSymbol == "PEGSCC" ) { if ( ASSETCHAINS_SELFIMPORT != "PEGSCC" ) return Invalid("PEGSCC-import-when-not PEGSCC"); // else if ( CheckPUBKEYimport(merkleBranchProof,rawproof,burnTx,payouts) < 0 ) // return Invalid("PEGSCC-import-failure"); } else if ( targetSymbol == "PUBKEY" ) { if ( ASSETCHAINS_SELFIMPORT != "PUBKEY" ) return Invalid("PUBKEY-import-when-not PUBKEY"); else if ( CheckPUBKEYimport(merkleBranchProof,rawproof,burnTx,payouts) < 0 ) return Invalid("PUBKEY-import-failure"); } else { if ( targetSymbol != ASSETCHAINS_SELFIMPORT ) return Invalid("invalid-gateway-import-coin"); else if ( UnmarshalBurnTx(burnTx,bindtxid,publishers,txids,burntxid,height,burnvout,rawburntx,destpub,amount)==0 || CheckGATEWAYimport(importTx,burnTx,targetSymbol,rawproof,bindtxid,publishers,txids,burntxid,height,burnvout,rawburntx,destpub,amount) < 0 ) return Invalid("GATEWAY-import-failure"); } } // return Invalid("test-invalid"); LOGSTREAM("importcoin", CCLOG_DEBUG2, stream << "Valid import tx! txid=" << importTx.GetHash().GetHex() << std::endl); return Valid(); }