Skip to content

fix(bolt12): Make CurrencyCode a validated wrapper type #3814

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

erickcestari
Copy link

@erickcestari erickcestari commented May 30, 2025

After performing differential fuzzing using bitcoinfuzz with rust-lightning and Core Lightning (CLN), I noticed that rust-lightning does not verify whether the offer_currency field contains valid UTF-8.

This commit convert CurrencyCode from type alias to struct with validation, ensuring
ISO 4217 compliance (3 ASCII uppercase letters) at construction time.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented May 30, 2025

👋 Thanks for assigning @jkczyz as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Copy link

codecov bot commented May 30, 2025

Codecov Report

Attention: Patch coverage is 73.33333% with 20 lines in your changes missing coverage. Please review.

Project coverage is 89.87%. Comparing base (78fee88) to head (71562a9).
Report is 88 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/offers/offer.rs 68.25% 20 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3814      +/-   ##
==========================================
+ Coverage   89.52%   89.87%   +0.34%     
==========================================
  Files         157      160       +3     
  Lines      125100   129205    +4105     
  Branches   125100   129205    +4105     
==========================================
+ Hits       112002   116123    +4121     
+ Misses      10408    10389      -19     
- Partials     2690     2693       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@erickcestari erickcestari force-pushed the validate-currency-code branch from 7232027 to 748747b Compare May 30, 2025 16:48
@erickcestari
Copy link
Author

erickcestari commented May 30, 2025

I took a closer look, and it seems this test vector has an invalid currency length in UTF-8 encoding, rather than being an invalid UTF-8 string itself.

  {
    "description": "Malformed: invalid currency UTF-8",
    "valid": false,
    "bolt12": "lno1qcpgqsg2q4q5cj2rg5tzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg"
  },

data part: 060280410a05414c4943451621020202020202020202020202020202020202020202020202020202020202020202

Looking at the data part, we observe 06 (currency type), 02 (length), and 8041 (value), which confirms that the error arises from an invalid currency length in UTF-8 encoding, not from an invalid UTF-8 sequence.

I'll open a PR on Bolts to fix it.

(edited)

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Aren't the currencies supposed to be the three-char currency specification standard? ie they should be ASCII only (and probably all-caps letter only, never numbers)?

Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

Thanks for jumping on this!

@jkczyz
Copy link
Contributor

jkczyz commented May 30, 2025

I took a closer look, and it seems this test vector has an invalid currency length in UTF-8 encoding, rather than being an invalid UTF-8 string itself.

  {
    "description": "Malformed: invalid currency UTF-8",
    "valid": false,
    "bolt12": "lno1qcpgqsg2q4q5cj2rg5tzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg"
  },

data part: 060280410a05414c4943451621020202020202020202020202020202020202020202020202020202020202020202

Looking at the data part, we observe 06 (currency type), 02 (length), and 2804 (value), which confirms that the error arises from an invalid currency length in UTF-8 encoding, not from an invalid UTF-8 sequence.

I'll open a PR on Bolts to fix it.

Ah, yeah either the test vector or the comment is wrong lol

@erickcestari
Copy link
Author

Aren't the currencies supposed to be the three-char currency specification standard? ie they should be ASCII only (and probably all-caps letter only, never numbers)?

Initially, I considered just aligning it with the UTF-8 type for consistency. But yes, it makes more sense to enforce stricter validation to ensure it's valid ASCII and adheres to the expected format.

@erickcestari erickcestari force-pushed the validate-currency-code branch from 748747b to 91e633a Compare May 31, 2025 15:05
@erickcestari erickcestari changed the title fix(bolt12): Add UTF-8 validation for offer currency field fix(bolt12): Add ASCII validation for offer currency field May 31, 2025
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt @jkczyz! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnull
Copy link
Contributor

tnull commented Jun 2, 2025

Ah, funny, seems this fixes #3813 we just opened. Will link the issue.

@thomash-acinq
Copy link

data part: 060280410a05414c4943451621020202020202020202020202020202020202020202020202020202020202020202

Looking at the data part, we observe 06 (currency type), 02 (length), and 2804 (value), which confirms that the error arises from an invalid currency length in UTF-8 encoding, not from an invalid UTF-8 sequence.

No, it's 06 (currency type), 02 (length), and 8041 (value). It seems that you've copied the '2' to read '2804'.

@erickcestari
Copy link
Author

data part: 060280410a05414c4943451621020202020202020202020202020202020202020202020202020202020202020202
Looking at the data part, we observe 06 (currency type), 02 (length), and 2804 (value), which confirms that the error arises from an invalid currency length in UTF-8 encoding, not from an invalid UTF-8 sequence.

No, it's 06 (currency type), 02 (length), and 8041 (value). It seems that you've copied the '2' to read '2804'.

Yes, My mistake on the transcription.

It's invalid UTF-8, but the decoder fails due to the length mismatch (expecting 3 bytes for currency) rather than the UTF-8 validation itself.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Rather than checking this when deserializing an offer, can we change CurrencyCode to be a real wrapper so that we can enforce it on creation/deserialization of that struct? Currently I believe we will let users build offers that we will refuse to parse.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @jkczyz! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
Copy link
Contributor

jkczyz commented Jun 2, 2025

Rather than checking this when deserializing an offer, can we change CurrencyCode to be a real wrapper so that we can enforce it on creation/deserialization of that struct? Currently I believe we will let users build offers that we will refuse to parse.

They would be helpful as upcoming OfferMessageFlow changes to support currencies could benefit from a dedicated type rather than a variant in an enum (cc: @shaavan).

@erickcestari erickcestari force-pushed the validate-currency-code branch 2 times, most recently from d9b2684 to 1ebbcd0 Compare June 3, 2025 12:38
@erickcestari erickcestari changed the title fix(bolt12): Add ASCII validation for offer currency field fix(bolt12): Make CurrencyCode a validated wrapper type Jun 3, 2025
Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

Will defer to @TheBlueMatt on some of these comments,

/// Creates a new CurrencyCode from a 3-byte array.
///
/// Returns an error if the bytes are not valid UTF-8 or not all ASCII uppercase.
pub fn new(code: [u8; 3]) -> Result<Self, Bolt12SemanticError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use the unit type () for the error given and map at the call site given it can't be any other variant than InvalidCurrencyCode?


/// Returns the currency code as a string slice.
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(&self.0) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if we should just use from_utf8().expect().

pub struct CurrencyCode([u8; 3]);

impl CurrencyCode {
/// Creates a new CurrencyCode from a 3-byte array.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/CurrencyCode/`CurrencyCode`

Likewise in other docs.

Convert CurrencyCode from type alias to struct with validation, ensuring
ISO 4217 compliance (3 ASCII uppercase letters) at construction time.
@erickcestari erickcestari force-pushed the validate-currency-code branch from 1ebbcd0 to 71562a9 Compare June 3, 2025 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Validate ISO 4217 compliance in BOLT12 CurrencyCode
6 participants