Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key tree signatures #48

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ BITCOIN_CORE_H = \
init.h \
key.h \
keystore.h \
keytree.h \
leveldbwrapper.h \
limitedmap.h \
main.h \
Expand Down Expand Up @@ -126,6 +127,7 @@ BITCOIN_CORE_H = \
streams.h \
sync.h \
threadsafety.h \
thresholdtree.h \
timedata.h \
tinyformat.h \
txdb.h \
Expand Down Expand Up @@ -246,6 +248,7 @@ libbitcoin_common_a_SOURCES = \
hash.cpp \
key.cpp \
keystore.cpp \
keytree.cpp \
merkleblock.cpp \
netbase.cpp \
pow.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ BITCOIN_TESTS =\
test/getarg_tests.cpp \
test/hash_tests.cpp \
test/key_tests.cpp \
test/keytree_tests.cpp \
test/main_tests.cpp \
test/mempool_tests.cpp \
test/miner_tests.cpp \
Expand Down
3 changes: 3 additions & 0 deletions src/core_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class CScript;
class CTransaction;
class uint256;
class UniValue;
class KeyTree;

// core_read.cpp
extern CScript ParseScript(std::string s);
Expand All @@ -21,9 +22,11 @@ extern bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
extern uint256 ParseHashUV(const UniValue& v, const std::string& strName);
extern uint256 ParseHashStr(const std::string&, const std::string& strName);
extern std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);
extern bool ParseKeyTree(const std::string &s, KeyTree& tree);

// core_write.cpp
extern std::string FormatScript(const CScript& script);
extern std::string FormatKeyTree(const KeyTree& keytree);
extern std::string EncodeHexTx(const CTransaction& tx);
extern std::string EncodeHexBlock(const CBlock& block);
extern void ScriptPubKeyToUniv(const CScript& scriptPubKey,
Expand Down
84 changes: 84 additions & 0 deletions src/core_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "core_io.h"

#include "keytree.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "script/script.h"
Expand Down Expand Up @@ -155,3 +156,86 @@ vector<unsigned char> ParseHexUV(const UniValue& v, const string& strName)
throw runtime_error(strName+" must be hexadecimal string (not '"+strHex+"')");
return ParseHex(strHex);
}

static bool ParseKeyTreeNode(const std::string &s, size_t &pos, KeyTreeNode& tree);
static bool ParseKeyTreeCall(const std::string &s, size_t &pos, unsigned long* num, std::vector<KeyTreeNode>& children)
{
if (s.size() == pos) return false;
if (s[pos] != '(') return false;
pos++;
int count = 0;
if (num) {
const char *ptr = &s[pos];
char *eptr = NULL;
*num = strtoul(ptr, &eptr, 10);
if (eptr == ptr) return false;
pos += eptr - ptr;
count++;
}
while (true) {
if (count) {
if (pos == s.size()) return false;
if (s[pos] == /*(*/')') {
pos++;
return true;
}
if (s[pos] != ',') return false;
pos++;
}
children.push_back(KeyTreeNode());
if (!ParseKeyTreeNode(s, pos, children.back())) return false;
count++;
}
}

static bool ParseKeyTreeNode(const std::string &s, size_t &pos, KeyTreeNode& tree)
{
while (pos < s.size() && isspace(s[pos])) pos++;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to also have this line at the top of ParseKeyTreeCall and at the top of the while loop in that function. That allows to have whitespace before ( and ,.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whitespace is not very deterministic...

if (s.size() >= pos + 66 && IsHex(s.substr(pos, 66))) {
std::vector<unsigned char> data = ParseHex(s.substr(pos, 66));
tree.leaf.Set(data.begin(), data.end());
pos += 66;
return tree.leaf.IsFullyValid();
}
if (s.size() >= pos + 2 && s.substr(pos, 2) == "OR") {
pos += 2;
if (!ParseKeyTreeCall(s, pos, NULL, tree.children)) return false;
if (tree.children.size() < 2) return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using < 2 rather than < 1 forbids constructs such as AND(key) or OR(key) (which would both be synonyms for key). I take it this is deliberate?

I think we want better error reporting here, even an integer code would be fine. Bitcoin core does stuff like this replying "parse failed" for specific more-or-less arbitrary reasons and it's frustrating to the user.

Copy link
Contributor Author

@sipa sipa Aug 23, 2015 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like having the rule, -if- we can report it to the user. Otherwise I think it'll result in some "mysterious" parse failures, which in the case of script-generated tree descriptions may also be intermittent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... introducing an enum with potential parse errors etc? Meh.

tree.threshold = 1;
return true;
}
if (s.size() >= pos + 3 && s.substr(pos, 3) == "AND") {
pos += 3;
if (!ParseKeyTreeCall(s, pos, NULL, tree.children)) return false;
if (tree.children.size() < 2) return false;
tree.threshold = tree.children.size();
return true;
}
if (s.size() >= pos + 9 && s.substr(pos, 9) == "THRESHOLD") {
pos += 9;
unsigned long num;
if (!ParseKeyTreeCall(s, pos, &num, tree.children)) return false;
if (tree.children.size() < 2) return false;
tree.threshold = num;
if (tree.threshold <= 1) return false;
if (tree.threshold >= tree.children.size()) return false;
return true;
}
return false;
}

bool ParseKeyTree(const std::string &s, KeyTree& tree)
{
size_t pos = 0;
if (!ParseKeyTreeNode(s, pos, tree.root)) return false;
if (pos != s.size()) return false;
uint64_t count = 0;
tree.hash = GetMerkleRoot(&tree.root, &count);
int levels = 0;
while (count > 1) {
count = (count + 1) >> 1;
levels++;
}
tree.levels = levels;
return true;
}
27 changes: 27 additions & 0 deletions src/core_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "core_io.h"

#include "base58.h"
#include "keytree.h"
#include "primitives/transaction.h"
#include "script/script.h"
#include "script/standard.h"
Expand Down Expand Up @@ -143,3 +144,29 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry)

entry.pushKV("hex", EncodeHexTx(tx)); // the hex-encoded transaction. used the name "hex" to be consistent with the verbose output of "getrawtransaction".
}

static std::string FormatKeyTreeNode(const KeyTreeNode& tree)
{
if (tree.threshold == 0) {
return HexStr(tree.leaf.begin(), tree.leaf.end());
}
std::string ret;
if (tree.threshold == 1) {
ret = "OR(";
} else if (tree.threshold == tree.children.size()) {
ret = "AND(";
} else {
ret = strprintf("THRESHOLD(%i,"/*)*/, tree.threshold);
}
for (size_t i = 0; i < tree.children.size(); i++) {
if (i) ret += ",";
ret += FormatKeyTreeNode(tree.children[i]);
}
ret += ")";
return ret;
}

std::string FormatKeyTree(const KeyTree& tree)
{
return FormatKeyTreeNode(tree.root);
}
77 changes: 76 additions & 1 deletion src/key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,81 @@ bool CKey::Derive(CKey& keyChild, unsigned char ccChild[32], unsigned int nChild
return ret;
}

bool CKey::PartialSigningNonce(const uint256& hash, std::vector<unsigned char>& pubnonceout) const {
if (!fValid)
return false;
secp256k1_pubkey_t pubnonce;
unsigned char secnonce[32];
LockObject(secnonce);
int ret = secp256k1_schnorr_generate_nonce_pair(secp256k1_context, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, NULL, &pubnonce, secnonce);
UnlockObject(secnonce);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be zeroing out secnonce before unlocking it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnlockObject wipes the associated memory.

if (!ret)
return false;
pubnonceout.resize(33 + 64);
int publen = 33;
secp256k1_ec_pubkey_serialize(secp256k1_context, &pubnonceout[0], &publen, &pubnonce, true);
// Sign the hash + pubnonce with a full signature, to prove possession of the corresponding private key.
uint256 hash2;
CSHA256().Write(hash.begin(), 32).Write(&pubnonceout[0], 33).Finalize(hash2.begin());
return secp256k1_schnorr_sign(secp256k1_context, hash2.begin(), &pubnonceout[33], begin(), secp256k1_nonce_function_rfc6979, NULL);
}

