Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
73 changes: 69 additions & 4 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = ["Stellar Development Foundation <info@stellar.org>"]
license = "Apache-2.0"
version = "27.0.0"
edition = "2021"
rust-version = "1.84.0"
rust-version = "1.87.0"

[[bin]]
name = "stellar-xdr"
Expand All @@ -20,7 +20,7 @@ crate-git-revision = "0.0.6"

[dependencies]
cfg_eval = { version = "0.1.2", optional = true }
stellar-strkey = { version = "0.0.13", optional = true }
stellar-strkey = { version = "0.0.17", optional = true }
base64 = { version = "0.22.1", optional = true }
serde = { version = "1.0.139", features = ["derive"], optional = true }
serde_with = { version = "3.12.0", features = ["schemars_0_8"], optional = true }
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#![allow(clippy::tabs_in_doc_comments)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::doc_lazy_continuation)]
// Pedantic lints raised by the newer clippy that ships with the bumped MSRV,
// firing on the auto-generated code.
Comment thread
leighmcculloch marked this conversation as resolved.
Outdated
#![allow(clippy::unnecessary_semicolon)]
#![allow(clippy::non_std_lazy_statics)]
#![allow(unused_attributes)]
Comment on lines +9 to 11

//! Library and CLI containing types and functionality for working with Stellar
Expand Down
36 changes: 14 additions & 22 deletions src/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ impl core::str::FromStr for MuxedAccount {
ed25519: Uint256(ed25519),
id,
})),
stellar_strkey::Strkey::PrivateKeyEd25519(_)
| stellar_strkey::Strkey::PreAuthTx(_)
stellar_strkey::Strkey::PreAuthTx(_)
| stellar_strkey::Strkey::HashX(_)
| stellar_strkey::Strkey::SignedPayloadEd25519(_)
| stellar_strkey::Strkey::Contract(_)
Expand Down Expand Up @@ -204,10 +203,8 @@ impl core::fmt::Display for SignerKeyEd25519SignedPayload {
ed25519: Uint256(ed25519),
payload,
} = self;
let k = stellar_strkey::ed25519::SignedPayload {
ed25519: *ed25519,
payload: payload.into(),
};
let k = stellar_strkey::ed25519::SignedPayload::new(*ed25519, payload.as_ref())
.map_err(|_| core::fmt::Error)?;
Comment on lines -201 to +207

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a subtle meaningful change here that I'd like to give a heads up and a chance for commentary before this is merged.

The Display::fmt implementation is called by ToString::to_string. Display::fmt can return an error, but ToString::to_string doesn't and so panics if fmt returns an error.

Previously you could construct a SignerKeyEd25519SignedPayload with a zero length payload all the way up to a u32::MAX length payload and it would render a string. It would panic in payloads longer than u32::MAX. However, that string generated might be invalid, since a payload must be 1 to 64 bytes long in stellar-core, we implemented the rs-stellar-strkey parse impl to always require within those lengths. Attempts to then parse the key would error.

The latest rs-stellar-strkey rectified the asymmetry in its own type for two reasons:

  1. The asymmetry was identified as a potential security issue that an application could be impacted by if they used the type naively.
  2. To support no-std no-alloc types a low max length was convenient.

This has a side-effect that it's possible to construct a SignerKeyEd25519SignedPayload xdr type whose Display impl returns an error and its ToString impl panics. It was possible before if the payload was u32::MAX+1+, but now it is possible with 0 and 65+

I think this change is inevitable unless we remove the limits from rs-stellar-strkey, and walked back the no-std no-alloc support for that type.

I don't think this can effect stellar-core, but there is some XDR, albeit invalid, that cannot be represented in string and json forms.

