Skip to content

Commit

Permalink
fix: fix authorize_entry to use the correct public key when passing…
Browse files Browse the repository at this point in the history
… `Keypair` as signer. (#971)
  • Loading branch information
overcat authored Sep 18, 2024
1 parent fd696d7 commit d5a3885
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 2 deletions.
11 changes: 10 additions & 1 deletion stellar_sdk/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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`.
Expand Down
132 changes: 131 additions & 1 deletion tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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,
)

0 comments on commit d5a3885

Please sign in to comment.