aqua-rs-sdk-core
Rust API reference for aqua-rs-sdk-core, the WASM-free core implementation of Aqua Protocol v4
aqua-rs-sdk-core is a minimal, WASM-free Rust implementation of the Aqua Protocol core: verifiable, portable data trees with anchors, typed objects, templates, cryptographic signatures, and selective disclosure. It is a compatible subset of the full aqua-rs-sdk (scheduled for a later publication): canonicalization and verification semantics are identical, and the machinery and signature templates it shares with the full SDK are byte-identical — the eleven audit templates, however, are a deliberate, test-bounded fork whose hashes differ from their full-SDK counterparts, pending upstream harmonization. That compatibility is not aspirational — it is enforced by a cross-implementation test suite (compat-tests/) that runs both crates side by side and cross-verifies signed trees in both directions.
The crate builds as a plain rlib — no wasm-bindgen, no wasmi, no cdylib — with 18 runtime dependencies, licensed Apache-2.0. Source: github.com/inblockio/aqua-rs-sdk-core.
Aqua Protocol v4 is under active development. The published components — aqua-rs-sdk-core and aqua-template-registry — are an early community release with a deliberately limited functionality scope. Version numbers have not yet been increased, backward compatibility is not yet provided, and breaking changes are expected and may occur without notice. The full aqua-rs-sdk, which extends this core, is scheduled for a later publication. For production use today, refer to the stable v3 documentation.
The normative protocol specification ships in the same repository under protocol-specification/. The code is the reference implementation; the specification states the protocol a conforming producer or verifier must follow.
Installation
The crate is not on crates.io yet (publication is planned); install it as a git dependency:
1[dependencies]2aqua-rs-sdk-core = { git = "https://github.com/inblockio/aqua-rs-sdk-core" }3tokio = { version = "1", features = ["macros", "rt-multi-thread"] } # for the async API4serde_json = "1"5hex = "0.4"tokio is only needed for the async signing and verification API; a fully synchronous verification path (verify_tree_sync) exists if you prefer not to pull in an async runtime.
Feature flags:
| Flag | Default | Purpose |
|---|---|---|
native | on | Enables the EIP-191 secp256k1 signer (Secp256k1Signer). The SigningCredentials::Secp256k1 variant exists regardless of the feature; without native, signing with it fails at runtime with a NotSupported error |
The Aquafier entry point
Aquafier is the SDK facade: a stateless, Clone + Send + Sync handle, safe to wrap in Arc and share across threads. Signing keys are passed per call as SigningCredentials; verification behavior is governed by a VerificationPolicy. Construct it with Aquafier::new() for defaults, or Aquafier::builder() for explicit configuration:
| Builder option | Default | Effect |
|---|---|---|
default_object_method | Method::Tree | Hashing method for object revisions; Tree enables field-level selective disclosure |
default_signature_method | Method::Scalar | Method for non-object revisions (signatures, anchors) |
hash_type | HashType::Sha3_256 | Hash algorithm for new trees (Blake3_256 is opt-in); existing trees keep the algorithm they were created with |
verification_policy | VerificationPolicy::strict() | Governs the relaxable verification decision points (strict(), offline(), debug(), or custom); structural validation and hash integrity always hard-fail regardless of policy |
The method reference below follows the task flow: create, sign, verify, link, export, disclose. Examples are fragments (// … marks elided context); a complete runnable program is in the Quick Start.
Creating trees
create_genesis_revision()
Creates the root revision of a new Aqua tree from file data (content hash, size, name).
Signature:
1pub fn create_genesis_revision(2 &self,3 file_data: FileData,4 method: Option<Method>,5) -> Result<Tree, MethodError>Parameters:
file_data:FileData::new(file_name, file_content, path)— the content to anchormethod:Noneuses the configured default (Tree)
Returns: the new Tree, or MethodError on failure.
Example:
1// … aquafier: Aquafier2let file = FileData::new(3 "hello.txt".to_string(),4 b"hello world".to_vec(),5 PathBuf::from("hello.txt"),6);7let tree = aquafier.create_genesis_revision(file, None)?;Two variants cover special cases: create_minimal_genesis_revision (content hash only, no file metadata) and create_genesis_revision_from_metadata (takes a FileMetadata with a pre-computed content hash and size, avoiding a redundant hash pass).
create_object()
Creates a typed object revision. The template_hash identifies the template whose JSON Schema the payload must satisfy; it becomes the revision's revision_type.
Signature:
1pub fn create_object(2 &self,3 template_hash: RevisionLink,4 previous_tree: Option<Tree>,5 payload: serde_json::Value,6 method: Option<Method>,7) -> Result<Tree, MethodError>Parameters:
template_hash: the template'sRevisionLink(its multihash identity)previous_tree:Some(tree)appends to an existing tree;Nonestarts a new onepayload: the object's JSON payloadmethod:Noneuses the configured default (Tree)
Returns: the updated Tree, or MethodError on failure.
create_object validates the payload against the template's JSON Schema only when it can resolve the template — and it can only resolve this crate's built-ins. For a custom, imported, or registry-sourced type it creates the revision unvalidated, and the mistake surfaces later, at the receiver's verification. Use create_object_validated for every non-built-in type.
create_object_validated()
Creates a typed object revision, validating the payload against a template resolved from explicit sources. This is the required creation path for custom, imported, and registry-sourced types.
Signature:
1pub fn create_object_validated(2 &self,3 template_hash: RevisionLink,4 previous_tree: Option<Tree>,5 payload: serde_json::Value,6 method: Option<Method>,7 template_sources: &[Tree],8) -> Result<Tree, CreateObjectError>Parameters:
template_sources: one-revision template trees, as produced bytemplate_treeor handed out by a registry import store. Resolution order matches verification: the previous tree's own revisions, the built-in catalog, then these sources- remaining parameters: as in
create_object
Returns: the updated Tree. Fails closed: CreateObjectError::TemplateNotFound if no source supplies the template (never a silent unvalidated create), AncestorTemplateNotFound if an ancestor in the template's derives_from chain is missing, SchemaViolation with per-field errors if the payload does not satisfy the schema. The resulting tree is byte-identical to what create_object would produce for the same inputs — this adds a gate, not a different construction.
Example:
1// … my_template: Template, my_template_link: RevisionLink2let source = aquafier.template_tree(&my_template, Some("my_template"))?;3let tree = aquafier.create_object_validated(4 my_template_link,5 None,6 serde_json::json!({ "field": "value" }),7 None,8 &[source],9)?;Templates
create_template()
Creates a new template definition and registers it with this Aquafier instance. The template's SHA3-256 hash becomes its type identifier.
Signature:
1pub fn create_template(2 &mut self,3 json_schema: serde_json::Value,4 template_name: String,5 enable_scalar: bool,6) -> Result<Tree, MethodError>Parameters:
json_schema: the JSON Schema that objects of this type must satisfytemplate_name: display name for the templateenable_scalar: allowScalar-method objects of this type in addition toTree-method
Returns: a single-revision template Tree. Note the &mut self: the template is also registered on the instance and listed by get_available_templates().
Example: see docs/template-authoring.md for the full authoring workflow (schema rules, hash pinning, shipping).
template_tree()
Wraps an existing template definition as a one-revision Aqua tree, keyed by the template's full multihash link. This is the portable-template shape: the unit a template author publishes, an import store keeps, export_tree accepts as a template source, and verify_aqua_tree_with_linked_trees resolves custom types from.
Signature:
1pub fn template_tree(2 &self,3 template: &schema::Template,4 name: Option<&str>,5) -> Result<Tree, MethodError>Parameters:
template: the parsed template definitionname: label for the tree'sfile_index(organizational metadata, never hashed);Nonefalls back to the built-in name or atemplate_<hash prefix>label
Returns: the one-revision template Tree.
Example:
1// … registry_template_json: String from a registry import store2let template: Template = serde_json::from_str(®istry_template_json)?;3let source = aquafier.template_tree(&template, Some("audit_user_turn_marker"))?;Signing
sign_aqua_tree()
Signs the latest revision in an Aqua tree, appending a Signature revision that points at the tree's tip. The credentials determine the signature suite:
| Variant | Field | Suite and identity |
|---|---|---|
SigningCredentials::Did | did_key: Vec<u8> (32-byte Ed25519 secret) | Ed25519, did:key:z6Mk… |
SigningCredentials::P256 | p256_key: Vec<u8> | ECDSA P-256, did:key:zDn… |
SigningCredentials::Secp256k1 | secp256k1_key: Vec<u8> (32-byte scalar) | EIP-191, did:pkh:eip155:… — requires the native feature |
Signature:
1pub async fn sign_aqua_tree(2 &self,3 aqua_tree: AquaTreeWrapper,4 credentials: &SigningCredentials,5 method: Option<Method>,6 ident_character: Option<String>,7) -> Result<AquaOperationData, MethodError>Parameters:
aqua_tree:AquaTreeWrapper::new(tree, file_object, revision)—revision: Nonetargets the tree's tipcredentials: one of the variants abovemethod:Noneuses the configured default (Scalar)ident_character: optional application-level label recorded in the signature revision's metadata (e.g. multi-signer workflows)
Returns: AquaOperationData — the signed tree in .aqua_tree, plus diagnostic log_data.
Example:
1// … tree: Tree from a create call2let (secret, _did) = aqua_rs_sdk_core::generate_ed25519();3let creds = SigningCredentials::Did { did_key: secret.to_vec() };4let signed = aquafier5 .sign_aqua_tree(AquaTreeWrapper::new(tree, None, None), &creds, None, None)6 .await?;Verifying
verify_aqua_tree()
Runs the full verification pipeline over a tree: structural validation, hash recomputation, type and schema checks, and signature verification for every revision.
Signature:
1pub async fn verify_aqua_tree(2 &self,3 aqua_tree_wrapper: AquaTreeWrapper,4 file_objects: Vec<FileData>,5) -> Result<VerificationResult, MethodError>Parameters:
aqua_tree_wrapper: the tree to verifyfile_objects: file contents for trees containing file genesis revisions (content hash verification)
Returns: a VerificationResult:
| Field | Type | Meaning |
|---|---|---|
outcome | VerificationOutcome | Verified, VerifiedWithWarnings (carries the tolerated policy warnings), or Failed — the single source of truth |
logs | Vec<LogData> | Per-revision diagnostic log entries |
wasm_outputs | HashMap<String, serde_json::Value> | WASM outputs keyed by revision hash; populated only when WASM verification passed (core has no WASM runtime and rejects compute-bearing templates instead) |
template_trust | HashMap<String, TemplateTrust> | Template-trust labels keyed by revision hash; populated only for revisions whose WASM verification executed |
result.is_verified() is true for Verified and VerifiedWithWarnings; result.is_clean() is true only for Verified.
Example:
1// … signed: AquaOperationData, file: FileData2let result = aquafier3 .verify_aqua_tree(4 AquaTreeWrapper::new(signed.aqua_tree, Some(file.clone()), None),5 vec![file],6 )7 .await?;8assert!(result.is_verified());Variants:
verify_aqua_tree_with_linked_trees(wrapper, linked_trees, file_objects)— additionally verifieslinked_treesin topological order and resolves cross-tree references; this is how custom and registry-imported template trees are supplied to the verifierverify_tree_sync(wrapper, file_objects)andverify_tree_sync_with_linked_trees(wrapper, linked_trees, file_objects)— the same pipeline with no async runtime required
Linking
link_aqua_tree()
Links one or more Aqua trees to the current tree via an Anchor revision whose structural_links contain the tip hashes of the linked trees, enabling cross-tree verification.
Signature:
1pub fn link_aqua_tree(2 &self,3 aqua_tree_wrapper: AquaTreeWrapper,4 link_aqua_tree_wrapper: Vec<AquaTreeWrapper>,5 method: Option<Method>,6) -> Result<Tree, MethodError>Parameters:
aqua_tree_wrapper: the tree receiving the anchorlink_aqua_tree_wrapper: the trees being linkedmethod:Noneuses the configured default (Scalar)
Returns: the updated Tree. See Anchors and Links for the structural-versus-compositional link semantics.
Exporting
export_tree()
Exports a tree as a self-descriptive artifact: embeds every template it references, plus those templates' full derives_from ancestry, so the result verifies on its own with no side inputs. This is the primary way to hand a tree to someone else.
Signature:
1pub fn export_tree(2 &self,3 tree: &Tree,4 extra_template_sources: &[Tree],5 options: &ExportOptions,6) -> Result<Tree, ExportTreeError>Parameters:
tree: the tree to export (never mutated; re-exporting an exported tree is a no-op)extra_template_sources: additional template trees to resolve from (e.g. from a registry import store); consulted after the tree's own revisions and the built-in catalogoptions:ExportOptions::default()/::self_descriptive()embeds everything, including built-ins ("built-in" is a property of the receiver, not the sender);::non_builtin_only()skips the catalog templates;::bare()embeds nothing — a plain clone
Returns: the exported Tree. Fails closed with ExportTreeError::UnresolvedTemplates listing every hash no source could supply; nothing is embedded in that case.
Example:
1// … tree: Tree, my_template_tree: Tree2let portable = aquafier.export_tree(&tree, &[my_template_tree], &ExportOptions::default())?;3assert!(missing_templates(&portable).is_empty());4 5let bare = aquafier.export_tree(&tree, &[], &ExportOptions::bare())?;missing_templates(&tree) is the same resolution check as a standalone lint: receivers use it to triage an incoming tree, publishers use it in CI.
Selective disclosure
redact_revision()
Produces a RedactedRevision from a tree-method revision, disclosing only the listed field paths; every other leaf is replaced by its value commitment. Only method: "tree" revisions can be field-redacted, and /nonce must never be disclosed (the request is refused).
Signature:
1pub fn redact_revision(2 revision: &AnyRevision,3 revision_hash: &RevisionLink,4 disclosed_paths: &[String],5) -> Result<RedactedRevision, RedactionError>For whole-tree disclosure, export_selective_tree(&tree, &policy) applies a DisclosurePolicy: DisclosurePolicy::full(&tree) disclosing everything, DisclosurePolicy::pseudonymous(&tree) applying the normative per-template allow-lists for audit artifacts (payload facts stay verifiable, identifying fields stay sealed), or DisclosurePolicy::with_profile(…) for custom profiles. Verification counterparts: verify_redacted_revision, verify_selective_tree.
Returns / semantics: see Selective Disclosure for the redaction model, what redacted verification can and cannot check, and the disclosure presets.
Helpers
| Need | API |
|---|---|
Every shipped template hash as data (19 entries, mirrors tests/audit_template_hashes.txt) | Aquafier::shipped_template_hashes() -> &'static [(&'static str, [u8; 32])] |
| The verification catalog only (14 templates resolvable as object types) | Aquafier::builtin_template_hashes() |
A fresh signing identity (secret key plus its did:key) | generate_ed25519() -> ([u8; 32], String) |
Convert a wire link (0x1620…) to the bare 64-hex digest used by ledgers and …_hash payload fields | RevisionLink::bare_digest(), bare_digest_hex() |
| A Merkle root over a possibly empty batch | primitives::merkle::try_merkle_root(&[Vec<u8>], &HashType) -> Option<Vec<u8>> |
Gotchas
merkle_rootpanics on an empty leaf slice, by documented design: it is a byte-for-byte shared primitive with the full SDK and cannot change behavior without forking a hashing path. For caller- or network-supplied batches, usetry_merkle_root, which returnsNoneinstead.- Template JSON files are byte-frozen. A template's hash is its type identity: one changed byte, even reformatted whitespace, produces a different hash and therefore a different type. Never reformat a shipped or published template definition.
- Linked and embedded template trees are keyed by the full multihash (
0x+ 68 hex), never by the bare 64-hex digest. Bare-digest keying is an internal implementation detail of the built-in catalog and is not usable for a linked or embedded tree;template_tree()produces the correctly keyed shape.
Testing
1cargo test # unit tests, standalone2cargo run --features native --bin verify-templates # recompute and check the template hash ledger3cargo run --example agent_audit_trail --features native # end-to-end agent audit trailexamples/agent_audit_trail.rs is the full walkthrough of the audit template family: four did:key identities, T1–T8 artifacts as signed trees, a Merkle round anchor, session close, self-descriptive export, verification of every tree, and pseudonymous redaction — see Audit Trails for AI Agents. The compat suite (cargo test --manifest-path compat-tests/Cargo.toml) requires a sibling checkout of the full aqua-rs-sdk, which is not yet public.
What is not included
WASM compute execution, the full-SDK template set, timestamping, the policy engine, the daemon/forest runtime, and a template registry client are outside this crate, and every exclusion answers explicitly — a fail-closed error, an explicit "not supported" message, or a policy-governed condition, never a silent pass. The exclusions table with the per-capability behavior lives in Release Status & Versioning. The invariant behind it: core is never more permissive than the full SDK under the same verification policy.
See also
- Quick Start — a complete first program with this crate
- Template Registry — publishing and importing templates
- Audit Trails for AI Agents — the T1–T8 audit template family in practice
- Verification — outcomes, stages, and policies in detail
- Selective Disclosure — the redaction model behind the disclosure API
