Add wrapper for Schnorr Proof
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 @@
 
 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()