88
99from  __future__ import  annotations 
1010
11+ import  binascii 
1112import  os 
1213from  enum  import  Enum 
1314from  typing  import  Optional , Type , Union 
1415
16+ import  base58 
17+ import  cbor2 
18+ from  cbor2  import  CBORTag 
1519from  typing_extensions  import  override 
1620
1721from  pycardano .crypto .bech32  import  decode , encode 
@@ -202,12 +206,32 @@ def __init__(
202206        self ._payment_part  =  payment_part 
203207        self ._staking_part  =  staking_part 
204208        self ._network  =  network 
209+ 
210+         # Byron address fields (only populated when decoding Byron addresses) 
211+         self ._byron_payload_hash : Optional [bytes ] =  None 
212+         self ._byron_attributes : Optional [dict ] =  None 
213+         self ._byron_type : Optional [int ] =  None 
214+         self ._byron_crc32 : Optional [int ] =  None 
215+ 
205216        self ._address_type  =  self ._infer_address_type ()
206-         self ._header_byte  =  self ._compute_header_byte ()
207-         self ._hrp  =  self ._compute_hrp ()
217+         self ._header_byte  =  self ._compute_header_byte () if  not  self .is_byron  else  None 
218+         self ._hrp  =  self ._compute_hrp () if  not  self .is_byron  else  None 
219+ 
220+     @property  
221+     def  is_byron (self ) ->  bool :
222+         """Check if this is a Byron-era address. 
223+ 
224+         Returns: 
225+             bool: True if this is a Byron address, False if Shelley/later. 
226+         """ 
227+         return  self ._byron_payload_hash  is  not None 
208228
209229    def  _infer_address_type (self ):
210230        """Guess address type from the combination of payment part and staking part.""" 
231+         # Check if this is a Byron address 
232+         if  self .is_byron :
233+             return  AddressType .BYRON 
234+ 
211235        payment_type  =  type (self .payment_part )
212236        staking_type  =  type (self .staking_part )
213237        if  payment_type  ==  VerificationKeyHash :
@@ -263,15 +287,35 @@ def address_type(self) -> AddressType:
263287        return  self ._address_type 
264288
265289    @property  
266-     def  header_byte (self ) ->  bytes :
267-         """Header byte that identifies the type of address.""" 
290+     def  header_byte (self ) ->  Optional [ bytes ] :
291+         """Header byte that identifies the type of address. None for Byron addresses. """ 
268292        return  self ._header_byte 
269293
270294    @property  
271-     def  hrp (self ) ->  str :
272-         """Human-readable prefix for bech32 encoder.""" 
295+     def  hrp (self ) ->  Optional [ str ] :
296+         """Human-readable prefix for bech32 encoder. None for Byron addresses. """ 
273297        return  self ._hrp 
274298
299+     @property  
300+     def  payload_hash (self ) ->  Optional [bytes ]:
301+         """Byron address payload hash (28 bytes). None for Shelley addresses.""" 
302+         return  self ._byron_payload_hash  if  self .is_byron  else  None 
303+ 
304+     @property  
305+     def  byron_attributes (self ) ->  Optional [dict ]:
306+         """Byron address attributes. None for Shelley addresses.""" 
307+         return  self ._byron_attributes  if  self .is_byron  else  None 
308+ 
309+     @property  
310+     def  byron_type (self ) ->  Optional [int ]:
311+         """Byron address type (0=Public Key, 2=Redemption). None for Shelley addresses.""" 
312+         return  self ._byron_type  if  self .is_byron  else  None 
313+ 
314+     @property  
315+     def  crc32_checksum (self ) ->  Optional [int ]:
316+         """Byron address CRC32 checksum. None for Shelley addresses.""" 
317+         return  self ._byron_crc32  if  self .is_byron  else  None 
318+ 
275319    def  _compute_header_byte (self ) ->  bytes :
276320        """Compute the header byte.""" 
277321        return  (self .address_type .value  <<  4  |  self .network .value ).to_bytes (
@@ -294,6 +338,16 @@ def _compute_hrp(self) -> str:
294338        return  prefix  +  suffix 
295339
296340    def  __bytes__ (self ):
341+         if  self .is_byron :
342+             payload  =  cbor2 .dumps (
343+                 [
344+                     self ._byron_payload_hash ,
345+                     self ._byron_attributes ,
346+                     self ._byron_type ,
347+                 ]
348+             )
349+             return  cbor2 .dumps ([CBORTag (24 , payload ), self ._byron_crc32 ])
350+ 
297351        payment  =  self .payment_part  or  bytes ()
298352        if  self .staking_part  is  None :
299353            staking  =  bytes ()
@@ -304,19 +358,21 @@ def __bytes__(self):
304358        return  self .header_byte  +  bytes (payment ) +  bytes (staking )
305359
306360    def  encode (self ) ->  str :
307-         """Encode the address in Bech32 format. 
361+         """Encode the address in Bech32 format (Shelley) or Base58 format (Byron) . 
308362
309363        More info about Bech32 `here <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#Bech32>`_. 
310364
311365        Returns: 
312-             str: Encoded address in Bech32. 
366+             str: Encoded address in Bech32 (Shelley) or Base58 (Byron) . 
313367
314368        Examples: 
315369            >>> payment_hash = VerificationKeyHash( 
316370            ...     bytes.fromhex("cc30497f4ff962f4c1dca54cceefe39f86f1d7179668009f8eb71e59")) 
317371            >>> print(Address(payment_hash).encode()) 
318372            addr1v8xrqjtlfluk9axpmjj5enh0uw0cduwhz7txsqyl36m3ukgqdsn8w 
319373        """ 
374+         if  self .is_byron :
375+             return  base58 .b58encode (bytes (self )).decode ("ascii" )
320376        return  encode (self .hrp , bytes (self ))
321377
322378    @classmethod  
@@ -345,8 +401,38 @@ def to_primitive(self) -> bytes:
345401    @classmethod  
346402    @limit_primitive_type (bytes , str ) 
347403    def  from_primitive (cls : Type [Address ], value : Union [bytes , str ]) ->  Address :
404+         # Convert string to bytes 
348405        if  isinstance (value , str ):
349-             value  =  bytes (decode (value ))
406+             # Check for Byron Base58 prefixes (common Byron patterns) 
407+             if  value .startswith (("Ae2td" , "Ddz" )):
408+                 return  cls ._from_byron_base58 (value )
409+ 
410+             # Try Bech32 decode for Shelley addresses 
411+             original_str  =  value 
412+             try :
413+                 value  =  bytes (decode (value ))
414+             except  Exception :
415+                 try :
416+                     return  cls ._from_byron_base58 (original_str )
417+                 except  Exception  as  e :
418+                     raise  DecodingException (f"Failed to decode address string: { e }  )
419+ 
420+         # At this point, value is always bytes 
421+         # Check if it's a Byron address (CBOR with tag 24) 
422+         try :
423+             decoded  =  cbor2 .loads (value )
424+             if  isinstance (decoded , (tuple , list )) and  len (decoded ) ==  2 :
425+                 if  isinstance (decoded [0 ], CBORTag ) and  decoded [0 ].tag  ==  24 :
426+                     # This is definitely a Byron address - validate and decode it 
427+                     return  cls ._from_byron_cbor (value )
428+         except  DecodingException :
429+             # Byron decoding failed with validation error - re-raise it 
430+             raise 
431+         except  Exception :
432+             # Not Byron CBOR (general CBOR decode error), continue with Shelley decoding 
433+             pass 
434+ 
435+         # Shelley address decoding (existing logic) 
350436        header  =  value [0 ]
351437        payload  =  value [1 :]
352438        addr_type  =  AddressType ((header  &  0xF0 ) >>  4 )
@@ -397,16 +483,150 @@ def from_primitive(cls: Type[Address], value: Union[bytes, str]) -> Address:
397483            return  cls (None , ScriptHash (payload ), network )
398484        raise  DeserializeException (f"Error in deserializing bytes: { value }  )
399485
486+     @classmethod  
487+     def  _from_byron_base58 (cls : Type [Address ], base58_str : str ) ->  Address :
488+         """Decode a Byron address from Base58 string. 
489+ 
490+         Args: 
491+             base58_str: Base58-encoded Byron address string. 
492+ 
493+         Returns: 
494+             Address: Decoded Byron address instance. 
495+ 
496+         Raises: 
497+             DecodingException: When decoding fails. 
498+         """ 
499+         try :
500+             cbor_bytes  =  base58 .b58decode (base58_str )
501+         except  Exception  as  e :
502+             raise  DecodingException (f"Failed to decode Base58 string: { e }  )
503+ 
504+         return  cls ._from_byron_cbor (cbor_bytes )
505+ 
506+     @classmethod  
507+     def  _from_byron_cbor (cls : Type [Address ], cbor_bytes : bytes ) ->  Address :
508+         """Decode a Byron address from CBOR bytes. 
509+ 
510+         Args: 
511+             cbor_bytes: CBOR-encoded Byron address bytes. 
512+ 
513+         Returns: 
514+             Address: Decoded Byron address instance. 
515+ 
516+         Raises: 
517+             DecodingException: When decoding fails. 
518+         """ 
519+         try :
520+             decoded  =  cbor2 .loads (cbor_bytes )
521+         except  Exception  as  e :
522+             raise  DecodingException (f"Failed to decode CBOR bytes: { e }  )
523+ 
524+         # Byron address structure: [CBORTag(24, payload), crc32] 
525+         if  not  isinstance (decoded , (tuple , list )) or  len (decoded ) !=  2 :
526+             raise  DecodingException (
527+                 f"Byron address must be a 2-element array, got { type (decoded )}  
528+             )
529+ 
530+         tagged_payload , crc32_checksum  =  decoded 
531+ 
532+         if  not  isinstance (tagged_payload , CBORTag ) or  tagged_payload .tag  !=  24 :
533+             raise  DecodingException (
534+                 f"Byron address must use CBOR tag 24, got { tagged_payload }  
535+             )
536+ 
537+         payload_cbor  =  tagged_payload .value 
538+         if  not  isinstance (payload_cbor , bytes ):
539+             raise  DecodingException (
540+                 f"Tag 24 must contain bytes, got { type (payload_cbor )}  
541+             )
542+ 
543+         computed_crc32  =  binascii .crc32 (payload_cbor ) &  0xFFFFFFFF 
544+         if  computed_crc32  !=  crc32_checksum :
545+             raise  DecodingException (
546+                 f"CRC32 checksum mismatch: expected { crc32_checksum } { computed_crc32 }  
547+             )
548+ 
549+         try :
550+             payload  =  cbor2 .loads (payload_cbor )
551+         except  Exception  as  e :
552+             raise  DecodingException (f"Failed to decode Byron address payload: { e }  )
553+ 
554+         if  not  isinstance (payload , (tuple , list )) or  len (payload ) !=  3 :
555+             raise  DecodingException (
556+                 f"Byron address payload must be a 3-element array, got { payload }  
557+             )
558+ 
559+         payload_hash , attributes , byron_type  =  payload 
560+ 
561+         if  not  isinstance (payload_hash , bytes ) or  len (payload_hash ) !=  28 :
562+             size  =  (
563+                 len (payload_hash )
564+                 if  isinstance (payload_hash , bytes )
565+                 else  f"type { type (payload_hash ).__name__ }  
566+             )
567+             raise  DecodingException (f"Payload hash must be 28 bytes, got { size }  )
568+ 
569+         if  not  isinstance (attributes , dict ):
570+             raise  DecodingException (
571+                 f"Attributes must be a dict, got { type (attributes )}  
572+             )
573+ 
574+         if  byron_type  not  in 0 , 2 ):
575+             raise  DecodingException (f"Byron type must be 0 or 2, got { byron_type }  )
576+ 
577+         # Create Address instance with Byron fields 
578+         addr  =  cls .__new__ (cls )
579+         addr ._payment_part  =  None 
580+         addr ._staking_part  =  None 
581+         addr ._byron_payload_hash  =  payload_hash 
582+         addr ._byron_attributes  =  attributes 
583+         addr ._byron_type  =  byron_type 
584+         addr ._byron_crc32  =  crc32_checksum 
585+         addr ._network  =  addr ._infer_byron_network ()
586+         addr ._address_type  =  AddressType .BYRON 
587+         addr ._header_byte  =  None 
588+         addr ._hrp  =  None 
589+         return  addr 
590+ 
591+     def  _infer_byron_network (self ) ->  Network :
592+         """Infer network from Byron address attributes. 
593+ 
594+         Returns: 
595+             Network: MAINNET or TESTNET (defaults to MAINNET). 
596+         """ 
597+         if  self ._byron_attributes  and  2  in  self ._byron_attributes :
598+             network_bytes  =  self ._byron_attributes [2 ]
599+             if  isinstance (network_bytes , bytes ):
600+                 try :
601+                     network_discriminant  =  cbor2 .loads (network_bytes )
602+                     # Mainnet: 764824073 (0x2D964A09), Testnet: 1097911063 (0x42659F17) 
603+                     if  network_discriminant  ==  1097911063 :
604+                         return  Network .TESTNET 
605+                 except  Exception :
606+                     pass 
607+         return  Network .MAINNET 
608+ 
400609    def  __eq__ (self , other ):
401610        if  not  isinstance (other , Address ):
402611            return  False 
403-         else :
612+ 
613+         if  self .is_byron  !=  other .is_byron :
614+             return  False 
615+ 
616+         if  self .is_byron :
404617            return  (
405-                 other .payment_part  ==  self .payment_part 
406-                 and  other .staking_part  ==  self .staking_part 
407-                 and  other .network  ==  self .network 
618+                 self ._byron_payload_hash  ==  other ._byron_payload_hash 
619+                 and  self ._byron_attributes  ==  other ._byron_attributes 
620+                 and  self ._byron_type  ==  other ._byron_type 
621+                 and  self ._byron_crc32  ==  other ._byron_crc32 
408622            )
409623
624+         return  (
625+             self .payment_part  ==  other .payment_part 
626+             and  self .staking_part  ==  other .staking_part 
627+             and  self .network  ==  other .network 
628+         )
629+ 
410630    def  __repr__ (self ):
411631        return  f"{ self .encode ()}  
412632
0 commit comments