cardanowall 0.2.0

Rust SDK for the Label 309 Proof-of-Existence standard.
Documentation
# cardanowall — Rust SDK for the Label 309 Proof-of-Existence standard

`cardanowall` is the Rust implementation of the Label 309 Proof-of-Existence (PoE)
toolkit: a standalone structural validator, a public verifier, a recipient
verifier with sealed-PoE decryption, and a gateway-agnostic HTTP client. It is a
**byte-parity sibling** of the TypeScript (`@cardanowall/sdk-ts`) and Python
(`cardanowall-sdk`) SDKs — it independently reproduces the same canonical-CBOR
bytes, validation verdicts, and cryptographic outputs, proven against the same
shared cross-implementation test vectors.

Nothing in this crate trusts a publisher or an issuer server. A proof verifies
from a Cardano transaction's metadata, an optional copy of the content bytes, and
a public blockchain explorer. The HTTP layer is blocking (`reqwest` in blocking
mode) and secure-by-default: every outbound call flows through one egress point
that enforces a protocol/method allowlist, a deny-host policy, bounded response
bodies and timeouts, and an SSRF guard for user-supplied URLs.

The companion `cardanowall` CLI binary is a separate crate built on top of this
SDK.

## What it is

- **A standalone verifier** with three roles, all service-independent:
  - **Structural validator**`poe_standard::validate_poe_record`, a pure
    function over canonical-CBOR bytes. No I/O, no signatures, no decryption.
  - **Public verifier**`verifier::verify_tx` resolves a transaction, extracts
    the label-309 record, validates it structurally, and verifies record-level
    signatures.
  - **Recipient verifier** — the public verifier plus an X25519 private key:
    decrypts a sealed PoE and recomputes plaintext hashes.
- **A gateway-agnostic client** (`client::Label309Client`) for any Label 309
  gateway: you pass an explicit base URL and an optional opaque bearer key.
- **The cryptographic building blocks** — hash, KDF, COSE, sealed-PoE,
  recipient encoding, Merkle, and seed-derived identity helpers.

## Install

Pre-1.0 and not yet published to crates.io. Build from the workspace, or depend
on it by path/git:

```toml
# Cargo.toml — by git, until published
[dependencies]
cardanowall = { git = "https://github.com/cardanowall/label-309-rs" }
```

Once published to crates.io the line will be:

```toml
# once published
[dependencies]
cardanowall = "0.1"
```

The crate requires the OS CSPRNG for the secure-by-default sealed-PoE wrap and
uses `rustls` for TLS (no system OpenSSL needed).

## Quick start

### Hash content (byte-identical across the SDK family)

```rust
use cardanowall::hash::{dual_hash, SHA2_256_ID, BLAKE2B_256_ID};
use cardanowall::hex;

let digests = dual_hash(b"contract v3");
println!("{} = {}", SHA2_256_ID, hex::encode(&digests.sha256));
println!("{} = {}", BLAKE2B_256_ID, hex::encode(&digests.blake2b256));
```

For large or streamed input, `hash::dual_hash_stream` computes both digests
without holding the whole input in memory.

### Validate a record structurally (pure, no I/O)

```rust
use cardanowall::poe_standard::{validate_poe_record, ValidateResult};

// `record_bytes` is the canonical-CBOR record, reassembled from the
// label-309 bytes-chunk array (records are tag-259-wrapped on the wire).
match validate_poe_record(&record_bytes) {
    ValidateResult::Valid { record, issues } => {
        // `issues` may carry warnings/info; the record is structurally sound.
        println!("valid record, {} issue(s)", issues.len());
        let _ = record;
    }
    ValidateResult::Invalid { issues } => {
        for issue in issues {
            eprintln!("{}: {}", issue.code.as_str(), issue.message);
        }
    }
}
```

### Verify a transaction end to end (public verifier)

```rust
use cardanowall::verifier::{verify_tx, ExitCode, Verdict, VerifyTxInput};

let mut input = VerifyTxInput::new("aabbccdd…");        // lowercase tx hash, no 0x
input.cardano_gateway_chain = Some(vec![
    "https://api.koios.rest/api/v1".to_string(),
]);

let report = verify_tx(&input);
match report.verdict {
    Verdict::Valid => println!("valid, exit {}", report.exit_code.as_u8()),
    Verdict::Pending => println!("not yet final: {} confirmations", report.num_confirmations),
    Verdict::Failed => eprintln!("failed, exit {}", report.exit_code.as_u8()),
}
assert!(matches!(report.exit_code, ExitCode::Ok | ExitCode::InsufficientDepth | ExitCode::Integrity | ExitCode::Network));
```

`verify_tx` never panics: a malformed record, a missing label-309 entry, or a
gateway failure each produce a `VerifyReport` with the corresponding verdict and
exit code (`0` ok, `1` integrity, `2` network, `3` insufficient depth). The
report carries a per-call HTTP audit trail in `report.http_calls`.

### Recipient verifier: decrypt a sealed PoE

`VerifyTxInput` accepts out-of-band recipient keys; the verifier then decrypts
the sealed item and recomputes the plaintext hashes as part of the verdict:

```rust
use cardanowall::verifier::{verify_tx, Decryption, Verdict, VerifyTxInput};

let mut input = VerifyTxInput::new("aabbccdd…");
input.cardano_gateway_chain = Some(vec!["https://api.koios.rest/api/v1".to_string()]);
input.decryption = Some(vec![Decryption::Recipient {
    item_index: 0,
    recipient_secret_key: recipient_x25519_or_xwing_secret, // Vec<u8>
}]);

let report = verify_tx(&input);
assert_eq!(report.verdict, Verdict::Valid);
```

### Sign a record off-host from a master seed

The seed-derived signer holds the Ed25519 secret in-memory and exposes only the
public key and the 64-byte signature, so the gateway never sees a private key:

```rust
use cardanowall::client::Signer;
use cardanowall::seed_derive::signer_from_seed;

let seed = [0u8; 32];                       // the 32-byte master identity seed
let signer = signer_from_seed(&seed)?;       // SeedDeriveError on wrong length
let pubkey = signer.signer_pubkey();         // raw 32-byte Ed25519 public key
let signature = signer.sign(&sig_structure_bytes)?; // 64-byte Ed25519 signature
# Ok::<(), cardanowall::seed_derive::SeedDeriveError>(())
```

`seed_derive::derive_ed25519_keypair`, `derive_x25519_keypair`, and
`derive_mlkem768x25519_keypair` (hybrid post-quantum X-Wing) expose the same
deterministic identity keys directly.

### Talk to a gateway (any Label 309 deployment)

The client targets no default host. You name the gateway with an explicit
`base_url`; the optional `api_key` is an opaque bearer forwarded verbatim as
`Authorization: Bearer …` (never validated or parsed). With no key the client is
anonymous and read-only.

```rust
use cardanowall::client::{Label309Client, Label309ClientConfig};

let client = Label309Client::new(Label309ClientConfig {
    base_url: Some("https://gateway.example".to_string()),
    api_key: Some("opaque-bearer-token".to_string()),
})?;

let balance = client.account().balance()?;        // AccountBalance
let record = client.records().get("aabbccdd…")?;  // RecordResource, by tx hash
# Ok::<(), Box<dyn std::error::Error>>(())
```

`cardanowall.com` is one example deployment; this SDK works against any gateway
that implements the Label 309 surface. A missing or empty `base_url` is the one
illegal config and raises `InvalidClientConfigError` from the constructor.

## API overview

