Skip to content
Closed

test #70

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
2 changes: 2 additions & 0 deletions tck/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@

# Import all handler modules to trigger @register_handler decorators
from . import sdk # setup, reset
from . import consensus # createTopic
from . import key # generateKey

__all__ = ["get_handler", "get_all_handlers", "safe_dispatch", "validate_request_params"]
163 changes: 163 additions & 0 deletions tck/handlers/consensus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
"""TCK handlers for Consensus Service operations (Topics)."""

from typing import Any, Dict, Optional, List
from hiero_sdk_python import (
Client, PrivateKey, PublicKey, TopicCreateTransaction, Duration,
AccountId, ResponseCode, TopicId
)
from hiero_sdk_python.utils.key_utils import Key
from .registry import register_handler, validate_request_params
from ..client_manager import get_client
from ..errors import JsonRpcError, INTERNAL_ERROR, INVALID_PARAMS, HIERO_ERROR


def _parse_key(key_string: Optional[str]) -> Optional[Key]:
"""Parse a key from a hex string.

Args:
key_string: Hex-encoded key string

Returns:
PublicKey or PrivateKey instance, or None if key_string is None

Raises:
JsonRpcError: If the key cannot be parsed
"""
if key_string is None:
return None

try:
# Try to parse as a private key first
try:
return PrivateKey.from_string(key_string)
except Exception:
# If it fails, try as a public key
return PublicKey.from_string(key_string)
except Exception as e:
raise JsonRpcError(INTERNAL_ERROR, f"Failed to parse key: {str(e)}")
Comment on lines +29 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using INVALID_PARAMS instead of INTERNAL_ERROR for key parsing failures.

When a user provides an invalid key string, this is an input validation error rather than an internal server error. Using INTERNAL_ERROR may mislead clients about the nature of the failure.

♻️ Proposed fix
     except Exception as e:
-        raise JsonRpcError(INTERNAL_ERROR, f"Failed to parse key: {str(e)}")
+        raise JsonRpcError(INVALID_PARAMS, f"Invalid key format: {str(e)}")



def _get_signers(signers_list: Optional[List[str]]) -> List[PrivateKey]:
"""Parse and return list of private keys for signing.

Args:
signers_list: List of hex-encoded private key strings

Returns:
List of PrivateKey instances
"""
if not signers_list:
return []

keys = []
for signer_str in signers_list:
try:
key = PrivateKey.from_string(signer_str)
keys.append(key)
except Exception as e:
raise JsonRpcError(INTERNAL_ERROR, f"Failed to parse signer key: {str(e)}")
Comment on lines +57 to +58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Same error code concern: Consider using INVALID_PARAMS for signer key parsing failures.

Consistent with the _parse_key function, invalid signer keys are user input errors.

♻️ Proposed fix
         except Exception as e:
