diff --git a/docs/protocol/badges-overview.html b/docs/protocol/badges-overview.html new file mode 100644 index 0000000000..935330fd10 --- /dev/null +++ b/docs/protocol/badges-overview.html @@ -0,0 +1,1292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+
+ +
+ + + + +

Revision 1, 2026-09-18

+

SimpleX Supporter Badges

+

Table of contents

+ +

Introduction

+

The goal is for SimpleX Chat to be partly funded by its users, who buy supporter badges. A badge is shown on the profile of the person who bought it, and it raises the limits that other users and servers apply to that person's files. This document describes what a badge is, what it grants, how it is issued and presented, and what it discloses about the holder.

+

In an ordinary messenger a paid feature is attached to an account, and the operator sees in one record who paid and what they did with the feature. The SimpleX network has no accounts, so there is nothing to attach a purchase to, and introducing an account for this purpose would remove the privacy that the rest of the design provides. A badge is therefore designed so that the purchase, which the badge service necessarily records, cannot be connected with the profile, the conversations or the servers of the person who made it.

+

What is a badge

+

A badge is a credential issued by the badge service, a bot operated by SimpleX Chat. The credential is a BBS signature over four values: a master key generated by the app; the expiry date; the badge type, currently supporter or legend; and a reserved field that is currently empty.

+

The holder of a BBS signature can prove that the issuer signed certain values, disclose some of those values, and keep the other values and the signature itself secret. The credential itself is therefore not sent to anyone. When the badge is to be shown or used, the app generates a proof that discloses the badge type, the expiry date and the reserved field, names the issuer key by its index, and hides the master key and the signature. Two proofs from the same credential cannot be linked to each other, by the issuer or by anyone else. Each proof is generated for one context, whether a conversation, a file or a session with a server, and is not accepted in any other.

+

The transport protocols of the network call the same credential an entitlement: a name, an expiry and an extra string, disclosed in a proof. The chat application puts the badge type into the name, so a server that verifies an entitlement and a contact who sees a badge are looking at different zero-knowledge proofs of the same credential.

+

What a badge grants

+
    +
  • A badge is shown on the profile to contacts, to the members of groups, and to the subscribers of channels.
  • +
  • A badge allows larger files. The default limit is 1GB; a supporter can send files up to 2GB, and a legend up to 5GB. The limit is applied by the recipient, whose app verifies the sender's proof before it accepts a file above the default size.
  • +
  • A badge extends the storage of files on servers. An XFTP server stores a file for 48 hours by default. It may be configured to store files for longer when they are uploaded by the holder of a badge of a given type; the example values in the server configuration file are 7 and 21 days for supporter and legend badge holders, respectively.
  • +
+

Two other uses are planned and described under Future work:

+
    +
  • A backup will be a link whose content the app replaces each time it makes a new backup, so that the same link restores the latest state. A badge will raise the size of the backup and the time it is stored, and the storage time is then the period the app can remain offline before the backup is lost.
  • +
  • Servers that verify the proof will apply higher rate limits when creating messaging queues, file chunks and notification tokens.
  • +
+

What a badge discloses

+

The badge service holds the purchase key, the number of months bought, and the payment record of the purchase. A card payment is linked to the purchase, as in any other service; a user who wants the payment itself to be private may pay with cryptocurrency. In either case the record is held by the service alone, and the service does not learn where the badge is later shown or used, because a zero-knowledge proof contains nothing that refers back to the credential or the purchase.

+

A party that verifies a proof learns the badge type and the expiry date. Since all credentials expiring in the same week have the same expiry date, these two values place the holder among the supporters whose badges expire that week.

+

Comparison with Signal badges

+

Signal sells donation badges using receipt credentials. The client obtains a credential against a payment without revealing its account, and then presents the credential once to the account server, which records its serial number, level and expiry against the account and attaches the badge to the account. The payment is not linked to the account cryptographically. The same mechanism is used for Signal's paid backups.

+

The differences follow from Signal having accounts and SimpleX not having them.

+

In Signal:

+
    +
  • The server holds the list of accounts that have badges.
  • +
  • The credential can be verified only by the server that issued it.
  • +
  • The credential is presented once, after which the badge is a property of the account.
  • +
  • The client requests the credential and redeems it in a single sequence of jobs without delay, so the server can match the request made under the payment with the redemption made under the account by their times and by network address.
  • +
  • The expiry of a credential is rounded up to the next day.
  • +
+

In SimpleX:

