Skip to content

Commit a34b79d

Browse files
authored
Merge pull request #212 from StellarCN/dev
feat: Support for SEP 10 challenge transaction.
2 parents 8ce51ab + a9a2ca1 commit a34b79d

File tree

3 files changed

+69
-11
lines changed

3 files changed

+69
-11
lines changed

stellar_base/builder.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
# coding: utf-8
22
import binascii
3+
import os
4+
import time
35
import warnings
46

7+
from . import memo
8+
from . import operation
59
from .asset import Asset
10+
from .exceptions import NoStellarSecretOrAddressError, StellarAddressInvalidError, SequenceError
11+
from .federation import federation, FederationError
612
from .horizon import HORIZON_LIVE, HORIZON_TEST
713
from .horizon import Horizon
814
from .keypair import Keypair
9-
from . import memo
1015
from .network import NETWORKS, Network
11-
from . import operation
1216
from .transaction import Transaction
1317
from .transaction_envelope import TransactionEnvelope as Te
14-
from .exceptions import NoStellarSecretOrAddressError, StellarAddressInvalidError, SequenceError
15-
from .federation import federation, FederationError
1618

1719

1820
class Builder(object):
@@ -446,7 +448,7 @@ def append_manage_buy_offer_op(self,
446448
selling = Asset(selling_code, selling_issuer)
447449
buying = Asset(buying_code, buying_issuer)
448450
op = operation.ManageBuyOffer(selling, buying, amount, price, offer_id,
449-
source)
451+
source)
450452
return self.append_op(op)
451453

452454
def append_manage_sell_offer_op(self,
@@ -939,3 +941,26 @@ def get_sequence(self):
939941

940942
address = self.horizon.account(self.address)
941943
return int(address.get('sequence'))
944+
945+
@classmethod
946+
def challenge_tx(cls, server_secret, client_account_id, archor_name, network='TESTNET', timeout=300):
947+
"""Returns a valid `SEP0010 <https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md>`_
948+
challenge transaction which you can use for Stellar Web Authentication.
949+
950+
:param server_secret: secret key for server's signing account.
951+
:param client_account_id: The stellar account that the wallet wishes to authenticate with the server.
952+
:param archor_name: Anchor's name to be used in the manage_data key.
953+
:param str network: The network to connect to for verifying and retrieving
954+
additional attributes from. 'PUBLIC' is an alias for 'Public Global Stellar Network ; September 2015',
955+
'TESTNET' is an alias for 'Test SDF Network ; September 2015'. Defaults to TESTNET.
956+
:param int timeout: Challenge duration in seconds (default to 5 minutes).
957+
:return: a valid SEP0010 challenge transaction which you can use for Stellar Web Authentication.
958+
:rtype: :class:`Builder`
959+
"""
960+
now = int(time.time())
961+
transaction = cls(secret=server_secret, network=network, sequence=-1)
962+
transaction.add_time_bounds({'minTime': now, 'maxTime': now + timeout})
963+
nonce = os.urandom(64)
964+
transaction.append_manage_data_op(data_name='{} auth'.format(archor_name), data_value=nonce,
965+
source=client_account_id)
966+
return transaction

stellar_base/keypair.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
import warnings
55

66
from .base58 import b58decode_check, b58encode_check
7+
from .exceptions import MissingSigningKeyError, BadSignatureError, NotValidParamError
78
from .stellarxdr import Xdr
89
from .utils import encode_check, StellarMnemonic, \
910
is_valid_address, is_valid_secret_key
10-
from .exceptions import MissingSigningKeyError, BadSignatureError, NotValidParamError
1111

1212
# noinspection PyBroadException
1313
try:
@@ -279,3 +279,9 @@ def to_old_address(self):
279279
def to_old_seed(self):
280280
seed = chr(33).encode() + self.raw_seed()
281281
return b58encode_check(seed)
282+
283+
def __eq__(self, other):
284+
if not isinstance(other, Keypair):
285+
return False
286+
287+
return self.signing_key == other.signing_key and self.verifying_key == other.verifying_key

tests/test_builder.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# encoding: utf-8
2+
import time
23

3-
import pytest
44
import mock
5+
import pytest
56

67
from stellar_base import memo
78
from stellar_base.builder import Builder
9+
from stellar_base.exceptions import NoStellarSecretOrAddressError, FederationError
810
from stellar_base.horizon import horizon_testnet, horizon_livenet, Horizon
911
from stellar_base.keypair import Keypair
10-
from stellar_base.exceptions import NoStellarSecretOrAddressError, FederationError
12+
from stellar_base.operation import ManageData
1113

1214

1315
@pytest.fixture(scope='module')
@@ -71,11 +73,11 @@ def test_builder_append_ops(self):
7173
pre_auth_tx=b"\x95\xe5\xbb\x95\x15\xd9\x9f\x82\x9d\xf9\x93\xc3'\x8e\xeb\xf1\nj!\xda\xa4\xa1\xe4\xf2<6cG}\x17\x97\xfe",
7274
signer_weight=1). \
7375
append_manage_sell_offer_op(selling_code='XLM', selling_issuer=None, buying_code='BEER',
74-
buying_issuer=bob_account, amount='1', price='10', offer_id=0). \
75-
append_manage_buy_offer_op(selling_code='XLM', selling_issuer=None, buying_code='BEER',
7676
buying_issuer=bob_account, amount='1', price='10', offer_id=0). \
77+
append_manage_buy_offer_op(selling_code='XLM', selling_issuer=None, buying_code='BEER',
78+
buying_issuer=bob_account, amount='1', price='10', offer_id=0). \
7779
append_create_passive_sell_offer_op(selling_code='XLM', selling_issuer=None, buying_code='BEER',
78-
buying_issuer=bob_account, amount='1', price={'n': 10, 'd': 1}). \
80+
buying_issuer=bob_account, amount='1', price={'n': 10, 'd': 1}). \
7981
append_account_merge_op(destination=bob_account). \
8082
append_inflation_op(). \
8183
append_manage_data_op(data_name='hello', data_value='world'). \
@@ -199,3 +201,28 @@ def test_network_and_horizon(self, setup, test_data):
199201
horizon_uri=setup.horizon_endpoint_uri)
200202
assert builder.network == 'PUBLIC'
201203
assert builder.horizon.horizon_uri == Horizon(horizon_uri=setup.horizon_endpoint_uri).horizon_uri
204+
205+
def test_challenge_tx(self):
206+
server_kp = Keypair.random()
207+
client_account_id = "GBDIT5GUJ7R5BXO3GJHFXJ6AZ5UQK6MNOIDMPQUSMXLIHTUNR2Q5CFNF"
208+
timeout = 600
209+
network = 'TESTNET'
210+
archor_name = "SDF"
211+
212+
tx = Builder.challenge_tx(server_secret=server_kp.seed().decode(),
213+
client_account_id=client_account_id,
214+
archor_name=archor_name,
215+
network=network,
216+
timeout=timeout)
217+
assert len(tx.ops) == 1
218+
op = tx.ops[0]
219+
assert isinstance(op, ManageData)
220+
assert op.data_name == "SDF auth"
221+
assert len(op.data_value) == 64
222+
assert op.source == client_account_id
223+
224+
now = int(time.time())
225+
assert now - 3 < tx.time_bounds['minTime'] < now + 3
226+
assert tx.time_bounds['maxTime'] - tx.time_bounds['minTime'] == timeout
227+
assert tx.keypair == server_kp
228+
assert tx.sequence == -1

0 commit comments

Comments
 (0)