static bool CombinePubNonces(const uint256& hash, const std::vector<std::vector<unsigned char> >& pubnonces, const std::vector<CPubKey>& pubkeys, secp256k1_pubkey_t& out) {
bool ret = pubnonces.size() > 0;
ret = ret && (pubnonces.size() == pubkeys.size());
std::vector<secp256k1_pubkey_t> parsed_pubnonces;
std::vector<const secp256k1_pubkey_t*> parsed_pubnonce_pointers;
parsed_pubnonces.reserve(pubnonces.size());
parsed_pubnonce_pointers.reserve(pubnonces.size());
std::vector<CPubKey>::const_iterator pit = pubkeys.begin();
for (std::vector<std::vector<unsigned char> >::const_iterator it = pubnonces.begin(); it != pubnonces.end(); ++it, ++pit) {
secp256k1_pubkey_t other_pubnonce;
ret = ret && (it->size() == 33 + 64);
ret = ret && secp256k1_ec_pubkey_parse(secp256k1_context, &other_pubnonce, &(*it)[0], 33);
// Verify the signature on the pubnonce.
uint256 hash2;
secp256k1_pubkey_t pubkey;
CSHA256().Write(hash.begin(), 32).Write(&(*it)[0], 33).Finalize(hash2.begin());
ret = ret && secp256k1_ec_pubkey_parse(secp256k1_context, &pubkey, &(*pit)[0], pit->size());
ret = ret && secp256k1_schnorr_verify(secp256k1_context, hash2.begin(), &(*it)[33], &pubkey);
if (ret) {
parsed_pubnonces.push_back(other_pubnonce);
parsed_pubnonce_pointers.push_back(&parsed_pubnonces.back());
}
}
return (ret && secp256k1_ec_pubkey_combine(secp256k1_context, &out, parsed_pubnonces.size(), &parsed_pubnonce_pointers[0]));
}

bool CKey::PartialSign(const uint256& hash, const std::vector<std::vector<unsigned char> >& other_pubnonces_in, const std::vector<CPubKey>& other_pubkeys_in, const std::vector<unsigned char>& my_pubnonce_in, std::vector<unsigned char>& vchPartialSig) const {
if (!fValid)
return false;
secp256k1_pubkey_t pubnonce, my_pubnonce, other_pubnonces;
unsigned char secnonce[32];
LockObject(secnonce);
int ret = my_pubnonce_in.size() == 33 + 64 && secp256k1_ec_pubkey_parse(secp256k1_context, &my_pubnonce, &my_pubnonce_in[0], 33);
ret = ret && secp256k1_schnorr_generate_nonce_pair(secp256k1_context, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, NULL, &pubnonce, secnonce);
ret = ret && memcmp(&pubnonce, &my_pubnonce, sizeof(pubnonce)) == 0;
ret = ret && CombinePubNonces(hash, other_pubnonces_in, other_pubkeys_in, other_pubnonces);
if (ret) {
vchPartialSig.resize(64);
ret = secp256k1_schnorr_partial_sign(secp256k1_context, hash.begin(), &vchPartialSig[0], begin(), secnonce, &other_pubnonces);
}
UnlockObject(secnonce);
return ret;
}

bool CombinePartialSignatures(const std::vector<std::vector<unsigned char> >& input, std::vector<unsigned char>& output) {
std::vector<const unsigned char*> sig_pointers;
sig_pointers.reserve(input.size());
for (std::vector<std::vector<unsigned char> >::const_iterator it = input.begin(); it != input.end(); ++it) {
if (it->size() != 64) return false;
sig_pointers.push_back(&((*it)[0]));
}
output.resize(64);
bool ret = !!secp256k1_schnorr_partial_combine(secp256k1_context, &output[0], sig_pointers.size(), &sig_pointers[0]);
return ret;
}

