def test_verify(testerchain, signature_verifier): message = os.urandom(100) # Generate Umbral key umbral_privkey = UmbralPrivateKey.gen_key() umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False) # Sign message using SHA-256 hash cryptography_priv_key = umbral_privkey.to_cryptography_privkey() signature_der_bytes = cryptography_priv_key.sign(message, ec.ECDSA(hashes.SHA256())) signature = Signature.from_bytes(signature_der_bytes, der_encoded=True) # Prepare message hash hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(message) message_hash = hash_ctx.finalize() # Get recovery id (v) before using contract # First try v = 0 recoverable_signature = bytes(signature) + bytes([0]) pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, message_hash, hasher=None) \ .format(compressed=False) if pubkey_bytes != umbral_pubkey_bytes: # Extracted public key is not ours, that means v = 1 recoverable_signature = bytes(signature) + bytes([1]) # Verify signature assert signature_verifier.functions.\ verify(message, recoverable_signature, umbral_pubkey_bytes[1:], ALGORITHM_SHA256).call() # Verify signature using wrong key umbral_privkey = UmbralPrivateKey.gen_key() umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False) assert not signature_verifier.functions.\ verify(message, recoverable_signature, umbral_pubkey_bytes[1:], ALGORITHM_SHA256).call()
class NotAPrivateKey: params = default_params() fake_signature = Signature.from_bytes( b'@\xbfS&\x97\xb3\x9e\x9e\xd3\\j\x9f\x0e\x8fY\x0c\xbeS\x08d\x0b%s\xf6\x17\xe2\xb6\xcd\x95u\xaapON\xd9E\xb3\x10M\xe1\xf4u\x0bL\x99q\xd6\r\x8e_\xe5I\x1e\xe5\xa2\xcf\xe5\x8be_\x077Gz' ) def public_key(self): return NotAPublicKey() def get_pubkey(self, *args, **kwargs): return self.public_key() def to_cryptography_privkey(self, *args, **kwargs): return self def sign(self, *args, **kwargs): return b'0D\x02 @\xbfS&\x97\xb3\x9e\x9e\xd3\\j\x9f\x0e\x8fY\x0c\xbeS\x08d\x0b%s\xf6\x17\xe2\xb6\xcd\x95u\xaap\x02 ON\xd9E\xb3\x10M\xe1\xf4u\x0bL\x99q\xd6\r\x8e_\xe5I\x1e\xe5\xa2\xcf\xe5\x8be_\x077Gz' @classmethod def stamp(cls, *args, **kwargs): return cls.fake_signature @classmethod def signature_bytes(cls, *args, **kwargs): return b'@\xbfS&\x97\xb3\x9e\x9e\xd3\\j\x9f\x0e\x8fY\x0c\xbeS\x08d\x0b%s\xf6\x17\xe2\xb6\xcd\x95u\xaapON\xd9E\xb3\x10M\xe1\xf4u\x0bL\x99q\xd6\r\x8e_\xe5I\x1e\xe5\xa2\xcf\xe5\x8be_\x077Gz'
def _deserialize(self, value, attr, data, **kwargs): if isinstance(value, bytes): return value try: return Signature.from_bytes(b64decode(value)) except InvalidNativeDataTypes as e: raise InvalidInputData(f"Could not parse {self.name}: {e}")
def sign(self, message: bytes) -> bytes: """ Signs a hashed message and returns a signature. :param message: The message to sign :return: Signature in bytes """ signature_der_bytes = API.ecdsa_sign(message, self._privkey) return Signature.from_bytes(signature_der_bytes, der_encoded=True)
def test_recover(testerchain, signature_verifier): message = os.urandom(100) # Prepare message hash hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(message) message_hash = hash_ctx.finalize() # Generate Umbral key and extract "address" from the public key umbral_privkey = UmbralPrivateKey.gen_key() umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False) signer_address = bytearray(testerchain.interface.w3.solidityKeccak(['bytes32'], [umbral_pubkey_bytes[1:]])) signer_address = to_normalized_address(signer_address[12:]) # Sign message using SHA-256 hash (because only 32 bytes hash can be used in the `ecrecover` method) cryptography_priv_key = umbral_privkey.to_cryptography_privkey() signature_der_bytes = cryptography_priv_key.sign(message, ec.ECDSA(hashes.SHA256())) signature = Signature.from_bytes(signature_der_bytes, der_encoded=True) # Get recovery id (v) before using contract # If we don't have recovery id while signing than we should try to recover public key with different v # Only the correct v will match the correct public key # First try v = 0 recoverable_signature = bytes(signature) + bytes([0]) pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, message_hash, hasher=None)\ .format(compressed=False) if pubkey_bytes != umbral_pubkey_bytes: # Extracted public key is not ours, that means v = 1 recoverable_signature = bytes(signature) + bytes([1]) pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, message_hash, hasher=None)\ .format(compressed=False) # Check that recovery was ok assert umbral_pubkey_bytes == pubkey_bytes # Check recovery method in the contract assert signer_address == to_normalized_address( signature_verifier.functions.recover(message_hash, recoverable_signature).call()) # Also numbers 27 and 28 can be used for v recoverable_signature = recoverable_signature[0:-1] + bytes([recoverable_signature[-1] + 27]) assert signer_address == to_normalized_address( signature_verifier.functions.recover(message_hash, recoverable_signature).call()) # Only number 0,1,27,28 are supported for v recoverable_signature = bytes(signature) + bytes([2]) with pytest.raises((TransactionFailed, ValueError)): signature_verifier.functions.recover(message_hash, recoverable_signature).call() # Signature must include r, s and v recoverable_signature = bytes(signature) with pytest.raises((TransactionFailed, ValueError)): signature_verifier.functions.recover(message_hash, recoverable_signature).call()
def test_sign_serialize_and_verify(execution_number): sk = SecretKey.random() pk = sk.public_key() signer = Signer(sk) message = b"peace at dawn" + str(execution_number).encode() signature = signer.sign(message) signature_bytes = bytes(signature) signature_restored = Signature.from_bytes(signature_bytes) assert signature_restored.verify(pk, message)
def sign_data(data, umbral_privkey): umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False) # Prepare hash of the data hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(data) data_hash = hash_ctx.finalize() # Sign data and calculate recoverable signature cryptography_priv_key = umbral_privkey.to_cryptography_privkey() signature_der_bytes = cryptography_priv_key.sign(data, ec.ECDSA(hashes.SHA256())) signature = Signature.from_bytes(signature_der_bytes, der_encoded=True) recoverable_signature = make_recoverable_signature(data_hash, signature, umbral_pubkey_bytes) return recoverable_signature
def test_verification_fail(): sk = SecretKey.random() pk = sk.public_key() signer = Signer(sk) message = b"peace at dawn" signature = signer.sign(message) # wrong message wrong_message = b"no peace at dawn" assert not signature.verify(pk, wrong_message) # bad signature signature_bytes = bytes(signature) signature_bytes = b'\x00' + signature_bytes[1:] signature_restored = Signature.from_bytes(signature_bytes) assert not signature_restored.verify(pk, message)
def test_sign_and_verify(execution_number): privkey = UmbralPrivateKey.gen_key() pubkey = privkey.get_pubkey() signer = Signer(private_key=privkey) message = b"peace at dawn" signature = signer(message=message) # Basic signature verification assert signature.verify(message, pubkey) assert not signature.verify(b"another message", pubkey) another_pubkey = UmbralPrivateKey.gen_key().pubkey assert not signature.verify(message, another_pubkey) # Signature serialization sig_bytes = bytes(signature) assert len(sig_bytes) == Signature.expected_bytes_length() restored_signature = Signature.from_bytes(sig_bytes) assert restored_signature == signature assert restored_signature.verify(message, pubkey)
def sign_data(data, umbral_privkey): umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes( is_compressed=False) # Prepare hash of the data hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(data) data_hash = hash_ctx.finalize() # Sign data and calculate recoverable signature cryptography_priv_key = umbral_privkey.to_cryptography_privkey() signature_der_bytes = cryptography_priv_key.sign( data, ec.ECDSA(hashes.SHA256())) signature = Signature.from_bytes(signature_der_bytes, der_encoded=True) recoverable_signature = bytes(signature) + bytes([0]) pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, data_hash, hasher=None) \ .format(compressed=False) if pubkey_bytes != umbral_pubkey_bytes: recoverable_signature = bytes(signature) + bytes([1]) return recoverable_signature
def test_signature_is_hashable(): sk = SecretKey.random() pk = sk.public_key() signer = Signer(sk) message = b'peace at dawn' message2 = b'no peace at dawn' signature = signer.sign(message) signature2 = signer.sign(message2) assert hash(signature) != hash(signature2) signature_restored = Signature.from_bytes(bytes(signature)) assert signature == signature_restored assert hash(signature) == hash(signature_restored) # Different hash, since signing involves some randomness signature3 = signer.sign(message) assert hash(signature) != hash(signature3)
def _validate(self, value): try: Signature.from_bytes(value) except InvalidNativeDataTypes as e: raise InvalidInputData(f"Could not parse {self.name}: {e}")