aboutsummaryrefslogtreecommitdiffstats
path: root/docs/research/089-messenger.md
blob: a74107d760789703f78e945e07df964c0f016c18 (plain)
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
# 089 — 메신저: 두 노드, 끝에서 끝까지 봉인, 노드 안의 화면

2026-09-27. 로드맵의 "E2EE 초저지연 메신저"를 런타임 쪽 능력으로 처음 끝까지 이은 것. 앱은 Vega의
`examples/messenger.aup`이다(앱 기능은 런타임 밖에 둔다). 여기서는 런타임이 무엇을 주고 얼마나 걸리는지를 적는다.

착수 기준:

- 바꿀 결정: 메신저를 이루는 것(봉인한 전달, 보낸 이 인증, 사람이 쓰는 화면, 그 사이의 지연)을 런타임의 일반 능력으로 줄지,
  메신저 전용 코드로 만들지. 전용 코드는 쓰지 않는다. 086·088의 봉인 전달과 설계 010의 표면으로 된다는 것을 보인다.
- 최소 확인: 두 노드가 서로에게 봉인해 보내고, 한쪽 화면에서 친 줄이 다른 쪽 화면에 그려지며, 사이의 중계는 평문을 보지
  못한다는 것을 검사로 지킨다. 한 줄이 상대 노드에 보이기까지의 시간을 봉인 유무와 저장 장치별로 잰다.
- 중단 기준: 메신저를 위해 런타임에 앱 전용 개념(대화, 연락처)을 넣어야 한다면 멈추고 다시 설계한다.

## 이어진 것

| 로드맵이 말한 것 | 무엇으로 |
|---|---|
| 끝에서 끝까지 암호(E2EE) | HPKE 봉인(RFC 9180). 전달선마다 세션 하나, 보내는 노드의 키로 auth 모드([088](088-sealed-sessions.md)) |
| P2P | 노드에서 노드로. 중계·프록시가 사이에 있어도 읽지도 바꾸지도 못한다([086](086-sealed-sends.md)) |
| 하드웨어 암호 직결 | CPU에 AES 명령이 있으면 AES-128-GCM(이 A76에서 ChaCha20-Poly1305의 2–3배), 없으면 ChaCha20-Poly1305 |
| 메모리 포렌식 면역 | 연 평문은 요청 티켓이 반납될 때 0으로 지운다. 개인키는 OpenSSL 보안 힙에 둔다(잠김, 코어 덤프 제외, 해제 때 지움) |
| 화면 | 노드 안의 Vega([설계 010](../design/010-surfaces.md)). 마지막 저장을 제자리에서 읽고, 친 키와 글자는 소유자의 줄로 |

앱은 이렇게 생겼다. 줄 목록 상태, 화면에서 치는 텍스트 입력 `say`, 상대 노드가 보내는 메시지 입력 `heard`, 상대에게 가는
출력 `said`다. `on say`가 줄을 더하고 `send said`한다. 노드는 이렇게 연결한다.

```sh
pure-serve --module messenger.pmod --store alice --key alice.key --name alice \
    --send said=http://bob:8802/inputs/heard --seal said=BOB_KEY --sender bob.said=BOB_KEY \
    --surface vega-surface.so -- display --symbols ui.vsym --live
```

런타임에 들어간 메신저 전용 코드는 없다. 대화와 연락처는 앱의 상태이고, 보낸 이는 전달의 출처와 키이며, 화면은 표면이다.

## 검사

Vega의 `tests/check.py`의 `messenger`는 앨리스와 밥의 노드를 띄운다. 각자 제 키를 갖고, 서로에게 봉인하며, 상대만을 보낸
이로 받는다. 둘 사이에는 기록하는 중계를 둔다. 각 노드에는 Vega가 픽셀 표면으로 들어 있어, 저장마다 한 프레임을 파일로
그린다.

1. 앨리스의 화면에 녹화한 키로 "hello bob"과 Enter를 친다.
2. 밥의 노드가 그 줄을 보인다.
3. 밥은 프로그램으로(HTTP) "hi alice"를 보낸다.
4. 앨리스의 노드와 화면에 그 줄이 그려진다.

검사는 다음을 확인한다.

- 두 노드의 마지막 프레임이 각 노드의 저장소를 `vega pixels`로 다시 그린 것과 바이트까지 같다.
- 중계가 본 본문에는 줄의 글자도 필드 이름도 없다.
- 방향마다 봉인 세션 하나가 있고, 봉인은 보낸 이의 키를 적는다.

Vega 검사 25개 모두와 함께 통과한다.

## 지연

한 프로그램이 앨리스의 노드에 줄 300개를 하나씩 보내고 둘을 잰다. 하나는 앨리스의 노드가 저장해 답할 때까지이고, 다른
하나는 밥의 노드가 그 줄을 보일 때까지다(밥의 제목 보기를 0.2 ms마다 읽는다). 두 노드는 직접 연결했다. 서버는 코어 0–2를
쓰고, 변형의 순서는 라운드마다 돌렸다([latency.py](../../research/experiments/015-messenger-latency/latency.py),
[원시 결과](../../research/experiments/015-messenger-latency/2026-09-27-sd-and-ram-linux-aarch64.jsonl)).

| 저장소 | 봉인 | 앨리스 저장 p50 | 밥에게 보임 p50 | p90 | p99 |
|---|---|---:|---:|---:|---:|
| RAM(`/dev/shm`) | 봉인·인증 | 0.34 ms | **0.79 ms** | 0.82–0.83 ms | **1.09–1.13 ms** |
| RAM | 없음 | 0.33 ms | 0.78 ms | 0.81–0.84 ms | 0.89–1.05 ms |
| SD 카드(ext4) | 봉인·인증 | 1.64–1.72 ms | 3.45–3.69 ms | 6.7–6.8 ms | 62–78 ms |
| SD 카드 | 없음 | 1.65–1.84 ms | 3.42–3.72 ms | 6.6–10.0 ms | 62–84 ms |

한 줄이 두 노드의 내구 커밋 두 번, HTTP 전달 하나, 봉인과 열기를 거쳐 상대에게 보이기까지 1 ms 안쪽이다. 봉인의 몫은 잡음
안이다. SD 카드의 긴 꼬리는 동기화 자체다. 앨리스의 저장 p99가 이미 20–66 ms이고, 밥의 저장이 또 하나를 더한다. 소프트웨어가
줄일 몫이 아니다. NVMe 같은 장치에서는 RAM 쪽에 가까울 것이다(잴 장치가 아직 없다).

## 한계와 다음

- **전방 비밀성**: 없다. 받는 노드의 키를 훔치면 기록해 둔 세션이 열린다. 받는 키를 돌려 쓰기(서명한 사전 키)가 다음이다.
- **보낸 이 인증**: auth 모드는 키 누설 사칭(KCI)에 약하다. 받는 노드의 키를 가진 쪽은 그 노드에게 누구로든 보낼 수 있다(088).
- **오프라인**: 받는 노드가 꺼져 있으면 보내는 노드의 보낼 함(저장된 send)이 기다린다. 두 노드가 동시에 켜져 있어야 간다.
  늘 켜진 우편함 노드(봉인한 요청을 열지 않고 맡아 두었다 전하는 것)가 다음 후보다.
- **여럿**: 한 출력은 한 수신자에게 간다. 연락처가 여럿이면 필드로 수신자를 고르는 전달이 필요하다.
- **메타데이터**: 출처, 번호, 크기, 보낸 이의 공개키가 중계에 보인다.
- **화면**: 노드 안의 표면은 키와 글자만 준다. 가리키기(터치로 행 고르기)는 표면 표에 아직 없다.