Skip to content

A Swift implementation of the Negentropy set-reconciliation protocol. Swift port of the negentropy Rust library by Yuki Kishimoto.

License

Notifications You must be signed in to change notification settings

damus-io/negentropy-swift

Repository files navigation

Negentropy Swift

A Swift implementation of the Negentropy set-reconciliation protocol.

This Swift implementation is a port of the negentropy Rust library, adapted to use idiomatic Swift patterns and APIs.

⚠️ Warning: This is experimental software. Furthermore, the initial port was done with extensive AI assistance and not yet extensively human-reviewed. The API and protocol implementation may change, and it has not been extensively tested in production environments. Use at your own risk.

Description

Negentropy is a protocol for efficient set reconciliation. It allows two parties to synchronize their sets of items by exchanging minimal data. The protocol uses fingerprinting, range splitting, and differential updates to minimize bandwidth usage.

Features

  • ✅ Efficient set reconciliation with minimal data transfer
  • ✅ Pure Swift implementation with no external dependencies (uses CryptoKit)
  • ✅ Protocol version 1 (0x61) compatible
  • ✅ Tests included
  • ✅ Swift 6.2 compatible

Requirements

  • iOS 16.0+ / macOS 13.0+ / tvOS 16.0+ / watchOS 9.0+
  • Swift 5.9+
  • Xcode 15.0+

Installation

Swift Package Manager

Add the following to your Package.swift file:

dependencies: [ .package(url: "https://github.com/damus-io/negentropy-swift.git", from: "0.1.0") ]

Or add it through Xcode:

  1. File → Add Package Dependencies
  2. Enter the repository URL
  3. Select the version you want to use

Usage

Basic Example

import Negentropy // Client setup var clientStorage = NegentropyStorageVector() let id1 = try Id(slice: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".data(using: .utf8)!) let id2 = try Id(slice: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".data(using: .utf8)!) try clientStorage.insert(timestamp: 0, id: id1) try clientStorage.insert(timestamp: 1, id: id2) try clientStorage.seal() // Server setup var serverStorage = NegentropyStorageVector() let id3 = try Id(slice: "cccccccccccccccccccccccccccccccc".data(using: .utf8)!) let id4 = try Id(slice: "11111111111111111111111111111111".data(using: .utf8)!) try serverStorage.insert(timestamp: 0, id: id1) // shared with client try serverStorage.insert(timestamp: 2, id: id3) try serverStorage.insert(timestamp: 3, id: id4) try serverStorage.seal() // Client initiates reconciliation var client = try Negentropy(storage: clientStorage, frameSizeLimit: 0) let initMessage = try client.initiate() // Server processes the message var server = try Negentropy(storage: serverStorage, frameSizeLimit: 0) let serverResponse = try server.reconcile(initMessage) // Client processes the response var haveIds: [Id] = [] // IDs client has that server needs var needIds: [Id] = [] // IDs client needs from server if let nextMessage = try client.reconcile(serverResponse, haveIds: &haveIds, needIds: &needIds) { // Continue reconciliation with nextMessage } else { // Reconciliation complete print("Client has \(haveIds.count) items server needs") print("Client needs \(needIds.count) items from server") }

Creating IDs

// From bytes let bytes = Array(repeating: UInt8(0xAA), count: 32) let id1 = Id(bytes: bytes) // From slice with validation let id2 = try Id(slice: bytes) // From Data let data = Data(repeating: 0xBB, count: 32) let id3 = try Id(data: data)

Storage Implementation

The library provides NegentropyStorageVector for in-memory storage, but you can implement your own storage by conforming to NegentropyStorageBase:

public protocol NegentropyStorageBase { func size() throws -> Int func getItem(at index: Int) throws -> Item? func iterate(begin: Int, end: Int, callback: (Item, Int) throws -> Bool) throws func findLowerBound(first: Int, last: Int, value: Bound) -> Int func fingerprint(begin: Int, end: Int) throws -> Fingerprint }

Frame Size Limits

You can specify a frame size limit to control the maximum size of messages:

// No limit (default) let negentropy1 = try Negentropy(storage: storage, frameSizeLimit: 0) // With limit (must be >= 4096) let negentropy2 = try Negentropy(storage: storage, frameSizeLimit: 8192)

API Reference

Core Types

Id

A 32-byte identifier used to uniquely identify items.

Item

Represents an item with a timestamp and ID. Items are sorted first by timestamp, then by ID.

Bound

Represents a range boundary with a partial or full item.

NegentropyStorageVector

In-memory storage implementation using an array.

Negentropy<Storage>

Main protocol implementation. Generic over the storage type.

Key Methods

Negentropy.initiate() -> [UInt8]

Creates the initial reconciliation message (client side).

Negentropy.reconcile(_ query: [UInt8]) -> [UInt8]

Processes a query and returns a response (server side).

Negentropy.reconcile(_ query: [UInt8], haveIds:needIds:) -> [UInt8]?

Processes a response and extracts IDs (client side). Returns nil when reconciliation is complete.

Error Handling

The library uses Swift's typed error handling. All operations that can fail throw NegentropyError:

public enum NegentropyError: Error { case idTooBig case invalidIdSize case frameSizeLimitTooSmall case notSealed case alreadySealed case alreadyBuiltInitialMessage case initiator case nonInitiator case unexpectedMode(UInt64) case parseEndsPrematurely case protocolVersionNotFound case invalidProtocolVersion case unsupportedProtocolVersion case conversionError case badRange }

Testing

Run tests using Swift Package Manager:

swift test

Or through Xcode:

  1. Open Package.swift in Xcode
  2. Product → Test (⌘U)

Performance Considerations

  • Items must be sorted by timestamp and ID for efficient reconciliation
  • Always call seal() on storage before using it with Negentropy
  • For large sets, consider using frame size limits to avoid large messages
  • The protocol is most efficient when sets have significant overlap

License

This project is distributed under the MIT software license. See the LICENSE file for details.

Credits

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

References

About

A Swift implementation of the Negentropy set-reconciliation protocol. Swift port of the negentropy Rust library by Yuki Kishimoto.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages