Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

475 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wallet Core (@interop/wallet-core)

Node.js CI NPM Version

Shared wallet-domain logic (WAS sync engine core and wallet Space layout contracts) for Interop wallet apps.

Table of Contents

Background

@interop/wallet-core is the shared, correctness-critical code two WAS-enabled wallet apps (a React Native mobile wallet and a browser wallet) hold in common: the cross-replica byte-compatibility surface both must agree on to converge on identical bytes. It is isomorphic (browser, Node.js, React Native) and has no UI or storage dependencies -- side effects are injected, and the dep-heavier protocol subpaths are import-directly-only.

The subpaths:

  • @interop/wallet-core/sync -- the Wallet Attached Storage (WAS) replication engine core: the SyncEngine orchestration (single-flight, migrate-once, memoized provisioning invalidated on demand via invalidateProvisioning, backoff), the runPull / runPush algorithms, the replica-side SyncStore seam, and the generic SyncedCollectionSpec shape. The wire contract and port (PwsSyncPort, WireDoc, DocCipher, ...) are re-exported from @interop/pws-client so an engine consumer imports one package.

  • @interop/wallet-core/space -- the wallet Space layout contract: the shared collection ids and descriptive specs (private-credentials, public-credentials, wallet-activity, app-keys), the wallet-activity wire shape with its pure addHistory* payload builders, the publicCredentialUrl derivation, and the pws-link QR hand-off contract (buildPwsLinkPayload / parsePwsLinkPayload / encodePwsLinkSecret) with its structural recognizer, isPwsLinkPayload. It also carries the inbox collection spec and the inbox message envelope (inboxGrantMessage / parseInboxGrantMessage). Contacts collection specs live in @interop/social-core.

  • @interop/wallet-core/webvh -- the account's did:webvh identity: the hosted DID log, its per-client update-key rotation, the client enrollment entries, the one-entry client-revocation edit (verification methods, update key, and standing commitments out in a single entry, the staged commitment recovered by log attribution), the enrolled-client listing over a caller-verified log (for a "your wallets" surface), the fetch-and-verify-the-published-log step those ceremonies share (and its discovery twin, which takes a Space's owner from the log published there), the PWS-backed store they write through, ZCap signing under the did:webvh verification-method id, and a standing credential's update-key ladder (the rung and ladder-VM derivation, and the attribution walks that recover a credential's acting rung and standing inventory from the log).

  • @interop/wallet-core/resourceLog -- the wallet-domain half of the Resource Log Profile, the hash-linked log format governing key resources co-managed between a wallet's clients and the storage server. Three pieces. The did:webvh controller adapter, which answers with the verified account document at a given version (the verifier dereferences each proof's method against it) and supplies the admission hook every verifier consults. The ceremony-tail license that hook carries, which bounds what a standing credential's ladder key may append -- above all refusing a silent rekey against an unchanged document. And a re-export of @interop/vh-resource-log's isResourceLogRefusal, the shared reading of the refusal taxonomy: which refusals a reader must not paper over with a cached copy, and which one it may (a chain-head rollback, reconcilable divergence). The adapter and the license read the account document through one leaf beside them: ladder-VM recognition, the credential class, and the two key-class readers, over the library's relation resolution. They therefore answer exactly as the client listing does. That leaf's public home is the webvh subpath. The generic half lives in @interop/vh-resource-log -- chain verification against an adversarial host, the chain-head pin, the entry builders, the read/append/create path, and the sealing sweep. Transport is in @interop/pws-client's /log subpath, and the hashing and proof kernel in @interop/did-method-webvh.

  • @interop/wallet-core/keys -- the user key and its wrap-set roster, governed by the key-map/user-key.jsonl resource log: minting, the roster's init/read/rotate primitives with their client-side guards (the verified log itself, the latest-seen epoch pin, the document-backed recipient resolver), the log-governed (and sealable) descriptor store those primitives drive, and the user key rotation cascade's per-collection op (re-epoch a collection onto the roster's current user key, staleness detected from durable state alone, history escrowed -- also the completion sweep's building block). The decisions behind that op and the roster convergence the login sweep runs are exported as pure predicates, userKeyRosterDrift and collectionEpochsCurrent. Also ensureWalletSpaceEpochs, the provision-time install of each encrypted wallet collection's key epoch[0] (a fresh random epoch key wrapped to the user key) -- the EDV-bearing second step of provisionWalletSpace. Also walletSpaceProvisioner, which builds the sync engine's ensureProvisioned closure over both steps, single-flight across concurrent callers. Also the client-key record codec: the contents and strict validation of the local record each wallet client keeps its own key material in (storage and wrapping stay app-side).

  • @interop/wallet-core/clients -- the enrolled-client management surface: the listing over the locally verified did:webvh log with names joined from the connections directory's wallet-client entries, the disconnect-eligibility policy as pure functions, the revocation cascade orchestrator (document edit, roster rotation with its seal backstop, collection fan-out, optional recovery re-mints), and the login-time roster policy (which now also seals a converged roster's governing log).

  • @interop/wallet-core/descriptors -- the log-governed descriptor source: every read re-verifies the collection's governing resource log, keyed per collection by collectionDescriptorLogPinId. It implements the EncryptionDescriptorSource seam of @interop/pws-client/edv, where the acquisition, cache fallback, unknown-epoch refresh policy, and self-refreshing EDV document cipher live.

  • @interop/wallet-core/keyring -- the unlock layer: the unlock derivation, the signed { version, encryption, wrapped, proof } account-pointer record codec (the proof is verified before the record is decrypted), and the unlock Space lifecycle.

  • @interop/wallet-core/connections -- the connections directory, one encrypted entry per party the wallet has dealt with (an app, an agent, a wallet client, a contact, a replica server): the entry codec, which reads leniently and keeps unknown members; the resource id keyed by the collection's blinded-index key; the PWS-backed store over the documents feed; the grant index a revocation reads, with the checks that drop a grant delegated to another party or targeting another Space; the compare-and-swap upsert helpers (recordGrants, removeGrants, retireConnection, unretireConnection, setConnectionLabel); the replica-server helpers over a replicas list (recordReplicas, recordReplicaGrant, settleReplicaRegistration, setReplicaMarker, clearReplicaMarker, removeReplica); and the writer arm on a wallet client's entry (lazy registration, the throttled touch, the per-entry sweep, and the join a history view resolves a revision's writerId through). An agent's entry also carries the wallet's side of the connection: the pairwise seed and its tag (connectionDidKey derives the did:key), the grants the agent handed the wallet, and the outbox of pending messages. Their readers (receivedGrants, liveInboxChannel, agentGrantDue, ...) and writers (recordReceivedGrants, recordRenewedGrants, settleOutboxItem, pruneSupersededGrants, dropReceivedGrant, clearReceivedGrants, markDeclined) live here too, as does the proof check a renewal runs over a recorded grant (verifyRecordedGrantProof, which verifies the delegation signature under a key the current document lists). The wallet writes every entry; no party ever writes one.

  • @interop/wallet-core/audiences -- the audience collection: a plaintext collection in the owner's Space whose posts are its Resources. It carries the provisioning attributes every audience shares (AUDIENCE_PROVISION_ATTRIBUTES) and the member grant (delegateAudienceGrant, GET and HEAD on the container, with no key epoch to escrow). Nothing marks an audience on the wire.

  • @interop/wallet-core/enrollment -- the client enrollment ceremony (connect code, approval, completion) plus the onboarding-response envelope that carries a connect code back over an exchange.

  • @interop/wallet-core/genesis -- the account-genesis ceremony: the local mint of a brand-new account's complete key set (mintAccountKeySet: Space id, client identity seed, user key, did:webvh update keys) and the staged provisioning both apps must encode identically (ensureAccountGenesis: Space provisioning, the optional KMS key-map acquisition, did:webvh genesis, user-key roster genesis after DID publication, epoch[0] on every encrypted roster collection, and the Space-controller promotion, also exported standing alone as ensurePromotedSpaceController). Idempotent end to end: a torn run heals by re-running. The keyring bind is deliberately not a stage, so a wallet with no unlock method bound at creation drives the same ceremony.

  • @interop/wallet-core/unlock -- standing unlock credentials: every unlock method (passphrase, passkey PRF, recovery code) as a standing credential in the recovery-code configuration, with self-enrolling login. The credential-derived client identity and binding MAC key, the update-key ladder (latent-and-consumed did:webvh update authority from a random seed carried in the unlock record, the current rung recovered from the log itself), the unlock record codec (shell / bridge delegation / ladder members under a credential-authenticated binding, with the bridge-only re-mint), the merged document-inventory edit (a verbatim keyAgreement entry or a publicKeyCommitment entry for a low-entropy-derived key, either naming its ladder's rung-0 hash as ladderCommitment), and the self-enrolling continuation with its composed completion.

  • @interop/wallet-core/recovery -- recovery codes on the roster identity model, over the unlock machinery: a code as a minimal always-enrolled wallet client (format and derivation, the document half of issuance / revocation / recovery with its spend-on-use continuation, the pre-minted did.jsonl delegation builder and the revocation cascade's bridge re-mint core; the record codec is the unlock subpath's, re-exported here).

  • @interop/wallet-core/clientAnnex -- the client annex, the sibling did:webvh log holding per-visit transient client keys in garbage-collected generations, published in the account's auxiliary annex Space. This is the authoring and maintenance surface of everything anchored on an unlock credential's update-key ladder: a credential's static annex rung (the ladder's derivation and attribution walks are the webvh subpath's), the annex log and its GC, ZCap signing under a ladder VM, the ladder-anchored account-log ceremonies (genesis, self-enrollment, forget, and the last-client transition to a ladder-anchored account), the credential-anchored account genesis with its mend and its per-visit readiness ensure, the transient-recovery continuation, and the replica grants (the pull delegation a replica server copies a Space under and the maintenance grant on a promoted replica Space's replicas route). It sits on top of the other subpaths and none of them import from it.

  • @interop/wallet-core/menders -- the mender registry keyed by invariant: the InvariantDeclaration and Registration types, the closed vocabularies (authorities, triggers, account shapes, evidence, mend outcomes, gap kinds), the INVARIANT_IDS census, the menderRegistry readers with heldAuthorities, the runMenderBlock runner with its mendReportAccumulator, and the derived sets a wallet's audit tests pin (transientReachableInvariants, deriveGaps, undeclaredGaps, undeclaredInvariants). A wallet declares its own invariant table and its own registrations; nothing here executes a ceremony, and the subpath imports nothing from the signing or KMS graph.

The request pipeline (input classification, VPR parsing, cryptosuite negotiation, VP composition, the App Connect app-key credential) now lives in @interop/wallet-request.

The byte-for-byte WAS identity derivation (agentsFromSecret / agentsFromSeed / agentsFromKeyAgent, singleKeyResolver, zcapClientForSigner) now lives at the @interop/pws-client/identity subpath.

Install

  • Node.js 24+ is recommended.
pnpm install @interop/wallet-core

Development

git clone https://github.com/interop-alliance/wallet-core.git
cd wallet-core
pnpm install

Usage

import { SyncEngine, runPull, runPush } from '@interop/wallet-core/sync'
import {
  PRIVATE_CREDENTIALS_COLLECTION,
  publicCredentialUrl,
  buildPwsLinkPayload,
  parsePwsLinkPayload,
  addHistoryCredentialCreated
} from '@interop/wallet-core/space'

The sync and space subpaths are re-exported from the package root as well. Every other subpath (webvh, resourceLog, keys, clients, descriptors, keyring, connections, audiences, enrollment, genesis, unlock, recovery, clientAnnex, menders) is import-directly-only, so consumers of the root never pull the signing / KMS / document-loader dependency graph.

Nine further exports are leaves of that same isolation, carved out to stay dependency-light, for an offline consumer such as @interop/wallet-backup that derives clients and opens records without ever touching the account log:

  • keys/clientKeyRecord -- the client-key record codec alone.
  • keyring/kdf -- the unlock KDF.
  • keyring/recordEnvelope -- the record envelope's descriptor mint, ciphers, addressed id, and frame parsers.
  • keys/userKey -- the user key's key-agreement half.
  • keys/userKeyGenerations -- the user-key-generation unwrap helpers.
  • unlock/standingClient -- the standing client derivation.
  • webvh/ladderDerivation -- the update-key rung and ladder-VM derivation.
  • recovery/recoveryCode -- the recovery code codec and its client derivation.
  • space/collections -- the wallet Space collection ids, specs and resource names, without the space barrel's transport graph.

None of these leaves reach wallet-core's own webvh, resourceLog, or clientAnnex modules. The module barrels still re-export the same names.

The testing subpath holds test fixtures only: the recorded-grant signer fixture, FAST_KDF (an HKDF stand-in for the shipped Argon2id unlock KDF), and the shared account builder, which takes a booted WAS server's URL and produces a real account through the package's own ceremonies in four named shapes (one passphrase, a second standing credential, an issued recovery code, one enrolled client). It imports no server package. Consumers restrict @interop/wallet-core/testing to their test globs with a lint rule.

Contribute

PRs accepted. See CONTRIBUTING.md for editor setup (Prettier, ESLint, and EditorConfig) and how it maps to CI.

If editing the Readme, please conform to the standard-readme specification.

License

MIT License © 2026 Interop Alliance.

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages