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}