Skip to content

Commit 3e0d38d

Browse files
committed
Key tree signatures
Adds: * Generic threshold tree code, and ability to iterate over their allowed combinations and count them. * Keytree layer that adds serializable key tree objects (which are threshold trees over public key leaves), and ability to compute their Merkle root and branches in logspace. * Human-readable format/parse code for key trees. * Signing/ismine code that can cope with key tree outputs. * Unit tests for all the above. * Wallet/walletdb code to store key trees. * RPCs addtreesigaddress and createkeysig.
1 parent 8f68447 commit 3e0d38d

31 files changed

+1993
-3
lines changed

src/Makefile.am

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ BITCOIN_CORE_H = \
9999
init.h \
100100
key.h \
101101
keystore.h \
102+
keytree.h \
102103
leveldbwrapper.h \
103104
limitedmap.h \
104105
main.h \
@@ -126,6 +127,7 @@ BITCOIN_CORE_H = \
126127
streams.h \
127128
sync.h \
128129
threadsafety.h \
130+
thresholdtree.h \
129131
timedata.h \
130132
tinyformat.h \
131133
txdb.h \
@@ -246,6 +248,7 @@ libbitcoin_common_a_SOURCES = \
246248
hash.cpp \
247249
key.cpp \
248250
keystore.cpp \
251+
keytree.cpp \
249252
merkleblock.cpp \
250253
netbase.cpp \
251254
pow.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ BITCOIN_TESTS =\
4141
test/getarg_tests.cpp \
4242
test/hash_tests.cpp \
4343
test/key_tests.cpp \
44+
test/keytree_tests.cpp \
4445
test/main_tests.cpp \
4546
test/mempool_tests.cpp \
4647
test/miner_tests.cpp \

src/core_io.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class CScript;
1313
class CTransaction;
1414
class uint256;
1515
class UniValue;
16+
class KeyTree;
1617

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

2527
// core_write.cpp
2628
extern std::string FormatScript(const CScript& script);
29+
extern std::string FormatKeyTree(const KeyTree& keytree);
2730
extern std::string EncodeHexTx(const CTransaction& tx);
2831
extern std::string EncodeHexBlock(const CBlock& block);
2932
extern void ScriptPubKeyToUniv(const CScript& scriptPubKey,

src/core_read.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "core_io.h"
66

7+
#include "keytree.h"
78
#include "primitives/block.h"
89
#include "primitives/transaction.h"
910
#include "script/script.h"
@@ -155,3 +156,86 @@ vector<unsigned char> ParseHexUV(const UniValue& v, const string& strName)
155156
throw runtime_error(strName+" must be hexadecimal string (not '"+strHex+"')");
156157
return ParseHex(strHex);
157158
}
159+
160+
static bool ParseKeyTreeNode(const std::string &s, size_t &pos, KeyTreeNode& tree);
161+
static bool ParseKeyTreeCall(const std::string &s, size_t &pos, unsigned long* num, std::vector<KeyTreeNode>& children)
162+
{
163+
if (s.size() == pos) return false;
164+
if (s[pos] != '(') return false;
165+
pos++;
166+
int count = 0;
167+
if (num) {
168+
const char *ptr = &s[pos];
169+
char *eptr = NULL;
170+
*num = strtoul(ptr, &eptr, 10);
171+
if (eptr == ptr) return false;
172+
pos += eptr - ptr;
173+
count++;
174+
}
175+
while (true) {
176+
if (count) {
177+
if (pos == s.size()) return false;
178+
if (s[pos] == /*(*/')') {
179+
pos++;
180+
return true;
181+
}
182+
if (s[pos] != ',') return false;
183+
pos++;
184+
}
185+
children.push_back(KeyTreeNode());
186+
if (!ParseKeyTreeNode(s, pos, children.back())) return false;
187+
count++;
188+
}
189+
}
190+
191+
static bool ParseKeyTreeNode(const std::string &s, size_t &pos, KeyTreeNode& tree)
192+
{
193+
while (pos < s.size() && isspace(s[pos])) pos++;
194+
if (s.size() >= pos + 66 && IsHex(s.substr(pos, 66))) {
195+
std::vector<unsigned char> data = ParseHex(s.substr(pos, 66));
196+
tree.leaf.Set(data.begin(), data.end());
197+
pos += 66;
198+
return tree.leaf.IsFullyValid();
199+
}
200+
if (s.size() >= pos + 2 && s.substr(pos, 2) == "OR") {
201+
pos += 2;
202+
if (!ParseKeyTreeCall(s, pos, NULL, tree.children)) return false;
203+
if (tree.children.size() < 2) return false;
204+
tree.threshold = 1;
205+
return true;
206+
}
207+
if (s.size() >= pos + 3 && s.substr(pos, 3) == "AND") {
208+
pos += 3;
209+
if (!ParseKeyTreeCall(s, pos, NULL, tree.children)) return false;
210+
if (tree.children.size() < 2) return false;
211+
tree.threshold = tree.children.size();
212+
return true;
213+
}
214+
if (s.size() >= pos + 9 && s.substr(pos, 9) == "THRESHOLD") {
215+
pos += 9;
216+
unsigned long num;
217+
if (!ParseKeyTreeCall(s, pos, &num, tree.children)) return false;
218+
if (tree.children.size() < 2) return false;
219+
tree.threshold = num;
220+
if (tree.threshold <= 1) return false;
221+
if (tree.threshold >= tree.children.size()) return false;
222+
return true;
223+
}
224+
return false;
225+
}
226+
227+
bool ParseKeyTree(const std::string &s, KeyTree& tree)
228+
{
229+
size_t pos = 0;
230+
if (!ParseKeyTreeNode(s, pos, tree.root)) return false;
231+
if (pos != s.size()) return false;
232+
uint64_t count = 0;
233+
tree.hash = GetMerkleRoot(&tree.root, &count);
234+
int levels = 0;
235+
while (count > 1) {
236+
count = (count + 1) >> 1;
237+
levels++;
238+
}
239+
tree.levels = levels;
240+
return true;
241+
}

