Part of epic #3602 .
Scope
Implement RequestSigner that produces gonka-compatible signed-request headers, with fixture-driven tests derived from the upstream Python reference (gonka-ai/gonka-openai MIT).
Files to create
crates/zeph-llm/src/gonka/signer.rs:
pub struct RequestSigner { signing_key : k256:: ecdsa:: SigningKey , address : String }
impl RequestSigner {
pub fn from_hex ( priv_hex : & str , chain_prefix : & str ) -> Result < Self , LlmError > ;
pub fn address ( & self ) -> & str ;
pub fn sign ( & self , body_bytes : & [ u8 ] , timestamp_ns : u128 , transfer_address : & str )
-> Result < String , LlmError > ;
}
crates/zeph-llm/src/gonka/fixtures.json — at least three signing test vectors generated from running the Python reference against fixed inputs (private key, body, timestamp_ns, transfer_address → expected base64 signature).
crates/zeph-llm/src/gonka/tests.rs — fixture-driven tests covering:
Address derivation matches Python output.
Signature byte-for-byte match for each vector.
Hex parsing errors (odd length, invalid chars).
Unicode body bytes.
Files to modify
crates/zeph-llm/Cargo.toml — add k256 = { version = \"0.13\", default-features = false, features = [\"ecdsa\", \"std\", \"sha256\"] }, ripemd = \"0.1\", bech32 (workspace), hex (workspace), sha2 (workspace).
Root Cargo.toml — promote bech32 = \"0.9.1\" to [workspace.dependencies] (currently transitive via age).
crates/zeph-llm/src/lib.rs — pub mod gonka; (only signer + endpoints submodules wired in this PR; provider lands in Implement OllamaProvider using ollama-rs #9 ).
Algorithm (per upstream Python reference)
payload_hash_hex = lowercase hex(sha256(body_bytes)) (64 chars).
input = payload_hash_hex || timestamp_ns.to_string() || transfer_address UTF-8 bytes.
digest = sha256(input).
sig = signing_key.sign(digest) — k256 0.13 produces deterministic low-S RFC6979.
Return base64::STANDARD.encode(sig.to_bytes()) (64 raw bytes → 88-char base64).
Address derivation: bech32(chain_prefix, ripemd160(sha256(compressed_secp256k1_pubkey))).
Acceptance
cargo nextest run -p zeph-llm -E 'test(gonka_signer)' green.
All three fixture vectors pass.
cargo clippy --workspace --features full -- -D warnings green.
New pub items have /// docs with # Examples (per project rule).
cargo doc --no-deps -p zeph-llm clean with RUSTDOCFLAGS=\"--deny rustdoc::broken_intra_doc_links\".
Block merge until live testnet probe confirms a real gonka node accepts a signature produced by this signer (per Risk M0: Project bootstrap — workspace and crate skeleton #1 in the plan).
CHANGELOG.md [Unreleased] updated.
Depends on
None (parallelisable with #5 , #6 , #8 ).
Size
M (~4h)
Part of epic #3602.
Scope
Implement
RequestSignerthat produces gonka-compatible signed-request headers, with fixture-driven tests derived from the upstream Python reference (gonka-ai/gonka-openaiMIT).Files to create
crates/zeph-llm/src/gonka/signer.rs:crates/zeph-llm/src/gonka/fixtures.json— at least three signing test vectors generated from running the Python reference against fixed inputs (private key, body, timestamp_ns, transfer_address → expected base64 signature).crates/zeph-llm/src/gonka/tests.rs— fixture-driven tests covering:Files to modify
crates/zeph-llm/Cargo.toml— addk256 = { version = \"0.13\", default-features = false, features = [\"ecdsa\", \"std\", \"sha256\"] },ripemd = \"0.1\",bech32(workspace),hex(workspace),sha2(workspace).Cargo.toml— promotebech32 = \"0.9.1\"to[workspace.dependencies](currently transitive viaage).crates/zeph-llm/src/lib.rs—pub mod gonka;(only signer + endpoints submodules wired in this PR; provider lands in Implement OllamaProvider using ollama-rs #9).Algorithm (per upstream Python reference)
payload_hash_hex = lowercase hex(sha256(body_bytes))(64 chars).input = payload_hash_hex || timestamp_ns.to_string() || transfer_addressUTF-8 bytes.digest = sha256(input).sig = signing_key.sign(digest)— k256 0.13 produces deterministic low-S RFC6979.base64::STANDARD.encode(sig.to_bytes())(64 raw bytes → 88-char base64).Address derivation:
bech32(chain_prefix, ripemd160(sha256(compressed_secp256k1_pubkey))).Acceptance
cargo nextest run -p zeph-llm -E 'test(gonka_signer)'green.cargo clippy --workspace --features full -- -D warningsgreen.pubitems have///docs with# Examples(per project rule).cargo doc --no-deps -p zeph-llmclean withRUSTDOCFLAGS=\"--deny rustdoc::broken_intra_doc_links\".CHANGELOG.md[Unreleased]updated.Depends on
None (parallelisable with #5, #6, #8).
Size
M (~4h)