mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-25 01:43:24 +00:00
deploy: b1dc25dc3e
This commit is contained in:
1 parent
6982e9c568
commit
f7de68a5f4
2 files changed
+24
-9
No files matched your search
@@ -992,7 +992,7 @@ window.addEventListener('click',(e)=>{
|
||||
<ul>
|
||||
<li>A badge does not restrict anything that is available today: the defaults are unchanged, and a badge only raises them.</li>
|
||||
<li>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.</li>
|
||||
<li>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.</li>
|
||||
<li>A badge cannot be transferred: a code can be redeemed once for each use it was issued with (usually one), and the credential obtained with it is usable only with the master key it was issued for.</li>
|
||||
<li>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.</li>
|
||||
<li>A badge cannot be revoked; credentials are issued for one month at a time instead.</li>
|
||||
</ul>
|
||||
@@ -1030,9 +1030,9 @@ window.addEventListener('click',(e)=>{
|
||||
<p>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.</p>
|
||||
<h3 id="buying-and-redeeming" tabindex="-1">Buying and redeeming</h3>
|
||||
<p>A badge is bought for one or more months, either on the web or in the app.</p>
|
||||
<p>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.</p>
|
||||
<p>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, or by a moderator of the service's managed group, and redeemed in the same way.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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 its latest credential; a code redeemed with a different purchase key is refused once all its uses are taken.</p>
|
||||
<h3 id="monthly-issuance" tabindex="-1">Monthly issuance</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
@@ -1070,7 +1070,7 @@ window.addEventListener('click',(e)=>{
|
||||
<li>A proof discloses no value that links it to another proof or to the purchase.</li>
|
||||
<li>A proof used in one context, whether a session, a conversation or a file, cannot be used in another context.</li>
|
||||
<li>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.</li>
|
||||
<li>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.</li>
|
||||
<li>Requests to the badge service cannot be linked to each other, to a profile or to a network address, other than the redemptions of one multi-use code and the codes issued in the service's managed group or redeemed by its members, and a request about a purchase can be made only by the holder of the purchase key.</li>
|
||||
<li>A credential cannot be forged: the issuer keys are fixed in apps and servers, and the app verifies a credential before storing it.</li>
|
||||
<li>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.</li>
|
||||
</ol>
|
||||
@@ -1081,10 +1081,12 @@ window.addEventListener('click',(e)=>{
|
||||
<ul>
|
||||
<li>See the purchase key of every badge, the master key generated for it, the number of months bought, and the payment record.</li>
|
||||
<li>Issue any credential, or refuse to issue one, as it holds the issuer key.</li>
|
||||
<li>Link a code issued in its managed group to the member who asked for it and to that group, as the group's messages, codes included, are kept in the service's chat database.</li>
|
||||
<li>Link a redemption to a member of its managed group whose profile shows a new badge soon after it.</li>
|
||||
</ul>
|
||||
<p><em>cannot:</em></p>
|
||||
<ul>
|
||||
<li>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.</li>
|
||||
<li>Connect a purchase with a profile, a contact, a group or a session with a server, other than a code issued in its managed group or redeemed by one of its members, as above - a proof contains nothing that refers back to the purchase.</li>
|
||||
<li>Learn where a badge is shown or used.</li>
|
||||
<li>Learn the network address of the app - requests reach the service through SMP servers, on connections created for the request.</li>
|
||||
</ul>
|
||||
@@ -1099,6 +1101,19 @@ window.addEventListener('click',(e)=>{
|
||||
<li>Reuse a profile proof or a file proof in another context.</li>
|
||||
<li>Distinguish the holder from the other supporters whose badges expire in the same week.</li>
|
||||
</ul>
|
||||
<p><strong>A member of the badge service's managed group</strong></p>
|
||||
<p><em>can:</em></p>
|
||||
<ul>
|
||||
<li>Redeem any code issued in the group that is not revoked and has uses left, as every code is posted to the group.</li>
|
||||
<li>See when each use of a multi-use code was redeemed.</li>
|
||||
<li>Issue free codes of any type as a moderator or above, and revoke any code whose text they hold as an admin or above.</li>
|
||||
<li>Become the group's owner by being the first to join it, before an owner is set up.</li>
|
||||
<li>Guess who redeemed a code, when a member's profile shows a new badge soon after the code's message changes.</li>
|
||||
</ul>
|
||||
<p><em>cannot:</em></p>
|
||||
<ul>
|
||||
<li>Redeem a revoked code, or a code with no uses left.</li>
|
||||
</ul>
|
||||
<p><strong>A server operator</strong></p>
|
||||
<p><em>can:</em></p>
|
||||
<ul>
|
||||
@@ -1127,7 +1142,7 @@ window.addEventListener('click',(e)=>{
|
||||
<p><strong>Compromise of the user's device or backup</strong></p>
|
||||
<p>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.</p>
|
||||
<p><strong>Interception of a code</strong></p>
|
||||
<p>A code is a bearer secret until it is redeemed; once redeemed, it is refused to any other purchase key.</p>
|
||||
<p>A code is a bearer secret until its last use is taken; after that, it is refused to any other purchase key.</p>
|
||||
<p><strong>A passive network observer</strong></p>
|
||||
<p><em>can:</em></p>
|
||||
<ul>
|
||||
|
||||
@@ -877,7 +877,7 @@ window.addEventListener('click',(e)=>{
|
||||
<p>A timeout hides the outcome, so the client repeats the identical signed request at its next trigger, never on a poll timer.</p>
|
||||
<ul>
|
||||
<li><code>getBadgeInvoice</code> — returns the open invoice again; a new invoice is created only when none is open.</li>
|
||||
<li><code>redeemBadgeCode</code> — a code already redeemed by the signing key returns the same <code>badgeCredential</code> and writes nothing; redeemed by another key, <code>code_used</code>. The client must therefore keep the key it first signed with, or a retry cannot be recognised.</li>
|
||||
<li><code>redeemBadgeCode</code> — a code already redeemed by the signing key returns its latest <code>badgeCredential</code> and writes nothing; once other keys have taken all its uses, <code>code_used</code>. The client must therefore keep the key it first signed with, or a retry cannot be recognised.</li>
|
||||
<li><code>purchaseBadge</code> — a payment already credited returns the same <code>badgeCredential</code> and writes nothing.</li>
|
||||
<li><code>upgradeBadgeSubscription</code> — evidence already applied returns the same result and writes nothing.</li>
|
||||
<li><code>issueBadge</code> — repeated within an issued period, returns the cached credential and writes nothing.</li>
|
||||
@@ -888,7 +888,7 @@ window.addEventListener('click',(e)=>{
|
||||
<ul>
|
||||
<li><code>getBadgeCatalog</code> → <code>badgeCatalog</code> — the prices and offers; signed, also the purchase's <code>badgeStatement</code>. Store builds never send it: prices come from the store and SKUs from app config.</li>
|
||||
<li><code>getBadgeInvoice</code> → <code>badgeInvoice</code> — prices the purchase for <code>badgeInfo</code> and <code>paymentVia</code> (<code>card</code> — Stripe; <code>crypto</code> — btc, xmr). The response holds the generic <code>invoice</code> — <code>invoiceId</code>, <code>price</code>, <code>discount</code>, the upgrade <code>credit</code>, <code>amount</code> = price − discount − credit, <code>currency</code>, <code>expiresAt</code>, and <code>paymentTo</code> (<code>url</code> for card; <code>address</code> and <code>cryptoAmount</code> for crypto) — beside the badge part, <code>badgeType</code> and <code>months</code>. <code>priceId</code> pins the price the client displayed; <code>offerId</code> selects a discounted duration, and its absence buys one month at that price. Price and offer status is checked here only: <code>deprecated</code> is still accepted, <code>disabled</code> is rejected; a badge type with no active price yields <code>product_unavailable</code>.</li>
|
||||
<li><code>redeemBadgeCode</code> → <code>badgeCredential</code> — redeems a code, records the credit, and issues the first credential, in one round trip. It carries <code>masterKey</code> and <code>code</code> and no <code>badgeRequest</code>: a code states no tier and no expiry, so the credential is what reports them. Errors: <code>code_invalid</code> for an unknown or malformed code, <code>code_used</code> when another key redeemed it, <code>code_expired</code> past a redemption deadline.</li>
|
||||
<li><code>redeemBadgeCode</code> → <code>badgeCredential</code> — redeems a code, records the credit, and issues the first credential, in one round trip. It carries <code>masterKey</code> and <code>code</code> and no <code>badgeRequest</code>: a code states no tier and no expiry, so the credential is what reports them. Errors: <code>code_invalid</code> for an unknown or malformed code, <code>code_used</code> when other keys have taken all its uses (a code has one use unless issued with more), <code>code_expired</code> past a redemption deadline.</li>
|
||||
<li><code>purchaseBadge</code> → <code>badgeCredential</code> — verifies the funding (<code>apple</code> JWS offline; <code>google</code> token via the Publisher API; <code>invoice</code> against webhook-confirmed settlement, <code>payment_pending</code> until it lands; <code>receipt</code>), records the credit, and issues the first credential, in one round trip. The response <code>receipt</code> is the recovery bearer secret (model § recovery); the service stores its hash; lifetime badges receive none.
|
||||
<ul>
|
||||
<li>Funding by <code>receipt</code> is a transfer (post-MVP): the unissued months of the purchase that receipt belongs to move to the signing key, recorded as <code>debit(transferOut)</code> on the source and <code>credit(transferIn)</code> on the new purchase, and the presented receipt is retired for a fresh one. The transferred period's issuance debits a month like any other. Lifetime badges hold no receipt, so support handles them.</li>
|
||||
@@ -914,7 +914,7 @@ window.addEventListener('click',(e)=>{
|
||||
<p><code>balance</code> — <code>lastEntry</code>, the client's last entry, asserting the position and the months it believes it holds.</p>
|
||||
<p>An assertion that names an entry the service holds is a prefix: the service proceeds and returns what follows it. Otherwise the service heals its own ledger first — provider evidence for charges, time for lapses — proceeds, and returns either the complete history or one <code>opening</code> credit. An <code>opening</code> entry is an absolute restatement: the ledger is reset to the amount it states, without relation to the preceding entry, which also serves a new device and, later, the discarding of old history into a brought-forward balance.</p>
|
||||
<h2 id="errors" tabindex="-1">Errors</h2>
|
||||
<p><code>retryAfter</code> marks the transient codes: <code>payment_pending</code>, <code>provider_unavailable</code>, <code>rate_limited</code>. <code>offer_disabled</code> calls for a catalog refresh. <code>code_invalid</code> covers unknown, malformed and revoked codes alike, so a guesser learns nothing from the difference; <code>code_used</code> — redeemed under another key; <code>code_expired</code> — past its redemption deadline. <code>receipt_invalid</code> covers unknown receipts. All other codes are terminal for the attempted command.</p>
|
||||
<p><code>retryAfter</code> marks the transient codes: <code>payment_pending</code>, <code>provider_unavailable</code>, <code>rate_limited</code>. <code>offer_disabled</code> calls for a catalog refresh. <code>code_invalid</code> covers unknown, malformed and revoked codes alike, so a guesser learns nothing from the difference; <code>code_used</code> — all its uses taken by other keys; <code>code_expired</code> — past its redemption deadline. <code>receipt_invalid</code> covers unknown receipts. All other codes are terminal for the attempted command.</p>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
Reference in new issue
Block a user