[
  {
    "slug": "the-referenced-server-logic-for-sparks-default-lightspark-ssp-is-not-publicly-accessible",
    "track": "privacy",
    "title": "The referenced Lightspark SSP server implementation is not publicly accessible",
    "cardTitle": "The referenced server logic for Spark’s default Lightspark SSP is not publicly accessible",
    "summary": "Spark's public repository directs local SSP work to a separate Lightspark Sparkcore path that was not publicly accessible when checked. The public repository exposes the SSP schema, clients, and flow documents, but not the referenced Python handlers or full server-side decision logic.",
    "summarySourceIds": [
      "source-7c91c24385",
      "source-f2455d7667",
      "source-7d948b1288",
      "source-a25d85acb3",
      "source-fab5596fde",
      "source-2873acf3aa",
      "source-cb886ceb70",
      "source-28fad93757",
      "source-f78d1bcb5d"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's public README directs engineers testing local SSP changes to run `sparkcore.server` from Lightspark's `webdev` project and links to a server-side Sparkcore test. The referenced repository URL returned 404 without authentication when rechecked on 2026-07-13.",
        "sourceIds": [
          "source-7c91c24385",
          "source-f2455d7667"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Public flow documentation expressly allows the SSP to reject a previously quoted static-deposit claim, while public wallet code routes default Lightning and cooperative-exit requests through the configured SSP.",
        "sourceIds": [
          "source-a25d85acb3",
          "source-fab5596fde",
          "source-2873acf3aa",
          "source-cb886ceb70",
          "source-28fad93757",
          "source-f78d1bcb5d"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "The default hosted Lightning and cooperative-exit path depends on policy decisions inside the one component outsiders cannot inspect. Neither public source review nor live schema introspection can exclude sanctions, Travel Rule, partner, or destination policy inside that component.",
        "sourceIds": [
          "source-7c91c24385",
          "source-f2455d7667",
          "source-fab5596fde",
          "source-2873acf3aa",
          "source-28fad93757",
          "source-f78d1bcb5d"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Public inaccessibility does not establish why the implementation is private, what policies it contains, or that sanctions screening is deployed there. Lightspark's open-source statement concerns Spark and does not expressly promise an open-source SSP.",
        "sourceIds": [
          "source-7c91c24385",
          "source-7d948b1288",
          "source-45abab1b69"
        ]
      }
    ],
    "sources": [
      {
        "label": "Sparkcore SSP instructions",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/README.md#L254-L283",
        "number": 1,
        "id": "source-7c91c24385",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "referenced non-public implementation",
        "url": "https://github.com/lightsparkdev/webdev/blob/150dd1ceecf85e122ec3ebd608d3c9f7c44f1969/sparkcore/sparkcore/spark/handlers/__tests__/test_transfer_v2.py#L35",
        "number": 2,
        "id": "source-f2455d7667",
        "verifiedAt": "2026-07-14",
        "type": "Referenced non-public implementation",
        "public": true,
        "immutable": true,
        "expectedUnavailable": true,
        "host": "github.com"
      },
      {
        "label": "public SSP schema",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/schemas/ssp_rc_schema.graphql",
        "number": 3,
        "id": "source-7d948b1288",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "documented SSP rejection point",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/.claude/flows/static_deposits.md#L25-L43",
        "number": 4,
        "id": "source-a25d85acb3",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "default Lightning SSP flow",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/sdks/js/packages/spark-sdk/src/spark-wallet/spark-wallet.ts#L4060-L4238",
        "number": 5,
        "id": "source-fab5596fde",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "default cooperative-exit flow",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/sdks/js/packages/spark-sdk/src/spark-wallet/spark-wallet.ts#L4929-L5100",
        "number": 6,
        "id": "source-2873acf3aa",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Breez SSP configuration",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark-wallet/src/config.rs#L51-L89",
        "number": 7,
        "id": "source-cb886ceb70",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Breez Lightning SSP flow",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark/src/services/lightning.rs#L645-L671",
        "number": 8,
        "id": "source-28fad93757",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Breez cooperative-exit SSP flow",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark/src/services/coop_exit.rs#L146-L156",
        "number": 9,
        "id": "source-f78d1bcb5d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Lightspark's open-source statement",
        "url": "https://www.lightspark.com/news/spark/introducing-spark",
        "number": 10,
        "id": "source-45abab1b69",
        "verifiedAt": "2026-07-14",
        "type": "Blog or announcement",
        "public": true,
        "immutable": false,
        "host": "lightspark.com"
      }
    ]
  },
  {
    "slug": "spark-gives-lightsparks-ssp-explicit-cross-wallet-privacy-bypass-paths",
    "track": "privacy",
    "title": "Spark gives the Lightspark-only SSP interface cross-wallet privacy bypasses",
    "cardTitle": "Spark gives Lightspark's SSP explicit cross-wallet privacy-bypass paths",
    "summary": "Spark defines an IP-restricted, Lightspark-only SSP interface for cross-wallet node, transfer, static-address, and tree queries. Reviewed handlers and a pinned commit explicitly bypass ordinary wallet-privacy checks for SSP calls.",
    "summarySourceIds": [
      "source-dd484c50cc",
      "source-934a7f8213",
      "source-a4d3b5f183",
      "source-32695dffcc"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The current Operator repository defines a build-tagged `SparkSspInternalService` as a “Lightspark-only SSP coordination surface.” Its IP-restricted methods include cross-wallet node, transfer, static-address, tree-snapshot, and node-detail queries.",
        "sourceIds": [
          "source-dd484c50cc"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Transfer and node-query handlers skip ordinary wallet- privacy checks when invoked with the SSP flag. A pinned Spark commit says the static-address endpoint likewise passes `isSSP=true` so the SSP can keep reading addresses across wallets after the public privacy filter is enabled.",
        "sourceIds": [
          "source-934a7f8213",
          "source-a4d3b5f183",
          "source-32695dffcc"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Spark privacy mode is not provider-blind. The Lightspark-only SSP interface has explicit, purpose-built routes around the privacy filters applied to ordinary public callers.",
        "sourceIds": [
          "source-dd484c50cc",
          "source-934a7f8213",
          "source-a4d3b5f183",
          "source-32695dffcc"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This is an internal, IP-restricted service, not a public authentication vulnerability. The code proves privileged read paths, not that every listed method returns every private record, how data is retained, or who inside Lightspark can access it.",
        "sourceIds": [
          "source-dd484c50cc",
          "source-934a7f8213",
          "source-a4d3b5f183",
          "source-32695dffcc"
        ]
      }
    ],
    "sources": [
      {
        "label": "Lightspark-only SSP interface",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/rpcpolicy/policy_lightspark.go#L1-L57",
        "number": 1,
        "id": "source-dd484c50cc",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "SSP transfer privacy bypass",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/transfer_handler.go#L2207-L2217",
        "number": 2,
        "id": "source-934a7f8213",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "SSP node-query privacy bypass",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/tree_query_handler.go#L37-L166",
        "number": 3,
        "id": "source-a4d3b5f183",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "static-address bypass commit",
        "url": "https://github.com/buildonspark/spark/commit/4d8320507a0b99869ddfd62fa08f225962c2127c",
        "number": 4,
        "id": "source-32695dffcc",
        "verifiedAt": "2026-07-14",
        "type": "Code commit",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "spark-operator-code-includes-a-wallet-identity-kill-switch-for-state-changing-actions",
    "track": "privacy",
    "title": "Current Spark code contains an identity-targeted wallet kill switch",
    "cardTitle": "Spark Operator code includes a wallet-identity kill switch for state-changing actions",
    "summary": "The reviewed Operator snapshot defines a per-wallet, per-Operator control named as a wallet kill switch and keyed by the wallet identity public key. Relevant state-changing handlers enforce it while read-only calls remain available; a network-wide freeze requires configuring every Operator.",
    "summarySourceIds": [
      "source-edb5fbf5eb",
      "source-30fe474ace"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark commit `08db5de`, titled “Add wallet kill switch for state-mutating RPCs,” added 390 lines across 18 files. Its summary says it can disable state mutations for a specific wallet.",
        "sourceIds": [
          "source-edb5fbf5eb"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The exact control is `spark.so.killswitch.wallet@<identity_pubkey_hex> = 1`. It targets a wallet identity public key while leaving read-only calls available.",
        "sourceIds": [
          "source-30fe474ace"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Spark Operators already possess a selective denial primitive keyed to the durable identity encoded by a Spark address. No protocol upgrade, wallet release, or public on-chain action is needed to target one identity's ability to transact.",
        "sourceIds": [
          "source-edb5fbf5eb",
          "source-30fe474ace"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The knob is local to each SO. The source says a system-wide freeze requires configuring every Operator; the public record does not show a production value or say the control was created for sanctions.",
        "sourceIds": [
          "source-edb5fbf5eb",
          "source-30fe474ace"
        ]
      }
    ],
    "sources": [
      {
        "label": "origin commit",
        "url": "https://github.com/buildonspark/spark/commit/08db5deea2d769b9f33c7f2aaf1cec67a183dd42",
        "number": 1,
        "id": "source-edb5fbf5eb",
        "verifiedAt": "2026-07-14",
        "type": "Code commit",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "knob definition",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/knobs/knobs.go#L65-L70",
        "number": 2,
        "id": "source-30fe474ace",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "spark-makes-a-targeted-wallet-freeze-externally-indistinguishable-from-an-ordinary-identity-error",
    "track": "privacy",
    "title": "The kill-switch response is deliberately disguised as a different error",
    "cardTitle": "Spark makes a targeted wallet freeze externally indistinguishable from an ordinary identity error",
    "summary": "When the wallet gate fires, the affected client receives the same PermissionDenied response and message used for a session-identity mismatch. Tests enforce that external indistinguishability; internal logs, metrics, and error codes retain the actual reason.",
    "summarySourceIds": [
      "source-d04caa27e1",
      "source-d20391edfa",
      "source-a3836e40b6"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "A gated wallet receives the same gRPC `PermissionDenied` code and exact message as an ordinary session-identity mismatch. The tests enforce this indistinguishability invariant.",
        "sourceIds": [
          "source-d04caa27e1",
          "source-a3836e40b6"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Only the Operator's internal error code, warning log, and `spark.so.killswitch.blocked_total` metric reveal that the kill switch fired.",
        "sourceIds": [
          "source-d04caa27e1",
          "source-d20391edfa"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "The affected user cannot determine from the wire response whether it made an authentication mistake or was deliberately targeted. That obstructs detection, evidence collection, and accountability for selective denial.",
        "sourceIds": [
          "source-d04caa27e1",
          "source-d20391edfa",
          "source-a3836e40b6"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Concealment is proven. The comment frames it as resistance to probing and does not identify compliance or sanctions as the motive.",
        "sourceIds": [
          "source-d04caa27e1",
          "source-d20391edfa"
        ]
      }
    ],
    "sources": [
      {
        "label": "authz.go",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/authz/authz.go#L36-L70",
        "number": 1,
        "id": "source-d04caa27e1",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "killswitch.go",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/authz/killswitch.go#L16-L58",
        "number": 2,
        "id": "source-d20391edfa",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "indistinguishability test",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/authz/killswitch_test.go#L16-L123",
        "number": 3,
        "id": "source-a3836e40b6",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "spark-operator-code-can-stop-a-pending-ssp-funded-incoming-transfer-before-wallet-credit",
    "track": "privacy",
    "title": "The gate can stop an incoming SSP-funded credit after it has been created",
    "cardTitle": "Spark Operator code can stop a pending SSP-funded incoming transfer before wallet credit",
    "summary": "The receiver-side task applies the wallet gate before completing pending transfers, expressly including SSP-funded incoming transfers. The source covers a freeze set after transfer creation but before task pickup. This establishes code behavior, not a known production compliance block.",
    "summarySourceIds": [
      "source-0d905f8be4"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "A receiver-side background task completes pending incoming transfers, expressly including SSP-funded transfers. The code says a freeze applied after transfer creation but before task pickup must prevent the credit from landing in the receiver wallet.",
        "sourceIds": [
          "source-0d905f8be4"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "This is not merely a control over a user initiating new sends. The enforcement point can deny receipt of an already-created, hosted incoming credit.",
        "sourceIds": [
          "source-0d905f8be4"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This proves handler behavior when the relevant SO gate is set, not that a production payment has been blocked for compliance.",
        "sourceIds": [
          "source-0d905f8be4"
        ]
      }
    ],
    "sources": [
      {
        "label": "receiver-side transfer comment and gate",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/transfer_handler.go#L5411-L5418",
        "number": 1,
        "id": "source-0d905f8be4",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "sparks-preimage-share-consensus-path-enforces-the-wallet-gate-for-ssp-and-lnurl-callers",
    "track": "privacy",
    "title": "Spark's preimage-share consensus path enforces the wallet gate even for SSP and LNURL callers",
    "cardTitle": "Spark’s preimage-share consensus path enforces the wallet gate for SSP and LNURL callers",
    "summary": "Spark's reviewed V2 preimage-share consensus path applies the wallet gate to the affected user even when an SSP, LNURL provider, or another hosted actor makes the call. The source says each Operator checks the identity during the Prepare phase.",
    "summarySourceIds": [
      "source-38e2915065"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The preimage-share path intentionally lacks a normal caller/user session match because an SSP, LNURL provider, or other hosted actor may act for the user. It therefore applies the gate directly to the affected user's identity.",
        "sourceIds": [
          "source-38e2915065"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The code comment says the check runs on every SO during the consensus Prepare phase and aborts the run for the frozen identity.",
        "sourceIds": [
          "source-38e2915065"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Moving the call through a hosted intermediary does not evade the control. The check is deliberately placed inside the multi-operator Lightning settlement path.",
        "sourceIds": [
          "source-38e2915065"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Each SO reads its own local knob. This proves enforcement in the reviewed V2 preimage-share consensus path, not every possible hosted Lightning operation; public code does not disclose production knob control or network-wide activation.",
        "sourceIds": [
          "source-38e2915065"
        ]
      }
    ],
    "sources": [
      {
        "label": "preimage-share consensus handler",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/preimage_share_flow_handler.go#L170-L183",
        "number": 1,
        "id": "source-38e2915065",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "spark-applies-its-wallet-gate-across-exits-deposits-claims-and-privileged-service-paths",
    "track": "privacy",
    "title": "The wallet gate is enforced across cooperative exits, deposit operations, leaf renewal, transfer claims, and privileged token broadcasting",
    "cardTitle": "Spark applies its wallet gate across exits, deposits, claims, and privileged-service paths",
    "summary": "Public Spark code applies the wallet gate to cooperative exits, ordinary and static deposit-address operations, deposit-tree creation, leaf renewal, transfer claims, and privileged token broadcasting. Privileged broadcaster authorization expressly does not bypass the target wallet's gate.",
    "summarySourceIds": [
      "source-c3aa6d8cd9",
      "source-a95f5e0fb3",
      "source-9c6e2a7187",
      "source-8b4eac492d",
      "source-a1988d5929",
      "source-34092e6953",
      "source-e4c5b6a708",
      "source-ad1c5661b8"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Guarded operations include cooperative exits, ordinary and static deposit-address generation, static-address rotation, deposit-tree creation, leaf renewal, and transfer claims.",
        "sourceIds": [
          "source-c3aa6d8cd9",
          "source-a95f5e0fb3",
          "source-9c6e2a7187",
          "source-8b4eac492d",
          "source-a1988d5929",
          "source-34092e6953",
          "source-e4c5b6a708"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "A privileged token broadcaster may bypass an ordinary identity check but is still expressly forbidden from bypassing the target wallet's gate.",
        "sourceIds": [
          "source-ad1c5661b8"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "The control is a maintained policy invariant across fast exit, deposit-management, claim, and privileged-service paths—not a UI switch affecting one ordinary send method.",
        "sourceIds": [
          "source-c3aa6d8cd9",
          "source-a95f5e0fb3",
          "source-34092e6953",
          "source-ad1c5661b8"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "These are state-mutating Spark protocol operations. The gate cannot prevent someone from broadcasting Bitcoin to an already-known on-chain deposit address, and cooperative-path refusal does not invalidate a usable pre-signed unilateral exit. The privileged-broadcaster example concerns tokens.",
        "sourceIds": [
          "source-c3aa6d8cd9",
          "source-a95f5e0fb3",
          "source-9c6e2a7187",
          "source-8b4eac492d",
          "source-a1988d5929",
          "source-34092e6953",
          "source-ad1c5661b8"
        ]
      }
    ],
    "sources": [
      {
        "label": "cooperative exit gate",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/coop_exit_handler.go#L44-L67",
        "number": 1,
        "id": "source-c3aa6d8cd9",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "ordinary deposit-address gate",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/deposit_handler.go#L135-L143",
        "number": 2,
        "id": "source-a95f5e0fb3",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "static deposit-address gate",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/deposit_handler.go#L310-L333",
        "number": 3,
        "id": "source-9c6e2a7187",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "static-address rotation gate",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/deposit_handler.go#L712-L732",
        "number": 4,
        "id": "source-8b4eac492d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "deposit-tree creation gate",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/deposit_handler.go#L892-L911",
        "number": 5,
        "id": "source-a1988d5929",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "leaf-renewal gate",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/renew_leaf_handler.go#L132-L149",
        "number": 6,
        "id": "source-34092e6953",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "transfer-claim gate",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/claim_transfer_flow_handler.go#L86-L98",
        "number": 7,
        "id": "source-e4c5b6a708",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "privileged-broadcaster invariant",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/tokens/broadcast_permissions.go#L27-L43",
        "number": 8,
        "id": "source-ad1c5661b8",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "when-enabled-sparks-targeted-wallet-gate-can-be-updated-live-applied-selectively-and-reversed",
    "track": "privacy",
    "title": "When enabled, Spark's Kubernetes knob provider makes targeted wallet freezes live-updatable, selective, and reversible",
    "cardTitle": "When enabled, Spark’s targeted wallet gate can be updated live, applied selectively, and reversed",
    "summary": "When Spark's Kubernetes knob provider is enabled, Operators watch a ConfigMap for live changes. An integration test freezes one wallet while preserving reads, leaves a second wallet unaffected, and restores the first wallet when the value is cleared; public evidence does not establish production activation.",
    "summarySourceIds": [
      "source-362d5b4d9f",
      "source-44bfcee027",
      "source-5f11a48393"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Operator startup creates the Kubernetes knob provider only when knob configuration is enabled. That provider watches its ConfigMap and updates internal knob values in real time.",
        "sourceIds": [
          "source-362d5b4d9f",
          "source-5f11a48393"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The integration test freezes Wallet A while its reads still work, leaves Wallet B unaffected, then restores Wallet A by clearing the value.",
        "sourceIds": [
          "source-44bfcee027"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "An operator can quietly intervene against one wallet, leave everyone else operating normally, and later reverse the intervention through operational configuration without an on-chain record.",
        "sourceIds": [
          "source-362d5b4d9f",
          "source-44bfcee027",
          "source-5f11a48393"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This proves the implementation and its integration-test behavior, not that production Operators enable the Kubernetes provider, who holds production RBAC, or whether Lightspark can modify Breez's or Flashnet's configuration.",
        "sourceIds": [
          "source-362d5b4d9f",
          "source-44bfcee027",
          "source-5f11a48393"
        ]
      }
    ],
    "sources": [
      {
        "label": "live ConfigMap watcher",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/knobs/k8.go#L65-L88",
        "number": 1,
        "id": "source-362d5b4d9f",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "targeting and reversal test",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/grpc_test/wallet_killswitch_test.go#L17-L83",
        "number": 2,
        "id": "source-44bfcee027",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "conditional Operator initialization",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/bin/operator/main.go#L445-L456",
        "number": 3,
        "id": "source-5f11a48393",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "grids-common-account-model-permits-a-spark-backed-wallet-to-carry-a-frozen-status-that-blocks-payments",
    "track": "privacy",
    "title": "Grid's common account model permits a Spark-backed Embedded Wallet to be FROZEN, a status that blocks sending and receiving",
    "cardTitle": "Grid’s common account model permits a Spark-backed wallet to carry a frozen status that blocks payments",
    "summary": "Grid's common account schema permits the EMBEDDED_WALLET type and FROZEN status to appear together. EMBEDDED_WALLET is Spark-backed; FROZEN is defined as unable to send or receive and may follow compliance or fraud signals. The record is a schema capability, not an observed freeze.",
    "summarySourceIds": [
      "source-4fac637bd4",
      "source-1b2b60f836",
      "source-b64f839f86",
      "source-eaaf6b593d"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Grid's common `InternalAccount` object requires both `type` and `status`. Its type enum includes `EMBEDDED_WALLET`, which Grid documentation identifies as the self-custodial wallet underlying Global Accounts.",
        "sourceIds": [
          "source-4fac637bd4",
          "source-1b2b60f836",
          "source-eaaf6b593d"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The common `FROZEN` status means the account cannot send or receive payments and says Grid may apply the status in response to compliance or fraud signals.",
        "sourceIds": [
          "source-4fac637bd4",
          "source-b64f839f86"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "The public Grid model already contains the exact policy outcome at issue: a compliance decision disables both directions of payment activity for a Spark-backed wallet account.",
        "sourceIds": [
          "source-4fac637bd4",
          "source-1b2b60f836",
          "source-b64f839f86",
          "source-eaaf6b593d"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The schemas establish an allowed state combination and its documented meaning. They do not show a production Embedded Wallet in that state or identify whether enforcement occurs in Grid, private Sparkcore, the SSP, Spark Operators, or several layers.",
        "sourceIds": [
          "source-4fac637bd4",
          "source-1b2b60f836",
          "source-b64f839f86",
          "source-eaaf6b593d"
        ]
      }
    ],
    "sources": [
      {
        "label": "InternalAccount",
        "url": "https://github.com/lightsparkdev/grid-api/blob/f53bd763aea58a71f2522d29ee4fc591e433ad36/openapi/components/schemas/customers/InternalAccount.yaml#L1-L23",
        "number": 1,
        "id": "source-4fac637bd4",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "InternalAccountType",
        "url": "https://github.com/lightsparkdev/grid-api/blob/f53bd763aea58a71f2522d29ee4fc591e433ad36/openapi/components/schemas/customers/InternalAccountType.yaml#L1-L19",
        "number": 2,
        "id": "source-1b2b60f836",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "InternalAccountStatus",
        "url": "https://github.com/lightsparkdev/grid-api/blob/f53bd763aea58a71f2522d29ee4fc591e433ad36/openapi/components/schemas/customers/InternalAccountStatus.yaml#L1-L25",
        "number": 3,
        "id": "source-b64f839f86",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Global Accounts on Spark",
        "url": "https://docs.lightspark.com/global-accounts/index",
        "number": 4,
        "id": "source-eaaf6b593d",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.lightspark.com"
      }
    ]
  },
  {
    "slug": "sparkscan-exposes-the-hidden-spark-identity-behind-lightning-payments",
    "track": "privacy",
    "title": "An unauthenticated network-wide feed exposes Spark identities behind Lightning payments",
    "cardTitle": "Sparkscan exposes the hidden Spark identity behind Lightning payments",
    "summary": "A sanitized unauthenticated mainnet sample returned Spark-side addresses and raw identity keys for every sampled Lightning record, together with exact amount and timestamp fields. The address is a durable wallet user ID even when an integrating app never shows it to the user.",
    "summarySourceIds": [
      "source-16b8fc9bea",
      "source-7c854330b8",
      "source-55b7da5619",
      "source-e825861365"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "In a sanitized read-only mainnet observation on 2026-07-14, an unauthenticated request to Sparkscan's latest-transactions endpoint returned 100 records. All 35 Lightning records contained the Spark-side address and raw identity public key, alongside exact amount and timestamp fields.",
        "sourceIds": [
          "source-16b8fc9bea"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Sparkscan documents that endpoint as returning network transactions “across all addresses,” with transaction type, status, exact amount, from/to identifiers, and creation and update times.",
        "sourceIds": [
          "source-7c854330b8"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Spark describes a Spark address as a user ID derived from the wallet identity key and says the same wallet always produces the same address. Spark separately says some Lightning-only applications never show that address to their users.",
        "sourceIds": [
          "source-55b7da5619",
          "source-e825861365"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Hiding the Spark address in a wallet interface does not hide it on the public data surface. A payer or merchant who knows a Lightning amount and time can search for a matching record, discover the otherwise-hidden durable Spark pseudonym, and then monitor any history that remains publicly queryable.",
        "sourceIds": [
          "source-16b8fc9bea",
          "source-7c854330b8",
          "source-55b7da5619",
          "source-e825861365"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The observation does not establish that the feed is complete or that any sampled user had enabled privacy mode. The amount-and-time identity match is a correlation method, not proof that every match is unique. Fully enforced privacy can still suppress covered Bitcoin queries when every relevant participant is private.",
        "sourceIds": [
          "source-16b8fc9bea",
          "source-e825861365"
        ]
      }
    ],
    "sources": [
      {
        "label": "sanitized mainnet observation",
        "url": "data/spark-mainnet-privacy-observation.json",
        "number": 1,
        "id": "source-16b8fc9bea",
        "verifiedAt": "2026-07-14",
        "type": "Reproducible audit manifest",
        "public": true,
        "immutable": true,
        "internal": true,
        "host": "Review of Spark's trust model"
      },
      {
        "label": "Sparkscan transaction API",
        "url": "https://docs.sparkscan.io/api/tx",
        "number": 2,
        "id": "source-7c854330b8",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.sparkscan.io"
      },
      {
        "label": "Spark addressing",
        "url": "https://docs.spark.money/wallets/addressing",
        "number": 3,
        "id": "source-55b7da5619",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "Spark privacy mode",
        "url": "https://docs.spark.money/wallets/privacy",
        "number": 4,
        "id": "source-e825861365",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      }
    ]
  },
  {
    "slug": "lightspark-says-it-handles-sanctions-screening-for-account-products-built-on-spark-wallets",
    "track": "privacy",
    "title": "Lightspark places sanctions operations in the operating layer of Spark-backed accounts",
    "cardTitle": "Lightspark says it handles sanctions screening for account products built on Spark wallets",
    "summary": "Lightspark describes Grid Global Accounts as running on Spark wallets. In the same product presentation, it says Lightspark handles identity verification, sanctions screening, risk review, and corridor compliance for the regulated movement layer.",
    "summarySourceIds": [
      "source-985cd0bcc1"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Lightspark says Grid Global Accounts “run on Spark” or are built on Spark wallets.",
        "sourceIds": [
          "source-985cd0bcc1"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "In the same product presentation, Lightspark says it handles identity verification, sanctions screening, risk review, and corridor compliance while powering the regulated movement underneath the platform.",
        "sourceIds": [
          "source-985cd0bcc1"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Sanctions operations are not merely an unrelated Lightspark business. Lightspark presents them as part of the operating layer of a commercial account product whose wallets run on Spark.",
        "sourceIds": [
          "source-985cd0bcc1"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The page does not disclose the internal screening provider or say an adverse result activates the SO wallet gate.",
        "sourceIds": [
          "source-985cd0bcc1"
        ]
      }
    ],
    "sources": [
      {
        "label": "Lightspark on-demand platforms",
        "url": "https://www.lightspark.com/businesses/on-demand-platforms",
        "number": 1,
        "id": "source-985cd0bcc1",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "lightspark.com"
      }
    ]
  },
  {
    "slug": "grids-contract-puts-compliance-judgment-transaction-execution-and-suspension-in-lightsparks-hands",
    "track": "privacy",
    "title": "Grid's contract assigns the compliance and execution decision to Lightspark",
    "cardTitle": "Grid’s contract puts compliance judgment, transaction execution, and suspension in Lightspark’s hands",
    "summary": "Lightspark's published Grid agreement template assigns Lightspark responsibility for end-user KYC, KYB, AML, and sanctions screening. It also assigns Lightspark control over its onboarding, verification, transaction-execution, suspension, and termination decisions.",
    "summarySourceIds": [
      "source-c37082e184"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Lightspark's published Grid MSA template makes Lightspark solely responsible for End-User KYC, KYB, AML, and sanctions-screening obligations.",
        "sourceIds": [
          "source-c37082e184"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "It gives Lightspark exclusive control over its decisions on onboarding, verification, transaction execution, suspension, and termination. A platform may instruct suspension or termination by API or written notice, including for “any other reason.”",
        "sourceIds": [
          "source-c37082e184"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "For the Spark-backed Grid product, Lightspark is not merely passive infrastructure awaiting a platform decision. The contract puts the compliance judgment and the execution/suspension response in Lightspark's hands.",
        "sourceIds": [
          "source-c37082e184"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This is a Grid template and does not establish identical terms for every customer or ordinary non-Grid Spark users.",
        "sourceIds": [
          "source-c37082e184"
        ]
      }
    ],
    "sources": [
      {
        "label": "Grid MSA section 2.5",
        "url": "https://github.com/lightsparkdev/grid-api/blob/f53bd763aea58a71f2522d29ee4fc591e433ad36/mintlify/legal/msa.mdx#L53-L61",
        "number": 1,
        "id": "source-c37082e184",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "lightsparks-grid-contract-expressly-permits-sanctions-based-refusal-reversal-and-freezing",
    "track": "privacy",
    "title": "The same contract expressly authorizes sanctions-based refusal and freezing",
    "cardTitle": "Lightspark’s Grid contract expressly permits sanctions-based refusal, reversal, and freezing",
    "summary": "Lightspark's published Grid agreement template permits it to refuse, delay, reverse, or freeze a Grid transaction because of law, suspected illegal activity, sanctions-screening results, or other identified risk. This is direct Grid evidence; it does not establish the enforcement path for ordinary non-Grid Spark users.",
    "summarySourceIds": [
      "source-ea7119d7ee",
      "source-bfa0fe6f0b"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The MSA says Lightspark may refuse, delay, reverse, or freeze a Grid transaction because of law, suspected fraud or illegal activity, sanctions-screening results, or another identified risk.",
        "sourceIds": [
          "source-bfa0fe6f0b"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "It separately permits Lightspark to modify, suspend, or discontinue a service component, supported network, or digital asset because of sanctions results and other risks.",
        "sourceIds": [
          "source-ea7119d7ee"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "The exact practical outcome alleged by the thesis—an adverse sanctions result producing transaction refusal or freeze—is already an express Lightspark contractual right for the Spark-backed product layer.",
        "sourceIds": [
          "source-ea7119d7ee",
          "source-bfa0fe6f0b"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The contract does not identify whether Grid, private Sparkcore, an SSP, an SO, or several components enforce a particular decision.",
        "sourceIds": [
          "source-ea7119d7ee",
          "source-bfa0fe6f0b"
        ]
      }
    ],
    "sources": [
      {
        "label": "Grid MSA section 2.1",
        "url": "https://github.com/lightsparkdev/grid-api/blob/f53bd763aea58a71f2522d29ee4fc591e433ad36/mintlify/legal/msa.mdx#L43-L45",
        "number": 1,
        "id": "source-ea7119d7ee",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "section 2.8(h)",
        "url": "https://github.com/lightsparkdev/grid-api/blob/f53bd763aea58a71f2522d29ee4fc591e433ad36/mintlify/legal/msa.mdx#L71-L81",
        "number": 2,
        "id": "source-bfa0fe6f0b",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "lightspark-says-grid-integrates-sanctions-checks-into-transactions-and-blocks-illicit-transfers-by-default",
    "track": "privacy",
    "title": "Lightspark says Grid integrates sanctions checks into transactions and blocks illicit transfers by default",
    "cardTitle": "Lightspark says Grid integrates sanctions checks into transactions and blocks illicit transfers by default",
    "summary": "Lightspark's sanctions explainer describes sender and receiver address checks when a transaction begins and immediate blocking when a match occurs. Its Grid description says compliance checks are integrated into transactions, including Bitcoin reward payments.",
    "summarySourceIds": [
      "source-ed5a10e1c1",
      "source-2f092920af"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Lightspark's own sanctions explainer describes the sanctions workflow as automatically checking both sending and receiving addresses when a transaction begins and blocking the transfer immediately when a match occurs.",
        "sourceIds": [
          "source-ed5a10e1c1"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Its “Grid Approach” says Grid integrates compliance directly into payment infrastructure, automatically checks transactions including Bitcoin rewards, and is designed to block illicit transfers by default.",
        "sourceIds": [
          "source-ed5a10e1c1",
          "source-2f092920af"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Destination-address screening and immediate blocking are not speculative policies attributed by critics; Lightspark describes them as intended Grid behavior. Grid also pays Bitcoin rewards to registered Spark wallet destinations.",
        "sourceIds": [
          "source-ed5a10e1c1",
          "source-2f092920af"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The explainer is high-level product material. It does not identify Spark addresses as the analytics input or disclose the Grid→Spark enforcement path.",
        "sourceIds": [
          "source-ed5a10e1c1",
          "source-2f092920af"
        ]
      }
    ],
    "sources": [
      {
        "label": "Lightspark sanctions explainer",
        "url": "https://www.lightspark.com/glossary/sanctions-screening",
        "number": 1,
        "id": "source-ed5a10e1c1",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "lightspark.com"
      },
      {
        "label": "Grid Rewards Spark-wallet flow",
        "url": "https://github.com/lightsparkdev/grid-api/blob/f53bd763aea58a71f2522d29ee4fc591e433ad36/mintlify/rewards/quickstart.mdx#L186-L230",
        "number": 2,
        "id": "source-2f092920af",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "one-public-participant-is-enough-to-expose-the-full-spark-transfer",
    "track": "privacy",
    "title": "A private participant's transfer remains readable when any counterparty is public",
    "cardTitle": "One public participant is enough to expose the full Spark transfer",
    "summary": "Spark grants public transfer access when any sender or receiver is readable, and its test suite preserves full participant detail in that case. Because outgoing Lightning leaves go to one fixed, publicly indexed SSP identity, this is a concrete privacy limitation rather than a hypothetical two-wallet edge case.",
    "summarySourceIds": [
      "source-d0ea1f9ed5",
      "source-0ee25660e2",
      "source-c7cffda559",
      "source-1dd9103534",
      "source-0bf860529d",
      "source-16b8fc9bea"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's current multi-sender/multi-receiver access check returns success as soon as any sender or receiver is publicly readable. It is an OR rule across participants, not an all-participants privacy rule.",
        "sourceIds": [
          "source-d0ea1f9ed5"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "A current repository test locks in the consequence: a public sender/private receiver transfer and a private sender/public receiver transfer are returned with participant detail, while a transfer whose participants are all private is filtered.",
        "sourceIds": [
          "source-0ee25660e2",
          "source-c7cffda559"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Outgoing Lightning payments transfer the user's selected Spark leaves to the SDK's fixed SSP identity. A dated unauthenticated mainnet query returned that exact SDK-published SSP identity with more than 2.25 million indexed transactions.",
        "sourceIds": [
          "source-1dd9103534",
          "source-0bf860529d",
          "source-16b8fc9bea"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Spark privacy is not compositional. A user can enable privacy and still have a specific payment's identity, amount, time, and relationship exposed through a public counterparty. The fixed SSP makes this a concrete concern for ordinary Lightning and swap flows rather than a contrived edge case between two hand-picked wallets.",
        "sourceIds": [
          "source-d0ea1f9ed5",
          "source-0ee25660e2",
          "source-1dd9103534",
          "source-0bf860529d",
          "source-16b8fc9bea"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The live observation proves that the SSP identity was publicly queryable, not that any sampled counterparty had privacy enabled. When every participant is private and enforcement is fully enabled, the tested implementation filters the transfer. This finding concerns transfer-record visibility, not access to wallet private keys.",
        "sourceIds": [
          "source-0ee25660e2",
          "source-16b8fc9bea"
        ]
      }
    ],
    "sources": [
      {
        "label": "multi-participant privacy rule",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/transfer_handler.go#L1933-L1968",
        "number": 1,
        "id": "source-d0ea1f9ed5",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "counterparty-privacy test",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/transfer_handler_query_by_id_test.go#L20-L69",
        "number": 2,
        "id": "source-0ee25660e2",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "public transfer fields",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/protos/spark.proto#L855-L900",
        "number": 3,
        "id": "source-c7cffda559",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Lightning transfer to fixed SSP",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/spark-wallet/spark-wallet.ts#L4173-L4215",
        "number": 4,
        "id": "source-1dd9103534",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "fixed SSP identity",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/wallet-config.ts#L11-L37",
        "number": 5,
        "id": "source-0bf860529d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "sanitized mainnet observation",
        "url": "data/spark-mainnet-privacy-observation.json",
        "number": 6,
        "id": "source-16b8fc9bea",
        "verifiedAt": "2026-07-14",
        "type": "Reproducible audit manifest",
        "public": true,
        "immutable": true,
        "internal": true,
        "host": "Review of Spark's trust model"
      }
    ]
  },
  {
    "slug": "lightspark-connect-exposes-chainalysis-backed-sanctions-screening-of-lightning-nodes",
    "track": "privacy",
    "title": "Lightspark Connect exposes Chainalysis-backed sanctions screening for Lightning nodes",
    "cardTitle": "Lightspark Connect exposes Chainalysis-backed sanctions screening of Lightning nodes",
    "summary": "Lightspark's official Connect SDK exposes screenNode for sanctions screening of a Lightning node using a configured provider token. The same SDK names Chainalysis, and archived official documentation describes screening a recipient node public key. This does not establish mandatory screening or Spark SSP use.",
    "summarySourceIds": [
      "source-f152b18d2c",
      "source-8c0d977466",
      "source-9d1a1c6341"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The official Connect SDK exposes `screenNode` for sanctions screening of a Lightning node and requires a valid API token for the selected compliance provider. The reviewed provider enum includes `CHAINALYSIS`.",
        "sourceIds": [
          "source-f152b18d2c",
          "source-8c0d977466"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Archived official Connect documentation describes the Chainalysis integration, customer API-key setup, and transmission of the receiver node's public key during screening.",
        "sourceIds": [
          "source-8c0d977466",
          "source-9d1a1c6341"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Lightspark already operates the exact class of control alleged in the narrower Lightning theory: inspect the Lightning counterparty with an external sanctions provider before payment.",
        "sourceIds": [
          "source-f152b18d2c",
          "source-8c0d977466",
          "source-9d1a1c6341"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This establishes a configurable Connect capability, not mandatory or default screening of every Connect payment and not a public call from the Spark SSP to Connect's `screenNode` mutation.",
        "sourceIds": [
          "source-f152b18d2c",
          "source-8c0d977466",
          "source-9d1a1c6341"
        ]
      }
    ],
    "sources": [
      {
        "label": "screenNode",
        "url": "https://github.com/lightsparkdev/js-sdk/blob/46c731cfaf9b1eeb9584d2176bc006076de3b2d1/packages/lightspark-sdk/src/client.ts#L1395-L1425",
        "number": 1,
        "id": "source-f152b18d2c",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "ComplianceProvider",
        "url": "https://github.com/lightsparkdev/js-sdk/blob/46c731cfaf9b1eeb9584d2176bc006076de3b2d1/packages/lightspark-sdk/src/objects/ComplianceProvider.ts#L3-L12",
        "number": 2,
        "id": "source-8c0d977466",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "archived official Lightspark Chainalysis guide",
        "url": "https://web.archive.org/web/20241224151636/https://docs.lightspark.com/lightspark-sdk/developer-guides/reconciliation-error-handling-testing",
        "number": 3,
        "id": "source-9d1a1c6341",
        "verifiedAt": "2026-07-14",
        "type": "Archived web source",
        "public": true,
        "immutable": true,
        "host": "web.archive.org"
      }
    ]
  },
  {
    "slug": "every-spark-operator-receives-the-full-lightning-invoice-not-merely-one-secret-share",
    "track": "privacy",
    "title": "Every Spark Operator receives the full Lightning invoice tied to the wallet identity",
    "cardTitle": "Every Spark Operator receives the full Lightning invoice—not merely one secret share",
    "summary": "Spark distributes the complete Lightning invoice, payment hash, amount, fee, direction, and wallet identities to every Operator while dividing only the cryptographic shares. Each Operator stores the original invoice beside the affected Spark identity.",
    "summarySourceIds": [
      "source-51c3222639",
      "source-1a72044d81",
      "source-645e870234",
      "source-fe5f91dc1c",
      "source-c64760b959",
      "source-7e09d01cfb"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "When creating a Lightning invoice, the SDK sends the coordinator the payment hash, complete BOLT11 invoice string, affected user's Spark identity, threshold, and one encrypted preimage share for every Operator.",
        "sourceIds": [
          "source-51c3222639"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The coordinator fans that original request to all Operators. Each Operator decrypts its own share and stores the full invoice string, payment hash, threshold, and user identity in its own database.",
        "sourceIds": [
          "source-1a72044d81",
          "source-645e870234"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The outgoing Lightning preimage-swap protocol likewise wraps the public request verbatim for all Operators. That request carries the BOLT11 invoice, exact sats, fee, payment hash, direction, receiver identity, and Spark transfer package.",
        "sourceIds": [
          "source-fe5f91dc1c",
          "source-c64760b959",
          "source-7e09d01cfb"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "The Lightning destination and invoice metadata are not visible only to the Lightning service that must route the payment. Spark deliberately replicates the full invoice beside the durable wallet identity across every Operator, allowing each Operator to link a Spark user to invoice amount, payment hash, memo or description hash, expiry, and Lightning payee.",
        "sourceIds": [
          "source-51c3222639",
          "source-1a72044d81",
          "source-645e870234",
          "source-fe5f91dc1c",
          "source-c64760b959",
          "source-7e09d01cfb"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Each Operator normally decrypts only its own preimage or key-tweak share; this does not prove that one Operator holds every secret share. An inbound Lightning invoice does not automatically reveal the original payer's real identity, and the sources do not establish Operator retention periods or staff access policy.",
        "sourceIds": [
          "source-1a72044d81",
          "source-645e870234",
          "source-fe5f91dc1c"
        ]
      }
    ],
    "sources": [
      {
        "label": "wallet preimage-share request",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/lightning.ts#L128-L179",
        "number": 1,
        "id": "source-51c3222639",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "all-Operator preimage-share fan-out",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/lightning_handler.go#L152-L285",
        "number": 2,
        "id": "source-1a72044d81",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "stored invoice and wallet identity",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/ent/schema/preimage_share.go#L31-L59",
        "number": 3,
        "id": "source-645e870234",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "all-Operator outgoing Lightning request",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/protos/spark_internal.proto#L544-L568",
        "number": 4,
        "id": "source-fe5f91dc1c",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "outgoing request fields",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/lightning.ts#L200-L278",
        "number": 5,
        "id": "source-c64760b959",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "BOLT 11 invoice fields",
        "url": "https://github.com/lightning/bolts/blob/94eb038c42e664dd7862faeec6508ccd25f63ff8/11-payment-encoding.md#L131-L160",
        "number": 6,
        "id": "source-7e09d01cfb",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "despite-an-announced-breez-ssp-both-reviewed-sdks-still-default-only-to-lightspark",
    "track": "privacy",
    "title": "Both reviewed default SDKs still expose one Lightspark SSP after an announced Breez SSP",
    "cardTitle": "Despite an announced Breez SSP, both reviewed SDKs still default only to Lightspark",
    "summary": "Spark permits multiple SSPs and Lightspark announced that Breez would operate one. The reviewed current Spark and Breez default configurations nevertheless identify the same single Lightspark SSP URL and identity.",
    "summarySourceIds": [
      "source-56182f9cb1",
      "source-98c093e969",
      "source-4009f37def",
      "source-cb886ceb70"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's FAQ says anyone can become an SSP and its goal is to maximize SSP diversity and redundancy. Lightspark is identified as the first.",
        "sourceIds": [
          "source-56182f9cb1"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Lightspark announced in May 2025 that Breez would operate an SSP alongside Lightspark. Current Spark and Breez configurations nevertheless expose the same single Lightspark URL and identity key; current published package artifacts preserve that configuration.",
        "sourceIds": [
          "source-98c093e969",
          "source-4009f37def",
          "source-cb886ceb70"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "The advertised provider plurality has not appeared in either public/default SDK family. Ordinary hosted flows therefore remain dependent on one private Lightspark policy layer.",
        "sourceIds": [
          "source-56182f9cb1",
          "source-98c093e969",
          "source-4009f37def",
          "source-cb886ceb70"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This proves public/default concentration, not exclusivity. A private or custom SSP may exist, and integrators can override the configuration.",
        "sourceIds": [
          "source-56182f9cb1",
          "source-4009f37def",
          "source-cb886ceb70"
        ]
      }
    ],
    "sources": [
      {
        "label": "Spark FAQ",
        "url": "https://docs.spark.money/learn/faq",
        "number": 1,
        "id": "source-56182f9cb1",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "Breez SSP announcement",
        "url": "https://www.lightspark.com/news/spark/breez-spark-partnership",
        "number": 2,
        "id": "source-98c093e969",
        "verifiedAt": "2026-07-14",
        "type": "Blog or announcement",
        "public": true,
        "immutable": false,
        "host": "lightspark.com"
      },
      {
        "label": "Spark SSP configuration",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/sdks/js/packages/spark-sdk/src/services/wallet-config.ts#L11-L38",
        "number": 3,
        "id": "source-4009f37def",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Breez SSP configuration",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark-wallet/src/config.rs#L51-L89",
        "number": 4,
        "id": "source-cb886ceb70",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "both-reviewed-sdks-fix-lightspark-so0-as-default-coordinator-without-automatic-failover",
    "track": "privacy",
    "title": "Lightspark is the fixed default coordinator and the reviewed SDKs do not fail over automatically",
    "cardTitle": "Both reviewed SDKs fix Lightspark SO0 as default coordinator without automatic failover",
    "summary": "The reviewed Spark and Breez SDK snapshots each configure three Operators with a 2-of-3 threshold but select Lightspark SO0 by fixed identifier or index. No automatic coordinator rotation or failover was found in either snapshot; integrators can supply custom configurations.",
    "summarySourceIds": [
      "source-251c106c7d",
      "source-bbc83e04f3",
      "source-7f2818a12d"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The public/default topology has three Operators and a 2-of-3 signing threshold, but selects Lightspark SO0 as one fixed coordinator.",
        "sourceIds": [
          "source-251c106c7d"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Numerous JavaScript flows call that configured coordinator; Breez's independent implementation likewise stores one coordinator index. Neither reviewed SDK contains automatic coordinator rotation or failover to SO1/SO2 when SO0 refuses or is unavailable.",
        "sourceIds": [
          "source-251c106c7d",
          "source-bbc83e04f3",
          "source-7f2818a12d"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "The threshold should not be mistaken for equivalent front-end liveness. Lightspark remains a practical coordination dependency for many ordinary operations even when two cryptographic signatures would suffice.",
        "sourceIds": [
          "source-251c106c7d",
          "source-bbc83e04f3",
          "source-7f2818a12d"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Custom configurations can choose another coordinator, and fixed coordination does not let SO0 forge the other Operators' signatures.",
        "sourceIds": [
          "source-251c106c7d",
          "source-bbc83e04f3",
          "source-7f2818a12d"
        ]
      }
    ],
    "sources": [
      {
        "label": "default topology and coordinator",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/sdks/js/packages/spark-sdk/src/services/wallet-config.ts#L184-L395",
        "number": 1,
        "id": "source-251c106c7d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "JavaScript coordinator lookup",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/sdks/js/packages/spark-sdk/src/services/config.ts#L207-L251",
        "number": 2,
        "id": "source-bbc83e04f3",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Breez coordinator pool",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark/src/operator/pool.rs#L17-L61",
        "number": 3,
        "id": "source-7f2818a12d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "spark-privacy-does-not-cover-a-globally-enumerable-token-ledger",
    "track": "privacy",
    "title": "Spark token transactions are publicly and globally enumerable",
    "cardTitle": "Spark privacy does not cover a globally enumerable token ledger",
    "summary": "Spark expressly excludes tokens from privacy mode, permits anonymous token queries with no wallet filter, and paginates the complete token-transaction table. Returned records expose owner public keys, exact amounts, outputs, status, and timestamps.",
    "summarySourceIds": [
      "source-e825861365",
      "source-098a27f73d",
      "source-6f3ecba399",
      "source-c45b4233c4",
      "source-15f0892207",
      "source-16b8fc9bea"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's privacy documentation expressly excludes token transactions. The Operator policy marks token metadata, transaction, and output query methods as unauthenticated.",
        "sourceIds": [
          "source-e825861365",
          "source-098a27f73d"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The official read-only client accepts an empty token-query filter. The server then starts from the complete token-transaction table and paginates it, rather than requiring a wallet address or transaction ID.",
        "sourceIds": [
          "source-6f3ecba399",
          "source-c45b4233c4"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Returned token records contain owner public keys, exact 128-bit amounts, transaction status, outputs, and timestamps. The sanitized live feed observation independently found mint, burn, single-output, and multi-output token records in an unauthenticated network sample.",
        "sourceIds": [
          "source-15f0892207",
          "source-16b8fc9bea"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Spark's token layer is not merely visible when an observer already knows a victim's address. It is a globally enumerable pseudonymous ledger from which an observer can discover addresses, follow inputs and outputs, identify change patterns, rank holdings, and build a transaction graph. Enabling Spark privacy does not alter that result.",
        "sourceIds": [
          "source-e825861365",
          "source-098a27f73d",
          "source-6f3ecba399",
          "source-c45b4233c4",
          "source-15f0892207",
          "source-16b8fc9bea"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Public keys are pseudonyms, not real-world identities by themselves. The design is publicly documented rather than a hidden backdoor, and it does not mean that privacy-enabled Bitcoin transfer history is globally enumerable through the same empty-filter path.",
        "sourceIds": [
          "source-e825861365",
          "source-c45b4233c4",
          "source-15f0892207"
        ]
      }
    ],
    "sources": [
      {
        "label": "Spark privacy mode",
        "url": "https://docs.spark.money/wallets/privacy",
        "number": 1,
        "id": "source-e825861365",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "unauthenticated token policy",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/rpcpolicy/policy.go#L225-L234",
        "number": 2,
        "id": "source-098a27f73d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "empty-filter read-only client",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/spark-readonly-client/spark-readonly-client.ts#L753-L823",
        "number": 3,
        "id": "source-6f3ecba399",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "global token query path",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/tokens/query_token_txs_handler.go#L454-L500",
        "number": 4,
        "id": "source-c45b4233c4",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "token output fields",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/protos/spark_token.proto#L70-L151",
        "number": 5,
        "id": "source-15f0892207",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "sanitized mainnet observation",
        "url": "data/spark-mainnet-privacy-observation.json",
        "number": 6,
        "id": "source-16b8fc9bea",
        "verifiedAt": "2026-07-14",
        "type": "Reproducible audit manifest",
        "public": true,
        "immutable": true,
        "internal": true,
        "host": "Review of Spark's trust model"
      }
    ]
  },
  {
    "slug": "flashnet-one-of-three-named-spark-operators-reserves-compliance-based-rejection-and-fund-freezing-in-its-service-terms",
    "track": "privacy",
    "title": "Flashnet itself reserves compliance-based transaction rejection and fund freezing",
    "cardTitle": "Flashnet—one of three named Spark Operators—reserves compliance-based rejection and fund freezing in its service terms",
    "summary": "Spark identifies Flashnet as one of its three Operators. Flashnet's published service terms exclude sanctioned users and state that its compliance policies may result in transaction rejection, service restriction, or fund freezing within Flashnet's application services.",
    "summarySourceIds": [
      "source-56182f9cb1",
      "source-d3052d801d"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's official FAQ names Flashnet as one of the three Spark Operators.",
        "sourceIds": [
          "source-56182f9cb1"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Flashnets Markets' terms exclude sanctioned persons and restricted territories, reserve geographic/IP/user blocking, and state that its AML, counter-terrorism, anti-fraud, and other compliance policies may lead it to reject transaction requests, freeze funds, or restrict service.",
        "sourceIds": [
          "source-d3052d801d"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "A second member of the three-Operator set—not only Lightspark—publicly embraces compliance-based transaction refusal and freezing within its commercial services. That makes policy alignment among the small operator group more plausible than a model in which the other Operators are categorically opposed to such controls.",
        "sourceIds": [
          "source-56182f9cb1",
          "source-d3052d801d"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Flashnet's terms govern its application Services. They do not disclose that SO2 uses the Spark wallet gate or applies sanctions policy to raw Spark consensus requests.",
        "sourceIds": [
          "source-56182f9cb1",
          "source-d3052d801d"
        ]
      }
    ],
    "sources": [
      {
        "label": "Spark FAQ",
        "url": "https://docs.spark.money/learn/faq",
        "number": 1,
        "id": "source-56182f9cb1",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "Flashnet Terms, access restrictions",
        "url": "https://www.flashnet.xyz/terms-of-service",
        "number": 2,
        "id": "source-d3052d801d",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "flashnet.xyz"
      }
    ]
  },
  {
    "slug": "every-spark-operator-receives-transfer-participants-and-records-not-merely-a-signing-share",
    "track": "privacy",
    "title": "Every Operator receives and stores transfer-participant metadata",
    "cardTitle": "Every Spark Operator receives transfer participants and records—not merely a signing share",
    "summary": "Spark's reviewed V3 transfer paths distribute participant identities and transfer records to every configured Operator. Operators outside a signing subset still persist the transfer rows, so threshold signing does not make transfer metadata Operator-blind.",
    "summarySourceIds": [
      "source-7fe4e68ea1",
      "source-88a2b7963f",
      "source-64583860e4",
      "source-09dbd74f97"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "In Spark's reviewed V3 consensus path, the coordinator selects every configured Operator. The participant-side implementation says `Prepare` runs on every SO and persists `Transfer` and `TransferLeaf` rows even on Operators outside the signing set.",
        "sourceIds": [
          "source-7fe4e68ea1",
          "source-88a2b7963f"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The reviewed legacy V3 path likewise sends every other Operator the sender identity, receiver identities, transfer package, and refund signatures. Each participant parses those identities and transactions before creating its local transfer rows.",
        "sourceIds": [
          "source-64583860e4",
          "source-09dbd74f97"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "FROST divides signing authority; it does not divide knowledge of who is transferring Spark leaves to whom. Compromise, compelled disclosure, or data use at any one Operator can therefore expose participant metadata without compromising the signing threshold.",
        "sourceIds": [
          "source-7fe4e68ea1",
          "source-88a2b7963f",
          "source-64583860e4",
          "source-09dbd74f97"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This establishes behavior in both reviewed V3 paths. It does not establish production retention periods, staff access, civil identity, or that any one Operator can sign a transfer by itself.",
        "sourceIds": [
          "source-7fe4e68ea1",
          "source-88a2b7963f",
          "source-64583860e4",
          "source-09dbd74f97"
        ]
      }
    ],
    "sources": [
      {
        "label": "all-Operator transfer persistence",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/send_transfer_flow_handler.go#L41-L88",
        "number": 1,
        "id": "source-7fe4e68ea1",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "all-Operator consensus selection",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/transfer_handler_mimo.go#L369-L382",
        "number": 2,
        "id": "source-88a2b7963f",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "legacy transfer fan-out",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/transfer_handler_mimo.go#L407-L446",
        "number": 3,
        "id": "source-64583860e4",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "participant-side transfer creation",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/internal_transfer_handler.go#L567-L661",
        "number": 4,
        "id": "source-09dbd74f97",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "a-normal-spark-wallet-identifies-itself-separately-to-every-operator",
    "track": "privacy",
    "title": "A normal wallet authenticates one persistent identity to every Operator",
    "cardTitle": "A normal Spark wallet identifies itself separately to every Operator",
    "summary": "A normal wallet authenticates the same durable identity to every Operator and queries each for that identity's leaves. Operator code can associate that pseudonym with connection metadata; structured request logging, when enabled, can also record request bodies and client details.",
    "summarySourceIds": [
      "source-c4d3460bf3",
      "source-7fc2cfcf3b",
      "source-5845bf4977",
      "source-49e455643a",
      "source-828ffbf245",
      "source-aba9f37ab0",
      "source-dd82f248c4",
      "source-9ceeb93504"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Ordinary wallet initialization creates a client for every configured signing Operator. Authentication to each endpoint presents and signs with the same wallet identity key; a full leaf synchronization then queries every Operator for leaves owned by that identity.",
        "sourceIds": [
          "source-c4d3460bf3",
          "source-7fc2cfcf3b",
          "source-5845bf4977"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Operator middleware places the verified identity into the request context and can resolve a client IP from trusted forwarding headers or the direct gRPC peer.",
        "sourceIds": [
          "source-49e455643a",
          "source-828ffbf245"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "When structured request logging is enabled, the reviewed logger can combine the identity with request bodies, origin, user agent, client environment, session ID, an authorization-token hash, and client IP. The generic protobuf formatter's automatic sensitive-field list contains only the exact field name `secret_share`.",
        "sourceIds": [
          "source-aba9f37ab0",
          "source-dd82f248c4",
          "source-9ceeb93504"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "A durable Spark pseudonym and connection metadata are simultaneously available at every Operator. Network-layer separation and a threshold signature do not make ordinary wallet activity Operator-blind.",
        "sourceIds": [
          "source-7fc2cfcf3b",
          "source-5845bf4977",
          "source-49e455643a",
          "source-828ffbf245",
          "source-aba9f37ab0"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "A Spark identity key is a pseudonym, not automatically a person's legal identity. Logging is configuration-dependent, retention is not public, and a VPN, proxy, or hosted wallet can cause an Operator to see an intermediary's network address rather than the end user's.",
        "sourceIds": [
          "source-49e455643a",
          "source-828ffbf245",
          "source-aba9f37ab0",
          "source-dd82f248c4"
        ]
      }
    ],
    "sources": [
      {
        "label": "wallet initialization",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/spark-wallet/spark-wallet.ts#L465-L485",
        "number": 1,
        "id": "source-c4d3460bf3",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "per-Operator identity authentication",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/connection/connection.ts#L325-L490",
        "number": 2,
        "id": "source-7fc2cfcf3b",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "all-Operator leaf synchronization",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/leaf-manager.ts#L292-L315",
        "number": 3,
        "id": "source-5845bf4977",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "identity-bound request context",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/authn/interceptor.go#L85-L155",
        "number": 4,
        "id": "source-49e455643a",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "client-IP extraction",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/middleware/headers.go#L14-L31",
        "number": 5,
        "id": "source-828ffbf245",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "request and client-metadata logger",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/common/logging/table.go#L143-L257",
        "number": 6,
        "id": "source-aba9f37ab0",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "conditional request-logger activation",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/bin/operator/main.go#L733-L737",
        "number": 7,
        "id": "source-dd82f248c4",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "protobuf redaction list",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/common/logging/proto.go#L14-L18",
        "number": 8,
        "id": "source-9ceeb93504",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "lightsparks-default-ssp-binds-invoices-and-bitcoin-destinations-to-the-operator-layer-wallet-identity",
    "track": "privacy",
    "title": "Lightspark's default SSP binds payment destinations to the same wallet identity used with Operators",
    "cardTitle": "Lightspark's default SSP binds invoices and Bitcoin destinations to the Operator-layer wallet identity",
    "summary": "The default wallet authenticates Lightspark's SSP with the same identity signer used at the Operator layer. The SSP model attaches that identity to request history containing full Lightning invoices and exact cooperative-exit Bitcoin destinations, creating a deterministic join key.",
    "summarySourceIds": [
      "source-a84d87cfa3",
      "source-99ed2d68bf",
      "source-f7ea55ee29",
      "source-4aae926ec2",
      "source-3fc06670da",
      "source-1a5ba9efdf",
      "source-400cb225ab",
      "source-0bf860529d",
      "source-ec3eddfbd9"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The wallet constructs its Operator services and SSP client from the same signer configuration. Both authentication flows present and sign with the wallet identity key.",
        "sourceIds": [
          "source-a84d87cfa3",
          "source-99ed2d68bf",
          "source-f7ea55ee29"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The public SSP schema connects a `SparkWalletUser` identity public key to paginated user-request history. Lightning-send records contain the complete BOLT11 invoice; cooperative-exit requests contain the exact Bitcoin withdrawal address and selected leaf IDs.",
        "sourceIds": [
          "source-4aae926ec2",
          "source-3fc06670da",
          "source-1a5ba9efdf",
          "source-400cb225ab"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The published mainnet default sends SSP requests to `api.lightspark.com` and names Lightspark SO0 as the fixed coordinator.",
        "sourceIds": [
          "source-0bf860529d",
          "source-ec3eddfbd9"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Lightspark does not need probabilistic blockchain analysis to associate a default wallet's SSP destinations with the Spark pseudonym it presents at the Operator interface. The same deterministic join key exists on both sides.",
        "sourceIds": [
          "source-99ed2d68bf",
          "source-f7ea55ee29",
          "source-4aae926ec2",
          "source-3fc06670da",
          "source-1a5ba9efdf",
          "source-400cb225ab",
          "source-0bf860529d",
          "source-ec3eddfbd9"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This proves default-architecture joinability and the destination-bearing SSP data model. It does not prove that Lightspark joins every dataset, how long records are retained, who can access them, or that a custom deployment cannot use another SSP or coordinator.",
        "sourceIds": [
          "source-a84d87cfa3",
          "source-4aae926ec2",
          "source-0bf860529d",
          "source-ec3eddfbd9"
        ]
      }
    ],
    "sources": [
      {
        "label": "wallet constructs the SSP client",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/spark-wallet/spark-wallet.ts#L285-L305",
        "number": 1,
        "id": "source-a84d87cfa3",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Operator identity authentication",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/connection/connection.ts#L438-L490",
        "number": 2,
        "id": "source-99ed2d68bf",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "SSP identity authentication",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/graphql/client.ts#L644-L720",
        "number": 3,
        "id": "source-f7ea55ee29",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "SSP wallet identity and request history",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/schemas/ssp_rc_schema.graphql#L1190-L1206",
        "number": 4,
        "id": "source-4aae926ec2",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "SSP Lightning-invoice record",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/schemas/ssp_rc_schema.graphql#L768-L803",
        "number": 5,
        "id": "source-3fc06670da",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "SSP cooperative-exit record",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/schemas/ssp_rc_schema.graphql#L299-L335",
        "number": 6,
        "id": "source-1a5ba9efdf",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "SSP cooperative-exit input",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/schemas/ssp_rc_schema.graphql#L990-L999",
        "number": 7,
        "id": "source-400cb225ab",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "default mainnet SSP endpoint",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/wallet-config.ts#L11-L37",
        "number": 8,
        "id": "source-0bf860529d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "default mainnet SSP and coordinator",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/wallet-config.ts#L265-L397",
        "number": 9,
        "id": "source-ec3eddfbd9",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "a-public-mode-spark-identity-exposes-exact-holdings-and-raw-bitcoin-transaction-material",
    "track": "privacy",
    "title": "Public-mode Spark exposes exact balances, leaf ownership, and raw Bitcoin transaction material",
    "cardTitle": "A public-mode Spark identity exposes exact holdings and raw Bitcoin transaction material",
    "summary": "For a publicly readable wallet, Spark's unauthenticated client can calculate its exact available balance and enumerate leaves. Returned records include ownership keys, tree relationships, exact values, timestamps, and multiple raw Bitcoin transaction fields.",
    "summarySourceIds": [
      "source-f916faf243",
      "source-b3a40353c5",
      "source-a4d3b5f183",
      "source-64a29de168"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's policy classifies node and balance queries as unauthenticated. The official read-only SDK exposes a `createPublic` client with authentication mode `none`.",
        "sourceIds": [
          "source-f916faf243",
          "source-b3a40353c5"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Given a Spark address, `getAvailableBalance` resolves its identity key, enumerates the wallet's readable leaves, and sums their exact satoshi values.",
        "sourceIds": [
          "source-b3a40353c5",
          "source-a4d3b5f183"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Returned node records include exact value, owner identity and signing keys, tree and parent relationships, status and timestamps, plus raw node, refund, direct, direct-refund, and direct-from-CPFP-refund transactions.",
        "sourceIds": [
          "source-64a29de168"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Once a public-mode Spark identity is learned, an unauthenticated observer can retrieve far more than a payment address: it can reconstruct current holdings and detailed Bitcoin transaction material around the wallet's leaves.",
        "sourceIds": [
          "source-f916faf243",
          "source-b3a40353c5",
          "source-a4d3b5f183",
          "source-64a29de168"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The observer must first know the Spark address, identity key, or node IDs. Enforced privacy mode can suppress covered queries. Raw transactions are not private keys and do not themselves confer spending authority.",
        "sourceIds": [
          "source-b3a40353c5",
          "source-a4d3b5f183",
          "source-64a29de168"
        ]
      }
    ],
    "sources": [
      {
        "label": "unauthenticated read policy",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/rpcpolicy/policy.go#L126-L170",
        "number": 1,
        "id": "source-f916faf243",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "public read-only client and balance query",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/spark-readonly-client/spark-readonly-client.ts#L75-L270",
        "number": 2,
        "id": "source-b3a40353c5",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "privacy-filtered node enumeration",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/tree_query_handler.go#L37-L166",
        "number": 3,
        "id": "source-a4d3b5f183",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "returned node fields",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/ent/treenode_extension.go#L17-L68",
        "number": 4,
        "id": "source-64a29de168",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "rotating-a-spark-deposit-address-leaves-the-operator-side-wallet-cluster-intact",
    "track": "privacy",
    "title": "Rotating a static deposit address does not rotate its Operator-side identity",
    "cardTitle": "Rotating a Spark deposit address leaves the Operator-side wallet cluster intact",
    "summary": "Spark describes static-address rotation as useful for privacy, but the implementation archives the old address and creates its replacement under the same immutable owner identity. That linkage is retained and distributed across the Operator set.",
    "summarySourceIds": [
      "source-cdb48ff208",
      "source-c5ba6f0ee5",
      "source-aa8c2dcac0",
      "source-bac2a7e6dd"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's implementation describes static-address rotation as useful for privacy, but archives the old address and creates its replacement under the same authenticated owner identity key.",
        "sourceIds": [
          "source-cdb48ff208"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Deposit-address rows bind each immutable address to an immutable owner identity. The address query groups records by that identity without excluding archived addresses.",
        "sourceIds": [
          "source-c5ba6f0ee5",
          "source-aa8c2dcac0"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Rotation gossips the owner identity and old address to every other Operator so that all Operators archive the same record.",
        "sourceIds": [
          "source-cdb48ff208",
          "source-bac2a7e6dd"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Rotation does not break the link between old and new deposit addresses for any Spark Operator. It is address hygiene against outside observers, not an Operator-unlinkability mechanism.",
        "sourceIds": [
          "source-cdb48ff208",
          "source-c5ba6f0ee5",
          "source-aa8c2dcac0",
          "source-bac2a7e6dd"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Rotation can still reduce address reuse and improve privacy against external Bitcoin-chain observers. The precise finding is the retained Operator-side cluster; public queryability remains subject to privacy enforcement.",
        "sourceIds": [
          "source-cdb48ff208",
          "source-aa8c2dcac0"
        ]
      }
    ],
    "sources": [
      {
        "label": "static-address rotation and identity",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/deposit_handler.go#L684-L830",
        "number": 1,
        "id": "source-cdb48ff208",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "immutable address-to-identity records",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/ent/schema/deposit_address.go#L28-L68",
        "number": 2,
        "id": "source-c5ba6f0ee5",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "identity-grouped static-address query",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/handler/tree_query_handler.go#L400-L441",
        "number": 3,
        "id": "source-aa8c2dcac0",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "archive gossip fields",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/protos/gossip.proto#L181-L190",
        "number": 4,
        "id": "source-bac2a7e6dd",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "sparks-forward-security-rests-on-an-act-of-key-deletion-users-cannot-verify",
    "track": "privacy",
    "title": "Spark's forward security depends on unverifiable deletion of old Operator shares",
    "cardTitle": "Spark's forward security rests on an act of key deletion users cannot verify",
    "summary": "Spark's own research calls deletion of historical Operator shares an unverifiable operational promise. The implementation contains real ephemeral-storage and cleanup controls, but it permits brief version coexistence, best-effort deletion, and a documented legacy main-database fallback.",
    "summarySourceIds": [
      "source-d1faa47e2c",
      "source-63b8094eb4",
      "source-012afa7925",
      "source-36997a93ba",
      "source-ae797f7959"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's own statechain research calls old-key deletion an operational promise rather than a cryptographic guarantee and says there is no proof or on-chain mechanism that can verify data destruction.",
        "sourceIds": [
          "source-d1faa47e2c",
          "source-63b8094eb4"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The reviewed implementation creates a separate ephemeral database for secrets that must not be backed up, rotates secret versions, and attempts to delete prior versions. Its documentation says old and new versions may coexist briefly and describes deletion as best effort, backed by a later cleanup task.",
        "sourceIds": [
          "source-012afa7925",
          "source-36997a93ba",
          "source-ae797f7959"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The same implementation documents a legacy fallback that stores the secret share in the main database when the ephemeral database is unavailable; public sources do not attest which production storage and backup configuration each Operator uses.",
        "sourceIds": [
          "source-012afa7925"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Users cannot independently verify the act on which forward security depends. If a threshold of historical Operator shares and the matching former-owner key survive, those keys can still authorize a fresh conflicting spend.",
        "sourceIds": [
          "source-d1faa47e2c",
          "source-63b8094eb4",
          "source-012afa7925",
          "source-36997a93ba",
          "source-ae797f7959"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The public code contains concrete deletion and cleanup controls. Nothing reviewed shows those controls failing in production or any Operator intentionally retaining an old share.",
        "sourceIds": [
          "source-012afa7925",
          "source-36997a93ba",
          "source-ae797f7959"
        ]
      }
    ],
    "sources": [
      {
        "label": "Spark statechain key-deletion analysis",
        "url": "https://www.spark.money/research/statechains-bitcoin-scaling-deep-dive",
        "number": 1,
        "id": "source-d1faa47e2c",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "spark.money"
      },
      {
        "label": "Spark trust model",
        "url": "https://docs.spark.money/learn/trust-model",
        "number": 2,
        "id": "source-63b8094eb4",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "ephemeral secret-storage design",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/entephemeral/README.md",
        "number": 3,
        "id": "source-012afa7925",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "old-version deletion logic",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/ent/signingkeyshare_extension.go#L205-L253",
        "number": 4,
        "id": "source-36997a93ba",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Operator share rotation",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/ent/signingkeyshare_extension.go#L847-L878",
        "number": 5,
        "id": "source-ae797f7959",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "sparks-published-2-of-3-setting-means-one-honest-operator-cannot-veto-two-retained-shares",
    "track": "privacy",
    "title": "Under the published 2-of-3 mainnet configuration, one honest Operator cannot prevent a retained-share attack",
    "cardTitle": "Spark's published 2-of-3 setting means one honest Operator cannot veto two retained shares",
    "summary": "The published mainnet SDK inherits a two-of-three Operator threshold. Because the user side combines with any two Operator shares, one honest or nonparticipating Operator cannot by itself prevent two matching retained shares from satisfying the Operator quorum.",
    "summarySourceIds": [
      "source-ec3eddfbd9",
      "source-c7f347b1ff",
      "source-0f8ac3c652",
      "source-d1faa47e2c"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's published base configuration sets the Operator threshold to two. The mainnet configuration inherits that value and lists three Operators: Lightspark, Breez, and Flashnet.",
        "sourceIds": [
          "source-ec3eddfbd9"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Operator code describes signing as `1 + (t, n)`: the user supplies one side and `config.Threshold` Operators supply the group side. DKG likewise sets `MinSigners` to the configured threshold.",
        "sourceIds": [
          "source-c7f347b1ff",
          "source-0f8ac3c652"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Any two matching Operator shares therefore satisfy the Operator side of the published default signature; participation by the third Operator is not required.",
        "sourceIds": [
          "source-ec3eddfbd9",
          "source-c7f347b1ff",
          "source-0f8ac3c652"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "Spark's general research says one honest Operator deleting its old share defeats prior-owner collusion. Under the published two-of-three setting, one honest or nonparticipating Operator cannot veto the other two: preventing reconstruction requires at least two Operators to have deleted or withheld their matching old shares.",
        "sourceIds": [
          "source-ec3eddfbd9",
          "source-c7f347b1ff",
          "source-0f8ac3c652",
          "source-d1faa47e2c"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This compares Spark's general trust-model explanation with its published SDK and signing code. It is not an independent attestation of the live servers' threshold, storage, or software, and it is not evidence that any old shares were retained.",
        "sourceIds": [
          "source-ec3eddfbd9",
          "source-d1faa47e2c"
        ]
      }
    ],
    "sources": [
      {
        "label": "published threshold and three Operators",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/wallet-config.ts#L265-L397",
        "number": 1,
        "id": "source-ec3eddfbd9",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "one-plus-threshold signing construction",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/helper/signing_coordinator.go#L433-L480",
        "number": 2,
        "id": "source-c7f347b1ff",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "DKG minimum signers",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/dkg/coordinator.go#L40-L52",
        "number": 3,
        "id": "source-0f8ac3c652",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Spark's one-honest-Operator explanation",
        "url": "https://www.spark.money/research/statechains-bitcoin-scaling-deep-dive",
        "number": 4,
        "id": "source-d1faa47e2c",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "spark.money"
      }
    ]
  },
  {
    "slug": "retained-lightspark-and-flashnet-keys-could-sign-a-conflicting-spend-of-a-lightspark-funded-lightning-leaf",
    "track": "privacy",
    "title": "Retained old keys would let Lightspark and Flashnet sign a conflicting spend of a Lightspark-funded Lightning leaf",
    "cardTitle": "Retained Lightspark and Flashnet keys could sign a conflicting spend of a Lightspark-funded Lightning leaf",
    "summary": "In Spark's reference inbound-Lightning flow, the SSP is the former owner of the leaf. Under the published two-of-three configuration, retained matching keys from the Lightspark SSP, Lightspark Operator, and Flashnet Operator would be sufficient to sign a fresh conflicting spend; no evidence shows retention or collusion.",
    "summarySourceIds": [
      "source-0bf860529d",
      "source-ec3eddfbd9",
      "source-3cb19f803b",
      "source-299eb4d5c2",
      "source-124e40dde4",
      "source-c7f347b1ff",
      "source-63b8094eb4",
      "source-aea66db78d",
      "source-444822d77f"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's reference inbound-Lightning flow has the SSP create and own a leaf, transfer it to the receiving user, and deliver the next user-side signing key; the receiver then claims the transfer with a newly generated final key.",
        "sourceIds": [
          "source-3cb19f803b"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "The published mainnet default points the SSP to Lightspark and uses a two-of-three Operator set comprising Lightspark, Breez, and Flashnet.",
        "sourceIds": [
          "source-0bf860529d",
          "source-ec3eddfbd9"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "Spark's transfer construction adds the difference between the old and new user keys to the Operator key and then derives the new owner key from the unchanged leaf verifying key. Algebraically, `new_user + new_operator = old_user + old_operator = leaf_verifying_key`. A matching historical user key and threshold set of historical Operator shares therefore remain a valid signing state if all were retained.",
        "sourceIds": [
          "source-299eb4d5c2",
          "source-124e40dde4",
          "source-c7f347b1ff",
          "source-63b8094eb4"
        ]
      },
      {
        "label": "Documented behavior",
        "text": "For a Lightspark-SSP-funded leaf, the former-owner role and one Operator role are at Lightspark; the Flashnet Operator supplies the second share needed by the published threshold. Flashnet separately says it helped build Spark, and a Lightspark co-founder's published CV lists an advisory role at Flashnet. Those documented working and personnel ties are context only—not evidence of common control, shared key access, or collusion.",
        "sourceIds": [
          "source-0bf860529d",
          "source-ec3eddfbd9",
          "source-c7f347b1ff",
          "source-aea66db78d",
          "source-444822d77f"
        ]
      }
    ],
    "analysis": [
      {
        "label": "Interpretation",
        "text": "If Lightspark retained the SSP's matching former owner key and old Lightspark Operator share, and Flashnet retained its matching old Operator share, those roles possess enough cryptographic material to sign a fresh transaction conflicting with the recipient's still-unspent leaf. The nonparticipation of Breez would not block that signature.",
        "sourceIds": [
          "source-0bf860529d",
          "source-ec3eddfbd9",
          "source-3cb19f803b",
          "source-299eb4d5c2",
          "source-124e40dde4",
          "source-c7f347b1ff",
          "source-63b8094eb4"
        ]
      }
    ],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This is a conditional cryptographic result, not an allegation of conduct. The keys must match the same leaf and historical state. Public evidence does not show retention, collusion, a conflicting signature, or a theft; the mainnet configuration is published client code rather than a live server attestation, and the production Lightspark SSP is not public. The attack assumes modified or offline signing behavior, not honest API use, and directly concerns the same unspent leaf rather than unrelated wallet funds.",
        "sourceIds": [
          "source-0bf860529d",
          "source-ec3eddfbd9",
          "source-3cb19f803b",
          "source-63b8094eb4",
          "source-aea66db78d",
          "source-444822d77f"
        ]
      }
    ],
    "sources": [
      {
        "label": "default mainnet SSP",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/wallet-config.ts#L11-L37",
        "number": 1,
        "id": "source-0bf860529d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "published mainnet Operator configuration",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/services/wallet-config.ts#L265-L397",
        "number": 2,
        "id": "source-ec3eddfbd9",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "reference SSP-funded Lightning receive",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/grpc_test/lightning_test.go#L118-L201",
        "number": 3,
        "id": "source-3cb19f803b",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "client transfer-key tweak",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/sdks/js/packages/spark-sdk/src/signer/signer.ts#L395-L429",
        "number": 4,
        "id": "source-299eb4d5c2",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "server share update and invariant verifying key",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/helper/key_tweak_helper.go#L76-L121",
        "number": 5,
        "id": "source-124e40dde4",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "one-plus-threshold signing construction",
        "url": "https://github.com/buildonspark/spark/blob/f1f968e68daa74d22e3398ce126302e35ac47f95/spark/so/helper/signing_coordinator.go#L433-L480",
        "number": 6,
        "id": "source-c7f347b1ff",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Spark key-deletion trust assumption",
        "url": "https://docs.spark.money/learn/trust-model",
        "number": 7,
        "id": "source-63b8094eb4",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "Flashnet build relationship",
        "url": "https://www.flashnet.xyz/writing/seed-round-1",
        "number": 8,
        "id": "source-aea66db78d",
        "verifiedAt": "2026-07-14",
        "type": "Blog or announcement",
        "public": true,
        "immutable": false,
        "host": "flashnet.xyz"
      },
      {
        "label": "published Lightspark-Flashnet advisory role",
        "url": "https://static1.squarespace.com/static/532383d3e4b00a718e33e1da/t/68261be1070c3c5a48155e3b/1747327969367/CV_Catalini_May_2025.pdf",
        "number": 9,
        "id": "source-444822d77f",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "static1.squarespace.com"
      }
    ]
  },
  {
    "slug": "no-complete-in-app-operatorless-exit-was-found-in-eight-reviewed-consumer-wallet-products",
    "track": "exit",
    "title": "No complete in-app unilateral exit was found across eight reviewed consumer-wallet surfaces",
    "cardTitle": "No complete in-app operatorless exit was found in eight reviewed consumer-wallet products",
    "summary": "The reviewed snapshots and public interfaces for Blink, Primal, Cake, Blitz, Radar, Club Orange, Wallet of Satoshi, and Bringin exposed no complete in-app sequence for preserving current exit state, supplying CPFP funds, ordered broadcasting, durable resume, timelock handling, and final sweeping. Closed-source findings are limited to public claims and observed interfaces.",
    "summarySourceIds": [
      "source-8b4af7513d",
      "source-cd1dc8f3a1",
      "source-bbf4f4f230",
      "source-03ef7ff7cd",
      "source-fca02e3502",
      "source-7a7287fdc3",
      "source-75c2bcb0fa",
      "source-57090499c1",
      "source-3bb9cc3854",
      "source-1b19988d50",
      "source-baab8de22f"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "At the pinned snapshots and review date, no complete in-app flow covering state acquisition, fee funding, ordered package broadcast, durable progress, timelock waiting, refund broadcast, and final sweep was found for Blink, Primal, Cake, Blitz, Radar, Club Orange, Wallet of Satoshi, or Bringin.",
        "sourceIds": [
          "source-8b4af7513d",
          "source-cd1dc8f3a1",
          "source-bbf4f4f230",
          "source-03ef7ff7cd",
          "source-fca02e3502",
          "source-7a7287fdc3",
          "source-75c2bcb0fa",
          "source-57090499c1",
          "source-3bb9cc3854",
          "source-1b19988d50",
          "source-baab8de22f"
        ]
      },
      {
        "label": "Open-source and binary review",
        "text": "Blink, Primal, Cake, Radar, and Blitz were reviewed at named commits. Club Orange was reviewed through its published 4.1.8 APK and release record. The audit terms, exclusions, artifact hash, and results are recorded in the consumer-wallet audit manifest.",
        "sourceIds": [
          "source-8b4af7513d",
          "source-cd1dc8f3a1",
          "source-bbf4f4f230",
          "source-03ef7ff7cd",
          "source-fca02e3502",
          "source-7a7287fdc3",
          "source-75c2bcb0fa",
          "source-57090499c1",
          "source-baab8de22f"
        ]
      },
      {
        "label": "Closed-source review",
        "text": "Wallet of Satoshi and Bringin publish custody or exit claims but not their mobile-app source or a complete consumer unilateral-exit procedure.",
        "sourceIds": [
          "source-3bb9cc3854",
          "source-1b19988d50"
        ]
      },
      {
        "label": "Product consequence",
        "text": "The reviewed record did not demonstrate an ordinary mobile user completing the full operatorless path inside any of the eight wallet interfaces.",
        "sourceIds": [
          "source-8b4af7513d",
          "source-cd1dc8f3a1",
          "source-bbf4f4f230",
          "source-03ef7ff7cd",
          "source-fca02e3502",
          "source-7a7287fdc3",
          "source-75c2bcb0fa",
          "source-57090499c1",
          "source-3bb9cc3854",
          "source-1b19988d50",
          "source-baab8de22f"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This is a dated review of named public repositories, one published binary, and public product records. It cannot rule out unavailable code, obfuscated functionality, or later versions. For Wallet of Satoshi and Bringin, the finding is limited to publicly verifiable claims, instructions, and product behavior.",
        "sourceIds": [
          "source-baab8de22f",
          "source-3bb9cc3854",
          "source-1b19988d50"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink mobile",
        "url": "https://github.com/blinkbitcoin/blink-mobile/tree/3f3108998b1db6bf55823284e932ff6313eea496",
        "number": 1,
        "id": "source-8b4af7513d",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Primal Android",
        "url": "https://github.com/PrimalHQ/primal-android-app/tree/c16997628c5111e56ac8b98b49d4297f5d52e480",
        "number": 2,
        "id": "source-cd1dc8f3a1",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Primal iOS",
        "url": "https://github.com/PrimalHQ/primal-ios-app/tree/91787a1209bd13f57afcb5fe100783102feae2fb",
        "number": 3,
        "id": "source-bbf4f4f230",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Cake",
        "url": "https://github.com/cake-tech/cake_wallet/tree/bff9b2fa9648b22c2fcc84062caba58861e9d24f",
        "number": 4,
        "id": "source-03ef7ff7cd",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar Android",
        "url": "https://github.com/radar-labs/radar-android/tree/8ca3e2b147fa32648afb04f8a42c4dd4b7db9eda",
        "number": 5,
        "id": "source-fca02e3502",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar iOS",
        "url": "https://github.com/radar-labs/Radar/tree/661a39cce1b9d270b6ae9d0914c15a836d103668",
        "number": 6,
        "id": "source-7a7287fdc3",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blitz",
        "url": "https://github.com/BlitzWallet/BlitzWallet/tree/173298bae4cb650fde4681ee7398d2e651bfbdae",
        "number": 7,
        "id": "source-75c2bcb0fa",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Club Orange releases",
        "url": "https://github.com/ClubOrangeBitcoin/ClubOrange-releases/releases/tag/v4.1.8",
        "number": 8,
        "id": "source-57090499c1",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "Wallet of Satoshi disclosure",
        "url": "https://walletofsatoshi.com/disclosure",
        "number": 9,
        "id": "source-3bb9cc3854",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "walletofsatoshi.com"
      },
      {
        "label": "Bringin launch",
        "url": "https://bringin.app/blog/bitcoin-payments-app-in-europe",
        "number": 10,
        "id": "source-1b19988d50",
        "verifiedAt": "2026-07-14",
        "type": "Blog or announcement",
        "public": true,
        "immutable": false,
        "host": "bringin.app"
      },
      {
        "label": "consumer-wallet exit audit manifest",
        "url": "data/consumer-wallet-exit-audit.json",
        "number": 11,
        "id": "source-baab8de22f",
        "verifiedAt": "2026-07-14",
        "type": "Reproducible audit manifest",
        "public": true,
        "immutable": true,
        "internal": true,
        "host": "Review of Spark's trust model"
      }
    ]
  },
  {
    "slug": "a-spark-destination-amount-does-not-disclose-the-exits-net-economic-recovery",
    "track": "exit",
    "title": "A Spark destination amount does not disclose the exit's net economic recovery",
    "cardTitle": "A Spark destination amount does not disclose the exit’s net economic recovery",
    "summary": "Blink’s exit mechanics separate the amount eventually swept from the Spark wallet, leaf value excluded by economic triage, and independent on-chain bitcoin spent on CPFP and funding transactions. A destination amount is therefore not the same measure as net economic recovery.",
    "summarySourceIds": [
      "source-b688829de4",
      "source-300d848d16",
      "source-e67505f5bd"
    ],
    "evidence": [
      {
        "label": "Fee mechanism",
        "text": "Spark exit and refund transactions are pre-signed at zero fee; the user must fund their CPFP fee bumps from an independent on-chain UTXO.",
        "sourceIds": [
          "source-b688829de4"
        ]
      },
      {
        "label": "Leaf triage",
        "text": "Blink's recovery tool prices each leaf separately using the CPFP cost of its chain plus the final sweep cost, and skips leaves whose recovery cost exceeds their value by default.",
        "sourceIds": [
          "source-300d848d16"
        ]
      },
      {
        "label": "Accounting consequence",
        "text": "Value swept to the destination, Spark value omitted as uneconomical, and separately supplied on-chain fee funding are different components. The destination amount alone therefore cannot establish the user's net economic recovery.",
        "sourceIds": [
          "source-b688829de4",
          "source-300d848d16",
          "source-e67505f5bd"
        ]
      },
      {
        "label": "Additional cost",
        "text": "The transaction that funds the CPFP address is itself outside the displayed Spark balance and outside the destination amount.",
        "sourceIds": [
          "source-b688829de4",
          "source-e67505f5bd"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This does not establish that every Spark exit is uneconomical. The result depends on fee rate and each leaf's value and chain depth; large, shallow leaves can remain economical.",
        "sourceIds": [
          "source-300d848d16",
          "source-e67505f5bd"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink fee-funding mechanics",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L40-L55",
        "number": 1,
        "id": "source-b688829de4",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink leaf economics",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L79-L98",
        "number": 2,
        "id": "source-300d848d16",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink exit accounting",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L99-L130",
        "number": 3,
        "id": "source-e67505f5bd",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "breez-0-19-0-ships-no-public-mobile-exit-api-its-draft-api-still-contacts-an-operator",
    "track": "exit",
    "title": "Breez still lacks a public mobile exit API, and even its proposed API prepares by contacting a live operator",
    "cardTitle": "Breez 0.19.0 ships no public mobile exit API; its draft API still contacts an operator",
    "summary": "Breez still does not expose a public mobile unilateral-exit API. Its open draft implementation begins by listing live leaves and reaching the coordinator-backed service; neither the shipped path nor the proposal starts from an offline recovery bundle.",
    "summarySourceIds": [
      "source-f178c2190e",
      "source-bc18e0dd62",
      "source-caa2acfafe",
      "source-3cc2feb29d"
    ],
    "evidence": [
      {
        "label": "Released interface",
        "text": "Breez labeled issue 374 High priority for milestone 0.14.0, but its public API remained unshipped through 0.19.0.",
        "sourceIds": [
          "source-f178c2190e",
          "source-caa2acfafe"
        ]
      },
      {
        "label": "Development record",
        "text": "Pull request 795 remained open, Draft, and non-mergeable/dirty after releases 0.14.0, 0.15.0, 0.18.0, and 0.19.0.",
        "sourceIds": [
          "source-bc18e0dd62",
          "source-caa2acfafe"
        ]
      },
      {
        "label": "Operator dependency",
        "text": "The shipped lower-level service unconditionally fetches leaves and parents from the coordinator. The proposed public API first lists live leaves and then reaches the same service.",
        "sourceIds": [
          "source-bc18e0dd62",
          "source-3cc2feb29d"
        ]
      },
      {
        "label": "Missing interface",
        "text": "Neither path accepts a pre-saved offline recovery bundle as its starting input.",
        "sourceIds": [
          "source-bc18e0dd62",
          "source-3cc2feb29d"
        ]
      },
      {
        "label": "Product consequence",
        "text": "The documented mobile surface has no exit call, and the unmerged design still cannot begin preparation after all operators have disappeared.",
        "sourceIds": [
          "source-f178c2190e",
          "source-bc18e0dd62",
          "source-caa2acfafe",
          "source-3cc2feb29d"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This finding is limited to Breez 0.19.0, the cited open issue, the unmerged draft, and the reviewed lower-level service. It does not rule out a later API or a separate wallet-owned recovery implementation.",
        "sourceIds": [
          "source-f178c2190e",
          "source-bc18e0dd62",
          "source-caa2acfafe",
          "source-3cc2feb29d"
        ]
      }
    ],
    "sources": [
      {
        "label": "issue 374",
        "url": "https://github.com/breez/spark-sdk/issues/374",
        "number": 1,
        "id": "source-f178c2190e",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "pull request 795",
        "url": "https://github.com/breez/spark-sdk/pull/795",
        "number": 2,
        "id": "source-bc18e0dd62",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "Breez releases",
        "url": "https://github.com/breez/spark-sdk/releases",
        "number": 3,
        "id": "source-caa2acfafe",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "current unilateral-exit service",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark/src/services/unilateral_exit.rs#L83-L195",
        "number": 4,
        "id": "source-3cc2feb29d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "a-seed-alone-is-not-an-operator-outage-recovery-backup-for-spark",
    "track": "exit",
    "title": "A seed alone is not an operator-outage recovery backup for Spark",
    "cardTitle": "A seed alone is not an operator-outage recovery backup for Spark",
    "summary": "Blink's published case study states that a seed cannot rediscover current leaves after Operators are offline. Its documented procedure requires complete, current, validated leaf and ancestor state, represented by a recovery bundle saved while Operators are reachable; with that state, the seed still supplies signing authority.",
    "summarySourceIds": [
      "source-346505b7fa",
      "source-f7944a4853"
    ],
    "evidence": [
      {
        "label": "State requirement",
        "text": "Blink states that current leaves cannot be discovered from the seed alone after Spark Operators are offline.",
        "sourceIds": [
          "source-346505b7fa"
        ]
      },
      {
        "label": "State representation",
        "text": "Blink's procedure stores the required current leaves and ancestor transactions in a recovery bundle. Another implementation could persist equivalent complete state in a different format.",
        "sourceIds": [
          "source-346505b7fa"
        ]
      },
      {
        "label": "Online preparation",
        "text": "The required state must be refreshed and validated while Operators remain reachable; the seed does not encode later changes to the wallet's Spark tree.",
        "sourceIds": [
          "source-346505b7fa"
        ]
      },
      {
        "label": "Outage consequence",
        "text": "If current, complete exit state was not saved in advance, the documented offline procedure has no graph from which to construct the exit.",
        "sourceIds": [
          "source-346505b7fa"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The seed still derives the signing keys, fee-funding address, and final sweep. Complete current leaf-and-ancestor state need not use Blink's JSON format, but it must exist before an all-Operator outage. With that state and fee funds, the documented exit proceeds without later Operator cooperation.",
        "sourceIds": [
          "source-346505b7fa",
          "source-f7944a4853"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink recovery-bundle requirement",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L19-L38",
        "number": 1,
        "id": "source-346505b7fa",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink seed-derived operations",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L209-L219",
        "number": 2,
        "id": "source-f7944a4853",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "breezs-default-mobile-persistence-does-not-retain-the-tree-needed-for-an-outage-exit",
    "track": "exit",
    "title": "Breez's default mobile persistence does not retain the needed exit tree",
    "cardTitle": "Breez’s default mobile persistence does not retain the tree needed for an outage exit",
    "summary": "The reviewed Breez mobile configuration stores ordinary wallet data in SQLite but supplies no persistent tree store, falling back to an in-memory tree store. The lower-level exit service requires tree nodes that this default mobile path does not durably retain.",
    "summarySourceIds": [
      "source-7ddb8a4a23",
      "source-2e849b3812",
      "source-bfb4f2bba6"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The default Breez SQLite backend returns tree_store: None.",
        "sourceIds": [
          "source-7ddb8a4a23"
        ]
      },
      {
        "label": "Technical details",
        "text": "WalletBuilder replaces a missing store with InMemoryTreeStore; normal leaf refresh uses include_parents=false; process death therefore does not preserve the ancestor graph through that default tree-store path.",
        "sourceIds": [
          "source-2e849b3812",
          "source-bfb4f2bba6"
        ]
      },
      {
        "label": "Product consequence",
        "text": "Durable operator-outage state is an additional integration feature, not a property supplied by the ordinary seed/SQLite setup.",
        "sourceIds": [
          "source-7ddb8a4a23",
          "source-2e849b3812",
          "source-bfb4f2bba6"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "A wallet developer can supply another persistent tree store or a separate encrypted recovery-state exporter; this finding is limited to the reviewed default persistence path.",
        "sourceIds": [
          "source-7ddb8a4a23",
          "source-2e849b3812"
        ]
      }
    ],
    "sources": [
      {
        "label": "SQLite backend",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/breez-sdk/core/src/persist/backend/sqlite.rs#L29-L40",
        "number": 1,
        "id": "source-7ddb8a4a23",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "WalletBuilder fallback",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark-wallet/src/wallet_builder.rs#L115-L127",
        "number": 2,
        "id": "source-2e849b3812",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "normal leaf query",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark/src/tree/service.rs#L197-L211",
        "number": 3,
        "id": "source-bfb4f2bba6",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "spark-exit-backups-must-track-wallet-mutations-a-one-time-seed-backup-cannot-stay-current",
    "track": "exit",
    "title": "Exit-state backup must track wallet mutations; a one-time seed backup cannot encode later state",
    "cardTitle": "Spark exit backups must track wallet mutations; a one-time seed backup cannot stay current",
    "summary": "Spark exit state can change when leaves are created, transferred, split, renewed, claimed, or spent. Blink's proposed mobile design therefore refreshes its recovery state after balance-changing events; a mnemonic backup made once cannot encode later tree mutations.",
    "summarySourceIds": [
      "source-845e009a6d"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Blink's mobile-integration plan says applications must not rely on seed-only unilateral recovery and should refresh an encrypted recovery bundle whenever an event can change the wallet's leaves or ancestors.",
        "sourceIds": [
          "source-845e009a6d"
        ]
      },
      {
        "label": "Examples",
        "text": "The plan names startup after synchronization, deposit claims, incoming and outgoing Spark transfers, swaps or withdrawals, completed receive flows, and leaf optimization as refresh triggers.",
        "sourceIds": [
          "source-845e009a6d"
        ]
      },
      {
        "label": "Operational consequence",
        "text": "A consumer implementation needs durable refresh, completeness validation, and restart-safe recovery state in addition to mnemonic backup.",
        "sourceIds": [
          "source-845e009a6d"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The plan identifies events that can change exit state; it does not claim every synchronization or every listed event necessarily mutates a particular wallet. Atomic update behavior is an implementation requirement inferred from keeping wallet state and its backup consistent, not a measured result in the plan.",
        "sourceIds": [
          "source-845e009a6d"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink mobile integration plan",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mobile-integration-plan.md",
        "number": 1,
        "id": "source-845e009a6d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "sparks-mainnet-cli-generates-packages-but-leaves-signing-ordered-broadcast-timelocks-and-sweeping-to-the-user",
    "track": "exit",
    "title": "Spark's current mainnet CLI is a package generator, not a complete recovery workflow",
    "cardTitle": "Spark’s mainnet CLI generates packages but leaves signing, ordered broadcast, timelocks, and sweeping to the user",
    "summary": "Spark's current CLI begins with live leaf discovery and requests external fee UTXO data. In normal mainnet mode it emits transactions and unsigned CPFP PSBTs with manual ordering instructions, but lacks a complete durable broadcast, resume, timelock-monitoring, and sweep workflow.",
    "summarySourceIds": [
      "source-f3d69c35d5"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The unilateralexit command begins with live wallet.getLeaves(), constructs a coordinator client, and asks the user for raw external UTXO information.",
        "sourceIds": [
          "source-f3d69c35d5"
        ]
      },
      {
        "label": "Technical details",
        "text": "In normal/mainnet mode it prints raw transactions and unsigned CPFP PSBTs, tells the user to sign them, and instructs manual root-to-leaf parent/child submission.",
        "sourceIds": [
          "source-f3d69c35d5"
        ]
      },
      {
        "label": "Missing operations",
        "text": "The command contains no complete mainnet broadcaster, durable resume engine, real-time timelock monitor, or final sweep workflow.",
        "sourceIds": [
          "source-f3d69c35d5"
        ]
      },
      {
        "label": "Product consequence",
        "text": "Even the reference command leaves the safety-critical multi-day execution to an expert using external Bitcoin tooling.",
        "sourceIds": [
          "source-f3d69c35d5"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This describes the cited Spark CLI command, not every possible exit tool. Separate software can automate broadcasting, persistence, timelock monitoring, and sweeping once complete state and fee funds exist.",
        "sourceIds": [
          "source-f3d69c35d5"
        ]
      }
    ],
    "sources": [
      {
        "label": "Spark CLI unilateralexit implementation",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/sdks/js/apps/spark-cli/src/cli.ts#L3525-L3895",
        "number": 1,
        "id": "source-f3d69c35d5",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "sparks-faq-reduces-exit-to-a-pre-signed-broadcast-its-beta-manual-documents-a-multi-stage-expert-procedure",
    "track": "exit",
    "title": "Spark's dedicated beta manual lists prerequisites omitted from its short-form exit description",
    "cardTitle": "Spark’s FAQ reduces exit to a pre-signed broadcast; its beta manual documents a multi-stage expert procedure",
    "summary": "Spark's FAQ says a user can exit at any time by broadcasting a pre-signed transaction. Its dedicated beta manual separately requires Bitcoin Core v29, external L1 UTXOs, economic leaf selection, exact package ordering, mainnet signing and timelock handling, and warns that the wallet is unusable until completion.",
    "summarySourceIds": [
      "source-56182f9cb1",
      "source-e9874abca5"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Spark's FAQ says users can exit at any time by broadcasting “your pre-signed transaction,” and its sovereignty page calls the escape hatch unconditional and trustless.",
        "sourceIds": [
          "source-56182f9cb1",
          "source-b2412dd493"
        ]
      },
      {
        "label": "Documented requirements",
        "text": "The dedicated guide labels the feature beta, instructs users to exit only leaves above 16,348 sats, requires Bitcoin Core v29 and external L1 UTXOs, requires exact root-to-leaf ordering, assigns mainnet signing and timelock handling to the user, and says the wallet remains unusable until completion.",
        "sourceIds": [
          "source-e9874abca5"
        ]
      },
      {
        "label": "Published case study",
        "text": "Blink's published 100,000-sat wallet had a complete 22-leaf graph of 253 package steps. Its economically selected four-leaf recovery still required roughly 24 pre-refund package confirmations, later refund broadcasts, and separate sweeps.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      },
      {
        "label": "Product consequence",
        "text": "The short description omits the prerequisites that decide whether an ordinary user can exercise the exit during an outage.",
        "sourceIds": [
          "source-56182f9cb1",
          "source-e9874abca5",
          "source-f2874fcc61"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The FAQ describes the protocol-level authorization property; the manual documents one current reference procedure. The contrast establishes omitted operational prerequisites, not that protocol-level exit is false or that every wallet has 253 packages.",
        "sourceIds": [
          "source-56182f9cb1",
          "source-e9874abca5",
          "source-f2874fcc61"
        ]
      }
    ],
    "sources": [
      {
        "label": "Spark FAQ",
        "url": "https://docs.spark.money/learn/faq",
        "number": 1,
        "id": "source-56182f9cb1",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "Spark sovereignty page",
        "url": "https://docs.spark.money/learn/sovereignty",
        "number": 2,
        "id": "source-b2412dd493",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "current unilateral-exit manual",
        "url": "https://docs.spark.money/wallets/unilateral-exit",
        "number": 3,
        "id": "source-e9874abca5",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "Blink case study",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md",
        "number": 4,
        "id": "source-f2874fcc61",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "one-100-000-sat-wallet-required-253-ordered-packages-252-failed-when-submitted-together",
    "track": "exit",
    "title": "A 100,000-sat mobile balance expanded into 253 ordered packages, and 252 failed the naive first submission",
    "cardTitle": "One 100,000-sat wallet required 253 ordered packages; 252 failed when submitted together",
    "summary": "Blink's published 100,000-sat wallet contained 22 leaves and 253 root-to-leaf package steps. When all packages were first submitted together, 252 were rejected because the transaction graph could not enter the mempool in that form.",
    "summarySourceIds": [
      "source-f2874fcc61"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Blink's published mainnet wallet contained 22 leaves and 253 root-to-leaf package steps.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      },
      {
        "label": "Observed result",
        "text": "The first attempt submitted all packages; 252 were rejected with TRUC descendant-policy or missing-input errors. Only the root package could enter.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      },
      {
        "label": "Reason recorded by the experiment",
        "text": "Each deeper package depended on ancestors not yet confirmed, so the tree had to be advanced sequentially.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      },
      {
        "label": "Product consequence",
        "text": "A generic “broadcast transactions” button is not a workable implementation of the actual graph.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Blink reports the off-chain rejection count for this particular 22-leaf wallet. The blockchain independently shows accepted transactions, but not every rejected package submission; other wallets can have smaller or differently shaped graphs.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink mainnet case study",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md",
        "number": 1,
        "id": "source-f2874fcc61",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "at-1-sat-vb-18-of-22-leaves-cost-more-to-exit-than-they-held",
    "track": "exit",
    "title": "Eighteen of twenty-two leaves were uneconomic even at 1 sat/vB",
    "cardTitle": "At 1 sat/vB, 18 of 22 leaves cost more to exit than they held",
    "summary": "In Blink's 100,000-sat case, 18 of 22 leaves holding 9,888 sats cost more to recover than their value at 1 sat/vB. Only four leaves holding 90,112 sats were selected for the documented attempt.",
    "summarySourceIds": [
      "source-f2874fcc61"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "In the same 100,000-sat case, 18 leaves holding 9,888 sats cost more to exit than their value at 1 sat/vB.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      },
      {
        "label": "Technical details",
        "text": "Only four leaves holding 90,112 sats were selected; a complete exit was estimated at about 77,500 sats in fees.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      },
      {
        "label": "Projected selected result",
        "text": "The plan left 9,888 sats behind and projected 89,668 sats at the destination. Because 8,388 sats of CPFP fees came from separate L1 bitcoin, projected net economic recovery was only 81,280 sats before the funding transaction's fee.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Large leaves can be economical at low fees; the fact is that a wallet's displayed balance can include many practically stranded fragments.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink mainnet case study",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md",
        "number": 1,
        "id": "source-f2874fcc61",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "blinks-first-documented-mainnet-recovery-bundle-silently-omitted-required-ancestry",
    "track": "exit",
    "title": "Blink's first documented mainnet recovery bundle was incomplete",
    "cardTitle": "Blink’s first documented mainnet recovery bundle silently omitted required ancestry",
    "summary": "Blink's first documented mainnet recovery bundle omitted a legacy root required for offline construction. The tool initially failed without Operator access and was changed to fetch missing roots explicitly and reject open chains before producing a corrected bundle.",
    "summarySourceIds": [
      "source-f2874fcc61",
      "source-f9624a7094"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "Blink reports that its first mainnet bundle omitted the root of each legacy tree because the bulk `query_nodes(include_parents=true)` response did not include that root.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      },
      {
        "label": "Observed result",
        "text": "Offline construction failed with “Exit chain is incomplete.”",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      },
      {
        "label": "Required fix",
        "text": "The exporter was changed to walk the graph, re-fetch missing ancestors by ID while online, and reject open chains.",
        "sourceIds": [
          "source-f2874fcc61",
          "source-f9624a7094"
        ]
      },
      {
        "label": "Product consequence",
        "text": "A saved file is not sufficient by itself: an exporter intended for outage recovery must establish that every selected leaf has a complete, closed ancestor chain before Operators become unavailable.",
        "sourceIds": [
          "source-f2874fcc61",
          "source-f9624a7094"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This is Blink's first documented mainnet bundle, not a claim that it was the first recovery bundle created by anyone. The corrected exporter addresses the recorded legacy-root failure.",
        "sourceIds": [
          "source-f2874fcc61",
          "source-f9624a7094"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink case study",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md",
        "number": 1,
        "id": "source-f2874fcc61",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Breez fix PR 917",
        "url": "https://github.com/breez/spark-sdk/pull/917",
        "number": 2,
        "id": "source-f9624a7094",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "breez-0-19-0-can-silently-omit-a-required-parent-and-produce-an-unbroadcastable-exit-package",
    "track": "exit",
    "title": "Breez 0.19.0 can silently produce an unbroadcastable renewed-leaf package",
    "cardTitle": "Breez 0.19.0 can silently omit a required parent and produce an unbroadcastable exit package",
    "summary": "Breez 0.19.0's exit status walker omitted the SplitLocked state even though Spark renewal handlers create that intermediate parent. The resulting package can omit a required unbroadcast parent; the reviewed correcting pull request remained open.",
    "summarySourceIds": [
      "source-179b8cd8ec",
      "source-ec07ac6207",
      "source-ef63979da2"
    ],
    "evidence": [
      {
        "label": "Documented behavior",
        "text": "The shipped EXIT_CHAIN_STATUSES list includes Available, Splitted, and OnChain, but omits SplitLocked.",
        "sourceIds": [
          "source-179b8cd8ec"
        ]
      },
      {
        "label": "Protocol fact",
        "text": "Spark's renewal handlers create a SplitLocked intermediate parent.",
        "sourceIds": [
          "source-ef63979da2"
        ]
      },
      {
        "label": "Failure",
        "text": "The exit walker can stop at that parent without reporting it, leaving the printed child dependent on a transaction that is neither on chain nor in the package.",
        "sourceIds": [
          "source-179b8cd8ec",
          "source-ec07ac6207",
          "source-ef63979da2"
        ]
      },
      {
        "label": "Snapshot",
        "text": "The fixing pull request 935 remained open after Breez 0.19.0, whose source still contained the omission.",
        "sourceIds": [
          "source-179b8cd8ec",
          "source-ec07ac6207"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This failure concerns renewed leaves whose ancestry uses the omitted `SplitLocked` status in Breez 0.19.0. It is not a claim that every Spark leaf or every unilateral-exit package is unbroadcastable.",
        "sourceIds": [
          "source-179b8cd8ec",
          "source-ec07ac6207",
          "source-ef63979da2"
        ]
      }
    ],
    "sources": [
      {
        "label": "Breez unilateral-exit source",
        "url": "https://github.com/breez/spark-sdk/blob/bce792ec7c583f640cb554b97cca7b05d29b9047/crates/spark/src/services/unilateral_exit.rs#L32-L36",
        "number": 1,
        "id": "source-179b8cd8ec",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "fix PR 935",
        "url": "https://github.com/breez/spark-sdk/pull/935",
        "number": 2,
        "id": "source-ec07ac6207",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "Spark renewal status",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/spark/so/handler/renew_leaf_handler.go#L390-L403",
        "number": 3,
        "id": "source-ef63979da2",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "wallet-usage-history-not-just-balance-determines-sparks-exit-workload",
    "track": "exit",
    "title": "Wallet usage history—not just balance—determines Spark's exit workload",
    "cardTitle": "Wallet usage history—not just balance—determines Spark’s exit workload",
    "summary": "Normal Spark use can split and re-split wallet state into multiple leaves, each with an independently sequenced exit chain. The workload of an emergency exit therefore depends on the wallet’s tree history as well as its displayed balance.",
    "summarySourceIds": [
      "source-087a914c79",
      "source-d4f6cc1d37",
      "source-90f14e5aee"
    ],
    "evidence": [
      {
        "label": "Fragmentation mechanism",
        "text": "Blink explains that ordinary payments split and re-split the Spark tree, causing a wallet to accumulate multiple leaves.",
        "sourceIds": [
          "source-087a914c79"
        ]
      },
      {
        "label": "Per-leaf graph",
        "text": "Every current leaf carries its own chain of ancestor and refund transactions that must be forced onto Bitcoin.",
        "sourceIds": [
          "source-087a914c79"
        ]
      },
      {
        "label": "Sequential depth",
        "text": "Mempool policy requires each level within a leaf's chain to confirm before the next level can enter the mempool.",
        "sourceIds": [
          "source-d4f6cc1d37"
        ]
      },
      {
        "label": "Mechanical consequence",
        "text": "Exit work is governed by the number of leaves and the depth of their chains, information that a displayed aggregate balance does not convey.",
        "sourceIds": [
          "source-087a914c79",
          "source-d4f6cc1d37"
        ]
      },
      {
        "label": "Mitigation",
        "text": "Blink recommends consolidating leaves while Operators are cooperative, confirming that prior wallet activity affects the later emergency-exit burden.",
        "sourceIds": [
          "source-90f14e5aee"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Leaf count and chain depth vary by wallet, and consolidation can reduce future work. This establishes history-dependent workload, not a fixed transaction multiplier or technical impossibility.",
        "sourceIds": [
          "source-087a914c79",
          "source-d4f6cc1d37",
          "source-90f14e5aee"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink wallet-tree mechanics",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L10-L17",
        "number": 1,
        "id": "source-087a914c79",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink TRUC sequencing",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L57-L77",
        "number": 2,
        "id": "source-d4f6cc1d37",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink consolidation takeaway",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L197-L215",
        "number": 3,
        "id": "source-90f14e5aee",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "higher-fee-rates-push-more-spark-leaves-below-the-economic-exit-threshold",
    "track": "exit",
    "title": "Higher fee rates push more Spark leaves below the economic exit threshold",
    "cardTitle": "Higher fee rates push more Spark leaves below the economic exit threshold",
    "summary": "Blink's published method evaluates each leaf against the CPFP cost of its full exit chain plus final sweep. As fee rates rise, costs rise while leaf value does not, so additional leaves can fall below the economic threshold. This is an economic limit, not a cryptographic one.",
    "summarySourceIds": [
      "source-300d848d16",
      "source-1670ed4b03",
      "source-0da7b54a8a"
    ],
    "evidence": [
      {
        "label": "Per-leaf test",
        "text": "Blink compares each leaf's value with the CPFP fees for its entire exit chain plus its final sweep.",
        "sourceIds": [
          "source-300d848d16"
        ]
      },
      {
        "label": "Scaling mechanism",
        "text": "As the fee rate rises, those transaction costs rise while the leaf's value does not.",
        "sourceIds": [
          "source-300d848d16",
          "source-1670ed4b03"
        ]
      },
      {
        "label": "Selection consequence",
        "text": "More leaves can become uneconomical to recover, so the tool skips them by default unless the user explicitly overrides that behavior.",
        "sourceIds": [
          "source-300d848d16",
          "source-1670ed4b03",
          "source-0da7b54a8a"
        ]
      },
      {
        "label": "Protocol/economic distinction",
        "text": "Such leaves remain part of the wallet state but are omitted from the economically selected on-chain recovery path.",
        "sourceIds": [
          "source-300d848d16",
          "source-0da7b54a8a"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This is an economic threshold, not a cryptographic failure or a claim that every leaf becomes unrecoverable. It depends on fee rate, leaf value, chain depth, and any prior consolidation.",
        "sourceIds": [
          "source-300d848d16",
          "source-1670ed4b03",
          "source-0da7b54a8a"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink per-leaf economics",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L79-L98",
        "number": 1,
        "id": "source-300d848d16",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink fee-rate sensitivity",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L125-L130",
        "number": 2,
        "id": "source-1670ed4b03",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink economics takeaway",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L197-L208",
        "number": 3,
        "id": "source-0da7b54a8a",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "a-spark-unilateral-exit-is-a-staged-recovery-procedure-not-a-single-emergency-broadcast",
    "track": "exit",
    "title": "A Spark unilateral exit is a staged recovery procedure, not a single emergency broadcast",
    "cardTitle": "A Spark unilateral exit is a staged recovery procedure, not a single emergency broadcast",
    "summary": "Blink's documented workflow advances packages confirmation by confirmation, pauses for CSV refund timelocks, and later requires separate sweeps. Under the documented standard-mempool relay path, automation can resume and parallelize independent leaf chains, but it cannot remove within-chain confirmation ordering or the CSV timelock.",
    "summarySourceIds": [
      "source-4eb0265bc8",
      "source-fd9413cd29",
      "source-985e2feadd"
    ],
    "evidence": [
      {
        "label": "Confirm-and-continue",
        "text": "Blink's recovery loop reconstructs package state, signs a CPFP bump, submits one package per leaf chain, waits for confirmation, and repeats.",
        "sourceIds": [
          "source-4eb0265bc8"
        ]
      },
      {
        "label": "Durable progress",
        "text": "Confirmed transactions are skipped when the tool is rerun, allowing recovery after rate limits, crashes, or reboots.",
        "sourceIds": [
          "source-4eb0265bc8"
        ]
      },
      {
        "label": "Timelock stage",
        "text": "The refund that hands each leaf to the user's key is deferred until its CSV lock matures; the recovery command must be rerun afterward to broadcast it.",
        "sourceIds": [
          "source-985e2feadd"
        ]
      },
      {
        "label": "Finalization stage",
        "text": "After refunds confirm, separate sweep construction, signing, and broadcasting move the outputs to the chosen destination.",
        "sourceIds": [
          "source-985e2feadd"
        ]
      },
      {
        "label": "Parallelism limit",
        "text": "Under Blink's documented standard-mempool relay path, funding fan-out can advance independent leaf chains concurrently, but it does not remove within-chain confirmation ordering or the refund timelock.",
        "sourceIds": [
          "source-fd9413cd29",
          "source-985e2feadd"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Blink's tooling automates and resumes this operatorless process once the required bundle and fee funds exist. The staged workflow demonstrates operational burden; it does not establish technical impossibility or that a mobile implementation can never be built.",
        "sourceIds": [
          "source-4eb0265bc8",
          "source-fd9413cd29",
          "source-985e2feadd"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink confirm-and-continue loop",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L132-L159",
        "number": 1,
        "id": "source-4eb0265bc8",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink parallelism mechanics",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L161-L185",
        "number": 2,
        "id": "source-fd9413cd29",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink timelock and sweep stages",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L187-L195",
        "number": 3,
        "id": "source-985e2feadd",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "the-documented-independent-exit-cannot-start-from-a-spark-balance-alone-it-needs-external-on-chain-fee-funding",
    "track": "exit",
    "title": "The documented self-operated exit requires external on-chain fee funding before Spark value is available",
    "cardTitle": "The documented independent exit cannot start from a Spark balance alone; it needs external on-chain fee funding",
    "summary": "The documented independent exit requires external on-chain fee funding before the Spark value becomes spendable on chain. A third party could sponsor the UTXO, but doing so replaces the user's funding requirement with a service dependency.",
    "summarySourceIds": [
      "source-e9874abca5",
      "source-8b97312bb1",
      "source-72af077ba8"
    ],
    "evidence": [
      {
        "label": "Documented mechanism",
        "text": "In the current procedure, pre-signed Spark exit and refund transactions pay zero fee and are relayed with fee-paying CPFP children.",
        "sourceIds": [
          "source-e9874abca5",
          "source-8b97312bb1"
        ]
      },
      {
        "label": "Manual requirement",
        "text": "Spark tells mainnet users to bring sufficient L1 UTXOs and use Bitcoin Core v29 submitpackage.",
        "sourceIds": [
          "source-e9874abca5"
        ]
      },
      {
        "label": "Timing consequence",
        "text": "The Spark value is not yet available as an ordinary spendable L1 output when those fee children must be funded.",
        "sourceIds": [
          "source-e9874abca5",
          "source-8b97312bb1"
        ]
      },
      {
        "label": "Independent-user consequence",
        "text": "A user relying only on the documented independent path cannot begin with a Spark balance alone; an external fee source must first supply a suitable UTXO.",
        "sourceIds": [
          "source-e9874abca5",
          "source-72af077ba8"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "The external UTXO need not always be personally supplied by the user. A wallet or sponsor could provide fee funding, but that replaces the user-funding requirement with dependency on that sponsor. It does not remove the documented standard-relay path's requirement for a fee-paying input outside the Spark balance.",
        "sourceIds": [
          "source-845e009a6d"
        ]
      }
    ],
    "sources": [
      {
        "label": "Spark unilateral-exit manual",
        "url": "https://docs.spark.money/wallets/unilateral-exit",
        "number": 1,
        "id": "source-e9874abca5",
        "verifiedAt": "2026-07-14",
        "type": "Official documentation",
        "public": true,
        "immutable": false,
        "host": "docs.spark.money"
      },
      {
        "label": "Spark package constructor",
        "url": "https://github.com/buildonspark/spark/blob/9c25e7fd0b0214d424a04cd6eecfa7d7f4e2584e/sdks/js/packages/spark-sdk/src/utils/unilateral-exit.ts",
        "number": 2,
        "id": "source-8b97312bb1",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blink funding transaction",
        "url": "https://mempool.space/tx/3ab20a4c40d75f524b6c7b9e5837bc0bdef6aa350e79d932aa134134f0957262",
        "number": 3,
        "id": "source-72af077ba8",
        "verifiedAt": "2026-07-14",
        "type": "On-chain transaction",
        "public": true,
        "immutable": true,
        "host": "mempool.space"
      },
      {
        "label": "Blink mobile integration plan",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mobile-integration-plan.md",
        "number": 4,
        "id": "source-845e009a6d",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "blinks-weekly-brief-described-seed-only-recovery-and-an-unswept-amount-as-received",
    "track": "exit",
    "title": "Blink's Weekly Brief described seed-only recovery and an unswept amount as received",
    "cardTitle": "Blink’s Weekly Brief described seed-only recovery and an unswept amount as received",
    "summary": "Blink's Weekly Brief said the exit used only the seed with no Operators online and that 89,668 sats reached the destination. Its linked case study requires previously saved recovery state and records the selected exits as still awaiting refund maturity and final sweeping.",
    "summarySourceIds": [
      "source-d84482b62d",
      "source-346505b7fa",
      "source-985e2feadd"
    ],
    "evidence": [
      {
        "label": "Published description",
        "text": "Blink's July 10 Weekly Brief said the exit was completed “using only the seed, with no operators online” and that 89,668 sats reached the destination.",
        "sourceIds": [
          "source-d84482b62d"
        ]
      },
      {
        "label": "Recovery-state requirement",
        "text": "The linked case study states that Operators must still be online when a complete, current recovery bundle is created; after they are offline, a seed alone cannot rediscover the current leaves.",
        "sourceIds": [
          "source-346505b7fa"
        ]
      },
      {
        "label": "Completion-status mismatch",
        "text": "The Brief said 89,668 sats reached the destination. Step 6 of the linked case study said the four selected exits were still waiting for refund maturity and described refund broadcast and sweeping as future operations.",
        "sourceIds": [
          "source-d84482b62d",
          "source-985e2feadd"
        ]
      },
      {
        "label": "Source inconsistency",
        "text": "Earlier portions of the same case study use “confirmed,” “arrives,” and “reach” language. Its explicit “At the time of writing” checkpoint nevertheless records the refunds as unswept. This review uses that status checkpoint when distinguishing completed transactions from projections.",
        "sourceIds": [
          "source-985e2feadd"
        ]
      },
      {
        "label": "Accounting distinction",
        "text": "Even as a projected destination output, 89,668 sats is not net economic recovery because 8,388 sats of CPFP fees came from separate on-chain bitcoin.",
        "sourceIds": [
          "source-e67505f5bd"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Real mainnet exit packages were advanced without later Operator cooperation. The discrepancy concerns prior state acquisition, final-sweep completion, and economic accounting—not whether the package sequence began.",
        "sourceIds": [
          "source-346505b7fa",
          "source-985e2feadd"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blink Weekly Brief",
        "url": "https://www.blink.sv/blog/weekly-brief-2026-28",
        "number": 1,
        "id": "source-d84482b62d",
        "verifiedAt": "2026-07-14",
        "type": "Blog or announcement",
        "public": true,
        "immutable": false,
        "host": "blink.sv"
      },
      {
        "label": "case-study recovery state",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L19-L38",
        "number": 2,
        "id": "source-346505b7fa",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "case-study Step 6",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L187-L195",
        "number": 3,
        "id": "source-985e2feadd",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "case-study accounting",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md#L99-L130",
        "number": 4,
        "id": "source-e67505f5bd",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "cake-describes-a-one-transaction-exit-but-its-reviewed-shipped-integration-did-not-expose-an-exit-flow",
    "track": "exit",
    "title": "Cake describes a one-transaction exit that its audited integration did not expose",
    "cardTitle": "Cake describes a one-transaction exit, but its reviewed shipped integration did not expose an exit flow",
    "summary": "Cake Wallet describes unilateral exit as publishing a pre-signed transaction. In the reviewed Cake snapshot, the pinned Breez version had not shipped its public mobile exit API and no complete recovery-bundle or unilateral-exit product flow was found.",
    "summarySourceIds": [
      "source-18aa58b58e",
      "source-03ef7ff7cd",
      "source-bcf6a760b9",
      "source-f178c2190e"
    ],
    "evidence": [
      {
        "label": "First-party claim",
        "text": "Cake says a recipient can “simply publish a pre-signed Bitcoin transaction” to perform a trustless unilateral exit.",
        "sourceIds": [
          "source-18aa58b58e"
        ]
      },
      {
        "label": "Shipped integration",
        "text": "The audited Cake tree pins Breez Spark Flutter 0.14.0 and contains no unilateral-exit or recovery-bundle flow.",
        "sourceIds": [
          "source-03ef7ff7cd",
          "source-bcf6a760b9"
        ]
      },
      {
        "label": "API fact",
        "text": "Breez targeted its public exit API for 0.14.0 but did not ship it; issue 374 remains open.",
        "sourceIds": [
          "source-bcf6a760b9",
          "source-f178c2190e"
        ]
      },
      {
        "label": "Published case study",
        "text": "Blink's published 100,000-sat wallet had a complete 22-leaf graph of 253 packages. The selected four-leaf recovery still required roughly 24 pre-refund package confirmations, later refund broadcasts, and final sweeps rather than one transaction.",
        "sourceIds": [
          "source-f2874fcc61"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Cake's article is a protocol-level attack sketch; it does not explicitly say that the current Cake UI contains an exit button. The product finding is limited to the pinned Cake source and its pinned Breez 0.14.0 dependency.",
        "sourceIds": [
          "source-18aa58b58e",
          "source-03ef7ff7cd",
          "source-bcf6a760b9",
          "source-f178c2190e"
        ]
      }
    ],
    "sources": [
      {
        "label": "Cake's Lightning article",
        "url": "https://blog.cakewallet.com/our-lightning-journey/",
        "number": 1,
        "id": "source-18aa58b58e",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "blog.cakewallet.com"
      },
      {
        "label": "Cake source",
        "url": "https://github.com/cake-tech/cake_wallet/tree/bff9b2fa9648b22c2fcc84062caba58861e9d24f",
        "number": 2,
        "id": "source-03ef7ff7cd",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Cake Breez dependency",
        "url": "https://github.com/cake-tech/cake_wallet/blob/bff9b2fa9648b22c2fcc84062caba58861e9d24f/cw_bitcoin/pubspec.yaml#L72-L75",
        "number": 3,
        "id": "source-bcf6a760b9",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Breez issue 374",
        "url": "https://github.com/breez/spark-sdk/issues/374",
        "number": 4,
        "id": "source-f178c2190e",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "Blink case study",
        "url": "https://github.com/blinkbitcoin/spark-unilateral-exit/blob/d47fe9b209cf57ca26a6fd7a855ef47388384182/docs/mainnet-exit-case-study.md",
        "number": 5,
        "id": "source-f2874fcc61",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "club-orange-bundled-lower-level-exit-code-without-exposing-an-app-facing-exit-operation",
    "track": "exit",
    "title": "Club Orange's reviewed app bundled exit primitives but exposed no app-facing exit operation",
    "cardTitle": "Club Orange bundled lower-level exit code without exposing an app-facing exit operation",
    "summary": "Club Orange's published 4.1.8 Android artifact bundled Breez Spark's lower-level unilateral-exit primitives, but its 46 exported BreezSdk method names contained no unilateral-exit or recovery operation and the targeted DEX search found no corresponding user-flow strings.",
    "summarySourceIds": [
      "source-57090499c1",
      "source-33fb864ff7",
      "source-baab8de22f"
    ],
    "evidence": [
      {
        "label": "Published artifact",
        "text": "Club Orange's public 4.1.8 Android release includes a downloadable APK and release metadata suitable for an independent binary review.",
        "sourceIds": [
          "source-57090499c1",
          "source-33fb864ff7"
        ]
      },
      {
        "label": "Exported interface",
        "text": "The native UniFFI library exposes 46 `BreezSdk` method symbols. None of their names contains `unilateral`, `exit`, or `recovery`.",
        "sourceIds": [
          "source-baab8de22f"
        ]
      },
      {
        "label": "User-flow strings",
        "text": "An exact search across all five DEX files found no unilateral-exit, recovery-bundle, or prepare-unilateral user-flow string.",
        "sourceIds": [
          "source-baab8de22f"
        ]
      },
      {
        "label": "Lower-level capability",
        "text": "The bundled native library does contain Breez Spark's lower-level unilateral-exit primitives. Their presence establishes SDK capability but does not expose an application workflow by itself.",
        "sourceIds": [
          "source-33fb864ff7",
          "source-baab8de22f"
        ]
      },
      {
        "label": "Artifact identity",
        "text": "The published 4.1.8 APK is 321,875,805 bytes with GitHub-reported SHA-256 `2b362ffbf25169123a4f3addcdd3c8ce3d9763b81bbb558ea5c91891cfdf9b41`.",
        "sourceIds": [
          "source-57090499c1",
          "source-33fb864ff7",
          "source-baab8de22f"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "Symbol and string searches cannot prove that no obfuscated or indirectly invoked code exists. The result is limited to the named published artifact and the recorded interface and string searches.",
        "sourceIds": [
          "source-baab8de22f"
        ]
      }
    ],
    "sources": [
      {
        "label": "4.1.8 release",
        "url": "https://github.com/ClubOrangeBitcoin/ClubOrange-releases/releases/tag/v4.1.8",
        "number": 1,
        "id": "source-57090499c1",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "4.1.8 APK",
        "url": "https://github.com/ClubOrangeBitcoin/ClubOrange-releases/releases/download/v4.1.8/ClubOrange.apk",
        "number": 2,
        "id": "source-33fb864ff7",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "consumer-wallet exit audit manifest",
        "url": "data/consumer-wallet-exit-audit.json",
        "number": 3,
        "id": "source-baab8de22f",
        "verifiedAt": "2026-07-14",
        "type": "Reproducible audit manifest",
        "public": true,
        "immutable": true,
        "internal": true,
        "host": "Review of Spark's trust model"
      }
    ]
  },
  {
    "slug": "blitzs-no-third-parties-recovery-tool-uses-sparks-cooperative-withdrawal-path",
    "track": "exit",
    "title": "Blitz's “no third parties” recovery tool performs a cooperative withdrawal",
    "cardTitle": "Blitz’s ‘no third parties’ recovery tool uses Spark’s cooperative-withdrawal path",
    "summary": "Blitz's separate recovery site describes recovery without third parties, but its implementation restores a Spark wallet through live services and calls cooperative withdrawal. It does not construct or broadcast unilateral-exit packages.",
    "summarySourceIds": [
      "source-0714c4e186"
    ],
    "evidence": [
      {
        "label": "First-party claim",
        "text": "Blitz's separate Spark Wallet Recovery Tool advertises “Self-custodial. Stateless. Trustless. No third parties involved.”",
        "sourceIds": [
          "source-0714c4e186"
        ]
      },
      {
        "label": "Source behavior",
        "text": "It initializes a live mainnet SparkWallet, reads balance through getBalance(), requests getWithdrawalFeeQuote(), calls withdraw(), and records a coopExitTxid/COOPERATIVE_EXIT.",
        "sourceIds": [
          "source-0714c4e186"
        ]
      },
      {
        "label": "Absent behavior",
        "text": "It contains no recovery-bundle ingestion, offline tree state, CPFP construction, ordered broadcast, timelock monitor, or sweep.",
        "sourceIds": [
          "source-0714c4e186"
        ]
      },
      {
        "label": "Related pre-release",
        "text": "Blitz's wallet can now export a bundle, but sends the user to the external Blink developer repository for the actual exit.",
        "sourceIds": [
          "source-d4de1daf47",
          "source-ad1235bb80"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "This finding concerns the cited Blitz recovery tool and reviewed pre-release. It does not say that cooperative recovery is useless or that Blitz cannot add an in-wallet unilateral path later.",
        "sourceIds": [
          "source-0714c4e186",
          "source-d4de1daf47",
          "source-ad1235bb80"
        ]
      }
    ],
    "sources": [
      {
        "label": "Blitz recovery tool",
        "url": "https://github.com/BlitzWallet/spark-recover/tree/eddcd34735436a83b9060f4a2069e6a1a884097c",
        "number": 1,
        "id": "source-0714c4e186",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Blitz PR 999",
        "url": "https://github.com/BlitzWallet/BlitzWallet/pull/999",
        "number": 2,
        "id": "source-d4de1daf47",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "Android 0.7.13-pre6",
        "url": "https://github.com/BlitzWallet/BlitzWallet/releases/tag/Android-v0.7.13-pre6",
        "number": 3,
        "id": "source-ad1235bb80",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      }
    ]
  },
  {
    "slug": "across-primal-radar-wallet-of-satoshi-and-bringin-control-claims-were-not-matched-by-a-demonstrated-unilateral-exit-path",
    "track": "exit",
    "title": "Across Primal, Radar, Wallet of Satoshi, and Bringin, control claims were not matched by a demonstrated unilateral-exit path",
    "cardTitle": "Across Primal, Radar, Wallet of Satoshi, and Bringin, control claims were not matched by a demonstrated unilateral-exit path",
    "summary": "Across Primal, Radar, Wallet of Satoshi, and Bringin, public control or recovery claims were not matched in the reviewed surfaces by a demonstrated complete user-operated exit. Closed-source findings are limited to published claims, instructions, and observed interfaces rather than unseen implementation code.",
    "summarySourceIds": [
      "source-80fd50e65b",
      "source-46ea798506",
      "source-1776ca9a71",
      "source-c2c81d46bf",
      "source-caac4b15c9",
      "source-49769df467",
      "source-3bb9cc3854",
      "source-1b19988d50",
      "source-baab8de22f"
    ],
    "evidence": [
      {
        "label": "Primal",
        "text": "Primal's pinned Android and iOS UI says users fully control bitcoin, have sole custody through the recovery phrase, and now hold the keys. The source-visible pinned application and UI code contained no unilateral-exit or recovery-bundle flow.",
        "sourceIds": [
          "source-cd1dc8f3a1",
          "source-80fd50e65b",
          "source-46ea798506",
          "source-bbf4f4f230",
          "source-1776ca9a71",
          "source-c2c81d46bf",
          "source-baab8de22f"
        ]
      },
      {
        "label": "Radar",
        "text": "Radar says it never holds the money and that no one can freeze or seize it but the user. Its recovery UI says the seed can restore the balance in Cake or another Spark wallet. Both pinned apps used Breez 0.18.0, and neither application's source-visible integration or UI contained the complete exit flow.",
        "sourceIds": [
          "source-fca02e3502",
          "source-fb31889a09",
          "source-7a7287fdc3",
          "source-caac4b15c9",
          "source-49769df467",
          "source-4dc7c56a5b",
          "source-2563cd7816",
          "source-baab8de22f"
        ]
      },
      {
        "label": "Wallet of Satoshi",
        "text": "Its disclosure says users retain full authority over private keys and describes unilateral exit as a permissionless alternative with longer processing and higher costs. Wallet of Satoshi publishes neither its mobile-app source nor a complete consumer unilateral-exit procedure.",
        "sourceIds": [
          "source-3bb9cc3854",
          "source-423abd1dd0"
        ]
      },
      {
        "label": "Bringin",
        "text": "Bringin says every v2 wallet ships with pre-signed exits and can leave Spark without Bringin's cooperation. Its mobile app is closed source, its public GitHub organization does not contain that app, and its launch material provides no operational exit procedure.",
        "sourceIds": [
          "source-1b19988d50",
          "source-6072e04d5b"
        ]
      }
    ],
    "analysis": [],
    "limitations": [
      {
        "label": "Strict boundary",
        "text": "These are dated claim-to-demonstrated-product comparisons. Seed restoration while Spark services remain available is distinct from outage recovery. Open-source absence findings are limited to the pinned commits and audit terms. Primal iOS pins an opaque compiled `PrimalShared.xcframework`, so that result is not a full transitive-binary audit. The Radar iOS archive likewise omits the Breez FFI binary and Pods submodule, so its result is limited to the source-visible integration and UI. Wallet of Satoshi and Bringin findings are limited to public claims, source availability, and published instructions. They do not prove that private or later implementations are impossible.",
        "sourceIds": [
          "source-baab8de22f",
          "source-3bb9cc3854",
          "source-1b19988d50"
        ]
      }
    ],
    "sources": [
      {
        "label": "Primal Android",
        "url": "https://github.com/PrimalHQ/primal-android-app/tree/c16997628c5111e56ac8b98b49d4297f5d52e480",
        "number": 1,
        "id": "source-cd1dc8f3a1",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Primal Android control claim",
        "url": "https://github.com/PrimalHQ/primal-android-app/blob/c16997628c5111e56ac8b98b49d4297f5d52e480/app/src/main/res/values/strings.xml#L1380",
        "number": 2,
        "id": "source-80fd50e65b",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Primal Android custody claims",
        "url": "https://github.com/PrimalHQ/primal-android-app/blob/c16997628c5111e56ac8b98b49d4297f5d52e480/app/src/main/res/values/strings.xml#L1574-L1588",
        "number": 3,
        "id": "source-46ea798506",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Primal iOS",
        "url": "https://github.com/PrimalHQ/primal-ios-app/tree/91787a1209bd13f57afcb5fe100783102feae2fb",
        "number": 4,
        "id": "source-bbf4f4f230",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Primal iOS backup claim",
        "url": "https://github.com/PrimalHQ/primal-ios-app/blob/91787a1209bd13f57afcb5fe100783102feae2fb/Primal/Scenes/Wallet/Backup/BackupWalletIntroController.swift#L35-L47",
        "number": 5,
        "id": "source-1776ca9a71",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Primal iOS confirmation claim",
        "url": "https://github.com/PrimalHQ/primal-ios-app/blob/91787a1209bd13f57afcb5fe100783102feae2fb/Primal/Scenes/Wallet/Backup/BackupWalletConfirmController.swift#L20-L30",
        "number": 6,
        "id": "source-c2c81d46bf",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar Android",
        "url": "https://github.com/radar-labs/radar-android/tree/8ca3e2b147fa32648afb04f8a42c4dd4b7db9eda",
        "number": 7,
        "id": "source-fca02e3502",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar Android Breez version",
        "url": "https://github.com/radar-labs/radar-android/blob/8ca3e2b147fa32648afb04f8a42c4dd4b7db9eda/gradle/libs.versions.toml#L191",
        "number": 8,
        "id": "source-fb31889a09",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar iOS",
        "url": "https://github.com/radar-labs/Radar/tree/661a39cce1b9d270b6ae9d0914c15a836d103668",
        "number": 9,
        "id": "source-7a7287fdc3",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar control claim",
        "url": "https://github.com/radar-labs/Radar/blob/661a39cce1b9d270b6ae9d0914c15a836d103668/README.md#L5-L20",
        "number": 10,
        "id": "source-caac4b15c9",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar recovery language",
        "url": "https://github.com/radar-labs/Radar/blob/661a39cce1b9d270b6ae9d0914c15a836d103668/Signal/translations/en.lproj/Localizable.strings#L8330-L8342",
        "number": 11,
        "id": "source-49769df467",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar iOS Breez version",
        "url": "https://github.com/radar-labs/Radar/blob/661a39cce1b9d270b6ae9d0914c15a836d103668/ThirdParty/BreezSdkSpark.podspec.json#L1-L21",
        "number": 12,
        "id": "source-4dc7c56a5b",
        "verifiedAt": "2026-07-14",
        "type": "Pinned source code",
        "public": true,
        "immutable": true,
        "host": "github.com"
      },
      {
        "label": "Radar App Store",
        "url": "https://apps.apple.com/ch/app/radar-chat-bitcoin/id6753939776?l=en-GB",
        "number": 13,
        "id": "source-2563cd7816",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "apps.apple.com"
      },
      {
        "label": "Wallet of Satoshi disclosure",
        "url": "https://walletofsatoshi.com/disclosure",
        "number": 14,
        "id": "source-3bb9cc3854",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "walletofsatoshi.com"
      },
      {
        "label": "Wallet of Satoshi Play listing",
        "url": "https://play.google.com/store/apps/details?id=com.livingroomofsatoshi.wallet",
        "number": 15,
        "id": "source-423abd1dd0",
        "verifiedAt": "2026-07-14",
        "type": "Web source",
        "public": true,
        "immutable": false,
        "host": "play.google.com"
      },
      {
        "label": "Bringin v2 launch",
        "url": "https://bringin.app/blog/bitcoin-payments-app-in-europe",
        "number": 16,
        "id": "source-1b19988d50",
        "verifiedAt": "2026-07-14",
        "type": "Blog or announcement",
        "public": true,
        "immutable": false,
        "host": "bringin.app"
      },
      {
        "label": "Bringin public GitHub organization",
        "url": "https://github.com/bringinxyz",
        "number": 17,
        "id": "source-6072e04d5b",
        "verifiedAt": "2026-07-14",
        "type": "Repository record",
        "public": true,
        "immutable": false,
        "host": "github.com"
      },
      {
        "label": "consumer-wallet exit audit manifest",
        "url": "data/consumer-wallet-exit-audit.json",
        "number": 18,
        "id": "source-baab8de22f",
        "verifiedAt": "2026-07-14",
        "type": "Reproducible audit manifest",
        "public": true,
        "immutable": true,
        "internal": true,
        "host": "Review of Spark's trust model"
      }
    ]
  }
]