Skip to main content

triblespace_core/repo/
commit.rs

1use crate::macros::entity;
2use crate::value::TryToValue;
3use crate::macros::pattern;
4use ed25519::Signature;
5use ed25519_dalek::SignatureError;
6use ed25519_dalek::SigningKey;
7use ed25519_dalek::Verifier;
8use ed25519_dalek::VerifyingKey;
9use itertools::Itertools;
10
11use ed25519::signature::Signer;
12
13use crate::blob::schemas::longstring::LongString;
14use crate::blob::schemas::simplearchive::SimpleArchive;
15use crate::blob::Blob;
16use crate::prelude::valueschemas::Handle;
17use crate::query::find;
18use crate::trible::TribleSet;
19use crate::value::Value;
20
21use crate::value::schemas::hash::Blake3;
22use hifitime::Epoch;
23
24/// Error returned when commit signature verification fails.
25pub enum ValidationError {
26    /// The metadata contains multiple signature entities for the same commit.
27    AmbiguousSignature,
28    /// No signature information was found in the metadata.
29    MissingSignature,
30    /// The signature did not match the commit bytes or the public key was invalid.
31    FailedValidation,
32}
33
34impl From<SignatureError> for ValidationError {
35    /// Converts an Ed25519 signature error into a [`ValidationError::FailedValidation`].
36    fn from(_: SignatureError) -> Self {
37        ValidationError::FailedValidation
38    }
39}
40
41/// Constructs commit metadata describing `content`, optional `metadata`, and its parent commits.
42///
43/// The resulting [`TribleSet`] is signed using `signing_key` so that its
44/// authenticity can later be verified. If `msg` is provided it is stored as a
45/// long commit message via a LongString blob handle. If `metadata` is provided
46/// it is stored as a SimpleArchive handle.
47pub fn commit_metadata(
48    signing_key: &SigningKey,
49    parents: impl IntoIterator<Item = Value<Handle<Blake3, SimpleArchive>>>,
50    msg: Option<Value<Handle<Blake3, LongString>>>,
51    content: Option<Blob<SimpleArchive>>,
52    metadata: Option<Value<Handle<Blake3, SimpleArchive>>>,
53) -> TribleSet {
54    let mut commit = TribleSet::new();
55    let commit_entity = crate::id::rngid();
56    let now = Epoch::now().expect("system time");
57
58    commit += entity! { &commit_entity @  super::timestamp: (now, now).try_to_value().expect("point interval")  };
59
60    if let Some(content) = content {
61        let handle = content.get_handle();
62        let signature = signing_key.sign(&content.bytes);
63
64        commit += entity! { &commit_entity @
65           super::content: handle,
66           super::signed_by: signing_key.verifying_key(),
67           super::signature_r: signature,
68           super::signature_s: signature,
69        };
70    }
71
72    if let Some(h) = msg {
73        commit += entity! { &commit_entity @
74           super::message: h,
75        };
76    }
77
78    if let Some(handle) = metadata {
79        commit += entity! { &commit_entity @
80           super::metadata: handle,
81        };
82    }
83
84    for parent in parents {
85        commit += entity! { &commit_entity @
86           super::parent: parent,
87        };
88    }
89
90    commit
91}
92
93/// Validates that the `metadata` blob genuinely signs the supplied commit
94/// `content`.
95///
96/// Returns an error if the signature information is missing, malformed or does
97/// not match the commit bytes.
98pub fn verify(content: Blob<SimpleArchive>, metadata: TribleSet) -> Result<(), ValidationError> {
99    let handle = content.get_handle();
100    let (pubkey, r, s) = match find!(
101    (pubkey: Value<_>, r, s),
102    pattern!(&metadata, [
103    {
104        super::content: handle,
105        super::signed_by: ?pubkey,
106        super::signature_r: ?r,
107        super::signature_s: ?s
108    }]))
109    .at_most_one()
110    {
111        Ok(Some(result)) => result,
112        Ok(None) => return Err(ValidationError::MissingSignature),
113        Err(_) => return Err(ValidationError::AmbiguousSignature),
114    };
115
116    let pubkey: VerifyingKey = pubkey.try_from_value()?;
117    let signature = Signature::from_components(r, s);
118    pubkey.verify(&content.bytes, &signature)?;
119    Ok(())
120}