begin consensus support for peg-ins

This commit is contained in:
Gregory Sanders 2017-09-15 14:43:02 -04:00
parent 34d7cd072f
commit 0b9c5eb0e0
3 changed files with 297 additions and 240 deletions

View file

@ -330,6 +330,9 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
{
if (!tx.IsCoinBase()) {
for (unsigned int i = 0; i < tx.vin.size(); i++) {
if (tx.vin[i].m_is_pegin) {
continue;
}
const COutPoint &prevout = tx.vin[i].prevout;
const CCoins* coins = AccessCoins(prevout.hash);
if (!coins || !coins->IsAvailable(prevout.n)) {

View file

@ -14,7 +14,7 @@
#include "consensus/consensus.h"
#include "consensus/merkle.h"
#include "consensus/validation.h"
#include "hash.h"
#include "crypto/hmac_sha256.h"
#include "init.h"
#include "issuance.h"
#include "policy/fees.h"
@ -306,6 +306,11 @@ static std::pair<int, int64_t> CalculateSequenceLocks(const CTransaction &tx, in
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
const CTxIn& txin = tx.vin[txinIndex];
// Peg-ins have no output height
if (txin.m_is_pegin) {
continue;
}
// Sequence numbers with the most significant bit set are not
// treated as relative lock-times, nor are they given any
// consensus-enforced meaning at this point.
@ -403,6 +408,11 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool
prevheights.resize(tx.vin.size());
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
const CTxIn& txin = tx.vin[txinIndex];
// pegins should not restrict validity of sequence locks
if (txin.m_is_pegin) {
prevheights[txinIndex] = -1;
continue;
}
CCoins coins;
if (!viewMemPool.GetCoins(txin.prevout.hash, coins)) {
return error("%s: Missing input", __func__);
@ -467,6 +477,10 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
unsigned int nSigOps = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
// Peg-in inputs are segwit-only
if (tx.vin[i].m_is_pegin) {
continue;
}
const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]);
if (prevout.scriptPubKey.IsPayToScriptHash())
nSigOps += prevout.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig);
@ -487,7 +501,10 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]);
if (tx.vin[i].m_is_pegin && (tx.wit.vtxinwit.size() <= i || !IsValidPeginWitness(tx.wit.vtxinwit[i].m_pegin_witness, tx.vin[i].prevout))) {
continue;
}
const CTxOut &prevout = tx.vin[i].m_is_pegin ? GetPeginOutputFromWitness(tx.wit.vtxinwit[i].m_pegin_witness) : inputs.GetOutputFor(tx.vin[i]);
nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, tx.wit.vtxinwit.size() > i ? &tx.wit.vtxinwit[i].scriptWitness : NULL, flags);
}
return nSigOps;
@ -704,43 +721,118 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve
targetGenerators.reserve(tx.vin.size() + GetNumIssuances(tx));
// Tally up value commitments, check balance
if (!tx.IsCoinBase())
for (size_t i = 0; i < tx.vin.size(); ++i)
{
// Assumes IsValidPeginWitness has been called successfully
const CTxOut out = tx.vin[i].m_is_pegin ? GetPeginOutputFromWitness(tx.wit.vtxinwit[i].m_pegin_witness) : cache.GetOutputFor(tx.vin[i]);
const CConfidentialValue& val = out.nValue;
const CConfidentialAsset& asset = out.nAsset;
for (size_t i = 0; i < tx.vin.size(); ++i)
{
if (val.IsNull() || asset.IsNull())
return false;
const CTxOut out = cache.GetOutputFor(tx.vin[i]);
const CConfidentialValue& val = out.nValue;
const CConfidentialAsset& asset = out.nAsset;
if (asset.IsExplicit()) {
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
assert(ret != 0);
}
else if (asset.IsCommitment()) {
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
return false;
}
else {
return false;
}
if (val.IsNull() || asset.IsNull())
targetGenerators.push_back(gen);
if (val.IsExplicit()) {
if (!MoneyRange(val.GetAmount()))
return false;
if (asset.IsExplicit()) {
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
assert(ret != 0);
}
else if (asset.IsCommitment()) {
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
return false;
}
else {
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, val.GetAmount(), &gen) != 1)
return false;
}
} else if (val.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &val.vchCommitment[0]) != 1)
return false;
} else {
return false;
}
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
// Each transaction input may have up to two "pseudo-inputs" to add to the LHS
// for (re)issuance and may require up to two rangeproof checks:
// blinded value of the new assets being made
// blinded value of the issuance tokens being made (only for initial issuance)
const CAssetIssuance& issuance = tx.vin[i].assetIssuance;
// No issuances to process, continue to next input
if (issuance.IsNull()) {
continue;
}
CAsset assetID;
CAsset assetTokenID;
// First construct the assets of the issuances and reissuance token
// These are calculated differently depending on if initial issuance or followup
// New issuance, compute the asset ids
if (issuance.assetBlindingNonce.IsNull()) {
uint256 entropy;
GenerateAssetEntropy(entropy, tx.vin[i].prevout, issuance.assetEntropy);
CalculateAsset(assetID, entropy);
// Null nAmount is considered explicit 0, so just check for commitment
CalculateReissuanceToken(assetTokenID, entropy, issuance.nAmount.IsCommitment());
} else {
//Re-issuance
// hashAssetIdentifier doubles as the entropy on reissuance
CalculateAsset(assetID, issuance.assetEntropy);
CalculateReissuanceToken(assetTokenID, issuance.assetEntropy, issuance.nAmount.IsCommitment());
// Must check that prevout is the blinded issuance token
// prevout's asset tag = assetTokenID + assetBlindingNonce
if (secp256k1_generator_generate_blinded(secp256k1_ctx_verify_amounts, &gen, assetTokenID.begin(), issuance.assetBlindingNonce.begin()) != 1)
return false;
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gencmp, &asset.vchCommitment[0]) != 1)
return false;
if (memcmp(&gen, &gencmp, 33))
return false;
}
// Process issuance of asset
if (!issuance.nAmount.IsNull()) {
// Generate asset generator and add to list of surjection targets
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, assetID.begin());
assert(ret == 1);
CConfidentialAsset issuanceAsset;
issuanceAsset.vchCommitment.resize(CConfidentialAsset::nCommittedSize);
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &issuanceAsset.vchCommitment[0], &gen);
targetGenerators.push_back(gen);
if (val.IsExplicit()) {
if (!MoneyRange(val.GetAmount()))
// Build value commitment and add to tally
if (issuance.nAmount.IsExplicit()) {
if (!MoneyRange(issuance.nAmount.GetAmount())) {
return false;
}
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, val.GetAmount(), &gen) != 1)
if (issuance.nAmount.GetAmount() == 0) {
continue;
}
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, issuance.nAmount.GetAmount(), &gen) != 1) {
return false;
}
}
else if (val.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &val.vchCommitment[0]) != 1)
else if (issuance.nAmount.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &issuance.nAmount.vchCommitment[0]) != 1) {
return false;
}
} else {
return false;
}
@ -749,135 +841,55 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve
vpCommitsIn.push_back(p);
p++;
// Each transaction input may have up to two "pseudo-inputs" to add to the LHS
// for (re)issuance and may require up to two rangeproof checks:
// blinded value of the new assets being made
// blinded value of the issuance tokens being made (only for initial issuance)
const CAssetIssuance& issuance = tx.vin[i].assetIssuance;
// No issuances to process, continue to next input
if (issuance.IsNull()) {
continue;
}
CAsset assetID;
CAsset assetTokenID;
// First construct the assets of the issuances and reissuance token
// These are calculated differently depending on if initial issuance or followup
// New issuance, compute the asset ids
if (issuance.assetBlindingNonce.IsNull()) {
uint256 entropy;
GenerateAssetEntropy(entropy, tx.vin[i].prevout, issuance.assetEntropy);
CalculateAsset(assetID, entropy);
// Null nAmount is considered explicit 0, so just check for commitment
CalculateReissuanceToken(assetTokenID, entropy, issuance.nAmount.IsCommitment());
} else {
//Re-issuance
// hashAssetIdentifier doubles as the entropy on reissuance
CalculateAsset(assetID, issuance.assetEntropy);
CalculateReissuanceToken(assetTokenID, issuance.assetEntropy, issuance.nAmount.IsCommitment());
// Must check that prevout is the blinded issuance token
// prevout's asset tag = assetTokenID + assetBlindingNonce
if (secp256k1_generator_generate_blinded(secp256k1_ctx_verify_amounts, &gen, assetTokenID.begin(), issuance.assetBlindingNonce.begin()) != 1)
return false;
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gencmp, &asset.vchCommitment[0]) != 1)
return false;
if (memcmp(&gen, &gencmp, 33))
return false;
}
// Process issuance of asset
if (!issuance.nAmount.IsNull()) {
// Generate asset generator and add to list of surjection targets
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, assetID.begin());
assert(ret == 1);
CConfidentialAsset issuanceAsset;
issuanceAsset.vchCommitment.resize(CConfidentialAsset::nCommittedSize);
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &issuanceAsset.vchCommitment[0], &gen);
targetGenerators.push_back(gen);
// Build value commitment and add to tally
if (issuance.nAmount.IsExplicit()) {
if (!MoneyRange(issuance.nAmount.GetAmount())) {
return false;
}
if (issuance.nAmount.GetAmount() == 0) {
continue;
}
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, issuance.nAmount.GetAmount(), &gen) != 1) {
return false;
}
}
else if (issuance.nAmount.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &issuance.nAmount.vchCommitment[0]) != 1) {
return false;
}
} else {
return false;
}
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
// Rangecheck must be done for blinded amount
if (issuance.nAmount.IsCommitment() && QueueCheck(pvChecks, new CRangeCheck(&issuance.nAmount, tx.wit.vtxinwit[i].vchIssuanceAmountRangeproof, issuanceAsset.vchCommitment, CScript(), cacheStore)) != SCRIPT_ERR_OK) {
return false;
}
}
// Only initial issuance can have reissuance tokens
if (issuance.assetBlindingNonce.IsNull() && !issuance.nInflationKeys.IsNull()) {
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, assetTokenID.begin());
assert(ret == 1);
CConfidentialAsset tokenAsset(assetTokenID);
tokenAsset.vchCommitment.resize(CConfidentialAsset::nCommittedSize);
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &tokenAsset.vchCommitment[0], &gen);
targetGenerators.push_back(gen);
if (issuance.nInflationKeys.IsExplicit()) {
if (!MoneyRange(issuance.nInflationKeys.GetAmount())) {
return false;
}
if (issuance.nInflationKeys.GetAmount() == 0) {
continue;
}
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, issuance.nInflationKeys.GetAmount(), &gen) != 1) {
return false;
}
}
else if (issuance.nInflationKeys.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &issuance.nInflationKeys.vchCommitment[0]) != 1) {
return false;
}
} else {
return false;
}
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
if (issuance.nInflationKeys.IsCommitment() && QueueCheck(pvChecks, new CRangeCheck(&issuance.nInflationKeys, tx.wit.vtxinwit[i].vchInflationKeysRangeproof, tokenAsset.vchCommitment, CScript(), cacheStore)) != SCRIPT_ERR_OK) {
return false;
}
} else if (!issuance.nInflationKeys.IsNull()) {
// Token amount field must be null for reissuance
// Rangecheck must be done for blinded amount
if (issuance.nAmount.IsCommitment() && QueueCheck(pvChecks, new CRangeCheck(&issuance.nAmount, tx.wit.vtxinwit[i].vchIssuanceAmountRangeproof, issuanceAsset.vchCommitment, CScript(), cacheStore)) != SCRIPT_ERR_OK) {
return false;
}
}
// Only initial issuance can have reissuance tokens
if (issuance.assetBlindingNonce.IsNull() && !issuance.nInflationKeys.IsNull()) {
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, assetTokenID.begin());
assert(ret == 1);
CConfidentialAsset tokenAsset(assetTokenID);
tokenAsset.vchCommitment.resize(CConfidentialAsset::nCommittedSize);
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &tokenAsset.vchCommitment[0], &gen);
targetGenerators.push_back(gen);
if (issuance.nInflationKeys.IsExplicit()) {
if (!MoneyRange(issuance.nInflationKeys.GetAmount())) {
return false;
}
if (issuance.nInflationKeys.GetAmount() == 0) {
continue;
}
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, issuance.nInflationKeys.GetAmount(), &gen) != 1) {
return false;
}
}
else if (issuance.nInflationKeys.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &issuance.nInflationKeys.vchCommitment[0]) != 1) {
return false;
}
} else {
return false;
}
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
if (issuance.nInflationKeys.IsCommitment() && QueueCheck(pvChecks, new CRangeCheck(&issuance.nInflationKeys, tx.wit.vtxinwit[i].vchInflationKeysRangeproof, tokenAsset.vchCommitment, CScript(), cacheStore)) != SCRIPT_ERR_OK) {
return false;
}
} else if (!issuance.nInflationKeys.IsNull()) {
// Token amount field must be null for reissuance
return false;
}
}
for (size_t i = 0; i < tx.vout.size(); ++i)
{
@ -1756,20 +1768,28 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
txundo.vprevout.reserve(tx.vin.size());
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const CTxIn &txin = tx.vin[i];
CCoinsModifier coins = inputs.ModifyCoins(txin.prevout.hash);
unsigned nPos = txin.prevout.n;
if (txin.m_is_pegin) {
std::pair<uint256, COutPoint> outpoint = std::make_pair(uint256(tx.wit.vtxinwit[i].m_pegin_witness.stack[2]), txin.prevout);
inputs.SetWithdrawSpent(outpoint, true);
// Dummy undo
txundo.vprevout.push_back(CTxInUndo());
if (nPos >= coins->vout.size() || coins->vout[nPos].IsNull())
assert(false);
} else {
CCoinsModifier coins = inputs.ModifyCoins(txin.prevout.hash);
unsigned nPos = txin.prevout.n;
// mark an outpoint spent, and construct undo information
txundo.vprevout.push_back(CTxInUndo(coins->vout[nPos]));
coins->Spend(nPos);
if (coins->vout.size() == 0) {
CTxInUndo& undo = txundo.vprevout.back();
undo.nHeight = coins->nHeight;
undo.fCoinBase = coins->fCoinBase;
undo.nVersion = coins->nVersion;
if (nPos >= coins->vout.size() || coins->vout[nPos].IsNull())
assert(false);
// mark an outpoint spent, and construct undo information
txundo.vprevout.push_back(CTxInUndo(coins->vout[nPos]));
coins->Spend(nPos);
if (coins->vout.size() == 0) {
CTxInUndo& undo = txundo.vprevout.back();
undo.nHeight = coins->nHeight;
undo.fCoinBase = coins->fCoinBase;
undo.nVersion = coins->nVersion;
}
}
}
}
@ -1811,23 +1831,39 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
const COutPoint &prevout = tx.vin[i].prevout;
const CCoins *coins = inputs.AccessCoins(prevout.hash);
assert(coins);
if (tx.vin[i].m_is_pegin) {
// Check existence and validity of pegin witness
if (tx.wit.vtxinwit.size() <= i || !IsValidPeginWitness(tx.wit.vtxinwit[i].m_pegin_witness, prevout)) {
return state.DoS(0, false, REJECT_INVALID, "bad-pegin-witness", true);
}
std::pair<uint256, COutPoint> pegin = std::make_pair(uint256(tx.wit.vtxinwit[i].m_pegin_witness.stack[2]), prevout);
if (inputs.IsWithdrawSpent(pegin)) {
return state.Invalid(false, REJECT_INVALID, "bad-txns-double-withdraw", strprintf("Double-withdraw of %s:%d", prevout.hash.ToString(), prevout.n));
}
if (setPeginsSpent.count(pegin)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-double-withdraw-in-obj", false,
strprintf("Double-withdraw of %s:%d in single tx/block", prevout.hash.ToString(), prevout.n));
}
setPeginsSpent.insert(pegin);
} else {
const CCoins *coins = inputs.AccessCoins(prevout.hash);
assert(coins);
// If prev is coinbase, check that it's matured
if (coins->IsCoinBase()) {
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY)
return state.Invalid(false,
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight));
}
// If prev is coinbase, check that it's matured
if (coins->IsCoinBase()) {
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY)
return state.Invalid(false,
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight));
}
// Check for negative or overflow input values
const CConfidentialValue& value = coins->vout[prevout.n].nValue;
if (value.IsExplicit()) {
nValueIn += value.GetAmount();
if (!MoneyRange(value.GetAmount()) || !MoneyRange(nValueIn))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
// Check for negative or overflow input values
const CConfidentialValue& value = coins->vout[prevout.n].nValue;
if (value.IsExplicit()) {
nValueIn += value.GetAmount();
if (!MoneyRange(value.GetAmount()) || !MoneyRange(nValueIn))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
}
}
}
@ -1865,7 +1901,19 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
if (fScriptChecks) {
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout;
const CCoins* coins = inputs.AccessCoins(prevout.hash);
// If input is peg-in, create "coin" to evaluate against
CCoins pegin_coin;
if (tx.vin[i].m_is_pegin) {
CMutableTransaction mtx;
mtx.vout.resize(prevout.n);
mtx.vout.push_back(GetPeginOutputFromWitness(tx.wit.vtxinwit[i].m_pegin_witness));
CTransaction tx(mtx);
// Height of "output" in script evaluation will be 0
pegin_coin.FromTx(tx, 0);
}
// from m_pegin_witness
const CCoins* coins = tx.vin[i].m_is_pegin ? &pegin_coin : inputs.AccessCoins(prevout.hash);
assert(coins);
// Verify signature
@ -1983,30 +2031,44 @@ bool AbortNode(CValidationState& state, const std::string& strMessage, const std
* @param out The out point that corresponds to the tx input.
* @return True on success.
*/
bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out, const CTxIn& txin)
bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out, const CTxIn& txin, const CScriptWitness& pegin_witness)
{
bool fClean = true;
CCoinsModifier coins = view.ModifyCoins(out.hash);
if (undo.nHeight != 0 ||
// Special-case genesis since nHeight is always 0, assume DB is clean
(Params().GenesisBlock().vtx[0]->GetHash() == out.hash && coins->IsPruned())) {
// undo data contains height: this is the last output of the prevout tx being spent
if (!coins->IsPruned())
fClean = fClean && error("%s: undo data overwriting existing transaction", __func__);
coins->Clear();
coins->fCoinBase = undo.fCoinBase;
coins->nHeight = undo.nHeight;
coins->nVersion = undo.nVersion;
if (!txin.m_is_pegin) {
CCoinsModifier coins = view.ModifyCoins(out.hash);
if (undo.nHeight != 0 ||
// Special-case genesis since nHeight is always 0, assume DB is clean
(Params().GenesisBlock().vtx[0]->GetHash() == out.hash && coins->IsPruned())) {
// undo data contains height: this is the last output of the prevout tx being spent
if (!coins->IsPruned())
fClean = fClean && error("%s: undo data overwriting existing transaction", __func__);
coins->Clear();
coins->fCoinBase = undo.fCoinBase;
coins->nHeight = undo.nHeight;
coins->nVersion = undo.nVersion;
} else {
if (coins->IsPruned())
fClean = fClean && error("%s: undo data adding output to missing transaction", __func__);
}
if (coins->IsAvailable(out.n))
fClean = fClean && error("%s: undo data overwriting existing output", __func__);
if (coins->vout.size() < out.n+1)
coins->vout.resize(out.n+1);
coins->vout[out.n] = undo.txout;
} else {
if (coins->IsPruned())
fClean = fClean && error("%s: undo data adding output to missing transaction", __func__);
if (!IsValidPeginWitness(pegin_witness, txin.prevout)) {
fClean = fClean && error("%s: peg-in occurred without proof", __func__);
} else {
std::pair<uint256, COutPoint> outpoint = std::make_pair(uint256(pegin_witness.stack[2]), txin.prevout);
bool fSpent = view.IsWithdrawSpent(outpoint);
if (!fSpent) {
fClean = fClean && error("%s: peg-in bitcoin txid not marked spent", __func__);
} else {
view.SetWithdrawSpent(outpoint, false);
}
}
}
if (coins->IsAvailable(out.n))
fClean = fClean && error("%s: undo data overwriting existing output", __func__);
if (coins->vout.size() < out.n+1)
coins->vout.resize(out.n+1);
coins->vout[out.n] = undo.txout;
return fClean;
}
@ -2062,7 +2124,8 @@ bool DisconnectBlock(const CBlock& block, CValidationState& state, const CBlockI
for (unsigned int j = tx.vin.size(); j-- > 0;) {
const COutPoint &out = tx.vin[j].prevout;
const CTxInUndo &undo = txundo.vprevout[j];
if (!ApplyTxInUndo(undo, view, out, tx.vin[j]))
const CScriptWitness &pegin_wit = tx.wit.vtxinwit.size() > j ? tx.wit.vtxinwit[j].m_pegin_witness : CScriptWitness();
if (!ApplyTxInUndo(undo, view, out, tx.vin[j], pegin_wit))
fClean = false;
}
}
@ -2207,54 +2270,43 @@ bool BitcoindRPCCheck(const bool init)
return true;
}
bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {
bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& prevout) {
// Format on stack is as follows:
// 1) txid - txid of parent chain transaction - TODO-PEGIN unneeded?
// 2) nout - output number that is a peg-in output - unneeded?
// 3) value - the value of the pegin output
// 4) asset type - the asset type being pegged in
// 5) genesis blockhash - genesis block of the parent chain
// 6) witness program - program that script evaluation will be evaluated against
// 7) serialized transaction - serialized bitcoin transaction
// 8) txout proof - merkle proof connecting transaction to header
// 1) value - the value of the pegin output
// 2) asset type - the asset type being pegged in
// 3) genesis blockhash - genesis block of the parent chain
// 4) witness program - program that script evaluation will be evaluated against
// 5) serialized transaction - serialized bitcoin transaction
// 6) txout proof - merkle proof connecting transaction to header
//
// First 6 values are enough to validate a peg-in without any internal knowledge
// First 4 values(plus prevout) are enough to validate a peg-in without any internal knowledge
// of Bitcoin serialization. This is useful for further abstraction by outsourcing
// the other validity checks to RPC calls. The last two stack items
// the other validity checks to RPC calls.
const std::vector<std::vector<unsigned char> >& stack = pegin_witness.stack;
// Must include all elements
if (stack.size() != 8) {
if (stack.size() != 6) {
return false;
}
// Txid must be included
if (stack[0].size() != 32) {
return false;
}
uint256 txid(stack[0]);
// Get output number
int nOut = CScriptNum(stack[1], true).getint();
// Get output value. Special 8 byte length to capture possible bitcoin values
CAmount value = CScriptNum(stack[2], true, 8).getint64();
CAmount value = CScriptNum(stack[0], true, 8).getint64();
// Get asset type
if (stack[3].size() != 32) {
if (stack[1].size() != 32) {
return false;
}
CAsset asset(stack[3]);
CAsset asset(stack[1]);
// Get genesis blockhash
if (stack[4].size() != 32) {
if (stack[2].size() != 32) {
return false;
}
uint256 gen_hash(stack[4]);
uint256 gen_hash(stack[2]);
// Get witness program
CScript witness_program(stack[5].begin(), stack[5].end());
CScript witness_program(stack[3].begin(), stack[3].end());
int version = -1;
std::vector<unsigned char> witnessProgram;
if (!witness_program.IsWitnessProgram(version, witnessProgram)) {
@ -2264,7 +2316,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {
// Get serialized transaction
Sidechain::Bitcoin::CTransactionRef pegtx;
try {
CDataStream pegtx_stream(stack[6], SER_NETWORK, PROTOCOL_VERSION);
CDataStream pegtx_stream(stack[4], SER_NETWORK, PROTOCOL_VERSION);
pegtx_stream >> pegtx;
if (!pegtx_stream.empty()) {
return false;
@ -2280,7 +2332,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {
std::vector<unsigned int> txIndices;
try {
CDataStream merkleBlockStream(stack[7], SER_NETWORK, PROTOCOL_VERSION);
CDataStream merkleBlockStream(stack[5], SER_NETWORK, PROTOCOL_VERSION);
merkleBlockStream >> merkle_block;
if (!merkleBlockStream.empty() || !CheckBitcoinProof(merkle_block.header.GetHash(), merkle_block.header.nBits)) {
return false;
@ -2294,12 +2346,12 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {
}
// Check that transaction matches txid
if (pegtx->GetHash() != txid) {
if (pegtx->GetHash() != prevout.hash) {
return false;
}
// Check that the merkle proof corresponds to the txid
if (txid != txHashes[0]) {
if (prevout.hash != txHashes[0]) {
return false;
}
@ -2314,15 +2366,14 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {
}
// Check the transaction nout/value matches
if (nOut < 0 || (unsigned int)nOut >= pegtx->vout.size() || value != pegtx->vout[nOut].nValue) {
if (prevout.n >= pegtx->vout.size() || value != pegtx->vout[prevout.n].nValue) {
return false;
}
// Check that the witness program matches the p2ch on the transaction output
opcodetype opcodeTmp;
unsigned char tweak[32];
CSHA256().Write(witness_program.data(), witness_program.size()).Finalize(tweak);
CScript fedpegscript = Params().GetConsensus().fedpegScript;
// fedpegscript should be multisig or OP_TRUE
txnouttype type;
std::vector<std::vector<unsigned char> > solutions;
@ -2336,9 +2387,11 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {
{
if (vch.size() == 33)
{
unsigned char tweak[32];
size_t pub_len = 33;
int ret;
unsigned char *pub_start = &(*(sdpc - pub_len));
CHMAC_SHA256(pub_start, pub_len).Write(witness_program.data(), witness_program.size()).Finalize(tweak);
secp256k1_pubkey pubkey;
ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_verify_amounts, &pubkey, pub_start, pub_len);
assert(ret == 1);
@ -2355,7 +2408,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {
// Check against expected p2sh output
CScriptID expectedP2SH(fedpegscript);
CScript expected_script(CScript() << OP_HASH160 << ToByteVector(expectedP2SH) << OP_EQUAL);
if (pegtx->vout[nOut].scriptPubKey != expected_script) {
if (pegtx->vout[prevout.n].scriptPubKey != expected_script) {
return false;
}
@ -2369,9 +2422,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {
// Constructs unblinded output to be used in amount and scriptpubkey checks during pegin
CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness) {
// Must check validity first for formatting reasons
assert(IsValidPeginWitness(pegin_witness));
return CTxOut(CAsset(pegin_witness.stack[3]), CScriptNum(pegin_witness.stack[2], true, 8).getint64(), CScript(pegin_witness.stack[5].begin(), pegin_witness.stack[5].end()));
return CTxOut(CAsset(pegin_witness.stack[1]), CScriptNum(pegin_witness.stack[0], true, 8).getint64(), CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end()));
}
// Protected by cs_main
@ -2587,7 +2638,11 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
// be in ConnectBlock because they require the UTXO set
prevheights.resize(tx.vin.size());
for (size_t j = 0; j < tx.vin.size(); j++) {
prevheights[j] = view.AccessCoins(tx.vin[j].prevout.hash)->nHeight;
if (tx.vin[j].m_is_pegin) {
prevheights[j] = -1;
} else {
prevheights[j] = view.AccessCoins(tx.vin[j].prevout.hash)->nHeight;
}
}
if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) {
@ -2610,10 +2665,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
{
std::vector<CCheck*> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, txdata[i], setPeginsSpent == NULL ? setPeginsSpentDummy : *setPeginsSpent, nScriptCheckThreads ? &vChecks : NULL)) {
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, txdata[i], setPeginsSpent == NULL ? setPeginsSpentDummy : *setPeginsSpent, nScriptCheckThreads ? &vChecks : NULL))
return error("ConnectBlock(): CheckInputs on %s failed with %s",
tx.GetHash().ToString(), FormatStateMessage(state));
}
control.Add(vChecks);
}

View file

@ -263,7 +263,7 @@ void ThreadScriptCheck();
/** Check if bitcoind connection via RPC is correctly working*/
bool BitcoindRPCCheck(bool init);
/** Checks pegin witness for validity */
bool IsValidPeginWitness(const CScriptWitness& pegin_witness);
bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& prevout);
/** Extracts an output from pegin witness for evaluation as a normal output */
CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness);
/** Check whether we are doing an initial block download (synchronizing from disk or network) */