cc @graydon @dmkozh @mootz12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why do we need to panic in the Display impl. Can't we just return something like <invalid>? While this may not necessarily affect env today, I can imagine code that logs the invalid payload if it fails some kind of validation - it would be weird for the logging logic to cause panic then.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will create the same problem that the auditors had with the strkey library, that the type will not roundtrip which can also be a failure point similar to the failure point we are trying to address here. Maybe it's more appropriate to use the Debug impl in that case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could look at introducing a validation into the XDR type so that it cannot be constructed with a payload outside the range. We do that today with max lengths on XDR var arrays, but not min lengths.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

, that the type will not roundtrip

Why is that an issue, if the input is invalid? IMO it doesn't make sense to require every input to be able to roundtrip. Moreover, this is about XDR types, not strkey types. There is no requirement for XDR types to roundtrip through Display, is there? It's not a serialization format.

I could look at introducing a validation into the XDR type so that it cannot be constructed with a payload outside the range.

That's an option, but I'm still not sold on the roundtrip argument TBH.

Comment on lines +206 to +207

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle empty signed-payloads without panicking

When a SignerKeyEd25519SignedPayload is decoded or constructed with an empty payload (the generated field is BytesM<64>, which has no non-empty invariant), this new call to stellar_strkey::ed25519::SignedPayload::new returns InvalidPayloadLength. Mapping that to fmt::Error makes Display fail, so callers using .to_string() panic and serde SerializeDisplay can no longer serialize a value that the XDR reader accepts. Either preserve the old encoding path for empty payloads or reject that value before it can be represented as this XDR type.

Useful? React with 👍 / 👎.

let s = k.to_string();
f.write_str(&s)?;
Ok(())
Expand All @@ -217,11 +214,10 @@ impl core::fmt::Display for SignerKeyEd25519SignedPayload {
impl core::str::FromStr for SignerKeyEd25519SignedPayload {
type Err = Error;
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
let stellar_strkey::ed25519::SignedPayload { ed25519, payload } =
stellar_strkey::ed25519::SignedPayload::from_str(s)?;
let sp = stellar_strkey::ed25519::SignedPayload::from_str(s)?;
Ok(SignerKeyEd25519SignedPayload {
ed25519: Uint256(ed25519),
payload: payload.try_into()?,
ed25519: Uint256(*sp.ed25519()),
payload: sp.payload().try_into()?,
})
}
}
Expand All @@ -240,16 +236,13 @@ impl core::str::FromStr for SignerKey {
stellar_strkey::Strkey::HashX(stellar_strkey::HashX(h)) => {
Ok(SignerKey::HashX(Uint256(h)))
}
stellar_strkey::Strkey::SignedPayloadEd25519(
stellar_strkey::ed25519::SignedPayload { ed25519, payload },
) => Ok(SignerKey::Ed25519SignedPayload(
SignerKeyEd25519SignedPayload {
ed25519: Uint256(ed25519),
payload: payload.try_into()?,
},
)),
stellar_strkey::Strkey::PrivateKeyEd25519(_)
| stellar_strkey::Strkey::Contract(_)
stellar_strkey::Strkey::SignedPayloadEd25519(sp) => Ok(
SignerKey::Ed25519SignedPayload(SignerKeyEd25519SignedPayload {
ed25519: Uint256(*sp.ed25519()),
payload: sp.payload().try_into()?,
}),
),
stellar_strkey::Strkey::Contract(_)
| stellar_strkey::Strkey::MuxedAccountEd25519(_)
| stellar_strkey::Strkey::LiquidityPool(_)
| stellar_strkey::Strkey::ClaimableBalance(_) => Err(Error::Invalid),
Expand Down Expand Up @@ -332,8 +325,7 @@ impl core::str::FromStr for ScAddress {
)) => Ok(ScAddress::ClaimableBalance(
ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash(claimable_balance)),
)),
stellar_strkey::Strkey::PrivateKeyEd25519(_)
| stellar_strkey::Strkey::PreAuthTx(_)
stellar_strkey::Strkey::PreAuthTx(_)
| stellar_strkey::Strkey::HashX(_)
| stellar_strkey::Strkey::SignedPayloadEd25519(_) => Err(Error::Invalid),
}
Expand Down
Loading