@@ -2364,17 +2364,20 @@ def validate(self):
23642364 if len (self .subsigs ) > constants .multisig_account_limit :
23652365 raise error .MultisigAccountSizeError
23662366
2367- def address (self ):
2368- """Return the multisig account address."""
2367+ def address_bytes (self ):
2368+ """Return the raw bytes of the multisig account address."""
23692369 msig_bytes = (
23702370 bytes (constants .msig_addr_prefix , "utf-8" )
23712371 + bytes ([self .version ])
23722372 + bytes ([self .threshold ])
23732373 )
23742374 for s in self .subsigs :
23752375 msig_bytes += s .public_key
2376- addr = encoding .checksum (msig_bytes )
2377- return encoding .encode_address (addr )
2376+ return encoding .checksum (msig_bytes )
2377+
2378+ def address (self ):
2379+ """Return the multisig account address."""
2380+ return encoding .encode_address (self .address_bytes ())
23782381
23792382 def verify (self , message ):
23802383 """Verify that the multisig is valid for the message."""
@@ -2509,6 +2512,7 @@ def __init__(self, program, args=None):
25092512 self .args = args
25102513 self .sig = None
25112514 self .msig = None
2515+ self .lmsig = None
25122516
25132517 @staticmethod
25142518 def _sanity_check_program (program ):
@@ -2561,6 +2565,8 @@ def dictify(self):
25612565 od ["sig" ] = base64 .b64decode (self .sig )
25622566 elif self .msig :
25632567 od ["msig" ] = self .msig .dictify ()
2568+ elif self .lmsig :
2569+ od ["lmsig" ] = self .lmsig .dictify ()
25642570 return od
25652571
25662572 @staticmethod
@@ -2570,8 +2576,17 @@ def undictify(d):
25702576 lsig .sig = base64 .b64encode (d ["sig" ]).decode ()
25712577 elif "msig" in d :
25722578 lsig .msig = Multisig .undictify (d ["msig" ])
2579+ elif "lmsig" in d :
2580+ lsig .lmsig = Multisig .undictify (d ["lmsig" ])
25732581 return lsig
25742582
2583+ def sig_count (self ):
2584+ return (
2585+ int (self .sig is not None )
2586+ + int (self .msig is not None )
2587+ + int (self .lmsig is not None )
2588+ )
2589+
25752590 def verify (self , public_key ):
25762591 """
25772592 Verifies LogicSig against the transaction's sender address
@@ -2584,29 +2599,38 @@ def verify(self, public_key):
25842599 the logic hash or the signature is valid against the sender\
25852600 address), false otherwise
25862601 """
2587- if self .sig and self .msig :
2588- return False
2589-
25902602 try :
25912603 self ._sanity_check_program (self .logic )
25922604 except error .InvalidProgram :
25932605 return False
25942606
2595- to_sign = constants .logic_prefix + self .logic
2596-
2597- if not self .sig and not self .msig :
2598- checksum = encoding .checksum (to_sign )
2599- return checksum == public_key
2607+ if self .sig_count () > 1 :
2608+ return False
26002609
26012610 if self .sig :
26022611 verify_key = VerifyKey (public_key )
26032612 try :
2613+ to_sign = constants .logic_prefix + self .logic
26042614 verify_key .verify (to_sign , base64 .b64decode (self .sig ))
26052615 return True
26062616 except (BadSignatureError , ValueError , TypeError ):
26072617 return False
26082618
2609- return self .msig .verify (to_sign )
2619+ if self .msig :
2620+ to_sign = constants .logic_prefix + self .logic
2621+ return self .msig .verify (to_sign )
2622+
2623+ if self .lmsig :
2624+ to_sign = (
2625+ constants .multisig_logic_prefix
2626+ + self .lmsig .address_bytes ()
2627+ + self .logic
2628+ )
2629+ return self .lmsig .verify (to_sign )
2630+
2631+ # Non-delegated
2632+ to_sign = constants .logic_prefix + self .logic
2633+ return public_key == encoding .checksum (to_sign )
26102634
26112635 def address (self ):
26122636 """
@@ -2626,6 +2650,18 @@ def sign_program(program, private_key):
26262650 signed = signing_key .sign (to_sign )
26272651 return base64 .b64encode (signed .signature ).decode ()
26282652
2653+ @staticmethod
2654+ def multisig_sign_program (program , private_key , multisig ):
2655+ private_key = base64 .b64decode (private_key )
2656+ signing_key = SigningKey (private_key [: constants .key_len_bytes ])
2657+ to_sign = (
2658+ constants .multisig_logic_prefix
2659+ + multisig .address_bytes ()
2660+ + program
2661+ )
2662+ signed = signing_key .sign (to_sign )
2663+ return base64 .b64encode (signed .signature ).decode ()
2664+
26292665 @staticmethod
26302666 def single_sig_multisig (program , private_key , multisig ):
26312667 index = - 1
@@ -2637,7 +2673,7 @@ def single_sig_multisig(program, private_key, multisig):
26372673 break
26382674 if index == - 1 :
26392675 raise error .InvalidSecretKeyError
2640- sig = LogicSig .sign_program (program , private_key )
2676+ sig = LogicSig .multisig_sign_program (program , private_key , multisig )
26412677
26422678 return sig , index
26432679
@@ -2657,7 +2693,7 @@ def sign(self, private_key, multisig=None):
26572693 already been provided
26582694 """
26592695 if not multisig :
2660- if self .msig :
2696+ if self .msig or self . lmsig :
26612697 raise error .LogicSigOverspecifiedSignature
26622698 self .sig = LogicSig .sign_program (self .logic , private_key )
26632699 else :
@@ -2667,7 +2703,7 @@ def sign(self, private_key, multisig=None):
26672703 self .logic , private_key , multisig
26682704 )
26692705 multisig .subsigs [index ].signature = base64 .b64decode (sig )
2670- self .msig = multisig
2706+ self .lmsig = multisig
26712707
26722708 def append_to_multisig (self , private_key ):
26732709 """
@@ -2680,12 +2716,12 @@ def append_to_multisig(self, private_key):
26802716 InvalidSecretKeyError: if no matching private key in multisig\
26812717 object
26822718 """
2683- if self .msig is None :
2719+ if self .lmsig is None :
26842720 raise error .InvalidSecretKeyError
26852721 sig , index = LogicSig .single_sig_multisig (
2686- self .logic , private_key , self .msig
2722+ self .logic , private_key , self .lmsig
26872723 )
2688- self .msig .subsigs [index ].signature = base64 .b64decode (sig )
2724+ self .lmsig .subsigs [index ].signature = base64 .b64decode (sig )
26892725
26902726 def __eq__ (self , other ):
26912727 if not isinstance (other , LogicSig ):
@@ -2695,6 +2731,7 @@ def __eq__(self, other):
26952731 and self .args == other .args
26962732 and self .sig == other .sig
26972733 and self .msig == other .msig
2734+ and self .lmsig == other .lmsig
26982735 )
26992736
27002737
@@ -2744,7 +2781,7 @@ def is_delegated(self) -> bool:
27442781 Returns:
27452782 bool: True if and only if this is a delegated LogicSigAccount.
27462783 """
2747- return bool (self .lsig .sig or self .lsig .msig )
2784+ return bool (self .lsig .sig or self .lsig .msig or self . lsig . lmsig )
27482785
27492786 def verify (self ) -> bool :
27502787 """
@@ -2757,6 +2794,15 @@ def verify(self) -> bool:
27572794 addr = self .address ()
27582795 return self .lsig .verify (encoding .decode_address (addr ))
27592796
2797+ def sig_count (self ) -> int :
2798+ """
2799+ Returns the number of cryptographic signatures on the LogicSig
2800+
2801+ Returns:
2802+ int: The number of signatures. Should never exceed 1.
2803+ """
2804+ return self .lsig .sig_count ()
2805+
27602806 def address (self ) -> str :
27612807 """
27622808 Get the address of this LogicSigAccount.
@@ -2767,7 +2813,7 @@ def address(self) -> str:
27672813 If the LogicSig is not delegated to another account, this will return an
27682814 escrow address that is the hash of the LogicSig's program code.
27692815 """
2770- if self .lsig . sig and self . lsig . msig :
2816+ if self .sig_count () > 1 :
27712817 raise error .LogicSigOverspecifiedSignature
27722818
27732819 if self .lsig .sig :
@@ -2778,6 +2824,9 @@ def address(self) -> str:
27782824 if self .lsig .msig :
27792825 return self .lsig .msig .address ()
27802826
2827+ if self .lsig .lmsig :
2828+ return self .lsig .lmsig .address ()
2829+
27812830 return self .lsig .address ()
27822831
27832832 def sign_multisig (self , multisig : Multisig , private_key : str ) -> None :
@@ -2874,6 +2923,8 @@ def __init__(
28742923 lsigAddr = transaction .sender
28752924 elif lsig .msig :
28762925 lsigAddr = lsig .msig .address ()
2926+ elif lsig .lmsig :
2927+ lsigAddr = lsig .lmsig .address ()
28772928 else :
28782929 lsigAddr = lsig .address ()
28792930 self .lsig = lsig
0 commit comments