diff --git a/.gitignore b/.gitignore index 6e9679f..2f9aae3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,8 +15,7 @@ inc/blst_aux.h* .vscode/ *.json .clang-format -*bindings/*/_*.so -*bindings/*/lib*.so +*bindings/*/*.so *bindings/python/ckzg_swig.py *bindings/python/ckzg.py *bindings/C#/ckzg_swig.cs diff --git a/min-bindings/python/Makefile b/min-bindings/python/Makefile index cdb2c93..c797f6b 100644 --- a/min-bindings/python/Makefile +++ b/min-bindings/python/Makefile @@ -1,11 +1,17 @@ INCLUDE_DIRS = .. ../../min-src ../../inc INCLUDE_PY = $(shell python -c 'import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))') +test: tests.py ckzg.so + python $< + test_swig: tests_swig.py _ckzg_swig.so python $< _ckzg_swig.so: c_kzg_4844_wrap.c ../../min-src/c_kzg_4844.o ../../lib/libblst.a clang -O -Wall -shared -fPIC -Wl,-Bsymbolic -I${INCLUDE_PY} ${addprefix -I,${INCLUDE_DIRS}} -o $@ $^ +ckzg.so: ckzg.c ../../min-src/c_kzg_4844.o ../../lib/libblst.a + clang -O -Wall -shared -fPIC -Wl,-Bsymbolic -I${INCLUDE_PY} ${addprefix -I,${INCLUDE_DIRS}} -o $@ $^ + c_kzg_4844_wrap.c ckzg_swig.py: ../c_kzg_4844.swg swig -DSWIGWORDSIZE64 -O -Wall -python -outcurrentdir $< diff --git a/min-bindings/python/ckzg.c b/min-bindings/python/ckzg.c new file mode 100644 index 0000000..6ca14db --- /dev/null +++ b/min-bindings/python/ckzg.c @@ -0,0 +1,56 @@ +#define PY_SSIZE_T_CLEAN +#include +#include "c_kzg_4844.h" + +static void free_capsule(PyObject *c) { + free(PyCapsule_GetPointer(c, PyCapsule_GetName(c))); +} + +static PyObject* bytes_to_bls_field_wrap(PyObject *self, PyObject *args) { + PyBytesObject *pybytes; + + if (!PyArg_ParseTuple(args, "S", &pybytes) || + PyBytes_Size((PyObject*)pybytes) != 32) + return PyErr_Format(PyExc_ValueError, "expected 32 bytes"); + + BLSFieldElement *out = (BLSFieldElement*)malloc(sizeof(BLSFieldElement)); + + if (out == NULL) return PyErr_NoMemory(); + + bytes_to_bls_field(out, (const uint8_t*)PyBytes_AsString((PyObject*)pybytes)); + + return PyCapsule_New(out, "BLSFieldElement", free_capsule); +} + +static PyObject* uint64s_from_BLSFieldElement_wrap(PyObject *self, PyObject *args) { + PyObject *c; + if (!PyArg_UnpackTuple(args, "uint64s_from_BLSFieldElement", 1, 1, &c) || + !PyCapsule_IsValid(c, "BLSFieldElement")) + return PyErr_Format(PyExc_ValueError, "expected a BLSFieldElement capsule"); + uint64_t out[4]; + uint64s_from_BLSFieldElement(out, PyCapsule_GetPointer(c, PyCapsule_GetName(c))); + return PyTuple_Pack(4, + PyLong_FromUnsignedLong(out[0]), + PyLong_FromUnsignedLong(out[1]), + PyLong_FromUnsignedLong(out[2]), + PyLong_FromUnsignedLong(out[3])); +} + +static PyMethodDef ckzgmethods[] = { + {"uint64s_from_BLSFieldElement", uint64s_from_BLSFieldElement_wrap, METH_VARARGS, "Convert a field element to a 4-tuple of uint64s"}, + {"bytes_to_bls_field", bytes_to_bls_field_wrap, METH_VARARGS, "Convert 32 bytes to a field element"}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef ckzg = { + PyModuleDef_HEAD_INIT, + "ckzg", + NULL, + -1, + ckzgmethods +}; + +PyMODINIT_FUNC PyInit_ckzg(void) +{ + return PyModule_Create(&ckzg); +} diff --git a/min-bindings/python/tests.py b/min-bindings/python/tests.py new file mode 100644 index 0000000..602db86 --- /dev/null +++ b/min-bindings/python/tests.py @@ -0,0 +1,25 @@ +import atexit +import ckzg +import random +import ssz + +def int_from_fr(fr): + digits = ckzg.uint64s_from_BLSFieldElement(fr) + res, mult = 0, 1 + for x in digits: + res += mult * x + mult *= 2**64 + return res + +# Simple test of bytes_to_bls_field + +bs = (329).to_bytes(32, "little") +fr = ckzg.bytes_to_bls_field(bs) +assert int_from_fr(fr) == 329 + +print('Tests passed') + +def cleanup(): + pass + +atexit.register(cleanup)