Exemplo n.º 1
0
 def withdrawal_credentials(self) -> bytes:
     if self.custom_withdrawal_credentials:
         return self.custom_withdrawal_credentials
     withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
     if self.custom_withdrawal_pk:
         withdrawal_credentials += SHA256(self.custom_withdrawal_pk)[1:]
     else:
         withdrawal_credentials += SHA256(self.withdrawal_pk)[1:]
     return withdrawal_credentials
Exemplo n.º 2
0
def _parent_SK_to_lamport_PK(*, parent_SK: int, index: int) -> bytes:
    """
    Derives the `index`th child's lamport PK from the `parent_SK`.
    """
    salt = index.to_bytes(4, byteorder='big')
    IKM = parent_SK.to_bytes(32, byteorder='big')
    lamport_0 = _IKM_to_lamport_SK(IKM=IKM, salt=salt)
    not_IKM = _flip_bits_256(parent_SK).to_bytes(32, byteorder='big')
    lamport_1 = _IKM_to_lamport_SK(IKM=not_IKM, salt=salt)
    lamport_SKs = lamport_0 + lamport_1
    lamport_PKs = [SHA256(sk) for sk in lamport_SKs]
    compressed_PK = SHA256(b''.join(lamport_PKs))
    return compressed_PK
Exemplo n.º 3
0
def _parent_SK_to_lamport_PK(*, parent_SK: int, index: int) -> bytes:
    """
    Derives the `index`th child's lamport PK from the `parent_SK`.

    Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2333.md#parent_sk_to_lamport_pk
    """
    salt = index.to_bytes(4, byteorder='big')
    IKM = parent_SK.to_bytes(32, byteorder='big')
    lamport_0 = _IKM_to_lamport_SK(IKM=IKM, salt=salt)
    not_IKM = _flip_bits_256(parent_SK).to_bytes(32, byteorder='big')
    lamport_1 = _IKM_to_lamport_SK(IKM=not_IKM, salt=salt)
    lamport_SKs = lamport_0 + lamport_1
    lamport_PKs = [SHA256(sk) for sk in lamport_SKs]
    compressed_PK = SHA256(b''.join(lamport_PKs))
    return compressed_PK
Exemplo n.º 4
0
 def encrypt(
     cls,
     *,
     secret: bytes,
     password: str,
     path: str = '',
     kdf_salt: bytes = randbits(256).to_bytes(32, 'big'),
     aes_iv: bytes = randbits(128).to_bytes(16, 'big')
 ) -> 'Keystore':
     """
     Encrypt a secret (BLS SK) as an EIP 2335 Keystore.
     """
     keystore = cls()
     keystore.uuid = str(uuid4())
     keystore.crypto.kdf.params['salt'] = kdf_salt
     decryption_key = keystore.kdf(password=cls._process_password(password),
                                   **keystore.crypto.kdf.params)
     keystore.crypto.cipher.params['iv'] = aes_iv
     cipher = AES_128_CTR(key=decryption_key[:16],
                          **keystore.crypto.cipher.params)
     keystore.crypto.cipher.message = cipher.encrypt(secret)
     keystore.crypto.checksum.message = SHA256(
         decryption_key[16:32] + keystore.crypto.cipher.message)
     keystore.pubkey = bls.SkToPk(int.from_bytes(secret, 'big')).hex()
     keystore.path = path
     return keystore
Exemplo n.º 5
0
def _get_checksum(entropy: bytes) -> int:
    """
    Determine the index of the checksum word given the entropy
    """
    _validate_entropy_length(entropy)
    checksum_length = len(entropy) // 4
    return int.from_bytes(SHA256(entropy), 'big') >> (256 - checksum_length)
Exemplo n.º 6
0
def get_mnemonic(*,
                 language: str,
                 words_path: str,
                 entropy: Optional[bytes] = None) -> str:
    """
    Return a mnemonic string in a given `language` based on `entropy` via the calculated checksum.

    Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
    """
    if entropy is None:
        entropy = randbits(256).to_bytes(32, 'big')
    entropy_length = len(entropy) * 8
    if entropy_length not in range(128, 257, 32):
        raise IndexError(
            f"`entropy_length` should be in [128, 160, 192,224, 256]. Got {entropy_length}."
        )
    checksum_length = (entropy_length // 32)
    checksum = int.from_bytes(SHA256(entropy), 'big') >> 256 - checksum_length
    entropy_bits = int.from_bytes(entropy, 'big') << checksum_length
    entropy_bits += checksum
    entropy_length += checksum_length
    mnemonic = []
    word_list = _get_word_list(language, words_path)
    for i in range(entropy_length // 11 - 1, -1, -1):
        index = (entropy_bits >> i * 11) & 2**11 - 1
        word = _get_word(word_list=word_list, index=index)
        mnemonic.append(word)
    return ' '.join(mnemonic)
Exemplo n.º 7
0
 def create_from_private_key(
     cls,
     *,
     secret: bytes,
     password: str,
     index: int = DEFAULT_INDEX,
     threshold: int = DEFAULT_THRESHOLD,
     shared_public_key: str = DEFAULT_SHARED_PUBLIC_KEY,
     shared_withdrawal_credentials: str = DEFAULT_SHARED_WITHDRAWAL_CREDS
 ) -> HorcruxPbkdf2Keystore:
     keystore = cls()
     keystore.uuid = str(uuid4())
     keystore.crypto.kdf.params["salt"] = randbits(256).to_bytes(32, "big")
     decryption_key = keystore.kdf(password=cls._process_password(password),
                                   **keystore.crypto.kdf.params)
     keystore.crypto.cipher.params["iv"] = randbits(128).to_bytes(16, "big")
     cipher = AES_128_CTR(key=decryption_key[:16],
                          **keystore.crypto.cipher.params)
     keystore.crypto.cipher.message = cipher.encrypt(secret)
     keystore.crypto.checksum.message = SHA256(
         decryption_key[16:32] + keystore.crypto.cipher.message)
     keystore.index = index
     keystore.threshold = threshold
     keystore.shared_public_key = shared_public_key
     keystore.shared_withdrawal_credentials = shared_withdrawal_credentials
     return keystore
Exemplo n.º 8
0
 def decrypt(self, password: str) -> bytes:
     decryption_key = self.kdf(password=password, **self.crypto.kdf.params)
     assert SHA256(
         decryption_key[16:32] +
         self.crypto.cipher.message) == self.crypto.checksum.message
     cipher = AES_128_CTR(key=decryption_key[:16],
                          **self.crypto.cipher.params)
     return cipher.decrypt(self.crypto.cipher.message)
Exemplo n.º 9
0
 def withdrawal_credentials(self) -> bytes:
     if self.withdrawal_type == WithdrawalType.BLS_WITHDRAWAL:
         withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
         withdrawal_credentials += SHA256(self.withdrawal_pk)[1:]
     elif (self.withdrawal_type == WithdrawalType.ETH1_ADDRESS_WITHDRAWAL
           and self.eth1_withdrawal_address is not None):
         withdrawal_credentials = ETH1_ADDRESS_WITHDRAWAL_PREFIX
         withdrawal_credentials += b'\x00' * 11
         withdrawal_credentials += self.eth1_withdrawal_address
     else:
         raise ValueError(f"Invalid withdrawal_type {self.withdrawal_type}")
     return withdrawal_credentials
Exemplo n.º 10
0
    def decrypt(self, password: str) -> bytes:
        """
        Retrieve the secret (BLS SK) from the self keystore by decrypting it with `password`
        """
        decryption_key = self.kdf(password=self._process_password(password),
                                  **self.crypto.kdf.params)
        if SHA256(decryption_key[16:32] +
                  self.crypto.cipher.message) != self.crypto.checksum.message:
            raise ValueError("Checksum message error")

        cipher = AES_128_CTR(key=decryption_key[:16],
                             **self.crypto.cipher.params)
        return cipher.decrypt(self.crypto.cipher.message)
Exemplo n.º 11
0
 def encrypt(cls, *, secret: bytes, password: str, path: str='',
             kdf_salt: bytes=randbits(256).to_bytes(32, 'big'),
             aes_iv: bytes=randbits(128).to_bytes(16, 'big')):
     keystore = cls()
     keystore.crypto.kdf.params['salt'] = kdf_salt
     decryption_key = keystore.kdf(password=password, **keystore.crypto.kdf.params)
     keystore.crypto.cipher.params['iv'] = aes_iv
     cipher = AES_128_CTR(key=decryption_key[:16], **keystore.crypto.cipher.params)
     keystore.crypto.cipher.message = cipher.encrypt(secret)
     keystore.crypto.checksum.message = SHA256(decryption_key[16:32] + keystore.crypto.cipher.message)
     keystore.pubkey = bls.PrivToPub(int.from_bytes(secret, 'big')).hex()
     keystore.path = path
     return keystore
Exemplo n.º 12
0
def _HKDF_mod_r(*, IKM: bytes, key_info: bytes = b'') -> int:
    """
    Hashes the IKM using HKDF and returns the answer as an int modulo r, the BLS field order.

    Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2333.md#hkdf_mod_r
    """
    L = 48  # `ceil((3 * ceil(log2(r))) / 16)`, where `r` is the order of the BLS 12-381 curve
    salt = b'BLS-SIG-KEYGEN-SALT-'
    SK = 0
    while SK == 0:
        salt = SHA256(salt)
        okm = HKDF(
            salt=salt,
            IKM=IKM + b'\x00',  # add postfix `I2OSP(0, 1)`
            L=L,
            info=key_info + L.to_bytes(2, 'big'),
        )
        SK = int.from_bytes(okm, byteorder='big') % bls_curve_order
    return SK
Exemplo n.º 13
0
def get_mnemonic(*, language: str, words_path: str, entropy: Optional[bytes]=None) -> str:
    """
    Return a mnemonic string in a given `language` based on `entropy`.
    """
    if entropy is None:
        entropy = randbits(256).to_bytes(32, 'big')
    entropy_length = len(entropy) * 8
    assert entropy_length in range(128, 257, 32)
    checksum_length = (entropy_length // 32)
    checksum = int.from_bytes(SHA256(entropy), 'big') >> 256 - checksum_length
    entropy_bits = int.from_bytes(entropy, 'big') << checksum_length
    entropy_bits += checksum
    entropy_length += checksum_length
    mnemonic = []
    word_list = _get_word_list(language, words_path)
    for i in range(entropy_length // 11 - 1, -1, -1):
        index = (entropy_bits >> i * 11) & 2**11 - 1
        word = _get_word(word_list=word_list, index=index)
        mnemonic.append(word)
    return ' '.join(mnemonic)
Exemplo n.º 14
0
def export_deposit_data_json(*, credentials: List[ValidatorCredentials],
                             folder: str):
    deposit_data: List[dict] = []
    for credential in credentials:
        deposit_datum = DepositMessage(
            pubkey=credential.signing_pk,
            withdrawal_credentials=SHA256(credential.withdrawal_pk),
            amount=credential.amount,
        )
        signed_deposit_datum = sign_deposit_data(deposit_datum,
                                                 credential.signing_sk)
        datum_dict = signed_deposit_datum.as_dict()
        datum_dict.update({'deposit_data_root': deposit_datum.hash_tree_root})
        datum_dict.update(
            {'signed_deposit_data_root': signed_deposit_datum.hash_tree_root})
        deposit_data.append(datum_dict)

    filefolder = os.path.join(folder, 'deposit_data-%i.json' % time.time())
    with open(filefolder, 'w') as f:
        json.dump(deposit_data, f, default=lambda x: x.hex())
    return filefolder
Exemplo n.º 15
0
def validate_deposit(deposit_data_dict: Dict[str, Any],
                     credential: Credential) -> bool:
    '''
    Checks whether a deposit is valid based on the eth2 rules.
    https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#deposits
    '''
    pubkey = BLSPubkey(bytes.fromhex(deposit_data_dict['pubkey']))
    withdrawal_credentials = bytes.fromhex(
        deposit_data_dict['withdrawal_credentials'])
    amount = deposit_data_dict['amount']
    signature = BLSSignature(bytes.fromhex(deposit_data_dict['signature']))
    deposit_message_root = bytes.fromhex(
        deposit_data_dict['deposit_data_root'])
    fork_version = bytes.fromhex(deposit_data_dict['fork_version'])

    # Verify pubkey
    if len(pubkey) != 48:
        return False
    if pubkey != credential.signing_pk:
        return False

    # Verify withdrawal credential
    if len(withdrawal_credentials) != 32:
        return False
    if withdrawal_credentials[:
                              1] == BLS_WITHDRAWAL_PREFIX == credential.withdrawal_prefix:
        if withdrawal_credentials[1:] != SHA256(credential.withdrawal_pk)[1:]:
            return False
    elif withdrawal_credentials[:
                                1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX == credential.withdrawal_prefix:
        if withdrawal_credentials[1:12] != b'\x00' * 11:
            return False
        if credential.eth1_withdrawal_address is None:
            return False
        if withdrawal_credentials[12:] != credential.eth1_withdrawal_address:
            return False
    else:
        return False

    # Verify deposit amount
    if not MIN_DEPOSIT_AMOUNT < amount <= MAX_DEPOSIT_AMOUNT:
        return False

    # Verify deposit signature && pubkey
    deposit_message = DepositMessage(
        pubkey=pubkey,
        withdrawal_credentials=withdrawal_credentials,
        amount=amount)
    domain = compute_deposit_domain(fork_version)
    signing_root = compute_signing_root(deposit_message, domain)
    if not bls.Verify(pubkey, signing_root, signature):
        return False

    # Verify Deposit Root
    signed_deposit = DepositData(
        pubkey=pubkey,
        withdrawal_credentials=withdrawal_credentials,
        amount=amount,
        signature=signature,
    )
    return signed_deposit.hash_tree_root == deposit_message_root
Exemplo n.º 16
0
def process_dispatcher_output(
    dispatcher_output: List[Dict[str, Any]],
    my_bls_public_key: BLSPubkey,
    my_bls_public_key_shares: List[BLSPubkey],
    my_bls_private_key_shares: List[int],
    my_rsa_key: RsaKey,
    my_index: int,
) -> Tuple[BLSPubkey, bytes, int]:
    """
    Processes output from the dispatcher to generate final
    horcrux BLS private key and shared BLS public key.
    """
    final_public_key_shares = [my_bls_public_key]
    horcrux_private_key_shares = [my_bls_private_key_shares[my_index]]
    horcrux_public_key_shares = [my_bls_public_key_shares[my_index]]
    for encrypted_data in dispatcher_output:
        # verify the RSA signature
        ciphertext = bytes.fromhex(encrypted_data["ciphertext"])
        signature = bytes.fromhex(encrypted_data["signature"])
        if not rsa_verify(
                RSA.import_key(encrypted_data["sender_rsa_public_key"]),
                ciphertext,
                signature,
        ):
            raise ValueError("Failed to verify the RSA signature.")

        data = json.loads(
            rsa_decrypt(
                private_key=my_rsa_key,
                enc_session_key=bytes.fromhex(
                    encrypted_data["enc_session_key"]),
                nonce=bytes.fromhex(encrypted_data["nonce"]),
                tag=bytes.fromhex(encrypted_data["tag"]),
                ciphertext=ciphertext,
            ))
        recipient_bls_public_keys = [
            bytes.fromhex(pub_key) for pub_key in data["public_key_shares"]
        ]
        horcrux_private_key_share = int(data["private_key_share"])

        if (bls_pop.SkToPk(horcrux_private_key_share) !=
                recipient_bls_public_keys[my_index]):
            raise ValueError("Received invalid BLS private key share.")

        final_public_key_shares.append(
            BLSPubkey(bytes.fromhex(data["public_key"])))
        horcrux_public_key_shares.append(
            BLSPubkey(recipient_bls_public_keys[my_index]))
        horcrux_private_key_shares.append(horcrux_private_key_share)

    final_public_key = bls_pop._AggregatePKs(final_public_key_shares)
    click.echo("Shared BLS Public Key: "
               f"{click.style(f'0x{final_public_key.hex()}', fg='green')}")

    withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
    withdrawal_credentials += SHA256(final_public_key)[1:]
    click.echo(
        "Withdrawal Credentials: "
        f"{click.style(f'0x{withdrawal_credentials.hex()}', fg='green')}")

    horcrux_private_key = 0
    for private_key_share in horcrux_private_key_shares:
        horcrux_private_key += private_key_share
        horcrux_private_key %= PRIME

    if bls_pop.SkToPk(horcrux_private_key) != bls_pop._AggregatePKs(
            horcrux_public_key_shares):
        raise ValueError("Invalid calculated horcrux private key")

    return final_public_key, withdrawal_credentials, horcrux_private_key
Exemplo n.º 17
0
 def withdrawal_credentials(self) -> bytes:
     withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
     withdrawal_credentials += SHA256(self.withdrawal_pk)[1:]
     return withdrawal_credentials