1use base64::{engine::general_purpose::STANDARD, Engine};
2use ed25519_dalek::{Signer, SigningKey};
3use serde_json::{json, Map, Value};
4
5use crate::canonical::{canonical, hash_canonical};
6use crate::error::Error;
7
8#[derive(Clone)]
9pub struct SignOpts {
10 pub privkey: [u8; 32],
11 pub pubkey: [u8; 32],
12}
13
14pub fn seal(turn: &Value, sign: Option<&SignOpts>) -> Result<Value, Error> {
15 let h = hash_canonical(turn)?;
16 let mut out = turn
17 .as_object()
18 .cloned()
19 .ok_or_else(|| Error::Invalid("turn not object".into()))?;
20 out.insert("hash".into(), json!(h));
21 if let Some(s) = sign {
22 let sk = SigningKey::from_bytes(&s.privkey);
23 let canonical_bytes = canonical(turn)?;
24 let sig = sk.sign(&canonical_bytes);
25 out.insert(
26 "sig".into(),
27 json!({
28 "alg": "ed25519",
29 "pubkey": STANDARD.encode(s.pubkey),
30 "sig": STANDARD.encode(sig.to_bytes()),
31 }),
32 );
33 }
34 Ok(Value::Object(out))
35}
36
37pub fn seal_chain(turns: &[Value], sign: Option<&SignOpts>) -> Result<Vec<Value>, Error> {
38 let mut out = Vec::with_capacity(turns.len());
39 let mut prev: Option<String> = None;
40 for t in turns {
41 let mut linked: Map<String, Value> = t
42 .as_object()
43 .cloned()
44 .ok_or_else(|| Error::Invalid("turn not object".into()))?;
45 if let Some(p) = &prev {
46 if !linked.contains_key("prev_hash") {
47 linked.insert("prev_hash".into(), json!(p));
48 }
49 }
50 let linked_v = Value::Object(linked);
51 let sealed = seal(&linked_v, sign)?;
52 prev = sealed
53 .get("hash")
54 .and_then(|v| v.as_str())
55 .map(String::from);
56 out.push(sealed);
57 }
58 Ok(out)
59}