Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dd6bb04
chore: optimize release profile for smaller binary size
overcat Mar 24, 2026
2224e52
refactor: replace chrono with manual timestamp formatting
overcat Mar 24, 2026
1a7bd88
refactor: replace serde/serde_json with lightweight ToJson trait
overcat Mar 24, 2026
63eb5d5
Merge branch 'develop' into next-release
overcat Jul 7, 2026
edbe60f
chore: fix clippy manual_is_multiple_of lint in is_leap_year
overcat Jul 7, 2026
7df210d
chore: apply rustfmt
overcat Jul 7, 2026
8acc38d
fix: display each source-account authorization tree from its root
overcat Jul 7, 2026
30ba717
refactor: unify authorization display under a single "Authorization" …
overcat Jul 7, 2026
06b78b6
[auto-update] Update Ragger snapshots
github-actions[bot] Jul 7, 2026
917e6a7
fix: avoid preallocating VecM capacity from untrusted length prefix
overcat Jul 7, 2026
9112b5e
chore: use correct ScvVec discriminant in depth limit test
overcat Jul 7, 2026
8789d4d
fix: cap sign_message length and hash without copying the message
overcat Jul 7, 2026
c10b484
fix: reject continuation chunks without a first chunk and reset conte…
overcat Jul 7, 2026
d7943a0
refactor: replace the app's only f64 with integer basis-point formatting
overcat Jul 7, 2026
9995bd8
fix: cap transaction operation count at the protocol limit
overcat Jul 7, 2026
c3ffbde
refactor: collapse generic amount formatting to a single i128 instant…
overcat Jul 7, 2026
d7f8b44
test: add leap-rule and year-9999 boundary cases for timestamp format…
overcat Jul 7, 2026
4bf6569
refactor: replace .expect() on PublicKey::from_payload with KeyDerive…
overcat Jul 7, 2026
382d1b9
fix: signal printable-amount failure with an empty string per app-exc…
overcat Jul 7, 2026
d91609c
fix: reject unvalidated trailing bytes in swap transaction validation
overcat Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
34 changes: 11 additions & 23 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ swap-utils = { path = "./swap-utils" }
[profile.release]
opt-level = 'z'
lto = true
codegen-units = 1
panic = 'abort'

[features]
default = ["ledger_device_sdk/nano_nbgl"]
Expand Down
210 changes: 210 additions & 0 deletions dataset/dataset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2318,6 +2318,216 @@ def op_invoke_host_function_with_auth_and_no_args() -> TransactionEnvelope:
)
return tx

@staticmethod
def op_invoke_host_function_with_auth_root_differs_from_host_function() -> (
TransactionEnvelope
):
# The root invocation of a source-account authorization entry is independent
# of the invoked host function, so it must be displayed from its root.
tx = (
common_builder(base_fee=500)
.append_invoke_contract_function_op(
contract_id="CA3B55CUVQCP4C4WXGYG5I2ED7AYE6AFNJB25SFXXVWGEVP3LUVTN7ND",
function_name="testfunc",
parameters=None,
source=kp0.public_key,
auth=[
stellar_xdr.SorobanAuthorizationEntry(
stellar_xdr.SorobanCredentials(
stellar_xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_SOURCE_ACCOUNT
),
stellar_xdr.SorobanAuthorizedInvocation(
function=stellar_xdr.SorobanAuthorizedFunction(
stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN,
contract_fn=stellar_xdr.InvokeContractArgs(
contract_address=Address(
"CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"
).to_xdr_sc_address(),
function_name=stellar_xdr.SCSymbol(
"unrelated_fn".encode()
),
args=[
scval.to_address(
"GCWNBLOHPARYAAF5W25NELURTERYS732Q7RRBTXRKBPGYCYLOFKCLKKA"
),
scval.to_int128(999 * 10**5),
],
),
),
sub_invocations=[
stellar_xdr.SorobanAuthorizedInvocation(
function=stellar_xdr.SorobanAuthorizedFunction(
stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN,
contract_fn=stellar_xdr.InvokeContractArgs(
contract_address=Address(
"CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75"
).to_xdr_sc_address(),
function_name=stellar_xdr.SCSymbol(
"transfer".encode()
),
args=[
scval.to_address(
"GCWNBLOHPARYAAF5W25NELURTERYS732Q7RRBTXRKBPGYCYLOFKCLKKA"
), # from
scval.to_address(
"GB42LIJ3V5KXCY32EFL4NL73OSI5PRCFJ3WNFMFX4QHGOAR7BFX2YC34"
), # to
scval.to_int128(399 * 10**5),
],
),
),
sub_invocations=[],
),
],
),
),
],
)
.add_time_bounds(0, 0)
.build()
)
return tx

