From 8b2af4db62f0570043bb1c9f453b2ad59dcb76d7 Mon Sep 17 00:00:00 2001 From: rosetta Date: Fri, 31 Jul 2026 05:06:37 -0700 Subject: [PATCH] Improve support and technical references --- src/pages/EncryptionPage.module.css | 44 +++++ src/pages/EncryptionPage.tsx | 185 +++++++++++++++++- src/pages/ProtoPage.module.css | 29 +-- src/pages/ProtoPage.tsx | 168 ++++++++++++++-- src/pages/SupportPage.module.css | 187 ++++++++++++++---- src/pages/SupportPage.tsx | 285 +++++++++++++++++++--------- 6 files changed, 717 insertions(+), 181 deletions(-) diff --git a/src/pages/EncryptionPage.module.css b/src/pages/EncryptionPage.module.css index 4137378..3a7c3b0 100644 --- a/src/pages/EncryptionPage.module.css +++ b/src/pages/EncryptionPage.module.css @@ -28,3 +28,47 @@ font-size: var(--mantine-font-size-sm); line-height: 1.7; } + +.callout { + margin: 18px 0 26px; + border: 1px solid light-dark(var(--mantine-color-blue-2), var(--mantine-color-blue-9)); + border-left: 3px solid var(--mantine-color-blue-6); + background: light-dark(var(--mantine-color-blue-0), rgba(25, 113, 194, 0.12)); +} + +.vectorGroup { + margin: 18px 0 24px; + border-top: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5)); +} + +.vectorValue { + padding: 14px 0; + border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5)); +} + +.vectorValue code { + display: block; + margin-top: 7px; + padding: 10px 12px; + background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7)); + font-size: 11px; + line-height: 1.6; + overflow-x: auto; + white-space: pre; +} + +.marker { + padding: 1px 3px; + background: light-dark(var(--mantine-color-blue-1), rgba(25, 113, 194, 0.24)); + color: inherit; +} + +.result { + margin: 18px 0; + padding: 12px 14px; + border-left: 3px solid var(--mantine-color-blue-6); + background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7)); + color: light-dark(var(--mantine-color-blue-9), var(--mantine-color-blue-2)); + font-size: var(--mantine-font-size-sm); + font-weight: 650; +} diff --git a/src/pages/EncryptionPage.tsx b/src/pages/EncryptionPage.tsx index 20e9020..ddb2b6a 100644 --- a/src/pages/EncryptionPage.tsx +++ b/src/pages/EncryptionPage.tsx @@ -1,4 +1,4 @@ -import { Anchor, Code, List, Table, Text, Title } from '@mantine/core'; +import { Anchor, Box, Code, List, Paper, Table, Text, Title } from '@mantine/core'; import { LegalList, LegalPage, @@ -9,12 +9,35 @@ import { SEO } from '../components/SEO/SEO'; import classes from './EncryptionPage.module.css'; const ATTACHMENT_LAYOUT = [ - ['Magic', '4 bytes', 'ASCII RAC2 (52 41 43 32)'], + ['Magic', '4 bytes', 'ASCII RAC1 (52 41 43 31)'], ['Salt', '16 bytes', 'Random per attachment encryption session'], ['IV', '12 bytes', 'Random for every encrypted chunk'], ['Ciphertext + tag', 'Variable', 'AES-256-GCM output and authentication tag'], ]; +const MESSAGE_TEST_VECTOR = { + plaintextHex: '48656c6c6f20526f7365747461', + contentKey: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + nonce: '202122232425262728292a2b2c2d2e2f3031323334353637', + ciphertextAndTag: '553c21a7141746e44bdb24c9299d28c1e78d59d10231cdfe5d9aa1d200', + recipientPrivate: '0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20', + recipientPublic: + '0484bf7562262bbd6940085748f3be6afa52ae317155181ece31b66351ccffa4b08cc43d63b2859d469fee15f31c9edb5324266e6fd0407e87382d60fc4511acd8', + ephemeralPrivate: '2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40', + ephemeralPublic: + '04207bba70bc66309baa582a6ac120fd52d68026c51f6326f8ccedcbd2c1b7eb828c18ff7dbee879a4335a05294dea1e99e251f4b3e3b020b507f87064993fb202', + sharedPoint: + '04f40bb548d147b2c3cdcf635658c3fca87a3c1f85c7ac006e9b9e80d4dc70d84de0be123a86ba5e43cadc1364b8e4ea9542e63a1a8ad1a50263c6080826ef2357', + wrappingKey: 'f40bb548d147b2c3cdcf635658c3fca87a3c1f85c7ac006e9b9e80d4dc70d84d', + wrappingIv: '4142434445464748494a4b4c4d4e4f50', + messageSecret: + '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637', + wrappedSecret: + '44651a40b3dd1ceecbb8f2acfd8229e60a6554fbfaea9838142e898c89d18ede34cc3328ddb3cab3eea77008120228ba4518a0b9b8022a510b79565b806fd208', + envelope: + 'NDE0MjQzNDQ0NTQ2NDc0ODQ5NGE0YjRjNGQ0ZTRmNTA6NDQ2NTFhNDBiM2RkMWNlZWNiYjhmMmFjZmQ4MjI5ZTYwYTY1NTRmYmZhZWE5ODM4MTQyZTg5OGM4OWQxOGVkZTM0Y2MzMzI4ZGRiM2NhYjNlZWE3NzAwODEyMDIyOGJhNDUxOGEwYjliODAyMmE1MTBiNzk1NjViODA2ZmQyMDg6MDQyMDdiYmE3MGJjNjYzMDliYWE1ODJhNmFjMTIwZmQ1MmQ2ODAyNmM1MWY2MzI2ZjhjY2VkY2JkMmMxYjdlYjgyOGMxOGZmN2RiZWU4NzlhNDMzNWEwNTI5NGRlYTFlOTllMjUxZjRiM2UzYjAyMGI1MDdmODcwNjQ5OTNmYjIwMg==', +}; + function TechnicalCode({ children }: { children: string }) { return ( @@ -23,6 +46,17 @@ function TechnicalCode({ children }: { children: string }) { ); } +function TechnicalValue({ label, value }: { label: string; value: string }) { + return ( + + + {label} + + {value} + + ); +} + export function EncryptionPage() { return ( <> @@ -112,7 +146,140 @@ keyEnvelope = Base64(iv || AES-CBC(wrappingKey, messageSecret) || ephemeral.publ - + + + + Test vector. The values below were calculated with the + same XChaCha20-Poly1305, secp256k1 ECDH, and AES-CBC operations used by + the client. Fixed keys and IVs make the result reproducible; they are + deliberately unsafe for production and must never be reused. + + + + + Sender: encode and encrypt the message + + + The UTF-8 string Hello Rosetta occupies 13 bytes. The sender + would normally generate the content key and nonce randomly. This example + substitutes fixed sequences so that every value can be checked exactly. + + + + + + + + + + The result contains 13 encrypted content bytes followed by the 16-byte + Poly1305 authentication tag.{' '} + + Any change to either part causes authenticated decryption to fail. + + + + + Sender: protect the message key for the recipient + + + The 32-byte content key and 24-byte nonce are joined into one logical + 56-byte message secret. An ephemeral secp256k1 key pair and the recipient + public key produce the shared ECDH point. Its 32-byte x-coordinate becomes + the AES key that wraps the message secret. + + + + + + + + + + + + + + + + Recipient: recover and decrypt + + + + Base64 decoding separates the wrapping IV, wrapped message secret, and + ephemeral public key. + + + ECDH with the recipient private key and the transmitted ephemeral + public key produces the same shared point and wrapping key. + + + AES-CBC decryption and PKCS#7 removal recover the exact 56-byte message + secret shown above. + + + The first 32 bytes become the XChaCha20 key and the remaining 24 bytes + become its nonce. Poly1305 verifies the encrypted content before it is + accepted. + + + {`recovered shared point matches: true +recovered message secret: ${MESSAGE_TEST_VECTOR.messageSecret} +XChaCha20-Poly1305 decrypt: 48656c6c6f20526f7365747461 +UTF-8 decode: Hello Rosetta`} + + Authenticated plaintext recovered: Hello Rosetta + + + In a real message, the content key, nonce, ephemeral private key, and + AES-CBC IV are newly generated. Therefore the ciphertext and envelope will + differ even when the plaintext and recipient are identical. + + + + Files, images, audio, and video are encrypted before upload. Binary data is split into independent chunks so that large files can be streamed, retried, @@ -127,7 +294,7 @@ keyEnvelope = Base64(iv || AES-CBC(wrappingKey, messageSecret) || ephemeral.publ data before plaintext is returned. - RAC2 || salt[16] || iv[12] || AES-256-GCM-ciphertext-and-tag + RAC1 || salt[16] || iv[12] || AES-256-GCM-ciphertext-and-tag @@ -155,7 +322,7 @@ keyEnvelope = Base64(iv || AES-CBC(wrappingKey, messageSecret) || ephemeral.publ - + A group uses a shared group secret available to its members. Group messages do not carry a new recipient envelope for every member. The current group @@ -170,12 +337,12 @@ keyEnvelope = Base64(iv || AES-CBC(wrappingKey, messageSecret) || ephemeral.publ → Base64(iv) : Base64(ciphertext)`} This compatibility format provides confidentiality but does not include an - AEAD authentication tag. Binary attachments use the newer RAC2 AES-GCM + AEAD authentication tag. Binary attachments use the RAC1 AES-GCM format described above. - + The recovery phrase is expanded to a BIP39 seed. SHA-256 of that seed becomes the 32-byte secp256k1 private key, and the client derives the @@ -189,7 +356,7 @@ keyEnvelope = Base64(iv || AES-CBC(wrappingKey, messageSecret) || ephemeral.publ - + End-to-end encryption protects direct-message content and binary attachment contents. It does not hide sender and recipient identifiers, timestamps, @@ -205,7 +372,7 @@ keyEnvelope = Base64(iv || AES-CBC(wrappingKey, messageSecret) || ephemeral.publ - + AEAD: authenticated encryption that provides diff --git a/src/pages/ProtoPage.module.css b/src/pages/ProtoPage.module.css index 4fe7e3c..746d2ee 100644 --- a/src/pages/ProtoPage.module.css +++ b/src/pages/ProtoPage.module.css @@ -11,6 +11,11 @@ white-space: pre; } +.comparisonTable { + margin: 18px 0 24px; + border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5)); +} + .docsLink { display: inline-block; margin-top: 44px; @@ -24,25 +29,6 @@ background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-9)); } -.hero { - max-width: 760px; - padding: 48px 0 44px; -} - -.hero h1 { - color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); - font-size: clamp(32px, 5vw, 44px); - font-weight: 600; - letter-spacing: -0.03em; - line-height: 1.08; -} - -.heroDescription { - margin-top: var(--mantine-spacing-md); - font-size: 16px; - line-height: 1.7; -} - .stats { margin: 0 0 52px; } @@ -94,7 +80,6 @@ display: grid; gap: var(--mantine-spacing-sm); margin-bottom: 16px; - background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-8)); } .schemaSection { @@ -196,10 +181,6 @@ padding-top: 28px; } - .hero { - padding-top: 38px; - } - .docsGrid { gap: 28px; } diff --git a/src/pages/ProtoPage.tsx b/src/pages/ProtoPage.tsx index 8aaf0de..67c05c6 100644 --- a/src/pages/ProtoPage.tsx +++ b/src/pages/ProtoPage.tsx @@ -383,6 +383,153 @@ field_id:uint8 | wire_type:uint8 | [length:uint16] | value + + + RCC reads versioned files named layer.N.yml. The top-level + version identifies the protocol layer, while + description records its purpose. Definitions are divided into + enumerations, reusable types, and packets. The YAML is a build-time schema; + it is not transmitted with every network request. + + + {`version: 3 +description: Add avatars and more CDN safety + +enums: + HandshakeStage: + type: int8 + values: + - id: 0 + name: COMPLETED + - id: 1 + name: NEED_DEVICE_VERIFICATION + +types: + NetworkDevice: + fields: + - id: 1 + name: networkStatus + type: NetworkStatus + - id: 2 + name: deviceName + type: string + +packets: + Handshake: + id: 0 + fields: + - id: 1 + name: publicKey + type: string + - id: 6 + name: handshakeStage? + type: HandshakeStage`} + + + + An enum declares its integer wire type and the stable decimal ID of each + value. + + + A type is a reusable TLV structure. It has fields but no packet ID of + its own. + + + A packet has a two-byte packet ID. Each field has its own one-byte ID, + name, and primitive, enum, or reusable type. + + + A question mark at the end of a field name makes that field optional; + the suffix is removed from the generated property name. The field is + omitted from the wire payload when no value is present. + + + A [] suffix declares a list. RCC serializes a list by + repeating the same field ID for every item. + + + YAML comments become generated API documentation. RCC also checks type + references, required fields, and stable field-ID use between layers. + + + + + + + RCC converts every definition into a model and a codec. Required fields are + checked before encoding and after decoding. Enum names remain readable in + application code, but only their numeric values are written to the network. + The server build can retain codecs for several layers while a client build + uses the current layer. + + + {`# RCC schema +Result: + id: 2 + fields: + - id: 1 + name: resultCode + type: ResultCode + +# Generated codec behavior +packet id 2 +writer.writeInt8(fieldId: 1, value: ResultCode.SUCCESS /* 0 */) +reader.getInt8(fieldId: 1) -> ResultCode.SUCCESS`} + + + + + + For a Result packet whose value is + ResultCode.SUCCESS, the complete RCC/TLV payload is five + bytes. The packet ID occupies two bytes; the field ID, wire type, and + int8 value occupy one byte each. + + + {`00 02 | 01 01 00 +^^^^^ ^^^^^^^^ +packet field 1 · INT8 · SUCCESS (0)`} + + +
+ + + Encoding + Payload + Size + TLV reduction + + + + + RCC / TLV + 00 02 01 01 00 + 5 bytes + — + + + JSON with enum name + {'{"resultCode":"SUCCESS"}'} + 24 bytes + 79% + + + JSON with numeric enum + {'{"resultCode":0}'} + 16 bytes + 69% + + +
+
+ + This is a deliberately small packet and excludes transport framing and + compression. The percentage varies with the data: long strings and files + dominate their field names, while small control packets receive the largest + relative benefit from numeric IDs and binary values. + +
+ See docs of PROTO → @@ -425,23 +572,6 @@ export function ProtoDocsPage() {
- - Rosetta - - - {' '} - / Proto / Docs - - - - Protocol documentation - - Complete protocol reference generated from the RCC layer schemas. Browse - packet definitions, types, enumerations, field IDs, optional values, - comments, and linked types. - - - - + setSearch(event.currentTarget.value)} @@ -586,7 +716,7 @@ export function ProtoDocsPage() { {resultCount} definitions in layer {layer.version} - + {[ diff --git a/src/pages/SupportPage.module.css b/src/pages/SupportPage.module.css index 59d3263..f8e0dc2 100644 --- a/src/pages/SupportPage.module.css +++ b/src/pages/SupportPage.module.css @@ -1,72 +1,179 @@ -.intro { - margin-top: 36px; - color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1)); - font-size: var(--mantine-font-size-md); - line-height: 1.7; +.page { + min-height: 70vh; + background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-8)); } -.searchArea { - margin-top: 28px; +.hero { + position: relative; + overflow: visible; + background: light-dark(var(--mantine-color-blue-0), rgba(25, 113, 194, 0.16)); } -.problemField textarea { - padding-top: 16px; - padding-left: 48px; - background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7)); - font-size: var(--mantine-font-size-md); - line-height: 1.65; +.heroInner { + position: relative; + min-height: 390px; + padding-top: 76px; + padding-bottom: 88px; +} + +.heroContent { + position: relative; + z-index: 2; +} + +.heroContent h1 { + color: light-dark(var(--mantine-color-dark-9), var(--mantine-color-white)); + font-size: clamp(34px, 6vw, 48px); + font-weight: 600; + letter-spacing: -0.035em; +} + +.heroDescription { + max-width: 580px; + font-size: 16px; +} + +.searchWrap { + position: relative; + width: min(100%, 680px); + margin-top: 24px; +} + +.searchInput input { + height: 64px; + padding-inline: 58px; + border-color: transparent; + background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7)); + box-shadow: 0 12px 38px rgba(25, 113, 194, 0.16); + font-size: 17px; +} + +.searchInput input:focus { + border-color: var(--mantine-color-blue-5); } .suggestions { - margin-top: 8px; - padding: 14px; + position: absolute; + z-index: 10; + top: calc(100% + 8px); + right: 0; + left: 0; + padding: 8px; + background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7)); } .suggestion { - display: block; - padding: 12px 10px; - border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 12px; + align-items: start; + padding: 13px 12px; + border-radius: var(--mantine-radius-sm); color: inherit; text-decoration: none; } -.suggestion:first-child { - border-top: 0; +.suggestion svg { + margin-top: 2px; + color: var(--mantine-color-blue-6); } .suggestion:hover { - background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7)); + background: light-dark(var(--mantine-color-blue-0), var(--mantine-color-dark-6)); text-decoration: none; } -.issuesSection, -.contact { - margin-top: 48px; - padding-top: 36px; - border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); +.decorativeIcon { + position: absolute; + z-index: 1; + color: var(--mantine-color-blue-6); + opacity: 0.62; + box-shadow: var(--mantine-shadow-sm); } -.issuesSection > h2, -.contact > h2 { - font-size: 22px; - font-weight: 650; +.iconOne { + top: 62px; + left: 5%; + transform: rotate(-9deg); } -.issueList { - margin-top: 20px; +.iconTwo { + top: 215px; + left: 12%; + transform: rotate(8deg); +} + +.iconThree { + top: 70px; + right: 6%; + transform: rotate(10deg); +} + +.iconFour { + right: 14%; + bottom: 64px; + transform: rotate(-7deg); +} + +.content { + padding-top: 88px; + padding-bottom: 104px; +} + +.content h2 { + font-size: 26px; + font-weight: 600; +} + +.sectionDescription { + max-width: 620px; } .issue { - scroll-margin-top: 100px; - padding: 26px 0; - border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); -} - -.issue:first-child { - border-top: 0; + scroll-margin-top: 96px; + border-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); + background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7)); } .issue h3 { - font-size: 17px; + font-size: 18px; font-weight: 650; } + +.contact { + display: flex; + align-items: center; + justify-content: space-between; + gap: 32px; + margin-top: 72px; + padding: 32px; + background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7)); +} + +@media (max-width: 48em) { + .heroInner { + min-height: 360px; + padding-top: 64px; + } + + .decorativeIcon { + opacity: 0.28; + } + + .iconTwo, + .iconFour { + display: none; + } + + .content { + padding-top: 72px; + padding-bottom: 80px; + } + + .contact { + align-items: flex-start; + flex-direction: column; + margin-top: 56px; + padding: 26px 22px; + } +} diff --git a/src/pages/SupportPage.tsx b/src/pages/SupportPage.tsx index b4b54c6..61ba0b8 100644 --- a/src/pages/SupportPage.tsx +++ b/src/pages/SupportPage.tsx @@ -1,9 +1,34 @@ -import { Anchor, Box, Button, Paper, Stack, Text, Textarea, Title } from '@mantine/core'; -import { IconMail, IconSearch } from '@tabler/icons-react'; +import { + ActionIcon, + Anchor, + Box, + Button, + Card, + Container, + Group, + Paper, + SimpleGrid, + Stack, + Text, + TextInput, + ThemeIcon, + Title, +} from '@mantine/core'; +import { + IconBook2, + IconHelpCircle, + IconLifebuoy, + IconMail, + IconMessageQuestion, + IconSearch, + IconShieldQuestion, + IconX, +} from '@tabler/icons-react'; import { useMemo, useState } from 'react'; import knownIssues from '../../known-issues.json'; -import { LegalPage } from '../components/LegalPage/LegalPage'; import { SEO } from '../components/SEO/SEO'; +import { SiteFooter } from '../components/SiteFooter/SiteFooter'; +import { SiteHeader } from '../components/SiteHeader/SiteHeader'; import classes from './SupportPage.module.css'; export function SupportPage() { @@ -30,7 +55,7 @@ export function SupportPage() { }) .filter(({ score }) => score > 0) .sort((left, right) => right.score - left.score) - .slice(0, 4); + .slice(0, 5); }, [normalizedQuery]); return ( @@ -41,98 +66,180 @@ export function SupportPage() { canonical="https://rosetta.im/support/" keywords="Rosetta support, known issues, account recovery, messaging help" /> - - - Describe the problem in your own words. Rosetta will suggest a relevant known - issue while you type. The text is processed only in this page and is not sent - to the support service. - + +
+ + + + + + - -