Skip to content

Pro backend updates for 0.2.0 backend#95

Open
jagerman wants to merge 32 commits into
session-foundation:devfrom
jagerman:pro-backend-updates
Open

Pro backend updates for 0.2.0 backend#95
jagerman wants to merge 32 commits into
session-foundation:devfrom
jagerman:pro-backend-updates

Conversation

@jagerman

Copy link
Copy Markdown
Member

This PR updates libsession-util for compatibility with the changes made in session-foundation/session-pro-backend#2, along with some related refactoring for integrating the changes with Session clients.

Session Pro: finalize the pre-launch wire/proof protocol + redesign the pro_backend API

Overview

This branch finalizes the Session Pro wire protocol and reworks libsession's
pro_backend / session_protocol code to match, ahead of launch. It is developed
in lockstep with the authoritative spec (session-pro-backend/docs/pro-wire-protocol.md)
and the backend implementation — every wire-affecting change here has a matching
backend change, and the pairing is validated end-to-end (see Testing).

Net effect: a smaller, simpler, more consistent API (+2,122 / −3,559), an opaque
request/response boundary so clients never parse the wire, and a signing scheme that no
longer depends on BLAKE2b.

A sibling branch (pro-backend-updates-pfs) carries the same changes on top of PR #90, adapted to the
post-quantum/std::byte primitives; it will be PRed separately.

Wire-protocol changes (coordinated with the backend spec)

These change bytes on the wire and/or in signatures, and land in lockstep with the backend:

  • Time is integer seconds, not milliseconds. Every Pro timestamp/duration is UNIX-epoch
    seconds as a signed int64_t. The only exceptions are the two upstream-provider event
    instants (purchased_ts, revoked_ts), emitted as JSON floats to preserve sub-second
    precision (kept internally as sys_ms / double).
  • Signatures sign the message directly — no BLAKE2b, no pre-hash (spec §1.1/§2/§3). All five
    Ed25519 signatures (the offline proof + four signed requests) are now Ed25519(key, M) where M
    is built by a single rule: 16-byte domain prefix, raw fixed-width keys/tags (self-delimiting),
    integers as canonical decimal ASCII (std::to_chars), strings verbatim (UTF-8), and one \0
    between adjacent variable-length fields. New src/pro_message.hpp implements this.
  • No version on requests; the proof keeps a plaintext version. Request shapes are
    domain-separated by endpoint + domain prefix, so requests carry no version at all. The proof
    (free-floating, offline-verified) keeps a plaintext version that selects its domain prefix
    (v0ProProof_v0_____) — it is a verification input, never a signed byte.
  • gen_index_hashrevocation_tag. Renamed everywhere (C/C++ structs + protobuf field);
    it is now a stored random 32-byte value, not a computed hash — clients compare it for equality
    only.
  • Payments collapse to one opaque payment_id. The per-provider typed ID fields and the
    provider/plan integer enums are gone; the wire carries an opaque payment_id string plus
    string provider_code / plan codes. Item status and account user_status are parsed as
    opaque pass-through strings.
  • Revocation list reshaped. Dropped the absurd per-entry entitlement-expiry; added per-entry
    effective_ts (the real gate) and a single list-level retain_for (memory-only aging) plus
    retry_in. Revocation ticket widened to 64-bit.
  • Response envelope redesigned (spec §5): status is a closed string enum
    (ok/fail/error), with an open/additive error_code slug and a single diagnostic error
    string (was an int status + errors[]). add_pro_payment is idempotent (re-claim = success);
    already_redeemed is gone.
  • Removed the dead SeshProBackend__ personalisation constant and the historical salted
    generation-token hashing.

