From 84b81ca399bcc4a73a3331b474fb98fd61224ba5 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 11 Dec 2023 12:55:49 -0500 Subject: [PATCH 1/2] Allow the various key-ish fields of a keyreg txn to be bytes Previously, these fields were required to be base64 encoded, which seems like a strange requirement. Now they may be bytes or b64 strings. Either way, they are stored in the python object as b64 strings to maintain compatibility with our original decision, regardless of how strange it was. Added a few doc string cleanups. --- algosdk/transaction.py | 88 +++++++++++----------------- tests/unit_tests/test_transaction.py | 16 +++++ 2 files changed, 50 insertions(+), 54 deletions(-) diff --git a/algosdk/transaction.py b/algosdk/transaction.py index 09e2a923..d4c531f5 100644 --- a/algosdk/transaction.py +++ b/algosdk/transaction.py @@ -419,8 +419,8 @@ class KeyregTxn(Transaction): Args: sender (str): address of sender sp (SuggestedParams): suggested params from algod - votekey (str): participation public key in base64 - selkey (str): VRF public key in base64 + votekey (str|bytes): participation public key bytes, optionally encoded in base64 + selkey (str|bytes): VRF public key bytes, optionally encoded in base64 votefst (int): first round to vote votelst (int): last round to vote votekd (int): vote key dilution @@ -430,7 +430,7 @@ class KeyregTxn(Transaction): transaction's valid rounds rekey_to (str, optional): additionally rekey the sender to this address nonpart (bool, optional): mark the account non-participating if true - StateProofPK: state proof + sprfkey (str|bytes, optional): state proof ID bytes, optionally encoded in base64 Attributes: sender (str) @@ -440,7 +440,7 @@ class KeyregTxn(Transaction): note (bytes) genesis_id (str) genesis_hash (str) - group(bytes) + group (bytes) votepk (str) selkey (str) votefst (int) @@ -471,13 +471,13 @@ def __init__( Transaction.__init__( self, sender, sp, note, lease, constants.keyreg_txn, rekey_to ) - self.votepk = votekey - self.selkey = selkey + self.votepk = self.fixed_bytes64(votekey, 32) + self.selkey = self.fixed_bytes64(selkey, 32) self.votefst = votefst self.votelst = votelst self.votekd = votekd self.nonpart = nonpart - self.sprfkey = sprfkey + self.sprfkey = self.fixed_bytes64(sprfkey, 64) if not sp.flat_fee: self.fee = max( @@ -520,6 +520,18 @@ def __eq__(self, other): and self.sprfkey == other.sprfkey ) + @staticmethod + def fixed_bytes64(key, size): + if key is None: + return None + if isinstance(key, (bytes, bytearray)) and len(key) == size: + return base64.b64encode(key) + if len(base64.b64decode(key)) == size: + return key + assert False, "{} is not {} bytes or b64 decodable as such".format( + key, size + ) + class KeyregOnlineTxn(KeyregTxn): """ @@ -529,8 +541,8 @@ class KeyregOnlineTxn(KeyregTxn): Args: sender (str): address of sender sp (SuggestedParams): suggested params from algod - votekey (str): participation public key in base64 - selkey (str): VRF public key in base64 + votekey (str|bytes): participation public key bytes, optionally encoded in base64 + selkey (str|bytes): VRF public key bytes, optionally encoded in base64 votefst (int): first round to vote votelst (int): last round to vote votekd (int): vote key dilution @@ -539,7 +551,7 @@ class KeyregOnlineTxn(KeyregTxn): with the same sender and lease can be confirmed in this transaction's valid rounds rekey_to (str, optional): additionally rekey the sender to this address - sprfkey (str, optional): state proof ID + sprfkey (str|bytes, optional): state proof ID bytes, optionally encoded in base64 Attributes: sender (str) @@ -549,7 +561,7 @@ class KeyregOnlineTxn(KeyregTxn): note (bytes) genesis_id (str) genesis_hash (str) - group(bytes) + group (bytes) votepk (str) selkey (str) votefst (int) @@ -590,12 +602,6 @@ def __init__( nonpart=False, sprfkey=sprfkey, ) - self.votepk = votekey - self.selkey = selkey - self.votefst = votefst - self.votelst = votelst - self.votekd = votekd - self.sprfkey = sprfkey if votekey is None: raise error.KeyregOnlineTxnInitError("votekey") if selkey is None: @@ -606,37 +612,19 @@ def __init__( raise error.KeyregOnlineTxnInitError("votelst") if votekd is None: raise error.KeyregOnlineTxnInitError("votekd") - if not sp.flat_fee: - self.fee = max( - self.estimate_size() * self.fee, constants.min_txn_fee - ) @staticmethod def _undictify(d): - votekey = base64.b64encode(d["votekey"]).decode() - selkey = base64.b64encode(d["selkey"]).decode() - votefst = d["votefst"] - votelst = d["votelst"] - votekd = d["votekd"] + args = { + "votekey": base64.b64encode(d["votekey"]).decode(), + "selkey": base64.b64encode(d["selkey"]).decode(), + "votefst": d["votefst"], + "votelst": d["votelst"], + "votekd": d["votekd"], + } + if "sprfkey" in d: - sprfID = base64.b64encode(d["sprfkey"]).decode() - - args = { - "votekey": votekey, - "selkey": selkey, - "votefst": votefst, - "votelst": votelst, - "votekd": votekd, - "sprfkey": sprfID, - } - else: - args = { - "votekey": votekey, - "selkey": selkey, - "votefst": votefst, - "votelst": votelst, - "votekd": votekd, - } + args["sprfkey"] = base64.b64encode(d["sprfkey"]).decode() return args @@ -668,7 +656,7 @@ class KeyregOfflineTxn(KeyregTxn): note (bytes) genesis_id (str) genesis_hash (str) - group(bytes) + group (bytes) type (str) lease (byte[32]) rekey_to (str) @@ -690,10 +678,6 @@ def __init__(self, sender, sp, note=None, lease=None, rekey_to=None): nonpart=False, sprfkey=None, ) - if not sp.flat_fee: - self.fee = max( - self.estimate_size() * self.fee, constants.min_txn_fee - ) @staticmethod def _undictify(d): @@ -728,7 +712,7 @@ class KeyregNonparticipatingTxn(KeyregTxn): note (bytes) genesis_id (str) genesis_hash (str) - group(bytes) + group (bytes) type (str) lease (byte[32]) rekey_to (str) @@ -750,10 +734,6 @@ def __init__(self, sender, sp, note=None, lease=None, rekey_to=None): nonpart=True, sprfkey=None, ) - if not sp.flat_fee: - self.fee = max( - self.estimate_size() * self.fee, constants.min_txn_fee - ) @staticmethod def _undictify(d): diff --git a/tests/unit_tests/test_transaction.py b/tests/unit_tests/test_transaction.py index d1e7b12e..395b0a4c 100644 --- a/tests/unit_tests/test_transaction.py +++ b/tests/unit_tests/test_transaction.py @@ -391,6 +391,22 @@ def test_serialize_keyreg_online(self): print(encoding.msgpack_encode(signed_txn)) self.assertEqual(golden, encoding.msgpack_encode(signed_txn)) + # Test that raw bytes are also acceptable inputs for keys + + txn = transaction.KeyregTxn( + pk, + sp, + base64.b64decode(votepk), + base64.b64decode(selpk), + votefirst, + votelast, + votedilution, + sprfkey=base64.b64decode(sprfKey), + ) + signed_txn = txn.sign(sk) + print(encoding.msgpack_encode(signed_txn)) + self.assertEqual(golden, encoding.msgpack_encode(signed_txn)) + def test_serialize_keyreg_offline(self): mn = ( "awful drop leaf tennis indoor begin mandate discover uncle seven " From b508b30497fca3d90e8e3601a38a2d6d853c641b Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 11 Dec 2023 13:40:30 -0500 Subject: [PATCH 2/2] _unbearable --- algosdk/transaction.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/algosdk/transaction.py b/algosdk/transaction.py index d4c531f5..e68cb4b7 100644 --- a/algosdk/transaction.py +++ b/algosdk/transaction.py @@ -471,13 +471,13 @@ def __init__( Transaction.__init__( self, sender, sp, note, lease, constants.keyreg_txn, rekey_to ) - self.votepk = self.fixed_bytes64(votekey, 32) - self.selkey = self.fixed_bytes64(selkey, 32) + self.votepk = self._fixed_bytes64(votekey, 32) + self.selkey = self._fixed_bytes64(selkey, 32) self.votefst = votefst self.votelst = votelst self.votekd = votekd self.nonpart = nonpart - self.sprfkey = self.fixed_bytes64(sprfkey, 64) + self.sprfkey = self._fixed_bytes64(sprfkey, 64) if not sp.flat_fee: self.fee = max( @@ -521,7 +521,7 @@ def __eq__(self, other): ) @staticmethod - def fixed_bytes64(key, size): + def _fixed_bytes64(key, size): if key is None: return None if isinstance(key, (bytes, bytearray)) and len(key) == size: