Review of Spark's trust model

Privacy, control, and safety

A normal wallet authenticates one persistent identity to every Operator

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

8 sourcesEvidence reviewed through July 14, 2026

What the evidence shows

Technical and product details

  1. Documented behavior

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

    Supporting material shown on this page

    wallet initializationPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    468      "createClientsAndSyncWallet",
    469      `initializing network=${this.config.getNetworkType()} coordinator=${formatUrlForLogs(
    470        this.config.getCoordinatorAddress(),
    471      )} signingOperators=${Object.keys(this.config.getSigningOperators()).length}`,
    472    );
    473    await this.connectionManager.createClients();
    per-Operator identity authenticationPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    461              });
    462              const protectedChallenge = challengeResp.protectedChallenge;
    463              const challenge = protectedChallenge?.challenge;
    464
    465              if (!challenge) {
    466                throw new SparkAuthenticationError(
    467                  "Invalid challenge response",
    all-Operator leaf synchronizationPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    292  public async getLeaves(isBalanceCheck: boolean = false): Promise<TreeNode[]> {
    293    const ownerIdentityPubkey = await this.config.signer.getIdentityPublicKey();
    294    const coordinatorId = this.config.getCoordinatorIdentifier();
    295    const network = this.config.getNetworkProto();
    296
    297    let operators = Object.entries(this.config.getSigningOperators());
    298    if (isBalanceCheck) {
  2. Documented behavior

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

    Supporting material shown on this page

    identity-bound request contextPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    85// AuthnInterceptor validates session tokens and adds session info to the context.
    86// Unauthenticated requests are rejected unless the method is explicitly marked as unauthenticated.
    87// For unauthenticated methods, we still attempt to extract the session if a token is present.
    88func (i *Interceptor) AuthnInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
    89	requireAuth := i.isAuthenticated(info.FullMethod)
    client-IP extractionPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    14// GetClientIP returns the client IP address from the request context. It first
    15// tries the x-forwarded-for header (for production behind an ALB). If XFF is
    16// entirely absent (e.g., local dev without ALB), it falls back to the gRPC peer
    17// address. If XFF is present but parsing fails (misconfigured position), it does
    18// NOT fall back — that's a configuration error that should surface loudly.
  3. Documented behavior

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

    Supporting material shown on this page

    request and client-metadata loggerPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    143const omittedResponseMessagePlaceholder = "MSG_OMITTED"
    144
    145// High-volume read-only gRPC methods whose response body should be omitted from spark-requests,
    146// as they dominate log storage and don't typically contain useful or interesting information for
    147// debugging.
    148var methodsWithoutResponseBodyLogging = map[string]struct{}{
    149	"/spark.SparkService/query_nodes":                         {},
    conditional request-logger activationPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    733	clientInfoProvider := sparkgrpc.NewGRPCClientInfoProvider(config.XffClientIpPosition)
    734	var tableLogger *logging.TableLogger
    735	if args.LogRequestStats && args.LogJSON {
    736		tableLogger = logging.NewTableLogger(clientInfoProvider)
    737	}
    protobuf redaction listPinned source code · github.com · checked 2026-07-14
    Relevant lines from the pinned source · open raw file
    14// In logstash we have a limit of 131KB (???) for the size of a message, so we don't want to be
    15// logging protos that are too big. So put a reasonable limit on this.
    16var maxBytes = 32 * 1024 // 32KB
    17
    18var sensitiveFields = []string{"secret_share"}

Interpretation

Why this matters for Spark's trust model

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

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

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

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

wallet initialization

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

Open original source
2

per-Operator identity authentication

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

Open original source
3

all-Operator leaf synchronization

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

Open original source
4

identity-bound request context

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

Open original source
5

client-IP extraction

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

Open original source
6

request and client-metadata logger

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

Open original source
7

conditional request-logger activation

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

Open original source
8

protobuf redaction list

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

Open original source

How sources, absence findings, and limitations were evaluated