Skip to content

Commit

Permalink
Add SignTx tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cedelavergne-ledger committed Oct 30, 2024
1 parent 90ef752 commit fc4b618
Show file tree
Hide file tree
Showing 4 changed files with 1,148 additions and 1 deletion.
311 changes: 310 additions & 1 deletion tests/application_client/command_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,62 @@
from input_files.cvote import MAX_CIP36_PAYLOAD_SIZE, CVoteTestCase
from input_files.signOpCert import OpCertTestCase
from input_files.signMsg import SignMsgTestCase, MessageAddressFieldType
from input_files.signTx import SignTxTestCase, TxInput, TxOutput, Certificate
from input_files.signTx import TxAuxiliaryDataType, TxAuxiliaryData, TxAuxiliaryDataHash, Withdrawal
from input_files.signTx import TxOutputBabbage, ThirdPartyAddressParams, CertificateType, CredentialType

from application_client.app_def import InsType, AddressType, StakingDataSourceType


class P1Type(IntEnum):
# Derive Address
P1_RETURN = 0x01
P1_DISPLAY = 0x02
# Sign CIP36 Vote
P1_INIT = 0x01
P1_CHUNK = 0x02
P1_CONFIRM = 0x03
P1_WITNESS = 0x04

# SignTx
P1_INPUTS = 0x02
P1_OUTPUTS = 0x03
P1_FEE = 0x04
P1_TTL = 0x05
P1_CERTIFICATES = 0x06
P1_WITHDRAWALS = 0x07
P1_AUX_DATA = 0x08
P1_VALIDITY_INTERVAL_START = 0x09
P1_TX_CONFIRM = 0x0a
P1_MINT = 0x0b
P1_SCRIPT_DATA_HASH = 0x0c
P1_COLLATERAL_INPUTS = 0x0d
P1_REQUIRED_SIGNERS = 0x0e
P1_TX_WITNESSES = 0x0f
P1_TOTAL_COLLATERAL = 0x10
P1_REFERENCE_INPUTS = 0x11
P1_COLLATERAL_OUTPUT = 0x12
P1_VOTING_PROCEDURES = 0x13
P1_TREASURY = 0x15
P1_DONATION = 0x16


class P2Type(IntEnum):
# SignTx Outputs
P2_BASIC_DATA = 0x30
P2_DATUM = 0x34
P2_DATUM_CHUNK = 0x35
P2_SCRIPT = 0x36
P2_SCRIPT_CHUNK = 0x37
P2_CONFIRM = 0x33
# SignTx Aux Data
P2_INIT = 0x36
P2_VOTE_KEY = 0x30
P2_DELEGATION = 0x37
P2_STAKING_KEY = 0x31
P2_PAYMENT_ADDRESS = 0x32
P2_NONCE = 0x33
P2_VOTING_PURPOSE = 0x35
P2_AUX_CONFIRM = 0x34

class CommandBuilder:
_CLA: int = 0xd7
Expand Down Expand Up @@ -270,6 +314,271 @@ def sign_msg_confirm(self) -> bytes:
return self._serialize(InsType.SIGN_MSG, P1Type.P1_CONFIRM, 0x00)


def sign_tx_init(self, testCase: SignTxTestCase) -> bytes:
"""APDU Builder for Sign TX - INIT step
Args:
testCase (SignTxTestCase): Test parameters
Returns:
Serial data APDU
"""

# Serialization format:
# Options (1B)
# NetworkId (1B)
# ProtocolMagic (4B)
# TTL option flag (1B)
# auxiliary Data option flag (1B)
# validityIntervalStart option flag (1B)
# mint
# scriptDataHash
# includeNetworkId
# collateralOutput
# totalCollateral
# treasury
# donation
# signingMode
# TX inputs length (4B)
# TX outputs length (4B)
# TX certificates length (4B)
# TX withdrawals length (4B)
# witnessLegacy
# collateralInputs
# requiredSigners
# referenceInputs
# votingProcedures
# witnessBabbage

