Skip to content

Advanced clients and OAuth

Start with the shared-account tutorial for a complete app. The generated reference covers public signatures, option fields, return types, and source comments across all eight library entry points. This page maps imports to tasks; chain, signing, and errors provide operation details. Code fragments below assume their named inputs already exist.

Start new applications with TasraClient from tasra-sdk/app. The runtime class is separate from the root module’s existing TasraClient session-client type. Install the latest SDK with npm install tasra-sdk@latest; see installation for runtime and TypeScript setup.

Entry point Import Use it for Requires
new TasraClient({manifest}) / TasraClient.fromManifest(url) tasra-sdk/app Create slots, use wallets and credentials, encrypt and sign Public deployment manifest; wallet and durable store for slot creation
createFileStore tasra-sdk/app/node Private durable state for a Node application A private local directory
createTasraChainClient tasra-sdk/chain Read contracts and discover the network RPC, chain ID, deployment addresses
createTasraWriteClient tasra-sdk/chain Create slots, fund them, change on-chain state Deployment configuration and funded transaction signer
createCommitteeSlotClient tasra-sdk/chain Credential-gated threshold encryption/decryption and FROST signing Chain client, slot, holder, credentials and verifier proofs
createTasraSlotClient tasra-sdk/chain Chain-discovered managed sessions Chain client, identity and session authentication
createTasraClient tasra-sdk Managed sessions with explicit endpoints Keeper URLs and session authentication; verifier for credential redemption

The remaining factories support advanced integrations and existing applications. Managed-session local decryption requires an exportable BLS slot. Threshold custody uses the committee client; it does not export the master key. For Ethereum signatures, see signEoaDigest, not committee FROST signing.

Call Returns Related guide
tasra.check() Chain and registry availability; authorization compatibility remains unknown Quickstart
tasra.slots.create(request) Ready typed slot with its committed access policy provisioned Application API
tasra.wallets.create() / tasra.wallets.connect(provider) Local wallet / promise of an external-wallet adapter Application API
tasra.wallets.fromSlot(slotId, {authorize}) Promise of a threshold Ethereum wallet with named durable transfers Shared account
tasra.identities.create() Fresh holder identity with a DID and explicit private-key export Credentials
tasra.credentials.issue(input) / authorize(input) SD-JWT credential / operation authorizer Credentials
parsePinnedNetworkManifest(text, sha256) Validated NetworkManifest; throws on checksum or schema mismatch Fuji configuration
createTasraChainClient(config) TasraChainClient with client and typed readers Chain reference
chain.readers.keyRegistry.getKeySlot(slotId) Promise of slot state, including exists, mode, publicKey, epoch, cancelled Address lookup
addressFromEoaPubkey(pubkey) EIP-55 checksummed Ethereum address Address lookup
committeeSignEoaDigest(options) Promise of {groupPublicKey, r, s, yParity} using request-bound authorization Complete tutorial
signEoaDigest(options) Promise of {groupPublicKey, r, s, yParity} Signing
client.openSession(slotId, auth) Promise of a managed Session Exportable vault

tasra-sdk/app provides TasraClient, the manifest loader, wallet and identity adapters, credential helpers, slot creation and native approval helpers. tasra-sdk/app/node provides createFileStore; keep this Node-only import out of browser bundles. See the application guide for the normal workflow and generated application reference for complete signatures.

