use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
use car_sync::crypto::{ed25519_verifying, wrap_org_key, x25519_public};
use car_sync::org_key_directory::{InMemoryOrgKeyDirectory, OrgKeyDirectory};
use car_sync::{
resolve_org_root, DerivedKeyProvider, InMemoryRelay, OrgAwareKeyProvider, OrgSigningKey,
OrgVerifyingKey, RelayConfig, Scope, StretchedMaster, Surface, SyncKeyProvider, SyncSession,
};
use serde_json::json;
const ORG: &str = "testorg";
const K_ORG: [u8; 32] = [0x5a; 32];
fn x25519_id(secret: &[u8], user: &str) -> x25519_dalek::StaticSecret {
car_sync::derive_x25519_identity(
&StretchedMaster::from_issued_high_entropy(secret, user),
user,
)
}
fn ed25519_id(secret: &[u8], user: &str) -> OrgSigningKey {
car_sync::derive_ed25519_identity(
&StretchedMaster::from_issued_high_entropy(secret, user),
user,
)
}
fn granter() -> OrgSigningKey {
ed25519_id(b"granter-login-secret", "acc_granter")
}
fn member_provider(
dir: &dyn OrgKeyDirectory,
passphrase: &str,
account_id: &str,
trusted: &[OrgVerifyingKey],
) -> (Arc<dyn SyncKeyProvider>, bool) {
let my_secret = x25519_id(passphrase.as_bytes(), account_id);
let resolved =
resolve_org_root(dir, ORG, &my_secret, account_id, trusted).expect("directory reachable");
let mut roots = HashMap::new();
let granted = resolved.is_some();
if let Some(r) = resolved {
assert_eq!(*r.root, K_ORG, "resolver recovered the true org key");
roots.insert(ORG.to_string(), BTreeMap::from([(r.epoch, r.root)]));
}
let personal: Arc<dyn SyncKeyProvider> = Arc::new(DerivedKeyProvider::from_login_secret(
passphrase.as_bytes(),
account_id,
));
(Arc::new(OrgAwareKeyProvider::new(personal, roots)), granted)
}
fn grant(
dir: &mut InMemoryOrgKeyDirectory,
passphrase: &str,
account_id: &str,
signer: &OrgSigningKey,
publisher: &str,
) {
let recipient_pub = x25519_public(&x25519_id(passphrase.as_bytes(), account_id));
let w = wrap_org_key(
&K_ORG,
ORG,
1,
account_id,
&recipient_pub,
publisher,
signer,
)
.unwrap();
dir.publish_wrapped(&w).unwrap();
}
fn session(
device: &str,
dir: &tempfile::TempDir,
provider: Arc<dyn SyncKeyProvider>,
) -> SyncSession {
SyncSession::open(
device,
&dir.path().join("oplog.jsonl"),
&dir.path().join("ckpts"),
car_sync::system_clock(),
)
.unwrap()
.with_key_provider(provider)
}
fn read_body(sess: &SyncSession, id: &str) -> Option<serde_json::Value> {
sess.state()
.log_entries(&Surface::Knowledge.tag())
.iter()
.find(|e| e.payload.get("id").and_then(|v| v.as_str()) == Some(id))
.map(|e| e.payload.get("body").cloned().unwrap_or(json!(null)))
}
#[test]
fn two_granted_members_share_an_org_op_end_to_end() {
let mut dir = InMemoryOrgKeyDirectory::new();
grant(
&mut dir,
"alice-pass",
"acc_alice",
&granter(),
"acc_granter",
);
grant(&mut dir, "bob-pass", "acc_bob", &granter(), "acc_granter");
let trusted = [ed25519_verifying(&granter())];
let (alice_kp, a_granted) = member_provider(&dir, "alice-pass", "acc_alice", &trusted);
let (bob_kp, b_granted) = member_provider(&dir, "bob-pass", "acc_bob", &trusted);
assert!(a_granted && b_granted, "both members resolved K_org");
let mut relay = InMemoryRelay::new(RelayConfig::default(), car_sync::system_clock());
let (da, db) = (tempfile::tempdir().unwrap(), tempfile::tempdir().unwrap());
let mut alice = session("alice-dev", &da, alice_kp);
let mut bob = session("bob-dev", &db, bob_kp);
let org_body = "the shared org brain remembers this";
alice
.append(
Scope::Shared { org: ORG.into() },
Surface::Knowledge,
json!({"id": "org-note", "body": org_body}),
)
.unwrap();
alice
.append(
Scope::Personal,
Surface::Knowledge,
json!({"id": "alice-note", "body": "private"}),
)
.unwrap();
alice.pump(&mut relay).unwrap();
bob.pump(&mut relay).unwrap();
assert_eq!(
read_body(&bob, "org-note")
.as_ref()
.and_then(|v| v.as_str()),
Some(org_body),
"bob reads alice's org op via the shared K_org"
);
assert!(
read_body(&bob, "alice-note").is_none(),
"bob must NOT read alice's personal op — the provider swap didn't collapse personal into shared"
);
}
#[test]
fn ungranted_member_receives_but_cannot_read_the_org_op() {
let mut dir = InMemoryOrgKeyDirectory::new();
grant(
&mut dir,
"alice-pass",
"acc_alice",
&granter(),
"acc_granter",
);
let trusted = [ed25519_verifying(&granter())];
let (alice_kp, _) = member_provider(&dir, "alice-pass", "acc_alice", &trusted);
let (mallory_kp, m_granted) = member_provider(&dir, "mallory-pass", "acc_mallory", &trusted);
assert!(
!m_granted,
"mallory has no trusted grant → Ok(None) → DenyCipher"
);
let mut relay = InMemoryRelay::new(RelayConfig::default(), car_sync::system_clock());
let (da, dm) = (tempfile::tempdir().unwrap(), tempfile::tempdir().unwrap());
let mut alice = session("alice-dev", &da, alice_kp);
let mut mallory = session("mallory-dev", &dm, mallory_kp);
alice
.append(
Scope::Shared { org: ORG.into() },
Surface::Knowledge,
json!({"id": "org-note", "body": "secret"}),
)
.unwrap();
alice.pump(&mut relay).unwrap();
let report = mallory.pump(&mut relay).unwrap();
assert!(
report.folded >= 1,
"mallory DID receive the op over the relay (delivery happened)"
);
assert!(
read_body(&mallory, "org-note").is_none(),
"but mallory cannot decrypt it — fail-closed, opaque envelope"
);
}
#[test]
fn wrap_from_an_untrusted_publisher_does_not_grant_access() {
let mut dir = InMemoryOrgKeyDirectory::new();
let mallory_signer = ed25519_id(b"mallory-login", "acc_mallory");
grant(
&mut dir,
"bob-pass",
"acc_bob",
&mallory_signer,
"acc_mallory",
);
let trusted = [ed25519_verifying(&granter())]; let (_, b_granted) = member_provider(&dir, "bob-pass", "acc_bob", &trusted);
assert!(!b_granted, "an untrusted-signed wrap grants nothing");
}