Skip to content

Commit f7a2447

Browse files
authored
Merge pull request #38 from AlexanderShenshin/did-sdk-compatibility
Updates for DID SDK compatibility
2 parents 92c12c0 + 2571ffb commit f7a2447

16 files changed

+139
-234
lines changed

src/hedera_sdk_python/account/account_create_transaction.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -171,21 +171,3 @@ def _execute_transaction(self, client, transaction_proto):
171171

172172
receipt = self.get_receipt(client)
173173
return receipt
174-
175-
def get_receipt(self, client, timeout=60):
176-
"""
177-
Retrieves the receipt for the transaction.
178-
179-
Args:
180-
client (Client): The client instance to query.
181-
timeout (int): Maximum time in seconds to wait for the receipt.
182-
183-
Returns:
184-
TransactionReceipt: The transaction receipt from the network.
185-
186-
Raises:
187-
Exception: If the transaction ID is not set or if receipt retrieval fails.
188-
"""
189-
if self.transaction_id is None:
190-
raise Exception("Transaction ID is not set. Did you forget to sign or execute the transaction?")
191-
return client.get_transaction_receipt(self.transaction_id, timeout)

src/hedera_sdk_python/client/client.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,19 @@ def get_node_account_ids(self):
8686

8787
def get_transaction_receipt(self, transaction_id, max_attempts=10, sleep_seconds=2):
8888
for attempt in range(max_attempts):
89-
try:
90-
receipt_query = TransactionGetReceiptQuery()
91-
receipt_query.set_transaction_id(transaction_id)
92-
receipt = receipt_query.execute(self)
93-
status = receipt.status
94-
95-
if status == ResponseCode.SUCCESS:
96-
return receipt
97-
elif status in (ResponseCode.UNKNOWN, ResponseCode.RECEIPT_NOT_FOUND):
98-
time.sleep(sleep_seconds)
99-
continue
100-
else:
101-
status_message = ResponseCode.get_name(status)
102-
raise Exception(f"Transaction failed with status: {status_message}")
103-
except Exception as e:
104-
print(f"Error retrieving transaction receipt: {e}")
89+
receipt_query = TransactionGetReceiptQuery()
90+
receipt_query.set_transaction_id(transaction_id)
91+
receipt = receipt_query.execute(self)
92+
status = receipt.status
93+
94+
if status == ResponseCode.SUCCESS:
95+
return receipt
96+
elif status in (ResponseCode.UNKNOWN, ResponseCode.BUSY, ResponseCode.RECEIPT_NOT_FOUND, ResponseCode.RECORD_NOT_FOUND, ResponseCode.PLATFORM_NOT_ACTIVE):
10597
time.sleep(sleep_seconds)
98+
continue
99+
else:
100+
status_message = ResponseCode.get_name(status)
101+
raise Exception(f"Error retrieving transaction receipt: {status_message}")
106102
raise Exception("Exceeded maximum attempts to fetch transaction receipt.")
107103

108104
def send_query(self, query, node_account_id, timeout=60):

src/hedera_sdk_python/consensus/topic_create_transaction.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,3 @@ def _execute_transaction(self, client, transaction_proto):
8181

8282
receipt = self.get_receipt(client)
8383
return receipt
84-
85-
def get_receipt(self, client, timeout=60):
86-
"""
87-
Retrieves the receipt for the transaction.
88-
89-
Args:
90-
client (Client): The client instance.
91-
timeout (int): Maximum time in seconds to wait for the receipt.
92-
93-
Returns:
94-
TransactionReceipt: The transaction receipt from the network.
95-
96-
Raises:
97-
Exception: If the transaction ID is not set or if receipt retrieval fails.
98-
"""
99-
if self.transaction_id is None:
100-
raise Exception("Transaction ID is not set.")
101-
102-
receipt = client.get_transaction_receipt(self.transaction_id, timeout)
103-
return receipt

src/hedera_sdk_python/consensus/topic_delete_transaction.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,4 @@ def _execute_transaction(self, client, transaction_proto):
6464
raise Exception(f"Error during transaction submission: {error_code} ({error_message})")
6565

