Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,7 @@
- [Echanges: Coinbase](tools/exchanges-coinbase/README.md)
- [HTTP](tools/http/README.md)
- [Templating: Jinja](tools/templating-jinja/README.md)

## Looking for a home

* [nexus-sdk/guides/tool-communication.md](nexus-sdk/guides/tool-communication.md)
103 changes: 84 additions & 19 deletions nexus-sdk/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Validate an off-chain Nexus Tool on the provided URL. This command checks whethe
As an improvement, the command could take a `[data]` parameter that invokes the Tool and checks the response against the output schema.
{% endhint %}

This command should also check that the URL is accessible by the Leader node. It should, however, be usable with `localhost` Tools for development purposes, printing a warning.
This command should also check that the URL is accessible by Leader nodes. For local testing, it should still be usable with `localhost` Tools, printing a warning.

---

Expand Down Expand Up @@ -116,6 +116,57 @@ This command requires that a wallet is connected to the CLI...

---

**`nexus tool auth`**

Commands for operating signed HTTP (message signatures) for off-chain Tools.

Signed HTTP is Nexus’ application-layer authentication for `POST /invoke`:

- Leader node → Tool requests are signed so the Tool can verify which Leader node is calling and prevent replay.
- Tool → Leader node responses are signed so the Leader node can verify provenance and bind a response to a specific request.

These commands help Tool operators:

- generate an Ed25519 Tool message-signing keypair,
- register/rotate the Tool’s message-signing public key on-chain (Network Auth), and
- export a local `allowed_leaders.json` file consumed by the Tool runtime for request verification (no RPC at runtime).

See also:

- [Tool Communication (HTTPS + Signed HTTP)](guides/tool-communication.md)

---

**`nexus tool auth keygen [--out <path>]`**

Generates a new Ed25519 Tool message-signing keypair.

- If `--out` is provided, writes a JSON file containing `private_key_hex` and `public_key_hex`.
- You will use the private key in the Tool runtime config (`tool_signing_key`).
- You will register the public key on-chain via `register-key`.

---

**`nexus tool auth register-key --tool-fqn <fqn> --signing-key <key-or-path> [--owner-cap <object_id>] [--description <text>] ...gas`**

Registers (or rotates) the Tool’s message-signing key in the on-chain Network Auth registry.

- Requires an `OwnerCap<OverTool>` (the tool ownership cap) to prove Tool identity.
- Requires a proof-of-possession signature so the chain can verify the registrant controls the private key.
- Returns the registered `tool_kid` (key id) which must match the Tool runtime config.

If `--owner-cap` is omitted, the CLI will try to use the OwnerCap saved in the CLI config for that Tool.

---

**`nexus tool auth export-allowed-leaders --leader <address>... --out <path>`**

Exports a local allowlist file (JSON) of permitted Leader nodes and their active signing keys.

This file is consumed by the Rust toolkit runtime (`allowed_leaders_path`) so the Tool can verify signed requests without performing Sui RPC calls at runtime.

---

**`nexus tool list`**

List all Nexus Tools available in the Tool Registry. This reads the dynamic object directly from Sui.
Expand Down Expand Up @@ -288,49 +339,63 @@ Removes the periodic schedule while leaving any existing sporadic occurrences un
This command requires that a wallet is connected to the CLI and holds sufficient SUI for gas.
{% endhint %}

### `nexus crypto`
### `nexus secrets`

Set of commands for managing the CLI’s encrypted secrets (master key, passphrase, identity key) and establishing secure sessions that power DAG data encryption.
Commands for managing the CLI’s local at-rest secret storage (master key + encryption behavior for data stored in `~/.nexus/crypto.toml`).

---

**`nexus crypto auth [--sui-gas-coin <object_id>] [--sui-gas-budget <mist>]`**
**`nexus secrets status`**

Runs the two-step handshake with the Nexus network to claim a pre-key, perform X3DH with your local identity key, and store a fresh Double Ratchet session on disk. The claimed pre-key bundle is what enables the CLI to complete a Signal-style secure session with the network: X3DH bootstraps shared secrets, and the Double Ratchet derived from that bundle encrypts every DAG payload going forward. The command returns both claim/associate transaction digests and prints the initial message in JSON format, enabling you to audit the handshake.
Reports whether secrets will be written encrypted or plaintext, and whether the OS keyring is available.

Before sending the associate transaction, the CLI automatically generates an identity key if one is missing and persists the session in `~/.nexus/crypto.toml`. All subsequent `nexus dag` commands load that session to encrypt entry-port payloads or decrypt remote results, so run `auth` whenever you rotate keys or see “No active sessions found.”
---

{% hint style="info" %}
This command requires that a wallet is connected to the CLI and spends gas for **two** programmable transactions. Use `--sui-gas-coin` / `--sui-gas-budget` if you need explicit control.
{% endhint %}
**`nexus secrets enable`**

