|
| 1 | +// Copyright (c) 2017-2017 The Bitcoin Core developers |
| 2 | +// Distributed under the MIT software license, see the accompanying |
| 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 4 | + |
| 5 | +#include <pegins.h> |
| 6 | + |
| 7 | +#include <arith_uint256.h> |
| 8 | +#include <block_proof.h> |
| 9 | +#include <chainparams.h> |
| 10 | +#include <crypto/hmac_sha256.h> |
| 11 | +#include <consensus/consensus.h> |
| 12 | +#include <consensus/validation.h> |
| 13 | +#include <mainchainrpc.h> |
| 14 | +#include <pow.h> |
| 15 | +#include <primitives/transaction.h> |
| 16 | +#include <primitives/bitcoin/merkleblock.h> |
| 17 | +#include <secp256k1.h> |
| 18 | +#include <script/interpreter.h> |
| 19 | +#include <script/standard.h> |
| 20 | +#include <streams.h> |
| 21 | +#include <util.h> |
| 22 | + |
| 23 | +// |
| 24 | +// ELEMENTS |
| 25 | +// |
| 26 | + |
| 27 | +namespace { |
| 28 | +static secp256k1_context* secp256k1_ctx_validation; |
| 29 | + |
| 30 | +class Secp256k1Ctx |
| 31 | +{ |
| 32 | +public: |
| 33 | + Secp256k1Ctx() { |
| 34 | + assert(secp256k1_ctx_validation == NULL); |
| 35 | + secp256k1_ctx_validation = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); |
| 36 | + assert(secp256k1_ctx_validation != NULL); |
| 37 | + } |
| 38 | + |
| 39 | + ~Secp256k1Ctx() { |
| 40 | + assert(secp256k1_ctx_validation != NULL); |
| 41 | + secp256k1_context_destroy(secp256k1_ctx_validation); |
| 42 | + secp256k1_ctx_validation = NULL; |
| 43 | + } |
| 44 | +}; |
| 45 | +static Secp256k1Ctx instance_of_secp256k1ctx; |
| 46 | +} |
| 47 | + |
| 48 | +bool GetAmountFromParentChainPegin(CAmount& amount, const Sidechain::Bitcoin::CTransaction& txBTC, unsigned int nOut) |
| 49 | +{ |
| 50 | + amount = txBTC.vout[nOut].nValue; |
| 51 | + return true; |
| 52 | +} |
| 53 | + |
| 54 | +bool GetAmountFromParentChainPegin(CAmount& amount, const CTransaction& txBTC, unsigned int nOut) |
| 55 | +{ |
| 56 | + //if (!txBTC.vout[nOut].nValue.IsExplicit()) { |
| 57 | + // return false; |
| 58 | + //} |
| 59 | + //if (!txBTC.vout[nOut].nAsset.IsExplicit()) { |
| 60 | + // return false; |
| 61 | + //} |
| 62 | + //if (txBTC.vout[nOut].nAsset.GetAsset() != Params().GetConsensus().parent_pegged_asset) { |
| 63 | + // return false; |
| 64 | + //} |
| 65 | + //amount = txBTC.vout[nOut].nValue.GetAmount(); |
| 66 | + //TODO(rebase) reenable above for CA/CT |
| 67 | + amount = txBTC.vout[nOut].nValue; |
| 68 | + return true; |
| 69 | +} |
| 70 | + |
| 71 | +// Takes federation redeem script and adds HMAC_SHA256(pubkey, scriptPubKey) as a tweak to each pubkey |
| 72 | +CScript calculate_contract(const CScript& federationRedeemScript, const CScript& scriptPubKey) { |
| 73 | + CScript scriptDestination; |
| 74 | + txnouttype type; |
| 75 | + std::vector<std::vector<unsigned char> > solutions; |
| 76 | + // Sanity check fedRedeemScript |
| 77 | + if (!Solver(federationRedeemScript, type, solutions) || (type != TX_MULTISIG && type != TX_TRUE)) { |
| 78 | + assert(false); |
| 79 | + } |
| 80 | + |
| 81 | + { |
| 82 | + CScript::const_iterator sdpc = federationRedeemScript.begin(); |
| 83 | + std::vector<unsigned char> vch; |
| 84 | + opcodetype opcodeTmp; |
| 85 | + while (federationRedeemScript.GetOp(sdpc, opcodeTmp, vch)) |
| 86 | + { |
| 87 | + size_t pub_len = 33; |
| 88 | + if (vch.size() == pub_len) |
| 89 | + { |
| 90 | + unsigned char tweak[32]; |
| 91 | + CHMAC_SHA256(vch.data(), pub_len).Write(scriptPubKey.data(), scriptPubKey.size()).Finalize(tweak); |
| 92 | + int ret; |
| 93 | + secp256k1_pubkey watchman; |
| 94 | + secp256k1_pubkey tweaked; |
| 95 | + ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_validation, &watchman, vch.data(), pub_len); |
| 96 | + assert(ret == 1); |
| 97 | + ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_validation, &tweaked, vch.data(), pub_len); |
| 98 | + assert(ret == 1); |
| 99 | + // If someone creates a tweak that makes this fail, they broke SHA256 |
| 100 | + ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx_validation, &tweaked, tweak); |
| 101 | + assert(ret == 1); |
| 102 | + unsigned char new_pub[33]; |
| 103 | + ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx_validation, new_pub, &pub_len, &tweaked, SECP256K1_EC_COMPRESSED); |
| 104 | + assert(ret == 1); |
| 105 | + assert(pub_len == 33); |
| 106 | + |
| 107 | + // push tweaked pubkey |
| 108 | + std::vector<unsigned char> pub_vec(new_pub, new_pub + pub_len); |
| 109 | + scriptDestination << pub_vec; |
| 110 | + |
| 111 | + // Sanity checks to reduce pegin risk. If the tweaked |
| 112 | + // value flips a bit, we may lose pegin funds irretrievably. |
| 113 | + // We take the tweak, derive its pubkey and check that |
| 114 | + // `tweaked - watchman = tweak` to check the computation |
| 115 | + // two different ways |
| 116 | + secp256k1_pubkey tweaked2; |
| 117 | + ret = secp256k1_ec_pubkey_create(secp256k1_ctx_validation, &tweaked2, tweak); |
| 118 | + assert(ret); |
| 119 | + ret = secp256k1_ec_pubkey_negate(secp256k1_ctx_validation, &watchman); |
| 120 | + assert(ret); |
| 121 | + secp256k1_pubkey* pubkey_combined[2]; |
| 122 | + pubkey_combined[0] = &watchman; |
| 123 | + pubkey_combined[1] = &tweaked; |
| 124 | + secp256k1_pubkey maybe_tweaked2; |
| 125 | + ret = secp256k1_ec_pubkey_combine(secp256k1_ctx_validation, &maybe_tweaked2, pubkey_combined, 2); |
| 126 | + assert(ret); |
| 127 | + assert(!memcmp(&maybe_tweaked2, &tweaked2, 64)); |
| 128 | + } else { |
| 129 | + // add to script untouched |
| 130 | + if (vch.size() > 0) { |
| 131 | + scriptDestination << vch; |
| 132 | + } else { |
| 133 | + scriptDestination << opcodeTmp; |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + return scriptDestination; |
| 140 | +} |
| 141 | + |
| 142 | +template<typename T> |
| 143 | +static bool CheckPeginTx(const std::vector<unsigned char>& tx_data, T& pegtx, const COutPoint& prevout, const CAmount claim_amount, const CScript& claim_script) |
| 144 | +{ |
| 145 | + try { |
| 146 | + CDataStream pegtx_stream(tx_data, SER_NETWORK, PROTOCOL_VERSION); |
| 147 | + pegtx_stream >> pegtx; |
| 148 | + if (!pegtx_stream.empty()) { |
| 149 | + return false; |
| 150 | + } |
| 151 | + } catch (std::exception& e) { |
| 152 | + // Invalid encoding of transaction |
| 153 | + return false; |
| 154 | + } |
| 155 | + |
| 156 | + // Check that transaction matches txid |
| 157 | + if (pegtx->GetHash() != prevout.hash) { |
| 158 | + return false; |
| 159 | + } |
| 160 | + |
| 161 | + if (prevout.n >= pegtx->vout.size()) { |
| 162 | + return false; |
| 163 | + } |
| 164 | + CAmount amount = 0; |
| 165 | + if (!GetAmountFromParentChainPegin(amount, *pegtx, prevout.n)) { |
| 166 | + return false; |
| 167 | + } |
| 168 | + // Check the transaction nout/value matches |
| 169 | + if (claim_amount != amount) { |
| 170 | + return false; |
| 171 | + } |
| 172 | + |
| 173 | + // Check that the witness program matches the p2ch on the p2sh-p2wsh transaction output |
| 174 | + CScript tweaked_fedpegscript = calculate_contract(Params().GetConsensus().fedpegScript, claim_script); |
| 175 | + CScript witness_output(GetScriptForWitness(tweaked_fedpegscript)); |
| 176 | + CScript expected_script(CScript() << OP_HASH160 << ToByteVector(CScriptID(witness_output)) << OP_EQUAL); |
| 177 | + if (pegtx->vout[prevout.n].scriptPubKey != expected_script) { |
| 178 | + return false; |
| 179 | + } |
| 180 | + |
| 181 | + return true; |
| 182 | +} |
| 183 | + |
| 184 | +template<typename T> |
| 185 | +static bool GetBlockAndTxFromMerkleBlock(uint256& block_hash, uint256& tx_hash, T& merkle_block, const std::vector<unsigned char>& merkle_block_raw) |
| 186 | +{ |
| 187 | + try { |
| 188 | + std::vector<uint256> tx_hashes; |
| 189 | + std::vector<unsigned int> tx_indices; |
| 190 | + CDataStream merkle_block_stream(merkle_block_raw, SER_NETWORK, PROTOCOL_VERSION); |
| 191 | + merkle_block_stream >> merkle_block; |
| 192 | + block_hash = merkle_block.header.GetHash(); |
| 193 | + |
| 194 | + if (!merkle_block_stream.empty()) { |
| 195 | + return false; |
| 196 | + } |
| 197 | + if (merkle_block.txn.ExtractMatches(tx_hashes, tx_indices) != merkle_block.header.hashMerkleRoot || tx_hashes.size() != 1) { |
| 198 | + return false; |
| 199 | + } |
| 200 | + tx_hash = tx_hashes[0]; |
| 201 | + } catch (std::exception& e) { |
| 202 | + // Invalid encoding of merkle block |
| 203 | + return false; |
| 204 | + } |
| 205 | + return true; |
| 206 | +} |
| 207 | + |
| 208 | +bool CheckParentProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params) |
| 209 | +{ |
| 210 | + bool fNegative; |
| 211 | + bool fOverflow; |
| 212 | + arith_uint256 bnTarget; |
| 213 | + |
| 214 | + bnTarget.SetCompact(nBits, &fNegative, &fOverflow); |
| 215 | + |
| 216 | + // Check range |
| 217 | + if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.parentChainPowLimit)) |
| 218 | + return false; |
| 219 | + |
| 220 | + // Check proof of work matches claimed amount |
| 221 | + if (UintToArith256(hash) > bnTarget) |
| 222 | + return false; |
| 223 | + |
| 224 | + return true; |
| 225 | +} |
| 226 | + |
| 227 | +bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& prevout, bool check_depth) { |
| 228 | + // 0) Return false if !consensus.has_parent_chain |
| 229 | + if (!Params().GetConsensus().has_parent_chain) { |
| 230 | + return false; |
| 231 | + } |
| 232 | + |
| 233 | + // Format on stack is as follows: |
| 234 | + // 1) value - the value of the pegin output |
| 235 | + // 2) asset type - the asset type being pegged in |
| 236 | + // 3) genesis blockhash - genesis block of the parent chain |
| 237 | + // 4) claim script - script to be evaluated for spend authorization |
| 238 | + // 5) serialized transaction - serialized bitcoin transaction |
| 239 | + // 6) txout proof - merkle proof connecting transaction to header |
| 240 | + // |
| 241 | + // First 4 values(plus prevout) are enough to validate a peg-in without any internal knowledge |
| 242 | + // of Bitcoin serialization. This is useful for further abstraction by outsourcing |
| 243 | + // the other validity checks to RPC calls. |
| 244 | + |
| 245 | + const std::vector<std::vector<unsigned char> >& stack = pegin_witness.stack; |
| 246 | + // Must include all elements |
| 247 | + if (stack.size() != 6) { |
| 248 | + return false; |
| 249 | + } |
| 250 | + |
| 251 | + CDataStream stream(stack[0], SER_NETWORK, PROTOCOL_VERSION); |
| 252 | + CAmount value; |
| 253 | + try { |
| 254 | + stream >> value; |
| 255 | + } catch (...) { |
| 256 | + return false; |
| 257 | + } |
| 258 | + |
| 259 | + if (!MoneyRange(value)) { |
| 260 | + return false; |
| 261 | + } |
| 262 | + |
| 263 | + // Get asset type |
| 264 | + if (stack[1].size() != 32) { |
| 265 | + return false; |
| 266 | + } |
| 267 | + //TODO(rebase) CA |
| 268 | + //CAsset asset(stack[1]); |
| 269 | + |
| 270 | + // Get genesis blockhash |
| 271 | + if (stack[2].size() != 32) { |
| 272 | + return false; |
| 273 | + } |
| 274 | + uint256 gen_hash(stack[2]); |
| 275 | + |
| 276 | + // Get claim_script, sanity check size |
| 277 | + CScript claim_script(stack[3].begin(), stack[3].end()); |
| 278 | + if (claim_script.size() > 100) { |
| 279 | + return false; |
| 280 | + } |
| 281 | + |
| 282 | + uint256 block_hash; |
| 283 | + uint256 tx_hash; |
| 284 | + int num_txs; |
| 285 | + // Get txout proof |
| 286 | + if (Params().GetConsensus().ParentChainHasPow()) { |
| 287 | + Sidechain::Bitcoin::CMerkleBlock merkle_block_pow; |
| 288 | + if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, merkle_block_pow, stack[5])) { |
| 289 | + return false; |
| 290 | + } |
| 291 | + if (!CheckParentProofOfWork(block_hash, merkle_block_pow.header.nBits, Params().GetConsensus())) { |
| 292 | + return false; |
| 293 | + } |
| 294 | + |
| 295 | + Sidechain::Bitcoin::CTransactionRef pegtx; |
| 296 | + if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) { |
| 297 | + return false; |
| 298 | + } |
| 299 | + |
| 300 | + num_txs = merkle_block_pow.txn.GetNumTransactions(); |
| 301 | + } else { |
| 302 | + //TODO(rebase) parent signed blocks |
| 303 | + //CMerkleBlock merkle_block; |
| 304 | + //if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, merkle_block, stack[5])) { |
| 305 | + // return false; |
| 306 | + //} |
| 307 | + |
| 308 | + //if (!CheckProofSignedParent(merkle_block.header, Params().GetConsensus())) { |
| 309 | + // return false; |
| 310 | + //} |
| 311 | + |
| 312 | + //CTransactionRef pegtx; |
| 313 | + //if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) { |
| 314 | + // return false; |
| 315 | + //} |
| 316 | + |
| 317 | + //num_txs = merkle_block.txn.GetNumTransactions(); |
| 318 | + } |
| 319 | + |
| 320 | + // Check that the merkle proof corresponds to the txid |
| 321 | + if (prevout.hash != tx_hash) { |
| 322 | + return false; |
| 323 | + } |
| 324 | + |
| 325 | + // Check the genesis block corresponds to a valid peg (only one for now) |
| 326 | + if (gen_hash != Params().ParentGenesisBlockHash()) { |
| 327 | + return false; |
| 328 | + } |
| 329 | + |
| 330 | + //TODO(rebase) CA |
| 331 | + //// Check the asset type corresponds to a valid pegged asset (only one for now) |
| 332 | + //if (asset != Params().GetConsensus().pegged_asset) { |
| 333 | + // return false; |
| 334 | + //} |
| 335 | + |
| 336 | + // Finally, validate peg-in via rpc call |
| 337 | + if (check_depth && gArgs.GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) { |
| 338 | + if (!IsConfirmedBitcoinBlock(block_hash, Params().GetConsensus().pegin_min_depth, num_txs)) { |
| 339 | + return false; |
| 340 | + } |
| 341 | + } |
| 342 | + return true; |
| 343 | +} |
| 344 | + |
| 345 | +// Constructs unblinded "bitcoin" output to be used in amount and scriptpubkey checks during pegin validation. |
| 346 | +CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness) { |
| 347 | + CDataStream stream(pegin_witness.stack[0], SER_NETWORK, PROTOCOL_VERSION); |
| 348 | + CAmount value; |
| 349 | + stream >> value; |
| 350 | + |
| 351 | + //TODO(rebase) CA |
| 352 | + //return CTxOut(CAsset(pegin_witness.stack[1]), value, CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end())); |
| 353 | + return CTxOut(value, CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end())); |
| 354 | +} |
0 commit comments