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)FabricLedgerInterface, PostgresLedgerInterface, or DynamoDBLedgerInterfaceany SigningInterfaceany AuthInterface

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:

InterfaceFABRIC_MODE / notes
FabricLedgerInterfaceamb (AWS Managed Blockchain) or local (Docker / native systemd peer)
PostgresLedgerInterfaceAny Postgres-compatible database (CockroachDB, pg, Supabase, RDS, Aurora, Neon) — pass a query(sql, params) → { rows } function.
DynamoDBLedgerInterfaceAWS 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.
MemoryLedgerInterfaceIn-process Map-backed store. No persistence. Use in unit tests and local dev — no server required. Exposes l1Entries(scopingId) and l2Entries(scopingId) test helpers.
CoverTrafficInterfaceDecorator wrapping 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

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

export class MyLedgerInterface extends LedgerInterface {
async submitTwoListWrite(scopingId, list1, list2) {
// Must atomically write L1 (payload, no identity) and L2 (identity, no payload)
// Must enforce: no join key between L1 and L2 in any reachable canonical state
}
// ... remaining methods
}

The invariant is enforced at the state-machine level. A custom interface writing to a state machine without the rejection predicate does not provide the two-list guarantee.


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 }
);