This is an automated email from the ASF dual-hosted git repository. sandreoli pushed a commit to branch issue7-schnorr-python-wrapper in repository https://gitbox.apache.org/repos/asf/incubator-milagro-MPC.git
commit 743bbb45238686810eef6dc8e4743a733524cd0d Author: Samuele Andreoli <[email protected]> AuthorDate: Mon Feb 17 16:17:43 2020 +0000 Add wrapper for Schnorr Proof --- python/CMakeLists.txt | 31 +++-- python/amcl_mpc.py | 0 python/amcl_schnorr.py | 352 +++++++++++++++++++++++++++++++++++++++++++++++++ python/test_schnorr.py | 144 ++++++++++++++++++++ 4 files changed, 515 insertions(+), 12 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index c2cdbd6..4da1c7f 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -17,23 +17,30 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) include(PythonSiteDirs) -install(FILES amcl_mpc.py DESTINATION ${PYTHON_SITE_PACKAGES}) +install(FILES amcl_mpc.py DESTINATION ${PYTHON_SITE_PACKAGES}) +install(FILES amcl_schnorr.py DESTINATION ${PYTHON_SITE_PACKAGES}) -file(COPY amcl_mpc.py DESTINATION "${PROJECT_BINARY_DIR}/python/") -file(COPY test_mta.py DESTINATION "${PROJECT_BINARY_DIR}/python/") -file(COPY test_r.py DESTINATION "${PROJECT_BINARY_DIR}/python/") -file(COPY test_s.py DESTINATION "${PROJECT_BINARY_DIR}/python/") -file(COPY test_ecdsa.py DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY amcl_mpc.py DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY amcl_schnorr.py DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY test_mta.py DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY test_r.py DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY test_s.py DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY test_ecdsa.py DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY test_schnorr.py DESTINATION "${PROJECT_BINARY_DIR}/python/") file(COPY "${PROJECT_SOURCE_DIR}/testVectors/mpc/MTA.json" DESTINATION "${PROJECT_BINARY_DIR}/python/") -file(COPY "${PROJECT_SOURCE_DIR}/testVectors/mpc/R.json" DESTINATION "${PROJECT_BINARY_DIR}/python/") -file(COPY "${PROJECT_SOURCE_DIR}/testVectors/mpc/S.json" DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY "${PROJECT_SOURCE_DIR}/testVectors/mpc/R.json" DESTINATION "${PROJECT_BINARY_DIR}/python/") +file(COPY "${PROJECT_SOURCE_DIR}/testVectors/mpc/S.json" DESTINATION "${PROJECT_BINARY_DIR}/python/") + +file(GLOB SCHNORR_TV "${PROJECT_SOURCE_DIR}/testVectors/schnorr/*.json") +file(COPY ${SCHNORR_TV} DESTINATION "${PROJECT_BINARY_DIR}/python/schnorr/") if(NOT CMAKE_BUILD_TYPE STREQUAL "ASan") - add_test(test_python_mpc_mta python3 test_mta.py) - add_test(test_python_mpc_r python3 test_r.py) - add_test(test_python_mpc_s python3 test_s.py) - add_test(test_python_mpc_ecdsa python3 test_ecdsa.py) + add_test(test_python_mpc_mta python3 test_mta.py) + add_test(test_python_mpc_r python3 test_r.py) + add_test(test_python_mpc_s python3 test_s.py) + add_test(test_python_mpc_ecdsa python3 test_ecdsa.py) + add_test(test_python_mpc_schnorr python3 test_schnorr.py) endif(NOT CMAKE_BUILD_TYPE STREQUAL "ASan") # Set the LD_LIBRARY_PATH or equivalent to the libraries can be loaded when diff --git a/python/amcl_mpc.py b/python/amcl_mpc.py old mode 100755 new mode 100644 diff --git a/python/amcl_schnorr.py b/python/amcl_schnorr.py new file mode 100644 index 0000000..07c671d --- /dev/null +++ b/python/amcl_schnorr.py @@ -0,0 +1,352 @@ +#!/usr/bin/env python3 + +""" +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +""" + +""" + +This module use cffi to access the c functions in the amcl_mpc library. + +""" +import cffi +import platform +import os + +import gc + +ffi = cffi.FFI() +ffi.cdef(""" +typedef long unsigned int BIG_512_60[9]; +typedef long unsigned int BIG_1024_58[18]; + +typedef struct { +unsigned int ira[21]; /* random number... */ +int rndptr; /* ...array & pointer */ +unsigned int borrow; +int pool_ptr; +char pool[32]; /* random pool */ +} csprng; + +typedef struct +{ + int len; + int max; + char *val; +} octet; + +extern void RAND_seed(csprng *R,int n,char *b); +extern void RAND_clean(csprng *R); +extern void OCT_clear(octet *O); + +extern void SCHNORR_random_challenge(csprng *RNG, octet *E); + +extern void SCHNORR_commit(csprng *RNG, octet *R, octet *C); +extern void SCHNORR_challenge(octet *V, octet *C, octet *E); +extern void SCHNORR_prove(octet *R, octet *E, octet *X, octet *P); +extern int SCHNORR_verify(octet *V, octet *C, octet *E, octet *P); +""") + +if (platform.system() == 'Windows'): + libamcl_mpc = ffi.dlopen("libamcl_mpc.dll") + libamcl_curve_secp256k1 = ffi.dlopen("libamcl_curve_SECP256K1.dll") + libamcl_core = ffi.dlopen("libamcl_core.dll") +elif (platform.system() == 'Darwin'): + libamcl_mpc = ffi.dlopen("libamcl_mpc.dylib") + libamcl_curve_secp256k1 = ffi.dlopen("libamcl_curve_SECP256K1.dylib") + libamcl_core = ffi.dlopen("libamcl_core.dylib") +else: + libamcl_mpc = ffi.dlopen("libamcl_mpc.so") + libamcl_curve_secp256k1 = ffi.dlopen("libamcl_curve_SECP256K1.so") + libamcl_core = ffi.dlopen("libamcl_core.so") + +# Constants +EGS = 32 +EFS = 32 +PTS = EFS + 1 + +OK = 0 +FAIL = 51 +INVALID_ECP = 52 + +def to_str(octet_value): + """Converts an octet type into a string + + Add all the values in an octet into an array. + + Args:: + + octet_value. An octet pointer type + + Returns:: + + String + + Raises: + Exception + """ + i = 0 + val = [] + while i < octet_value.len: + val.append(octet_value.val[i]) + i = i + 1 + out = b'' + for x in val: + out = out + x + return out + + +def make_octet(length, value=None): + """Generates an octet pointer + + Generates an empty octet or one filled with the input value + + Args:: + + length: Length of empty octet + value: Data to assign to octet + + Returns:: + + oct_ptr: octet pointer + val: data associated with octet to prevent garbage collection + + Raises: + + """ + oct_ptr = ffi.new("octet*") + if value: + val = ffi.new("char [%s]" % len(value), value) + oct_ptr.val = val + oct_ptr.max = len(value) + oct_ptr.len = len(value) + else: + val = ffi.new("char []", length) + oct_ptr.val = val + oct_ptr.max = length + oct_ptr.len = 0 + return oct_ptr, val + + +def create_csprng(seed): + """Make a Cryptographically secure pseudo-random number generator instance + + Make a Cryptographically secure pseudo-random number generator instance + + Args:: + + seed: random seed value + + Returns:: + + rng: Pointer to cryptographically secure pseudo-random number generator instance + + Raises: + + """ + seed_val = ffi.new("char [%s]" % len(seed), seed) + seed_len = len(seed) + + # random number generator + rng = ffi.new('csprng*') + libamcl_core.RAND_seed(rng, seed_len, seed_val) + + return rng + + +def kill_csprng(rng): + """Kill a random number generator + + Deletes all internal state + + Args:: + + rng: Pointer to cryptographically secure pseudo-random number generator instance + + Returns:: + + Raises: + + """ + libamcl_core.RAND_clean(rng) + + return 0 + + +def random_challenge(rng): + """Generate a random challenge for the Schnorr's Proof + + Generates a random value e in [0, .., q] suitable as a + random challenge for Schnorr's Proofs + + Args:: + + rng: Pointer to cryptographically secure pseudo-random + number generator instance + + Returns:: + + e: Random challenge + + Raises: + + """ + + e, e_val = make_octet(EGS) + _ = e_val # Suppress warning + + libamcl_mpc.SCHNORR_random_challenge(rng, e) + + return to_str(e) + + +def commit(rng, r=None): + """Generate a commitment for the Schnorr's proof + + Generates a random value r in [0, .., q] and masks it + with a DLOG + + Args:: + + rng : Pointer to cryptographically secure pseudo-random + number generator instance + r : Deterministic value for r + + Returns:: + + r : Generated random value + C : Public ECP of the DLOG. r.G + + Raises: + + """ + if r is None: + r_oct, r_val = make_octet(EGS) + else: + r_oct, r_val = make_octet(None, r) + rng = ffi.NULL + + C, C_val = make_octet(PTS) + _ = r_val, C_val # Suppress warning + + + libamcl_mpc.SCHNORR_commit(rng, r_oct, C) + + r = to_str(r_oct) + + # Clean memory + libamcl_core.OCT_clear(r_oct) + + return r, to_str(C) + + +def challenge(V, C): + """Generate a deterministic challenge for the Schnorr's Proof + + Generates a deterministic value r in [0, .., q] suitable as a + random challenge for Schnorr's Proofs. It is generated as + described in RFC8235#section-3.3 + + Args:: + + V : Public ECP of the DLOG. V = x.G + C : Commitment for the Schnorr's Proof + + Returns:: + + e : Deterministic challenge + + Raises: + + """ + V_oct, V_val = make_octet(None, V) + C_oct, C_val = make_octet(None, C) + _ = V_val, C_val # Suppress warning + + e, e_val = make_octet(EGS) + _ = e_val # Suppress warning + + libamcl_mpc.SCHNORR_challenge(V_oct, C_oct, e) + + return to_str(e) + + +def prove(r, e, x): + """Generate proof + + Generates the proof for the Schnorr protocol. + P = r - e * x mod q + + Args:: + + r : Secret value used in the commitment + e : Challenge for the Schnorr's protocol + x : Secret exponent of the DLOG V = x.G + + Returns:: + + p : Proof for the Schnorr's protocol + + Raises: + + """ + r_oct, r_val = make_octet(None, r) + e_oct, e_val = make_octet(None, e) + x_oct, x_val = make_octet(None, x) + _ = r_val, e_val, x_val # Suppress warning + + p, p_val = make_octet(EGS) + _ = p_val # Suppress warning + + libamcl_mpc.SCHNORR_prove(r_oct, e_oct, x_oct, p) + + # Clean memory + libamcl_core.OCT_clear(r_oct) + libamcl_core.OCT_clear(x_oct) + + return to_str(p) + + +def verify(V, C, e, p): + """Verify a Schnorr's proof + + Check that C = p.G + e.V + + Args:: + + V : Public ECP of the DLOG. V = x.G + C : Commitment for the Schnorr's Proof + e : Challenge for the Schnorr's Proof + p : Proof + + Returns:: + + ec : OK if the verification is successful, or an error code + + Raises: + + """ + V_oct, V_val = make_octet(None, V) + C_oct, C_val = make_octet(None, C) + e_oct, e_val = make_octet(None, e) + p_oct, p_val = make_octet(None, p) + _ = V_val, C_val, e_val, p_val # Suppress warning + + ec = libamcl_mpc.SCHNORR_verify(V_oct, C_oct, e_oct, p_oct) + + return ec diff --git a/python/test_schnorr.py b/python/test_schnorr.py new file mode 100755 index 0000000..1392acb --- /dev/null +++ b/python/test_schnorr.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +""" +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +""" + +import unittest +import json +import amcl_schnorr + + +class TestCommit(unittest.TestCase): + """ Test Schnorr's Proof Commitment """ + + def setUp(self): + # Deterministic PRNG for testing purposes + seed_hex = "78d0fb6705ce77dee47d03eb5b9c5d30" + seed = bytes.fromhex(seed_hex) + self.rng = amcl_schnorr.create_csprng(seed) + + r_hex = "e8a04212cc20520429d854a5bb02b51b4281e663c90a4a4ec0b505171f9bc26a" + C_hex = "028fe6cafe6e6cef6c47be31cb449faa9495d22a6cb47e057b91c97d807882c439" + self.r_golden = bytes.fromhex(r_hex) + self.C_golden = bytes.fromhex(C_hex) + + with open("schnorr/commit.json", "r") as f: + self.tv = json.load(f) + + def test_tv(self): + """ Test using test vectors """ + + for vector in self.tv: + r_golden = bytes.fromhex(vector["R"]) + C_golden = bytes.fromhex(vector["C"]) + + r, C = amcl_schnorr.commit(None, r_golden) + + self.assertEqual(r, r_golden) + self.assertEqual(C, C_golden) + + def test_random(self): + """ Test using pseudo random r """ + + r, C = amcl_schnorr.commit(self.rng) + + self.assertEqual(r, self.r_golden) + self.assertEqual(C, self.C_golden) + + +class TestChallenge(unittest.TestCase): + """ Test Schnorr's Proof Deterministic Challenge """ + + def setUp(self): + with open("schnorr/challenge.json", "r") as f: + self.tv = json.load(f) + + def test_tv(self): + """ Test using test vectors """ + + for vector in self.tv: + V = bytes.fromhex(vector["V"]) + C = bytes.fromhex(vector["C"]) + + e_golden = bytes.fromhex(vector["E"]) + + e = amcl_schnorr.challenge(V, C) + + self.assertEqual(e, e_golden) + + +class TestProve(unittest.TestCase): + """ Test Schnorr's Proof Proof generation """ + + def setUp(self): + with open("schnorr/prove.json", "r") as f: + self.tv = json.load(f) + + def test_tv(self): + """ Test using test vectors """ + + for vector in self.tv: + r = bytes.fromhex(vector["R"]) + e = bytes.fromhex(vector["E"]) + x = bytes.fromhex(vector["X"]) + + p_golden = bytes.fromhex(vector["P"]) + + p = amcl_schnorr.prove(r, e, x) + + self.assertEqual(p, p_golden) + + +class TestVerify(unittest.TestCase): + """ Test Schnorr's Proof Verification """ + + def setUp(self): + with open("schnorr/verify.json", "r") as f: + self.tv = json.load(f) + + def test_tv(self): + """ Test using test vectors """ + + for vector in self.tv: + V = bytes.fromhex(vector["V"]) + C = bytes.fromhex(vector["C"]) + e = bytes.fromhex(vector["E"]) + p = bytes.fromhex(vector["P"]) + + ec = amcl_schnorr.verify(V, C, e, p) + + self.assertEqual(ec, amcl_schnorr.OK) + + def test_error_code(self): + """ Test error codes are propagated """ + + vector = self.tv[0] + + V = bytes.fromhex(vector["C"]) + C = bytes.fromhex(vector["V"]) + e = bytes.fromhex(vector["E"]) + p = bytes.fromhex(vector["P"]) + + ec = amcl_schnorr.verify(V, C, e, p) + + self.assertEqual(ec, amcl_schnorr.FAIL) + + +if __name__ == '__main__': + unittest.main()
