Sagynbaev.Tessera.Attestations
4.0.0
dotnet add package Sagynbaev.Tessera.Attestations --version 4.0.0
NuGet\Install-Package Sagynbaev.Tessera.Attestations -Version 4.0.0
<PackageReference Include="Sagynbaev.Tessera.Attestations" Version="4.0.0" />
<PackageVersion Include="Sagynbaev.Tessera.Attestations" Version="4.0.0" />
<PackageReference Include="Sagynbaev.Tessera.Attestations" />
paket add Sagynbaev.Tessera.Attestations --version 4.0.0
#r "nuget: Sagynbaev.Tessera.Attestations, 4.0.0"
#:package Sagynbaev.Tessera.Attestations@4.0.0
#addin nuget:?package=Sagynbaev.Tessera.Attestations&version=4.0.0
#tool nuget:?package=Sagynbaev.Tessera.Attestations&version=4.0.0
Tessera
Privacy-preserving, chain-agnostic identity and reputation infrastructure for .NET.
DIDs, signed attestations, selective disclosure via Merkle bundles, Bulletproof-based
predicate proofs over committed values, and on-chain anchoring of attestation roots and
revocation epochs. Plug in any network by implementing IChainAnchor — Solana, Cardano,
EVM, and Stellar adapters are included — plus generic building blocks for permissioned EVM
tokens gated by identity.
Website: https://tessera-website-sepia.vercel.app/
What this is for
- Binding humans to decentralized identifiers (
did:tessera:...). - Issuing and verifying generic attestations — humanity, phone, wallet control, region, reputation score, agent identity.
- Producing presentations a holder can hand to a verifier: Merkle inclusion plus selective predicate proofs over committed values.
- Anchoring attestation roots and revocation epochs on-chain without writing any identity data on-chain.
What this is not for
- Not a zkVM, not a proving network.
- Not a token, governance, or DAO toolkit.
- Not a prediction-market or DeFi library.
- Not a research-only cryptography experiment.
Packages
Published on nuget.org under the Sagynbaev. prefix (e.g. the Tessera.Sdk assembly ships as
the Sagynbaev.Tessera.Sdk package); namespaces remain Tessera.*.
| Package (assembly) | Purpose |
|---|---|
Tessera.Sdk |
Entry point for most consumers. High-level Holder, Issuer, Verifier facades. |
Tessera.Core |
DidId, Base58. Zero external dependencies. |
Tessera.Did |
DidDocument, DidService, IDidStore, wallet/channel binding, revocation. |
Tessera.Attestations |
Attestation, AttestationIssuer, MerkleTree, AttestationVerifier, PresentationVerifier, IIssuerRegistry, CredentialProof (incl. CombineCommitments/CombineOpenings for predicates over a sum/difference of commitments), ChainSnapshot. |
Tessera.Cryptography |
Pure-C# secp256k1, Pedersen commitments, Bulletproofs (no external deps). |
Tessera.Signing |
Production Ed25519 (NSec / libsodium). Drop-in Ed25519Verifier and Ed25519IssuerSigner. |
Tessera.EntityFrameworkCore |
EF Core IDidStore and IIssuerRegistry over any relational provider (Postgres, SQL Server, SQLite). |
Tessera.Chains.Abstractions |
IChainAnchor + IAllowlistGateway + DidHash — chain-agnostic interfaces. |
Tessera.Chains.Solana |
Solana adapter targeting the identity-registry Anchor program. |
Tessera.Chains.Cardano |
Cardano adapter (CardanoSharp + Blockfrost): CardanoChainAnchor targeting the Aiken identity-registry Plutus V3 validators (preprod), with a metadata-mode fallback. |
Tessera.Chains.Stellar |
Stellar adapter scaffold targeting a Soroban anchor contract. |
Tessera.Chains.Midnight |
Midnight adapter scaffold — Compact contract + transaction layer pending (reads report no anchor, writes throw NotSupported). |
Tessera.Chains.Evm |
Generic EVM adapter (Nethereum): EvmChainAnchor + EvmAllowlistGateway, any chainId/RPC. |
Tessera.Sources.Sumsub |
Layer-2 plugin: Sumsub KYC → kyc_verified / jurisdiction attestations. |
Tessera.Sources.XRoad |
Layer-2 plugin: X-Road government registry → residency / property / encumbrance. |
Tessera.Sources.Bitcoin |
Layer-2 plugin: proven control of Bitcoin addresses (BIP-137 signed challenge) → btc_control (address count only) + Pedersen-committed btc_balance / btc_hodl_age, each bound to a point-in-time chain snapshot (height/hash/time). Esplora (mempool.space / blockstream.info) provider. |
🔒 4.0.0 — the security-hardening release. On top of the v3.2.0 baseline (authenticated holder presentations, fail-closed revocation, address-bound wallet binding, authenticated on-chain anchors — see Security), a multi-round audit (no Critical findings) made
Tessera.Cryptographyconstant-time (limb-based arithmetic + a branchless scalar multiplication, cross-checked against an independent BouncyCastle oracle), rejected non-canonical encodings, added controller-authenticated wallet binding, and removed the legacy duplicate crypto stack (breaking). It is still from-scratch managed code, so an external crypto audit remains recommended. Threat model, findings & limitations: docs/security-audit-readiness.md · full changelog: CHANGELOG.md.
Repository layout
Tessera/
├── src/
│ ├── Tessera.Core/ DidId, Base58
│ ├── Tessera.Did/ DID model + service
│ ├── Tessera.Channels/ channel binding (internal, not packaged)
│ ├── Tessera.Attestations/ Attestations + Merkle + CredentialProof + schema registry
│ ├── Tessera.Cryptography/ secp256k1 + Bulletproofs
│ ├── Tessera.Signing/ Ed25519 (NSec)
│ ├── Tessera.EntityFrameworkCore/ Postgres/SQL Server/SQLite stores
│ ├── Tessera.Chains.Abstractions/ IChainAnchor, IAllowlistGateway, DidHash
│ ├── Tessera.Chains.Solana/ Solana adapter (Solnet)
│ ├── Tessera.Chains.Cardano/ Cardano adapter (CardanoSharp + Blockfrost, Aiken Plutus V3)
│ ├── Tessera.Chains.Evm/ Generic EVM adapter (Nethereum) + allowlist gateway
│ ├── Tessera.Chains.Stellar/ Stellar adapter scaffold
│ ├── Tessera.Chains.Midnight/ Midnight adapter scaffold (Compact + tx layer pending)
│ ├── Tessera.Sdk/ Holder, Issuer, Verifier, IssuancePipeline, policy
│ ├── Tessera.Sources.Sumsub/ Layer-2 plugin: Sumsub KYC
│ ├── Tessera.Sources.XRoad/ Layer-2 plugin: X-Road government registry
│ └── Tessera.Sources.Bitcoin/ Layer-2 plugin: proof of Bitcoin control + balance (NBitcoin)
│
├── chains/
│ ├── solana/programs/identity-registry/ Anchor program (adapter: complete)
│ ├── evm/ Hardhat: IdentityRegistry, Allowlist, PermissionedToken
│ ├── cardano/contracts/identity-registry/ Aiken validators (Plutus V3, preprod; adapter: complete)
│ └── stellar/contracts/attestation-verifier/ Soroban contract (adapter: in progress)
│
├── examples/
│ ├── PrivacyApps/ ConfidentialTransfer, SealedBidAuction, PrivateVoting
│ ├── PermissionedToken/ Layer-3 reference: compliance flow end-to-end
│ ├── CardanoCreditLine/ Income attestation → anchor on Cardano preprod → verify
│ └── BitcoinCreditLine/ Proof of Bitcoin balance ≥ 1 BTC → anchor on Cardano → verify
│
├── Tessera/ v2.x monolith — kept for backward compat
└── docs/
├── architecture.md layering, packages, on-chain/off-chain boundary
└── security-audit-readiness.md audit dossier, threat model, known limitations
See docs/architecture.md for the on-chain/off-chain boundary and the package dependency rules.
Quick start
The SDK is the entry point. The three facades cover the three roles in any attestation flow: holder, issuer, verifier.
Install
Package IDs are prefixed
Sagynbaev.on nuget.org (the bareTessera/Tessera.*IDs are owned by other authors). The assembly names and namespaces are unchanged — you still writeusing Tessera.Sdk;. The single meta-package isSagynbaev.Tessera.
dotnet add package Sagynbaev.Tessera.Sdk
dotnet add package Sagynbaev.Tessera.Signing
# pick the chain adapter you need:
dotnet add package Sagynbaev.Tessera.Chains.Cardano # or Sagynbaev.Tessera.Chains.Solana / .Chains.Evm
# pick a store (or use the in-memory one for tests):
dotnet add package Sagynbaev.Tessera.EntityFrameworkCore
Holder side — create a DID, accept an attestation, present it
using Tessera.Sdk;
using Tessera.Signing;
using Tessera.Did;
// One-time keypair for the human/agent who controls this DID.
var (controllerPriv, controllerPub) = Ed25519.GenerateKeypair();
var holder = await Holder.CreateAsync(controllerPub, new HolderOptions
{
Store = new InMemoryDidStore(), // or EfCoreDidStore for Postgres
SignatureVerifier = new Ed25519Verifier(),
ChainAnchor = solanaAnchor, // optional; null = offline mode
});
// `holder.Did` is "did:tessera:<base58(sha256(pubkey||"v1"))>" — deterministic, not chosen.
// Later: accept an issuer-signed attestation, anchor the new root on-chain.
holder.AcceptAttestation(attestationFromIssuer);
await holder.AnchorRootAsync();
// Build a presentation for a relying app, disclosing only what it needs.
// The holder AUTHENTICATES it by signing the canonical challenge with the controller
// key; the matching public key is embedded so the verifier re-derives the DID and
// checks the signature with no store lookup.
var presentation = holder.BuildSignedPresentation(
verifier: new DidId("did:tessera:my-relying-app"),
attestationTypes: new[] { "phone_verified" },
sessionNonce: RandomBytes(16),
asOfRevocationEpoch: 0,
chain: "solana",
signChallenge: ch => Ed25519.Sign(controllerPriv, ch.Span));
Issuer side — sign attestations, publish your key
using var signer = new Ed25519IssuerSigner(issuerPrivateKey);
var issuer = new Issuer(new DidId("did:tessera:my-issuer-service"), signer);
var attestation = issuer.Issue(
type: AttestationTypes.PhoneVerified,
subject: subjectDid,
payload: new AttestationPayload { Method = "twilio_v2" },
validity: TimeSpan.FromDays(365));
// Register yourself once so verifiers can find you:
await issuerRegistry.RegisterAsync(issuer.BuildRegistryRecord(
schemaUri: "https://schemas.zkp/attestation/v1"));
Verifier side — check a presentation against a policy
var verifier = new Verifier(new VerifierOptions
{
IssuerRegistry = issuerRegistry,
SignatureVerifier = new Ed25519Verifier(),
ChainAnchor = solanaAnchor,
});
var result = await verifier.VerifyPresentationAsync(presentation, new VerificationPolicy
{
ExpectedVerifier = new DidId("did:tessera:my-relying-app"),
ExpectedSessionNonce = nonceIssuedAtSessionStart,
RequireCurrentRevocationEpoch = true,
});
if (!result.Valid)
return Unauthorized(result.Reason); // e.g. "verifier_mismatch", "revocation_stale"
Predicate proof over a committed attestation value
For attestations carrying a Pedersen commitment, the holder proves a predicate
(e.g. income ≥ 50,000) without revealing the value. Bulletproofs on secp256k1,
implemented from scratch. The proof is bound to the attestation's commitment, so it
cannot be reused for a different value:
using Tessera.Attestations;
var cp = new CredentialProof();
// Issuer commits to the value in the attestation; the holder keeps the opening.
var (commitment, opening) = cp.CommitValue(85_000);
var attestation = issuer.Issue(AttestationTypes.Accredited, subjectDid,
new AttestationPayload { Method = "payroll", Commitment = commitment });
// Holder proves income ≥ 50,000, bound to that commitment.
var bundle = cp.ProveBoundMinimum(actualValue: 85_000, minimumRequired: 50_000, opening, label: "income");
// Verifier confirms the proof is valid AND about this attestation's committed value.
bool valid = cp.VerifyBound(commitment, bundle); // learns only "income ≥ 50,000"
The verifier policy enforces this declaratively via PredicateRequirement (see
docs/architecture.md). The unbound ProveMinimum/Verify remain
available as a standalone primitive but are not accepted by the policy.
Storage
IDidStore and IIssuerRegistry are pluggable. Two implementations ship:
InMemoryDidStore/InMemoryIssuerRegistry— for tests and offline dev.EfCoreDidStore/EfCoreIssuerRegistry— EF Core 8, provider-agnostic.
Postgres example:
services.AddDbContext<TesseraDbContext>(opts =>
opts.UseNpgsql(connectionString));
services.AddScoped<IDidStore, EfCoreDidStore>();
services.AddScoped<IIssuerRegistry, EfCoreIssuerRegistry>();
services.AddSingleton<ISignatureVerifier, Ed25519Verifier>();
Generate migrations against your chosen provider:
dotnet ef migrations add InitialTessera --project Tessera.EntityFrameworkCore
Chains
The on-chain layer stores only Merkle attestation roots and revocation epochs. DID documents, attestations, and proofs are never written on-chain.
| Chain | Status | Code |
|---|---|---|
| Solana | Adapter complete; one-command devnet deploy (scripts/deploy-devnet.sh) — the env-gated smoke suite runs live against the deployed program; record the id in chains/solana/DEPLOYMENT.md |
chains/solana/ |
| EVM | Adapter complete; contracts + ABI checked in | chains/evm/ |
| Cardano | Adapter complete; Aiken Plutus V3 validators (preprod) — aiken check green, blueprint checked in; preprod script addresses in chains/cardano/DEPLOYMENT.md |
chains/cardano/ |
| Stellar | Adapter scaffold; anchor contract pending | chains/stellar/contracts/attestation-verifier/ |
| Midnight | Adapter scaffold; Compact contract + tx layer pending (mainnet is live) | src/Tessera.Chains.Midnight/ |
The Solana adapter speaks to a minimal Anchor program whose four core anchoring instructions are
register_did, update_root, bump_revocation, register_issuer (plus admin-gated
initialize / deactivate_issuer). The EVM adapter
(Tessera.Chains.Evm) drives the equivalent IdentityRegistry.sol
on any EVM network — chainId/RPC/contract are pure configuration. The Cardano adapter
(Tessera.Chains.Cardano) drives the same four operations under eUTXO via the Aiken
identity-registry Plutus V3 validators
(state-thread tokens + inline datums, preprod), with a metadata-mode fallback for demos. Off-chain
verification stays in C#. Native Midnight integration is planned — see Roadmap.
Permissioned tokens (reference)
Generic building blocks let any permissioned EVM token gate ownership on Tessera identity, with zero token/provider specifics in the core:
IAllowlistGateway+EvmAllowlistGatewayreflect an off-chain verification decision onto an on-chain transfer-restriction contract (Add/Revoke), compatible with a simple allowlist or an ERC-3643/T-REX whitelist module via configuration.IssuancePipelineturns pluggableIAttestationSources (e.g. Sumsub, X-Road, Bitcoin) into signed attestations;VerificationPolicydeclares required types + predicate (range-proof) rules.
examples/PermissionedToken assembles these into the target
scenario — a permissioned BEP-20 (PermissionedToken.sol)
whose transfers are gated by the allowlist. Its end-to-end test walks KYC/registry onboarding →
DID + attestations → presentation → policy → allowlist admission → token ownership, then revokes
KYC and shows transfers are blocked. See docs/security-audit-readiness.md
for the audit dossier and known limitations.
v2 → v3
v3 is a breaking cut from the v2.x monolith. v2.x consumers keep working until they upgrade.
| v2 type | v3 replacement |
|---|---|
Tessera.Core.Zkp (HMAC equality) |
Removed. Use CredentialProof for ZK predicates. |
Tessera.Interfaces.IBlockchain |
Tessera.Chains.IChainAnchor. |
Tessera.Integration.Stellar.* |
Tessera.Chains.Stellar. |
Tessera.Crypto.* |
Tessera.Cryptography. |
Tessera.Privacy.CredentialProof |
Tessera.Attestations.CredentialProof. |
Security
The current release (4.0.0) is the security-hardening release — constant-time cryptography, canonical encodings, controller-authenticated wallet binding, and removal of the legacy duplicate crypto stack — on top of the v3.2.0 baseline. The guarantees that matter at the trust boundary:
- Authenticated holder presentations. A presentation carries the holder's controller
public key (
PresentationBinding.HolderPublicKey, 32-byte Ed25519) and a signature over a canonicalPresentationChallenge(verifier, session nonce, revocation epoch, chain,CreatedAt, disclosed leaf hashes). The verifier confirmsDidId.FromControllerKey(HolderPublicKey) == Holderand checks the signature — so audience, nonce, epoch and freshness are all holder-authenticated, not just plaintext fields. Build withHolder.BuildSignedPresentation(..., signChallenge: ch => Ed25519.Sign(controllerPriv, ch.Span)), or split viaBuildPresentationChallenge+BuildPresentation(..., holderSignature, createdAt)for out-of-band / hardware signers. - Fail-closed revocation + freshness. When a chain anchor is configured, a presentation
bound to an epoch older than the chain's current epoch is rejected unconditionally.
RequireCurrentRevocationEpochadditionally demands a reachable anchor and an EXACT match to the current epoch (it no longer silently skips whenExpectedAnchorRootis supplied). A presentation freshness window (MaxPresentationAge/MaxClockSkew, both authenticated via the signedCreatedAt) bounds replay. - Address-bound, controller-authenticated wallet binding. Binding a wallet to a DID proves the
bound address is controlled by the wallet key via
IWalletControlVerifier(the default covers Solana base58(pubkey) and fails closed on chains it does not understand).BuildWalletChallengebinds the wallet pubkey, and binding nonces are single-use through anINonceStore. The authenticatedBindWalletAsyncoverload additionally requires a DID-controller signature overBuildWalletBindAuthChallenge, so a wallet owner cannot attach their wallet to another principal's DID.DidService.GetActiveAsyncenforces revocation on resolve; pepper providers reject all-zero / low-entropy peppers. - Constant-time, malleability-resistant primitives.
Tessera.Cryptographysecp256k1 is constant-time (fixed 4×64-bit limb arithmetic; a fixed-iteration, branchlessPoint.ScalarMulover complete add/double formulas), so the secret scalar does not leak through timing. Point and proof-scalar deserialization rejects non-canonical encodings (x ≥ p,s ≥ n) rather than reducing them, closing byte-malleability of commitments/proofs. Both are cross-checked against an independent BouncyCastle oracle. The issuer registry refuses to overwrite an existing issuer's public key, and attestation verification supports an expiry cap (MaxAttestationAge/RequireExpiry). - Source ↔ DID binding. Sumsub KYC requires the applicant
externalUserIdto equal the subject DID; X-Road uses server-asserted identifiers verified against the request. Both clients require HTTPS. - Authenticated on-chain anchors. EVM
IdentityRegistry.registerDidrequires a controller ECDSA signature bound to(didHash, root, chainid, contract); the C# adapter signs it and checksreceipt.Statuson every write. Solanaregister_issueris admin-gated via aRegistryConfigPDA (initialize(admin)/deactivate_issuer); the adapter fails closed on RPC errors and checks the owning program + discriminator. Cardano metadata-mode reads authenticate the controller (tx input address + embedded controller signature overdid_hash‖root‖epoch) and the Aikenissuer_registryis governance-gated. - Soroban Ed25519. The Stellar
attestation-verifierwas redesigned to Ed25519 public-key verification with admininitialize/set_issuer; the trusted issuer key lives in contract storage. The HMAC secret is no longer a caller-supplied argument — any--hmac_key/ZKP_HMAC_KEYusage is removed. - Reference apps. In
examples/PrivacyApps, voting uses a 1-bit range proof ({0,1}); the sealed-bid auction enforces both bounds; the confidential transfer checks Pedersen balance conservation (amount + change == sender balance) in addition to the range proofs.
Caveats: Tessera.Cryptography is now constant-time and rejects non-canonical encodings, but it
is still from-scratch managed code — an external cryptography audit remains recommended, and two
BREAKING wire-format hardening items (length-prefixed Fiat–Shamir transcript labels; RFC-6962 Merkle
odd-node handling) plus a type-tagged claim-canonicalization format are deferred. The Anchor (Solana),
Aiken (Cardano) and Solidity (EVM) contract changes above are source-level: they require the
respective toolchain build (anchor build / aiken build + regenerated plutus.json / Hardhat
compile) and redeploy to take effect on a live network. See
docs/security-audit-readiness.md for the full threat model.
Roadmap
Planned, not yet shipped:
- Cardano mainnet — the preprod path is live today (see the Chains table); mainnet is the next step.
- External security audit of
Tessera.Cryptography— now constant-time and BouncyCastle cross-checked, but still self-implemented; the audit dossier is already public in docs/security-audit-readiness.md. - Native Midnight integration — a zkSNARK stack with selective disclosure on Midnight. Does not exist today.
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. 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. |
-
net8.0
- Sagynbaev.Tessera.Core (>= 4.0.0)
- Sagynbaev.Tessera.Cryptography (>= 4.0.0)
NuGet packages (7)
Showing the top 5 NuGet packages that depend on Sagynbaev.Tessera.Attestations:
| Package | Downloads |
|---|---|
|
Sagynbaev.Tessera.Sources.Sumsub
Layer-2 plugin: maps a Sumsub KYC result to Tessera AttestationDrafts (kyc_verified, jurisdiction). Implements IAttestationSource; depends only on the core. |
|
|
Sagynbaev.Tessera.Sources.XRoad
Layer-2 plugin: maps government-registry lookups over X-Road to Tessera AttestationDrafts (jurisdiction/residency, property_right, encumbrance). Implements IAttestationSource; depends only on the core. |
|
|
Sagynbaev.Tessera.Signing
Production Ed25519 signing/verification for Tessera. Wraps NSec.Cryptography (libsodium) and implements ISignatureVerifier and IIssuerSigner so consumers do not need to bring their own crypto. |
|
|
Sagynbaev.Tessera
Privacy-preserving identity and reputation infrastructure for .NET. DIDs, attestations, selective disclosure via Merkle bundles, and Bulletproof predicate proofs over committed values. Meta-package — references Tessera.Core, Tessera.Did, Tessera.Attestations, Tessera.Cryptography, Tessera.Chains.Abstractions. |
|
|
Sagynbaev.Tessera.Sdk
High-level facades for Tessera: Holder, Issuer, Verifier. The entry point most consumers should use. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.0.0 | 0 | 6/17/2026 |
| 3.3.1 | 0 | 6/17/2026 |
| 3.3.0 | 114 | 6/14/2026 |
| 3.3.0-preview.2 | 64 | 6/14/2026 |
| 3.3.0-preview.1 | 65 | 6/14/2026 |
| 3.2.0 | 119 | 6/13/2026 |
| 3.1.1 | 128 | 6/10/2026 |