-            raise JsonRpcError(INTERNAL_ERROR, f"Failed to parse signer key: {str(e)}")
+            raise JsonRpcError(INVALID_PARAMS, f"Invalid signer key format: {str(e)}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception as e:
raise JsonRpcError(INTERNAL_ERROR, f"Failed to parse signer key: {str(e)}")
except Exception as e:
raise JsonRpcError(INVALID_PARAMS, f"Invalid signer key format: {str(e)}")

return keys


@register_handler("createTopic")
def create_topic_handler(params: Dict[str, Any], session_id: Optional[str] = None) -> Dict[str, Any]:
"""Create a new topic using TopicCreateTransaction.

Params:
memo (str, optional): Topic memo
adminKey (str, optional): Admin key (hex-encoded)
submitKey (str, optional): Submit key (hex-encoded)
autoRenewPeriod (str, optional): Auto-renew period in seconds
autoRenewAccountId (str, optional): Account ID for auto-renewal
commonTransactionParams (dict, optional): Common params including signers

Returns:
Dict with status and topicId
"""
# Get client from session
client = get_client(session_id or "default")
if client is None:
raise JsonRpcError(INTERNAL_ERROR, "No client initialized for this session")

try:
# Create the transaction
tx = TopicCreateTransaction()

# Set memo if provided
if "memo" in params and params["memo"] is not None:
memo = params["memo"]
# Validate memo length (max 100 bytes)
if len(memo.encode('utf-8')) > 100:
raise JsonRpcError(HIERO_ERROR, "Hiero error", {"status": "MEMO_TOO_LONG"})
# Check for null bytes
if '\0' in memo:
raise JsonRpcError(HIERO_ERROR, "Hiero error", {"status": "INVALID_ZERO_BYTE_IN_STRING"})
tx.set_memo(memo)

# Set admin key if provided
if "adminKey" in params and params["adminKey"] is not None:
admin_key = _parse_key(params["adminKey"])
if admin_key:
tx.set_admin_key(admin_key)

# Set submit key if provided
if "submitKey" in params and params["submitKey"] is not None:
submit_key = _parse_key(params["submitKey"])
if submit_key:
tx.set_submit_key(submit_key)

# Set auto-renew period if provided
if "autoRenewPeriod" in params and params["autoRenewPeriod"] is not None:
try:
period = int(params["autoRenewPeriod"])
# Validate auto-renew period (approximate range: 6999999 to 8000001)
if period <= 0 or period < 6999999 or period > 8000001:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Redundant condition in auto-renew period validation.

The condition period <= 0 or period < 6999999 is redundant since period < 6999999 already covers all non-positive values.

♻️ Proposed simplification
-                if period <= 0 or period < 6999999 or period > 8000001:
+                if period < 6999999 or period > 8000001:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if period <= 0 or period < 6999999 or period > 8000001:
if period < 6999999 or period > 8000001:

raise JsonRpcError(HIERO_ERROR, "Hiero error", {"status": "AUTORENEW_DURATION_NOT_IN_RANGE"})
tx.set_auto_renew_period(period)
except ValueError:
raise JsonRpcError(INVALID_PARAMS, "Invalid params", "autoRenewPeriod must be a string representing an integer")
Comment on lines +117 to +118
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Missing exception chaining.

The raise statement discards the original exception context. Use raise ... from None if intentional, or preserve the chain for debugging.

♻️ Proposed fix to preserve exception chain
             except ValueError:
-                raise JsonRpcError(INVALID_PARAMS, "Invalid params", "autoRenewPeriod must be a string representing an integer")
+                raise JsonRpcError(INVALID_PARAMS, "Invalid params", "autoRenewPeriod must be a string representing an integer") from None


# Set auto-renew account if provided
if "autoRenewAccountId" in params and params["autoRenewAccountId"] is not None:
try:
auto_renew_account = AccountId.from_string(params["autoRenewAccountId"])
tx.set_auto_renew_account(auto_renew_account)
except Exception as e:
raise JsonRpcError(INVALID_PARAMS, "Invalid params", f"Invalid autoRenewAccountId: {str(e)}")

# Freeze the transaction with the client
tx.freeze_with(client)

# Get signers from commonTransactionParams if provided
signers = []
if "commonTransactionParams" in params:
common_params = params["commonTransactionParams"]
if "signers" in common_params and isinstance(common_params["signers"], list):
signers = _get_signers(common_params["signers"])

# Sign the transaction with any additional signers
for signer in signers:
tx.sign(signer)

# Execute the transaction
receipt = tx.execute(client)

# Check the receipt status
if receipt.status != ResponseCode.SUCCESS:
status_name = ResponseCode(receipt.status).name
raise JsonRpcError(HIERO_ERROR, "Hiero error", {"status": status_name})

# Extract the topic ID
topic_id = receipt.topic_id
if not topic_id:
raise JsonRpcError(INTERNAL_ERROR, "Transaction succeeded but no topic ID returned")

return {
"status": ResponseCode(receipt.status).name,
"topicId": str(topic_id)
}

except JsonRpcError:
raise
except Exception as e:
raise JsonRpcError(HIERO_ERROR, "Hiero error", str(e))
185 changes: 185 additions & 0 deletions tck/handlers/key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""TCK handlers for key generation and manipulation."""

from typing import Any, Dict, Optional, List
from hiero_sdk_python import PrivateKey, PublicKey
from hiero_sdk_python.hapi.services import basic_types_pb2
from hiero_sdk_python.utils.key_utils import key_to_proto
from .registry import register_handler
from ..errors import JsonRpcError, INVALID_PARAMS, INTERNAL_ERROR


def _key_to_hex(key: Any) -> str:
"""Convert a key to its DER-encoded hex representation.

Args:
key: PrivateKey or PublicKey instance

Returns:
Hex string representation of the key
"""
proto_key = key_to_proto(key)
if proto_key is None:
raise JsonRpcError(INTERNAL_ERROR, "Failed to convert key to protobuf")

# Serialize to bytes and convert to hex
key_bytes = proto_key.SerializeToString()
return key_bytes.hex().upper()
Comment on lines +11 to +26
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify key_to_proto extracts public key from private key
ast-grep --pattern $'def key_to_proto($$$):
    $$$
'

Repository: MonaaEid/hiero-sdk-python

Length of output: 51


🏁 Script executed:

cd /tmp && find . -name "key_utils.py" -type f 2>/dev/null | head -5

Repository: MonaaEid/hiero-sdk-python

Length of output: 51


🏁 Script executed:

rg "def key_to_proto" --type py -A 10

Repository: MonaaEid/hiero-sdk-python

Length of output: 1056


🏁 Script executed:

rg "key_to_proto" --type py -B 2 -A 5 | head -50

Repository: MonaaEid/hiero-sdk-python

Length of output: 3238


🏁 Script executed:

sed -n '30,50p' src/hiero_sdk_python/utils/key_utils.py

Repository: MonaaEid/hiero-sdk-python

Length of output: 712


🏁 Script executed:

cat src/hiero_sdk_python/utils/key_utils.py | head -80

Repository: MonaaEid/hiero-sdk-python

Length of output: 1739


🏁 Script executed:

cat -n tck/handlers/key.py | sed -n '50,70p'

Repository: MonaaEid/hiero-sdk-python

Length of output: 1049


🏁 Script executed:

cat -n tck/handlers/key.py | sed -n '110,165p'

Repository: MonaaEid/hiero-sdk-python

Length of output: 3241


🏁 Script executed:

rg "def to_string_der" --type py -A 3

Repository: MonaaEid/hiero-sdk-python

Length of output: 665


🏁 Script executed:

rg "class PrivateKey" --type py -A 20 | head -50

Repository: MonaaEid/hiero-sdk-python

Length of output: 1629


🏁 Script executed:

rg "def to_string_der\|def to_bytes_raw\|def to_string_raw" src/hiero_sdk_python/crypto/private_key.py -A 5

Repository: MonaaEid/hiero-sdk-python

Length of output: 51


🏁 Script executed:

cat -n src/hiero_sdk_python/crypto/private_key.py | grep -A 10 "def to_string_der"

Repository: MonaaEid/hiero-sdk-python

Length of output: 469


🏁 Script executed:

cat -n src/hiero_sdk_python/crypto/private_key.py | sed -n '386,400p'

Repository: MonaaEid/hiero-sdk-python

Length of output: 614


Critical: _key_to_hex returns public key hex for private keys, not private key hex.

The key_to_proto function in src/hiero_sdk_python/utils/key_utils.py (lines 38-46) extracts and converts the public key when a PrivateKey is passed: return key.public_key()._to_proto(). This means _key_to_hex(private_key) serializes the public key, not the private key.

This affects:

  • Lines 57-58: ed25519PrivateKey endpoint
  • Lines 65-66: ecdsaSecp256k1PrivateKey endpoint
  • Lines 115, 119: keyList private key serialization
  • Lines 158, 162: thresholdKey private key serialization

Use PrivateKey.to_string_der() for private key serialization:

🐛 Proposed fix
 def _key_to_hex(key: Any) -> str:
-    """Convert a key to its DER-encoded hex representation.
+    """Convert a key to its protobuf-serialized hex representation.
+    
+    Note: For PrivateKey, this returns the PUBLIC key's protobuf serialization,
+    as key_to_proto extracts the public key. Use PrivateKey.to_string_der() for private keys.
     
     Args:
         key: PrivateKey or PublicKey instance

Then update private key handlers:

         if key_type == "ed25519PrivateKey":
             private_key = PrivateKey.generate_ed25519()
+            private_key_hex = private_key.to_string_der()
             return {
-                "key": _key_to_hex(private_key),
-                "privateKeys": [_key_to_hex(private_key)]
+                "key": private_key_hex,
+                "privateKeys": [private_key_hex]
             }

Apply same fix to ecdsaSecp256k1PrivateKey, and update keyList/thresholdKey private key appends to use private_key.to_string_der() instead of _key_to_hex(private_key).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _key_to_hex(key: Any) -> str:
"""Convert a key to its DER-encoded hex representation.
Args:
key: PrivateKey or PublicKey instance
Returns:
Hex string representation of the key
"""
proto_key = key_to_proto(key)
if proto_key is None:
raise JsonRpcError(INTERNAL_ERROR, "Failed to convert key to protobuf")
# Serialize to bytes and convert to hex
key_bytes = proto_key.SerializeToString()
return key_bytes.hex().upper()
def _key_to_hex(key: Any) -> str:
"""Convert a key to its protobuf-serialized hex representation.
Note: For PrivateKey, this returns the PUBLIC key's protobuf serialization,
as key_to_proto extracts the public key. Use PrivateKey.to_string_der() for private keys.
Args:
key: PrivateKey or PublicKey instance
Returns:
Hex string representation of the key
"""
proto_key = key_to_proto(key)
if proto_key is None:
raise JsonRpcError(INTERNAL_ERROR, "Failed to convert key to protobuf")
# Serialize to bytes and convert to hex
key_bytes = proto_key.SerializeToString()
return key_bytes.hex().upper()



@register_handler("generateKey")
def generate_key_handler(params: Dict[str, Any], session_id: Optional[str] = None) -> Dict[str, Any]:
"""Generate cryptographic keys for testing.

Params:
type (str, required): Type of key to generate
- ed25519PrivateKey: Generate a new Ed25519 private key
- ecdsaSecp256k1PrivateKey: Generate a new ECDSA (secp256k1) private key
- ed25519PublicKey: Get public key from private key or generate public key
- ecdsaSecp256k1PublicKey: Get public key from private key or generate public key
- keyList: Generate a keyList with multiple keys
- thresholdKey: Generate a thresholdKey
fromKey (str, optional): Private key to derive public key from (hex string)
threshold (int, optional): Threshold for thresholdKey type
keys (list, optional): List of key specs for keyList/thresholdKey

Returns:
Dict with key information and privateKeys list
"""
try:
key_type = params.get("type")
if not key_type:
raise JsonRpcError(INVALID_PARAMS, "Missing required parameter: type")

if key_type == "ed25519PrivateKey":
# Generate new Ed25519 private key
private_key = PrivateKey.generate_ed25519()
return {
"key": _key_to_hex(private_key),
"privateKeys": [_key_to_hex(private_key)]
}

elif key_type == "ecdsaSecp256k1PrivateKey":
# Generate new ECDSA (secp256k1) private key
private_key = PrivateKey.generate_ecdsa_secp256k1()
return {
"key": _key_to_hex(private_key),
"privateKeys": [_key_to_hex(private_key)]
}

elif key_type == "ed25519PublicKey":
# Get or generate Ed25519 public key
if "fromKey" in params and params["fromKey"]:
# Derive public key from private key
private_key = PrivateKey.from_string(params["fromKey"])
public_key = private_key.public_key()
else:
# Generate new key pair and return public key
private_key = PrivateKey.generate_ed25519()
public_key = private_key.public_key()

return {
"key": _key_to_hex(public_key)
}

elif key_type == "ecdsaSecp256k1PublicKey":
# Get or generate ECDSA (secp256k1) public key
if "fromKey" in params and params["fromKey"]:
# Derive public key from private key
private_key = PrivateKey.from_string(params["fromKey"])
public_key = private_key.public_key()
else:
# Generate new key pair and return public key
private_key = PrivateKey.generate_ecdsa_secp256k1()
public_key = private_key.public_key()

return {
"key": _key_to_hex(public_key)
}
Comment on lines +69 to +97
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if PrivateKey has methods to determine key type
ast-grep --pattern $'class PrivateKey:
    $$$
    def is_ed25519($$$):
        $$$
    $$$
}'

Repository: MonaaEid/hiero-sdk-python

Length of output: 51


🏁 Script executed:

# First, let's explore the repository structure and find where PrivateKey is imported from
cat tck/handlers/key.py | head -30

Repository: MonaaEid/hiero-sdk-python

Length of output: 1082


🏁 Script executed:

# Also search for the PrivateKey class definition
fd -t f -e py | xargs rg "class PrivateKey" -l

Repository: MonaaEid/hiero-sdk-python

Length of output: 111


🏁 Script executed:

# Look for the from_string method definition
rg "def from_string" -A 5 -B 2

Repository: MonaaEid/hiero-sdk-python

Length of output: 12219


🏁 Script executed:

sed -n '1,100p' src/hiero_sdk_python/crypto/private_key.py

Repository: MonaaEid/hiero-sdk-python

Length of output: 3707


🏁 Script executed:

# Look for the full from_string method
sed -n '/def from_string/,/^[[:space:]]*def /p' src/hiero_sdk_python/crypto/private_key.py | head -50

Repository: MonaaEid/hiero-sdk-python

Length of output: 1619


🏁 Script executed:

# Check if PrivateKey has any methods/properties to determine its type
rg "def.*type|self\._.*type|self\.key_type" src/hiero_sdk_python/crypto/private_key.py

Repository: MonaaEid/hiero-sdk-python

Length of output: 231


🏁 Script executed:

# View the specific lines 69-97 in the handler file
sed -n '69,97p' tck/handlers/key.py

Repository: MonaaEid/hiero-sdk-python

Length of output: 1342


🏁 Script executed:

# Find and examine the from_bytes method
sed -n '/def from_bytes/,/^[[:space:]]*def /p' src/hiero_sdk_python/crypto/private_key.py | head -80

Repository: MonaaEid/hiero-sdk-python

Length of output: 2012


🏁 Script executed:

# Also check if PrivateKey has a method to get/determine its type
rg "def.*_key_type|def.*is_ed|def.*is_ecdsa|_private_key\.__class__" src/hiero_sdk_python/crypto/private_key.py

Repository: MonaaEid/hiero-sdk-python

Length of output: 134


🏁 Script executed:

# Check the full context around lines 69-97 and see if there's any validation
sed -n '50,110p' tck/handlers/key.py

Repository: MonaaEid/hiero-sdk-python

Length of output: 2650


🏁 Script executed:

# Also look for the is_ed25519 and is_ecdsa method implementations
sed -n '/def is_ed25519/,/^[[:space:]]*def /p' src/hiero_sdk_python/crypto/private_key.py | head -30

Repository: MonaaEid/hiero-sdk-python

Length of output: 348


🏁 Script executed:

# Check is_ecdsa as well
sed -n '/def is_ecdsa/,/^[[:space:]]*def /p' src/hiero_sdk_python/crypto/private_key.py | head -30

Repository: MonaaEid/hiero-sdk-python

Length of output: 342


Use key-type-specific loaders and validate key types when deriving from fromKey.

When fromKey is provided for ecdsaSecp256k1PublicKey, the code uses PrivateKey.from_string(), which defaults to Ed25519 for 32-byte keys (trying Ed25519 first, then ECDSA). This means a 32-byte ECDSA private key will be silently interpreted as Ed25519, producing an incorrect public key.

Fix: For ecdsaSecp256k1PublicKey, use PrivateKey.from_string_ecdsa() instead of PrivateKey.from_string(). Alternatively, validate the loaded key type using private_key.is_ed25519() or private_key.is_ecdsa() matches the requested type.


elif key_type == "keyList":
# Generate a KeyList with multiple keys
keys_spec = params.get("keys", [])
if not isinstance(keys_spec, list):
raise JsonRpcError(INVALID_PARAMS, "keys parameter must be a list")

key_list_proto = basic_types_pb2.KeyList()
private_keys_hex = []

for key_spec in keys_spec:
if not isinstance(key_spec, dict):
raise JsonRpcError(INVALID_PARAMS, "Each key spec must be a dict")

spec_type = key_spec.get("type")
if spec_type == "ed25519PrivateKey":
private_key = PrivateKey.generate_ed25519()
private_keys_hex.append(_key_to_hex(private_key))
proto_key = key_to_proto(private_key)
elif spec_type == "ecdsaSecp256k1PrivateKey":
private_key = PrivateKey.generate_ecdsa_secp256k1()
private_keys_hex.append(_key_to_hex(private_key))
proto_key = key_to_proto(private_key)
else:
raise JsonRpcError(INVALID_PARAMS, f"Unsupported key type in keyList: {spec_type}")

if proto_key:
key_list_proto.keys.append(proto_key)
Comment on lines +108 to +125
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Code duplication in key spec parsing.

The key spec parsing logic for keyList (lines 108-125) and thresholdKey (lines 151-168) is nearly identical. Consider extracting a helper function to reduce duplication.

♻️ Proposed refactoring
def _parse_key_spec(key_spec: Dict[str, Any]) -> tuple[PrivateKey, Any]:
    """Parse a key spec and return (private_key, proto_key)."""
    if not isinstance(key_spec, dict):
        raise JsonRpcError(INVALID_PARAMS, "Each key spec must be a dict")
    
    spec_type = key_spec.get("type")
    if spec_type == "ed25519PrivateKey":
        private_key = PrivateKey.generate_ed25519()
    elif spec_type == "ecdsaSecp256k1PrivateKey":
        private_key = PrivateKey.generate_ecdsa_secp256k1()
    else:
        raise JsonRpcError(INVALID_PARAMS, f"Unsupported key type: {spec_type}")
    
    return private_key, key_to_proto(private_key)

Also applies to: 151-168


# Serialize KeyList to bytes and convert to hex
key_list_bytes = key_list_proto.SerializeToString()
key_list_hex = key_list_bytes.hex().upper()

return {
"key": key_list_hex,
"privateKeys": private_keys_hex
}

elif key_type == "thresholdKey":
# Generate a ThresholdKey with threshold and keys
threshold = params.get("threshold")
if threshold is None:
raise JsonRpcError(INVALID_PARAMS, "threshold parameter required for thresholdKey")

keys_spec = params.get("keys", [])
if not isinstance(keys_spec, list):
raise JsonRpcError(INVALID_PARAMS, "keys parameter must be a list")

threshold_key_proto = basic_types_pb2.ThresholdKey()
threshold_key_proto.threshold = int(threshold)

private_keys_hex = []

for key_spec in keys_spec:
if not isinstance(key_spec, dict):
raise JsonRpcError(INVALID_PARAMS, "Each key spec must be a dict")

spec_type = key_spec.get("type")
if spec_type == "ed25519PrivateKey":
private_key = PrivateKey.generate_ed25519()
private_keys_hex.append(_key_to_hex(private_key))
proto_key = key_to_proto(private_key)
elif spec_type == "ecdsaSecp256k1PrivateKey":
private_key = PrivateKey.generate_ecdsa_secp256k1()
private_keys_hex.append(_key_to_hex(private_key))
proto_key = key_to_proto(private_key)
else:
raise JsonRpcError(INVALID_PARAMS, f"Unsupported key type in thresholdKey: {spec_type}")

if proto_key:
threshold_key_proto.keys.keys.append(proto_key)

# Serialize ThresholdKey to bytes and convert to hex
threshold_key_bytes = threshold_key_proto.SerializeToString()
threshold_key_hex = threshold_key_bytes.hex().upper()

return {
"key": threshold_key_hex,
"privateKeys": private_keys_hex
}

else:
raise JsonRpcError(INVALID_PARAMS, f"Unsupported key type: {key_type}")

except JsonRpcError:
raise
except Exception as e:
raise JsonRpcError(INTERNAL_ERROR, f"Failed to generate key: {str(e)}")
Loading
Loading