techne-sdk 0.2.0

TechneSDK's logic written once: version ranking (eventState) and offline record verification over the published check, shipped to TypeScript as WebAssembly; with opt-in atproto plumbing (service auth, DID resolution, signed labels, proven record fetches, PDS password sessions) behind the `crypto` and `net` features.
Documentation
//! Commons service accounts: the password of an account a service owns (`crypto`; ported from regenOS
//! `regenos_lexicons::account`, P20.7 G4).
//!
//! A service account (`rule-features`, `mirror-*`, `commons-root`, ...) is a plain atproto PDS
//! password account. The service that owns it holds ONE secret, `<SERVICE>_ACCOUNT_SECRET`, and
//! derives each account's password from it and the account's fixed creation label, so no password is
//! stored anywhere and a new account needs no configuration:
//!
//! `account_password(secret, label)` = base64url, no padding, of HMAC-SHA256(key = the secret's
//! UTF-8 bytes, message = the label's UTF-8 bytes).
//!
//! **The label, never the handle.** The label is the first label of the handle the account was
//! created with (`rule-features` for `rule-features.example.test`), fixed in the owner's code or
//! configuration. A password has to exist before the DID does (`createAccount`), so it cannot come
//! from the DID; and it must not come from the live handle, which can change (identity by DID, review
//! agent 12). Once created, the account IS its DID: the owner stores the DID `createAccount` returned
//! and signs in with the DID as the `createSession` identifier, refuses a sign-in that answers another
//! DID, and never creates the account again once a DID is stored.
//!
//! The same contract is implemented outside this repo (the almanac-mirror `commons-mirror`
//! pipeline); every implementation must pass the golden vectors in
//! `testdata/account_password_vectors.json`, which were computed with openssl, not with this code.
//! Copy that file rather than restating the values.

use base64::Engine;
use hmac::{Hmac, Mac};
use sha2::Sha256;

/// The password of the service account created with label `label` (e.g. `rule-features`), for its
/// owner's `secret`. Never pass a full or live handle: see the module docs.
pub fn account_password(secret: &str, label: &str) -> String {
    let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect("HMAC takes a key of any length");
    mac.update(label.as_bytes());
    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(mac.finalize().into_bytes())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn account_password_matches_the_golden_vectors() {
        let file: serde_json::Value =
            serde_json::from_str(include_str!("../testdata/account_password_vectors.json")).expect("vectors parse");
        let vectors = file["vectors"].as_array().expect("a vectors array");
        assert!(!vectors.is_empty());
        for v in vectors {
            let (secret, label) = (v["secret"].as_str().unwrap(), v["label"].as_str().unwrap());
            assert!(!label.contains('.'), "a vector's message is a label, never a handle: {label}");
            assert_eq!(account_password(secret, label), v["password"].as_str().unwrap(), "{secret:?} {label}");
        }
    }

    #[test]
    fn the_password_depends_on_the_whole_label_and_the_secret() {
        let p = account_password("s", "rule-features");
        assert_eq!(p.len(), 43, "32 bytes, base64url without padding");
        assert!(p.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_'));
        assert_ne!(p, account_password("s", "rule-feature"));
        assert_ne!(p, account_password("t", "rule-features"));
    }
}