מפרט הפרוטוקול של qub

qub הוא פרוטוקול להתחייבויות זמניות קריפטוגרפיות: מערכת לאטימת מילים לתאריך עתידי ולהוכחה, כאשר תאריך זה מגיע, מה בדיוק נאמר ומתי.

שלושה אבני בניין מאפשרים את זה. drand הוא משואת אקראיות מבוזרת — תאריך הגילוי נאכף על ידי הפיזיקה, לא על ידי רצונו הטוב של גורם כלשהו. אחסון ציבורי קבוע הוא מאגר ציבורי חסין שיבוש — אף גורם אינו יכול לערוך או למחוק qub לאחר שנאטם. ML-DSA-65 היא חתימה דיגיטלית פוסט-קוונטית — כל qub קשור לזוג מפתחות שהסוד שלו לעולם אינו עוזב את מכשיר המחבר.

יחד, אבני בניין אלה יוצרים אמירה שהיא נעולת-זמן, מעידה-על-שיבוש, וניתנת לייחוס — קבלה שערכה גדל ככל שמשתפרת יכולתו של העולם לזייף את העבר.

שאר המסמך הוא המפרט הנורמטיבי הנדרש למימושים תואמי-הדדיות.


מפרט פרוטוקול qub

שדה ערך
גרסה 1.0 (גרסת פרוטוקול 0x01, גרסת עטיפה חיצונית 0x01)
תאריך 2026-05-01
סטטוס טיוטה
נסקר עד 2026-05-01

מסמך זה הוא מפרט הפרוטוקול הנורמטיבי עבור מערכת ההתחייבות הזמנית qub. הוא מגדיר מבני נתונים, כללי סריאליזציה, נוסחאות גזירה ונהלי אימות הנדרשים למימושים תואמי-הדדיות.