data = bytes()
data += testCase.options.to_bytes(8, "big")
data += testCase.tx.network.networkId.to_bytes(1, "big")
data += testCase.tx.network.protocol.to_bytes(4, "big")
data += self._serializeOptionFlags(testCase.tx.ttl is not None)
data += self._serializeOptionFlags(testCase.tx.auxiliaryData is not None)
data += self._serializeOptionFlags(testCase.tx.validityIntervalStart is not None)
data += self._serializeOptionFlags(len(testCase.tx.mint) > 0)
data += self._serializeOptionFlags(testCase.tx.scriptDataHash is not None)
data += self._serializeOptionFlags(testCase.tx.includeNetworkId is not None)
data += self._serializeOptionFlags(testCase.tx.collateralOutput is not None)
data += self._serializeOptionFlags(testCase.tx.totalCollateral is not None)
data += self._serializeOptionFlags(testCase.tx.treasury is not None)
data += self._serializeOptionFlags(testCase.tx.donation is not None)
data += testCase.signingMode.to_bytes(1, "big")
data += len(testCase.tx.inputs).to_bytes(4, "big")
data += len(testCase.tx.outputs).to_bytes(4, "big")
data += len(testCase.tx.certificates).to_bytes(4, "big")
data += len(testCase.tx.withdrawals).to_bytes(4, "big")
data += len(testCase.tx.collateralInputs).to_bytes(4, "big")
data += len(testCase.tx.requiredSigners).to_bytes(4, "big")
data += len(testCase.tx.referenceInputs).to_bytes(4, "big")
data += len(testCase.tx.votingProcedures).to_bytes(4, "big")
# Compute the number of Babbage Witnesses Buffer
nbPaths = sum(1 for input in testCase.tx.inputs if input.path)
data += nbPaths.to_bytes(4, "big") # number of Babbage Witnesses Buffer
return self._serialize(InsType.SIGN_TX, P1Type.P1_INIT, 0x00, data)


def sign_tx_aux_data(self, auxData: TxAuxiliaryData) -> bytes:
"""APDU Builder for Sign TX - AUX DATA step
Args:
auxData (TxAuxiliaryData): Test parameters
Returns:
Serial data APDU
"""

# Serialization format:
data = bytes()
if isinstance(auxData, TxAuxiliaryDataHash):
data += TxAuxiliaryDataType.ARBITRARY_HASH.to_bytes(1, "big")
data += bytes.fromhex(auxData.hashHex)
else:
data += TxAuxiliaryDataType.CIP36_REGISTRATION.to_bytes(1, "big")
return self._serialize(InsType.SIGN_TX, P1Type.P1_AUX_DATA, 0x00, data)


def sign_tx_inputs(self, txInput: TxInput) -> bytes:
"""APDU Builder for Sign TX - INPUTS step
Args:
txInput (TxInput): Test parameters
Returns:
Serial data APDU
"""

# Serialization format:
data = bytes()
data += bytes.fromhex(txInput.txHashHex)
data += txInput.outputIndex.to_bytes(4, "big")
return self._serialize(InsType.SIGN_TX, P1Type.P1_INPUTS, 0x00, data)


def sign_tx_outputs_basic(self, txOutput: TxOutput) -> bytes:
"""APDU Builder for Sign TX - OUTPUTS step - BASIC DATA level
Args:
txOutput (TxOutput): Test parameters
Returns:
Serial data APDU
"""

# Serialization format:
# Format (1B)
# Tx Output destination
# Coin (8B)
# TokenBundle Length (4B)
# datum option flag (1B)
# referenceScriptHex option flag (1B)

data = bytes()
data += txOutput.format.to_bytes(1, "big")
data += txOutput.destinationType.to_bytes(1, "big")
if isinstance(txOutput.destination, ThirdPartyAddressParams):
data += int(len(txOutput.destination.addressHex) / 2).to_bytes(4, "big")
data += bytes.fromhex(txOutput.destination.addressHex)
else:
data += self._serializeAddressParams(txOutput.destination)
data += txOutput.amount.to_bytes(8, "big")
data += len(txOutput.tokenBundle).to_bytes(4, "big")
data += self._serializeOptionFlags(txOutput.datumHashHex is not None)
if isinstance(txOutput, TxOutputBabbage):
data += self._serializeOptionFlags(txOutput.referenceScriptHex is not None)
return self._serialize(InsType.SIGN_TX, P1Type.P1_OUTPUTS, P2Type.P2_BASIC_DATA, data)


