Skip to main content

Crate cratefield_adapter_turnkey

Crate cratefield_adapter_turnkey 

Source
Expand description

cratefield-adapter-turnkey — the Turnkey provider for the KeySigner port (issue #761).

Every end user gets a Turnkey sub-organization: their passkey is the root user, a wallet is minted inside it, and our backend is only a delegated access user — a second root user whose API key (the P-256 pair this crate stamps with, held in the Secrets port) is scoped by EFFECT_ALLOW policies to exactly what the venture allows, then dropped from the root quorum so it can never widen its own permissions. set_kill_switch freezes the backend’s signing with one EFFECT_DENY policy; clear_kill_switch lifts it.

Signing goes by key reference through the port: EVM transactions as ACTIVITY_TYPE_SIGN_TRANSACTION_V2 (the one activity Turnkey’s policy engine parses, so eth.tx.to, eth.tx.chain_id, eth.tx.value and eth.tx.data[0..10] conditions bind), digests — userOpHash, EIP-712, Solana messages — as ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2. No export activity is ever wrapped.

use cratefield_adapter_turnkey::{AllowRule, key_ref};
use cratefield_signer::Scheme;

// The allow rules a sub-organization's backend is scoped to, and the
// reference naming one of its keys — both built without any I/O.
let rule = AllowRule::evm(
    1,
    "0x000000000000000000000000000000000000aaaa",
    Some("0xa9059cbb"), // ERC-20 transfer
    Some(1_000_000_000_000_000_000),
)?;
assert_eq!(
    rule.condition(),
    "eth.tx.to == '0x000000000000000000000000000000000000aaaa' \
     && eth.tx.chain_id == 1 \
     && eth.tx.data[0..10] == '0xa9059cbb' \
     && eth.tx.value <= 1000000000000000000"
);
let reference = key_ref(
    "acme",
    "11111111-2222-3333-4444-555555555555",
    Scheme::Secp256k1,
    "0x000000000000000000000000000000000000aaaa",
)?;
assert!(reference.as_str().starts_with("turnkey/acme/"));

§Reading order

  • api: TurnkeySigner — construction, the setup flow, kill switches, and the typed activity calls underneath the port.
  • policy: the pure builders — allow rules, the delegated-access consensus, and the validators that keep the policy language injection-proof.
  • stamp: the X-Stamp, signed inside the call that unseals the API key.
  • evm: the EIP-1559 wire forms — the unsigned transaction sign_transaction asks for, and the signed one back.
  • signer: the KeySigner implementation and the key-reference format, with every answer verified against the reference’s identity before it is returned.

§Why the port’s conformance suite does not run here

cratefield_signer::key_signer_conformance starts at create_key, and this provider’s keys are born with their owner’s passkey at sub-organization creation — the port’s signature cannot carry an attestation. Its determinism check (RFC 6979 / ed25519, “a provider that disagrees is doing something exotic”) also excludes a provider whose signatures are minted by distributed signing infrastructure. The adapter’s tests cover what transfers: signing verified against the published identity, scheme mismatches, malformed payloads, and the status-to-error mapping.

Re-exports§

pub use api::PasskeyAttestation;
pub use api::SubOrgSetup;
pub use api::SubOrganization;
pub use api::TurnkeySigner;
pub use policy::AllowRule;
pub use policy::EvmAddress;
pub use policy::ProgramKey;
pub use policy::Selector;
pub use policy::da_consensus;
pub use signer::key_ref;

Modules§

api
The Turnkey API surface this crate speaks: JSON POSTs to https://api.turnkey.com/public/v1/submit/<activity> with an X-Stamp header, answered by an activity whose status is the verdict. One TurnkeySigner serves one venture: the parent organization id, and the Secrets-port name of the P-256 API key the delegated access user stamps with — the same key pair registered as our backend user’s key on the parent organization.
evm
The EIP-1559 wire forms this crate trades in: the unsigned transaction hex ACTIVITY_TYPE_SIGN_TRANSACTION_V2 asks for — 0x02 ‖ rlp([chain_id, nonce, max_priority_fee, max_fee, gas, to, value, data, access_list]), the exact bytes whose keccak256 is the signing hash the signer port computes — and the signed transaction back, RLP-decoded into the (r, s, v) the port returns.
policy
Pure policy builders: the EFFECT_ALLOW conditions and the delegated-access consensus Turnkey’s policy engine evaluates, plus the strict validators that make them injection-proof.
signer
The KeySigner port, over Turnkey: signing by key reference — turnkey/{venture}/{sub-organization}/{scheme}/{address} — where the address is the sub-organization wallet’s account the payload belongs to. The reference carries everything a signature needs, the way a SecretsSigner store name does, so key() is a parse, not a round trip.
stamp
The X-Stamp: Turnkey’s API-key authentication header. A stamp is base64url of a JSON object naming the signer’s compressed P-256 public key (hex), a DER ECDSA signature over the exact request body (hex), and the scheme SIGNATURE_SCHEME_TK_API_P256 — Turnkey verifies the signature against the body as it received it, so the stamp is the last thing built before the request is sent.