Skip to content

feat: Pubky Homegate FFI — key derivation, auth & session ops#80

Merged
ovitrif merged 5 commits intomasterfrom
feat/pubky-homegate-ffi
Mar 27, 2026
Merged

feat: Pubky Homegate FFI — key derivation, auth & session ops#80
ovitrif merged 5 commits intomasterfrom
feat/pubky-homegate-ffi

Conversation

@ben-kaufman
Copy link
Copy Markdown
Collaborator

Summary

  • Add deterministic Pubky Ed25519 key derivation from BIP39 seed (HMAC-SHA256 with paykit/pubky domain separator)
  • Add homeserver signup/signin via the pubky crate's signer API
  • Add authenticated session operations: put, delete, list, and one-shot put-with-secret-key (for redirect.json on old identity during Ring upgrade)
  • Add fetch_pubky_file_string convenience wrapper for UTF-8 public file fetches
  • All functions exposed via UniFFI for iOS/Android/Python bindings
  • 13 new tests (32 total in pubky module)

New FFI Functions

Function Sync/Async Purpose
derive_pubky_secret_key(seed) sync Derive Ed25519 secret from BIP39 seed
pubky_public_key_from_secret(hex) sync Derive z32 public key from secret
pubky_sign_up(secret, homeserver, code?) async Register on homeserver
pubky_sign_in(secret) async Re-authenticate
pubky_session_put(session, path, content) async Write via session
pubky_session_delete(session, path) async Delete via session
pubky_session_list(session, dir_path) async List directory contents
pubky_put_with_secret_key(secret, path, content) async Sign in + write (one-shot)
fetch_pubky_file_string(uri) async Fetch public file as UTF-8

Test plan

  • cargo test --lib modules::pubky::tests — 32 tests pass
  • Integration test with staging homeserver: derive key → sign up via Homegate → put/get/delete

🤖 Generated with Claude Code

Copy link
Copy Markdown
Collaborator

@ovitrif ovitrif left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — clean, well-tested code that follows established project patterns. All 32 tests pass.

A few nits (none blocking):

  1. #[non_exhaustive] missing on PubkyError — every other error enum in the codebase has it. One-line fix in errors.rs.

  2. WriteFailed used for delete (session.rs:72) — semantically odd. Consider renaming to StorageFailed to match the session.storage() API, or adding operation context to the reason string.

  3. fetch_pubky_file maps HTTP errors to ResolutionFailed (resolve.rs:39,44) — FetchFailed already exists and fits better here.

  4. No test for pubky_put_with_secret_key — the only new public function with zero coverage. Trivial error-path test following the sign_up/sign_in pattern.

  5. import_session passes None for HTTP client (session.rs:122) — creates a new client + revalidates on every put/delete/list call. Worth documenting the overhead, and investigating whether the existing Pubky SDK client can be reused.

ben-kaufman and others added 3 commits March 27, 2026 08:57
Add key derivation (HMAC-SHA256 from BIP39 seed with "paykit/pubky" domain),
signup/signin, and authenticated storage operations (put/delete/list) to the
pubky module, exposed via UniFFI for iOS/Android/Python bindings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add #[non_exhaustive] to PubkyError to match codebase convention
- Use FetchFailed instead of ResolutionFailed for HTTP errors in fetch_pubky_file
- Add operation context to delete error message in WriteFailed
- Add missing test for pubky_put_with_secret_key

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ben-kaufman ben-kaufman force-pushed the feat/pubky-homegate-ffi branch from 4966952 to 54e01ec Compare March 26, 2026 23:59
ben-kaufman and others added 2 commits March 27, 2026 09:07
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ovitrif ovitrif self-requested a review March 27, 2026 10:19
Copy link
Copy Markdown
Collaborator

@ovitrif ovitrif left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

1. Changes Overview

