def recoverable(ot_key, tracking_key): """Takes a onetime key and a tracking key and returns if the private onetime key is recoverable. >>> longterm_key = keygen() >>> longterm_pub = longterm_key[1] >>> ot_key = generate_ot_key(longterm_pub) >>> trackingkey = key_to_trackingkey(longterm_key) >>> recoverable(ot_key, trackingkey) True >>> wrong_lt_key = keygen() >>> wrong_lt_pub = wrong_lt_key[1] >>> wrong_trackingkey = key_to_trackingkey(wrong_lt_key) >>> recoverable(ot_key, wrong_trackingkey) False """ (ot_pubkey, dh_key) = ot_key (ot_pubkey, dh_key) = (binascii.unhexlify(ot_pubkey), binascii.unhexlify(dh_key)) (a, B) = tracking_key hashval = ietf_ed25519.sha512( ietf_ed25519.point_compress( ietf_ed25519.point_mul( int(a, 16), ietf_ed25519.point_decompress(dh_key)))) first = ietf_ed25519.point_mul( int.from_bytes(hashval, "little"), ietf_ed25519.G) second = ietf_ed25519.point_decompress(binascii.unhexlify(B)) key_ = ietf_ed25519.point_compress( ietf_ed25519.point_add(first, second)) return ot_pubkey == key_
def verify(public_keys, msg, signature): """Checks if a LWW-ringsignature is valid. >>> (sec, pub) = keygen() >>> public_keys = [pub] >>> m = "some message" >>> sig = ringsign(public_keys, sec, m) >>> verify(public_keys, m, sig) True >>> public_keys = [keygen()[1] for i in range(3)] >>> (sec, pub) = keygen() >>> public_keys.append(pub) >>> m = "some message" >>> sig = ringsign(public_keys, sec, m) >>> verify(public_keys, m, sig) True >>> verify(public_keys, "wrong message", sig) False """ # Typechecks for sig assert isinstance(signature, tuple) assert isinstance(signature[1], int) assert isinstance(signature[2], list) # The public_keys need to be sorted, since we need an ordering for # verification. list.sort(public_keys) ringsize = len(public_keys) L = [0 for i in range(ringsize)] R = [0 for i in range(ringsize)] c = [0 for i in range(ringsize + 1)] (key_image, c[0], s) = signature # check if the keyimage is in the subgroup generated by G key_image = binascii.unhexlify(key_image) if not ietf_ed25519.point_equal( ietf_ed25519.point_mul(ietf_ed25519.q, ietf_ed25519.point_decompress(key_image)), (0, 1, 1, 0)): return False # recover all the c for i in range(ringsize): L[i] = ietf_ed25519.point_add( ietf_ed25519.point_mul(s[i], ietf_ed25519.G), ietf_ed25519.point_mul( c[i], ietf_ed25519.point_decompress( binascii.unhexlify(public_keys[i])))) L[i] = ietf_ed25519.point_compress(L[i]) R[i] = ietf_ed25519.point_add( ietf_ed25519.point_mul( s[i], H_P( ietf_ed25519.point_decompress( binascii.unhexlify(public_keys[i])))), ietf_ed25519.point_mul(c[i], ietf_ed25519.point_decompress(key_image))) R[i] = ietf_ed25519.point_compress(R[i]) c[i + 1] = sha512_modp(str.encode(msg) + L[i] + R[i]) # Is the ring closed correctly? return c[ringsize] == c[0]
def keyimage(secret): """Returns the keyimage of the secret key. This can be used for linking two signatures. """ secret_as_int = int(secret, 16) public = ietf_ed25519.point_mul(secret_as_int, ietf_ed25519.G) solution = ietf_ed25519.point_mul(secret_as_int, H_P(public)) return ietf_ed25519.point_compress(solution)
def recover_sec_key(ot_key, keypair): """Takes a onetime public key and a keypair and recovers the onetime secret key if possible. >>> longterm_key = keygen() >>> longterm_pub = longterm_key[1] >>> ot_key = generate_ot_key(longterm_pub) >>> (ot_pubkey, dh_key) = ot_key >>> trackingkey = key_to_trackingkey(longterm_key) >>> recovered_sec_key = recover_sec_key(ot_key, longterm_key) >>> lww_signature.secret_to_public(recovered_sec_key) == ot_pubkey True """ (ot_pubkey, dh_key) = ot_key ((a, b), (A, B)) = keypair first = ietf_ed25519.sha512( ietf_ed25519.point_compress( ietf_ed25519.point_mul( int(a, 16), ietf_ed25519.point_decompress(binascii.unhexlify(dh_key))))) first = int.from_bytes(first, "little") second = int(b, 16) ot_sec_key = first + second ot_sec_key = hex(ot_sec_key) # Check correctness assert(ot_pubkey == lww_signature.secret_to_public(ot_sec_key)) return ot_sec_key
def H_P(P): """Hash function returning a point. Note that the intuitive idea of computing hash(P)*G is insecure, since this is not indifferentiable from a random oracle. We know the discrete log and in the case of the LWW-signature we ca cancel the anonymity property. Instead we compute hash(P)+i for increasing i until it is a valid point in the subgroup generated by G (not only on the curve). This is comparable to the algorithm in Ben Lynn's thesis, where he computes H(P) and tries to find a point on the curve with this x-coordinate. If he cannot solve for the -coordinate, he increases H(P) by 1. >>> Neutral = [0,1,1,0] >>> H_P(Neutral) # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS (4645..., 3154...) """ hash = hashlib.sha256(ietf_ed25519.point_compress(P)).digest() point = ietf_ed25519.point_decompress(hash) while not point or not ietf_ed25519.point_equal( ietf_ed25519.point_mul(ietf_ed25519.q, point), (0, 1, 1, 0)): # repeat if the results of the hash s not a valid point. # or if the point is not in the subgroup generated by g. # i.e., check that the order of the point is the order of g num = int.from_bytes(hash, "little") + 1 hash = int.to_bytes(num, 32, "little") point = ietf_ed25519.point_decompress(hash) return point
def secret_to_public(secret): """Given a secret key this returns the public key. >>> (sec, pub) = keygen() >>> secret_to_public(sec) == pub True """ secret_as_int = int(secret, 16) return binascii.hexlify( ietf_ed25519.point_compress( ietf_ed25519.point_mul(secret_as_int, ietf_ed25519.G))).decode()
def generate_ot_key(public_key, nonce=None): """Derives a onetime publickey. The corresponding onetime private key can only be recovered with knowledge of the private_key of the public_key. More exactly the output is (ot_pubkey, dh_key), where dh_key and the private key corresponding to the public_key in the input are needed to compute ot_privkey, the onetime private key. The nonce should be unique modulo the group order and can be reused for different public keys. It will be generated automaticaly if not set. >>> import os >>> nonce = os.urandom(32) >>> (_, pk) = keygen() >>> ot_key = generate_ot_key(pk, nonce) >>> (_, pk) = keygen() >>> ot_key = generate_ot_key(pk) """ if not nonce: nonce = os.urandom(32) (A, B) = public_key (A, B) = (binascii.unhexlify(A), binascii.unhexlify(B)) nonce = int.from_bytes(nonce, "little") % ietf_ed25519.q hashval = ietf_ed25519.sha512( ietf_ed25519.point_compress( ietf_ed25519.point_mul( nonce, ietf_ed25519.point_decompress(A)))) first = ietf_ed25519.point_mul( int.from_bytes(hashval, "little"), ietf_ed25519.G) second = ietf_ed25519.point_decompress(B) ot_pubkey = ietf_ed25519.point_add(first, second) dh_key = ietf_ed25519.point_mul(nonce, ietf_ed25519.G) # In the Cryptonote Whitepaper dh_key is called R # Next we compress the points ot_pubkey = binascii.hexlify(ietf_ed25519.point_compress(ot_pubkey)).decode() dh_key = binascii.hexlify(ietf_ed25519.point_compress(dh_key)).decode() return (ot_pubkey, dh_key)
def ringsign(public_keys, secret, msg): """Returns a LWW-ringsignature. public_keys also contains the public key of the signer. We return the signature. Since we need always the same ordering on the public keys, we will order them before signing. >>> public_keys = [keygen()[1] for i in range(3)] >>> (sec, pub) = keygen() >>> public_keys.append(pub) >>> m = "some message" >>> signature = ringsign(public_keys, sec, m) """ pub = secret_to_public(secret) list.sort(public_keys) # we sort the list, since the order is important signindex = public_keys.index(pub) key_image = keyimage(secret) secret_as_int = int(secret, 16) # signindex is the index where we start computing the signature # aka the index where we know the corresponding privkey ringsize = len(public_keys) L = [0 for i in range(ringsize)] R = [0 for i in range(ringsize)] c = [0 for i in range(ringsize)] alpha = random.SystemRandom().randrange(ietf_ed25519.p) s = [ random.SystemRandom().randrange(ietf_ed25519.q) for i in range(ringsize) ] L[signindex] = ietf_ed25519.point_mul(alpha, ietf_ed25519.G) L[signindex] = ietf_ed25519.point_compress(L[signindex]) R[signindex] = ietf_ed25519.point_mul( alpha, H_P( ietf_ed25519.point_decompress( binascii.unhexlify(public_keys[signindex])))) R[signindex] = ietf_ed25519.point_compress(R[signindex]) c[(signindex + 1) % ringsize] = sha512_modp(str.encode(msg) + L[signindex] + R[signindex]) # iterate through the other indices for i in [ j % ringsize for j in range(signindex + 1, signindex + ringsize) ]: L[i] = ietf_ed25519.point_add( ietf_ed25519.point_mul(s[i], ietf_ed25519.G), ietf_ed25519.point_mul( c[i], ietf_ed25519.point_decompress( binascii.unhexlify(public_keys[i])))) L[i] = ietf_ed25519.point_compress(L[i]) R[i] = ietf_ed25519.point_add( ietf_ed25519.point_mul( s[i], H_P( ietf_ed25519.point_decompress( binascii.unhexlify(public_keys[i])))), ietf_ed25519.point_mul(c[i], ietf_ed25519.point_decompress(key_image))) R[i] = ietf_ed25519.point_compress(R[i]) c[(i + 1) % ringsize] = sha512_modp(str.encode(msg) + L[i] + R[i]) # stitch the ring together s[signindex] = (alpha - c[signindex] * secret_as_int) % ietf_ed25519.q signature = (binascii.hexlify(key_image).decode(), c[0], s) # validate if the computed signature is correct assert (verify(public_keys, msg, signature)) return signature