An encrypted personal cloud built on the AT Protocol.

Sharing#

Resolve#

Resolves a handle or DID to its PDS and X25519 public key. Used internally by share, exposed as a standalone command for inspection.

sequenceDiagram
    participant User
    participant CLI
    participant CallerPDS as Caller's PDS
    participant PLC as PLC Directory
    participant TargetPDS as Target's PDS

    User->>CLI: opake resolve alice.example.com

    alt Input is a handle
        CLI->>CallerPDS: com.atproto.identity.resolveHandle
        CallerPDS-->>CLI: did:plc:alice
    else Input is a DID
        CLI->>CLI: Use directly
    end

    CLI->>PLC: GET /did:plc:alice (DID document)
    PLC-->>CLI: { alsoKnownAs, service: [#atproto_pds → pds-url] }

    CLI->>TargetPDS: com.atproto.repo.getRecord (publicKey/self)
    TargetPDS-->>CLI: PublicKeyRecord { publicKey, algo }

    CLI->>User: DID, handle, PDS URL, public key, algorithm

Share#

Grants another user access to a document by wrapping the content key to their public key.

sequenceDiagram
    participant User
    participant CLI
    participant PDS as Own PDS
    participant PLC as PLC Directory
    participant RecipientPDS as Recipient's PDS
    participant Crypto

    User->>CLI: opake share photo.jpg alice.example.com

    CLI->>CLI: Resolve filename → AT-URI

    Note over CLI,RecipientPDS: Resolve recipient identity
    CLI->>PLC: DID document for recipient
    PLC-->>CLI: { pds_url }
    CLI->>RecipientPDS: getRecord (publicKey/self)
    RecipientPDS-->>CLI: recipient's X25519 public key

    Note over CLI,PDS: Fetch content key from own document
    CLI->>PDS: getRecord (document)
    PDS-->>CLI: Document record with owner's wrappedKey
    CLI->>Crypto: unwrap_key(owner_wrappedKey, private_key)
    Crypto-->>CLI: content key K

    Note over CLI,PDS: Create grant
    CLI->>Crypto: wrap_key(K, recipient_pubkey, recipient_did)
    Crypto-->>CLI: wrappedKey for recipient

    CLI->>PDS: createRecord (grant)
    PDS-->>CLI: { uri, cid }

    CLI->>User: Shared: at://did/.../grant-tid

Revoke#

Deletes a grant record. The recipient loses network access to the wrapped key.

sequenceDiagram
    participant User
    participant CLI
    participant PDS

    User->>CLI: opake revoke at://did/.../grant-tid

    CLI->>CLI: Validate URI is a grant collection
    CLI->>PDS: com.atproto.repo.deleteRecord (grant collection, rkey)
    PDS-->>CLI: 200 OK

    CLI->>User: Revoked

For true forward secrecy, the document should also be re-encrypted with a new content key — the schema supports this but the CLI doesn't automate it yet.

Pending Share (recipient not ready)#

When the recipient hasn't set up Opake yet (no publicKey/self), the share is queued as a pendingShare record on the PDS. The daemon retries periodically.

sequenceDiagram
    participant User
    participant CLI
    participant PDS as Own PDS
    participant PLC as PLC Directory
    participant RecipientPDS as Recipient's PDS

    User->>CLI: opake share photo.jpg bob.test

    CLI->>CLI: Resolve filename → AT-URI
    CLI->>PLC: DID document for recipient
    PLC-->>CLI: { pds_url }
    CLI->>RecipientPDS: getRecord (publicKey/self)
    RecipientPDS-->>CLI: 404 Not Found

    Note over CLI,PDS: Recipient hasn't set up Opake — queue for retry
    CLI->>PDS: createRecord (pendingShare)
    PDS-->>CLI: { uri }
    CLI->>User: Share queued — will complete when they log in

    Note over CLI: Later, daemon tick...
    CLI->>PDS: listRecords (pendingShare)
    PDS-->>CLI: [ pendingShare record ]

    CLI->>PLC: DID document for recipient
    PLC-->>CLI: { pds_url }
    CLI->>RecipientPDS: getRecord (publicKey/self)
    RecipientPDS-->>CLI: PublicKeyRecord { publicKey }

    Note over CLI: Recipient is ready — complete the share
    CLI->>PDS: getRecord (document)
    PDS-->>CLI: Document with owner's wrappedKey
    CLI->>CLI: unwrap content key, wrap to recipient

    CLI->>PDS: createRecord (grant)
    PDS-->>CLI: { uri }
    CLI->>PDS: deleteRecord (pendingShare)
    PDS-->>CLI: 200 OK

Pending shares expire after 7 days. The daemon also deletes expired records on each pass.