@@ -528,74 +528,99 @@ class PublicKey:
528528 returns the corresponding P2trAddress object
529529 """
530530
531- def __init__ (self , hex_str : str ) -> None :
531+ def __init__ (self , hex_str : str = None , message : str = None , signature : bytes = None ) -> None :
532532 """
533533 Parameters
534534 ----------
535535 hex_str : str
536536 the public key in hex string
537+
538+ In case of generating public key from message and signature:-
539+ message : str
540+ the message
541+ signature : str
542+ the compressed signature in bytes
537543
538544 Raises
539545 ------
540546 TypeError
541547 If first byte of public key (corresponding to SEC format) is
542548 invalid.
543549 """
544- hex_str = hex_str .strip ()
550+ if hex_str :
551+ hex_str = hex_str .strip ()
545552
546- # Normalize hex string by removing '0x' prefix and any whitespace
547- if hex_str .lower ().startswith ('0x' ):
548- hex_str = hex_str [2 :]
553+ # Normalize hex string by removing '0x' prefix and any whitespace
554+ if hex_str .lower ().startswith ('0x' ):
555+ hex_str = hex_str [2 :]
549556
550- # expects key as hex string - SEC format
551- first_byte_in_hex = hex_str [:2 ] # 2 hex chars = 1 byte
552- hex_bytes = h_to_b (hex_str )
557+ # expects key as hex string - SEC format
558+ first_byte_in_hex = hex_str [:2 ] # 2 hex chars = 1 byte
559+ hex_bytes = h_to_b (hex_str )
553560
554- taproot = False
561+ taproot = False
555562
556- # check if compressed or not
557- if len (hex_bytes ) > 33 :
558- # uncompressed - SEC format: 0x04 + x + y coordinates (x,y are 32 byte
559- # numbers)
563+ # check if compressed or not
564+ if len (hex_bytes ) > 33 :
565+ # uncompressed - SEC format: 0x04 + x + y coordinates (x,y are 32 byte
566+ # numbers)
560567
561- # remove first byte and instantiate ecdsa key
562- self .key = VerifyingKey .from_string (hex_bytes [1 :], curve = SECP256k1 )
563- elif len (hex_bytes ) > 31 :
564- # key is either compressed or in x-only taproot format
568+ # remove first byte and instantiate ecdsa key
569+ self .key = VerifyingKey .from_string (hex_bytes [1 :], curve = SECP256k1 )
570+ elif len (hex_bytes ) > 31 :
571+ # key is either compressed or in x-only taproot format
565572
566- # taproot public keys are exactly 32 bytes
567- if len (hex_bytes ) == 32 :
568- taproot = True
573+ # taproot public keys are exactly 32 bytes
574+ if len (hex_bytes ) == 32 :
575+ taproot = True
569576
570- # compressed - SEC FORMAT: 0x02|0x03 + x coordinate (if 02 then y
571- # is even else y is odd. Calculate y and then instantiate the ecdsa key
572- x_coord = int (hex_str [2 :], 16 )
577+ # compressed - SEC FORMAT: 0x02|0x03 + x coordinate (if 02 then y
578+ # is even else y is odd. Calculate y and then instantiate the ecdsa key
579+ x_coord = int (hex_str [2 :], 16 )
573580
574- # y = modulo_square_root( (x**3 + 7) mod p ) -- there will be 2 y values
575- y_values = sqrt_mod (
576- (x_coord ** 3 + 7 ) % Secp256k1Params ._p , Secp256k1Params ._p , True
577- )
581+ # y = modulo_square_root( (x**3 + 7) mod p ) -- there will be 2 y values
582+ y_values = sqrt_mod (
583+ (x_coord ** 3 + 7 ) % Secp256k1Params ._p , Secp256k1Params ._p , True
584+ )
578585
579- assert y_values is not None
580- # check SEC format's first byte to determine which of the 2 values to use
581- if first_byte_in_hex == "02" or taproot :
582- # y is the even value
583- if y_values [0 ] % 2 == 0 : # type: ignore
584- y_coord = y_values [0 ] # type: ignore
586+ assert y_values is not None
587+ # check SEC format's first byte to determine which of the 2 values to use
588+ if first_byte_in_hex == "02" or taproot :
589+ # y is the even value
590+ if y_values [0 ] % 2 == 0 : # type: ignore
591+ y_coord = y_values [0 ] # type: ignore
592+ else :
593+ y_coord = y_values [1 ] # type: ignore
594+ elif first_byte_in_hex == "03" :
595+ # y is the odd value
596+ if y_values [0 ] % 2 == 0 : # type: ignore
597+ y_coord = y_values [1 ] # type: ignore
598+ else :
599+ y_coord = y_values [0 ] # type: ignore
585600 else :
586- y_coord = y_values [1 ] # type: ignore
587- elif first_byte_in_hex == "03" :
588- # y is the odd value
589- if y_values [0 ] % 2 == 0 : # type: ignore
590- y_coord = y_values [1 ] # type: ignore
591- else :
592- y_coord = y_values [0 ] # type: ignore
593- else :
594- raise TypeError ("Invalid SEC compressed format" )
595-
596- uncompressed_hex = f"{ x_coord :064x} { y_coord :064x} "
597- uncompressed_hex_bytes = h_to_b (uncompressed_hex )
598- self .key = VerifyingKey .from_string (uncompressed_hex_bytes , curve = SECP256k1 )
601+ raise TypeError ("Invalid SEC compressed format" )
602+
603+ uncompressed_hex = f"{ x_coord :064x} { y_coord :064x} "
604+ uncompressed_hex_bytes = h_to_b (uncompressed_hex )
605+ self .key = VerifyingKey .from_string (uncompressed_hex_bytes , curve = SECP256k1 )
606+ elif message and signature :
607+ if (len (signature ) != 65 ):
608+ raise ValueError ("Invalid signature length" )
609+
610+ recovery_id = signature [0 ] - 31 #Extract recovery id
611+
612+ signature = signature [1 :] #Remove recovery id from signature
613+
614+ message_magic = add_magic_prefix (message )
615+ # create message digest
616+ message_digest = hashlib .sha256 (hashlib .sha256 (message_magic ).digest ()).digest ()
617+
618+ recovered_keys = VerifyingKey .from_public_key_recovery_with_digest (
619+ signature , message_digest , curve = SECP256k1 , hashfunc = hashlib .sha256 , sigdecode = sigdecode_string
620+ )
621+ self .key = recovered_keys [recovery_id ]
622+ else :
623+ raise TypeError ("Parameters missing" )
599624
600625 @classmethod
601626 def from_hex (cls , hex_str : str ) -> PublicKey :
@@ -665,11 +690,12 @@ def is_y_even(self) -> bool:
665690 return y % 2 == 0
666691
667692 @classmethod
668- def from_message_signature (cls , signature ):
669- # TODO implement (add signature=None in __init__, etc.)
670- # TODO plus does this apply to DER signatures as well?
671- # return cls(signature=signature)
672- raise BaseException ("NO-OP!" )
693+ def from_message_signature (cls , message , signature ):
694+ """Creates a public key from a message signature
695+ """
696+ #Note: Only works for compressed signatures because DER encoding does not contain the recovery id
697+ return cls (message = message , signature = signature )
698+ # raise BaseException("NO-OP!")
673699
674700 @classmethod
675701 def verify_message (cls , address : str , signature : str , message : str ) -> bool :
0 commit comments