libid-profiles 0.8.0

The libID ceremony profiles: what each platform's notarized sessions request and reveal, mirroring CeremonyProfile.sol byte for byte.
Documentation
// Generated by scripts/regen-ceremony-profiles.py. Do not edit.
// The source of truth is solidity/contracts/ceremony/profiles.json.

//! The single source of truth for the ceremony profiles. Solidity, Rust and
//! TypeScript constants are generated from this file by
//! scripts/regen-ceremony-profiles.py. The Platform Verifiers are hand
//! written and READ these constants; only the values live here.
//!
//! THESE STRINGS ARE OURS, NOT THE SPECIFICATION'S. ceremony-common fixes no
//! literal of its own: platform-ceremonies REQ-PLAT-01 fixes the profile
//! NAMES, and every byte below is the profile author's choice. So this is a
//! cross-implementation agreement, and it is agreed here rather than four
//! times over.
//!
//! Four components must produce the same bytes: this repository's verifiers,
//! the browser that notarizes three of the four sessions, the deployment that
//! notarizes the fourth, and the notary that signs what they observed. A
//! disagreement is silent -- a Consumer dispatching on one string and a
//! verifier registered under another simply never meet, and a request line a
//! prover composes one byte differently is an attestation rejected with no
//! error that says why.
//!
//! Changing a value here changes what a deployed verifier accepts. That is a
//! new ceremonyVersion, not an edit: --check compares every shipped profile
//! against the last release and refuses a changed profile that kept its
//! version.

/// Which shape a platform's immutable identifier takes in its response.
///
/// The bare-integer form takes its structural terminator with it, which is
/// what proves the revealed digits are the whole number rather than a prefix
/// of a longer one (REQ-PLAT-51).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IdShape {
    /// `"id":"2244994945"`.
    JsonString,
    /// `"id":583231,`
    JsonInteger,
}

/// One notarized session: which server, and which request.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Session {
    /// The lowercase TLS server name the notary authenticates, with no
    /// trailing dot. `authorityId` is its keccak256, which the verifier
    /// compares against the constant its profile pins (REQ-COMMON-21A). It is
    /// also the `Host` header the request carries.
    pub authority: &'static str,
    pub method: &'static str,
    pub path: &'static str,
    /// The request-line prefix the verifier compares byte for byte, trailing
    /// space included. The space is what stops `GET /user ` prefixing
    /// `GET /users/me`.
    pub request_line: &'static str,
}

/// The token session: the OAuth exchange.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TokenSession {
    pub session: Session,
    /// The body field committed rather than revealed, ordered last so the
    /// committed run is a suffix (REQ-COMMON-22). `None` for a public client,
    /// whose request hides nothing and is revealed whole.
    pub secret_field: Option<&'static str>,
}

/// The identity session: the authenticated read that names the account.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IdentitySession {
    pub session: Session,
    pub id_field: &'static str,
    pub id_shape: IdShape,
    pub handle_field: &'static str,
}

/// One platform's ceremony profile at one Platform Ceremony Version.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Profile {
    /// The platform name. `platformId` is its keccak256.
    pub platform: &'static str,
    /// A profile is the pair, not the name (REQ-PLAT-01).
    pub ceremony_version: u16,
    /// `None` where the profile notarizes nothing.
    pub token: Option<TokenSession>,
    pub identity: Option<IdentitySession>,
}

impl Profile {
    /// How many attestations a submission for this profile carries. Derived
    /// from the sessions rather than stated beside them (REQ-COMMON-41).
    pub const fn attestation_count(&self) -> u8 {
        self.token.is_some() as u8 + self.identity.is_some() as u8
    }
}

/// Google's evidence is a signed token checked against Google's published
/// keys, so the profile notarizes nothing: no session, no attestation,
/// and no Notary Fee.
pub const GOOGLE: Profile = Profile {
    platform: "google",
    ceremony_version: 1,
    token: None,
    identity: None,
};

/// A public client with S256 PKCE and two browser-owned sessions, both
/// served by the same host.
pub const X: Profile = Profile {
    platform: "x",
    ceremony_version: 1,
    token: Some(TokenSession {
        session: Session {
            authority: "api.x.com",
            method: "POST",
            path: "/2/oauth2/token",
            request_line: "POST /2/oauth2/token ",
        },
        secret_field: None,
    }),
    identity: Some(IdentitySession {
        session: Session {
            authority: "api.x.com",
            method: "GET",
            path: "/2/users/me",
            request_line: "GET /2/users/me ",
        },
        id_field: "id",
        id_shape: IdShape::JsonString,
        handle_field: "username",
    }),
};

/// A confidential client. The exchange runs in the deployment because the
/// secret cannot reach the browser, and the two sessions are served by
/// DIFFERENT hosts -- which is why one pinned authority per profile would
/// be wrong.
pub const GITHUB: Profile = Profile {
    platform: "github",
    ceremony_version: 1,
    token: Some(TokenSession {
        session: Session {
            authority: "github.com",
            method: "POST",
            path: "/login/oauth/access_token",
            request_line: "POST /login/oauth/access_token ",
        },
        secret_field: Some("client_secret"),
    }),
    identity: Some(IdentitySession {
        session: Session {
            authority: "api.github.com",
            method: "GET",
            path: "/user",
            request_line: "GET /user ",
        },
        id_field: "id",
        id_shape: IdShape::JsonInteger,
        handle_field: "login",
    }),
};

/// The closed launch list. A platform outside it has no profile, and
/// `CeremonyProfile.attestationCount` reverts on one.
pub const LAUNCH: &[&Profile] = &[&GOOGLE, &X, &GITHUB];

/// The launch profile for a platform name, or nothing.
///
/// Nothing, rather than a default: a caller that cannot name the platform has
/// nothing to notarize, and guessing produces evidence no verifier accepts.
pub fn launch(platform: &str) -> Option<&'static Profile> {
    LAUNCH.iter().copied().find(|p| p.platform == platform)
}

/// Governance-owned launch parameters, in seconds.
pub const MAX_FUTURE_ATTESTATION_SKEW_SECONDS: u64 = 300;
pub const PROOF_LIFETIME_SECONDS_X: u64 = 3600;
pub const PROOF_LIFETIME_SECONDS_GITHUB: u64 = 3600;