neco-vault 0.1.1

minimum dependency memory-only signing vault built on neco-secp
Documentation
# neco-vault

Memory-only signing vault built on `neco-secp`, for Nostr client apps that need to sign events without exposing secret keys.

It's a higher-level layer: secret keys stay inside the vault and aren't returned by any public API — only signed events, ciphertext, and public keys come out.

A Japanese architecture note is available in [ARCHITECTURE-ja.md](ARCHITECTURE-ja.md).

## Features

- `nostr`: enable Nostr signing through `neco-secp`
- `nip04`: enable NIP-04 encrypt/decrypt through the vault
- `nip44`: enable NIP-44 encrypt/decrypt through the vault
- `nip17`: enable gift-wrap DM helpers through the vault
- `encrypted`: encrypted import/export via AES-256-CBC with scrypt-based key derivation (adds `aes`, `cbc`, `getrandom`, `scrypt` deps)
- `encrypted-legacy-v1`: opt-in v1 import compatibility via SHA-256(passphrase)
- `security-hardening`: enable optional random delay / dummy operation hardening hooks
- `wasm`: reserved feature for future browser / wasm integration

## Design

The vault never returns plaintext secret keys. All operations that need a secret key run inside the vault; only the result (a signed event, ciphertext, or public key) comes out.

## Usage

### Basic signing

```rust
use neco_secp::{SecretKey, UnsignedEvent};
use neco_vault::{Vault, VaultConfig};

let mut vault = Vault::new(VaultConfig::default()).unwrap();
let secret = SecretKey::generate().unwrap();

vault.import_plaintext("main", secret, 100).unwrap();

let signed = vault.sign_event(
    "main",
    UnsignedEvent {
        created_at: 101,
        kind: 1,
        tags: vec![],
        content: "hello".to_string(),
    },
    102,
).unwrap();

assert_eq!(signed.kind, 1);
```

### Active account

The first imported account is automatically set as active. Use `sign_event_active` to sign without specifying a label.

```rust
vault.import_plaintext("alice", secret, 100).unwrap();
// "alice" is now active

vault.set_active("alice").unwrap();
let label = vault.active_label(); // Some("alice")

let signed = vault.sign_event_active(event, 102).unwrap();
```

Removing the active account sets active to `None`.

```rust
vault.remove("alice").unwrap();
assert_eq!(vault.active_label(), None);
```

Use `labels()` to list all stored account labels.

### Encrypted import/export (`encrypted` feature)

Encrypted export uses scrypt-derived keys and AES-256-CBC. Import accepts only the v2 format by default; older SHA-256-derived v1 blobs don't load unless you enable the `encrypted-legacy-v1` feature.

```rust
let data = vault.export_encrypted("alice", b"passphrase").unwrap();
vault.import_encrypted("bob", b"passphrase", &data, 200).unwrap();
```

### NIP-04 / NIP-44 through the vault

```rust
let bob = vault.public_key("bob").unwrap();
let payload = vault.nip44_encrypt_active(&bob, "hello", 101).unwrap();
let text = vault.nip44_decrypt_active(&bob, &payload, 102).unwrap();
assert_eq!(text, "hello");
```

### Security hardening (`security-hardening` feature)

`SecurityConfig` lets callers enable constant-time touch points, random delay, and dummy operations for secret-key use paths.

## API

| Item | Description |
|------|-------------|
| `SecurityConfig` | Optional hardening flags for secret-key use paths |
| `VaultConfig` | Cache timeout configuration |
| `Vault` | Memory-only secret storage and signing entry point |
| `VaultError` | Vault-level error type |
| `Vault::import_plaintext` | Import a secret key under a label |
| `Vault::remove` | Delete an account by label |
| `Vault::labels` | Return all stored labels |
| `Vault::set_active` | Set the active account label |
| `Vault::active_label` | Return the current active label |
| `Vault::public_key` | Return the xonly public key for a label |
| `Vault::public_key_active` | Return the xonly public key for the active account |
| `Vault::set_security_config` | Update runtime hardening settings |
| `Vault::security_config` | Return current hardening settings |
| `Vault::sign_event` | Sign a Nostr event with a named account |
| `Vault::sign_event_active` | Sign a Nostr event with the active account |
| `Vault::nip04_encrypt`, `Vault::nip04_decrypt` | NIP-04 vault encryption helpers (`nip04` feature) |
| `Vault::nip44_encrypt`, `Vault::nip44_decrypt` | NIP-44 vault encryption helpers (`nip44` feature) |
| `Vault::create_sealed_dm`, `Vault::open_gift_wrap_dm` | NIP-17 vault DM helpers (`nip17` feature) |
| `Vault::export_encrypted` | Export a secret key as AES-256-CBC encrypted bytes (`encrypted` feature) |
| `Vault::import_encrypted` | Import a secret key from encrypted bytes (`encrypted` feature) |

## License

MIT