Capability Tokens & P2P Functionality

### Zome Calls - Zome calls have a unified security model - Protects all zome calls independently of where they originated (externally or within the conductor) - When the conductor receives a zome call request, it checks whether it complies with the security checks: - If the call is signed by the agent with the public key for the cell, the call is accepted and executed - If not, the conductor looks up the source chain for that cell for **capability tokens** for the requested function - If some capability grant allows the request, it is executed - Otherwise, the request is rejected
### Capability Tokens (I) - Private entries that manage authorization to execute zome calls by sources external to the cell - Two types of private entries: - Capability Grants - Stored by the grantor of the zome call request - Allow an external source to execute a zome call request - When they are deleted, that grant is no longer taken into account in the security checks - This means that the cell is always in control of the grants that it makes to external sources - Capability Claim - Stored by the grantee of the capability - They are not checked by the conductor to check whether an external zome call requests is valid - Mostly a convenience entry type just to store information about the Capability Grant - Both can be created as any other entry, at any point in time
### Capability Tokens (II) #### Types of Capability Grant - Unrestricted: disable security checks - Any call to the function coming from any source will be executed - This can be a huge security risk, so we generally don't grant unrestricted capability grants unless there is a really good reason - Transferable: any source that knows a specific secret - The zome call must carry a secret - Only the calls that carry the secret specified in the capability token will be executed - The secret can be stored in the source chain of the caller with a "Capability Claim" entry - Assigned to specific agents - The zome call must carry the agent public key of the caller and its signature and also a capability secret - Only the calls that a) carry the secret specified and b) are made by one of the agents specified in the capability token are allowed to make the call

Capability Tokens (III)

Declaring the functions to grant capabilities for


use hdk::prelude::*;

#[hdk_extern]
fn zome_function_a(input: String) -> ExternResult<ActionHash> { // We want to grant 
                                                                // a capability to 
                                                                // execute this function
  // Create, create links, get, query... 
}
 
// Declares which functions we want to grant access to
fn functions_to_grant_capability_for() -> ExternResult<GrantedFunctions> { // Type required 
                                                                           // to specify 
                                                                           // the functions
  let zome_name = zome_info()?.name; // Getting the zome name
  let fn_name = FunctionName::from("zome_function_a"); // Just a wrapper around a "String"
                                                          // (String::from("zome_function_a"))

  let mut functions: BTreeSet<(ZomeName, FunctionName)> = BTreeSet::new();
  functions.insert((zome_name, fn_name)); // Grant access to the zome function 
                                          // "zome_function_a" of this zome

  Ok(GrantedFunctions::Listed(functions)) // Can be "Listed" functions or "All" functions
}

Capability Tokens (IV)

Create unrestricted grant


use hdk::prelude::*;

#[hdk_extern]
fn grant_unrestricted_capability(_: ()) -> ExternResult<()> {
  let functions = functions_to_grant_capability_for()?; // Declared in the previous slide

  let access = CapAccess::Unrestricted; // Anyone can call this function, 
                                        // even unidentified sources

  let capability_grant = CapGrantEntry { // Private entry built-in to Holochain and the HDK
    functions,
    access,
    tag: String::from("unrestricted"), // Arbitrary humand readable tag, just for convenience
  };

  create_cap_grant(capability_grant)?; // Actually commit the capability
                                       // From this point on, "zome_function_a" 
                                       // can be called from any source
  Ok(())
}

Capability Tokens (V)

Create transferable grant


// Generate a random capability secret to give to a the granted agent
fn cap_secret() -> ExternResult<CapSecret> { // "CapSecret" is a wrapper around a byte array
  let bytes = random_bytes(64)?; // "random_bytes" is a utility function from the HDK
  let secret = CapSecret::try_from(bytes.into_vec())
      .map_err(|_| wasm_error!(WasmErrorInner::Guest("Could not build secret".into())))?;

  Ok(secret)
}

#[hdk_extern]
fn grant_transferable_capability(_: ()) -> ExternResult<CapSecret> {
  let secret = cap_secret()?;

  let access = CapAccess::Transferable { 
      secret, // Only requests that carry this secret will be able to execute "zome_function_a"
  };
  let capability_grant = CapGrantEntry {
    functions: functions_to_grant_capability_for()?,
    access,
    tag: String::from("transferable capability"), // Convenience tag
  };

  create_cap_grant(capability_grant)?;

  Ok(secret)
}

Capability Tokens (VI)

Create assigned grant


use hdk::prelude::*;

#[hdk_extern]
fn grant_assigned_capability(authorized_agent: AgentPubKey) -> ExternResult<()> {

  /* To whom do we grant access to those functions? */

  let mut assignees: BTreeSet<AgentPubKey> = BTreeSet::new(); // Type required by the API 
                                                              // to specify the granted agents
  assignees.insert(authorized_agent); // Assign capabilty to the given "authorized_agent"

  let access = CapAccess::Assigned {     
    secret: cap_secret()?,
    assignees              // Only requests that **both** carry this secret and are signed by 
                           // any of the assignees will be able to execute "zome_function_a"
  };
  let capability_grant = CapGrantEntry {
    functions: functions_to_grant_capability_for()?,
    access,
    tag: String::from("assigned capability"), // Convenience tag
  };

  create_cap_grant(capability_grant)?;

  Ok(())
}

Capability Tokens (VII)

Create capability claim


use hdk::prelude::*;

#[hdk_extern]
fn create_claim_for_zome_function_a(
  grant_data: (AgentPubKey, CapSecret) 
) -> ExternResult<()> { // The "CapSecret" would be provided by another process, 
                        // maybe a bluetooth handshake

  let cap_claim = CapClaimEntry { // Private entry built-in to Holochain and the HDK
    grantor: grant_data.0, // Just to remember which agent to call
    secret: grant_data.1, // Store the secret to be able to add it to the request
    tag: String::from("claim for zome_function_a"), // Can be totally different from the
                                                    // tag in the capability grant
  };

  create_cap_claim(cap_claim)?; // Create the claim privately, nothing else happens

  Ok(())
}

Capability Tokens (VIII)

Query the created capability claim

              
use hdk::prelude::*;

// Imagine we have already stored some CapClaims
// and we want to retrieve all stored CapClaims from a certain grantor
fn query_cap_claims_for(grantor: &AgentPubKey) -> ExternResult<Vec<CapClaim>> {
  let claims_records = query(
    ChainQueryFilter::new()
      .entry_type(EntryType::CapClaim) // Only query Capability Claim related records
      .include_entries(true),          // Include the Capability Claim entries in the records
  )?;

  let claims_from_grantor: Vec<CapClaim> = claims_records
    .into_iter()
    .filter_map(|record| record.entry().as_option().cloned()) // Extract the entry 
                                                              // from the record
    .filter_map(|entry| match entry {
      Entry::CapClaim(claim) => Some(claim.clone()), // Deserialize the entry to a "CapClaim"
      _ => None,
    })
    .filter(|claim| claim.grantor.eq(&grantor)) // Only select the claims with 
                                                // the given grantor
    .collect();

  Ok(claims_from_grantor)
}
### 3 Sources of Zome Calls - Conductor receives request on the application port - Any process external to Holochain can bind to the application port of the conductor using a websocket connection - Usually a web GUI - Bridge call - Any cell in a conductor can call another cell in the same conductor, by cell ID - Remote call - Any cell in a network can call another cell in the same network, by agent public key

Bridged call

              
#[hdk_extern]
pub fn store_file(file_bytes: SerializedBytes) -> ExternResult<EntryHash> {
  let cell_role_name = String::from("file_storage_provider");
  let zome_call_response = call(
    CallTargetCell::OtherRole(cell_role_name), // "CallTargetCell" can be: 
                                               // - "OtherRole": must be one of the roles 
                                               // specified in the happ manifest
                                               // - "OtherCell": call an arbitrary 
                                               // cell with its cell ID
                                               // - "Local": calls the caller cell
    ZomeName::from(String::from("file_storage")), // Name of t; he zome to call
    FunctionName(String::from("store_file")), // Name of the zome function to call
    None, // Capability secret, if necessary
    file_bytes, // Input for the zome function
  )?;

  let agent_pubkey = agent_info()?.agent_latest_pubkey;

  match zome_call_response {
    ZomeCallResponse::Ok(result) => { // Of type ExternIO, wrapper around byte array
      let entry_hash: EntryHash = result.decode()  // ".decode()" deserializes the byte array, 
                                                   // trying to interpret it as the requested typing
        .map_err(|err| wasm_error!(String::from(err)))?;
      Ok(entry_hash)
    },
    ZomeCallResponse::Unauthorized( // Callee deleted the capability grant before we called it
      cell_id, zome_name, function_name, callee, agent_pubkey
    ) => {
      Err(wasm_error!(WasmErrorInner::Guest("Agent revoked the capability".into())))
    },
    _ => {
      Err(wasm_error!(WasmErrorInner::Guest(format!("There was an error by call: {:?}", zome_call_response))))
    },
  }
}
              
            

Remote call


use hdk::prelude::*;

// Defined in the "Capability Tokens (VIII) slide"
fn query_cap_claims_for(grantor: &AgentPubKey) -> ExternResult<Vec<CapClaim>> {}

// Call "zome_function_a" from the "callee"'s cell and return its result
#[hdk_extern]
fn call_zome_function_a(callee: AgentPubKey) -> ExternResult<ActionHash> { 
  let cap_claim_entry: CapClaimEntry = query_cap_claims_for(&callee)?
    .first()
    .cloned()
    .ok_or(wasm_error!(WasmErrorInner::Guest(String::from("No CapClaims for this grantor"))))?;

  let zome_call_response = call_remote(
    callee, // The agent we are calling, must be a participant in this network
    zome_name()?.name, // We are calling a function defined in this zome
    FunctionName(String::from("zome_function_a")), // The name of the function we are calling    
    Some(cap_claim_entry.secret), // The "CapSecret" that we committed privately as the claim
    String::from("example input") // Zome function input must derive "Serialize" and "Deserialize"
  )?;

  match zome_call_response { // "zome_call_response" is the same type than in the bridged call response
    ZomeCallResponse::Ok(result) => {
      let action_hash: ActionHash = result.decode().map_err(|err| wasm_error!(err))?;
      Ok(action_hash)
    },
    ZomeCallResponse::NetworkError(err) => {
      Err(wasm_error!(WasmErrorInner::Guest(format!("There was a network error: {:?}", err))))
    },
    _ => Err(wasm_error!(WasmErrorInner::Guest(
        format!("Failed to handle remote call {:?}", zome_call_response)
    ))),
  }
}
## call_info() - HDK function - Returns information about the source of the zome call that's being executed
      
use hdk::prelude::*;

#[hdk_extern]
fn request_retrieve_private_entries(_: ()) -> ExternResult<Record> { // This function could 
                                                                     // be called via 
                                                                     // call_remote
  let info = call_info()?;

  let caller: AgentPubKey = info.provenance;
  match info.cap_grant {
    CapGrant::ChainAuthor(my_pub_key) => {}, // The cell's agent called this function
    CapGrant::RemoteAgent(zome_call_cap_grant) => {} // An external agent called this function 
                                                     // via call or call_remote, and it was 
                                                     // authorized because this 
                                                     // "zome_call_cap_grant" exists in the 
                                                     // source chain for this cell
  }

  // ... execute the rest of the code
}
### Signals - Don't wait for a response: fire and forget - Not reliable: signals can be lost without our code knowing - 2 types: - Remote Signals - Concurrent call to multiple agents - Very similar to "call_remote" - Useful for notifications - UI Signals - Send message from the conductor to all processes that are listening

Remote Signals


use hdk::prelude::*;

#[hdk_extern]
fn notify(agents_to_notify: Vec<AgentPubKey>) -> ExternResult<()> {
  let input_string = String::from("input parameter");

  remote_signal(input_string, agents_to_notify)?; // Doesn't wait for the agents 
                                                  // to receive the signal

  Ok(())
}

#[hdk_extern]
fn recv_remote_signal(signal: String) -> ExternResult<()> { // Callback that holochain 
                                                            // will execute in the 
                                                            // callee's cell with 
                                                            // the received signal

  // Handle the signal in the appropriate way
}

UI Signals


use hdk::prelude::*;

#[hdk_extern]
fn notify_ui(_: ()) -> ExternResult<()> {
  let input_string = String::from("input parameter");

  emit_signal(input_string)?; // No other parameter other than the input because 
                              // all the processes connected to the conductor 
                              // will be notified

  Ok(())
}

That's it!