PyBitmessage-2021-04-27/src/pyelliptic/eccblind.py

374 lines
12 KiB
Python

"""
ECC blind signature functionality based on
"An Efficient Blind Signature Scheme
Based on the Elliptic CurveDiscrete Logarithm Problem" by Morteza Nikooghadama
<mnikooghadam@sbu.ac.ir> and Ali Zakerolhosseini <a-zaker@sbu.ac.ir>,
http://www.isecure-journal.com/article_39171_47f9ec605dd3918c2793565ec21fcd7a.pdf
"""
# variable names are based on the math in the paper, so they don't conform
# to PEP8
import time
from hashlib import sha256
from struct import pack, unpack
from .openssl import OpenSSL
# first byte in serialisation can contain data
Y_BIT = 0x01
COMPRESSED_BIT = 0x02
# formats
BIGNUM = '!32s'
EC = '!B32s'
PUBKEY = '!BB33s'
class Expiration(object):
"""Expiration of pubkey"""
@staticmethod
def deserialize(val):
"""Create an object out of int"""
year = ((val & 0xF0) >> 4) + 2020
month = val & 0x0F
assert month < 12
return Expiration(year, month)
def __init__(self, year, month):
assert isinstance(year, int)
assert year > 2019 and year < 2036
assert isinstance(month, int)
assert month < 12
self.year = year
self.month = month
self.exp = year + month / 12.0
def serialize(self):
"""Make int out of object"""
return ((self.year - 2020) << 4) + self.month
def verify(self):
"""Check if the pubkey has expired"""
now = time.gmtime()
return self.exp >= now.tm_year + (now.tm_mon - 1) / 12.0
class Value(object):
"""Value of a pubkey"""
@staticmethod
def deserialize(val):
"""Make object out of int"""
return Value(val)
def __init__(self, value=0xFF):
assert isinstance(value, int)
self.value = value
def serialize(self):
"""Make int out of object"""
return self.value & 0xFF
def verify(self, value):
"""Verify against supplied value"""
return value <= self.value
class ECCBlind(object): # pylint: disable=too-many-instance-attributes
"""
Class for ECC blind signature functionality
"""
# init
k = None
R = None
F = None
d = None
Q = None
a = None
b = None
c = None
binv = None
r = None
m = None
m_ = None
s_ = None
signature = None
exp = None
val = None
def ec_get_random(self):
"""
Random integer within the EC order
"""
randomnum = OpenSSL.BN_new()
OpenSSL.BN_rand(randomnum, OpenSSL.BN_num_bits(self.n), 0, 0)
return randomnum
def ec_invert(self, a):
"""
ECC inversion
"""
inverse = OpenSSL.BN_mod_inverse(0, a, self.n, self.ctx)
return inverse
def ec_gen_keypair(self):
"""
Generate an ECC keypair
We're using compressed keys
"""
d = self.ec_get_random()
Q = OpenSSL.EC_POINT_new(self.group)
OpenSSL.EC_POINT_mul(self.group, Q, d, 0, 0, 0)
return (d, Q)
def ec_Ftor(self, F):
"""
x0 coordinate of F
"""
# F = (x0, y0)
x0 = OpenSSL.BN_new()
y0 = OpenSSL.BN_new()
OpenSSL.EC_POINT_get_affine_coordinates(self.group, F, x0, y0, self.ctx)
OpenSSL.BN_free(y0)
return x0
def _ec_point_serialize(self, point):
"""Make an EC point into a string"""
try:
x = OpenSSL.BN_new()
y = OpenSSL.BN_new()
OpenSSL.EC_POINT_get_affine_coordinates(
self.group, point, x, y, 0)
y_byte = (OpenSSL.BN_is_odd(y) & Y_BIT) | COMPRESSED_BIT
l_ = OpenSSL.BN_num_bytes(self.n)
try:
bx = OpenSSL.malloc(0, l_)
OpenSSL.BN_bn2binpad(x, bx, l_)
out = bx.raw
except AttributeError:
# padding manually
bx = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(x))
OpenSSL.BN_bn2bin(x, bx)
out = bx.raw.rjust(l_, b'\x00')
return pack(EC, y_byte, out)
finally:
OpenSSL.BN_clear_free(x)
OpenSSL.BN_clear_free(y)
def _ec_point_deserialize(self, data):
"""Make a string into an EC point"""
y_bit, x_raw = unpack(EC, data)
x = OpenSSL.BN_bin2bn(x_raw, OpenSSL.BN_num_bytes(self.n), 0)
y_bit &= Y_BIT
retval = OpenSSL.EC_POINT_new(self.group)
OpenSSL.EC_POINT_set_compressed_coordinates(self.group,
retval,
x,
y_bit,
self.ctx)
return retval
def _bn_serialize(self, bn):
"""Make a string out of BigNum"""
l_ = OpenSSL.BN_num_bytes(self.n)
try:
o = OpenSSL.malloc(0, l_)
OpenSSL.BN_bn2binpad(bn, o, l_)
return o.raw
except AttributeError:
o = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(bn))
OpenSSL.BN_bn2bin(bn, o)
return o.raw.rjust(l_, b'\x00')
def _bn_deserialize(self, data):
"""Make a BigNum out of string"""
x = OpenSSL.BN_bin2bn(data, OpenSSL.BN_num_bytes(self.n), 0)
return x
def _init_privkey(self, privkey):
"""Initialise private key out of string/bytes"""
self.d = self._bn_deserialize(privkey)
def privkey(self):
"""Make a private key into a string"""
return pack(BIGNUM, self.d)
def _init_pubkey(self, pubkey):
"""Initialise pubkey out of string/bytes"""
unpacked = unpack(PUBKEY, pubkey)
self.expiration = Expiration.deserialize(unpacked[0])
self.value = Value.deserialize(unpacked[1])
self.Q = self._ec_point_deserialize(unpacked[2])
def pubkey(self):
"""Make a pubkey into a string"""
return pack(PUBKEY, self.expiration.serialize(),
self.value.serialize(),
self._ec_point_serialize(self.Q))
def __init__(self, curve="secp256k1", pubkey=None, privkey=None, # pylint: disable=too-many-arguments
year=2025, month=11, value=0xFF):
self.ctx = OpenSSL.BN_CTX_new()
# ECC group
self.group = OpenSSL.EC_GROUP_new_by_curve_name(
OpenSSL.get_curve(curve))
# Order n
self.n = OpenSSL.BN_new()
OpenSSL.EC_GROUP_get_order(self.group, self.n, self.ctx)
# Generator G
self.G = OpenSSL.EC_GROUP_get0_generator(self.group)
# Identity O (infinity)
self.iO = OpenSSL.EC_POINT_new(self.group)
OpenSSL.EC_POINT_set_to_infinity(self.group, self.iO)
if privkey:
assert pubkey
# load both pubkey and privkey from bytes
self._init_privkey(privkey)
self._init_pubkey(pubkey)
elif pubkey:
# load pubkey from bytes
self._init_pubkey(pubkey)
else:
# new keypair
self.d, self.Q = self.ec_gen_keypair()
if not year or not month:
now = time.gmtime()
if now.tm_mon == 12:
self.expiration = Expiration(now.tm_year + 1, 1)
else:
self.expiration = Expiration(now.tm_year, now.tm_mon + 1)
else:
self.expiration = Expiration(year, month)
self.value = Value(value)
def __del__(self):
OpenSSL.BN_free(self.n)
OpenSSL.BN_CTX_free(self.ctx)
def signer_init(self):
"""
Init signer
"""
# Signer: Random integer k
self.k = self.ec_get_random()
# R = kG
self.R = OpenSSL.EC_POINT_new(self.group)
OpenSSL.EC_POINT_mul(self.group, self.R, self.k, 0, 0, 0)
return self._ec_point_serialize(self.R)
def create_signing_request(self, R, msg):
"""
Requester creates a new signing request
"""
self.R = self._ec_point_deserialize(R)
msghash = sha256(msg).digest()
# Requester: 3 random blinding factors
self.F = OpenSSL.EC_POINT_new(self.group)
OpenSSL.EC_POINT_set_to_infinity(self.group, self.F)
temp = OpenSSL.EC_POINT_new(self.group)
abinv = OpenSSL.BN_new()
# F != O
while OpenSSL.EC_POINT_cmp(self.group, self.F, self.iO, self.ctx) == 0:
self.a = self.ec_get_random()
self.b = self.ec_get_random()
self.c = self.ec_get_random()
# F = b^-1 * R...
self.binv = self.ec_invert(self.b)
OpenSSL.EC_POINT_mul(self.group, temp, 0, self.R, self.binv, 0)
OpenSSL.EC_POINT_copy(self.F, temp)
# ... + a*b^-1 * Q...
OpenSSL.BN_mul(abinv, self.a, self.binv, self.ctx)
OpenSSL.EC_POINT_mul(self.group, temp, 0, self.Q, abinv, 0)
OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, 0)
# ... + c*G
OpenSSL.EC_POINT_mul(self.group, temp, 0, self.G, self.c, 0)
OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, 0)
# F = (x0, y0)
self.r = self.ec_Ftor(self.F)
# Requester: Blinding (m' = br(m) + a)
self.m = OpenSSL.BN_new()
OpenSSL.BN_bin2bn(msghash, len(msghash), self.m)
self.m_ = OpenSSL.BN_new()
OpenSSL.BN_mod_mul(self.m_, self.b, self.r, self.n, self.ctx)
OpenSSL.BN_mod_mul(self.m_, self.m_, self.m, self.n, self.ctx)
OpenSSL.BN_mod_add(self.m_, self.m_, self.a, self.n, self.ctx)
return self._bn_serialize(self.m_)
def blind_sign(self, m_):
"""
Signer blind-signs the request
"""
self.m_ = self._bn_deserialize(m_)
self.s_ = OpenSSL.BN_new()
OpenSSL.BN_mod_mul(self.s_, self.d, self.m_, self.n, self.ctx)
OpenSSL.BN_mod_add(self.s_, self.s_, self.k, self.n, self.ctx)
OpenSSL.BN_free(self.k)
return self._bn_serialize(self.s_)
def unblind(self, s_):
"""
Requester unblinds the signature
"""
self.s_ = self._bn_deserialize(s_)
s = OpenSSL.BN_new()
OpenSSL.BN_mod_mul(s, self.binv, self.s_, self.n, self.ctx)
OpenSSL.BN_mod_add(s, s, self.c, self.n, self.ctx)
OpenSSL.BN_free(self.a)
OpenSSL.BN_free(self.b)
OpenSSL.BN_free(self.c)
self.signature = (s, self.F)
return self._bn_serialize(s) + self._ec_point_serialize(self.F)
def verify(self, msg, signature, value=1):
"""
Verify signature with certifier's pubkey
"""
# convert msg to BIGNUM
self.m = OpenSSL.BN_new()
msghash = sha256(msg).digest()
OpenSSL.BN_bin2bn(msghash, len(msghash), self.m)
# init
s, self.F = (self._bn_deserialize(signature[0:32]),
self._ec_point_deserialize(signature[32:]))
if self.r is None:
self.r = self.ec_Ftor(self.F)
lhs = OpenSSL.EC_POINT_new(self.group)
rhs = OpenSSL.EC_POINT_new(self.group)
OpenSSL.EC_POINT_mul(self.group, lhs, s, 0, 0, 0)
OpenSSL.EC_POINT_mul(self.group, rhs, 0, self.Q, self.m, 0)
OpenSSL.EC_POINT_mul(self.group, rhs, 0, rhs, self.r, 0)
OpenSSL.EC_POINT_add(self.group, rhs, rhs, self.F, self.ctx)
retval = OpenSSL.EC_POINT_cmp(self.group, lhs, rhs, self.ctx)
if retval == -1:
raise RuntimeError("EC_POINT_cmp returned an error")
elif not self.value.verify(value):
return False
elif not self.expiration.verify():
return False
elif retval != 0:
return False
return True