Genesis Self-Check Callback

To enforce access control for a network, a DNA can require a membrane proof, which is a piece of data that gets entered by the user and written to their source chain. The genesis_self_check function can guard against user entry error and help prevent them from being accidentally marked as a bad actor.

Membrane proof: a joining code for a network

While a network seed acts like a network-wide passcode, you might need to enforce more fine-grained membership control. You can do this by writing validation code for the contents of theAgentValidationPkg record. This genesis record contains data that the user enters on app installation.

A membrane proof can be basic, like an invite code, or it can be something complex like a signature from an agent that has authority to admit members.

Membership control isn't implemented yet

This feature is not fully implemented. Currently, validators merely record a validation failure and supply it on request. Our plan is to connect membrane proof validation outcomes to block lists, so agents can reject connection attempts from a peer with an invalid membrane proof.

The need for basic pre-validation

Most useful membrane proofs will require access to network data in order to fully validate them. But an agent can’t self-validate their own membership proof at genesis time, because they haven’t joined the network yet. This creates a minor problem; they may accidentally type or paste their membrane proof wrong, but won’t find out until they try to join the network and get blocked by their peers.

To reduce the risk, you can define a genesis_self_check function that checks the membrane proof before network communications start. This function is limited — it naturally doesn’t have access to DHT data – but it can be a useful guard against basic data entry errors.

Define a genesis_self_check callback

genesis_self_check must take a single argument of type GenesisSelfCheckData and return a value of type ValidateCallbackResult wrapped in an ExternResult.

Here’s an example that checks that the membrane proof exists and is the right length:

use hdi::prelude::*;

#[hdk_extern]
pub fn genesis_self_check(data: GenesisSelfCheckData) -> ExternResult<ValidateCallbackResult> {
    if let Some(membrane_proof) = data.membrane_proof {
        if membrane_proof.bytes().len() == 64 {
            return Ok(ValidateCallbackResult::Valid);
        }
        return Ok(ValidateCallbackResult::Invalid("Membrane proof is not the right length. Please check it and enter it again.".into()));
    }
    Ok(ValidateCallbackResult::Invalid("This network needs a membrane proof to join.".into()))
}

This more complex example deserializes an Ed25519 signature and checks that it matches the agent’s public key and the public key of an agent with the authority to admit members, which is taken from the DNA’s properties block.

use hdi::prelude::*;
use base64::prelude::*;

// A type for deserializing the DNA properties that this integrity zome needs.
// This struct lets a network creator specify who gets to create joining
// certificates.
#[dna_properties]
pub struct DnaProperties {
    // Because the DNA properties are given as YAML, this field should be a
    // string that someone can paste in.
    authorized_joining_certificate_issuer: AgentPubKeyB64,
}

#[hdk_extern]
pub fn genesis_self_check(data: GenesisSelfCheckData) -> ExternResult<ValidateCallbackResult> {
    let Some(membrane_proof) = data.membrane_proof else {
        return Ok(ValidateCallbackResult::Invalid("This network needs a membrane proof to join.".into()));
    }

    // Accept a string, because this is something a user can paste into a
    // form field.
    let Ok(cert: Signature) = std::str::from_utf8(membrane_proof.bytes())
        // Expect it to be Base64-encoded; convert it into raw bytes.
        .and_then(|s| BASE64_STANDARD::decode(s))
        // And then into a Signature.
        .and_then(|b| b.try_into())
    else {
        return Ok(ValidateCallbackResult::Invalid("Couldn't decode membrane proof into joining certificate."));
    }

    // Check the certificate against the signing authority.
    let dna_props = DnaProperties::try_from_dna_properties()?;
    let cert_is_valid = verify_signature(
        dna_props.authorized_joining_certificate_issuer,
        cert,
        data.agent_key
    )?;
    if cert_is_valid {
        return Ok(ValidateCallbackResult::Valid);
    } else {
        return Ok(ValidateCallbackResult::Invalid("Joining certificate wasn't valid. Please try entering it again or asking the certificate issuer for a new one."));
    }
}

Fully validating a membrane proof

Your validate callback also needs to validate a membrane proof in order to actually enforce network access. At the very least, it should apply the same rules as genesis_self_check does, and add on any checks that require DHT data. We’ll explore this in full on the validate callback page.

Reference

Further reading

It looks like you are using Internet Explorer. While the basic content is available, this is no longer a supported browser by the manufacturer, and no attention is being given to having IE work well here.