+
    +
  • A server verifies a proof for one session and cannot link that session to any other.
  • +
  • A credential can be verified against the issuer's public key by contacts, channel relays and independently operated servers.
  • +
  • A credential is presented every time the badge is shown or used, with a new proof each time.
  • +
  • The request for a renewed credential and the profile update that presents it are made on different days.
  • +
  • All credentials expiring in the same week have the same expiry.
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertySignal badgesSimpleX badges
Account requiredYesNo
Operator holds the list of badge holdersYesNo
Verified byThe issuing serverAny party with the issuer's public key
Presentations per credentialOneUnlimited
Proof bound to the context of presentationNoYes
Expiry rounded up toThe next dayThe end of the following Monday
Issuance and presentation on different daysNoYes
Usable on independently operated serversNoYes
+

Non-goals

+
    +
  • A badge does not restrict anything that is available today: the defaults are unchanged, and a badge only raises them.
  • +
  • A badge does not create an identity: it has no persistent identifier, it is not linked across conversations, and an incognito profile does not show it.
  • +
  • A badge cannot be transferred: a code can be redeemed once, and the credential obtained with it is usable only with the master key it was issued for.
  • +
  • A badge does not exempt its holder from the limits a server applies: it lowers the cost of a resource without removing the limit on it.
  • +
  • A badge cannot be revoked; credentials are issued for one month at a time instead.
  • +
+

Architecture

+
   ┌─────────────────┐   code    ┌──────────────┐   purchase    ┌──────────┐
+   │  Web purchase   │ ────────> │   Holder     │ ────────────> │  Issuer  │
+   └─────────────────┘           │  (the app)   │ <──────────── └──────────┘
+                                 └──────┬───────┘   credential
+                                        │           every month
+                                        │  a new proof each time
+              ┌─────────────────────────┼───────────────────────┐
+              │                         │                       │
+         ┌────▼─────┐             ┌─────▼──────┐          ┌─────▼──────┐
+         │ Contacts,│             │ File       │          │ The user's │
+         │ groups,  │             │ recipients │          │ own servers│
+         │ channels │             └────────────┘          └────────────┘
+         └──────────┘
+
+

Participants

+
    +
  • The issuer is the badge service, a bot on the SimpleX network with a contact address. It holds the secret key with which credentials are signed. Apps and servers hold a list of eight issuer public keys, and every credential includes the index of the key that signed it, so the service can move to the next key without a release of apps or servers.
  • +
  • The holder is the app. It keeps the credential in the user's profile, generates proofs on the device, and renews the credential every month.
  • +
  • The verifiers are contacts, group members, channel relays, the recipients of files, and the user's own servers. A verifier holds the issuer public keys and no other information about badges.
  • +
+

Service requests

+

The app and the badge service communicate through one-off service requests, a primitive of the SimpleX agent described in One-off requests to service addresses. Although the service has a contact address, it is not a contact of the app, and no conversation with it exists.

+

A request is a single message to the service's address. The app establishes a double ratchet, with post-quantum key agreement, from the keys published in the service's address link, encrypts the request with the double ratchet, and sends it to the address queue on the service's server in the same way as any message to a server chosen by another party, that is, through a proxy server when private routing is enabled (which is the default). With the request the app sends the address of a reply queue that it created for this request on one of its own servers. The service decrypts the request with a receiving ratchet initialised from its private keys, sends the reply to the reply queue under the same ratchet, and deletes its state; the app deletes the reply queue and the ratchet when it has the reply or when the request times out.

+

This exchange has the following properties:

+
    +
  • Unlike a request made directly to the service over HTTP, the app does not connect to the service, nor the service to the app. Each communicates only with SMP servers, so the service does not see the app's network address, and with private routing the service's server does not see it either.
  • +
  • Unlike a chat connection, which is a persistent channel through which all requests made over it can be linked to each other, a service request does not create persistent state. Each request uses new keys and a new reply queue, so two requests from the same app cannot be linked by the service or by servers.
  • +
  • A reply that decrypts proves that it came from the holder of the keys published in the service's link and signed by its root key, so a server cannot substitute a reply.
  • +
  • The badge service answers a repeated request with the same result and does not execute the operation twice, so the app can repeat a request whose reply was lost.
  • +
+

A purchase is identified by an Ed25519 key pair that the app generates for it and does not use for any other purpose. Every request concerning the purchase, the redemption and each monthly renewal, is signed with this key, and the signature covers the request together with a value derived from the ratchet of that exchange, so it is valid for that exchange only and cannot be replayed. The agent verifies the signature and delivers the verified public key to the service with the request, and the service accepts a request about a purchase only when the verified key is the purchase key.

+

Buying and redeeming

+

A badge is bought for one or more months, either on the web or in the app.

+

On the web the purchase produces a code, which is then redeemed in the app. The code is the only data passed from the web site to the app: the web site does not learn which app redeems a code, and the app does not see the payment. The code is generated in the buyer's browser, and the service stores only its hash; at redemption the app presents the code itself, and the service matches it against the stored hash. A badge issued without a sale, for example in compensation for a problem, is a code generated by the operator and redeemed in the same way.

