From d5a3885f51c1a83b78e5a2d2aa316fc3183d74d3 Mon Sep 17 00:00:00 2001 From: Jun Luo <4catcode@gmail.com> Date: Wed, 18 Sep 2024 09:34:08 +0800 Subject: [PATCH] fix: fix `authorize_entry` to use the correct public key when passing `Keypair` as signer. (#971) --- stellar_sdk/auth.py | 11 +++- tests/test_auth.py | 132 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/stellar_sdk/auth.py b/stellar_sdk/auth.py index 90759aff..1b3fcfe9 100644 --- a/stellar_sdk/auth.py +++ b/stellar_sdk/auth.py @@ -31,6 +31,10 @@ def authorize_entry( * on a particular network (uniquely identified by its passphrase, see :class:`stellar_sdk.Network`) * until a particular ledger sequence is reached. + Note that if using the function form of `signer`, the signer is assumed to be + the entry's credential address. If you need a different key to sign the + entry, you will need to use different method (e.g., fork this code). + :param entry: an unsigned Soroban authorization entry. :param signer: either a :class:`Keypair` or a function which takes a payload (a :class:`stellar_xdr.HashIDPreimage` instance) input and returns a bytes signature, @@ -71,10 +75,11 @@ def authorize_entry( payload = sha256(preimage.to_xdr_bytes()) if isinstance(signer, Keypair): signature = signer.sign(payload) + public_key = signer.raw_public_key() else: signature = signer(preimage) + public_key = Address.from_xdr_sc_address(addr_auth.address).key - public_key = Address.from_xdr_sc_address(addr_auth.address).key try: Keypair.from_raw_ed25519_public_key(public_key).verify(payload, signature) except BadSignatureError as e: @@ -112,6 +117,10 @@ def authorize_invocation( This is in contrast to :func:`authorize_entry`, which signs an existing entry "in place". + Note that if using the function form of `signer`, the signer is assumed to be + the entry's credential address. If you need a different key to sign the + entry, you will need to use different method (e.g., fork this code). + :param signer: either a :class:`Keypair` or a function which takes a payload (a :class:`stellar_xdr.HashIDPreimage` instance) input and returns a bytes signature, the signing key should correspond to the address in the `entry`. diff --git a/tests/test_auth.py b/tests/test_auth.py index f762e2f0..fdfe1234 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -364,6 +364,9 @@ def test_sign_authorize_entry_with_signature_mismatch_raise(self): signer = Keypair.from_secret( "SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN" ) + signer_fn = lambda preimage: signer.sign( + utils.sha256(preimage.to_xdr_bytes() + b"invalid") + ) valid_until_ledger_sequence = 654656 network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE @@ -391,7 +394,10 @@ def test_sign_authorize_entry_with_signature_mismatch_raise(self): with pytest.raises(ValueError, match="signature doesn't match payload."): authorize_entry( - entry.to_xdr(), signer, valid_until_ledger_sequence, network_passphrase + entry.to_xdr(), + signer_fn, + valid_until_ledger_sequence, + network_passphrase, ) def test_sign_authorize_invocation_with_keypair_signer(self): @@ -484,3 +490,127 @@ def test_sign_authorize_invocation_with_function_signer(self): == stellar_xdr.SCValType.SCV_VEC ) assert len(signed_entry.credentials.address.signature.vec.sc_vec) == 1 + + def test_sign_authorize_entry_with_keypair_signer_not_equal_credential_address( + self, + ): + contract_id = "CDCYWK73YTYFJZZSJ5V7EDFNHYBG4QN3VUNG2IGD27KJDDPNCZKBCBXK" + signer = Keypair.from_secret( + "SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN" + ) + valid_until_ledger_sequence = 654656 + network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE + + credential_address = "GADBBY4WFXKKFJ7CMTG3J5YAUXMQDBILRQ6W3U5IWN5TQFZU4MWZ5T4K" + credentials = stellar_xdr.SorobanCredentials( + type=stellar_xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_ADDRESS, + address=stellar_xdr.SorobanAddressCredentials( + address=Address(credential_address).to_xdr_sc_address(), + nonce=stellar_xdr.Int64(123456789), + signature_expiration_ledger=stellar_xdr.Uint32(0), + signature=stellar_xdr.SCVal(type=stellar_xdr.SCValType.SCV_VOID), + ), + ) + invocation = stellar_xdr.SorobanAuthorizedInvocation( + function=stellar_xdr.SorobanAuthorizedFunction( + type=stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN, + contract_fn=stellar_xdr.InvokeContractArgs( + contract_address=Address(contract_id).to_xdr_sc_address(), + function_name=scval.to_symbol("increment").sym, + args=[scval.to_address(signer.public_key), scval.to_uint32(10)], + ), + ), + sub_invocations=[], + ) + entry = stellar_xdr.SorobanAuthorizationEntry(credentials, invocation) + + signed_entry = authorize_entry( + entry.to_xdr(), signer, valid_until_ledger_sequence, network_passphrase + ) + + preimage = stellar_xdr.HashIDPreimage( + type=stellar_xdr.EnvelopeType.ENVELOPE_TYPE_SOROBAN_AUTHORIZATION, + soroban_authorization=stellar_xdr.HashIDPreimageSorobanAuthorization( + network_id=stellar_xdr.Hash(Network(network_passphrase).network_id()), + nonce=stellar_xdr.Int64(123456789), + signature_expiration_ledger=stellar_xdr.Uint32( + valid_until_ledger_sequence + ), + invocation=invocation, + ), + ) + signature = signer.sign(utils.sha256(preimage.to_xdr_bytes())) + + expected_entry = stellar_xdr.SorobanAuthorizationEntry( + credentials=stellar_xdr.SorobanCredentials( + type=stellar_xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_ADDRESS, + address=stellar_xdr.SorobanAddressCredentials( + address=Address(credential_address).to_xdr_sc_address(), + nonce=stellar_xdr.Int64(123456789), + signature_expiration_ledger=stellar_xdr.Uint32( + valid_until_ledger_sequence + ), + signature=scval.to_vec( + [ + scval.to_map( + { + scval.to_symbol("public_key"): scval.to_bytes( + signer.raw_public_key() + ), + scval.to_symbol("signature"): scval.to_bytes( + signature + ), + } + ) + ] + ), + ), + ), + root_invocation=invocation, + ) + + assert expected_entry == signed_entry + assert id(expected_entry) != id(signed_entry) + + def test_sign_authorize_entry_with_function_signer_not_equal_credential_address_raise( + self, + ): + contract_id = "CDCYWK73YTYFJZZSJ5V7EDFNHYBG4QN3VUNG2IGD27KJDDPNCZKBCBXK" + signer = Keypair.from_secret( + "SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN" + ) + signer_fn = lambda preimage: signer.sign(utils.sha256(preimage.to_xdr_bytes())) + + valid_until_ledger_sequence = 654656 + network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE + + credential_address = "GADBBY4WFXKKFJ7CMTG3J5YAUXMQDBILRQ6W3U5IWN5TQFZU4MWZ5T4K" + credentials = stellar_xdr.SorobanCredentials( + type=stellar_xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_ADDRESS, + address=stellar_xdr.SorobanAddressCredentials( + address=Address(credential_address).to_xdr_sc_address(), + nonce=stellar_xdr.Int64(123456789), + signature_expiration_ledger=stellar_xdr.Uint32(0), + signature=stellar_xdr.SCVal(type=stellar_xdr.SCValType.SCV_VOID), + ), + ) + invocation = stellar_xdr.SorobanAuthorizedInvocation( + function=stellar_xdr.SorobanAuthorizedFunction( + type=stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN, + contract_fn=stellar_xdr.InvokeContractArgs( + contract_address=Address(contract_id).to_xdr_sc_address(), + function_name=scval.to_symbol("increment").sym, + args=[scval.to_address(signer.public_key), scval.to_uint32(10)], + ), + ), + sub_invocations=[], + ) + entry = stellar_xdr.SorobanAuthorizationEntry(credentials, invocation) + + with pytest.raises(ValueError, match="signature doesn't match payload."): + authorize_entry( + entry.to_xdr(), + signer_fn, + valid_until_ledger_sequence, + network_passphrase, + )