| Module                                   | Surface                                                                                                                                                                                                       |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hash`                                   | `sha256`, `blake2b256`, `dual_hash`, `dual_hash_stream`; `SHA2_256_ID`, `BLAKE2B_256_ID`                                                                                                                      |
| `poe_standard`                           | `validate_poe_record`, `encode_poe_record`, `encode_record_body_for_signing`, `chunk_bytes` / `bytes_chunk_array_concat`, `chunk_uri` / `reconstruct_chunked_uri`; `PoeRecord`, `ValidateResult`, `ErrorCode` |
| `verifier`                               | `verify_tx`, `VerifyTxInput`, `VerifyReport`, `Verdict`, `ExitCode`, `Profile`, `Decryption`; `verify_record_signatures`, `extract_label_309_metadata`, `resolve_cardano_tx`, `verify_report_to_dict`         |
| `client`                                 | `Label309Client`, `Label309ClientConfig`; namespaces `poe()` / `records()` / `inbox()` / `account()`; `Signer`, off-host signing helpers, `HttpError`, `ProblemDetails`                                       |
| `seed_derive`                            | `signer_from_seed`, `derive_ed25519_keypair`, `derive_x25519_keypair`, `derive_mlkem768x25519_keypair`, `derive_bookmark_key`                                                                                 |
| `sealed_poe`                             | `ecies_sealed_poe_wrap_secure`, `ecies_sealed_poe_unwrap`, `ecies_sealed_poe_trial_decrypt`, envelope/slots/kem/aead                                                                                          |
| `recipient`                              | `encode_age_x25519_recipient`, `encode_age_xwing_recipient`, `parse_age_recipient`, bech32 helpers                                                                                                            |
| `merkle`                                 | `merkle_root`, `merkle_inclusion_proof`, `verify_inclusion`, `encode_leaves_list` / `decode_leaves_list`                                                                                                      |
| `kdf`                                    | `hkdf_sha256`                                                                                                                                                                                                 |
| `cbor`, `cose`, `hex`, `ids`, `webhooks` | canonical CBOR, COSE_Sign1, hex, wire identifiers, webhook signature verification                                                                                                                             |

The crate denies `unsafe_code` and missing docs; `cargo doc` is the exhaustive,
always-current reference.

### Conformance profiles

The verifier reports a record at the highest profile it could check, in strict
superset order: `core` (hash / uris / merkle) → `signed` (adds record-level
`sigs[]`) → `sealed` (adds the `enc` envelope structure) → `recipient-sealed`
(adds byte-level decryption with a recipient key, the default). A lower-profile
verifier that meets a higher-profile field emits an info issue and continues; it
never reports the record invalid.

## Cross-implementation parity

This crate is a **byte-parity twin** of `@cardanowall/sdk-ts` and
`cardanowall-sdk` (Python). The three implementations are tested against the same
shared known-answer-test vectors and a captured mainnet record corpus:

- the **canonical CBOR** of an encoded record is identical bit-for-bit;
- `validate_poe_record` returns the **same verdict and error codes**;
- `verify_tx`'s report serialises (via `verify_report_to_dict`) to the
  **byte-identical JSON** the TS and Python SDKs emit for the same transaction;
- the **seed-derivation** `info` labels and outputs match, so one master seed
  yields the same identity keys in every SDK;
- the sealed-PoE wrap/unwrap and the X-Wing (ML-KEM-768 + X25519) hybrid KEM
  produce identical wire bytes.

Picking Rust, TypeScript, or Python is an ergonomics decision, not a
compatibility one.

## Standard and service independence

A Label 309 proof is verifiable with no cooperation from whoever published it:

1. fetch the transaction's label-309 metadata from any public Cardano explorer;
2. reassemble and structurally validate the record (`validate_poe_record`);
3. verify record-level signatures and, for a sealed PoE, decrypt with the
   recipient key and recompute plaintext hashes.

`verify_tx` performs all three. No issuer server is contacted, and a deny-host
policy lets a verifier prove this by blocking any vendor host outright. The
records are stored on-chain as a CBOR bytes-chunk array under metadata label 309
(tag-259-wrapped); reassemble before validating — the helpers in `poe_standard`
do this for you.

## Relation to the other packages

- **`@cardanowall/crypto-core`** — closed-catalogue cryptographic primitives
  (hash, KDF, signature, KEM, AEAD, CBOR, COSE, sealed-PoE, Merkle, recipient
  encoding, seed derivation). The portable building blocks.
- **`@cardanowall/poe-standard`** — the Label 309 wire-format library: record
  schema, canonical-CBOR encoder, pure structural validator, error-code
  catalogue.
- **`@cardanowall/sdk-ts`** — the browser + Node TypeScript SDK: verifier,
  agnostic client, off-host signing, seed-derived identity helpers.
- **`cardanowall-sdk`** — the Python SDK: a byte-identical parity twin of
  `sdk-ts`.
- **`cardanowall` (this crate)** — the Rust SDK: the byte-parity twin in Rust,
  blocking HTTP, secure-by-default egress. The `cardanowall` CLI is a separate
  crate built on it.

## License

Apache-2.0.