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) | FabricLedgerInterface, PostgresLedgerInterface, or DynamoDBLedgerInterface | any SigningInterface | any 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:
| Interface | FABRIC_MODE / notes |
|---|---|
FabricLedgerInterface | amb (AWS Managed Blockchain) or local (Docker / native systemd peer) |
PostgresLedgerInterface | Any Postgres-compatible database (CockroachDB, pg, Supabase, RDS, Aurora, Neon) — pass a query(sql, params) → { rows } function. |
DynamoDBLedgerInterface | 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 | 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 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". |
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_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 }
);