feat: Pubky Homegate FFI — key derivation, auth & session ops#80
feat: Pubky Homegate FFI — key derivation, auth & session ops#80
Conversation
ovitrif
left a comment
There was a problem hiding this comment.
LGTM — clean, well-tested code that follows established project patterns. All 32 tests pass.
A few nits (none blocking):
-
#[non_exhaustive]missing onPubkyError— every other error enum in the codebase has it. One-line fix inerrors.rs. -
WriteFailedused for delete (session.rs:72) — semantically odd. Consider renaming toStorageFailedto match thesession.storage()API, or adding operation context to the reason string. -
fetch_pubky_filemaps HTTP errors toResolutionFailed(resolve.rs:39,44) —FetchFailedalready exists and fits better here. -
No test for
pubky_put_with_secret_key— the only new public function with zero coverage. Trivial error-path test following thesign_up/sign_inpattern. -
import_sessionpassesNonefor HTTP client (session.rs:122) — creates a new client + revalidates on everyput/delete/listcall. Worth documenting the overhead, and investigating whether the existingPubkySDK client can be reused.
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>
4966952 to
54e01ec
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ovitrif
left a comment
There was a problem hiding this comment.
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 ResolutionFailed → FetchFailed |
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) |
Summary
paykit/pubkydomain separator)pubkycrate's signer APIfetch_pubky_file_stringconvenience wrapper for UTF-8 public file fetchesNew FFI Functions
derive_pubky_secret_key(seed)pubky_public_key_from_secret(hex)pubky_sign_up(secret, homeserver, code?)pubky_sign_in(secret)pubky_session_put(session, path, content)pubky_session_delete(session, path)pubky_session_list(session, dir_path)pubky_put_with_secret_key(secret, path, content)fetch_pubky_file_string(uri)Test plan
cargo test --lib modules::pubky::tests— 32 tests pass🤖 Generated with Claude Code