Key Custody and Selective Disclosure

Asqav lets a customer decide who holds the signing key, who co-signs an action, and how much of a receipt a verifier gets to see. These are signer-side controls a regulated buyer asks for: a human approval bound into the receipt, a private key that never enters Asqav's database, a payload Asqav signs without ever reading, and a way to disclose one field of a receipt without revealing the rest. Each control on this page is deployed and callable from the asqav SDK today.

Co-sign with a user-held key (WebAuthn and FIDO2)

Some actions should not be the agent's call alone. Asqav accepts a second signature from the end user, produced by a hardware passkey or platform authenticator, and binds it into the same receipt.

The sign call takes a user_intent envelope: a signature the end user produced over the asserted bytes, in one of three algorithms, ed25519, ecdsa-p256, or webauthn. For the WebAuthn algorithm, Asqav parses the COSE credential public key, reconstructs the authenticator data and client-data hash, and verifies the FIDO2 assertion server-side before the receipt is signed. The receipt then records that a real user co-signed the action, not just the agent.

python
receipt = agent.sign(
    "payment:transfer",
    {"amount": 5000, "to": "acct_9f2"},
    user_intent={
        "algorithm": "webauthn",
        "public_key": "<COSE credential public key>",
        "signature": "<FIDO2 assertion>",
        "signed_message": "<authenticatorData || sha256(clientDataJSON)>",
    },
)

The TypeScript client takes the same envelope as userIntent. When Asqav cannot verify the assertion, the receipt is refused rather than signed without the co-signature.

Keep the private key out of Asqav's database

For the default hosted signer, Asqav holds the agent key encrypted at rest with AES-256-GCM. Customers who want the key never to exist in Asqav's database choose a customer-managed key instead.

When an agent is configured for a customer KMS, the agent's secret_key column is null and the key lives in the customer's KMS or HSM. Asqav supports AWS KMS and GCP KMS for ML-DSA-65, both backed by a FIPS-validated HSM, selected per deployment. The signer calls the KMS to sign. The private key material never enters Asqav's process or storage. See Key Management for the configuration.

For deployments that want the key and the payload to stay on the customer side entirely, the signer runs as a customer-deployed container and forwards only digests upstream. See Self-hosted Signer and Out-of-process signing.

Sign a payload Asqav never reads

Asqav can sign an action over a digest alone. In hash-only mode, the default in the cloud, the caller sends a sha256:<hex> digest of the canonical action context plus a bounded, allowlisted metadata bag. Asqav signs the digest and records it as payload_digest. The prompt, the tool arguments, and the model output never cross the wire.

python
receipt = agent.sign(
    "filesystem:write",
    hash="sha256:" + sha256_hex_of_canonical_context,
)

The receipt still verifies against the public key, and a holder who has the original context can re-derive the digest and confirm it matches. Asqav is a notary that never receives the context itself, only the digest, which keeps prompts and outputs inside the customer boundary while still producing a verifiable receipt.

Disclose one field, withhold the rest

A receipt sometimes needs to prove one fact to an outside service without handing over everything. Asqav issues a post-quantum SD-JWT (selective-disclosure JWT) so a holder can reveal a chosen subset of claims and keep the rest sealed.

The token is signed with ML-DSA-65 and carries a hash for each disclosable claim. The holder presents only the disclosures it chooses. A verifier re-derives each disclosed claim's hash, confirms it belongs to the signed set, and rejects any disclosure that does not. The verifier gets cryptographic assurance that each revealed value was part of the original signed token, without seeing the claims that stayed sealed.

python
token = agent.issue_sd_token(
    claims={"tier": "enterprise", "region": "eu", "seat_count": 40},
    disclosable=["tier", "region", "seat_count"],
)

# Reveal only the tier to the downstream service.
presented = token.present(disclose=["tier"])

Require more than one signer (threshold signing)

For the highest-assurance actions, no single party should hold the whole key. Asqav supports threshold ML-DSA signing: the key is split into shares with a Shamir secret-sharing scheme, and a configurable number of participants must each contribute a partial signature before the combiner produces the final receipt signature. The shares are encrypted at rest.

python
config = asqav.create_signing_group("agt_9f2", min_approvals=2, total_shares=3)

A signing session collects the required approvals, each producing a partial signature, and only a quorum of participants can produce a valid receipt. No single share, and no single participant, can sign alone. See Multi-Party Signing for the session flow.

How these compose

The controls stack. An action can be hash-only so Asqav never reads the payload, co-signed by a user passkey, signed with a customer-managed key that never enters Asqav's database, and gated behind a threshold of approvers. Whatever the combination, the result is the same wire format, verifiable against the public key at GET /.well-known/jwks.json, with no Asqav account required by the reader.

Related