mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
221 lines
7.5 KiB
C++
221 lines
7.5 KiB
C++
// Copyright (c) 2018-2018 The Elements developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <primitives/pak.h>
|
|
#include <pubkey.h>
|
|
#include <dynafed.h>
|
|
|
|
// ELEMENTS
|
|
|
|
// PAK entries loaded by config file entries
|
|
// These will be embedded into coinbase transaction
|
|
// during block creation and tested against during
|
|
// testproposedblock
|
|
boost::optional<CPAKList> g_paklist_config;
|
|
// PAK entries loaded from latest coinbase pak commitment
|
|
CPAKList g_paklist_blockchain;
|
|
|
|
///////////
|
|
|
|
namespace {
|
|
|
|
static secp256k1_context *secp256k1_ctx_pak;
|
|
|
|
class CSecp256k1Init {
|
|
public:
|
|
CSecp256k1Init() {
|
|
secp256k1_ctx_pak = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
|
|
}
|
|
~CSecp256k1Init() {
|
|
secp256k1_context_destroy(secp256k1_ctx_pak);
|
|
}
|
|
};
|
|
static CSecp256k1Init instance_of_csecp256k1;
|
|
}
|
|
|
|
bool CPAKList::operator==(const CPAKList &other) const
|
|
{
|
|
if (this->m_offline_keys.size() != other.m_offline_keys.size()) {
|
|
return false;
|
|
} else {
|
|
for (unsigned int i = 0; i < this->m_offline_keys.size(); i++) {
|
|
if (memcmp(&this->m_offline_keys[i], &other.m_offline_keys[i], sizeof(secp256k1_pubkey)) != 0 ||
|
|
memcmp(&this->m_online_keys[i], &other.m_online_keys[i], sizeof(secp256k1_pubkey)) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CPAKList::FromBytes(CPAKList &paklist, const std::vector<std::vector<unsigned char> >& offline_keys_bytes, const std::vector<std::vector<unsigned char> >& online_keys_bytes)
|
|
{
|
|
if(offline_keys_bytes.size() != online_keys_bytes.size()
|
|
|| offline_keys_bytes.size() > SECP256K1_WHITELIST_MAX_N_KEYS) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<secp256k1_pubkey> offline_keys;
|
|
std::vector<secp256k1_pubkey> online_keys;
|
|
for (unsigned int i = 0; i < offline_keys_bytes.size(); i++) {
|
|
secp256k1_pubkey pubkey1;
|
|
secp256k1_pubkey pubkey2;
|
|
int ret1 = secp256k1_ec_pubkey_parse(secp256k1_ctx_pak, &pubkey1, &offline_keys_bytes[i][0], offline_keys_bytes[i].size());
|
|
int ret2 = secp256k1_ec_pubkey_parse(secp256k1_ctx_pak, &pubkey2, &online_keys_bytes[i][0], online_keys_bytes[i].size());
|
|
|
|
if (ret1 != 1 || ret2 != 1) {
|
|
return false;
|
|
}
|
|
offline_keys.push_back(pubkey1);
|
|
online_keys.push_back(pubkey2);
|
|
}
|
|
|
|
paklist = CPAKList(offline_keys, online_keys);
|
|
return true;
|
|
}
|
|
|
|
void CPAKList::ToBytes(std::vector<std::vector<unsigned char> >& offline_keys, std::vector<std::vector<unsigned char> >& online_keys) const
|
|
{
|
|
offline_keys.resize(0);
|
|
online_keys.resize(0);
|
|
|
|
for (unsigned int i = 0; i < m_offline_keys.size(); i++) {
|
|
unsigned char pubkey[33];
|
|
size_t outputlen = 33;
|
|
secp256k1_ec_pubkey_serialize(secp256k1_ctx_pak, pubkey, &outputlen, &m_offline_keys[i], SECP256K1_EC_COMPRESSED);
|
|
offline_keys.push_back(std::vector<unsigned char>(pubkey, pubkey+outputlen));
|
|
secp256k1_ec_pubkey_serialize(secp256k1_ctx_pak, pubkey, &outputlen, &m_online_keys[i], SECP256K1_EC_COMPRESSED);
|
|
online_keys.push_back(std::vector<unsigned char>(pubkey, pubkey+outputlen));
|
|
}
|
|
}
|
|
|
|
// Proof follows the OP_RETURN <genesis_block_hash> <destination_scriptpubkey>
|
|
// in multiple pushes: <full_pubkey> <proof>
|
|
bool ScriptHasValidPAKProof(const CScript& script, const uint256& genesis_hash, const CPAKList& paklist)
|
|
{
|
|
assert(script.IsPegoutScript(genesis_hash));
|
|
|
|
if (paklist.IsReject()) {
|
|
return false;
|
|
}
|
|
|
|
CScript::const_iterator pc = script.begin();
|
|
std::vector<unsigned char> data;
|
|
opcodetype opcode;
|
|
|
|
script.GetOp(pc, opcode, data);
|
|
script.GetOp(pc, opcode, data);
|
|
script.GetOp(pc, opcode, data);
|
|
|
|
CScript chain_dest(data.begin(), data.end());
|
|
|
|
// Grab pubkey hash within the extracted sub-script
|
|
std::vector<unsigned char> extracted_pubkey_hash;
|
|
|
|
// Get full pubkey
|
|
if (!script.GetOp(pc, opcode, data) || data.size() != 33 || opcode > OP_PUSHDATA4) {
|
|
return false;
|
|
}
|
|
CPubKey full_pubkey(data.begin(), data.end());
|
|
|
|
// Accept any standard single-key type
|
|
if (chain_dest.IsPayToPubkeyHash()) {
|
|
extracted_pubkey_hash = std::vector<unsigned char>(chain_dest.begin()+3, chain_dest.begin()+23);
|
|
if (full_pubkey.GetID() != uint160(extracted_pubkey_hash)) {
|
|
return false;
|
|
}
|
|
} else if (chain_dest.IsPayToWitnessPubkeyHash()) {
|
|
extracted_pubkey_hash = std::vector<unsigned char>(chain_dest.begin()+2, chain_dest.begin()+22);
|
|
if (full_pubkey.GetID() != uint160(extracted_pubkey_hash)) {
|
|
return false;
|
|
}
|
|
} else if (chain_dest.IsPayToScriptHash()) {
|
|
// Take full_pubkey, and hash it to match against chain_dest
|
|
CScript p2wpkh(CScript() << OP_0 << ToByteVector(full_pubkey.GetID()));
|
|
unsigned char h160[20];
|
|
CHash160().Write(p2wpkh.data(), p2wpkh.size()).Finalize(h160);
|
|
if (memcmp(h160, chain_dest.data()+2, sizeof(h160))) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
// Parse pubkey
|
|
secp256k1_pubkey pubkey;
|
|
if (secp256k1_ec_pubkey_parse(secp256k1_ctx_pak, &pubkey, &data[0], data.size()) != 1) {
|
|
return false;
|
|
}
|
|
|
|
if (!script.GetOp(pc, opcode, data) || opcode > OP_PUSHDATA4 || data.size() == 0) {
|
|
return false;
|
|
}
|
|
|
|
// Parse whitelist proof
|
|
secp256k1_whitelist_signature sig;
|
|
if (secp256k1_whitelist_signature_parse(secp256k1_ctx_pak, &sig, &data[0], data.size()) != 1)
|
|
return false;
|
|
|
|
if (secp256k1_whitelist_signature_n_keys(&sig) != paklist.size()) {
|
|
return false;
|
|
}
|
|
|
|
if (secp256k1_whitelist_verify(secp256k1_ctx_pak, &sig, &paklist.OnlineKeys()[0], &paklist.OfflineKeys()[0], paklist.size(), &pubkey) != 1) {
|
|
return false;
|
|
}
|
|
|
|
//No more pushes allowed
|
|
if (script.GetOp(pc, opcode, data)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CPAKList CreatePAKListFromExtensionSpace(const std::vector<std::vector<unsigned char>>& extension_space)
|
|
{
|
|
CPAKList paklist;
|
|
std::vector<std::vector<unsigned char>> offline_keys;
|
|
std::vector<std::vector<unsigned char>> online_keys;
|
|
for (const auto& entry : extension_space) {
|
|
if (entry.size() != 66) {
|
|
return CPAKList();
|
|
}
|
|
// Dumbly tries to extract two pubkeys, relies on FromBytes to parse/validate
|
|
offline_keys.emplace_back(entry.begin(), entry.begin()+33);
|
|
online_keys.emplace_back(entry.begin()+33, entry.end());
|
|
}
|
|
if (!CPAKList::FromBytes(paklist, offline_keys, online_keys)) {
|
|
return CPAKList();
|
|
}
|
|
return paklist;
|
|
}
|
|
|
|
CPAKList GetActivePAKList(const CBlockIndex* pblockindex, const Consensus::Params& params)
|
|
{
|
|
assert(pblockindex);
|
|
|
|
return CreatePAKListFromExtensionSpace(ComputeNextBlockFullCurrentParameters(pblockindex, params).m_extension_space);
|
|
}
|
|
|
|
bool IsPAKValidOutput(const CTxOut& txout, const CPAKList& paklist)
|
|
{
|
|
const CChainParams& params = Params();
|
|
if (txout.scriptPubKey.IsPegoutScript(params.ParentGenesisBlockHash()) &&
|
|
txout.nAsset.IsExplicit() && txout.nAsset.GetAsset() == params.GetConsensus().pegged_asset &&
|
|
(!ScriptHasValidPAKProof(txout.scriptPubKey, params.ParentGenesisBlockHash(), paklist))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IsPAKValidTx(const CTransaction& tx, const CPAKList& paklist)
|
|
{
|
|
for (const auto& txout : tx.vout) {
|
|
if (!IsPAKValidOutput(txout, paklist)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|