use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signer, SigningKey};
use serde_json::{json, Map, Value};
use crate::canonical::{canonical, hash_canonical};
use crate::error::Error;
#[derive(Clone)]
pub struct SignOpts {
pub privkey: [u8; 32],
pub pubkey: [u8; 32],
}
pub fn seal(turn: &Value, sign: Option<&SignOpts>) -> Result<Value, Error> {
let h = hash_canonical(turn)?;
let mut out = turn
.as_object()
.cloned()
.ok_or_else(|| Error::Invalid("turn not object".into()))?;
out.insert("hash".into(), json!(h));
if let Some(s) = sign {
let sk = SigningKey::from_bytes(&s.privkey);
let canonical_bytes = canonical(turn)?;
let sig = sk.sign(&canonical_bytes);
out.insert(
"sig".into(),
json!({
"alg": "ed25519",
"pubkey": STANDARD.encode(s.pubkey),
"sig": STANDARD.encode(sig.to_bytes()),
}),
);
}
Ok(Value::Object(out))
}
pub fn seal_chain(turns: &[Value], sign: Option<&SignOpts>) -> Result<Vec<Value>, Error> {
let mut out = Vec::with_capacity(turns.len());
let mut prev: Option<String> = None;
for t in turns {
let mut linked: Map<String, Value> = t
.as_object()
.cloned()
.ok_or_else(|| Error::Invalid("turn not object".into()))?;
if let Some(p) = &prev {
if !linked.contains_key("prev_hash") {
linked.insert("prev_hash".into(), json!(p));
}
}
let linked_v = Value::Object(linked);
let sealed = seal(&linked_v, sign)?;
prev = sealed
.get("hash")
.and_then(|v| v.as_str())
.map(String::from);
out.push(sealed);
}
Ok(out)
}