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:
| Tier | LedgerInterface | SigningInterface | AuthInterface |
|---|---|---|---|
| Local dev / test | MemoryLedgerInterface | SoftwareSigningInterface | any |
| Community | shyware-hosted (no config) | SoftwareSigningInterface (dev) | JwksAuthInterface or CognitoAuthInterface |
| Hosted dedicated | FabricLedgerInterface (amb) | any SigningInterface | any AuthInterface |
| Self-hosted (BYOL) | Any LedgerInterface below | any SigningInterface | any 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.
| Deployment | LedgerInterface | Party count | Integrity property |
|---|---|---|---|
| Single-node chain | FabricLedgerInterface (local), CometBFTLedgerInterface (single validator) | 1 operator | Tamper-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 chain | FabricLedgerInterface (amb, multi-member), CometBFTLedgerInterface (multi-validator), EthereumLedgerInterface, AlgorandLedgerInterface, CordaLedgerInterface | ≥3 independent operators | Practically 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 store | PostgresLedgerInterface, DynamoDBLedgerInterface | 1 operator | Policy-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:
| Interface | Guarantee | Notes |
|---|---|---|
FabricLedgerInterface | Chain-enforced | FABRIC_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). |
CometBFTLedgerInterface | Chain-enforced | Talks 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. |
EthereumLedgerInterface | Chain-enforced | EVM-compatible (a private Besu/Quorum network, an L2, or public mainnet/testnet). The rejection predicate is compiled into the deployed contract, not the adapter. |
AlgorandLedgerInterface | Chain-enforced | Public Algorand (MainNet/TestNet) or a local sandbox. Fast finality — fits high-throughput consumer voting. |
CordaLedgerInterface | Chain-enforced | R3 Corda — notary-enforced uniqueness maps naturally onto the L1 replay-protection and L2 sybil-resistance checks. Fits regulated-finance embodiments (shycontracts, shycustody). |
PostgresLedgerInterface | Application-enforced | Any Postgres-compatible database (CockroachDB, pg, Supabase, RDS, Aurora, Neon) — pass a query(sql, params) → { rows } function. |
DynamoDBLedgerInterface | Application-enforced | AWS 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. |
MemoryLedgerInterface | None (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. |
CoverTrafficInterface | Decorator | Wraps 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". |
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_PROVIDER | Interface | Notes |
|---|---|---|
jwks | JwksAuthInterface | Verifies JWTs against AUTH_JWKS_URI. Requires jose. Preferred — provider-agnostic (Auth0, Okta, Azure AD, any OIDC issuer). |
cognito | CognitoAuthInterface | Verifies AccessToken (not IdToken) via aws-jwt-verify. Region derived from the pool ID prefix. |
firebase | FirebaseAuthInterface | Verifies 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_PROVIDER | Interface | Notes |
|---|---|---|
otel | OtelTelemetryInterface | Emits spans via OTLP. Reads OTEL_EXPORTER_OTLP_ENDPOINT. |
xray | XRayTelemetryInterface | Emits spans directly to X-Ray daemon UDP. |
noop | NoopTelemetryInterface | Discards 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_PROVIDER | Interface | Key env vars |
|---|---|---|
aws-kms | AwsKmsSigningInterface | SIGNING_KEY_ID, CLOUD_REGION |
gcp-kms | GcpKmsSigningInterface | SIGNING_KEY_ID (full resource name) or GCP_PROJECT + GCP_KEY_RING + GCP_KEY_NAME |
vault | VaultSigningInterface | VAULT_ADDR, VAULT_TOKEN, SIGNING_KEY_ID |
azure | AzureKeyVaultSigningInterface | SIGNING_KEY_ID (full key URI) or AZURE_VAULT_NAME + AZURE_KEY_NAME |
software | SoftwareSigningInterface | Dev/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 }
);