def secret_key(self, passphrase=None, ed25519_seed=True): """ Creates base58 encoded private key representation :param passphrase: encryption phrase for the private key :param ed25519_seed: encode seed rather than full key for ed25519 curve (True by default) :return: the secret key associated with this key, if available """ if not self.secret_exponent: raise ValueError("Secret key not known.") if self.curve == b'ed' and ed25519_seed: key = pysodium.crypto_sign_sk_to_seed(self.secret_exponent) else: key = self.secret_exponent if passphrase: if not ed25519_seed: raise NotImplementedError salt = pysodium.randombytes(8) encryption_key = hashlib.pbkdf2_hmac( hash_name="sha512", password=scrub_input(passphrase), salt=salt, iterations=32768, dklen=32) encrypted_sk = pysodium.crypto_secretbox(msg=key, nonce=b'\000' * 24, k=encryption_key) key = salt + encrypted_sk # we have to combine salt and encrypted key in order to decrypt later prefix = self.curve + b'esk' else: prefix = self.curve + b'sk' return base58_encode(key, prefix).decode()
def sign(self, message, generic=False): """ Sign a raw sequence of bytes :param message: sequence of bytes, raw format or hexadecimal notation :param generic: do not specify elliptic curve if set to True :return: signature in base58 encoding """ message = scrub_input(message) if not self.is_secret: raise ValueError("Cannot sign without a secret key.") # Ed25519 if self.curve == b"ed": digest = pysodium.crypto_generichash(message) signature = pysodium.crypto_sign_detached(digest, self._secret_key) # Secp256k1 elif self.curve == b"sp": pk = secp256k1.PrivateKey(self._secret_key) signature = pk.ecdsa_serialize_compact( pk.ecdsa_sign(message, digest=blake2b_32)) # P256 elif self.curve == b"p2": r, s = sign(msg=message, d=bytes_to_int(self._secret_key), hashfunc=blake2b_32) signature = int_to_bytes(r) + int_to_bytes(s) else: assert False if generic: prefix = b'sig' else: prefix = self.curve + b'sig' return base58_encode(signature, prefix).decode()
def verify(self, signature, message): """ Verify signature, raise exception if it is not valid. :param message: sequance of bytes, raw format or hexadecimal notation :param signature: a signature in base58 encoding :raises: ValueError if signature is not valid """ signature = scrub_input(signature) message = scrub_input(message) if not self.public_point: raise ValueError("Cannot verify without a public key") if signature[:3] != b'sig': # not generic if self.curve != signature[:2]: # "sp", "p2" "ed" raise ValueError("Signature and public key curves mismatch.") signature = base58_decode(signature) # Ed25519 if self.curve == b"ed": digest = pysodium.crypto_generichash(message) try: pysodium.crypto_sign_verify_detached(signature, digest, self.public_point) except ValueError: raise ValueError('Signature is invalid.') # Secp256k1 elif self.curve == b"sp": pk = secp256k1.PublicKey(self.public_point, raw=True) sig = pk.ecdsa_deserialize_compact(signature) if not pk.ecdsa_verify(message, sig, digest=blake2b_32): raise ValueError('Signature is invalid.') # P256 elif self.curve == b"p2": pk = fastecdsa.encoding.sec1.SEC1Encoder.decode_public_key( self.public_point, curve=fastecdsa.curve.P256) r, s = bytes_to_int(signature[:32]), bytes_to_int(signature[32:]) if not fastecdsa.ecdsa.verify( sig=(r, s), msg=message, Q=pk, hashfunc=blake2b_32): raise ValueError('Signature is invalid.') else: assert False
def from_encoded_key(cls, key, passphrase=''): """ Creates a key object from a base58 encoded key. :param key: a public or secret key in base58 encoding :param passphrase: the passphrase used if the key provided is an encrypted private key """ key = scrub_input(key) curve = key[:2] # "sp", "p2" "ed" if curve not in [b'sp', b'p2', b'ed']: raise ValueError("Invalid prefix for a key encoding.") if not len(key) in [54, 55, 88, 98]: raise ValueError("Invalid length for a key encoding.") encrypted = (key[2:3] == b'e') public_or_secret = key[3:5] if encrypted else key[2:4] if public_or_secret not in [b'pk', b'sk']: raise Exception("Invalid prefix for a key encoding.") key = base58_decode(key) is_secret = (public_or_secret == b'sk') if not is_secret: return cls.from_public_point(key, curve) if encrypted: if not passphrase: raise ValueError( "Encrypted key provided without a passphrase.") salt, encrypted_sk = key[:8], key[8:] encryption_key = hashlib.pbkdf2_hmac( hash_name="sha512", password=scrub_input(passphrase), salt=salt, iterations=32768, dklen=32) key = pysodium.crypto_secretbox_open(c=encrypted_sk, nonce=b'\000' * 24, k=encryption_key) del passphrase return cls.from_secret_exponent(key, curve)
def test_scrub_input(self, input_data, expected): self.assertEqual(expected, scrub_input(input_data))
def blake2b_32(v=b''): return blake2b(scrub_input(v), digest_size=32)
def blake2b_32(v=b''): """ Get a BLAKE2B hash of bytes """ return blake2b(scrub_input(v), digest_size=32)