def get_signature_recovery_value(message: bytes, signature: Union[bytes, Signature], public_key: Union[bytes, UmbralPublicKey], is_prehashed: bool = False) -> bytes: """ Obtains the recovery value of a standard ECDSA signature. :param message: Signed message :param signature: The signature from which the pubkey is recovered :param public_key: The public key for verifying the signature :param is_prehashed: True if the message is already pre-hashed. Default is False, and message will be hashed with SHA256 :return: The compressed byte-serialized representation of the recovered public key """ signature = bytes(signature) ecdsa_signature_size = Signature.expected_bytes_length() if len(signature) != ecdsa_signature_size: raise ValueError( f"The signature size should be {ecdsa_signature_size} B.") kwargs = dict(hasher=None) if is_prehashed else {} for v in (0, 1): v_byte = bytes([v]) recovered_pubkey = PublicKey.from_signature_and_message( serialized_sig=signature + v_byte, message=message, **kwargs) if bytes(public_key) == recovered_pubkey.format(compressed=True): return v_byte else: raise ValueError( "Signature recovery failed. " "Either the message, the signature or the public key is not correct" )
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 recover_pubkey_from_signature(message: bytes, signature: Union[bytes, Signature], v_value_to_try: int, is_prehashed: bool = False) -> bytes: """ Recovers a serialized, compressed public key from a signature. It allows to specify a potential v value, in which case it assumes the signature has the traditional (r,s) raw format. If a v value is not present, it assumes the signature has the recoverable format (r, s, v). :param message: Signed message :param signature: The signature from which the pubkey is recovered :param v_value_to_try: A potential v value to try :param is_prehashed: True if the message is already pre-hashed. Default is False, and message will be hashed with SHA256 :return: The compressed byte-serialized representation of the recovered public key """ signature = bytes(signature) expected_signature_size = Signature.expected_bytes_length() if not len(signature) == expected_signature_size: raise ValueError( f"The signature size should be {expected_signature_size} B.") if v_value_to_try in (0, 1, 27, 28): if v_value_to_try >= 27: v_value_to_try -= 27 signature = signature + v_value_to_try.to_bytes(1, 'big') else: raise ValueError("Wrong v value. It should be 0, 1, 27 or 28.") kwargs = dict(hasher=None) if is_prehashed else {} pubkey = PublicKey.from_signature_and_message(serialized_sig=signature, message=message, **kwargs) return pubkey.format(compressed=True)
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 from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'KFrag': """ Instantiate a KFrag object from the serialized data. """ curve = curve if curve is not None else default_curve() bn_size = CurveBN.expected_bytes_length(curve) point_size = Point.expected_bytes_length(curve) signature_size = Signature.expected_bytes_length(curve) arguments = {'curve': curve} splitter = BytestringSplitter( bn_size, # id (CurveBN, bn_size, arguments), # bn_key (Point, point_size, arguments), # point_commitment (Point, point_size, arguments), # point_precursor 1, # keys_in_signature (Signature, signature_size, arguments), # signature_for_proxy (Signature, signature_size, arguments), # signature_for_bob ) components = splitter(data) return cls(identifier=components[0], bn_key=components[1], point_commitment=components[2], point_precursor=components[3], keys_in_signature=components[4], signature_for_proxy=components[5], signature_for_bob=components[6])
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(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 from_bytes(cls, data: bytes, curve: ec.EllipticCurve = None): """ Instantiate a KFrag object from the serialized data. """ curve = curve if curve is not None else default_curve() bn_size = CurveBN.expected_bytes_length(curve) point_size = Point.expected_bytes_length(curve) splitter = BytestringSplitter( bn_size, # id (CurveBN, bn_size), # bn_key (Point, point_size), # point_noninteractive (Point, point_size), # point_commitment (Point, point_size), # point_xcoord (Signature, Signature.expected_bytes_length(curve))) components = splitter(data) return cls(*components)
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 from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CorrectnessProof': """ Instantiate CorrectnessProof from serialized data. """ curve = curve if curve is not None else default_curve() bn_size = CurveBN.expected_bytes_length(curve) point_size = Point.expected_bytes_length(curve) arguments = {'curve': curve} splitter = BytestringSplitter( (Point, point_size, arguments), # point_e2 (Point, point_size, arguments), # point_v2 (Point, point_size, arguments), # point_kfrag_commitment (Point, point_size, arguments), # point_kfrag_pok (CurveBN, bn_size, arguments), # bn_sig (Signature, Signature.expected_bytes_length(curve), arguments), # kfrag_signature ) components = splitter(data, return_remainder=True) components.append(components.pop() or None) return cls(*components)
def recover_pubkey_from_signature(prehashed_message, signature, v_value_to_try=None) -> bytes: """ Recovers a serialized, compressed public key from a signature. It allows to specify a potential v value, in which case it assumes the signature has the traditional (r,s) raw format. If a v value is not present, it assumes the signature has the recoverable format (r, s, v). :param prehashed_message: Prehashed message :param signature: The signature from which the pubkey is recovered :param v_value_to_try: A potential v value to try :return: The compressed byte-serialized representation of the recovered public key """ signature = bytes(signature) ecdsa_signature_size = Signature.expected_bytes_length() if not v_value_to_try: expected_signature_size = ecdsa_signature_size + 1 if not len(signature) == expected_signature_size: raise ValueError( f"When not passing a v value, " f"the signature size should be {expected_signature_size} B.") elif v_value_to_try in (0, 1, 27, 28): expected_signature_size = ecdsa_signature_size if not len(signature) == expected_signature_size: raise ValueError( f"When passing a v value, " f"the signature size should be {expected_signature_size} B.") if v_value_to_try >= 27: v_value_to_try -= 27 signature = signature + v_value_to_try.to_bytes(1, 'big') else: raise ValueError("Wrong v value. It should be 0, 1, 27 or 28.") pubkey = PublicKey.from_signature_and_message(serialized_sig=signature, message=prehashed_message, hasher=None) return pubkey.format(compressed=True)
def _validate(self, value): try: Signature.from_bytes(value) except InvalidNativeDataTypes as e: raise InvalidInputData(f"Could not parse {self.name}: {e}")