Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions test/pycardano/test_transaction.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import json
import os
import tempfile
from dataclasses import dataclass
from fractions import Fraction
from test.pycardano.util import check_two_way_cbor

import pytest
from nacl.signing import SigningKey as NACLSigningKey
from typeguard import TypeCheckError

from pycardano import ParameterChangeAction
Expand Down Expand Up @@ -701,6 +703,48 @@ def test_decode_param_update_proposal_tx():
].gov_action.protocol_param_update.treasury_growth_rate == Fraction(1, 10)


def test_sign_dex_transaction_with_raw_plutus_data_inline_datum():
# Regression for https://github.com/python-cardano/pycardano/issues/402
#
# Wallets like Eternl export unsigned DEX swap CBORs that contain outputs with
# RawPlutusData inline datums (indefinite-length encoded). Before fixes #474 and #479,
# round-tripping such a transaction corrupted the body CBOR, producing a different hash
# and making any externally-computed signature unverifiable.
#
# This test uses the Liqwid DEX transaction (signed, Conway era) as a representative
# fixture: it contains two outputs with complex inline Plutus datums.
with open("test/resources/cbors/liqwid.json") as f:
cbor_hex = json.load(f)["cborHex"]

tx = Transaction.from_cbor(cbor_hex)

# The tx body must round-trip to identical bytes so the hash is stable.
assert tx.to_cbor_hex() == cbor_hex, "Full transaction CBOR did not round-trip"

# Both outputs carry inline datums decoded from complex indefinite-length Plutus data.
outputs_with_datum = [o for o in tx.transaction_body.outputs if o.datum is not None]
assert len(outputs_with_datum) == 2

# The transaction id is derived from blake2b-256 of the body CBOR.
# If the body encodes differently after decode, this id would change.
expected_id = TransactionId.from_primitive(
"1c280d01277784ce94c2fb14a5c97c96f80c08d0d812da4ecbd1d78397d03dab"
)
assert tx.id == expected_id

# Signing the body hash must produce a verifiable signature.
sk = PaymentSigningKey.from_json("""{
"type": "GenesisUTxOSigningKey_ed25519",
"description": "Genesis Initial UTxO Signing Key",
"cborHex": "5820093be5cd3987d0c9fd8854ef908f7746b69e2d73320db6dc0f780d81585b84c2"
}""")
body_hash = tx.transaction_body.hash()
signature = sk.sign(body_hash)
# Verify the signature is valid against the body hash using nacl directly.
nacl_vk = NACLSigningKey(sk.payload).verify_key
nacl_vk.verify(body_hash, signature)


def test_decode_byron_transaction():
tx_cbor_hex = """83a400818258205d5f5c04aaa2367c5a700cf6ba9e9da76e214a0a1485a174618cb38b292bf0d9000182825839016a2fcce35ec3795b9418ae49b69074a17cdd0a7c60ae6ba63fc85eff17eabf85728a590b7785f27d60dea7d4bcb356b438b9d577a45547fe1b0000001e3001052482584c82d818584283581c91d0a0518e3e764e13f6ef37580a6be8ab14da4f3066fd01af01da6aa101581e581cabbf051bdee353839fbb21a6d4e6c584138a6a33896bb96d4124a330001a3592e2cc1a0a6526b0021a0002964d031a012f6296a10081825820e8fe69f9fd8afcb4792e3ca0f08b49e6eece1788c2d7b026096cfdbd1344a9bc5840dcef77b73af0922005f4b60d21333628348864c405ff52efd3f72523bf2c790e662650ad9951d306b40ce5beddf5b8eebb6731156b8b7617f6614b9ffdf2fb05f6"""
tx = Transaction.from_cbor(tx_cbor_hex)
Expand Down