Skip to content

Commit fb8a453

Browse files
committed
Add pegin validation utility methods
1 parent 037b260 commit fb8a453

File tree

2 files changed

+379
-0
lines changed

2 files changed

+379
-0
lines changed

src/pegins.cpp

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
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+
}

src/pegins.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) 2017-2018 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+
#ifndef BITCOIN_PEGINS_H
6+
#define BITCOIN_PEGINS_H
7+
8+
#include <amount.h>
9+
#include <consensus/params.h>
10+
#include <primitives/bitcoin/transaction.h>
11+
#include <primitives/transaction.h>
12+
#include <script/script.h>
13+
14+
/** Calculates script necessary for p2ch peg-in transactions */
15+
CScript calculate_contract(const CScript& federationRedeemScript, const CScript& witnessProgram);
16+
bool GetAmountFromParentChainPegin(CAmount& amount, const Sidechain::Bitcoin::CTransaction& txBTC, unsigned int nOut);
17+
bool GetAmountFromParentChainPegin(CAmount& amount, const CTransaction& txBTC, unsigned int nOut);
18+
/** Check whether a parent chain block hash satisfies the proof-of-work requirement specified by nBits */
19+
bool CheckParentProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&);
20+
/** Checks pegin witness for validity */
21+
bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& prevout, bool check_depth = true);
22+
// Constructs unblinded output to be used in amount and scriptpubkey checks during pegin
23+
CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness);
24+
25+
#endif // BITCOIN_PEGINS_H

0 commit comments

Comments
 (0)