Skip to content

std::time::SystemTime has nanosecond precision but Payjoin only sees u32 UnixTime second precision #893

Closed

Description

@DanGould

In roundtrip testing a new concrete v2::PjParams struct I realized SystemTime includes nanosecond precision. After serializing SystemTime into the EX1 u32 expiration parameter, and deserializing back into the type that precision is lost and an assert_eq failed, raising this issue, since we are storing time as SystemTime in many parts of the payjoin crate.

It seems to me that encoding this information internally as precise as the URL-serialized representation would be more accurate. For this purpose we may even use bitcoin::absolute::Time. Other than accuracy, changing to a u32 UnixTime abstraction gives us the advantage that u32 would work in wasm where SystemTime is not available.

I think this may be a good first issue to start, but it might be tricky to get the ergonomics right to completely close this issue. We'd need to figure out what type (u64, u32, or bitcoin::absolute::Time?) to expose in the API arguments and across the FFI boundary even if the internal representation becomes absolute::Time. We may take a look at how conversion ergonomics from SystemTime::now() to bitcoin::absolute::Time (or similar functionality) are designed as inspiration.

Activity

  1. added this to the payjoin-1.0 milestone on Jul 22, 2025
  2. added
    good first issueDoes NOT mean "an LLM can do this." Please discuss the constraints and intended approach.
    on Jul 22, 2025
  3. added theissue type on Jul 22, 2025
  4. Mshehu5 commented on Jul 22, 2025

    @Mshehu5
    Contributor

    Can I be assigned this issue

  5. DanGould commented on Jul 28, 2025

    @DanGould
    MemberAuthor

    From @Mshehu5, somewhere else:

    From what I understand, the task involves replacing the use of SystemTime in the codebase with bitcoin::absolute::Time, correct?

    If so, could you please clarify which specific parts of the codebase should be updated?

    Specifically the issue is replacing the use of SystemTime in the functional state machine of the payjoin crate. The structured data (structs, enums, errors, etc.) relying on std::time::SystemTime need to rely on u64, u32, or bitcoin::absolute::Time instead, since only the latter encodes the second (rather than nanosecond) precision with which the protocol is concerned. I suggest studying the difference between the types, making a suggestion, and writing up rationale for whatever decision you find most appropriate or otherwise asking specific questions about the differences between them. bitcoin::absolute::Time came up because it's already a dependency and makes it explicit we're talking about a concrete unix time as a seconds-precision unsigned integer under the hood, but you could argue for u64 or u32 as well.

    Working around SystemTime::now() calls is outside of the scope of this issue. That call does have side-effects that should be eventually worked around by making the state machine methods that currently call SystemTime::now() take now in seconds as input (in the type you choose) instead. This can be done as a follow up or separate concern.

  6. Mshehu5 commented on Aug 8, 2025

    @Mshehu5
    Contributor

    Thank you for clarifying, and sorry for the late reply.

    This is just based on my research, feel free to correct me

    After analyzing the SystemTime precision issue and comparing different time representations, bitcoin::absolute::Time seems like the optimal choice for Payjoin.

    Comparison Analysis

    bitcoin::absolute::Time (Recommended)

    • Wraps a u32 Unix timestamp, matching Payjoin’s precision exactly
    • Maintained by the rust-bitcoin team with extensive testing
    • built-in error handling and conversion methods
    • WASM-compatible and already part of the dependency tree
    • Improves code clarity with clear semantic meaning
    • Eliminates roundtrip precision loss

    u32 Unix Timestamp

    • Matches the payjoin format but lacks type safety
    • Requires manual error handling and conversion logic
    • Prone to misuse (e.g., arithmetic errors, unit confusion)
    • Increases maintenance burden for the team

    u64 Unix Timestamp

    • Overkill for the payjoin needs (valid until ~584 billion AD)
    • Consumes double the memory compared to u32
    • Still requires manual conversions and error handling
    • BIP 77 specifically uses u32, so conversion would still be necessary

    The core issue lies in SystemTime’s nanosecond precision, which leads to roundtrip test failures when serialized to u32.
    bitcoin::absolute::Time resolves this by maintaining second-level precision internally while offering robust time handling.

    This abstraction layer is an advantage, not a cost it prevents common time-related bugs and is maintained by domain experts.
    Since rust-bitcoin is already a dependency, this introduces no additional overhead while significantly improving type safety and developer experience.

    Let me know if you're okay with this direction, and I’ll go ahead and push the PR.

  7. DanGould commented on Aug 17, 2025

    @DanGould
    MemberAuthor

    I'm ok with this direction. thank you for thinking it through @Mshehu5. These notes and reasoning are easy to follow and make sense to me.

  8. arminsabouri commented on Sep 18, 2025

    @arminsabouri
    Contributor

    Closed by #1047

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

apigood first issueDoes NOT mean "an LLM can do this." Please discuss the constraints and intended approach.tech debt

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions