NSpark.Signer.Wire 0.1.0-alpha.1

This is a prerelease version of NSpark.Signer.Wire.
dotnet add package NSpark.Signer.Wire --version 0.1.0-alpha.1
                    
NuGet\Install-Package NSpark.Signer.Wire -Version 0.1.0-alpha.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NSpark.Signer.Wire" Version="0.1.0-alpha.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NSpark.Signer.Wire" Version="0.1.0-alpha.1" />
                    
Directory.Packages.props
<PackageReference Include="NSpark.Signer.Wire" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NSpark.Signer.Wire --version 0.1.0-alpha.1
                    
#r "nuget: NSpark.Signer.Wire, 0.1.0-alpha.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NSpark.Signer.Wire@0.1.0-alpha.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NSpark.Signer.Wire&version=0.1.0-alpha.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=NSpark.Signer.Wire&version=0.1.0-alpha.1&prerelease
                    
Install as a Cake Tool

nspark-signer

NuGet Downloads CI License .NET Swift

Multi-language remote signer for the Spark protocol. Holds wallet keys outside the process that orchestrates Spark flows — your gRPC server (e.g. nspark) calls into the signer over a long-lived bidirectional gRPC stream to get signatures, ECIES decrypts, FROST partials, and tweak-batch payloads.

dotnet add package NSpark.Signer.Wire

The canonical wire protocol is spark.signer.v1alpha1, defined under proto/. Per-language implementations live in language-named subdirectories.

Why this exists

Real wallets hold private keys on user-controlled devices (phones, laptops, hardware wallets) — not on the server that builds transactions. The signer needs to initiate the connection (devices can't accept inbound from behind NAT), but the server is the one that knows when a signature is needed. So the gRPC topology is inverted: the signer is the client; the server pushes signing requests down an open bidi stream.

  Signer (Swift / Kotlin / …)              Server (your gRPC service, hosts nspark)
       │                                              │
       │ ──── opens bidi RPC + auth header ──────────►│
       │ ◄─── Challenge (32 random bytes) ────────────│
       │ ──── Hello (identity_proof) ────────────────►│  verifies proof + ownership
       │ ◄─── Welcome ────────────────────────────────│
       │                                              │
       │ ◄─── Request { sign_with_identity_key, … } ──│  ← server-pushed signing call
       │ ──── Response { der_signature: … } ─────────►│
       │                                              │
       │       (… more Requests / Responses …)        │

The Hello.identity_proof is an ECDSA signature over the server's Challenge.nonce, made with the wallet's identity private key. It proves the signer holds the key — defends against stolen-token replay where someone might try to claim a wallet they don't control.

Quick start

You have a .NET gRPC server. You want to host the signer endpoint and call into it when your code needs to sign:

// Program.cs
using NSpark.Signer.Wire;

builder.Services.AddRemoteSparkSigner();
builder.Services.AddScoped<ISignerAuthenticator, YourTokenAuth>();
builder.Services.AddScoped<IWalletOwnershipResolver, YourWalletDb>();

var app = builder.Build();
app.MapRemoteSparkSigner();          // exposes RemoteSigner.Connect on the existing Kestrel
app.Run();
// In any endpoint where you need to sign:
public async Task<IResult> Send(
    SendRequest req,
    IRemoteSparkSignerProvider signers,
    SparkConnection spark,
    CancellationToken ct)
{
    var signer = signers.GetSigner(req.WalletPubkey);                // throws SignerUnavailableException if offline
    var wallet = await spark.CreateWalletAsync(signer, ct);
    var transfer = await wallet.SendAsync(req.ReceiverPubkey, req.AmountSats, ct: ct);
    return Results.Ok(transfer);
}

Two interfaces to implement. Four lines in Program.cs. The library handles the handshake, identity-proof verification, per-wallet routing, the 17-method ISparkSigner adapter, heartbeats, cancellation, and connection lifecycle.

Full integration guide: docs/integrators.md.

Repo layout

proto/                                canonical wire protocol (spark.signer.v1alpha1)
  spark/signer/v1alpha1/
    signer_service.proto              service + envelopes
    signer_session.proto              handshake, error codes, capabilities
    signer_requests.proto             17 *Request/*Response method pairs
    signer_types.proto                SigningCommitment, SoTarget, NonceHandle, …
    spark_orchestration.proto         types embedded as ECIES payloads (mirrors nspark spark.proto)

dotnet/
  src/
    NSpark.Signer.Wire/               .NET server library — drop into your gRPC host
  test-server/                        runnable test server + HTTP fixture endpoints

swift/
  Sources/
    NSparkSigner/                     Swift Package — embed in iOS/macOS host apps
    NSparkSignerTestSupport/          spawn-test-server + HTTP clients for fixtures
  Tests/
    NSparkSignerTests/                unit tests
    NSparkSignerIntegrationTests/     live-network integration tests (gated on env vars)

docs/
  protocol.md                         wire protocol spec
  integrators.md                      .NET host integration guide
  swift-client.md                     Swift host embed guide
  trust-model.md                      security boundaries + threat model

CHANGELOG.md                          version history

Status

Verified live on Spark mainnet (23 integration tests, all green):

  • All 17 NSpark.Signer.ISparkSigner methods proxy correctly over the wire signer
  • Real Spark-to-Spark transfers, Lightning send + receive (claim), token transfer + mint + burn
  • Identity-proof handshake with NBitcoin verification
  • Opaque UUID nonce/adaptor handles with TTL eviction
  • Swift client reconnect loop with exponential backoff
  • Cancellation propagation (server Cancel → in-flight Swift task cancelled)
  • Pluggable ISignerAuthenticator + IWalletOwnershipResolver

Not yet wired (intentionally — see trust model):

  • TLS guidance and helpers (you bring your own Kestrel TLS config for now)
  • Rate limiting / per-signer request quotas
  • Metrics + structured tracing
  • Multi-instance signer registry (current registry is in-process; multi-instance topologies need sticky routing or a shared registry — flagged in docs)
  • NuGet / SwiftPM published artifacts (still consumed via project / path refs)

Languages

Language Role State Path
Swift Signer (client) Working swift/Sources/NSparkSigner/
.NET Server (host) Working dotnet/src/NSpark.Signer.Wire/
Kotlin Signer (client) Planned kotlin/ (future)

The .proto files are language-neutral — adding a new signer implementation means generating client stubs from proto/spark/signer/v1alpha1/ and implementing the 17 method handlers + the bidi stream dispatcher. Reference: see how Swift does it under swift/Sources/NSparkSigner/Dispatcher/RequestDispatcher.swift and Transport/BidiStreamConnection.swift.

Versioning

Wire protocol package is spark.signer.v1alpha1. Breaking changes are allowed while we're in v1alpha1 — we'll promote to v1 once at least two non-Swift implementations have integrated. See CHANGELOG.md.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-alpha.1 86 5/27/2026