From 5ede1370532a9c7fa3c9726d292299c25fd2415e Mon Sep 17 00:00:00 2001 From: Mostafa Date: Tue, 11 Mar 2025 15:27:21 +0800 Subject: [PATCH 1/3] feat: add sign_bytes method to generate data that needs to be signed --- examples/example_transfer_transaction_bls.py | 2 +- .../example_transfer_transaction_ed25519.py | 2 +- pactus/transaction/transaction.py | 36 ++++++++++++++++--- tests/test_transaction.py | 19 +++++++--- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/examples/example_transfer_transaction_bls.py b/examples/example_transfer_transaction_bls.py index d9b4926..f03d86f 100644 --- a/examples/example_transfer_transaction_bls.py +++ b/examples/example_transfer_transaction_bls.py @@ -22,7 +22,7 @@ def main() -> None: tx = Transaction.create_transfer_tx(lock_time, sender, receiver, amount, fee, memo) signed_tx = tx.sign(sec) - print(f"Signed transaction hex: {signed_tx}") + print(f"Signed transaction hex: {signed_tx.hex()}") if __name__ == "__main__": diff --git a/examples/example_transfer_transaction_ed25519.py b/examples/example_transfer_transaction_ed25519.py index c89abe3..f1c9d5a 100644 --- a/examples/example_transfer_transaction_ed25519.py +++ b/examples/example_transfer_transaction_ed25519.py @@ -22,7 +22,7 @@ def main() -> None: tx = Transaction.create_transfer_tx(lock_time, sender, receiver, amount, fee, memo) signed_tx = tx.sign(sec) - print(f"Signed transaction hex: {signed_tx}") + print(f"Signed transaction hex: {signed_tx.hex()}") if __name__ == "__main__": diff --git a/pactus/transaction/transaction.py b/pactus/transaction/transaction.py index 039f027..be8f459 100644 --- a/pactus/transaction/transaction.py +++ b/pactus/transaction/transaction.py @@ -79,7 +79,13 @@ def create_withdraw_tx( return cls(lock_time, fee, memo, payload) def _get_unsigned_bytes(self, buf: bytes) -> bytes: - """Get unsigned bytes of the transaction, including the payload.""" + """ + Generates the unsigned representation of the transaction, + including flags and payload. + + This method appends various transaction components to the buffer + in a specific order, ensuring correct serialization. + """ encoding.append_uint8(buf, self.flags) encoding.append_uint8(buf, self.version) encoding.append_uint32(buf, self.lock_time) @@ -90,17 +96,37 @@ def _get_unsigned_bytes(self, buf: bytes) -> bytes: return buf - def sign(self, private_key: PrivateKey) -> str: - """Make a raw transaction, sign it and return the signed bytes.""" + def sign_bytes(self) -> bytes: + """ + Generates the transaction data that needs to be signed. + + The signature should be computed over this data, excluding the + transaction flags, which are removed before returning. + """ + buf = bytearray() + sign_bytes = self._get_unsigned_bytes(buf) + + return sign_bytes[1:] # flags is not part of the sign bytes. + + def sign(self, private_key: PrivateKey) -> bytes: + """ + Generates the signed representation of the transaction, + including the flags, payload, and cryptographic signature. + + This method first generates the data needs to be signed, + signs it using the provided private key, and then appends + both the signature and the corresponding public key to ensure + verifiability. + """ buf = bytearray() sign_bytes = self._get_unsigned_bytes(buf) sig = private_key.sign( bytes(sign_bytes[1:]), - ) # flags is not part of the sign bytes. + ) pub = private_key.public_key() encoding.append_fixed_bytes(buf, sig.raw_bytes()) encoding.append_fixed_bytes(buf, pub.raw_bytes()) - return buf.hex() + return buf diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 7a465c6..a5962d5 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -20,9 +20,9 @@ def test_sign_transfer(self): tx = Transaction.create_transfer_tx( lock_time, sender, receiver, amount, fee, memo ) - signed_data = tx.sign(prv) - expected_data = ( + signed_data = tx.sign(prv) + expected_signed_data = ( "00" # Flags "01" # Version "56341200" # LockTime @@ -36,10 +36,21 @@ def test_sign_transfer(self): + "af0f74917f5065af94727ae9541b0ddcfb5b828a9e016b02498f477ed37fb44d5d882495afb6fd4f9773e4ea9deee436" # Public Key + "030c4d61c6e3a1151585e1d838cae1444a438d089ce77e10c492a55f6908125c5be9b236a246e4082d08de564e111e65" ) - self.maxDiff = None - self.assertEqual(expected_data, signed_data) + self.assertEqual(expected_signed_data, signed_data.hex()) + sign_bytes = tx.sign_bytes() + expected_sign_bytes = ( + "01" # Version + "56341200" # LockTime + "c0843d" # Fee + "0474657374" # Memo + "01" # PayloadType + + "02a195d7fecba4c636832f1db0cd0ea14db6db8c71" # Sender + + "025e81869376b54f8a360f48930ea741e3b8771db2" # Receiver + + "8094ebdc03" # Amount + ) + self.assertEqual(expected_sign_bytes, sign_bytes.hex()) if __name__ == "__main__": unittest.main() From 99b793d5390271caeb726534b2407d77b806ccd1 Mon Sep 17 00:00:00 2001 From: Mostafa Date: Tue, 11 Mar 2025 15:36:52 +0800 Subject: [PATCH 2/3] chore: fix linting issues --- examples/example_get_node_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example_get_node_info.py b/examples/example_get_node_info.py index 2ef479d..b32420a 100644 --- a/examples/example_get_node_info.py +++ b/examples/example_get_node_info.py @@ -10,7 +10,7 @@ def main() -> None: # Creating a stub from channel stub = NetworkStub(channel) - # Initialize a request and call get consensus info method + # Initialize a request and call get node info method req = GetNodeInfoRequest() res = stub.GetNodeInfo(req) From a34e72fe6140b70f9e3249d0c821b099e901a599 Mon Sep 17 00:00:00 2001 From: Mostafa Date: Tue, 11 Mar 2025 15:37:40 +0800 Subject: [PATCH 3/3] chore: fix linting issues --- examples/example_multisig.py | 1 + pactus/transaction/transaction.py | 6 +++--- tests/test_transaction.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/example_multisig.py b/examples/example_multisig.py index f25dc8b..84f3b91 100644 --- a/examples/example_multisig.py +++ b/examples/example_multisig.py @@ -1,5 +1,6 @@ from pactus.crypto.bls.signature import Signature + def main() -> None: sig1 = Signature.from_string( "a628a8709fe00366d7150244447cc43e8637d76a20674b006b00f7a61109dab53ba5f1f66cd07219fd1e4a6bc7299d2d" diff --git a/pactus/transaction/transaction.py b/pactus/transaction/transaction.py index be8f459..55e82fe 100644 --- a/pactus/transaction/transaction.py +++ b/pactus/transaction/transaction.py @@ -80,7 +80,7 @@ def create_withdraw_tx( def _get_unsigned_bytes(self, buf: bytes) -> bytes: """ - Generates the unsigned representation of the transaction, + Generate the unsigned representation of the transaction, including flags and payload. This method appends various transaction components to the buffer @@ -98,7 +98,7 @@ def _get_unsigned_bytes(self, buf: bytes) -> bytes: def sign_bytes(self) -> bytes: """ - Generates the transaction data that needs to be signed. + Generate the transaction data that needs to be signed. The signature should be computed over this data, excluding the transaction flags, which are removed before returning. @@ -110,7 +110,7 @@ def sign_bytes(self) -> bytes: def sign(self, private_key: PrivateKey) -> bytes: """ - Generates the signed representation of the transaction, + Generate the signed representation of the transaction, including the flags, payload, and cryptographic signature. This method first generates the data needs to be signed, diff --git a/tests/test_transaction.py b/tests/test_transaction.py index a5962d5..34bd113 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -52,5 +52,6 @@ def test_sign_transfer(self): ) self.assertEqual(expected_sign_bytes, sign_bytes.hex()) + if __name__ == "__main__": unittest.main()