#![cfg(feature = "component-tests")]
use wasmtime::component::{Component, Linker};
use wasmtime::{Config, Engine, Store};
wasmtime::component::bindgen!({
path: "wit",
world: "aethel-core",
});
const ARTIFACT: &str = "aethel_core.component.wasm";
fn instantiate() -> (Store<()>, AethelCore) {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(ARTIFACT);
assert!(
path.exists(),
"{} not found. Build it first:\n cargo build --release \
--target wasm32-unknown-unknown --no-default-features --features component\n \
wasm-tools component new target/wasm32-unknown-unknown/release/aethel_core.wasm \
-o {}",
ARTIFACT,
ARTIFACT
);
let mut config = Config::new();
config.wasm_component_model(true);
let engine = Engine::new(&config).expect("engine");
let component = Component::from_file(&engine, &path).expect("load component");
let linker = Linker::new(&engine);
let mut store = Store::new(&engine, ());
let bindings = AethelCore::instantiate(&mut store, &component, &linker)
.expect("instantiate the component");
(store, bindings)
}
#[test]
fn the_component_instantiates() {
let _ = instantiate();
}
#[test]
fn projection_through_the_component_matches_the_native_api() {
let (mut store, bindings) = instantiate();
let secret = [0x5Au8; 32];
let tau = b"execution-proof-context".to_vec();
let randomness = [0xC3u8; 32];
let via_component = bindings
.aethel_core_identity()
.call_plp_project_at_context(
&mut store,
&secret,
&tau,
&randomness,
)
.expect("host call")
.expect("plp-project-at-context returned err");
let identity = aethel_core::plp::MasterIdentity::from_seed(&secret);
let native = identity.project_at_context(&tau, &randomness);
assert_eq!(via_component.tau, native.tau.to_vec(), "tau differs");
assert_eq!(
via_component.salt,
native.salt.to_vec(),
"salt differs between the component and the native API"
);
assert_eq!(
via_component.public_b,
native.public_b.coeffs().to_vec(),
"public_b differs between the component and the native API"
);
}
#[test]
fn prove_and_verify_round_trip_inside_the_component() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let secret = [0x11u8; 32];
let tau = b"round-trip".to_vec();
let randomness = [0x77u8; 32];
let projection = identity
.call_plp_project_at_context(&mut store, &secret, &tau, &randomness)
.expect("host call")
.expect("projection");
let proof = identity
.call_plp_prove_identity(&mut store, &secret, &tau, &randomness)
.expect("host call")
.expect("proof");
let verified = identity
.call_plp_verify(&mut store, &projection, &proof)
.expect("host call")
.expect("verify returned err");
assert!(verified, "an honestly generated proof failed to verify through the component");
}
#[test]
fn plp_verify_separates_a_false_verdict_from_unparseable_input() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let secret = [0x11u8; 32];
let tau = b"verdict-vs-parse".to_vec();
let randomness = [0x77u8; 32];
let projection = identity
.call_plp_project_at_context(&mut store, &secret, &tau, &randomness)
.expect("host call")
.expect("projection");
let proof = identity
.call_plp_prove_identity(&mut store, &secret, &tau, &randomness)
.expect("host call")
.expect("proof");
assert!(
identity
.call_plp_verify(&mut store, &projection, &proof)
.expect("host call")
.expect("verify returned err"),
"control: the honest proof should verify before it is tampered with"
);
let mut tampered = proof.clone();
tampered.response_z[0] = tampered.response_z[0].wrapping_add(1);
match identity
.call_plp_verify(&mut store, &projection, &tampered)
.expect("host call")
{
Ok(false) => {}
Ok(true) => panic!("a tampered proof verified"),
Err(e) => panic!("a well-formed but invalid proof returned err({e:?}); it should be a verdict, ok(false)"),
}
let mut malformed = proof.clone();
malformed.response_z.truncate(3);
match identity
.call_plp_verify(&mut store, &projection, &malformed)
.expect("host call")
{
Err(aethel::core::types::IdentityError::SerializationError) => {}
Err(other) => panic!("expected serialization-error for unparseable input, got {other:?}"),
Ok(v) => panic!("unparseable input produced a verdict ok({v}) instead of err; a parse failure is not a verification result"),
}
}
#[test]
fn component_error_variant_reachability() {
const RESERVED: [&str; 3] = [
"norm-bound-violation",
"challenge-mismatch",
"invalid-attribute-commitment",
];
const REACHABLE: [&str; 5] = [
"invalid-input-length",
"serialization-error",
"rejection-sampling-failed",
"threshold-not-met",
"invalid-share-set",
];
let wit = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("wit/aethel-core.wit"),
)
.expect("read wit source");
fn doc_block_above<'a>(wit: &'a str, case: &str) -> &'a str {
let decl = wit
.find(&format!("
{case},"))
.unwrap_or_else(|| panic!("`{case}` is not declared as a bare variant case"));
let before = &wit[..decl];
let mut cut = before.len();
for line in before.lines().rev() {
if line.trim_start().starts_with("///") {
cut -= line.len() + 1;
} else {
break;
}
}
&before[cut..]
}
for case in RESERVED {
assert!(
doc_block_above(&wit, case).contains("RESERVED"),
"`{case}` is declared without a RESERVED marker in its own doc block. Either it now has a producer - in which case document that, move it from RESERVED to REACHABLE here, and add a test reaching it through the component - or the marker was lost."
);
}
for case in REACHABLE {
assert!(
!doc_block_above(&wit, case).contains("RESERVED"),
"`{case}` is marked RESERVED but is reachable and tested. If it genuinely lost its last producer, move it to RESERVED here and say so in src/identity_error.rs; otherwise the marker is wrong."
);
}
}
#[test]
fn a_master_identity_secret_has_no_route_to_htss_split() {
let wit = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("wit/aethel-core.wit"),
)
.expect("read wit source");
let start = wit.find("resource master-identity").expect("resource is declared");
let body = &wit[start..start + wit[start..].find("
}").expect("resource block ends")];
for forbidden in ["secret-key:", "secret:", "export-key:", "seed:", "private-key:"] {
assert!(
!body.contains(forbidden),
"master-identity now exposes `{forbidden}`, so raw key material can leave the resource and be passed to htss-split. That invalidates the L1 boundary review on Component::htss_split - redo it before shipping this."
);
}
assert!(
body.contains("public-key:"),
"control: the resource body was not parsed correctly, so the absence checks above prove nothing"
);
}
#[test]
fn a_proof_from_one_context_does_not_verify_at_another() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let secret = [0x11u8; 32];
let randomness = [0x77u8; 32];
let tau_a = b"context-alpha-0X3-78".to_vec();
let tau_b = b"context-beta-0X3-78".to_vec();
let proj_a = identity
.call_plp_project_at_context(&mut store, &secret, &tau_a, &randomness)
.expect("host call")
.expect("projection a");
let proj_b = identity
.call_plp_project_at_context(&mut store, &secret, &tau_b, &randomness)
.expect("host call")
.expect("projection b");
let proof_a = identity
.call_plp_prove_identity(&mut store, &secret, &tau_a, &randomness)
.expect("host call")
.expect("proof a");
assert!(
identity
.call_plp_verify(&mut store, &proj_a, &proof_a)
.expect("host call")
.expect("verify returned err"),
"control: the proof should verify at the context it was produced for"
);
match identity
.call_plp_verify(&mut store, &proj_b, &proof_a)
.expect("host call")
{
Ok(false) => {}
Ok(true) => panic!("a proof produced at tau_a verified against tau_b's projection"),
Err(e) => panic!("cross-context verification returned err({e:?}); a well-formed proof at the wrong context is a verdict, ok(false)"),
}
}
#[test]
fn a_short_secret_returns_invalid_input_length_not_a_sentinel() {
let (mut store, bindings) = instantiate();
let result = bindings
.aethel_core_identity()
.call_plp_project_at_context(
&mut store,
&[0u8; 31], b"ctx",
&[0u8; 32],
)
.expect("host call");
match result {
Err(aethel::core::types::IdentityError::InvalidInputLength) => {}
Err(other) => panic!("expected invalid-input-length, got {:?}", other),
Ok(_) => panic!(
"a 31-byte secret was accepted. The WIT declares list<u8> and cannot \
express the 32-byte bound, so the implementation owns it"
),
}
}
#[test]
fn htss_round_trips_and_reports_threshold_not_met() {
let (mut store, bindings) = instantiate();
let sharing = bindings.aethel_core_secret_sharing();
let secret = b"32-byte key material for HTSS !!".to_vec();
assert_eq!(secret.len(), 32, "test setup");
let (shares, root) = sharing
.call_htss_split(&mut store, &secret)
.expect("host call")
.expect("split");
assert_eq!(shares.len(), 5, "expected a 3-of-5 split");
let recovered = sharing
.call_htss_reconstruct(&mut store, &shares[..3], &root)
.expect("host call")
.expect("reconstruct");
assert_eq!(recovered, secret, "key material did not survive the component round trip");
let below = sharing
.call_htss_reconstruct(&mut store, &shares[..2], &root)
.expect("host call");
match below {
Err(aethel::core::types::IdentityError::ThresholdNotMet) => {}
Err(other) => panic!("expected threshold-not-met, got {:?}", other),
Ok(_) => panic!("two shares of a 3-of-5 split reconstructed something"),
}
}
#[test]
fn htss_refuses_a_share_set_with_a_repeated_index() {
let (mut store, bindings) = instantiate();
let sharing = bindings.aethel_core_secret_sharing();
let secret = b"32-byte key material for HTSS !!".to_vec();
let (shares, root) = sharing
.call_htss_split(&mut store, &secret)
.expect("host call")
.expect("split");
let repeated = vec![shares[0].clone(), shares[0].clone(), shares[1].clone()];
match sharing
.call_htss_reconstruct(&mut store, &repeated, &root)
.expect("host call")
{
Err(aethel::core::types::IdentityError::InvalidShareSet) => {}
Err(other) => panic!("expected invalid-share-set, got {:?}", other),
Ok(_) => panic!("a share set with a repeated index reconstructed something"),
}
}
#[test]
fn htss_refuses_shares_authenticated_against_a_different_sharings_root() {
let (mut store, bindings) = instantiate();
let sharing = bindings.aethel_core_secret_sharing();
let (victim_shares, victim_root) = sharing
.call_htss_split(&mut store, &b"32-byte key material for HTSS !!".to_vec())
.expect("host call")
.expect("split victim");
let attacker_payload: Vec<u8> = (0..32u8).map(|i| i ^ 0xAA).collect();
let (attacker_shares, _attacker_root) = sharing
.call_htss_split(&mut store, &attacker_payload)
.expect("host call")
.expect("split attacker");
match sharing
.call_htss_reconstruct(&mut store, &attacker_shares[..3], &victim_root)
.expect("host call")
{
Err(aethel::core::types::IdentityError::InvalidShareSet) => {}
Err(other) => panic!("expected invalid-share-set, got {:?}", other),
Ok(_) => panic!(
"shares from one sharing authenticated against a different sharing's root, \
through the component"
),
}
let mixed = vec![
victim_shares[0].clone(),
victim_shares[1].clone(),
attacker_shares[2].clone(),
];
match sharing
.call_htss_reconstruct(&mut store, &mixed, &victim_root)
.expect("host call")
{
Err(aethel::core::types::IdentityError::InvalidShareSet) => {}
Err(other) => panic!("expected invalid-share-set, got {:?}", other),
Ok(_) => panic!("a single substituted share from a different sharing was accepted"),
}
}
#[test]
fn htss_reconstruct_refuses_a_wrong_length_root() {
let (mut store, bindings) = instantiate();
let sharing = bindings.aethel_core_secret_sharing();
let (shares, mut root) = sharing
.call_htss_split(&mut store, &b"32-byte key material for HTSS !!".to_vec())
.expect("host call")
.expect("split");
root.push(0);
match sharing
.call_htss_reconstruct(&mut store, &shares[..3], &root)
.expect("host call")
{
Err(aethel::core::types::IdentityError::InvalidInputLength) => {}
Err(other) => panic!("expected invalid-input-length, got {:?}", other),
Ok(_) => panic!("a 33-byte root was accepted"),
}
}
#[test]
fn two_projections_at_one_tau_do_not_share_a_context_matrix() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let secret = [0x42u8; 32].to_vec();
let tau = b"block-height-1000".to_vec();
let first = identity
.call_plp_project_at_context(&mut store, &secret, &tau, &[0x11u8; 32])
.expect("host call")
.expect("project");
let second = identity
.call_plp_project_at_context(&mut store, &secret, &tau, &[0x22u8; 32])
.expect("host call")
.expect("project");
assert_eq!(first.tau, second.tau, "test setup: both projections are at one tau");
assert_ne!(
first.salt, second.salt,
"two projections at one tau shared a salt, so they share A and the averaging attack is back"
);
assert_ne!(
first.public_b, second.public_b,
"two projections at one tau produced the same sample"
);
}
#[test]
fn a_proof_is_bound_to_the_randomness_that_built_its_projection() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let secret = [0x42u8; 32].to_vec();
let tau = b"block-height-1000".to_vec();
let right = [0x11u8; 32];
let wrong = [0x22u8; 32];
let projection = identity
.call_plp_project_at_context(&mut store, &secret, &tau, &right)
.expect("host call")
.expect("project");
let good = identity
.call_plp_prove_identity(&mut store, &secret, &tau, &right)
.expect("host call")
.expect("prove");
assert!(
identity
.call_plp_verify(&mut store, &projection, &good)
.expect("host call")
.expect("verify"),
"a proof built with the projection's own randomness failed to verify"
);
let mismatched = identity
.call_plp_prove_identity(&mut store, &secret, &tau, &wrong)
.expect("host call")
.expect("prove");
assert!(
!identity
.call_plp_verify(&mut store, &projection, &mismatched)
.expect("host call")
.expect("verify"),
"a proof built under different randomness verified against this projection, so the randomness parameter is not actually binding A"
);
}
#[test]
fn attestation_interface_is_not_part_of_the_world() {
let source = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("wit/aethel-core.wit"),
)
.expect("read wit source");
assert!(
!source.contains("interface attestation"),
"the attestation interface reappeared in wit/aethel-core.wit — see \
src/component.rs's module doc for why it was removed"
);
assert!(
!source.contains("export attestation"),
"attestation is exported from the world again without the interface \
definition being restored consistently"
);
}
#[test]
fn an_identity_can_be_generated_inside_the_component() {
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
let id = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let pk = api.call_public_key(&mut store, id).expect("host call");
assert!(!pk.is_empty(), "an identity was generated with an empty public key");
}
#[test]
fn short_entropy_is_refused_by_the_component() {
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
match api.call_generate(&mut store, b"too short").expect("host call") {
Err(aethel::core::types::IdentityError::InvalidInputLength) => {}
Err(other) => panic!("expected invalid-input-length, got {other:?}"),
Ok(_) => panic!("9 bytes of entropy produced an identity"),
}
}
#[test]
fn sign_and_verify_round_trip_through_the_component() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let api = identity.master_identity();
let id = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let pk = api.call_public_key(&mut store, id).expect("host call");
let message = b"the message that was actually signed";
let sig = api
.call_sign(&mut store, id, message)
.expect("host call")
.expect("sign");
let ok = identity
.call_verify_signature(&mut store, &pk, message, &sig)
.expect("host call")
.expect("verify");
assert!(ok, "an honestly produced signature failed to verify");
}
#[test]
fn a_tampered_message_and_a_wrong_key_both_fail_verification() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let api = identity.master_identity();
let signer = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let other = api
.call_generate(&mut store, b"a completely different entropy!!")
.expect("host call")
.expect("generate");
let signer_pk = api.call_public_key(&mut store, signer).expect("host call");
let other_pk = api.call_public_key(&mut store, other).expect("host call");
assert_ne!(signer_pk, other_pk, "two entropies produced the same key");
let message = b"transfer 10 to alice";
let sig = api
.call_sign(&mut store, signer, message)
.expect("host call")
.expect("sign");
let tampered = identity
.call_verify_signature(&mut store, &signer_pk, b"transfer 99 to alice", &sig)
.expect("host call")
.expect("verify");
assert!(!tampered, "a signature verified against a message it was not made over");
let wrong_key = identity
.call_verify_signature(&mut store, &other_pk, message, &sig)
.expect("host call")
.expect("verify");
assert!(!wrong_key, "a signature verified under a key that did not produce it");
}
#[test]
fn generation_is_deterministic_and_entropy_dependent() {
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
let gen = |store: &mut Store<()>, entropy: &[u8]| {
let id = api
.call_generate(&mut *store, entropy)
.expect("host call")
.expect("generate");
api.call_public_key(&mut *store, id).expect("host call")
};
let a = gen(&mut store, b"deterministic entropy for tests!");
let b = gen(&mut store, b"deterministic entropy for tests!");
let c = gen(&mut store, b"a completely different entropy!!");
assert_eq!(a, b, "the same entropy produced two different identities");
assert_ne!(a, c, "different entropy produced the same identity");
}
#[test]
fn a_generated_identity_projects_and_proves() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let api = identity.master_identity();
let id = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let projection = api
.call_project_at_context(&mut store, id, b"context-one", &[0x5Au8; 32])
.expect("host call")
.expect("project");
let proof = api
.call_prove(&mut store, id, b"context-one", &[0x5Au8; 32])
.expect("host call")
.expect("prove");
let verified = identity
.call_plp_verify(&mut store, &projection, &proof)
.expect("host call")
.expect("verify");
assert!(verified, "a proof from a generated identity failed to verify");
let other = api
.call_project_at_context(&mut store, id, b"context-two", &[0x5Au8; 32])
.expect("host call")
.expect("project");
assert_ne!(
projection.public_b, other.public_b,
"two contexts produced the same projection"
);
}
#[test]
fn the_resource_enforces_the_randomness_floor() {
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
let id = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
match api
.call_project_at_context(&mut store, id, b"ctx", &[0u8; 31])
.expect("host call")
{
Err(aethel::core::types::IdentityError::InvalidInputLength) => {}
Err(other) => panic!("expected invalid-input-length, got {other:?}"),
Ok(_) => panic!("31 bytes of randomness was accepted"),
}
}
const ISSUER_SEED: &[u8] = b"issuer seed for the test suite!!";
const ISSUE_R: &[u8] = b"issuance randomness for tests!!!";
const BLIND_R: &[u8] = b"blinding randomness for tests!!!";
const PRES_R: &[u8] = b"presentation randomness tests!!!";
const PROJ_R: &[u8] = b"projection randomness for tests!";
const ATTRS: [u64; 8] = [31, 1990, 7, 42, 100, 5, 9, 12345];
fn disclose_first() -> exports::aethel::core::identity::DisclosureAttributes {
exports::aethel::core::identity::DisclosureAttributes::ATTRIBUTE0
}
#[test]
fn a_credential_can_be_issued_and_presented_through_the_component() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let ids = identity.master_identity();
let creds = identity.credential();
let holder = ids
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let cred = creds
.call_issue(&mut store, holder, ISSUER_SEED, &ATTRS, ISSUE_R)
.expect("host call")
.expect("issue");
let presentation = creds
.call_present(
&mut store,
cred,
holder,
b"context-alpha",
PROJ_R,
disclose_first(),
BLIND_R,
PRES_R,
)
.expect("host call")
.expect("present");
let projection = ids
.call_project_at_context(&mut store, holder, b"context-alpha", PROJ_R)
.expect("host call")
.expect("project");
let verified = identity
.call_saap_verify_presentation(
&mut store,
ISSUER_SEED,
&presentation,
&projection,
b"context-alpha",
)
.expect("host call")
.expect("verify");
assert!(
verified,
"an honestly issued and presented credential failed to verify through the component"
);
assert_eq!(
presentation.disclosed_values[0], ATTRS[0],
"the disclosed attribute did not survive the round trip"
);
}
#[test]
fn a_presentation_fails_against_another_identity_through_the_component() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let ids = identity.master_identity();
let creds = identity.credential();
let holder = ids
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let stranger = ids
.call_generate(&mut store, b"a completely different entropy!!")
.expect("host call")
.expect("generate");
let cred = creds
.call_issue(&mut store, holder, ISSUER_SEED, &ATTRS, ISSUE_R)
.expect("host call")
.expect("issue");
let presentation = creds
.call_present(
&mut store,
cred,
holder,
b"context-alpha",
PROJ_R,
disclose_first(),
BLIND_R,
PRES_R,
)
.expect("host call")
.expect("present");
let stranger_projection = ids
.call_project_at_context(&mut store, stranger, b"context-alpha", PROJ_R)
.expect("host call")
.expect("project");
let verified = identity
.call_saap_verify_presentation(
&mut store,
ISSUER_SEED,
&presentation,
&stranger_projection,
b"context-alpha",
)
.expect("host call")
.expect("verify");
assert!(
!verified,
"a presentation verified against a different identity's projection"
);
}
#[test]
fn rewriting_a_disclosed_attribute_is_caught_through_the_component() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let ids = identity.master_identity();
let creds = identity.credential();
let holder = ids
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let cred = creds
.call_issue(&mut store, holder, ISSUER_SEED, &ATTRS, ISSUE_R)
.expect("host call")
.expect("issue");
let mut presentation = creds
.call_present(
&mut store,
cred,
holder,
b"context-alpha",
PROJ_R,
disclose_first(),
BLIND_R,
PRES_R,
)
.expect("host call")
.expect("present");
let projection = ids
.call_project_at_context(&mut store, holder, b"context-alpha", PROJ_R)
.expect("host call")
.expect("project");
presentation.disclosed_values[0] += 1;
let verified = identity
.call_saap_verify_presentation(
&mut store,
ISSUER_SEED,
&presentation,
&projection,
b"context-alpha",
)
.expect("host call")
.expect("verify");
assert!(!verified, "a rewritten disclosed attribute still verified");
}
#[test]
fn a_presentation_cannot_certify_its_own_context() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let ids = identity.master_identity();
let creds = identity.credential();
let holder = ids
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let cred = creds
.call_issue(&mut store, holder, ISSUER_SEED, &ATTRS, ISSUE_R)
.expect("host call")
.expect("issue");
let presentation = creds
.call_present(
&mut store,
cred,
holder,
b"context-alpha",
PROJ_R,
disclose_first(),
BLIND_R,
PRES_R,
)
.expect("host call")
.expect("present");
let projection = ids
.call_project_at_context(&mut store, holder, b"context-alpha", PROJ_R)
.expect("host call")
.expect("project");
let verified = identity
.call_saap_verify_presentation(
&mut store,
ISSUER_SEED,
&presentation,
&projection,
b"context-beta",
)
.expect("host call")
.expect("verify");
assert!(
!verified,
"a presentation made for context-alpha verified under context-beta"
);
}
#[test]
fn a_presentation_fails_under_a_different_issuer() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let ids = identity.master_identity();
let creds = identity.credential();
let holder = ids
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let cred = creds
.call_issue(&mut store, holder, ISSUER_SEED, &ATTRS, ISSUE_R)
.expect("host call")
.expect("issue");
let presentation = creds
.call_present(
&mut store,
cred,
holder,
b"context-alpha",
PROJ_R,
disclose_first(),
BLIND_R,
PRES_R,
)
.expect("host call")
.expect("present");
let projection = ids
.call_project_at_context(&mut store, holder, b"context-alpha", PROJ_R)
.expect("host call")
.expect("project");
let verified = identity
.call_saap_verify_presentation(
&mut store,
b"a different issuer seed entirely",
&presentation,
&projection,
b"context-alpha",
)
.expect("host call")
.expect("verify");
assert!(
!verified,
"a presentation verified under an issuer that never issued it"
);
}
#[test]
fn two_presentations_of_one_credential_are_not_linkable() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let ids = identity.master_identity();
let creds = identity.credential();
let holder = ids
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let cred = creds
.call_issue(&mut store, holder, ISSUER_SEED, &ATTRS, ISSUE_R)
.expect("host call")
.expect("issue");
let first = creds
.call_present(
&mut store,
cred,
holder,
b"context-one",
PROJ_R,
disclose_first(),
b"blinding for presentation one!!!",
PRES_R,
)
.expect("host call")
.expect("present");
let second = creds
.call_present(
&mut store,
cred,
holder,
b"context-two",
PROJ_R,
disclose_first(),
b"blinding for presentation two!!!",
PRES_R,
)
.expect("host call")
.expect("present");
assert_ne!(
first.t_blind, second.t_blind,
"two presentations of one credential reused the same blinded commitment"
);
assert_ne!(
first.challenge, second.challenge,
"two presentations reused the same challenge"
);
for (p, tau) in [(&first, &b"context-one"[..]), (&second, &b"context-two"[..])] {
let projection = ids
.call_project_at_context(&mut store, holder, tau, PROJ_R)
.expect("host call")
.expect("project");
assert!(
identity
.call_saap_verify_presentation(&mut store, ISSUER_SEED, p, &projection, tau)
.expect("host call")
.expect("verify"),
"an unlinkable presentation stopped verifying"
);
}
}
#[test]
fn hidden_attributes_are_not_published_by_the_component() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let ids = identity.master_identity();
let creds = identity.credential();
let holder = ids
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let cred = creds
.call_issue(&mut store, holder, ISSUER_SEED, &ATTRS, ISSUE_R)
.expect("host call")
.expect("issue");
let presentation = creds
.call_present(
&mut store,
cred,
holder,
b"context-alpha",
PROJ_R,
disclose_first(),
BLIND_R,
PRES_R,
)
.expect("host call")
.expect("present");
assert_eq!(presentation.disclosed_values[0], ATTRS[0], "slot 0 was disclosed");
for slot in 1..8 {
assert_eq!(
presentation.disclosed_values[slot], 0,
"undisclosed slot {slot} was published"
);
assert_ne!(
presentation.disclosed_values[slot], ATTRS[slot],
"undisclosed slot {slot} leaked its value"
);
}
}
const SEAL_KEY: &[u8] = b"a sealing key of thirty-two byte";
#[test]
fn a_sealed_identity_round_trips_through_the_component() {
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
let original = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let original_pk = api.call_public_key(&mut store, original).expect("host call");
let sealed = api
.call_export_sealed(&mut store, original, SEAL_KEY)
.expect("host call")
.expect("seal");
let reopened = api
.call_import_sealed(&mut store, &sealed, SEAL_KEY)
.expect("host call")
.expect("open");
let reopened_pk = api.call_public_key(&mut store, reopened).expect("host call");
assert_eq!(original_pk, reopened_pk, "the reopened identity is a different one");
let message = b"signed after being reopened";
let signature = api
.call_sign(&mut store, reopened, message)
.expect("host call")
.expect("sign");
assert!(
bindings
.aethel_core_identity()
.call_verify_signature(&mut store, &original_pk, message, &signature)
.expect("host call")
.expect("verify"),
"a signature from the reopened identity did not verify under the original key"
);
}
#[test]
fn two_sealed_identities_stay_distinct_through_the_component() {
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
let first = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let second = api
.call_generate(&mut store, b"a completely different entropy!!")
.expect("host call")
.expect("generate");
let first_sealed = api
.call_export_sealed(&mut store, first, SEAL_KEY)
.expect("host call")
.expect("seal");
let second_sealed = api
.call_export_sealed(&mut store, second, SEAL_KEY)
.expect("host call")
.expect("seal");
assert_ne!(first_sealed, second_sealed, "two identities sealed to the same bytes");
let a = api
.call_import_sealed(&mut store, &first_sealed, SEAL_KEY)
.expect("host call")
.expect("open");
let b = api
.call_import_sealed(&mut store, &second_sealed, SEAL_KEY)
.expect("host call")
.expect("open");
let a_pk = api.call_public_key(&mut store, a).expect("host call");
let b_pk = api.call_public_key(&mut store, b).expect("host call");
assert_ne!(a_pk, b_pk, "two sealed identities reopened as the same identity");
assert_eq!(
a_pk,
api.call_public_key(&mut store, first).expect("host call"),
"the wrong identity came back"
);
}
#[test]
fn a_sealed_identity_resists_the_wrong_key_and_tampering() {
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
let identity = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let sealed = api
.call_export_sealed(&mut store, identity, SEAL_KEY)
.expect("host call")
.expect("seal");
assert!(
api.call_import_sealed(&mut store, &sealed, b"a different sealing key, 32 byte")
.expect("host call")
.is_err(),
"a sealed identity opened under the wrong key"
);
for index in [0usize, 5, sealed.len() - 1] {
let mut tampered = sealed.clone();
tampered[index] ^= 0x01;
assert!(
api.call_import_sealed(&mut store, &tampered, SEAL_KEY)
.expect("host call")
.is_err(),
"a blob with byte {index} flipped still opened"
);
}
}
#[test]
fn the_sealed_blob_does_not_carry_the_identity_in_the_clear() {
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
let entropy = b"deterministic entropy for tests!";
let identity = api
.call_generate(&mut store, entropy)
.expect("host call")
.expect("generate");
let sealed = api
.call_export_sealed(&mut store, identity, SEAL_KEY)
.expect("host call")
.expect("seal");
assert!(
!sealed.windows(entropy.len()).any(|w| w == entropy.as_slice()),
"the generation entropy appears verbatim in the sealed blob"
);
let pk = api.call_public_key(&mut store, identity).expect("host call");
assert!(
!sealed.windows(32).any(|w| w == &pk[..32]),
"the public key appears in the sealed blob, making it identifiable at rest"
);
}
#[test]
fn a_reopened_identity_projects_identically() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let api = identity.master_identity();
let original = api
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let sealed = api
.call_export_sealed(&mut store, original, SEAL_KEY)
.expect("host call")
.expect("seal");
let reopened = api
.call_import_sealed(&mut store, &sealed, SEAL_KEY)
.expect("host call")
.expect("open");
let a = api
.call_project_at_context(&mut store, original, b"ctx", &[0x5Au8; 32])
.expect("host call")
.expect("project");
let b = api
.call_project_at_context(&mut store, reopened, b"ctx", &[0x5Au8; 32])
.expect("host call")
.expect("project");
assert_eq!(
a.public_b, b.public_b,
"the reopened identity projects to a different value"
);
}