Add detection of liquidv1 fedpeg script, startup sanity check

This commit is contained in:
Gregory Sanders 2019-01-15 10:22:45 -05:00
parent 32aec9af0b
commit 592a622b4b
6 changed files with 96 additions and 8 deletions

View file

@ -70,20 +70,23 @@ bool GetAmountFromParentChainPegin(CAmount& amount, const CTransaction& txBTC, u
}
// Takes federation redeem script and adds HMAC_SHA256(pubkey, scriptPubKey) as a tweak to each pubkey
CScript calculate_contract(const CScript& federationRedeemScript, const CScript& scriptPubKey) {
CScript calculate_contract(const CScript& federation_script, const CScript& scriptPubKey) {
CScript scriptDestination;
txnouttype type;
std::vector<std::vector<unsigned char> > solutions;
// Sanity check fedRedeemScript
if (!Solver(federationRedeemScript, type, solutions) || (type != TX_MULTISIG && type != TX_TRUE)) {
unsigned int required;
std::vector<std::vector<unsigned char>> keys;
// Sanity check federation_script only to match 3 templates
if (federation_script != CScript() << OP_TRUE &&
!MatchMultisig(federation_script, required, keys) &&
!MatchLiquidWatchman(federation_script)) {
assert(false);
}
{
CScript::const_iterator sdpc = federationRedeemScript.begin();
CScript::const_iterator sdpc = federation_script.begin();
std::vector<unsigned char> vch;
opcodetype opcodeTmp;
while (federationRedeemScript.GetOp(sdpc, opcodeTmp, vch))
while (federation_script.GetOp(sdpc, opcodeTmp, vch))
{
size_t pub_len = 33;
if (vch.size() == pub_len)
@ -352,3 +355,75 @@ CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness) {
//return CTxOut(CAsset(pegin_witness.stack[1]), value, CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end()));
return CTxOut(value, CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end()));
}
bool MatchLiquidWatchman(const CScript& script)
{
CScript::const_iterator it = script.begin();
std::vector<unsigned char> data;
opcodetype opcode;
// Stack depth check for branch choice
if (!script.GetOp(it, opcode, data) || opcode != OP_DEPTH) {
return false;
}
// Take in value, then check equality
if (!script.GetOp(it, opcode, data) ||
!script.GetOp(it, opcode, data) ||
opcode != OP_EQUAL) {
return false;
}
// IF EQUAL
if (!script.GetOp(it, opcode, data) || opcode != OP_IF) {
return false;
}
// Take in value k, make sure minimally encoded number from 1 to 16
if (!script.GetOp(it, opcode, data) ||
opcode > OP_16 ||
(opcode < OP_1NEGATE && !CheckMinimalPush(data, opcode))) {
return false;
}
opcodetype opcode2 = opcode;
std::vector<unsigned char> num = data;
// Iterate through multisig stuff until ELSE is hit
while (opcode != OP_ELSE) {
if (!script.GetOp(it, opcode, data)) {
return false;
}
}
// Take minimally-encoded CSV push number k'
if (!script.GetOp(it, opcode, data) ||
opcode > OP_16 || (opcode < OP_1NEGATE && !CheckMinimalPush(data, opcode))) {
return false;
}
// CSV
if (!script.GetOp(it, opcode, data) || opcode != OP_CHECKSEQUENCEVERIFY) {
return false;
}
// Drop the CSV number
if (!script.GetOp(it, opcode, data) || opcode != OP_DROP) {
return false;
}
// Take the minimally-encoded n of k-of-n multisig arg
if (!script.GetOp(it, opcode, data) ||
opcode > OP_16 || (opcode < OP_1NEGATE && !CheckMinimalPush(data, opcode)) ) {
return false;
}
// The two multisig k-numbers must not match, otherwise ELSE branch can not be reached
if (opcode == opcode2 && num == data) {
return false;
}
// Find the ENDIF
while (opcode != OP_ENDIF) {
if (!script.GetOp(it, opcode, data)) {
return false;
}
}
// CHECKMULTISIG
if (!script.GetOp(it, opcode, data) || opcode != OP_CHECKMULTISIG) {
return false;
}
// No more pushes
return (it + 1 == script.end());
}

View file

@ -22,4 +22,13 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
// Constructs unblinded output to be used in amount and scriptpubkey checks during pegin
CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness);
/* Belt-and-suspenders-only matching against telescoped multisig used on Liquid v1:
* Pseudo-structure:
* Check number of elements on stack
* If enough for federation multisig, push all multisig args onto stack except OP_CMS
* If not, check CSV timeout, then if successful, push emergency key multisig args on
* stack except OP_CMS. End if, then push OP_CMS.
*/
bool MatchLiquidWatchman(const CScript& script);
#endif // BITCOIN_PEGINS_H

View file

@ -233,7 +233,7 @@ bool static CheckPubKeyEncoding(const valtype &vchPubKey, unsigned int flags, co
return true;
}
bool static CheckMinimalPush(const valtype& data, opcodetype opcode) {
bool CheckMinimalPush(const valtype& data, opcodetype opcode) {
// Excludes OP_1NEGATE, OP_1-16 since they are by definition minimal
assert(0 <= opcode && opcode <= OP_PUSHDATA4);
if (data.size() == 0) {

View file

@ -196,4 +196,6 @@ size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey,
int FindAndDelete(CScript& script, const CScript& b);
bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode);
#endif // BITCOIN_SCRIPT_INTERPRETER_H

View file

@ -72,7 +72,7 @@ static constexpr bool IsSmallInteger(opcodetype opcode)
return opcode >= OP_1 && opcode <= OP_16;
}
static bool MatchMultisig(const CScript& script, unsigned int& required, std::vector<valtype>& pubkeys)
bool MatchMultisig(const CScript& script, unsigned int& required, std::vector<valtype>& pubkeys)
{
opcodetype opcode;
valtype data;

View file

@ -203,4 +203,6 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
*/
CScript GetScriptForWitness(const CScript& redeemscript);
bool MatchMultisig(const CScript& script, unsigned int& required, std::vector<std::vector<unsigned char>>& pubkeys);
#endif // BITCOIN_SCRIPT_STANDARD_H