Update python bindings (#145)

This commit is contained in:
Justin Traglia 2023-02-20 10:00:13 -06:00 committed by GitHub
parent 46d529cf00
commit daa5f79fe3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 249 additions and 58 deletions

View File

@ -51,37 +51,98 @@ static PyObject* blob_to_kzg_commitment_wrap(PyObject *self, PyObject *args) {
return out;
}
static PyObject* compute_aggregate_kzg_proof_wrap(PyObject *self, PyObject *args) {
PyObject *b, *s;
static PyObject* compute_kzg_proof_wrap(PyObject *self, PyObject *args) {
PyObject *b, *z, *s;
if (!PyArg_UnpackTuple(args, "compute_aggregate_kzg_proof", 2, 2, &b, &s) ||
if (!PyArg_UnpackTuple(args, "compute_kzg_proof_wrap", 3, 3, &b, &z, &s) ||
!PyBytes_Check(b) ||
!PyBytes_Check(z) ||
!PyCapsule_IsValid(s, "KZGSettings"))
return PyErr_Format(PyExc_ValueError, "expected bytes, trusted setup");
return PyErr_Format(PyExc_ValueError, "expected bytes, bytes, trusted setup");
Py_ssize_t n = PyBytes_Size(b);
if (n % BYTES_PER_BLOB != 0)
return PyErr_Format(PyExc_ValueError, "expected blobs to be a multiple of BYTES_PER_BLOB bytes");
n = n / BYTES_PER_BLOB;
if (PyBytes_Size(b) != BYTES_PER_BLOB)
return PyErr_Format(PyExc_ValueError, "expected blobs to be BYTES_PER_BLOB bytes");
if (PyBytes_Size(z) != BYTES_PER_FIELD_ELEMENT)
return PyErr_Format(PyExc_ValueError, "expected blobs to be BYTES_PER_FIELD_ELEMENT bytes");
PyObject *out = PyBytes_FromStringAndSize(NULL, BYTES_PER_PROOF);
if (out == NULL) return PyErr_NoMemory();
Blob *blobs = (Blob *)PyBytes_AsString(b);
KZGProof *k = (KZGProof *)PyBytes_AsString(out);
if (compute_aggregate_kzg_proof(k, blobs, n,
PyCapsule_GetPointer(s, "KZGSettings")) != C_KZG_OK) {
Blob *blob = (Blob *)PyBytes_AsString(b);
Bytes32 *z_bytes = (Bytes32 *)PyBytes_AsString(z);
KZGProof *proof = (KZGProof *)PyBytes_AsString(out);
if (compute_kzg_proof(proof, blob, z_bytes, PyCapsule_GetPointer(s, "KZGSettings")) != C_KZG_OK) {
Py_DECREF(out);
return PyErr_Format(PyExc_RuntimeError, "compute_aggregate_kzg_proof failed");
return PyErr_Format(PyExc_RuntimeError, "compute_kzg_proof failed");
}
return out;
}
static PyObject* verify_aggregate_kzg_proof_wrap(PyObject *self, PyObject *args) {
static PyObject* compute_blob_kzg_proof_wrap(PyObject *self, PyObject *args) {
PyObject *b, *s;
if (!PyArg_UnpackTuple(args, "compute_kzg_proof_wrap", 2, 2, &b, &s) ||
!PyBytes_Check(b) ||
!PyCapsule_IsValid(s, "KZGSettings"))
return PyErr_Format(PyExc_ValueError, "expected bytes, trusted setup");
if (PyBytes_Size(b) != BYTES_PER_BLOB)
return PyErr_Format(PyExc_ValueError, "expected blobs to be BYTES_PER_BLOB bytes");
PyObject *out = PyBytes_FromStringAndSize(NULL, BYTES_PER_PROOF);
if (out == NULL) return PyErr_NoMemory();
Blob *blob = (Blob *)PyBytes_AsString(b);
KZGProof *proof = (KZGProof *)PyBytes_AsString(out);
if (compute_blob_kzg_proof(proof, blob, PyCapsule_GetPointer(s, "KZGSettings")) != C_KZG_OK) {
Py_DECREF(out);
return PyErr_Format(PyExc_RuntimeError, "compute_blob_kzg_proof failed");
}
return out;
}
static PyObject* verify_kzg_proof_wrap(PyObject *self, PyObject *args) {
PyObject *c, *z, *y, *p, *s;
if (!PyArg_UnpackTuple(args, "verify_kzg_proof", 5, 5, &c, &z, &y, &p, &s) ||
!PyBytes_Check(c) ||
!PyBytes_Check(z) ||
!PyBytes_Check(y) ||
!PyBytes_Check(p) ||
!PyCapsule_IsValid(s, "KZGSettings"))
return PyErr_Format(PyExc_ValueError,
"expected bytes, bytes, bytes, bytes, trusted setup");
if (PyBytes_Size(c) != BYTES_PER_COMMITMENT)
return PyErr_Format(PyExc_ValueError, "expected commitment to be BYTES_PER_COMMITMENT bytes");
if (PyBytes_Size(z) != BYTES_PER_FIELD_ELEMENT)
return PyErr_Format(PyExc_ValueError, "expected z to be BYTES_PER_FIELD_ELEMENT bytes");
if (PyBytes_Size(y) != BYTES_PER_FIELD_ELEMENT)
return PyErr_Format(PyExc_ValueError, "expected y to be BYTES_PER_FIELD_ELEMENT bytes");
if (PyBytes_Size(p) != BYTES_PER_PROOF)
return PyErr_Format(PyExc_ValueError, "expected proof to be BYTES_PER_PROOF bytes");
const Bytes48 *commitment_bytes = (Bytes48 *)PyBytes_AsString(c);
const Bytes32 *z_bytes = (Bytes32 *)PyBytes_AsString(z);
const Bytes32 *y_bytes = (Bytes32 *)PyBytes_AsString(y);
const Bytes48 *proof_bytes = (Bytes48 *)PyBytes_AsString(p);
bool ok;
if (verify_kzg_proof(&ok,
commitment_bytes, z_bytes, y_bytes, proof_bytes,
PyCapsule_GetPointer(s, "KZGSettings")) != C_KZG_OK) {
return PyErr_Format(PyExc_RuntimeError, "verify_kzg_proof failed");
}
if (ok) Py_RETURN_TRUE; else Py_RETURN_FALSE;
}
static PyObject* verify_blob_kzg_proof_wrap(PyObject *self, PyObject *args) {
PyObject *b, *c, *p, *s;
if (!PyArg_UnpackTuple(args, "verify_aggregate_kzg_proof", 4, 4, &b, &c, &p, &s) ||
if (!PyArg_UnpackTuple(args, "verify_blob_kzg_proof", 4, 4, &b, &c, &p, &s) ||
!PyBytes_Check(b) ||
!PyBytes_Check(c) ||
!PyBytes_Check(p) ||
@ -89,41 +150,79 @@ static PyObject* verify_aggregate_kzg_proof_wrap(PyObject *self, PyObject *args)
return PyErr_Format(PyExc_ValueError,
"expected bytes, bytes, bytes, trusted setup");
if (PyBytes_Size(b) != BYTES_PER_BLOB)
return PyErr_Format(PyExc_ValueError, "expected blob to be BYTES_PER_BLOB bytes");
if (PyBytes_Size(c) != BYTES_PER_COMMITMENT)
return PyErr_Format(PyExc_ValueError, "expected commitment to be BYTES_PER_COMMITMENT bytes");
if (PyBytes_Size(p) != BYTES_PER_PROOF)
return PyErr_Format(PyExc_ValueError, "expected proof to be BYTES_PER_PROOF bytes");
Py_ssize_t n = PyBytes_Size(b);
if (n % BYTES_PER_BLOB != 0)
return PyErr_Format(PyExc_ValueError, "expected blobs to be a multiple of BYTES_PER_BLOB bytes");
n = n / BYTES_PER_BLOB;
Py_ssize_t m = PyBytes_Size(c);
if (m % BYTES_PER_COMMITMENT != 0)
return PyErr_Format(PyExc_ValueError, "expected commitments to be a multiple of BYTES_PER_COMMITMENT bytes");
m = m / BYTES_PER_COMMITMENT;
if (m != n)
return PyErr_Format(PyExc_ValueError, "expected same number of commitments as polynomials");
const Blob* blobs = (Blob *)PyBytes_AsString(b);
const Blob *blob_bytes = (Blob *)PyBytes_AsString(b);
const Bytes48 *commitment_bytes = (Bytes48 *)PyBytes_AsString(c);
const Bytes48 *proof_bytes = (Bytes48 *)PyBytes_AsString(p);
const Bytes48 *commitments_bytes = (Bytes48 *)PyBytes_AsString(c);
bool out;
if (verify_aggregate_kzg_proof(&out,
blobs, commitments_bytes, n, proof_bytes,
bool ok;
if (verify_blob_kzg_proof(&ok,
blob_bytes, commitment_bytes, proof_bytes,
PyCapsule_GetPointer(s, "KZGSettings")) != C_KZG_OK) {
return PyErr_Format(PyExc_RuntimeError, "verify_aggregate_kzg_proof failed");
return PyErr_Format(PyExc_RuntimeError, "verify_blob_kzg_proof failed");
}
if (out) Py_RETURN_TRUE; else Py_RETURN_FALSE;
if (ok) Py_RETURN_TRUE; else Py_RETURN_FALSE;
}
static PyObject* verify_blob_kzg_proof_batch_wrap(PyObject *self, PyObject *args) {
PyObject *b, *c, *p, *s;
if (!PyArg_UnpackTuple(args, "verify_blob_kzg_proof_batch", 4, 4, &b, &c, &p, &s) ||
!PyBytes_Check(b) ||
!PyBytes_Check(c) ||
!PyBytes_Check(p) ||
!PyCapsule_IsValid(s, "KZGSettings"))
return PyErr_Format(PyExc_ValueError,
"expected bytes, bytes, bytes, trusted setup");
Py_ssize_t blobs_count = PyBytes_Size(b);
if (blobs_count % BYTES_PER_BLOB != 0)
return PyErr_Format(PyExc_ValueError, "expected blobs to be a multiple of BYTES_PER_BLOB bytes");
blobs_count = blobs_count / BYTES_PER_BLOB;
Py_ssize_t commitments_count = PyBytes_Size(c);
if (commitments_count % BYTES_PER_COMMITMENT != 0)
return PyErr_Format(PyExc_ValueError, "expected commitments to be a multiple of BYTES_PER_COMMITMENT bytes");
commitments_count = commitments_count / BYTES_PER_COMMITMENT;
Py_ssize_t proofs_count = PyBytes_Size(p);
if (proofs_count % BYTES_PER_PROOF != 0)
return PyErr_Format(PyExc_ValueError, "expected blobs to be a multiple of BYTES_PER_PROOF bytes");
proofs_count = proofs_count / BYTES_PER_PROOF;
if (blobs_count != commitments_count || blobs_count != proofs_count) {
return PyErr_Format(PyExc_ValueError, "expected same number of blobs/commitments/proofs");
}
const Blob *blobs_bytes = (Blob *)PyBytes_AsString(b);
const Bytes48 *commitments_bytes = (Bytes48 *)PyBytes_AsString(c);
const Bytes48 *proofs_bytes = (Bytes48 *)PyBytes_AsString(p);
bool ok;
if (verify_blob_kzg_proof_batch(&ok,
blobs_bytes, commitments_bytes, proofs_bytes, blobs_count,
PyCapsule_GetPointer(s, "KZGSettings")) != C_KZG_OK) {
return PyErr_Format(PyExc_RuntimeError, "verify_blob_kzg_proof_batch failed");
}
if (ok) Py_RETURN_TRUE; else Py_RETURN_FALSE;
}
static PyMethodDef ckzgmethods[] = {
{"load_trusted_setup", load_trusted_setup_wrap, METH_VARARGS, "Load trusted setup from file path"},
{"blob_to_kzg_commitment", blob_to_kzg_commitment_wrap, METH_VARARGS, "Create a commitment from a blob"},
{"compute_aggregate_kzg_proof", compute_aggregate_kzg_proof_wrap, METH_VARARGS, "Compute aggregate KZG proof"},
{"verify_aggregate_kzg_proof", verify_aggregate_kzg_proof_wrap, METH_VARARGS, "Verify aggregate KZG proof"},
{"compute_kzg_proof", compute_kzg_proof_wrap, METH_VARARGS, "Compute a proof for a blob/field"},
{"compute_blob_kzg_proof", compute_blob_kzg_proof_wrap, METH_VARARGS, "Compute a proof for a blob"},
{"verify_kzg_proof", verify_kzg_proof_wrap, METH_VARARGS, "Verify a proof for the given inputs"},
{"verify_blob_kzg_proof", verify_blob_kzg_proof_wrap, METH_VARARGS, "Verify a blob/commitment/proof combo"},
{"verify_blob_kzg_proof_batch", verify_blob_kzg_proof_batch_wrap, METH_VARARGS, "Verify multiple blob/commitment/proof combos"},
{NULL, NULL, 0, NULL}
};

View File

@ -1,36 +1,128 @@
import glob
from os.path import join
from os.path import isfile
import ckzg
import random
# Commit to a few random blobs
###############################################################################
# Constants
###############################################################################
BLOB_SIZE = 4096
MAX_BLOBS_PER_BLOCK = 16
blob_to_kzg_commitment_tests = "../../tests/blob_to_kzg_commitment/*"
compute_kzg_proof_tests = "../../tests/compute_kzg_proof/*"
compute_blob_kzg_proof_tests = "../../tests/compute_blob_kzg_proof/*"
verify_kzg_proof_tests = "../../tests/verify_kzg_proof/*"
verify_blob_kzg_proof_tests = "../../tests/verify_blob_kzg_proof/*"
verify_blob_kzg_proof_batch_tests = "../../tests/verify_blob_kzg_proof_batch/*"
blobs = [
# use zero final bytes to easily ensure the encodings are valid
b''.join([b''.join([random.randbytes(31), bytes(1)]) for _ in range(BLOB_SIZE)])
for _ in range(3)
]
###############################################################################
# Helper Functions
###############################################################################
ts = ckzg.load_trusted_setup("../../src/trusted_setup.txt")
def get_blob(path):
with open(path, "r") as f:
return bytes.fromhex(f.read())
kzg_commitments = b''.join([ckzg.blob_to_kzg_commitment(blob, ts) for blob in blobs])
def get_bytes32(path):
with open(path, "r") as f:
return bytes.fromhex(f.read())
# Compute proof for these blobs
def get_bytes48(path):
with open(path, "r") as f:
return bytes.fromhex(f.read())
blobs_bytes = b''.join(blobs)
def get_boolean(path):
with open(path, "r") as f:
return "true" in f.read()
proof = ckzg.compute_aggregate_kzg_proof(blobs_bytes, ts)
###############################################################################
# Tests
###############################################################################
# Verify proof
def test_blob_to_kzg_commitment(ts):
for test in glob.glob(blob_to_kzg_commitment_tests):
blob = get_blob(join(test, "blob.txt"))
try:
commitment = ckzg.blob_to_kzg_commitment(blob, ts)
expected_commitment = get_bytes48(join(test, "commitment.txt"))
assert commitment == expected_commitment
except:
assert not isfile(join(test, "commitment.txt"))
assert ckzg.verify_aggregate_kzg_proof(blobs_bytes, kzg_commitments, proof, ts), 'verify failed'
def test_compute_kzg_proof(ts):
for test in glob.glob(compute_kzg_proof_tests):
blob = get_blob(join(test, "blob.txt"))
input_point = get_bytes32(join(test, "input_point.txt"))
try:
proof = ckzg.compute_kzg_proof(blob, input_point, ts)
expected_proof = get_bytes48(join(test, "proof.txt"))
assert proof == expected_proof
except:
assert not isfile(join(test, "proof.txt"))
# Verification fails at wrong value
def test_compute_blob_kzg_proof(ts):
for test in glob.glob(compute_blob_kzg_proof_tests):
blob = get_blob(join(test, "blob.txt"))
try:
proof = ckzg.compute_blob_kzg_proof(blob, ts)
expected_proof = get_bytes48(join(test, "proof.txt"))
assert proof == expected_proof
except:
assert not isfile(join(test, "proof.txt"))
other = b'x' if not blobs_bytes.startswith(b'x') else b'y'
other_bytes = other + blobs_bytes[1:]
def test_verify_kzg_proof(ts):
for test in glob.glob(verify_kzg_proof_tests):
commitment = get_bytes48(join(test, "commitment.txt"))
input_point = get_bytes32(join(test, "input_point.txt"))
claimed_value = get_bytes32(join(test, "claimed_value.txt"))
proof = get_bytes48(join(test, "proof.txt"))
try:
ok = ckzg.verify_kzg_proof(commitment, input_point, claimed_value, proof, ts)
expected_ok = get_boolean(join(test, "ok.txt"))
assert ok == expected_ok
except:
assert not isfile(join(test, "ok.txt"))
assert not ckzg.verify_aggregate_kzg_proof(other_bytes, kzg_commitments, proof, ts), 'verify succeeded incorrectly'
def test_verify_blob_kzg_proof(ts):
for test in glob.glob(verify_blob_kzg_proof_tests):
blob = get_bytes32(join(test, "blob.txt"))
commitment = get_bytes48(join(test, "commitment.txt"))
proof = get_bytes48(join(test, "proof.txt"))
try:
ok = ckzg.verify_blob_kzg_proof(blob, commitment, proof, ts)
expected_ok = get_boolean(join(test, "ok.txt"))
assert ok == expected_ok
except:
assert not isfile(join(test, "ok.txt"))
print('tests passed')
def test_verify_blob_kzg_proof_batch(ts):
for test in glob.glob(verify_blob_kzg_proof_batch_tests):
blob_files = sorted(glob.glob(join(test, "blobs/*")))
blobs = b"".join([get_blob(b) for b in blob_files])
commitment_files = sorted(glob.glob(join(test, "commitments/*")))
commitments = b"".join([get_bytes48(c) for c in commitment_files])
proof_files = sorted(glob.glob(join(test, "proofs/*")))
proofs = b"".join([get_bytes48(p) for p in proof_files])
try:
ok = ckzg.verify_blob_kzg_proof_batch(blobs, commitments, proofs, ts)
expected_ok = get_boolean(join(test, "ok.txt"))
assert ok == expected_ok
except:
assert not isfile(join(test, "ok.txt"))
###############################################################################
# Main Logic
###############################################################################
if __name__ == "__main__":
ts = ckzg.load_trusted_setup("../../src/trusted_setup.txt")
test_blob_to_kzg_commitment(ts)
test_compute_kzg_proof(ts)
test_compute_blob_kzg_proof(ts)
test_verify_kzg_proof(ts)
test_verify_blob_kzg_proof(ts)
test_verify_blob_kzg_proof_batch(ts)
print('tests passed')