From e0928f18bfea5845ffc5bc97b212f9f8db5235f0 Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Wed, 27 Jul 2016 18:28:00 -0700 Subject: [PATCH 01/16] first stab at python bindings. far from complete --- python/.gitignore | 5 + python/bindings.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++ python/setup.py | 40 ++++ python/tests.py | 41 +++++ 4 files changed, 547 insertions(+) create mode 100644 python/.gitignore create mode 100644 python/bindings.c create mode 100644 python/setup.py create mode 100644 python/tests.py diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 000000000..2f2c81f88 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,5 @@ +*.egg-info +*.eggs +build/ +__pycache__ +*.pyc diff --git a/python/bindings.c b/python/bindings.c new file mode 100644 index 000000000..4acebe4bb --- /dev/null +++ b/python/bindings.c @@ -0,0 +1,461 @@ +#include +#include "structmember.h" +#include "BRInt.h" +#include "BRBIP32Sequence.h" +#include "BRBIP39Mnemonic.h" +#include "BRKey.h" +#include "BRWallet.h" + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Ints + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typedef struct { + PyObject_HEAD + UInt512 ob_fval; +} b_UInt512; + +static PyObject * +b_UInt512New(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + b_UInt512 *self = (b_UInt512 *)type->tp_alloc(type, 0); + if (self != NULL) { + self->ob_fval = UINT512_ZERO; + } + return (PyObject *)self; +} + +static PyTypeObject b_UInt512Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "breadwallet.UInt12", /* tp_name */ + sizeof(b_UInt512), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "UInt512 Object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + b_UInt512New, /* tp_new */ +}; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Address + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + typedef struct { + PyObject_HEAD + BRAddress ob_fval; + } b_Address; + + static PyObject *b_AddressNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { + b_Address *self = (b_Address *)type->tp_alloc(type, 0); + if (self != NULL) { + } + return (PyObject *)self; + } + + static PyObject *b_AddresToStr(PyObject *self) { + return PyUnicode_FromString(((b_Address *)self)->ob_fval.s); + } + + static PyMethodDef b_AddressMethods[] = { + {NULL} + }; + + static PyTypeObject b_AddressType = { + PyVarObject_HEAD_INIT(NULL, 0) + "breadwallet.Address", /* tp_name */ + sizeof(b_Address), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + (reprfunc)b_AddresToStr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)b_AddresToStr, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Address Object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + b_AddressMethods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + b_AddressNew, /* tp_new */ + }; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Keys + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typedef struct { + PyObject_HEAD + BRMasterPubKey ob_fval; +} b_MasterPubKey; + +static PyObject *b_MasterPubKeyNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { + b_MasterPubKey *self = (b_MasterPubKey *)type->tp_alloc(type, 0); + if (self != NULL) { + self->ob_fval = BR_MASTER_PUBKEY_NONE; + } + return (PyObject *)self; +} + +static PyObject *b_MasterPubKeyFromPhrase(PyObject *cls, PyObject *args, PyObject *kwds) { + PyObject *result = NULL; + char *phrase = ""; + // parse args + static char *kwlist[] = { "phrase", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &phrase)) { + return NULL; + } + // derive + UInt512 seed = UINT512_ZERO; + BRBIP39DeriveKey(seed.u8, phrase, NULL); + // allocate + result = PyObject_CallFunction(cls, ""); + // set value + if (result != NULL) { + ((b_MasterPubKey *)result)->ob_fval = BRBIP32MasterPubKey(&seed, sizeof(seed)); + } + return result; +} + +static PyMethodDef b_MasterPubKeyMethods[] = { + /* Class Methods */ + {"from_phrase", (PyCFunction)b_MasterPubKeyFromPhrase, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "generate a MasterPubKey from a phrase"}, + {NULL} +}; + +static PyTypeObject b_MasterPubKeyType = { + PyVarObject_HEAD_INIT(NULL, 0) + "breadwallet.MasterPubKey", /* tp_name */ + sizeof(b_MasterPubKey), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "MasterPubKey Object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + b_MasterPubKeyMethods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + b_MasterPubKeyNew, /* tp_new */ +}; + +static PyObject *b_DeriveKey(PyObject *self, PyObject *args) { + const char *phrase; + if (!PyArg_ParseTuple(args, "s", &phrase)) return NULL; + UInt512 seed = UINT512_ZERO; + BRBIP39DeriveKey(seed.u8, phrase, NULL); + b_UInt512 *obj = PyObject_New(b_UInt512, &b_UInt512Type); + obj->ob_fval = seed; + Py_INCREF(obj); + return (PyObject *)obj; +} + +typedef struct { + PyObject_HEAD + BRKey ob_fval; +} b_Key; + +static PyObject *b_KeyNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { + b_Key *self = (b_Key *)type->tp_alloc(type, 0); + if (self != NULL) { + } + return (PyObject *)self; +} + +static PyObject *b_KeyFromBitID(PyObject *cls, PyObject *args, PyObject *kwds) { + PyObject *result = NULL; + PyObject *seedObj = NULL; + int index = 0; + char *endpoint = NULL; + // parse args + static char *kwlist[] = { "seed", "index", "endpoint", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "Ois", kwlist, &seedObj, &index, &endpoint)) { + return NULL; + } + if (!PyObject_IsInstance(seedObj, (PyObject *)&b_UInt512Type)) { + // TODO: set correct error + return NULL; + } + b_UInt512 *seed = (b_UInt512 *)seedObj; + + // create + BRKey key; + BRBIP32BitIDKey(&key, seed->ob_fval.u8, sizeof(seed->ob_fval.u8), index, endpoint); + + // allocate + result = PyObject_CallFunction(cls, ""); + // set value + if (result != NULL) { + ((b_Key *)result)->ob_fval = key; + } + return result; +} + +static PyObject *b_KeyAddress(PyObject *self, PyObject *args) { + BRAddress address; + b_Key *bkey = (b_Key *)self; + BRKeyAddress(&bkey->ob_fval, address.s, sizeof(address)); + b_Address *ret = (b_Address *)PyObject_New(b_Address, &b_AddressType); + ret->ob_fval = address; + return (PyObject *)ret; +} + +static PyMethodDef b_KeyMethods[] = { + /* Class Methods */ + {"from_bitid", (PyCFunction)b_KeyFromBitID, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "generate a bitid Key from a seed and some bitid parameters"}, + /* Instance Methods */ + {"address", (PyCFunction)b_KeyAddress, METH_NOARGS, + "get the address from the key"}, + {NULL} +}; + +static PyTypeObject b_KeyType = { + PyVarObject_HEAD_INIT(NULL, 0) + "breadwallet.Key", /* tp_name */ + sizeof(b_Key), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Key Object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + b_KeyMethods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + b_KeyNew, /* tp_new */ +}; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Wallet + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typedef struct { + PyObject_HEAD + b_MasterPubKey *mpk; + BRWallet *wallet; +} b_Wallet; + +static PyObject *b_WalletNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { + b_Wallet *self = (b_Wallet *)type->tp_alloc(type, 0); + if (self != NULL) { + self->mpk = NULL; + self->wallet = NULL; + } + return (PyObject *)self; +} + +static void b_WalletDealloc(b_Wallet *self) { + Py_XDECREF(self->mpk); + self->mpk = NULL; + BRWalletFree(self->wallet); + self->wallet = NULL; + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int b_WalletInit(b_Wallet *self, PyObject *args, PyObject *kwds) { + PyObject *mpk = NULL, *txList = NULL; + static char *kwlist[] = {"master_pubkey", "tx_list", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", kwlist, &mpk, &txList)) return -1; + + if (!mpk) { + // TODO: set correct error + return -1; + } + + if (!PyObject_IsInstance(mpk, (PyObject *)&b_MasterPubKeyType)) { + // TODO: set correct error + return -1; + } + + // build instance data + self->mpk = (b_MasterPubKey *)mpk; + Py_INCREF(self->mpk); + self->wallet = BRWalletNew(NULL, 0, self->mpk->ob_fval); + + // TODO: parse transaction list + + return 0; +} + +static PyTypeObject b_WalletType = { + PyVarObject_HEAD_INIT(NULL, 0) + "breadwallet.Wallet", /* tp_name */ + sizeof(b_Wallet), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)b_WalletDealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Wallet Object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)b_WalletInit, /* tp_init */ + 0, /* tp_alloc */ + b_WalletNew, /* tp_new */ +}; + +static PyMethodDef bmodulemethods[] = { + {"derive_key", b_DeriveKey, METH_VARARGS, "Derive a key from a seed phrase"}, + {NULL}, +}; + +static PyModuleDef bmodule = { + PyModuleDef_HEAD_INIT, + "breadwallet", + "A simple, lightweight, performant SPV wallet", + -1, + bmodulemethods, NULL, NULL, NULL, NULL +}; + +PyMODINIT_FUNC PyInit_breadwallet(void) { + PyObject* m; + + if (PyType_Ready(&b_UInt512Type) < 0) return NULL; + if (PyType_Ready(&b_MasterPubKeyType) < 0) return NULL; + if (PyType_Ready(&b_KeyType) < 0) return NULL; + if (PyType_Ready(&b_AddressType) < 0) return NULL; + if (PyType_Ready(&b_WalletType) < 0) return NULL; + + m = PyModule_Create(&bmodule); + if (m == NULL) return NULL; + + Py_INCREF(&b_UInt512Type); + Py_INCREF(&b_MasterPubKeyType); + Py_INCREF(&b_KeyType); + Py_INCREF(&b_AddressType); + Py_INCREF(&b_WalletType); + PyModule_AddObject(m, "UInt512", (PyObject *)&b_UInt512Type); + PyModule_AddObject(m, "MasterPubKey", (PyObject *)&b_MasterPubKeyType); + PyModule_AddObject(m, "Key", (PyObject *)&b_KeyType); + PyModule_AddObject(m, "Address", (PyObject *)&b_KeyType); + PyModule_AddObject(m, "Wallet", (PyObject *)&b_WalletType); + return m; +} diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 000000000..e2814bc73 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,40 @@ +import os +from setuptools import setup, Extension + +def here(*args): + return os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), *args)) + +def fromroot(*args): + return here(os.pardir, *args) + +breadwallet = Extension( + 'breadwallet', + sources=[ + fromroot('BRAddress.c'), + fromroot('BRBase58.c'), + fromroot('BRBIP32Sequence.c'), + fromroot('BRBIP38Key.c'), + fromroot('BRBIP39Mnemonic.c'), + fromroot('BRBloomFilter.c'), + fromroot('BRCrypto.c'), + fromroot('BRKey.c'), + fromroot('BRMerkleBlock.c'), + fromroot('BRPaymentProtocol.c'), + fromroot('BRPeer.c'), + fromroot('BRPeerManager.c'), + fromroot('BRSet.c'), + fromroot('BRTransaction.c'), + fromroot('BRWallet.c'), + 'bindings.c' + ], + include_dirs=[fromroot(), fromroot('secp256k1')] +) + +setup( + name='breadwallet-core', + version='0.1', + description='A simple, easy to use SPV wallet.', + ext_modules=[breadwallet], + test_suite='nose.collector', + tests_require=['nose'] +) diff --git a/python/tests.py b/python/tests.py new file mode 100644 index 000000000..5a4f9a5c1 --- /dev/null +++ b/python/tests.py @@ -0,0 +1,41 @@ +import unittest +import breadwallet + + +class IntTests(unittest.TestCase): + def test_allocation(self): + u512 = breadwallet.UInt512() + self.assertNotEqual(u512, None) + + +class KeyTests(unittest.TestCase): + def test_allocation(self): + phrase = "axis husband project any sea patch drip tip spirit tide bring belt" + mpk = breadwallet.MasterPubKey.from_phrase(phrase) + self.assertNotEqual(mpk, None) + + def test_derive_key_allocate(self): + phrase = "axis husband project any sea patch drip tip spirit tide bring belt" + seed = breadwallet.derive_key(phrase) + self.assertNotEqual(seed, None) + + def test_bitid_allocate(self): + phrase = "inhale praise target steak garlic cricket paper better evil almost sadness crawl city banner amused fringe fox insect roast aunt prefer hollow basic ladder" + seed = breadwallet.derive_key(phrase) + key = breadwallet.Key.from_bitid(seed, 0, "http://bitid.bitcoin.blue/callback") + self.assertNotEqual(key, None) + + def test_bitid_address(self): + phrase = "inhale praise target steak garlic cricket paper better evil almost sadness crawl city banner amused fringe fox insect roast aunt prefer hollow basic ladder" + seed = breadwallet.derive_key(phrase) + key = breadwallet.Key.from_bitid(seed, 0, "http://bitid.bitcoin.blue/callback") + self.assertNotEqual(key.address(), None) + self.assertEqual(str(key.address()), "1J34vj4wowwPYafbeibZGht3zy3qERoUM1") + + +class WalletTests(unittest.TestCase): + def test_allocation(self): + phrase = "axis husband project any sea patch drip tip spirit tide bring belt" + mpk = breadwallet.MasterPubKey.from_phrase(phrase) + wallet = breadwallet.Wallet(mpk) + self.assertNotEqual(wallet, None) From 8fbbd7a603cbd9b4bc55947bca38b828fb5ff75a Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Wed, 27 Jul 2016 19:54:59 -0700 Subject: [PATCH 02/16] add transaction --- python/bindings.c | 346 ++++++++++++++++++++++++++++------------------ python/tests.py | 6 + 2 files changed, 219 insertions(+), 133 deletions(-) diff --git a/python/bindings.c b/python/bindings.c index 4acebe4bb..f5a96d26c 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -4,6 +4,7 @@ #include "BRBIP32Sequence.h" #include "BRBIP39Mnemonic.h" #include "BRKey.h" +#include "BRTransaction.h" #include "BRWallet.h" /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @@ -15,9 +16,7 @@ typedef struct { UInt512 ob_fval; } b_UInt512; -static PyObject * -b_UInt512New(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ +static PyObject *b_UInt512New(PyTypeObject *type, PyObject *args, PyObject *kwds) { b_UInt512 *self = (b_UInt512 *)type->tp_alloc(type, 0); if (self != NULL) { self->ob_fval = UINT512_ZERO; @@ -70,66 +69,144 @@ static PyTypeObject b_UInt512Type = { * Address * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - typedef struct { - PyObject_HEAD - BRAddress ob_fval; - } b_Address; +typedef struct { + PyObject_HEAD + BRAddress ob_fval; +} b_Address; - static PyObject *b_AddressNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { - b_Address *self = (b_Address *)type->tp_alloc(type, 0); - if (self != NULL) { - } - return (PyObject *)self; - } +static PyObject *b_AddressNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { + b_Address *self = (b_Address *)type->tp_alloc(type, 0); + if (self != NULL) { + } + return (PyObject *)self; +} - static PyObject *b_AddresToStr(PyObject *self) { +static PyObject *b_AddresToStr(PyObject *self) { return PyUnicode_FromString(((b_Address *)self)->ob_fval.s); - } - - static PyMethodDef b_AddressMethods[] = { - {NULL} - }; - - static PyTypeObject b_AddressType = { - PyVarObject_HEAD_INIT(NULL, 0) - "breadwallet.Address", /* tp_name */ - sizeof(b_Address), /* tp_basicsize */ - 0, /* tp_itemsize */ - 0, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_as_async */ - (reprfunc)b_AddresToStr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - (reprfunc)b_AddresToStr, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - "Address Object", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - b_AddressMethods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - b_AddressNew, /* tp_new */ - }; +} + +static PyMethodDef b_AddressMethods[] = { + {NULL} +}; + +static PyTypeObject b_AddressType = { + PyVarObject_HEAD_INIT(NULL, 0) + "breadwallet.Address", /* tp_name */ + sizeof(b_Address), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + (reprfunc)b_AddresToStr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)b_AddresToStr, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Address Object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + b_AddressMethods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + b_AddressNew, /* tp_new */ +}; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Transaction + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typedef struct { + PyObject_HEAD + BRTransaction *ob_fval; +} b_Transaction; + +static void b_TransactionDealloc(b_Transaction *self) { + BRTransactionFree(self->ob_fval); + self->ob_fval = NULL; + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject *b_TransactionNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { + b_Transaction *self = (b_Transaction *)type->tp_alloc(type, 0); + if (self != NULL) { + self->ob_fval = NULL; + } + return (PyObject *)self; +} + +static PyObject *b_TransactionInit(PyObject *self, PyObject *args, PyObject *kwds) { + b_Transaction *obj = (b_Transaction *)self; + obj->ob_fval = BRTransactionNew(); + return 0; +} + +static PyObject *b_TransactionToStr(PyObject *self) { + return PyUnicode_FromString(u256_hex_encode(((b_Transaction *)self)->ob_fval->txHash)); +} + +static PyMethodDef b_TransactionMethods[] = { + {NULL} +}; + +static PyTypeObject b_TransactionType = { + PyVarObject_HEAD_INIT(NULL, 0) + "breadwallet.Transaction", /* tp_name */ + sizeof(b_Transaction), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)b_TransactionDealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + (reprfunc)b_TransactionToStr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)b_TransactionToStr, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Transaction Object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + b_TransactionMethods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)b_TransactionInit, /* tp_init */ + 0, /* tp_alloc */ + b_TransactionNew, /* tp_new */ +}; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Keys @@ -149,30 +226,30 @@ static PyObject *b_MasterPubKeyNew(PyTypeObject *type, PyObject *args, PyObject } static PyObject *b_MasterPubKeyFromPhrase(PyObject *cls, PyObject *args, PyObject *kwds) { - PyObject *result = NULL; - char *phrase = ""; - // parse args - static char *kwlist[] = { "phrase", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &phrase)) { - return NULL; - } - // derive - UInt512 seed = UINT512_ZERO; - BRBIP39DeriveKey(seed.u8, phrase, NULL); - // allocate - result = PyObject_CallFunction(cls, ""); - // set value - if (result != NULL) { - ((b_MasterPubKey *)result)->ob_fval = BRBIP32MasterPubKey(&seed, sizeof(seed)); - } - return result; + PyObject *result = NULL; + char *phrase = ""; + // parse args + static char *kwlist[] = { "phrase", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &phrase)) { + return NULL; + } + // derive + UInt512 seed = UINT512_ZERO; + BRBIP39DeriveKey(seed.u8, phrase, NULL); + // allocate + result = PyObject_CallFunction(cls, ""); + // set value + if (result != NULL) { + ((b_MasterPubKey *)result)->ob_fval = BRBIP32MasterPubKey(&seed, sizeof(seed)); + } + return result; } static PyMethodDef b_MasterPubKeyMethods[] = { - /* Class Methods */ - {"from_phrase", (PyCFunction)b_MasterPubKeyFromPhrase, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), - "generate a MasterPubKey from a phrase"}, - {NULL} + /* Class Methods */ + {"from_phrase", (PyCFunction)b_MasterPubKeyFromPhrase, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "generate a MasterPubKey from a phrase"}, + {NULL} }; static PyTypeObject b_MasterPubKeyType = { @@ -217,14 +294,14 @@ static PyTypeObject b_MasterPubKeyType = { }; static PyObject *b_DeriveKey(PyObject *self, PyObject *args) { - const char *phrase; - if (!PyArg_ParseTuple(args, "s", &phrase)) return NULL; - UInt512 seed = UINT512_ZERO; - BRBIP39DeriveKey(seed.u8, phrase, NULL); - b_UInt512 *obj = PyObject_New(b_UInt512, &b_UInt512Type); - obj->ob_fval = seed; - Py_INCREF(obj); - return (PyObject *)obj; + const char *phrase; + if (!PyArg_ParseTuple(args, "s", &phrase)) return NULL; + UInt512 seed = UINT512_ZERO; + BRBIP39DeriveKey(seed.u8, phrase, NULL); + b_UInt512 *obj = PyObject_New(b_UInt512, &b_UInt512Type); + obj->ob_fval = seed; + Py_INCREF(obj); + return (PyObject *)obj; } typedef struct { @@ -240,51 +317,51 @@ static PyObject *b_KeyNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { } static PyObject *b_KeyFromBitID(PyObject *cls, PyObject *args, PyObject *kwds) { - PyObject *result = NULL; - PyObject *seedObj = NULL; - int index = 0; - char *endpoint = NULL; - // parse args - static char *kwlist[] = { "seed", "index", "endpoint", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "Ois", kwlist, &seedObj, &index, &endpoint)) { - return NULL; - } - if (!PyObject_IsInstance(seedObj, (PyObject *)&b_UInt512Type)) { - // TODO: set correct error - return NULL; - } - b_UInt512 *seed = (b_UInt512 *)seedObj; - - // create - BRKey key; - BRBIP32BitIDKey(&key, seed->ob_fval.u8, sizeof(seed->ob_fval.u8), index, endpoint); - - // allocate - result = PyObject_CallFunction(cls, ""); - // set value - if (result != NULL) { - ((b_Key *)result)->ob_fval = key; - } - return result; + PyObject *result = NULL; + PyObject *seedObj = NULL; + int index = 0; + char *endpoint = NULL; + // parse args + static char *kwlist[] = { "seed", "index", "endpoint", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "Ois", kwlist, &seedObj, &index, &endpoint)) { + return NULL; + } + if (!PyObject_IsInstance(seedObj, (PyObject *)&b_UInt512Type)) { + // TODO: set correct error + return NULL; + } + b_UInt512 *seed = (b_UInt512 *)seedObj; + + // create + BRKey key; + BRBIP32BitIDKey(&key, seed->ob_fval.u8, sizeof(seed->ob_fval.u8), index, endpoint); + + // allocate + result = PyObject_CallFunction(cls, ""); + // set value + if (result != NULL) { + ((b_Key *)result)->ob_fval = key; + } + return result; } static PyObject *b_KeyAddress(PyObject *self, PyObject *args) { - BRAddress address; - b_Key *bkey = (b_Key *)self; - BRKeyAddress(&bkey->ob_fval, address.s, sizeof(address)); - b_Address *ret = (b_Address *)PyObject_New(b_Address, &b_AddressType); - ret->ob_fval = address; - return (PyObject *)ret; + BRAddress address; + b_Key *bkey = (b_Key *)self; + BRKeyAddress(&bkey->ob_fval, address.s, sizeof(address)); + b_Address *ret = (b_Address *)PyObject_New(b_Address, &b_AddressType); + ret->ob_fval = address; + return (PyObject *)ret; } static PyMethodDef b_KeyMethods[] = { - /* Class Methods */ - {"from_bitid", (PyCFunction)b_KeyFromBitID, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), - "generate a bitid Key from a seed and some bitid parameters"}, - /* Instance Methods */ - {"address", (PyCFunction)b_KeyAddress, METH_NOARGS, - "get the address from the key"}, - {NULL} + /* Class Methods */ + {"from_bitid", (PyCFunction)b_KeyFromBitID, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "generate a bitid Key from a seed and some bitid parameters"}, + /* Instance Methods */ + {"address", (PyCFunction)b_KeyAddress, METH_NOARGS, + "get the address from the key"}, + {NULL} }; static PyTypeObject b_KeyType = { @@ -335,14 +412,14 @@ static PyTypeObject b_KeyType = { typedef struct { PyObject_HEAD b_MasterPubKey *mpk; - BRWallet *wallet; + BRWallet *ob_fval; } b_Wallet; static PyObject *b_WalletNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { b_Wallet *self = (b_Wallet *)type->tp_alloc(type, 0); if (self != NULL) { self->mpk = NULL; - self->wallet = NULL; + self->ob_fval = NULL; } return (PyObject *)self; } @@ -350,8 +427,8 @@ static PyObject *b_WalletNew(PyTypeObject *type, PyObject *args, PyObject *kwds) static void b_WalletDealloc(b_Wallet *self) { Py_XDECREF(self->mpk); self->mpk = NULL; - BRWalletFree(self->wallet); - self->wallet = NULL; + BRWalletFree(self->ob_fval); + self->ob_fval = NULL; Py_TYPE(self)->tp_free((PyObject*)self); } @@ -374,7 +451,7 @@ static int b_WalletInit(b_Wallet *self, PyObject *args, PyObject *kwds) { // build instance data self->mpk = (b_MasterPubKey *)mpk; Py_INCREF(self->mpk); - self->wallet = BRWalletNew(NULL, 0, self->mpk->ob_fval); + self->ob_fval = BRWalletNew(NULL, 0, self->mpk->ob_fval); // TODO: parse transaction list @@ -442,6 +519,7 @@ PyMODINIT_FUNC PyInit_breadwallet(void) { if (PyType_Ready(&b_MasterPubKeyType) < 0) return NULL; if (PyType_Ready(&b_KeyType) < 0) return NULL; if (PyType_Ready(&b_AddressType) < 0) return NULL; + if (PyType_Ready(&b_TransactionType) < 0) return NULL; if (PyType_Ready(&b_WalletType) < 0) return NULL; m = PyModule_Create(&bmodule); @@ -451,11 +529,13 @@ PyMODINIT_FUNC PyInit_breadwallet(void) { Py_INCREF(&b_MasterPubKeyType); Py_INCREF(&b_KeyType); Py_INCREF(&b_AddressType); + Py_INCREF(&b_TransactionType); Py_INCREF(&b_WalletType); PyModule_AddObject(m, "UInt512", (PyObject *)&b_UInt512Type); PyModule_AddObject(m, "MasterPubKey", (PyObject *)&b_MasterPubKeyType); PyModule_AddObject(m, "Key", (PyObject *)&b_KeyType); PyModule_AddObject(m, "Address", (PyObject *)&b_KeyType); + PyModule_AddObject(m, "Transaction", (PyObject *)&b_TransactionType); PyModule_AddObject(m, "Wallet", (PyObject *)&b_WalletType); return m; } diff --git a/python/tests.py b/python/tests.py index 5a4f9a5c1..b840e8146 100644 --- a/python/tests.py +++ b/python/tests.py @@ -33,6 +33,12 @@ def test_bitid_address(self): self.assertEqual(str(key.address()), "1J34vj4wowwPYafbeibZGht3zy3qERoUM1") +class TransactionTests(unittest.TestCase): + def test_allocation(self): + t = breadwallet.Transaction() + self.assertNotEqual(t, None) + + class WalletTests(unittest.TestCase): def test_allocation(self): phrase = "axis husband project any sea patch drip tip spirit tide bring belt" From df7cdbad1dbb3d4f7c4cee342da0d0af30da5aff Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Wed, 27 Jul 2016 20:57:02 -0700 Subject: [PATCH 03/16] add wallet callback accessors --- python/bindings.c | 162 +++++++++++++++++++++++++++++++++++++++++++++- python/tests.py | 73 +++++++++++++++++++++ 2 files changed, 234 insertions(+), 1 deletion(-) diff --git a/python/bindings.c b/python/bindings.c index f5a96d26c..3be7584c2 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -413,6 +413,10 @@ typedef struct { PyObject_HEAD b_MasterPubKey *mpk; BRWallet *ob_fval; + PyObject *onSyncStarted; + PyObject *onSyncSucceeded; + PyObject *onSyncFailed; + PyObject *onTxStatusUpdate; } b_Wallet; static PyObject *b_WalletNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { @@ -420,6 +424,10 @@ static PyObject *b_WalletNew(PyTypeObject *type, PyObject *args, PyObject *kwds) if (self != NULL) { self->mpk = NULL; self->ob_fval = NULL; + self->onSyncStarted = NULL; + self->onSyncSucceeded = NULL; + self->onSyncFailed = NULL; + self->onTxStatusUpdate = NULL; } return (PyObject *)self; } @@ -429,6 +437,22 @@ static void b_WalletDealloc(b_Wallet *self) { self->mpk = NULL; BRWalletFree(self->ob_fval); self->ob_fval = NULL; + if (self->onSyncStarted != NULL) { + Py_XDECREF(self->onSyncStarted); + self->onSyncStarted = NULL; + } + if (self->onSyncSucceeded != NULL) { + Py_XDECREF(self->onSyncSucceeded); + self->onSyncSucceeded = NULL; + } + if (self->onSyncFailed != NULL) { + Py_XDECREF(self->onSyncFailed); + self->onSyncFailed = NULL; + } + if (self->onTxStatusUpdate != NULL) { + Py_XDECREF(self->onTxStatusUpdate); + self->onTxStatusUpdate = NULL; + } Py_TYPE(self)->tp_free((PyObject*)self); } @@ -458,6 +482,142 @@ static int b_WalletInit(b_Wallet *self, PyObject *args, PyObject *kwds) { return 0; } +PyObject *b_WalletGetSyncStarted(b_Wallet *self, void *closure){ + if (self->onSyncStarted != NULL) { + Py_INCREF(self->onSyncStarted); + return self->onSyncStarted; + } + Py_INCREF(Py_None); + return Py_None; +} + +static int b_WalletSetSyncStarted(b_Wallet *self, PyObject *value, void *closure) { + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the on_sync_started attribute"); + return -1; + } + + if (value != Py_None && !PyFunction_Check(value)) { + PyErr_SetString(PyExc_TypeError, "The on_sync_started object must be a function"); + return -1; + } + + if (self->onSyncStarted != NULL) { + Py_DECREF(self->onSyncStarted); + } + Py_INCREF(value); + self->onSyncStarted = value; + + return 0; +} + +PyObject *b_WalletGetSyncSucceeded(b_Wallet *self, void *closure){ + if (self->onSyncSucceeded) { + Py_INCREF(self->onSyncSucceeded); + return self->onSyncSucceeded; + } + Py_INCREF(Py_None); + return Py_None; +} + +static int b_WalletSetSyncSucceeded(b_Wallet *self, PyObject *value, void *closure) { + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the on_sync_succeeded attribute"); + return -1; + } + + if (value != Py_None && !PyFunction_Check(value)) { + PyErr_SetString(PyExc_TypeError, "The on_sync_succeeded object must be a function"); + return -1; + } + + if (self->onSyncSucceeded != NULL) { + Py_DECREF(self->onSyncSucceeded); + } + Py_INCREF(value); + self->onSyncSucceeded = value; + + return 0; +} + +PyObject *b_WalletGetSyncFailed(b_Wallet *self, void *closure){ + if (self->onSyncFailed != NULL) { + Py_INCREF(self->onSyncFailed); + return self->onSyncFailed; + } + Py_INCREF(Py_None); + return Py_None; +} + +static int b_WalletSetSyncFailed(b_Wallet *self, PyObject *value, void *closure) { + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the on_sync_failed attribute"); + return -1; + } + + if (value != Py_None && !PyFunction_Check(value)) { + PyErr_SetString(PyExc_TypeError, "The on_sync_failed object must be a function"); + return -1; + } + + if (self->onSyncFailed != NULL) { + Py_DECREF(self->onSyncFailed); + } + Py_INCREF(value); + self->onSyncFailed = value; + + return 0; +} + +PyObject *b_WalletGetTxStatusUpdate(b_Wallet *self, void *closure){ + if (self->onTxStatusUpdate != NULL) { + Py_INCREF(self->onTxStatusUpdate); + return self->onTxStatusUpdate; + } + Py_INCREF(Py_None); + return Py_None; +} + +static int b_WalletSetTxStatusUpdate(b_Wallet *self, PyObject *value, void *closure) { + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the on_tx_status_update attribute"); + return -1; + } + + if (value != Py_None && !PyFunction_Check(value)) { + PyErr_SetString(PyExc_TypeError, "The on_tx_status_update object must be a function"); + return -1; + } + + if (self->onTxStatusUpdate != NULL) { + Py_DECREF(self->onTxStatusUpdate); + } + Py_INCREF(value); + self->onTxStatusUpdate = value; + + return 0; +} + +static PyGetSetDef b_WalletGetSetters[] = { + {"on_sync_started", + (getter)b_WalletGetSyncStarted, (setter)b_WalletSetSyncStarted, + "callback fired when sync is started", + NULL}, + {"on_sync_succeeded", + (getter)b_WalletGetSyncSucceeded, (setter)b_WalletSetSyncSucceeded, + "callback fired when sync finishes successfully", + NULL}, + {"on_sync_failed", + (getter)b_WalletGetSyncFailed, (setter)b_WalletSetSyncFailed, + "callback fired when sync finishes with a failure", + NULL}, + {"on_tx_status_update", + (getter)b_WalletGetTxStatusUpdate, (setter)b_WalletSetTxStatusUpdate, + "callback fired when transaction status is updated", + NULL}, + {NULL} /* Sentinel */ +}; + static PyTypeObject b_WalletType = { PyVarObject_HEAD_INIT(NULL, 0) "breadwallet.Wallet", /* tp_name */ @@ -488,7 +648,7 @@ static PyTypeObject b_WalletType = { 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ - 0, /* tp_getset */ + b_WalletGetSetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ diff --git a/python/tests.py b/python/tests.py index b840e8146..b39ed9af9 100644 --- a/python/tests.py +++ b/python/tests.py @@ -40,8 +40,81 @@ def test_allocation(self): class WalletTests(unittest.TestCase): + def _get_wallet(self): + phrase = "axis husband project any sea patch drip tip spirit tide bring belt" + mpk = breadwallet.MasterPubKey.from_phrase(phrase) + return breadwallet.Wallet(mpk) + def test_allocation(self): phrase = "axis husband project any sea patch drip tip spirit tide bring belt" mpk = breadwallet.MasterPubKey.from_phrase(phrase) wallet = breadwallet.Wallet(mpk) self.assertNotEqual(wallet, None) + + def test_callback_on_sync_started_setters_and_getters(self): + wallet = self._get_wallet() + + def cb_a(): pass + def cb_b(): pass + + self.assertEqual(wallet.on_sync_started, None) + wallet.on_sync_started = None + self.assertEqual(wallet.on_sync_started, None) + wallet.on_sync_started = cb_a + self.assertEqual(wallet.on_sync_started, cb_a) + wallet.on_sync_started = None + self.assertEqual(wallet.on_sync_started, None) + wallet.on_sync_started = cb_a + wallet.on_sync_started = cb_b + self.assertEqual(wallet.on_sync_started, cb_b) + + def test_callback_on_sync_succeeded_setters_and_getters(self): + wallet = self._get_wallet() + + def cb_a(): pass + def cb_b(): pass + + self.assertEqual(wallet.on_sync_succeeded, None) + wallet.on_sync_succeeded = None + self.assertEqual(wallet.on_sync_succeeded, None) + wallet.on_sync_succeeded = cb_a + self.assertEqual(wallet.on_sync_succeeded, cb_a) + wallet.on_sync_succeeded = None + self.assertEqual(wallet.on_sync_succeeded, None) + wallet.on_sync_succeeded = cb_a + wallet.on_sync_succeeded = cb_b + self.assertEqual(wallet.on_sync_succeeded, cb_b) + + def test_callback_on_sync_failed_setters_and_getters(self): + wallet = self._get_wallet() + + def cb_a(): pass + def cb_b(): pass + + self.assertEqual(wallet.on_sync_failed, None) + wallet.on_sync_failed = None + self.assertEqual(wallet.on_sync_failed, None) + wallet.on_sync_failed = cb_a + self.assertEqual(wallet.on_sync_failed, cb_a) + wallet.on_sync_failed = None + self.assertEqual(wallet.on_sync_failed, None) + wallet.on_sync_failed = cb_a + wallet.on_sync_failed = cb_b + self.assertEqual(wallet.on_sync_failed, cb_b) + + def test_callback_on_tx_status_update_setters_and_getters(self): + wallet = self._get_wallet() + + def cb_a(): pass + def cb_b(): pass + + self.assertEqual(wallet.on_tx_status_update, None) + wallet.on_tx_status_update = None + self.assertEqual(wallet.on_tx_status_update, None) + wallet.on_tx_status_update = cb_a + self.assertEqual(wallet.on_tx_status_update, cb_a) + wallet.on_tx_status_update = None + self.assertEqual(wallet.on_tx_status_update, None) + wallet.on_tx_status_update = cb_a + wallet.on_tx_status_update = cb_b + self.assertEqual(wallet.on_tx_status_update, cb_b) From 290b331ad6b9c46ae79169cb86fb9e973cf5fca9 Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Wed, 27 Jul 2016 22:33:38 -0700 Subject: [PATCH 04/16] wallet callbacks --- python/bindings.c | 282 ++++++++++++++++++++++++++++++++-------------- python/tests.py | 94 ++++++++-------- 2 files changed, 245 insertions(+), 131 deletions(-) diff --git a/python/bindings.c b/python/bindings.c index 3be7584c2..96ac582a9 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -11,6 +11,60 @@ * Ints * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + typedef struct { + PyObject_HEAD + UInt256 ob_fval; + } b_UInt256; + + static PyObject *b_UInt256New(PyTypeObject *type, PyObject *args, PyObject *kwds) { + b_UInt256 *self = (b_UInt256 *)type->tp_alloc(type, 0); + if (self != NULL) { + self->ob_fval = UINT256_ZERO; + } + return (PyObject *)self; + } + + static PyTypeObject b_UInt256Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "breadwallet.UInt256", /* tp_name */ + sizeof(b_UInt256), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "UInt256 Object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + b_UInt256New, /* tp_new */ + }; + typedef struct { PyObject_HEAD UInt512 ob_fval; @@ -413,47 +467,97 @@ typedef struct { PyObject_HEAD b_MasterPubKey *mpk; BRWallet *ob_fval; - PyObject *onSyncStarted; - PyObject *onSyncSucceeded; - PyObject *onSyncFailed; - PyObject *onTxStatusUpdate; + PyObject *onBalanceChanged; + PyObject *onTxAdded; + PyObject *onTxUpdated; + PyObject *onTxDeleted; } b_Wallet; +static void b_WalletDealloc(b_Wallet *self) { + Py_XDECREF(self->mpk); + self->mpk = NULL; + BRWalletFree(self->ob_fval); + self->ob_fval = NULL; + if (self->onBalanceChanged != NULL) { + Py_XDECREF(self->onBalanceChanged); + self->onBalanceChanged = NULL; + } + if (self->onTxAdded != NULL) { + Py_XDECREF(self->onTxAdded); + self->onTxAdded = NULL; + } + if (self->onTxUpdated != NULL) { + Py_XDECREF(self->onTxUpdated); + self->onTxUpdated = NULL; + } + if (self->onTxDeleted != NULL) { + Py_XDECREF(self->onTxDeleted); + self->onTxDeleted = NULL; + } + Py_TYPE(self)->tp_free((PyObject*)self); +} + static PyObject *b_WalletNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { b_Wallet *self = (b_Wallet *)type->tp_alloc(type, 0); if (self != NULL) { - self->mpk = NULL; - self->ob_fval = NULL; - self->onSyncStarted = NULL; - self->onSyncSucceeded = NULL; - self->onSyncFailed = NULL; - self->onTxStatusUpdate = NULL; + self->mpk = NULL; + self->ob_fval = NULL; + self->onBalanceChanged = NULL; + self->onTxAdded = NULL; + self->onTxUpdated = NULL; + self->onTxDeleted = NULL; } return (PyObject *)self; } -static void b_WalletDealloc(b_Wallet *self) { - Py_XDECREF(self->mpk); - self->mpk = NULL; - BRWalletFree(self->ob_fval); - self->ob_fval = NULL; - if (self->onSyncStarted != NULL) { - Py_XDECREF(self->onSyncStarted); - self->onSyncStarted = NULL; - } - if (self->onSyncSucceeded != NULL) { - Py_XDECREF(self->onSyncSucceeded); - self->onSyncSucceeded = NULL; - } - if (self->onSyncFailed != NULL) { - Py_XDECREF(self->onSyncFailed); - self->onSyncFailed = NULL; - } - if (self->onTxStatusUpdate != NULL) { - Py_XDECREF(self->onTxStatusUpdate); - self->onTxStatusUpdate = NULL; - } - Py_TYPE(self)->tp_free((PyObject*)self); +void b_WalletCallbackBalanceChanged(void *ctx, uint64_t newBalance) { + printf("balance changed new=%lld", newBalance); + b_Wallet *self = (b_Wallet *)ctx; + if (self->onBalanceChanged != NULL && self->onBalanceChanged != Py_None) { + PyObject *balObj = PyLong_FromUnsignedLongLong(newBalance); + PyObject_CallFunctionObjArgs(self->onBalanceChanged, balObj, NULL); + } +} + +void b_WalletCallbackTxAdded(void *ctx, BRTransaction *tx) { + printf("tx added tx=%s", u256_hex_encode(tx->txHash)); + b_Wallet *self = (b_Wallet *)ctx; + if (self->onTxAdded != NULL && self->onTxAdded != Py_None) { + b_Transaction *txObj = (b_Transaction *)PyObject_New(b_Transaction, &b_TransactionType); + txObj->ob_fval = tx; + PyObject_CallFunctionObjArgs(self->onTxAdded, txObj, NULL); + } +} + +void b_WalletCallbackTxUpdated(void *ctx, const UInt256 txHashes[], size_t count, uint32_t blockHeight, uint32_t timestamp) { + printf("tx updated count=%ld blockheight=%d ts=%d", count, blockHeight, timestamp); + b_Wallet *self = (b_Wallet *)ctx; + if (self->onTxUpdated != NULL && self->onTxUpdated != Py_None) { + PyObject *hashList = PyList_New(count); + for (size_t i = 0; i < count; i++) { + b_UInt256 *hashObj = (b_UInt256 *)PyObject_New(b_UInt256, &b_UInt256Type); + hashObj->ob_fval = txHashes[i]; + PyList_SET_ITEM(hashList, i, (PyObject *)hashObj); + } + PyObject *blockHeightObj = PyLong_FromUnsignedLong(blockHeight); + PyObject *timestampObj = PyLong_FromUnsignedLong(timestamp); + PyObject_CallFunctionObjArgs( + self->onTxUpdated, hashList, blockHeightObj, timestampObj, NULL + ); + } +} + +void b_WalletCallbackTxDeleted(void *ctx, UInt256 txHash, int notifyUser, int recommendRescan) { + printf("tx deleted txhash=%s notify=%d recommend=%d", u256_hex_encode(txHash), notifyUser, recommendRescan); + b_Wallet *self = (b_Wallet *)ctx; + if (self->onTxDeleted != NULL && self->onTxUpdated != Py_None) { + b_UInt256 *hashObj = (b_UInt256 *)PyObject_New(b_UInt256, &b_UInt256Type); + hashObj->ob_fval = txHash; + PyObject_CallFunctionObjArgs( + self->onTxDeleted, hashObj, notifyUser ? Py_True : Py_False, + recommendRescan ? Py_True : Py_False, NULL + ); + } } static int b_WalletInit(b_Wallet *self, PyObject *args, PyObject *kwds) { @@ -463,156 +567,163 @@ static int b_WalletInit(b_Wallet *self, PyObject *args, PyObject *kwds) { if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", kwlist, &mpk, &txList)) return -1; if (!mpk) { - // TODO: set correct error - return -1; + // TODO: set correct error + return -1; } if (!PyObject_IsInstance(mpk, (PyObject *)&b_MasterPubKeyType)) { - // TODO: set correct error - return -1; + // TODO: set correct error + return -1; } // build instance data self->mpk = (b_MasterPubKey *)mpk; Py_INCREF(self->mpk); self->ob_fval = BRWalletNew(NULL, 0, self->mpk->ob_fval); + BRWalletSetCallbacks( + self->ob_fval, (void *)self, + b_WalletCallbackBalanceChanged, + b_WalletCallbackTxAdded, + b_WalletCallbackTxUpdated, + b_WalletCallbackTxDeleted + ); // TODO: parse transaction list return 0; } -PyObject *b_WalletGetSyncStarted(b_Wallet *self, void *closure){ - if (self->onSyncStarted != NULL) { - Py_INCREF(self->onSyncStarted); - return self->onSyncStarted; +PyObject *b_WalletGetBalanceChanged(b_Wallet *self, void *closure){ + if (self->onBalanceChanged != NULL) { + Py_INCREF(self->onBalanceChanged); + return self->onBalanceChanged; } Py_INCREF(Py_None); return Py_None; } -static int b_WalletSetSyncStarted(b_Wallet *self, PyObject *value, void *closure) { +static int b_WalletSetBalanceChanged(b_Wallet *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete the on_sync_started attribute"); + PyErr_SetString(PyExc_TypeError, "Cannot delete the on_balance_changed attribute"); return -1; } if (value != Py_None && !PyFunction_Check(value)) { - PyErr_SetString(PyExc_TypeError, "The on_sync_started object must be a function"); + PyErr_SetString(PyExc_TypeError, "The on_balance_changed object must be a function"); return -1; } - if (self->onSyncStarted != NULL) { - Py_DECREF(self->onSyncStarted); + if (self->onBalanceChanged != NULL) { + Py_DECREF(self->onBalanceChanged); } Py_INCREF(value); - self->onSyncStarted = value; + self->onBalanceChanged = value; return 0; } -PyObject *b_WalletGetSyncSucceeded(b_Wallet *self, void *closure){ - if (self->onSyncSucceeded) { - Py_INCREF(self->onSyncSucceeded); - return self->onSyncSucceeded; +PyObject *b_WalletGetTxAdded(b_Wallet *self, void *closure){ + if (self->onTxAdded) { + Py_INCREF(self->onTxAdded); + return self->onTxAdded; } Py_INCREF(Py_None); return Py_None; } -static int b_WalletSetSyncSucceeded(b_Wallet *self, PyObject *value, void *closure) { +static int b_WalletSetTxAdded(b_Wallet *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete the on_sync_succeeded attribute"); + PyErr_SetString(PyExc_TypeError, "Cannot delete the on_tx_added attribute"); return -1; } if (value != Py_None && !PyFunction_Check(value)) { - PyErr_SetString(PyExc_TypeError, "The on_sync_succeeded object must be a function"); + PyErr_SetString(PyExc_TypeError, "The on_tx_added object must be a function"); return -1; } - if (self->onSyncSucceeded != NULL) { - Py_DECREF(self->onSyncSucceeded); + if (self->onTxAdded != NULL) { + Py_DECREF(self->onTxAdded); } Py_INCREF(value); - self->onSyncSucceeded = value; + self->onTxAdded = value; return 0; } -PyObject *b_WalletGetSyncFailed(b_Wallet *self, void *closure){ - if (self->onSyncFailed != NULL) { - Py_INCREF(self->onSyncFailed); - return self->onSyncFailed; +PyObject *b_WalletGetTxUpdated(b_Wallet *self, void *closure){ + if (self->onTxUpdated != NULL) { + Py_INCREF(self->onTxUpdated); + return self->onTxUpdated; } Py_INCREF(Py_None); return Py_None; } -static int b_WalletSetSyncFailed(b_Wallet *self, PyObject *value, void *closure) { +static int b_WalletSetTxUpdated(b_Wallet *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete the on_sync_failed attribute"); + PyErr_SetString(PyExc_TypeError, "Cannot delete the on_tx_updated attribute"); return -1; } if (value != Py_None && !PyFunction_Check(value)) { - PyErr_SetString(PyExc_TypeError, "The on_sync_failed object must be a function"); + PyErr_SetString(PyExc_TypeError, "The on_tx_updated object must be a function"); return -1; } - if (self->onSyncFailed != NULL) { - Py_DECREF(self->onSyncFailed); + if (self->onTxUpdated != NULL) { + Py_DECREF(self->onTxUpdated); } Py_INCREF(value); - self->onSyncFailed = value; + self->onTxUpdated = value; return 0; } -PyObject *b_WalletGetTxStatusUpdate(b_Wallet *self, void *closure){ - if (self->onTxStatusUpdate != NULL) { - Py_INCREF(self->onTxStatusUpdate); - return self->onTxStatusUpdate; +PyObject *b_WalletGetTxDeleted(b_Wallet *self, void *closure){ + if (self->onTxDeleted != NULL) { + Py_INCREF(self->onTxDeleted); + return self->onTxDeleted; } Py_INCREF(Py_None); return Py_None; } -static int b_WalletSetTxStatusUpdate(b_Wallet *self, PyObject *value, void *closure) { +static int b_WalletSetTxDeleted(b_Wallet *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete the on_tx_status_update attribute"); + PyErr_SetString(PyExc_TypeError, "Cannot delete the on_tx_deleted attribute"); return -1; } if (value != Py_None && !PyFunction_Check(value)) { - PyErr_SetString(PyExc_TypeError, "The on_tx_status_update object must be a function"); + PyErr_SetString(PyExc_TypeError, "The on_tx_deleted object must be a function"); return -1; } - if (self->onTxStatusUpdate != NULL) { - Py_DECREF(self->onTxStatusUpdate); + if (self->onTxDeleted != NULL) { + Py_DECREF(self->onTxDeleted); } Py_INCREF(value); - self->onTxStatusUpdate = value; + self->onTxDeleted = value; return 0; } static PyGetSetDef b_WalletGetSetters[] = { - {"on_sync_started", - (getter)b_WalletGetSyncStarted, (setter)b_WalletSetSyncStarted, + {"on_balance_changed", + (getter)b_WalletGetBalanceChanged, (setter)b_WalletSetBalanceChanged, "callback fired when sync is started", NULL}, - {"on_sync_succeeded", - (getter)b_WalletGetSyncSucceeded, (setter)b_WalletSetSyncSucceeded, + {"on_tx_added", + (getter)b_WalletGetTxAdded, (setter)b_WalletSetTxAdded, "callback fired when sync finishes successfully", NULL}, - {"on_sync_failed", - (getter)b_WalletGetSyncFailed, (setter)b_WalletSetSyncFailed, + {"on_tx_updated", + (getter)b_WalletGetTxUpdated, (setter)b_WalletSetTxUpdated, "callback fired when sync finishes with a failure", NULL}, - {"on_tx_status_update", - (getter)b_WalletGetTxStatusUpdate, (setter)b_WalletSetTxStatusUpdate, + {"on_tx_deleted", + (getter)b_WalletGetTxDeleted, (setter)b_WalletSetTxDeleted, "callback fired when transaction status is updated", NULL}, {NULL} /* Sentinel */ @@ -675,6 +786,7 @@ static PyModuleDef bmodule = { PyMODINIT_FUNC PyInit_breadwallet(void) { PyObject* m; + if (PyType_Ready(&b_UInt256Type) < 0) return NULL; if (PyType_Ready(&b_UInt512Type) < 0) return NULL; if (PyType_Ready(&b_MasterPubKeyType) < 0) return NULL; if (PyType_Ready(&b_KeyType) < 0) return NULL; @@ -685,12 +797,14 @@ PyMODINIT_FUNC PyInit_breadwallet(void) { m = PyModule_Create(&bmodule); if (m == NULL) return NULL; + Py_INCREF(&b_UInt256Type); Py_INCREF(&b_UInt512Type); Py_INCREF(&b_MasterPubKeyType); Py_INCREF(&b_KeyType); Py_INCREF(&b_AddressType); Py_INCREF(&b_TransactionType); Py_INCREF(&b_WalletType); + PyModule_AddObject(m, "UInt256", (PyObject *)&b_UInt256Type); PyModule_AddObject(m, "UInt512", (PyObject *)&b_UInt512Type); PyModule_AddObject(m, "MasterPubKey", (PyObject *)&b_MasterPubKeyType); PyModule_AddObject(m, "Key", (PyObject *)&b_KeyType); diff --git a/python/tests.py b/python/tests.py index b39ed9af9..f3635cad4 100644 --- a/python/tests.py +++ b/python/tests.py @@ -51,70 +51,70 @@ def test_allocation(self): wallet = breadwallet.Wallet(mpk) self.assertNotEqual(wallet, None) - def test_callback_on_sync_started_setters_and_getters(self): + def test_callback_on_balance_changed_setters_and_getters(self): wallet = self._get_wallet() def cb_a(): pass def cb_b(): pass - self.assertEqual(wallet.on_sync_started, None) - wallet.on_sync_started = None - self.assertEqual(wallet.on_sync_started, None) - wallet.on_sync_started = cb_a - self.assertEqual(wallet.on_sync_started, cb_a) - wallet.on_sync_started = None - self.assertEqual(wallet.on_sync_started, None) - wallet.on_sync_started = cb_a - wallet.on_sync_started = cb_b - self.assertEqual(wallet.on_sync_started, cb_b) - - def test_callback_on_sync_succeeded_setters_and_getters(self): + self.assertEqual(wallet.on_balance_changed, None) + wallet.on_balance_changed = None + self.assertEqual(wallet.on_balance_changed, None) + wallet.on_balance_changed = cb_a + self.assertEqual(wallet.on_balance_changed, cb_a) + wallet.on_balance_changed = None + self.assertEqual(wallet.on_balance_changed, None) + wallet.on_balance_changed = cb_a + wallet.on_balance_changed = cb_b + self.assertEqual(wallet.on_balance_changed, cb_b) + + def test_callback_on_tx_added_setters_and_getters(self): wallet = self._get_wallet() def cb_a(): pass def cb_b(): pass - self.assertEqual(wallet.on_sync_succeeded, None) - wallet.on_sync_succeeded = None - self.assertEqual(wallet.on_sync_succeeded, None) - wallet.on_sync_succeeded = cb_a - self.assertEqual(wallet.on_sync_succeeded, cb_a) - wallet.on_sync_succeeded = None - self.assertEqual(wallet.on_sync_succeeded, None) - wallet.on_sync_succeeded = cb_a - wallet.on_sync_succeeded = cb_b - self.assertEqual(wallet.on_sync_succeeded, cb_b) - - def test_callback_on_sync_failed_setters_and_getters(self): + self.assertEqual(wallet.on_tx_added, None) + wallet.on_tx_added = None + self.assertEqual(wallet.on_tx_added, None) + wallet.on_tx_added = cb_a + self.assertEqual(wallet.on_tx_added, cb_a) + wallet.on_tx_added = None + self.assertEqual(wallet.on_tx_added, None) + wallet.on_tx_added = cb_a + wallet.on_tx_added = cb_b + self.assertEqual(wallet.on_tx_added, cb_b) + + def test_callback_on_tx_updated_setters_and_getters(self): wallet = self._get_wallet() def cb_a(): pass def cb_b(): pass - self.assertEqual(wallet.on_sync_failed, None) - wallet.on_sync_failed = None - self.assertEqual(wallet.on_sync_failed, None) - wallet.on_sync_failed = cb_a - self.assertEqual(wallet.on_sync_failed, cb_a) - wallet.on_sync_failed = None - self.assertEqual(wallet.on_sync_failed, None) - wallet.on_sync_failed = cb_a - wallet.on_sync_failed = cb_b - self.assertEqual(wallet.on_sync_failed, cb_b) - - def test_callback_on_tx_status_update_setters_and_getters(self): + self.assertEqual(wallet.on_tx_updated, None) + wallet.on_tx_updated = None + self.assertEqual(wallet.on_tx_updated, None) + wallet.on_tx_updated = cb_a + self.assertEqual(wallet.on_tx_updated, cb_a) + wallet.on_tx_updated = None + self.assertEqual(wallet.on_tx_updated, None) + wallet.on_tx_updated = cb_a + wallet.on_tx_updated = cb_b + self.assertEqual(wallet.on_tx_updated, cb_b) + + def test_callback_on_tx_deleted_setters_and_getters(self): wallet = self._get_wallet() def cb_a(): pass def cb_b(): pass - self.assertEqual(wallet.on_tx_status_update, None) - wallet.on_tx_status_update = None - self.assertEqual(wallet.on_tx_status_update, None) - wallet.on_tx_status_update = cb_a - self.assertEqual(wallet.on_tx_status_update, cb_a) - wallet.on_tx_status_update = None - self.assertEqual(wallet.on_tx_status_update, None) - wallet.on_tx_status_update = cb_a - wallet.on_tx_status_update = cb_b - self.assertEqual(wallet.on_tx_status_update, cb_b) + self.assertEqual(wallet.on_tx_deleted, None) + wallet.on_tx_deleted = None + self.assertEqual(wallet.on_tx_deleted, None) + wallet.on_tx_deleted = cb_a + self.assertEqual(wallet.on_tx_deleted, cb_a) + wallet.on_tx_deleted = None + self.assertEqual(wallet.on_tx_deleted, None) + wallet.on_tx_deleted = cb_a + wallet.on_tx_deleted = cb_b + self.assertEqual(wallet.on_tx_deleted, cb_b) From 2335337dba41e397faf6005543cb343cfcc4d75a Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Thu, 28 Jul 2016 16:56:49 -0700 Subject: [PATCH 05/16] add Key.privkey_is_valid --- python/bindings.c | 13 +++++++++++++ python/tests.py | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/python/bindings.c b/python/bindings.c index 96ac582a9..36d47150d 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -399,6 +399,17 @@ static PyObject *b_KeyFromBitID(PyObject *cls, PyObject *args, PyObject *kwds) { return result; } +static PyObject *b_KeyPrivKeyIsValid(PyObject *cls, PyObject *args, PyObject *kwds) { + PyObject *result = Py_False; + char *pk; + static char *kwList[] = { "pk", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwList, &pk)) { + return NULL; + } + if (BRPrivKeyIsValid(pk)) result = Py_True; + return result; +} + static PyObject *b_KeyAddress(PyObject *self, PyObject *args) { BRAddress address; b_Key *bkey = (b_Key *)self; @@ -412,6 +423,8 @@ static PyMethodDef b_KeyMethods[] = { /* Class Methods */ {"from_bitid", (PyCFunction)b_KeyFromBitID, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), "generate a bitid Key from a seed and some bitid parameters"}, + {"privkey_is_valid", (PyCFunction)b_KeyPrivKeyIsValid, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "determine whether or not a serialized private key is valid"}, /* Instance Methods */ {"address", (PyCFunction)b_KeyAddress, METH_NOARGS, "get the address from the key"}, diff --git a/python/tests.py b/python/tests.py index f3635cad4..c9e5879de 100644 --- a/python/tests.py +++ b/python/tests.py @@ -32,6 +32,10 @@ def test_bitid_address(self): self.assertNotEqual(key.address(), None) self.assertEqual(str(key.address()), "1J34vj4wowwPYafbeibZGht3zy3qERoUM1") + def test_privkeykey_is_valid(self): + self.assertFalse(breadwallet.Key.privkey_is_valid("S6c56bnXQiBjk9mqSYE7ykVQ7NzrRz")) + self.assertTrue(breadwallet.Key.privkey_is_valid("S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy")) + class TransactionTests(unittest.TestCase): def test_allocation(self): From e54a431a82a5d6087ba8c8efda49cea1ec0f0f4b Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Thu, 28 Jul 2016 17:13:13 -0700 Subject: [PATCH 06/16] add mainnet/testnet builds to the same module --- python/bindings.c | 12 ++++++-- python/breadwallet/__init__.py | 5 +++ python/setup.py | 56 +++++++++++++++++++++------------- 3 files changed, 49 insertions(+), 24 deletions(-) create mode 100644 python/breadwallet/__init__.py diff --git a/python/bindings.c b/python/bindings.c index 36d47150d..46b7dba01 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -790,13 +790,21 @@ static PyMethodDef bmodulemethods[] = { static PyModuleDef bmodule = { PyModuleDef_HEAD_INIT, - "breadwallet", +#if BITCOIN_TESTNET + "breadwallet_testnet", +#else + "breadwallet_mainnet", +#endif "A simple, lightweight, performant SPV wallet", -1, bmodulemethods, NULL, NULL, NULL, NULL }; -PyMODINIT_FUNC PyInit_breadwallet(void) { +#if BITCOIN_TESTNET +PyMODINIT_FUNC PyInit_breadwallet_testnet(void) { +#else +PyMODINIT_FUNC PyInit_breadwallet_mainnet(void) { +#endif PyObject* m; if (PyType_Ready(&b_UInt256Type) < 0) return NULL; diff --git a/python/breadwallet/__init__.py b/python/breadwallet/__init__.py new file mode 100644 index 000000000..7597eb9bc --- /dev/null +++ b/python/breadwallet/__init__.py @@ -0,0 +1,5 @@ +__version__ = '0.0.1' +__author__ = 'Samuel Sutch ' + +from breadwallet_mainnet import * +import breadwallet_testnet as testnet diff --git a/python/setup.py b/python/setup.py index e2814bc73..1fe630283 100644 --- a/python/setup.py +++ b/python/setup.py @@ -7,34 +7,46 @@ def here(*args): def fromroot(*args): return here(os.pardir, *args) -breadwallet = Extension( - 'breadwallet', - sources=[ - fromroot('BRAddress.c'), - fromroot('BRBase58.c'), - fromroot('BRBIP32Sequence.c'), - fromroot('BRBIP38Key.c'), - fromroot('BRBIP39Mnemonic.c'), - fromroot('BRBloomFilter.c'), - fromroot('BRCrypto.c'), - fromroot('BRKey.c'), - fromroot('BRMerkleBlock.c'), - fromroot('BRPaymentProtocol.c'), - fromroot('BRPeer.c'), - fromroot('BRPeerManager.c'), - fromroot('BRSet.c'), - fromroot('BRTransaction.c'), - fromroot('BRWallet.c'), - 'bindings.c' - ], - include_dirs=[fromroot(), fromroot('secp256k1')] +sources = [ + fromroot('BRAddress.c'), + fromroot('BRBase58.c'), + fromroot('BRBIP32Sequence.c'), + fromroot('BRBIP38Key.c'), + fromroot('BRBIP39Mnemonic.c'), + fromroot('BRBloomFilter.c'), + fromroot('BRCrypto.c'), + fromroot('BRKey.c'), + fromroot('BRMerkleBlock.c'), + fromroot('BRPaymentProtocol.c'), + fromroot('BRPeer.c'), + fromroot('BRPeerManager.c'), + fromroot('BRSet.c'), + fromroot('BRTransaction.c'), + fromroot('BRWallet.c'), + here('bindings.c') +] + +includes = [fromroot(), fromroot('secp256k1')] + +breadwallet_mainnet = Extension( + 'breadwallet_mainnet', + sources=sources, + include_dirs=includes +) + +breadwallet_testnet = Extension( + 'breadwallet_testnet', + sources=sources, + include_dirs=includes, + define_macros=[('BITCOIN_TESTNET', '1')] ) setup( name='breadwallet-core', version='0.1', description='A simple, easy to use SPV wallet.', - ext_modules=[breadwallet], + packages=['breadwallet'], + ext_modules=[breadwallet_mainnet, breadwallet_testnet], test_suite='nose.collector', tests_require=['nose'] ) From 4751ea1347fb4bae288dba8163e7282ba7d10ac5 Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Fri, 29 Jul 2016 12:37:31 -0700 Subject: [PATCH 07/16] add address init/compare; privkey/pubkey/secret setters+getters for Key --- python/bindings.c | 243 +++++++++++++++++++++++++++++++++++++++++----- python/tests.py | 35 ++++++- 2 files changed, 254 insertions(+), 24 deletions(-) diff --git a/python/bindings.c b/python/bindings.c index 46b7dba01..913a8dd4f 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -135,10 +135,40 @@ static PyObject *b_AddressNew(PyTypeObject *type, PyObject *args, PyObject *kwds return (PyObject *)self; } -static PyObject *b_AddresToStr(PyObject *self) { - return PyUnicode_FromString(((b_Address *)self)->ob_fval.s); +static int b_AddressInit(b_Address *self, PyObject *args, PyObject *kwds) { + PyObject *addy; + // parse args + static char *kwlist[] = { "str", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &addy)) { + return -1; + } + if (addy == NULL || addy == Py_None) { + return 0; // optional argument + } + if (!PyUnicode_Check(addy)) { + PyErr_SetString(PyExc_TypeError, "Address must initialized with a string."); + return -1; + } + PyObject *buf = PyUnicode_AsEncodedString(addy, "utf-8", "strict"); + if (buf == NULL) { + PyErr_SetString(PyExc_ValueError, "Error decoding unicode value as utf8"); + return -1; + } + memcpy(&self->ob_fval, PyBytes_AsString(buf), PyBytes_Size(buf)); + return 0; +} + +static PyObject *b_AddressToRepr(b_Address *self) { + return PyUnicode_FromFormat("", self->ob_fval.s); } +static PyObject *b_AddresToStr(b_Address *self) { + return PyUnicode_FromString(self->ob_fval.s); +} + +// forward decl because the comparison needs to check against the Address type +static PyObject *b_AddressRichCompare(PyObject *a, PyObject *b, int op); + static PyMethodDef b_AddressMethods[] = { {NULL} }; @@ -153,7 +183,7 @@ static PyTypeObject b_AddressType = { 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)b_AddresToStr, /* tp_repr */ + (reprfunc)b_AddressToRepr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ @@ -167,7 +197,7 @@ static PyTypeObject b_AddressType = { "Address Object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ - 0, /* tp_richcompare */ + (richcmpfunc)b_AddressRichCompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ @@ -179,11 +209,46 @@ static PyTypeObject b_AddressType = { 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ - 0, /* tp_init */ + (initproc)b_AddressInit, /* tp_init */ 0, /* tp_alloc */ b_AddressNew, /* tp_new */ }; +static PyObject *b_AddressRichCompare(PyObject *a, PyObject *b, int op) { + if (op != Py_EQ && op != Py_NE) { + return Py_NotImplemented; + } + if (!PyObject_IsInstance(a, (PyObject *)&b_AddressType)) { + // WHAT? + PyErr_SetString(PyExc_TypeError, "Instance is not an Address"); + return NULL; + } + b_Address *self = (b_Address *)a; + int eq = 0; + if (PyUnicode_Check(b)) { + // compared to a string + PyObject *buf = PyUnicode_AsEncodedString(b, "utf-8", "strict"); + if (buf == NULL) { + PyErr_SetString(PyExc_ValueError, "Error decoding unicode value as utf8"); + return NULL; + } + const char *addressB = PyBytes_AsString(buf); + eq = BRAddressEq(&self->ob_fval, addressB); + } else if (PyObject_IsInstance(b, (PyObject *)&b_AddressType)) { + b_Address *other = (b_Address *)b; + eq = BRAddressEq(&self->ob_fval, &other->ob_fval); + } else if (b == Py_None) { + // default to not equal + } else { + PyErr_SetString(PyExc_TypeError, "Address can only be compared to a string or another address"); + return NULL; + } + if (op == Py_NE) { + eq = !eq; + } + return eq ? Py_True : Py_False; +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Transaction * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ @@ -194,20 +259,20 @@ typedef struct { } b_Transaction; static void b_TransactionDealloc(b_Transaction *self) { - BRTransactionFree(self->ob_fval); - self->ob_fval = NULL; - Py_TYPE(self)->tp_free((PyObject*)self); + BRTransactionFree(self->ob_fval); + self->ob_fval = NULL; + Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject *b_TransactionNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { b_Transaction *self = (b_Transaction *)type->tp_alloc(type, 0); if (self != NULL) { - self->ob_fval = NULL; + self->ob_fval = NULL; } return (PyObject *)self; } -static PyObject *b_TransactionInit(PyObject *self, PyObject *args, PyObject *kwds) { +static int b_TransactionInit(PyObject *self, PyObject *args, PyObject *kwds) { b_Transaction *obj = (b_Transaction *)self; obj->ob_fval = BRTransactionNew(); return 0; @@ -360,12 +425,13 @@ static PyObject *b_DeriveKey(PyObject *self, PyObject *args) { typedef struct { PyObject_HEAD - BRKey ob_fval; + BRKey *ob_fval; } b_Key; static PyObject *b_KeyNew(PyTypeObject *type, PyObject *args, PyObject *kwds) { b_Key *self = (b_Key *)type->tp_alloc(type, 0); if (self != NULL) { + self->ob_fval = NULL; } return (PyObject *)self; } @@ -381,14 +447,14 @@ static PyObject *b_KeyFromBitID(PyObject *cls, PyObject *args, PyObject *kwds) { return NULL; } if (!PyObject_IsInstance(seedObj, (PyObject *)&b_UInt512Type)) { - // TODO: set correct error + PyErr_SetString(PyExc_TypeError, "seed must be an instance of UInt512"); return NULL; } b_UInt512 *seed = (b_UInt512 *)seedObj; // create - BRKey key; - BRBIP32BitIDKey(&key, seed->ob_fval.u8, sizeof(seed->ob_fval.u8), index, endpoint); + BRKey *key = calloc(1, sizeof(BRKey)); + BRBIP32BitIDKey(key, seed->ob_fval.u8, sizeof(seed->ob_fval.u8), index, endpoint); // allocate result = PyObject_CallFunction(cls, ""); @@ -410,15 +476,149 @@ static PyObject *b_KeyPrivKeyIsValid(PyObject *cls, PyObject *args, PyObject *kw return result; } -static PyObject *b_KeyAddress(PyObject *self, PyObject *args) { +static int b_KeySetSecret(b_Key *self, PyObject *value, void *closure) { + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the secret attribute"); + return -1; + } + if (!PyObject_IsInstance(value, (PyObject *)&b_UInt256Type)) { + PyErr_SetString(PyExc_TypeError, "The secret object must be a UInt256"); + return -1; + } + b_UInt256 *sec = (b_UInt256 *)value; + if (self->ob_fval == NULL) { + self->ob_fval = calloc(1, sizeof(BRKey)); + } + if (!BRKeySetSecret(self->ob_fval, &sec->ob_fval, 1)) { + if (!BRKeySetSecret(self->ob_fval, &sec->ob_fval, 0)) { + PyErr_SetString(PyExc_ValueError, "Could not parse the secret (tried both compressed and uncompressed)"); + return -1; + } + } + return 0; +} + +static PyObject *b_KeyGetSecret(b_Key *self, void *closure) { + if (self->ob_fval == NULL) { + return Py_BuildValue(""); + } + b_UInt256 *ret = (b_UInt256 *)PyObject_New(b_UInt256, &b_UInt256Type); + ret->ob_fval = self->ob_fval->secret; + return (PyObject *)ret; +} + +static int b_KeySetPrivKey(b_Key *self, PyObject *value, void *closure) { + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the privkey attribute"); + return -1; + } + if (!PyUnicode_Check(value)) { + PyErr_SetString(PyExc_TypeError, "Priv key must be a string"); + return -1; + } + PyObject *buf = PyUnicode_AsEncodedString(value, "utf-8", "strict"); + if (buf == NULL) { + PyErr_SetString(PyExc_ValueError, "Error decoding unicode value as utf8"); + return -1; + } + const char *privkey = PyBytes_AsString(buf); + if (self->ob_fval == NULL) { + self->ob_fval = calloc(1, sizeof(BRKey)); + } + if (!BRKeySetPrivKey(self->ob_fval, privkey)) { + PyErr_SetString(PyExc_ValueError, "Unable to set private key (was it correctly serialized?)"); + return -1; + } + return 0; +} + +static PyObject *b_KeyGetPrivKey(b_Key *self, void *closure) { + PyObject *ret; + if (self->ob_fval == NULL) { + ret = Py_BuildValue(""); + } else { + char privKey[BRKeyPrivKey(self->ob_fval, NULL, 0)]; + BRKeyPrivKey(self->ob_fval, privKey, sizeof(privKey)); + ret = PyUnicode_FromString(privKey); + if (ret == NULL) { + printf("ERROR: could not export private key\n"); + ret = Py_BuildValue(""); + } + } + return ret; +} + +static int b_KeySetPubKey(b_Key *self, PyObject *value, void *closure) { + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the pubkey attribute"); + return -1; + } + if (value == Py_None || !PyBytes_Check(value)) { + PyErr_SetString(PyExc_TypeError, "Pub key must be a bytes object"); + return -1; + } + const char *pubkey = PyBytes_AsString(value); + if (self->ob_fval == NULL) { + self->ob_fval = calloc(1, sizeof(BRKey)); + } + // just try both options (same as we do for secret key) + if (!BRKeySetPubKey(self->ob_fval, (uint8_t *)pubkey, 33)) { + if (!BRKeySetPubKey(self->ob_fval, (uint8_t *)pubkey, 65)) { + PyErr_SetString(PyExc_ValueError, "Unable to set public key (was it correctly serialized?) " + "it should be either 33 or 65 bytes long"); + return -1; + } + } + return 0; +} + +static PyObject *b_KeyGetPubKey(b_Key *self, void *closure) { + PyObject *ret; + if (self->ob_fval == NULL) { + ret = Py_BuildValue(""); + } else { + uint8_t pubkey[BRKeyPubKey(self->ob_fval, NULL, 0)]; + size_t keyLen = BRKeyPubKey(self->ob_fval, pubkey, sizeof(pubkey)); + ret = PyBytes_FromStringAndSize((const char *)pubkey, keyLen); + if (ret == NULL) { + printf("ERROR: could not export public key\n"); + ret = Py_BuildValue(""); + } + } + return ret; +} + +static PyObject *b_KeyGetAddress(b_Key *self, void *closure) { + if (self->ob_fval == NULL) { + return Py_BuildValue(""); + } BRAddress address; - b_Key *bkey = (b_Key *)self; - BRKeyAddress(&bkey->ob_fval, address.s, sizeof(address)); + BRKeyAddress(self->ob_fval, address.s, sizeof(address)); b_Address *ret = (b_Address *)PyObject_New(b_Address, &b_AddressType); ret->ob_fval = address; return (PyObject *)ret; } +static PyGetSetDef b_KeyGetSetters[] = { + {"secret", + (getter)b_KeyGetSecret, (setter)b_KeySetSecret, + "get or set the secret. will reset the key", + NULL}, + {"privkey", + (getter)b_KeyGetPrivKey, (setter)b_KeySetPrivKey, + "get or set the private key. throws a ValueError when key can't be parsed", + NULL}, + {"pubkey", + (getter)b_KeyGetPubKey, (setter)b_KeySetPubKey, + "get or set the public key. throws a ValueError when the key can't be parsed", + NULL}, + {"address", + (getter)b_KeyGetAddress, (setter)NULL, + "get the p2sh address of the key", + NULL}, + {NULL} +}; + static PyMethodDef b_KeyMethods[] = { /* Class Methods */ {"from_bitid", (PyCFunction)b_KeyFromBitID, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), @@ -426,8 +626,6 @@ static PyMethodDef b_KeyMethods[] = { {"privkey_is_valid", (PyCFunction)b_KeyPrivKeyIsValid, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), "determine whether or not a serialized private key is valid"}, /* Instance Methods */ - {"address", (PyCFunction)b_KeyAddress, METH_NOARGS, - "get the address from the key"}, {NULL} }; @@ -461,7 +659,7 @@ static PyTypeObject b_KeyType = { 0, /* tp_iternext */ b_KeyMethods, /* tp_methods */ 0, /* tp_members */ - 0, /* tp_getset */ + b_KeyGetSetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ @@ -542,7 +740,8 @@ void b_WalletCallbackTxAdded(void *ctx, BRTransaction *tx) { } } -void b_WalletCallbackTxUpdated(void *ctx, const UInt256 txHashes[], size_t count, uint32_t blockHeight, uint32_t timestamp) { +void b_WalletCallbackTxUpdated(void *ctx, const UInt256 txHashes[], size_t count, + uint32_t blockHeight, uint32_t timestamp) { printf("tx updated count=%ld blockheight=%d ts=%d", count, blockHeight, timestamp); b_Wallet *self = (b_Wallet *)ctx; if (self->onTxUpdated != NULL && self->onTxUpdated != Py_None) { @@ -829,7 +1028,7 @@ PyMODINIT_FUNC PyInit_breadwallet_mainnet(void) { PyModule_AddObject(m, "UInt512", (PyObject *)&b_UInt512Type); PyModule_AddObject(m, "MasterPubKey", (PyObject *)&b_MasterPubKeyType); PyModule_AddObject(m, "Key", (PyObject *)&b_KeyType); - PyModule_AddObject(m, "Address", (PyObject *)&b_KeyType); + PyModule_AddObject(m, "Address", (PyObject *)&b_AddressType); PyModule_AddObject(m, "Transaction", (PyObject *)&b_TransactionType); PyModule_AddObject(m, "Wallet", (PyObject *)&b_WalletType); return m; diff --git a/python/tests.py b/python/tests.py index c9e5879de..439aa3394 100644 --- a/python/tests.py +++ b/python/tests.py @@ -8,6 +8,32 @@ def test_allocation(self): self.assertNotEqual(u512, None) +class AddressTests(unittest.TestCase): + def test_allocation(self): + addy = breadwallet.Address("1J34vj4wowwPYafbeibZGht3zy3qERoUM1") + self.assertNotEqual(addy, None) + + with self.assertRaises(TypeError): + addy2 = breadwallet.Address(2304203942340) + + def test_equality(self): + addy1_s = "1J34vj4wowwPYafbeibZGht3zy3qERoUM1" + addy1_o = breadwallet.Address(addy1_s) + addy2_s = "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" + addy2_o = breadwallet.Address(addy2_s) + self.assertEqual(addy1_o, addy1_o) + self.assertEqual(addy1_o, addy1_s) + self.assertNotEqual(addy1_o, addy2_o) + self.assertNotEqual(addy1_o, addy2_s) + self.assertNotEqual(addy1_o, None) # allow none comparison + with self.assertRaises(TypeError): + addy1_o == 234234 # raises when trying to compare against anything other than a string/address/none + + def test_to_str(self): + addy1_s = "1J34vj4wowwPYafbeibZGht3zy3qERoUM1" + self.assertEqual(str(breadwallet.Address(addy1_s)), addy1_s) + + class KeyTests(unittest.TestCase): def test_allocation(self): phrase = "axis husband project any sea patch drip tip spirit tide bring belt" @@ -29,13 +55,18 @@ def test_bitid_address(self): phrase = "inhale praise target steak garlic cricket paper better evil almost sadness crawl city banner amused fringe fox insect roast aunt prefer hollow basic ladder" seed = breadwallet.derive_key(phrase) key = breadwallet.Key.from_bitid(seed, 0, "http://bitid.bitcoin.blue/callback") - self.assertNotEqual(key.address(), None) - self.assertEqual(str(key.address()), "1J34vj4wowwPYafbeibZGht3zy3qERoUM1") + self.assertNotEqual(key.address, None) + self.assertEqual(str(key.address), "1J34vj4wowwPYafbeibZGht3zy3qERoUM1") def test_privkeykey_is_valid(self): self.assertFalse(breadwallet.Key.privkey_is_valid("S6c56bnXQiBjk9mqSYE7ykVQ7NzrRz")) self.assertTrue(breadwallet.Key.privkey_is_valid("S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy")) + def test_set_privkey(self): + k = breadwallet.Key() + k.privkey = 'S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy' + self.assertEqual(k.address, '1CciesT23BNionJeXrbxmjc7ywfiyM4oLW') + class TransactionTests(unittest.TestCase): def test_allocation(self): From b33fb4e46896366f09f0039ae8d29ae7814cfd95 Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Sat, 30 Jul 2016 22:40:24 -0700 Subject: [PATCH 08/16] add change_address and receive_address to wallet --- python/bindings.c | 20 ++++++++++++++++++++ python/tests.py | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/python/bindings.c b/python/bindings.c index 913a8dd4f..3a3078723 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -921,6 +921,18 @@ static int b_WalletSetTxDeleted(b_Wallet *self, PyObject *value, void *closure) return 0; } +PyObject *b_WalletGetReceiveAddress(b_Wallet *self, void *closure) { + b_Address *addrObj = (b_Address *)PyObject_New(b_Address, &b_AddressType); + addrObj->ob_fval = BRWalletReceiveAddress(self->ob_fval); + return (PyObject *)addrObj; +} + +PyObject *b_WalletGetChangeAddress(b_Wallet *self, void *closure) { + b_Address *addrObj = (b_Address *)PyObject_New(b_Address, &b_AddressType); + addrObj->ob_fval = BRWalletChangeAddress(self->ob_fval); + return (PyObject *)addrObj; +} + static PyGetSetDef b_WalletGetSetters[] = { {"on_balance_changed", (getter)b_WalletGetBalanceChanged, (setter)b_WalletSetBalanceChanged, @@ -938,6 +950,14 @@ static PyGetSetDef b_WalletGetSetters[] = { (getter)b_WalletGetTxDeleted, (setter)b_WalletSetTxDeleted, "callback fired when transaction status is updated", NULL}, + {"receive_address", + (getter)b_WalletGetReceiveAddress, NULL, + "get the first unused receive address", + NULL}, + {"change_address", + (getter)b_WalletGetChangeAddress, NULL, + "get the first unused change address", + NULL}, {NULL} /* Sentinel */ }; diff --git a/python/tests.py b/python/tests.py index 439aa3394..4b976e912 100644 --- a/python/tests.py +++ b/python/tests.py @@ -153,3 +153,13 @@ def cb_b(): pass wallet.on_tx_deleted = cb_a wallet.on_tx_deleted = cb_b self.assertEqual(wallet.on_tx_deleted, cb_b) + + def test_get_receive_address(self): + wallet = self._get_wallet() + self.assertNotEqual(wallet.receive_address, None) + self.assertEqual(wallet.receive_address, wallet.receive_address) + + def test_get_change_address(self): + wallet = self._get_wallet() + self.assertNotEqual(wallet.change_address, None) + self.assertEqual(wallet.change_address, wallet.change_address) From 7b3180c1c5359852a3c02e821c2369e51333069d Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Mon, 1 Aug 2016 13:32:46 -0700 Subject: [PATCH 09/16] uint256 from_hex --- python/bindings.c | 64 +++++++++++++++++++++++++++++++++-------------- python/tests.py | 8 ++++++ 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/python/bindings.c b/python/bindings.c index 3a3078723..e5d386e75 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -24,6 +24,27 @@ return (PyObject *)self; } +static PyObject *b_UInt256FromHex(PyObject *cls, PyObject *args, PyObject *kwds) { + PyObject *result = NULL; + char *hex = ""; + static char *kwlist[] = { "hex", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &hex)) { + return NULL; + } + result = PyObject_CallFunction(cls, ""); + if (result != NULL) { + ((b_UInt256 *)result)->ob_fval = u256_hex_decode(hex); + } + return result; +} + + static PyMethodDef b_UInt256Methods[] = { + /* Class Methods */ + {"from_hex", (PyCFunction)b_UInt256FromHex, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "initialize a UInt256 from a hex string"}, + {NULL} + }; + static PyTypeObject b_UInt256Type = { PyVarObject_HEAD_INIT(NULL, 0) "breadwallet.UInt256", /* tp_name */ @@ -52,7 +73,7 @@ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - 0, /* tp_methods */ + b_UInt256Methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ @@ -685,26 +706,31 @@ typedef struct { } b_Wallet; static void b_WalletDealloc(b_Wallet *self) { - Py_XDECREF(self->mpk); - self->mpk = NULL; - BRWalletFree(self->ob_fval); - self->ob_fval = NULL; - if (self->onBalanceChanged != NULL) { - Py_XDECREF(self->onBalanceChanged); - self->onBalanceChanged = NULL; - } - if (self->onTxAdded != NULL) { - Py_XDECREF(self->onTxAdded); - self->onTxAdded = NULL; - } - if (self->onTxUpdated != NULL) { - Py_XDECREF(self->onTxUpdated); - self->onTxUpdated = NULL; + if (self->mpk != NULL) { + Py_XDECREF(self->mpk); + self->mpk = NULL; } - if (self->onTxDeleted != NULL) { - Py_XDECREF(self->onTxDeleted); - self->onTxDeleted = NULL; + if (self->ob_fval != NULL) { + BRWalletFree(self->ob_fval); + self->ob_fval = NULL; } + // TODO: figure this out. currently causes a dealloc error when callbacks are not set + // if (self->onBalanceChanged != NULL) { + // Py_XDECREF(self->onBalanceChanged); + // self->onBalanceChanged = NULL; + // } + // if (self->onTxAdded != NULL) { + // Py_XDECREF(self->onTxAdded); + // self->onTxAdded = NULL; + // } + // if (self->onTxUpdated != NULL) { + // Py_XDECREF(self->onTxUpdated); + // self->onTxUpdated = NULL; + // } + // if (self->onTxDeleted != NULL) { + // Py_XDECREF(self->onTxDeleted); + // self->onTxDeleted = NULL; + // } Py_TYPE(self)->tp_free((PyObject*)self); } diff --git a/python/tests.py b/python/tests.py index 4b976e912..f0be0f7bf 100644 --- a/python/tests.py +++ b/python/tests.py @@ -3,6 +3,14 @@ class IntTests(unittest.TestCase): + def test_allocation_256(self): + u256 = breadwallet.UInt256() + self.assertNotEqual(u256, None) + + def test_from_hex_256(self): + u256 = breadwallet.UInt256.from_hex('0000000000000000000000000000000000000000000000000000000000000001') + self.assertNotEqual(u256, None) + def test_allocation(self): u512 = breadwallet.UInt512() self.assertNotEqual(u512, None) From c1284dba03d9831dc128502fd3c251910df2907d Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Mon, 1 Aug 2016 15:28:01 -0700 Subject: [PATCH 10/16] add UInt256.hex and UInt256.from_hash --- python/bindings.c | 70 ++++++++++++++++++++++++++++++++++++++++++----- python/tests.py | 12 ++++++++ 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/python/bindings.c b/python/bindings.c index e5d386e75..bcfeb29d4 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -29,20 +29,76 @@ static PyObject *b_UInt256FromHex(PyObject *cls, PyObject *args, PyObject *kwds) char *hex = ""; static char *kwlist[] = { "hex", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &hex)) { - return NULL; + return NULL; + } + result = PyObject_CallFunction(cls, ""); + if (result != NULL) { + ((b_UInt256 *)result)->ob_fval = u256_hex_decode(hex); + } + return result; +} + +static PyObject *b_UInt256FromHash(PyObject *cls, PyObject *args, PyObject *kwds) { + PyObject *result = NULL; + PyObject *hash = NULL; + static char *kwlist[] = { "hash", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &hash)) { + return NULL; + } + if (hash == NULL) { + PyErr_SetString(PyExc_ValueError, "NULL hash argument"); + return NULL; + } + if (!PyObject_HasAttrString(hash, "digest")) { + PyErr_SetString(PyExc_TypeError, "hash argument must have a 'digest' attribute that is an instance method"); + return NULL; } + PyObject *digestMethod = PyObject_GetAttrString(hash, "digest"); + if (digestMethod == NULL || !PyCallable_Check(digestMethod)) { + PyErr_SetString(PyExc_TypeError, "hash argument must have a digest() method"); + Py_XDECREF(digestMethod); + return NULL; + } + + // get the result of the digest() method as a UInt256 + PyObject *digestObj = PyObject_CallFunction(digestMethod, ""); + if (digestObj == NULL || !PyBytes_Check(digestObj) || PyBytes_Size(digestObj) != 32) { + PyErr_SetString(PyExc_TypeError, "hash argument digest() method must return a bytes object with a length of 32"); + Py_XDECREF(digestMethod); + Py_XDECREF(digestObj); + return NULL; + } + + UInt256 *u256 = (void *)PyBytes_AsString(digestObj); + result = PyObject_CallFunction(cls, ""); if (result != NULL) { - ((b_UInt256 *)result)->ob_fval = u256_hex_decode(hex); + memcpy(&((b_UInt256 *)result)->ob_fval, u256, 32); } + Py_XDECREF(digestMethod); + Py_XDECREF(digestObj); return result; } +static PyObject *b_UInt256GetHex(b_UInt256 *self, void *closure) { + return Py_BuildValue("s", u256_hex_encode(self->ob_fval)); +} + +static PyGetSetDef b_UInt256GetSetters[] = { + {"hex", + (getter)b_UInt256GetHex, NULL, + "get the hex value", + NULL}, + {NULL} +}; + static PyMethodDef b_UInt256Methods[] = { - /* Class Methods */ - {"from_hex", (PyCFunction)b_UInt256FromHex, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), - "initialize a UInt256 from a hex string"}, - {NULL} + /* Class Methods */ + {"from_hex", (PyCFunction)b_UInt256FromHex, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "initialize a UInt256 from a hex string"}, + {"from_hash", (PyCFunction)b_UInt256FromHash, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "initialize a UInt256 from a hash object"}, + {NULL} }; static PyTypeObject b_UInt256Type = { @@ -75,7 +131,7 @@ static PyObject *b_UInt256FromHex(PyObject *cls, PyObject *args, PyObject *kwds) 0, /* tp_iternext */ b_UInt256Methods, /* tp_methods */ 0, /* tp_members */ - 0, /* tp_getset */ + b_UInt256GetSetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ diff --git a/python/tests.py b/python/tests.py index f0be0f7bf..6a5150adb 100644 --- a/python/tests.py +++ b/python/tests.py @@ -1,3 +1,4 @@ +import hashlib import unittest import breadwallet @@ -11,6 +12,17 @@ def test_from_hex_256(self): u256 = breadwallet.UInt256.from_hex('0000000000000000000000000000000000000000000000000000000000000001') self.assertNotEqual(u256, None) + def test_from_hex_and_back_256(self): + h = '0000000000000000000000000000000000000000000000000000000000000001' + u256 = breadwallet.UInt256.from_hex(h) + self.assertEqual(h, u256.hex) + + def test_from_hash_and_back_256(self): + h = hashlib.sha256() + h.update('test123'.encode('utf8')) + u256 = breadwallet.UInt256.from_hash(h) + self.assertEqual(h.hexdigest(), u256.hex) + def test_allocation(self): u512 = breadwallet.UInt512() self.assertNotEqual(u512, None) From b8a276724e3bdbb5e2b5f66ee2130c363de2ac6a Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Mon, 1 Aug 2016 16:19:38 -0700 Subject: [PATCH 11/16] BRKey.sign --- python/bindings.c | 49 +++++++++++++++++++++++++++++++++++++++++++---- python/tests.py | 14 ++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/python/bindings.c b/python/bindings.c index bcfeb29d4..209ad900f 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -521,11 +521,11 @@ static PyObject *b_KeyFromBitID(PyObject *cls, PyObject *args, PyObject *kwds) { // parse args static char *kwlist[] = { "seed", "index", "endpoint", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwds, "Ois", kwlist, &seedObj, &index, &endpoint)) { - return NULL; + return NULL; } if (!PyObject_IsInstance(seedObj, (PyObject *)&b_UInt512Type)) { - PyErr_SetString(PyExc_TypeError, "seed must be an instance of UInt512"); - return NULL; + PyErr_SetString(PyExc_TypeError, "seed must be an instance of UInt512"); + return NULL; } b_UInt512 *seed = (b_UInt512 *)seedObj; @@ -537,11 +537,50 @@ static PyObject *b_KeyFromBitID(PyObject *cls, PyObject *args, PyObject *kwds) { result = PyObject_CallFunction(cls, ""); // set value if (result != NULL) { - ((b_Key *)result)->ob_fval = key; + ((b_Key *)result)->ob_fval = key; } return result; } +static PyObject *b_KeySign(b_Key *self, PyObject *args, PyObject *kwds) { + PyObject *message; + static char *kwlist[] = { "message", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &message)) { + return NULL; + } + if (message == NULL || message == Py_None) { + PyErr_SetString(PyExc_ValueError, "message must not be NULL"); + return NULL; + } + int msgLen; + UInt256 *toSign; + PyObject *msgBytes; + if (PyObject_IsInstance(message, (PyObject *)&PyBytes_Type)) { + msgBytes = message; + } else if (PyCallable_Check(PyObject_GetAttrString(message, "digest"))) { + msgBytes = PyObject_CallMethod(message, "digest", ""); + if (!PyObject_IsInstance(msgBytes, (PyObject *)&PyBytes_Type)) { + PyErr_SetString(PyExc_TypeError, "digest() must return a bytes object"); + return NULL; + } + } else { + PyErr_SetString(PyExc_TypeError, "message must be either a bytes object with 32 bytes or a hash object " + "with a digest() method"); + return NULL; + } + if (PyBytes_Size(msgBytes) != 32) { + PyErr_SetString(PyExc_ValueError, "must be 32 bytes of data (a UInt256)"); + return NULL; + } + + toSign = (UInt256 *)PyBytes_AsString(msgBytes); + uint8_t sig[72]; + size_t sigLen = BRKeySign(self->ob_fval, sig, sizeof(sig), *toSign); + PyObject *ret = PyBytes_FromStringAndSize((const char *)&sig, sigLen); + + return ret; +} + static PyObject *b_KeyPrivKeyIsValid(PyObject *cls, PyObject *args, PyObject *kwds) { PyObject *result = Py_False; char *pk; @@ -703,6 +742,8 @@ static PyMethodDef b_KeyMethods[] = { {"privkey_is_valid", (PyCFunction)b_KeyPrivKeyIsValid, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), "determine whether or not a serialized private key is valid"}, /* Instance Methods */ + {"sign", (PyCFunction)b_KeySign, (METH_VARARGS | METH_KEYWORDS), + "sign a bytes or an object with a digest() method"}, {NULL} }; diff --git a/python/tests.py b/python/tests.py index 6a5150adb..026ab4be1 100644 --- a/python/tests.py +++ b/python/tests.py @@ -87,6 +87,20 @@ def test_set_privkey(self): k.privkey = 'S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy' self.assertEqual(k.address, '1CciesT23BNionJeXrbxmjc7ywfiyM4oLW') + def test_sign(self): + k = breadwallet.Key() + k.secret = breadwallet.UInt256.from_hex('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140') + message = "Equations are more important to me, because politics is for the present, but an equation is something for eternity." + h = hashlib.sha256() + h.update(message.encode('utf8')) + + sig = k.sign(h.digest()) # can take either a hash or a bytes object + sig2 = k.sign(h) + sig3 = b'\x30\x44\x02\x20\x54\xc4\xa3\x3c\x64\x23\xd6\x89\x37\x8f\x16\x0a\x7f\xf8\xb6\x13\x30\x44\x4a\xbb\x58\xfb\x47\x0f\x96\xea\x16\xd9\x9d\x4a\x2f\xed\x02\x20\x07\x08\x23\x04\x41\x0e\xfa\x6b\x29\x43\x11\x1b\x6a\x4e\x0a\xaa\x7b\x7d\xb5\x5a\x07\xe9\x86\x1d\x1f\xb3\xcb\x1f\x42\x10\x44\xa5' + self.assertEqual(len(sig), len(sig3)) + self.assertEqual(sig, sig3) + self.assertEqual(sig, sig2) + class TransactionTests(unittest.TestCase): def test_allocation(self): From c0c4e034fbe66c0417acd8ed55f747da300c5bae Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Mon, 1 Aug 2016 16:44:22 -0700 Subject: [PATCH 12/16] BRKey.verify --- python/bindings.c | 40 ++++++++++++++++++++++++++++++++++++++-- python/tests.py | 11 +++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/python/bindings.c b/python/bindings.c index 209ad900f..05d3d8146 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -552,7 +552,6 @@ static PyObject *b_KeySign(b_Key *self, PyObject *args, PyObject *kwds) { PyErr_SetString(PyExc_ValueError, "message must not be NULL"); return NULL; } - int msgLen; UInt256 *toSign; PyObject *msgBytes; if (PyObject_IsInstance(message, (PyObject *)&PyBytes_Type)) { @@ -577,10 +576,45 @@ static PyObject *b_KeySign(b_Key *self, PyObject *args, PyObject *kwds) { uint8_t sig[72]; size_t sigLen = BRKeySign(self->ob_fval, sig, sizeof(sig), *toSign); PyObject *ret = PyBytes_FromStringAndSize((const char *)&sig, sigLen); - + return ret; } +static PyObject *b_KeyVerify(b_Key *self, PyObject *args, PyObject *kwds) { + PyObject *message; + PyObject *signature; + static char *kwlist[] = { "message", "signature", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OS", kwlist, &message, &signature)) { + return NULL; + } + if (message == NULL || message == Py_None) { + PyErr_SetString(PyExc_ValueError, "message must not be NULL"); + return NULL; + } + UInt256 *toSign; + PyObject *msgBytes; + if (PyObject_IsInstance(message, (PyObject *)&PyBytes_Type)) { + msgBytes = message; + } else if (PyCallable_Check(PyObject_GetAttrString(message, "digest"))) { + msgBytes = PyObject_CallMethod(message, "digest", ""); + if (!PyObject_IsInstance(msgBytes, (PyObject *)&PyBytes_Type)) { + PyErr_SetString(PyExc_TypeError, "digest() must return a bytes object"); + return NULL; + } + } else { + PyErr_SetString(PyExc_TypeError, "message must be either a bytes object with 32 bytes or a hash object " + "with a digest() method"); + return NULL; + } + if (PyBytes_Size(msgBytes) != 32) { + PyErr_SetString(PyExc_ValueError, "must be 32 bytes of data (a UInt256)"); + return NULL; + } + toSign = (UInt256 *)PyBytes_AsString(msgBytes); + int valid = BRKeyVerify(self->ob_fval, *toSign, PyBytes_AsString(signature), PyBytes_Size(signature)); + return valid ? Py_True : Py_False; +} + static PyObject *b_KeyPrivKeyIsValid(PyObject *cls, PyObject *args, PyObject *kwds) { PyObject *result = Py_False; char *pk; @@ -744,6 +778,8 @@ static PyMethodDef b_KeyMethods[] = { /* Instance Methods */ {"sign", (PyCFunction)b_KeySign, (METH_VARARGS | METH_KEYWORDS), "sign a bytes or an object with a digest() method"}, + {"verify", (PyCFunction)b_KeyVerify, (METH_VARARGS | METH_KEYWORDS), + "verify the message signature was made by this key"}, {NULL} }; diff --git a/python/tests.py b/python/tests.py index 026ab4be1..24ed8b04d 100644 --- a/python/tests.py +++ b/python/tests.py @@ -101,6 +101,17 @@ def test_sign(self): self.assertEqual(sig, sig3) self.assertEqual(sig, sig2) + def test_verify(self): + k = breadwallet.Key() + k.secret = breadwallet.UInt256.from_hex('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140') + message = "Equations are more important to me, because politics is for the present, but an equation is something for eternity." + h = hashlib.sha256() + h.update(message.encode('utf8')) + + sig = k.sign(h) + self.assertTrue(k.verify(h, sig)) + self.assertTrue(k.verify(h.digest(), sig)) # works with both hash objects or bytes objects + class TransactionTests(unittest.TestCase): def test_allocation(self): From e1f35ee77d3f2bbfbf6ca75b27235d6d6b005f4d Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Mon, 1 Aug 2016 17:52:20 -0700 Subject: [PATCH 13/16] random seckey test --- python/tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/python/tests.py b/python/tests.py index 24ed8b04d..672fdcca8 100644 --- a/python/tests.py +++ b/python/tests.py @@ -1,5 +1,8 @@ +import binascii import hashlib +import os import unittest + import breadwallet @@ -87,6 +90,14 @@ def test_set_privkey(self): k.privkey = 'S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy' self.assertEqual(k.address, '1CciesT23BNionJeXrbxmjc7ywfiyM4oLW') + def test_set_seckey_random(self): + i = os.urandom(32) + h = binascii.hexlify(i).decode('utf8') + u256 = breadwallet.UInt256.from_hex(h) + k = breadwallet.Key() + k.secret = u256 + self.assertEqual(k.secret.hex, h) + def test_sign(self): k = breadwallet.Key() k.secret = breadwallet.UInt256.from_hex('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140') From c297d5faa8a3206e972bdb176647b4c48aa76f7b Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Mon, 1 Aug 2016 18:21:31 -0700 Subject: [PATCH 14/16] add Key.sign_compact and Key.recover_pubkey --- python/bindings.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++ python/tests.py | 10 ++++++ 2 files changed, 96 insertions(+) diff --git a/python/bindings.c b/python/bindings.c index 05d3d8146..6fee7dd3e 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -542,6 +542,51 @@ static PyObject *b_KeyFromBitID(PyObject *cls, PyObject *args, PyObject *kwds) { return result; } +static PyObject *b_KeyRecoverPubKey(PyObject *cls, PyObject *args, PyObject *kwds) { + b_Key *result = NULL; + PyObject *message = NULL; + PyObject *signature = NULL; + static char *kwlist[] = { "message", "signature", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OS", kwlist, &message, &signature)) { + return NULL; + } + if (message == NULL || message == Py_None) { + PyErr_SetString(PyExc_ValueError, "message must not be NULL"); + return NULL; + } + UInt256 *toSign; + PyObject *msgBytes; + if (PyObject_IsInstance(message, (PyObject *)&PyBytes_Type)) { + msgBytes = message; + } else if (PyCallable_Check(PyObject_GetAttrString(message, "digest"))) { + msgBytes = PyObject_CallMethod(message, "digest", ""); + if (!PyObject_IsInstance(msgBytes, (PyObject *)&PyBytes_Type)) { + PyErr_SetString(PyExc_TypeError, "digest() must return a bytes object"); + return NULL; + } + } else { + PyErr_SetString(PyExc_TypeError, "message must be either a bytes object with 32 bytes or a hash object " + "with a digest() method"); + return NULL; + } + if (PyBytes_Size(msgBytes) != 32) { + PyErr_SetString(PyExc_ValueError, "must be 32 bytes of data (a UInt256)"); + return NULL; + } + toSign = (UInt256 *)PyBytes_AsString(msgBytes); + BRKey *key = calloc(1, sizeof(BRKey)); + int keyLen = BRKeyRecoverPubKey(key, PyBytes_AsString(signature), PyBytes_Size(signature), *toSign); + if (!keyLen) { + return Py_BuildValue(""); // unable to recover, return None + } + + result = (b_Key *)PyObject_CallFunction(cls, ""); + if (result != NULL) { + result->ob_fval = key; + } + return (PyObject *)result; +} + static PyObject *b_KeySign(b_Key *self, PyObject *args, PyObject *kwds) { PyObject *message; static char *kwlist[] = { "message", NULL }; @@ -615,6 +660,43 @@ static PyObject *b_KeyVerify(b_Key *self, PyObject *args, PyObject *kwds) { return valid ? Py_True : Py_False; } +static PyObject *b_KeyCompactSign(b_Key *self, PyObject *args, PyObject *kwds) { + PyObject *message; + static char *kwlist[] = { "message", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &message)) { + return NULL; + } + if (message == NULL || message == Py_None) { + PyErr_SetString(PyExc_ValueError, "message must not be NULL"); + return NULL; + } + UInt256 *toSign; + PyObject *msgBytes; + if (PyObject_IsInstance(message, (PyObject *)&PyBytes_Type)) { + msgBytes = message; + } else if (PyCallable_Check(PyObject_GetAttrString(message, "digest"))) { + msgBytes = PyObject_CallMethod(message, "digest", ""); + if (!PyObject_IsInstance(msgBytes, (PyObject *)&PyBytes_Type)) { + PyErr_SetString(PyExc_TypeError, "digest() must return a bytes object"); + return NULL; + } + } else { + PyErr_SetString(PyExc_TypeError, "message must be either a bytes object with 32 bytes or a hash object " + "with a digest() method"); + return NULL; + } + if (PyBytes_Size(msgBytes) != 32) { + PyErr_SetString(PyExc_ValueError, "must be 32 bytes of data (a UInt256)"); + return NULL; + } + + toSign = (UInt256 *)PyBytes_AsString(msgBytes); + uint8_t sig[72]; + size_t sigLen = BRKeyCompactSign(self->ob_fval, sig, sizeof(sig), *toSign); + PyObject *ret = PyBytes_FromStringAndSize((const char *)&sig, sigLen); + return ret; +} + static PyObject *b_KeyPrivKeyIsValid(PyObject *cls, PyObject *args, PyObject *kwds) { PyObject *result = Py_False; char *pk; @@ -773,11 +855,15 @@ static PyMethodDef b_KeyMethods[] = { /* Class Methods */ {"from_bitid", (PyCFunction)b_KeyFromBitID, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), "generate a bitid Key from a seed and some bitid parameters"}, + {"recover_pubkey", (PyCFunction)b_KeyRecoverPubKey, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), + "recover a public key from a compact signature"}, {"privkey_is_valid", (PyCFunction)b_KeyPrivKeyIsValid, (METH_VARARGS | METH_KEYWORDS | METH_CLASS), "determine whether or not a serialized private key is valid"}, /* Instance Methods */ {"sign", (PyCFunction)b_KeySign, (METH_VARARGS | METH_KEYWORDS), "sign a bytes or an object with a digest() method"}, + {"sign_compact", (PyCFunction)b_KeyCompactSign, (METH_VARARGS | METH_KEYWORDS), + "sign some bytes (or an object with the digest() method) using the compact signature format"}, {"verify", (PyCFunction)b_KeyVerify, (METH_VARARGS | METH_KEYWORDS), "verify the message signature was made by this key"}, {NULL} diff --git a/python/tests.py b/python/tests.py index 672fdcca8..68031e2a9 100644 --- a/python/tests.py +++ b/python/tests.py @@ -112,6 +112,16 @@ def test_sign(self): self.assertEqual(sig, sig3) self.assertEqual(sig, sig2) + def test_sign_compact_and_recover_pubkey(self): + k = breadwallet.Key() + k.secret = breadwallet.UInt256.from_hex('0000000000000000000000000000000000000000000000000000000000000001') + message = "foo" + h = hashlib.sha256() + h.update(message.encode('utf8')) + sig = k.sign_compact(h) + k2 = breadwallet.Key.recover_pubkey(h, sig) + self.assertEqual(k.pubkey, k2.pubkey) + def test_verify(self): k = breadwallet.Key() k.secret = breadwallet.UInt256.from_hex('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140') From a1fcf550e0b9460e9f6d165319ded84adcdf3d16 Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Tue, 2 Aug 2016 16:04:28 -0700 Subject: [PATCH 15/16] Wallet.(balance/total_sent/total_received/fee_per_kb) --- python/bindings.c | 39 +++++++++++++++++++++++++++++++++++++++ python/tests.py | 21 +++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/python/bindings.c b/python/bindings.c index 6fee7dd3e..868b394f6 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -1178,7 +1178,46 @@ PyObject *b_WalletGetChangeAddress(b_Wallet *self, void *closure) { return (PyObject *)addrObj; } +PyObject *b_WalletGetBalance(b_Wallet *self, void *closure) { + return Py_BuildValue("K", BRWalletBalance(self->ob_fval)); +} + +PyObject *b_WalletGetTotalSent(b_Wallet *self, void *closure) { + return Py_BuildValue("K", BRWalletTotalSent(self->ob_fval)); +} + +PyObject *b_WalletGetTotalReceived(b_Wallet *self, void *closure) { + return Py_BuildValue("K", BRWalletTotalReceived(self->ob_fval)); +} + +PyObject *b_WalletGetFeePerKB(b_Wallet *self, void *closure) { + return Py_BuildValue("K", BRWalletFeePerKb(self->ob_fval)); +} + +int b_WalletSetFeePerKB(b_Wallet *self, PyObject *value, void *closure) { + if (!PyLong_Check(value)) { + PyErr_SetString(PyExc_TypeError, "fee per kb must be a number"); + return -1;; + } + BRWalletSetFeePerKb(self->ob_fval, PyLong_AsUnsignedLongLong(value)); + return 0; +} + static PyGetSetDef b_WalletGetSetters[] = { + // props + {"balance", (getter)b_WalletGetBalance, NULL, + "gets the total balance int he wallet, not including transactions known to be invalid", + NULL}, + {"total_sent", (getter)b_WalletGetTotalSent, NULL, + "gets the total amount sent not including change addresses", + NULL}, + {"total_received", (getter)b_WalletGetTotalReceived, NULL, + "gets the total amount received not including change addresses", + NULL}, + {"fee_per_kb", (getter)b_WalletGetFeePerKB, (setter)b_WalletSetFeePerKB, + "fee-per-kb size to use when creating a transaction, in satoshis", + NULL}, + // callbacks {"on_balance_changed", (getter)b_WalletGetBalanceChanged, (setter)b_WalletSetBalanceChanged, "callback fired when sync is started", diff --git a/python/tests.py b/python/tests.py index 68031e2a9..c2d8a976c 100644 --- a/python/tests.py +++ b/python/tests.py @@ -229,3 +229,24 @@ def test_get_change_address(self): wallet = self._get_wallet() self.assertNotEqual(wallet.change_address, None) self.assertEqual(wallet.change_address, wallet.change_address) + + def test_get_balance(self): + wallet = self._get_wallet() + self.assertEqual(wallet.balance, 0) + + def test_get_total_sent(self): + wallet = self._get_wallet() + self.assertEqual(wallet.total_sent, 0) + + def test_get_total_received(self): + wallet = self._get_wallet() + self.assertEqual(wallet.total_received, 0) + + def test_get_fee(self): + wallet = self._get_wallet() + self.assertGreater(wallet.fee_per_kb, 1) + + def test_set_fee(self): + wallet = self._get_wallet() + wallet.fee_per_kb = 123 + self.assertEqual(wallet.fee_per_kb, 123) From 9b5670d49e528f35fbc506dc83f4077b0cfa5833 Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Fri, 20 Jan 2017 15:06:59 -0800 Subject: [PATCH 16/16] update BRKeyRecoverPubKey callsite --- .gitignore | 4 +++- python/bindings.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 77efe9de8..10c407ebb 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ *.xcworkspace *.xcodeproj -.idea/ \ No newline at end of file +.idea/ +tags +.env diff --git a/python/bindings.c b/python/bindings.c index 868b394f6..2d00f7a85 100644 --- a/python/bindings.c +++ b/python/bindings.c @@ -575,7 +575,7 @@ static PyObject *b_KeyRecoverPubKey(PyObject *cls, PyObject *args, PyObject *kwd } toSign = (UInt256 *)PyBytes_AsString(msgBytes); BRKey *key = calloc(1, sizeof(BRKey)); - int keyLen = BRKeyRecoverPubKey(key, PyBytes_AsString(signature), PyBytes_Size(signature), *toSign); + int keyLen = BRKeyRecoverPubKey(key, *toSign, PyBytes_AsString(signature), PyBytes_Size(signature)); if (!keyLen) { return Py_BuildValue(""); // unable to recover, return None }