Skip to main content

Interfaces

Overview

Every shyware deployment wires together four interfaces. The application code — submitTwoListWrite, commitPeriodClose, voteSubmission, etc. — is identical regardless of which concrete interfaces are active. Only environment variables change between environments.

Consumer app
└── LedgerInterface — where the two-list canonical state lives
└── AuthInterface — how bearer tokens are verified
└── TelemetryInterface — where spans and traces go
└── SigningInterface — who signs period-close attestations

Which concrete implementations you need depends on your deployment tier:

TierLedgerInterfaceSigningInterfaceAuthInterface
Local dev / testMemoryLedgerInterfaceSoftwareSigningInterfaceany
Communityshyware-hosted (no config)SoftwareSigningInterface (dev)JwksAuthInterface or CognitoAuthInterface
Hosted dedicatedFabricLedgerInterface (amb)any SigningInterfaceany AuthInterface
Self-hosted (BYOL)Any LedgerInterface belowany SigningInterfaceany AuthInterface

Two different guarantees behind one interface

Every LedgerInterface implementation exposes the same five methods and produces the same shape of result. They do not all provide the same integrity guarantee, and picking one without understanding the difference is the single most consequential choice in a deployment.

Chain-enforced (structural). The rejection predicate — no join key between L1 and L2 in any reachable canonical state — is compiled into the state-transition function itself and enforced by every node that validates a block. No single party, including the operator, can write a state the chain will accept as valid if it violates the invariant. This is the property the patent's "improvement to computer architecture" framing (§101) rests on.

Application-enforced (policy). The same shape of separation (two tables, no shared join column) is written by adapter code running with full database credentials. It is a correct, auditable implementation of the pattern — but the guarantee that it stays that way depends on the adapter code being correct and the DB operator not writing around it. This is enforcement by policy, not by architecture; it is exactly the distinction the patent's own "authority-partitioned utility preservation" framing draws between canonical state and everything else.

Neither tier is "wrong" — they serve different threat models and cost points. But describe them accurately: a PostgresLedgerInterface deployment is not "running on a blockchain," it is running the two-list pattern on a conventional database.

DeploymentLedgerInterfaceParty countIntegrity property
Single-node chainFabricLedgerInterface (local), CometBFTLedgerInterface (single validator)1 operatorTamper-evident. Chain-enforced rejection predicate; every write is signed and hash-chained. One operator controls all nodes, so a sufficiently privileged insider could roll back or fork the chain — but not silently: KMS-signed period-close attestations, CloudWatch/X-Ray traces, and S3 Object Lock audit logs are independent of the ledger operator and would show the discontinuity. Cost-efficient; this is the tier most self-hosted (BYOL) deployments should target.
Multi-party chainFabricLedgerInterface (amb, multi-member), CometBFTLedgerInterface (multi-validator), EthereumLedgerInterface, AlgorandLedgerInterface, CordaLedgerInterface≥3 independent operatorsPractically tamper-proof. No single operator, including the deployment's own, can rewrite accepted history without the collusion of an independent majority. This is the top-tier guarantee and the deployment shape the patent's reference architecture assumes.
Application-enforced storePostgresLedgerInterface, DynamoDBLedgerInterface1 operatorPolicy-enforced. Same two-list shape, no on-chain/consensus layer underneath it. Appropriate for dev, low-stakes internal tools, or a bridge while standing up a real chain — not a substitute for one in a deployment where the anonymity/non-derivability claims matter.

LedgerInterface

Interface (adapters/ledger/interface.js):

export class LedgerInterface {
async submitTwoListWrite(scopingId, list1, list2) {} // atomic two-list write
async getCount(scopingId) {} // count-match query
async rescindTwoListWrite(scopingId, submissionId, identityHash) {} // bilateral withdrawal
async replaceTwoListWrite(scopingId, oldId, newList1, identityHash) {} // replace List 1 entry
async commitPeriodClose(scopingId, l1Root, l2Root, attestation) {} // period-close attestation
async disconnect() {}
}

The interface is the only place in the consumer stack that knows the ledger's address, TLS configuration, or authentication credentials.

Concrete implementations:

InterfaceGuaranteeNotes
FabricLedgerInterfaceChain-enforcedFABRIC_MODE=local (native systemd peer/orderer, single-node) or amb (AWS Managed Blockchain — single account today; genuinely multi-party once additional AWS-account members join the network).
CometBFTLedgerInterfaceChain-enforcedTalks to the Go core's own ABCI state machine — the reference implementation the patent describes. COMETBFT_VALIDATORS — one endpoint for single-node dev/production, several for a real validator set.
EthereumLedgerInterfaceChain-enforcedEVM-compatible (a private Besu/Quorum network, an L2, or public mainnet/testnet). The rejection predicate is compiled into the deployed contract, not the adapter.
AlgorandLedgerInterfaceChain-enforcedPublic Algorand (MainNet/TestNet) or a local sandbox. Fast finality — fits high-throughput consumer voting.
CordaLedgerInterfaceChain-enforcedR3 Corda — notary-enforced uniqueness maps naturally onto the L1 replay-protection and L2 sybil-resistance checks. Fits regulated-finance embodiments (shycontracts, shycustody).
PostgresLedgerInterfaceApplication-enforcedAny Postgres-compatible database (CockroachDB, pg, Supabase, RDS, Aurora, Neon) — pass a query(sql, params) → { rows } function.
DynamoDBLedgerInterfaceApplication-enforcedAWS DynamoDB — three tables (shy_l1, shy_l2, shy_period_close); two-list write uses TransactWriteItems for atomicity. Peer deps: @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb.
MemoryLedgerInterfaceNone (test only)In-process Map-backed store. No persistence. Use in unit tests and local dev — no server required. Exposes l1Entries(scopingId) and l2Entries(scopingId) test helpers.
CoverTrafficInterfaceDecoratorWraps any real interface. Fires timing-indistinguishable dummy writes at deployment.cover_traffic_rate per minute. Dummies are stripped before any canonical write — count-match invariant always preserved. Enabled via deployment.submission_dispatch: "cover_traffic".
warning

