77import unittest
88from typing import cast
99
10- from ecdsa import SECP256k1 , SigningKey , VerifyingKey , util
10+ from cryptography .hazmat .primitives .asymmetric import ec
11+ from cryptography .hazmat .primitives import hashes , serialization
12+ from cryptography .hazmat .primitives .asymmetric .utils import (
13+ decode_dss_signature ,
14+ encode_dss_signature ,
15+ )
1116
1217from . import asymmetric_crypto
1318from .bcs import Deserializer , Serializer
1621class PrivateKey (asymmetric_crypto .PrivateKey ):
1722 LENGTH : int = 32
1823
19- key : SigningKey
24+ key : ec . EllipticCurvePrivateKey
2025
21- def __init__ (self , key : SigningKey ):
26+ def __init__ (self , key : ec . EllipticCurvePrivateKey ):
2227 self .key = key
2328
2429 def __eq__ (self , other : object ):
2530 if not isinstance (other , PrivateKey ):
2631 return NotImplemented
27- return self .key == other .key
32+ # Compare private values
33+ return self ._to_bytes () == other ._to_bytes ()
2834
2935 def __str__ (self ):
3036 return self .aip80 ()
3137
38+ def _to_bytes (self ) -> bytes :
39+ """Convert private key to raw 32-byte representation."""
40+ private_numbers = self .key .private_numbers ()
41+ return private_numbers .private_value .to_bytes (PrivateKey .LENGTH , byteorder = "big" )
42+
43+ @staticmethod
44+ def _from_bytes (key_bytes : bytes ) -> ec .EllipticCurvePrivateKey :
45+ """Create private key from raw 32-byte representation."""
46+ if len (key_bytes ) != PrivateKey .LENGTH :
47+ raise Exception ("Length mismatch" )
48+ private_value = int .from_bytes (key_bytes , byteorder = "big" )
49+ return ec .derive_private_key (private_value , ec .SECP256K1 ())
50+
3251 @staticmethod
3352 def from_hex (value : str | bytes , strict : bool | None = None ) -> PrivateKey :
3453 """
@@ -43,9 +62,7 @@ def from_hex(value: str | bytes, strict: bool | None = None) -> PrivateKey:
4362 )
4463 if len (parsed_value .hex ()) != PrivateKey .LENGTH * 2 :
4564 raise Exception ("Length mismatch" )
46- return PrivateKey (
47- SigningKey .from_string (parsed_value , SECP256k1 , hashlib .sha3_256 )
48- )
65+ return PrivateKey (PrivateKey ._from_bytes (parsed_value ))
4966
5067 @staticmethod
5168 def from_str (value : str , strict : bool | None = None ) -> PrivateKey :
@@ -59,61 +76,95 @@ def from_str(value: str, strict: bool | None = None) -> PrivateKey:
5976 return PrivateKey .from_hex (value , strict )
6077
6178 def hex (self ) -> str :
62- return f"0x{ self .key . to_string ().hex ()} "
79+ return f"0x{ self ._to_bytes ().hex ()} "
6380
6481 def aip80 (self ) -> str :
6582 return PrivateKey .format_private_key (
6683 self .hex (), asymmetric_crypto .PrivateKeyVariant .Secp256k1
6784 )
6885
6986 def public_key (self ) -> PublicKey :
70- return PublicKey (self .key .verifying_key )
87+ return PublicKey (self .key .public_key () )
7188
7289 @staticmethod
7390 def random () -> PrivateKey :
74- return PrivateKey (
75- SigningKey .generate (curve = SECP256k1 , hashfunc = hashlib .sha3_256 )
76- )
91+ return PrivateKey (ec .generate_private_key (ec .SECP256K1 ()))
7792
7893 def sign (self , data : bytes ) -> Signature :
79- sig = self .key .sign_deterministic (data , hashfunc = hashlib .sha3_256 )
80- n = SECP256k1 .generator .order ()
81- r , s = util .sigdecode_string (sig , n )
94+ # Use deterministic ECDSA (RFC 6979) with SHA3-256
95+ signature_der = self .key .sign (
96+ data , ec .ECDSA (hashes .SHA3_256 ())
97+ )
98+ # Decode DER signature to get r and s
99+ r , s = decode_dss_signature (signature_der )
100+
101+ # SECP256K1 curve order
102+ n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
103+
82104 # The signature is valid for both s and -s, normalization ensures that only s < n // 2 is valid
83105 if s > (n // 2 ):
84- mod_s = (s * - 1 ) % n
85- sig = util .sigencode_string (r , mod_s , n )
86- return Signature (sig )
106+ s = n - s
107+
108+ # Encode r and s as raw bytes (32 bytes each)
109+ sig_bytes = r .to_bytes (32 , byteorder = "big" ) + s .to_bytes (32 , byteorder = "big" )
110+ return Signature (sig_bytes )
87111
88112 @staticmethod
89113 def deserialize (deserializer : Deserializer ) -> PrivateKey :
90114 key = deserializer .to_bytes ()
91115 if len (key ) != PrivateKey .LENGTH :
92116 raise Exception ("Length mismatch" )
93117
94- return PrivateKey (SigningKey . from_string (key , SECP256k1 , hashlib . sha3_256 ))
118+ return PrivateKey (PrivateKey . _from_bytes (key ))
95119
96120 def serialize (self , serializer : Serializer ):
97- serializer .to_bytes (self .key . to_string ())
121+ serializer .to_bytes (self ._to_bytes ())
98122
99123
100124class PublicKey (asymmetric_crypto .PublicKey ):
101125 LENGTH : int = 64
102126 LENGTH_WITH_PREFIX_LENGTH : int = 65
103127
104- key : VerifyingKey
128+ key : ec . EllipticCurvePublicKey
105129
106- def __init__ (self , key : VerifyingKey ):
130+ def __init__ (self , key : ec . EllipticCurvePublicKey ):
107131 self .key = key
108132
109133 def __eq__ (self , other : object ):
110134 if not isinstance (other , PublicKey ):
111135 return NotImplemented
112- return self .key == other .key
136+ # Compare public key bytes
137+ return self .to_crypto_bytes () == other .to_crypto_bytes ()
113138
114139 def __str__ (self ) -> str :
115140 return self .hex ()
116141
142+ @staticmethod
143+ def _from_uncompressed_bytes (key_bytes : bytes ) -> ec .EllipticCurvePublicKey :
144+ """Create public key from uncompressed format (64 or 65 bytes)."""
145+ # Handle optional 0x04 prefix
146+ if len (key_bytes ) == PublicKey .LENGTH_WITH_PREFIX_LENGTH :
147+ if key_bytes [0 ] != 0x04 :
148+ raise Exception ("Invalid public key format" )
149+ key_bytes = key_bytes [1 :]
150+ elif len (key_bytes ) != PublicKey .LENGTH :
151+ raise Exception ("Length mismatch" )
152+
153+ # Split into x and y coordinates
154+ x = int .from_bytes (key_bytes [:32 ], byteorder = "big" )
155+ y = int .from_bytes (key_bytes [32 :], byteorder = "big" )
156+
157+ # Create public key from numbers
158+ public_numbers = ec .EllipticCurvePublicNumbers (x , y , ec .SECP256K1 ())
159+ return public_numbers .public_key ()
160+
161+ def _to_uncompressed_bytes (self ) -> bytes :
162+ """Convert public key to uncompressed format (64 bytes, no prefix)."""
163+ public_numbers = self .key .public_numbers ()
164+ x_bytes = public_numbers .x .to_bytes (32 , byteorder = "big" )
165+ y_bytes = public_numbers .y .to_bytes (32 , byteorder = "big" )
166+ return x_bytes + y_bytes
167+
117168 @staticmethod
118169 def from_str (value : str ) -> PublicKey :
119170 if value [0 :2 ] == "0x" :
@@ -124,23 +175,30 @@ def from_str(value: str) -> PublicKey:
124175 and len (value ) != PublicKey .LENGTH_WITH_PREFIX_LENGTH * 2
125176 ):
126177 raise Exception ("Length mismatch" )
127- return PublicKey (
128- VerifyingKey .from_string (bytes .fromhex (value ), SECP256k1 , hashlib .sha3_256 )
129- )
178+ return PublicKey (PublicKey ._from_uncompressed_bytes (bytes .fromhex (value )))
130179
131180 def hex (self ) -> str :
132- return f"0x04{ self .key . to_string ().hex ()} "
181+ return f"0x04{ self ._to_uncompressed_bytes ().hex ()} "
133182
134183 def verify (self , data : bytes , signature : asymmetric_crypto .Signature ) -> bool :
135184 try :
136185 signature = cast (Signature , signature )
137- self .key .verify (signature .data (), data )
186+ sig_bytes = signature .data ()
187+
188+ # Parse r and s from raw signature bytes (32 bytes each)
189+ r = int .from_bytes (sig_bytes [:32 ], byteorder = "big" )
190+ s = int .from_bytes (sig_bytes [32 :], byteorder = "big" )
191+
192+ # Encode as DER for verification
193+ sig_der = encode_dss_signature (r , s )
194+
195+ self .key .verify (sig_der , data , ec .ECDSA (hashes .SHA3_256 ()))
138196 except Exception :
139197 return False
140198 return True
141199
142200 def to_crypto_bytes (self ) -> bytes :
143- return b"\x04 " + self .key . to_string ()
201+ return b"\x04 " + self ._to_uncompressed_bytes ()
144202
145203 @staticmethod
146204 def deserialize (deserializer : Deserializer ) -> PublicKey :
@@ -152,7 +210,7 @@ def deserialize(deserializer: Deserializer) -> PublicKey:
152210 else :
153211 raise Exception ("Length mismatch" )
154212
155- return PublicKey (VerifyingKey . from_string (key , SECP256k1 , hashlib . sha3_256 ))
213+ return PublicKey (PublicKey . _from_uncompressed_bytes (key ))
156214
157215 def serialize (self , serializer : Serializer ):
158216 serializer .to_bytes (self .to_crypto_bytes ())
0 commit comments