#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "mcp-client")]
#[cfg_attr(docsrs, doc(cfg(feature = "mcp-client")))]
pub mod mcp;
pub mod programs {
use solana_program::{pubkey, pubkey::Pubkey};
pub const COMPLIANCE_REGISTRY: Pubkey = pubkey!("7q19zbMMFCPSDhJhh3cfUVJstin6r1Q4dgmeDAuQERyK");
pub const SP1_VERIFIER: Pubkey = pubkey!("5xrWphWXoFnXJh7jYt3tyWZAwX1itbyyxJQs8uumiRTW");
pub const CONSENT_MANAGER: Pubkey = pubkey!("D5mLHU4uUQAkoMvtviAzBe1ugpdxfdqQ7VuGoKLaTjfB");
pub const ART_VAULT: Pubkey = pubkey!("C7sGZFeWPxEkaGHACwqdzCcy4QkacqPLYEwEarVpidna");
pub const AIVERIFY_ATTESTATION: Pubkey =
pubkey!("DSCVxsdJd5wVJan5WqQfpKkqxazWJR7D7cjd3r65s6cm");
pub const AGENT_REGISTRY: Pubkey = pubkey!("5qeuUAaJi9kTzsfmiphQ89PNrpqy7xW7sCvhBZQ6mya7");
pub const PAYMENT_GATEWAY: Pubkey = pubkey!("4Qj6GziMjUfh4TszuSnasnEqnASqQBS6SHw6YAu9U23Q");
pub const FEE_DISTRIBUTOR: Pubkey = pubkey!("88eKEEMMnugv8AFWRvqa4i7LEiL7tM9bEuPTVkRbD76x");
pub const AGENT_WALLET_FACTORY: Pubkey =
pubkey!("AjRqmxyieQieov2qsNefdYpa6HbPhzciED7s5TfZi1in");
}
pub mod pdas {
use super::programs;
use sha2::{Digest, Sha256};
use solana_program::pubkey::Pubkey;
pub fn attestation_pda(subject: &Pubkey, commitment: &[u8; 32]) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[b"attestation", subject.as_ref(), commitment],
&programs::COMPLIANCE_REGISTRY,
)
}
pub fn consent_pda(
user: &Pubkey,
data_fiduciary: &Pubkey,
purpose_hash: &[u8; 32],
) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[
b"consent",
user.as_ref(),
data_fiduciary.as_ref(),
purpose_hash,
],
&programs::CONSENT_MANAGER,
)
}
pub fn art_vault_pda(authority: &Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(&[b"art_vault", authority.as_ref()], &programs::ART_VAULT)
}
pub fn aiverify_pda(model_hash: &[u8; 32]) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[b"aiverify", model_hash],
&programs::AIVERIFY_ATTESTATION,
)
}
pub fn agent_pda(authority: &Pubkey, name: &str) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[b"agent", authority.as_ref(), name.as_bytes()],
&programs::AGENT_REGISTRY,
)
}
pub fn purpose_hash(purpose_text_bytes: &[u8]) -> [u8; 32] {
let mut h = Sha256::new();
h.update(purpose_text_bytes);
let out = h.finalize();
let mut arr = [0u8; 32];
arr.copy_from_slice(&out);
arr
}
pub fn commitment_from_subject(subject_text: &str) -> [u8; 32] {
purpose_hash(subject_text.as_bytes())
}
}
pub mod seeds {
pub const ATTESTATION: &[u8] = b"attestation";
pub const CONSENT: &[u8] = b"consent";
pub const ART_VAULT: &[u8] = b"art_vault";
pub const AIVERIFY: &[u8] = b"aiverify";
pub const AGENT: &[u8] = b"agent";
}
pub mod public_values {
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PublicValuesError {
#[error("public_inputs must be exactly 96 bytes, got {0}")]
WrongLength(usize),
}
pub struct PublicValues {
pub threshold: u32,
pub subject_commitment: [u8; 32],
pub meets_threshold: bool,
}
pub fn parse(public_inputs: &[u8]) -> Result<PublicValues, PublicValuesError> {
if public_inputs.len() != 96 {
return Err(PublicValuesError::WrongLength(public_inputs.len()));
}
let mut threshold_bytes = [0u8; 4];
threshold_bytes.copy_from_slice(&public_inputs[28..32]);
let threshold = u32::from_be_bytes(threshold_bytes);
let mut subject_commitment = [0u8; 32];
subject_commitment.copy_from_slice(&public_inputs[32..64]);
let meets_threshold = public_inputs[95] != 0;
Ok(PublicValues {
threshold,
subject_commitment,
meets_threshold,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use solana_program::pubkey::Pubkey;
#[test]
fn consent_pda_is_deterministic() {
let user = Pubkey::new_unique();
let fid = Pubkey::new_unique();
let h = pdas::purpose_hash(b"marketing_communications");
let (a, _) = pdas::consent_pda(&user, &fid, &h);
let (b, _) = pdas::consent_pda(&user, &fid, &h);
assert_eq!(a, b);
}
#[test]
fn consent_pda_differs_across_purposes() {
let user = Pubkey::new_unique();
let fid = Pubkey::new_unique();
let h1 = pdas::purpose_hash(b"marketing");
let h2 = pdas::purpose_hash(b"analytics");
let (a, _) = pdas::consent_pda(&user, &fid, &h1);
let (b, _) = pdas::consent_pda(&user, &fid, &h2);
assert_ne!(a, b);
}
#[test]
fn purpose_hash_is_sha256() {
let h = pdas::purpose_hash(b"");
assert_eq!(
hex_lower(&h),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
#[test]
fn aiverify_pda_has_global_uniqueness_per_model() {
let h = [0u8; 32];
let (pda1, _) = pdas::aiverify_pda(&h);
let (pda2, _) = pdas::aiverify_pda(&h);
assert_eq!(pda1, pda2);
}
#[test]
fn parse_public_values_rejects_wrong_length() {
assert!(public_values::parse(&[0u8; 95]).is_err());
assert!(public_values::parse(&[0u8; 97]).is_err());
}
#[test]
fn parse_public_values_extracts_fields() {
let mut pi = [0u8; 96];
pi[28..32].copy_from_slice(&70u32.to_be_bytes());
for b in pi.iter_mut().take(64).skip(32) {
*b = 0xaa;
}
pi[95] = 1;
let pv = public_values::parse(&pi).unwrap();
assert_eq!(pv.threshold, 70);
assert_eq!(pv.subject_commitment, [0xaau8; 32]);
assert!(pv.meets_threshold);
}
fn hex_lower(b: &[u8]) -> String {
let mut s = String::with_capacity(b.len() * 2);
for byte in b {
s.push_str(&format!("{:02x}", byte));
}
s
}
}
pub use programs::*;