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
53 changes: 53 additions & 0 deletions crypto/identity/proof_of_possession.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import sys
import os
from os.path import dirname

sys.path.append(os.path.join(dirname(dirname(__file__)), 'thirdparty/bls-signatures/python-impl'))

from schemes import PopSchemeMPL, PrivateKey as BLSSchemePrivateKey

from crypto.identity.bls_private_key import BLSPrivateKey


class ProofOfPossession:
@staticmethod
def derive_bls_private_key(passphrase: str) -> bytes:
"""Derives a BLS private key (32 bytes) from a BIP-39 mnemonic."""
return BLSPrivateKey.from_passphrase(passphrase).private_key

@classmethod
def derive_bls_public_key(cls, passphrase: str) -> str:
"""Derives the BLS12-381 G1 public key from a mnemonic. Returns hex (96 chars)."""
sk = BLSSchemePrivateKey.from_bytes(cls.derive_bls_private_key(passphrase))
return bytes(sk.get_g1()).hex()

@classmethod
def build_proof_of_possession(cls, private_key_bytes: bytes) -> dict:
"""Builds proof of possession for a given private key.

Args:
private_key_bytes: 32-byte BLS private key

Returns:
dict with 'pk' (hex, 48 bytes G1) and 'pop' (hex, 96 bytes G2)

Raises:
ValueError: if the key is not exactly 32 bytes or is the zero scalar
"""
if int.from_bytes(private_key_bytes, 'big') == 0:
raise ValueError('BLS secret key must not be zero')

sk = BLSSchemePrivateKey.from_bytes(private_key_bytes)
pk = bytes(sk.get_g1()).hex()
pop = bytes(PopSchemeMPL.pop_prove(sk)).hex()

return {'pk': pk, 'pop': pop}

@classmethod
def from_passphrase(cls, passphrase: str) -> dict:
"""Convenience: derives private key from mnemonic and builds PoP.

Returns:
dict with 'pk' (hex, 48 bytes G1) and 'pop' (hex, 96 bytes G2)
"""
return cls.build_proof_of_possession(cls.derive_bls_private_key(passphrase))
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from crypto.enums.contract_addresses import ContractAddresses
from crypto.identity.proof_of_possession import ProofOfPossession
from crypto.transactions.builder.abstract_transaction_builder import AbstractTransactionBuilder
from crypto.transactions.types.validator_registration import ValidatorRegistration


class ValidatorRegistrationBuilder(AbstractTransactionBuilder):
def __init__(self, data: dict):
super().__init__(data)

self.to(ContractAddresses.CONSENSUS.value)

def validator_public_key(self, validator_public_key: str):
self.transaction.data['validatorPublicKey'] = validator_public_key
def validator_passphrase(self, passphrase: str):
bls = ProofOfPossession.from_passphrase(passphrase)
self.transaction.data['validatorPublicKey'] = '0x' + bls['pk']
self.transaction.data['validatorProof'] = '0x' + bls['pop']
self.transaction.refresh_payload_data()
return self

Expand Down
20 changes: 20 additions & 0 deletions crypto/transactions/builder/validator_update_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from crypto.enums.contract_addresses import ContractAddresses
from crypto.identity.proof_of_possession import ProofOfPossession
from crypto.transactions.builder.abstract_transaction_builder import AbstractTransactionBuilder
from crypto.transactions.types.validator_update import ValidatorUpdate


class ValidatorUpdateBuilder(AbstractTransactionBuilder):
def __init__(self, data: dict):
super().__init__(data)
self.to(ContractAddresses.CONSENSUS.value)

def validator_passphrase(self, passphrase: str):
bls = ProofOfPossession.from_passphrase(passphrase)
self.transaction.data['validatorPublicKey'] = '0x' + bls['pk']
self.transaction.data['validatorProof'] = '0x' + bls['pop']
self.transaction.refresh_payload_data()
return self

def get_transaction_instance(self, data: dict):
return ValidatorUpdate(data)
4 changes: 4 additions & 0 deletions crypto/transactions/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from crypto.transactions.types.unvote import Unvote
from crypto.transactions.types.validator_registration import ValidatorRegistration
from crypto.transactions.types.validator_resignation import ValidatorResignation
from crypto.transactions.types.validator_update import ValidatorUpdate

from crypto.enums.abi_function import AbiFunction
from crypto.utils.abi_decoder import AbiDecoder
Expand Down Expand Up @@ -95,6 +96,9 @@ def __guess_transaction_from_data(self, data: dict) -> AbstractTransaction:
if function_name == AbiFunction.VALIDATOR_RESIGNATION.value:
return ValidatorResignation(data)

if function_name == AbiFunction.UPDATE_VALIDATOR.value:
return ValidatorUpdate(data)

username_payload_data = self.decode_payload(data, ContractAbiType.USERNAMES)
if username_payload_data is not None:
function_name = username_payload_data.get('functionName')
Expand Down
12 changes: 7 additions & 5 deletions crypto/transactions/types/validator_registration.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
from typing import Optional
from crypto.transactions.types.abstract_transaction import AbstractTransaction
from crypto.utils.abi_encoder import AbiEncoder
from crypto.enums.abi_function import AbiFunction
from crypto.utils.transaction_utils import TransactionUtils


class ValidatorRegistration(AbstractTransaction):
def __init__(self, data: dict):
payload = self._decode_payload(data)
if payload:
data['validatorPublicKey'] = TransactionUtils.parse_hex_from_str(payload.get('args', [None])[0]) if payload.get('args') else None
data['validatorPublicKey'], data['validatorProof'] = payload['args']

super().__init__(data)

def get_payload(self) -> str:
if 'validatorPublicKey' not in self.data:
if 'validatorPublicKey' not in self.data or 'validatorProof' not in self.data:
return ''
encoder = AbiEncoder()
return encoder.encode_function_call(AbiFunction.VALIDATOR_REGISTRATION.value, ['0x' + self.data['validatorPublicKey']])
return encoder.encode_function_call(
AbiFunction.VALIDATOR_REGISTRATION.value,
[self.data['validatorPublicKey'], self.data['validatorProof']],
)
21 changes: 21 additions & 0 deletions crypto/transactions/types/validator_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from crypto.transactions.types.abstract_transaction import AbstractTransaction
from crypto.utils.abi_encoder import AbiEncoder
from crypto.enums.abi_function import AbiFunction


class ValidatorUpdate(AbstractTransaction):
def __init__(self, data: dict):
payload = self._decode_payload(data)
if payload:
data['validatorPublicKey'], data['validatorProof'] = payload['args']

super().__init__(data)

def get_payload(self) -> str:
if 'validatorPublicKey' not in self.data or 'validatorProof' not in self.data:
return ''
encoder = AbiEncoder()
return encoder.encode_function_call(
AbiFunction.UPDATE_VALIDATOR.value,
[self.data['validatorPublicKey'], self.data['validatorProof']],
)
Loading
Loading