File Path Change Summary
src/lib.rs +9 new FFI entry points (UniFFI) for key derivation, sign-up/in, session ops, file fetch
src/modules/pubky/keys.rs New file — key derivation & keypair helpers
src/modules/pubky/session.rs New file — sign-up, sign-in, session put/delete/list, put-with-secret-key
src/modules/pubky/errors.rs +2 error variants (KeyError, WriteFailed); add #[non_exhaustive]
src/modules/pubky/mod.rs Register & re-export keys and session submodules
src/modules/pubky/resolve.rs +fetch_pubky_file_string; change fetch_pubky_file errors from ResolutionFailedFetchFailed
src/modules/pubky/tests.rs +33 new tests covering keys, session, resolution, profiles, contacts, fetch
Cargo.toml / Cargo.lock Version bump
Package.swift Updated binary target checksums/urls
bindings/android/… Regenerated Kotlin bindings + updated .so binaries
bindings/ios/… Regenerated Swift bindings + updated .a / .xcframework binaries
bindings/python/… Regenerated Python bindings + updated .dylib binary

2. New Public APIs (FFI Methods)

Fn Name Purpose Tests (N)
fetch_pubky_file_string Fetch a public pubky:// resource as UTF-8 string 1
derive_pubky_secret_key Derive Ed25519 secret key from 64-byte BIP39 seed (HMAC-SHA256) 6
pubky_public_key_from_secret Derive z32-encoded public key from hex secret key 4
pubky_sign_up Sign up on a homeserver; returns session secret (pubkey_z32:cookie) 1
pubky_sign_in Re-authenticate with existing secret key; returns new session secret 1
pubky_session_put Write content to a path on the user's homeserver via session 1
pubky_session_delete Delete a resource at path on the user's homeserver via session 1
pubky_put_with_secret_key Sign-in + write in one shot (e.g. for redirect.json) 1
pubky_session_list List resources in a directory on the user's homeserver 1

3. New Methods

Fn Name Visibility Summary / Purpose
keys::keypair_from_hex pub(super) Reconstruct Keypair from hex-encoded 32-byte secret key
keys::derive_pubky_secret_key pub Derive secret key from 64-byte seed via HMAC-SHA256
keys::pubky_public_key_from_secret pub Convert hex secret key to z32 public key
session::pubky_sign_up pub Sign up on homeserver, return session secret
session::pubky_sign_in pub Sign in with secret key, return session secret
session::pubky_session_put pub Import session + write content to path
session::pubky_session_delete pub Import session + delete resource at path
session::pubky_put_with_secret_key pub Sign in + write in one operation
session::pubky_session_list pub Import session + list directory entries
session::import_session private Helper: import PubkySession from secret token
resolve::fetch_pubky_file_string pub Fetch public resource as UTF-8 string (wraps fetch_pubky_file)

4. New Tests

Test Name Type
resolve_pubky_uri Unit
resolve_pubky_prefixed_form Unit
resolve_invalid_uri Unit
complete_without_active_flow_returns_no_active_flow Unit (async)
cancel_without_active_flow_returns_no_active_flow Unit (async)
fetch_malformed_uri_returns_error Unit (async)
profile_from_full_app_user Unit
profile_from_minimal_app_user Unit
profile_from_user_with_empty_links Unit
profile_deserialized_from_full_json Unit
profile_deserialized_from_name_only_json Unit
profile_missing_name_json_fails Unit
profile_invalid_json_fails Unit
fetch_profile_invalid_key_returns_fetch_failed Unit (async)
fetch_profile_empty_key_returns_fetch_failed Unit (async)
fetch_contacts_invalid_key_returns_fetch_failed Unit (async)
fetch_contacts_empty_key_returns_fetch_failed Unit (async)
derive_secret_key_returns_valid_hex Unit
derive_secret_key_is_deterministic Unit
derive_secret_key_different_seeds_produce_different_keys Unit
derive_secret_key_rejects_short_seed Unit
derive_secret_key_rejects_empty_seed Unit
public_key_from_derived_secret_roundtrips Unit
public_key_from_invalid_hex_returns_error Unit
public_key_from_empty_hex_returns_error Unit
public_key_from_wrong_length_hex_returns_error Unit
sign_up_invalid_secret_key_returns_error Unit (async)
sign_in_invalid_secret_key_returns_error Unit (async)
put_with_secret_key_invalid_key_returns_error Unit (async)
session_put_invalid_session_returns_error Unit (async)
session_delete_invalid_session_returns_error Unit (async)
session_list_invalid_session_returns_error Unit (async)
fetch_file_string_malformed_uri_returns_error Unit (async)

@ovitrif ovitrif merged commit aaa627d into master Mar 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants