qub 프로토콜 명세

qub은 암호학적 시간 약속을 위한 프로토콜입니다. 단어를 미래 날짜까지 봉인한 뒤, 정확히 무엇이 봉인되었고 어떤 drand 라운드가 공개를 제한했는지 검증하며, 저장소 트랜잭션 또는 투명성 로그 증명이 있는 경우 암호문이 언제까지 약속되었는지 독립적으로 타임스탬프된 상한을 검증하는 시스템입니다.

세 가지 기본 요소가 이를 가능하게 합니다. drand는 탈중앙화된 무작위성 비콘입니다. 공개 일자는 qub의 선의가 아니라 암호학적으로 강제됩니다. 내구성 저장소와 추가 전용 투명성 로그는 봉인된 바이트를 보존하고 일괄 약속을 영구 공개 저장소에 앵커링합니다. 유료 T3 경로는 개별 영구 저장소 트랜잭션도 기록합니다. ML-DSA-65는 포스트양자 디지털 서명입니다. 작성자 서명이 활성화되면 qub은 비밀 키가 작성자의 기기를 떠나지 않는 키 쌍에 결속됩니다.

이 기본 요소들이 함께 만들어내는 진술은 시간 잠금되어 있고 변조가 드러나며, 선택적으로 귀속되고 독립적으로 타임스탬프될 수 있습니다. 세상이 과거를 조작하는 능력이 향상될수록 가치가 커지는 영수증입니다.

이 문서의 나머지 부분은 상호 운용 가능한 구현을 위해 요구되는 규범적 사양입니다.


qub 프로토콜 사양

항목 값
문서 릴리스 1.0.0 (protocol-v1.0.0)
와이어 프로토콜 0x01
외부 래퍼 0x01
시행일 2026-09-23
상태 현재
검토 완료 시점 2026-09-23

이 문서는 qub 시간 약속 시스템의 규범적 프로토콜 사양입니다. 상호 운용 가능한 구현에 요구되는 데이터 구조, 직렬화 규칙, 도출 공식, 검증 절차를 정의합니다.

범위: 프로토콜 계층은 의도적으로 언어 중립적입니다 — qub 본문은 불투명한 평문 / 마크다운 / 약정 바이트이며, 로케일 인식 렌더링은 열람기의 책임입니다 (qub.social 웹 앱, <qub-embed> iframe, MCP 클라이언트 등).


1. 표기법 및 규약

표기 의미
u8, u64, i64 지정된 비트 폭의 부호 없는/부호 있는 정수
[u8; N] N 바이트의 고정 길이 바이트 배열
Vec<u8> 가변 길이 바이트 배열
Option<T> T 타입의 값, 또는 부재
String UTF-8 텍스트 문자열, NFC 정규화됨
`
SHA3-256(x) 바이트 문자열 x의 NIST SHA3-256 해시 (FIPS 202)
ceil(x) 천장 함수: x 이상의 가장 작은 정수
CBOR Concise Binary Object Representation (RFC 8949)
big-endian 최상위 바이트 먼저

원본 이미지 구성에 사용되는 모든 정수는 별도로 명시되지 않는 한 big-endian 고정 폭 바이트 배열로 인코딩됩니다 (i64 → 8 바이트, u8 → 1 바이트).

모든 타임스탬프는 UTC Unix 초입니다.


2. 데이터 구조

2.1 ComposeQub (작성자 인메모리 상태)

CBOR로 직렬화되지 않습니다. 영구 저장소에 기록되지 않습니다. 작성자 앱에 로컬로 존재합니다.

ComposeQub {
    draft_id:       [u8; 16],        // Random, generated locally
    created_at:     i64,             // Unix seconds UTC
    unlock_at:      Option<i64>,     // Unix seconds UTC; None while composing
    visibility:     u8,              // 0x00 = private; 0x01 = public
    content_type:   u8,              // 0x01 text; 0x03 pact; 0x04 verdict
    plaintext:      Vec<u8>,         // Raw body bytes (UTF-8 for text)
    sender_label:   Option<String>,  // Display name; V2-signed when authorship is enabled
    title:          Option<String>,  // Plaintext countdown title; bound via title_hash
    reply_to:       Option<[u8; 32]>,// Parent qub_id; V2-signed when authorship is enabled
    outcome_at:     Option<i64>,     // Optional future judgment time; bound to qub_id
    status:         DraftStatus,     // Composing | Sealed | Uploaded | Failed
}

2.2 QubEnvelope (복호화된 페이로드)

정규 CBOR (§3)을 사용하여 직렬화됩니다. SealedQub 내부에 암호화됩니다. 복호화 후 콘텐츠 무결성을 증명하는 구조입니다.

QubEnvelope {
    version:             u8,              // Protocol major version (0x01 for v1)
    qub_id:              [u8; 32],        // Derived (see §4.1)
    content_type:        u8,              // Content type registry (see §6)
    created_at:          i64,             // Unix seconds UTC
    unlock_at:           i64,             // Unix seconds UTC
    outcome_at:          Option<i64>,     // When reality renders judgment; bound to qub_id
    sender_label:        Option<String>,  // Not in qub_id; V2-signed when authorship is enabled
    reply_to:            Option<[u8; 32]>,// Parent qub_id; not in qub_id; V2-signed when present
    body:                Vec<u8>,         // UTF-8 text or canonical CBOR pact/verdict body
    body_hash:           [u8; 32],        // SHA3-256(body) (see §4.2)
    sig_alg:             u8,              // Signature algorithm (see §9.2)
    author_signature:    Option<Vec<u8>>, // Set when sig_alg != 0x00
    author_pubkey:       Option<Vec<u8>>, // Set when sig_alg != 0x00
    cosigner_pubkey:     Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
    cosigner_signature:  Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}

기준선 (서명되지 않은 텍스트 qub): version = 0x01, content_type = 0x01, sig_alg = 0x00이며 서명 및 공동서명자 필드는 없습니다. 그 밖의 선택적 메타데이터 필드는 존재할 수 있습니다.

기타 v1 구성: content_type = 0x03 (약정 본문, §6.1 참조); sig_alg = 0x01 (ML-DSA-65)에 author_signature 및 author_pubkey 존재 (§9.3 참조); 공동서명된 약정에 대해 cosigner_pubkey 및 cosigner_signature 함께 존재 (§9.7 참조); 회신 체인 qub에 대해 부모 qub의 qub_id로 설정된 reply_to (서명 범위에 관한 함의는 §9.3 참조).

2.3 SealedQub (정규 와이어 형식)

정규 CBOR (§3)을 사용하여 직렬화됩니다. 이것이 내부 와이어 아티팩트입니다. 공개 전달은 이 바이트를 래퍼 없이 저장하고, 비공개 전달은 저장 전에 OuterWrapper로 래핑합니다(§13).

SealedQub {
    version:           u8,              // Protocol major version (0x01 for v1)
    qub_id:            [u8; 32],        // Same as QubEnvelope.qub_id
    visibility:        u8,              // 0x00 = private/wrapped; 0x01 = public/bare
    unlock_at:         i64,             // Unix seconds UTC
    outcome_at:        Option<i64>,     // Surfaced on the verdict-watch CTA
                                        //   before reveal; mirrors QubEnvelope.outcome_at;
                                        //   bound to qub_id via the §4.1 preimage.
    drand_chain_id:    String,          // drand chain hash (hex string)
    drand_round:       u64,             // Target drand round number
    drand_chain_version: Option<u8>,    // W3 — chain-migration version. Absent / 0 = quicknet
                                        //   (the only chain today). Lets a future chain swap
                                        //   be expressed on the wire without a breaking format
                                        //   change. NOT part of the §4.1 qub_id preimage, so its
                                        //   addition never alters an existing qub's identity.
    tlock_ciphertext:  Vec<u8>,         // tlock-encrypted QubEnvelope CBOR bytes
    recipient_pubkey:  Option<[u8; 32]>,// Reserved field; accepted by canonical CBOR
                                        //   but not interpreted by the v1 reference viewer
    title:             Option<String>,  // Plaintext title surfaced on the viewer
                                        //   countdown before reveal. Bound to qub_id
                                        //   via title_hash (§4.1). 1..=100 NFC code
                                        //   points, no hostile/control code points.
}

2.4 RevealedQub (열람기 애플리케이션 상태)

CBOR로 직렬화되지 않습니다. 열람기 앱에 로컬로 존재합니다. 복호화 및 검증 성공 후에 구성됩니다.

RevealedQub {
    qub_id:              [u8; 32],
    arweave_tx_id:       String,
    visibility:          u8,
    content_type:        u8,
    created_at:          i64,
    unlock_at:           i64,
    outcome_at:          Option<i64>,       // Carried from both wire layers; drives the verdict-watch block
    drand_chain_id:      String,
    drand_round:         u64,
    sender_label:        Option<String>,
    title:               Option<String>,    // Carried forward from SealedQub.title
    reply_to:            Option<[u8; 32]>,
    body:                Vec<u8>,
    body_hash:           [u8; 32],
    body_hash_verified:  bool,
    author_signature:    Option<Vec<u8>>,
    author_pubkey:       Option<Vec<u8>>,
    signature_verified:  Option<bool>,
    cosigner_pubkey:     Option<Vec<u8>>,
    cosigner_signature:  Option<Vec<u8>>,
    cosigner_verified:   Option<bool>,
}

3. 정규 CBOR 프로파일

모든 SealedQub 및 QubEnvelope 직렬화는 이 프로파일을 준수해야 합니다. 동일한 논리적 구조가 주어진 두 구현은 동일한 바이트를 생성해야 합니다.

3.1 인코딩 규칙

규칙 사양
표준 RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
맵 키 정렬 인코딩된 바이트 길이 우선 정렬 (짧은 것이 긴 것보다 먼저), 그 다음 사전식 (동일 길이 인코딩에 대해 바이트별)
정수 인코딩 최단 형식: 0–23은 초기 바이트에; 24–255는 2 바이트에; 256–65535는 3 바이트에; 이하 동일
길이 인코딩 확정 길이만 허용. 불확정 길이 배열, 맵, 바이트 문자열, 텍스트 문자열 금지 (추가 정보 = 31 금지).
태그 CBOR 태그 없음 (주 타입 6 금지).
부동 소수점 부동 소수점 없음 (주 타입 7의 값 0xF9–0xFB 금지).
텍스트 문자열 UTF-8 인코딩, NFC 정규화됨 (유니코드 정규화 형식 C).
바이트 문자열 원시 바이트. CBOR 계층에서 base64 인코딩 없음.
중복 키 오류와 함께 거부. 파서는 중복 맵 키를 조용히 받아들여서는 안 됩니다.
알려지지 않은 키 오류와 함께 거부. 파서는 타입의 정규 키 집합 밖에 있는 맵 키를 허용해서는 안 됩니다 — 서로 다른 두 정규 바이트 문자열이 결코 같은 값으로 디코딩되어서는 안 되며 (encode(decode(x)) == x), 서명된 페이로드의 경우 추가 키는 두 서명이 모두 약속하는 숨겨진 콘텐츠가 됩니다. 스키마 진화는 추가 키가 아니라 version을 통해 이루어집니다.
단순 값 true (0xF5), false (0xF4), null (0xF6)만 허용됩니다.
선택적 필드 부재한 선택적 필드는 CBOR 맵에서 완전히 생략됩니다 (null로 인코딩되지 않음). 존재하는 선택적 필드는 정렬된 키 순서로 포함됩니다.

3.2 검증된 정규 키 순서

이 키 순서는 규범적입니다. 구현은 정확히 이 순서로 키를 방출해야 합니다. 디버그 어설션은 비-릴리즈 빌드에서 순서를 검증해야 합니다.

QubEnvelope (버전 0x01, 서명되지 않음, 모든 선택적 필드 부재):

"body"                (5 encoded bytes)
"qub_id"              (7 encoded bytes)
"sig_alg"             (8 encoded bytes)
"version"             (8 encoded bytes)
"reply_to"            (9 encoded bytes)   ← only if present (reply chains)
"body_hash"           (10 encoded bytes)
"unlock_at"           (10 encoded bytes)
"created_at"          (11 encoded bytes)
"outcome_at"          (11 encoded bytes)  ← only if present (verdict mechanic)
"content_type"        (13 encoded bytes)
"sender_label"        (13 encoded bytes)  ← only if present
"author_pubkey"       (14 encoded bytes)  ← only if present
"cosigner_pubkey"     (16 encoded bytes)  ← only if present (pact cosign)
"author_signature"    (17 encoded bytes)  ← only if present
"cosigner_signature"  (19 encoded bytes)  ← only if present (pact cosign)

QubEnvelope 키 순서 도출: 각 키는 CBOR 텍스트 문자열입니다. 인코딩된 길이 = 1 바이트 헤더 + 문자열 길이 (24 바이트 미만의 문자열에 대해). 총 인코딩 길이 우선 정렬, 동일 길이 키에 대해서는 사전식 정렬.

SealedQub (버전 0x01, 공개, 수신자 없음):

"title"             (6 encoded bytes)   ← only if present
"qub_id"            (7 encoded bytes)
"version"           (8 encoded bytes)
"unlock_at"         (10 encoded bytes)
"outcome_at"        (11 encoded bytes)  ← only if present (verdict mechanic)
"visibility"        (11 encoded bytes)
"drand_round"       (12 encoded bytes)
"drand_chain_id"    (15 encoded bytes)
"recipient_pubkey"  (17 encoded bytes)  ← only if present
"tlock_ciphertext"  (17 encoded bytes)
"drand_chain_version" (20 encoded bytes) ← only if present (W3; absent = quicknet)

PactTerms (약정 본문, content_type 0x03):

"notes"         (6 encoded bytes)  ← only if present
"terms"         (6 encoded bytes)
"title"         (6 encoded bytes)
"party_a"       (8 encoded bytes)
"party_b"       (8 encoded bytes)
"pact_version"  (13 encoded bytes)

PactTerm (terms 배열의 행):

"key"    (4 encoded bytes)
"value"  (6 encoded bytes)

PartyIdentifier (party_a / party_b 맵):

"label"    (6 encoded bytes)
"contact"  (8 encoded bytes)  ← only if present

3.3 바이트 인코딩 참조

타입 CBOR 인코딩 예시
SHA3-256 해시 (32 바이트) 0x58 0x20 + 32 바이트 body_hash, qub_id
타임스탬프 (i64) 주 타입 0 (양수) 또는 1 (음수), 최단 인코딩 Unix 초
버전 (u8, 값 1) 0x01 (단일 바이트)
콘텐츠 타입 (u8, 값 1) 0x01 (단일 바이트)
sig_alg (u8, 값 0) 0x00 (단일 바이트)
ML-DSA-65 서명 (3,309 바이트) 0x59 0x0C 0xED + 3,309 바이트 author_signature, cosigner_signature
ML-DSA-65 공개 키 (1,952 바이트) 0x59 0x07 0xA0 + 1,952 바이트 author_pubkey, cosigner_pubkey

4. 규범적 도출

4.1 qub_id

qub_id는 qub을 고유하게 식별하고 QubEnvelope을 SealedQub에 결속합니다. 봉투 콘텐츠로부터 결정론적으로 도출됩니다.

qub_id = SHA3-256(
    "QUB_ID_V2"          ||  // domain separator: ASCII bytes [0x51 0x55 0x42 0x5F 0x49 0x44 0x5F 0x56 0x32] (9 bytes) + 0x00 padding (1 byte) = 10 bytes
    version              ||  // u8 (1 byte)
    content_type         ||  // u8 (1 byte)
    created_at           ||  // i64 big-endian (8 bytes)
    unlock_at            ||  // i64 big-endian (8 bytes)
    outcome_at_or_zero   ||  // i64 big-endian (8 bytes; 0 when outcome_at is absent)
    drand_round          ||  // u64 big-endian (8 bytes)
    body_hash            ||  // [u8; 32] (32 bytes)
    title_hash               // [u8; 32] (32 bytes; absent-sentinel = [0u8; 32])
)
// Total preimage: 108 bytes → 32-byte output

도메인 분리자 인코딩: 문자열 "QUB_ID_V2"는 9개의 ASCII 바이트입니다. 정렬을 위해 10 바이트에 도달하도록 단일 0x00 패딩 바이트가 추가됩니다. 구현은 정확히 다음 10 바이트를 사용해야 합니다: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

outcome_at 인코딩: 릴리스 전 구현 개정에서 선택적 outcome_at 필드를 결속에 포함하기 위해 원본 이미지를 92바이트에서 100바이트로 확장했습니다. 부재한 outcome_at은 8개의 0 바이트로 인코딩됩니다. 프로토콜 검증기는 모든 곳에서 outcome_at <= 0을 거부하므로 이 센티널은 적법한 값과 충돌할 수 없습니다. §3.2(와이어 형식) 및 이 필드의 판정 메커니즘을 설명하는 저장소 내 tasks/verdict-uplift-plan.md를 참조하십시오.

drand_round 인코딩: 이후의 릴리스 전 구현 개정에서 drand_round(대상 drand 라운드, §4.3)를 결속에 포함하기 위해 원본 이미지를 100바이트에서 108바이트로 확장하고 도메인 분리자를 QUB_ID_V2로 올렸습니다. 이는 타임록 라운드를 qub 정체성에 결속합니다. 게이트웨이는 표시된 unlock_at이 의미하는 라운드와 다른 라운드(예: 이미 지난 라운드)로 암호문을 재결속할 수 없습니다. 또한 잠금 해제 절차(§8)는 tlock 암호문 스탠자에 포함된 라운드가 unlock_round(unlock_at)과 일치하는지 검증하므로, 표시된 잠금 해제 시각을 실제 복호화 제한 라운드와 연결합니다.

속성:

4.2 body_hash

body_hash = SHA3-256(body)

여기서 body는 원시 Vec<u8> 콘텐츠 페이로드입니다. 텍스트 qub의 경우, 이는 UTF-8 인코딩된 qub 본문입니다.

4.2.1 title_hash

title_hash = SHA3-256(NFC(title).utf8_bytes)   if title is present
title_hash = [0u8; 32]                         if title is absent

여기서 title은 공개 전에 열람기 카운트다운에 표시되는 선택적 평문 제목입니다 (§3.2 참조). NFC 정규화는 해시 시점에 실행되어 시각적으로 동등한 코드 포인트 시퀀스에 대해 다이제스트가 안정적입니다. 전체 0 센티넬은 부재 케이스를 위해 예약됩니다. 빈 문자열은 "부재"의 비정규 인코딩으로서 정규 CBOR 경계에서 거부됩니다 (정규 인코딩은 필드 전체를 생략합니다).

4.3 잠금 해제 라운드 매핑

drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
매개변수 출처 예시
unlock_at 사용자가 선택한 UTC Unix 초 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time drand 체인 정보 (genesis_time) 1595431050
chain_period_seconds drand 체인 정보 (period) 30

이것이 기준 tlock 매핑(drand의 CurrentRound)입니다. drand는 라운드 N을 chain_genesis_time + (N - 1) * chain_period_seconds에 게시하므로, 이 공식은 unlock_at 시점의 현재 라운드, 즉 unlock_at에 도달한 열람기가 처음 사용할 수 있는 서명의 라운드를 선택합니다.

정렬 속성(실제 배포에서 중요한 경우): (unlock_at - chain_genesis_time)이 chain_period_seconds로 정확히 나누어떨어지면 선택한 라운드의 서명은 정확히 unlock_at에 게시되며 그보다 빠르지 않습니다. 기준 배포에서는 항상 이 조건이 성립합니다. quicknet의 제네시스 시각(1692803367)은 3초 주기로 나누어떨어지고 기준 앱은 잠금 해제 시각을 정각 분으로 고정합니다. 정렬되지 않은 unlock_at에서는 선택한 라운드의 서명이 unlock_at보다 한 비콘 주기 미만 먼저 게시됩니다. 시간 약속의 정밀도는 비콘 한 주기입니다.

기존 릴리스 전 매핑과 잠금 해제 측 허용 오차: 원래 매핑은 ceil((unlock_at - chain_genesis_time) / chain_period_seconds)였습니다. 위의 주기 정렬 경우에는 unlock_at보다 정확히 한 주기 전에 게시되는 라운드를 선택해 암호문을 한 주기 일찍 복호화할 수 있었습니다. 두 매핑은 델타가 주기로 나누어떨어질 때 정확히 +1 차이가 나며 그 밖에는 일치합니다. drand_round가 변경 불가능한 qub_id 원본 이미지에 포함되므로(§4.1) 기존 매핑으로 봉인된 아티팩트는 다시 도출할 수 없습니다. 따라서 §8 단계 6a 라운드 교차 검사를 수행하는 검증기는 저장된 drand_round가 도출된 라운드 또는 도출된 라운드에서 1을 뺀 값인 경우를 모두 허용해야 하며, tlock 스탠자 라운드는 저장된 라운드와 정확히 같아야 합니다. 이 허용 오차가 앞당길 수 있는 가장 이른 제한 서명은 한 주기뿐입니다. 약정 스테이징 서비스도 스테이징과 공동서명 시 동일한 허용 오차를 적용합니다. 현재 매핑의 라운드가 약속된 qub_id를 재현하지 못하고 델타가 주기로 나누어떨어지면 1을 뺀 라운드로 재시도하고, 재계산된 라운드로 맹목적으로 봉인하지 않고 qub_id가 실제로 결속하는 라운드로 완성된 약정을 봉인합니다.

검증: unlock_at은 봉인 시점에 미래여야 합니다. unlock_at은 created_at으로부터 10년 이상 떨어져서는 안 됩니다 (장기 drand 의존성 리스크를 제한하기 위함; UI는 2년 이상의 공개 일자에 대해 경고해야 합니다).


5. 와이어 형식 뉴타입

와이어 형식 뉴타입은 CBOR 바이트를 JSON, 원시 평문, 또는 다른 바이트 인코딩과 혼동하는 것에 대한 컴파일 타임 안전성을 제공합니다.

타입 포함 생성자 소비자
SealedQubCbor SealedQub의 정규 CBOR serialize_sealed_qub() 내부 와이어 아티팩트. 공개 전달에서는 래퍼 없이, 비공개 전달에서는 래핑되어 저장된 뒤 열람기에 의해 복구됨
QubEnvelopeCbor QubEnvelope의 정규 CBOR serialize_qub_envelope() tlock 암호화 입력, tlock 복호화 출력

5.1 생성 규칙

// Production code — only through CBOR serialisers:
let sealed = SealedQubCbor::from_encoded(cbor_bytes);

// There is deliberately NO From<Vec<u8>> implementation.
// You cannot accidentally wrap arbitrary bytes in a wire format type.

// Accessing raw bytes:
let bytes: &[u8] = sealed.as_bytes();
let bytes: Vec<u8> = sealed.into_bytes();

5.2 생성 시 검증

from_encoded()는 입력이 유효한 CBOR 맵 헤더로 시작하는지 검증해야 합니다. 이중 파싱을 피하기 위해 완전한 구조적 검증은 생성 시점이 아니라 파싱 시점에 일어납니다.


6. 콘텐츠 타입 레지스트리

값 타입 최대 본문 크기 비고
0x00 예약 (무효) — 사용해서는 안 됩니다
0x01 평문 텍스트 (UTF-8, 제한된 마크다운) 유료 50 KB / 무료 10 KB 렌더링 규칙은 §10 참조. 무료 / 유료 분할은 업로드 서비스에 의해 강제됩니다. 프로토콜 계층의 절대 상한은 50 KB입니다.
0x02 예약 (향후) — 향후 콘텐츠 타입을 위해 할당됨; v1에서는 유효하지 않습니다. 열람기는 아래 규칙에 따라 거부해야 합니다.
0x03 약정 (양자 합의, CBOR 본문) 100 KB 본문은 정규 CBOR PactTerms입니다 (§6.1). 공동서명자 서명은 §9.7에 따릅니다.
0x04 판정 (작성자 자기 채점, CBOR 본문) 8 KB 본문은 정규 CBOR VerdictBody입니다 (§6.2). 시스템 측 verdict 의도에 의해서만 방출됩니다. 상위 관계는 본문이 아니라 Parent-Tx-Id Arweave 태그에 있습니다. verdict-uplift-plan §3.4 참조.

열람기는 알려지지 않은 콘텐츠 타입을 명확한 사용자 가시 오류와 함께 거부해야 합니다. 열람기는 알려지지 않은 타입을 텍스트로 렌더링하려 시도해서는 안 됩니다.

6.1 약정 본문 (content_type = 0x03)

약정 본문은 PactTerms 값의 정규 CBOR 인코딩입니다:

PactTerms {
    pact_version:  u8,                    // 0x01 for structured/v1
    title:         String,                // ≤ 200 bytes, NFC
    terms:         Vec<PactTerm>,         // ≤ 20 rows
    party_a:       PartyIdentifier,       // initiator
    party_b:       PartyIdentifier,       // counter-signer
    notes:         Option<String>,        // ≤ 5,000 bytes, NFC; absent key if none
}

PactTerm       { key: String (≤ 100 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }

세 맵 모두에 대한 정규 CBOR 키 순서는 §3.2에 주어져 있습니다. 직렬화된 약정 CBOR 총 크기는 100 KB를 초과해서는 안 됩니다 (§6과 일치).

스키마 판별자. structured/v1 약정의 terms의 첫 번째 행은 { key: "pact_schema", value: "structured/v1" }이어야 합니다. 이 마커가 없는 행은 "custom" 약정이며 구조화된 검증이나 스키마 인식 렌더링을 받지 않습니다.

고정된 확인 슬롯. structured/v1 약정은 다음 키 하에 정확히 4개의 확인 행을 가집니다:

"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"

각각의 value는 (role, kind) 쌍에 의해 선택된 8개의 고정된 영어 문자열 중 하나이며, 여기서 role ∈ { seller, buyer, provider, client }, kind ∈ { standard, capacity }입니다. 문자열 자체는 규범적 프로토콜 데이터입니다 — 두 당사자의 ML-DSA-65 서명이 body_hash를 통해 정확한 바이트에 결속됩니다. 이들은 현지화되지 않습니다; 서명된 본문은 언어 중립적입니다. 어떠한 문구 변경도 새로운 스키마 버전 (structured/v2)을 요구합니다.

8개의 문자열, 그 조회 (acknowledgement_for(role, kind)), 그리고 각각의 근거는 참조 구현에 의해 고정됩니다. 준수 구현은 바이트 동일한 확인 값을 방출해야 합니다; 네 가지 역할 조합 모두를 다루는 골든 픽스처 SHA3-256 body-hash 테스트가 어떤 드리프트라도 포착합니다.

열람기 표시 순서. 확인 문자열에는 "described above"와 같은 어구가 포함되어 있으며, 이는 설명 / 범위 행이 확인보다 먼저 렌더링됨을 전제로 합니다. 열람기는 terms 배열을 CBOR 순서로 렌더링해야 합니다; 재정렬은 산문의 의미를 깨뜨립니다.

상대방 연락처. 당사자 B의 contact가 유효한 이메일 주소일 때, qub 업로드 서비스는 스테이지 시점에 검토 / 공동서명 초대 이메일을 자동 발송하고, 이후의 공동서명을 동일한 주소의 검증과 결속합니다 (§9.7). 당사자 B 연락처가 부재한 약정은 여전히 공동서명될 수 있지만, 오직 대역 외 채널을 통해서만 가능합니다 — 서비스는 일치하는 15분 이메일 검증 마커를 생성할 수 없는 공동서명 요청을 거부합니다.

6.2 판정 본문 (content_type = 0x04)

판정 본문은 VerdictBody 값의 정규 CBOR 인코딩입니다:

VerdictBody {
    verdict_version: u8,                  // 0x01 for structured/v1
    outcome:         u8,                  // 1=Right · 2=Partial · 3=Wrong · 4=Unfalsifiable
    reflection:      Option<String>,      // ≤ 2,000 bytes NFC; "what changed, what did you learn"
    evidence_url:    Option<String>,      // ≤ 2,048 bytes; HTTPS only; absent key when omitted
}

정규 CBOR 키 순서:

"outcome"          (8 encoded bytes)
"reflection"       (11 encoded bytes)  ← only if present
"evidence_url"     (13 encoded bytes)  ← only if present
"verdict_version"  (16 encoded bytes)

직렬화된 판정 CBOR 총 크기는 8 KB를 초과해서는 안 됩니다 (위 레지스트리 행과 일치).

Outcome 열거형. 와이어 바이트는 의도 중립적입니다; 네 가지 구분 Right / Partial / Wrong / Unfalsifiable은 판정을 동반하는 모든 의도의 결과 공간을 포괄합니다. 의도별 라벨(Right에 대해 "적중", "지킴", "출시", "확인" 등)은 상위 qub의 의도에 따라 해소되는 열람기 측 렌더링 사항입니다 — 와이어는 언어 및 의도 중립으로 유지됩니다. 1..=4 범위를 벗어난 값은 디코드 시점에 거부해야 합니다.

상위 연결. 판정 qub은 본문에 상위 참조를 담지 않습니다. 상위 qub의 Arweave 트랜잭션 id는 업로드 시점에 Parent-Tx-Id 저장소 태그로 방출됩니다(§7 저장소 태그 계층). 이렇게 하면 본문은 자기 평가에 대한 자체 완결적 서명 진술로 유지되며, 감사 체인("무엇에 대해 맞았는가?")은 Arweave 태그 조회를 통해 성립됩니다.

증거 URL 안전성 (규범적). evidence_url이 존재할 때, 검증자(작성 측, 와이어 측, Worker 엣지)는 다음을 강제해야 합니다:

  1. HTTPS만. 문자열은 바이트 시퀀스 https://로 시작해야 합니다. 그 외의 스킴 — http, ftp, javascript, data, file 등 — 은 거부합니다.
  2. 길이 상한. ≤ 2,048 바이트 (브라우저 URL 실용 한계).
  3. NFC + 적대적 코드포인트 확인. title 및 reflection과 동일한 규칙 — bidi-override / 영폭 / tag-block / BOM / C0 / C1 코드포인트는 거부합니다. 정의는 Rust의 crate::handle::contains_hostile_text_codepoint 및 TS의 workers/api/src/utils/unicode.ts::isHostileCodepoint와 일치해야 합니다 (일치 유지).
  4. 공백 없음, ASCII 제어 문자 없음. URL 어디에서도 공백 / DEL / 0x20 미만 바이트는 거부합니다 — bidi 규칙이 막지 못하는 \n/\t 주입 벡터를 차단합니다.
  5. 비어 있지 않은 호스트 세그먼트. https://와 첫 /, ?, 또는 # 사이의 모든 것은 비어 있지 않아야 합니다.

서버 측 페치 없음. Worker는 URL을 프록시하거나, 페치하거나, 미리보기 해서는 안 됩니다. 프로토콜은 문자열을 저장하며, 렌더링은 rel="nofollow noopener noreferrer" target="_blank"와 함께 링크 텍스트 옆에 표시되는 호스트와 함께 열람기 측에서 일어납니다.

회고. 선택적인 작성자 작성 회고 텍스트("무엇이 바뀌었나요, 무엇을 배우셨나요"). title과 동일한 NFC + 적대적 코드포인트 검증. 비어 있거나 공백만 있는 입력은 구성 시점에 부재로 접힙니다.

스키마 버전. v1은 verdict_version = 0x01만 지원합니다. 향후의 스키마 개정은 이 바이트를 올리고 §12에 따라 새로운 프로토콜 버전과 함께 배포합니다.


7. 봉인 프로토콜

완전한 봉인 시퀀스. 각 단계는 규범적입니다.

 1. User composes plaintext and metadata in ComposeQub.
 2. Validate:
    a. body is non-empty.
    b. body size ≤ max for content_type and user tier (see §6).
    c. unlock_at is in the future.
    d. unlock_at ≤ created_at + 10 years.
    e. content_type is a known, supported value.
    f. visibility is 0x00 (private) or 0x01 (public).
 3. Compute body_hash = SHA3-256(body).
 4. Set created_at = current Unix seconds UTC.
 5. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
    compute drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
    (§4.3). (Computed here, before qub_id, because drand_round is bound into the
    qub_id preimage—§4.1.)
 6. Compute qub_id (see §4.1), folding in drand_round from step 5.
 7. Construct QubEnvelope with all fields.
 8. Serialise QubEnvelope using canonical CBOR → bytes B.
    Assert: serialised output matches canonical profile (§3).
 9. Compute C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Construct SealedQub with tlock_ciphertext = C and matching qub_id, version,
    visibility, unlock_at, drand_chain_id, and drand_round.
11. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12. Select the delivery shape from visibility:
    a. Private (0x00): generate K = 32 random bytes and N = 12 random bytes
       using a CSPRNG. Compute W = wrap_sealed_qub(SealedQubCbor,
       qub_id=qub_id, key=K, nonce=N) per §13. Upload payload = W.
    b. Public (0x01): upload payload = bare SealedQubCbor; do not generate K.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit the selected upload payload to the qub upload service. For a private
    browser seal, the service is byte-blind to the inner SealedQubCbor and never
    receives K. The Builder `/api/v1/seal` route is an explicit exception: it
    receives plaintext and caller-supplied K in memory, then persists neither.
16. Receive arweave_tx_id from the service. For private delivery, construct
    `<origin>/c/<arweave_tx_id>#<base64url(K)>` (or the equivalent short-code
    path). For public delivery, omit the fragment. Browsers do not transmit URL
    fragments to servers, so K from the browser-seal path is not observed by
    qub.social or any storage gateway.

저장소 태그 계층(대역 외). qub 업로드 서비스는 선택된 업로드 페이로드와 함께 의도적으로 작은 저장소 트랜잭션 태그 세트를 첨부합니다. Content-Type=application/octet-stream은 규범적으로 요구됩니다. 참조 서비스는 작성자가 노출하기로 선택한 경우 세 개의 선택적 태그를 추가로 첨부합니다. Intent(허용 목록으로 검증한 작성 의도 — announcement, thesis, prediction, letter, secret, commitment, proof 또는 시스템이 방출한 verdict), Author(작성자의 §9.3 공개 키 지문을 64자의 소문자 16진수로 표현), Parent-Tx-Id(회신 체인에서 상위 qub의 저장소 트랜잭션 ID, 43자의 base64url)입니다.

Author 태그는 qub별 옵트인입니다. 참조 작성자 앱은 사용자가 봉인 시점에 공개 귀속을 명시적으로 활성화한 경우에만 이를 첨부합니다. 기본값인 토글 꺼짐 상태에서는 Author 태그가 기록되지 않고 qub은 체인상에서 귀속되지 않습니다. 영구 저장소의 어떤 정보도 업로드를 작성자의 핸들, 이메일 또는 다른 qub과 연결하지 않습니다. 토글이 켜져 있을 때 Author 지문은 §9.5 증명 체인을 통해 작성자가 선택한 @handle로 해석됩니다. 회신 체인 관계와 Intent는 신원을 식별하지 않습니다. 비공개 전달의 경우 외부 래퍼(§13)는 식별 가능한 내부 SealedQub 아티팩트를 암호화하므로, 저장된 래퍼를 수집하고 공개 drand 서명을 얻더라도 K 없이는 본문을 복구할 수 없습니다. 저장소 태그는 의도적으로 공개 메타데이터로 유지됩니다.

참조 서비스는 의도적으로 App-Name, App-Version, 또는 Type 태그를 첨부하지 않습니다: 그러한 단일 값 필터는 GraphQL 쿼리에 qub 전체 말뭉치를 반환할 것이며, 이는 래퍼의 본문 전용 기밀성 범위와 일치하지 않습니다.

준수 검증자는 §11 제3자 검증을 위해 어떠한 저장소 태그에도 의존해서는 안 됩니다; 본문 해시 / qub_id / 서명은 오직 내부 CBOR에만 결속되며, 태그 세트에는 절대 결속되지 않습니다.


8. 잠금 해제 프로토콜

완전한 잠금 해제 시퀀스. 각 단계는 규범적입니다.

 1. Viewer opens delivery URL. Extract arweave_tx_id from the path and retain
    the optional URL fragment. Do not assume a missing fragment is an error:
    public/bare delivery intentionally has no K.
 2. Check denylist. If tx_id is denylisted → display block message. Stop.
 3. Fetch the stored bytes (with multi-gateway fallback).
 3a. Resolve the delivery shape structurally:
    a. If the bytes parse as OuterWrapper, require a well-formed 32-byte K in
       the URL fragment, require wrapper version 0x01, and unwrap per §13.
       Any missing/malformed K or AEAD failure is a terminal error.
    b. Otherwise require the bytes to parse as bare SealedQubCbor; no K is
       required. If neither shape parses, report an integrity error.
 4. Parse SealedQubCbor → SealedQub.
 5. Validate: SealedQub.version is known (0x01), visibility is known, and the
    delivery shape matches it (wrapped = 0x00 private; bare = 0x01 public).
    Reject any mismatch or unknown value.
 6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
 6a. Round-binding check. Recompute expected_round from
    SealedQub.unlock_at per §4.3. Reject unless SealedQub.drand_round ==
    expected_round OR SealedQub.drand_round == expected_round - 1 (the
    legacy pre-release mapping—see §4.3), AND the round baked into the tlock
    ciphertext stanza (read via the age/tlock header, no signature required)
    == SealedQub.drand_round exactly. The stanza round is the one that
    actually gates decryption; without this check a malicious creator could
    bind the ciphertext to an already-past round while displaying a future
    countdown, so anyone reading the stored bytes could decrypt before
    unlock_at. Implementations with no chain identity (test mocks) skip this
    check.
 7. Once current time ≥ SealedQub.unlock_at:
    a. Fetch drand round signature for SealedQub.drand_round from drand network.
    b. Compute B = tlock_decrypt(SealedQub.tlock_ciphertext, round_signature).
 8. Parse B → QubEnvelope.
 9. Validate QubEnvelope.version is known.
10. Verify: SHA3-256(QubEnvelope.body) == QubEnvelope.body_hash.
    Fail → integrity error.
11. Verify: QubEnvelope.qub_id == SealedQub.qub_id.
    Fail → integrity error.
12. Verify: QubEnvelope.unlock_at == SealedQub.unlock_at.
    Fail → integrity error.
12a. Verify: QubEnvelope.outcome_at == SealedQub.outcome_at (both absent, or
    both present and equal). Fail → integrity error.
12b. Content re-derivation. Recompute qub_id per §4.1 from the decrypted
    fields — (QubEnvelope.version, content_type, created_at, unlock_at,
    outcome_at, SealedQub.drand_round, QubEnvelope.body_hash,
    title_hash(SealedQub.title)) — and verify it equals SealedQub.qub_id.
    Fail → integrity error. The pairwise checks in steps 10-12a only prove
    the two layers agree with EACH OTHER; a forger who rewrites a bound
    field consistently on both surfaces (a pre-reveal title swap, or a
    post-round body swap with a recomputed body_hash re-encrypted to the
    same round under the same qub_id) passes them all. Only re-deriving
    the identity from content closes this.
13. Verify: QubEnvelope.content_type is known and renderable.
    Known values: 0x01 (text), 0x03 (pact), 0x04 (verdict).
    Unknown → display error.
14. If QubEnvelope.sig_alg != 0x00 → verify author signature (see §9.4).
15. If cosigner_pubkey or cosigner_signature present → verify cosigner (see §9.7).
16. Render content using the appropriate renderer (see §10 for text and §6 for pact/verdict).
17. Construct RevealedQub for display.

9. 작성자 서명

9.1 근거

qub은 영구 저장소에 저장됩니다. 작성자 서명은 무기한 위조 불가능해야 하며, 이것이 v1.0이 qub의 영구 수명 내에 보안성이 저하될 수 있는 고전적 방식이 아닌 포스트 양자 ML-DSA-65 방식 (FIPS 204)을 사용하는 이유입니다.

9.2 알고리즘 레지스트리

sig_alg 방식 키 크기 서명 크기 상태
0x00 서명 없음(미서명) — — 활성
0x01 ML-DSA-65 (FIPS 204) 1,952바이트 3,309바이트 활성
0x02 Ed25519 32바이트 64바이트 예약된 상수, 프로토콜 v1에서는 지원되지 않음

프로토콜 v1 열람기는 예약된 0x02 값을 포함해 {0x00, 0x01} 이외의 모든 값을 거부해야 합니다. 예약은 우발적인 재사용을 방지할 뿐 활성화를 뜻하지 않습니다. 이를 활성화하려면 §15에 설명한 통제된 변경이 필요합니다.

9.3 서명 원본 이미지 구성

두 가지 원본 이미지 버전이 존재해 왔습니다. 모든 서명은 V2를 사용해야 하며, 검증자는 V2만 수용해야 합니다. 레거시 V1 원본 이미지 (역사적 참조를 위해 아래에 문서화됨)는 V2 마이그레이션 동안 검증 전용 폴백으로 수용되었습니다; 그 폴백은 폐기되었으며 V1 전용 서명은 이제 거부됩니다.

V2 (현행 — 모든 신규 작성자 서명, 그리고 약정 스테이징 / 공동서명 흐름의 두 서명 모두에 의해 생성됨):

sig_input = SHA3-256(
    "QUB_AUTHOR_SIG_V2"  ||    // domain separator (17 bytes)
    version              ||    // u8 (1 byte)
    qub_id               ||    // [u8; 32] (32 bytes)
    body_hash            ||    // [u8; 32] (32 bytes)
    unlock_at            ||    // i64 big-endian (8 bytes)
    0x00                 ||    // u8 (1 byte): MUST be 0x00 in v1.x
    sender_label_hash    ||    // [u8; 32]: SHA3-256(NFC(sender_label)),
                               //   or 32 zero bytes when absent
    reply_to_or_zero           // [u8; 32]: parent qub_id, or 32 zero
                               //   bytes when absent
)

// Total preimage: 155 bytes → 32-byte hash

signature = Sign(author_secret_key, sig_input)

sender_label_hash는 title_hash (§4.2.1)와 동일한 부재 센티넬 관례를 따릅니다: 32개의 0 바이트는 유효한 SHA3-256 출력이 아니므로, "부재"는 존재하는 라벨과 절대 충돌할 수 없습니다. 모든 필드는 고정 너비이므로, 원본 이미지는 길이 접두사 없이도 모호하지 않습니다.

V1 (레거시 — 폐기됨; 더 이상 생성되지 않으며 검증 시 더 이상 수용되지 않음):

sig_input = SHA3-256(
    "QUB_AUTHOR_SIG_V1"  ||    // domain separator (17 bytes)
    version              ||    // u8 (1 byte)
    qub_id               ||    // [u8; 32] (32 bytes)
    body_hash            ||    // [u8; 32] (32 bytes)
    unlock_at            ||    // i64 big-endian (8 bytes)
    0x00                       // u8 (1 byte): MUST be 0x00 in v1.0
)

// Total preimage: 91 bytes → 32-byte hash

V1 원본 이미지는 sender_label과 reply_to를 생략했습니다. 이는 V2로의 마이그레이션 동안 검증 전용 폴백으로 수용되었습니다; 그 폴백은 이후 폐기되었으며 — 검증자는 V2 원본 이미지만 수용해야 합니다. 이 정의는 역사적 참조와 아래의 도메인 분리자 설명을 위해 여기에 보존됩니다. V1에 대해서만 검증되는 서명은 검증 실패로 취급되어야 합니다.

도메인 분리자: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2"는 각각 17개의 ASCII 바이트입니다 ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). 패딩 없음. 상이한 분리자가 두 구성을 도메인 분리하므로, 한 원본 이미지에 대한 서명은 다른 것으로 절대 검증될 수 없습니다.

org_id_present 바이트: unlock_at 다음 바이트는 0x00이어야 합니다. 참조 구현은 이를 crates/qub-core/src/signing.rs에서 상수 ORG_ID_PRESENT_INDIVIDUAL = 0x00으로 노출합니다; 검증을 위해 sig_input을 재구성하는 열람기는 동일한 바이트를 방출해야 합니다.

서명 범위 — 무엇이 다루어지고 무엇이 다루어지지 않는가. V2 sig_input은 version, qub_id, body_hash, unlock_at, sender_label, reply_to에 직접 결속됩니다 (고정된 도메인 분리자 및 org_id_present 바이트에 더해). qub_id 자체는 §4.1 원본 이미지를 통해 version, content_type, created_at, unlock_at, outcome_at, drand_round, body_hash로부터 도출되므로, 그 필드들의 어떤 변경도 다른 qub_id를 생성하고 서명을 전이적으로 무효화합니다. 따라서 인증되는 표면은:

필드 서명에 의해 인증됨 방법
version ✓ sig_input에 대한 직접 입력
qub_id ✓ 직접 입력
body_hash ✓ 직접 입력
unlock_at ✓ 직접 입력
sender_label ✓ sender_label_hash를 통한 직접 입력 (V2 원본 이미지 — 유일하게 수용되는 형식)
reply_to ✓ reply_to_or_zero를 통한 직접 입력 (V2 원본 이미지 — 유일하게 수용되는 형식)
content_type ✓ 전이적으로, qub_id 원본 이미지를 통해
created_at ✓ 전이적으로, qub_id 원본 이미지를 통해
outcome_at ✓ 전이적으로, qub_id 원본 이미지를 통해
drand_round ✓ 전이적으로, qub_id 원본 이미지를 통해
body ✓ 전이적으로, body_hash = SHA3-256(body)를 통해
author_pubkey — (암묵적) 서명을 검증한 키가 정의상 작성자임
cosigner_pubkey / cosigner_signature — 동일한 sig_input에 대해 독립적으로 서명됨 (§9.7 참조)
drand_chain_id, tlock_ciphertext, visibility — 외부 SealedQub 필드, 봉투 내부에 없음 — 자체 구조적 불변량 (라운드 / 체인 일관성)에 의해 다루어지지만 작성자 서명에 의해서는 다루어지지 않음. (drand_round은 이제 qub_id 원본 이미지를 통해 전이적으로 결속됩니다 — 위 참조.)

왜 V2가 유일하게 수용되는 원본 이미지인가.

sender_label 또는 reply_to를 최종 사용자에게 표시하는 구현은 라벨이 아니라 인증된 정체성 (공개 키 지문, 증명)을 주요 정체성 신호로 표면화해야 합니다.

9.4 검증 절차

1. Read sig_alg from QubEnvelope.
2. If sig_alg == 0x00 → unsigned. No verification. Display "unsigned qub."
3. If sig_alg is unknown → reject. Display "unrecognised signature scheme."
4. Extract author_signature and author_pubkey. If either is absent → integrity error.
5. Reconstruct sig_input using fields from QubEnvelope (V2 formula, §9.3).
6. Verify(author_pubkey, sig_input, author_signature). The V2 preimage is the
   only accepted form — the legacy V1 fallback is retired (§9.3), so a
   signature that does not verify against V2 fails, full stop.
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."

서명 검증은 가장 비용이 큰 연산입니다 (특히 ML-DSA-65). 더 저렴한 모든 검사 (해시, qub_id, unlock_at)가 통과된 후에 수행되어야 합니다.

9.5 정체성 증명

정체성 증명 — author_pubkey를 qub 핸들, 이메일 주소, 소셜 핸들, 패스키 자격 증명과 같은 사람이 인식 가능한 정체성 청구로 매핑하는 것 — 은 열람기 측 점진적 향상이며 서명 검증에 필요하지 않습니다. 증명을 표시 정체성으로 해석하는 열람기는 다음 우선순위를 적용해야 합니다:

handle > email > social > fingerprint

지문 폴백은 SHA3-256(author_pubkey)의 소문자 16진수입니다; 이는 서명된 어떤 qub에 대해서도 항상 사용 가능합니다. 열람기는 표시를 위해 이를 축약할 수 있습니다 — 참조 열람기는 qub: 뒤에 처음과 마지막 4 바이트를 렌더링합니다 (qub:<8 hex>…<8 hex>).

준수 검증자는 qub API에 연락하지 않고, 영구 저장소 및 drand 이외의 어떠한 네트워크 없이도, 어떠한 서버측 조회 없이도 §9.4의 모든 검사를 완료할 수 있습니다. 증명 해석은 서명 검증이 성공한 후에만 수행되는 별도의 최선 노력 단계입니다.

9.6 크기 영향

Ed25519 ML-DSA-65
서명 64 바이트 3,309 바이트
공개 키 32 바이트 1,952 바이트
qub당 총합 96 바이트 5,261 바이트
저장 비용 차이 (~$5/MB 기준) ~$0.0005 ~$0.026

500–2,000 바이트의 텍스트 qub에 대해, ML-DSA-65는 저장 크기를 대략 세 배로 만듭니다. 절대 비용은 무시할 만합니다.

9.7 공동서명자 검증 (약정 양자 합의)

양자 합의 (content_type = 0x03)에 대해, 두 번째 서명 계층은 두 당사자 모두가 동일한 조건에 동의했음을 증명합니다.

봉투 필드:

두 필드 모두 함께 존재하거나 모두 부재해야 합니다. 정확히 하나만 존재하면, 열람기는 무결성 오류를 보고해야 합니다.

검증 절차:

1. If cosigner_pubkey absent and cosigner_signature absent → no cosigner. Done.
2. If exactly one is present → integrity error.
3. Verify cosigner_pubkey != author_pubkey (prevent self-cosigning).
   Fail → display "cosigner pubkey must differ from author."
4. Reconstruct sig_input using the same formula as §9.3 (V2 only — the
   legacy V1 fallback is retired; all pact clients produce V2 signatures).
5. Verify(cosigner_pubkey, sig_input, cosigner_signature).
6. Success → display "co-signed by [cosigner fingerprint]."
7. Failure → display "co-signature verification failed."

속성:

이메일 결속 게이트 (운영적). 스테이지된 약정이 당사자 B 이메일 연락처 (§6.1)를 포함할 때, qub 업로드 서비스는 스테이징 ID 및 그 연락처의 정규화 이메일 해시 모두와 일치하는 단명 이메일 검증 마커가 존재하지 않으면 공동서명 요청을 거부해야 합니다. 매직 링크 토큰이 staging_id를 포함하고 검증된 주소가 SHA-256(normalise_email(party_b.contact))과 일치할 때 /api/v1/auth/verify에 의해 마커가 작성됩니다 — 여기서 normalise_email(addr)은 로컬 부분의 대소문자를 보존하고 도메인 부분만 소문자화하며 (RFC 5321 §2.3.11에 따라), 여기서 SHA-256은 NIST FIPS 180-4 해시입니다 (§4 도출에서 사용되는 SHA3-256과는 구별됨) — 발행 후 900초 (15분) 후에 만료됩니다. 이는 운영적 사칭 방지 게이트이며, 온체인 qub 증명의 일부가 아닙니다 — §11을 재현하는 제3자 검증자는 어떠한 서버측 조회 없이 영구 저장소 및 drand만을 필요로 합니다. 마커는 오직 서버측에만 존재하며 서명된 본문의 일부가 절대 아닙니다.

크기 영향 (ML-DSA-65 작성자 + 공동서명자):

구성 요소 크기
작성자 서명 3,309 바이트
작성자 공개 키 1,952 바이트
공동서명자 서명 3,309 바이트
공동서명자 공개 키 1,952 바이트
총 암호 오버헤드 10,522 바이트
저장 비용 차이 ~$0.05

10. 마크다운 렌더링 및 살균

이 절은 보안에 결정적입니다. 열람기는 제한된 마크다운 부분집합을 사용하여 텍스트 qub (content_type = 0x01)을 렌더링합니다.

10.1 허용되는 요소

10.2 금지되는 요소

요소 처리
원시 HTML (<div>, <script> 등) 완전히 제거됨. 어떠한 HTML도 통과하지 않음.
이미지 (![alt](url)) 제거됨. 이미지 구문은 출력에서 제거됨.
링크 ([text](url)) URL이 보이는 평문으로 렌더링됨. 자동 링크 없음. 명시적 사용자 행동 없이는 클릭 불가능.
위험한 URL 스킴 javascript:, data:, vbscript:, file: — 제거됨.
iframe, 임베드, 객체 제거됨.
HTML 엔티티 안전한 경우에만 표시 문자로 디코딩됨.

10.3 구현

구현은 차단 목록이 아닌 엄격한 허용 목록 파서를 사용해야 합니다. 권장 접근 방식:

  1. pulldown-cmark (또는 동등물)을 사용하여 마크다운을 파싱합니다.
  2. AST를 순회하며 허용 목록 (§10.1)에 없는 노드를 모두 제거합니다.
  3. 링크 노드의 경우: URL을 클릭 가능한 <a> 요소가 아닌 보이는 텍스트로 방출합니다.
  4. 필터링된 AST를 타입화된 중간 표현으로 변환합니다 (예: 안전한 변형만 가지는 MarkdownNode 열거형). 원시 HTML은 이 IR에서 구조적으로 표현 불가능합니다.
  5. 타입화된 IR로부터 대상 뷰 계층으로 렌더링합니다 (예: 반응형 뷰 컴포넌트, DOM 노드). 어느 지점에서도 HTML 문자열 연결 또는 innerHTML 사용 없음.

차단 목록 접근 방식은 새로운 마크다운 확장이나 파서의 특이사항이 필터링되지 않은 요소를 도입할 수 있어 취약합니다. 타입화된 AST 접근 방식은 XSS를 구조적으로 불가능하게 만듭니다 — 임의의 HTML을 담을 수 있는 변형이 존재하지 않습니다.

10.4 크기 및 구조 제한


11. 제3자 검증

저장된 바이트(비공개/래핑된 qub이라면 K도 함께)를 보유한 제3자는 누구나 qub의 협조 없이 암호화 아티팩트를 검증할 수 있습니다. 독립적으로 타임스탬프가 지정된 존재 주장을 하려면 qub별 영구 저장소 포함을 검증하거나 §16 투명성 로그 증명을 검증해야 합니다.

1. Obtain the stored bytes. For a private delivery, also obtain K from the
   delivery-link fragment.
2. Resolve the delivery shape (§8 step 3a): unwrap OuterWrapper with K, or
   accept bare SealedQubCbor. Require shape/visibility agreement.
3. Parse SealedQubCbor → SealedQub; validate protocol version, visibility,
   content type, chain identity, and structural bounds.
4. Recompute expected_round from unlock_at (§4.3); require the stored round
   (allowing the documented legacy minus-one case) and ciphertext-stanza round
   to agree exactly as §8 step 6a specifies.
5. Obtain the drand signature for SealedQub.drand_round and verify its BLS
   signature against the pinned chain public key.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify envelope/sealed equality for qub_id, unlock_at, and outcome_at.
10. Recompute qub_id from the decrypted fields, sealed drand_round, and title;
    require it to equal the carried qub_id (§4.1).
11. If sig_alg != 0x00, verify the V2 author signature (§9.4). If cosigner
    fields are present, verify their pairing, key separation, and signature
    (§9.7).
12. For an existence-time claim, independently verify either:
    a. the permanent-storage transaction's data-to-id binding, owner, block
       inclusion, and block timestamp; or
    b. a §16 inclusion proof through its pinned anchor and anchor block time.
13. Report each verified claim separately; do not collapse an absent storage
    or anchor proof into a successful timing verdict.

검증이 증명하는 것:

증명 입력 확립하는 내용
유효한 번들/봉인 아티팩트 + drand 서명 복구된 본문이 body_hash와 일치하고, qub_id에 결속된 메타데이터가 온전하며, 암호문이 선언된 drand 라운드에 결속되고 그 라운드가 경과했음을 확립합니다. 암호문이 언제 생성되었는지는 확립하지 않습니다.
유효한 V2 작성자/공동서명자 서명 해당 비밀 키의 보유자가 §9.3의 서명된 표면을 인증했음을 확립합니다.
독립적으로 검증한 qub별 저장소 트랜잭션 정확히 그 저장 암호문이 블록 타임스탬프 이전에 존재했음을 확립합니다.
유효한 앵커링된 투명성 로그 증명 앵커 블록이 제공하는 약속 시각 상한을 포함하여 §16.11의 리프 종류별 주장을 확립합니다.

검증이 증명하지 않는 것:

비증명 이유
작성권 sender_label은 장식적입니다. sig_alg ≥ 0x01 없이는 누구든 이 콘텐츠를 봉인했을 수 있습니다.
의도 아티팩트는 바이트와 암호학적 관계를 증명하지, 작성자가 주관적으로 의미한 바를 증명하지 않습니다.
.qub만으로 성립하는 기존 약속 작성자는 결속된 라운드가 지난 뒤에도 유효한 번들을 조립할 수 있습니다. 포함된 drand 서명은 라운드가 지났음을 증명하지만, 암호문이 그 전에 존재했음을 증명하지 않습니다.
정확한 봉인 버튼 시각 저장소 또는 앵커 블록 타임스탬프는 독립적으로 검증 가능한 상한이며, 사용자의 로컬 동작보다 늦을 수 있습니다. sealed_at/received_at 주장은 증거가 아닙니다.

구현된 투명성 로그(§16)는 변조 증거가 남는 순서와 신뢰가 필요 없는 약속 시각 상한(앵커 블록 시각)을 qub 전반으로 확장하며, 범위는 리프 종류에 따라 정해집니다(§16.11). 작성권이나 의도를 추가하지 않으며, 기본 바이트 맹목 업로드 경로 자체로는 body_hash나 drand_round를 증명하지 않습니다. 이들은 계속 아티팩트 검사에서 도출됩니다.


12. 버전 관리 및 릴리스 통제

문서 릴리스, 내부 와이어 프로토콜, 외부 래퍼는 서로 별개의 버전 공간입니다. 따라서 문서만의 명확화가 바이트를 암묵적으로 변경하지 않으며, 향후 와이어 마이그레이션이 편집상의 개정으로 가장할 수도 없습니다.

12.1 문서 릴리스 버전

이 사양은 의미적 문서 릴리스(MAJOR.MINOR.PATCH)와 변경 불가능한 Git 태그 protocol-v<release>를 사용합니다.

릴리스 상태는 초안(아직 규범적이지 않음), 현재(유일하게 권장되는 구현 대상), 대체됨(과거 검증을 위해 보존됨) 중 하나입니다. 버전 없는 /protocol 경로는 현재 릴리스를 표시하며, 릴리스 태그는 정확한 소스와 함께 게시된 모든 로케일을 보존합니다. 상태나 릴리스 번호를 변경하려면 같은 검토 변경에서 이 표와 릴리스 이력을 함께 갱신해야 합니다.

문서 릴리스 발효일 상태 와이어 프로토콜 래퍼 소스
1.0.0 2026-09-23 현재 0x01 0x01 protocol-v1.0.0

12.2 프로토콜 버전

SealedQub 및 QubEnvelope 모두의 version 필드 (u8)는 주 프로토콜 버전을 식별합니다.

12.3 프로토콜 버전 이력

버전 값 설명
v1 0x01 비공개/래핑 및 공개/비래핑 전달, 텍스트(0x01), 약정(0x03), 판정(0x04) 본문, ML-DSA-65 V2 작성자/공동서명자 서명, drand quicknet tlock, SHA3-256.

12.4 정방향 호환성

알려지지 않은 CBOR 맵 키 (§3.2 정규 순서에 없는 키)를 가진 QubEnvelope을 만나는 v1 열람기는 디코드 오류와 함께 그것을 거부해야 합니다 (§3.1). 정방향 호환성은 키 허용이 아니라 version 필드에 실립니다: 향후의 추가 (사소한 메타데이터라도) 는 새로운 version 값 아래 배포되며, v1 열람기는 서명이 약속하는 콘텐츠를 조용히 버리는 대신 명확한 "더 새로운 프로토콜" 오류와 함께 그것을 거부합니다.

sig_alg = 0x01 (ML-DSA-65)을 만나지만 ML-DSA-65 검증 지원이 부족한 v1 열람기는 qub을 완전히 거부하기보다 "서명은 존재하나 검증할 수 없음" 알림과 함께 qub 콘텐츠를 표시해야 합니다. 참조 구현은 오늘 v1 레지스트리에 다른 유효한 알고리즘이 없기 때문에 0x00 및 0x01을 제외한 모든 sig_alg 값을 거부합니다 — 엄격한 거부와 소프트 페일은 세 번째 알고리즘이 등록될 때까지 관찰적으로 동일합니다. §9.2가 새 항목을 인정하는 순간 위의 소프트 페일 동작은 부담을 지게 되며, 그 시점에서 참조 열람기는 소프트 페일하도록 업데이트될 것입니다.

12.5 외부 래퍼 버전

§13에 기술된 OuterWrapper는 SealedQub.version 및 QubEnvelope.version과 독립적인 자체 version 바이트를 가집니다. 두 버전 공간은 별도로 발전합니다: 향후 포스트 양자 안전 대칭 교체는 내부 프로토콜 버전을 건드리지 않고 래퍼 바이트를 올리고, 향후 프로토콜 계층 추가 (예: 새 봉투 필드)는 래퍼 바이트를 건드리지 않고 내부 버전을 올립니다.

OUTER_WRAPPER_VERSION_* 값 알고리즘 상태
OUTER_WRAPPER_VERSION_1 0x01 12바이트 논스, 16바이트 인증 태그를 가진 AES-256-GCM, qub_id에 결속된 AAD 비공개 전달에 활성
— 0x02–0xFF 예약됨 향후

열람기는 알려지지 않은 래퍼 버전을 명확한 오류와 함께 거부해야 합니다. 프로토콜은 구체적인 마이그레이션 동인이 나타날 때까지 (예: 다른 AEAD를 선호하는 NIST 지침) 의도적으로 래퍼 버전 공간을 좁게 유지합니다; 0x02 슬롯은 알고리즘을 도입하는 동일한 개정판에서 할당될 것입니다.


13. 외부 암호화 래퍼

13.1 근거

프로토콜 계층 (QubEnvelope → tlock → SealedQub)은 봉인된 qub을 시간 잠금시킵니다: 본문은 unlock_at까지 그리고 drand 라운드 서명이 공개될 때까지 읽을 수 없습니다. 그러나 잠금 해제 후에는 라운드 서명이 공개되고 SealedQub의 정규 CBOR 형태가 인식 가능하므로, 영구 저장소 트랜잭션을 색인한 수집자는 qub 전체 말뭉치를 대량으로 복호화할 수 있습니다.

비공개 전달에서는 외부 암호화 래퍼가 정규 SealedQubCbor와 저장된 바이트 사이에 대칭 AEAD 계층을 추가해 그 경로를 차단합니다. 브라우저 봉인 경로에서 256비트 키 K는 전달 URL의 URL 프래그먼트와 사용자 기기에만 존재합니다. 브라우저는 URL 프래그먼트를 서버로 전송하지 않으므로 qub.social, 모든 저장소 게이트웨이와 그 앞의 모든 CDN은 관찰상 K를 알 수 없습니다. 따라서 비공개 qub의 저장 표현은 작성자가 공유하기로 한 URL 없이는 평문을 복구할 수 없는 불투명한 암호문입니다. 공개 전달은 이 계층을 의도적으로 생략합니다(§13.8).

순효과:

13.2 계층화

plaintext body                       ← QubEnvelope.body (§2.2)
  ↓ canonical CBOR (§3)
envelope CBOR
  ↓ tlock encrypt to drand round (§7 step 10)
tlock_ciphertext (inside SealedQub) (§2.3)
  ↓ canonical CBOR (§3)
SealedQubCbor bytes                  ← inner wire artifact
  ├─ public (visibility=0x01) ───────────────▶ stored directly (§13.8)
  └─ private (visibility=0x00)
       ↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12, this section)
     OuterWrapper CBOR bytes         ← stored private payload

프로토콜 계층의 봉인 및 잠금 해제 (§7, §8)는 래퍼 경계 아래에서 변경되지 않습니다; 래퍼는 seal()의 호출 사이트에서 부착되고 unlock()의 호출 사이트에서 분리됩니다.

13.3 OuterWrapper 데이터 구조

struct OuterWrapper {
    version:    u8,           // 0x01, see §12.5
    qub_id:     [u8; 32],     // copied from inner SealedQub; AEAD AAD
    nonce:      [u8; 12],     // 96-bit AEAD nonce
    ciphertext: Vec<u8>,      // AES-256-GCM(K, nonce, SealedQubCbor, AAD=qub_id) || 16-byte tag
}

필드 불변량.

CBOR 인코딩. 동일한 키 정렬 규칙 (인코딩된 바이트 길이 오름차순, 그 다음 사전식 정렬)을 가진 §3에 따른 정규 CBOR. 네 개의 키는:

키 인코딩된 바이트 순서
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

따라서 OuterWrapper CBOR의 첫 바이트는 4-항목 맵에 대한 확정 길이 맵 헤더 (0xA4)입니다.

13.4 qub_id에 대한 AAD 결속

래퍼는 qub_id를 AEAD 추가 인증 데이터로 결속합니다. 이는 세 종류의 공격에 대한 부담을 지는 구조적 방어입니다:

공격 방어
래퍼의 다른 qub_id 필드 아래로 암호문 이동 AAD 불일치 → AEAD 인증 실패
qub A의 URL 프래그먼트를 qub B의 저장 바이트와 혼합 잘못된 키(그리고 독립적으로 결속된 AAD) → AEAD 인증 실패
업로드 후 래퍼의 qub_id 필드 변조 AAD 불일치 → AEAD 인증 실패

래퍼 평문에 qub_id를 포함시키는 것은 열거 면역성을 의미 있게 약화시키지 않습니다 — qub_id 자체가 다이제스트로부터 복구 가능한 원본 이미지가 없는 §4.1 원본 이미지의 SHA3-256 해시이며, 래퍼 바이트를 이미 수집한 열거자는 업로드의 존재 자체로부터 추론할 수 없는 어떠한 것도 가시적인 qub_id로부터 알 수 없습니다.

13.5 래핑 및 언래핑 알고리즘

wrap_sealed_qub(SealedQubCbor S, qub_id Q, key K, nonce N):
    require K.len() == 32 and N.len() == 12 and Q.len() == 32
    I := canonical_cbor_decode(S) as SealedQub
    require I.qub_id == Q               // reject mismatched caller AAD
    C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
    // C includes the 16-byte authentication tag at the end
    return canonical_cbor_encode(OuterWrapper{
        version:    0x01,
        qub_id:     Q,
        nonce:      N,
        ciphertext: C,
    })

unwrap_sealed_qub(OuterWrapper bytes W, key K):
    require K.len() == 32
    O := canonical_cbor_decode(W) as OuterWrapper
    require O.version == 0x01           // §12.5
    P := AES_256_GCM_decrypt(
            key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
         )
    // any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
    S := canonical_cbor_decode(P) as SealedQub
    require S.qub_id == O.qub_id       // explicit inner/outer cross-check
    return P                            // P is the validated SealedQubCbor

실패 모드 붕괴. 잘못된 K, 잘못된 논스, AAD 불일치, 변조된 암호문은 모두 동일한 DECRYPT_FAILED 오류를 생성합니다. 이는 의도적인 AEAD 속성입니다: 실패 모드를 구별한다면 원격 공격자가 잘못된 형식의 래퍼를 보내고 응답 시간을 측정하여 탐색할 수 있는 사이드 채널을 만들어낼 것입니다. 참조 구현은 모든 AEAD 실패를 단일 오류 형태로 붕괴시켜야 합니다.

13.6 키 자료 및 배포

래핑 키 K는 CSPRNG에 의해 qub당 생성되는 256비트 균일 무작위 값입니다. 참조 구현은 이를 다음에서 소싱합니다:

배포: K는 URL 안전 base64 (RFC 4648 §5, 패딩 없음)로 인코딩되어 전달 링크의 프래그먼트 구성 요소로 추가되어야 합니다:

delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>

프래그먼트는 준수하는 브라우저에 의해 어떠한 서버로도 전송되지 않습니다. 사용자 기기를 넘어 프래그먼트를 포함한 전체 전달 링크를 유지하는 복구 채널 (서버측 기록 색인, 옵트인 이메일 자동 전송)은 기본 암호 분쇄 자세에 대한 명시적인 교환이며, 명시적인 사용자 동의에 게이트되어야 합니다.

프래그먼트 손실. 사용자가 URL 프래그먼트를 잃고 복구 채널이 없으면, qub은 읽을 수 없습니다. 이는 설계의 부담을 지는 교환이며 봉인 시점에 사용자에게 공개되어야 합니다. MVP는 명시적인 "이 URL을 저장하라" 문구 및 옵트인하는 사용자에 대한 검증된 이메일 복구 채널로 봉인 시점 공개를 강화합니다.

13.7 이 절의 범위 외

13.8 공개 qub (래퍼 생략)

외부 래퍼는 전달 계층에서 선택적입니다. 작성자는 qub을 공개로 봉인할 수 있으며, 이 경우 정규 SealedQubCbor는 OuterWrapper 계층과 키 K 없이 저장 파이프라인으로 직접 들어갑니다.

SealedQubCbor bytes  ──(public)──▶  stored as-is
SealedQubCbor bytes  ──(private)─▶  AES-256-GCM(K, …) ▶ OuterWrapper ▶ stored

공개 qub은 시간 잠금되지만 링크 게이트되지 않습니다. drand 라운드가 게시될 때까지 읽을 수 없지만(tlock 계층은 변경되지 않음), 잠금 해제 후에는 저장소 트랜잭션 ID를 가진 누구나 복호화할 수 있습니다. K가 없으므로 URL 프래그먼트가 필요하지 않습니다. 이는 서버가 구동해야 하는 표면을 위한 의도적인 절충입니다. 공개 알림 이메일, 프래그먼트가 없는 oEmbed/자동 임베드 링크, 더 풍부한 공개 후 SEO에는 서버가 보유하지 않는 비밀 없이 작동하는 링크가 필요합니다(§13.6). 게시자가 완전한 프래그먼트 포함 역량을 제공하면 비공개 qub도 명시적 <qub-embed src="full_delivery_url"> 형식을 사용할 수 있습니다.

생산자가 반드시 고려해야 하는 결과:

비공개 (래핑됨)가 기본값으로 유지됩니다; 공개는 명시적인 qub별 작성자 선택입니다.


14. 테스트 벡터

14.1 qub_id 도출

Input:
  version      = 0x01
  content_type = 0x01
  created_at   = 1735689600 (2025-01-01 00:00:00 UTC)
  unlock_at    = 1736294400 (2025-01-08 00:00:00 UTC)
  outcome_at   = absent
  drand_round  = 4695446  (= floor((1736294400 - 1595431050) / 30) + 1, §4.3 mapping, drand mainnet params §14.2)
  body         = "Hello, future."  (UTF-8, 14 bytes)
  title        = absent

Intermediate:
  body_hash  = SHA3-256("Hello, future.")
             = 76ab8b3f843c6ed4f2d0fd75b9f457b4
               ad49dd4450f9c22723ae430e3af3211d
  title_hash = [0u8; 32]   (title absent — §4.2.1 sentinel)

Domain separator (10 bytes):
  [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]

Preimage (108 bytes—current protocol v1):
  domain_separator    ||  // 10 bytes
  0x01                ||  // version
  0x01                ||  // content_type
  0x0000000067748580  ||  // created_at as i64 big-endian (1735689600)
  0x00000000677DC000  ||  // unlock_at as i64 big-endian (1736294400)
  0x0000000000000000  ||  // outcome_at_or_zero (outcome_at absent)
  0x000000000047A596  ||  // drand_round as u64 big-endian (4695446)
  body_hash           ||  // 32 bytes
  title_hash              // 32 bytes (all-zeros sentinel; title absent)

Expected output:
  qub_id = SHA3-256(preimage)
         = 4a84e3dfaec32954949c30073f8e6506
           fd3204c1bb97f9162b81c7587afe412e

구현은 이 입력에 대해 동일한 body_hash와 qub_id 값을 산출해야 합니다. 이 테스트 벡터는 가장 먼저 작성되는 단위 테스트여야 합니다. 위 정규 값은 참조 구현이 계산했으며 비트 단위로 일치해야 합니다. 과거 출시 전 프로토타입 레이아웃(어떤 라이브 qub도 처음 두 레이아웃에 의존하지 않음)은 outcome_at 이전에 92바이트(3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0), outcome_at_or_zero 추가 후에 100바이트(b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed)를 사용했습니다. 현재 108바이트 레이아웃은 이어서 drand_round를 추가하고 QUB_ID_V2 도메인 구분자를 사용합니다. 초기 108바이트 벡터는 레거시 ceil 라운드 매핑(drand_round = 4695445)을 사용해 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4를 생성했습니다. 해당 라운드 입력에 대해서는 여전히 유효한 qub_id지만, 위 예는 §4.3의 현재 라운드 매핑을 따릅니다.

14.2 잠금 해제 라운드 매핑

Input:
  unlock_at           = 1735689600
  chain_genesis_time  = 1595431050
  chain_period_seconds = 30

Calculation:
  (1735689600 - 1595431050) / 30 = 4675285.0
  floor(4675285.0) + 1 = 4675286

drand_round = 4675286

라운드 4675286은 1595431050 + (4675286 - 1) * 30 = 1735689600, 즉 정확히 unlock_at에 게시되며 그보다 일찍 게시되지 않습니다. 레거시 출시 전 ceil 매핑은 30초 이른 1735689570에 게시되는 4675285를 산출했으며, 검증자는 §4.3에 따라 이 레거시 라운드를 수용합니다.

14.3 정규 CBOR 라운드 트립

구현은 모든 유효한 입력에 대해 serialize(parse(serialize(qub))) == serialize(qub)을 검증해야 합니다. 이는 단일 벡터가 아닌 속성 테스트입니다.

14.4 PactTerms CBOR (content_type 0x03)

Input:
  pact_version = 1
  title        = "Scooter deposit"
  terms        = [
    { key: "Item",    value: "Honda Metropolitan scooter" },
    { key: "Price",   value: "$100" },
    { key: "Deposit", value: "$10" }
  ]
  party_a      = { label: "Alice" }
  party_b      = { label: "Bob", contact: "bob@example.com" }
  notes        = absent

Canonical CBOR key order (PactTerms):
  "notes"(6) < "terms"(6) < "title"(6) < "party_a"(8) < "party_b"(8) < "pact_version"(13)

Canonical CBOR key order (PactTerm):
  "key"(4) < "value"(6)

Canonical CBOR key order (PartyIdentifier):
  "label"(6) < "contact"(8)

정규 CBOR 바이트 및 SHA3-256 body_hash는 참조 구현에 의해 계산됩니다. 구현은 이 입력에 대해 바이트 동일한 CBOR을 생성해야 합니다.

구현은 또한 모든 유효한 PactTerms 입력에 대해 serialize(parse(serialize(pact))) == serialize(pact)을 검증해야 합니다 (속성 테스트).

14.5 외부 래퍼 교차 언어 벡터

외부 래퍼 (§13)는 crates/qub-core/tests/vectors/wrapper_v1.json에 별도의 정규 픽스처를 가집니다. 각 케이스는 (key, nonce, qub_id, sealed_cbor) 튜플을 불투명한 16진수 입력으로 고정하고 특정 expected_wrapper_hex 출력을 단언합니다. 두 참조 구현 모두 동일한 JSON 파일을 소비합니다:

픽스처는 현재 저수준 래퍼 케이스 세 개를 고정합니다. 이들은 §13.8 전달 형태 불변량과 독립적으로 결정적 OuterWrapper 인코딩과 AEAD 상호운용성을 검사합니다. 특히 역사적인 이름 basic-text-public과 내부 visibility = 0x01은 생성된 래핑 바이트를 준수하는 공개 전달로 만들지 않습니다. 생산자는 여전히 공개 내부 바이트를 비래핑 상태로 저장하고 비공개(0x00) 내부 바이트만 래핑해야 합니다.

케이스 범위
basic-text-public 역사적인 저수준 픽스처 이름입니다. 선택적 필드가 없는 가장 작은 현실적인 SealedQub 형태로, 래퍼 바이트만 검사하며 §13.8을 준수하는 저장 전달은 아닙니다.
with-recipient-pubkey recipient_pubkey가 설정된 SealedQub(예약된 향후 경로)입니다. 다른 내부 CBOR 키 세트를 검사하며, 픽스처 내용이 서로 다르기 때문에 독립적으로 다른 qub_id를 생성합니다(recipient_pubkey 자체는 §4.1 원본 이미지에 포함되지 않음).
longer-body ~4 KiB 본문 — 내부 봉투와 외부 암호문 모두 안에서 멀티바이트 CBOR 길이 접두사를 실행합니다.

구현은 기록된 입력에 대해 바이트 동일한 expected_wrapper_hex를 생성해야 합니다. 픽스처 재생성은 QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors를 요구하며 의도적인 형식 변경을 위해 예약됩니다.


15. 암호 프로파일 거버넌스 (향후)

이 절은 v1에 대해 정보적이며 qub의 암호 기본 요소 중 어디라도 두 번째 알고리즘이 진입하는 첫 시점에 규범적이 됩니다.

15.1 현재 자세

프로토콜 v1은 기본 요소당 정확히 하나의 알고리즘에 결속됩니다:

검증자는 현재 활성 기본 요소별로 키와 서명 길이를 하드코딩합니다. sig_alg 및 래퍼 버전 바이트는 명시적 선택자지만, v1은 대역 내 협상을 수행하지 않고 위의 활성 값만 허용합니다.

15.2 의도된 형태

두 번째 알고리즘이 프로토콜에 진입할 때, 검증자는 기본 요소별로 허용되는 값의 정확한 집합 — sig_alg, drand 체인, 래퍼 버전, 콘텐츠 타입 — 을 나열하는 이름 붙은 CryptoProfile (예: ExqubV1)에 대해 구성될 것입니다. 프로파일은 검증 시점에 고정되며, 대역 내에서 협상되지 않습니다. 활성 프로파일 밖의 어떠한 값도 거부됩니다.

이는 ML-DSA-87을 추가하거나 Ed25519를 활성화하는 것이 기존 검증자 구성을 소급적으로 약화시킬 수 없음을 보장합니다: v1 검증자는 v2 프로파일이 게시된 후에도 v1 검증자로 남습니다.

15.3 트리거 조건

다음 중 어떠한 것이라도 제안되면 §15를 규범적 상태로 승격합니다:

그때까지 §15는 향후 PR이 협상 표면을 처음부터 재논의하기보다 알려진 대상에 대해 착지하도록 마이그레이션 형태를 고정하는 자리 표시자입니다.


16. 투명성 로그 및 내구성 계층 (구현 완료 — 검토 완료)

상태. 이 섹션은 구현된 (W5/UP-B1, 단계 1–8), 여기 명시된 프로듀서 및 트러스트 루트 범위와 함께. 와이어 형식, 해싱, 검증기 경로는 작동 중입니다: 코어 머클 + 표준 CBOR 타입(qub-core), TypeScript 미러 + ANS-104 번들러 (workers/api/src/crypto/), 단일 작가 LogDO + 좌표 키 기반 R2 노드 저장소, 그 /upload 로그 추가 시도, 일일 앵커 + 번들러 배수 크론, 그 GET /api/v1/qub/:tx_id/proof (포함) 그리고 GET /api/v1/log/consistency (RFC 9162) 증명 엔드포인트, 포함 증명의 타입화된 형태가 담긴 .qub 번들 (§17.5), 네이티브 ANS-104 앵커 검증기 (tools/qub-verify), 그리고 이중 자가 출판 헤드 훅(§16.6). 성공적인 /upload 항상 R2-내구성이 있지만 ~일 때만 로그로 덮여 있습니다 LOG_DO 구성되고 인라인 추가가 성공하면; 그때서야 응답이 전달됩니다 log_seq, receipt, 그리고 anchor_status. 만약 RECEIPT_SK 없거나 잘못된 경우, 그 영수증의 sig_b64url 비어 있으며 부인 방지를 제공하지 않습니다. 현재 /seal 그리고 pact 게시 경로는 개별 Arweave 트랜잭션을 예약하지만 로그 리프를 추가하지 않습니다. 현재 어떤 코드도 수행하지 않습니다 /upload 추가 실패 후 나중에 제안된 조정에 대한 논평. W5 외부 검토가 완료되었습니다: §16.15는 설계 결정과 출시 제약을 기록하지만, 이러한 제약은 방금 언급한 생산자 범위를 확장하지 않습니다. 세 개의 신뢰/배포 항목이 여전히 제한됨: (a) 전용 앵커 지갑(ANCHOR_JWK; LogProfile.anchor_owner 여전히 ~이다 [0xAB; 32] 플레이스홀더); (b) 영수증 서명 키 그리고 일치하는 공개 키 핀 (RECEIPT_SK 선택 사항이며 LogProfile.receipt_pubkey 현재 비어 있음); 그리고 (c) self-published-heads GitHub 저장소 + 토큰 (§16.6). 앵커/프로필 핀이 제공될 때까지, 독립 실행형 검증기는 완전히 앵커링되고 핀된 검증을 주장하기보다는 증명 상태를 정직하게 보고합니다. 설계는 엄격히 추가적입니다 그리고 있습니다 변경 없음 SealedQub / QubEnvelope 와이어 포맷.

16.1 근거 및 내구성 단계

현재의 발행 경로는 승인과 Arweave 확인을 분리합니다: 개별 트랜잭션을 파생하고 서명하며, 아티팩트와 정확한 제출 상태를 R2에 저장한 후 비동기적으로 게시합니다. 투명성 로그는 일반 하위 집합에 대해 독립적으로 고정된 순서 계층을 추가합니다. /upload 요청의 LogDO append가 성공함:

계층 이름 보증 언제
T1 R2-첫 번째 동기 확인 내구성 기준 — 성공이 반환되기 전에 봉인된 바이트와 정확한 게시 상태가 내구성 있는 저장소에 기록됩니다. 현재 출판 경로 전반에 걸쳐 구현됨.
T2 일괄 투명도 로그 포함 추가만 가능하고 변조가 감지되는 커밋 + 포함되고 고정되면 전체 순서 지정. 현재 제작자: 성공적 LogDO 에서 추가 /upload; 응답은 영수증 튜플을 포함합니다. 보편적이지 않습니다.
T3 Per-qub 아르위브 영구성 qub에 대한 개별 Arweave 거래. 현재 모든 수락된 출판물에 대해 준비되어 있으며 비동기적으로 게시됩니다. 정확하게 서명된 거래는 전달될 때까지 배수 가능한 발신함에 남아 있습니다.

계층은 현재 상업적 계획이 아니라 고유한 증거 및 내구성 속성을 설명합니다. 현재 코드에서는 여전히 수락된 각 게시물마다 개별 Arweave 거래를 예약하며, T3를 유료 업셀로만 제공하지 않습니다. API 키/계정 할당량 한도는 여전히 별도의 애플리케이션 제어로 남아 있습니다.

내구성 정직. T1 쓰기는 동기식이므로, 성공적인 응답은 Arweave 게이트웨이를 기다리지 않고도 애플리케이션 수준의 내구성을 확립합니다. 그것 자체로는 독립적인 타임스탬프를 생성하지 않습니다. 확인된 개별 트랜잭션은 블록 시간의 상한을 제공합니다. 전체 T2 영수증 튜플을 포함한 응답의 경우, 다음 확인된 앵커가 아래에 설명된 로그 증명을 제공할 수 있습니다. 튜플이 없으면, 어떤 표면도 이 qub가 이미 투명성 로그에 있음을 의미할 수 없습니다. 앵커 및 출판 지연에는 프로토콜 수준의 수치 SLA가 없습니다.

16.2 로그잎 구조 (두 개의 결정된 형태)

로그 항목은 LogLeaf, §3.1 프로필에 따라 손으로 쓴 정식 CBOR로 인코딩됨(확정 길이, 태그 없음, 부동소수점 없음, 가장 짧은 형식의 정수, NFC 텍스트, 없을 경우 선택적 필드 생략, 키는 인코딩된 바이트 길이를 기준으로 오름차순 정렬 후 바이트 단위로 정렬). §3.1 parse → re-encode → compare 정규 가드가 적용됩니다 해싱 전에 인코딩 경로에서 (디코드뿐만 아니라), 따라서 두 구현은 정수 폭 또는 키 순서의 차이로 인해 리프 바이트에 대해 다를 수 없습니다. 모든 정수는 u8 / u64 / i64; 모든 다이제스트는 32바이트 바이트 문자열입니다 (bstr[32]). 저장된 Arweave 트랜잭션 ID는 32바이트 SHA-256 다이제스트 그대로 전달됩니다 bstr[32], 절대 base64url 텍스트 문자열 (§3.3에 해당).

그 잎은 가지고 있다 두 개의 도형이 선택됨 kind 바이트, 일반 업로드 경로는 바이트를 알 수 없기 때문에: POST /api/v1/upload 고의로 두 가지 허용된 페이로드 형태를 불투명하게 처리하고 수신합니다 qub_id 그리고 unlock_at 오직 신뢰할 수 없는 클라이언트 주장의 형태로만. 기본 개인 경로에서는, body_hash, drand_round, created_at, 그리고 drand_chain_version 또한 근로자가 키를 전혀 가지지 못하는 §13 외부 래퍼 안에 숨겨져 있습니다. 타입 시스템은 또한 파생된 생산자에 대한 증명된 형태를 정의합니다. body_hash / drand_round 그 자체. 현재 /seal 라우트에는 해당 값들이 있지만 호출하지 않습니다 LogDO, 따라서 현재 생산은 단지 주장된 것을 배출합니다(0x02) 성공적인 일반 업로드에서 나오는 잎사귀들. 분할은 인증된 생산자가 연결되어 있는 척하지 않고 모든 커밋된 값을 정직하게 유지합니다:

열쇠 엔클. 길이 타입 존재 의미
seq 넷 u64 필수 글로벌 0 기반 리프 인덱스; 포함 증명이 커밋하는 위치.
kind 다섯 u8 필수 0x01 입증 가능(정의됨, 현재 방출되지 않음) 또는 0x02 주장됨 (클라이언트-봉인 / 바이트-블라인드 업로드).
ref 넷 bstr[32] 필수 리프 참조 ID. 확인됨 → 원본 qub_id. 주장된 → the 눈이 먼 아이디 SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1).
chash 여섯 bstr[32] 필수 콘텐츠 주소 SHA3-256(stored_bytes) — 노동자가 항상 두 경로에서 정직하게 계산할 수 있는 유일한 내용적 연결 고리.
unlock_at 열 i64 필수 복사됨(입증됨) 또는 주장됨(주장됨); 검증됨 > 0 잎으로 들어가기 전에.
received_at 열두 i64 필수 R2-ack에서 작업자 실시간 시계. 비증거적 (운영자 주장; §16.6). 자기 설명을 위해 존재하며, 결코 증명은 아님. 검증됨 > 0.
body_hash 열 bstr[32] kind=0x01 오직 생략됨 0x02 — 근로자는 §13에 따라 그것이 부족하다.
drand_round 열두 u64 kind=0x01 오직 생략됨 0x02.

A kind=0x02 잎은 의도적으로 아무것도 저지르지 않는다 body_hash 또한 ~아니다 drand_round: 이는 불투명한 암호문이 콘텐츠 주소에서의 약속과 정렬을 증명한다 chash, 주장하며 qub_id 그리고 unlock_at* — 평문이나 라운드가 아님. 주장된 큐브(qub)의 평문/라운드 다리는 기존 §11에서 나옴 .qub-번들 검증, 로그에서가 아님 (§16.11). drand_chain_version 잎 안에 있지 않습니다(기본 경로의 래퍼 안에 있습니다); 체인 세분성은 앵커에 있습니다(§16.7). 인코더 규율: 모든 제로를 거부합니다 ref 또는 chash, 그리고 0 이하를 거부 unlock_at / received_at, 를 반영하여 outcome_at > 0 감시병 입장 cbor.rs.

16.2.1 프라이빗-큐브 블라인딩

로그는 §13 외부 래퍼가 방지하기 위해 존재하는 열거 오라클이 되어서는 안 된다 (§13.1). 개인(래핑된) qub의 경우 asserted 잎이 약속을 한다 눈이 먼 식별자 SHA3-256(qub_id ‖ log_blind_secret), 어디 log_blind_secret 서버에 보관된 비밀이며, 생략합니다 body_hash. 제3자는 이러한 잎을 특정 대상에 연결할 수 없다 qub_id; 쿱의 소유자는 배송 URL을 가지고 있으며 따라서 qub_id, 자신의 포함 여부를 확인하기 위해 블라인드를 다시 계산할 수 있습니다. 공개 큐브(이미 열거 가능, 이미 포함하고 있는 Visibility: public Arweave 태그는 §13.8에 따라 원본을 커밋합니다 qub_id. 이것은 독립적인 검증 가능성이 의도적으로 하중을 지탱하는 프라이버시 불변성에 양보되는 유일한 장소이다; 개인 큐브에 대한 독립적인 연결은 chash (§16.9).

log_blind_secret 양육권 (해결됨 — §16.15 Q4). 블라인드는 잎사귀 비연결성을 보호하며, 평문 기밀성은 보호하지 않습니다(§13 래퍼가 이를 독립적으로 다룹니다). log_blind_secret 타협, 어떤 것에 대해서도 qub_id 상대방은 이미 가지고 있거나 재구성할 수 있다(그가 가진 모든 큐브의 번들/URL과 모든 낮은 엔트로피 또는 공개된 것 포함) qub_id) 그것은 리프를 다시 계산합니다 ref 안에 하나의 해시 그리고 그것을 연결합니다 — 이것은 알려진 모집단의 직접 연결이며, 알 수 없는 공간에 대한 무차별 대입이 아닙니다. 분류 log_blind_secret 다른 서버 비밀과 동일한 보관 계층에서 상관관계/시빌 등급 비밀로서 유지되며, 앞으로만 회전합니다(회전은 미래의 잎을 다시 블라인드 처리하며, 이미 고정된 잎과 소급적으로 연결을 끊을 수는 없습니다).

16.3 잎과 노드 해싱

RFC 6962 §2.1 도메인 분리 해싱을 SHA-256 대신 SHA3-256으로:

leaf_hash      = SHA3-256(0x00 || canonical_cbor(LogLeaf))
node_hash(l,r) = SHA3-256(0x01 || l || r)
empty tree     = SHA3-256("")          // defined but never anchored

도메인 접두사 바이트 0x02 (입력 체인, §16.4) 및 0x03 (STH 해시, §16.6)는 예약되어 있으며 이들과는 겹치지 않습니다. 이들은 단일 바이트이므로 기존 10바이트 ASCII 도메인 구분자와 충돌할 수 없습니다.QUB_ID_V2, 등). 이 트리는 RFC 6962입니다 왼쪽-전체 균형이 맞지 않음 트리(각 내부는 서브트리 잎 수보다 엄격히 작은 가장 큰 2의 거듭제곱에서 분할됨), 이는 포함 및 일관성 증명이 하나의 감사 경로 알고리즘을 공유할 수 있게 합니다. 참조 사양에는 명시적인 좌/우 도출 의사코드가 포함되어 있으며, 그리고 다음을 고정합니다 비2의거듭제곱(5-리프) 테스트 벡터 그래서 4엽 벡터가 숨기는 오른쪽 가장자리 프로모션 경우가 실행됩니다.

16.4 해시 체이닝(내부)

LogDO는 충돌 일관성을 위해서만 내부 항목 체인을 유지합니다. 그것은 한 번도 발행되지 않았고 검증자에게도 공개되지 않음:

entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1]  = SHA3-256("QUB_TLOG_GENESIS_V1")

게시된 추가 전용 권한은 누적 머클 루트 + 그 앵커 (§16.5–16.6), 연산자가 잎을 제공하는 원시 순서가 절대 아님: 체인은 제공된 어떤 순서에서도 재계산되므로, 오직 고정된 루트만 표준 위치를 고정한다.

16.5 누적 머클 트리 및 배칭

있다 끊임없이 성장하는 RFC 6962 트리 하나 모든 잎 위에 seq 순서 — 배치별로 격리된 트리가 아님. (순전한 prefix 관계가 아니므로 '일관성 증명'이 신뢰할 수 없기 때문에, 캐리-리프 체인 방식의 배치별 구조는 거부됨.) 누적 트리는 진정한 RFC 9162 일관성 증명을 제공하며, 단일 최신 앵커가 이전의 어떤 qub에 대해서도 포함을 증명할 수 있게 한다.

그 LogDO Durable Object는 단일 작가 (blockConcurrencyWhile, 반사 QuotaDO / EntitlementDO) — 공유 로그에 덧붙이는 것은 공유 상태에 대한 읽기-수정-쓰기이므로 반드시 DO를 통해야 하며, 절대 KV를 통해서는 안 됩니다. 이는 트리의 오른쪽 끝 경계(frontier)를 캐시합니다(O(log n) 해시) 따라서 배치를 닫는 것은 O(batch). A 배치 잎들의 집합이 함께 고정되어 있으며; 구현된 트리거는 다음과 같습니다 tree_size 최소한의 선금 LOG_BATCH_MAX_LEAVES (기본값 4096), 앵커 케이던스에 도달한 나이, 또는 명시적인 관리/크론 강제 종료. root_i 리프에 대한 누적 머클 트리 해시입니다 0 .. tree_size_i.

16.6 Arweave Anchor를 통한 서명된 트리 헤드

아르위브 앵커 트랜잭션 이다 서명된 트리 헤드 그리고 대체하다 트리 헤드 자체를 위한 연산자 서명*: 일일 앵커는 Arweave 트랜잭션 때문에 qub 키가 필요 없다 owner 서명입니다. 해자 이론은 지속됩니다 — 변하지 않는 기초가, qub에 의해 보관된 비밀이 아니라, 고정된 뿌리를 지탱하는 역할을 합니다.

로그 디자인은 하나의 따뜻한 성공적 추가를 요구합니다 영수증 키 (§16.10), 고정됨 LogProfile 그리고 교차 서명됨 anchor_owner. 현재 구현은 해당 신뢰 루트(provisioning)를 완료하지 않았습니다: RECEIPT_SK 선택 사항이며, 없거나 잘못된 키는 sig_b64url: "", 그리고 컴파일된 LogProfile.receipt_pubkey 비어 있습니다. 이러한 영수증은 첨부된 잎을 설명할 수 있지만 아니다 부인할 수 없는 서명된 영수증. 더 강한 설계 주장은 검증자가 일치하는 공개 키를 고정하고 앵커 소유자가 그것에 대해 교차 서명한 후에만 적용됩니다. 완전한 영수증 튜플 없이 제출된 응답은 로그 수락 주장을 하지 않으며, 서명이 비어 있는 경우 추가 위치 주장은 하지만 서명 검증 주장은 하지 않습니다.

그 SignedTreeHead 정준 CBOR(인코딩된 길이에 따른 키): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (이전 sth_hash; 생성 = 32개의 영 바이트), log_id:bstr[32], first_seq:u64, anchored_at:i64. 그것의 해시는 sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).

고정된 신뢰 루트. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). 적합한 검증자는 반드시 요구해야 한다 anchor_tx.owner == LogProfile.anchor_owner, 어디에 anchor_owner (영수증 키 공개 키)는 내장되어 있습니다 qub_core 처럼 LogProfile — 이미 포함된 퀵넷 상수와 함께 DrandTimelockProvider::quicknet() — 그리고 검증기 바이너리와 함께 배포됩니다. 검증기는 또한 Arweave 트랜잭션을 반드시 검증해야 합니다 데이터 → 로컬에서 tx_id 바인딩 게이트웨이를 신뢰하기보다는 /raw/ 응답. 이것은 악성 지갑 동등성 문제를 해결합니다: 'Arweave에 고정됨'은 검증자가 어떤 지갑을 고정하는지 명시하지 않으면 의미가 없습니다.

**회전은 재사용이 아니라 §15 거버넌스 확장입니다 (해결됨 — §16.15 Q3).** §15.2의 프로필 표면은 현재 sig_algs / drand 체인 / 래퍼 버전 / 콘텐츠 유형만 나열하고 있으며, §15.3의 트리거는 이들 중 어떤 것도 나열하지 않습니다 — LogProfile / anchor_owner 이다 아니다 그러나 §15의 표면에서. 따라서 회전 거버넌스는 구축되어야 합니다: §15.3은 아래와 같이 추가하기 위해 확장됩니다 LogProfile 트리거, 그리고 회전은 부호 있는 LogProfile 검증기 업데이트에서 발송된 범프. A 계획된 회전은 발신 → 수신 교차 서명을 전달한다; a 타협 중심의 회전은 불가능합니다(그때 나가는 키가 신뢰할 수 없거나 사용 불가이기 때문에) 그리고 §15에 따라 관리되는 범프로 대체되며, 그 사이에 피해를 제한하는 이전 앵커 포크 검사가 수행됩니다(아래 참조).

모호성 창(일류 신뢰 매개변수). 잎은 그 덮개 앵커가 Arweave-일 때만 회피 저항력을 가진다확인됨. 창문은 received_at → anchor confirmation (캐던스 + 아르위브 최종성, 프로토콜 지연 보장은 없음). 신뢰 루트 제공 이전에, 현재 구현은 qub의 운영 무결성과 존재하는 모든 서명되지 않은 추가 메타데이터를 제공합니다; 계획된 부인 방지(non-repudiation) 보장은 제공하지 않습니다. 세 가지 책임성 아티팩트가 완성된 설계를 정의합니다 (증인 모델은 §16.15 Q2의 결의안임):

  1. 봉인 영수증(공급 의존) — 업로드의 로그 추가가 성공할 때 SCT 아날로그가 반환됩니다(§16.10). 그것은 오직 sig_b64url 비어 있지 않다 그리고 일치하는 공개 키/앵커-소유자 관계가 검증자에 고정되어 있습니다. 현재 비어 있는 프로덕션 프로필 핀은 해당 판정을 지원할 수 없습니다. 이 제어는 생략된 영수증 튜플이나 서명되지 않은 영수증에는 적용되지 않습니다.
  2. 발표된 모니터 방법론 + 이전 체인 워크 — 앵커 prev 체인은 머리부터 → 창세기까지 걷는다; 한 개에서 두 개의 닻이 있는 포크 size 다른 것과 함께 root, 또는 부러진 prev)는 잘못된 행동에 대한 출판 가능한 증거입니다. 애매모호함 감지는 묵시적 가정이 아니라 명시된 운영상의 약속입니다.
  3. 이중 자가 출판 헤드 — 각 새로운 머리 {sth_hash, tree_size} qub 소유 전용에 게시됩니다 공개, 추가 전용 GitHub 저장소 (하중을 지탱하는 변조 방지 자체 출판 다리), 단지 최선의 노력에 의한 사회적 게시물 증거로만 제공됩니다. 실패한 게시물은 반드시 페이지를 표시해야 하며(조용히 실패하면 안 됨). 구현됨 (단계 8)으로 publishHead 앵커 크론에 연결workers/api/src/utils/heads-publish.ts): a PUT 내용 API에 없는 sha 추가 전용(append-only) 422 즉 이미 head가 게시되었음을 의미하며, 절대 덮어쓰기가 아님); 선택적 참여 / 배포 게이트 적용 PUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO} 그리고 저장소가 준비될 때까지 비활성 상태입니다. GitHub의 심각한 오류 페이지를 통해 health_alert 채널 및 내구성 실패 지표를 튀기다 (m:tlog_publish_head_fail); Arweave 앵커 자체는 게시 실패 시 절대 롤백하지 않습니다. "조용히 실패하지 않는" 것은 운영자가 반드시 대시보드 알림을 해야 하는 내구성 지표에 의해 보장되며, 설령 최선의 노력을 다해 이메일 페이지가 전달되지 않더라도 말입니다. "anchor-on-advance"(크론이 크기가 증가할 때만 게시됨)에서 두 가지 정직한 한계가 발생합니다: 일시적인 GitHub 실패는 틈 해당 크기의 게시된 헤드 시퀀스에서는 — 제한되어 있고, 조용하지 않으며(페이지를 요청하고), 각 헤드가 슈퍼셋 트리를 커밋하기 때문에, §16.9 일관성 증명이 그 간극을 연결합니다; 중요한 것은, 그 일관성 증명이 다음으로부터 계산된다는 점입니다 GitHub 표면이 아닌 권위 있는 Arweave 기반 트리, 따라서 GitHub의 공백은 검증 가능성을 약화시키지 않습니다. 게시된 헤드의 공백을 채우는 따라잡기 백필(catch-up backfill)은 연기된 향상(deferred enhancement)입니다.

정직의 구속(구속적 제약). qub가 계획된 게시 표면 둘 다를 제어하기 때문에, 이것은 자가 출판, 독립적으로 목격되지 않음. 어떤 제품, 마케팅 또는 법적 표면도 해당 기록이 '독립적으로 목격되었다'고 주장할 수 없습니다. 수령/프로필/헤드 게이트가 제공된 후 허용되는 주장은 모호한 표현은 감지할 수 있으며 성공적으로 서명된 추가 항목은 부인할 수 없는 영수증을 남깁니다. 그때까지는 해당 주장은 이용할 수 없습니다. 진정한 독립 제3자 증인은 향후 §15 거버넌스 업데이트로 연기됩니다.

received_at 연산자에 의해 확인되었고 어떠한 청구도 그것에 의지할 수 없다 — 어떤 제품 / 법적 / API / 증명 렌더링 표면에서도 증거나 논쟁 확인으로 절대 표시되지 않습니다. Arweave 앵커 블록 시간 T 유일한 무신뢰 타임스탬프(“기록됨”의 상한). 모니터 정상성 검사에서 received_at 반드시 비교해야 함 T, 운영자가 제어하는 것에 반대가 아니라 anchored_at STH 필드; 이러한 검사는 정직한 운영자의 시계 버그에 대한 보호 장치일 뿐입니다. 아니다 악의적인 운영자에 대한 책임 통제 (§16.15 Q5).

16.7 앵커 거래 형식 및 주기

그 AnchorBundle §16.8 번들러를 통해 작성된 정준 CBOR Arweave 트랜잭션 본문: ver:u8, sth:bstr (정경의 SignedTreeHead 바이트), prev_anchor:bstr (이전 앵커 tx ID 원시 바이트; 제네시스에서는 생략됨), chain_hash:tstr (적용 중인 드랜드 체인 — 퀵넷), 그리고 배치의 leaf-CBOR 스트림 입력 seq 주문 그래서 앵커는 독립적입니다: 모니터가 다시 파생됩니다 root 제로 큐브 의존성으로 본문에서. (리프 스트림이 고용량에서 커지면, 향후 개정에서는 참조로 리프 범위만 커밋할 수 있음; 기록은 되었지만 v1에서는 채택되지 않음.)

Arweave 태그는 의도적으로 열거할 수 있습니다 — 이 로그는 비공개 qubs와 달리 찾도록 설계되었습니다: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. 태그는 신뢰할 수 없는 힌트; CBOR 본문이 유일한 권위입니다.

케이던스: 기본적으로 매일, 볼륨과 함께 다시 검토됨(크기 트리거는 부하 시 유효한 주기를 자동으로 단축함). 현재 프로듀서는 유료 봉인 강제 앵커 훅을 구현하지 않음. 그 앵커 지갑 전용이며 저속으로, 업로드 지갑과 분리되어야 하며 — 반드시 그것이어야 합니다 자체 JWK (업로드 월렛의 논리적 역할이 아닌 별개의 키) 따라서 업로드 월렛이 침해되더라도 앵커를 위조할 수 없으며, 하루별 앵커 거래 한도가 엄격하게 설정되어 있습니다. 수탁 자세는 명확하게 명시되어 있습니다: a 좁은 범위의 단축키와 조밀한 차단기, 낮은 잔액, '콜드'가 아니라 — 매일 자동 서명하는 지갑은 절대로 콜드일 수 없으며, 사양서에서도 그렇지 않다고 주장하지 않는다.

16.8 ANS-104 번들러

자체 제작된 ANS-104 DataItem 인코더 및 딥 해시 서명기, 약 300라인, 웹 크립토만 사용, npm 의존성 없음 (둘 다 Turbo SDK가 실패함 npm ci --ignore-scripts 공급망 게이트). 데이터 항목 바이트 레이아웃:

signatureType (2, LE) || raw_signature || owner || target(flag+0|32) || anchor(flag+0|32) || num_tags(8, LE) || tags_len(8, LE) || avro_tags || data

서명은 아르위브입니다 deepHash — 재귀적인 SHA-384 다이제스트(Arweave의 와이어 요구, crypto.subtle.digest("SHA-384")) 넘어 ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — 그런 다음 지갑 JWK를 통해 딥 해시에 대해 RSA-PSS crypto.subtle; id = base64url(SHA-256(signature)). 여기서 SHA-384는 Arweave-와이어 전용 원시형으로 격리되었으며, 결코 qub 신뢰 원시형이 아님 (§15는 울타리를 기록합니다; qub 트러스트 해싱은 전체적으로 SHA3-256입니다).

ANS-104 코드 경로는 연기된 폴백/배수 장치를 제공하고 기록합니다 AnchorBundle 데이터 항목. 일반적인 게시 경로는 먼저 정확한 서명된 Arweave 트랜잭션을 생성하고 그 JSON을 내구성이 있는 아웃박스에 보관합니다. 직접 게시하기는 지연 시간을 최적화하는 방법이고, 드레인 경로는 번들러 대체를 적용하기 전에 동일한 트랜잭션을 재시도합니다. 서명 방식 (해결됨 — §16.15 Q8): v1이 RSA-PSS(서명 유형 1)로 서명함 기존 Arweave 지갑 JWK 메커니즘 재사용(새로운 장기 키 보관 없음, '한 가지 비밀 감소' 논문 지원); Ed25519는 §15 PQ-이전 경로로 연기됩니다.

핸드롤 딥 해시는 W5에서 위험이 가장 높고 자연적 커버리지가 가장 낮은 코드이므로, 그 게이팅은 협상할 수 없는 (§16.15 질문 8)

  1. 다국어용 고정 장치 tlog_v1.json (Rust + TS, §14.5 wrapper_v1.json 패턴) 딥 해시, DataItem 바이트 + ID, 리프 해시, 5-리프 루트 + 감사 경로, STH 해시, 포함 증명, 일관성 증명을 포함 — 안에서 서명 방향과 검증 방향 모두 (검증 방향이 중요한 이유는 §16.6의 로컬 tx → tx_id 확인이 깊은 해시를 작성자뿐만 아니라 모든 독립 검증기로 끌어오기 때문입니다).
  2. 참조 ANS-104 번들러를 통해 한 번 수행되는 상호 운용 라운드트립, 소비로 정적 테스트 데이터만 — 절대 npm 런타임 의존성이 아님 (웹 크립토 전용 / 설치 스크립트 없음 자세가 유지됩니다).
  3. 딥 해시 + RSA-PSS 경로는 반드시 이를 통해 왕복해야 합니다 같은 crypto.subtle 원시값 생산용으로 사용되므로 사내 인코더는 바이트 호환됩니다.
  4. 진행 중인 번들 후 수용 모니터 각 앵커/폴백 DataItem이 실제로 Arweave 수용을 달성하는지 확인하며, 경보와 서킷 브레이커가 있습니다 — 왜냐하면 딥 해시는 Arweave 사용 불가 폴백 큐에도 사용되므로, 조용한 회귀가 발생하면 해당 큐가 존재하는 정확한 중단 동안 네트워크에서 거부된 항목으로 채워지게 되기 때문입니다.

16.9 포함 및 일관성 증명

둘 다 RFC 9162, SHA3-256이며, 정식 CBOR로 제공됩니다.

포함 증명 — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (정확한 리프 CBOR — 검증자가 다시 계산함 leaf_hash 자체적으로 그리고 제공된 해시를 절대 신뢰하지 않는다) index:u64, size:u64, audit:[bstr[32]], root:bstr[32], anchor:{ txid:bstr[32], batch:u64, sth:bstr[32], log_id:bstr[32], block_height?:u64, anchored_at?:i64 }.

일관성 증명 — GET /api/v1/log/consistency?first=<size_a>&second=<size_b>: ver:u8, first_size:u64, second_size:u64, first_root:bstr[32], second_root:bstr[32], nodes:[bstr[32]], first_anchor, second_anchor. 테스트 벡터에 의해 고정된 단일 명확한 키 목록.

독립 검증 (쿱 서버 없음, §11 확장):

1.  Parse .qub bundle → SealedQub; recompute qub_id (§4.1).
2.  Read leaf.kind.
3a. kind=0x01 (attested):
      assert leaf.ref == qub_id
      assert leaf.body_hash   == SHA3-256(body)
      assert leaf.drand_round == unlock_round(unlock_at)
3b. kind=0x02 (asserted):
      assert leaf.ref == SHA3-256(qub_id || blind)   // holder supplies blind
      OR treat ref as opaque and bind via leaf.chash == SHA3-256(stored_bytes)
4.  Recompute leaf_hash = SHA3-256(0x00 || leaf); fold `audit` per RFC 6962
    using index/size; require derived root == proof.root.
5.  Fetch anchor.txid from any gateway; verify the tx data → tx_id binding
    (do not trust a gateway /raw/ response); REQUIRE anchor_tx.owner ==
    LogProfile.anchor_owner.
6.  Parse AnchorBundle; require committed root == proof.root and size ==
    proof.size; read the Arweave block time T.
7.  Emit the claim scoped by leaf.kind (§16.11).

증거 제공 저장소는 반드시 좌표 키로 지정되어야 합니다(해결됨 — §16.15 Q7, 차단 전제 조건). 콜드-리프 증명 생성은 정확성과 중립적이다 오직 ~일 경우에만 R2 감사 자료는 절대 트리 좌표를 키로 사용하는 지속적인 머클 노드 저장소 (level, index) — 퍼 단위가 아닌batch 노드 델타. 좌표 키 저장소를 사용하면, 어떤 (leaf i, size N) 감사 경로는 집합입니다 O(log N) 직접 R2 GET와 함께 배치 경계를 넘어 다시 계산하지 않음; 배치 키 기반 스토어에서는 그렇지 않으며, 이것이 이번 해결책이 메우는 저장 레이아웃 격차입니다. 리프 본문 또한 콘텐츠 기반으로 주소 지정됩니다. seq. W5 테스트 벡터는 반드시 증명해야 한다 로그DO 저장이 삭제된 상태에서 R2 + Arweave만 사용하여 훨씬 나중의 루트에 대항하는 창세기 시대의 콜드 리프, 따라서 §16.13의 복구-안전 주장(claim)은 단순히 주장된 것이 아니라 뒷받침됩니다. 그 O(log N) 순차적 R2 GETs에 속하다 오직 비동기 증명 엔드포인트에서 — 결코 씰 핫 경로(§16.10)나 틱별 크론에서 실행되지 않습니다.

16.10 R2-첫 번째 확인 순서

구현된 POST /api/v1/upload 순서는:

  1. 프론트 하프 게이트(인증, 검증, 멱등성 샤드 키) — 변경 없음.
  2. 정확한 개별 Arweave 거래를 생성하고, 태그를 지정하며, 서명하세요. 이것은 파생됩니다 tx_id 로컬에서는 거래 생성이 게이트웨이에서 보상/앵커 메타데이터를 가져올 수 있지만, 준비 실패는 여전히 확인 전에 요청을 실패하게 합니다.
  3. 동기적으로 선택한 인공물을 ~에 작성하다 qub-cache/<tx_id> 그리고 안정적인 생성-작업/아웃박스 기록을 지속합니다. 이것들은 내구성과 재시도 기준입니다; 정산 이전의 실패는 503을 반환합니다.
  4. 언제 LOG_DO 설정되어, 동기적으로 시도하다 LogDO.append(leaf). 단일 작가가 할당합니다 seq, 항목 체인을 확장하고 프런티어를 업데이트합니다. append RPC는 그것만 수행합니다; 배치 종료는 알람에서 오프 경로로 실행됩니다. append 전송/애플리케이션 오류는 현재 실패-유연: 응답은 여전히 없이도 성공할 수 있습니다 log_seq, receipt, 또는 anchor_status. 구현 관련 주석이 있음에도 불구하고, 현재는 자동 후속 로그 조정이 연결되어 있지 않습니다.
  5. 승인을 반환하십시오. 포함하십시오 { log_seq, anchor_status: "pending", receipt } append가 완전히 성공한 튜플을 반환했을 때만. receipt.sig_b64url 영수증 서명자가 부재할 때 비어 있으며; 클라이언트는 해당 값을 서명되었거나 부인불가라고 절대 호출해서는 안 됩니다. 튜플의 부재는 오직 내구성 있는 게시만을 의미하며, 투명성 로그 수락을 의미하지 않습니다.
  6. 정확하게 서명된 거래를 게시하기 위해 하나의 연기된 작업을 사용하십시오. 성공하면 아웃박스를 제거하고, 실패하면 제한된 드레인 크론을 위해 남겨두며 이미 확인된 것을 변경해서는 안 됩니다. tx_id. 임시 메타데이터 및 기타 최선의 노력으로 제공되는 사이드카도 연기됩니다.

지연 경계. 요청 경로에는 전반적인 권한/할당 작업, 거래 준비/서명, 내구성 있는 R2 쓰기, 그리고 (구성된 경우) LogDO 시도. < 300 ms 설계 검토에서는 프로토콜 보장이 아닌 운영 목표로 나타나며, 현재 거래 준비 단계에서는 게이트웨이 메타데이터 요청을 수행할 수 있습니다. 지연 알람과 출시 게이트는 검증자가 확인할 수 있는 증거가 아니라 운영 통제입니다.

16.11 신뢰 모델 — 리프 종류에 의해 범위가 지정된 정확한 주장

위하여 kind=0x01 (입증된): 이 콘텐츠 — 바디 매칭 body_hash, 으로 식별됨 qub_id — qub의 추가 전용 로그에 위치에서 커밋되었습니다 seq 그리고 Arweave 블록 시간보다 늦지 않게 존재했습니다 T; drand 라운드까지 암호학적으로 읽을 수 없었습니다 R = unlock_round(unlock_at). 이것은 전체입니다 {tlock round binding + Merkle inclusion + anchored root} 삼중의.

위하여 kind=0x02 (주장됨, 기본): *내용 주소가 있는 불투명한 암호문 chash, 주장하며 qub_id 그리고 unlock_at, 위치에 있는 추가 전용 로그에 커밋되었습니다 seq 그리고 Arweave 블록 시간보다 늦지 않게 존재했습니다 T둥근 다리와 몸체 다리는 기존 §11에서 제공됩니다. .qub-번들 검증 (qub_core::unlock), 아니다 로그 단위로; 로그가 단순한 쿼브당 거래에 추가하는 것은 변조를 드러내는 순서, 신뢰할 필요 없는 상한 약속 시간, 그리고 이중 언행 방지입니다.

두 청구항 모두 §11에 따라 제외됨: 저작권 없는 저작 sig_alg ≥ 0x01, 의도, 그리고 하위 앵커-세분화 타이밍. 어느 것도 어떤 주장도 의지하게 두지 않습니다 received_at.

클레임 상한선 (구속 발사 제한 — §16.15 Q1 해결됨). 주장된 (kind=0x02) 잎, 위에 범위가 지정된 청구는 천장 어떤 제품, 마케팅, 조건, 또는 증명 제공 표면에서도 주장할 수 있습니다. 어떤 표면도 로그가 바이트 블라인드 업로드의 내용이나 언락 라운드를 증명한다고 명시하거나 암시할 수 없습니다 — 로그는 순서 + 불투명한 암호문에 대한 신뢰 없는 상한 약정 시간을 증명합니다. 내용과 라운드 증명은 오직 기존 §11에서만 나옵니다. .qub-번들 검증은 로그와 무관합니다. 성공적인 추가/수신이 없는 출판물은 로그 청구 자체가 없습니다.