@staticmethod
def op_invoke_host_function_with_multiple_source_account_auth() -> (
TransactionEnvelope
):
# A single operation can carry several authorization entries with
# source-account credentials; each tree must be displayed separately.
tx = (
common_builder(base_fee=500)
.append_invoke_contract_function_op(
contract_id="CA3B55CUVQCP4C4WXGYG5I2ED7AYE6AFNJB25SFXXVWGEVP3LUVTN7ND",
function_name="testfunc",
parameters=None,
source=kp0.public_key,
auth=[
stellar_xdr.SorobanAuthorizationEntry(
stellar_xdr.SorobanCredentials(
stellar_xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_SOURCE_ACCOUNT
),
stellar_xdr.SorobanAuthorizedInvocation(
function=stellar_xdr.SorobanAuthorizedFunction(
stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN,
contract_fn=stellar_xdr.InvokeContractArgs(
contract_address=Address(
"CA3B55CUVQCP4C4WXGYG5I2ED7AYE6AFNJB25SFXXVWGEVP3LUVTN7ND"
).to_xdr_sc_address(),
function_name=stellar_xdr.SCSymbol(
"testfunc".encode()
),
args=[],
),
),
sub_invocations=[
stellar_xdr.SorobanAuthorizedInvocation(
function=stellar_xdr.SorobanAuthorizedFunction(
stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN,
contract_fn=stellar_xdr.InvokeContractArgs(
contract_address=Address(
"CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75"
).to_xdr_sc_address(),
function_name=stellar_xdr.SCSymbol(
"test".encode()
),
args=[],
),
),
sub_invocations=[],
),
],
),
),
# ADDRESS credentials are not authorized by the transaction
# signature and must be skipped in the display.
stellar_xdr.SorobanAuthorizationEntry(
stellar_xdr.SorobanCredentials(
stellar_xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_ADDRESS,
stellar_xdr.SorobanAddressCredentials(
Address(
"GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"
).to_xdr_sc_address(),
stellar_xdr.Int64(111324345),
stellar_xdr.Uint32(34543543),
scval.to_void(),
),
),
stellar_xdr.SorobanAuthorizedInvocation(
function=stellar_xdr.SorobanAuthorizedFunction(
stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN,
contract_fn=stellar_xdr.InvokeContractArgs(
contract_address=Address(
"CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75"
).to_xdr_sc_address(),
function_name=stellar_xdr.SCSymbol(
"transfer".encode()
),
args=[
scval.to_address(
"GCWNBLOHPARYAAF5W25NELURTERYS732Q7RRBTXRKBPGYCYLOFKCLKKA"
), # from
scval.to_address(
"GB42LIJ3V5KXCY32EFL4NL73OSI5PRCFJ3WNFMFX4QHGOAR7BFX2YC34"
), # to
scval.to_int128(399 * 10**5),
],
),
),
sub_invocations=[],
),
),
stellar_xdr.SorobanAuthorizationEntry(
stellar_xdr.SorobanCredentials(
stellar_xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_SOURCE_ACCOUNT
),
stellar_xdr.SorobanAuthorizedInvocation(
function=stellar_xdr.SorobanAuthorizedFunction(
stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN,
contract_fn=stellar_xdr.InvokeContractArgs(
contract_address=Address(
"CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA"
).to_xdr_sc_address(),
function_name=stellar_xdr.SCSymbol(
"approve".encode()
),
args=[
scval.to_address(
"GCWNBLOHPARYAAF5W25NELURTERYS732Q7RRBTXRKBPGYCYLOFKCLKKA"
), # from
scval.to_address(
"GB42LIJ3V5KXCY32EFL4NL73OSI5PRCFJ3WNFMFX4QHGOAR7BFX2YC34"
), # to
scval.to_int128(123 * 10**5), # amount
scval.to_uint32(234524532),
],
),
),
sub_invocations=[
stellar_xdr.SorobanAuthorizedInvocation(
function=stellar_xdr.SorobanAuthorizedFunction(
stellar_xdr.SorobanAuthorizedFunctionType.SOROBAN_AUTHORIZED_FUNCTION_TYPE_CONTRACT_FN,
contract_fn=stellar_xdr.InvokeContractArgs(
contract_address=Address(
"CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"
).to_xdr_sc_address(),
function_name=stellar_xdr.SCSymbol(
"burn".encode()
),
args=[],
),
),
sub_invocations=[],
),
],
),
),
],
)
.add_time_bounds(0, 0)
.build()
)
return tx