6666
receipt = self.get_receipt(client)
67-
return receipt
68-
69-
def get_receipt(self, client, timeout=60):
70-
"""
71-
Retrieves the receipt for the transaction.
72-
73-
Args:
74-
client (Client): The client instance.
75-
timeout (int): Maximum time in seconds to wait for the receipt.
76-
77-
Returns:
78-
TransactionReceipt: The transaction receipt from the network.
79-
80-
Raises:
81-
Exception: If the transaction ID is not set or if receipt retrieval fails.
82-
"""
83-
if self.transaction_id is None:
84-
raise Exception("Transaction ID is not set.")
85-
86-
receipt = client.get_transaction_receipt(self.transaction_id, timeout)
8767
return receipt

src/hedera_sdk_python/consensus/topic_message_submit_transaction.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -85,23 +85,3 @@ def _execute_transaction(self, client, transaction_proto):
8585

8686
receipt = self.get_receipt(client)
8787
return receipt
88-
89-
def get_receipt(self, client, timeout=60):
90-
"""
91-
Retrieves the receipt for the transaction.
92-
93-
Args:
94-
client (Client): The client instance.
95-
timeout (int): Maximum time in seconds to wait for the receipt.
96-
97-
Returns:
98-
TransactionReceipt: The transaction receipt from the network.
99-
100-
Raises:
101-
Exception: If the transaction ID is not set or if receipt retrieval fails.
102-
"""
103-
if self.transaction_id is None:
104-
raise Exception("Transaction ID is not set.")
105-
106-
receipt = client.get_transaction_receipt(self.transaction_id, timeout)
107-
return receipt

src/hedera_sdk_python/consensus/topic_update_transaction.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(
1616
):
1717
super().__init__()
1818
self.topic_id = topic_id
19-
self.memo = memo
19+
self.memo = memo or ""
2020
self.admin_key = admin_key
2121
self.submit_key = submit_key
2222
self.auto_renew_period = auto_renew_period
@@ -171,23 +171,3 @@ def _execute_transaction(self, client, transaction_proto):
171171

172172
receipt = self.get_receipt(client)
173173
return receipt
174-
175-
def get_receipt(self, client, timeout=60):
176-
"""
177-
Retrieves the receipt for the transaction.
178-
179-
Args:
180-
client (Client): The client instance.
181-
timeout (int): Maximum time in seconds to wait for the receipt.
182-
183-
Returns:
184-
TransactionReceipt: The transaction receipt from the network.
185-
186-
Raises:
187-
Exception: If the transaction ID is not set or if receipt retrieval fails.
188-
"""
189-
if self.transaction_id is None:
190-
raise Exception("Transaction ID is not set.")
191-
192-
receipt = client.get_transaction_receipt(self.transaction_id, timeout)
193-
return receipt

