Skip to content

Tracking: update to bitcoin_uri-0.2.0 #1867

Description

@DanGould

Every wallet that integrated payjoin runs two parsers over one URI string: its own BIP 21 parser for display and routing, and payjoin-ffi's Uri for the protocol. The bugs that produces are the same everywhere. + decoded as a space, endpoints dropped on reparse, pj= built by string concatenation, and none of them can parse a body-less URI, which is what a silent payment URI is.

The fix is one parser that wallets reach through the bindings they already load. Almost nobody consumes this from Rust. The Rust parser is scoped in payjoin/bitcoin_uri#12; this issue tracks the part that reaches wallets, which is payjoin-ffi's Uri surface.

Order of work

  1. bitcoin_uri 0.2 ships (0.2: BIP 321-shaped parser core, and make the body address optional bitcoin_uri#12). Body-less URIs and verbatim re-emission cannot be bolted onto 0.1, so this is the prerequisite.
  2. PjParam::from_uri. PjParam::from_uri(&bitcoin_uri::Uri<impl NetworkValidation>) -> Result<Option<PjParam>, PjParseError> plus pjos handling, behind an optional bitcoin-uri feature so bitcoin_uri version changes stay out of payjoin's semver surface until that crate is 1.0. This replaces the private adapter impls in payjoin/src/core/uri/mod.rs, which implement traits 0.2 removes. A malformed pj becomes an error from this call rather than a failure of the whole URI parse, so Cove can delete its strip-and-retry workaround.
  3. Bump the dependency and rebase Uri / PjUri on 0.2. Insulate bitcoin uri #1756 moved the bitcoin_uri types behind newtypes, so this is a minor for payjoin. One decision first: Uri::address() returns &Address and a body-less URI has none. Either a new Option-returning accessor with the existing one kept, or a type split where v1/v2 PjUri requires a body and the silent-payment static form is its own type.
  4. payjoin-ffi Uri becomes the full parser. This is the deliverable wallets can use. Additive on the existing object: params() as an ordered list of {key, baseKey, required, rawValue, value} records, get(baseKey), getAll(baseKey), instructions() as a record enum in document order, unsatisfiedRequirements(understood), sourceStr(), isValidForNetwork(network), and a UriBuilder object. Typed errors, never bare strings. PjParam.fromUri(uri) on the same object. No new namespace and no new native library, so nothing depends on external-type support in the C# or Dart generators.
  5. Emit through the builder. Receiver-side URI construction goes through bitcoin_uri::UriBuilder so encoding is RFC 3986 in one place, in Rust and in the bindings.
  6. Static-session parser. The silent-payment static session URI is body-less (bitcoin:?sp=…&pj=…). That parser has only been exercised against body-address URIs and needs a follow-up once 0.2 lands.

Not in this issue: a standalone bitcoin-uri-ffi crate. Every wallet this is for already loads payjoin-ffi. A separate crate waits until a wallet that does not use payjoin asks for one.

What done looks like

A wallet's bitcoin: branch collapses to one Uri.parse call plus handoffs: PjParam.fromUri for payjoin, the invoice string from instructions() to whatever BOLT 11 decoder it already has, the sp string to its silent payments library. The wallet keeps its scheme dispatcher for liquid:, LNURL, PSBT, and bare pasted strings. It deletes its BIP 21 parser, its + workaround, its network sniffing, and its hand-built pj=. The measure is lines deleted in Bull Bitcoin, Cake, and Wasabi. Migrations themselves are tracked in integrations-tracker.

Acceptance

  • rust-payjoin depends on bitcoin_uri 0.2 with no change to its own public API beyond additions
  • PjParam::from_uri exists and a URI with a malformed pj still parses as a plain Uri
  • No bitcoin_uri trait impls remain in rust-payjoin
  • bitcoin:?sp=…&pj=… parses through the payjoin Uri newtype and through payjoin-ffi
  • payjoin-ffi Uri exposes params, instructions, requirements, source string, network check, and a builder, with typed errors
  • Receiver-emitted URIs are produced by UriBuilder
  • A + inside a pj value survives Dart and C# round trips unchanged

Activity

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

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions