From cd53b4dde2ad3f9d57b77a70fbba5300990dd146 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 28 Dec 2020 01:01:54 +0000 Subject: [PATCH] added signedby proof --- onionrblocks/customtypes.py | 2 ++ onionrblocks/generators/anonvdf.py | 3 +- onionrblocks/generators/signedby.py | 55 +++++++++++++++++++++++++++++ tests/test_signedby.py | 44 +++++++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 onionrblocks/generators/signedby.py create mode 100644 tests/test_signedby.py diff --git a/onionrblocks/customtypes.py b/onionrblocks/customtypes.py index ea8a3af..d742af5 100644 --- a/onionrblocks/customtypes.py +++ b/onionrblocks/customtypes.py @@ -8,3 +8,5 @@ AnonymousVDFBlockResult = namedtuple( HexChecksum = NewType("HexChecksum", str) DecChecksum = NewType("DecChecksum", int) DecChecksumStr = NewType("DecChecksumStr", str) +RawEd25519PrivateKey = NewType("RawEd25519PrivateKey", bytes) +RawEd25519PublicKey = NewType("RawEd25519PublicKey", bytes) diff --git a/onionrblocks/generators/anonvdf.py b/onionrblocks/generators/anonvdf.py index 03ea51d..54bb0da 100644 --- a/onionrblocks/generators/anonvdf.py +++ b/onionrblocks/generators/anonvdf.py @@ -13,7 +13,8 @@ class AnonVDFGenerator(generator.KastenBaseGenerator): def get_ttl_seconds_per_rounds(cls, rounds: int): # 8000 rounds = 1 second (2.8ghz python) = 1 hour storage if rounds < 8000: - raise ValueError("Rounds must be at least 8000") + raise ValueError( + "Rounds must be at least 8000") return (rounds / 8000) * 60 @classmethod diff --git a/onionrblocks/generators/signedby.py b/onionrblocks/generators/signedby.py new file mode 100644 index 0000000..7e21e35 --- /dev/null +++ b/onionrblocks/generators/signedby.py @@ -0,0 +1,55 @@ +from hashlib import sha3_256 +from typing import Union + +from nacl.signing import SigningKey, VerifyKey +from nacl.exceptions import BadSignatureError +from kasten import generator, Kasten +from kasten.exceptions import InvalidID +from kasten.types import KastenPacked, KastenChecksum + +from onionrblocks.customtypes import RawEd25519PrivateKey, RawEd25519PublicKey + +class Signed(generator.KastenBaseGenerator): + + @classmethod + def generate( + cls, + packed_bytes: KastenPacked, + signingKey: Union[SigningKey, RawEd25519PrivateKey] + ) -> Kasten: + """Sign a digest of packed bytes, return a Kasten instance of it.""" + # Use libsodium/pynacl (ed25519) + hashed = sha3_256(packed_bytes).digest() + try: + signed = signingKey.sign(hashed) + except AttributeError: + signingKey = SigningKey(signingKey) + signed = signingKey.sign(hashed) + # The KastenChecksum will be 64 bytes message then 32 bytes of the hash + # This can be fed right back into VerifyKey without splitting up + return Kasten(signed, packed_bytes, None, + auto_check_generator=False) + + + @staticmethod + def validate_id( + hash: KastenChecksum, + packed_bytes: KastenPacked, + verify_key: Union[VerifyKey, RawEd25519PublicKey]) -> None: + # Hash is 64 bytes message then 32 bytes of the hash of the packed_bytes + if len(hash) != 86: + raise InvalidID("Block not have proper signature length") + actual_hash = sha3_256(packed_bytes).digest() + if not isinstance(verify_key, VerifyKey): + verify_key = VerifyKey(verify_key) + + # Ensure that the digest is correct + # Done in addition of the signature bc the sha3 can still ID blocks + # and to prevent swapping sigs + if actual_hash != hash[64:]: + raise InvalidID("Invalid sha3_256 digest") + # Ensure that the signature is correct + try: + verify_key.verify(hash) + except BadSignatureError as e: + raise InvalidID(repr(e)) diff --git a/tests/test_signedby.py b/tests/test_signedby.py new file mode 100644 index 0000000..90cf3e0 --- /dev/null +++ b/tests/test_signedby.py @@ -0,0 +1,44 @@ +import unittest +from onionrblocks.generators import signedby +import kasten +import hashlib +from nacl.signing import SigningKey, VerifyKey + +from time import time +from math import floor + +def sha3_hash_bytes(data): + return hashlib.sha3_256(data).digest() + +class TestSignedByProof(unittest.TestCase): + def test_signed_by_create(self): + key = SigningKey.generate() + test_data = kasten.generator.pack.pack( + b"test", "tst") + gen = signedby.Signed.generate(test_data, key) + self.assertEqual( + gen.get_packed(), + test_data + ) + hashed = hashlib.sha3_256(test_data).digest() + signed = key.sign(hashed) + self.assertEqual(signed, gen.id) + key.verify_key.verify(signed) + self.assertEqual(hashlib.sha3_256(test_data).digest(), signed[64:]) + + def test_signed_by_create_raw_keys(self): + key = SigningKey.generate() + test_data = kasten.generator.pack.pack( + b"test", "tst") + gen = signedby.Signed.generate(test_data, key.encode()) + self.assertEqual( + gen.get_packed(), + test_data + ) + hashed = hashlib.sha3_256(test_data).digest() + signed = key.sign(hashed) + self.assertEqual(signed, gen.id) + key.verify_key.verify(signed) + self.assertEqual(hashlib.sha3_256(test_data).digest(), signed[64:]) + +unittest.main() \ No newline at end of file