Skip to main content

chio_web3/
identity.rs

1use serde::{Deserialize, Serialize};
2
3use crate::crypto::{PublicKey, Signature};
4use crate::error::Web3ContractError;
5use crate::validation::{ensure_non_empty, ensure_unique_copy_values, ensure_unique_strings};
6
7pub const CHIO_KEY_BINDING_CERTIFICATE_SCHEMA: &str = "chio.key-binding-certificate.v1";
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum Web3KeyBindingPurpose {
12    Anchor,
13    Settle,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct Web3IdentityBindingCertificate {
19    pub schema: String,
20    pub chio_identity: String,
21    pub chio_public_key: PublicKey,
22    pub chain_scope: Vec<String>,
23    pub purpose: Vec<Web3KeyBindingPurpose>,
24    pub settlement_address: String,
25    pub issued_at: u64,
26    pub expires_at: u64,
27    pub nonce: String,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(deny_unknown_fields)]
32pub struct SignedWeb3IdentityBinding {
33    pub certificate: Web3IdentityBindingCertificate,
34    pub signature: Signature,
35}
36
37pub fn validate_web3_identity_binding(
38    binding: &SignedWeb3IdentityBinding,
39) -> Result<(), Web3ContractError> {
40    if binding.certificate.schema != CHIO_KEY_BINDING_CERTIFICATE_SCHEMA {
41        return Err(Web3ContractError::UnsupportedSchema(
42            binding.certificate.schema.clone(),
43        ));
44    }
45    ensure_non_empty(&binding.certificate.chio_identity, "binding.chio_identity")?;
46    ensure_non_empty(
47        &binding.certificate.settlement_address,
48        "binding.settlement_address",
49    )?;
50    ensure_non_empty(&binding.certificate.nonce, "binding.nonce")?;
51    if binding.certificate.chain_scope.is_empty() {
52        return Err(Web3ContractError::MissingField("binding.chain_scope"));
53    }
54    if binding.certificate.purpose.is_empty() {
55        return Err(Web3ContractError::MissingField("binding.purpose"));
56    }
57    ensure_unique_strings(&binding.certificate.chain_scope, "binding.chain_scope")?;
58    ensure_unique_copy_values(&binding.certificate.purpose, "binding.purpose")?;
59    if binding.certificate.issued_at >= binding.certificate.expires_at {
60        return Err(Web3ContractError::InvalidBinding(
61            "identity binding issued_at must be earlier than expires_at".to_string(),
62        ));
63    }
64    Ok(())
65}
66
67pub fn verify_web3_identity_binding(
68    binding: &SignedWeb3IdentityBinding,
69) -> Result<(), Web3ContractError> {
70    validate_web3_identity_binding(binding)?;
71    let verified = binding
72        .certificate
73        .chio_public_key
74        .verify_canonical(&binding.certificate, &binding.signature)
75        .map_err(|error| Web3ContractError::InvalidBinding(error.to_string()))?;
76    if !verified {
77        return Err(Web3ContractError::InvalidBinding(
78            "identity binding signature verification failed".to_string(),
79        ));
80    }
81    Ok(())
82}