You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@milagro.apache.org by sa...@apache.org on 2020/02/18 14:37:31 UTC
[incubator-milagro-MPC] 01/06: Add wrapper for Schnorr Proof
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 <sa...@yahoo.it>
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()