1#![cfg_attr(docsrs, feature(doc_cfg))]
38
39#[cfg(feature = "mcp-client")]
40#[cfg_attr(docsrs, doc(cfg(feature = "mcp-client")))]
41pub mod mcp;
42
43pub mod programs {
44 use solana_program::{pubkey, pubkey::Pubkey};
46
47 pub const COMPLIANCE_REGISTRY: Pubkey = pubkey!("7q19zbMMFCPSDhJhh3cfUVJstin6r1Q4dgmeDAuQERyK");
48 pub const SP1_VERIFIER: Pubkey = pubkey!("5xrWphWXoFnXJh7jYt3tyWZAwX1itbyyxJQs8uumiRTW");
49 pub const CONSENT_MANAGER: Pubkey = pubkey!("D5mLHU4uUQAkoMvtviAzBe1ugpdxfdqQ7VuGoKLaTjfB");
50 pub const ART_VAULT: Pubkey = pubkey!("C7sGZFeWPxEkaGHACwqdzCcy4QkacqPLYEwEarVpidna");
51 pub const AIVERIFY_ATTESTATION: Pubkey =
52 pubkey!("DSCVxsdJd5wVJan5WqQfpKkqxazWJR7D7cjd3r65s6cm");
53 pub const AGENT_REGISTRY: Pubkey = pubkey!("5qeuUAaJi9kTzsfmiphQ89PNrpqy7xW7sCvhBZQ6mya7");
54 pub const PAYMENT_GATEWAY: Pubkey = pubkey!("4Qj6GziMjUfh4TszuSnasnEqnASqQBS6SHw6YAu9U23Q");
55 pub const FEE_DISTRIBUTOR: Pubkey = pubkey!("88eKEEMMnugv8AFWRvqa4i7LEiL7tM9bEuPTVkRbD76x");
56 pub const AGENT_WALLET_FACTORY: Pubkey =
57 pubkey!("AjRqmxyieQieov2qsNefdYpa6HbPhzciED7s5TfZi1in");
58}
59
60pub mod pdas {
61 use super::programs;
65 use sha2::{Digest, Sha256};
66 use solana_program::pubkey::Pubkey;
67
68 pub fn attestation_pda(subject: &Pubkey, commitment: &[u8; 32]) -> (Pubkey, u8) {
70 Pubkey::find_program_address(
71 &[b"attestation", subject.as_ref(), commitment],
72 &programs::COMPLIANCE_REGISTRY,
73 )
74 }
75
76 pub fn consent_pda(
78 user: &Pubkey,
79 data_fiduciary: &Pubkey,
80 purpose_hash: &[u8; 32],
81 ) -> (Pubkey, u8) {
82 Pubkey::find_program_address(
83 &[
84 b"consent",
85 user.as_ref(),
86 data_fiduciary.as_ref(),
87 purpose_hash,
88 ],
89 &programs::CONSENT_MANAGER,
90 )
91 }
92
93 pub fn art_vault_pda(authority: &Pubkey) -> (Pubkey, u8) {
95 Pubkey::find_program_address(&[b"art_vault", authority.as_ref()], &programs::ART_VAULT)
96 }
97
98 pub fn aiverify_pda(model_hash: &[u8; 32]) -> (Pubkey, u8) {
100 Pubkey::find_program_address(
101 &[b"aiverify", model_hash],
102 &programs::AIVERIFY_ATTESTATION,
103 )
104 }
105
106 pub fn agent_pda(authority: &Pubkey, name: &str) -> (Pubkey, u8) {
108 Pubkey::find_program_address(
109 &[b"agent", authority.as_ref(), name.as_bytes()],
110 &programs::AGENT_REGISTRY,
111 )
112 }
113
114 pub fn purpose_hash(purpose_text_bytes: &[u8]) -> [u8; 32] {
118 let mut h = Sha256::new();
119 h.update(purpose_text_bytes);
120 let out = h.finalize();
121 let mut arr = [0u8; 32];
122 arr.copy_from_slice(&out);
123 arr
124 }
125
126 pub fn commitment_from_subject(subject_text: &str) -> [u8; 32] {
131 purpose_hash(subject_text.as_bytes())
132 }
133}
134
135pub mod seeds {
136 pub const ATTESTATION: &[u8] = b"attestation";
140 pub const CONSENT: &[u8] = b"consent";
141 pub const ART_VAULT: &[u8] = b"art_vault";
142 pub const AIVERIFY: &[u8] = b"aiverify";
143 pub const AGENT: &[u8] = b"agent";
144}
145
146pub mod public_values {
147 use thiserror::Error;
157
158 #[derive(Debug, Error)]
159 pub enum PublicValuesError {
160 #[error("public_inputs must be exactly 96 bytes, got {0}")]
161 WrongLength(usize),
162 }
163
164 pub struct PublicValues {
165 pub threshold: u32,
166 pub subject_commitment: [u8; 32],
167 pub meets_threshold: bool,
168 }
169
170 pub fn parse(public_inputs: &[u8]) -> Result<PublicValues, PublicValuesError> {
171 if public_inputs.len() != 96 {
172 return Err(PublicValuesError::WrongLength(public_inputs.len()));
173 }
174 let mut threshold_bytes = [0u8; 4];
175 threshold_bytes.copy_from_slice(&public_inputs[28..32]);
176 let threshold = u32::from_be_bytes(threshold_bytes);
177
178 let mut subject_commitment = [0u8; 32];
179 subject_commitment.copy_from_slice(&public_inputs[32..64]);
180
181 let meets_threshold = public_inputs[95] != 0;
182
183 Ok(PublicValues {
184 threshold,
185 subject_commitment,
186 meets_threshold,
187 })
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use solana_program::pubkey::Pubkey;
195
196 #[test]
197 fn consent_pda_is_deterministic() {
198 let user = Pubkey::new_unique();
199 let fid = Pubkey::new_unique();
200 let h = pdas::purpose_hash(b"marketing_communications");
201 let (a, _) = pdas::consent_pda(&user, &fid, &h);
202 let (b, _) = pdas::consent_pda(&user, &fid, &h);
203 assert_eq!(a, b);
204 }
205
206 #[test]
207 fn consent_pda_differs_across_purposes() {
208 let user = Pubkey::new_unique();
209 let fid = Pubkey::new_unique();
210 let h1 = pdas::purpose_hash(b"marketing");
211 let h2 = pdas::purpose_hash(b"analytics");
212 let (a, _) = pdas::consent_pda(&user, &fid, &h1);
213 let (b, _) = pdas::consent_pda(&user, &fid, &h2);
214 assert_ne!(a, b);
215 }
216
217 #[test]
218 fn purpose_hash_is_sha256() {
219 let h = pdas::purpose_hash(b"");
220 assert_eq!(
222 hex_lower(&h),
223 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
224 );
225 }
226
227 #[test]
228 fn aiverify_pda_has_global_uniqueness_per_model() {
229 let h = [0u8; 32];
230 let (pda1, _) = pdas::aiverify_pda(&h);
231 let (pda2, _) = pdas::aiverify_pda(&h);
232 assert_eq!(pda1, pda2);
233 }
234
235 #[test]
236 fn parse_public_values_rejects_wrong_length() {
237 assert!(public_values::parse(&[0u8; 95]).is_err());
238 assert!(public_values::parse(&[0u8; 97]).is_err());
239 }
240
241 #[test]
242 fn parse_public_values_extracts_fields() {
243 let mut pi = [0u8; 96];
244 pi[28..32].copy_from_slice(&70u32.to_be_bytes());
246 for b in pi.iter_mut().take(64).skip(32) {
248 *b = 0xaa;
249 }
250 pi[95] = 1;
252
253 let pv = public_values::parse(&pi).unwrap();
254 assert_eq!(pv.threshold, 70);
255 assert_eq!(pv.subject_commitment, [0xaau8; 32]);
256 assert!(pv.meets_threshold);
257 }
258
259 fn hex_lower(b: &[u8]) -> String {
260 let mut s = String::with_capacity(b.len() * 2);
261 for byte in b {
262 s.push_str(&format!("{:02x}", byte));
263 }
264 s
265 }
266}
267
268pub use programs::*;