+

In the app, the user pays by card, in cryptocurrency, or through the app store. The app requests an invoice from the service, or presents the receipt of the app store, and the service issues the credential once the payment is confirmed.

+

In both cases the app generates the master key and the purchase key pair before the purchase. To redeem a code, the app sends the code and the master key to the service, which issues the credential. A code redeemed a second time with the same purchase key returns the same credential; a code redeemed with a different purchase key is refused.

+

Monthly issuance

+

A credential is issued for one month at a time, however many months were bought. The service keeps a count of the months remaining for each purchase key, and issues the next credential when the app asks for it. Unused months, e.g. if the app was offline, lapse at the next badge issuance.

+

Credentials are issued monthly to limit what the expiry date discloses. If credentials were issued for the whole term, a user who bought a year would hold a credential expiring on a day a year ahead, when few other credentials expire, and this date would be disclosed in every proof for a year. Instead, every credential is issued for one month, and its expiry is rounded to the end of the Monday following the end of the paid month (UTC). For example, if the paid month ends on Wednesday 14 October 2026, the credential expires at the end of Monday 19 October, and so does every credential whose month ends between Monday 12 and Sunday 18 October. All credentials whose paid month ends in the same week thus expire at the same instant.

+

The renewal is split over two days for the same reason. The app requests the next credential on the day before the current one expires, and when the current one expires, it switches to the new one and sends its updated profile to its contacts. In the example above, the app asks the service for the new credential on Monday 19 October and starts showing it on Tuesday 20 October, so an observer of both the request and the profile update cannot correlate them by time.

+

The renewal runs in the background and does not require any action from the user. Recipients accept a badge for seven days after its expiry, and servers for one day, so a renewal delayed by a few days is not visible to contacts.

+

Presentation

+

A BBS proof is generated over a string, called the presentation header, and is verified only against the same string. The string binds the proof to the context in which it is presented. Without it, a proof received in one conversation could be copied and presented in another. Between apps the string is sent with the proof, and the recipient checks that it is the string it expects; servers know the string already, so it is not sent to them.

+

The context of a conversation is the same string over which message signatures are computed:

+
    +
  • In a direct chat it is a hash derived from the state of the end-to-end encryption, which only the two sides hold.
  • +
  • In a group it is the member's identifier together with the member's signing key.
  • +
  • In a channel it is the channel's identifier together with the member's identifier, or the channel's identifier alone when a message is sent in the name of the channel.
  • +
+

A session with a server is identified by the TLS session identifier, which both sides derive from the TLS handshake and which differs on every connection.

+

Each time the app sends its profile, whether to a new contact, to a group it joins, or to everyone when the profile is updated, it generates a new proof and includes it. The proof is bound to the conversation in which the profile is sent: in a direct chat to the connection with the contact, in a group to the member's identity in that group, and in a channel to the member's identity established by the channel's roster. In a group the badge is accepted only from a message signed with the member's key, and in a channel it is verified against the key that the roster establishes for the member.

+

A file larger than the default limit is sent with two proofs. The first is placed in the file invitation, the message that announces the file, and is bound to the conversation and to the size of the file; the recipient's app verifies it when the invitation arrives. The second is placed in the file description, the record of where the chunks of the file are stored and how they are decrypted, and is bound in addition to a hash of the description and to the storage time of the file; the app verifies it before the download begins.

+

Servers

+

The client presents the entitlement proof in the transport handshake, bound to the TLS session identifier. The server verifies it once, when the session is established, and applies the result to every command in the session.

+

The client presents the proof only to its own servers, that is, to those configured for the profile, which are identified by the certificate fingerprint pinned in TLS. It does not present the proof to a server whose address it received from a contact or found in a file description, because a file description names the servers on which the chunks are stored, and these are chosen by the sender. If the app presented its proof to every server it connected to, a sender could store a file on a server of their own and learn, when the file is downloaded, that the person downloading it holds a badge.

+

On XFTP servers the proof extends the storage time of files. The server grants the smaller of the time requested by the client and the maximum configured for the badge type, and returns the resulting expiry. SMP servers and notification servers accept the same proof in their handshakes but do not yet make use of it.

+

The exchange between the app and the badge service, that is, the commands, responses and errors, is described in the badge service protocol.

+

Cryptographic primitives

+

Badges are built on BBS signatures, in the BLS12-381-SHA-256 suite, implemented by libbbs over blst. A credential is a signature over four values under the header SimpleX badges v1; a proof discloses three of them and is 304 bytes long. BBS was chosen for four properties that the design needs together:

+
    +
  • A proof can be verified by anyone who holds the issuer's public key, so contacts and independently operated servers verify badges without consulting the issuer.
  • +
  • A proof discloses only the values selected.
  • +
  • A proof cannot be linked to any other proof of the same credential.
  • +
  • A proof is generated over a presentation header, so it can be bound to the context in which it is presented.
  • +