list2.identityHash must include the scopingId as a derivation input: H(uid || scopingId). Using H(uid) alone breaks cross-scoping unlinkability.

Use deriveIdentityHash(uid, scopingId) from protocol/submissionId.js.

Implementing a custom LedgerInterface

Any chain or store your consumer developers already operate can become a LedgerInterface — that's the point of the interface. Two paths, depending on what you're wrapping:

Wrapping an existing chain/database as-is (application-enforced tier). Follow dynamodb.js or cockroach.js as your template: dynamic-import the client SDK inside each method (keeps it an optional peer dependency, not a hard install for every consumer), write L1/L2 as separate rows/documents/keys with no shared join column, and implement _rejectIfJoinable the same way dynamodb.js does — a cheap, mechanical guard that at least catches an accidental join key even though the deeper guarantee here is still policy, not structure.

Wrapping a chain with a deployed contract/chaincode (chain-enforced tier). Follow fabric.js + shyware.go as your template — the adapter is a thin client; the actual rejection predicate, replay protection, and sybil-resistance checks must be compiled into the on-chain program itself (a Solidity contract, TEAL program, CorDapp contract, ABCI application — whatever your chain's unit of on-chain logic is called), not left to the adapter's client-side JS. Port shyware.go's four checks into your chain's contract language:

import { LedgerInterface } from '@co-mission/shyware-sdk/adapters/ledger/interface.js'

export class MyLedgerInterface extends LedgerInterface {
async submitTwoListWrite(scopingId, list1, list2) {
// Thin client only. The deployed contract/chaincode must itself enforce:
// 1. no join key between list1.submissionId and list2.identityHash
// 2. sybil resistance: reject if this identityHash already has an L2
// entry for this scopingId
// 3. replay protection: reject if this submissionId already exists in L1
// 4. count-match: L1 and L2 counts move together, atomically
}
// ... remaining methods
}

A custom interface whose client enforces the rejection predicate, rather than the chain it writes to, is application-enforced regardless of which chain it targets — see the guarantee table above before documenting it as "chain-enforced" to your own consumer developers.


AuthInterface

AUTH_PROVIDERInterfaceNotes
jwksJwksAuthInterfaceVerifies JWTs against AUTH_JWKS_URI. Requires jose. Preferred — provider-agnostic (Auth0, Okta, Azure AD, any OIDC issuer).
cognitoCognitoAuthInterfaceVerifies AccessToken (not IdToken) via aws-jwt-verify. Region derived from the pool ID prefix.
firebaseFirebaseAuthInterfaceVerifies Firebase ID tokens via Google's public JWKS. Requires jose and FIREBASE_PROJECT_ID.

Both interfaces produce the same uid string for the same authenticated user.


TelemetryInterface

TELEMETRY_PROVIDERInterfaceNotes
otelOtelTelemetryInterfaceEmits spans via OTLP. Reads OTEL_EXPORTER_OTLP_ENDPOINT.
xrayXRayTelemetryInterfaceEmits spans directly to X-Ray daemon UDP.
noopNoopTelemetryInterfaceDiscards all spans. For test environments.

Span names are identical across interfaces: fabric.peer.submit, fabric.peer.query, fabric.peer.period-close.


SigningInterface

All interfaces produce Sign(SHA-256(l1MerkleRoot || l2MerkleRoot || scopingId || timestamp)) — verifiable by any party with the public key.

SIGNING_PROVIDERInterfaceKey env vars
aws-kmsAwsKmsSigningInterfaceSIGNING_KEY_ID, CLOUD_REGION
gcp-kmsGcpKmsSigningInterfaceSIGNING_KEY_ID (full resource name) or GCP_PROJECT + GCP_KEY_RING + GCP_KEY_NAME
vaultVaultSigningInterfaceVAULT_ADDR, VAULT_TOKEN, SIGNING_KEY_ID
azureAzureKeyVaultSigningInterfaceSIGNING_KEY_ID (full key URI) or AZURE_VAULT_NAME + AZURE_KEY_NAME
softwareSoftwareSigningInterfaceDev/test only — key is ephemeral

Protocol derivation functions

import {
deriveSubmissionId, // beacon-conditioned: H(canonicalBlockHash || nonce)
deriveIdentityHash, // scoping-bound: H(uid || scopingId)
generateSubmissionNonce
} from '@co-mission/shyware-sdk/protocol/submissionId.js';

const blockHash = await ledgerInterface.getLatestBlockHash();
const nonce = generateSubmissionNonce();
const submissionId = await deriveSubmissionId(blockHash, nonce);
const identityHash = await deriveIdentityHash(uid, scopingId);

await ledgerInterface.submitTwoListWrite(
scopingId,
{ submissionId, payloadCommitment: H(payload) },
{ identityHash }
);