Add signature options to validator signing

The JSON RPC API has changed and now demands a BLS signature
with the BLS key(s) to be added.
pull/35/head
MaxMustermann2 3 years ago
parent c81f9eff41
commit 4f266619fa
No known key found for this signature in database
GPG Key ID: 4F4AB9DB6FF24C94
  1. 5
      pyhmy/staking_signing.py
  2. 1
      pyhmy/staking_structures.py
  3. 55
      pyhmy/validator.py
  4. 20
      tests/sdk-pyhmy/test_validator.py

@ -271,6 +271,9 @@ def _sign_create_validator(transaction_dict, private_key):
bls_keys = apply_formatter_to_array( hexstr_if_str(to_bytes), # formatter bls_keys = apply_formatter_to_array( hexstr_if_str(to_bytes), # formatter
sanitized_transaction.pop('bls-public-keys') sanitized_transaction.pop('bls-public-keys')
) )
bls_key_sigs = apply_formatter_to_array( hexstr_if_str(to_bytes), # formatter
sanitized_transaction.pop('bls-key-sigs')
)
sanitized_transaction['stakeMsg'] = \ sanitized_transaction['stakeMsg'] = \
apply_formatters_to_sequence( [ apply_formatters_to_sequence( [
hexstr_if_str(to_bytes), # address hexstr_if_str(to_bytes), # address
@ -279,6 +282,7 @@ def _sign_create_validator(transaction_dict, private_key):
hexstr_if_str(to_int), # min self delegation (in ONE), decimals are silently dropped hexstr_if_str(to_int), # min self delegation (in ONE), decimals are silently dropped
hexstr_if_str(to_int), # max total delegation (in ONE), decimals are silently dropped hexstr_if_str(to_int), # max total delegation (in ONE), decimals are silently dropped
identity, # bls public keys identity, # bls public keys
identity, # bls key sigs
hexstr_if_str(to_int), # amount (the Hexlify in the SDK drops the decimals, which is what we will do too) hexstr_if_str(to_int), # amount (the Hexlify in the SDK drops the decimals, which is what we will do too)
], [ ], [
convert_one_to_hex(sanitized_transaction.pop('validatorAddress')), convert_one_to_hex(sanitized_transaction.pop('validatorAddress')),
@ -287,6 +291,7 @@ def _sign_create_validator(transaction_dict, private_key):
math.floor(sanitized_transaction.pop('min-self-delegation')), # Decimal floors it correctly math.floor(sanitized_transaction.pop('min-self-delegation')), # Decimal floors it correctly
math.floor(sanitized_transaction.pop('max-total-delegation')), math.floor(sanitized_transaction.pop('max-total-delegation')),
bls_keys, bls_keys,
bls_key_sigs,
math.floor(sanitized_transaction.pop('amount')), math.floor(sanitized_transaction.pop('amount')),
] ]
) )

@ -134,6 +134,7 @@ class CreateValidator:
big_endian_int, # min self delegation big_endian_int, # min self delegation
big_endian_int, # max total delegation big_endian_int, # max total delegation
CountableList(Binary.fixed_length(48, allow_empty=True)), # bls-public-keys array of unspecified length, each key of 48 CountableList(Binary.fixed_length(48, allow_empty=True)), # bls-public-keys array of unspecified length, each key of 48
CountableList(Binary.fixed_length(96, allow_empty=True)), # bls-key-sigs array of unspecified length, each sig of 96
big_endian_int, # amount big_endian_int, # amount
], True)), # strictly these number of elements ], True)), # strictly these number of elements
('nonce', big_endian_int), ('nonce', big_endian_int),