+

Credentials of the kind Signal uses can be verified only by their issuer, and a receipt credential discloses its serial number when presented, so it is presented once. Single-use tokens, such as blind signatures and the tokens of Privacy Pass, are likewise spent when presented and do not include values such as an expiry or a type.

+

Service requests are protected by the double ratchet of the SimpleX agent, with X448 key agreement and the sntrup761 KEM, established from keys published in the service's link. The purchase keys with which requests are signed, and the member keys that form part of the conversation context in groups, are Ed25519 keys. Codes are hashed with SHA-256, and the file description in the context of a file proof with SHA-512.

+

Security

+

Design objectives

+
    +
  1. A proof discloses no value that links it to another proof or to the purchase.
  2. +
  3. A proof used in one context, whether a session, a conversation or a file, cannot be used in another context.
  4. +
  5. The timing of presentations does not identify the holder: all credentials expiring in the same week share the same expiry, and the renewal request and the profile update are made on different days.
  6. +
  7. Requests to the badge service cannot be linked to each other, to a profile or to a network address, and a request about a purchase can be made only by the holder of the purchase key.
  8. +
  9. A credential cannot be forged: the issuer keys are fixed in apps and servers, and the app verifies a credential before storing it.
  10. +
  11. A missing or failed proof leaves the default limit in place, and a server cannot be configured to grant a badge type less than the default.
  12. +
+

Threat model

+

This threat model assumes the SimpleX network threat model and addresses the threats specific to badges.

+

The badge service

+

can:

+
    +
  • See the purchase key of every badge, the master key generated for it, the number of months bought, and the payment record.
  • +
  • Issue any credential, or refuse to issue one, as it holds the issuer key.
  • +
+

cannot:

+
    +
  • Connect a purchase with a profile, a contact, a group or a session with a server - a proof contains nothing that refers back to the purchase.
  • +
  • Learn where a badge is shown or used.
  • +
  • Learn the network address of the app - requests reach the service through SMP servers, on connections created for the request.
  • +
+

A contact, a group member or a channel relay

+

can:

+
    +
  • See the badge type and the expiry date.
  • +
+

cannot:

+
    +
  • Tell whether a badge seen in another conversation belongs to the same person.
  • +
  • Reuse a profile proof or a file proof in another context.
  • +
  • Distinguish the holder from the other supporters whose badges expire in the same week.
  • +
+

A server operator

+

can:

+
    +
  • See the badge type and the expiry date once in each session.
  • +
  • Group together the sessions of the supporters whose badges expire in the same week.
  • +
+

cannot:

+
    +
  • Reuse the proof on another connection.
  • +
  • Link a session to the purchase, to a profile or to sessions on other servers, except through the week of expiry.
  • +
  • Obtain a proof from a client that does not use the server.
  • +
+

The server that hosts the badge service's address

+

can:

+
    +
  • See that requests arrive for the service.
  • +
+

cannot:

+
    +
  • See the content of requests.
  • +
  • See the network address of the app when private routing is used.
  • +
  • See the replies, which are sent to a queue on a server chosen by the app.
  • +
+

One badge on many machines

+

Since proofs are unlinkable, a server cannot count the sessions of a single credential, so the benefit granted by a badge must be limited without counting sessions. For this reason a badge lowers the cost of a resource without removing the limit on it, and limits per session remain in force.

+

Compromise of the user's device or backup

+

An attacker who obtains the credential and the purchase key can use the badge until it expires and renew it while months remain. There is no revocation, so the loss is limited to the months bought.

+

Interception of a code

+

A code is a bearer secret until it is redeemed; once redeemed, it is refused to any other purchase key.

+

A passive network observer

+

can:

+
    +
  • Observe SimpleX traffic between clients and servers.
  • +
+

cannot:

+
    +
  • See a proof - proofs to servers are inside TLS, in a handshake block of fixed size, and proofs to other users and requests to the badge service are inside end-to-end encrypted messages.
  • +
  • Distinguish a session in which a proof was presented from a session without one.
  • +
+

Future work

+
    +
  • Backups will be stored on XFTP servers, and a badge will raise their size and their storage time, as described under What a badge grants.
  • +
  • SMP servers and notification servers will verify the proof. A server that verifies it can charge the holder of a badge less for creating queues, file chunks and notification tokens (under the planned proof-of-work scheme, the required effort is divided by a factor configured for each badge type), and can apply higher rate limits and larger quotas.
  • +
  • Subscriptions that renew automatically, the transfer of remaining months to a new device, and the pausing of a prepaid badge are planned.
  • +
+
+
+
+
+
+ + + + + + + + + + \ No newline at end of file