Repository navigation
Expand file tree
/
Copy pathdocs.html
More file actions
2034 lines (1760 loc) · 173 KB
/
Copy pathdocs.html
File metadata and controls
2034 lines (1760 loc) · 173 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Stade Documentation — guides, references and resources for the serverless, end-to-end encrypted, post-quantum messaging app." />
<meta name="theme-color" content="#0a0a0a" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<title>Docs — Stade</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0a0a0a;
--bg-elev: #101010;
--bg-card: rgba(26,26,26,0.6);
--bg-card-hover: rgba(34,34,34,0.82);
--border: rgba(140,140,140,0.1);
--border-strong: rgba(170,170,170,0.2);
--border-accent: rgba(224,224,224,0.32);
--text: #f0f0f0;
--text-muted: #8a8a8a;
--text-dim: #545454;
--accent: #e0e0e0;
--accent-light: #ffffff;
--accent-2: #b0b0b0;
--accent-3: #cacaca;
--accent-glow: rgba(224,224,224,0.38);
--gradient: linear-gradient(135deg, #e0e0e0 0%, #b0b0b0 100%);
--radius: 16px;
--radius-lg: 24px;
--sidebar-w: 280px;
--toc-w: 232px;
--nav-h: 65px;
}
html { scroll-behavior: smooth; }
body {
font-family: 'Google Sans', 'Product Sans', system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.7;
-webkit-font-smoothing: antialiased;
}
body::before {
content: '';
position: fixed; inset: 0;
background:
radial-gradient(ellipse 70% 45% at 10% -10%, rgba(224,224,224,0.14), transparent 55%),
radial-gradient(ellipse 45% 35% at 95% 4%, rgba(176,176,176,0.1), transparent 55%);
pointer-events: none; z-index: 0;
}
a { color: inherit; text-decoration: none; }
[hidden] { display: none !important; }
nav {
position: sticky; top: 0; z-index: 100;
height: var(--nav-h);
backdrop-filter: blur(22px) saturate(180%);
-webkit-backdrop-filter: blur(22px) saturate(180%);
background: rgba(10,10,10,0.82);
border-bottom: 1px solid var(--border);
}
.nav-inner {
display: flex; align-items: center; justify-content: space-between;
padding: 0 28px; height: 100%; max-width: 1440px; margin: 0 auto; gap: 20px;
}
.nav-left { display: flex; align-items: center; gap: 16px; }
.logo {
display: flex; align-items: center; gap: 10px;
font-weight: 700; font-size: 18px; letter-spacing: -0.01em; color: var(--text); flex-shrink: 0;
}
.logo-mark { width: 32px; height: 32px; border-radius: 9px; display: block; object-fit: cover; flex-shrink: 0; }
.docs-tag {
font-size: 12px; font-weight: 600; font-family: 'JetBrains Mono', monospace;
color: var(--accent-light); padding: 3px 9px; border-radius: 6px;
background: rgba(224,224,224,0.1); border: 1px solid var(--border-accent);
}
.nav-links { display: flex; gap: 4px; list-style: none; }
.nav-links a {
color: var(--text-muted); font-size: 14px; font-weight: 500;
padding: 6px 13px; border-radius: 8px; transition: all 0.2s;
}
.nav-links a:hover { color: var(--text); background: rgba(255,255,255,0.06); }
.nav-right { display: flex; align-items: center; gap: 10px; }
.nav-cta {
display: inline-flex; align-items: center; gap: 7px;
padding: 8px 18px; border-radius: 10px;
background: rgba(255,255,255,0.05); border: 1px solid var(--border-strong);
color: var(--text); font-size: 14px; font-weight: 500; transition: all 0.2s; white-space: nowrap;
}
.nav-cta:hover { background: rgba(255,255,255,0.09); border-color: rgba(255,255,255,0.25); }
.lang-toggle {
display: inline-flex; align-items: center; justify-content: center;
min-width: 38px; height: 38px; padding: 0 12px; flex-shrink: 0;
background: none; border: 1px solid var(--border-strong); border-radius: 8px;
cursor: pointer; color: var(--text); font-family: 'JetBrains Mono', monospace;
font-size: 12px; font-weight: 700; letter-spacing: 0.04em; transition: all 0.2s;
}
.lang-toggle:hover { background: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.3); }
.sidebar-toggle {
display: none; width: 38px; height: 38px; flex-shrink: 0;
background: none; border: 1px solid var(--border-strong); border-radius: 8px;
cursor: pointer; color: var(--text); align-items: center; justify-content: center; padding: 0;
}
.sidebar-toggle svg { width: 18px; height: 18px; }
.docs-shell {
display: grid;
grid-template-columns: var(--sidebar-w) minmax(0, 1fr) var(--toc-w);
max-width: 1440px; margin: 0 auto; position: relative; z-index: 1;
}
.sidebar {
position: sticky; top: var(--nav-h);
height: calc(100vh - var(--nav-h));
border-right: 1px solid var(--border);
}
.sidebar-scroll {
height: 100%; overflow-y: auto;
padding: 28px 18px 48px;
}
.sidebar-search {
display: flex; align-items: center; gap: 9px; width: 100%;
padding: 9px 13px; border-radius: 10px; font-family: inherit; text-align: left;
background: rgba(0,0,0,0.32); border: 1px solid var(--border-strong);
color: var(--text-dim); font-size: 13px; margin-bottom: 26px; cursor: pointer;
transition: border-color 0.18s, color 0.18s;
}
.sidebar-search:hover { border-color: var(--border-accent); color: var(--text-muted); }
.sidebar-search svg { width: 15px; height: 15px; flex-shrink: 0; }
.sidebar-search .kbd {
margin-left: auto; font-family: 'JetBrains Mono', monospace; font-size: 11px;
padding: 2px 7px; border-radius: 5px; background: rgba(255,255,255,0.05); border: 1px solid var(--border);
}
.nav-group { margin-bottom: 28px; }
.nav-group-title {
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
color: var(--text-dim); padding: 0 12px; margin-bottom: 10px;
}
.nav-group ul { list-style: none; display: flex; flex-direction: column; gap: 2px; }
.nav-group a {
display: block; padding: 7px 12px; border-radius: 8px;
font-size: 14px; color: var(--text-muted); transition: all 0.18s; cursor: pointer;
}
.nav-group a:hover { color: var(--text); background: rgba(255,255,255,0.05); }
.nav-group a.active {
color: var(--accent-light); background: rgba(224,224,224,0.1);
box-shadow: inset 2px 0 0 var(--accent);
}
.doc-main { min-width: 0; padding: 48px 56px 96px; }
.breadcrumb {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
font-size: 13px; color: var(--text-dim); margin-bottom: 22px;
}
.breadcrumb span.sep { opacity: 0.5; }
.breadcrumb a { cursor: pointer; }
.breadcrumb a:hover { color: var(--text-muted); }
.breadcrumb .current { color: var(--text-muted); }
.doc-article { max-width: 760px; }
.doc-article h1 {
font-size: clamp(30px, 4vw, 42px); font-weight: 700;
letter-spacing: -0.02em; line-height: 1.12; margin-bottom: 16px;
}
.doc-lead { font-size: 18px; color: var(--text-muted); line-height: 1.65; margin-bottom: 40px; }
.doc-article h2 {
font-size: 24px; font-weight: 700; letter-spacing: -0.01em;
margin: 52px 0 16px; scroll-margin-top: calc(var(--nav-h) + 24px);
}
.doc-article h3 {
font-size: 18px; font-weight: 600; margin: 32px 0 12px;
scroll-margin-top: calc(var(--nav-h) + 24px);
}
.doc-article p { color: var(--text-muted); font-size: 15.5px; margin-bottom: 18px; }
.doc-article ul, .doc-article ol { color: var(--text-muted); font-size: 15.5px; margin: 0 0 18px; padding-left: 22px; }
.doc-article li { margin-bottom: 8px; }
.doc-article li::marker { color: var(--accent); }
.doc-article strong { color: var(--text); font-weight: 600; }
.doc-article a.inline { color: var(--accent-light); cursor: pointer; }
.doc-article a.inline:hover { text-decoration: underline; }
.doc-article hr { border: none; border-top: 1px solid var(--border); margin: 40px 0; }
code {
font-family: 'JetBrains Mono', monospace; font-size: 0.88em;
background: rgba(0,0,0,0.35); border: 1px solid var(--border);
padding: 1px 6px; border-radius: 5px; color: var(--accent-light);
}
pre {
background: rgba(0,0,0,0.42); border: 1px solid var(--border-strong);
border-radius: var(--radius); padding: 18px 20px; overflow-x: auto; margin: 0 0 22px;
}
pre code { background: none; border: none; padding: 0; color: var(--text); font-size: 13px; line-height: 1.75; }
.spec-table { width: 100%; border-collapse: collapse; margin: 0 0 24px; font-size: 14px; }
.spec-table th, .spec-table td { text-align: left; padding: 11px 16px; border: 1px solid var(--border); vertical-align: top; }
.spec-table th { background: rgba(255,255,255,0.03); color: var(--text-dim); font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; }
.spec-table td { color: var(--text-muted); }
.spec-table td.val { font-family: 'JetBrains Mono', monospace; color: var(--accent-light); font-size: 13px; }
.spec-table dt { color: var(--text); font-weight: 600; }
.pill-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 22px; }
.pill {
font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 500;
padding: 5px 12px; border-radius: 8px;
background: rgba(224,224,224,0.08); border: 1px solid rgba(224,224,224,0.22); color: var(--accent-light);
}
.callout {
display: flex; gap: 14px; padding: 18px 20px; border-radius: var(--radius);
background: rgba(224,224,224,0.06); border: 1px solid var(--border-accent);
margin: 28px 0;
}
.callout svg { width: 20px; height: 20px; color: var(--accent-light); flex-shrink: 0; margin-top: 2px; }
.callout p { margin: 0; font-size: 14.5px; color: var(--text-muted); }
.callout.warn { background: rgba(122,122,122,0.06); border-color: rgba(122,122,122,0.3); }
.callout.warn svg { color: #a0a0a0; }
.callout.ok { background: rgba(202,202,202,0.06); border-color: rgba(202,202,202,0.28); }
.callout.ok svg { color: var(--accent-3); }
.doc-footer-nav {
display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
margin-top: 64px; padding-top: 32px; border-top: 1px solid var(--border);
}
.doc-footer-nav a {
display: flex; flex-direction: column; gap: 4px; padding: 18px 22px;
border-radius: var(--radius); border: 1px solid var(--border);
background: var(--bg-card); transition: all 0.2s; cursor: pointer;
}
.doc-footer-nav a:hover { border-color: var(--border-accent); background: var(--bg-card-hover); }
.doc-footer-nav a.next { text-align: right; }
.doc-footer-nav a.empty { visibility: hidden; }
.doc-footer-nav .dir { font-size: 12px; color: var(--text-dim); }
.doc-footer-nav .ttl { font-size: 15px; font-weight: 600; color: var(--text); }
.toc {
position: sticky; top: var(--nav-h);
height: calc(100vh - var(--nav-h));
overflow-y: auto; padding: 48px 24px;
border-left: 1px solid var(--border);
}
.toc-title {
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
color: var(--text-dim); margin-bottom: 14px;
}
.toc ul { list-style: none; display: flex; flex-direction: column; gap: 2px; }
.toc a {
display: block; padding: 5px 12px; border-radius: 7px;
font-size: 13px; color: var(--text-muted); border-left: 2px solid transparent; transition: all 0.18s;
}
.toc a:hover { color: var(--text); }
.toc a.sub { padding-left: 24px; font-size: 12.5px; }
.toc a.active { color: var(--accent-light); border-left-color: var(--accent); }
.sidebar-scrim {
display: none; position: fixed; inset: var(--nav-h) 0 0; z-index: 90;
background: rgba(6,6,6,0.6); backdrop-filter: blur(2px);
}
.search-overlay {
display: none; position: fixed; inset: 0; z-index: 200;
background: rgba(6,6,6,0.7); backdrop-filter: blur(4px);
padding: 14vh 20px 20px; justify-content: center; align-items: flex-start;
}
.search-overlay.open { display: flex; }
.search-modal {
width: 100%; max-width: 580px; max-height: 70vh; display: flex; flex-direction: column;
background: var(--bg-elev); border: 1px solid var(--border-strong);
border-radius: var(--radius); overflow: hidden;
box-shadow: 0 40px 110px -30px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.03) inset;
}
.search-field { display: flex; align-items: center; gap: 12px; padding: 16px 20px; border-bottom: 1px solid var(--border); }
.search-field svg { width: 18px; height: 18px; color: var(--text-dim); flex-shrink: 0; }
.search-field input {
flex: 1; background: none; border: none; outline: none;
color: var(--text); font-family: inherit; font-size: 16px;
}
.search-field input::placeholder { color: var(--text-dim); }
.search-field .esc {
font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--text-dim);
padding: 3px 8px; border-radius: 5px; background: rgba(255,255,255,0.05); border: 1px solid var(--border);
}
.search-results { overflow-y: auto; padding: 8px; }
.search-result {
display: flex; flex-direction: column; gap: 2px; padding: 10px 14px;
border-radius: 10px; cursor: pointer;
}
.search-result .r-title { font-size: 14.5px; color: var(--text); font-weight: 500; }
.search-result .r-group { font-size: 12px; color: var(--text-dim); }
.search-result:hover, .search-result.active { background: rgba(224,224,224,0.12); }
.search-result.active .r-title { color: var(--accent-light); }
.search-empty { padding: 28px 16px; text-align: center; color: var(--text-dim); font-size: 14px; }
@media (max-width: 1180px) {
.docs-shell { grid-template-columns: var(--sidebar-w) minmax(0, 1fr); }
.toc { display: none; }
.doc-main { padding: 44px 44px 88px; }
}
@media (max-width: 860px) {
.docs-shell { grid-template-columns: 1fr; }
.nav-links { display: none; }
.sidebar-toggle { display: inline-flex; }
.sidebar {
position: fixed; top: var(--nav-h); left: 0; z-index: 95;
width: var(--sidebar-w); max-width: 84vw;
background: var(--bg-elev);
transform: translate3d(-100%,0,0); transition: transform 0.28s ease;
will-change: transform; backface-visibility: hidden;
}
.sidebar.open { transform: translate3d(0,0,0); }
.sidebar-scrim.open { display: block; }
.doc-main { padding: 36px 24px 80px; }
}
@media (max-width: 560px) {
.nav-cta span { display: none; }
.doc-footer-nav { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<nav>
<div class="nav-inner">
<div class="nav-left">
<button class="sidebar-toggle" id="sidebarToggle" data-tr-aria="Kenar çubuğunu aç/kapat" aria-label="Toggle sidebar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
</button>
<a href="index.html" class="logo">
<img src="logo.png" class="logo-mark" alt="Stade" />
Stade
</a>
<span class="docs-tag" data-tr="dokümanlar">docs</span>
</div>
<ul class="nav-links">
<li><a href="index.html#features" data-tr="Özellikler">Features</a></li>
<li><a href="index.html#transport" data-tr="Taşıma">Transport</a></li>
<li><a href="index.html#how" data-tr="Nasıl Çalışır">How It Works</a></li>
<li><a href="index.html#tech" data-tr="Teknik">Technical</a></li>
<li><a href="docs.html" data-tr="Dokümanlar">Docs</a></li>
<li><a href="downloads.html" data-tr="İndirmeler">Downloads</a></li>
<li><a href="articles.html" data-tr="Makaleler">Articles</a></li>
</ul>
<div class="nav-right">
<a href="https://github.com/Stade-App/stade" class="nav-cta" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" fill="currentColor" width="15" height="15"><path d="M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.4-1.8-1.4-1.8-1-.7.1-.7.1-.7 1.2 0 1.9 1.2 1.9 1.2 1 1.8 2.8 1.3 3.5 1 0-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.2.5-2.3 1.3-3.1-.2-.4-.6-1.6 0-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.6.2 2.8.1 3.2.7.8 1.3 1.9 1.3 3.1 0 4.6-2.8 5.6-5.5 5.9.5.4.9 1.1.9 2.3v3.3c0 .3.1.7.8.6A12 12 0 0 0 12 .3"/></svg>
<span>GitHub</span>
</a>
<button class="lang-toggle" id="langToggle" type="button" data-tr-aria="Dili değiştir" aria-label="Switch language">TR</button>
</div>
</div>
</nav>
<div class="docs-shell">
<div class="sidebar-scrim" id="sidebarScrim"></div>
<aside class="sidebar" id="sidebar">
<div class="sidebar-scroll">
<button class="sidebar-search" id="searchTrigger" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<span data-tr="Dokümanlarda ara">Search docs</span>
<span class="kbd">Ctrl K</span>
</button>
<div class="nav-group">
<div class="nav-group-title" data-tr="Başlarken">Getting Started</div>
<ul>
<li><a data-id="introduction" data-tr="Giriş">Introduction</a></li>
<li><a data-id="installation" data-tr="Kurulum">Installation</a></li>
<li><a data-id="quick-start" data-tr="Hızlı Başlangıç">Quick Start</a></li>
<li><a data-id="creating-your-identity" data-tr="Kimliğinizi Oluşturma">Creating Your Identity</a></li>
</ul>
</div>
<div class="nav-group">
<div class="nav-group-title" data-tr="Temel Kavramlar">Core Concepts</div>
<ul>
<li><a data-id="architecture-overview" data-tr="Mimariye Genel Bakış">Architecture Overview</a></li>
<li><a data-id="peer-to-peer-model" data-tr="Eşler Arası Model">Peer-to-Peer Model</a></li>
<li><a data-id="encryption" data-tr="Şifreleme">Encryption</a></li>
<li><a data-id="identity-and-keys" data-tr="Kimlik ve Anahtarlar">Identity & Keys</a></li>
</ul>
</div>
<div class="nav-group">
<div class="nav-group-title" data-tr="Taşıma Katmanları">Transport Layers</div>
<ul>
<li><a data-id="transport-layers" data-tr="Genel Bakış">Overview</a></li>
<li><a data-id="lan">LAN</a></li>
<li><a data-id="tor">Tor</a></li>
</ul>
</div>
<div class="nav-group">
<div class="nav-group-title" data-tr="Rehberler">Guides</div>
<ul>
<li><a data-id="stadium">Stadium</a></li>
<li><a data-id="verifying-a-contact" data-tr="Bir Kişiyi Doğrulama">Verifying a Contact</a></li>
<li><a data-id="locking-the-app" data-tr="Uygulamayı Kilitleme">Locking the App</a></li>
<li><a data-id="troubleshooting" data-tr="Sorun Giderme">Troubleshooting</a></li>
</ul>
</div>
<div class="nav-group">
<div class="nav-group-title" data-tr="Referans">Reference</div>
<ul>
<li><a data-id="cryptographic-primitives" data-tr="Kriptografik Temel Bileşenler">Cryptographic Primitives</a></li>
<li><a data-id="glossary" data-tr="Sözlük">Glossary</a></li>
<li><a data-id="faq" data-tr="SSS">FAQ</a></li>
</ul>
</div>
</div>
</aside>
<main class="doc-main">
<div class="breadcrumb" id="breadcrumb"></div>
<article class="doc-article" id="introduction" data-group="Getting Started" data-title="Introduction" data-group-tr="Başlarken" data-title-tr="Giriş">
<h1>Introduction</h1>
<p class="doc-lead">Stade is a private messenger built on a simple promise: <strong>a message belongs only to the two people having the conversation.</strong> No company sits in the middle, no server stores your chats, and no account ties your identity to a phone number or an email address.</p>
<h2 id="what-makes-stade-different">What makes Stade different</h2>
<p>Most messaging apps route your messages through their servers. Even when those messages are encrypted, the provider still sees who you talk to, when, and from where. Stade removes that middleman entirely:</p>
<ul>
<li><strong>No servers.</strong> Two devices connect <em>directly</em> to each other. The app is the whole network.</li>
<li><strong>No accounts.</strong> You are identified by a cryptographic key you generate on your own device. Nothing is registered anywhere.</li>
<li><strong>End-to-end encryption by default.</strong> Every message is sealed so only the recipient's device can open it. Keys never leave your device.</li>
<li><strong>Post-quantum cryptography.</strong> Stade combines classical elliptic-curve cryptography with NIST's post-quantum algorithms (ML-KEM and ML-DSA), so conversations stay protected even against a future quantum adversary.</li>
<li><strong>Metadata resistance.</strong> Conversations can travel over Tor, hiding not just <em>what</em> you say but <em>who</em> you talk to and <em>where</em> you are.</li>
</ul>
<h2 id="what-you-can-do">What you can do with it</h2>
<ul>
<li>Exchange one-to-one text messages with full end-to-end encryption.</li>
<li>Create and participate in group chats.</li>
<li>Broadcast to an open audience with a <a class="inline" data-go="stadium">Stadium</a>, a public channel where only the admin posts.</li>
<li>Connect over your <strong>local network</strong> (fast, zero-internet) or over <strong>Tor</strong> (anonymous, works across the internet).</li>
<li>Verify a contact's identity with a <strong>safety number</strong>, so you know there is no impostor in the middle.</li>
<li>Lock the app behind a password that encrypts your entire message database at rest.</li>
</ul>
<h2 id="where-it-runs">Where it runs</h2>
<p>Stade is a single application that runs on <strong>Android 8.0 (API 26)</strong> and newer, and on the <strong>desktop</strong> — Windows, Linux and macOS. The same cryptography, identity format and wire protocol are shared across every platform, so an Android user and a desktop user are simply two peers talking to each other.</p>
<div class="callout warn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<p>Because there is no server, Stade cannot read, log, recover or reset anything for you. That is the point — but it also means <strong>you</strong> hold the keys. Lose your device and your backup, and that identity and its conversations are gone.</p>
</div>
<template class="i18n-tr"><h1>Giriş</h1>
<p class="doc-lead">Stade, basit bir vaade dayanan özel bir mesajlaşma uygulamasıdır: <strong>bir mesaj yalnızca sohbeti yapan iki kişiye aittir.</strong> Aradaki bir şirket yoktur, sohbetlerinizi saklayan bir sunucu yoktur ve kimliğinizi bir telefon numarasına veya e-posta adresine bağlayan bir hesap yoktur.</p>
<h2 id="what-makes-stade-different">Stade'i farklı kılan nedir</h2>
<p>Çoğu mesajlaşma uygulaması, mesajlarınızı kendi sunucuları üzerinden yönlendirir. Bu mesajlar şifrelenmiş olsa bile, sağlayıcı yine de kiminle, ne zaman ve nereden konuştuğunuzu görür. Stade bu aracıyı tamamen ortadan kaldırır:</p>
<ul>
<li><strong>Sunucu yok.</strong> İki cihaz birbirine <em>doğrudan</em> bağlanır. Uygulamanın kendisi ağın tamamıdır.</li>
<li><strong>Hesap yok.</strong> Kendi cihazınızda oluşturduğunuz kriptografik bir anahtarla tanımlanırsınız. Hiçbir yerde hiçbir şey kayıtlı değildir.</li>
<li><strong>Varsayılan olarak uçtan uca şifreleme.</strong> Her mesaj, yalnızca alıcının cihazının açabileceği şekilde mühürlenir. Anahtarlar cihazınızdan asla çıkmaz.</li>
<li><strong>Kuantum sonrası kriptografi.</strong> Stade, klasik eliptik eğri kriptografisini NIST'in kuantum sonrası algoritmalarıyla (ML-KEM ve ML-DSA) birleştirir, böylece konuşmalar gelecekteki bir kuantum saldırganına karşı bile korunmaya devam eder.</li>
<li><strong>Meta veri direnci.</strong> Konuşmalar Tor üzerinden seyahat edebilir; bu da yalnızca <em>ne</em> söylediğinizi değil, <em>kiminle</em> konuştuğunuzu ve <em>nerede</em> olduğunuzu da gizler.</li>
</ul>
<h2 id="what-you-can-do">Neler yapabilirsiniz</h2>
<ul>
<li>Tam uçtan uca şifrelemeyle birebir metin mesajları alışverişi yapın.</li>
<li>Grup sohbetleri oluşturun ve bunlara katılın.</li>
<li>Yalnızca yöneticinin paylaşım yaptığı herkese açık bir kanal olan bir <a class="inline" data-go="stadium">Stadium</a> ile açık bir kitleye yayın yapın.</li>
<li><strong>Yerel ağınız</strong> üzerinden (hızlı, internetsiz) veya <strong>Tor</strong> üzerinden (anonim, internet üzerinden çalışır) bağlanın.</li>
<li>Bir kişinin kimliğini bir <strong>güvenlik numarasıyla</strong> doğrulayın, böylece arada bir sahtekar olmadığını bilirsiniz.</li>
<li>Uygulamayı, tüm mesaj veritabanınızı diskte şifreleyen bir parolayla kilitleyin.</li>
</ul>
<h2 id="where-it-runs">Nerede çalışır</h2>
<p>Stade, <strong>Android 8.0 (API 26)</strong> ve üzerinde ile <strong>masaüstünde</strong> — Windows, Linux ve macOS — çalışan tek bir uygulamadır. Aynı kriptografi, kimlik biçimi ve iletişim protokolü tüm platformlarda paylaşılır; böylece bir Android kullanıcısı ile bir masaüstü kullanıcısı, birbiriyle konuşan yalnızca iki eş olur.</p>
<div class="callout warn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<p>Sunucu olmadığı için Stade sizin adınıza hiçbir şeyi okuyamaz, günlüğe kaydedemez, kurtaramaz veya sıfırlayamaz. Bu, tam olarak amaçlanan şeydir — ama aynı zamanda anahtarları <strong>sizin</strong> elinizde tuttuğunuz anlamına da gelir. Cihazınızı ve yedeğinizi kaybederseniz, o kimlik ve konuşmaları da gitmiş olur.</p>
</div></template>
</article>
<article class="doc-article" id="installation" data-group="Getting Started" data-title="Installation" data-group-tr="Başlarken" data-title-tr="Kurulum" hidden>
<h1>Installation</h1>
<p class="doc-lead">Stade ships as a native application for Android and for the desktop. You can install a prebuilt package or build it yourself from source.</p>
<h2 id="android">Android</h2>
<p><strong>Requirements:</strong> Android 8.0 (API level 26) or newer.</p>
<ol>
<li><a class="inline" href="downloads.html">Download the Stade APK</a> (<code>composeApp-debug.apk</code> for development builds, or a signed release APK).</li>
<li>Allow installation from your browser or file manager (<em>Settings → Apps → Special access → Install unknown apps</em>).</li>
<li>Open the APK and tap <strong>Install</strong>.</li>
</ol>
<p>The embedded Tor binaries for every Android ABI (<code>arm64-v8a</code>, <code>armeabi-v7a</code>, <code>x86_64</code>, <code>x86</code>) are bundled inside the APK, so Tor works out of the box with no extra download.</p>
<h2 id="desktop">Desktop</h2>
<p>Stade produces native installers per operating system:</p>
<table class="spec-table">
<tr><th>OS</th><th>Package format</th></tr>
<tr><td>Windows</td><td class="val">.exe installer</td></tr>
<tr><td>Linux</td><td class="val">.deb and AppImage</td></tr>
<tr><td>macOS</td><td class="val">.dmg</td></tr>
</table>
<p>Install the package the same way you would any other desktop app. On first launch the app extracts its bundled Tor runtime into a private application directory. On Linux it installs as <code>stade</code>, categorized under <em>Network</em>.</p>
<h2 id="building-from-source">Building from source</h2>
<p>Stade is a Kotlin Multiplatform + Compose Multiplatform project built with Gradle.</p>
<p><strong>Requirements:</strong></p>
<ul>
<li>JDK 17 (the project pins a JVM toolchain to 17).</li>
<li>The Android SDK for Android builds, with <code>compileSdk</code> 35.</li>
<li>An internet connection on the first build — Gradle downloads the official Tor Expert Bundle (version 13.5.6) for each target and stages it into the app's resources.</li>
</ul>
<pre><code>./gradlew :composeApp:assembleDebug # debug APK
./gradlew :composeApp:assembleRelease # release APK (needs a keystore)
./gradlew :composeApp:run # run the desktop app
./gradlew :composeApp:packageDistributionForCurrentOS # native desktop installer</code></pre>
<ul>
<li><strong>Tor binaries are hash-checked at build time.</strong> You can pin each platform's expected SHA-256 via <code>gradle.properties</code>; an unpinned hash logs a warning, a mismatched pinned hash fails the build.</li>
<li><strong>Native desktop packaging is host-only.</strong> <code>jpackage</code> builds an installer only for the OS it runs on — build the <code>.deb</code>/AppImage on Linux, the <code>.exe</code> on Windows, the <code>.dmg</code> on macOS.</li>
<li><strong>Cloud-synced folders are redirected.</strong> Inside OneDrive, Dropbox, Google Drive, iCloud, pCloud or Box, build output relocates to <code>~/.stade-build/<project-name>/…</code> so the sync client doesn't fight Gradle.</li>
</ul>
<h3 id="release-signing">Release signing (Android)</h3>
<p>To produce a signed release, add your keystore to <code>local.properties</code>:</p>
<pre><code>keystore.path=/absolute/path/to/your.keystore
keystore.password=...
keystore.alias=...
keystore.keyPassword=...</code></pre>
<p>If no keystore is configured, release builds fall back to the debug signing key so the build still succeeds for testing.</p>
<template class="i18n-tr"><h1>Kurulum</h1>
<p class="doc-lead">Stade, Android ve masaüstü için yerel bir uygulama olarak sunulur. Önceden derlenmiş bir paketi kurabilir veya kaynak koddan kendiniz derleyebilirsiniz.</p>
<h2 id="android">Android</h2>
<p><strong>Gereksinimler:</strong> Android 8.0 (API seviyesi 26) veya üzeri.</p>
<ol>
<li><a class="inline" href="downloads.html">Stade APK'sını indirin</a> (geliştirme derlemeleri için <code>composeApp-debug.apk</code>, ya da imzalı bir sürüm APK'sı).</li>
<li>Tarayıcınızdan veya dosya yöneticinizden kuruluma izin verin (<em>Ayarlar → Uygulamalar → Özel erişim → Bilinmeyen uygulamaları yükle</em>).</li>
<li>APK'yı açın ve <strong>Yükle</strong>'ye dokunun.</li>
</ol>
<p>Her Android ABI'si (<code>arm64-v8a</code>, <code>armeabi-v7a</code>, <code>x86_64</code>, <code>x86</code>) için gömülü Tor ikili dosyaları APK'nın içine paketlenmiştir; böylece Tor, ekstra indirme yapmadan kutudan çıktığı gibi çalışır.</p>
<h2 id="desktop">Masaüstü</h2>
<p>Stade, her işletim sistemi için yerel kurulum dosyaları üretir:</p>
<table class="spec-table">
<tr><th>İşletim Sistemi</th><th>Paket biçimi</th></tr>
<tr><td>Windows</td><td class="val">.exe installer</td></tr>
<tr><td>Linux</td><td class="val">.deb and AppImage</td></tr>
<tr><td>macOS</td><td class="val">.dmg</td></tr>
</table>
<p>Paketi, herhangi bir masaüstü uygulaması gibi kurun. İlk açılışta uygulama, gömülü Tor çalışma zamanını özel bir uygulama dizinine çıkarır. Linux'ta <em>Network</em> kategorisi altında <code>stade</code> olarak kurulur.</p>
<h2 id="building-from-source">Kaynaktan derleme</h2>
<p>Stade, Gradle ile derlenen bir Kotlin Multiplatform + Compose Multiplatform projesidir.</p>
<p><strong>Gereksinimler:</strong></p>
<ul>
<li>JDK 17 (proje, JVM araç zincirini 17'ye sabitler).</li>
<li>Android derlemeleri için <code>compileSdk</code> 35 ile Android SDK.</li>
<li>İlk derlemede internet bağlantısı — Gradle, her hedef için resmi Tor Expert Bundle'ı (sürüm 13.5.6) indirir ve uygulamanın kaynaklarına yerleştirir.</li>
</ul>
<pre><code>./gradlew :composeApp:assembleDebug # debug APK
./gradlew :composeApp:assembleRelease # release APK (needs a keystore)
./gradlew :composeApp:run # run the desktop app
./gradlew :composeApp:packageDistributionForCurrentOS # native desktop installer</code></pre>
<ul>
<li><strong>Tor ikili dosyaları derleme sırasında hash ile denetlenir.</strong> Her platformun beklenen SHA-256 değerini <code>gradle.properties</code> üzerinden sabitleyebilirsiniz; sabitlenmemiş bir hash uyarı verir, sabitlenmiş ama eşleşmeyen bir hash derlemeyi başarısız kılar.</li>
<li><strong>Yerel masaüstü paketleme yalnızca ana makinede çalışır.</strong> <code>jpackage</code> yalnızca çalıştığı işletim sistemi için bir kurulum dosyası üretir — <code>.deb</code>/AppImage'ı Linux'ta, <code>.exe</code>'yi Windows'ta, <code>.dmg</code>'yi macOS'ta derleyin.</li>
<li><strong>Bulutla senkronize klasörler yönlendirilir.</strong> OneDrive, Dropbox, Google Drive, iCloud, pCloud veya Box içindeyken derleme çıktısı <code>~/.stade-build/<project-name>/…</code> konumuna taşınır, böylece senkronizasyon istemcisi Gradle ile çakışmaz.</li>
</ul>
<h3 id="release-signing">Sürüm imzalama (Android)</h3>
<p>İmzalı bir sürüm üretmek için anahtar deponuzu <code>local.properties</code> dosyasına ekleyin:</p>
<pre><code>keystore.path=/absolute/path/to/your.keystore
keystore.password=...
keystore.alias=...
keystore.keyPassword=...</code></pre>
<p>Bir anahtar deposu yapılandırılmamışsa, sürüm derlemeleri hata ayıklama imzalama anahtarına geri döner; böylece derleme test için yine de başarılı olur.</p></template>
</article>
<article class="doc-article" id="quick-start" data-group="Getting Started" data-title="Quick Start" data-group-tr="Başlarken" data-title-tr="Hızlı Başlangıç" hidden>
<h1>Quick Start</h1>
<p class="doc-lead">This guide takes you from a fresh install to your first encrypted message.</p>
<h2 id="qs-create">1. Create your identity</h2>
<p>The first time you open Stade, you are asked for a <strong>nickname</strong>. Type one and tap <strong>Create</strong>. That is the entire signup — Stade generates a fresh set of cryptographic keys on your device and derives your <strong>Stade ID</strong> from them:</p>
<pre><code>STADE-XXXX-XXXX-XXXX</code></pre>
<p>This is your public address. It is safe to share; it contains no private information.</p>
<h2 id="qs-invite">2. Share your invite</h2>
<p>To let someone add you, share your <strong>invite</strong> — a self-contained, signed bundle carrying your nickname, public keys and current reachable addresses (LAN and/or Tor). Share it as a <code>STADE2-…</code> text code, a <strong>QR code</strong>, or a <code>.stadeid</code> file. It is signed twice — classical <em>and</em> post-quantum — so the recipient's app can confirm it really came from your keys.</p>
<h2 id="qs-add">3. Add a contact</h2>
<p>Open <strong>Add Contact</strong> and paste their <code>STADE2-…</code> code, scan their QR, or open their <code>.stadeid</code> file. Give the contact a name and confirm. At this point Stade has parsed and verified the invite locally, but the contact is not saved yet — it first needs to <strong>connect to the peer and complete a live handshake</strong>.</p>
<h2 id="qs-wait">4. Wait for the connection</h2>
<ul>
<li><strong>LAN</strong> — if you are both on the same network, this is nearly instant.</li>
<li><strong>Tor</strong> — works across the internet, but the first connection can take a few seconds to a couple of minutes while Tor bootstraps and the hidden-service descriptor propagates. This is normal.</li>
</ul>
<p>Both peers must be online and running the app for the handshake to complete. The connection manager keeps retrying with backoff, so you don't have to time it perfectly.</p>
<h2 id="qs-send">5. Send a message</h2>
<p>Open the conversation, type and send. The first message establishes a <strong>Double Ratchet</strong> session; every message after that uses a fresh key, so even if one key were ever exposed, your other messages stay protected.</p>
<h2 id="qs-verify">6. Verify your contact (recommended)</h2>
<p>Open the contact's profile and tap <strong>Verify</strong>. Compare the <strong>safety number</strong> shown on both devices — read it aloud or compare in person. If they match, you have cryptographic proof there is no impostor in the middle.</p>
<div class="callout ok">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
<p>That's it — you now have a serverless, end-to-end-encrypted conversation. If a connection won't establish, see <a class="inline" data-go="troubleshooting">Troubleshooting</a>.</p>
</div>
<template class="i18n-tr"><h1>Hızlı Başlangıç</h1>
<p class="doc-lead">Bu kılavuz sizi yeni bir kurulumdan ilk şifreli mesajınıza kadar götürür.</p>
<h2 id="qs-create">1. Kimliğinizi oluşturun</h2>
<p>Stade'i ilk açtığınızda sizden bir <strong>takma ad</strong> istenir. Bir tane yazın ve <strong>Oluştur</strong>'a dokunun. Kayıt işleminin tamamı bu kadar — Stade cihazınızda yepyeni bir kriptografik anahtar seti oluşturur ve bunlardan <strong>Stade ID</strong>'nizi türetir:</p>
<pre><code>STADE-XXXX-XXXX-XXXX</code></pre>
<p>Bu sizin genel adresinizdir. Paylaşmak güvenlidir; hiçbir özel bilgi içermez.</p>
<h2 id="qs-invite">2. Davetinizi paylaşın</h2>
<p>Birinin sizi eklemesine izin vermek için <strong>davetinizi</strong> paylaşın — takma adınızı, genel anahtarlarınızı ve mevcut ulaşılabilir adreslerinizi (LAN ve/veya Tor) taşıyan, kendi kendine yeten, imzalı bir paket. Bunu bir <code>STADE2-…</code> metin kodu, bir <strong>QR kodu</strong> veya bir <code>.stadeid</code> dosyası olarak paylaşın. İki kez imzalanır — hem klasik <em>hem de</em> kuantum sonrası — böylece alıcının uygulaması davetin gerçekten sizin anahtarlarınızdan geldiğini doğrulayabilir.</p>
<h2 id="qs-add">3. Bir kişi ekleyin</h2>
<p><strong>Kişi Ekle</strong>'yi açın ve onların <code>STADE2-…</code> kodunu yapıştırın, QR kodlarını tarayın veya <code>.stadeid</code> dosyalarını açın. Kişiye bir isim verin ve onaylayın. Bu noktada Stade daveti yerel olarak ayrıştırmış ve doğrulamıştır, ancak kişi henüz kaydedilmemiştir — önce <strong>eşe bağlanması ve canlı bir el sıkışmayı tamamlaması</strong> gerekir.</p>
<h2 id="qs-wait">4. Bağlantıyı bekleyin</h2>
<ul>
<li><strong>LAN</strong> — ikiniz de aynı ağdaysanız bu neredeyse anındadır.</li>
<li><strong>Tor</strong> — internet üzerinden çalışır, ancak Tor önyükleme yaparken ve gizli hizmet tanımlayıcısı yayılırken ilk bağlantı birkaç saniyeden birkaç dakikaya kadar sürebilir. Bu normaldir.</li>
</ul>
<p>El sıkışmanın tamamlanması için her iki eşin de çevrimiçi olması ve uygulamayı çalıştırıyor olması gerekir. Bağlantı yöneticisi geri çekilmeli olarak yeniden denemeye devam eder, bu yüzden zamanlamayı mükemmel yapmanız gerekmez.</p>
<h2 id="qs-send">5. Bir mesaj gönderin</h2>
<p>Konuşmayı açın, yazın ve gönderin. İlk mesaj bir <strong>Double Ratchet</strong> oturumu kurar; ondan sonraki her mesaj yeni bir anahtar kullanır, böylece bir anahtar açığa çıksa bile diğer mesajlarınız korunmaya devam eder.</p>
<h2 id="qs-verify">6. Kişinizi doğrulayın (önerilir)</h2>
<p>Kişinin profilini açın ve <strong>Doğrula</strong>'ya dokunun. Her iki cihazda gösterilen <strong>güvenlik numarasını</strong> karşılaştırın — yüksek sesle okuyun veya yüz yüze karşılaştırın. Eşleşiyorsa, arada bir sahtekar olmadığına dair kriptografik kanıtınız var demektir.</p>
<div class="callout ok">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
<p>Bu kadar — artık sunucusuz, uçtan uca şifreli bir konuşmanız var. Bir bağlantı kurulamıyorsa <a class="inline" data-go="troubleshooting">Sorun Giderme</a> bölümüne bakın.</p>
</div></template>
</article>
<article class="doc-article" id="creating-your-identity" data-group="Getting Started" data-title="Creating Your Identity" data-group-tr="Başlarken" data-title-tr="Kimliğinizi Oluşturma" hidden>
<h1>Creating Your Identity</h1>
<p class="doc-lead">In Stade there is no signup server, no username database and no password recovery. Your identity is a set of cryptographic keys generated locally the moment you choose a nickname.</p>
<h2 id="cyi-generated">What gets generated</h2>
<p>When you create an identity, Stade generates <strong>four key pairs</strong> and stores them in your encrypted local database:</p>
<table class="spec-table">
<tr><th>Purpose</th><th>Algorithm</th><th>Family</th></tr>
<tr><td>Signing (identity)</td><td class="val">Ed25519</td><td>Classical</td></tr>
<tr><td>Key agreement (DH)</td><td class="val">X25519</td><td>Classical</td></tr>
<tr><td>Key encapsulation (KEM)</td><td class="val">ML-KEM-768</td><td>Post-quantum</td></tr>
<tr><td>Signing (PQ)</td><td class="val">ML-DSA-65</td><td>Post-quantum</td></tr>
</table>
<p>The <strong>Ed25519</strong> and <strong>ML-DSA</strong> pairs prove that messages and invites really came from you — Stade signs with <em>both</em>, so an attacker would have to break a classical <em>and</em> a post-quantum signature to impersonate you. The <strong>X25519</strong> and <strong>ML-KEM</strong> pairs establish shared secrets with a contact; combining them means the session key is secure unless <em>both</em> are broken. The private halves never leave your device.</p>
<h2 id="cyi-stade-id">How your Stade ID is derived</h2>
<p>Your Stade ID is a short, human-readable fingerprint of your <em>public</em> identity keys:</p>
<ol>
<li>Concatenate a domain label (<code>stade-id-v1</code>), your Ed25519 public signing key and your ML-DSA public key.</li>
<li>Hash that with <strong>BLAKE2b-256</strong>.</li>
<li>Take 60 bits of the digest as the identity value, plus a 4-bit checksum.</li>
<li>Encode in <strong>Crockford Base32</strong> (no ambiguous I, L, O, U) — 12 characters grouped <code>STADE-XXXX-XXXX-XXXX</code>.</li>
</ol>
<p>Because the ID is derived <em>from your keys</em>, it cannot be forged: anyone who receives your invite recomputes the ID and checks it matches. The format is also typo-tolerant — input is uppercased, spaces/dashes stripped, and look-alikes (<code>O→0</code>, <code>I→1</code>, <code>L→1</code>) remapped before the checksum is validated.</p>
<h2 id="cyi-share">What is safe to share</h2>
<ul>
<li><strong>Your Stade ID and invite are public.</strong> They contain only public keys and reachability hints — sharing them is how people add you.</li>
<li><strong>Your private keys are never shared, exported or transmitted.</strong> They live only in your encrypted database.</li>
</ul>
<div class="callout warn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<p>No server holds a copy of anything. Lose your device <strong>and</strong> your backup of the app's data directory, and the identity and its conversations are unrecoverable. A new identity means a new Stade ID, and contacts would need to add you again.</p>
</div>
<template class="i18n-tr"><h1>Kimliğinizi Oluşturma</h1>
<p class="doc-lead">Stade'de kayıt sunucusu, kullanıcı adı veritabanı veya parola kurtarma yoktur. Kimliğiniz, bir takma ad seçtiğiniz anda yerel olarak oluşturulan bir kriptografik anahtar setidir.</p>
<h2 id="cyi-generated">Ne üretilir</h2>
<p>Bir kimlik oluşturduğunuzda Stade <strong>dört anahtar çifti</strong> üretir ve bunları şifreli yerel veritabanınızda saklar:</p>
<table class="spec-table">
<tr><th>Amaç</th><th>Algoritma</th><th>Aile</th></tr>
<tr><td>İmzalama (kimlik)</td><td class="val">Ed25519</td><td>Klasik</td></tr>
<tr><td>Anahtar anlaşması (DH)</td><td class="val">X25519</td><td>Klasik</td></tr>
<tr><td>Anahtar kapsülleme (KEM)</td><td class="val">ML-KEM-768</td><td>Kuantum sonrası</td></tr>
<tr><td>İmzalama (KS)</td><td class="val">ML-DSA-65</td><td>Kuantum sonrası</td></tr>
</table>
<p><strong>Ed25519</strong> ve <strong>ML-DSA</strong> çiftleri, mesajların ve davetlerin gerçekten sizden geldiğini kanıtlar — Stade <em>her ikisiyle de</em> imzalar, böylece bir saldırganın sizi taklit edebilmesi için hem klasik hem de kuantum sonrası bir imzayı kırması gerekir. <strong>X25519</strong> ve <strong>ML-KEM</strong> çiftleri bir kişiyle paylaşılan sırlar kurar; bunları birleştirmek, oturum anahtarının yalnızca <em>ikisi de</em> kırılırsa güvensiz hale geleceği anlamına gelir. Özel yarılar cihazınızdan asla çıkmaz.</p>
<h2 id="cyi-stade-id">Stade ID'niz nasıl türetilir</h2>
<p>Stade ID'niz, <em>genel</em> kimlik anahtarlarınızın kısa, insan tarafından okunabilir bir parmak izidir:</p>
<ol>
<li>Bir alan etiketini (<code>stade-id-v1</code>), Ed25519 genel imzalama anahtarınızı ve ML-DSA genel anahtarınızı birleştirin.</li>
<li>Bunu <strong>BLAKE2b-256</strong> ile özetleyin (hash'leyin).</li>
<li>Özetin 60 bitini kimlik değeri olarak alın, artı 4 bitlik bir sağlama toplamı.</li>
<li><strong>Crockford Base32</strong> ile kodlayın (belirsiz I, L, O, U yok) — <code>STADE-XXXX-XXXX-XXXX</code> olarak gruplanmış 12 karakter.</li>
</ol>
<p>Kimlik <em>anahtarlarınızdan</em> türetildiği için sahtesi yapılamaz: davetinizi alan herkes ID'yi yeniden hesaplar ve eşleştiğini kontrol eder. Biçim ayrıca yazım hatalarına toleranslıdır — girdi büyük harfe çevrilir, boşluklar/tireler kaldırılır ve benzer görünen karakterler (<code>O→0</code>, <code>I→1</code>, <code>L→1</code>) sağlama toplamı doğrulanmadan önce yeniden eşlenir.</p>
<h2 id="cyi-share">Neyin paylaşılması güvenlidir</h2>
<ul>
<li><strong>Stade ID'niz ve davetiniz herkese açıktır.</strong> Yalnızca genel anahtarlar ve ulaşılabilirlik ipuçları içerirler — bunları paylaşmak, insanların sizi eklemesinin yoludur.</li>
<li><strong>Özel anahtarlarınız asla paylaşılmaz, dışa aktarılmaz veya iletilmez.</strong> Yalnızca şifreli veritabanınızda yaşarlar.</li>
</ul>
<div class="callout warn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<p>Hiçbir sunucu hiçbir şeyin kopyasını tutmaz. Cihazınızı <strong>ve</strong> uygulamanın veri dizininin yedeğini kaybederseniz, kimlik ve konuşmaları kurtarılamaz olur. Yeni bir kimlik, yeni bir Stade ID anlamına gelir ve kişilerin sizi yeniden eklemesi gerekir.</p>
</div></template>
</article>
<article class="doc-article" id="architecture-overview" data-group="Core Concepts" data-title="Architecture Overview" data-group-tr="Temel Kavramlar" data-title-tr="Mimariye Genel Bakış" hidden>
<h1>Architecture Overview</h1>
<p class="doc-lead">Stade is one application that runs on Android and desktop from a single shared codebase. This is the map: the major pieces, how they layer, and how a message travels from your keyboard to your contact's screen.</p>
<h2 id="ao-codebase">One codebase, many platforms</h2>
<p>Stade is written in <strong>Kotlin Multiplatform (KMP)</strong> with a <strong>Compose Multiplatform</strong> UI, organized into layered source sets:</p>
<ul>
<li><code>commonMain</code> — the bulk of the app: UI, identity, contacts, messaging, the handshake, the Double Ratchet, the sync protocol and transport <em>interfaces</em>. Platform-agnostic.</li>
<li><code>jvmCommonMain</code> — shared by Android and desktop: the BouncyCastle-backed cryptography, the LAN and Tor transports, and the encrypted vault.</li>
<li><code>androidMain</code> / <code>desktopMain</code> — thin platform layers: the Android <code>Activity</code>/<code>Service</code>, the desktop <code>main()</code>, Tor binary loading and file pickers.</li>
</ul>
<p>Platform-specific behavior is declared in <code>commonMain</code> as an <code>expect</code> and implemented per platform with <code>actual</code> — for example <code>platformCrypto()</code> and <code>platformPq()</code> resolve to the JVM/BouncyCastle implementations.</p>
<h2 id="ao-layers">The layers</h2>
<pre><code>UI Compose screens, view state
App services ContactManager / MessageManager, ChatService / GroupManager, SyncEngine
Cryptography CryptoApi (Ed25519, X25519, AEAD, HKDF), PqCrypto (ML-KEM-768, ML-DSA-65),
HandshakeService, DoubleRatchet
Transport ConnectionManager (dial/retry/backoff), LanTransport, TorTransport
Storage SQLDelight / SQLite, Vault (password-encrypted DB at rest)</code></pre>
<p>The single object that wires everything together is the <strong><code>AppContainer</code></strong> — it constructs the crypto providers, the database, the managers, the transports and the sync engine, and exposes them to the UI.</p>
<h2 id="ao-flow">How a message flows</h2>
<ol>
<li><code>SyncEngine.queueOutgoing</code> asks the <strong>Double Ratchet</strong> session to <em>seal</em> the plaintext, producing a ratchet frame with a fresh message key.</li>
<li>The sealed frame is wrapped in a <code>MessagePayload</code>, serialized and placed in the <strong>Outbox</strong> so it survives until acknowledged.</li>
<li>The active session sends the frame over whichever transport is connected, using length-prefixed framing (<code>FrameCodec</code>: a one-byte record type + four-byte length + payload).</li>
<li>The peer's Double Ratchet <strong>opens</strong> it, the message is stored and shown, and an <strong>ACK</strong> comes back to mark it delivered.</li>
</ol>
<p>A background pinger keeps the connection warm, and the <strong>ConnectionManager</strong> continuously dials un-connected contacts with exponential backoff until a session is established.</p>
<h2 id="ao-state">Where state lives</h2>
<p>Messages, contacts, groups and transport settings live in a local <strong>SQLite</strong> database accessed through <strong>SQLDelight</strong> (package <code>dev.stade.db</code>). At rest that database is encrypted by the Vault under a key derived from your password; while unlocked a decrypted working copy exists, and when you lock or close it is re-encrypted and the plaintext securely wiped.</p>
<h2 id="ao-principles">Design principles</h2>
<ul>
<li><strong>No server, ever.</strong> Every peer is symmetric; there is no privileged node.</li>
<li><strong>Defense in depth via hybrid crypto.</strong> Classical and post-quantum algorithms are combined so a break in either family alone does not compromise a session.</li>
<li><strong>Fail closed.</strong> Handshakes that don't match expected keys, sizes, protocol versions or transcript commitments are rejected rather than downgraded.</li>
</ul>
<template class="i18n-tr"><h1>Mimariye Genel Bakış</h1>
<p class="doc-lead">Stade, tek bir paylaşılan kod tabanından Android ve masaüstünde çalışan tek bir uygulamadır. Bu, ana parçaların, birbirleriyle nasıl katmanlandıklarının ve bir mesajın klavyenizden kişinizin ekranına nasıl gittiğinin haritasıdır.</p>
<h2 id="ao-codebase">Tek kod tabanı, birçok platform</h2>
<p>Stade, <strong>Compose Multiplatform</strong> arayüzüyle <strong>Kotlin Multiplatform (KMP)</strong> ile yazılmıştır ve katmanlı kaynak setleri halinde düzenlenmiştir:</p>
<ul>
<li><code>commonMain</code> — uygulamanın büyük kısmı: arayüz, kimlik, kişiler, mesajlaşma, el sıkışma, Double Ratchet, senkronizasyon protokolü ve taşıma <em>arayüzleri</em>. Platformdan bağımsızdır.</li>
<li><code>jvmCommonMain</code> — Android ve masaüstü tarafından paylaşılır: BouncyCastle destekli kriptografi, LAN ve Tor taşımaları ve şifreli kasa.</li>
<li><code>androidMain</code> / <code>desktopMain</code> — ince platform katmanları: Android <code>Activity</code>/<code>Service</code>, masaüstü <code>main()</code>, Tor ikili dosya yükleme ve dosya seçiciler.</li>
</ul>
<p>Platforma özgü davranışlar <code>commonMain</code> içinde bir <code>expect</code> olarak tanımlanır ve her platformda <code>actual</code> ile uygulanır — örneğin <code>platformCrypto()</code> ve <code>platformPq()</code>, JVM/BouncyCastle uygulamalarına çözümlenir.</p>
<h2 id="ao-layers">Katmanlar</h2>
<pre><code>UI Compose screens, view state
App services ContactManager / MessageManager, ChatService / GroupManager, SyncEngine
Cryptography CryptoApi (Ed25519, X25519, AEAD, HKDF), PqCrypto (ML-KEM-768, ML-DSA-65),
HandshakeService, DoubleRatchet
Transport ConnectionManager (dial/retry/backoff), LanTransport, TorTransport
Storage SQLDelight / SQLite, Vault (password-encrypted DB at rest)</code></pre>
<p>Her şeyi birbirine bağlayan tek nesne <strong><code>AppContainer</code></strong>'dır — kripto sağlayıcılarını, veritabanını, yöneticileri, taşımaları ve senkronizasyon motorunu inşa eder ve bunları arayüze sunar.</p>
<h2 id="ao-flow">Bir mesaj nasıl akar</h2>
<ol>
<li><code>SyncEngine.queueOutgoing</code>, <strong>Double Ratchet</strong> oturumundan düz metni <em>mühürlemesini</em> ister ve yeni bir mesaj anahtarıyla bir ratchet çerçevesi üretir.</li>
<li>Mühürlenmiş çerçeve bir <code>MessagePayload</code> içine sarılır, serileştirilir ve onaylanana kadar hayatta kalması için <strong>Giden Kutusu</strong>'na yerleştirilir.</li>
<li>Etkin oturum, uzunluk ön ekli çerçeveleme kullanarak (<code>FrameCodec</code>: bir bayt kayıt türü + dört bayt uzunluk + yük) çerçeveyi bağlı olan taşıma üzerinden gönderir.</li>
<li>Eşin Double Ratchet'i onu <strong>açar</strong>, mesaj saklanır ve gösterilir, teslim edildiğini işaretlemek için bir <strong>ACK</strong> geri döner.</li>
</ol>
<p>Arka plandaki bir ping mekanizması bağlantıyı sıcak tutar ve <strong>ConnectionManager</strong>, bir oturum kurulana kadar bağlı olmayan kişileri üstel geri çekilmeyle sürekli aramaya devam eder.</p>
<h2 id="ao-state">Durum nerede yaşar</h2>
<p>Mesajlar, kişiler, gruplar ve taşıma ayarları, <strong>SQLDelight</strong> (paket <code>dev.stade.db</code>) üzerinden erişilen yerel bir <strong>SQLite</strong> veritabanında yaşar. Beklemede iken bu veritabanı, parolanızdan türetilen bir anahtar altında Kasa tarafından şifrelenir; kilit açıkken şifresi çözülmüş bir çalışma kopyası bulunur, kilitlendiğinde veya kapatıldığında yeniden şifrelenir ve düz metin güvenli bir şekilde silinir.</p>
<h2 id="ao-principles">Tasarım ilkeleri</h2>
<ul>
<li><strong>Asla sunucu yok.</strong> Her eş simetriktir; ayrıcalıklı bir düğüm yoktur.</li>
<li><strong>Hibrit kriptografi ile derinlemesine savunma.</strong> Yalnızca bir ailedeki bir kırılmanın oturumu tehlikeye atmaması için klasik ve kuantum sonrası algoritmalar birleştirilir.</li>
<li><strong>Kapalı başarısız olma (fail closed).</strong> Beklenen anahtarlarla, boyutlarla, protokol sürümleriyle veya transkript taahhütleriyle eşleşmeyen el sıkışmalar, düşürülmek yerine reddedilir.</li>
</ul></template>
</article>
<article class="doc-article" id="peer-to-peer-model" data-group="Core Concepts" data-title="Peer-to-Peer Model" data-group-tr="Temel Kavramlar" data-title-tr="Eşler Arası Model" hidden>
<h1>Peer-to-Peer Model</h1>
<p class="doc-lead">Stade has no servers. There is no central node that relays, stores or brokers your messages. Every device runs the same code and is a full, equal peer.</p>
<h2 id="p2p-direct">Two peers, one direct connection</h2>
<p>A conversation is a <strong>direct connection between two devices</strong>. Each device both <em>listens</em> for incoming connections and <em>dials</em> out to its contacts. When two contacts are online and reachable, one side's dial meets the other's listener and a session is born. Because the link is direct:</p>
<ul>
<li>Your messages are never uploaded to a third party.</li>
<li>There is no operator to be subpoenaed, hacked, or to go out of business and take your data.</li>
<li>Delivery requires <strong>both peers online</strong> at overlapping times. Stade keeps an <strong>Outbox</strong> and retries, but a message only arrives once a connection is actually established.</li>
</ul>
<h2 id="p2p-find">How peers find each other</h2>
<p>A peer can be reached at one or more <strong>addresses</strong>, each tagged by transport:</p>
<pre><code>lan://192.168.1.42:5901 a local-network address
tor://<onion-address>:<port> a Tor hidden-service address</code></pre>
<p>Addresses are exchanged inside your <strong>invite</strong> and refreshed inside the <strong>HELLO</strong> at the start of every handshake, so contacts learn your current addresses automatically. On the LAN, peers also <strong>broadcast their presence</strong> over UDP so they can discover each other with nothing typed in.</p>
<h2 id="p2p-cm">The connection manager</h2>
<p>The <strong><code>ConnectionManager</code></strong> turns "I have a contact" into "I have a live session." It runs continuously and:</p>
<ul>
<li>Tries each known address of any contact not currently connected, through the matching transport.</li>
<li>Applies <strong>backoff</strong> so an offline peer isn't hammered.</li>
<li>Runs a separate <strong>pending-dial loop</strong> for freshly added invite addresses.</li>
<li>Skips your <em>own</em> addresses and de-duplicates parallel attempts.</li>
</ul>
<p>Every attempt is recorded as a diagnostic (<code>TRYING → CONNECT_OK → HANDSHAKE_OK</code>, or the matching failure), which powers the connection-status detail in the UI.</p>
<h2 id="p2p-session">Establishing a session</h2>
<p>Connecting at the socket level is only step one. Before any message flows, the two peers run a mutual <strong>handshake</strong> that exchanges identities, proves possession of both the classical and post-quantum private keys, checks each Stade ID matches its keys, rejects version/size mismatches and downgrades, and derives a shared root key from a hybrid Diffie–Hellman + ML-KEM exchange. Only then is the contact considered <strong>connected</strong> — and, if it was new, actually saved.</p>
<h2 id="p2p-resilience">Resilience</h2>
<ul>
<li><strong>Self-healing transports.</strong> The Tor transport restarts its process if it dies; the LAN transport falls back across a range of ports.</li>
<li><strong>Multiple addresses, raced in parallel.</strong> If a peer is reachable on both LAN and Tor, both are attempted and the first handshake wins.</li>
<li><strong>Idempotent delivery.</strong> Messages carry IDs; a peer that already has one just re-acknowledges instead of storing a duplicate.</li>
</ul>
<p>Because no server aggregates data, no party — not even Stade's developers — has a contact graph, message logs, timestamps or address books. That information exists only on the two devices in a conversation.</p>
<template class="i18n-tr"><h1>Eşler Arası Model</h1>
<p class="doc-lead">Stade'in sunucusu yoktur. Mesajlarınızı ileten, saklayan veya aracılık eden merkezi bir düğüm yoktur. Her cihaz aynı kodu çalıştırır ve tam, eşit bir eştir.</p>
<h2 id="p2p-direct">İki eş, tek doğrudan bağlantı</h2>
<p>Bir konuşma, <strong>iki cihaz arasında doğrudan bir bağlantıdır.</strong> Her cihaz hem gelen bağlantıları <em>dinler</em> hem de kişilerini <em>arar</em>. İki kişi çevrimiçi ve ulaşılabilir olduğunda, bir tarafın araması diğerinin dinleyicisiyle buluşur ve bir oturum doğar. Bağlantı doğrudan olduğu için:</p>
<ul>
<li>Mesajlarınız hiçbir zaman üçüncü bir tarafa yüklenmez.</li>
<li>Mahkeme celbi alınacak, hacklenecek veya iş yapmayı bırakıp verilerinizi alıp götürecek bir operatör yoktur.</li>
<li>Teslimat, çakışan zamanlarda <strong>her iki eşin de çevrimiçi olmasını</strong> gerektirir. Stade bir <strong>Giden Kutusu</strong> tutar ve yeniden dener, ancak bir mesaj yalnızca gerçekten bir bağlantı kurulduğunda ulaşır.</li>
</ul>
<h2 id="p2p-find">Eşler birbirini nasıl bulur</h2>
<p>Bir eşe, her biri taşımaya göre etiketlenmiş bir veya daha fazla <strong>adresten</strong> ulaşılabilir:</p>
<pre><code>lan://192.168.1.42:5901 a local-network address
tor://<onion-address>:<port> a Tor hidden-service address</code></pre>
<p>Adresler <strong>davetinizin</strong> içinde paylaşılır ve her el sıkışmanın başındaki <strong>HELLO</strong> içinde yenilenir; böylece kişiler mevcut adreslerinizi otomatik olarak öğrenir. Yerel ağda, eşler ayrıca hiçbir şey yazmadan birbirlerini keşfedebilmek için varlıklarını UDP üzerinden <strong>yayınlar</strong>.</p>
<h2 id="p2p-cm">Bağlantı yöneticisi</h2>
<p><strong><code>ConnectionManager</code></strong>, "bir kişim var"ı "canlı bir oturumum var"a çevirir. Sürekli çalışır ve:</p>
<ul>
<li>Şu anda bağlı olmayan herhangi bir kişinin bilinen her adresini, eşleşen taşıma üzerinden dener.</li>
<li>Çevrimdışı bir eşin bombardımana tutulmaması için <strong>geri çekilme</strong> uygular.</li>
<li>Yeni eklenen davet adresleri için ayrı bir <strong>bekleyen arama döngüsü</strong> çalıştırır.</li>
<li>Kendi adreslerinizi atlar ve paralel denemeleri tekilleştirir.</li>
</ul>
<p>Her deneme, arayüzdeki bağlantı durumu ayrıntısını besleyen bir tanılama olarak kaydedilir (<code>TRYING → CONNECT_OK → HANDSHAKE_OK</code>, veya eşleşen başarısızlık).</p>
<h2 id="p2p-session">Bir oturum kurma</h2>
<p>Soket seviyesinde bağlanmak yalnızca birinci adımdır. Herhangi bir mesaj akmadan önce, iki eş kimlikleri değiştiren, hem klasik hem de kuantum sonrası özel anahtarlara sahip olduklarını kanıtlayan, her Stade ID'nin kendi anahtarlarıyla eşleştiğini kontrol eden, sürüm/boyut uyuşmazlıklarını ve düşürmeleri reddeden ve hibrit bir Diffie–Hellman + ML-KEM alışverişinden paylaşılan bir kök anahtar türeten karşılıklı bir <strong>el sıkışma</strong> çalıştırır. Ancak o zaman kişi <strong>bağlı</strong> sayılır — ve yeniyse, gerçekten kaydedilir.</p>
<h2 id="p2p-resilience">Dayanıklılık</h2>
<ul>
<li><strong>Kendi kendini iyileştiren taşımalar.</strong> Tor taşıması sürecinin çökmesi durumunda yeniden başlatır; LAN taşıması bir port aralığı boyunca geri döner.</li>
<li><strong>Birden fazla adres, paralel olarak yarıştırılır.</strong> Bir eşe hem LAN hem de Tor üzerinden ulaşılabiliyorsa, ikisi de denenir ve ilk el sıkışmayı tamamlayan kazanır.</li>
<li><strong>Bağımsız (idempotent) teslimat.</strong> Mesajlar kimlik taşır; zaten birine sahip olan bir eş, kopyasını saklamak yerine yalnızca yeniden onaylar.</li>
</ul>
<p>Hiçbir sunucu veri toplamadığı için, hiçbir taraf — Stade'in geliştiricileri bile — bir kişi grafiğine, mesaj günlüklerine, zaman damgalarına veya adres defterlerine sahip değildir. Bu bilgi yalnızca bir konuşmadaki iki cihazda var olur.</p></template>
</article>
<article class="doc-article" id="encryption" data-group="Core Concepts" data-title="Encryption" data-group-tr="Temel Kavramlar" data-title-tr="Şifreleme" hidden>
<h1>Encryption</h1>
<p class="doc-lead">Stade's encryption has one job: make sure only the two devices in a conversation can read it — and keep that true even against an attacker who records everything today and owns a quantum computer tomorrow.</p>
<h2 id="enc-hybrid">Hybrid by design</h2>
<p>Every secret-establishing and authentication step is <strong>hybrid</strong>: it combines a well-trusted classical algorithm with a NIST post-quantum one.</p>
<ul>
<li><strong>Key agreement:</strong> X25519 Diffie–Hellman <strong>+</strong> ML-KEM-768 encapsulation.</li>
<li><strong>Authentication:</strong> Ed25519 signatures <strong>+</strong> ML-DSA-65 signatures.</li>
</ul>
<p>The shared secret feeding a session is the <em>concatenation</em> of the classical and post-quantum secrets, run through a KDF. An attacker must break <strong>both</strong> families to compromise a session.</p>
<h2 id="enc-handshake">Stage 1 — The handshake (PQXDH-style)</h2>
<ol>
<li><strong>HELLO ↔ HELLO.</strong> Each side sends its identity (Stade ID, nickname, all four public keys), a random 32-byte nonce, its addresses and a <strong>transcript commitment</strong> hashing the protocol version and all public keys. Each side checks the peer's Stade ID derives from those keys, that sizes and versions match, and that the commitment matches what it recomputes. A mismatch is treated as a possible <strong>downgrade attack</strong> and rejected.</li>
<li><strong>AUTH ↔ AUTH.</strong> Each side signs a message built from the peer's nonce and both commitments using <strong>both</strong> Ed25519 and ML-DSA private keys; the peer verifies both. The nonce prevents replay.</li>
<li><strong>KEM_OFFER.</strong> The peers are ordered deterministically (lexicographically by Stade ID) into initiator and responder; the initiator encapsulates a secret to the responder's ML-KEM key and the responder decapsulates it.</li>
<li><strong>Root key derivation.</strong> Each side concatenates the X25519 secret with the ML-KEM secret and runs it through <strong>HKDF</strong>, salted with a hash of the full transcript, to produce the 32-byte <strong>root key</strong>.</li>
</ol>
<p>Because the root key is bound to the entire transcript, both peers derive the same root key only if they saw the exact same, untampered handshake.</p>
<h2 id="enc-ratchet">Stage 2 — The Double Ratchet</h2>
<p>The root key seeds a <strong>post-quantum Double Ratchet</strong> that protects the ongoing conversation:</p>
<ul>
<li><strong>Forward secrecy</strong> — each message uses a unique key from a one-way chain, so compromising today's key does not reveal yesterday's messages.</li>
<li><strong>Post-compromise security (self-healing)</strong> — the session periodically performs a fresh key exchange, locking an attacker back out.</li>
<li>Each <strong>DH ratchet step</strong> performs a fresh X25519 exchange <em>and</em> generates a fresh ML-KEM key pair, mixing the post-quantum secret back into the root key continuously.</li>
<li>Message keys derive a separate AEAD key and nonce; the plaintext is sealed with <strong>ChaCha20-Poly1305</strong>, with the header authenticated as associated data.</li>
<li><strong>Out-of-order</strong> messages are handled by caching skipped keys (bounded window of 128). Decryption is <strong>transactional</strong> — state rolls back if authentication fails, so a forged frame can't poison the session.</li>
</ul>
<h2 id="enc-rest">Stage 3 — Encryption at rest</h2>
<ul>
<li>Your message database is stored as a file encrypted with <strong>AES-256-GCM</strong> under a random data-encryption key (DEK).</li>
<li>That DEK is itself encrypted with a key derived from <strong>your password</strong> via <strong>PBKDF2-HMAC-SHA256</strong> (210,000 iterations).</li>
<li>On unlock the DEK is recovered and the database decrypted into a working copy; on lock or quit it is re-encrypted and the plaintext securely overwritten and deleted.</li>
</ul>
<h2 id="enc-attacker">What an attacker on the network sees</h2>
<p>An adversary capturing Stade traffic sees length-prefixed ciphertext frames — no contents, no ability to forge or inject without the private keys. Over <strong>Tor</strong> they additionally cannot tell <em>who</em> is talking to <em>whom</em> or <em>where</em>. The one thing encryption alone can't prevent is a man-in-the-middle substituting keys at first contact — which is what <a class="inline" data-go="verifying-a-contact">Verifying a Contact</a> catches.</p>
<template class="i18n-tr"><h1>Şifreleme</h1>
<p class="doc-lead">Stade'in şifrelemesinin tek bir görevi vardır: bir konuşmayı yalnızca içindeki iki cihazın okuyabilmesini sağlamak — ve bunu, bugün her şeyi kaydeden ve yarın bir kuantum bilgisayara sahip olan bir saldırgana karşı bile doğru tutmak.</p>
<h2 id="enc-hybrid">Tasarım gereği hibrit</h2>
<p>Sır kuran ve kimlik doğrulayan her adım <strong>hibrittir</strong>: iyi güvenilen bir klasik algoritmayı bir NIST kuantum sonrası algoritmasıyla birleştirir.</p>
<ul>
<li><strong>Anahtar anlaşması:</strong> X25519 Diffie–Hellman <strong>+</strong> ML-KEM-768 kapsülleme.</li>
<li><strong>Kimlik doğrulama:</strong> Ed25519 imzaları <strong>+</strong> ML-DSA-65 imzaları.</li>
</ul>
<p>Bir oturumu besleyen paylaşılan sır, klasik ve kuantum sonrası sırların bir KDF'den geçirilen <em>birleşimidir</em>. Bir saldırganın bir oturumu tehlikeye atmak için <strong>her iki</strong> aileyi de kırması gerekir.</p>
<h2 id="enc-handshake">Aşama 1 — El sıkışma (PQXDH tarzı)</h2>
<ol>
<li><strong>HELLO ↔ HELLO.</strong> Her taraf kimliğini (Stade ID, takma ad, dört genel anahtarın tümü), rastgele 32 baytlık bir nonce, adreslerini ve protokol sürümünü ve tüm genel anahtarları özetleyen bir <strong>transkript taahhüdünü</strong> gönderir. Her taraf, eşin Stade ID'sinin bu anahtarlardan türediğini, boyutların ve sürümlerin eşleştiğini ve taahhüdün kendi yeniden hesapladığıyla eşleştiğini kontrol eder. Bir uyuşmazlık, olası bir <strong>düşürme saldırısı</strong> olarak kabul edilir ve reddedilir.</li>
<li><strong>AUTH ↔ AUTH.</strong> Her taraf, eşin nonce'undan ve her iki taahhütten oluşturulan bir mesajı hem Ed25519 hem de ML-DSA özel anahtarlarını kullanarak imzalar; eş her ikisini de doğrular. Nonce, tekrar oynatmayı önler.</li>
<li><strong>KEM_OFFER.</strong> Eşler deterministik olarak (Stade ID'ye göre sözlüksel olarak) başlatıcı ve yanıtlayıcı olarak sıralanır; başlatıcı yanıtlayıcının ML-KEM anahtarına bir sır kapsüller ve yanıtlayıcı bunu açar.</li>
<li><strong>Kök anahtar türetimi.</strong> Her taraf, X25519 sırrını ML-KEM sırrıyla birleştirir ve 32 baytlık <strong>kök anahtarı</strong> üretmek için tam transkriptin bir özetiyle tuzlanmış olarak <strong>HKDF</strong>'den geçirir.</li>
</ol>
<p>Kök anahtar tüm transkripte bağlı olduğu için, iki eş yalnızca tam olarak aynı, üzerinde oynanmamış el sıkışmayı gördülerse aynı kök anahtarı türetir.</p>
<h2 id="enc-ratchet">Aşama 2 — Double Ratchet</h2>
<p>Kök anahtar, devam eden konuşmayı koruyan bir <strong>kuantum sonrası Double Ratchet</strong>'i besler:</p>
<ul>
<li><strong>İleri gizlilik</strong> — her mesaj tek yönlü bir zincirden benzersiz bir anahtar kullanır, böylece bugünün anahtarını tehlikeye atmak dünün mesajlarını ortaya çıkarmaz.</li>
<li><strong>Tehlikeye atılma sonrası güvenlik (kendi kendini iyileştirme)</strong> — oturum periyodik olarak yeni bir anahtar alışverişi yapar ve saldırganı yeniden dışarıda bırakır.</li>
<li>Her <strong>DH ratchet adımı</strong> yeni bir X25519 alışverişi yapar <em>ve</em> yeni bir ML-KEM anahtar çifti üretir, kuantum sonrası sırrı sürekli olarak kök anahtara geri karıştırır.</li>
<li>Mesaj anahtarları ayrı bir AEAD anahtarı ve nonce türetir; düz metin, ilişkili veri olarak doğrulanan başlıkla <strong>ChaCha20-Poly1305</strong> ile mühürlenir.</li>
<li><strong>Sırasız</strong> mesajlar, atlanan anahtarları önbelleğe alarak işlenir (128'lik sınırlı bir pencere). Şifre çözme <strong>işlemseldir</strong> — kimlik doğrulama başarısız olursa durum geri alınır, böylece sahte bir çerçeve oturumu zehirleyemez.</li>
</ul>
<h2 id="enc-rest">Aşama 3 — Bekleme durumunda şifreleme</h2>
<ul>
<li>Mesaj veritabanınız, rastgele bir veri şifreleme anahtarı (DEK) altında <strong>AES-256-GCM</strong> ile şifrelenmiş bir dosya olarak saklanır.</li>
<li>Bu DEK'in kendisi, <strong>PBKDF2-HMAC-SHA256</strong> (210.000 yineleme) aracılığıyla <strong>parolanızdan</strong> türetilen bir anahtarla şifrelenir.</li>
<li>Kilit açıldığında DEK kurtarılır ve veritabanının şifresi çalışma kopyasına çözülür; kilitlendiğinde veya çıkıldığında yeniden şifrelenir ve düz metin güvenli bir şekilde üzerine yazılıp silinir.</li>
</ul>
<h2 id="enc-attacker">Ağdaki bir saldırganın gördüğü</h2>
<p>Stade trafiğini yakalayan bir düşman, uzunluk ön ekli şifreli metin çerçeveleri görür — içerik yok, özel anahtarlar olmadan sahtecilik veya enjeksiyon yapma imkanı yok. <strong>Tor</strong> üzerinden ayrıca <em>kimin</em> <em>kiminle</em> veya <em>nerede</em> konuştuğunu söyleyemezler. Yalnızca şifrelemenin önleyemediği tek şey, ilk temasta anahtarları değiştiren bir ortadaki adam saldırısıdır — ki bunu yakalayan şey <a class="inline" data-go="verifying-a-contact">Bir Kişiyi Doğrulama</a>'dır.</p></template>
</article>
<article class="doc-article" id="identity-and-keys" data-group="Core Concepts" data-title="Identity & Keys" data-group-tr="Temel Kavramlar" data-title-tr="Kimlik ve Anahtarlar" hidden>
<h1>Identity & Keys</h1>
<p class="doc-lead">Stade replaces accounts with keys. This describes the keys that make up an identity, how your Stade ID and invite are built from them, and how each key is used.</p>
<h2 id="ik-pairs">The four key pairs</h2>
<table class="spec-table">
<tr><th>Key pair</th><th>Algorithm</th><th>Used for</th></tr>
<tr><td>Signing</td><td class="val">Ed25519</td><td>Authenticating you (classical signatures)</td></tr>
<tr><td>Handshake (DH)</td><td class="val">X25519</td><td>Classical Diffie–Hellman key agreement</td></tr>
<tr><td>KEM</td><td class="val">ML-KEM-768</td><td>Post-quantum key encapsulation</td></tr>
<tr><td>PQ signing</td><td class="val">ML-DSA-65</td><td>Authenticating you (post-quantum signatures)</td></tr>
</table>
<p>The public halves are packed into your invite; the private halves never leave the device. A <code>LocalIdentity</code> holds all four pairs plus your nickname and creation time; a contact's <code>RemoteIdentity</code> holds the same four <em>public</em> keys with no private material.</p>
<h2 id="ik-id">The Stade ID</h2>
<p>Your Stade ID is a compact, self-certifying fingerprint of your public identity, formatted <code>STADE-XXXX-XXXX-XXXX</code>. It hashes (<code>BLAKE2b-256</code>) a domain label with your Ed25519 and ML-DSA public keys, takes 60 bits plus a 4-bit checksum, and encodes in Crockford Base32. Anyone with your invite recomputes it — if the keys were swapped, the ID wouldn't match. Because both signing keys feed the hash, the ID commits you to both.</p>
<h2 id="ik-invite">The invite</h2>
<p>Where the Stade ID is a <em>fingerprint</em>, the <strong>invite</strong> is the <em>full</em> public bundle. It is a binary record, Base32-encoded and prefixed <code>STADE2-</code>, containing a magic marker and version, your nickname, your four public keys, your reachable LAN/Tor addresses, and <strong>two signatures</strong> (one Ed25519, one ML-DSA). On parse, the app checks magic/version/sizes, verifies <strong>both</strong> signatures, and recomputes the Stade ID. Only a fully valid, doubly-signed invite is accepted. Invites can also be a <strong>QR code</strong> (chunked when large) or a <code>.stadeid</code> file.</p>
<h2 id="ik-runtime">How each key is used at runtime</h2>
<ul>
<li><strong>Ed25519 + ML-DSA</strong> — sign your invite and each handshake's AUTH message; verified on every connection.</li>
<li><strong>X25519</strong> — the classical half of the handshake secret, and the ratcheting key that advances the Double Ratchet.</li>
<li><strong>ML-KEM-768</strong> — the post-quantum half of the handshake secret, with a fresh ML-KEM key pair generated at each ratchet step to keep re-injecting post-quantum security.</li>
</ul>
<p>Stade never relies on a single algorithm for either secrecy or authenticity. A future break of <em>either</em> the elliptic-curve or the lattice family — but not both — still leaves your identity and conversations protected.</p>
<template class="i18n-tr"><h1>Kimlik ve Anahtarlar</h1>
<p class="doc-lead">Stade, hesapların yerine anahtarları koyar. Burada bir kimliği oluşturan anahtarlar, Stade ID'niz ve davetinizin bunlardan nasıl inşa edildiği ve her anahtarın nasıl kullanıldığı açıklanır.</p>
<h2 id="ik-pairs">Dört anahtar çifti</h2>
<table class="spec-table">
<tr><th>Anahtar çifti</th><th>Algoritma</th><th>Kullanım amacı</th></tr>
<tr><td>İmzalama</td><td class="val">Ed25519</td><td>Sizi doğrulama (klasik imzalar)</td></tr>
<tr><td>El sıkışma (DH)</td><td class="val">X25519</td><td>Klasik Diffie–Hellman anahtar anlaşması</td></tr>
<tr><td>KEM</td><td class="val">ML-KEM-768</td><td>Kuantum sonrası anahtar kapsülleme</td></tr>
<tr><td>KS imzalama</td><td class="val">ML-DSA-65</td><td>Sizi doğrulama (kuantum sonrası imzalar)</td></tr>
</table>
<p>Genel yarılar davetinize paketlenir; özel yarılar cihazdan asla çıkmaz. Bir <code>LocalIdentity</code>, dört çiftin tümünü takma adınız ve oluşturma zamanınızla birlikte tutar; bir kişinin <code>RemoteIdentity</code>'si, özel materyal olmadan aynı dört <em>genel</em> anahtarı tutar.</p>
<h2 id="ik-id">Stade ID</h2>
<p>Stade ID'niz, <code>STADE-XXXX-XXXX-XXXX</code> biçiminde, genel kimliğinizin kompakt, kendi kendini onaylayan bir parmak izidir. Ed25519 ve ML-DSA genel anahtarlarınızla bir alan etiketini özetler (<code>BLAKE2b-256</code>), 60 bit artı 4 bitlik bir sağlama toplamı alır ve Crockford Base32 ile kodlar. Davetinize sahip olan herkes bunu yeniden hesaplar — anahtarlar değiştirilmiş olsaydı, ID eşleşmezdi. Her iki imzalama anahtarı da özeti beslediği için, ID sizi ikisine de bağlar.</p>