Advertisement
k98kurz

vrf.py

Sep 8th, 2021 (edited)
1,259
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.21 KB | None | 0 0
  1. """Ed25519 verifiable random function. Important functions are prove,
  2. proof2hash, and verify.
  3. Reference used: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-02
  4. NB: liberties were taken to avoid flaws in the hash_to_curve specification
  5. documented in Section 5.4.1.1 and to slightly simplify the process over all;
  6. instead of the computation time for hash_to_curve being variable and determined
  7. by alpha, opening the process to timing attacks, this implementation does it in
  8. one try by multiplying the public key by the hash of the input alpha.
  9. """
  10.  
  11.  
  12. from hashlib import new
  13. from nacl.signing import SigningKey, VerifyKey
  14. from nacl.bindings import (
  15.     crypto_core_ed25519_scalar_reduce,
  16.     crypto_scalarmult_ed25519_noclamp,
  17.     crypto_scalarmult_ed25519,
  18.     crypto_scalarmult_ed25519_base_noclamp,
  19.     crypto_core_ed25519_scalar_mul,
  20.     crypto_core_ed25519_scalar_add,
  21.     crypto_core_ed25519_sub,
  22. )
  23. from sys import argv
  24.  
  25.  
  26. """Helper functions"""
  27. def clamp_scalar(scalar: bytes, from_private_key: bool = False) -> bytes:
  28.     """Make a clamped scalar."""
  29.     if type(scalar) is bytes and len(scalar) >= 32:
  30.         x_i = bytearray(scalar[:32])
  31.     elif type(scalar) is SigningKey:
  32.         x_i = bytearray(new('sha512', bytes(scalar)).digest()[:32])
  33.         from_private_key = True
  34.     else:
  35.         raise ValueError('not a SigningKey and not 32+ bytes scalar')
  36.  
  37.     if from_private_key:
  38.         # set bits 0, 1, and 2 to 0
  39.         # nb: lsb is right-indexed
  40.         x_i[0] &= 0b11111000
  41.         # set bit 254 to 1
  42.         x_i[31] |= 0b01000000
  43.  
  44.     # set bit 255 to 0
  45.     x_i[31] &= 0b01111111
  46.  
  47.     return bytes(x_i)
  48.  
  49. def H_big(*parts) -> bytes:
  50.     """The big, 64-byte hash function."""
  51.     return new('sha512', b''.join(parts)).digest()
  52.  
  53. def H_small(*parts) -> bytes:
  54.     """The small, 32-byte hash function."""
  55.     return crypto_core_ed25519_scalar_reduce(H_big(*parts))
  56.  
  57. def xor(b1: bytes, b2: bytes) -> bytes:
  58.     """XOR two equal-length byte strings together."""
  59.     b3 = bytearray()
  60.     for i in range(len(b1)):
  61.         b3.append(b1[i] ^ b2[i])
  62.  
  63.     return bytes(b3)
  64.  
  65. def bytes_are_same(b1: bytes, b2: bytes) -> bool:
  66.     """Timing-attack safe bytes comparison."""
  67.     return len(b1) == len(b2) and int.from_bytes(xor(b1, b2), 'little') == 0
  68.  
  69. def hash_to_curve(y: VerifyKey, alpha: bytes) -> bytes:
  70.     """Return a curve point from y multiplied by the hash of alpha."""
  71.     h = clamp_scalar(H_small(alpha))
  72.     return crypto_scalarmult_ed25519_noclamp(h, bytes(y))
  73.  
  74.  
  75. """Main functions."""
  76. def prove(sk: SigningKey, alpha: bytes):
  77.     """Generate a proof."""
  78.     if type(sk) is not SigningKey:
  79.         raise TypeError('sk must be a nacl.signing.SigningKey')
  80.     if type(alpha) is not bytes:
  81.         raise TypeError('alpha must be bytes')
  82.  
  83.     xseed = H_big(bytes(sk))
  84.     x = clamp_scalar(xseed[:32], True)
  85.  
  86.     h = hash_to_curve(sk.verify_key, alpha)
  87.     gamma = crypto_scalarmult_ed25519(x, h)
  88.     k = clamp_scalar(new('sha256', xseed[32:] + h).digest())
  89.     gk = crypto_scalarmult_ed25519_base_noclamp(k)
  90.     hk = crypto_scalarmult_ed25519_noclamp(k, h)
  91.     c = H_small(h, gamma, gk, hk)
  92.     cx = crypto_core_ed25519_scalar_mul(c, x)
  93.     s = crypto_core_ed25519_scalar_add(k, cx)
  94.  
  95.     return (gamma, c, s)
  96.  
  97. def proof2hash(pi: tuple):
  98.     """Get the hash of the proof."""
  99.     if type(pi) is not tuple:
  100.         raise TypeError('pi must be tuple of 3 values of 32 bytes each')
  101.     if type(pi[0]) is not bytes:
  102.         raise TypeError('pi must be tuple of 3 values of 32 bytes each')
  103.     if len(pi[0]) != 32:
  104.         raise ValueError('pi must be tuple of 3 values of 32 bytes each')
  105.  
  106.     three = b'\x03'
  107.     (gamma, c, s) = pi
  108.     cofactor = (8).to_bytes(32, 'little')
  109.     gammacofactor = crypto_scalarmult_ed25519_noclamp(cofactor, gamma)
  110.     return H_small(three, gammacofactor)
  111.  
  112. def verify(y: VerifyKey, alpha: bytes, pi: tuple):
  113.     """Verify that the proof is valid."""
  114.     if type(y) is not VerifyKey:
  115.         raise TypeError('y must be a nacl.signing.VerifyKey')
  116.     if type(alpha) is not bytes:
  117.         raise TypeError('alpha must be bytes')
  118.     if type(pi) is not tuple:
  119.         raise TypeError('pi must be tuple of 3 values of 32 bytes each')
  120.     if len(pi) != 3:
  121.         raise ValueError('pi must be tuple of 3 values of 32 bytes each')
  122.     for part in pi:
  123.         if type(part) is not bytes or len(part) != 32:
  124.             raise ValueError('pi must be tuple of 3 values of 32 bytes each')
  125.  
  126.     (gamma, c, s) = pi
  127.     gs = crypto_scalarmult_ed25519_base_noclamp(s)
  128.     yc = crypto_scalarmult_ed25519_noclamp(c, bytes(y))
  129.     u = crypto_core_ed25519_sub(gs, yc)
  130.     h = hash_to_curve(y, alpha)
  131.     hs = crypto_scalarmult_ed25519_noclamp(s, h)
  132.     gammac = crypto_scalarmult_ed25519_noclamp(c, gamma)
  133.     v = crypto_core_ed25519_sub(hs, gammac)
  134.     cprime = H_small(h, gamma, u, v)
  135.     return bytes_are_same(c, cprime)
  136.  
  137.  
  138. """License."""
  139. def license() -> str:
  140.     """ISC License
  141.  
  142.        Copyleft (c) 2021, k98kurz
  143.  
  144.        Permission to use, copy, modify, and/or distribute this software for any
  145.        purpose with or without fee is hereby granted, provided that the above
  146.        copyleft notice and this permission notice appear in all copies.
  147.  
  148.        THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  149.        WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  150.        MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  151.        ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  152.        WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  153.        ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  154.        OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  155.    """
  156.     return license.__doc__
  157.  
  158.  
  159. """Instantiation from the cli."""
  160. def main(name, mode = '', value1 = '', value2 = '', value3 = ''):
  161.     if mode == 'prove':
  162.         if len(value1) != 64 or len(value2) < 1:
  163.             return usage(name)
  164.         sk = SigningKey(bytes.fromhex(value1))
  165.         if value2[:4] == 'hex:':
  166.             alpha = bytes.fromhex(value2[4:])
  167.         else:
  168.             alpha = bytes(value2, 'utf-8')
  169.         pi = prove(sk, alpha)
  170.         print(''.join([part.hex() for part in pi]))
  171.     elif mode == 'verify':
  172.         if len(value1) != 64 or len(value2) < 1 or len(value3) != 192:
  173.             return usage(name)
  174.         vk = VerifyKey(bytes.fromhex(value1))
  175.         if value2[:4] == 'hex:':
  176.             alpha = bytes.fromhex(value2[4:])
  177.         else:
  178.             alpha = bytes(value2, 'utf-8')
  179.         proof = bytes.fromhex(value3)
  180.         pi = (proof[:32], proof[32:64], proof[64:])
  181.         print('valid' if verify(vk, alpha, pi) else 'invalid')
  182.     elif mode == 'gethash':
  183.         if len(value1) != 192:
  184.             return usage(name)
  185.         proof = bytes.fromhex(value1)
  186.         pi = (proof[:32], proof[32:64], proof[64:])
  187.         print(proof2hash(pi).hex())
  188.     else:
  189.         usage(name)
  190.  
  191. def usage(name):
  192.     print(f'usage:\t{name} prove [skey_hex] [utf8string|hex:hexstring]')
  193.     print(f'\t{name} verify [vkey_hex] [utf8string|hex:hexstring] [proof]')
  194.     print(f'\t{name} gethash [proof]')
  195.  
  196.  
  197. if __name__ == '__main__':
  198.     main(*argv)
  199.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement