-
Notifications
You must be signed in to change notification settings - Fork 386
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
|
||
#include "core_io.h" | ||
|
||
#include "keytree.h" | ||
#include "primitives/block.h" | ||
#include "primitives/transaction.h" | ||
#include "script/script.h" | ||
|
@@ -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++; | ||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The human representation is intentionally deterministic, allowing only a
single representation for every tree (using the simplest construct
available). I guess we could theoretically allow either OR(one) or
AND(one), though.
Maybe that rule is overkill, and we can allow thresholds of 1 or n, single
arguments, whitespace, lowercase commands, ...
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be zeroing out There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
@@ -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); | ||
|
||
{ | ||
|
There was a problem hiding this comment.
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 thewhile
loop in that function. That allows to have whitespace before(
and,
.There was a problem hiding this comment.
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...