@ -58,6 +58,7 @@ class Validator:
raise InvalidValidatorError(1, f'{address} is not valid ONE address') raise InvalidValidatorError(1, f'{address} is not valid ONE address')
self._address = address self._address = address
self._bls_keys = [] self._bls_keys = []
self._bls_key_sigs = []
self._name = None self._name = None
self._identity = None self._identity = None
@ -151,6 +152,47 @@ class Validator:
""" """
return self._bls_keys return self._bls_keys
def add_bls_key_sig(self, key) -> bool:
"""
Add BLS public key to validator BLS keys if not already in list
Returns
-------
bool
If adding BLS key succeeded
"""
key = self._sanitize_input(key)
if key not in self._bls_key_sigs:
self._bls_key_sigs.append(key)
return True
return False
def remove_bls_key_sig(self, key) -> bool:
"""
Remove BLS public key from validator BLS keys if exists
Returns
-------
bool
If removing BLS key succeeded
"""
key = self._sanitize_input(key)
if key in self._bls_key_sigs:
self._bls_key_sigs.remove(key)
return True
return False
def get_bls_key_sigs(self) -> list:
"""
Get list of validator BLS keys
Returns
-------
list
List of validator BLS keys (strings)
"""
return self._bls_key_sigs
def set_name(self, name): def set_name(self, name):
""" """
Set validator name Set validator name
@ -578,6 +620,7 @@ class Validator:
"max-rate": '0', "max-rate": '0',
"max-change-rate": '0', "max-change-rate": '0',
"bls-public-keys": [ "" ] "bls-public-keys": [ "" ]
"bls-key-sigs": [ "" ]
} }
Raises Raises
@ -603,12 +646,18 @@ class Validator:
self._bls_keys = [] self._bls_keys = []
for key in info['bls-public-keys']: for key in info['bls-public-keys']:
self.add_bls_key(key) self.add_bls_key(key)
self._bls_key_sigs = []
for key in info['bls-key-sigs']:
self.add_bls_key_sig(key)
except KeyError as e: except KeyError as e:
raise InvalidValidatorError(3, 'Info has missing key') from e raise InvalidValidatorError(3, 'Info has missing key') from e
def load_from_blockchain(self, endpoint=_default_endpoint, timeout=_default_timeout): def load_from_blockchain(self, endpoint=_default_endpoint, timeout=_default_timeout):
""" """
Import validator information from blockchain with given address Import validator information from blockchain with given address
At the moment, this is unable to fetch the BLS Signature, which is not implemented
in the Node API
Parameters Parameters
---------- ----------
@ -674,7 +723,8 @@ class Validator:
"rate": self._rate, "rate": self._rate,
"max-rate": self._max_rate, "max-rate": self._max_rate,
"max-change-rate": self._max_change_rate, "max-change-rate": self._max_change_rate,
"bls-public-keys": self._bls_keys "bls-public-keys": self._bls_keys,
"bls-key-sigs": self._bls_key_sigs
} }
return info return info
@ -705,7 +755,7 @@ class Validator:
info['chainId'] = chain_id info['chainId'] = chain_id
return sign_staking_transaction(info, private_key) return sign_staking_transaction(info, private_key)
def sign_edit_validator_transaction(self, nonce, gas_price, gas_limit, rate, bls_key_to_add, bls_key_to_remove, private_key, chain_id=None) -> SignedTransaction: def sign_edit_validator_transaction(self, nonce, gas_price, gas_limit, rate, bls_key_to_remove, bls_key_to_add, bls_key_to_add_sig, private_key, chain_id=None) -> SignedTransaction:
""" """
Create but not post a transaction to Edit the Validator using private_key Create but not post a transaction to Edit the Validator using private_key
@ -737,6 +787,7 @@ class Validator:
_ = info.pop('amount') # also unused _ = info.pop('amount') # also unused
info['bls-key-to-remove'] = bls_key_to_remove info['bls-key-to-remove'] = bls_key_to_remove
info['bls-key-to-add'] = bls_key_to_add info['bls-key-to-add'] = bls_key_to_add
info['bls-key-to-add-sig'] = bls_key_to_add_sig
if chain_id: if chain_id:
info['chainId'] = chain_id info['chainId'] = chain_id
return sign_staking_transaction(info, private_key) return sign_staking_transaction(info, private_key)