16.12 버전 관리 및 W3 조정

있다 아니요 SealedQub 와이어 범프 그러므로 프로토콜 버전 업그레이드 없음 (§12.2): 로그는 기존 필드와 바이트를 커밋하는 사이드카이므로 §12.3 프로토콜 버전 기록에는 포함되지 않습니다. W3의 선택 사항 drand_chain_version 손대지 않았으며 유일한 선택사항으로 남아 있습니다 SealedQub 현장. 대신 로그는 자체 독립적인 버전 공간을 도입한다 — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — §12.5 포장 버전 독립성 반영(포장은 프로토콜 버전과 독립적인 버전 바이트를 가지고 있으며, 로그 버전도 동일한 구분을 따릅니다).

증명 배달은 기본적으로 가져오며, 선택적으로 동행할 수 있습니다. 증명은 봉인 시점에 존재할 수 없습니다(앵커가 아직 작성되지 않았기 때문에), 따라서 봉인 시점 .qub 번들은 증거 없이 유지됩니다. W7의 검증기가 가져옵니다 GET …/proof 한 번, 또는 완전히 오프라인 모드에서 공개된 것으로부터 증명을 재구성한다 AnchorBundle Arweave 쿼리를 통해 Log-Id. 그 .qub 번들(W7)은 예약된 선택 사항 inclusion_proof 회원 — 봉인 시 부재, 냉장 아카이빙을 위한 포스트 앵커 재수출로 채워짐 — W3의 '선택적, 기본적으로 생략, 추가적' 패턴을 따름 drand_chain_version.

16.13 보유

LogDO 오픈 테일, R2 증명 제공 기판, 앵커 서킷 브레이커 카운터, 번들러 백업 큐에 대한 보존 기간은 다음과 같이 지정됩니다 docs/DATA-RETENTION.md. 원칙: 로그의 핫 항목별 저장소(LogDO)는 앵커 후 회수 가능하며; 그 감사 자료는 좌표-키로 된 (level, index) 머클 노드 저장소 + 그 seq-주소 지정된 리프 본문(§16.9) + Arweave 앵커 — 는 영구적입니다. DO에서 콜드 리프를 되찾아도 발급된 증명은 무효가 되지 않습니다. 왜냐하면 증명은 DO가 아닌 그 영구적인 R2 노드 스토어와 Arweave 앵커를 기준으로 해결되기 때문입니다(그리고 §16.9에서 삭제된 DO 테스트 벡터가 이를 증명합니다).

16.14 테스트 벡터

W5는 다국어 장치를 배송합니다 tlog_v1.json (§16.8) 더한 작용 벡터: a kind=0x01 그리고 한 kind=0x02 잎 → leaf_hash; 5-잎 누적 루트; 하나의 포함 증명; 하나의 일관성 증명; 하나 AnchorBundle; 및 하나의 DataItem ID. 이들은 §14.5 외부 래퍼 벡터와 함께 존재하며 Rust 양쪽에서 사용됩니다 (qub-core) 및 TypeScript (워커) 구현.

16.15 검토 결정 (W5 — 해결됨)

W5 외부 검토(대립적 설계 검토 + 소유자 승인)가 완료되었습니다. 아래의 각 결정은 확정되었으며 위 §16 본문에 반영되어 있습니다; 바인딩 실행 제약 마지막에 다시 진술됩니다. 그에 따라 실행이 진행될 수 있습니다.

  1. 기본 경로 (kind=0x02) 잎 정직 — 해결됨. 지정된 대로 두 장짜리 종류를 분리하여 배송하십시오: kind=0x02 어느 쪽에도 속하지 않는다 body_hash 또한 ~아니다 drand_round. 아니요 *_body_hash 바이트 블라인드 경로상의 필드(통합자에게 가장 읽기 쉬운 거짓 "검증된" 신호가 될 것이며, 번들에서 §11이 이미 제공하는 편의 기능입니다). 아니다 로그로 증명된 qubs에 대해 서버-인장을 요구하십시오(이는 평문을 워커를 통해 강제로 보내고 암호 분쇄 해자를 파괴하게 됩니다). 모든 자기 설명적인 단락 회로는 여기에 속합니다 .qub 번들 / 증명 봉투는 검증자가 재계산한 필드로, 절대 리프 필드가 아님. 소유자 확인 청구 한도: §16.11.
  2. 모호한 표현 / 누락 책임 — 설계 완료, 제공 불완전. 설계에서는 봉인 영수증 키를 고정하도록 요구합니다 LogProfile 그리고 교차 서명됨 anchor_owner, 게다가 모니터링 방법론, 이전 체인 워크(prev-chain walk), 그리고 이중 자체 출판 헤드(dual self-published heads)가 포함됩니다. §16.6에 자세히 설명된 대로 컴파일된 프로필과 배포 훅은 여전히 자리표시자/선택 사항이므로, 이러한 게이트가 닫힐 때까지 더 강력한 감지 가능 + 수령 확인된(claim) 주장은 유효하지 않습니다. 이것은 절대 독립적으로 목격됨으로 판매되어서는 안 됩니다. 진정한 제3자 목격은 §15 거버넌스 상승으로 연기됩니다.
  3. 고정된 앵커 소유자 신뢰 루트 + 회전 — 해결됨. 채택하다 LogProfile 핀 (§16.6); 검증자가 확인합니다 anchor_tx.owner == anchor_owner 그리고 tx 데이터 → tx_id 바인딩을 로컬에서 검증합니다. 회전 관리 체계는 §15입니다 빌드 확장 (§15.3 트리거 추가), 재사용 아님; 계획된 로테이션은 상호 확인을 하고, 타협 기반 로테이션은 손상 제한과 함께 포크 체크가 있는 §15 범프를 사용합니다.
  4. 프라이빗-큅 잎 블라인딩 — 해결됨. 개인 큐브를 위해 계속 눈가림하기 (ref = SHA3-256(qub_id ‖ log_blind_secret)), 생 qub_id 공용 QUB에 대해서는 (이미 §16.2.1 참조), chash 독립적인 넥타이로서. log_blind_secret 상관관계/시빌 등급 비밀이며, 순방향 회전만 허용 (§16.2.1).
  5. received_at — 결의되었다. 잎 안에 보관하고, 약속은 하지만 명시적으로 증거로는 사용하지 않으며; 어떤 표면에서도 증거나 논쟁 확인으로 등장하지 않는다. 어떤 모니터의 상태 점검도 Arweave 블록 시간과 비교한다. T, 운영자가 조작하는 것이 아닌 anchored_at (§16.6).
  6. 계층식 증명 타이밍 — 설계 해상도, 현재 라우팅 아님. 검토된 설계는 앵커-블록 타이밍을 일괄 처리 계층에 할당하고 정확한 시간 증명을 유료 T3에 할당하며, 전자에는 숫자 SLA가 없습니다. 현재 경로는 이러한 상업적 구분을 연결하지 않았습니다: 모든 승인된 게시물에 대해 개별 거래를 예약하며, 로그 커버리지는 §16.1/§16.10에 명시된 대로 조건부로 유지됩니다. 제품 설명에는 구현 방식을 설명해야 하며, 이 미래의 계층 분할을 설명해서는 안 됩니다.
  7. 근로자에 관한 누적 안건 — 해결됨. 단일 누적 RFC 9162 트리 + 프론티어 캐시 단일 작성자 LogDO (~1k 쓰기/초 DO 한계 대비 넉넉한 여유; 접근 시점까지 샤드 루트의 머클 분할 연기). 좌표 키 기반 (level, index) R2 노드 저장소 + 삭제된 DO 콜드-리프 테스트 벡터가 구현되었습니다 (§16.9). < 300 ms 프로토콜 약속이 아니라 설계/운영 목표로 남아 있습니다 (§16.10).
  8. ANS-104 서명 방식 + 딥 해시 — 해결됨. RSA-PSS(서명 유형 1, 전용 앵커-지갑 JWK 재사용); Ed25519는 §15 PQ 경로로 연기됨. 손수 만든 SHA-384 딥 해시는 양방향 크로스 구현 픽스처, 정적 전용 참조 번들러 상호 운용성 체크, 공유-에 따라 제한됨crypto.subtle 왕복 여행 및 번들 후 Arweave 수용 모니터(§16.8).

바인딩 실행 제약 조건 (실행으로 옮기기 + 제품/법률 검토):


17. 이식 가능한 검증 번들(.qub)

상태. 이 절은 구현되었습니다(W7/UP-C2). qub_core::export가 번들을 생성하고 파싱하며, tools/qub-verify는 오프라인에서 번들을 검증하는 공개 독립형 CLI입니다. §11과 §16.9는 이미 독립 검증자가 소비하는 단위로 .qub 번들을 언급합니다. 이 절은 해당 바이트와 검증 절차를 명시합니다. 이는 엄격히 추가적입니다. 번들은 기존 §11 입력을 패키징하며 온체인 와이어 형식을 변경하지 않습니다.

17.1 목적

§11은 제3자가 qub의 협조 없이 qub의 암호화 아티팩트를 검증할 수 있음을 확립합니다. .qub 번들은 봉인된 CBOR과 이를 잠금 해제하는 drand 라운드 서명을 하나의 자기 완결적 아티팩트로 패키징하여 해당 검증을 이식 가능하고 오프라인에서 가능하게 만듭니다. 수신자는 네트워크 호출 없이(저장소 가져오기, 실시간 drand 요청, qub API 호출 모두 없음) 콘텐츠 무결성, 라운드 결속, 작성권 서명을 검증할 수 있습니다. 번들만으로는 암호문이 언제 생성되었는지 증명할 수 없습니다. 독립적으로 검증된 저장소 트랜잭션 또는 앵커링된 로그 증명이 별도의 존재 시각 주장을 제공합니다(§11, §17.5).

17.2 번들 형식

QubBundle은 §3.1 프로파일에 따라 직접 작성한 정규 CBOR입니다(확정 길이, 태그 없음, 부동소수점 없음, 최단 형식 정수, NFC 텍스트, 부재 시 선택적 필드 생략, 인코딩 바이트 길이 오름차순 후 바이트순 키 정렬). 15자 키 세 개의 순서는 d < i < s입니다. 원시 .qub 파일은 정확히 이 바이트이며, URL 또는 복사·붙여넣기 전송에서는 같은 바이트를 base64url(패딩 없음)로 표현합니다.

키 인코딩 길이 타입 존재 여부 의미
version 8 u8 필수 번들 형식 버전(0x01).
sealed_at 10 i64 선택 작성자가 주장한 봉인 시각(Unix 초). 자기 기술적이며 증거가 아닙니다.
drand_round 12 u64 필수 qub이 잠긴 라운드. 포함된 봉인 qub의 투영입니다.
arweave_tx_id 14 tstr 필수 봉인 바이트가 저장된 트랜잭션 ID(출처 포인터).
drand_chain_id 15 tstr 필수 drand 체인(16진수). 포함된 봉인 qub의 투영입니다.
drand_signature 16 bstr 필수 drand_round의 drand 비콘 서명, 즉 암호문을 잠금 해제하는 값.
inclusion_proof 16 bstr 선택 §16 투명성 로그 Merkle 포함 증명(§17.5).
sealed_qub_cbor 16 bstr 필수 §13 언래핑 후의 내부 SealedQubCbor 바이트, 즉 §11 검증 입력.

drand_round와 drand_chain_id는 도구가 내부 CBOR을 파싱하지 않고 읽을 수 있도록 전달하는 sealed_qub_cbor의 편의 투영입니다. 생성 시 도출되며 디코딩 시 파싱한 봉인 qub과 대조해 다시 검사됩니다. 최상위 필드가 페이로드와 일치하지 않는 번들은 거부됩니다. 인코더 규율은 나머지 와이어 형식과 같습니다. 빈 drand_signature 또는 arweave_tx_id를 거부하고 모든 가변 길이 필드에 상한을 적용합니다.

17.3 포함된 drand 서명이 증명하는 것

번들은 검증자가 가져오도록 요구하지 않고 drand 서명을 포함합니다. drand 체인을 통한 tlock 복호화(§8)는 결속된 라운드의 진짜 비콘 서명으로만 성공할 수 있습니다. 체인은 라운드가 지난 뒤에만 그 값을 게시하며, 해당 값은 체인 공개 키 아래의 유효한 BLS 서명입니다. 위조되거나 잘못된 서명은 BLS 검증 또는 IBE/AEAD 복호화에 실패합니다. 따라서 복호화되는 번들은 암호문이 라운드 R에 결속되었고 라운드 R이 지났음을 증명합니다. 검증자는 체인(DrandTimelockProvider::quicknet())을 고정하고 §11 라운드 결속 검사를 적용하므로 번들은 암호문이 결속되지 않은 라운드를 주장할 수 없습니다.

이는 생성 타임스탬프가 아니라 공개 조건 증명입니다. 라운드 R이 지난 뒤에는 누구나 R에 대한 새 암호문을 만들고 이미 공개된 서명을 패키징할 수 있습니다. 따라서 번들만으로 암호문이나 콘텐츠가 R 이전, unlock_at 이전 또는 어떤 사건 이전에 존재했다는 증명이라고 설명해서는 안 됩니다.

17.4 오프라인 검증 절차

qub-verify <file.qub>는 고정된 DrandTimelockProvider로 qub_core::unlock::unlock을 구동하며 번들만으로 표준 §11 절차를 수행합니다.

1. Parse the .qub bytes → QubBundle (canonical-CBOR guard; bound every field;
   re-check drand_round / drand_chain_id against the embedded sealed qub).
2. BLS-verify bundle.drand_signature for the pinned chain and round, then
   tlock_decrypt(sealed.tlock_ciphertext, bundle.drand_signature) → QubEnvelope.
3. Verify SHA3-256(body) == body_hash               (§11 step 8).
4. Verify QubEnvelope.qub_id   == SealedQub.qub_id   (§11 step 9).
5. Verify QubEnvelope.unlock_at == SealedQub.unlock_at (§11 step 10).
6. Verify ciphertext round == unlock_round(unlock_at) and the chain binding.
7. If sig_alg != 0x00: verify author_signature (and any cosigner; §9.4).
8. Report integrity, round-elapsed/round-binding, authorship, and cosigner
   verdicts separately, plus the recovered body. Do not report a commitment
   timestamp unless step 9 succeeds.
9. Optional existence-time leg: verify an included §16 proof through its pinned
   anchor, or independently verify the referenced storage transaction. Report
   its block time as an upper bound on ciphertext existence.

CLI는 성공적으로 검증되면 0, 검증에 실패하면(아직 잠김, 본문 해시 불일치, 손상된 라운드/체인 결속 또는 서명 검증 실패) 1, 사용법 오류/잘못된 형식의 번들이면 2로 종료합니다. --json 보고서는 자동화를 위해 같은 판정을 전달합니다. 번들이 자기 완결적이므로 검증자 크레이트(qub-core)와 CLI(qub-verify)가 제3자에게 필요한 유일한 소프트웨어입니다. 둘 다 공개되어 있으며 프로토콜의 기존 검증 경로를 재사용하므로 별도의 암호화 구현이 없습니다.

17.5 투명성 로그와의 관계

inclusion_proof는 §16 Merkle 포함 증명을 위한 선택적 슬롯입니다. 번들 단독 검증(§17.4)은 무결성, 라운드 결속/라운드 경과, 선택적 작성권에 대해 완전하지만, 의도적으로 독립적인 타임스탬프가 있는 존재 주장을 하지 않습니다. 채워져 있고 앵커까지 완전히 검증된 inclusion_proof는 번들 형식 버전을 변경하지 않고 §16.11의 리프 종류별 약속과 상한 시각을 추가합니다. 증명이 없다는 것은 "증명이 포함되지 않음"만 의미하며, "유효하지 않음"이나 반드시 "앵커링되지 않음"을 뜻하지 않습니다.

참조 구현에서 슬롯은 이제 타입이 지정되어 있습니다. qub_core::export::QubBundle::inclusion_proof_typed()는 같은 불투명 CBOR 필드를 통해 §16.9 전체 구조(리프, 감사 경로, 앵커링된 루트, AnchorRef)를 전달하는 Option<InclusionProof>를 반환하며 번들 형식 버전은 올라가지 않습니다. 독립형 qub-verify CLI는 --anchor 경로를 통해 이를 소비합니다. 앵커 지갑이 프로비저닝될 때까지(§16 상태) 값이 있지만 소유자가 자리표시자인 증명은 완전한 앵커 검증됨이 아니라 포함만 확인됨으로 보고합니다.