def sign_tx_outputs_confirm(self) -> bytes:
"""APDU Builder for Sign TX - OUTPUTS step - CONFIRM level
Returns:
Serial data APDU
"""

return self._serialize(InsType.SIGN_TX, P1Type.P1_OUTPUTS, P2Type.P2_CONFIRM)


def sign_tx_fee(self, testCase: SignTxTestCase) -> bytes:
"""APDU Builder for Sign TX - FEE step
Args:
testCase (SignTxTestCase): Test parameters
Returns:
Serial data APDU
"""

# Serialization format:
data = bytes()
data += testCase.tx.fee.to_bytes(8, "big")
return self._serialize(InsType.SIGN_TX, P1Type.P1_FEE, 0x00, data)


def sign_tx_ttl(self, testCase: SignTxTestCase) -> bytes:
"""APDU Builder for Sign TX - TTL step
Args:
testCase (SignTxTestCase): Test parameters
Returns:
Serial data APDU
"""

# Serialization format:
data = bytes()
assert testCase.tx.ttl is not None
data += testCase.tx.ttl.to_bytes(8, "big")
return self._serialize(InsType.SIGN_TX, P1Type.P1_TTL, 0x00, data)


def sign_tx_withdrawal(self, withdrawal: Withdrawal) -> bytes:
"""APDU Builder for Sign TX - WITHDRAWAL step
Args:
withdrawal (Withdrawal): Test parameters
Returns:
Serial data APDU
"""

# Serialization format:
data = bytes()
data += withdrawal.amount.to_bytes(8, "big")
data += withdrawal.type.to_bytes(1, "big")
if withdrawal.stakeCredential.keyValue.startswith("m/"):
data += pack_derivation_path(withdrawal.stakeCredential.keyValue)
else:
data += bytes.fromhex(withdrawal.stakeCredential.keyValue)
return self._serialize(InsType.SIGN_TX, P1Type.P1_WITHDRAWALS, 0x00, data)


def sign_tx_certificate(self, certificate: Certificate) -> bytes:
"""APDU Builder for Sign TX - WITHDRAWAL step
Args:
certificate (Certificate): Test parameters
Returns:
Serial data APDU
"""

# Serialization format:
data = bytes()
if certificate.type in (CertificateType.STAKE_REGISTRATION, CertificateType.STAKE_DEREGISTRATION):
data += certificate.type.to_bytes(1, "big")
else:
raise NotImplementedError("Not implemented yet")
if certificate.stakeCredential.keyValue.startswith("m/"):
data += CredentialType.KEY_PATH.to_bytes(1, "big")
data += pack_derivation_path(certificate.stakeCredential.keyValue)
else:
data += CredentialType.KEY_HASH.to_bytes(1, "big")
data += bytes.fromhex(certificate.stakeCredential.keyValue)
return self._serialize(InsType.SIGN_TX, P1Type.P1_CERTIFICATES, 0x00, data)


def sign_tx_confirm(self) -> bytes:
"""APDU Builder for Sign TX - CONFIRM step
Returns:
Serial data APDU
"""

# Serialization format:
return self._serialize(InsType.SIGN_TX, P1Type.P1_TX_CONFIRM)


def sign_tx_witness(self, path: str) -> bytes:
"""APDU Builder for Sign TX - WITNESS step
Args:
path (str): Input Test path
Returns:
Serial data APDU
"""

# Serialization format:
data = bytes()
data += pack_derivation_path(path)
return self._serialize(InsType.SIGN_TX, P1Type.P1_TX_WITNESSES, 0x00, data)


def _serializeOptionFlags(self, included: bool) -> bytes:
"""Serialize Flag option value
Args:
included (boolean): Flag value
Returns:
Serial data APDU
"""
value = 0x02 if included else 0x01
return value.to_bytes(1, "big")


def _serializeAddressParams(self, testCase: DeriveAddressTestCase) -> bytes:
"""Serialize address parameters
Expand Down
Loading

0 comments on commit fc4b618

Please sign in to comment.