@ -49,7 +49,8 @@ def test_load_validator(setup_blockchain):
'max-rate': '0.9', 'max-rate': '0.9',
'max-change-rate': '0.05', 'max-change-rate': '0.05',
'rate': '0.01', 'rate': '0.01',
'bls-public-keys': ['0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611'], 'bls-public-keys': ['0x30b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202'],
'bls-key-sigs': ['0x68f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b7062414'],
'max-total-delegation': convert_one_to_atto(40000) 'max-total-delegation': convert_one_to_atto(40000)
} }
test_validator_object.load(info) test_validator_object.load(info)
@ -57,7 +58,9 @@ def test_load_validator(setup_blockchain):
test_validator_loaded = True test_validator_loaded = True
""" """
TypeScript signature source TypeScript signature source (is outdated because the JS SDK has not been updated for SlotKeySigs)
For now I have checked that the below transaction to localnet works
---
const description: Description = new Description('Alice', 'alice', 'alice.harmony.one', 'Bob', "Don't mess with me!!!") const description: Description = new Description('Alice', 'alice', 'alice.harmony.one', 'Bob', "Don't mess with me!!!")
const commissionRates: CommissionRate = new CommissionRate(new Decimal('0.01'), new Decimal('0.9'), new Decimal('0.05')) const commissionRates: CommissionRate = new CommissionRate(new Decimal('0.01'), new Decimal('0.9'), new Decimal('0.05'))
const stakeMsg: CreateValidator = new CreateValidator( const stakeMsg: CreateValidator = new CreateValidator(
@ -91,10 +94,12 @@ def test_create_validator_sign(setup_blockchain):
100, 100,
'4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48', '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48',
None).rawTransaction.hex() None).rawTransaction.hex()
assert signed_hash == '0xf9010580f8bf94ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dcc8872386f26fc10000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a0878678326eac9000000f1b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b622476086118a021e27c1806e59a4000024a047c6d444971d4d3c48e8b255aa0e543ebb47b60f761582694e5af5330445aba5a04db1ffea9cca9f9e56e8f782c689db680992903acfd9c06f4593f7fd9a781bd7' assert signed_hash == '0xf9016a80f9012394ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dcc8872386f26fc10000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a0878678326eac9000000f1b030b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202f862b86068f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b70624148a021e27c1806e59a4000023a0650feed3d7bbcb9ed36575c829bfdc600d53cd7a4fcc0e78ddf26d740f1900a8a027798fc245ee5c66847327b7454670b3b84cb3998527c4fbc0f967b787f56bc2'
""" """
Signature matched from TypeScript Signature matched from TypeScript (is outdated because the JS SDK has not been updated for SlotKeyToAddSig)
For now I have checked that the below transaction to localnet works
---
import { import {
CreateValidator, CreateValidator,
EditValidator, EditValidator,
@ -141,11 +146,12 @@ def test_edit_validator_sign(setup_blockchain):
int(convert_one_to_atto(1)), int(convert_one_to_atto(1)),
100, 100,
'0.06', '0.06',
'0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612', # add key '0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612', # remove key
"0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611", # remove key "0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611", # add key
"0x68f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b7062414", # add key sig
'4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48', '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48',
2).rawTransaction.hex() 2).rawTransaction.hex()
assert signed_hash == '0xf9012101f8d094ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121c887d529ae9e8600008a021e19e0c9bab24000008a0878678326eac9000000b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b6224760861202880de0b6b3a76400006428a0656d6741687ec1e42d1699274584a1777964e939b0ef11f3ff0e161859da21a2a03fc51e067f9fb6c96bee5ceccad4104f5b4b334a86a36a2f53d10b9a8e4a268a' assert signed_hash == '0xf9012101f8d094ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121c887d529ae9e8600008a021e19e0c9bab24000008a0878678326eac9000000b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b6224760861102880de0b6b3a76400006428a0782ed9f909b5c68eb050a917a274d9187a4c8f13799f21ba351bdcb11290c6c7a00a3b4ec8431290565acbbbb8231cafe32ed7c0b6211e7cf570b459cb733e0995'
@pytest.mark.run(order=4) @pytest.mark.run(order=4)
def test_invalid_validator(setup_blockchain): def test_invalid_validator(setup_blockchain):

Loading…
Cancel
Save