Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """Ed25519 verifiable random function. Important functions are prove,
- proof2hash, and verify.
- Reference used: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-02
- NB: liberties were taken to avoid flaws in the hash_to_curve specification
- documented in Section 5.4.1.1 and to slightly simplify the process over all;
- instead of the computation time for hash_to_curve being variable and determined
- by alpha, opening the process to timing attacks, this implementation does it in
- one try by multiplying the public key by the hash of the input alpha.
- """
- from hashlib import new
- from nacl.signing import SigningKey, VerifyKey
- from nacl.bindings import (
- crypto_core_ed25519_scalar_reduce,
- crypto_scalarmult_ed25519_noclamp,
- crypto_scalarmult_ed25519,
- crypto_scalarmult_ed25519_base_noclamp,
- crypto_core_ed25519_scalar_mul,
- crypto_core_ed25519_scalar_add,
- crypto_core_ed25519_sub,
- )
- from sys import argv
- """Helper functions"""
- def clamp_scalar(scalar: bytes, from_private_key: bool = False) -> bytes:
- """Make a clamped scalar."""
- if type(scalar) is bytes and len(scalar) >= 32:
- x_i = bytearray(scalar[:32])
- elif type(scalar) is SigningKey:
- x_i = bytearray(new('sha512', bytes(scalar)).digest()[:32])
- from_private_key = True
- else:
- raise ValueError('not a SigningKey and not 32+ bytes scalar')
- if from_private_key:
- # set bits 0, 1, and 2 to 0
- # nb: lsb is right-indexed
- x_i[0] &= 0b11111000
- # set bit 254 to 1
- x_i[31] |= 0b01000000
- # set bit 255 to 0
- x_i[31] &= 0b01111111
- return bytes(x_i)
- def H_big(*parts) -> bytes:
- """The big, 64-byte hash function."""
- return new('sha512', b''.join(parts)).digest()
- def H_small(*parts) -> bytes:
- """The small, 32-byte hash function."""
- return crypto_core_ed25519_scalar_reduce(H_big(*parts))
- def xor(b1: bytes, b2: bytes) -> bytes:
- """XOR two equal-length byte strings together."""
- b3 = bytearray()
- for i in range(len(b1)):
- b3.append(b1[i] ^ b2[i])
- return bytes(b3)
- def bytes_are_same(b1: bytes, b2: bytes) -> bool:
- """Timing-attack safe bytes comparison."""
- return len(b1) == len(b2) and int.from_bytes(xor(b1, b2), 'little') == 0
- def hash_to_curve(y: VerifyKey, alpha: bytes) -> bytes:
- """Return a curve point from y multiplied by the hash of alpha."""
- h = clamp_scalar(H_small(alpha))
- return crypto_scalarmult_ed25519_noclamp(h, bytes(y))
- """Main functions."""
- def prove(sk: SigningKey, alpha: bytes):
- """Generate a proof."""
- if type(sk) is not SigningKey:
- raise TypeError('sk must be a nacl.signing.SigningKey')
- if type(alpha) is not bytes:
- raise TypeError('alpha must be bytes')
- xseed = H_big(bytes(sk))
- x = clamp_scalar(xseed[:32], True)
- h = hash_to_curve(sk.verify_key, alpha)
- gamma = crypto_scalarmult_ed25519(x, h)
- k = clamp_scalar(new('sha256', xseed[32:] + h).digest())
- gk = crypto_scalarmult_ed25519_base_noclamp(k)
- hk = crypto_scalarmult_ed25519_noclamp(k, h)
- c = H_small(h, gamma, gk, hk)
- cx = crypto_core_ed25519_scalar_mul(c, x)
- s = crypto_core_ed25519_scalar_add(k, cx)
- return (gamma, c, s)
- def proof2hash(pi: tuple):
- """Get the hash of the proof."""
- if type(pi) is not tuple:
- raise TypeError('pi must be tuple of 3 values of 32 bytes each')
- if type(pi[0]) is not bytes:
- raise TypeError('pi must be tuple of 3 values of 32 bytes each')
- if len(pi[0]) != 32:
- raise ValueError('pi must be tuple of 3 values of 32 bytes each')
- three = b'\x03'
- (gamma, c, s) = pi
- cofactor = (8).to_bytes(32, 'little')
- gammacofactor = crypto_scalarmult_ed25519_noclamp(cofactor, gamma)
- return H_small(three, gammacofactor)
- def verify(y: VerifyKey, alpha: bytes, pi: tuple):
- """Verify that the proof is valid."""
- if type(y) is not VerifyKey:
- raise TypeError('y must be a nacl.signing.VerifyKey')
- if type(alpha) is not bytes:
- raise TypeError('alpha must be bytes')
- if type(pi) is not tuple:
- raise TypeError('pi must be tuple of 3 values of 32 bytes each')
- if len(pi) != 3:
- raise ValueError('pi must be tuple of 3 values of 32 bytes each')
- for part in pi:
- if type(part) is not bytes or len(part) != 32:
- raise ValueError('pi must be tuple of 3 values of 32 bytes each')
- (gamma, c, s) = pi
- gs = crypto_scalarmult_ed25519_base_noclamp(s)
- yc = crypto_scalarmult_ed25519_noclamp(c, bytes(y))
- u = crypto_core_ed25519_sub(gs, yc)
- h = hash_to_curve(y, alpha)
- hs = crypto_scalarmult_ed25519_noclamp(s, h)
- gammac = crypto_scalarmult_ed25519_noclamp(c, gamma)
- v = crypto_core_ed25519_sub(hs, gammac)
- cprime = H_small(h, gamma, u, v)
- return bytes_are_same(c, cprime)
- """License."""
- def license() -> str:
- """ISC License
- Copyleft (c) 2021, k98kurz
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyleft notice and this permission notice appear in all copies.
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- """
- return license.__doc__
- """Instantiation from the cli."""
- def main(name, mode = '', value1 = '', value2 = '', value3 = ''):
- if mode == 'prove':
- if len(value1) != 64 or len(value2) < 1:
- return usage(name)
- sk = SigningKey(bytes.fromhex(value1))
- if value2[:4] == 'hex:':
- alpha = bytes.fromhex(value2[4:])
- else:
- alpha = bytes(value2, 'utf-8')
- pi = prove(sk, alpha)
- print(''.join([part.hex() for part in pi]))
- elif mode == 'verify':
- if len(value1) != 64 or len(value2) < 1 or len(value3) != 192:
- return usage(name)
- vk = VerifyKey(bytes.fromhex(value1))
- if value2[:4] == 'hex:':
- alpha = bytes.fromhex(value2[4:])
- else:
- alpha = bytes(value2, 'utf-8')
- proof = bytes.fromhex(value3)
- pi = (proof[:32], proof[32:64], proof[64:])
- print('valid' if verify(vk, alpha, pi) else 'invalid')
- elif mode == 'gethash':
- if len(value1) != 192:
- return usage(name)
- proof = bytes.fromhex(value1)
- pi = (proof[:32], proof[32:64], proof[64:])
- print(proof2hash(pi).hex())
- else:
- usage(name)
- def usage(name):
- print(f'usage:\t{name} prove [skey_hex] [utf8string|hex:hexstring]')
- print(f'\t{name} verify [vkey_hex] [utf8string|hex:hexstring] [proof]')
- print(f'\t{name} gethash [proof]')
- if __name__ == '__main__':
- main(*argv)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement