diff --git a/.commitlintrc.yml b/.commitlintrc.yml new file mode 100644 index 0000000..8d3697a --- /dev/null +++ b/.commitlintrc.yml @@ -0,0 +1,19 @@ +extends: + - '@commitlint/config-conventional' + +rules: + header-max-length: [2, always, 100] + type-enum: + - 2 + - always + - - build + - chore + - ci + - docs + - feat + - fix + - perf + - refactor + - revert + - style + - test \ No newline at end of file diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000..57918ab --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,16 @@ +name: Commitlint + +on: + pull_request: + types: [opened, reopened, edited, synchronize] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: wagoid/commitlint-github-action@v6 + with: + configFile: .commitlintrc.yml \ No newline at end of file diff --git a/.github/workflows/publish-go.yml b/.github/workflows/publish-go.yml index 24251a3..9f7ca55 100644 --- a/.github/workflows/publish-go.yml +++ b/.github/workflows/publish-go.yml @@ -3,7 +3,7 @@ name: Publish Go on: push: tags: - - "v*" + - "fabric-v*" workflow_dispatch: permissions: diff --git a/.github/workflows/publish-js.yml b/.github/workflows/publish-js.yml index 2392a7d..f0aa499 100644 --- a/.github/workflows/publish-js.yml +++ b/.github/workflows/publish-js.yml @@ -3,7 +3,7 @@ name: Publish JS packages on: push: tags: - - "v*" + - "fabric-v*" workflow_dispatch: jobs: @@ -26,7 +26,7 @@ jobs: - name: Determine version id: version run: | - VERSION="${GITHUB_REF_NAME#v}" + VERSION="${GITHUB_REF_NAME#fabric-v}" if [[ ! "$GITHUB_REF" == refs/tags/* ]]; then VERSION="0.0.0-dev.$(date +%Y%m%d%H%M%S)" fi diff --git a/.github/workflows/publish-kotlin.yml b/.github/workflows/publish-kotlin.yml index 232bb91..2f595a3 100644 --- a/.github/workflows/publish-kotlin.yml +++ b/.github/workflows/publish-kotlin.yml @@ -3,7 +3,7 @@ name: Publish Kotlin on: push: tags: - - "v*" + - "fabric-v*" workflow_dispatch: permissions: @@ -21,7 +21,7 @@ jobs: - name: Determine version id: version run: | - VERSION="${GITHUB_REF_NAME#v}" + VERSION="${GITHUB_REF_NAME#fabric-v}" if [[ ! "$GITHUB_REF" == refs/tags/* ]]; then VERSION="0.0.0-dev.$(date +%Y%m%d%H%M%S)" fi diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 00ac3ae..9c25ec2 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -3,7 +3,7 @@ name: Publish Python on: push: tags: - - "v*" + - "fabric-v*" workflow_dispatch: permissions: @@ -25,7 +25,7 @@ jobs: - name: Determine version id: version run: | - VERSION="${GITHUB_REF_NAME#v}" + VERSION="${GITHUB_REF_NAME#fabric-v}" if [[ ! "$GITHUB_REF" == refs/tags/* ]]; then VERSION="0.0.0-dev.$(date +%Y%m%d%H%M%S)" fi diff --git a/.github/workflows/publish-swift.yml b/.github/workflows/publish-swift.yml index 133875a..634eab4 100644 --- a/.github/workflows/publish-swift.yml +++ b/.github/workflows/publish-swift.yml @@ -3,7 +3,7 @@ name: Publish Swift on: push: tags: - - "v*" + - "fabric-v*" workflow_dispatch: permissions: diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml new file mode 100644 index 0000000..11d4eea --- /dev/null +++ b/.github/workflows/release-plz.yml @@ -0,0 +1,48 @@ +name: Release-plz + +permissions: + pull-requests: write + contents: write + +on: + push: + branches: [main] + +jobs: + release-plz-pr: + name: Release-plz PR + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'spacesprotocol' }} + concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + release-plz-release: + name: Release-plz publish + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'spacesprotocol' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_PLZ_TOKEN }} + - uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/snippets.yml b/.github/workflows/snippets.yml index f76b2b7..db91e3d 100644 --- a/.github/workflows/snippets.yml +++ b/.github/workflows/snippets.yml @@ -2,8 +2,8 @@ name: Extract code snippets on: push: - paths: - - "fabric/examples/**" + tags: + - "fabric-v*" workflow_dispatch: permissions: @@ -17,8 +17,29 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Determine versions + id: version + run: | + if [[ "$GITHUB_REF" == refs/tags/fabric-v* ]]; then + echo "version=${GITHUB_REF_NAME#fabric-v}" >> "$GITHUB_OUTPUT" + else + echo "version=latest" >> "$GITHUB_OUTPUT" + fi + # Pull libveritas version from what we actually depend on + LV_VERSION=$(grep 'libveritas-jvm' fabric/kotlin/build.gradle.kts | head -1 | sed 's/.*:\([0-9][^"]*\).*/\1/') + echo "lv_version=$LV_VERSION" >> "$GITHUB_OUTPUT" + - name: Extract snippets run: | + VERSION="${{ steps.version.outputs.version }}" + LV_VERSION="${{ steps.version.outputs.lv_version }}" + + # Replace version placeholders in example files + find fabric/examples -type f \( -name "*.rs" -o -name "*.mjs" -o -name "*.go" -o -name "*.py" -o -name "*.kt" -o -name "*.swift" \) \ + -exec sed -i "s/{{VERSION}}/$VERSION/g" {} + && \ + find fabric/examples -type f \( -name "*.rs" -o -name "*.mjs" -o -name "*.go" -o -name "*.py" -o -name "*.kt" -o -name "*.swift" \) \ + -exec sed -i "s/{{LV_VERSION}}/$LV_VERSION/g" {} + + cd fabric/examples mkdir -p /tmp/snippets diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0e0c780 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + + - name: Build + run: cargo build -p fabric-rs -p relay + + - name: Run tests + run: cargo test -p fabric-rs + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --all -- --check + - run: cargo clippy -p fabric-rs -p relay --all-features -- -D warnings \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4abf780..39b0bfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -440,9 +440,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "bdk_chain" +name = "bdk_chain_backport" version = "0.21.0" -source = "git+https://github.com/buffrr/bdk.git?rev=43bca8643dec6fdda99e4a29bf88709729af349e#43bca8643dec6fdda99e4a29bf88709729af349e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580eb55991b7a2415d71d195935f4f88b138257a5e7756724d77974c2f15153" dependencies = [ "bdk_core", "bitcoin", @@ -453,8 +454,9 @@ dependencies = [ [[package]] name = "bdk_core" -version = "0.4.0" -source = "git+https://github.com/buffrr/bdk.git?rev=43bca8643dec6fdda99e4a29bf88709729af349e#43bca8643dec6fdda99e4a29bf88709729af349e" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b545aea1efc090e4f71f1dd5468090d9f54c3de48002064c04895ef811fbe0b2" dependencies = [ "bitcoin", "hashbrown 0.14.5", @@ -462,11 +464,12 @@ dependencies = [ ] [[package]] -name = "bdk_wallet" +name = "bdk_wallet_backport" version = "1.0.0-beta.6" -source = "git+https://github.com/buffrr/bdk.git?rev=43bca8643dec6fdda99e4a29bf88709729af349e#43bca8643dec6fdda99e4a29bf88709729af349e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf2fadfbc496ddc97a19bc99196abafe6d393eb0f6e51f36aa6cf93b9aeb8db" dependencies = [ - "bdk_chain", + "bdk_chain_backport", "bip39", "bitcoin", "miniscript", @@ -620,6 +623,31 @@ dependencies = [ "hex-conservative 0.3.2", ] +[[package]] +name = "bitcoin_yuki" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745c612d5df594e30f526bcd62af6e13909e26c109a6d9600197010f2f944a57" +dependencies = [ + "anyhow", + "bincode", + "bip324", + "bitcoin", + "clap", + "env_logger", + "futures-util", + "hex", + "jsonrpsee", + "reqwest", + "rusqlite", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber 0.3.23", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -692,7 +720,8 @@ dependencies = [ [[package]] name = "borsh_utils" version = "0.1.0" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#b2e982ce231928e637973df4cc1d5933579d2bd6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c0cd2efcc60ce44ca958baa626593a23006b5418b1fa8f038c23aa85f7a97f" dependencies = [ "bitcoin", "borsh", @@ -1103,7 +1132,7 @@ dependencies = [ ] [[package]] -name = "fabric" +name = "fabric-rs" version = "0.1.0" dependencies = [ "dashmap", @@ -1759,7 +1788,7 @@ version = "0.1.0" dependencies = [ "axum", "borsh", - "fabric", + "fabric-rs", "hex", "libveritas", "libveritas_testutil", @@ -2006,8 +2035,9 @@ dependencies = [ [[package]] name = "libveritas" -version = "0.1.0" -source = "git+https://github.com/spacesprotocol/libveritas.git#b9163abd6361ccdd0a61ea35859ae1244fd6d823" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cf115fd0ad4269e52e6887481c216735a72b8e6c606317bbe9b97a3b7f19e3" dependencies = [ "base64 0.22.1", "borsh", @@ -2024,8 +2054,9 @@ dependencies = [ [[package]] name = "libveritas_testutil" -version = "0.1.0" -source = "git+https://github.com/spacesprotocol/libveritas.git#b9163abd6361ccdd0a61ea35859ae1244fd6d823" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69a2e9b822fcdb97d696d8b354148dc283f0119b0589dc67a309a71e911b525" dependencies = [ "bitcoin", "borsh", @@ -2042,8 +2073,9 @@ dependencies = [ [[package]] name = "libveritas_zk" -version = "0.1.0" -source = "git+https://github.com/spacesprotocol/libveritas.git#b9163abd6361ccdd0a61ea35859ae1244fd6d823" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c67914770647dd88766cd7da471776bebd28751dd561d7782e978b12d9524cf" dependencies = [ "borsh", "serde", @@ -2723,10 +2755,11 @@ dependencies = [ "anyhow", "axum", "base64 0.22.1", + "bitcoin_yuki", "borsh", "clap", "env_logger", - "fabric", + "fabric-rs", "governor", "hex", "libveritas", @@ -2745,7 +2778,6 @@ dependencies = [ "tower-http", "tracing", "url", - "yuki", ] [[package]] @@ -3372,7 +3404,8 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "sip7" version = "0.1.0" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#b2e982ce231928e637973df4cc1d5933579d2bd6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b684197991554bec0801fb44663974608d59ac6df4f95d2d84a3955e82499a85" dependencies = [ "base64 0.22.1", "hex", @@ -3430,8 +3463,9 @@ dependencies = [ [[package]] name = "spacedb" -version = "0.0.12" -source = "git+https://github.com/spacesprotocol/spacedb.git#43f23e78e4e5fffb8d89661a4b7f39ab43a5a644" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a48cda82e951391df9d0a54c96f8b04e117ff39abad5359b181cb71c80798d77" dependencies = [ "bincode", "borsh", @@ -3444,7 +3478,8 @@ dependencies = [ [[package]] name = "spaces_checkpoint" version = "0.1.0" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#b2e982ce231928e637973df4cc1d5933579d2bd6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "044392ea966b553c792fcf85b72e4da08ea2c8ea37ded4eeb43cb491be528de4" dependencies = [ "anyhow", "clap", @@ -3462,8 +3497,9 @@ dependencies = [ [[package]] name = "spaces_client" -version = "0.0.7" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#b2e982ce231928e637973df4cc1d5933579d2bd6" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d817eddfabcd6d3115f1c5c9b19a0f64ead6542d216a79dac1afe9d7c19ffb4" dependencies = [ "anyhow", "base64 0.22.1", @@ -3497,7 +3533,8 @@ dependencies = [ [[package]] name = "spaces_nums" version = "0.1.0" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#b2e982ce231928e637973df4cc1d5933579d2bd6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2015555e35781fc95ac0eb8e50bef6e70929248ea61d4101b195b4d0abb13e1d" dependencies = [ "bech32", "bitcoin", @@ -3511,8 +3548,9 @@ dependencies = [ [[package]] name = "spaces_protocol" -version = "0.0.7" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#b2e982ce231928e637973df4cc1d5933579d2bd6" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f46b7eb14311193304301a394a30e5f7bdfd7fd9f70a5c8113610bf2b5cc2c" dependencies = [ "bitcoin", "borsh", @@ -3522,11 +3560,12 @@ dependencies = [ [[package]] name = "spaces_wallet" -version = "0.0.7" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#b2e982ce231928e637973df4cc1d5933579d2bd6" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703c9b4b9d4fa4c7097bf46a471699604f1bc3102559609ab66e97c8965258b0" dependencies = [ "anyhow", - "bdk_wallet", + "bdk_wallet_backport", "bech32", "bitcoin", "borsh", @@ -4542,30 +4581,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "yuki" -version = "0.0.1" -source = "git+https://github.com/imperviousinc/yuki.git#f638494a20f13ff87bfa1d962ee1d5586957483a" -dependencies = [ - "anyhow", - "bincode", - "bip324", - "bitcoin", - "clap", - "env_logger", - "futures-util", - "hex", - "jsonrpsee", - "reqwest", - "rusqlite", - "serde", - "serde_json", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber 0.3.23", -] - [[package]] name = "zerocopy" version = "0.8.48" diff --git a/Cargo.toml b/Cargo.toml index 0037337..2800acc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,22 @@ resolver = "2" members = ["fabric/rust", "relay", "tests"] +[workspace.package] +version = "0.1.0" +edition = "2024" +rust-version = "1.85" +license = "MIT" +repository = "https://github.com/spacesprotocol/certrelay" +homepage = "https://spacesprotocol.org" +authors = ["Buffrr "] + [workspace.dependencies] -libveritas = { git = "https://github.com/spacesprotocol/libveritas.git" } -libveritas_testutil = { git = "https://github.com/spacesprotocol/libveritas.git" } +fabric-rs = { path = "fabric/rust", version = "0.1.0" } +libveritas = { version = "0.1" } +libveritas_testutil = { version = "0.1" } -spaces_client = {git = "https://github.com/spacesprotocol/spaces.git", branch = "subspaces"} -spaces_protocol = {git = "https://github.com/spacesprotocol/spaces.git", branch = "subspaces"} -spaces_nums = {git = "https://github.com/spacesprotocol/spaces.git", branch = "subspaces"} -spaces_checkpoint = {git = "https://github.com/spacesprotocol/spaces.git", branch = "subspaces"} -spacedb = { git = "https://github.com/spacesprotocol/spacedb.git", features = ["hash-idx"] } +spaces_client = { version = "0.1" } +spaces_protocol = { version = "0.1" } +spaces_nums = { version = "0.1" } +spaces_checkpoint = { version = "0.1" } +spacedb = { version = "0.1", features = ["hash-idx"] } diff --git a/README.md b/README.md index 80d82ba..b38740a 100644 --- a/README.md +++ b/README.md @@ -1,296 +1,61 @@ # Certrelay -Certrelay is a certificate relay network for the [Spaces protocol](https://spacesprotocol.org). It stores and serves cryptographic proofs that bind human-readable names (handles) to owner keys anchored to Bitcoin. +Certificate relay network for the [Spaces protocol](https://spacesprotocol.org). Stores and serves cryptographic proofs that bind human-readable names to owner keys anchored to Bitcoin. -The entire protocol is plain HTTP, so relays are directly queryable from browsers, mobile apps, and any language with an HTTP client. +## Overview -A client can query any relay to resolve a handle like `alice@bitcoin` into its current owner key, sovereignty state, and optional owner-signed data. All verifiable against Bitcoin's chain state. +Certrelay consists of two components: +- **relay** — HTTP server that verifies certificates, stores them in SQLite, and gossips with peers +- **fabric** — Client library available in Rust, JavaScript, Go, Python, Kotlin, and Swift -## Fabric client +The protocol is plain HTTP — relays are queryable from browsers, mobile apps, and any language with an HTTP client. All verification is done client-side against Bitcoin's chain state. -Fabric is the high-level client for certrelay. It handles peer discovery, anchor verification, relay selection so you just call `resolve`. +## Fabric Client +For documentation on using Fabric to resolve handles, publish records, and verify identities, see: -### JavaScript / TypeScript +**[spacesprotocol.org/docs](https://spacesprotocol.org/docs)** -Install the package for your platform: +### Quick Start ```bash -# Browser or Node.js -npm install @spacesprotocol/fabric-web - -# React Native -npm install @spacesprotocol/fabric-react-native -``` - -Resolve a handle: - -```ts -import { Fabric } from '@spacesprotocol/fabric-web'; -// or: import { Fabric } from '@spacesprotocol/fabric-react-native'; - -const fabric = new Fabric(); -const zone = await fabric.resolve("alice@bitcoin"); - -console.log(zone.handle()); // "alice@bitcoin" -console.log(zone.toJson()); // full zone data as JSON -``` - -Resolve multiple handles: - -```ts -const zones = await fabric.resolveAll(["alice@bitcoin", "bob@bitcoin"]); -for (const zone of zones) { - console.log(zone.handle, zone.toJson()); -} -``` - -Export a `.spacecert` certificate chain: - -```ts -const certBytes = await fabric.export("alice@bitcoin"); -``` - -Broadcast a signed message: - -```ts -await fabric.broadcast(messageBytes); -``` - -By default, Fabric discovers anchors from seed relays. If you have a trusted anchor set hash (e.g. from your own Bitcoin node), pass it for **trustless** verification: - -```ts -const fabric = new Fabric({ anchorSetHash: "ab3f...c7d2" }); -``` - -### Rust - -```rust -use fabric::client::Fabric; - -let fabric = Fabric::new(); -let zone = fabric.resolve("alice@bitcoin").await?; - -println!("{}: {:?}", zone.handle, zone.sovereignty); - -// With a trusted anchor set hash -let fabric = Fabric::new().with_anchor_set("ab3f...c7d2"); -``` - -### Packages - -| Package | Description | -|---------|-------------| -| `@spacesprotocol/fabric-core` | Provider-agnostic core (advanced use) | -| `@spacesprotocol/fabric-web` | Browser & Node.js (WASM backend) | -| `@spacesprotocol/fabric-react-native` | React Native (native backend) | -| `fabric` (Rust crate) | Rust client | - -The web and React Native packages re-export everything from core, so most consumers only need one dependency. If you need custom provider wiring, use `fabric-core` directly. - -## Protocol - -### Endpoints - -| Method | Path | Description | -|--------|------|-------------| -| POST | `/query` | Resolve handles (JSON request, binary response) | -| POST | `/message` | Submit a certificate message (binary) | -| POST | `/announce` | Announce a peer relay (JSON) | -| GET | `/peers` | List known peers (JSON) | -| GET | `/hints` | Lightweight freshness check (JSON) | -| POST | `/chain-proof` | Build a chain proof (JSON request, binary response) | -| GET | `/anchors` | Get trust anchor set (JSON) | - -### Resolving handles - -Send a `POST /query` with a JSON body: - -```json -{ - "queries": [ - { - "space": "@bitcoin", - "handles": ["alice", "bob"] - } - ] -} -``` - -Each query targets a **space** (e.g. `@bitcoin`) and lists the **handles** within it to resolve. You can query multiple spaces in a single request. - -The response is a binary-encoded `Message` containing the chain proofs and certificates needed to verify each handle. - -### Epoch hints - -If the client has previously resolved a space and cached its epoch root, it can include an epoch hint to skip redundant proofs: +# Rust +cargo add fabric-rs -```json -{ - "queries": [ - { - "space": "@bitcoin", - "handles": ["alice"], - "epoch_hint": { - "root": "abcdef...", - "height": 870000 - } - } - ] -} -``` - -The relay will omit the receipt proof if the client's cached epoch is still current, reducing response size. - -### Freshness hints - -`GET /hints?q=alice@bitcoin,bob@bitcoin,@bitcoin` returns lightweight freshness data without fetching full certificates: - -```json -{ - "anchor_tip": 870100, - "hints": [ - { - "epoch_tip": 870000, - "name": "@bitcoin", - "seq": 5, - "delegate_seq": 3, - "epochs": [ - { - "epoch": 870000, - "res": [ - { "seq": 2, "name": "alice@bitcoin" } - ] - } - ] - } - ] -} -``` - -Fabric uses this to compare freshness across multiple relays and pick the most up-to-date one before querying. - -### Trust anchors - -`GET /anchors` returns the relay's current anchor set. An optional `?root=` parameter fetches a specific set by hash. - -Response headers include `X-Anchor-Root` and `X-Anchor-Height`, which allows cheap freshness comparison via `HEAD /anchors` without downloading the full set. - -## Verification - -Clients verify relay responses locally using [libveritas](https://github.com/spacesprotocol/libveritas). No trust is placed in the relay itself. All data is proven against Bitcoin's chain state via merkle proofs and ZK receipts. - -### Trust anchor bootstrapping - -A client needs one or more **root anchors** to verify messages. A root anchor is a snapshot of Bitcoin's chain state at a specific block height, containing: - -- Block hash and height -- Spaces tree merkle root -- Ptrs tree merkle root (commitments, delegations, key rotations) - -Anchors can be obtained from a trusted source (hardcoded in the client, fetched from a known relay, or derived from a Bitcoin node). Fabric handles this automatically by fetching from seed relays and verifying the anchor set hash. - -### Verification flow - -``` -Client Relay - | | - | POST /query (JSON) | - |------------------------------>| - | | - | binary Message | - |<------------------------------| - | | - | verify_message(msg) | - | -> Vec | -``` - -1. Client sends a query -2. Relay responds with a `Message` containing proofs -3. Client calls `verify_message` which checks all merkle proofs, ZK receipts, and signatures -4. If verification succeeds, the client gets a list of `Zone` objects, one per handle - -### What a Zone contains - -Each verified `Zone` represents the current state of a handle: - -| Field | Type | Description | -|-------|------|-------------| -| `handle` | SName | The resolved handle (e.g. `alice@bitcoin`) | -| `sovereignty` | SovereigntyState | Commitment finality status | -| `script_pubkey` | ScriptBuf | Current Bitcoin script controlling the handle | -| `offchain_data` | Option\ | Owner-signed arbitrary data | -| `commitment` | ProvableOption\ | On-chain state commitment | -| `delegate` | ProvableOption\ | Delegation info | -| `anchor` | u32 | Block height of the proof snapshot | - -### Sovereignty states - -| State | Meaning | -|-------|---------| -| **Sovereign** | Commitment is finalized, the handle is fully self-governing | -| **Pending** | Commitment exists but hasn't reached finality yet | -| **Dependent** | No commitment, handle operates under parent space authority | - -### Offchain data - -Handle owners can attach arbitrary signed data without making on-chain transactions. The `OffchainData` contains: - -- `seq` - sequence number for versioning (higher = newer) -- `data` - arbitrary byte payload -- `signature` - Schnorr signature from the handle's current owner - -The signature is verified during `verify_message`. Applications can use this to store TLS certificates, service endpoints, payment addresses, or any other metadata. - -## Client implementation - -Fabric is the recommended way to interact with the relay network. Under the hood it: - -1. **Discovers peers** by fetching `GET /peers` from seed relays and collecting URLs -2. **Fetches trust anchors** by polling `HEAD /anchors` across seeds, voting on the freshest anchor set hash, then downloading and verifying the full set via `GET /anchors?root=` -3. **Selects relays** by querying `GET /hints` on multiple relays in parallel and ranking them by freshness -4. **Resolves handles** by posting to `POST /query` on the freshest relay, verifying the binary response with libveritas, and falling through to the next relay on failure -5. **Caches root zones** so subsequent queries for handles in the same space can include epoch hints and skip redundant proofs - -For broadcasting, Fabric submits the message to multiple relays via `POST /message` for gossip propagation. +# JavaScript / TypeScript +npm install @spacesprotocol/fabric-web +# Go +go get github.com/spacesprotocol/fabric-go -## Peer discovery +# Python +pip install fabric-resolver -Relays form a gossip network. New relays announce themselves via `POST /announce` and can be discovered by any client via `GET /peers`: +# Kotlin +implementation("org.spacesprotocol:fabric:0.1.0") -```json -[ - { - "source_ip": "203.0.113.1", - "url": "https://relay2.example.com", - "capabilities": 0 - } -] +# Swift +.package(url: "https://github.com/spacesprotocol/fabric-swift.git", from: "0.1.0") ``` -This allows clients to fall back to alternative relays if one is unavailable. - -## Running a relay +## Running a Relay ```bash cargo install --git https://github.com/spacesprotocol/certrelay.git --bin certrelay certrelay ``` -That's it. On first run, certrelay will: -1. Download a checkpoint so it can sync quickly (~8MB) +On first run, certrelay will: +1. Download a checkpoint (~8MB) 2. Build hash indexes (~2 min) 3. Start an embedded Bitcoin light client ([yuki](https://github.com/imperviousinc/yuki)) and [spaced](https://github.com/spacesprotocol/spaces) node 4. Sync to the chain tip and start serving -No external Bitcoin node or Spaces node required. Data is stored in `~/.certrelay` by default. +No external Bitcoin node required. Data is stored in `~/.certrelay` by default. ### Configuration -All options can be set via CLI flags or environment variables: - | Flag | Env | Default | Description | |------|-----|---------|-------------| | `--chain` | `CERTRELAY_CHAIN` | `mainnet` | Network (`mainnet`, `testnet4`) | @@ -302,7 +67,6 @@ All options can be set via CLI flags or environment variables: | `--remote-ip-header` | `CERTRELAY_REMOTE_IP_HEADER` | - | Header for client IP behind reverse proxy | | `--is-bootstrap` | `CERTRELAY_BOOTSTRAP` | `false` | Run as a bootstrap node | | `--skip-checkpoint-sync` | - | `false` | Skip checkpoint download, sync from scratch | -| `--anchor-refresh` | `CERTRELAY_ANCHOR_REFRESH` | `300` | Anchor refresh interval in seconds | ### Public relay behind a reverse proxy @@ -315,8 +79,10 @@ certrelay \ ### Using an external spaced node -If you already run a spaced node, point certrelay at it to skip the embedded light client: - ```bash certrelay --spaced-rpc-url http://user:password@127.0.0.1:12888 ``` + +## License + +MIT \ No newline at end of file diff --git a/fabric/examples/go/main.go b/fabric/examples/go/main.go index 86f6a00..57f7b87 100644 --- a/fabric/examples/go/main.go +++ b/fabric/examples/go/main.go @@ -1,5 +1,9 @@ package main +// +// go get github.com/spacesprotocol/fabric-go +// + import ( "encoding/hex" "fmt" diff --git a/fabric/examples/js/index.mjs b/fabric/examples/js/index.mjs index e788783..aac89ba 100644 --- a/fabric/examples/js/index.mjs +++ b/fabric/examples/js/index.mjs @@ -1,3 +1,7 @@ +// +// npm install @spacesprotocol/fabric-web +// + import { Fabric, RecordSet, Record, MessageBuilder } from "@spacesprotocol/fabric-web"; import { signSchnorr } from "@spacesprotocol/fabric-web/signing"; diff --git a/fabric/examples/kotlin/Example.kt b/fabric/examples/kotlin/Example.kt index 035ff02..b0dd707 100644 --- a/fabric/examples/kotlin/Example.kt +++ b/fabric/examples/kotlin/Example.kt @@ -1,3 +1,13 @@ +// +// implementation("org.spacesprotocol:fabric:{{VERSION}}") +// implementation("org.spacesprotocol:libveritas-jvm:{{LV_VERSION}}") +// + +// +// implementation("org.spacesprotocol:fabric:{{VERSION}}") +// implementation("org.spacesprotocol:libveritas:{{LV_VERSION}}") +// + import org.spacesprotocol.fabric.Fabric import org.spacesprotocol.fabric.RecordSet import org.spacesprotocol.fabric.Record diff --git a/fabric/examples/python/example.py b/fabric/examples/python/example.py index 3b0c731..3e08f97 100644 --- a/fabric/examples/python/example.py +++ b/fabric/examples/python/example.py @@ -1,3 +1,7 @@ +# +# pip install fabric-resolver +# + import asyncio from fabric import Fabric, RecordSet, Record, sign_schnorr diff --git a/fabric/examples/rust/Cargo.lock b/fabric/examples/rust/Cargo.lock index fe7525c..7d5f091 100644 --- a/fabric/examples/rust/Cargo.lock +++ b/fabric/examples/rust/Cargo.lock @@ -410,7 +410,8 @@ dependencies = [ [[package]] name = "borsh_utils" version = "0.1.0" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#8bfdffc02d7a49c07f7bf7da77d63a606a502515" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c0cd2efcc60ce44ca958baa626593a23006b5418b1fa8f038c23aa85f7a97f" dependencies = [ "bitcoin", "borsh", @@ -1157,8 +1158,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libveritas" -version = "0.1.0" -source = "git+https://github.com/spacesprotocol/libveritas.git#a7d86225bac32cf26383cd8b9d2fcff6d8ce7829" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cf115fd0ad4269e52e6887481c216735a72b8e6c606317bbe9b97a3b7f19e3" dependencies = [ "base64 0.22.1", "borsh", @@ -1175,8 +1177,9 @@ dependencies = [ [[package]] name = "libveritas_zk" -version = "0.1.0" -source = "git+https://github.com/spacesprotocol/libveritas.git#a7d86225bac32cf26383cd8b9d2fcff6d8ce7829" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c67914770647dd88766cd7da471776bebd28751dd561d7782e978b12d9524cf" dependencies = [ "borsh", "serde", @@ -2019,7 +2022,8 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "sip7" version = "0.1.0" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#8bfdffc02d7a49c07f7bf7da77d63a606a502515" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b684197991554bec0801fb44663974608d59ac6df4f95d2d84a3955e82499a85" dependencies = [ "base64 0.22.1", "hex", @@ -2051,8 +2055,9 @@ dependencies = [ [[package]] name = "spacedb" -version = "0.0.12" -source = "git+https://github.com/spacesprotocol/spacedb.git#43f23e78e4e5fffb8d89661a4b7f39ab43a5a644" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a48cda82e951391df9d0a54c96f8b04e117ff39abad5359b181cb71c80798d77" dependencies = [ "borsh", "sha2", @@ -2061,7 +2066,8 @@ dependencies = [ [[package]] name = "spaces_nums" version = "0.1.0" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#8bfdffc02d7a49c07f7bf7da77d63a606a502515" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2015555e35781fc95ac0eb8e50bef6e70929248ea61d4101b195b4d0abb13e1d" dependencies = [ "bech32", "bitcoin", @@ -2075,8 +2081,9 @@ dependencies = [ [[package]] name = "spaces_protocol" -version = "0.0.7" -source = "git+https://github.com/spacesprotocol/spaces.git?branch=subspaces#8bfdffc02d7a49c07f7bf7da77d63a606a502515" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f46b7eb14311193304301a394a30e5f7bdfd7fd9f70a5c8113610bf2b5cc2c" dependencies = [ "bitcoin", "borsh", diff --git a/fabric/examples/rust/src/main.rs b/fabric/examples/rust/src/main.rs index a271ea6..2b791d6 100644 --- a/fabric/examples/rust/src/main.rs +++ b/fabric/examples/rust/src/main.rs @@ -1,3 +1,7 @@ +// +// cargo add fabric-rs +// + use fabric::client::{Fabric}; use fabric::libveritas::builder::MessageBuilder; use fabric::libveritas::cert::CertificateChain; diff --git a/fabric/examples/swift/Example.swift b/fabric/examples/swift/Example.swift index 86dd213..6eedc55 100644 --- a/fabric/examples/swift/Example.swift +++ b/fabric/examples/swift/Example.swift @@ -1,3 +1,7 @@ +// +// .package(url: "https://github.com/spacesprotocol/fabric-swift.git", from: "{{VERSION}}") +// + import Fabric func exampleResolveIntro() async throws { diff --git a/fabric/go/go.mod b/fabric/go/go.mod index 25a4b87..1cc1bf7 100644 --- a/fabric/go/go.mod +++ b/fabric/go/go.mod @@ -4,7 +4,7 @@ go 1.22 require ( github.com/btcsuite/btcd/btcec/v2 v2.3.6 - github.com/spacesprotocol/libveritas-go v0.0.0-dev.20260407170108 + github.com/spacesprotocol/libveritas-go v0.1.2 ) require ( diff --git a/fabric/go/go.sum b/fabric/go/go.sum index 6c01b1f..173fd97 100644 --- a/fabric/go/go.sum +++ b/fabric/go/go.sum @@ -8,5 +8,5 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/spacesprotocol/libveritas-go v0.0.0-dev.20260407170108 h1:tpFQOIBRJpM0dYYc4VpAh/OayA8ZPvqr1taTuSZdQ98= -github.com/spacesprotocol/libveritas-go v0.0.0-dev.20260407170108/go.mod h1:HXnX2FNL43ueJuedsQwX9GF5jRHYZLqXYfFLWz900H8= +github.com/spacesprotocol/libveritas-go v0.1.2 h1:uMhRnQaPWgDIJtYGXo9wO10aav36G8lmgWXK3gw73gc= +github.com/spacesprotocol/libveritas-go v0.1.2/go.mod h1:HXnX2FNL43ueJuedsQwX9GF5jRHYZLqXYfFLWz900H8= diff --git a/fabric/js/fabric-web/package.json b/fabric/js/fabric-web/package.json index 4b40008..afc69b2 100644 --- a/fabric/js/fabric-web/package.json +++ b/fabric/js/fabric-web/package.json @@ -27,7 +27,7 @@ "dependencies": { "@noble/curves": "^1.8.0", "@spacesprotocol/fabric-core": "*", - "@spacesprotocol/libveritas": "^0.0.0-dev.20260407165333" + "@spacesprotocol/libveritas": "^0.1.2" }, "devDependencies": { "@types/node": "^25.5.0", diff --git a/fabric/js/fabric-web/src/index.ts b/fabric/js/fabric-web/src/index.ts index d1fa2ac..bd6a4f0 100644 --- a/fabric/js/fabric-web/src/index.ts +++ b/fabric/js/fabric-web/src/index.ts @@ -7,20 +7,41 @@ import * as libveritas from "@spacesprotocol/libveritas"; export type FabricOptions = Omit; +const wasmInit: (() => Promise) | undefined = + (libveritas as any).default ?? (libveritas as any).init ?? (libveritas as any).__wbg_init; + +let wasmReady: Promise | null = null; + +async function ensureInit(): Promise { + if (!wasmInit) return; + if (!wasmReady) { + wasmReady = wasmInit().catch(() => { + // Already initialized (bundler environments) — safe to ignore + wasmReady = Promise.resolve(); + }); + } + await wasmReady; +} + /** - * Initialize the WASM module. Must be called before using Fabric - * when loading via `