Review of Spark's trust model

Privacy, control, and safety

Under the published 2-of-3 mainnet configuration, one honest Operator cannot prevent a retained-share attack

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. 1234

4 sourcesEvidence reviewed through July 14, 2026

What the evidence shows

Technical and product details

  1. Documented behavior

    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. 1

    Supporting material shown on this page

    published threshold and three OperatorsPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    324    baseUrl: getSspUrl("REGTEST"),
    325    identityPublicKey: getSspIdentityPublicKey("REGTEST"),
    326  },
    327};
    328
    329const MAINNET_WALLET_CONFIG: Required<ConfigOptions> = {
    330  ...BASE_CONFIG,
  2. Documented behavior

    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. 23

    Supporting material shown on this page

    one-plus-threshold signing constructionPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    453type KeyPackageProvider func(ctx context.Context, config *so.Config, keyshareIDs []uuid.UUID) (map[uuid.UUID]*pbfrost.KeyPackage, error)
    454
    455func SignFrostInternal(ctx context.Context, config *so.Config, jobs []*SigningJob, getKeyPackages KeyPackageProvider, sparkServiceClientFactory SparkServiceFrostSignerFactory) ([]*SigningResult, error) {
    456	selection := OperatorSelection{Option: OperatorSelectionOptionThreshold, Threshold: int(config.Threshold)}
    457	signingKeyshareIDs := SigningKeyshareIDsFromSigningJobs(jobs)
    458	signingKeyshares, err := getKeyPackages(ctx, config, signingKeyshareIDs)
    459	if err != nil {
    DKG minimum signersPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    45	requestIDString := requestID.String()
    46	initRequest := &pbdkg.InitiateDkgRequest{
    47		RequestId:        requestIDString,
    48		KeyCount:         keyCount,
    49		MinSigners:       config.Threshold,
    50		MaxSigners:       uint64(len(config.SigningOperatorMap)),
    51		CoordinatorIndex: config.Index,
  3. Documented behavior

    Any two matching Operator shares therefore satisfy the Operator side of the published default signature; participation by the third Operator is not required. 123

    Supporting material shown on this page

    published threshold and three OperatorsPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    324    baseUrl: getSspUrl("REGTEST"),
    325    identityPublicKey: getSspIdentityPublicKey("REGTEST"),
    326  },
    327};
    328
    329const MAINNET_WALLET_CONFIG: Required<ConfigOptions> = {
    330  ...BASE_CONFIG,
    one-plus-threshold signing constructionPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    453type KeyPackageProvider func(ctx context.Context, config *so.Config, keyshareIDs []uuid.UUID) (map[uuid.UUID]*pbfrost.KeyPackage, error)
    454
    455func SignFrostInternal(ctx context.Context, config *so.Config, jobs []*SigningJob, getKeyPackages KeyPackageProvider, sparkServiceClientFactory SparkServiceFrostSignerFactory) ([]*SigningResult, error) {
    456	selection := OperatorSelection{Option: OperatorSelectionOptionThreshold, Threshold: int(config.Threshold)}
    457	signingKeyshareIDs := SigningKeyshareIDsFromSigningJobs(jobs)
    458	signingKeyshares, err := getKeyPackages(ctx, config, signingKeyshareIDs)
    459	if err != nil {
    DKG minimum signersPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    45	requestIDString := requestID.String()
    46	initRequest := &pbdkg.InitiateDkgRequest{
    47		RequestId:        requestIDString,
    48		KeyCount:         keyCount,
    49		MinSigners:       config.Threshold,
    50		MaxSigners:       uint64(len(config.SigningOperatorMap)),
    51		CoordinatorIndex: config.Index,

Interpretation

Why this matters for Spark's trust model

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. 1234

This section states the trust-model consequence of the cited evidence. Conditional or inferred claims remain bounded by the next section.

Scope control

What this does not establish

  • 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. 14

Sources and excerpts

Primary sources

The relevant details and available source-code excerpts appear alongside the claims above. This list preserves the complete original-source trail for independent verification.

1

published threshold and three Operators

Pinned source code · github.com · Pinned or archival · checked 2026-07-14

Open original source
2

one-plus-threshold signing construction

Pinned source code · github.com · Pinned or archival · checked 2026-07-14

Open original source
3

DKG minimum signers

Pinned source code · github.com · Pinned or archival · checked 2026-07-14

Open original source
4

Spark's one-honest-Operator explanation

Web source · spark.money · Live source · checked 2026-07-14

Open original source

How sources, absence findings, and limitations were evaluated