Group Symbols
Managed client createTasraClient, TasraClient, Session, SessionAuth, VpJwtAuth, OpenSessionOpts, SignOpts
Errors TasraError, TasraHttpError, AuthDeniedError, NodeUnreachableError, ThresholdNotMetError, SlotRotatedError, CommitteeAuthorizeError, DcqlMalformedError, VerifierAgentSessionError, isAuthDenied, isRetryable
Crypto encryptEnvelope, decryptWithMasterKey, toBytes, fromBytes, buildTasraText, parseTasraPost, isTasraPost, GroupEnvelope
Identity-scoped IBE ibeEncrypt, ibeCombineExtract, ibeCombineDecrypt, ibeDecryptWithKey, requestIbeExtractionPartials; large objects: ibeSealBlob, ibeOpenBlob, ibeUnwrapBlobKey, ibeBlobDecryptKey, ibeDecryptBlobChunk, ibeBlobChunkRange, ibeBlobDigest, IbeBlobHeader
Node client fetchMpk, fetchAndAssembleKey
Signing signCustody, signWithShardDelivery, signUserRequest, signEoaDigest, ethSignatureV, addressFromEoaPubkey, aggregateFrostSignature, verifyFrostSignature
Decryption (threshold) decryptCustody, decryptWithShardDelivery, combineDecryptShares, verifyDecryptShare
Verifier auth + JWT redeemCredential, redeemRenewalToken, createRenewal, revokeRenewal, issueAdminCredential, revokeSlotUser, verifyPresentation, verifyVpJwt, decodeJwtClaims, jwtExpMs, isJwtExpiringSoon + IssuedToken, JwtClaims
DCQL (OID4VP-DCQL) validateDcql, evaluateDcql, selectDcql, canonicalizeDcql, isOid4vpRule, jsonCredential, evaluateIdentityScoped, scopeCovers, DcqlMalformedError, DCQL_MAX_RULE_LEN + DcqlQuery, CredentialView
Verifier Agent sessions createOid4vpSession, pollOid4vpSession, waitForSession, payloadDigest, VerifierAgentSessionError; oauth kind: createOauthSession, submitOauthResponse + CreateSessionParams, CreateOauthSessionResult, SessionStatusResult
DPoP (RFC 9449) createDpopKey, auth0DpopSigner, jwkThumbprint, accessTokenHash, isHeaderSafeNonce + DpopSigner, DpopKey
Committee tokens not on the main entry — see tasra-sdk/committee
Slot funding httpFaucet (sovereign slot creation lives in tasra-sdk/chain)
Utils hexToBytes

For new apps, use tasra.slots.bls(...) and tasra.slots.frost(...) for credential-gated threshold custody. createCommitteeSlotClient remains available for advanced committee integrations. Managed Session.decrypt requires an exportable personal-vault slot; the session primitives, verifier-auth helpers, and crypto are what it composes (use them directly when you need finer control). To resolve endpoints from chain instead of hardcoding them, use the tasra-sdk/chain clients (createTasraSlotClient / createCommitteeSlotClient). Verifier-auth helpers are pure HTTP/JSON; JWT inspection is client-side only (expiry/UX), never a signature check. DCQL is a generic evaluator — scope semantics are the consuming product’s concern.

Large objects under IBE — ibeSealBlob / ibeOpenBlob

Section titled “Large objects under IBE — ibeSealBlob / ibeOpenBlob”

ibeEncrypt is a KEM for a small payload. For an image, a scan series or any multi-megabyte object, seal an envelope: a fresh 32-byte data key IBE-wrapped to the identity, the body AES-256-GCM in fixed chunks (WebCrypto, default 1 MiB), each chunk’s nonce blobId[0..8] ‖ be32(i) and its AAD "keykeeper/ibe-blob/v1" ‖ blobId ‖ be32(i) ‖ last ‖ identity — so a chunk cannot be reordered, dropped, truncated or replayed under another identity.

import {ibeSealBlob, ibeOpenBlob, ibeUnwrapBlobKey, ibeBlobDecryptKey, ibeDecryptBlobChunk, ibeBlobChunkRange} from 'tasra-sdk'
// producer (offline, nothing but the slot's public MPK)
const {header, body} = await ibeSealBlob(mpk, 'did:key:zPatient/imaging/2026-09', pngBytes, {contentType: 'image/png'})
// store `body` as a blob and `header` beside it (header.wrappedKey is the IBE ciphertext of the data key)
// consumer, after an identity-scoped extraction gave it sk_ID for that identity
const png = await ibeOpenBlob(skId, header, body) // whole object in memory, or …
const dek = ibeUnwrapBlobKey(skId, header) // … stream it:
const key = await ibeBlobDecryptKey(dek)
for (let i = 0; i < header.chunkCount; i++) {
const {start, end} = ibeBlobChunkRange(header, i) // byte range of chunk i in `body`
const plain = await ibeDecryptBlobChunk(key, header, i, await fetchRange(start, end))
}

sk_ID is a per-identity capability, so one extraction opens every object sealed to that identity; time-box identities (…/2026-09) rather than expecting to revoke one. Use a producer-authenticated manifest containing both the header and the body digest (ibeBlobDigest(body)) so the consumer can reject substituted metadata or ciphertext before decrypting. A signed body digest alone does not protect the header.

Chunk size must be an integer of at least 1024 bytes. Empty input still has one chunk: zero plaintext bytes and a 16-byte authentication tag. Exact chunk-size multiples add no extra chunk; the last partial chunk contains the remaining bytes. ibeBlobChunkRange returns an exclusive end; an HTTP Range request ends at end - 1. Validate safe-integer indices in [0, header.chunkCount) and use a trusted header. The SDK rejects negative and past-end indices; it does not validate every malformed header or fractional index. Authenticate the header alongside the body digest before trusting metadata such as contentType.

tasra-sdk/oid4vp — credential wallets against the Verifier Agent

Section titled “tasra-sdk/oid4vp — credential wallets against the Verifier Agent”

The wallet protocol uses OpenID4VP 1.0 with DCQL, SD-JWT VC + KB-JWT, JARM direct_post.jwt, and OpenID4VCI pre-authorized issuance. This subpath supports two application roles:

// APP / RELYING PARTY: sign the operation with the slot creator's EVM key (EIP-712
// PresentationOperation), open a session on the verifier-agent, hand the openid4vp:// payload to a wallet,
// collect the compound token — the credentials never come back here.
import {openVerifierAgentSession, awaitVerifierAgentResult} from 'tasra-sdk/oid4vp'
const session = await openVerifierAgentSession({verifierAgentUrl, chainId, keyRegistry, slotId, action: 'sign', message, description, signer: creatorAccount})
showQr(session.qrPayload)
const {token} = await awaitVerifierAgentResult(session) // checks token.request_hash against the session
// WALLET: receive a credential from any OpenID4VCI issuer, then answer a request: fetch + verify
// the JAR (did:web → the verifier-agent's set document), plan against its dcql_query, let the user pick ONE
// credential, disclose only the claims asked for, bind with a KB-JWT, encrypt, POST.
import {randomHolderKey, receiveCredential, presentToRequestUri} from 'tasra-sdk/oid4vp'
const holder = randomHolderKey() // P-256 did:jwk, cnf.kid = did#0
const {credential} = await receiveCredential({offerUri, holder})
await presentToRequestUri(qrPayload, [{sdJwt: credential}], holder, {choose: askUser})

planPresentation / buildResponse / submitResponse are the steps behind presentToRequestUri for a UI that wants a consent screen between them. One credential per presentation: a rule that needs two credentials must use credential_sets with single-credential options (what the Hovi Wallet can satisfy). Issuers use issueSdJwtVc (P-256 did:key or did:jwk issuer); the request binding (requestHash, derivedNonce) lives in binding for the verifier-agent side. A wallet copies the nonce; it never interprets it.

Bring your own identity provider — OAuth + DPoP against the Verifier Agent

Section titled “Bring your own identity provider — OAuth + DPoP against the Verifier Agent”

A slot whose rule uses the oauth+access-token+dpop format is authorized by an access token your users already get from your own IdP, sender-constrained with DPoP (RFC 9449). The rule is ordinary OID4VP-DCQL over the token’s own claims — iss pinned, ["aud", null] containing the platform audience, meta.max_age_secs, plus whatever roles you require:

{"credentials":[{"id":"t","format":"oauth+access-token+dpop","meta":{"max_age_secs":300},
"claims":[
{"path":["iss"],"values":["https://idp.example.com/realms/acme"]},
{"path":["aud",null],"values":["https://agent.tasra.example/authz/43114"]},
{"path":["realm_access","roles",null],"values":["treasury-signer"]}]}]}

validateDcql refuses an oauth+* query without the pinned iss, the ["aud", null] entry or meta.max_age_secs (1–86400). The audience is derived, never chosen — platformAudience(agentOrigin, chainId) from tasra-sdk/committee, or read it back from any createOauthSession reply.

import {auth0DpopSigner, createOauthSession, submitOauthResponse, waitForSession} from 'tasra-sdk'
import {presentationOperationTypedData} from 'tasra-sdk/oid4vp'
const {typedData, operation, messageHex} = presentationOperationTypedData({
chainId, keyRegistry, slotId, action: 'sign', message, description: 'Treasury payout #42',
})
const operationSig = await creatorSigner.signTypedData(typedData)
const session = await createOauthSession(verifierAgentUrl, {operation, operationSig, messageHex})
// → {sessionId, pollSecret, nonce, dpopHtu, platformAudience} — no QR, no Request Object
await submitOauthResponse(verifierAgentUrl, {
sessionId: session.sessionId, pollSecret: session.pollSecret,
accessToken, nonce: session.nonce, dpopHtu: session.dpopHtu,
signer, // MUST sign with the key the token is bound to
})
const result = await waitForSession(verifierAgentUrl, session.sessionId, session.pollSecret)
if (result.status !== 'done') throw new Error(`refused: ${result.error}`)
// result.compoundToken + result.verifierProofs → the keeper's /v1/committee/sign

The signer is decided by where your token’s DPoP key lives:

Your token came from signer
auth0-spa-js with useDpop auth0DpopSigner((args) => auth0.generateDpopProof(args))
oidc-client-ts with dpop enabled {proof: ({htm, htu, nonce}) => userManager.dpopProof(htu, user, htm, nonce)} (it computes ath from the user’s token)
your own token request with a key from createDpopKey() key.signer

createDpopKey().signer mints resource-request proofs only — it always sets ath and nonce — and the SDK has no token-request proof helper, so it cannot obtain the token from the IdP for you. Prefer letting your OIDC library own the key.

Sign over session.dpopHtu, not the request URL. The platform’s htu is {agentOrigin}/v1/sessions/oauth-response (no session id), while the request goes to /v1/sessions/{id}/oauth-response. submitOauthResponse passes dpopHtu to the signer; a generic DPoP fetch helper that derives htu from the request URL — including auth0-spa-js’s fetchWithAuth — is refused.

submitOauthResponse resolving does not mean authorized. The agent accepts the delivery and runs the committee; a refusal (wrong role, stale token, wrong audience…) surfaces from waitForSession as status: 'failed' with the verifiers’ reasons in error (it RETURNS a failed session rather than throwing — check status). submitOauthResponse answers the agent’s use_dpop_nonce challenge once by itself.

What the platform proves this way is PRESENCE, not intent — the user never sees the operation; pin dual control on slots that need intent. Configuring a tenant identity provider is a deployment task, documented by whoever operates the network.

tasra-sdk/committee — protocol internals

Section titled “tasra-sdk/committee — protocol internals”

The compound-token layer — committee draw, canonical hashing, ed25519 quorum verification, verifier-set Merkle proofs, wire codecs, and the /v1/committee-authorize orchestration — lives behind its own subpath:

import {verifyCompoundToken, selectVerifierCommittee} from 'tasra-sdk/committee'

It also carries the derivations platformAudience(origin, chainId) and dpopHtu(origin) (+ normalizeOrigin, OAUTH_RESPONSE_PATH). Use these helpers to preserve the deployment protocol’s exact audience and URI binding.

Most consumers never need it. Typed slots on TasraClient handle these operations. Advanced integrations can use createCommitteeSlotClient from tasra-sdk/chain; reach for protocol primitives when implementing or auditing the protocol itself.

These ~35 symbols are deep protocol internals — canonical hashing, Merkle proofs, the committee draw, wire codecs — and they are not on the main entry, so import {…} from 'tasra-sdk' autocompletes to the managed surface rather than to compoundTokenCanonicalBytes.


Back to the README · Documentation index