Skip to content

Commit

Permalink
Tests: Rework craft_tx and encode_tx into a more simple and usable si…
Browse files Browse the repository at this point in the history
…ngle function
  • Loading branch information
fbeutin-ledger committed Oct 11, 2023
1 parent ed8a6c9 commit cd2ab0a
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 144 deletions.
10 changes: 4 additions & 6 deletions test/python/apps/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ragger.backend.interface import BackendInterface, RAPDU

from ..utils import handle_lib_call_start_or_stop, int_to_minimally_sized_bytes, prefix_with_len_custom
from .exchange_transaction_builder import SubCommand, craft_transaction_proposal
from .exchange_transaction_builder import SubCommand

MAX_CHUNK_SIZE = 255

Expand Down Expand Up @@ -102,14 +102,12 @@ def set_partner_key(self, credentials: bytes) -> RAPDU:
def check_partner_key(self, signed_credentials: bytes) -> RAPDU:
return self._exchange(Command.CHECK_PARTNER, signed_credentials)

def process_transaction(self, transaction: bytes, fees: int) -> RAPDU:
payload = craft_transaction_proposal(self.subcommand, transaction, fees)

def process_transaction(self, transaction: bytes) -> RAPDU:
if self.subcommand == SubCommand.SWAP or self.subcommand == SubCommand.FUND or self.subcommand == SubCommand.SELL:
return self._exchange(Command.PROCESS_TRANSACTION_RESPONSE, payload=payload)
return self._exchange(Command.PROCESS_TRANSACTION_RESPONSE, payload=transaction)

else:
payload_split = [payload[x:x + MAX_CHUNK_SIZE] for x in range(0, len(payload), MAX_CHUNK_SIZE)]
payload_split = [transaction[x:x + MAX_CHUNK_SIZE] for x in range(0, len(transaction), MAX_CHUNK_SIZE)]
for i, p in enumerate(payload_split):
p2 = self.subcommand
# Send all chunks with P2_MORE except for the last chunk
Expand Down
9 changes: 4 additions & 5 deletions test/python/apps/exchange_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ragger.error import ExceptionRAPDU

from .exchange import ExchangeClient, Rate, SubCommand, Errors
from .exchange_transaction_builder import get_partner_curve, craft_tx, encode_tx, extract_payout_ticker, extract_refund_ticker, get_credentials
from .exchange_transaction_builder import get_partner_curve, extract_payout_ticker, extract_refund_ticker, get_credentials, craft_and_sign_tx
from . import cal as cal
from .signing_authority import SigningAuthority, LEDGER_SIGNER

Expand Down Expand Up @@ -77,12 +77,11 @@ def _perform_valid_exchange(self, subcommand, tx_infos, fees, ui_validation):
ex.check_partner_key(LEDGER_SIGNER.sign(credentials))

# Craft the exchange transaction proposal and have it signed by the enrolled partner
tx = craft_tx(subcommand, tx_infos, transaction_id)
signed_tx = encode_tx(subcommand, partner, tx)
tx, tx_signature = craft_and_sign_tx(subcommand, tx_infos, transaction_id, fees, partner)

# Send the exchange transaction proposal and its signature
ex.process_transaction(tx, fees)
ex.check_transaction_signature(signed_tx)
ex.process_transaction(tx)
ex.check_transaction_signature(tx_signature)

# Ask our fake CAL the coin configuration for both payout and refund tickers (None for refund in case of FUND or SELL)
payout_ticker = extract_payout_ticker(subcommand, tx_infos)
Expand Down
47 changes: 27 additions & 20 deletions test/python/apps/exchange_transaction_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SubCommandSpecs:
partner_curve: ec.EllipticCurve
signature_computation: SignatureComputation
signature_encoding: SignatureEncoding
payload_encoding: PayloadEncoding
default_payload_encoding: PayloadEncoding
transaction_type: Callable
required_fields: Iterable[str]
transaction_id_field: str
Expand All @@ -65,8 +65,8 @@ def format_transaction(self, transaction: bytes) -> bytes:
else:
return transaction

def encode_payload(self, raw_transaction: bytes) -> bytes:
if self.payload_encoding == PayloadEncoding.BASE_64_URL:
def encode_payload(self, raw_transaction: bytes, url_encode: bool) -> bytes:
if url_encode == True:
return urlsafe_b64encode(raw_transaction)
else:
return raw_transaction
Expand All @@ -77,19 +77,19 @@ def encode_signature(self, signature_to_encode: bytes) -> bytes:
signature_to_encode = r.to_bytes(32, "big") + s.to_bytes(32, "big")
return signature_to_encode

def create_transaction(self, conf: Dict, transaction_id: bytes) -> bytes:
def _create_transaction(self, conf: Dict, transaction_id: bytes) -> bytes:
# Alter a copy of conf to not modify the actual conf
c = conf.copy()
c[self.transaction_id_field] = transaction_id
raw_transaction = self.transaction_type(**c).SerializeToString()
return self.encode_payload(raw_transaction)
return self.encode_payload(raw_transaction, (self.default_payload_encoding == PayloadEncoding.BASE_64_URL))


SWAP_NG_SPECS: SubCommandSpecs = SubCommandSpecs(
partner_curve = ec.SECP256R1(),
signature_computation = SignatureComputation.DOT_PREFIXED_BASE_64_URL,
signature_encoding = SignatureEncoding.PLAIN_R_S,
payload_encoding = PayloadEncoding.BASE_64_URL,
default_payload_encoding = PayloadEncoding.BASE_64_URL,
transaction_type = NewTransactionResponse,
required_fields = ["payin_address", "payin_extra_id", "refund_address", "refund_extra_id",
"payout_address", "payout_extra_id", "currency_from", "currency_to",
Expand All @@ -103,7 +103,7 @@ def create_transaction(self, conf: Dict, transaction_id: bytes) -> bytes:
partner_curve = ec.SECP256K1(),
signature_computation = SignatureComputation.BINARY_ENCODED_PAYLOAD,
signature_encoding = SignatureEncoding.DER,
payload_encoding = PayloadEncoding.BYTES_ARRAY,
default_payload_encoding = PayloadEncoding.BYTES_ARRAY,
transaction_type = NewTransactionResponse,
required_fields = ["payin_address", "payin_extra_id", "refund_address", "refund_extra_id",
"payout_address", "payout_extra_id", "currency_from", "currency_to",
Expand All @@ -117,7 +117,7 @@ def create_transaction(self, conf: Dict, transaction_id: bytes) -> bytes:
partner_curve = ec.SECP256R1(),
signature_computation = SignatureComputation.DOT_PREFIXED_BASE_64_URL,
signature_encoding = SignatureEncoding.PLAIN_R_S,
payload_encoding = PayloadEncoding.BASE_64_URL,
default_payload_encoding = PayloadEncoding.BASE_64_URL,
transaction_type = NewSellResponse,
transaction_id_field = "device_transaction_id",
required_fields = ["trader_email", "in_currency", "in_amount", "in_address", "out_currency", "out_amount"],
Expand All @@ -132,7 +132,7 @@ def create_transaction(self, conf: Dict, transaction_id: bytes) -> bytes:
partner_curve = ec.SECP256R1(),
signature_computation = SignatureComputation.DOT_PREFIXED_BASE_64_URL,
signature_encoding = SignatureEncoding.PLAIN_R_S,
payload_encoding = PayloadEncoding.BASE_64_URL,
default_payload_encoding = PayloadEncoding.BASE_64_URL,
transaction_type = NewFundResponse,
required_fields = ["user_id", "account_name", "in_currency", "in_amount", "in_address"],
transaction_id_field = "device_transaction_id",
Expand All @@ -144,7 +144,7 @@ def create_transaction(self, conf: Dict, transaction_id: bytes) -> bytes:
partner_curve = ec.SECP256R1(),
signature_computation = SignatureComputation.DOT_PREFIXED_BASE_64_URL,
signature_encoding = SignatureEncoding.DER,
payload_encoding = PayloadEncoding.BASE_64_URL,
default_payload_encoding = PayloadEncoding.BASE_64_URL,
transaction_type = NewFundResponse,
required_fields = ["user_id", "account_name", "in_currency", "in_amount", "in_address"],
transaction_id_field = "device_transaction_id",
Expand All @@ -161,17 +161,30 @@ def create_transaction(self, conf: Dict, transaction_id: bytes) -> bytes:
SubCommand.FUND_NG: FUND_NG_SPECS,
}

def craft_tx(subcommand: SubCommand, conf: Dict, transaction_id: bytes) -> bytes:
def craft_pb(subcommand: SubCommand, tx_infos: Dict, transaction_id: bytes) -> bytes:
subcommand_specs = SUBCOMMAND_TO_SPECS[subcommand]
assert subcommand_specs.check_conf(conf)
return subcommand_specs.create_transaction(conf, transaction_id)
assert subcommand_specs.check_conf(tx_infos)
return subcommand_specs._create_transaction(tx_infos, transaction_id)

def encode_tx(subcommand: SubCommand, signer: SigningAuthority, tx: bytes) -> bytes:
def encode_transaction_signature(subcommand: SubCommand, signer: SigningAuthority, tx: bytes) -> bytes:
subcommand_specs = SUBCOMMAND_TO_SPECS[subcommand]
formated_transaction = subcommand_specs.format_transaction(tx)
signed_transaction = signer.sign(formated_transaction)
return subcommand_specs.encode_signature(signed_transaction)

def craft_transaction(subcommand: SubCommand, transaction: bytes, fees: int) -> bytes:
subcommand_specs = SUBCOMMAND_TO_SPECS[subcommand]
fees_bytes = int_to_minimally_sized_bytes(fees)
prefix_length = 2 if (subcommand == SubCommand.SWAP_NG or subcommand == SubCommand.FUND_NG or subcommand == SubCommand.SELL_NG) else 1
payload = prefix_with_len_custom(transaction, prefix_length) + prefix_with_len(fees_bytes)
return payload

def craft_and_sign_tx(subcommand: SubCommand, tx_infos: Dict, transaction_id: bytes, fees: int, signer: SigningAuthority):
pb = craft_pb(subcommand, tx_infos, transaction_id)
tx = craft_transaction(subcommand, pb, fees)
signed_tx = encode_transaction_signature(subcommand, signer, pb)
return tx, signed_tx

def extract_payout_ticker(subcommand: SubCommand, tx_infos: Dict) -> str:
subcommand_specs = SUBCOMMAND_TO_SPECS[subcommand]
return tx_infos[subcommand_specs.payout_field]
Expand All @@ -186,12 +199,6 @@ def extract_refund_ticker(subcommand: SubCommand, tx_infos: Dict) -> Optional[st
def get_partner_curve(subcommand: SubCommand) -> ec.EllipticCurve:
return SUBCOMMAND_TO_SPECS[subcommand].partner_curve

def craft_transaction_proposal(subcommand: SubCommand, transaction: bytes, fees: int) -> bytes:
fees_bytes = int_to_minimally_sized_bytes(fees)
prefix_length = 2 if (subcommand == SubCommand.SWAP_NG or subcommand == SubCommand.FUND_NG or subcommand == SubCommand.SELL_NG) else 1
payload = prefix_with_len_custom(transaction, prefix_length) + prefix_with_len(fees_bytes)
return payload

def get_credentials(subcommand: SubCommand, partner: SigningAuthority) -> bytes:
if subcommand == SubCommand.SWAP_NG or subcommand == SubCommand.SELL_NG or subcommand == SubCommand.FUND_NG:
return partner.credentials_ng
Expand Down
28 changes: 12 additions & 16 deletions test/python/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ragger.error import ExceptionRAPDU

from .apps.exchange import ExchangeClient, Rate, SubCommand, Errors, Command, P2_EXTEND, P2_MORE, EXCHANGE_CLASS
from .apps.exchange_transaction_builder import get_partner_curve, craft_tx, encode_tx, LEGACY_SUBCOMMANDS, ALL_SUBCOMMANDS, NEW_SUBCOMMANDS, craft_transaction_proposal, get_credentials
from .apps.exchange_transaction_builder import get_partner_curve, LEGACY_SUBCOMMANDS, ALL_SUBCOMMANDS, NEW_SUBCOMMANDS, get_credentials, craft_and_sign_tx
from .apps.signing_authority import SigningAuthority, LEDGER_SIGNER
from .apps import cal as cal

Expand Down Expand Up @@ -57,7 +57,7 @@ def test_extension_forbidden_for_subcommand(self, backend, subcommand):
credentials = get_credentials(subcommand, partner)
ex.set_partner_key(credentials)
ex.check_partner_key(LEDGER_SIGNER.sign(credentials))
tx = craft_tx(subcommand, TX_INFOS[subcommand], transaction_id)
tx, _ = craft_and_sign_tx(subcommand, TX_INFOS[subcommand], transaction_id, FEES, partner)
# Send the exchange transaction proposal and its signature

for ext in [P2_MORE, P2_EXTEND, (P2_EXTEND | P2_MORE)]:
Expand All @@ -82,20 +82,18 @@ def test_extension_many_chunks(self, backend, subcommand):
credentials = get_credentials(subcommand, partner)
ex.set_partner_key(credentials)
ex.check_partner_key(LEDGER_SIGNER.sign(credentials))
tx = craft_tx(subcommand, TX_INFOS[subcommand], transaction_id)
signed_tx = encode_tx(subcommand, partner, tx)
tx, tx_signature = craft_and_sign_tx(subcommand, TX_INFOS[subcommand], transaction_id, FEES, partner)

# Artificially create many chunks to test the TX concatenation
payload = craft_transaction_proposal(subcommand, tx, FEES)
payload_split = [payload[x:x + 70] for x in range(0, len(payload), 70)]
for i, p in enumerate(payload_split):
tx_split = [tx[x:x + 70] for x in range(0, len(tx), 70)]
for i, p in enumerate(tx_split):
p2 = subcommand
if i != len(payload_split) - 1:
if i != len(tx_split) - 1:
p2 |= P2_MORE
if i != 0:
p2 |= P2_EXTEND
backend.exchange(EXCHANGE_CLASS, Command.PROCESS_TRANSACTION_RESPONSE, p1=Rate.FIXED, p2=p2, data=p)
ex.check_transaction_signature(signed_tx)
ex.check_transaction_signature(tx_signature)


@pytest.mark.parametrize("subcommand", NEW_SUBCOMMANDS)
Expand All @@ -106,8 +104,7 @@ def test_extension_advanced_usage(self, backend, subcommand):
credentials = get_credentials(subcommand, partner)
ex.set_partner_key(credentials)
ex.check_partner_key(LEDGER_SIGNER.sign(credentials))
tx = craft_tx(subcommand, TX_INFOS[subcommand], transaction_id)
signed_tx = encode_tx(subcommand, partner, tx)
tx, tx_signature = craft_and_sign_tx(subcommand, TX_INFOS[subcommand], transaction_id, FEES, partner)

# Start with an EXTEND flag
with pytest.raises(ExceptionRAPDU) as e:
Expand All @@ -123,15 +120,14 @@ def test_extension_advanced_usage(self, backend, subcommand):
backend.exchange(EXCHANGE_CLASS, Command.PROCESS_TRANSACTION_RESPONSE, p1=Rate.FIXED, p2=(subcommand | P2_MORE), data=b"0011233445566778899")

# Restart a chunk send with correct data, artificially create many chunks to test the TX concatenation
payload = craft_transaction_proposal(subcommand, tx, FEES)
payload_split = [payload[x:x + 70] for x in range(0, len(payload), 70)]
for i, p in enumerate(payload_split):
tx_split = [tx[x:x + 70] for x in range(0, len(tx), 70)]
for i, p in enumerate(tx_split):
p2 = subcommand
if i != len(payload_split) - 1:
if i != len(tx_split) - 1:
p2 |= P2_MORE
if i != 0:
p2 |= P2_EXTEND
backend.exchange(EXCHANGE_CLASS, Command.PROCESS_TRANSACTION_RESPONSE, p1=Rate.FIXED, p2=p2, data=p)

# Check the signature to ensure the data received has not been corrupted
ex.check_transaction_signature(signed_tx)
ex.check_transaction_signature(tx_signature)
Loading

0 comments on commit cd2ab0a

Please sign in to comment.