API changes (libsession surface)

  • pro_backend is now free functions returning an opaque ProRequest. Each builder
    (add_payment_request, pro_proof_request, payment_details_request, refund_request,
    revocations_request) takes loose args (keys, provider_code, payment_id, timestamps) and
    returns ProRequest{ endpoint, content_type, data }. Clients relay data verbatim under
    content_type and never parse it — libsession owns the wire encoding. Response parsing is the
    symmetric set of free functions (parse_*).
  • The C request API is a thin shim over the C++ one — the C ..._request_build functions own a
    C++ ProRequest via an opaque handle and point endpoint/content_type/data into it
    (zero-copy); freeing deletes it. No duplicated request-building logic, no input structs.
  • ResponseBase carries ResponseStatus status (closed Ok/Fail/Error), plus
    std::optional<std::string> error_code / error; explicit operator bool() for success.
  • Removed ProProof::hash() and C session_protocol_pro_proof_hash() — the 32-byte signed
    digest no longer exists. ProProof::signed_message() returns the exact bytes signed, if needed.
  • New constants/accessors, owned by libsession (single source of truth, so clients stop
    hardcoding): backend base URL, the backend signing PUBKEY (+ PUBKEY_X25519), per-provider
    support/management provider_urls(), and visible_platforms() — the user-visible purchasable
    provider slugs (google_play, app_store; excludes the hidden rangeproof) for building the
    "purchase via …" list. Signing domain prefixes moved into C++ std::string_view constants.
  • Renamed *_unix_ts members to *_at where the value is a sys_seconds (raw-integer unix
    timestamps keep _unix_ts); ProRequest gained an explicit content_type.

Notable design decisions

  • Clients never touch the wire or sign anything. The request boundary is opaque bytes; proof
    verification is verify_signature() / status(). This means the signing rework above is
    invisible to client code — no client changes required for it.
  • Domain separation over versioning-in-hash. A new request shape earns a new endpoint; the
    proof binds its version via the domain prefix it selects. No version byte is signed.
  • std::to_chars decimal integers, explicitly not locale-aware formatters, so the signed bytes
    are identical across languages/platforms and need no endian handling.

Testing

  • Full unit suite green on the branch.
  • Live cross-implementation validation: a companion integration harness round-trips signed
    requests and proofs against a real backend clone (with the matching spec commit) — this catches
    wire/signing drift (field order, integer encoding, framing, domain prefix) that self-consistent
    unit tests cannot. Both the signing rework and the envelope redesign pass end-to-end.
  • Big-endian audit: utils/test-bigendian.sh builds and runs the hash/protocol/pro/config
    suites under emulated s390x (local qemu-user-static) to guard against host-byte-order
    assumptions.

Client migration notes

  • Swap any direct JSON building/parsing for the pro_backend *_request() / parse_* functions;
    relay ProRequest.data under ProRequest.content_type unmodified.
  • Response handling: branch on status (ok/fail/error); map error_code slugs to localized
    strings (the error string is an English diagnostic, not user-facing). Drop any
    already-redeemed special-casing.
  • Read backend URL/PUBKEY/provider URLs/purchasable platforms from libsession instead of local
    copies.
  • No action needed for the signing change itself — it is entirely inside libsession.

jagerman and others added 30 commits July 16, 2026 20:06
The proof and request signing hashes fed multi-byte integers (proof expiry, request unix timestamps, get-details count) into blake2b by reinterpret_cast'ing the host integer's raw memory, which matches the backend's explicit little-endian encoding only because our targets are little-endian. Wrap each in oxenc::host_to_little_inplace() before hashing so the little-endian encoding is explicit and correct regardless of host byte order. No wire change on little-endian platforms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
proof_hash_internal defined a constexpr PRO_BACKEND_BLAKE2B_PERSONALISATION = "SeshProBackend__" that was never used; the proof hash actually uses ProProof________ (SESSION_PROTOCOL_BUILD_PROOF_HASH_PERSONALISATION). The backend confirms SeshProBackend__ appears nowhere in its real signing path (only a stale example verifier), so it is pure dead code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The revocation-list ticket was a uint32, which can wrap or be rolled back by a disaster-recovery restore, silently making a client that is 'ahead' treat the list as unchanged and miss revocations. Widen it to int64_t in the C and C++ structs, docstrings, and JSON parse.

json_require previously accepted only unsigned integer types, so json_require<int64_t> tripped its trailing static_assert. Extend the integer branch to signed types by switching the type dispatch to STL concepts std::floating_point/std::integral/std::same_as (dropping the is_one_of type lists); the bool check moves ahead of the integral one since bool satisfies std::integral. Also drop the needless value-init of the local 'type'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert all Session Pro timestamps and durations from millisecond to integer-second units, per the authoritative wire spec. Signed BLAKE2b hashes (proof expiry; generate-proof/get-details/set-refund request timestamps) fold in seconds; wire JSON keys drop the unit suffix (_unix_ts_ms -> _ts, request nonce -> ts, grace_period_duration_ms -> grace_period_duration); internal C++ time_points become sys_seconds and durations chrono::seconds; and the C API surface is likewise seconds (breaking it, which clients absorb in the client-migration phase). 8-byte little-endian hash encoding is unchanged (value is seconds now). Adds a session::sys_seconds alias.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When we load oxen-logging via system lib, the code that was supposed to
also inject fmt/spdlog with it was not working properly because the "did
we go via submodule" was falsely returning true.  This fixes it by using
an intermediate library so that we can properly distinguish between the
cases (final oxen::logging exists - submodule, which already gives
fmt/spdlog; otherwise use the intermediate target and set final
oxen::logging alias manually).

This also fixes the default STATIC_BUNDLE to depend on
BUILD_STATIC_DEPS, rather than NOT BUILD_SHARED_LIBS, so that
-DBUILD_STATIC_DEPS=OFF doesn't accidentally default to attempting a
static bundle without static deps.  (That case is also now detected and
becomes a fatal error).
Extends the ms->seconds wire-format change (2758108) into the config
layer, and switches all Pro timestamp/duration fields from uint64_t to
signed int64_t to match the rest of the codebase (unsigned bought
nothing for wall-clock values).

- config (convo_info_volatile / user_profile / config-pro): proof
  expiry, contact pro-expiry, and pro access-expiry are stored and
  exposed in seconds; synced dict keys "e"/"E" reused (safe: Pro config
  is not live in shipped clients). Drops the _ms C-API name suffixes.
- pro_backend + session_protocol C API: timestamps/durations
  uint64_t -> int64_t. Bitsets, counts, flags, and pfs hash-entry values
  are left unsigned.
- tests: field/key/unit updates; fix test_session_protocol, which
  2758108 left encoding expiryunixts and passing decode ts in ms after
  the code moved to seconds -- broke the proof-signature round-trip,
  undetected because the suite could not previously link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the wire spec (§2.1, delta session-foundation#2): rename the libsession symbol and the
backend-JSON wire keys. The value stays an opaque 32-byte tag that
clients compare for equality against the revocation list; the "random
value, not BLAKE2b(counter, salt)" nature change is entirely backend-side.

- C/C++ struct fields, C API, config fields (pro_revocation_tag), and the
  "gen_index_hash" JSON keys in the generate-proof response + revocation
  list.
- Protobuf field genIndexHash (ProProof field session-foundation#2) is left as-is: the wire
  is keyed by field number, renaming it would require regenerating the
  committed SessionProtos.pb.* and touches the shared cross-client schema.
- Config dict key "g" kept (internal, opaque; no format change).
- Stale "hash of the generation index" comments updated to describe the
  opaque revocation tag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the gen_index_hash -> revocation_tag rename into the shared
SessionProtos schema so the name is consistent everywhere (no permanent
mismatch between the proto field and the C++/wire names).

Field number is unchanged (ProProof field 2), so this is wire-compatible
on the binary protobuf; only the schema field *name* and the generated
accessors change. Regenerated SessionProtos.pb.{cc,h} with protoc
3.21.12 (rename-only diff; WebSocketResources unchanged).

NOTE: SessionProtos is the cross-client message schema -- clients that
reference the generated `genIndexHash` / `genindexhash()` accessors must
update to `revocationTag` / `revocationtag()`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y_in/retain_for

Per the wire spec (§4, delta session-foundation#6). The per-entry expiry_ts (whose value was the
entitlement expiry -- up to a year for long subs, absurd for what is only a
cleanup timer) is dropped and replaced by:

- per-entry `effective_ts`: a matching proof is revoked only once the client
  clock reaches this (revoking on tag-match alone is too early);
- response-level `retry_in` (recommended poll interval) and `retain_for`
  (memory-only local aging window, ~ the max proof-validity window).

libsession-util parses and exposes these; the client applies the matching and
aging semantics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…yment_id

Delete the SESSION_PRO_BACKEND_PAYMENT_PROVIDER and SESSION_PRO_BACKEND_PLAN
enums and the payment-provider metadata table; providers and plans are now
opaque wire strings (canonical SESSION_PRO_BACKEND_PAYMENT_PROVIDER_CODE_*
constants for the known ones). Collapse the per-provider payment fields
(google_payment_token/google_order_id/apple_*/rangeproof_order_id) into a
single opaque payment_id; multi-part providers fold their parts in per a
backend-defined composite. Rename payment status REFUNDED -> REVOKED.

The add-payment and set-refund signed hashes now cover provider_code | payment_id
(dropping the provider enum byte and the separate order_id).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Pro hashing personalisation strings and proof digest routine pointed at
pinned line numbers in the session-pro-backend GitHub source, which rot as that
repo changes. Point them at the authoritative pro-wire-protocol.md sections
(§2 proof, §3 signed requests) instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reshape the Session Pro request/response API for a clean, symmetric design
ahead of launch:

- Request builders are now free functions returning ProRequest{endpoint, body}
  (add_payment_request, pro_proof_request, payment_details_request,
  refund_request, revocations_request), each paired with a *_sigs/*_sig signer.
  The request C++ structs are deleted.
- Response parsing is symmetric free functions (parse_add_payment,
  parse_pro_proof, parse_payment_details, parse_refund, parse_revocations)
  rather than X::parse methods; a shared fill_proof_response() backs the two
  proof responses.
- Response base renamed ResponseHeader -> Response; the
  AddProPaymentOrGenerateProProofResponse mega-type becomes ProProofResponse
  with empty AddProPaymentResponse/GenerateProProofResponse derivations.
- Endpoint strings live in a single master table; C SESSION_PRO_BACKEND_*_ENDPOINT
  symbols and C++ ProRequest.endpoint both view it.
- The wire version for each request type is library-owned (a per-type constant),
  not a caller parameter; the version param and _request_build_to_json helpers
  are removed.
- Key derivation happens once via normalize_privkey() rather than per hash.
- Re-add provider support/management URLs (URL-only, no English names) keyed by
  provider code so clients don't each duplicate them: C++ provider_urls() returns
  std::optional<ProviderUrls>; C session_pro_backend_get_provider_urls() returns
  all-NULL fields for a provider with no URLs.

The C API (_request_build_sigs/sig, _request_to_json, _response_parse) is
preserved for the iOS client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…redeemed→purchased)

The get-details wire key `unredeemed_ts` is renamed `purchased_ts` (the field is
the provider's purchase instant, not a payment state), and `purchased_ts` and
`revoked_ts` are now JSON floats carrying the upstream provider's sub-second
precision (pro-wire-protocol.md §1/§5).

Parse those two as floating-point seconds and keep the precision rather than
truncating: the C++ ProPaymentItem stores them as millisecond-resolution sys_ms,
and the C API exposes them as double fractional seconds (millisecond-precise via
sys_ms). All other timestamps remain whole-second integers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…the proof

Per pro-wire-protocol.md Delta session-foundation#11, the in-digest version byte and the JSON
`version` field are removed everywhere except the proof carries its version.

- Requests (add-payment, generate-proof, get-details, set-refund, get-revocations):
  no version byte in the signed hash, no `version` in the body; the library-owned
  per-request version constants are deleted. A new request shape gets a new endpoint.
- The proof keeps a plaintext `version` field — it is a free-floating, offline-verified
  credential with no endpoint. It is no longer hashed as a byte; instead it selects the
  personalisation (`ProProof________` -> `ProProof_v0_____`), and that choice is what
  binds the version into the signature. proof_hash_internal no longer takes/hashes it.
- SetPaymentRefundRequestedResponse loses its `version` field (C++ + C).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete the three dead [[maybe_unused]] dump_pro_* stderr helpers (several with
malformed format specifiers, e.g. "%" PRId64 "zu"), and convert the remaining
fprintf(stderr, "ERROR: ...") diagnostics to Catch2 INFO/UNSCOPED_INFO so they
surface through the test framework instead of raw stderr. Per-error messages use
UNSCOPED_INFO so they survive to the post-loop assertion. Drops the now-unused
<cinttypes> include. No test logic change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the incremental `nlohmann::json j; j["k"] = v; ...` pattern in the four
*_body helpers with a single braced json literal returned directly
(`return nlohmann::json{...}.dump();`, no named temporary). Cosmetic only:
nlohmann sorts object keys on dump, so the serialized bodies are byte-identical
(the C-vs-C++ request cross-check tests confirm), and the C _request_to_json
wrappers call the same helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per pro-wire-protocol.md §1 (Q13), the get-details payment-item `status` and the
account `user_status` travel as stable string codes, not integers — as
`payment_provider`/`plan` already do. libsession still parsed both as integers
mapped to hand-synced C enums, which (a) was incompatible with the live backend
(payment status has been a string on the wire) and (b) hard-failed the entire
get-details parse on any unknown value.

Parse both as opaque pass-through std::string (C: char[64] + _count), so unknown
or future codes degrade gracefully instead of breaking old clients. Delete the
SESSION_PRO_BACKEND_PAYMENT_STATUS and SESSION_PRO_BACKEND_USER_PRO_STATUS integer
enums and their "must match backend" coupling. Known codes: payment status
unredeemed/redeemed/expired/revoked; user_status never/active/expired.

Backend shipped the matching change (session-pro-backend b3db4e3); in lockstep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the all-zero PUBKEY placeholder with the Session Pro Backend's real
Ed25519 public signing key (current test deployment, expected to carry through to
production). Consumers verify a proof was issued by the backend by checking its
signature against this key (ProProof::verify_signature). Trivially editable if the
backend signing key ever changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add session::pro_backend::URL (= https://pro.session.codes) so clients read the
canonical production base URL from libsession rather than hand-carrying it, and
export the Ed25519 signing pubkey to the C API (SESSION_PRO_BACKEND_PUBKEY) for C
consumers (iOS); C++ already has pro_backend::PUBKEY. Both C symbols point at the
single C++ definitions (no duplicated literal/key). Clients may still override the
URL for dev/test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
utils/test-bigendian.sh builds + runs the test suite inside an emulated s390x
(qemu-user) so endian-sensitive byte serialization (Pro signed-digest hashing,
config, protobuf) is exercised on a real big-endian host. Manual/occasional audit,
deliberately not wired into CI. (The [endian] make_hashable known-answer test is
pfs-only; on stable the [hash]/[pro_backend]/[session_protocol]/[config] tags still
exercise the serialization paths.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the privileged tonistiigi/binfmt helper container in favour of a
one-time host install of qemu-user-static + binfmt-support (registers the
binfmt_misc handlers with the F flag, which is what makes them work inside
containers). Guard on the handler being present. Also bump the base image
bookworm -> sid so the audit runs against a modern toolchain representative
of what the code targets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The *_unix_ts members/params are sys_seconds / sys_ms time points, so the
name baked the representation into the identifier. Rename them to *_at (as
the backend did) to stop embedding the type. Pure rename: JSON wire keys and
protobuf accessors are untouched, so there is no wire change. Also drop the
stale expiry_unix_ts_ms grace-period doc formula (the wire is integer seconds
now; no *_ms field survives).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Requests now carry {endpoint, content_type, data} instead of {endpoint, body}.
Clients relay `data` verbatim under `content_type` and must NOT assume or parse
a format -- libsession owns the wire encoding and may change it. Renames the C
mirror to match: session_pro_backend_to_json -> session_pro_backend_request
(json field -> data, plus endpoint + content_type), *_request_to_json ->
*_request_build, to_json_free -> request_free. The content type is single-sourced
("application/json") next to the endpoint strings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The C request API reimplemented the wire pipeline: a per-request _build_sigs
that signed, an input struct the caller hand-filled with pubkeys + precomputed
sigs, and a _request_build that re-serialized the body from them. Replace all
of it with loose-arg builders that take the same inputs as the C++ *_request()
functions (private keys + params) and just call them -- one wire implementation,
living in C++.

session_pro_backend_request now owns the built C++ ProRequest in an opaque
void* and points endpoint/content_type/data into it (zero copy); request_free
deletes it. Deletes the _build_sigs/_build_sig functions, the per-request input
structs, the add_pro_payment_user_transaction struct, and the
master_rotating_signatures/signature return structs. Tests and the dev-server
integration block are rewritten to the single-call API.

Also tightens the internal *_hash/*_body helpers to fixed-extent spans
(span<...,32> for pubkeys, span<...,64> for sigs) so sizes are enforced at
compile time rather than silently truncated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Desktop consumes the backend's X25519 pubkey as a constant, so expose it
alongside PUBKEY: session::pro_backend::PUBKEY_X25519 (C++) and the C export
SESSION_PRO_BACKEND_PUBKEY_X25519. A unit test asserts the hardcoded bytes
equal the runtime crypto_sign_ed25519_pk_to_curve25519(PUBKEY) conversion so
they cannot drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ded tags

Iterated the occasional big-endian audit into a working state (validated on
real s390x): use the host's qemu-user-static + binfmt-support (no privileged
tonistiigi/binfmt container); debian:sid for a modern toolchain; a clean
build dir each run; -DENABLE_NETWORKING_SROUTER=OFF plus libngtcp2/gnutls/
libevent from apt to satisfy the mandatory oxen::quic without building it from
source; and fail loudly if any filter tag matches no tests (guards against a
tag rename silently shrinking the audit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Response::status was a uint32_t whose domain changed by endpoint (0/1/2 for
most, plus 100/101 for add-payment) -- and for every response except add-payment
it just duplicated !errors.empty(). Remove it: Response is now { errors } with a
success() helper (C: response_header drops status; success == errors_count == 0),
and add-payment collapses to the same -- the backend already sends the human
reason (including already-redeemed / unknown-payment) in errors, so display is
unaffected. Deletes the AddProPaymentResponseStatus C++ enum, the C
SESSION_PRO_BACKEND_ADD_PRO_PAYMENT_RESPONSE_STATUS enum, and its magic-int
values. Parsers still read the wire "status" int internally to decide success;
they just no longer surface it. (A future machine-readable discriminator, if
needed, should be an opaque string slug on the wire like the Q13 statuses.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r (Delta session-foundation#12)

Reworks the response envelope to spec §5 / Delta session-foundation#12. The wire `status` becomes
a string ok/fail/error parsed into a CLOSED ResponseStatus enum -- an unrecognized
value is a protocol error, never passed through (backend confirmed status is
exhaustive, Q14). The `errors` array becomes a single `error` string
(diagnostic/fallback, first-reason-only). A new open/additive `error_code` slug
(opaque; unknown values pass through) carries the machine-readable outcome.

Response is renamed ResponseBase (it's only ever a base) and is now
{ ResponseStatus status; optional<string> error_code; optional<string> error; }
with an explicit operator bool(). The C response_header mirrors it (status enum +
error_code/error string8, {NULL,0} == none). get-details account status key
renamed status -> user_status (Q15). Deletes the obsolete integer status enum.
A libsession-side parse failure yields a protocol error (error_code
"invalid_response").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… drop BLAKE2b

Implements pro-wire-protocol.md Delta session-foundation#13 / §1.1: all five Ed25519
signatures (the offline proof §2 + the four signed requests §3.1–3.4)
now sign the message bytes directly instead of a BLAKE2b-256 digest.

- Add session::pro::signed_message() (src/pro_message.hpp): builds the
  signed message per §1.1 — 16-byte domain prefix, fixed-width raw
  keys/tags (self-delimiting), integers as canonical decimal ASCII
  (std::to_chars), strings verbatim, a single NUL between adjacent
  variable-length fields.
- Replace ProProof::hash() with ProProof::signed_message() and delete the
  C session_protocol_pro_proof_hash() (the 32-byte digest no longer
  exists in the protocol).
- Delete make_blake2b32_hasher; BLAKE2b is gone from the Pro signing path.
- Move the 16-byte domain-prefix constants into C++ (session_protocol.hpp)
  as std::string_view and rename *_PERS -> *_DOMAIN; drop the C char[]
  ..._HASH_PERSONALISATION constants.
- Update tests to sign via signed_message() and to compare the proof's
  signature (not a hash) across the encode/decode round-trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Client-facing enumeration of the user-visible purchasable payment
platforms (provider-code slugs: google_play, app_store) so clients build
their "purchase via …" store list at runtime instead of hardcoding two
stores. Hidden mechanisms (rangeproof) are handled but never listed — they
surface only for users who already hold them.

C++ `visible_platforms()` returns std::span<const std::string_view>; C
`session_pro_backend_visible_platforms(size_t*)` returns a static
pointer-array derived from it. Both built from the existing
SESSION_PRO_BACKEND_PAYMENT_PROVIDER_CODE_* constants (single source).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(+ formatting)
@jagerman
jagerman force-pushed the pro-backend-updates branch from 6b7d55a to 4ea3941 Compare July 21, 2026 20:17
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.

1 participant