Source code for ecpy.borromean

# Copyright 2016 Cedric Mesnil <cedric.mesnil@ubinity.com>, Ubinity SAS
#
# Licensed 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.

#python 2 compatibility
from builtins import int,pow

import hashlib
import random
import binascii

from ecpy.curves     import Curve,Point
from ecpy.keys       import ECPublicKey, ECPrivateKey
from ecpy.formatters import decode_sig, encode_sig, list_formats
from ecpy            import ecrand
from ecpy.curves     import ECPyException

def _h(b):
    return binascii.hexlify(b)

def _point_to_bytes(point, compressed = True):
    """ Point serialisation.
    
    Serialization is the standard one:
    
    - O2 x    for even x in compressed form
    - 03 x    for odd x in compressed form
    - 04 x y  for uncompressed form
    
    
    """
    if compressed:
        b = point.x.to_bytes(32,'big')
        if point.y & 1:
            b = b"\x03"+b
        else:
            b = b"\x02"+b
    else:
        b = b"\x04"+point.x.to_bytes(32,'big')+point.y.to_bytes(32,'big')
    return b

def _borromean_hash(m,e,i,j, H):
    """
    All params are bytes.
    
    m: bytes     message
    e: bytes     point
    i: int       ring index
    j: int       secret index
    """
    i = int(i).to_bytes(4,'big')
    j = int(j).to_bytes(4,'big')              
    sha256 = H()
    sha256.update(e)
    sha256.update(m)
    sha256.update(i)
    sha256.update(j)
    d = sha256.digest()
    return d

[docs]class Borromean: """ Borromean Ring signer implementation according to: https://github.com/Blockstream/borromean_paper/blob/master/borromean_draft_0.01_9ade1e49.pdf https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/rangeproof/borromean_impl.h ElementsProject implementation has some tweaks compared to PDF. This implementation is ElementsProject compliant. For now, only secp256k1+sha256 is supported. This constraint will be release soon. Args: fmt (str) : in/out signature format. See :mod:`ecpy.formatters`. IGNORED. """ def __init__(self, fmt="BTUPLE") : self.fmt = fmt self._curve = Curve.get_curve('secp256k1') self._hash = hashlib.sha256
[docs] def sign(self, msg, rings, pv_keys, pv_keys_index): """ Signs a message hash. The public `rings` argument is a tuple of public key array. In other words each element of the ring tuple is an array containing the public keys list of that ring A Private key must be given for each provided ring. For each private key, the corresponding public key is specified by its index in the ring. Exemple: let r1 be the first ring with 2 keys: pu11, pu12 let 21 be the second ring with 3 keys: pu21,pu22,pu23 let say we want to produce a signature with sec12 and sec21 `sign` should be called as:: borromean.sign(m, ([pu11,pu12],[pu21,pu22,pu23]), [sec12, sec21], [1,0]) The return value is a tuple (e0, [s0,s1....]). Each value is encoded as binary (bytes). Args: msg (bytes) : the message hash to sign rings (tuple of (ecpy.keys.ECPublicKey[]): public key rings pv_keys (ecpy.keys.ECPrivateKey[]) : key to use for signing pv_keys_index (int[]) : Returns: (e0, [s0,s1....]) : signature """ #shorcuts G = self._curve.generator order = self._curve.order #set up locals ring_count = len(rings) privkeys = pv_keys pubkeys = [] rsizes = [] for r in rings: pubkeys = pubkeys+r rsizes.append(len(r)) e0 = None s = [None]*len(pubkeys) k = [None]*len(rings) #step2-3 r0 = 0 sha256_e0 = self._hash() for i in range (0,ring_count): k[i] = random.randint(1,order) kiG = k[i]*G j0 = pv_keys_index[i] e_ij = _point_to_bytes(kiG) for j in range(j0+1, rsizes[i]): s[r0+j] = random.randint(1,order) e_ij = _borromean_hash(m,e_ij,i,j, self._hash) e_ij = int.from_bytes(e_ij,'big') sG_eP = s[r0+j]*G + e_ij*pubkeys[r0+j].W e_ij = _point_to_bytes(sG_eP) sha256_e0.update(e_ij) r0 += rsizes[i] sha256_e0.update(m) e0 = sha256_e0.digest() #step 4 r0 = 0 for i in range (0, ring_count): j0 = pv_keys_index[i] e_ij = _borromean_hash(m,e0,i,0, self._hash) e_ij = int.from_bytes(e_ij,'big') for j in range(0, j0): s[r0+j] = random.randint(1,order) sG_eP = s[r0+j]*G + e_ij*pubkeys[r0+j].W e_ij = _borromean_hash(m,_point_to_bytes(sG_eP),i,j+1, self._hash) e_ij = int.from_bytes(e_ij,'big') s[r0+j0] = (k[i]-privkeys[i].d*e_ij)%order r0 += rsizes[i] s = [int(sij).to_bytes(32,'big') for sij in s] return (e0,s)
[docs] def verify(self, msg, sig, rings): """ Verifies a message signature. Args: msg (bytes) : the message hash to verify the signature sig (bytes) : signature to verify rings (key.ECPublicKey): key to use for verifying Returns: boolean : True if signature is verified, False else """ #shortcuts G = self._curve.generator #set up locals ring_count = len(rings) pubkeys = [] rsizes = [] for r in rings: pubkeys = pubkeys+r rsizes.append(len(r)) #verify e0 = sig[0] s = sig[1] sha256_e0 = self._hash() r0 = 0 for i in range (0,ring_count): e_ij = _borromean_hash(m,e0,i,0, self._hash) for j in range(0,rsizes[i]): e_ij = int.from_bytes(e_ij,'big') s_ij = int.from_bytes(s[r0+j],'big') sG_eP = s_ij*G + e_ij*pubkeys[r0+j].W e_ij = _point_to_bytes(sG_eP) if j != rsizes[i]-1: e_ij = _borromean_hash(m,e_ij,i,j+1, self._hash) else: sha256_e0.update(e_ij) r0 += rsizes[i] sha256_e0.update(m) e0x = sha256_e0.digest() return e0 == e0x
if __name__ == "__main__": import sys def strsig(sigma): print("e0: %s"%h(sigma[0])) i=0 for s in sigma[1]: print("s%d: %s"%(i,h(s))) i += 1 try: # # layout: # nrings = 2 # ring 1 has 2 keys # ring 2 has 3 keys # # pubs=[ring1-key1, ring1-key2, # ring2-key1, ring2-key2, ring2-key3] # # k = [ring1-rand, ring2-rand] # sec = [ring1-sec2, ring2-sec1] # rsizes = [2,3] # secidx = [1,0] # # cv = Curve.get_curve('secp256k1') seckey0 = ECPrivateKey(0xf026a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) seckey1 = ECPrivateKey(0xf126a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) seckey2 = ECPrivateKey(0xf226a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) seckey3 = ECPrivateKey(0xf326a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) seckey4 = ECPrivateKey(0xf426a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) seckey5 = ECPrivateKey(0xf526a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) seckey6 = ECPrivateKey(0xf626a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) seckey7 = ECPrivateKey(0xf726a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) seckey8 = ECPrivateKey(0xf826a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) pubkey0 = seckey0.get_public_key() pubkey1 = seckey1.get_public_key() pubkey2 = seckey2.get_public_key() pubkey3 = seckey3.get_public_key() pubkey4 = seckey4.get_public_key() pubkey5 = seckey5.get_public_key() pubkey6 = seckey6.get_public_key() pubkey7 = seckey7.get_public_key() pubkey8 = seckey8.get_public_key() allpubs = [pubkey0, pubkey1, pubkey2, pubkey3, pubkey4, pubkey5,pubkey6, pubkey7] allsecs = [seckey0, seckey1, seckey2, seckey3, seckey4, seckey5,seckey6, seckey7] m = int(0x800102030405060708090a0b0c0d0e0f800102030405060708090a0b0c0d0e0f) m = m.to_bytes(32,'big') borromean = Borromean() for l in range(2,len(allpubs)): pubs = allpubs[:l] secs = allsecs[:l] print("pool has %d key"%len(pubs)) for i in range(1,len(pubs)): pubring1 = pubs[0:i] pubring2 = pubs[i:] secring1 = secs[0:i] secring2 = secs[i:] print("ring1 has %d keys"%len(pubring1)) print("ring2 has %d keys"%len(pubring2)) for s1 in range(0,len(pubring1)): for s2 in range(0,len(pubring2)): print("testing %d %d"%(s1,s2)) pubset = (pubring1 , pubring2) secset = [secring1[s1] , secring2[s2]] secidx = [s1,s2] sigma = borromean.sign(m, pubset, secset, secidx ) assert(borromean.verify( m, sigma, pubset, )) e0 = sigma[0] e0 = e0[1:]+e0[:1] sigma = (e0,sigma[1]) assert(not borromean.verify(m, sigma, pubset)) # ##OK! print("All internal assert OK!") finally: pass