Skip to main content

RFC 9104: FNP - Fork Node Protocol (Core Specification)

1. OVERVIEW

FNP (Fork Node Protocol) combines LSEQ CRDT position encoding with ChaCha20-Poly1305 encryption for conflict-free collaborative document editing with end-to-end encryption.

2. PROTOCOL MODEL

2.1 LSEQ Position System

Each character position uses fractional indexing:
Position = [site_id, clock, site_id, clock, ...]

Example:
Initial: "hello"
  h: [1, 1]
  e: [1, 2]
  l: [1, 3]
  l: [1, 4]
  o: [1, 5]

User A inserts "X" between 'h' and 'e':
  h: [1, 1]
  X: [1, 1.5] = [1, 1, 1, 1]  (fractional between 1,1 and 1,2)
  e: [1, 2]

User B (concurrent) inserts "Y" between 'h' and 'e':
  h: [1, 1]
  Y: [1, 1, 2, 1]  (fractional between 1,1 and 1,2, different site)
  e: [1, 2]

Merge result: "h" < X < Y < "e" by deterministic position comparison

2.2 Position Comparison

Positions are compared lexicographically:
  [1, 1] < [1, 1, 1, 1] < [1, 1, 2, 1] < [1, 2]

Algorithm:
  Compare(pos_a, pos_b):
    For i = 0 to min(len(a), len(b)):
      if a[i] != b[i]:
        return a[i] < b[i]
    Return len(a) < len(b)

3. ENCRYPTION LAYER

3.1 ChaCha20-Poly1305 (AEAD)

Each operation encrypted separately:
message EncryptedOperation {
  bytes nonce = 1;              // 12-byte random nonce
  bytes ciphertext = 2;         // Encrypted operation
  bytes tag = 3;                // 16-byte authentication tag
  bytes metadata = 4;           // Plaintext metadata
}
Key Derivation:
root_key = 32-byte key (from key exchange)

For operation[i]:
  nonce[i] = random_12_bytes()
  plaintext = serialize_cbor(operation)

  (ciphertext, tag) = chacha20_poly1305_encrypt(
    key=root_key,
    nonce=nonce,
    plaintext=plaintext,
    aad=blake3(metadata)
  )

3.2 Optional Order-Preserving Encryption (OPE)

For searchable encryption:
OPE Encrypt(plaintext, key) -> ciphertext_preserves_order

Example:
  OPE(5, key) < OPE(10, key) < OPE(15, key)

Used for: Searching without decryption

4. DETERMINISTIC MERGE ALGORITHM

4.1 Three-Way Merge

Three-Way-Merge(base, local, remote):
  1. Extract operations from each:
     local_ops = operations(base, local)
     remote_ops = operations(base, remote)

  2. Aggregate by position:
     Insert operations: pos -> op
     Delete operations: pos -> "DELETE"

  3. Resolve conflicts:
     Same position, different operations?
       Use ConflictResolution strategy

  4. Topological sort:
     Sort all operations by LSEQ position

  5. Build result:
     result = base
     Apply sorted operations deterministically

  6. Verify commutativity:
     Assert Apply(op_a, Apply(op_b, doc)) ==
            Apply(op_b, Apply(op_a, doc))

4.2 ConflictResolution Strategy

enum ConflictResolution {
  LamportClock,      // Timestamp-based (for VEST integration)
  SiteID,            // Lexicographic site ordering
  LastWriteWins,     // Last timestamp wins
  VectorClock,       // Causal ordering (for WEAVE)
}

5. OPERATION TYPES

5.1 InsertOperation

message InsertOperation {
  bytes position = 1;           // LSEQ position (variable length)
  bytes value = 2;              // Character or object
  bytes site_id = 3;            // Peer identifier
  uint64 lamport_clock = 4;     // Ordering
}

5.2 DeleteOperation

message DeleteOperation {
  bytes position = 1;           // LSEQ position to delete
  bytes site_id = 2;
  uint64 lamport_clock = 3;
}

5.3 MergeOperation

message MergeOperation {
  bytes state_hash = 1;         // Blake3(serialized state)
  bytes remote_state_hash = 2;  // Peer's state hash
  repeated InsertOperation inserts = 3;
  repeated DeleteOperation deletes = 4;
}

6. STATE MANAGEMENT

6.1 Document State

ForkNodeState {
  operations: BTreeMap<LSEQ Position, Operation>,
  lamport_clock: u64,
  vector_clocks: Map<SiteID, u64>,
  state_hash: Blake3Hash,
  encryption_key: [u8; 32],
  metadata: {
    created_at: Roughtime,
    last_updated: Roughtime,
    author: SiteID,
  }
}

6.2 Undo/Redo Stack

UndoStack: Vec<MergeOperation>
RedoStack: Vec<MergeOperation>

Undo: Pop from UndoStack, push inverse to RedoStack, rebuild state
Redo: Pop from RedoStack, push to UndoStack, rebuild state

7. DETERMINISM GUARANTEES

7.1 Operation Ordering

Invariant: Same operations applied in any order produce identical result Proof:
  • Operations on different positions commute (independent)
  • Operations on same position ordered by LSEQ comparison
  • LSEQ comparison is total order (reflexive, transitive, asymmetric)
  • Therefore: operations form partial order with unique topological sort

7.2 State Hash Determinism

StateHash = Blake3(
  serialize_canonical_cbor(
    sorted_operations_by_lseq_position
  )
)

Property: hash(state) == hash(state') iff state == state'

8. CONFLICT DETECTION

8.1 Concurrent Edits

Timeline: A ────────────> B

          └──C ──────> D

A and D concurrent? Check if C in path(A, D)
If no: operations are concurrent, conflict possible

Conflict = Edit(A, D) differs from Edit(C, B) at same position

8.2 Merge Conflict Example

Base: "hello"

User A: "hXello" (insert X at pos 1.5)
User B: "hYello" (insert Y at pos 1.5)

LSEQ Resolution:
  A's nonce < B's nonce → "hXYello"
  Deterministic, no user intervention needed

9. INTEROPERABILITY MATRIX

LanguageLSEQChaCha20-Poly1305MergeStatus
RustRef impl
Go⚠️⚠️Planned
Python⚠️⚠️Planned
JavaScript⚠️⚠️Planned

10. CONFORMANCE REQUIREMENTS

All FNP implementations MUST:
  1. ✅ Support LSEQ position encoding
  2. ✅ Implement deterministic three-way merge
  3. ✅ Use Blake3 for state hashing
  4. ✅ Support ChaCha20-Poly1305 encryption
  5. ✅ Verify operations before accepting
  6. ✅ Maintain operation ordering invariants
  7. ✅ Produce identical state for same operations regardless of order

11. EXAMPLES

Example 1: Collaborative Document

Document: "hello world"

User A (Site 1, Clock 1-3):
  Insert 'X' at [1, 1.5]
  Insert 'Y' at [1, 1.7]
  Result: "hXYello world"

User B (Site 2, Clock 1-2):
  Insert 'Z' at [1, 1.6]
  Result: "hZello world"

Merge at User A:
  Positions: [1,1] < [1,1.5] < [1,1.6] < [1,1.7] < [1,2]
  Result: "hXZYello world" ✓ Deterministic

12. SECURITY PROPERTIES

12.1 Confidentiality

ChaCha20-Poly1305 encrypts all operations; metadata only.

12.2 Integrity

Poly1305 MAC ensures operations unmodified in transit.

12.3 Forward Secrecy

With key rotation: old encrypted operations remain secure if key compromised.

REFERENCES

  • RFC 7539: ChaCha20 and Poly1305
  • “LSEQ: an adaptive structure for sequences in distributed collaborative editing” (Nédelec et al., 2013)
  • RFC 9102: VEST Core

END OF RFC 9104