src/core_write.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "core_io.h"
66

77
#include "base58.h"
8+
#include "keytree.h"
89
#include "primitives/transaction.h"
910
#include "script/script.h"
1011
#include "script/standard.h"
@@ -143,3 +144,29 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry)
143144

144145
entry.pushKV("hex", EncodeHexTx(tx)); // the hex-encoded transaction. used the name "hex" to be consistent with the verbose output of "getrawtransaction".
145146
}
147+
148+
static std::string FormatKeyTreeNode(const KeyTreeNode& tree)
149+
{
150+
if (tree.threshold == 0) {
151+
return HexStr(tree.leaf.begin(), tree.leaf.end());
152+
}
153+
std::string ret;
154+
if (tree.threshold == 1) {
155+
ret = "OR(";
156+
} else if (tree.threshold == tree.children.size()) {
157+
ret = "AND(";
158+
} else {
159+
ret = strprintf("THRESHOLD(%i,"/*)*/, tree.threshold);
160+
}
161+
for (size_t i = 0; i < tree.children.size(); i++) {
162+
if (i) ret += ",";
163+
ret += FormatKeyTreeNode(tree.children[i]);
164+
}
165+
ret += ")";
166+
return ret;
167+
}
168+
169+
std::string FormatKeyTree(const KeyTree& tree)
170+
{
171+
return FormatKeyTreeNode(tree.root);
172+
}

src/key.cpp

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,81 @@ bool CKey::Derive(CKey& keyChild, unsigned char ccChild[32], unsigned int nChild
144144
return ret;
145145
}
146146

147+
bool CKey::PartialSigningNonce(const uint256& hash, std::vector<unsigned char>& pubnonceout) const {
148+
if (!fValid)
149+
return false;
150+
secp256k1_pubkey_t pubnonce;
151+
unsigned char secnonce[32];
152+
LockObject(secnonce);
153+
int ret = secp256k1_schnorr_generate_nonce_pair(secp256k1_context, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, NULL, &pubnonce, secnonce);
154+
UnlockObject(secnonce);
155+
if (!ret)
156+
return false;
157+
pubnonceout.resize(33 + 64);
158+
int publen = 33;
159+
secp256k1_ec_pubkey_serialize(secp256k1_context, &pubnonceout[0], &publen, &pubnonce, true);
160+
// Sign the hash + pubnonce with a full signature, to prove possession of the corresponding private key.
161+
uint256 hash2;
162+
CSHA256().Write(hash.begin(), 32).Write(&pubnonceout[0], 33).Finalize(hash2.begin());
163+
return secp256k1_schnorr_sign(secp256k1_context, hash2.begin(), &pubnonceout[33], begin(), secp256k1_nonce_function_rfc6979, NULL);
164+
}
165+
166+
static bool CombinePubNonces(const uint256& hash, const std::vector<std::vector<unsigned char> >& pubnonces, const std::vector<CPubKey>& pubkeys, secp256k1_pubkey_t& out) {
167+
bool ret = pubnonces.size() > 0;
168+
ret = ret && (pubnonces.size() == pubkeys.size());
169+
std::vector<secp256k1_pubkey_t> parsed_pubnonces;
170+
std::vector<const secp256k1_pubkey_t*> parsed_pubnonce_pointers;
171+
parsed_pubnonces.reserve(pubnonces.size());
172+
parsed_pubnonce_pointers.reserve(pubnonces.size());
173+
std::vector<CPubKey>::const_iterator pit = pubkeys.begin();
174+
for (std::vector<std::vector<unsigned char> >::const_iterator it = pubnonces.begin(); it != pubnonces.end(); ++it, ++pit) {
175+
secp256k1_pubkey_t other_pubnonce;
176+
ret = ret && (it->size() == 33 + 64);
177+
ret = ret && secp256k1_ec_pubkey_parse(secp256k1_context, &other_pubnonce, &(*it)[0], 33);
178+
// Verify the signature on the pubnonce.
179+
uint256 hash2;
180+
secp256k1_pubkey_t pubkey;
181+
CSHA256().Write(hash.begin(), 32).Write(&(*it)[0], 33).Finalize(hash2.begin());
182+
ret = ret && secp256k1_ec_pubkey_parse(secp256k1_context, &pubkey, &(*pit)[0], pit->size());
183+
ret = ret && secp256k1_schnorr_verify(secp256k1_context, hash2.begin(), &(*it)[33], &pubkey);
184+
if (ret) {
185+
parsed_pubnonces.push_back(other_pubnonce);
186+
parsed_pubnonce_pointers.push_back(&parsed_pubnonces.back());
187+
}
188+
}
189+
return (ret && secp256k1_ec_pubkey_combine(secp256k1_context, &out, parsed_pubnonces.size(), &parsed_pubnonce_pointers[0]));
190+
}
191+
192+
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 {
193+
if (!fValid)
194+
return false;
195+
secp256k1_pubkey_t pubnonce, my_pubnonce, other_pubnonces;
196+
unsigned char secnonce[32];
197+
LockObject(secnonce);
198+
int ret = my_pubnonce_in.size() == 33 + 64 && secp256k1_ec_pubkey_parse(secp256k1_context, &my_pubnonce, &my_pubnonce_in[0], 33);
199+
ret = ret && secp256k1_schnorr_generate_nonce_pair(secp256k1_context, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, NULL, &pubnonce, secnonce);
200+
ret = ret && memcmp(&pubnonce, &my_pubnonce, sizeof(pubnonce)) == 0;
201+
ret = ret && CombinePubNonces(hash, other_pubnonces_in, other_pubkeys_in, other_pubnonces);
202+
if (ret) {
203+
vchPartialSig.resize(64);
204+
ret = secp256k1_schnorr_partial_sign(secp256k1_context, hash.begin(), &vchPartialSig[0], begin(), secnonce, &other_pubnonces);
205+
}
206+
UnlockObject(secnonce);
207+
return ret;
208+
}
209+
210+
bool CombinePartialSignatures(const std::vector<std::vector<unsigned char> >& input, std::vector<unsigned char>& output) {
211+
std::vector<const unsigned char*> sig_pointers;
212+
sig_pointers.reserve(input.size());
213+
for (std::vector<std::vector<unsigned char> >::const_iterator it = input.begin(); it != input.end(); ++it) {
214+
if (it->size() != 64) return false;
215+
sig_pointers.push_back(&((*it)[0]));
216+
}
217+
output.resize(64);
218+
bool ret = !!secp256k1_schnorr_partial_combine(secp256k1_context, &output[0], sig_pointers.size(), &sig_pointers[0]);
219+
return ret;
220+
}
221+
147222
bool CExtKey::Derive(CExtKey &out, unsigned int nChild) const {
148223
out.nDepth = nDepth + 1;
149224
CKeyID id = key.GetPubKey().GetID();
@@ -205,7 +280,7 @@ bool ECC_InitSanityCheck() {
205280
void ECC_Start() {
206281
assert(secp256k1_context == NULL);
207282

208-
secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
283+
secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
209284
assert(ctx != NULL);
210285

211286
{

src/key.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,16 @@ class CKey
133133
*/
134134
bool Sign(const uint256& hash, std::vector<unsigned char>& vchSig, uint32_t test_case = 0) const;
135135

136+
/**
137+
* Create a public nonce to communicate to other parties for creating a multisignature.
138+
*/
139+
bool PartialSigningNonce(const uint256& hash, std::vector<unsigned char>& pubnonce) const;
140+
141+
/**
142+
* Create a part of a multisignature given all parties' public nonces.
143+
*/
144+
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;
145+
136146
/**
137147
* Create a compact signature (65 bytes), which allows reconstructing the used public key.
138148
* The format is one header byte, followed by two times 32 bytes for the serialized r and s values.
@@ -178,6 +188,9 @@ struct CExtKey {
178188
void SetMaster(const unsigned char* seed, unsigned int nSeedLen);
179189
};
180190

191+
/** Combine multiple partial signatures into a full one. */
192+
bool CombinePartialSignatures(const std::vector<std::vector<unsigned char> >& input, std::vector<unsigned char>& output);
193+
181194
/** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */
182195
void ECC_Start(void);
183196

src/keystore.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,28 @@ bool CBasicKeyStore::HaveWatchOnly() const
8686
LOCK(cs_KeyStore);
8787
return (!setWatchOnly.empty());
8888
}
89+
90+
bool CBasicKeyStore::AddKeyTree(const KeyTree &tree)
91+
{
92+
LOCK(cs_KeyStore);
93+
mapKeyTrees[tree.hash] = tree;
94+
return true;
95+
}
96+
97+
bool CBasicKeyStore::HaveKeyTree(const uint256& hash) const
98+
{
99+
LOCK(cs_KeyStore);
100+
return mapKeyTrees.count(hash) > 0;
101+
}
102+
103+
bool CBasicKeyStore::GetKeyTree(const uint256& hash, KeyTree& tree) const
104+
{
105+
LOCK(cs_KeyStore);
106+
KeyTreeMap::const_iterator mi = mapKeyTrees.find(hash);
107+
if (mi != mapKeyTrees.end())
108+
{
109+
tree = (*mi).second;
110+
return true;
111+
}
112+
return false;
113+
}

src/keystore.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define BITCOIN_KEYSTORE_H
88

99
#include "key.h"
10+
#include "keytree.h"
1011
#include "pubkey.h"
1112
#include "sync.h"
1213

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

44+
//! Support for pubkey trees
45+
virtual bool AddKeyTree(const KeyTree &tree) =0;
46+
virtual bool HaveKeyTree(const uint256& hash) const =0;
47+
virtual bool GetKeyTree(const uint256& hash, KeyTree& tree) const =0;
48+
4349
//! Support for Watch-only addresses
4450
virtual bool AddWatchOnly(const CScript &dest) =0;
4551
virtual bool RemoveWatchOnly(const CScript &dest) =0;
@@ -48,6 +54,7 @@ class CKeyStore
4854
};
4955

5056
typedef std::map<CKeyID, CKey> KeyMap;
57+
typedef std::map<uint256, KeyTree> KeyTreeMap;
5158
typedef std::map<CScriptID, CScript > ScriptMap;
5259
typedef std::set<CScript> WatchOnlySet;
5360

@@ -56,6 +63,7 @@ class CBasicKeyStore : public CKeyStore
5663
{
5764
protected:
5865
KeyMap mapKeys;
66+
KeyTreeMap mapKeyTrees;
5967
ScriptMap mapScripts;
6068
WatchOnlySet setWatchOnly;
6169

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

111+
virtual bool AddKeyTree(const KeyTree &tree);
112+
virtual bool HaveKeyTree(const uint256& hash) const;
113+
virtual bool GetKeyTree(const uint256& hash, KeyTree& tree) const;
114+
103115
virtual bool AddWatchOnly(const CScript &dest);
104116
virtual bool RemoveWatchOnly(const CScript &dest);
105117
virtual bool HaveWatchOnly(const CScript &dest) const;

0 commit comments

Comments
 (0)