노트북 04 — CLI를 통한 Alice↔Bob 전체 세션#

inbox 디렉터리를 공유하는 두 개의 실제 CLI 프로세스를 띄우고, 5번의 왕복 메시지를 관찰합니다.

import os, subprocess, tempfile, shutil
from pathlib import Path

workdir = Path(tempfile.mkdtemp(prefix="pqmsg_nb04_"))
alice_home = workdir / "alice"; alice_home.mkdir()
bob_home   = workdir / "bob";   bob_home.mkdir()
shared_inbox = workdir / "shared_inbox"; shared_inbox.mkdir()
(alice_home / "inbox").symlink_to(shared_inbox, target_is_directory=True)
(bob_home / "inbox").symlink_to(shared_inbox, target_is_directory=True)
print("workdir:", workdir)
workdir: /tmp/pqmsg_nb04__f_wyd4i
def run(home, *args):
    env = os.environ.copy()
    env["PQMSG_HOME"] = str(home)
    r = subprocess.run(["pqmsg"] + list(args), env=env, capture_output=True, text=True, timeout=60)
    return r

print(run(alice_home, "init", "--name", "alice").stdout.strip())
print(run(bob_home,   "init", "--name", "bob").stdout.strip())
Generated identity for 'alice' at /tmp/pqmsg_nb04__f_wyd4i/alice/identity.json
Share your contact file: pqmsg export-contact --output /tmp/alice.pub
Generated identity for 'bob' at /tmp/pqmsg_nb04__f_wyd4i/bob/identity.json
Share your contact file: pqmsg export-contact --output /tmp/bob.pub
alice_pub = workdir / "alice.pub"
bob_pub   = workdir / "bob.pub"
run(alice_home, "export-contact", "--output", str(alice_pub))
run(bob_home,   "export-contact", "--output", str(bob_pub))
run(alice_home, "import-contact", str(bob_pub),   "--as", "bob")
run(bob_home,   "import-contact", str(alice_pub), "--as", "alice")
print("contacts exchanged")
contacts exchanged
for i in range(5):
    msg = f"round {i}: hello from alice"
    r = run(alice_home, "send", "bob", msg)
    print("ALICE:", r.stdout.strip())
    r = run(bob_home, "recv")
    print("BOB:  ", r.stdout.strip())
ALICE: sent message #0 to bob (1827 bytes)
BOB:   [from alice, msg #0]: round 0: hello from alice
ALICE: sent message #1 to bob (335 bytes)
BOB:   [from alice, msg #1]: round 1: hello from alice
ALICE: sent message #2 to bob (335 bytes)
BOB:   [from alice, msg #2]: round 2: hello from alice
ALICE: sent message #3 to bob (335 bytes)
BOB:   [from alice, msg #3]: round 3: hello from alice
ALICE: sent message #4 to bob (335 bytes)
BOB:   [from alice, msg #4]: round 4: hello from alice

각 “ALICE” 줄은 Alice→Bob 체인 키를 전진시키고, 각 “BOB” 줄은 Bob의 Alice→Bob 수신 체인을 전진시킵니다. 양쪽이 동기화된 상태로 유지됩니다.#

print("--- Alice session state ---")
print(run(alice_home, "show-keys", "bob").stdout)
print("--- Bob session state ---")
print(run(bob_home, "show-keys", "alice").stdout)
--- Alice session state ---
peer: bob
send_index: 5
recv_index: 0
chain_key_send (first 8 bytes): fb55d2a095a1c42a
chain_key_recv (first 8 bytes): c83dcc2f29d68d19

--- Bob session state ---
peer: alice
send_index: 0
recv_index: 5
chain_key_send (first 8 bytes): c83dcc2f29d68d19
chain_key_recv (first 8 bytes): fb55d2a095a1c42a
shutil.rmtree(workdir, ignore_errors=True)
print("cleaned", workdir)
cleaned /tmp/pqmsg_nb04__f_wyd4i

방금 본 것#

  • 각각 자체 ~/.pq-messenger 디렉터리를 가진 두 개의 독립적인 OS 프로세스.

  • 공유 파일 큐 “네트워크”.

  • 단일 하이브리드 X3DH 핸드셰이크를 앞에 두고, 5번의 암호화/복호화 왕복.

  • 단조 증가하는 송신/수신 인덱스 — 동작 중인 대칭 래칫.

실제 배포에서는 완전한 Double Ratchet(DH 재키잉), 적절한 상호 인증(서명 + 발행된 prekey), 그리고 오프라인 전송을 위한 서버를 원할 것입니다. 이는 다음 단계의 복잡성으로, 의도적으로 독자에게 남겨두었습니다.