@staticmethod
def op_invoke_host_function_without_auth_and_no_source() -> TransactionEnvelope:
tx = (
Expand Down
8 changes: 8 additions & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release Notes

## Unreleased

### Updated

- Each Soroban authorization entry with source-account credentials is now displayed in full from its root invocation; previously the root was assumed to match the invoked host function and was never shown, hiding the actual authorized call when they differ.
- Authorization entries are now labeled `Authorization` with a hierarchical index (e.g. `1`, `1-1`, `1-2-1`), replacing the `Nested Authorization` label.
- The `Nested Authorization` setting has been renamed to `Authorization Details`; when disabled, authorization trees in transactions are not displayed at all.

## v6.0.2

### Updated
Expand Down
2 changes: 1 addition & 1 deletion src/app_ui/get_public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use ledger_device_sdk::nbgl::NbglAddressReview;

pub fn ui_get_public_key(public_key: &[u8]) -> Result<bool, AppSW> {
let address = stellar_strkey::ed25519::PublicKey::from_payload(public_key)
.expect("caller guarantees 32-byte Stellar public key")
.map_err(|_| AppSW::KeyDeriveFail)?
.to_string();

// Display the address confirmation screen.
Expand Down
2 changes: 1 addition & 1 deletion src/app_ui/sign_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use stellarlib::{
pub fn ui_sign_tx(raw_data: &[u8], signer: &[u8]) -> Result<bool, AppSW> {
let config = Settings.to_format_config();
let signer = stellar_strkey::ed25519::PublicKey::from_payload(signer)
.expect("caller guarantees 32-byte Stellar public key")
.map_err(|_| AppSW::KeyDeriveFail)?
.to_string();
let mut parser = Parser::new(raw_data);
let tx_signature_payload =
Expand Down
15 changes: 14 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ pub struct AppContext<const MAX: usize> {
pub path: Bip32Path,
pub raw_data: RawDataBuffer<MAX>,
pub review_finished: bool,
/// Whether a first chunk has been received and a multi-chunk transfer is
/// in progress. Continuation chunks are rejected while this is false.
chunk_started: bool,
}

impl<const MAX: usize> AppContext<MAX> {
Expand All @@ -122,6 +125,7 @@ impl<const MAX: usize> AppContext<MAX> {
self.raw_data.clear();
self.path = Default::default();
self.review_finished = false;
self.chunk_started = false;
}

/// Handles multi-chunk data reception for handlers that accumulate data across multiple APDUs.
Expand All @@ -137,7 +141,8 @@ impl<const MAX: usize> AppContext<MAX> {
///
/// # Returns
/// * `Ok(())` if the chunk was successfully processed
/// * `Err(AppSW)` if an error occurred (wrong length, data too large, etc.)
/// * `Err(AppSW)` if an error occurred (wrong length, data too large,
/// continuation chunk without a first chunk, etc.)
pub fn handle_chunk(&mut self, comm: &mut Comm, first: bool) -> Result<(), AppSW> {
let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?;
if first {
Expand All @@ -154,7 +159,15 @@ impl<const MAX: usize> AppContext<MAX> {

// extend_from_slice now handles the size check internally
self.raw_data.extend_from_slice(remaining_data)?;

self.chunk_started = true;
} else {
// A continuation without a preceding first chunk would otherwise
// proceed with the default path and whatever the buffer holds.
if !self.chunk_started {
return Err(AppSW::DataParsingFail);
}

// extend_from_slice now handles the size check internally
self.raw_data.extend_from_slice(data)?;
}
Expand Down
Loading
Loading