Ensures a master key exists in the OS keyring and rewrites `~/.nexus/crypto.toml` so secret fields are stored encrypted.

---

**`nexus crypto generate-identity-key`**
**`nexus secrets disable [--yes]`**

Rewrites local secret state as plaintext and deletes the master key from the OS keyring.

---

Creates a brand-new long-term identity key and stores it (encrypted) inside `~/.nexus/crypto.toml`. Because peers can no longer trust sessions tied to the previous identity, the CLI makes it clear that all stored sessions become invalid. Run `nexus crypto auth` immediately after to populate a replacement session.
**`nexus secrets rotate [--yes]`**

Rotates the master key and re-encrypts local secret state. Use when you want a new master key but can still decrypt existing data.

---

**`nexus crypto init-key [--force]`**
**`nexus secrets wipe [--yes]`**

Deletes the master key and deletes `~/.nexus/crypto.toml`. Use when you lost the master key (e.g., moved machines) and want a clean slate.

Generates a random 32‑byte master key with [`OsRng`](https://docs.rs/rand/latest/rand/rngs/struct.OsRng.html) and writes it to the OS keyring under the `nexus-cli-store/master-key` entry. The master key controls access to every encrypted field (`Secret<T>`) in the CLI configuration. Rotating it without also wiping the encrypted data would leave the ciphertext inaccessible, so this command automatically truncates the cryptographic configuration after a successful write.
---

Use `--force` to overwrite an existing raw key or stored passphrase; doing so deletes all saved sessions and identity material because it can no longer be decrypted.
### `nexus crypto`

Set of commands for establishing secure sessions that power DAG data encryption (X3DH identity key + Double Ratchet sessions).

---

**`nexus crypto set-passphrase [--stdin] [--force]`**
**`nexus crypto auth [--sui-gas-coin <object_id>] [--sui-gas-budget <mist>]`**

Stores a user-provided passphrase in the OS keyring (`nexus-cli-store/passphrase`) and derives the same 32‑byte master key via Argon2id whenever secrets need to be decrypted. By default the command prompts interactively; `--stdin` allows piping from scripts or CI.
Runs the two-step handshake with the Nexus network to claim a pre-key, perform X3DH with your local identity key, and store a fresh Double Ratchet session on disk. The claimed pre-key bundle is what enables the CLI to complete a Signal-style secure session with the network: X3DH bootstraps shared secrets, and the Double Ratchet derived from that bundle encrypts every DAG payload going forward. The command returns both claim/associate transaction digests and prints the initial message in JSON format, enabling you to audit the handshake.

Like `init-key`, it refuses to overwrite an existing persistent key unless `--force`. Empty or whitespace-only passphrases are rejected to avoid unusable configs.
Before sending the associate transaction, the CLI automatically generates an identity key if one is missing and persists the session in `~/.nexus/crypto.toml`. All subsequent `nexus dag` commands load that session to encrypt entry-port payloads or decrypt remote results, so run `auth` whenever you rotate keys or see “No active sessions found.”

{% hint style="info" %}
This command requires that a wallet is connected to the CLI and spends gas for **two** programmable transactions. Use `--sui-gas-coin` / `--sui-gas-budget` if you need explicit control.
{% endhint %}

---

**`nexus crypto key-status`**
**`nexus crypto generate-identity-key`**

Reports where the current master key will be loaded from, following the same priority order as the runtime resolver: `NEXUS_CLI_STORE_PASSPHRASE` environment variable, keyring passphrase entry, or raw key entry. If a raw key is in use the CLI prints the first 8 hex characters so you can distinguish multiple installations; otherwise it notes the source or that no persistent key exists yet.
Creates a brand-new long-term identity key and stores it inside `~/.nexus/crypto.toml` (encrypted if at-rest encryption is enabled). Because peers can no longer trust sessions tied to the previous identity, the CLI makes it clear that all stored sessions become invalid. Run `nexus crypto auth` immediately after to populate a replacement session.

---

Expand Down
12 changes: 12 additions & 0 deletions nexus-sdk/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The [`NexusClient`] provides access to:
- [`CryptoActions`]: perform cryptographic handshakes with Nexus
- [`WorkflowActions`]: publish and execute workflows (DAGs)
- [`SchedulerActions`]: create and manage scheduler tasks, occurrences, and periodic schedules
- [`NetworkAuthActions`]: manage message-signing key bindings for Tools/Leader nodes

You can initialize a `NexusClient` via [`NexusClient::builder()`] with:

Expand Down Expand Up @@ -46,6 +47,17 @@ async fn main() -> anyhow::Result<()> {

---

## 🔏 Network Auth (signed HTTP)

`NexusClient` exposes `network_auth()` for Tool/Leader node message-signing key operations:

- register/rotate a Tool message-signing key on-chain, and
- export a local allowlist file of permitted Leader nodes for Tool-side verification (no RPC at runtime).

This is the same functionality exposed via the CLI under `nexus tool auth ...`.

---

## 🔑 Signer

### Signing Mechanism
Expand Down
38 changes: 23 additions & 15 deletions nexus-sdk/guides/onchain-tool-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ In `sources/my_onchain_tool.move`:
```move
module my_onchain_tool::my_onchain_tool;

use nexus_primitives::data;
use nexus_primitives::proof_of_uid::ProofOfUID;
use nexus_workflow::tool_output::{Self, ToolOutput};
use nexus_primitives::tagged_output::{Self, TaggedOutput};
use sui::bag::{Self, Bag};
use sui::clock::Clock;
use sui::transfer::share_object;
Expand Down Expand Up @@ -103,7 +104,7 @@ public struct MyToolState has key {
```move
/// Tool execution output variants.
/// This enum is used for automatic schema generation during registration.
/// It's not used during execution. Only the ToolOutput object is used.
/// It's not used during execution. Only the TaggedOutput object is used.
public enum Output {
Ok {
result: u64,
Expand Down Expand Up @@ -149,7 +150,7 @@ This is the core of your tool, the function that performs the actual execution:
/// CRITICAL REQUIREMENTS:
/// 1. First parameter: worksheet: &mut ProofOfUID
/// 2. Last parameter: ctx: &mut TxContext
/// 3. Return type: ToolOutput
/// 3. Return type: TaggedOutput
/// 4. Must stamp worksheet with witness ID
public fun execute(
worksheet: &mut ProofOfUID,
Expand All @@ -158,7 +159,7 @@ public fun execute(
input_value: u64,
clock: &Clock,
ctx: &mut TxContext,
): ToolOutput {
): TaggedOutput {
// Get the witness for stamping.
let witness = state.witness();

Expand All @@ -168,37 +169,44 @@ public fun execute(
// Implement your tool logic here
if (input_value == 0) {
// Return error variant
tool_output::err(b"Input value cannot be zero")
Self::new(b"err")
.with_named_payload(b"reason", data::inline_one(b"Input value cannot be zero").as_string())
} else if (input_value > 1000) {
// Return custom variant
tool_output::variant(b"custom_result")
.with_field(b"data", tool_output::string_value(b"large_value_processed"))
.with_field(b"timestamp", tool_output::number_value(sui::clock::timestamp_ms(clock).to_string().into_bytes()))
Self::new(b"custom_result")
.with_named_payload(b"data", data::inline_one(b"large_value_processed").as_string())
.with_named_payload(b"timestamp", data::inline_one(sui::clock::timestamp_ms(clock).to_string().into_bytes()).as_number())
} else {
// Return success variant
let result = input_value * 2;
tool_output::ok()
.with_field(b"result", tool_output::number_value(result.to_string().into_bytes()))
Self::new(b"ok")
.with_named_payload(b"result", data::inline_one(result.to_string().into_bytes()).as_number())
}
}
```

#### Understanding Field Value Types

When adding fields to `ToolOutput`, you must use typed constructor functions to ensure proper JSON formatting:
When adding fields to `TaggedOutput`, you must use the fluent API with type hints to ensure proper JSON formatting:

```move
// Numeric values (u8, u16, u32, u64, u128, u256)
.with_field(b"count", tool_output::number_value(value.to_string().into_bytes()))
.with_named_payload(b"count", data::inline_one(value.to_string().into_bytes()).as_number())

// String values (will be wrapped in quotes in JSON)
.with_field(b"message", tool_output::string_value(b"Hello world"))
.with_named_payload(b"message", data::inline_one(b"Hello world").as_string())

// Boolean values (true/false without quotes in JSON)
.with_field(b"success", tool_output::bool_value(b"true"))
.with_named_payload(b"success", data::inline_one(b"true").as_bool())

// Address values (prefixed with "0x" and wrapped in quotes in JSON)
.with_field(b"sender", tool_output::address_value(address.to_string().into_bytes()))
.with_named_payload(b"sender", data::inline_one(address.to_string().into_bytes()).as_address())

// Raw JSON values (objects, arrays, null - passed through as-is)
.with_named_payload(b"metadata", data::inline_one(b"{\"key\":\"value\"}").as_raw())

// Many values (for loops in nexus)
.with_named_payload(b"items", data::inline_many(items).as_number())
```

This typing ensures that the Nexus framework correctly parses and processes your tool's outputs.
Expand Down
24 changes: 12 additions & 12 deletions nexus-sdk/guides/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,24 +143,23 @@ Note that this coin can only be used to pay for Nexus and tool invocation fees o

## Configure Encryption for Nexus workflows

Nexus encrypts every sensitive value in your CLI config and every DAG payload using a [Signal-inspired](https://signal.org/docs/) stack: a persistent master key protects secrets at rest, an [X3DH](https://signal.org/docs/specifications/x3dh/) identity key authenticates you to the network, and a [Double-Ratchet](https://signal.org/docs/specifications/doubleratchet/) session derived from an on-chain pre-key encrypts runtime traffic. Follow the steps below in order; each one builds on the previous.
Nexus encrypts every DAG payload using a [Signal-inspired](https://signal.org/docs/) stack: an [X3DH](https://signal.org/docs/specifications/x3dh/) identity key authenticates you to the network, and a [Double-Ratchet](https://signal.org/docs/specifications/doubleratchet/) session derived from an on-chain pre-key encrypts runtime traffic. The CLI can also optionally encrypt its locally stored crypto state at rest using a master key in your OS keyring. Follow the steps below in order; each one builds on the previous.

### 1. Initialize the CLI master key

The CLI stores encrypted blobs (identity key, sessions, Walrus credentials, etc.) in `~/.nexus/*.toml`. Those blobs are decrypted using a 32-byte master key that lives either in your OS keyring or is derived from a passphrase via [Argon2id](https://en.wikipedia.org/wiki/Argon2).
The CLI stores its local crypto state (identity key, sessions, etc.) in `~/.nexus/crypto.toml`. By default, the CLI will **auto-enable** at-rest encryption the first time it needs to persist secret state by creating a 32-byte master key in your OS keyring. If the keyring is unavailable (common in headless/CI environments), the CLI will warn and store secrets in plaintext so it can continue to work.

```bash
# Option A: generate a raw master key inside the OS keyring
nexus crypto init-key
Note: your Sui private key (configured via `nexus conf set --sui.pk`) is stored in `~/.nexus/conf.toml` as plaintext, so treat that file as sensitive.

# Option B: store a passphrase (useful for headless or CI)
printf "my-strong-passphrase" | nexus crypto set-passphrase --stdin
```bash
# Optional: check whether secrets will be encrypted at rest
nexus secrets status
```

Both commands refuse to overwrite existing credentials unless you add `--force`, because rotating the master key invalidates every encrypted entry. You can confirm which source will be used by running:
If you want to enable encryption proactively (instead of waiting for the first secret write), run:

```bash
nexus crypto key-status
nexus secrets enable
```

### 2. Generate an identity key
Expand All @@ -171,7 +170,7 @@ Your long-term identity key represents the "public face" of the CLI in the Signa
nexus crypto generate-identity-key
```

This writes a freshly generated X25519 key pair into `~/.nexus/crypto.toml`, encrypted with the master key from Step 1.
This writes a freshly generated X25519 key pair into `~/.nexus/crypto.toml` (encrypted if secrets encryption is enabled and the keyring is available).

### 3. Establish a Signal-style session

Expand All @@ -185,7 +184,7 @@ Behind the scenes the command:

1. Submits a programmable transaction that calls `pre_key_vault::claim_pre_key_for_self`, emitting the pre-key bundle bytes for your Sui address.
1. Runs the X3DH sender flow locally using your identity key and that bundle, deriving shared secrets and the first Double-Ratchet message.
1. Persists the resulting session (encrypted) to `~/.nexus/crypto.toml`.
1. Persists the resulting session to `~/.nexus/crypto.toml` (encrypted if a master key is configured).
1. Sends a second programmable transaction that associates the claimed pre-key object with your address and delivers the initial encrypted message to the network.

Every `nexus dag execute` / `inspect-execution` call now loads this session to encrypt entry ports and decrypt remote-hosted outputs. If you delete the session or rotate keys, simply rerun `nexus crypto auth` to mint a replacement.
Expand All @@ -201,12 +200,13 @@ sequenceDiagram
participant Sui as Sui chain
participant Vault as PreKeyVault contract
participant Network as Nexus network
participant LeaderNode as Leader node

CLI->>Conf: Load config + decrypt identity key
CLI->>Sui: Submit claim_pre_key_for_self PTB (request)
Sui->>Vault: execute pre_key_vault::claim_pre_key_for_self
Vault-->>Sui: Emit PreKeyRequestedEvent (no bundle yet)
Leader->>Sui: Submit fulfill_pre_key_for_user PTB
LeaderNode->>Sui: Submit fulfill_pre_key_for_user PTB
Sui->>Vault: execute pre_key_vault::fulfill_pre_key_for_user
Vault-->>Sui: Emit PreKeyFulfilledEvent (carries pre_key bytes)
CLI->>Sui: poll events, read pre_key bytes from PreKeyFulfilledEvent
Expand Down
Loading