היקף: שכבת הפרוטוקול נייטרלית-שפה באופן מכוון — גוף ה-qub הוא טקסט גלוי / markdown / בתי pact אטומים, ורינדור מודע-לוקאל הוא באחריות הצופה (אפליקציית הרשת qub.social, iframe של <qub-embed>, לקוחות MCP וכו').


1. סימון ומוסכמות

סימון משמעות
u8, u64, i64 מספרים שלמים ללא סימן / עם סימן ברוחב סיביות מוגדר
[u8; N] מערך בתים באורך קבוע של N בתים
Vec<u8> מערך בתים באורך משתנה
Option<T> ערך מטיפוס T, או נעדר
String מחרוזת טקסט UTF-8, מנורמלת ב-NFC
`
SHA3-256(x) גיבוב NIST SHA3-256 של מחרוזת הבתים x (FIPS 202)
ceil(x) פונקציית תקרה: המספר השלם הקטן ביותר ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian הבית המשמעותי ביותר ראשון

כל המספרים השלמים בבניית טרום-תמונות מקודדים כמערכי בתים big-endian ברוחב קבוע (i64 → 8 בתים, u8 → בית 1) אלא אם צוין אחרת.

כל חותמות הזמן הן שניות Unix ב-UTC.


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,              // 0x01 = public (only value in MVP)
    content_type:   u8,              // 0x01 = text (only value in MVP)
    plaintext:      Vec<u8>,         // UTF-8 qub body
    sender_label:   Option<String>,  // Decorative display name; not authenticated
    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>,     // V1.1 — when reality renders judgment (verdict-uplift-plan §3.1)
    sender_label:        Option<String>,  // Decorative; not authenticated in MVP
    reply_to:            Option<[u8; 32]>,// Parent qub_id for reply chains; not in qub_id preimage; not signed (see §9.3)
    body:                Vec<u8>,         // Content payload (UTF-8 for text, CBOR for pact)
    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, כל שדות ה-Option נעדרים.

תצורות v1 אחרות: content_type = 0x03 (גוף pact, ראו §6.1); sig_alg = 0x01 (ML-DSA-65) עם author_signature ו-author_pubkey נוכחים (ראו §9.3); cosigner_pubkey ו-cosigner_signature נוכחים יחד עבור pacts חתומים-במשותף (ראו §9.7); reply_to מוגדר ל-qub_id של ה-qub האב עבור qubs של שרשרת תגובות (ראו §9.3 להשלכות היקף-החתימה).

2.3 SealedQub (פורמט קווי קנוני)

מסורייל באמצעות CBOR קנוני (§3). נכתב לאחסון קבוע. זהו הארטיפקט שעל-השרשרת.

SealedQub {
    version:           u8,              // Protocol major version (0x01 for v1)
    qub_id:            [u8; 32],        // Same as QubEnvelope.qub_id
    visibility:        u8,              // 0x01 = public; v1 viewers reject other values
    unlock_at:         i64,             // Unix seconds UTC
    outcome_at:        Option<i64>,     // V1.1 — 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
    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 control characters.
}

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>,       // V1.1 — מועבר הלאה מ-QubEnvelope.outcome_at / SealedQub.outcome_at; מניע את גוש מעקב-פסק-הדין בעמוד החשיפה (verdict-uplift-plan §5.1)
    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 (דרישות קידוד דטרמיניסטי בסיסיות)
סדר מפתחות במפה ממוין לפי אורך בתים מקודד תחילה (קצר לפני ארוך), ואז לקסיקוגרפית (בית-אחר-בית עבור קידודים באותו אורך)
קידוד מספרים שלמים הצורה הקצרה ביותר: 0–23 בבית הראשוני; 24–255 ב-2 בתים; 256–65535 ב-3 בתים; וכו'
קידוד אורך אורכים מוגדרים בלבד. אין מערכים, מפות, מחרוזות בתים או מחרוזות טקסט באורך בלתי-מוגדר (additional info = 31 אסור).
תגיות אין תגיות CBOR (טיפוס ראשי 6 אסור).
נקודה צפה אין floats (טיפוס ראשי 7 ערכים 0xF9–0xFB אסורים).
מחרוזות טקסט מקודדות ב-UTF-8, מנורמלות ב-NFC (Unicode Normalization Form C).
מחרוזות בתים בתים גולמיים. אין קידוד base64 בשכבת ה-CBOR.
מפתחות כפולים דחו עם שגיאה. מנתחים חייבים שלא לקבל בשקט מפתחות מפה כפולים.
מפתחות לא ידועים דחו עם שגיאה. מנתחים חייבים שלא לסבול מפתחות מפה מחוץ לקבוצת המפתחות הקנונית של הטיפוס — אסור ששתי מחרוזות בתים קנוניות שונות יפוענחו אי-פעם לאותו ערך (encode(decode(x)) == x), ובמטענים חתומים מפתח נוסף היה מהווה תוכן נסתר ששתי החתימות מתחייבות אליו. התפתחות סכמה עוברת דרך version, לעולם לא דרך מפתחות נוספים.
ערכים פשוטים רק true (0xF5), false (0xF4) ו-null (0xF6) מותרים.
שדות אופציונליים שדות אופציונליים נעדרים מושמטים ממפת ה-CBOR לחלוטין (לא מקודדים כ-null). שדות אופציונליים נוכחים נכללים בסדר מפתחות ממוין.

3.2 סדרי מפתחות קנוניים מאומתים

סדרי מפתחות אלה הם נורמטיביים. מימושים חייבים לפלוט מפתחות בדיוק בסדר זה. אסרציות ניפוי באגים מומלץ שיאמתו את הסדר בבנייות שאינן release.

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 (V1.1 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 (V1.1 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)

PactTerms (גוף pact, 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 bytes 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 bytes author_signature, cosigner_signature
מפתח ציבורי ML-DSA-65 (1,952 בתים) 0x59 0x07 0xA0 + 1,952 bytes 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. בית ריפוד 0x00 בודד מצורף כדי להגיע ל-10 בתים ליישור. מימושים חייבים להשתמש בדיוק ב-10 בתים אלה: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

קידוד outcome_at: V1.1 הרחיב את הטרום-תמונה מ-92 ל-100 בתים כדי לקפל את השדה האופציונלי outcome_at לתוך הקשירה. outcome_at נעדר מקודד כ-8 בתי אפס; מאמתי הפרוטוקול דוחים outcome_at <= 0 בכל מקום כך שערך-סנטינל זה אינו יכול להתנגש עם ערך לגיטימי. ראו §3.2 (פורמט קווי) ואת tasks/verdict-uplift-plan.md שבעץ למנגנון הפסיקה שמניע שדה זה.

קידוד drand_round: V1.2 הרחיב את הטרום-תמונה מ-100 ל-108 בתים כדי לקפל את drand_round (סבב ה-drand היעד, §4.3) לתוך הקשירה, והעלה את מפריד התחום ל-QUB_ID_V2. זה קושר את סבב ה-timelock לתוך זהות ה-qub: שער אינו יכול לקשור מחדש את הצופן לסבב שונה (למשל סבב שכבר חלף) מזה שמשתמע מ-unlock_at המוצג. נוהל השחרור (§8) מאמת בנוסף שהסבב הצרוב בתוך משבצת הצופן של ה-tlock תואם ל-unlock_round(unlock_at), כך שזמן השחרור המוצג הוא באופן מוכח הסבב השולט בפענוח.

תכונות:

4.2 body_hash

body_hash = SHA3-256(body)

כאשר body הוא מטען התוכן הגולמי מסוג Vec<u8>. עבור qubs טקסט, זהו גוף ה-qub המקודד ב-UTF-8.

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 רץ בזמן הגיבוב כך שהתקציר יציב על פני רצפי נקודות-קוד שקולים-ויזואלית. ערך הסנטינל של כולו-אפסים שמור למקרה הנעדר; מחרוזת ריקה נדחית בגבול ה-CBOR הקנוני כקידוד לא-קנוני של "נעדר" (הקידוד הקנוני משמיט את השדה לחלוטין).

4.3 מיפוי סבב-שחרור

drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
פרמטר מקור דוגמה
unlock_at שניות Unix UTC שנבחרו על ידי המשתמש 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time מידע שרשרת drand (genesis_time) 1595431050
chain_period_seconds מידע שרשרת drand (period) 30

פעולת ה-ceil() בוחרת את סבב ה-drand הראשון שזמן הגילוי שלו ≥ unlock_at. זה מבטיח שה-qub אינו הופך לבר-פענוח לפני זמן השחרור שנבחר.

מקרה קצה: אם (unlock_at - chain_genesis_time) מתחלק בדיוק ב-chain_period_seconds, התוצאה היא אותו סבב מדויק — ה-qub נפתח בדיוק בזמן הגילוי של אותו סבב.

אימות: unlock_at חייב להיות בעתיד בזמן האטימה. unlock_at חייב שלא להיות יותר מ-10 שנים מ-created_at (כדי להגביל סיכון תלות-drand ארוכת-טווח; הממשק מומלץ שיזהיר עבור תאריכי שחרור מעבר ל-2 שנים).


5. Newtypes של פורמט קווי

Newtypes של פורמט קווי מספקים בטיחות בזמן הידור נגד בלבול של בתי CBOR עם JSON, טקסט גלוי גולמי או קידודי בתים אחרים.

טיפוס מכיל מיוצר על ידי נצרך על ידי
SealedQubCbor CBOR קנוני של SealedQub serialize_sealed_qub() העלאת אחסון-קבוע, שליפת צופה
QubEnvelopeCbor CBOR קנוני של QubEnvelope 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, Markdown מוגבל) 50 KB בתשלום / 10 KB חינם ראו §10 לכללי רינדור. החלוקה חינם / בתשלום נאכפת על ידי שירות ההעלאה; תקרת-הקשיחות ברמת הפרוטוקול היא 50 KB.
0x02 שמור (עתידי) מוקצה עבור טיפוס תוכן עתידי; לא תקף ב-v1. צופים חייבים לדחות אותו לפי הכלל שלהלן.
0x03 Pact (הסכם דו-צדדי, גוף CBOR) 100 KB הגוף הוא PactTerms בקידוד CBOR קנוני (§6.1). חתימת מאשר-משותף לפי §9.7.
0x04 פסק דין (ציון עצמי של היוצר, גוף CBOR) 8 KB הגוף הוא VerdictBody בקידוד CBOR קנוני (§6.2). נפלט אך ורק על ידי ה-intent המערכתי verdict. הקשר ל-qub ההורה נמצא בתג Arweave Parent-Tx-Id, ולא בגוף. ראו verdict-uplift-plan §3.4.

צופים חייבים לדחות טיפוסי תוכן לא ידועים עם שגיאה ברורה הנראית למשתמש. צופים חייבים שלא לנסות לרנדר טיפוסים לא ידועים כטקסט.

6.1 גוף Pact (content_type = 0x03)

גוף pact הוא הקידוד הקנוני ב-CBOR של ערך PactTerms:

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), value: String (≤ 2,000) }   // NFC on both sides
PartyIdentifier{ label: String (≤ 100), contact: Option<String (≤ 320)> }

סדרי מפתחות CBOR קנוניים לכל שלוש המפות ניתנים ב-§3.2. סך ה-CBOR המסורייל של ה-pact חייב שלא לעבור 100 KB (תואם ל-§6).

מבחין סכמה. השורה הראשונה ב-terms עבור pact מסוג structured/v1 חייבת להיות { key: "pact_schema", value: "structured/v1" }. שורות ללא סמן זה הן pacts "מותאמים אישית" ואינן מקבלות אימות מובנה או רינדור מודע-סכמה.

משבצות אישור מוקפאות. pacts מסוג structured/v1 נושאים בדיוק ארבע שורות אישור תחת המפתחות הבאים:

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

ה-value לכל אחת הוא אחת משמונה מחרוזות אנגלית מוקפאות שנבחרות לפי הזוג (role, kind), כאשר role ∈ { seller, buyer, provider, client } ו-kind ∈ { standard, capacity }. המחרוזות עצמן הן נתוני פרוטוקול נורמטיביים — חתימות ה-ML-DSA-65 של שני הצדדים מתחייבות לבתים המדויקים דרך body_hash. הן אינן מתורגמות; הגוף החתום הוא נייטרלי-שפה. כל שינוי ניסוח מצריך גרסת סכמה חדשה (structured/v2).

שמונה המחרוזות, החיפוש שלהן (acknowledgement_for(role, kind)) והרציונל לכל אחת מקובעים על ידי מימוש-ההפניה. מימושים תואמים חייבים לפלוט ערכי אישור זהים-בתים; בדיקות body-hash של SHA3-256 על golden-fixture המכסות את כל ארבעת צירופי התפקידים תופסות כל סטייה.

סדר תצוגה בצופה. מחרוזות האישור מכילות ביטויים כגון "described above", המניחים ששורות התיאור / ההיקף מרונדרות לפני האישורים. צופים חייבים לרנדר את מערך ה-terms בסדר CBOR; שינוי סדר שובר את סמנטיקת הפרוזה.

איש קשר של הצד שכנגד. כאשר ה-contact של צד B הוא כתובת דוא"ל תקפה, שירות העלאת ה-qub שולח אוטומטית דוא"ל הזמנה לסקירה / חתימה-משותפת בזמן ההכנה וקושר את החתימה-המשותפת הסופית לאימות אותה כתובת (§9.7). pacts שאיש הקשר של צד B שלהם נעדר עדיין יכולים להיחתם במשותף, אך רק דרך ערוץ מחוץ-לפס — השירות מסרב לבקשות חתימה-משותפת שאינן יכולות להפיק סמן אימות-דוא"ל תואם בן 15 דקות.

6.2 גוף פסק דין (content_type = 0x04)

גוף פסק דין הוא הקידוד הקנוני ב-CBOR של ערך VerdictBody:

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 (תואם לשורה במרשם לעיל).

Enum של תוצאה. הבית על ה-wire הוא נייטרלי-intent; ארבע הקטגוריות Right / Partial / Wrong / Unfalsifiable מכסות את מרחב התוצאות של כל intent נושא-פסק-דין. תוויות ספציפיות-ל-intent ("צדקתי" / "עמדתי בזה" / "סופק" / "אושר" ל-Right וכו') הן עניין רינדור בצד הצופה הנפתר מול ה-intent של ה-qub ההורה — שכבת ה-wire נשארת נייטרלית בשפה וב-intent. ערכים מחוץ ל-1..=4 חייבים להידחות בעת הפענוח.

קישור להורה. qub פסק דין אינו נושא את ההפניה להורה בגוף שלו. מזהה הטרנזקציה של Arweave של ה-qub ההורה נפלט בזמן ההעלאה כתג אחסון Parent-Tx-Id (§7, שכבת תגי האחסון). כך הגוף נשאר הצהרת הערכה-עצמית חתומה ועצמאית; שרשרת הביקורת ("צדק במה?") נקבעת דרך חיפוש תג Arweave.

בטיחות כתובת הראייה (נורמטיבי). כאשר evidence_url קיים, מאמתים (בצד ההרכבה, בצד ה-wire, בקצה ה-Worker) חייבים לאכוף את הבאים:

  1. HTTPS בלבד. המחרוזת חייבת להתחיל ברצף הבתים https://. כל סכמה אחרת — http, ftp, javascript, data, file וכו' — נדחית.
  2. תקרת אורך. ≤ 2,048 בתים (גבול URL מעשי בדפדפנים).
  3. בדיקת NFC + קודפוינטים עוינים. אותו כלל כמו ל-title ול-reflection — קודפוינטים של bidi-override / zero-width / tag-block / BOM / C0 / C1 נדחים. ההגדרה תואמת לפונקציה הראסטית crate::handle::contains_hostile_text_codepoint ולפונקציית ה-TS שב-workers/api/src/utils/unicode.ts::isHostileCodepoint (יש לשמור על סנכרון מלא).
  4. ללא רווחים, ללא תווי בקרה של ASCII. רווחים / DEL / בתים מתחת ל-0x20 בכל מקום בכתובת נדחים — סוגר את וקטור ההזרקה של \n/\t שכלל ה-bidi אינו מכסה.
  5. קטע מארח לא-ריק. כל מה שבין https:// לבין ה-/, ? או # הראשון חייב להיות לא-ריק.

ללא משיכת תוכן בצד השרת. ה-Worker אסור לו לעבור פרוקסי, למשוך או להציג תצוגה מקדימה של הכתובת. הפרוטוקול מאחסן מחרוזת; הרינדור מתבצע בצד הצופה עם rel="nofollow noopener noreferrer" target="_blank" ועם מארח גלוי המוצג לצד טקסט הקישור.

רפלקציה. טקסט רפלקציה אופציונלי שכותב היוצר ("מה השתנה, מה למדת"). אותו אימות NFC ובדיקת קודפוינטים עוינים כמו ל-title. קלט ריק / שמכיל רק רווחים מתכווץ ל"נעדר" בזמן הבנייה.

גרסת סכמה. 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.
 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 = ceil((unlock_at - chain_genesis_time) / chain_period_seconds).
    (Computed here, before qub_id, because drand_round is bound into the qub_id
    preimage — §4.1, V1.2.)
 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,
    unlock_at, drand_chain_id, drand_round.
12. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12a. Generate K = 32 random bytes (CSPRNG) and N = 12 random bytes (CSPRNG).
     Compute W = wrap_sealed_qub(SealedQubCbor, qub_id=qub_id, key=K, nonce=N)
     per §13. The bytes uploaded to permanent storage are the OuterWrapper CBOR W,
     never the bare SealedQubCbor. K leaves the device only as the URL
     fragment in step 16.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit W (the OuterWrapper bytes) to the qub upload service; the service
    signs and uploads to permanent storage. The service is byte-blind to the inner
    SealedQubCbor and never receives K.
16. Receive arweave_tx_id from the service. Construct delivery URL as
    `<origin>/c/<arweave_tx_id>#<base64url(K)>` (or `<origin>/s/<short_code>#<base64url(K)>`
    when a short code is allocated). Browsers do not transmit URL fragments
    to servers, so K is never observed by qub.social or any storage gateway.

שכבת תגיות אחסון (מחוץ-לפס). שירות העלאת ה-qub מצרף קבוצה קטנה במכוון של תגיות עסקת אחסון לצד המטען העטוף. Content-Type=application/octet-stream נדרש נורמטיבית. שירות-ההפניה מצרף בנוסף שלוש תגיות אופציונליות כאשר היוצר בוחר לחשוף אותן: Intent (כוונת חיבור מאומתת-מול-רשימת-היתר — למשל, quote, reply, commitment), Author (טביעת-אצבע מפתח ה-§9.3 של היוצר כ-64 תווי hex קטנים) ו-Parent-Tx-Id (מזהה עסקת האחסון של ה-qub האב עבור שרשראות תגובות, 43 תווי base64url).

תגית ה-Author היא בהצטרפות-מרצון לכל qub: אפליקציית היוצר של ההפניה מצרפת אותה רק כאשר המשתמש מאפשר במפורש ייחוס ציבורי בזמן האטימה. כאשר המתג כבוי — ברירת המחדל — לא נכתבת תגית Author וה-qub אינו מיוחס בשרשרת: שום דבר באחסון הקבוע אינו קושר את ההעלאה לכינוי, דוא"ל או qubs אחרים של היוצר. כאשר המתג דלוק, טביעת האצבע של Author מתפענחת ל-@handle שבחר היוצר דרך שרשרת האישורים של §9.5. יחסי שרשרת-תגובות ו-Intent אינם מזהים. העטיפה החיצונית (§13) מגינה על ה-גוף הפנימי מפני קורלציית טקסט-מוצפן — מונעת מקוצֵר לזהות ולפענח-בכמות העלאות בצורת-qub לאחר שסבב ה-drand שלהן מתפרסם.

שירות-ההפניה במכוון אינו מצרף תגיות App-Name, App-Version או Type: כל מסנן בעל-ערך-יחיד כזה יחזיר את כל קורפוס ה-qub לשאילתת GraphQL, מה שאינו עקבי עם היקף החסיון בגוף-בלבד של העטיפה.

מאמת תואם חייב שלא להיות תלוי בשום תגית אחסון לאימות צד-שלישי של §11; גיבוב הגוף / qub_id / החתימה מתחייבים רק ל-CBOR הפנימי, לעולם לא לקבוצת התגיות.


8. פרוטוקול השחרור

רצף השחרור המלא. כל שלב הוא נורמטיבי.

 1. Viewer opens delivery URL. Extract arweave_tx_id from path AND
    K = base64url_decode(fragment) from the URL fragment. If the fragment
    is absent or malformed → display "this URL is missing its decryption
    key" and stop; the viewer MUST NOT contact the storage gateway
    without K, since fetching wrapped bytes the viewer cannot decrypt
    serves no purpose and only leaks the access attempt.
 2. Check denylist. If tx_id is denylisted → display block message. Stop.
 3. Fetch OuterWrapper bytes from permanent storage (with multi-gateway fallback).
 3a. Unwrap: parse the bytes as OuterWrapper (§13), verify the wrapper
    `version` byte is `0x01`, and compute SealedQubCbor =
    unwrap_sealed_qub(OuterWrapper, key=K). Any AEAD authentication
    failure (wrong K, tampered ciphertext, swapped qub_id-as-AAD,
    swapped nonce) → display "this URL's decryption key does not match
    the stored qub" and stop. Authentication failures are
    indistinguishable to the viewer per §13.5.
 4. Parse SealedQubCbor → SealedQub.
 5. Validate: SealedQub.version is known (0x01). Reject unknown versions.
 6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
 6a. Round-binding check (V1.2). Recompute expected_round =
    ceil((SealedQub.unlock_at - chain_genesis_time) / chain_period_seconds).
    Reject unless SealedQub.drand_round == expected_round AND the round baked
    into the tlock ciphertext stanza (read via the age/tlock header, no signature
    required) == expected_round. 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.
13. Verify: QubEnvelope.content_type is known and renderable.
    Known values: 0x01 (text), 0x03 (pact). 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 appropriate renderer (see §10 for text, §6 for pact).
17. Construct RevealedQub for display.

9. חתימת מחברוּת

9.1 רציונל

qubs מאוחסנים באחסון קבוע. חתימות מחברוּת חייבות להישאר בלתי-ניתנות-לזיוף ללא הגבלת זמן, ולכן v1.0 משתמש בסכמת ה-ML-DSA-65 הפוסט-קוונטית (FIPS 204) במקום סכמה קלאסית שאבטחתה עשויה להתדרדר במהלך משך החיים הקבוע של ה-qub.

9.2 מרשם אלגוריתמים

sig_alg סכמה גודל מפתח גודל חתימה
0x00 ללא חתימה (לא חתום)
0x01 ML-DSA-65 (FIPS 204) 1,952 בתים 3,309 בתים

צופים חייבים לדחות ערכי sig_alg לא ידועים.

9.3 בניית טרום-תמונה חתומה

היו קיימות שתי גרסאות של טרום-תמונה. כל החתימות חייבות להשתמש ב-V2, ומאמתים חייבים לקבל את V2 בלבד. טרום-תמונת V1 המיושנת (המתועדת להלן לצורך עיון היסטורי) התקבלה כחלופת-אימות-בלבד במהלך המעבר ל-V2; חלופה זו הוצאה משימוש וחתימת V1-בלבד נדחית כעת.

V2 (הנוכחית — מופקת על ידי כל חתימת מחבר חדשה, ועל ידי שתי החתימות של זרימת ההכנה / החתימה-המשותפת של pact):

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 בתי אפס אינם פלט 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. מימוש-ההפניה חושף זאת כקבוע ORG_ID_PRESENT_INDIVIDUAL = 0x00 ב-crates/qub-core/src/signing.rs; צופים המשחזרים sig_input לאימות חייבים לפלוט את אותו בית.

היקף חתימה — מה מכוסה ומה לא. ה-sig_input של V2 מתחייב ישירות ל-version, qub_id, body_hash, unlock_at, sender_label ו-reply_to (בנוסף למפריד התחום הקבוע ובית ה-org_id_present). qub_id עצמו נגזר מ-version, content_type, created_at, unlock_at, outcome_at, drand_round ו-body_hash דרך הטרום-תמונה של §4.1, כך שכל שינוי בשדות אלה מפיק 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 (V1.2)
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, כתובת דוא"ל, כינוי רשת חברתית או אישור passkey — הם שיפור מתקדם בצד-הצופה ואינם נדרשים לאימות חתימה. צופים המפענחים אישורים לזהות תצוגה חייבים להחיל את הקדימוּת:

handle > email > social > fingerprint

חלופת טביעת-האצבע היא ה-hex הקטן של SHA3-256(author_pubkey); היא תמיד זמינה עבור כל qub חתום. צופים רשאים לקצר אותה לתצוגה — צופה-ההפניה מרנדר qub: ואחריו ארבעת הבתים הראשונים והאחרונים (qub:<8 hex>…<8 hex>).

מאמת תואם יכול להשלים כל בדיקה ב-§9.4 ללא יצירת קשר עם ה-API של qub, ללא רשת מעבר לאחסון קבוע ו-drand, וללא שום חיפוש בצד-השרת. פענוח אישורים הוא שלב נפרד בעל-מאמץ-מיטבי המבוצע רק לאחר שאימות החתימה הצליח.

9.6 השפעת גודל

Ed25519 ML-DSA-65
חתימה 64 בתים 3,309 בתים
מפתח ציבורי 32 בתים 1,952 בתים
סך הכל לכל qub 96 בתים 5,261 בתים
הפרש עלות אחסון (ב-~$5/MB) ~$0.0005 ~$0.026

עבור qub טקסט של 500–2,000 בתים, ML-DSA-65 משלש בערך את הגודל המאוחסן. העלות המוחלטת זניחה.

9.7 אימות מאשר-משותף (הסכמים דו-צדדיים של pact)

עבור הסכמים דו-צדדיים (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."

תכונות:

שער קישור-דוא"ל (תפעולי). כאשר pact בהכנה נושא איש קשר דוא"ל של צד B (§6.1), שירות העלאת ה-qub חייב לסרב לבקשת החתימה-המשותפת אלא אם קיים סמן אימות-דוא"ל קצר-מועד התואם גם למזהה ההכנה וגם לגיבוב הדוא"ל-המנורמל של אותו איש קשר. הסמן נכתב על ידי /api/v1/auth/verify כאשר אסימון הקישור-הקסם נושא staging_id והכתובת המאומתת תואמת ל-SHA-256(normalise_email(party_b.contact)) — כאשר normalise_email(addr) משמר את אותיות החלק-המקומי וממיר לאותיות קטנות רק את חלק התחום (לפי RFC 5321 §2.3.11), ו-SHA-256 כאן הוא גיבוב NIST FIPS 180-4 (שונה מ-SHA3-256 שבשימוש בגזירות §4) — ופג 900 שניות (15 דקות) לאחר ההנפקה. זהו שער אנטי-התחזות תפעולי, לא חלק מהוכחת ה-qub שעל-השרשרת — מאמת צד-שלישי המשחזר את §11 זקוק רק לאחסון קבוע ול-drand, ללא שום חיפוש בצד-השרת. הסמן קיים בצד-השרת בלבד ולעולם אינו חלק מהגוף החתום.

השפעת גודל (מחבר ML-DSA-65 + מאשר-משותף):

רכיב גודל
חתימת מחבר 3,309 בתים
מפתח ציבורי של מחבר 1,952 בתים
חתימת מאשר-משותף 3,309 בתים
מפתח ציבורי של מאשר-משותף 1,952 בתים
סך תקורת קריפטו 10,522 בתים
הפרש עלות אחסון ~$0.05

10. רינדור וחיטוי Markdown

סעיף זה הוא קריטי-לאבטחה. הצופה מרנדר qubs טקסט (content_type = 0x01) באמצעות תת-קבוצה מוגבלת של Markdown.

10.1 אלמנטים מותרים

10.2 אלמנטים אסורים

אלמנט טיפול
HTML גולמי (<div>, <script> וכו') מוסר לחלוטין. שום HTML אינו עובר.
תמונות (![alt](url)) מוסר. תחביר תמונה מוסר מהפלט.
קישורים ([text](url)) הכתובת מרונדרת כטקסט פשוט גלוי. אינה מקושרת אוטומטית. אינה ניתנת ללחיצה ללא פעולת משתמש מפורשת.
סכמות URL מסוכנות javascript:, data:, vbscript:, file: — מוסרות.
Iframes, embeds, objects מוסרים.
ישויות HTML מפוענחות לתווי תצוגה רק אם בטוחות.

10.3 מימוש

מימושים חייבים להשתמש במנתח רשימת-היתר קפדני, לא ברשימת-חסימה. הגישה המומלצת:

  1. נתחו את ה-Markdown באמצעות pulldown-cmark (או שווה-ערך).
  2. הלכו על ה-AST והשמיטו כל צומת שאינו ברשימת-ההיתר (§10.1).
  3. עבור צמתי קישור: פלטו את הכתובת כטקסט גלוי, לא כאלמנט <a> הניתן ללחיצה.
  4. המירו את ה-AST המסונן לייצוג ביניים מטופס (למשל, enum מסוג MarkdownNode עם וריאנטים בטוחים בלבד). HTML גולמי בלתי-ניתן-לייצוג מבנית בייצוג ביניים זה.
  5. רנדרו מייצוג הביניים המטופס לשכבת התצוגה היעד (למשל, רכיבי תצוגה ריאקטיביים, צמתי DOM). אין שרשור מחרוזות HTML או innerHTML בשום שלב.

גישות רשימת-חסימה שבריריות מכיוון שהרחבות Markdown חדשות או מוזרויות מנתח יכולות להכניס אלמנטים לא-מסוננים. גישת ה-AST המטופס הופכת XSS לבלתי-אפשרי מבנית — אין וריאנט שיכול לשאת HTML שרירותי.

10.4 מגבלות גודל ומבנה


11. אימות צד-שלישי

כל צד שלישי יכול לאמת qub ציבורי ללא שיתוף פעולה של qub. נוהל האימות:

1. Obtain arweave_tx_id (from delivery URL or direct knowledge).
2. Fetch SealedQubCbor from any storage gateway.
3. Confirm storage block inclusion (block height, block timestamp).
4. Parse SealedQubCbor → SealedQub.
5. Fetch drand round signature for SealedQub.drand_round.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify QubEnvelope.qub_id == SealedQub.qub_id.
10. Verify QubEnvelope.unlock_at == SealedQub.unlock_at.
11. If sig_alg != 0x00: verify author_signature (see §9.4).
12. All checks pass → qub is verified.

מה האימות מוכיח:

הוכחה מה היא מבססת
התחייבות הטקסט המוצפן היה קיים עד חותמת הזמן של בלוק האחסון.
שלמות גוף הטקסט הגלוי תואם את הגיבוב שהתחייבו אליו ולא שונה.
תזמון התוכן היה בלתי-קריא עד סבב ה-drand, התואם את זמן השחרור שנבחר (בכפוף להנחות האבטחה של tlock ו-drand).

מה האימות אינו מוכיח:

אי-הוכחה מדוע
מחברוּת ה-sender_label הוא דקורטיבי. ללא sig_alg0x01, כל אחד היה יכול לאטום תוכן זה.
כוונה ה-qub מוכיח תוכן ותזמון, לא מה שהיוצר התכוון סובייקטיבית.
תזמון טרום-אירוע הכללת בלוק האחסון עשויה לפגר אחר ההעלאה בפועל בדקות. חותמת זמן ההתחייבות היא זמן הבלוק, לא הרגע שבו המשתמש לחץ "אטום".

12. ניהול גרסאות

12.1 גרסת פרוטוקול

שדה ה-version (u8) גם ב-SealedQub וגם ב-QubEnvelope מזהה את גרסת הפרוטוקול הראשית.

12.2 היסטוריית גרסאות

גרסה ערך תיאור
v1 0x01 qubs טקסט ציבוריים (content_type 0x01), הסכמים דו-צדדיים של pact (0x03, סכמת structured/v1, מחבר ML-DSA-65 + מאשר-משותף), tlock, SHA3-256

12.3 תאימות-קדימה

צופה v1 הנתקל ב-QubEnvelope עם מפתחות מפת CBOR לא ידועים (מפתחות שאינם בסדר הקנוני של §3.2) חייב לדחות אותו עם שגיאת פענוח (§3.1). תאימות-קדימה נשענת על שדה ה-version, לא על סובלנות למפתחות: תוספות עתידיות — אפילו מטא-נתונים מינוריים — מגיעות תחת ערך version חדש, שאותו צופה v1 דוחה עם שגיאת "פרוטוקול חדש יותר" ברורה במקום להשמיט בשקט תוכן שהחתימות מתחייבות אליו.

צופה v1 הנתקל ב-sig_alg = 0x01 (ML-DSA-65) אך חסר תמיכת אימות ML-DSA-65 מומלץ שיציג את תוכן ה-qub עם הודעת "חתימה נוכחת אך אינה ניתנת לאימות", לא ידחה את ה-qub לחלוטין. מימוש-ההפניה כיום דוחה כל ערך sig_alg למעט 0x00 ו-0x01 מכיוון שמרשם v1 אינו מכיל אלגוריתם תקף אחר — דחייה קפדנית וכשל-רך זהים בתצפית עד שאלגוריתם שלישי נרשם. התנהגות הכשל-הרך לעיל הופכת לנושאת-משקל ברגע ש-§9.2 מקבל ערך חדש, וצופה-ההפניה יעודכן לכשל-רך באותה נקודה.

12.4 גרסת עטיפה חיצונית

ה-OuterWrapper המתואר ב-§13 נושא בית version משלו, בלתי-תלוי ב-SealedQub.version וב-QubEnvelope.version. שני מרחבי הגרסאות מתפתחים בנפרד: החלפה סימטרית בטוחת-פוסט-קוונטים עתידית מקדמת את בית העטיפה מבלי לגעת בגרסת הפרוטוקול הפנימית, ותוספת ברמת-הפרוטוקול עתידית (למשל, שדה מעטפה חדש) מקדמת את הגרסה הפנימית מבלי לגעת בבית העטיפה.

OUTER_WRAPPER_VERSION_* ערך אלגוריתם סטטוס
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM עם nonce של 12 בתים, תג אימות של 16 בתים, AAD קשור ל-qub_id ברירת מחדל v1
0x020xFF שמור עתידי

צופים חייבים לדחות גרסאות עטיפה לא ידועות עם שגיאה ברורה. הפרוטוקול שומר במכוון את מרחב גרסאות העטיפה צר עד שמופיע מניע מיגרציה קונקרטי (למשל, הנחיית NIST המעדיפה AEAD שונה); משבצת 0x02 תוקצה באותה רוויזיה שמכניסה את האלגוריתם.


13. עטיפת הצפנה חיצונית

13.1 רציונל

שכבות הפרוטוקול (QubEnvelope → tlock → SealedQub) הופכות qub אטום לנעול-זמן: הגוף בלתי-קריא עד unlock_at וחתימת סבב ה-drand פורסמה. לאחר השחרור, עם זאת, חתימת הסבב ציבורית וצורת ה-CBOR הקנונית של SealedQub ניתנת לזיהוי, כך שקוצֵר שאינדקס עסקאות אחסון-קבוע יכול לפענח-בכמות את כל קורפוס ה-qub.

עטיפת ההצפנה החיצונית סוגרת את הערוץ הזה על ידי הוספת שכבת AEAD סימטרית נוספת בין ה-SealedQubCbor הקנוני לבתים הנכתבים לאחסון קבוע. המפתח בן 256 הסיביות K חי רק במקטע ה-URL של כתובת המסירה ובמכשירי המשתמשים; דפדפנים אינם משדרים מקטעי URL לשרתים, כך ש-qub.social, כל שער אחסון וכל CDN מול אחד מהם הם עיוורים-בתצפית ל-K. כל qub באחסון קבוע הוא לפיכך טקסט מוצפן אטום שהטקסט הגלוי שלו בלתי-ניתן-לשחזור ללא ה-URL שהיוצר בחר לשתף.

השפעה נטו:

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
  ↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12a, this section)
OuterWrapper CBOR bytes              ← uploaded to permanent storage (§7 step 15)

האטימה והשחרור ברמת הפרוטוקול (§7, §8) אינם משתנים מתחת לגבול העטיפה; העטיפה מתחברת באתר הקריאה של seal() ומתנתקת באתר הקריאה של unlock().

13.3 מבנה נתונים של OuterWrapper

struct OuterWrapper {
    version:    u8,           // 0x01, see §12.4
    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. CBOR קנוני לפי §3, עם אותו כלל סדר-מפתחות (ממוין לפי אורך בתים מקודד עולה, ואז לקסיקוגרפית). ארבעת המפתחות הם:

מפתח בתים מקודדים סדר
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

הבית הראשון של ה-OuterWrapper CBOR הוא לפיכך כותרת המפה באורך-מוגדר עבור מפה בת 4 רשומות (0xA4).

13.4 קשירת AAD ל-qub_id

העטיפה קושרת את qub_id כנתון מאומת נוסף של AEAD. זוהי ההגנה המבנית הנושאת-משקל נגד שלוש מחלקות של התקפה:

התקפה הגנה
העברת טקסט מוצפן תחת שדה qub_id שונה בעטיפה אי-התאמת AAD → אימות AEAD נכשל
ערבוב מקטע ה-URL של qub A עם בתי האחסון-הקבוע של qub B אי-התאמת AAD → אימות AEAD נכשל
שיבוש שדה ה-qub_id של העטיפה לאחר ההעלאה אי-התאמת AAD → אימות AEAD נכשל

נשיאת qub_id בטקסט הגלוי של העטיפה אינה מחלישה משמעותית את חסינות המנייה — qub_id עצמו הוא גיבוב SHA3-256 של הטרום-תמונה של §4.1 ללא טרום-תמונה ניתנת-לשחזור מהתקציר, ומנייה שכבר קצרה את בתי העטיפה אינה לומדת דבר מה-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
    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.4
    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
    return P                            // P is the inner SealedQubCbor

קריסת מצב-כשל. K שגוי, nonce שגוי, אי-התאמת AAD וטקסט מוצפן משובש כולם מפיקים את אותה שגיאת DECRYPT_FAILED. זוהי תכונת AEAD מכוונת: הבחנה במצב הכשל הייתה יוצרת ערוץ-צד שתוקף מרוחק יכול לבחון בשליחת עטיפות פגומות ובמדידת זמן התגובה. מימושי-הפניה חייבים לקרוס את כל כשלי ה-AEAD לצורת שגיאה יחידה.

13.6 חומר מפתח והפצה

מפתח העטיפה K הוא ערך אקראי אחיד בן 256 סיביות שנוצר לכל-qub על ידי CSPRNG. מימושי-ההפניה שואבים אותו מ:

הפצה: K חייב להיות מקודד כ-base64 בטוח-ל-URL (RFC 4648 §5, ללא ריפוד) ומצורף לכתובת המסירה כרכיב המקטע:

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

המקטע לעולם אינו משודר לשום שרת על ידי דפדפן תואם. ערוצי שחזור (אינדקס היסטוריה בצד-השרת, שליחה-אוטומטית-בדוא"ל בהצטרפות-מרצון) המשמרים את כתובת המסירה המלאה — כולל המקטע — מעבר למכשיר המשתמש הם פשרה מפורשת מול עמדת ברירת המחדל של גריסה-קריפטוגרפית וחייבים להיות מוגבלים בהסכמת משתמש מפורשת.

אובדן מקטע. אם משתמש מאבד את מקטע ה-URL ואין לו ערוץ שחזור, ה-qub בלתי-קריא. זוהי הפשרה הנושאת-משקל של העיצוב וחייבת להיחשף למשתמש בזמן האטימה. ה-MVP מחזק את חשיפת זמן-האטימה עם טקסט מפורש "שמרו כתובת זו" וערוץ שחזור בדוא"ל-מאומת למשתמשים המצטרפים מרצון.

13.7 מחוץ-להיקף עבור סעיף זה

13.8 qubs ציבוריים (השמטת העטיפה)

העטיפה החיצונית היא אופציונלית בשכבת המסירה. יוצר רשאי לחתום qub כציבורי, ובמקרה זה ה-SealedQubCbor הקנוני נכתב לאחסון קבוע ישירות, ללא שכבת OuterWrapper וללא מפתח K:

SealedQubCbor bytes  ──(public)──▶  uploaded to permanent storage as-is
SealedQubCbor bytes  ──(private)─▶  AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded

qub ציבורי הוא נעול-זמן אך לא מבוקר-קישור: הוא נשאר בלתי-קריא עד שסבב ה-drand שלו מתפרסם (שכבת ה-tlock אינה משתנה), אך לאחר השחרור כל מי שברשותו ה-arweave_tx_id יכול לפענח אותו — אין צורך במקטע URL, מכיוון שאין K. זוהי הפשרה המכוונת עבור משטחים שהשרת חייב להניע: הודעות דוא"ל על שחרור, הטמעות צד-שלישי ו-SEO עשיר יותר לאחר השחרור — כולם זקוקים לקישור שפועל ללא סוד שהשרת לעולם אינו מחזיק בו (§13.6).

השלכות שעל יצרן חייב להביא בחשבון:

פרטי (עטוף) נשאר ברירת המחדל; ציבורי הוא בחירת יוצר מפורשת לכל 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  = 4695445  (= (1736294400 - 1595431050) / 30, 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 — V1.2):
  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)
  0x000000000047A595  ||  // drand_round as u64 big-endian (4695445)
  body_hash           ||  // 32 bytes
  title_hash              // 32 bytes (all-zeros sentinel; title absent)

Expected output:
  qub_id = SHA3-256(preimage)
         = 3a9fcb31b750d985c262fada6d4f777f
           d6a28be831d941d85c131f5a4bbaf8a4

מימושים חייבים להפיק ערכי body_hash ו-qub_id זהים עבור קלט זה. וקטור בדיקה זה מומלץ שיהיה בדיקת היחידה הראשונה שנכתבת. הערכים הקנוניים לעיל חושבו על ידי מימוש-ההפניה וחייבים להתאים סיבית-אחר-סיבית. פריסות טרום-תמונה היסטוריות (טרום-השקה — שום qub חי לא הסתמך עליהן): ה-qub_id של V1.0 בן 92 הבתים היה 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; ה-qub_id של V1.1 בן 100 הבתים (לאחר קיפול outcome_at_or_zero) היה b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. V1.2 מקפל את drand_round ומעלה את מפריד התחום ל-QUB_ID_V2.

14.2 מיפוי סבב-שחרור

Input:
  unlock_at           = 1735689600
  chain_genesis_time  = 1595431050
  chain_period_seconds = 30

Calculation:
  (1735689600 - 1595431050) / 30 = 4675285.0
  ceil(4675285.0) = 4675285

drand_round = 4675285

14.3 הלוך-ושוב של CBOR קנוני

מימושים חייבים לאמת ש-serialize(parse(serialize(qub))) == serialize(qub) עבור כל הקלטים התקפים. זוהי בדיקת תכונה, לא וקטור יחיד.

14.4 CBOR של PactTerms (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 הקנוניים ו-body_hash של SHA3-256 מחושבים על ידי מימוש-ההפניה. מימושים חייבים להפיק CBOR זהה-בתים עבור קלט זה.

מימושים חייבים גם לאמת ש-serialize(parse(serialize(pact))) == serialize(pact) עבור כל קלטי PactTerms התקפים (בדיקת תכונה).

14.5 וקטורים חוצי-שפות של העטיפה החיצונית

לעטיפה החיצונית (§13) יש fixture קנוני נפרד ב-crates/qub-core/tests/vectors/wrapper_v1.json. כל מקרה מקבע צמד (key, nonce, qub_id, sealed_cbor) כקלטי hex אטומים ומאשר פלט expected_wrapper_hex ספציפי. שני מימושי-ההפניה צורכים את אותו קובץ JSON:

ה-fixture מקבע כרגע שלושה מקרים:

מקרה כיסוי
basic-text-public צורת SealedQub הריאליסטית הקטנה ביותר; ללא שדות אופציונליים. מבססת את צורת העטיפה הקנונית עבור qub טיפוסי-v1.0.
with-recipient-pubkey SealedQub עם recipient_pubkey מוגדר (מסלול שלב 2). קבוצת מפתחות CBOR פנימית שונה, qub_id שונה.
longer-body גוף ~4 KiB — מפעיל קידומות אורך CBOR רב-בתיות גם בתוך המעטפה הפנימית וגם בתוך הטקסט המוצפן החיצוני.

מימושים חייבים להפיק expected_wrapper_hex זהה-בתים עבור הקלטים המוקלטים. יצירה מחדש של ה-fixture מצריכה QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors ושמורה לשינויי פורמט מכוונים.


15. ממשל פרופיל קריפטו (עתיד)

סעיף זה אינפורמטיבי עבור v1 והופך לנורמטיבי בפעם הראשונה שאלגוריתם שני נכנס לכל אחת מאבני הבניין הקריפטוגרפיות של qub.

15.1 עמדה נוכחית

פרוטוקול v1 קושר בדיוק אלגוריתם אחד לכל אבן בניין:

מאמתים כיום מקודדים-קשיח אורכי מפתח וחתימה לכל אבן בניין. שום משטח זריזות אינו נחשף על ידי הפורמט הקווי.

15.2 צורה מיועדת

כאשר אלגוריתם שני נכנס לפרוטוקול, המאמת יוגדר עבור CryptoProfile בעל-שם (למשל, ExqubV1) המפרט את הקבוצה המדויקת של ערכים מותרים לכל אבן בניין — sig_algs, שרשראות drand, גרסאות עטיפה, טיפוסי תוכן. הפרופיל מקובע בזמן האימות, לעולם אינו נמשא-ונתן בתוך-הפס. כל ערך מחוץ לפרופיל הפעיל נדחה.

זה מבטיח שהוספת ML-DSA-87 או הפעלת Ed25519 אינן יכולות להחליש למפרע תצורות מאמת קיימות: מאמת v1 נשאר מאמת v1 גם לאחר שפורסם פרופיל v2.

15.3 תנאי הפעלה

קדמו את §15 לסטטוס נורמטיבי כאשר מוצע כל אחד מהבאים:

עד אז §15 הוא מציין-מקום המקבע את צורת המיגרציה כך ש-PRs עתידיים ינחתו מול יעד ידוע במקום להתדיין מחדש על משטח המשא-ומתן מאפס.


16. יומן שקיפות ושכבות עמידות (עיצוב — סקירה הושלמה)

סטטוס. סעיף זה הוא מפרט עיצוב. הפורמטים הקוויים, הגיבוב ומודל האמון שלהלן נורמטיביים עבור המימוש, אך טרם נשלח קוד יומן-שקיפות. הסקירה החיצונית של W5 הושלמה: §16.15 מתעדת את ההכרעות שיושבו ואת אילוצי ההשקה המחייבים שעלו ממנה. המימוש רשאי להתקדם תחת אילוצים אלה. §16 נשאר צופה-קדימה באותו מובן כמו §15 — הוא מקבע את היעד כך שהמימוש ינחת מול עיצוב מיושב במקום לגזור מחדש את מודל האמון בסקירת הקוד. הוא תוספתי בלבד — כל qub קיים שומר על עסקת ה-Arweave הפרטנית שלו ואין שינוי בפורמט הקווי של SealedQub / QubEnvelope.

16.1 רציונל ושכבות עמידות

כיום העמידות של qub וההתחייבות הזמנית שלו נשענות שתיהן על עסקת Arweave יחידה לכל-qub (§11). זה צומד את השהיית האטימה לגמירות של Arweave, הופך את ההעלאה לכל-qub לתקרת עלות מוצר (ARWEAVE_DAILY_CEILING), ואינו נותן סדר מגלה-שיבוש בין qubs. יומן השקיפות מוסיף שתי שכבות מתחת לאותה שכבה יחידה ומסביבה:

שכבה שם ערובה מתי
T1 אישור סינכרוני R2-תחילה רצפת עמידות — בתים חתומים נכתבים לאחסון עמיד לפני שהאטימה חוזרת (< 300 ms p95). כל qub, סינכרונית (§16.10).
T2 הכללה מאוגדת ביומן השקיפות התחייבות אוניברסלית בשיטת הוספה-בלבד, מגלת-שיבוש + סדר מלא, מעוגנת ל-Arweave. כל qub, נדחית + מאוגדת (§16.5–16.7).
T3 קביעות Arweave לכל-qub עסקת Arweave פרטנית עבור ה-qub. מכירת-תוספת בתשלום, ונסיגת אי-זמינות-Arweave (§16.8).

T2 הופך את Arweave-לכל-qub לבחירה (T3) במקום נתיב העמידות היחיד. ARWEAVE_DAILY_CEILING מוצא משימוש כתקרת מוצר ומונמך למפסק-זרם בארנק העוגן הייעודי בלבד (§16.7); אטימות משתמשים לעולם אינן נדחות בשל חריגה ממנו.

יושר עמידות (יושב — §16.15 Q6). העמידות אינה נסוגה: כתיבת ה-R2 של T1 היא סינכרונית וחד-כתיבה, כך ש-qub בשכבה החינמית שלא רכש T3 עמיד במלואו ברגע שהאטימה חוזרת. מה שמתגבש בגסות הוא זמן ההתחייבות בחסם-עליון הניתן-להוכחה: עבור qub חינמי הוא הופך לזמן בלוק העוגן ולא לזמן בלוק של עסקה לכל-qub. בנפח נמוך — מצב ההשקה-המוקדמת ושעות-השפל הריאליסטי — הקצב היומי המלא הוא הרצפה הטיפוסית, לא קצה נדיר. מסגור המוצר הוא לפיכך חסם עליון ללא השהייה מספרית מחויבת — "חתום ועמיד כעת; חותמת זמן ציבורית בלתי-תלויה מתווספת בעוגן היומן הבא (בדרך כלל יומית)" — והוכחת התחייבות בדיוק-שעה היא תכונה בתשלום של T3, נחשפת במשטח השוואת-השכבות ובתנאים (§16.11, §16.15 Q6). כל חסם זמן הוא SLO פנימי בלבד, לעולם לא SLA משווק.

16.2 מבנה LogLeaf (שתי צורות מחויבות)

רשומת יומן היא LogLeaf, מקודדת כ-CBOR קנוני בכתב-יד תחת פרופיל §3.1 (אורך-מוגדר, ללא תגיות, ללא floats, מספרים שלמים בצורה קצרה-ביותר, טקסט NFC, שדות אופציונליים מושמטים בהיעדרם, מפתחות ממוינים לפי אורך בתים מקודד עולה ואז לקסיקוגרפית). שומר הקנוניות parse → re-encode → compare של §3.1 מיושם על נתיב הקידוד לפני הגיבוב (לא רק על הפענוח), כך ששני מימושים אינם יכולים לחלוק על בתי העלה דרך הבדל ברוחב-שלם או בסדר-מפתחות. כל המספרים השלמים הם u8 / u64 / i64; כל התקצירים הם מחרוזות בתים בנות 32 בתים (bstr[32]). מזהה עסקת Arweave מאוחסן הוא תקציר SHA-256 גולמי בן 32 בתים הנישא כ-bstr[32], לעולם לא מחרוזת טקסט base64url (תואם §3.3).

לעלה יש שתי צורות הנבחרות לפי בית kind, מכיוון שבנתיב ההעלאה ברירת-המחדל ה-Worker עיוור-בתים: POST /api/v1/upload מקבל רק qub_id ו-unlock_at כטענות לקוח לא-מהימנותbody_hash, drand_round, created_at ו-drand_chain_version כולם חתומים בתוך העטיפה החיצונית של §13, שאת מפתחה ה-Worker לעולם אינו מחזיק. רק נתיב האטימה-בשרת (POST /api/v1/seal) גוזר body_hash / drand_round מטקסט גלוי. צורת עלה יחידה הנושאת body_hash + drand_round הייתה לפיכך מתחייבת לערכים שהמפעיל מעולם לא אימת עבור רוב ה-qubs האמיתיים. הפיצול שומר על יושר כל ערך מחויב:

מפתח אורך מקודד טיפוס נוכחות משמעות
seq 4 u64 חובה אינדקס עלה גלובלי מבוסס-0; המיקום שאליו הוכחת ההכללה מתחייבת.
kind 5 u8 חובה 0x01 מאושש (אטימה-בשרת) או 0x02 נטען (אטימה-בלקוח / העלאה עיוורת-בתים).
ref 4 bstr[32] חובה מזהה הפניה של העלה. מאושש → qub_id גולמי. נטען → המזהה המוסווה SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1).
chash 6 bstr[32] חובה כתובת תוכן SHA3-256(stored_bytes) — קשירת התוכן היחידה שה-Worker תמיד יכול לחשב ביושר, בשני הנתיבים.
unlock_at 10 i64 חובה מועתק (מאושש) או נטען (נטען); מאומת > 0 לפני שהוא נכנס לעלה.
received_at 12 i64 חובה שעון-הקיר של ה-Worker באישור-R2. לא-ראייתי (נטען-בידי-המפעיל; §16.6). נוכח לתיאור-עצמי, לעולם לא הוכחה. מאומת > 0.
body_hash 10 bstr[32] kind=0x01 בלבד מושמט ב-0x02 — ה-Worker חסר אותו תחת §13.
drand_round 12 u64 kind=0x01 בלבד מושמט ב-0x02.

עלה kind=0x02 מתחייב במכוון לא ל-body_hash ולא ל-drand_round: הוא מאשש את ההתחייבות והסדר של טקסט מוצפן אטום בכתובת-התוכן chash, הטוען qub_id ו-unlock_at — לא את הטקסט הגלוי או הסבב שלו. רגלי הטקסט-הגלוי/הסבב עבור qub נטען מגיעות מאימות חבילת ה-.qub הקיים של §11, לא מהיומן (§16.11). drand_chain_version אינו בעלה (הוא בתוך העטיפה בנתיב ברירת-המחדל); רזולוציית השרשרת חיה על העוגן (§16.7). משמעת מקדד: דחה ref או chash שכולם אפסים, ודחה unlock_at / received_at לא-חיוביים, במקביל לשומר הסנטינל outcome_at > 0 ב-cbor.rs.

16.2.1 הסוואת qub פרטי

היומן אסור שיהפוך לאורקל המנייה שהעטיפה החיצונית של §13 קיימת כדי למנוע (§13.1). עבור qub פרטי (עטוף) העלה הנטען מתחייב למזהה המוסווה SHA3-256(qub_id ‖ log_blind_secret), כאשר log_blind_secret הוא סוד מוחזק-בשרת, ומשמיט body_hash. צד שלישי אינו יכול לקשור עלה כזה ל-qub_id ספציפי; מחזיק ה-qub, שבידיו כתובת המסירה ולכן qub_id, יכול לחשב מחדש את ההסוואה כדי לאשר את הכללתו שלו. qub ציבורי (כבר ניתן-למנייה, כבר נושא את תג ה-Arweave Visibility: public לפי §13.8) מתחייב ל-qub_id גולמי. זהו המקום היחיד שבו ניתנות-האימות העצמאית נכנעת במכוון לאינווריאנט פרטיות נושא-משקל; הקשירה העצמאית עבור qubs פרטיים היא chash (§16.9).

משמורת log_blind_secret (יושב — §16.15 Q4). ההסוואה מגינה על אי-קישוריות עלים, לא על חסיון טקסט-גלוי (העטיפה של §13 מחזיקה בכך באופן בלתי-תלוי). בפריצה ל-log_blind_secret, עבור כל qub_id שהיריב כבר מחזיק או יכול לשחזר (כל qub שחבילתו/כתובתו בידיו, וכן כל qub_id בעל-אנטרופיה-נמוכה או ציבורי) הוא מחשב מחדש את ref העלה בגיבוב אחד וקושר אותו — זוהי קישוריות ישירה של אוכלוסייה ידועה, לא חיפוש-כוח-גס מעל מרחב לא-ידוע. סווג את log_blind_secret כסוד בדרגת-קורלציה/Sybil באותה שכבת-משמורת כשאר סודות השרת, וסובב קדימה בלבד (סיבוב מסווה-מחדש עלים עתידיים; הוא אינו יכול לבטל קישור למפרע של עלים שכבר עוגנו).

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 (כל פיצול פנימי בחזקת-השתיים הגדולה ביותר הקטנה ממש ממספר עלי תת-העץ), מה שמאפשר להוכחות הכללה ועקביות לחלוק אלגוריתם נתיב-ביקורת אחד. מפרט ההפניה נושא פסאודו-קוד מפורש לגזירת שמאל/ימין ומקבע וקטור בדיקה שאינו חזקת-שתיים (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")

הסמכות בשיטת הוספה-בלבד המתפרסמת היא שורש ה-Merkle המצטבר + העוגן שלו (§16.5–16.6), לעולם לא הסדר הגולמי שבו המפעיל במקרה מגיש עלים: השרשרת מחושבת מחדש עבור כל סדר שמוגש, כך שרק השורש המעוגן מקבע מיקום קנוני.

16.5 עץ Merkle מצטבר ואיגוד

יש עץ RFC 6962 אחד הולך-וגדל מעל כל העלים בסדר seq — לא עצים מבודדים לכל-אגד. (בנייה משורשרת-עלה-נשא לכל-אגד נדחתה: היא אינה יחס-קידומת אמיתי, כך ש"הוכחות העקביות" שלה לא-נכונות.) העץ המצטבר נותן הוכחות עקביות אמיתיות של RFC 9162 ומאפשר לעוגן יחיד עדכני להוכיח הכללה עבור כל qub ישן יותר.

האובייקט הדורבל LogDO הוא הכותב היחיד (blockConcurrencyWhile, במקביל ל-QuotaDO / EntitlementDO) — הוספה ליומן משותף היא קריאה-שינוי-כתיבה על מצב משותף ולכן חייבת לעבור דרך DO, לעולם לא KV. הוא שומר במטמון את ספר הקצה-הימני של העץ (O(log n) גיבובים) כך שסגירת אגד היא O(batch). אגד הוא קבוצת העלים המעוגנים יחד; ההדקים שלו ניתנים-לתצורה, לא מוקפאים-בפרוטוקול: קידום tree_size של לפחות LOG_BATCH_MAX_LEAVES (ברירת מחדל 4096), או גיל המגיע לקצב העיגון, או שטיפה כפויה כשאטימת T3 בתשלום נוחתת. root_i הוא גיבוב עץ ה-Merkle המצטבר מעל העלים 0 .. tree_size_i.

16.6 ראש עץ חתום באמצעות עוגן Arweave

עסקת העוגן של Arweave היא ראש העץ החתום ומחליפה חתימת מפעיל עבור ראש העץ עצמו: העוגן היומי אינו זקוק למפתח qub מכיוון שה-owner של עסקת ה-Arweave הוא החתימה. תזת החפיר נשמרת — המצע הבלתי-משתנה, לא סוד מוחזק-qub, נושא-משקל עבור השורש המעוגן.

יש בדיוק מפתח חתימת qub חם אחד בעיצוב, והוא מקובע: מפתח הקבלה לכל-אטימה (§16.10). מפתחו הציבורי מחויב ב-LogProfile (מופץ עם המאמת) וגם חתום-צולב בידי anchor_owner, כך שמאמת מתקף קבלה מול אותו שורש מקובע כמו העוגן. זוהי הכרעת §16.15 Q2 — מפתח קבלה לא-מקובע, ניתן-לסיבוב-בידי-המפעיל היה ניתן-להכחשה (המפעיל יכול היה להכחיש שהמפתח שלו), מה שהיה מבטל את ערך האחריותיות של הקבלה מול היריב ברמת-המפעיל שלשמו הקבלה קיימת להרתיע. לכן: qub אינו מחזיק מפתח חתימת-יומן לא-מקובע; מפתח הקבלה מקובע וחתום-צולב בידי anchor_owner.

ה-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 — לצד קבועי ה-quicknet שכבר ב-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 חתום הנשלח בעדכון מאמת. סיבוב מתוכנן נושא חתימה-צולבת יוצאת → נכנסת; סיבוב מונע-פריצה אינו יכול (המפתח היוצא לא-מהימן/לא-זמין בדיוק אז) ונסוג לקידום המנוהל-בידי-§15, עם בדיקת פיצול-העוגן-הקודם (להלן) החוסמת נזק בינתיים.

חלון ההכחשה-הכפולה (פרמטר אמון מהמעלה הראשונה). עלה עמיד-להכחשה-כפולה רק לאחר שהעוגן המכסה אותו אושר ב-Arweave. החלון הוא received_at → אישור העוגן (≤ קצב + גמירות Arweave). בתוכו הערובות היחידות הן קבלת האטימה המקובעת (§16.10) והשלמות התפעולית של qub. שלושה ארטיפקטים של אחריותיות הופכים זאת ליושר ולא לנפנוף-ידיים (מודל העֵד הוא הכרעת §16.15 Q2):

  1. קבלת אטימה חתומה מקובעת — האנלוג ל-SCT המוחזר בתגובת ההעלאה (§16.10), חתום בידי מפתח הקבלה המקובע, החתום-צולב בידי anchor_owner. עלה שנשמט לפני עוגנו מותיר לקורבן קבלה בלתי-ניתנת-להכחשה לפרסם, סוגר את חור ההשמטה-השקטה.
  2. מתודולוגיית ניטור מפורסמת + הליכת שרשרת-prev — שרשרת ה-prev של העוגן עוברת מהראש→הבראשית; פיצול (שני עוגנים ב-size אחד עם root שונה, או prev שבור) הוא הוכחה ניתנת-לפרסום של התנהגות-שגויה. גילוי הכחשה-כפולה הוא מחויבות תפעולית מוצהרת, לא הנחה שקטה.
  3. ראשים כפולים מפורסמים-עצמית — כל ראש חדש {sth_hash, tree_size} מתפרסם למאגר GitHub ציבורי בשיטת הוספה-בלבד בבעלות-qub ייעודי (רגל הפרסום-העצמי המגלה-שיבוש נושאת-המשקל), עם פרסום חברתי כאישוש מאמץ-מיטבי בלבד. פרסום שנכשל חייב להזניק זימון (לא להיכשל בשקט).

חסם יושר (אילוץ מחייב). מכיוון ש-qub שולט בשני משטחי הפרסום, זהו פרסום-עצמי, לא עדות בלתי-תלויה. שום משטח מוצר, שיווק או משפט לא רשאי לטעון שהיומן "נצפה בלתי-תלוי"; הטענה המותרת היא שהכחשה-כפולה ניתנת-לגילוי ומותירה קבלה בלתי-ניתנת-להכחשה. עד צד-שלישי בלתי-תלוי אמיתי נדחה לקידום ממשל עתידי של §15.

received_at נטען-בידי-המפעיל ושום טענה לא רשאית להישען עליו — הוא לעולם אינו מוצג כהוכחה או כאישוש מחלוקת על שום משטח מוצר / משפט / API / רינדור-הוכחה. זמן בלוק העוגן של Arweave T הוא חותמת הזמן חסרת-האמון היחידה (חסם עליון על "נרשם עד"). כל בדיקת שפיות של ניטור על received_at חייבת להשוות מול T, לא מול שדה ה-STH anchored_at הנשלט-בידי-המפעיל; בדיקה כזו היא שומר מול באג שעון של מפעיל ישר בלבד, לא בקרת אחריותיות מול מפעיל זדוני (§16.15 Q5).

16.7 פורמט עסקת העוגן וקצב

ה-AnchorBundle הוא גוף עסקת ה-Arweave ב-CBOR-קנוני, נכתב דרך האוגד של §16.8: ver:u8, sth:bstr (בתי SignedTreeHead קנוניים), prev_anchor:bstr (מזהה עסקת העוגן הקודם בבתים גולמיים; מושמט בבראשית), chain_hash:tstr (שרשרת ה-drand בתוקף — quicknet), וכן זרם ה-CBOR-של-העלים של האגד בסדר seq כך שהעוגן בפני-עצמו: ניטור גוזר מחדש את root מהגוף עם אפס תלות ב-qub. (אם זרם העלים נעשה גדול בנפח גבוה, רוויזיה עתידית רשאית להתחייב רק לטווח עלים בהפניה; צוין, לא אומץ ב-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 הוא הסמכות היחידה.

קצב: יומי כברירת מחדל, נבחן-מחדש עם הנפח (הדק הגודל מקצר אוטומטית את הקצב האפקטיבי תחת עומס); אטימת T3 בתשלום כופה עוגן כך שלקוחות משלמים לעולם אינם ממתינים יום. ארנק העוגן ייעודי ונמוך-מהירות, נפרד מארנק ההעלאה — הוא חייב להיות JWK משלו (מפתח נפרד, לא תפקיד לוגי על ארנק ההעלאה) כך שפריצה לארנק-ההעלאה אינה יכולה לזייף עוגנים — עם תקציב עסקאות-עוגן יומי קשיח (ה-ARWEAVE_DAILY_CEILING המונמך). עמדת המשמורת מוצהרת בפשטות: מפתח חם צר-היקף עם מפסק-זרם הדוק ויתרה נמוכה, לא "קר" — ארנק החותם אוטומטית מדי יום אינו יכול להיות קר, והמפרט אינו מעמיד פנים אחרת.

16.8 אוגד ANS-104

מקדד DataItem ביתי של ANS-104 וחותם גיבוב-עומק, בערך 300 שורות, Web Crypto בלבד, אפס תלויות npm (שני ה-SDK של Turbo נכשלים בשער שרשרת-האספקה npm ci --ignore-scripts). פריסת בתי DataItem:

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 של Arweave — תקציר SHA-384 רקורסיבי (דרישת הקו של Arweave, crypto.subtle.digest("SHA-384")) מעל ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — ואז RSA-PSS מעל גיבוב-העומק עם ה-JWK של הארנק דרך crypto.subtle; id = base64url(SHA-256(signature)). ה-SHA-384 כאן מבודד כפרימיטיב-של-קו-Arweave-בלבד, לעולם לא פרימיטיב אמון של qub (§15 מתעד את הגדר; גיבוב האמון של qub הוא SHA3-256 לכל אורכו).

נתיב קוד אחד משרת שלושה צרכנים: קביעות T3 בתשלום לכל-qub, נסיגת אי-זמינות-Arweave (העמד את ה-DataItem בתור, החזר את אישור ה-R2-תחילה ללא תלות — זה סוגר את מבוי ה-ARWEAVE_UNAVAILABLE 503 הסתום הנוכחי), וכתיבת ה-AnchorBundle. סכמת חתימה (יושב — §16.15 Q8): v1 חותם עם RSA-PSS (טיפוס חתימה 1) תוך שימוש חוזר במנגנון ה-JWK של ארנק ה-Arweave הקיים (אפס משמורת מפתח חדש ארוך-חיים, משרת את תזת "סוד אחד פחות"); Ed25519 נדחה לנתיב מיגרציית-ה-PQ של §15.

גיבוב-העומק המעובד-ביד הוא הקוד בעל הסיכון-הגבוה-ביותר והכיסוי-הטבעי-הנמוך-ביותר ב-W5, כך שהשעירות שלו בלתי-ניתנת-למשא-ומתן (§16.15 Q8):

  1. ה-fixture חוצה-השפות tlog_v1.json (Rust + TS, תבנית ה-wrapper_v1.json של §14.5) מכסה גיבוב-עומק, בתי DataItem + id, גיבובי עלים, שורש בן 5 עלים + נתיב ביקורת, גיבוב STH, הוכחת הכללה והוכחת עקביות — בשני הכיוונים: החתימה והאימות (כיוון האימות חשוב מכיוון שבדיקת עסקה מקומית → tx_id של §16.6 מושכת את גיבוב-העומק לתוך כל מאמת עצמאי, לא רק הכותב).
  2. הלוך-ושוב חד-פעמי של אינטר-אופרביליות דרך אוגד ANS-104 ייחוס, נצרך כנתוני בדיקה סטטיים בלבד — לעולם לא תלות ריצה של npm (עמדת ה-Web-Crypto-בלבד / ללא-סקריפטי-התקנה נשמרת).
  3. נתיב גיבוב-העומק + RSA-PSS חייב לעשות הלוך-ושוב דרך אותם פרימיטיבים של crypto.subtle שהייצור משתמש בהם, כך שהמקדד הביתי תואם-בתים.
  4. ניטור קבלה שלאחר-איגוד מתמשך מאשר שכל DataItem של עוגן / נסיגה אכן משיג קבלה ב-Arweave, עם התראה + מפסק-זרם — מכיוון שגיבוב-העומק משרת גם את תור נסיגת אי-זמינות-Arweave, כך שנסיגה שקטה הייתה ממלאת את התור הזה בפריטים דחויי-רשת בדיוק במהלך ההשבתה שלכיסויה הוא קיים.

16.9 הוכחות הכללה ועקביות

שתיהן RFC 9162, SHA3-256, מוגשות כ-CBOR קנוני.

InclusionProofGET /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 }.

ConsistencyProofGET /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. רשימת מפתחות חד-משמעית יחידה, מקובעת בווקטור בדיקה.

אימות עצמאי (ללא שרת qub, מרחיב §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 הוא מאגר צמתי-Merkle עמיד הממופתח לפי קואורדינטת עץ מוחלטת (level, index) — לא הפרשי צומת לכל-batch. עם מאגר ממופתח-קואורדינטה, כל נתיב ביקורת (leaf i, size N) הוא קבוצת O(log N) GET ישירים של R2 ללא חישוב-מחדש מעבר לגבולות אגד; עם מאגר ממופתח-אגד הוא אינו, וזהו פער פריסת-האחסון שהכרעה זו סוגרת. גופי העלים ניתנים-למיעון-תוכן באופן דומה לפי seq. וקטור בדיקה של W5 חייב להוכיח עלה-קר מעידן-הבראשית מול שורש מאוחר-בהרבה תוך שימוש ב-R2 + Arweave בלבד כשאחסון ה-LogDO נמחק, כך שטענת בטיחות-הרקלמציה ב-§16.13 מגובה ולא נטענת. ה-O(log N) GET סדרתיים של R2 שייכים רק לנקודת-הקצה האסינכרונית של ההוכחה — לעולם לא לנתיב האטימה החם (§16.10) או ל-cron לכל-טיק.

16.10 סדר אישור R2-תחילה

רצף ה-POST /api/v1/upload הופך ל:

  1. שערי החצי-הקדמי (אימות, ולידציה, מפתח-שבר של אי-כפילות) — ללא שינוי.
  2. סינכרונית await QUB_CACHE.put(qub-cache/<tx_id>, wrappedBytes) — רצפת העמידות; סוגרת גם את מרוץ הקדם-מטמון של W1 (לשעבר ctx.waitUntil לאחר ההגשה ל-Arweave).
  3. סינכרונית await LogDO.append(leaf) — RPC אחד של DO באותו colo; הכותב היחיד מקצה seq, מרחיב את שרשרת הרשומות, ומעדכן את הקצה. (RMW על מצב משותף → DO, לעולם לא KV.) ה-RPC של append עושה רק זאת; עבודת סגירת-אגד ה-Merkle בת O(batch) רצה מחוץ ל-RPC הזה על האזעקה של ה-LogDO, אחרת ה-p95 של ההוספה מזנק בכל אטימה ה-LOG_BATCH_MAX_LEAVES-ית.
  4. החזר את האישור כעת — עם קבלת האטימה (חתומה בידי מפתח הקבלה המקובע, §16.6) ו-{ tx_id, log_seq, anchor_status: "pending" }. פריסת ה-Arweave רבת-השניות מוסרת מהנתיב הקריטי.
  5. אחד ctx.waitUntil מעמיד-בתור את העבודה הנדחית: ההגשה לכל-qub ל-Arweave (כעת מאמץ-מיטבי / בתשלום; בכשל היא מנותבת לתור נסיגת האוגד במקום 503 למשתמש) בתוספת כתיבות המטא-הזמני הקיימות. סגירת האגד והעיגון רצות בנפרד מהאזעקה של ה-LogDO ומה-cron היומי של העוגן. אין ctx.waitUntil בתוך לולאה; מפתח השבר הקיים של אי-הכפילות נשמר.

תקציב השהייה (יושב — §16.15 Q7). היעד < 300 ms p95 הוא שער השקה נמדד, לא הנחה. הנתיב הקריטי היושר הוא קריאות ה-KV של החצי-הקדמי + PUT אחד של R2 + שני אובייקטים דורבל מסודרים-סדרתית — חיוב מכסת-האטימה הקיים של QuotaDO וגם ההוספה החדשה של LogDO — כך שהתקציב חייב להביא בחשבון שני הלוך-ושוב של DO באותו colo, לא אחד. שלח אזעקת השהייה של LogDO במקביל לזו של QuotaDO והתייחס לנסיגת p95 כחוסם שחרור.

16.11 מודל אמון — הטענה המדויקת, מוגדרת-היקף לפי סוג העלה

עבור kind=0x01 (מאושש): "תוכן זה — גוף התואם את body_hash, מזוהה על ידי qub_id — הותחייב ליומן בשיטת הוספה-בלבד של qub במיקום seq והתקיים לא יאוחר מזמן בלוק Arweave T; הוא היה בלתי-קריא קריפטוגרפית עד סבב drand R = unlock_round(unlock_at)." זוהי השלישייה המלאה {קשירת סבב tlock + הכללת Merkle + שורש מעוגן}.

עבור kind=0x02 (נטען, ברירת המחדל): "טקסט מוצפן אטום עם כתובת-תוכן chash, הטוען qub_id ו-unlock_at, הותחייב ליומן בשיטת הוספה-בלבד במיקום seq והתקיים לא יאוחר מזמן בלוק Arweave T." רגלי הסבב והגוף מסופקות על ידי אימות חבילת ה-.qub הקיים של §11 (qub_core::unlock), לא על ידי היומן; מה שהיומן מוסיף מעל עסקת לכל-qub חשופה הוא סדר מגלה-שיבוש, זמן התחייבות בחסם-עליון חסר-אמון, ועמידות להכחשה-כפולה.

שתי הטענות אינן כוללות, לפי §11: מחברוּת ללא sig_alg ≥ 0x01, כוונה, ותזמון בגרנולריות תת-עוגן. אף אחת אינה מאפשרת לטענה כלשהי להישען על received_at.

תקרת טענה (אילוץ השקה מחייב — יושב §16.15 Q1). עבור qubs חינמיים / ברירת-מחדל (kind=0x02), הטענה ה-kind=0x02 המוגדרת-היקף לעיל היא התקרה על מה שכל משטח מוצר, שיווק, תנאים או רינדור-הוכחה רשאי לטעון. שום משטח לא רשאי לקבוע או לרמוז שהיומן מוכיח את התוכן או את סבב השחרור של qub ברירת-מחדל — היומן מוכיח סדר + זמן התחייבות בחסם-עליון חסר-אמון של טקסט מוצפן אטום. הוכחת תוכן וסבב מגיעה אך ורק מאימות חבילת ה-.qub הקיים של §11, שאינו תלוי-יומן. זהו חוסם השקה קשיח על העתק, לא העדפה סגנונית; זוהי ההכרעה השומרת על יושר נתיב ברירת-המחדל העיוור-בתים.

16.12 ניהול גרסאות ותיאום W3

אין קפיצת קו של SealedQub ולכן אין קפיצת גרסת-פרוטוקול (§12.1): היומן הוא רכיב-צד המתחייב לשדות ובתים קיימים, כך שהוא אינו נכנס להיסטוריית גרסת-הפרוטוקול של §12.2. ה-drand_chain_version האופציונלי של W3 אינו נוגע ונשאר השדה האופציונלי היחיד של SealedQub. במקום זאת היומן מציג מרחבי גרסה בלתי-תלויים משלו — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — במקביל לאי-תלות גרסת-העטיפה של §12.4 (העטיפה נושאת בית גרסה בלתי-תלוי בגרסת הפרוטוקול, וגרסאות היומן עוקבות אחר אותו הפרדה).

מסירת ההוכחה נמשכת כברירת מחדל, עם נסיעה-נלווית אופציונלית. הוכחה אינה יכולה להתקיים בזמן האטימה (העוגן טרם נכתב), כך שחבילת ה-.qub של זמן-האטימה נשארת חסרת-הוכחה. המאמת של W7 מושך GET …/proof פעם אחת, או במצב לא-מקוון-מלא משחזר את ההוכחה מה-AnchorBundle הציבורי דרך שאילתת Arweave על Log-Id. חבילת ה-.qub (W7) שומרת שדה inclusion_proof אופציונלי — נעדר באטימה, מאוכלס בייצוא-מחדש שלאחר-עיגון לארכוב-קר — בעקבות אותה תבנית "אופציונלי, מושמט כברירת מחדל, תוספתי" של ה-drand_chain_version של W3.

16.13 שימור

חלונות השימור עבור הזנב הפתוח של ה-LogDO, מצע הגשת-ההוכחות של R2, מוני מפסק-הזרם של העוגן, ותור נסיגת האוגד מצוינים ב-docs/DATA-RETENTION.md. עיקרון: האחסון החם לכל-רשומה של היומן (LogDO) ניתן-לרקלמציה לאחר עיגון; חומר הביקורת שלו — מאגר צמתי-ה-Merkle הממופתח-קואורדינטה (level, index) + גופי העלים ממופתחי-ה-seq (§16.9) + עוגני ה-Arweave — קבוע. רקלמציה של עלה-קר מה-DO לעולם אינה מבטלת הוכחה שהוצאה, מכיוון שהוכחה מתקפת מול אותו מאגר צמתי R2 קבוע ועוגן ה-Arweave, לא ה-DO (ווקטור הבדיקה של ה-DO-המחוק של §16.9 מוכיח זאת).

16.14 וקטורי בדיקה

W5 שולח את ה-fixture חוצה-השפות tlog_v1.json (§16.8) בתוספת וקטורים מעובדים: עלה kind=0x01 ועלה kind=0x02leaf_hash; השורש המצטבר בן 5 העלים; הוכחת הכללה אחת; הוכחת עקביות אחת; AnchorBundle אחד; ומזהה DataItem אחד. אלה חיים לצד וקטורי העטיפה-החיצונית של §14.5 ומופעלים על ידי שני המימושים: Rust (qub-core) ו-TypeScript (Worker).

16.15 הכרעות סקירה (W5 — יושבו)

הסקירה החיצונית של W5 (מעבר עיצוב יריב + אישור הבעלים) הושלמה. כל הכרעה שלהלן מיושבת ומשתקפת בטקסט של §16 לעיל; אילוצי ההשקה המחייבים מצוינים-מחדש בסוף. המימוש רשאי להתקדם תחתיהם.

  1. יושר עלה של נתיב ברירת-המחדל (kind=0x02) — יושב. שלח את פיצול שני-סוגי-העלה כמצוין: kind=0x02 מתחייב לא ל-body_hash ולא ל-drand_round. אין שדה *_body_hash בנתיב העיוור-בתים (הוא היה האות ה"מאומת" הכוזב הקריא-ביותר עבור משלבים והוא נוחות ש-§11 כבר מספק מהחבילה). אל תדרוש אטימה-בשרת עבור qubs מאושרי-יומן (זה היה כופה טקסט גלוי דרך ה-Worker והורס את חפיר הגריסה-הקריפטוגרפית). כל קיצור-דרך מתאר-עצמי שייך לחבילת ה-.qub / מעטפת ההוכחה כשדה מחושב-מחדש-בידי-המאמת, לעולם לא שדה עלה. תקרת טענה מאושרת-בעלים: §16.11.
  2. אחריותיות הכחשה-כפולה / השמטה — יושב. מפתח קבלת-האטימה מקובע ב-LogProfile + חתום-צולב בידי anchor_owner (סוגר את הסתירה הקודמת של "אין מפתח חתימה"; §16.6). מודל העֵד בהשקה: קבלה מקובעת + מתודולוגיית ניטור + הליכת שרשרת-prev + ראשים כפולים מפורסמים-עצמית (מאגר GitHub ציבורי בבעלות-qub, חברתי מאמץ-מיטבי), משווק כניתן-לגילוי + מקובל-קבלה, לעולם לא נצפה בלתי-תלוי. עד צד-שלישי אמיתי נדחה לקידום ממשל של §15.
  3. שורש אמון מקובע של anchor-owner + סיבוב — יושב. אמץ את הקיבוע של LogProfile (§16.6); המאמת בודק anchor_tx.owner == anchor_owner ומאמת את קשירת נתוני העסקה → tx_id באופן מקומי. ממשל הסיבוב הוא הרחבה של §15 לבנייה (הדק §15.3 נוסף), לא שימוש-חוזר; סיבובים מתוכננים חותמים-צולב, סיבובים מונעי-פריצה נסוגים לקידום של §15 עם בדיקת הפיצול החוסמת נזק.
  4. הסוואת עלה של qub פרטי — יושב. שמור הסוואה עבור qubs פרטיים (ref = SHA3-256(qub_id ‖ log_blind_secret)), qub_id גולמי עבור qubs ציבוריים (כבר §16.2.1), chash כקשירה העצמאית. log_blind_secret הוא סוד בדרגת-קורלציה/Sybil, סובב-קדימה בלבד (§16.2.1).
  5. received_at — יושב. שמור אותו בעלה, מחויב אך לא-ראייתי במפורש; לעולם לא מוצג כהוכחה או אישוש מחלוקת על שום משטח. כל בדיקת שפיות של ניטור משווה מול זמן בלוק Arweave T, לא מול ה-anchored_at הנשלט-בידי-המפעיל (§16.6).
  6. תזמון ניתן-להוכחה בשכבה החינמית — יושב (אישור בעלים). העמידות אינה נסוגה; רק זמן ההתחייבות בחסם-עליון הניתן-להוכחה מתגבש בגסות לזמן בלוק העוגן. העתק השכבה-החינמית אינו משתמש ב-SLA מספרי ("…מתווסף בעוגן היומן הבא, בדרך כלל יומית"); הוכחת דיוק-שעה היא תכונה בתשלום של T3, נחשפת במשטח השוואת-השכבות + בתנאים (§16.1).
  7. עץ מצטבר על Workers — יושב. עץ RFC 9162 מצטבר יחיד + LogDO כותב-יחיד עם קצה-מטמון (מרווח-נוחות נוח מול תקרת ה-DO של ~1k כתיבות/שנייה; דחה שיברור Merkle-של-שורשי-שברים עד הקרבה אליה). תנאי-מקדים חוסם: מאגר צמתי R2 ממופתח-קואורדינטה (level, index) + וקטור בדיקת העלה-הקר של ה-DO-המחוק (§16.9); < 300 ms הוא שער השקה נמדד מעל שני DOs מסודרים-סדרתית (§16.10).
  8. סכמת חתימת ANS-104 + גיבוב-עומק — יושב. RSA-PSS (טיפוס חתימה 1, תוך שימוש חוזר ב-JWK של ארנק-העוגן הייעודי); Ed25519 נדחה לנתיב ה-PQ של §15. גיבוב-העומק SHA-384 המעובד-ביד משוער על ה-fixture חוצה-המימושים דו-הכיווני, בדיקת אינטר-אופרביליות של אוגד-ייחוס סטטי-בלבד, הלוך-ושוב של crypto.subtle המשותף, וניטור קבלת-Arweave שלאחר-איגוד (§16.8).

אילוצי השקה מחייבים (העבר אל המימוש + סקירת מוצר/משפט):