use abi::records::exec::Attest; use abi::repo::get_record; use abi::{AtUri, Call, LexiconType, TypedLexicon, TypedStrongRef}; use crate::nsid::{BID, RECEIPT}; use serde::Serialize; use serde_json::{Value, json}; use crate::input::strong; use crate::nsid::ATTEST; /// Attestations come from the host (com.fedfork.exec.attest); without a record /// it answers with the host's did:key. pub fn attest(me: &str, record: Option<&Value>) -> Call { Call::new("", ATTEST).input(json!(Attest { repository: me.into(), record: record.cloned() })) } pub fn typed(record: T) -> Value { json!(TypedLexicon::new(record)) } /// The host's signed copy must be the record we asked for, plus signatures. pub fn signed(result: Result, unsigned: &Value) -> Result { let record = result?["record"].take(); let mut bare = record.clone(); let signatures = bare.as_object_mut().and_then(|o| o.remove("signatures")); if !signatures.as_ref().and_then(Value::as_array).is_some_and(|s| !s.is_empty()) || &bare != unsigned { return Err("attest must return the record with signatures added".into()); } Ok(record) } pub fn created(result: Result) -> Result { strong(&result?, "createRecord output") } /// A peer's public record, read from its own PDS. pub fn read(uri: &AtUri) -> Call { get_record(uri).service(&uri.repo) } /// The winner keys its receipt by the accept: receipt-\n"))[0..32]>. pub fn receipt_uri(bidder: &str, accept: &TypedStrongRef) -> AtUri { AtUri::new(bidder, RECEIPT, format!("receipt-{}", hex16(&format!("{}\n{}", accept.uri, accept.cid)))) } /// A bidder keys its bid by the rfp: bid-\0#"))[0..20]>. pub fn bid_uri(bidder: &str, rfp: &TypedStrongRef) -> AtUri { AtUri::new(bidder, BID, format!("bid-{}", &hex(&format!("{bidder}\0{}#{}", rfp.uri, rfp.cid))[..20])) } pub fn hex16(key: &str) -> String { hex(key)[..32].to_string() } fn hex(key: &str) -> String { use sha2::{Digest, Sha256}; Sha256::digest(key.as_bytes()).iter().map(|b| format!("{b:02x}")).collect() } /// base64url without padding (the webrtc:// and iroh:// URL refs). pub fn b64url(bytes: &[u8]) -> String { const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; let mut out = String::new(); for chunk in bytes.chunks(3) { let n = chunk.iter().enumerate().fold(0u32, |n, (i, b)| n | u32::from(*b) << (16 - 8 * i)); out.extend((0..=chunk.len()).map(|i| A[(n >> (18 - 6 * i) & 63) as usize] as char)); } out }