Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions hathor/nanocontracts/rng.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from typing import Any, Sequence, TypeVar

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
from cryptography.hazmat.primitives.ciphers import Cipher, CipherContext, algorithms

from hathor.difficulty import Hash

Expand All @@ -36,19 +36,20 @@ class NanoRNG(metaclass=NoMethodOverrideMeta):
This implementation uses the ChaCha20 encryption as RNG.
"""

__slots__ = ('__seed', '__encryptor', '__frozen')
__slots__ = ('__seed', '__encryptor')

def __init__(self, seed: bytes) -> None:
self.__seed = Hash(seed)
self.__seed: Hash
object.__setattr__(self, '_NanoRNG__seed', Hash(seed))

key = self.__seed
nonce = self.__seed[:16]

algorithm = algorithms.ChaCha20(key, nonce)
cipher = Cipher(algorithm, mode=None)
self.__encryptor = cipher.encryptor()

self.__frozen = True
self.__encryptor: CipherContext
object.__setattr__(self, '_NanoRNG__encryptor', cipher.encryptor())

@classmethod
def create_with_shell(cls, seed: bytes) -> NanoRNG:
Expand All @@ -63,9 +64,7 @@ class ShellNanoRNG(NanoRNG):
return ShellNanoRNG(seed=seed)

def __setattr__(self, name: str, value: Any) -> None:
if getattr(self, '_NanoRNG__frozen', False):
raise AttributeError("Cannot assign methods to this object.")
super().__setattr__(name, value)
raise AttributeError("Cannot assign methods to this object.")

@property
def seed(self) -> Hash:
Expand Down
70 changes: 70 additions & 0 deletions tests/nanocontracts/test_rng.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,37 +73,107 @@ def test_rng_override(self) -> None:
seed = b'0' * 32
rng = NanoRNG(seed=seed)

#
# Existing attribute on instance
#

# protected by overridden __setattr__
with pytest.raises(AttributeError, match='Cannot assign methods to this object.'):
rng._NanoRNG__seed = b'1' * 32

# protected by overridden __setattr__
with pytest.raises(AttributeError, match='Cannot assign methods to this object.'):
setattr(rng, '_NanoRNG__seed', b'1' * 32)

# it doesn't protect against this case
object.__setattr__(rng, '_NanoRNG__seed', b'changed')
assert getattr(rng, '_NanoRNG__seed') == b'changed'

#
# New attribute on instance
#

# protected by overridden NanoRNG.__setattr__
with pytest.raises(AttributeError, match='Cannot assign methods to this object.'):
rng.new_attr = 123

# protected by overridden NanoRNG.__setattr__
with pytest.raises(AttributeError, match='Cannot assign methods to this object.'):
setattr(rng, 'new_attr', 123)

# protected by __slots__
with pytest.raises(AttributeError, match="'NanoRNG' object has no attribute 'new_attr'"):
object.__setattr__(rng, 'new_attr', 123)

#
# Existing method on instance
#

# protected by overridden NanoRNG.__setattr__
with pytest.raises(AttributeError, match='Cannot assign methods to this object.'):
rng.random = lambda self: 2 # type: ignore[method-assign, misc, assignment]

# protected by overridden NanoRNG.__setattr__
with pytest.raises(AttributeError, match='Cannot assign methods to this object.'):
setattr(rng, 'random', lambda self: 2)

# protected by overridden NanoRNG.__setattr__
with pytest.raises(AttributeError, match='Cannot assign methods to this object.'):
from types import MethodType
rng.random = MethodType(lambda self: 2, rng) # type: ignore[method-assign]

# protected by __slots__
with pytest.raises(AttributeError, match='\'NanoRNG\' object attribute \'random\' is read-only'):
object.__setattr__(rng, 'random', lambda self: 2)

#
# Existing method on class
#

# protected by overridden NoMethodOverrideMeta.__setattr__
with pytest.raises(AttributeError, match='Cannot override method `random`'):
NanoRNG.random = lambda self: 2 # type: ignore[method-assign]

# protected by overridden NoMethodOverrideMeta.__setattr__
with pytest.raises(AttributeError, match='Cannot override method `random`'):
setattr(NanoRNG, 'random', lambda self: 2)

# protected by Python itself
with pytest.raises(TypeError, match='can\'t apply this __setattr__ to NoMethodOverrideMeta object'):
object.__setattr__(NanoRNG, 'random', lambda self: 2)

#
# Existing method on __class__
#

# protected by overridden NoMethodOverrideMeta.__setattr__
with pytest.raises(AttributeError, match='Cannot override method `random`'):
rng.__class__.random = lambda self: 2 # type: ignore[method-assign]

# protected by overridden NoMethodOverrideMeta.__setattr__
with pytest.raises(AttributeError, match='Cannot override method `random`'):
setattr(rng.__class__, 'random', lambda self: 2)

# protected by Python itself
with pytest.raises(TypeError, match='can\'t apply this __setattr__ to NoMethodOverrideMeta object'):
object.__setattr__(rng.__class__, 'random', lambda self: 2)

#
# New attribute on class
#

# protected by overridden NoMethodOverrideMeta.__setattr__
with pytest.raises(AttributeError, match='Cannot override method `new_attr`'):
NanoRNG.new_attr = 123

# protected by overridden NoMethodOverrideMeta.__setattr__
with pytest.raises(AttributeError, match='Cannot override method `new_attr`'):
setattr(NanoRNG, 'new_attr', 123)

# protected by Python itself
with pytest.raises(TypeError, match='can\'t apply this __setattr__ to NoMethodOverrideMeta object'):
object.__setattr__(NanoRNG, 'new_attr', 123)

assert rng.random() < 1

def test_rng_shell_class(self) -> None:
Expand Down