#![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"
);
let native_public_b: Vec<u32> = native
.public_b
.iter()
.flat_map(|p| p.coeffs().to_vec())
.collect();
assert_eq!(
via_component.public_b.len(),
aethel_core::plp::MODULE_K * aethel_core::plp::N,
"the component returned a public_b of the wrong rank"
);
assert_eq!(
via_component.public_b, native_public_b,
"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 purpose_signing_through_the_component_matches_the_native_api() {
let entropy = b"deterministic entropy for tests!";
let purpose = aethel_core::signing::purpose::VAULT_SPEND_INTENT_V1;
let message = b"spend 10 from the agent wallet";
let native = aethel_core::signing::Identity::generate(entropy).expect("native generate");
let native_sig = native
.sign_with_purpose(purpose, message)
.expect("native sign_with_purpose");
let (mut store, bindings) = instantiate();
let api = bindings.aethel_core_identity().master_identity();
let id = api
.call_generate(&mut store, entropy)
.expect("host call")
.expect("generate");
let component_sig = api
.call_sign_with_purpose(&mut store, id, purpose, message)
.expect("host call")
.expect("sign-with-purpose");
assert_eq!(
component_sig, native_sig,
"the component's purpose signature differs from the native one for the same inputs"
);
assert!(
aethel_core::signing::verify_with_purpose(&native.public_key(), purpose, message, &component_sig)
.expect("native verify_with_purpose"),
"the native verifier rejected the component's purpose signature"
);
}
#[test]
fn purpose_separation_holds_through_the_component() {
let a = aethel_core::signing::purpose::VAULT_SPEND_INTENT_V1;
let b = aethel_core::signing::purpose::VAULT_SETTLEMENT_RECEIPT_V1;
let message = b"spend 10 from the agent wallet";
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 under_a = api
.call_sign_with_purpose(&mut store, id, a, message)
.expect("host call")
.expect("sign-with-purpose");
let plain = api
.call_sign(&mut store, id, message)
.expect("host call")
.expect("sign");
let mut verify_with = |purpose: &[u8], msg: &[u8], sig: &[u8]| {
identity
.call_verify_signature_with_purpose(&mut store, &pk, purpose, msg, sig)
.expect("host call")
.expect("verify-signature-with-purpose")
};
assert!(verify_with(a, message, &under_a), "a purpose-A signature failed to verify under A");
assert!(!verify_with(b, message, &under_a), "a purpose-A signature verified under purpose B");
assert!(
!verify_with(a, b"spend 99 from the agent wallet", &under_a),
"a purpose-A signature verified over a message it was not made over"
);
assert!(!verify_with(a, message, &plain), "a plain signature verified under purpose A");
let plain_verify = |store: &mut Store<()>, sig: &[u8]| {
identity
.call_verify_signature(store, &pk, message, sig)
.expect("host call")
.expect("verify-signature")
};
assert!(plain_verify(&mut store, &plain), "a plain signature failed plain verification");
assert!(
!plain_verify(&mut store, &under_a),
"a purpose-A signature verified under the empty context"
);
}
fn registries_agree(component: &[(String, Vec<u8>)], native: &[(&str, &[u8])]) -> bool {
component.len() == native.len()
&& component
.iter()
.zip(native)
.all(|((cn, cb), (nn, nb))| cn == nn && cb.as_slice() == *nb)
}
fn registered(
store: &mut Store<()>,
identity: &exports::aethel::core::identity::Guest,
) -> Vec<(String, Vec<u8>)> {
identity
.call_registered_purposes(store)
.expect("host call")
.into_iter()
.map(|p| (p.name, p.bytes))
.collect()
}
#[test]
fn the_component_exports_exactly_the_native_registry() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let from_component = registered(&mut store, &identity);
let native = aethel_core::signing::purpose::ALL;
assert!(!from_component.is_empty(), "the component exported no purposes");
assert!(registries_agree(&from_component, native), "component registry differs from native");
let mut missing = from_component.clone();
missing.pop();
assert!(!registries_agree(&missing, native), "a missing purpose went unnoticed");
let mut extra = from_component.clone();
extra.push(("PLANTED_V1".into(), b"planted/v1".to_vec()));
assert!(!registries_agree(&extra, native), "an extra purpose went unnoticed");
let mut renamed = from_component.clone();
renamed[0].0.push('X');
assert!(!registries_agree(&renamed, native), "a rename went unnoticed");
let mut changed = from_component.clone();
changed[0].1[0] ^= 1;
assert!(!registries_agree(&changed, native), "changed bytes went unnoticed");
}
#[test]
fn every_exported_purpose_is_well_formed() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let list = registered(&mut store, &identity);
for (name, bytes) in &list {
assert!(!name.is_empty(), "a purpose has no name");
assert!(!bytes.is_empty(), "{name} is empty");
assert!(bytes.len() <= 255, "{name} is over the 255-byte limit");
}
for (i, (name_a, bytes_a)) in list.iter().enumerate() {
for (name_b, bytes_b) in &list[i + 1..] {
assert_ne!(name_a, name_b, "two purposes share a name");
assert_ne!(bytes_a, bytes_b, "{name_a} and {name_b} share bytes");
}
}
}
#[test]
fn every_exported_purpose_signs_and_verifies_only_under_itself() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let api = identity.master_identity();
let list = registered(&mut store, &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"one statement, many purposes";
for (name, bytes) in &list {
let signature = api
.call_sign_with_purpose(&mut store, id, bytes, message)
.expect("host call")
.unwrap_or_else(|_| panic!("sign-with-purpose refused {name}"));
for (other_name, other_bytes) in &list {
let verified = identity
.call_verify_signature_with_purpose(&mut store, &pk, other_bytes, message, &signature)
.expect("host call")
.expect("verify-signature-with-purpose");
assert_eq!(
verified,
name == other_name,
"a signature made under {name} {} under {other_name}",
if verified { "verified" } else { "failed to verify" }
);
}
let plain = identity
.call_verify_signature(&mut store, &pk, message, &signature)
.expect("host call")
.expect("verify-signature");
assert!(!plain, "a signature made under {name} verified under the empty context");
}
}
#[test]
fn a_purpose_over_255_bytes_is_refused_by_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"a message";
let longest = vec![b'p'; 255];
let too_long = vec![b'p'; 256];
let sig = api
.call_sign_with_purpose(&mut store, id, &longest, message)
.expect("host call")
.expect("a 255-byte purpose must be accepted");
assert!(
identity
.call_verify_signature_with_purpose(&mut store, &pk, &longest, message, &sig)
.expect("host call")
.expect("verify"),
"a signature under a 255-byte purpose failed to verify"
);
match api
.call_sign_with_purpose(&mut store, id, &too_long, message)
.expect("host call")
{
Err(aethel::core::types::IdentityError::InvalidInputLength) => {}
other => panic!("sign-with-purpose with a 256-byte purpose returned {other:?}"),
}
match identity
.call_verify_signature_with_purpose(&mut store, &pk, &too_long, message, &sig)
.expect("host call")
{
Err(aethel::core::types::IdentityError::InvalidInputLength) => {}
other => panic!("verify-signature-with-purpose with a 256-byte purpose returned {other:?}"),
}
}
#[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 issuer = identity
.issuer_public_parameters()
.call_derive(&mut store, ISSUER_SEED)
.expect("host call")
.expect("derive issuer public parameters");
let verified = identity
.call_saap_verify_presentation(
&mut store,
issuer,
&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 issuer = identity
.issuer_public_parameters()
.call_derive(&mut store, ISSUER_SEED)
.expect("host call")
.expect("derive issuer public parameters");
let verified = identity
.call_saap_verify_presentation(
&mut store,
issuer,
&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 issuer = identity
.issuer_public_parameters()
.call_derive(&mut store, ISSUER_SEED)
.expect("host call")
.expect("derive issuer public parameters");
let verified = identity
.call_saap_verify_presentation(
&mut store,
issuer,
&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 issuer = identity
.issuer_public_parameters()
.call_derive(&mut store, ISSUER_SEED)
.expect("host call")
.expect("derive issuer public parameters");
let verified = identity
.call_saap_verify_presentation(
&mut store,
issuer,
&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 issuer = identity
.issuer_public_parameters()
.call_derive(&mut store, b"a different issuer seed entirely")
.expect("host call")
.expect("derive issuer public parameters");
let verified = identity
.call_saap_verify_presentation(
&mut store,
issuer,
&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"
);
let issuer = identity
.issuer_public_parameters()
.call_derive(&mut store, ISSUER_SEED)
.expect("host call")
.expect("derive issuer public parameters");
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, 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"
);
}
#[test]
fn issuer_public_parameters_round_trip_through_serialisation() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let ids = identity.master_identity();
let creds = identity.credential();
let params = identity.issuer_public_parameters();
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 issuer = params
.call_derive(&mut store, ISSUER_SEED)
.expect("host call")
.expect("derive");
let published = params
.call_serialize(&mut store, issuer)
.expect("host call");
assert_ne!(
published.as_slice(),
ISSUER_SEED,
"published issuer parameters were the issuer seed itself"
);
let reloaded = params
.call_deserialize(&mut store, &published)
.expect("host call")
.expect("deserialize");
let round_tripped = params
.call_serialize(&mut store, reloaded)
.expect("host call");
assert_eq!(published, round_tripped, "serialisation did not round trip");
let verified = identity
.call_saap_verify_presentation(
&mut store,
reloaded,
&presentation,
&projection,
b"context-alpha",
)
.expect("host call")
.expect("verify");
assert!(
verified,
"a presentation did not verify against reloaded public parameters"
);
}
#[test]
fn deserialising_wrong_length_parameters_is_refused() {
let (mut store, bindings) = instantiate();
let params = bindings.aethel_core_identity().issuer_public_parameters();
for bad in [&b""[..], &b"too short"[..], &[7u8; 33][..]] {
let result = params.call_deserialize(&mut store, bad).expect("host call");
assert!(
matches!(
result,
Err(aethel::core::types::IdentityError::InvalidInputLength)
),
"a {}-byte parameter blob was accepted",
bad.len()
);
}
}
#[test]
fn public_parameters_are_derived_not_copied() {
let (mut store, bindings) = instantiate();
let params = bindings.aethel_core_identity().issuer_public_parameters();
let one = params
.call_derive(&mut store, ISSUER_SEED)
.expect("host call")
.expect("derive");
let two = params
.call_derive(&mut store, b"a different issuer seed entirely")
.expect("host call")
.expect("derive");
let a = params.call_serialize(&mut store, one).expect("host call");
let b = params.call_serialize(&mut store, two).expect("host call");
assert_ne!(a, b, "two issuers published identical parameters");
assert_eq!(a.len(), 32, "published parameters were not 32 bytes");
let again = params
.call_derive(&mut store, ISSUER_SEED)
.expect("host call")
.expect("derive");
let a_again = params.call_serialize(&mut store, again).expect("host call");
assert_eq!(a, a_again, "deriving twice from one seed disagreed");
}
#[test]
fn a_short_issuer_seed_is_refused() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let params = identity.issuer_public_parameters();
let result = params
.call_derive(&mut store, b"too short")
.expect("host call");
assert!(
matches!(
result,
Err(aethel::core::types::IdentityError::InvalidInputLength)
),
"a 9-byte issuer seed was accepted"
);
let holder = identity
.master_identity()
.call_generate(&mut store, b"deterministic entropy for tests!")
.expect("host call")
.expect("generate");
let issued = identity
.credential()
.call_issue(&mut store, holder, b"too short", &ATTRS, ISSUE_R)
.expect("host call");
assert!(
matches!(
issued,
Err(aethel::core::types::IdentityError::InvalidInputLength)
),
"a credential was issued under a 9-byte issuer seed"
);
}
struct PlpVector {
name: String,
projection: Vec<u8>,
proof: Vec<u8>,
context: Vec<u8>,
expected: bool,
}
fn load_plp_vectors() -> Vec<PlpVector> {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/aethel-plp-1");
let mut paths: Vec<_> = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("read {}: {e}", dir.display()))
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.extension().map(|e| e == "txt").unwrap_or(false))
.collect();
paths.sort();
paths
.iter()
.map(|path| {
let text = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
let mut projection = None;
let mut proof = None;
let mut context = None;
let mut expected = None;
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let (key, value) = line.split_once('=').expect("key=value line");
match key {
"projection" => projection = Some(hex::decode(value).expect("hex")),
"proof" => proof = Some(hex::decode(value).expect("hex")),
"context" => context = Some(hex::decode(value).expect("hex")),
"expected" => expected = Some(value == "true"),
other => panic!("unknown vector field: {other}"),
}
}
PlpVector {
name: path.file_name().unwrap().to_string_lossy().into_owned(),
projection: projection.expect("projection"),
proof: proof.expect("proof"),
context: context.expect("context"),
expected: expected.expect("expected"),
}
})
.collect()
}
#[test]
fn component_plp_verify_bytes_matches_the_vectors() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let vectors = load_plp_vectors();
assert!(
!vectors.is_empty(),
"no vector files found — see tests/plp_vectors.rs"
);
for v in &vectors {
let verdict = identity
.call_plp_verify_bytes(&mut store, &v.projection, &v.proof, &v.context)
.expect("host call");
match verdict {
Ok(actual) => assert_eq!(
actual, v.expected,
"vector {}: component plp-verify-bytes returned {} but expected {}",
v.name, actual, v.expected
),
Err(e) => panic!(
"vector {} failed to decode through the component: {e:?}",
v.name
),
}
}
}
#[test]
fn component_encode_projection_and_proof_round_trip_through_plp_verify_bytes() {
let (mut store, bindings) = instantiate();
let identity = bindings.aethel_core_identity();
let secret = [0x2cu8; 32];
let tau = b"component-encode-round-trip".to_vec();
let randomness = [0x3du8; 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 projection_bytes = identity
.call_encode_projection(&mut store, &projection)
.expect("host call");
let proof_bytes = identity
.call_encode_proof(&mut store, &proof)
.expect("host call");
assert!(
!projection_bytes.is_empty(),
"encode-projection produced an empty envelope"
);
assert!(
!proof_bytes.is_empty(),
"encode-proof produced an empty envelope"
);
let verdict = identity
.call_plp_verify_bytes(&mut store, &projection_bytes, &proof_bytes, &tau)
.expect("host call")
.expect("plp-verify-bytes returned err");
assert!(
verdict,
"a component-encoded projection/proof pair did not verify through plp-verify-bytes"
);
}