Skip to content

Commit

Permalink
refactor!: Updated signer parameter in auth to accept a callable re…
Browse files Browse the repository at this point in the history
…turning (public_key, signatures) instead of just public_key.
  • Loading branch information
overcat committed Oct 8, 2024
1 parent 447f80e commit 0b31b64
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Release History
### Pending
#### Update
- feat!: support constructors in contract creation via `TransactionBuilder.append_create_contract_op`, the signature of the function has been changed.
- refactor!: Updated `signer` parameter in auth to accept a callable returning (public_key, signatures) instead of just public_key.

### Version 11.1.0

Expand Down
35 changes: 14 additions & 21 deletions stellar_sdk/auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import copy
import random
from typing import Callable, Optional, Union
from typing import Callable, Optional, Tuple, Union

from . import scval
from . import xdr as stellar_xdr
Expand All @@ -15,7 +15,7 @@

def authorize_entry(
entry: Union[stellar_xdr.SorobanAuthorizationEntry, str],
signer: Union[Keypair, Callable[[stellar_xdr.HashIDPreimage], bytes]],
signer: Union[Keypair, Callable[[stellar_xdr.HashIDPreimage], Tuple[str, bytes]]],
valid_until_ledger_sequence: int,
network_passphrase: str,
) -> stellar_xdr.SorobanAuthorizationEntry:
Expand All @@ -31,14 +31,11 @@ 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,
the signing key should correspond to the address in the `entry`.
(a :class:`stellar_xdr.HashIDPreimage` instance) input and returns a tuple of
(str, bytes), where the first str is the public key and the second
bytes is the signature. The signing key should correspond to the address in the `entry`.
:param valid_until_ledger_sequence: the (exclusive) future ledger sequence number until which
this authorization entry should be valid (if `currentLedgerSeq==validUntil`, this is expired)
:param network_passphrase: the network passphrase is incorporated into the signature (see :class:`stellar_sdk.Network` for options)
Expand Down Expand Up @@ -75,13 +72,12 @@ def authorize_entry(
payload = sha256(preimage.to_xdr_bytes())
if isinstance(signer, Keypair):
signature = signer.sign(payload)
public_key = signer.raw_public_key()
kp = Keypair.from_raw_ed25519_public_key(signer.raw_public_key())
else:
signature = signer(preimage)
public_key = Address.from_xdr_sc_address(addr_auth.address).key

public_key, signature = signer(preimage)
kp = Keypair.from_public_key(public_key)
try:
Keypair.from_raw_ed25519_public_key(public_key).verify(payload, signature)
kp.verify(payload, signature)
except BadSignatureError as e:
raise ValueError("signature doesn't match payload.") from e

Expand All @@ -91,7 +87,7 @@ def authorize_entry(
[
scval.to_map(
{
scval.to_symbol("public_key"): scval.to_bytes(public_key),
scval.to_symbol("public_key"): scval.to_bytes(kp.raw_public_key()),
scval.to_symbol("signature"): scval.to_bytes(signature),
}
)
Expand All @@ -101,7 +97,7 @@ def authorize_entry(


def authorize_invocation(
signer: Union[Keypair, Callable[[stellar_xdr.HashIDPreimage], bytes]],
signer: Union[Keypair, Callable[[stellar_xdr.HashIDPreimage], Tuple[str, bytes]]],
public_key: Optional[str],
valid_until_ledger_sequence: int,
invocation: stellar_xdr.SorobanAuthorizedInvocation,
Expand All @@ -117,13 +113,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`.
(a :class:`stellar_xdr.HashIDPreimage` instance) input and returns a tuple of
(str, bytes), where the first str is the public key and the second
bytes is the signature. The signing key should correspond to the address in the `entry`.
:param public_key: the public identity of the signer (when providing a :class:`Keypair` to `signer`,
this can be omitted, as it just uses the public key of the keypair)
:param valid_until_ledger_sequence: the (exclusive) future ledger sequence number until which
Expand Down
81 changes: 67 additions & 14 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ def test_sign_authorize_entry_with_base64_entry_and_function_signer(self):
signer = Keypair.from_secret(
"SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN"
)
signer_fn = lambda preimage: signer.sign(utils.sha256(preimage.to_xdr_bytes()))
signer_fn = lambda preimage: (
signer.public_key,
signer.sign(utils.sha256(preimage.to_xdr_bytes())),
)

valid_until_ledger_sequence = 654656
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE
Expand Down Expand Up @@ -247,7 +250,10 @@ def test_sign_authorize_entry_with_xdr_entry_and_function_signer(self):
signer = Keypair.from_secret(
"SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN"
)
signer_fn = lambda preimage: signer.sign(utils.sha256(preimage.to_xdr_bytes()))
signer_fn = lambda preimage: (
signer.public_key,
signer.sign(utils.sha256(preimage.to_xdr_bytes())),
)
valid_until_ledger_sequence = 654656
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE

Expand Down Expand Up @@ -364,8 +370,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")
signer_fn = lambda preimage: (
signer.public_key,
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 @@ -448,7 +455,10 @@ def test_sign_authorize_invocation_with_function_signer(self):
signer = Keypair.from_secret(
"SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN"
)
signer_fn = lambda preimage: signer.sign(utils.sha256(preimage.to_xdr_bytes()))
signer_fn = lambda preimage: (
signer.public_key,
signer.sign(utils.sha256(preimage.to_xdr_bytes())),
)
valid_until_ledger_sequence = 654656
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE

Expand Down Expand Up @@ -572,14 +582,17 @@ def test_sign_authorize_entry_with_keypair_signer_not_equal_credential_address(
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(
def test_sign_authorize_entry_with_function_signer_not_equal_credential_address(
self,
):
contract_id = "CDCYWK73YTYFJZZSJ5V7EDFNHYBG4QN3VUNG2IGD27KJDDPNCZKBCBXK"
signer = Keypair.from_secret(
"SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN"
)
signer_fn = lambda preimage: signer.sign(utils.sha256(preimage.to_xdr_bytes()))
signer_fn = lambda preimage: (
signer.public_key,
signer.sign(utils.sha256(preimage.to_xdr_bytes())),
)

valid_until_ledger_sequence = 654656
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE
Expand Down Expand Up @@ -607,10 +620,50 @@ def test_sign_authorize_entry_with_function_signer_not_equal_credential_address_
)
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,
)
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)

0 comments on commit 0b31b64

Please sign in to comment.