Skip to content

Commit de9ea50

Browse files
committed
add Key.sign_compact and Key.recover_pubkey
1 parent 44bec1e commit de9ea50

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

python/bindings.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,51 @@ static PyObject *b_KeyFromBitID(PyObject *cls, PyObject *args, PyObject *kwds) {
542542
return result;
543543
}
544544

545+
static PyObject *b_KeyRecoverPubKey(PyObject *cls, PyObject *args, PyObject *kwds) {
546+
b_Key *result = NULL;
547+
PyObject *message = NULL;
548+
PyObject *signature = NULL;
549+
static char *kwlist[] = { "message", "signature", NULL };
550+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OS", kwlist, &message, &signature)) {
551+
return NULL;
552+
}
553+
if (message == NULL || message == Py_None) {
554+
PyErr_SetString(PyExc_ValueError, "message must not be NULL");
555+
return NULL;
556+
}
557+
UInt256 *toSign;
558+
PyObject *msgBytes;
559+
if (PyObject_IsInstance(message, (PyObject *)&PyBytes_Type)) {
560+
msgBytes = message;
561+
} else if (PyCallable_Check(PyObject_GetAttrString(message, "digest"))) {
562+
msgBytes = PyObject_CallMethod(message, "digest", "");
563+
if (!PyObject_IsInstance(msgBytes, (PyObject *)&PyBytes_Type)) {
564+
PyErr_SetString(PyExc_TypeError, "digest() must return a bytes object");
565+
return NULL;
566+
}
567+
} else {
568+
PyErr_SetString(PyExc_TypeError, "message must be either a bytes object with 32 bytes or a hash object "
569+
"with a digest() method");
570+
return NULL;
571+
}
572+
if (PyBytes_Size(msgBytes) != 32) {
573+
PyErr_SetString(PyExc_ValueError, "must be 32 bytes of data (a UInt256)");
574+
return NULL;
575+
}
576+
toSign = (UInt256 *)PyBytes_AsString(msgBytes);
577+
BRKey *key = calloc(1, sizeof(BRKey));
578+
int keyLen = BRKeyRecoverPubKey(key, PyBytes_AsString(signature), PyBytes_Size(signature), *toSign);
579+
if (!keyLen) {
580+
return Py_BuildValue(""); // unable to recover, return None
581+
}
582+
583+
result = (b_Key *)PyObject_CallFunction(cls, "");
584+
if (result != NULL) {
585+
result->ob_fval = key;
586+
}
587+
return (PyObject *)result;
588+
}
589+
545590
static PyObject *b_KeySign(b_Key *self, PyObject *args, PyObject *kwds) {
546591
PyObject *message;
547592
static char *kwlist[] = { "message", NULL };
@@ -615,6 +660,43 @@ static PyObject *b_KeyVerify(b_Key *self, PyObject *args, PyObject *kwds) {
615660
return valid ? Py_True : Py_False;
616661
}
617662

663+
static PyObject *b_KeyCompactSign(b_Key *self, PyObject *args, PyObject *kwds) {
664+
PyObject *message;
665+
static char *kwlist[] = { "message", NULL };
666+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &message)) {
667+
return NULL;
668+
}
669+
if (message == NULL || message == Py_None) {
670+
PyErr_SetString(PyExc_ValueError, "message must not be NULL");
671+
return NULL;
672+
}
673+
UInt256 *toSign;
674+
PyObject *msgBytes;
675+
if (PyObject_IsInstance(message, (PyObject *)&PyBytes_Type)) {
676+
msgBytes = message;
677+
} else if (PyCallable_Check(PyObject_GetAttrString(message, "digest"))) {
678+
msgBytes = PyObject_CallMethod(message, "digest", "");
679+
if (!PyObject_IsInstance(msgBytes, (PyObject *)&PyBytes_Type)) {
680+
PyErr_SetString(PyExc_TypeError, "digest() must return a bytes object");
681+
return NULL;
682+
}
683+
} else {
684+
PyErr_SetString(PyExc_TypeError, "message must be either a bytes object with 32 bytes or a hash object "
685+
"with a digest() method");
686+
return NULL;
687+
}
688+
if (PyBytes_Size(msgBytes) != 32) {
689+
PyErr_SetString(PyExc_ValueError, "must be 32 bytes of data (a UInt256)");
690+
return NULL;
691+
}
692+
693+
toSign = (UInt256 *)PyBytes_AsString(msgBytes);
694+
uint8_t sig[72];
695+
size_t sigLen = BRKeyCompactSign(self->ob_fval, sig, sizeof(sig), *toSign);
696+
PyObject *ret = PyBytes_FromStringAndSize((const char *)&sig, sigLen);
697+
return ret;
698+
}
699+
618700
static PyObject *b_KeyPrivKeyIsValid(PyObject *cls, PyObject *args, PyObject *kwds) {
619701
PyObject *result = Py_False;
620702
char *pk;
@@ -773,11 +855,15 @@ static PyMethodDef b_KeyMethods[] = {
773855
/* Class Methods */
774856
{"from_bitid", (PyCFunction)b_KeyFromBitID, (METH_VARARGS | METH_KEYWORDS | METH_CLASS),
775857
"generate a bitid Key from a seed and some bitid parameters"},
858+
{"recover_pubkey", (PyCFunction)b_KeyRecoverPubKey, (METH_VARARGS | METH_KEYWORDS | METH_CLASS),
859+
"recover a public key from a compact signature"},
776860
{"privkey_is_valid", (PyCFunction)b_KeyPrivKeyIsValid, (METH_VARARGS | METH_KEYWORDS | METH_CLASS),
777861
"determine whether or not a serialized private key is valid"},
778862
/* Instance Methods */
779863
{"sign", (PyCFunction)b_KeySign, (METH_VARARGS | METH_KEYWORDS),
780864
"sign a bytes or an object with a digest() method"},
865+
{"sign_compact", (PyCFunction)b_KeyCompactSign, (METH_VARARGS | METH_KEYWORDS),
866+
"sign some bytes (or an object with the digest() method) using the compact signature format"},
781867
{"verify", (PyCFunction)b_KeyVerify, (METH_VARARGS | METH_KEYWORDS),
782868
"verify the message signature was made by this key"},
783869
{NULL}

python/tests.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ def test_sign(self):
112112
self.assertEqual(sig, sig3)
113113
self.assertEqual(sig, sig2)
114114

115+
def test_sign_compact_and_recover_pubkey(self):
116+
k = breadwallet.Key()
117+
k.secret = breadwallet.UInt256.from_hex('0000000000000000000000000000000000000000000000000000000000000001')
118+
message = "foo"
119+
h = hashlib.sha256()
120+
h.update(message.encode('utf8'))
121+
sig = k.sign_compact(h)
122+
k2 = breadwallet.Key.recover_pubkey(h, sig)
123+
self.assertEqual(k.pubkey, k2.pubkey)
124+
115125
def test_verify(self):
116126
k = breadwallet.Key()
117127
k.secret = breadwallet.UInt256.from_hex('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140')

0 commit comments

Comments
 (0)