use crate::types::ProofReceiptPayload;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use serde_json::Value;
use std::collections::BTreeMap;
const PROOF_PAYLOAD_FIELDS: &[&str] = &[
"version",
"receiptId",
"correlationId",
"spatialAnchorId",
"spatialPlacementId",
"issuedAt",
"renderedAt",
"dwellMs",
"nonce",
"witness",
];
pub fn canonicalise_proof_payload(p: &ProofReceiptPayload) -> String {
let mut out = String::with_capacity(256);
out.push('{');
for (i, field) in PROOF_PAYLOAD_FIELDS.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push('"');
out.push_str(field);
out.push_str("\":");
let value_json = match *field {
"version" => serde_json::to_string(&p.version).unwrap(),
"receiptId" => serde_json::to_string(&p.receipt_id).unwrap(),
"correlationId" => serde_json::to_string(&p.correlation_id).unwrap(),
"spatialAnchorId" => serde_json::to_string(&p.spatial_anchor_id).unwrap(),
"spatialPlacementId" => {
serde_json::to_string(&p.spatial_placement_id).unwrap()
}
"issuedAt" => serde_json::to_string(&p.issued_at).unwrap(),
"renderedAt" => serde_json::to_string(&p.rendered_at).unwrap(),
"dwellMs" => serde_json::to_string(&p.dwell_ms).unwrap(),
"nonce" => serde_json::to_string(&p.nonce).unwrap(),
"witness" => serde_json::to_string(&p.witness).unwrap(),
_ => unreachable!(),
};
out.push_str(&value_json);
}
out.push('}');
out
}
pub fn canonicalise_proof_signing_input(p: &ProofReceiptPayload, key_id: &str) -> String {
format!("{}|{}", canonicalise_proof_payload(p), key_id)
}
pub fn canonical_sort_keys(value: &Value) -> String {
let normalised = normalise(value);
serde_json::to_string(&normalised).expect("infallible: normalised JSON")
}
fn normalise(value: &Value) -> Value {
match value {
Value::Object(map) => {
let mut sorted: BTreeMap<String, Value> = BTreeMap::new();
for (k, v) in map.iter() {
sorted.insert(k.clone(), normalise(v));
}
let mut out = serde_json::Map::new();
for (k, v) in sorted {
out.insert(k, v);
}
Value::Object(out)
}
Value::Array(items) => {
Value::Array(items.iter().map(normalise).collect())
}
other => other.clone(),
}
}
pub fn base64url_encode(bytes: &[u8]) -> String {
URL_SAFE_NO_PAD.encode(bytes)
}
pub fn base64url_decode(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
let trimmed = s.trim_end_matches('=');
URL_SAFE_NO_PAD.decode(trimmed)
}
pub fn sha256_prefixed(canonical: &str) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(canonical.as_bytes());
let bytes = hasher.finalize();
format!("sha256:{}", hex_encode(bytes.as_slice()))
}
fn hex_encode(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for b in bytes {
out.push(HEX[(*b >> 4) as usize] as char);
out.push(HEX[(*b & 0x0f) as usize] as char);
}
out
}