evo-dispatch 세션 중계 종단간 암호화(E2E) 규격 (초안 r2)
원천 · framework/evomind/web/workflow-project-evo-dispatch기준 · 릴리스 v0.1.0커밋 · 734af815파일 · docs/protocol/e2e.md기준선 · 2026-11-12
- 대상: 세션 요청 공간
/mcp의 메시지만. 개발 요청(/mcp/dev)과 버그 신고(/mcp/bugs)는 범위 밖이다. - 기반 규격:
client-protocol.mdv1. 이 문서는 그 위의 선택 확장이며schema_version은"1"그대로다. - 상태: 초안 r2 — r1은 (Human 승인), 서버는. r2는 워크그룹 자동 처리 표시(
extensions.execution)를 서명 범위에 넣는다(§2.3). 사용자 결정 2026-11-03. 구현 Issue: (이 규격), (서버). - 알고리즘 조합 이름:
evo-e2e-1
1. 목표와 한계
섹션 제목: “1. 목표와 한계”서버(dispatch 서버, DB, 운영자)가 메시지의 title·body·links를 읽지 못하게 한다. 결정된 범위(D1~D5)는 다음과 같다.
| 항목 | 정한 것 |
|---|---|
| D1 막는 위협 | 서버가 가만히 읽는 경우(DB 유출, 운영자 열람). 서버가 공개키를 바꿔치기하는 능동 공격은 막지 못한다. 기기 지문 확인은 후속 Issue다 |
| D2 보낸 쪽 서명 | 포함한다. 서버나 제3자가 가짜 요청을 끼워 넣지 못하게 한다 |
| D3 웹 채팅 끝점 | evo-service 백엔드(client_kind: evo-web). dispatch 서버에는 숨기지만 evo-service 서버는 읽을 수 있다 |
| D4 전환 | 평문과 함께 쓰다가, 서버 설정 REQUIRE_E2E로 암호문만 받는다 |
| D5 구현 순서 | Evo CLI → evo-mac → evo-windows → evo-service 백엔드 |
못 지키는 것:
- 메타데이터: 보낸 이·받는 이·시각·
agent·workspace·kind·reply_to·글 크기는 서버가 평문으로 본다. 서버가 라우팅해야 한다. - 모델 제공자: 복호화된 평문은 모델 호출에 들어간다. 이 규격은 dispatch 서버를 막을 뿐이다.
- 공개키 바꿔치기: 서버가 받는 기기의 공개키를 자기 것으로 바꾸고 중간에서 읽을 수 있다(§3.3).
- 다운그레이드:
REQUIRE_E2E를 켜기 전에는 서버가 암호문을 버리고 평문으로 바꿔 보내지 못할 뿐, 보내는 쪽이 평문을 고르는 것은 막을 수 없다. - 재전송: 서버가 서명된 같은 봉투를 다시 줄 수 있다(§5의 확인으로 줄인다).
2. 암호 구성 (evo-e2e-1)
섹션 제목: “2. 암호 구성 (evo-e2e-1)”| 용도 | 방식 |
|---|---|
| 내용 암호화 | 메시지마다 무작위 32바이트 내용 키 K. ChaCha20-Poly1305(RFC 8439), nonce 12바이트(부분마다 새로 무작위), 태그 16바이트 |
| 내용 키 전달 | 받는 기기마다 HPKE(RFC 9180) Base 모드로 K를 감싼다. KEM DHKEM(X25519, HKDF-SHA256)(0x0020), KDF HKDF-SHA256(0x0001), AEAD ChaCha20-Poly1305(0x0003). 감쌀 때마다 새 임시 키를 쓰고 sequence 0 한 번만 쓴다 |
| 보낸 이 인증 | Ed25519(RFC 8032) 서명. HPKE Base는 보낸 이를 인증하지 않아 서명을 따로 붙인다 |
| 인코딩 | 모든 바이너리는 base64url, 패딩 없음(RFC 4648 §5) |
2.1 평문
섹션 제목: “2.1 평문”- 제목 부분:
title의 UT 바이트(1~800바이트. 글자 수 200자는 보내는 쪽이 지킨다). - 본문 부분: UT JSON 객체
{"body": <문자열>, "links": [ ... ]}.links는 없으면 생략한다. 길이는 128KiB(131072바이트) 이하다.body16384자·links20개 같은 형식 규칙은 평문 규격(message.schema.json)과 같고 보내는 쪽과 받는 쪽이 검사한다(서버는 열 수 없다).
2.2 AAD·info 문자열
섹션 제목: “2.2 AAD·info 문자열”VER = evo-dispatch/e2e/1. 아래에서 ‖는 바이트 이어 붙이기, 0x00은 구분 바이트다. 문자열은 모두 UT이다.
ctx=sender_device‖ 0x00 ‖kind‖ 0x00 ‖to‖ 0x00 ‖reply_to‖ 0x00 ‖sent_atkind는request·reply.reply_to는 없으면 빈 문자열.to는 받는 주소의 정규 문자열:user,agent:<이름>,workspace:<이름>,session:<client_kind>:<session_id>,workgroup:<workgroup_id>,workgroup_member:<workgroup_id>:<alias>(워크그룹 주소,workgroup.md. ).- 워크그룹 주소의 받을 기기는
dispatch_workgroups의 그 그룹 멤버 중 보내는 세션이 아닌 것(workgroup_member면 그 별칭 하나)이다. 활성dispatch_sessions행과client_kind·session_id로 맞춰 기기 공개키를 찾고, 공개키가 없는 멤버가 하나라도 있으면 보내지 않는다. sent_at은 봉투의sent_at과 같은 문자열이다.
- 제목 AAD =
VER/title‖ 0x00 ‖ctx - 본문 AAD =
VER/body‖ 0x00 ‖ctx - HPKE
info=VER/hpke - HPKE AAD(기기마다) =
VER/wrap‖ 0x00 ‖sender_device‖ 0x00 ‖recipient_device‖ 0x00 ‖kind‖ 0x00 ‖to‖ 0x00 ‖reply_to‖ 0x00 ‖sent_at - 서명 입력 =
VER/sig‖ 0x00 ‖ctx‖ 0x00 ‖ SHA-256(title_ct원문 바이트) ‖ SHA-256(ct원문 바이트)
2.3 r2: 실행 표시를 서명에 넣기 (봉투 v 2)
섹션 제목: “2.3 r2: 실행 표시를 서명에 넣기 (봉투 v 2)”워크그룹 메시지의 extensions.execution(workgroup.md §4)은 평문으로 오가서, 서버나 중간자가 auto를 켜거나 hop을 낮출 수 있다. r2는 이 값을 서명 범위에 넣는다. 변경은 이것뿐이다.
- 봉투
v가2이면VER는evo-dispatch/e2e/2다.VER로 시작하는 모든 문자열(AAD·info·서명 입력)에 이 값을 쓰므로, r1만 아는 받는 쪽은 서명을 확인하지 못해 버린다(조용히 잘못 처리하지 않는다). ctx의 끝에 한 필드가 붙는다:ctx=sender_device‖ 0x00 ‖kind‖ 0x00 ‖to‖ 0x00 ‖reply_to‖ 0x00 ‖sent_at‖ 0x00 ‖executionexecution은 메시지의extensions.execution에서 만든 정규 문자열이다. 없으면 빈 문자열이다.- 있으면
auto=<0|1>;wg=<workgroup_id>;hop=<n>.auto는true면1,false면0.wg는 소문자 UUID.hop은 생략했어도0을 넣는 10진수(앞의 0 없음).
- 있으면
- 보내는 쪽은
extensions.execution을 두는 메시지에 반드시v2를 쓴다. 두지 않는 메시지는v1을 그대로 써도 되고(execution이 빈 문자열인v2도 된다), 그러면 r1 클라이언트와 계속 통한다. - 받는 쪽은 받은
extensions.execution에서 같은 규칙으로execution을 다시 만들어 서명을 확인한다. 서버나 중간자가 값을 바꿨으면 서명이 맞지 않아 버린다. 자동 처리는v2 봉투의 서명이 확인됐을 때만 한다(v1 봉투의 실행 표시는 무시하고 사람에게 보여 준다). - 서버는
extensions.execution이 있는 암호화 메시지가enc.v2가 아니면invalid_request로 거부한다(형식 검사뿐, 서명은 열지 않는다).v가 1·2 말고 다른 값이어도invalid_request다. - 시험 벡터: r1
vector.json과 r2vector-r2.json(워크그룹 주소workgroup:<id>, 실행 표시auto=1;wg=<id>;hop=0, 봉투v2)이 있다(§예시와 시험).
서명이 kind·to·reply_to·sent_at을 덮으므로, 서버가 요청을 답장으로 바꾸거나 다른 주소로 돌려 보내면 받는 쪽이 알아챈다.
3. 키
섹션 제목: “3. 키”3.1 기기 키
섹션 제목: “3.1 기기 키”- 기기(
device_id)마다 키 쌍 두 개를 만든다: 키 전달용 X25519(kem), 서명용 Ed25519(sign). 개인키는 기기 밖으로 나가지 않는다. - 보관: Evo CLI는
~/.evo/의 0600 파일(device-id와 같은 방식), evo-mac은 Keychain, evo-windows는 OS 보호 저장(safeStorage), evo-service 백엔드는 DB에 서버 비밀로 감싸 보관한다(웹 채팅 사용자마다). 이는 권고이고 구현 Issue에서 정한다. - 키를 잃으면 그 기기로 온 안 읽은 메시지는 풀 수 없다(보존 기간이 짧아 영향이 작다). 새 키를 만들어 다시 올리면 된다.
3.2 공개키 올리기(presence)
섹션 제목: “3.2 공개키 올리기(presence)”활성 신호(POST /v1/presence)에 선택 항목 e2e를 싣는다. 스키마는 schemas/presence.schema.json이다.
"e2e": { "suite": "evo-e2e-1", "kem_public": "<43자>", "sign_public": "<43자>" }kem_public·sign_public은 각각 32바이트 공개키의 base64url이다.- 서버는
device_id에 묶어 마지막 값을 저장한다. 같은device_id가 키를 바꾸면 새 값으로 덮는다(키 교체). e2e가 있는 세션만 암호화 메시지를 받을 수 있다. 없는 세션은 평문만 받는다.
3.3 공개키 알기
섹션 제목: “3.3 공개키 알기”- 보내는 쪽은 보내기 직전에
dispatch_sessions를 불러 받을 기기의 공개키를 얻는다. 결과의 각 세션에device_id와 (있으면)e2e가 추가된다. - 받는 쪽이 보낸 이의 서명 공개키를 알 방법: 서버가
dispatch_inbox·dispatch_read결과에enc가 있는 메시지마다from_keys(보낸 기기의e2e값)를 채운다. 보낸 세션이 이미 끝났어도 키는 서버가 보존 기간 동안device_id별로 가진다. - 서버가 준 공개키는 신뢰 근거가 아니다. 서버가 능동적으로 속이면 막지 못한다(D1). 받는 쪽은 처음 본
device_id의 키를 기억하고(TOFU), 이후 값이 바뀌면 사용자에게 알리는 것을 권고한다(SHOULD). 지문 확인 UX는 후속 Issue다.
4. 봉투
섹션 제목: “4. 봉투”스키마는 schemas/message.schema.json의 $defs/envelope다. dispatch_send가 받고, 메시지의 enc 필드로 저장·전달된다.
| 필드 | 내용 |
|---|---|
v |
1, 또는 r2의 2(§2.3) |
suite |
evo-e2e-1 |
sender_device |
보낸 기기 device_id. 서버가 보낸 세션의 device_id와 같은지 본다 |
sent_at |
보낸 시각(UTC, 평문 스키마의 timestamp 형식) |
title_ct |
base64url(nonce 12 ‖ 암호문 ‖ 태그 16). 내용 키 K로 제목 AAD |
ct |
base64url(nonce 12 ‖ 암호문 ‖ 태그 16). K로 본문 AAD. 본문 부분이다 |
ct_sha256 |
ct 원문 바이트의 SHA-256(base64url). ct가 빠진 inbox 결과에서도 서명을 확인하려는 값이다 |
recipients[] |
device_id, enc(HPKE 캡슐, 32바이트), wrapped_key(감싼 K, 48바이트) |
sig |
서명 입력(§2.2)에 대한 Ed25519 서명 64바이트 |
recipients[]는 1~64개. 같은 기기의 세션이 여럿이어도 기기당 한 항목이다.dispatch_send에서는ct가 필수다. 서버가 돌려주는 모양:- 받는 세션의
dispatch_inbox:ct를 빼고,recipients는 호출 기기 항목만 남긴다.from_keys를 붙인다.title은 없다. - 받는 세션의
dispatch_read:ct를 포함하고recipients는 호출 기기 항목만 남긴다.from_keys를 붙인다.body는 없다. - 보낸 세션(
dispatch_send응답,dispatch_read상태 조회):ct를 빼고recipients항목은 전부 둔다.from_keys는 없다. - 서명은
recipients를 덮지 않으므로 자르는 것이 서명을 깨지 않는다.
- 받는 세션의
- 평문 필드(
title,body,links)와enc를 동시에 두지 않는다. 둘 다 없거나 둘 다 있으면invalid_request다.
5. 절차
섹션 제목: “5. 절차”5.1 보내기
섹션 제목: “5.1 보내기”dispatch_sessions로 받을 세션을 정하고 그 기기들의e2e공개키를 얻는다. 받을 세션 중e2e가 없는 것이 있으면 §7로 간다.- 무작위
K(32바이트)를 만든다. 제목·본문 부분을 §2.2의 AAD로 각각 새 nonce(12바이트 무작위)와 함께 암호화한다. - 받는 기기마다 HPKE로
K를 감싼다. 보내는 기기 자신의 다른 세션이 받는 세션이면 자기 기기도 받는 기기에 넣는다. - 서명 입력을 만들어
sign개인키로 서명한다. dispatch_send를to,kind,reply_to,enc로 부른다(title·body·links는 두지 않는다).recipient_mismatch를 받으면 응답의missing기기 목록을 보고 1단계부터 다시 한다(받는 세션이 그사이 바뀐 것). 두세 번 되풀이하면 사용자에게 알린다.
5.2 받기
섹션 제목: “5.2 받기”dispatch_inbox로 목록을 얻는다.enc메시지는title_ct·ct_sha256·sig와 내 기기 항목,from_keys를 준다.- 서명을 먼저 확인한다. 서명 입력의
SHA-256(ct)자리에는ct_sha256를 쓴다. 확인에 실패하면 그 메시지는 버리고 제목을 보여 주지 않는다. - 내 기기 항목의
enc·wrapped_key로K를 푼 다음title_ct를 풀어 제목을 보여 준다. dispatch_read로ct를 받으면SHA-256(ct)가ct_sha256와 같은지 보고,K로 본문 부분을 풀어 JSON을 읽는다.- 풀거나 검증이 실패하면 그 메시지는 쓸 수 없다. 사용자에게 알리고 평문으로 취급하지 않는다.
- 확인할 것(
SHOULD):(sender_device, 본문 nonce)중복이면 같은 메시지를 다시 받은 것으로 보고 버린다.sent_at이 서버의created_at과 10분 넘게 다르면 의심 표시를 한다.
복호화한 내용도 실행 명령이 아니다. 기존 규칙(규격 §10)이 그대로 적용된다. 서명은 보낸 기기를 증명할 뿐 내용이 안전하다는 뜻이 아니다.
6. 서버 규칙
섹션 제목: “6. 서버 규칙”서버 구현은 다(설정 REQUIRE_E2E, 환경변수 이름 그대로). 서버는 봉투의 내용을 열지 않는다. 본다고 정한 것은 다음뿐이다.
- 스키마 검사(필드 형식, 길이 상한).
ct는 base64url 174800자 이하, 요청 본문은 기존 상한(256KiB) 이하다(64기기 × 약 200자를 더해도 안에 든다). 정의되지 않은 필드는 거부한다. ct_sha256이ct원문의 SHA-256과 같은지(서버가 계산해 본다. 내용은 열지 않는다). 깨진 봉투를 일찍 거른다.sender_device가 호출 세션의device_id와 같다.- 받는 세션 집합(규격 §3)을 계산해, 그 세션들의
device_id가 모두recipients[]에 있어야 한다. 하나라도 없으면 거부하고 저장하지 않는다(recipient_mismatch, 응답에 없는device_id목록missing).recipients[]에 받는 세션이 아닌 기기가 더 있는 것은 허용한다. - 받는 세션 중
e2e공개키가 없는 세션이 있으면recipient_mismatch다(missing에 그 기기). - 서버 설정
REQUIRE_E2E=true이면 평문dispatch_send를e2e_required로 거부한다. 앱 기본값은 꺼짐이고, 배포 compose는 기본적으로 켠다(2026-11-04 전환 종료). - 로그·메트릭에 봉투 내용(
title_ct,ct,wrapped_key,sig)과 개인키를 남기지 않는다. 공개키 전체도 남기지 않는다. - 저장·보존·읽음·만료·정리는 평문 메시지와 같다(규격 §7).
7. 전환
섹션 제목: “7. 전환”- 서버가
enc를 받을 수 있게 되고REQUIRE_E2E는 꺼져 있다. 평문과 암호문이 함께 흐른다. - 클라이언트는 받을 세션이 모두
e2e를 올렸으면 암호화해서 보낸다. 일부라도 못 하면 사용자에게 알리고 평문으로 보낼지 정하게 한다. 몰래 평문으로 내려가지 않는다. 클라이언트 구현에 따라 평문 선택지를 주지 않고 보내기를 막는 쪽도 있다(evo-windows, §11.4). 이 규격은 평문으로 내려가지 않는 것만 요구하고 선택지를 주는 방식은 정하지 않는다. - 모든 클라이언트가 지원하면
REQUIRE_E2E=true로 켠다. 켜면 평문dispatch_send와e2e없는 세션으로의 보내기가 거부된다. 2026-11-04 켰다(cli·mac·windows·web 지원 완료, android·ios는 클라이언트 없음). 배포 compose 기본값도true다. - 평문 메시지를 받는 쪽은 그대로 받는다(옛 클라이언트와 전환 기간의 호환).
8. 오류 코드
섹션 제목: “8. 오류 코드”기존 코드(규격 §9)에 다음을 더한다.
| 코드 | HTTP | 언제 |
|---|---|---|
recipient_mismatch |
409 | 봉투의 recipients[]가 받는 세션의 기기를 다 덮지 못한다, 또는 받는 세션에 e2e 키가 없다. 응답에 missing(기기 device_id 목록, 정렬)이 있다 |
e2e_required |
400 | REQUIRE_E2E가 켜진 서버에 평문으로 보냈다 |
MCP tool의 오류 결과는 같은 모양(error, message, 그리고 missing)이다.
그 밖의 형식 오류(평문과 enc 동시 지정, 스키마 위반, 크기 초과, sender_device 불일치)는 invalid_request다.
9. 시험 벡터
섹션 제목: “9. 시험 벡터”examples/e2e/에 있다. 시험용 키만 쓴다(RFC 8032 7.1 TEST 1의 서명 키, RFC 9180 A.2의 X25519 키). 실제 키·토큰이 아니다.
| 파일 | 내용 |
|---|---|
vector.json |
입력(평문, K, nonce, 임시 키)과 출력(봉투). 받는 기기 두 개 |
vector-r2.json |
r2 벡터. 봉투 v 2, to는 workgroup:<id>, inputs.execution·inputs.extensions가 서명된 실행 표시. 서명 입력 앞부분은 evo-dispatch/e2e/2/sig‖0x00‖sender_device‖0x00‖request‖0x00‖workgroup:<id>‖0x00‖(빈 reply_to)‖0x00‖sent_at‖0x00‖auto=1;wg=<id>;hop=0‖0x00‖… 이다 |
GenerateVector.java |
벡터 생성기(JDK 25 표준 라이브러리). 먼저 RFC 9180 A.2의 값으로 HPKE 구현을 자체 점검한다 |
verify.mjs |
독립 검증기(Node 표준 crypto만 사용, 생성기와 코드 공유 없음). 서명·ct_sha256·내용 키·제목·본문을 확인하고 변조·kind·to 바꿔치기가 거부되는지 본다 |
java docs/protocol/examples/e2e/GenerateVector.java > /tmp/vector.json # 재생성, vector.json과 같아야 한다node docs/protocol/examples/e2e/verify.mjs # 전부 통과해야 한다java docs/protocol/examples/e2e/GenerateVector.java r2 > /tmp/vector-r2.json # 재생성, vector-r2.json과 같아야 한다node docs/protocol/examples/e2e/verify.mjs vector-r2.json # r2도 전부 통과해야 한다새 구현은 vector.json의 envelope를 받는 쪽으로 풀어 inputs와 같은 제목·본문이 나오는지, 보내는 쪽으로 같은 입력에서 같은 봉투가 나오는지(임시 키·nonce를 고정한 시험 모드에서) 확인한다.
10. 미확인·미정
섹션 제목: “10. 미확인·미정”- HPKE 지원: Go 표준
crypto/hpke(문서 확인, 도입 버전 미확인), CryptoKit(macOS 14 이상으로 기억하나 미확인, evo-mac 최소는 macOS 13), Node(내장 HPKE 없음, 위verify.mjs처럼 X25519·HKDF·ChaCha20-Poly1305로 직접 구성 가능), Java(evo-service, 미확인). 모르는 곳은 위 벡터가 맞는지 보고 직접 구성해도 된다. - 기기 지문 확인과 키 바꿔치기 방어(후속 Issue).
- 다중 기기의 키 교체·폐기 절차, 키 백업. 이 규격은 키를 잃으면 안 읽은 메시지를 잃는 것을 받아들인다.
- 개발 요청·버그 신고의 암호화(범위 밖으로 결정).
- 서버가
dispatch_sessions·inbox에 싣는 필드 이름은 에서device_id,e2e,from_keys로 확정했다.examples/valid·invalid에e2e를 실은 활성 신호 예시가 있다(서버 시험이 실제 서버로 보낸다).
11. 개발 서버 실측 기록 (2026-11-04)
섹션 제목: “11. 개발 서버 실측 기록 (2026-11-04)”REQUIRE_E2E=true인 개발 서버에서 암호 메시지가 웹까지 실제로 흐르는 것을 확인했다. 아래는 규격 문장이 아니라 확인한 사실의 기록이다(§1~§10의 규범은 그대로다).
11.1 조건
섹션 제목: “11.1 조건”| 항목 | 값 |
|---|---|
| 서버 | 개발 서버(REQUIRE_E2E=true, 배포 compose 기본값) |
| 보내는 곳 | evo-cli 1.0.0-dev.1565, 개인용 로그인(evo-personal-auth) |
| 받는 곳 | 일반용 웹 — evo-service 백엔드가 client_kind: evo-web으로 활성 신호를 보낸다(§1 D3). 브라우저 탭이 열려 있는 동안만 |
| 시험 계정 | tmp-evo-check(시험 전용) |
| 주소 | to=user — 같은 사용자의 활성 세션 중 보낸 세션을 뺀 나머지 |
11.2 절차와 결과
섹션 제목: “11.2 절차와 결과”- 브라우저에서 웹 탭을 연다 → evo-web 세션이
dispatch_sessions에 나타난다(evo-cli가 확인). - evo-cli가
dispatch_send로 암호 메시지 1건을 보낸다 → 응답은"encrypted for 1 device(s)"(recipients[]에 받는 기기 1개), 메시지 id63375e5e-9f0f-4f92-9e1d-599239d2076b. - 웹 화면에서 본문이 복호화되어 표시된다(evo-service 확인).
- 결과: 성공. 받는 세션이 evo-web 하나였고, 서버가 그 기기를 §6 규칙대로
recipients[]에 고정해 전달했다. - 서명 확인은 통과해야 본문이 열리는 구조(§5.2)라 본문 표시로 갈음한다. 웹 화면의 경고 문구는 따로 점검하지 않았다.
kind는request로 보냈다.dispatch_send의kind는request·reply뿐이라 시험 메시지도 그중 하나다.
11.3 “웹 세션이 안 보인다” 진단 순서
섹션 제목: “11.3 “웹 세션이 안 보인다” 진단 순서”이번에 원인이 세 번 갈렸다. 위에서부터 본다.
- 브라우저 탭이 열려 있는가 — 웹 활성 신호는 탭이 임대(lease)를 갱신하는 동안에만 evo-service 서버가 보낸다. 탭을 닫으면 90초 안에 세션이 사라진다(§4 유효 시간).
- 로그인한 계정의 issuer가 같은가 — 같은 사람이라도 조직용(
evo-auth)과 개인용(evo-personal-auth) 로그인은 iss+sub가 달라 서로 다른 사용자다. 시험 계정은 개인용이다. - 개발 서버를 방금 재배포했는가 — 서버가 메모리에 들고 있던 사용자 SSO 토큰이 사라져, 사용자가 다시 로그인하기 전까지 웹 활성 신호가 나가지 않는다. 재로그인하면 다시 보인다.
- 그 세션에
e2e공개키가 있는가 —dispatch_sessions에서e2e가 비어 있으면 암호 메시지를 받을 수 없어 보내기가recipient_mismatch(missing)로 거부된다(§8).
보내는 쪽이 recipient_mismatch를 받았는데 dispatch_sessions에 세션은 보인다면 4번, 세션 자체가 안 보인다면 1~3번이다.
11.4 클라이언트 쪽 평문 거부 (evo-windows 회신, 2026-11-04)
섹션 제목: “11.4 클라이언트 쪽 평문 거부 (evo-windows 회신, 2026-11-04)”evo-windows 개발 에이전트가 2026-11-04 회신(개발 요청 1d048058)에서 앞선 보고의 문장을 정정하며 자기 앱 동작을 알려 왔다. 규격 문장이 아니라 확인한 사실의 기록이다.
- 모든
dispatch호출이e2eCall을 지난다. 기기 키가 있으면 암호화해 보내고, 키를 읽지 못하면(키 파일을 못 읽는 등) 서버로 보내기 전에 앱이 막고dispatch: not sent — this device has no e2e key (<이유>); <평문 거부 안내>를 보여 준다. - 받는 세션에 키가 없을 때도 같은 형식으로
not sent — N receiving session(s) have no e2e key를 보여 준다. - 그래서 앱은 정상 동작에서 서버의
e2e_required(400)를 받지 않는다. 이 거부 문구는 앱의 단위 시험이 확인했고, 개발 서버에서 앱으로 평문을 보내 400을 받는 시험은 앱 구조상 만들 수 없다고 evo-windows가 밝혔다. - 서버 쪽 거부 경로는 evo-dispatch의 서버 시험
E2eRequiredTest가 덮는다(dispatch.e2e.required=true에서 평문dispatch_send는e2e_required이고 메시지를 저장하지 않는다). - §11.1~§11.3의 실측은
REQUIRE_E2E=true인 서버에서 한 것이다. 앞선 회신10c6beae의 “서버 설정을 켜지 않은 상태에서의 시험”이라는 문장은 설정을 확인하지 않고 쓴 잘못된 서술이었고 evo-windows가 이를 정정했다.
답변은 문서를 근거로 만들지만 틀릴 수 있습니다. 출처 문서를 함께 확인하세요.