src/hedera_sdk_python/crypto/private_key.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class PrivateKey:
99
Represents a private key that can be either Ed25519 or ECDSA (secp256k1).
1010
"""
1111

12-
def __init__(self, private_key):
12+
def __init__(self, private_key: ec.EllipticCurvePrivateKey | ed25519.Ed25519PrivateKey):
1313
"""
1414
Initializes a PrivateKey from a cryptography PrivateKey object.
1515
"""
@@ -56,26 +56,21 @@ def generate_ecdsa(cls):
5656
return cls(private_key)
5757

5858
@classmethod
59-
def from_string(cls, key_str):
59+
def from_bytes(cls, key_bytes: bytes):
6060
"""
61-
Load a private key from a hex-encoded string. For Ed25519, expects 32 bytes.
61+
Load a private key from bytes. For Ed25519, expects 32 bytes.
6262
For ECDSA (secp256k1), also expects 32 bytes (raw scalar).
63-
If it's DER-encoded, tries to parse and detect Ed25519 vs ECDSA.
63+
If the key is DER-encoded, tries to parse and detect Ed25519 vs ECDSA.
6464
6565
Args:
66-
key_str (str): The hex-encoded private key string.
66+
key_bytes (bytes): Private key bytes.
6767
6868
Returns:
6969
PrivateKey: A new instance of PrivateKey.
7070
7171
Raises:
7272
ValueError: If the key is invalid or unsupported.
7373
"""
74-
try:
75-
key_bytes = bytes.fromhex(key_str)
76-
except ValueError:
77-
raise ValueError("Invalid hex-encoded private key string.")
78-
7974
if len(key_bytes) == 32:
8075
try:
8176
ed_priv = ed25519.Ed25519PrivateKey.from_private_bytes(key_bytes)
@@ -104,6 +99,29 @@ def from_string(cls, key_str):
10499

105100
raise ValueError("Unsupported private key type.")
106101

102+
@classmethod
103+
def from_string(cls, key_str):
104+
"""
105+
Load a private key from a hex-encoded string. For Ed25519, expects 32 bytes.
106+
For ECDSA (secp256k1), also expects 32 bytes (raw scalar).
107+
If the key is DER-encoded, tries to parse and detect Ed25519 vs ECDSA.
108+
109+
Args:
110+
key_str (str): The hex-encoded private key string.
111+
112+
Returns:
113+
PrivateKey: A new instance of PrivateKey.
114+
115+
Raises:
116+
ValueError: If the key is invalid or unsupported.
117+
"""
118+
try:
119+
key_bytes = bytes.fromhex(key_str.removeprefix("0x"))
120+
except ValueError:
121+
raise ValueError("Invalid hex-encoded private key string.")
122+
123+
return cls.from_bytes(key_bytes)
124+
107125
def sign(self, data: bytes) -> bytes:
108126
"""
109127
Signs the given data using this private key (Ed25519 or ECDSA).

src/hedera_sdk_python/crypto/public_key.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,76 @@ class PublicKey:
66
Represents a public key that can be either Ed25519 or ECDSA (secp256k1).
77
"""
88

9-
def __init__(self, public_key):
9+
def __init__(self, public_key: ec.EllipticCurvePublicKey | ed25519.Ed25519PublicKey):
1010
"""
1111
Initializes a PublicKey from a cryptography PublicKey object.
1212
"""
1313
self._public_key = public_key
1414

15+
@classmethod
16+
def from_bytes(cls, key_bytes: bytes):
17+
"""
18+
Load a public key from bytes.
19+
For Ed25519, expects 32 bytes. Raw bytes for ECDSA are not supported for now.
20+
If the key is DER-encoded, tries to parse and detect Ed25519 vs ECDSA.
21+
22+
Args:
23+
key_bytes (bytes): Public key bytes.
24+
25+
Returns:
26+
PublicKey: A new instance of PublicKey.
27+
28+
Raises:
29+
ValueError: If the key is invalid or unsupported.
30+
"""
31+
32+
if len(key_bytes) == 32:
33+
ed_public = ed25519.Ed25519PublicKey.from_public_bytes(key_bytes)
34+
return cls(ed_public)
35+
# TODO: Consider adding support for creating ECDSA public key instance from raw encoded point
36+
# Java SDK example: https://github.com/hiero-ledger/hiero-sdk-java/blob/main/sdk-java/src/main/java/org/hiero/sdk/java/PublicKeyECDSA.java#L46
37+
#
38+
# Potential approach for Python:
39+
# ec_public = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), key_bytes)
40+
41+
try:
42+
public_key = serialization.load_der_public_key(key_bytes)
43+
except Exception as e:
44+
raise ValueError(f"Failed to load public key (DER): {e}")
45+
46+
if isinstance(public_key, ed25519.Ed25519PublicKey):
47+
return cls(public_key)
48+
49+
if isinstance(public_key, ec.EllipticCurvePublicKey):
50+
if not isinstance(public_key.curve, ec.SECP256K1):
51+
raise ValueError("Only secp256k1 ECDSA is supported.")
52+
return cls(public_key)
53+
54+
raise ValueError("Unsupported public key type.")
55+
56+
@classmethod
57+
def from_string(cls, key_str):
58+
"""
59+
Load a public key from a hex-encoded string.
60+
For Ed25519, expects 32 bytes. Raw bytes string for ECDSA is not supported for now.
61+
If the key is DER-encoded, tries to parse and detect Ed25519 vs ECDSA.
62+
63+
Args:
64+
key_str (str): The hex-encoded public key string.
65+
66+
Returns:
67+
PublicKey: A new instance of PublicKey.
68+
69+
Raises:
70+
ValueError: If the key is invalid or unsupported.
71+
"""
72+
try:
73+
key_bytes = bytes.fromhex(key_str.removeprefix("0x"))
74+
except ValueError:
75+
raise ValueError("Invalid hex-encoded public key string.")
76+
77+
return cls.from_bytes(key_bytes)
78+
1579
def verify(self, signature: bytes, data: bytes) -> None:
1680
"""
1781
Verifies a signature for the given data using this public key.

src/hedera_sdk_python/timestamp.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from datetime import datetime, timedelta
1+
from datetime import datetime, timedelta, timezone
22
import random
33
import time
44

5+
from hedera_sdk_python.hapi.services.timestamp_pb2 import Timestamp as TimestampProto
6+
57

68
class Timestamp:
79
"""
@@ -72,7 +74,7 @@ def to_date(self) -> datetime:
7274
Returns:
7375
datetime: A `datetime` instance.
7476
"""
75-
return datetime.fromtimestamp(self.seconds) + timedelta(
77+
return datetime.fromtimestamp(self.seconds, tz=timezone.utc) + timedelta(
7678
microseconds=self.nanos // 1000
7779
)
7880

@@ -92,29 +94,28 @@ def plus_nanos(self, nanos: int) -> "Timestamp":
9294

9395
return Timestamp(new_seconds, new_nanos)
9496

95-
def to_protobuf(self) -> dict:
97+
def to_protobuf(self) -> TimestampProto:
9698
"""
97-
Convert the `Timestamp` to a protobuf-compatible dictionary.
99+
Convert the `Timestamp` to corresponding protobuf object.
98100
99101
Returns:
100-
dict: A dictionary representation of the `Timestamp`.
102+
dict: A protobuf representation of the `Timestamp`.
101103
"""
102-
return {"seconds": self.seconds, "nanos": self.nanos}
104+
return TimestampProto(seconds=self.seconds, nanos=self.nanos)
103105

104106
@staticmethod
105-
def from_protobuf(pb_obj) -> "Timestamp":
107+
def from_protobuf(pb_obj: TimestampProto) -> "Timestamp":
106108
"""
107109
Create a `Timestamp` from a protobuf object.
108110
109111
Args:
110-
pb_obj (dict): A protobuf-like dictionary with `seconds` and `nanos`.
112+
pb_obj (timestamp_pb2.Timestamp): A protobuf Timestamp object.
111113
112114
Returns:
113115
Timestamp: A `Timestamp` instance.
114116
"""
115-
seconds = pb_obj.get("seconds", 0)
116-
nanos = pb_obj.get("nanos", 0)
117-
return Timestamp(seconds, nanos)
117+
118+
return Timestamp(pb_obj.seconds, pb_obj.nanos)
118119

119120
def __str__(self) -> str:
120121
"""

src/hedera_sdk_python/tokens/token_associate_transaction.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -83,23 +83,3 @@ def _execute_transaction(self, client, transaction_proto):
8383

8484
receipt = self.get_receipt(client)
8585
return receipt
86-
87-
def get_receipt(self, client, timeout=60):
88-
"""
89-
Retrieves the receipt for the transaction.
90-
91-
Args:
92-
client (Client): The client instance.
93-
timeout (int): Maximum time in seconds to wait for the receipt.
94-
95-
Returns:
96-
TransactionReceipt: The transaction receipt from the network.
97-
98-
Raises:
99-
Exception: If the transaction ID is not set or if receipt retrieval fails.
100-
"""
101-
if self.transaction_id is None:
102-
raise Exception("Transaction ID is not set.")
103-
104-
receipt = client.get_transaction_receipt(self.transaction_id, timeout)
105-
return receipt

0 commit comments

Comments
 (0)