Skip to content

Commit

Permalink
add pegin witness validation and extraction helper functions
Browse files Browse the repository at this point in the history
  • Loading branch information
instagibbs committed Nov 6, 2017
1 parent 16973cd commit 34d7cd0
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ libbitcoin_wallet_a_SOURCES = \
wallet/wallet.cpp \
wallet/walletdb.cpp \
policy/rbf.cpp \
primitives/bitcoin/merkleblock.cpp \
primitives/bitcoin/block.cpp \
$(BITCOIN_CORE_H)

# crypto primitives library
Expand Down
6 changes: 6 additions & 0 deletions src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ class CScriptNum
return m_value;
}

// Only used for peg-in witness values
int64_t getint64() const
{
return m_value;
}

std::vector<unsigned char> getvch() const
{
return serialize(m_value);
Expand Down
170 changes: 170 additions & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
#include "pow.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "primitives/bitcoin/block.h"
#include "primitives/bitcoin/transaction.h"
#include "primitives/bitcoin/merkleblock.h"
#include "random.h"
#include "script/script.h"
#include "script/sigcache.h"
Expand Down Expand Up @@ -2204,6 +2207,173 @@ bool BitcoindRPCCheck(const bool init)
return true;
}

bool IsValidPeginWitness(const CScriptWitness& pegin_witness) {

// 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
//
// First 6 values 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

const std::vector<std::vector<unsigned char> >& stack = pegin_witness.stack;
// Must include all elements
if (stack.size() != 8) {
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();

// Get asset type
if (stack[3].size() != 32) {
return false;
}
CAsset asset(stack[3]);

// Get genesis blockhash
if (stack[4].size() != 32) {
return false;
}
uint256 gen_hash(stack[4]);

// Get witness program
CScript witness_program(stack[5].begin(), stack[5].end());
int version = -1;
std::vector<unsigned char> witnessProgram;
if (!witness_program.IsWitnessProgram(version, witnessProgram)) {
return false;
}

// Get serialized transaction
Sidechain::Bitcoin::CTransactionRef pegtx;
try {
CDataStream pegtx_stream(stack[6], SER_NETWORK, PROTOCOL_VERSION);
pegtx_stream >> pegtx;
if (!pegtx_stream.empty()) {
return false;
}
} catch (std::exception& e) {
// Invalid encoding of transaction
return false;
}

// Get txout proof
Sidechain::Bitcoin::CMerkleBlock merkle_block;
std::vector<uint256> txHashes;
std::vector<unsigned int> txIndices;

try {
CDataStream merkleBlockStream(stack[7], SER_NETWORK, PROTOCOL_VERSION);
merkleBlockStream >> merkle_block;
if (!merkleBlockStream.empty() || !CheckBitcoinProof(merkle_block.header.GetHash(), merkle_block.header.nBits)) {
return false;
}
if (merkle_block.txn.ExtractMatches(txHashes, txIndices) != merkle_block.header.hashMerkleRoot || txHashes.size() != 1) {
return false;
}
} catch (std::exception& e) {
// Invalid encoding of merkle block
return false;
}

// Check that transaction matches txid
if (pegtx->GetHash() != txid) {
return false;
}

// Check that the merkle proof corresponds to the txid
if (txid != txHashes[0]) {
return false;
}

// Check the genesis block corresponds to a valid peg (only one for now)
if (gen_hash != Params().ParentGenesisBlockHash()) {
return false;
}

// Check the asset type corresponds to a valid pegged asset (only one for now)
if (asset != Params().GetConsensus().pegged_asset) {
return false;
}

// Check the transaction nout/value matches
if (nOut < 0 || (unsigned int)nOut >= pegtx->vout.size() || value != pegtx->vout[nOut].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;
if (!Solver(fedpegscript, type, solutions) || (type != TX_MULTISIG && type != TX_TRUE)) {
assert(false);
}
{
CScript::iterator sdpc = fedpegscript.begin();
std::vector<unsigned char> vch;
while (fedpegscript.GetOp(sdpc, opcodeTmp, vch))
{
if (vch.size() == 33)
{
size_t pub_len = 33;
int ret;
unsigned char *pub_start = &(*(sdpc - pub_len));
secp256k1_pubkey pubkey;
ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_verify_amounts, &pubkey, pub_start, pub_len);
assert(ret == 1);
// If someone creates a tweak that makes this fail, they broke SHA256
ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx_verify_amounts, &pubkey, tweak);
assert(ret == 1);
ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx_verify_amounts, pub_start, &pub_len, &pubkey, SECP256K1_EC_COMPRESSED);
assert(ret == 1);
assert(pub_len == 33);
}
}
}

// 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) {
return false;
}

// Finally, validate peg-in via rpc call
if (GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) {
// TODO-PEGIN return useful error message
return IsConfirmedBitcoinBlock(merkle_block.header.GetHash(), GetArg("-peginconfirmationdepth", DEFAULT_PEGIN_CONFIRMATION_DEPTH));
}
return true;
}

// 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()));
}

// Protected by cs_main
VersionBitsCache versionbitscache;

Expand Down
4 changes: 4 additions & 0 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ void UnloadBlockIndex();
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);
/** 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) */
bool IsInitialBlockDownload();
/** Format a string that describes several potential problems detected by the core.
Expand Down

0 comments on commit 34d7cd0

Please sign in to comment.