1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-14 11:39:03 +00:00

Add multiasset sending and min validity to Cardano transactions

(cherry picked from commit e4c406822c)
This commit is contained in:
Rafael Korbas 2021-01-25 17:12:32 +01:00 committed by Tomas Susanka
parent 013f36981d
commit ffa96205fb
21 changed files with 847 additions and 87 deletions

View File

@ -126,6 +126,8 @@ message CardanoSignTx {
repeated CardanoTxCertificateType certificates = 9; // transaction certificates - added in shelley
repeated CardanoTxWithdrawalType withdrawals = 10; // transaction withdrawals - added in shelley
optional bytes metadata = 11; // transaction metadata - added in shelley
optional uint64 validity_interval_start = 12; // transaction validity start - added in allegra
/**
* Structure representing cardano transaction input
*/
@ -144,6 +146,17 @@ message CardanoSignTx {
// repeated uint32 address_n = 2; // moved to address_parameters
optional uint64 amount = 3; // amount to spend
optional CardanoAddressParametersType address_parameters = 4; // parameters used to derive the address
repeated CardanoAssetGroupType token_bundle = 5; // custom assets - added in mary
}
message CardanoAssetGroupType {
required bytes policy_id = 1; // asset group policy id
repeated CardanoTokenType tokens = 2; // asset name-amount pair
}
message CardanoTokenType {
required bytes asset_name_bytes = 1; // asset name as bytestring (may be either ascii string or hash)
required uint64 amount = 2; // asset amount
}
/**

View File

@ -635,6 +635,205 @@
"result": {
"error_message": "Invalid metadata"
}
},
{
"description": "Too many tokens in output",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [],
"withdrawals": [],
"metadata": "",
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
"token_bundle": [
{
"policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "01aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "7878754"
},
{
"asset_name_bytes": "02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "03aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "04aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "05aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "06aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "07aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "08aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "09aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
}
]
},
{
"policy_id": "75a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "10aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "11aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "12aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "13aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "14aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "16aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "17aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
}
]
}
]
}
]
},
"result": {
"error_message": "Maximum tx output size"
}
},
{
"description": "Repeated asset name in multiasset token group",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [],
"withdrawals": [],
"metadata": "",
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
"token_bundle": [
{
"policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "74652474436f696e",
"amount": "7878754"
},
{
"asset_name_bytes": "74652474436f696e",
"amount": "1234"
}
]
}
]
}
]
},
"result": {
"error_message": "Invalid token bundle in output"
}
},
{
"description": "Repeated policyId in multiasset output",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [],
"withdrawals": [],
"metadata": "",
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
"token_bundle": [
{
"policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "74652474436f696e",
"amount": "7878754"
}
]
},
{
"policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "74652474436f696f",
"amount": "7878754"
}
]
}
]
}
]
},
"result": {
"error_message": "Invalid token bundle in output"
}
}
]
}

View File

@ -541,6 +541,146 @@
"tx_hash": "47cf79f20c6c62edb4162b3b232a57afc1bd0b57c7fd8389555276408a004776",
"serialized_tx": "83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018382582f82d818582583581cc817d85b524e3d073795819a25cdbb84cff6aa2bbb3a081980d248cba10242182a001a0fb6fc611a002dd2e882581d60cb03849e268f989b5a843107bad7fa2908246986a8f3d643f8c184800182582f82d818582583581c98c3a558f39d1d993cc8770e8825c70a6d0f5a9eb243501c4526c29da10242182a001aa8566c011a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840cc11adf81cb3c3b75a438325f8577666f5cbb4d5d6b73fa6dbbcf5ab36897df34eecacdb54c3bc3ce7fc594ebb2c7aa4db4700f4290facad9b611a035af8710a582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63545a10242182af6"
}
},
{
"description": "Mary era transaction with multiasset output",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"validity_interval_start": 47,
"certificates": [],
"withdrawals": [],
"metadata": "",
"input_flow": [["YES"], ["YES"], ["YES"], ["SWIPE", "YES"], ["SWIPE", "YES"]],
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
"amount": "1234",
"token_bundle": [
{
"policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "74652474436f696e",
"amount": "7878754"
}
]
}
]
},
{
"addressType": 0,
"path": "m/1852'/1815'/0'/0/0",
"stakingPath": "m/1852'/1815'/0'/2/0",
"amount": "7120787"
}
]
},
"result": {
"tx_hash": "b7269ddc59e4094a6581c653e0d5dc1e553e3a5fb6ffae47d3d094dff1cfe87b",
"serialized_tx": "83a500818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018282583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff821904d2a1581c95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39a14874652474436f696e1a007838628258390180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b42771a006ca79302182a030a08182fa100818258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c15840e9ab9920f24f7fdf10c90c9c1794cd9efea03dd4b3add405e5f9ffb61874d2704d376269649f8f5c57ec69b2df74fa94f73191fbeb21987b4b887743af454c06f6"
}
},
{
"description": "Mary era transaction with different policies and tokens",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"validity_interval_start": 47,
"certificates": [],
"withdrawals": [],
"metadata": "",
"input_flow": [["YES"], ["YES"], ["YES"], ["SWIPE", "YES"], ["YES"], ["SWIPE", "YES"], ["SWIPE", "SWIPE", "YES"], ["YES"]],
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
"amount": "1234",
"token_bundle": [
{
"policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "74652474436f696e",
"amount": "7878754"
},
{
"asset_name_bytes": "456c204e69c3b16f",
"amount": "1234"
}
]
},
{
"policy_id": "75a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "7564247542686911",
"amount": "47"
}
]
}
]
},
{
"addressType": 0,
"path": "m/1852'/1815'/0'/0/0",
"stakingPath": "m/1852'/1815'/0'/2/0",
"amount": "7120787"
}
]
},
"result": {
"tx_hash": "0b929def7bd9f44f5602f809bc0f9be30521f6b93d625525cf33b956993bfb22",
"serialized_tx": "83a500818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018282583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff821904d2a2581c75a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39a1487564247542686911182f581c95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39a248456c204e69c3b16f1904d24874652474436f696e1a007838628258390180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b42771a006ca79302182a030a08182fa100818258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c158408751e397bd9610735a92e65eab02c04aa61507f425e53c0119ddc06047bfac279439ee2bf6e0d572defa9e5649a1ea1fc2b8144041ab4970f39cd6850d4d670ef6"
}
},
{
"description": "Mary era transaction with no TTL/validity start",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"certificates": [],
"withdrawals": [],
"metadata": "",
"input_flow": [["YES"]],
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"addressType": 0,
"path": "m/1852'/1815'/0'/0/0",
"stakingPath": "m/1852'/1815'/0'/2/0",
"amount": "7120787"
}
]
},
"result": {
"tx_hash": "b621e22f7cb9aac1a70a3362fde88bdfd31fc100e20f3f3c24a7b853536b4f50",
"serialized_tx": "83a300818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b70001818258390180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b42771a006ca79302182aa100818258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1584088c35c125664935117d9aa1173cae5f01967b02f6b716b1a135570b2fee74728f2f3e39d56b748302c36e2407d7bfefc4054ca1e60dd857e461734ae41d00500f6"
}
}
]
}

View File

@ -1,11 +1,9 @@
# Cardano
MAINTAINER = Gabriel Kerekeš <gabriel.kerekes@vacuumlabs.com>
MAINTAINER = Rafael Korbaš <rafael.korbas@vacuumlabs.com>
ORIGINAL AUTHOR = Juraj Muravský <juraj.muravsky@vacuumlabs.com>
SHELLEY UPDATE AUTHOR = Gabriel Kerekeš <gabriel.kerekes@vacuumlabs.com>
REVIEWER = Jan Matejek <jan.matejek@satoshilabs.com>, Tomas Susanka <tomas.susanka@satoshilabs.com>
-----
@ -13,15 +11,16 @@ REVIEWER = Jan Matejek <jan.matejek@satoshilabs.com>, Tomas Susanka <tomas.susan
## Useful links
[Cardano documentation](https://docs.cardano.org/en/latest/) - official documentation.
[Cardano developer documentation](https://developers.cardano.org/) - official developer documentation.
[Delegation Design Spec](https://hydra.iohk.io/build/2006688/download/1/delegation_design_spec.pdf) - contains information about delegation (addresses, certificates, withdrawals, ...).
[Shelley CDDL spec](https://github.com/input-output-hk/cardano-ledger-specs/blob/460ee17d22cacb3ac4d90536ebe90500a356a1c9/shelley/chain-and-ledger/shelley-spec-ledger-test/cddl-files/shelley.cddl).
[Multi Asset CDDL spec](https://github.com/input-output-hk/cardano-ledger-specs/blob/097890495cbb0e8b62106bcd090a5721c3f4b36f/shelley-ma/shelley-ma-test/cddl-files/shelley-ma.cddl).
[Byron address format](https://github.com/input-output-hk/cardano-wallet/wiki/About-Address-Format---Byron).
[The Shelley 1852' purpose and staking path](https://github.com/input-output-hk/implementation-decisions/blob/e2d1bed5e617f0907bc5e12cf1c3f3302a4a7c42/text/1852-hd-chimeric.md).
[cbor.me](http://cbor.me/) - very useful tool for CBOR inspection.
## Important notes
Unfortunately we are aware of the fact that currently at most ~14 inputs are supported per transaction. We suspect this is due to the memory heavy CBOR implementation. This should be fixed in the near future.
Unfortunately we are aware of the fact that currently at most ~14 inputs are supported per transaction. To resolve this, the cardano app will have to be rewritten to support transaction streaming.
Cardano requires a custom `seed.py` file and `Keychain` class. This is because the original Cardano derivation schemes don't separate seed generation from key tree derivation and also because we need to support both Byron (44') and Shelley (1852') purposes. More on this can be found [here](https://github.com/satoshilabs/slips/blob/master/slip-0023.md) and [here](https://github.com/input-output-hk/implementation-decisions/blob/e2d1bed5e617f0907bc5e12cf1c3f3302a4a7c42/text/1852-hd-chimeric.md).
@ -93,7 +92,7 @@ Testnet: `stake_test1uqfz49rtntfa9h0s98f6s28sg69weemgjhc4e8hm66d5yac643znq`
Transactions don't have a distinct type. Every transaction may transfer funds, post a certificate, withdraw funds or do all at once (to a point).
_Unfortunately we are aware of the fact that currently at most ~14 inputs are supported per transaction. We suspect this is due to the memory heavy CBOR implementation. This should be fixed in the near future._
_Unfortunately we are aware of the fact that currently at most ~14 inputs are supported per transaction. This should be resolved when the cardano app is updated to support transaction streaming._
#### Witnesses
@ -105,7 +104,29 @@ They only need to contain the public key (not the extended public key) and the s
_Byron witnesses_:
In order to be able to properly verify them, Byron witnesses need to contain the public key, signature, chain code and address attributes (which are empty on mainnet or contain the protocol magic on testnet).
More on witness structure can be found [here](https://github.com/input-output-hk/cardano-ledger-specs/blob/460ee17d22cacb3ac4d90536ebe90500a356a1c9/shelley/chain-and-ledger/shelley-spec-ledger-test/cddl-files/shelley.cddl#L213).
More on witness structure can be found [here](https://github.com/input-output-hk/cardano-ledger-specs/blob/097890495cbb0e8b62106bcd090a5721c3f4b36f/shelley-ma/shelley-ma-test/cddl-files/shelley-ma.cddl#L219).
#### Multi Asset support
_Multi Asset support has been added in the Cardano Mary era_
_Quote from [developer docs](https://developers.cardano.org/en/development-environments/native-tokens/multi-asset-tokens-explainer/):_
> This feature extends the existing accounting infrastructure defined in the ledger model, which is designed for processing ada-only transactions, to accommodate transactions that simultaneously use a range of assets. These assets include ada and a variety of user-define custom token types.
Transaction outputs may include custom tokens on top of ADA tokens:
```
1: [
[
address, [
ADA_amount, {
policy_id: {
asset_name: asset_amount
}}]]]
```
Please see the transaction below for more details.
**The serialized transaction output size is currently limited to 512 bytes. This limitation is a mitigation measure to prevent sending large (especially change) outputs containing many tokens that Trezor would not be able to spend given that currently the full Cardano transaction is held in-memory. Once Cardano-transaction signing is refactored to be streamed, this limit can be lifted**
#### Certificates
@ -122,7 +143,7 @@ And these three which are not supported by Trezor at the moment:
Stake key de-registration and delegation certificates both need to be witnessed by the corresponding staking key.
You can read more on certificates in the [delegation design spec](https://hydra.iohk.io/build/2006688/download/1/delegation_design_spec.pdf#subsection.3.4).
Info about their structure can be found [here](https://github.com/input-output-hk/cardano-ledger-specs/blob/460ee17d22cacb3ac4d90536ebe90500a356a1c9/shelley/chain-and-ledger/shelley-spec-ledger-test/cddl-files/shelley.cddl#L102).
Info about their structure can be found [here](https://github.com/input-output-hk/cardano-ledger-specs/blob/097890495cbb0e8b62106bcd090a5721c3f4b36f/shelley-ma/shelley-ma-test/cddl-files/shelley-ma.cddl#L102).
#### Withdrawals
@ -132,7 +153,7 @@ You can read more on withdrawals in the [delegation design spec](https://hydra.i
#### Metadata
Each transaction may contain metadata. Metadata format can be found [here](https://github.com/input-output-hk/cardano-ledger-specs/blob/460ee17d22cacb3ac4d90536ebe90500a356a1c9/shelley/chain-and-ledger/shelley-spec-ledger-test/cddl-files/shelley.cddl#L210). It's basically a CBOR serialized map and can contain numbers, bytes, strings or nested maps/lists.
Each transaction may contain metadata. Metadata format can be found [here](https://github.com/input-output-hk/cardano-ledger-specs/blob/097890495cbb0e8b62106bcd090a5721c3f4b36f/shelley-ma/shelley-ma-test/cddl-files/shelley-ma.cddl#L212). It's basically a CBOR serialized map and can contain numbers, bytes, strings or nested maps/lists.
Due to memory limitations we currently enforce a maximum size of 500 bytes for metadata.
@ -145,12 +166,11 @@ Due to memory limitations we currently enforce a maximum size of 500 bytes for m
You can use a combination of [cardano-node](https://github.com/input-output-hk/cardano-node) and cardano-cli (part of the cardano-node repo) to submit a transaction.
## Serialization format
Cardano uses [CBOR](https://www.rfc-editor.org/info/rfc7049) as a serialization format. [Here](https://github.com/input-output-hk/cardano-ledger-specs/blob/460ee17d22cacb3ac4d90536ebe90500a356a1c9/shelley/chain-and-ledger/shelley-spec-ledger-test/cddl-files/shelley.cddl) is the [CDDL](https://tools.ietf.org/html/rfc8610) specification for Shelley.
Cardano uses [CBOR](https://www.rfc-editor.org/info/rfc7049) as a serialization format. [Here](https://github.com/input-output-hk/cardano-ledger-specs/blob/097890495cbb0e8b62106bcd090a5721c3f4b36f/shelley-ma/shelley-ma-test/cddl-files/shelley-ma.cddl) is the [CDDL](https://tools.ietf.org/html/rfc8610) specification for after Multi Asset support has been added.
#### Raw transaction example
```
83a600818258200d4a5315236df09f331158cae0f78d3df6cdb952a387bcd160dcb1bd2c708c6b00018182583900667ee84f714720123b92dd159bc306925020c460d464cea40eebc59f6c72a09118a3307789bc6d79e3b2149468f62df586085bcee687ca4d1b00000018fab759cd021a00030d40031a0007a120048182018200581cf228837e81c3baaa1879dbeff94e86fa5eba342aa05cd6d1c3bf23ed05a1581de06c72a09118a3307789bc6d79e3b2149468f62df586085bcee687ca4d1b00000001ad72b9d4a10082825820d198d009e0e482bc940331c3709c7ccdc1decbf0675e7c06380c1da3e129e7265840f62f02511ac77eebdbd87221a3bc9c93cf0971a13107ff41b6ea4ea14d720b361f41ab4994b91022763a10ebe1edf8174ca31ec2c7f56be72759d7e75303b603825820f1cea7b5d7f81e6e7858681634d957117ffe4e78bf9d475dbae9101baddda49858407ac236ad5684d22848a725246e83a043611c8ecebf04864d1b9fae6a33f23684790bc05d44a49b1c0a48df00151acafdcc93c29faf93663c9ed704cefd1a4b0df6
83a700818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018282583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff821904d2a1581c95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39a14874652474436f696e1910e18258390180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b427719115c02182a030a048182008200581c122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b427705a1581de1122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b42771903e80814a10082825820bc65be1b0b9d7531778a1317c2aa6de936963c3f9ac7d5ee9e9eda25e0c97c5e5840c6e85c7eec254f765ddc119b1f40ef50944dcb1882822c3d61641785bbc312d1049ed0a92ded74745986f5d464d0d0caafc9f0c66285a056309d3d39cf19b20e8258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c158401feabb9e56bca7d3cb75f0942d1ebaec92f167193c70fb9b416e9ae3d3e0f368f49fde3f4a862eb6a02f9a27834d0b7c1f6dd689616809432c99f3ab7249ad0ef6
```
#### The same transactions with structure description
@ -163,27 +183,46 @@ Cardano uses [CBOR](https://www.rfc-editor.org/info/rfc7049) as a serialization
{
# inputs [id, index]
# uint(0), array(1), array(2), bytes(32), uint(0)
0: [[h'0D4...', 0]],
0: [[h'3B4...', 0]],
# outputs [address, amount]
# uint(1), array(1), array(2), bytes(57), uint(107285535181)
1: [[h'006...', 107285535181]],
# outputs [address, [ada_amount, { policy_id => { asset_name => asset_amount }}]]
# uint(1), array(2)
1: [
# multi asset output
# array(2), bytes(57), uint(1234), map(1), bytes(28), map(1), bytes(8), uint(4321)
[
h'01E...', [
1234, {
h'95A...': {
h'74652474436F696E': 4321
}
}
]
],
# output containing only ADA [address, ada_amount]
# array(2), bytes(57), uint(4444)
[h'018...', 4444],
]
# fee
# uint(2), uint(200000)
2: 200000,
# uint(2), uint(42)
2: 42,
# ttl
# uint(3), uint(500000)
3: 500000,
# uint(3), uint(10)
3: 10,
# certificates [[type, [keyhash/scripthash, keyhash]]]
# uint(4), array(1), array(2), uint(1), array(2), uint(0), bytes(28)
4: [[1,[0, h'F22...']]],
# uint(4), array(1), array(2), uint(0), array(2), uint(0), bytes(28)
4: [[0,[0, h'122...']]],
# withdrawal [reward_address: amount]
# uint(5), map(1), bytes(29), uint(7204944340)
5: {h'E06...': 7204944340}
5: {h'E11...': 1000},
# validity_interval_start
# uint(8), uint(20)
8: 20
},
# witnesses
# map(1)
@ -192,9 +231,9 @@ Cardano uses [CBOR](https://www.rfc-editor.org/info/rfc7049) as a serialization
# uint(0), array(2)
0: [
# array(2), bytes(32), bytes(64)
[h'D19...', h'F62...'],
[h'BC6...', h'C6E...'],
# array(2), bytes(32), bytes(64)
[h'F1C...', h'7AC...']
[h'5D0...', h'1FE...']
]
},

View File

@ -4,6 +4,7 @@ INVALID_ADDRESS = wire.ProcessError("Invalid address")
NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch!")
INVALID_CERTIFICATE = wire.ProcessError("Invalid certificate")
INVALID_WITHDRAWAL = wire.ProcessError("Invalid withdrawal")
INVALID_TOKEN_BUNDLE_OUTPUT = wire.ProcessError("Invalid token bundle in output")
INVALID_METADATA = wire.ProcessError("Invalid metadata")
INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE = wire.ProcessError(
"Stakepool registration transaction cannot contain other certificates nor withdrawals"

View File

@ -1,7 +1,7 @@
from apps.cardano.helpers.paths import ACCOUNT_PATH_INDEX, unharden
if False:
from typing import List
from typing import List, Optional
def variable_length_encode(number: int) -> bytes:
@ -35,3 +35,10 @@ def format_account_number(path: List[int]) -> str:
raise ValueError("Path is too short.")
return "#%d" % (unharden(path[ACCOUNT_PATH_INDEX]) + 1)
def format_optional_int(number: Optional[int]) -> str:
if number is None:
return "n/a"
return str(number)

View File

@ -25,7 +25,7 @@ from .address import (
pack_reward_address_bytes,
)
from .helpers import protocol_magics
from .helpers.utils import format_account_number, to_account_path
from .helpers.utils import format_account_number, format_optional_int, to_account_path
if False:
from typing import List, Optional
@ -35,6 +35,8 @@ if False:
CardanoTxCertificateType,
CardanoTxWithdrawalType,
CardanoPoolParametersType,
CardanoAssetGroupType,
CardanoTokenType,
)
from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType
@ -62,10 +64,22 @@ def format_coin_amount(amount: int) -> str:
return "%s %s" % (format_amount(amount, 6), "ADA")
async def confirm_sending(ctx: wire.Context, amount: int, to: str) -> None:
def is_printable_ascii_bytestring(bytestr: bytes) -> bool:
return all((32 < b < 127) for b in bytestr)
async def confirm_sending(
ctx: wire.Context,
ada_amount: int,
token_bundle: List[CardanoAssetGroupType],
to: str,
) -> None:
for token_group in token_bundle:
await confirm_sending_token_group(ctx, token_group)
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.normal("Confirm sending:")
page1.bold(format_coin_amount(amount))
page1.bold(format_coin_amount(ada_amount))
page1.normal("to")
to_lines = list(chunks(to, 17))
@ -76,6 +90,55 @@ async def confirm_sending(ctx: wire.Context, amount: int, to: str) -> None:
await require_confirm(ctx, Paginated(pages))
async def confirm_sending_token_group(
ctx: wire.Context, token_group: CardanoAssetGroupType
) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.bold("Policy id: ")
page1.mono(hexlify(token_group.policy_id).decode())
await require_confirm(ctx, page1)
for token_number, token in enumerate(token_group.tokens, 1):
if is_printable_ascii_bytestring(token.asset_name_bytes):
await confirm_sending_token_ascii(ctx, token, token_number)
else:
await confirm_sending_token_hex(ctx, token, token_number)
async def confirm_sending_token_ascii(
ctx: wire.Context, token: CardanoTokenType, token_number: int
) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.normal("Asset #%s name (ASCII):" % (token_number))
page1.bold(token.asset_name_bytes.decode("ascii"))
page1.normal("Amount sent:")
page1.bold(format_amount(token.amount, 0))
await require_confirm(ctx, page1)
async def confirm_sending_token_hex(
ctx: wire.Context, token: CardanoTokenType, token_number: int
) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.bold("Asset #%s name (hex):" % (token_number))
page1.mono(hexlify(token.asset_name_bytes).decode())
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page2.normal("Amount sent:")
page2.bold(format_amount(token.amount, 0))
await require_confirm(ctx, Paginated([page1, page2]))
async def show_warning_tx_output_contains_tokens(ctx: wire.Context) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.normal("The following")
page1.normal("transaction output")
page1.normal("contains tokens.")
page1.br_half()
page1.normal("Continue?")
await require_confirm(ctx, page1)
async def show_warning_tx_no_staking_info(
ctx: wire.Context, address_type: EnumTypeCardanoAddressType, amount: int
) -> None:
@ -156,7 +219,8 @@ async def confirm_transaction(
amount: int,
fee: int,
protocol_magic: int,
ttl: int,
ttl: Optional[int],
validity_interval_start: Optional[int],
has_metadata: bool,
is_network_id_verifiable: bool,
) -> None:
@ -173,8 +237,8 @@ async def confirm_transaction(
if is_network_id_verifiable:
page2.normal("Network:")
page2.bold(protocol_magics.to_ui_string(protocol_magic))
page2.normal("Transaction TTL:")
page2.bold(str(ttl))
page2.normal("Valid since: %s" % format_optional_int(validity_interval_start))
page2.normal("TTL: %s" % format_optional_int(ttl))
pages.append(page2)
if has_metadata:
@ -295,12 +359,14 @@ async def confirm_stake_pool_metadata(
await require_confirm(ctx, Paginated([page1, page2]))
async def confirm_transaction_network_ttl(ctx, protocol_magic: int, ttl: int) -> None:
async def confirm_transaction_network_ttl(
ctx, protocol_magic: int, ttl: Optional[int], validity_interval_start: Optional[int]
) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.normal("Network:")
page1.bold(protocol_magics.to_ui_string(protocol_magic))
page1.normal("Transaction TTL:")
page1.bold(str(ttl))
page1.normal("Valid since: %s" % format_optional_int(validity_interval_start))
page1.normal("TTL: %s" % format_optional_int(ttl))
await require_confirm(ctx, page1)

View File

@ -22,6 +22,7 @@ from .helpers import (
INVALID_METADATA,
INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE,
INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS,
INVALID_TOKEN_BUNDLE_OUTPUT,
INVALID_WITHDRAWAL,
LOVELACE_MAX_SUPPLY,
network_ids,
@ -49,6 +50,7 @@ from .layout import (
show_warning_tx_different_staking_account,
show_warning_tx_network_unverifiable,
show_warning_tx_no_staking_info,
show_warning_tx_output_contains_tokens,
show_warning_tx_pointer_address,
show_warning_tx_staking_key_hash,
)
@ -60,10 +62,17 @@ if False:
from trezor.messages.CardanoTxInputType import CardanoTxInputType
from trezor.messages.CardanoTxOutputType import CardanoTxOutputType
from trezor.messages.CardanoTxWithdrawalType import CardanoTxWithdrawalType
from typing import Dict, List, Tuple
from trezor.messages.CardanoAssetGroupType import CardanoAssetGroupType
from typing import Dict, List, Tuple, Union
CborizedTokenBundle = Dict[bytes, Dict[bytes, int]]
CborizedTxOutput = Tuple[bytes, Union[int, Tuple[int, CborizedTokenBundle]]]
METADATA_HASH_SIZE = 32
MINTING_POLICY_ID_LENGTH = 28
MAX_METADATA_LENGTH = 500
MAX_ASSET_NAME_LENGTH = 32
MAX_TX_OUTPUT_SIZE = 512
@seed.with_keychain
@ -197,10 +206,64 @@ def _validate_outputs(
"Each output must have an address field or address_parameters!"
)
_validate_token_bundle(output.token_bundle)
_validate_max_tx_output_size(keychain, output, protocol_magic, network_id)
if total_amount > LOVELACE_MAX_SUPPLY:
raise wire.ProcessError("Total transaction amount is out of range!")
def _validate_token_bundle(token_bundle: List[CardanoAssetGroupType]) -> None:
seen_policy_ids = set()
for token_group in token_bundle:
policy_id = bytes(token_group.policy_id)
if len(policy_id) != MINTING_POLICY_ID_LENGTH:
raise INVALID_TOKEN_BUNDLE_OUTPUT
if policy_id in seen_policy_ids:
raise INVALID_TOKEN_BUNDLE_OUTPUT
else:
seen_policy_ids.add(policy_id)
if not token_group.tokens:
raise INVALID_TOKEN_BUNDLE_OUTPUT
seen_asset_name_bytes = set()
for token in token_group.tokens:
asset_name_bytes = bytes(token.asset_name_bytes)
if len(asset_name_bytes) > MAX_ASSET_NAME_LENGTH:
raise INVALID_TOKEN_BUNDLE_OUTPUT
if asset_name_bytes in seen_asset_name_bytes:
raise INVALID_TOKEN_BUNDLE_OUTPUT
else:
seen_asset_name_bytes.add(asset_name_bytes)
def _validate_max_tx_output_size(
keychain: seed.Keychain,
output: CardanoTxOutputType,
protocol_magic: int,
network_id: int,
) -> None:
"""
This limitation is a mitigation measure to prevent sending
large (especially change) outputs containing many tokens that Trezor
would not be able to spend reliably given that
currently the full Cardano transaction is held in-memory.
Once Cardano-transaction signing is refactored to be streamed, this
limit can be lifted
"""
cborized_output = _cborize_output(keychain, output, protocol_magic, network_id)
serialized_output = cbor.encode(cborized_output)
if len(serialized_output) > MAX_TX_OUTPUT_SIZE:
raise wire.ProcessError(
"Maximum tx output size (%s bytes) exceeded!" % MAX_TX_OUTPUT_SIZE
)
def _ensure_no_signing_inputs(inputs: List[CardanoTxInputType]):
if any(i.address_n for i in inputs):
raise INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS
@ -271,9 +334,11 @@ def _cborize_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
0: inputs_for_cbor,
1: outputs_for_cbor,
2: msg.fee,
3: msg.ttl,
}
if msg.ttl:
tx_body[3] = msg.ttl
if msg.certificates:
certificates_for_cbor = _cborize_certificates(keychain, msg.certificates)
tx_body[4] = certificates_for_cbor
@ -289,6 +354,9 @@ def _cborize_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
if msg.metadata:
tx_body[7] = _hash_metadata(bytes(msg.metadata))
if msg.validity_interval_start:
tx_body[8] = msg.validity_interval_start
return tx_body
@ -301,19 +369,46 @@ def _cborize_outputs(
outputs: List[CardanoTxOutputType],
protocol_magic: int,
network_id: int,
) -> List[Tuple[bytes, int]]:
result = []
for output in outputs:
amount = output.amount
if output.address_parameters:
address = derive_address_bytes(
keychain, output.address_parameters, protocol_magic, network_id
)
else:
# output address is validated in _validate_outputs before this happens
address = get_address_bytes_unsafe(output.address)
) -> List[CborizedTxOutput]:
return [
_cborize_output(keychain, output, protocol_magic, network_id)
for output in outputs
]
result.append((address, amount))
def _cborize_output(
keychain: seed.Keychain,
output: CardanoTxOutputType,
protocol_magic: int,
network_id: int,
) -> CborizedTxOutput:
amount = output.amount
if output.address_parameters:
address = derive_address_bytes(
keychain, output.address_parameters, protocol_magic, network_id
)
else:
# output address is validated in _validate_outputs before this happens
address = get_address_bytes_unsafe(output.address)
if not output.token_bundle:
return (address, amount)
else:
return (address, (amount, _cborize_token_bundle(output.token_bundle)))
def _cborize_token_bundle(
token_bundle: List[CardanoAssetGroupType],
) -> CborizedTokenBundle:
result = {}
for token_group in token_bundle:
cborized_policy_id = bytes(token_group.policy_id)
result[cborized_policy_id] = cborized_token_group = {}
for token in token_group.tokens:
cborized_asset_name = bytes(token.asset_name_bytes)
cborized_token_group[cborized_asset_name] = token.amount
return result
@ -481,6 +576,7 @@ async def _show_standard_tx(
fee=msg.fee,
protocol_magic=msg.protocol_magic,
ttl=msg.ttl,
validity_interval_start=msg.validity_interval_start,
has_metadata=has_metadata,
is_network_id_verifiable=is_network_id_verifiable,
)
@ -500,7 +596,9 @@ async def _show_stake_pool_registration_tx(
ctx, keychain, pool_parameters.owners, msg.network_id
)
await confirm_stake_pool_metadata(ctx, pool_parameters.metadata)
await confirm_transaction_network_ttl(ctx, msg.protocol_magic, msg.ttl)
await confirm_transaction_network_ttl(
ctx, msg.protocol_magic, msg.ttl, msg.validity_interval_start
)
await confirm_stake_pool_registration_final(ctx)
@ -525,7 +623,10 @@ async def _show_outputs(
total_amount += output.amount
await confirm_sending(ctx, output.amount, address)
if len(output.token_bundle) > 0:
await show_warning_tx_output_contains_tokens(ctx)
await confirm_sending(ctx, output.amount, output.token_bundle, address)
return total_amount

View File

@ -0,0 +1,31 @@
# Automatically generated by pb2py
# fmt: off
import protobuf as p
from .CardanoTokenType import CardanoTokenType
if __debug__:
try:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
class CardanoAssetGroupType(p.MessageType):
def __init__(
self,
*,
policy_id: bytes,
tokens: List[CardanoTokenType] = None,
) -> None:
self.tokens = tokens if tokens is not None else []
self.policy_id = policy_id
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('policy_id', p.BytesType, p.FLAG_REQUIRED),
2: ('tokens', CardanoTokenType, p.FLAG_REPEATED),
}

View File

@ -30,6 +30,7 @@ class CardanoSignTx(p.MessageType):
ttl: int = None,
network_id: int = None,
metadata: bytes = None,
validity_interval_start: int = None,
) -> None:
self.inputs = inputs if inputs is not None else []
self.outputs = outputs if outputs is not None else []
@ -40,6 +41,7 @@ class CardanoSignTx(p.MessageType):
self.ttl = ttl
self.network_id = network_id
self.metadata = metadata
self.validity_interval_start = validity_interval_start
@classmethod
def get_fields(cls) -> Dict:
@ -53,4 +55,5 @@ class CardanoSignTx(p.MessageType):
9: ('certificates', CardanoTxCertificateType, p.FLAG_REPEATED),
10: ('withdrawals', CardanoTxWithdrawalType, p.FLAG_REPEATED),
11: ('metadata', p.BytesType, None),
12: ('validity_interval_start', p.UVarintType, None),
}

View File

@ -0,0 +1,29 @@
# Automatically generated by pb2py
# fmt: off
import protobuf as p
if __debug__:
try:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
class CardanoTokenType(p.MessageType):
def __init__(
self,
*,
asset_name_bytes: bytes,
amount: int,
) -> None:
self.asset_name_bytes = asset_name_bytes
self.amount = amount
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('asset_name_bytes', p.BytesType, p.FLAG_REQUIRED),
2: ('amount', p.UVarintType, p.FLAG_REQUIRED),
}

View File

@ -3,6 +3,7 @@
import protobuf as p
from .CardanoAddressParametersType import CardanoAddressParametersType
from .CardanoAssetGroupType import CardanoAssetGroupType
if __debug__:
try:
@ -17,10 +18,12 @@ class CardanoTxOutputType(p.MessageType):
def __init__(
self,
*,
token_bundle: List[CardanoAssetGroupType] = None,
address: str = None,
amount: int = None,
address_parameters: CardanoAddressParametersType = None,
) -> None:
self.token_bundle = token_bundle if token_bundle is not None else []
self.address = address
self.amount = amount
self.address_parameters = address_parameters
@ -31,4 +34,5 @@ class CardanoTxOutputType(p.MessageType):
1: ('address', p.UnicodeType, None),
3: ('amount', p.UVarintType, None),
4: ('address_parameters', CardanoAddressParametersType, None),
5: ('token_bundle', CardanoAssetGroupType, p.FLAG_REPEATED),
}

View File

@ -15,7 +15,7 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from ipaddress import ip_address
from typing import List
from typing import List, Optional
from . import messages, tools
from .tools import expect
@ -36,9 +36,13 @@ REQUIRED_FIELDS_POOL_PARAMETERS = (
"owners",
)
REQUIRED_FIELDS_WITHDRAWAL = ("path", "amount")
REQUIRED_FIELDS_TOKEN_GROUP = ("policy_id", "tokens")
REQUIRED_FIELDS_TOKEN = ("asset_name_bytes", "amount")
INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields"
INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY = "The output's token_bundle entry is invalid"
ADDRESS_TYPES = (
messages.CardanoAddressType.BYRON,
messages.CardanoAddressType.BASE,
@ -107,15 +111,61 @@ def create_output(output) -> messages.CardanoTxOutputType:
if not (contains_address or contains_address_type):
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
address = None
address_parameters = None
token_bundle = None
if contains_address:
return messages.CardanoTxOutputType(
address=output["address"], amount=int(output["amount"])
)
address = output["address"]
else:
return _create_change_output(output)
address_parameters = _create_change_output_address_parameters(output)
if "token_bundle" in output:
token_bundle = _create_token_bundle(output["token_bundle"])
return messages.CardanoTxOutputType(
address=address,
address_parameters=address_parameters,
amount=int(output["amount"]),
token_bundle=token_bundle,
)
def _create_change_output(output) -> messages.CardanoTxOutputType:
def _create_token_bundle(token_bundle) -> List[messages.CardanoAssetGroupType]:
result = []
for token_group in token_bundle:
if not all(k in token_group for k in REQUIRED_FIELDS_TOKEN_GROUP):
raise ValueError(INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY)
result.append(
messages.CardanoAssetGroupType(
policy_id=bytes.fromhex(token_group["policy_id"]),
tokens=_create_tokens(token_group["tokens"]),
)
)
return result
def _create_tokens(tokens) -> List[messages.CardanoTokenType]:
result = []
for token in tokens:
if not all(k in token for k in REQUIRED_FIELDS_TOKEN):
raise ValueError(INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY)
result.append(
messages.CardanoTokenType(
asset_name_bytes=bytes.fromhex(token["asset_name_bytes"]),
amount=int(token["amount"]),
)
)
return result
def _create_change_output_address_parameters(
output,
) -> messages.CardanoAddressParametersType:
if "path" not in output:
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
@ -123,7 +173,7 @@ def _create_change_output(output) -> messages.CardanoTxOutputType:
if "stakingKeyHash" in output:
staking_key_hash_bytes = bytes.fromhex(output.get("stakingKeyHash"))
address_parameters = create_address_parameters(
return create_address_parameters(
int(output["addressType"]),
tools.parse_path(output["path"]),
tools.parse_path(output.get("stakingPath")),
@ -133,10 +183,6 @@ def _create_change_output(output) -> messages.CardanoTxOutputType:
output.get("certificateIndex"),
)
return messages.CardanoTxOutputType(
address_parameters=address_parameters, amount=int(output["amount"])
)
def create_certificate(certificate) -> messages.CardanoTxCertificateType:
CERTIFICATE_MISSING_FIELDS_ERROR = ValueError(
@ -301,7 +347,8 @@ def sign_tx(
inputs: List[messages.CardanoTxInputType],
outputs: List[messages.CardanoTxOutputType],
fee: int,
ttl: int,
ttl: Optional[int],
validity_interval_start: Optional[int],
certificates: List[messages.CardanoTxCertificateType] = (),
withdrawals: List[messages.CardanoTxWithdrawalType] = (),
metadata: bytes = None,
@ -314,6 +361,7 @@ def sign_tx(
outputs=outputs,
fee=fee,
ttl=ttl,
validity_interval_start=validity_interval_start,
certificates=certificates,
withdrawals=withdrawals,
metadata=metadata,

View File

@ -57,7 +57,8 @@ def sign_tx(client, file, protocol_magic, network_id, testnet):
inputs = [cardano.create_input(input) for input in transaction["inputs"]]
outputs = [cardano.create_output(output) for output in transaction["outputs"]]
fee = transaction["fee"]
ttl = transaction["ttl"]
ttl = transaction.get("ttl")
validity_interval_start = transaction.get("validity_interval_start")
certificates = [
cardano.create_certificate(certificate)
for certificate in transaction.get("certificates", ())
@ -76,6 +77,7 @@ def sign_tx(client, file, protocol_magic, network_id, testnet):
outputs,
fee,
ttl,
validity_interval_start,
certificates,
withdrawals,
metadata,

View File

@ -0,0 +1,31 @@
# Automatically generated by pb2py
# fmt: off
from .. import protobuf as p
from .CardanoTokenType import CardanoTokenType
if __debug__:
try:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
class CardanoAssetGroupType(p.MessageType):
def __init__(
self,
*,
policy_id: bytes,
tokens: List[CardanoTokenType] = None,
) -> None:
self.tokens = tokens if tokens is not None else []
self.policy_id = policy_id
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('policy_id', p.BytesType, p.FLAG_REQUIRED),
2: ('tokens', CardanoTokenType, p.FLAG_REPEATED),
}

View File

@ -30,6 +30,7 @@ class CardanoSignTx(p.MessageType):
ttl: int = None,
network_id: int = None,
metadata: bytes = None,
validity_interval_start: int = None,
) -> None:
self.inputs = inputs if inputs is not None else []
self.outputs = outputs if outputs is not None else []
@ -40,6 +41,7 @@ class CardanoSignTx(p.MessageType):
self.ttl = ttl
self.network_id = network_id
self.metadata = metadata
self.validity_interval_start = validity_interval_start
@classmethod
def get_fields(cls) -> Dict:
@ -53,4 +55,5 @@ class CardanoSignTx(p.MessageType):
9: ('certificates', CardanoTxCertificateType, p.FLAG_REPEATED),
10: ('withdrawals', CardanoTxWithdrawalType, p.FLAG_REPEATED),
11: ('metadata', p.BytesType, None),
12: ('validity_interval_start', p.UVarintType, None),
}

View File

@ -0,0 +1,29 @@
# Automatically generated by pb2py
# fmt: off
from .. import protobuf as p
if __debug__:
try:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
class CardanoTokenType(p.MessageType):
def __init__(
self,
*,
asset_name_bytes: bytes,
amount: int,
) -> None:
self.asset_name_bytes = asset_name_bytes
self.amount = amount
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('asset_name_bytes', p.BytesType, p.FLAG_REQUIRED),
2: ('amount', p.UVarintType, p.FLAG_REQUIRED),
}

View File

@ -3,6 +3,7 @@
from .. import protobuf as p
from .CardanoAddressParametersType import CardanoAddressParametersType
from .CardanoAssetGroupType import CardanoAssetGroupType
if __debug__:
try:
@ -17,10 +18,12 @@ class CardanoTxOutputType(p.MessageType):
def __init__(
self,
*,
token_bundle: List[CardanoAssetGroupType] = None,
address: str = None,
amount: int = None,
address_parameters: CardanoAddressParametersType = None,
) -> None:
self.token_bundle = token_bundle if token_bundle is not None else []
self.address = address
self.amount = amount
self.address_parameters = address_parameters
@ -31,4 +34,5 @@ class CardanoTxOutputType(p.MessageType):
1: ('address', p.UnicodeType, None),
3: ('amount', p.UVarintType, None),
4: ('address_parameters', CardanoAddressParametersType, None),
5: ('token_bundle', CardanoAssetGroupType, p.FLAG_REPEATED),
}

View File

@ -24,6 +24,7 @@ from .Cancel import Cancel
from .CancelAuthorization import CancelAuthorization
from .CardanoAddress import CardanoAddress
from .CardanoAddressParametersType import CardanoAddressParametersType
from .CardanoAssetGroupType import CardanoAssetGroupType
from .CardanoBlockchainPointerType import CardanoBlockchainPointerType
from .CardanoGetAddress import CardanoGetAddress
from .CardanoGetPublicKey import CardanoGetPublicKey
@ -34,6 +35,7 @@ from .CardanoPoolRelayParametersType import CardanoPoolRelayParametersType
from .CardanoPublicKey import CardanoPublicKey
from .CardanoSignTx import CardanoSignTx
from .CardanoSignedTx import CardanoSignedTx
from .CardanoTokenType import CardanoTokenType
from .CardanoTxCertificateType import CardanoTxCertificateType
from .CardanoTxInputType import CardanoTxInputType
from .CardanoTxOutputType import CardanoTxOutputType

View File

@ -65,7 +65,8 @@ def test_cardano_sign_tx(client, parameters, result):
inputs=inputs,
outputs=outputs,
fee=parameters["fee"],
ttl=parameters["ttl"],
ttl=parameters.get("ttl"),
validity_interval_start=parameters.get("validity_interval_start"),
certificates=certificates,
withdrawals=withdrawals,
metadata=bytes.fromhex(parameters["metadata"]),
@ -96,7 +97,8 @@ def test_cardano_sign_tx_failed(client, parameters, result):
inputs=inputs,
outputs=outputs,
fee=parameters["fee"],
ttl=parameters["ttl"],
ttl=parameters.get("ttl"),
validity_interval_start=parameters.get("validity_interval_start"),
certificates=certificates,
withdrawals=withdrawals,
metadata=bytes.fromhex(parameters["metadata"]),

View File

@ -1,25 +1,28 @@
{
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_with_change0]": "ff1ad82caac745577d51f574da41ad0364b2453524ff2e138a927ea03463103d",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_with_change1]": "179ad9e910ba421b51a6492f2e63b5ab8be0c9513d6692cf9bb94dcc210d201c",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_with_multiple_inputs]": "fa870dea0bf144c1142d04b9ef7dad74d357cd7b730e232b19d1c8a28edcb132",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_without_change0]": "fa870dea0bf144c1142d04b9ef7dad74d357cd7b730e232b19d1c8a28edcb132",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_without_change1]": "699fb7c9c2537a25366a865e512d2c15b6134077bc7c89a6eb58344535fef91d",
"cardano-test_sign_tx.py::test_cardano_sign_tx[sample_stake_pool_registration_certificate]": "e680f96cbed1ec77a80b49e61e201be7de539d52443b587499bfae05467b58dd",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_base_address_change_o-0c37e6dc": "3f92f33e81342622a95308e4ce236368f65d099d7646c9a2b2480f1730fbd458",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_base_address_change_output]": "7158886e32f1a1e3cb5876036d0bd627b895de28cc6c6ebdb291aa36f51514b2",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_base_script_address_c-466ef44c": "5e81ca3f97f73ceb1498db47ff41e9fb9565ccca3087ea97159fa00b04926c37",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_enterprise_address_ch-15518a4c": "7f0a248889f336eab3042454c5e2a6d99da79beafcdba340538c98010a5ccee0",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_pointer_address_change_output]": "bbbdb41e894af11290bc8645d0b7cebc6b4488d6c2bba45e74147ad71e9cc13f",
"cardano-test_sign_tx.py::test_cardano_sign_tx[stake_pool_registration_certificate_with_no_p-0bbad967": "7e96c91d46c2b1564ce145b7a496fc825e2cd4dc3331839ac83049f1e253379b",
"cardano-test_sign_tx.py::test_cardano_sign_tx[stake_pool_registration_on_testnet]": "d00e78baff80f218b719ad4b0966a1ac7e5e9a3544cc34365ad0b942bc4a318d",
"cardano-test_sign_tx.py::test_cardano_sign_tx[testnet_transaction0]": "994900bb0b0d52d0872dae1254fc8d5ed337e51be0e2ddd85b9d613bc9b11687",
"cardano-test_sign_tx.py::test_cardano_sign_tx[testnet_transaction1]": "120eb04eae3bbcd492e4e0ab19e25bd2e718e4fa9620d5a0496eaee5704f1951",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_metadata]": "e0707009a202512da0e38ec536d24d0522f12453ca0bcf91ff074e7653b7958f",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_deregistration]": "3d2ae35075551db91aa24dedbc388a37f58f0b618e073a73aed6a094a352b306",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_deregistration_and_withdrawal]": "60f510ca75fc1288c92b50ac38ce2d413b608cc008a5ac519772936a98e60253",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_registration_and_stake-3fdfc583": "604b63443e2d554fd3173e748439c29a968597b4db82ad98f56176cda225128f",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_registration_certifica-e7bd462a": "ab4eb02c8769a90c0c5a643826d6affbab9cda1a55b5bfa1cc1066ac97a86368",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_registration_certificate]": "c804dd78f120d14ef2585f04a148df23ab6da539d35b2b20b5bfc6092a4a0da7",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_with_change0]": "5e2334cff9d0946ec722d69d7065023d707d8208a0ceee928824fd006edb93ad",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_with_change1]": "1ac78675dd64f4acbf7cf8f24bc1492bec5f0bce32a3f860742d5fa1922afb24",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_with_multiple_inputs]": "8151b1b3cfa5ef79a256409d78b0219eb43b8fb498776b16e2f43bb5c9a38c55",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_without_change0]": "8151b1b3cfa5ef79a256409d78b0219eb43b8fb498776b16e2f43bb5c9a38c55",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mainnet_transaction_without_change1]": "66a8f9e4a2590c304046c8802673fb373d3fa9f3baa03dbc361818dd56fb1eab",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mary_era_transaction_with_different_policies_-1dbb1bfb": "accbbaba30610e45d9cbd74a3a6693cff7b65811dbd7ec0c8db8a6adc014d9d5",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mary_era_transaction_with_multiasset_output]": "cf3dd28eb435afc09eace0f2b02e7bae18978d0f47a6a43f27040cb6b6f5460e",
"cardano-test_sign_tx.py::test_cardano_sign_tx[mary_era_transaction_with_no_ttl-validity_start]": "3954d5a5374321ba8328a86e5302878d231c9ec8f1bb70de257a5f24ffdc2dd6",
"cardano-test_sign_tx.py::test_cardano_sign_tx[sample_stake_pool_registration_certificate]": "8e8b165ad23d69e4d5f52c5a6cb1f11b27829d54d5e44dd401b13875667e6287",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_base_address_change_o-0c37e6dc": "10cb4b3cbb74b3d44e430f3be1affd489ca6dfde179f1916d0abb82fbfff8764",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_base_address_change_output]": "ce02b6982e756401f8dd006e64c86825a4254dc57e6c2077f7c62848c9a3d178",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_base_script_address_c-466ef44c": "4a46dd8ac42295d853646a55ca1b85023b2235af6155be663b1de10a6c98def2",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_enterprise_address_ch-15518a4c": "079c395ab5dfa10d9bc6364e4201c128cd02ade9fad730390720784360b17d79",
"cardano-test_sign_tx.py::test_cardano_sign_tx[simple_transaction_with_pointer_address_change_output]": "d5d251a3a597abc1df5480651dfbf15d59f7b207bf351131906862b0aaa07a51",
"cardano-test_sign_tx.py::test_cardano_sign_tx[stake_pool_registration_certificate_with_no_p-0bbad967": "28a4cc241374f3125d60d33a87c0456ffe9509817d1849d8bfc0e378d6c48a07",
"cardano-test_sign_tx.py::test_cardano_sign_tx[stake_pool_registration_on_testnet]": "d8303a92b2d5a67d81543eea2fb822ac2ab994ffa07c7d6d23db24a38f626dab",
"cardano-test_sign_tx.py::test_cardano_sign_tx[testnet_transaction0]": "0f2330f22f60905681622bec047bfd67d1aeb3a30c7523d20dee62b59dd86dec",
"cardano-test_sign_tx.py::test_cardano_sign_tx[testnet_transaction1]": "af40cd3886590c39a36c503c1dddfff60ea3ec8a0948e45c109b45c83056d72a",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_metadata]": "d364e0bc601fc16088bd7717822b554f3d4bd234e1ed779cc5bf9acdb316bf26",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_deregistration]": "a22ad00e9e58a64eca1f295191fa36937beb09b41620e64942498788e448e3a3",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_deregistration_and_withdrawal]": "b89e4b24a93197752607d62fa88f918590ff94a2c40007ead6773a3d1bda7285",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_registration_and_stake-3fdfc583": "a7a3f01b6972aeab505e9df70c83111ef03102b62d5260164ed36a0140d7d9dc",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_registration_certifica-e7bd462a": "9912dd5d7fe33c3f4c48e813c39d717947d9c96a15f4349ed153548314c4cf80",
"cardano-test_sign_tx.py::test_cardano_sign_tx[transaction_with_stake_registration_certificate]": "45c35b504a486f709af64c5827231446269b3396335fd014da8c3015a1d799cc",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[all_tx_inputs_must_be_external_(without_path)]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[certificate_has_invalid_pool_size]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[certificate_has_non_staking_path]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
@ -43,10 +46,13 @@
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[output_total_is_too_high]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[pool_reward_address_belongs_to_differe-e79b6855": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[pool_reward_address_is_a_base_address]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[repeated_asset_name_in_multiasset_token_group]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[repeated_policyid_in_multiasset_output]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[shelley_mainnet_transaction_with_testn-af110e3e": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[shelley_testnet_transaction_with_mainn-ba78ab8f": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[testnet_protocol_magic_with_mainnet_network_id]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[testnet_transaction_with_mainnet_output]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[too_many_tokens_in_output]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[two_owners_with_path]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[unsupported_address_type]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_amount_is_too_large]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",