use abi::records::temp::BadgeBlueKeys; use abi::repo::{create, create_record}; use abi::{AtUri, Call, Handle, LexiconType, On, Run}; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; use crate::calls::{attest, created, typed}; use crate::ids::CallId; use crate::input::{Args, Route, Step, did}; use crate::nsid::ASSOCIATE_CONFIRM; #[derive(Default, Serialize, Deserialize)] pub struct BindKey { key: String, } pub fn bind_key(run: Run) -> Result { let (mut step, on) = Step::::new(run)?; step.args.signed_in()?; let me = step.args.me(); match on { On::Start => step.out.call(CallId::Attest, attest(me, None)), On::Result(CallId::Attest, result) => { let key = result?["key"].as_str().filter(|k| k.starts_with("did:key:")).ok_or("attest must return a did:key")?.to_string(); step.out.call(CallId::Create, bind(me, &key, step.args.created_at()?)); step.state.key = key; } On::Result(CallId::Create, result) => return finish(step, result), _ => {} } step.handle() } /// The badgeBlueKeys record binding `key` (this host's attestation key) to `me`, at the rkey verifiers read. pub fn bind(me: &str, key: &str, created_at: abi::Datetime) -> Call { let badge = BadgeBlueKeys { key_id: key.into(), name: Some("socialweb.computer requester attestation".into()), challenge: me.into(), service: Some("market_requester".into()), created_at, }; create_record(me, BadgeBlueKeys::lexicon_type(), Some(&badge_rkey(me, key)), typed(badge)) } /// Where verifiers look for `key`'s binding to `me`. pub fn binding(me: &str, key: &str) -> AtUri { AtUri::new(me, BadgeBlueKeys::lexicon_type(), badge_rkey(me, key)) } #[derive(Default, Serialize, Deserialize)] pub struct Associate { peer: String, role: String, } // Old: did-key-associator main.js doRequesterAssociate / doBidderAssociate. pub fn associate(run: Run) -> Result { let (mut step, on) = Step::::new(run)?; step.args.signed_in()?; let me = step.args.me(); match on { On::Start => { let peer = did(step.args.arg("peer"), "peer")?; let role = match step.args.arg("role").as_str().unwrap_or("requester") { role @ ("requester" | "bidder") => role.to_string(), _ => return Err("role must be requester or bidder".into()), }; step.out.call(CallId::Confirm, Route::Proxy(format!("{peer}#{role}_associate")).call(ASSOCIATE_CONFIRM, json!({}))); step.state = Associate { peer, role }; } On::Result(CallId::Confirm, result) => { result?; let Associate { peer, role } = &step.state; let badge = BadgeBlueKeys { key_id: peer.clone(), name: Some(format!("{role}-{}", &peer[peer.len().saturating_sub(8)..])), challenge: me.into(), service: Some(format!("{role}_associate")), created_at: step.args.created_at()?, }; step.out.call(CallId::Create, create(me, badge)); } On::Result(CallId::Create, result) => return finish(step, result), _ => {} } step.handle() } // The state plus the created record's strong ref. fn finish Deserialize<'de> + Default>(mut step: Step, result: Result) -> Result { let mut answer = json!(step.state); answer["record"] = json!(created(result)?); step.out.finish(answer); step.handle() } fn base58btc(bytes: &[u8]) -> String { const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; let mut digits: Vec = Vec::new(); for &byte in bytes { let mut carry = byte as u32; for digit in digits.iter_mut() { carry += (*digit as u32) << 8; *digit = (carry % 58) as u8; carry /= 58; } while carry > 0 { digits.push((carry % 58) as u8); carry /= 58; } } let zeros = bytes.iter().take_while(|&&b| b == 0).count(); "1".repeat(zeros) + &digits.iter().rev().map(|&d| ALPHABET[d as usize] as char).collect::() } // Verifiers (market-atproto keyBoundByBadgeBlueKey) getRecord exactly this rkey: // base58btc(sha256("did:keyId")[0..24])[0..32]. fn badge_rkey(did: &str, key: &str) -> String { base58btc(&Sha256::digest(format!("{did}:{key}").as_bytes())[..24]).chars().take(32).collect() }