bool CExtKey::Derive(CExtKey &out, unsigned int nChild) const {
out.nDepth = nDepth + 1;
CKeyID id = key.GetPubKey().GetID();
Expand Down Expand Up @@ -205,7 +280,7 @@ bool ECC_InitSanityCheck() {
void ECC_Start() {
assert(secp256k1_context == NULL);

secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
assert(ctx != NULL);

{
Expand Down
13 changes: 13 additions & 0 deletions src/key.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ class CKey
*/
bool Sign(const uint256& hash, std::vector<unsigned char>& vchSig, uint32_t test_case = 0) const;

/**
* Create a public nonce to communicate to other parties for creating a multisignature.
*/
bool PartialSigningNonce(const uint256& hash, std::vector<unsigned char>& pubnonce) const;

/**
* Create a part of a multisignature given all parties' public nonces.
*/
bool PartialSign(const uint256& hash, const std::vector<std::vector<unsigned char> >& other_pubnonces_in, const std::vector<CPubKey>& other_pubkeys_in, const std::vector<unsigned char>& my_pubnonce_in, std::vector<unsigned char>& vchPartialSig) const;

/**
* Create a compact signature (65 bytes), which allows reconstructing the used public key.
* The format is one header byte, followed by two times 32 bytes for the serialized r and s values.
Expand Down Expand Up @@ -178,6 +188,9 @@ struct CExtKey {
void SetMaster(const unsigned char* seed, unsigned int nSeedLen);
};

/** Combine multiple partial signatures into a full one. */
bool CombinePartialSignatures(const std::vector<std::vector<unsigned char> >& input, std::vector<unsigned char>& output);

/** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */
void ECC_Start(void);

Expand Down
25 changes: 25 additions & 0 deletions src/keystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,28 @@ bool CBasicKeyStore::HaveWatchOnly() const
LOCK(cs_KeyStore);
return (!setWatchOnly.empty());
}

bool CBasicKeyStore::AddKeyTree(const KeyTree &tree)
{
LOCK(cs_KeyStore);
mapKeyTrees[tree.hash] = tree;
return true;
}

bool CBasicKeyStore::HaveKeyTree(const uint256& hash) const
{
LOCK(cs_KeyStore);
return mapKeyTrees.count(hash) > 0;
}

bool CBasicKeyStore::GetKeyTree(const uint256& hash, KeyTree& tree) const
{
LOCK(cs_KeyStore);
KeyTreeMap::const_iterator mi = mapKeyTrees.find(hash);
if (mi != mapKeyTrees.end())
{
tree = (*mi).second;
return true;
}
return false;
}
12 changes: 12 additions & 0 deletions src/keystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define BITCOIN_KEYSTORE_H

#include "key.h"
#include "keytree.h"
#include "pubkey.h"
#include "sync.h"

Expand Down Expand Up @@ -40,6 +41,11 @@ class CKeyStore
virtual bool HaveCScript(const CScriptID &hash) const =0;
virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const =0;

//! Support for pubkey trees
virtual bool AddKeyTree(const KeyTree &tree) =0;
virtual bool HaveKeyTree(const uint256& hash) const =0;
virtual bool GetKeyTree(const uint256& hash, KeyTree& tree) const =0;

//! Support for Watch-only addresses
virtual bool AddWatchOnly(const CScript &dest) =0;
virtual bool RemoveWatchOnly(const CScript &dest) =0;
Expand All @@ -48,6 +54,7 @@ class CKeyStore
};

typedef std::map<CKeyID, CKey> KeyMap;
typedef std::map<uint256, KeyTree> KeyTreeMap;
typedef std::map<CScriptID, CScript > ScriptMap;
typedef std::set<CScript> WatchOnlySet;

Expand All @@ -56,6 +63,7 @@ class CBasicKeyStore : public CKeyStore
{
protected:
KeyMap mapKeys;
KeyTreeMap mapKeyTrees;
ScriptMap mapScripts;
WatchOnlySet setWatchOnly;

Expand Down Expand Up @@ -100,6 +108,10 @@ class CBasicKeyStore : public CKeyStore
virtual bool HaveCScript(const CScriptID &hash) const;
virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const;

virtual bool AddKeyTree(const KeyTree &tree);
virtual bool HaveKeyTree(const uint256& hash) const;
virtual bool GetKeyTree(const uint256& hash, KeyTree& tree) const;

virtual bool AddWatchOnly(const CScript &dest);
virtual bool RemoveWatchOnly(const CScript &dest);
virtual bool HaveWatchOnly(const CScript &dest) const;
Expand Down
Loading