node: Function to load quorum identity

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-09-19 22:08:52 +02:00
parent 2f07e39312
commit bc226a9c38
No known key found for this signature in database
2 changed files with 60 additions and 20 deletions

View File

@ -368,8 +368,8 @@ pub enum IdentityError {
Git(#[from] git::Error), Git(#[from] git::Error),
#[error("verification: {0}")] #[error("verification: {0}")]
Verification(#[from] VerificationError), Verification(#[from] VerificationError),
#[error("root hash `{0}` does not match repository")] #[error("root hash `{0}` does not match project")]
MismatchedRoot(Id), MismatchedRoot(Oid),
#[error("commit signature for {0} is invalid: {1}")] #[error("commit signature for {0} is invalid: {1}")]
InvalidSignature(PublicKey, crypto::Error), InvalidSignature(PublicKey, crypto::Error),
#[error("quorum not reached: {0} signatures for a threshold of {1}")] #[error("quorum not reached: {0} signatures for a threshold of {1}")]
@ -377,13 +377,13 @@ pub enum IdentityError {
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Identity<V> { pub struct Identity<I> {
/// The head of the identity branch. This points to a commit that /// The head of the identity branch. This points to a commit that
/// contains the current document blob. /// contains the current document blob.
pub head: Oid, pub head: Oid,
/// The canonical identifier for this identity. /// The canonical identifier for this identity.
/// This is the object id of the initial document blob. /// This is the object id of the initial document blob.
pub root: Id, pub root: I,
/// The object id of the current document blob. /// The object id of the current document blob.
pub current: Oid, pub current: Oid,
/// Revision number. The initial document has a revision of `0`. /// Revision number. The initial document has a revision of `0`.
@ -392,33 +392,43 @@ pub struct Identity<V> {
pub doc: Doc<Verified>, pub doc: Doc<Verified>,
/// Signatures over this identity. /// Signatures over this identity.
pub signatures: HashMap<PublicKey, Signature>, pub signatures: HashMap<PublicKey, Signature>,
}
verified: PhantomData<V>, impl Identity<Oid> {
pub fn verified(self, id: Id) -> Result<Identity<Id>, IdentityError> {
// The root hash must be equal to the id.
if self.root != *id {
return Err(IdentityError::MismatchedRoot(self.root));
}
Ok(Identity {
root: id,
head: self.head,
current: self.current,
revision: self.revision,
doc: self.doc,
signatures: self.signatures,
})
}
} }
impl Identity<Untrusted> { impl Identity<Untrusted> {
pub fn load<'r, R: ReadRepository<'r>>( pub fn load<'r, R: ReadRepository<'r>>(
id: &Id,
remote: &RemoteId, remote: &RemoteId,
repo: &R, repo: &R,
) -> Result<Option<Identity<Trusted>>, IdentityError> { ) -> Result<Option<Identity<Oid>>, IdentityError> {
if let Some(head) = Doc::<Untrusted>::head(remote, repo)? { if let Some(head) = Doc::<Untrusted>::head(remote, repo)? {
let mut history = repo.revwalk(head)?.collect::<Vec<_>>(); let mut history = repo.revwalk(head)?.collect::<Vec<_>>();
// Retrieve root document. // Retrieve root document.
let root_oid = history.pop().unwrap()?.into(); let root_oid = history.pop().unwrap()?.into();
let root_blob = Doc::blob_at(root_oid, repo)?.unwrap(); let root_blob = Doc::blob_at(root_oid, repo)?.unwrap();
let root = Id::from(root_blob.id()); let root: git::Oid = root_blob.id().into();
let trusted = Doc::from_json(root_blob.content()).unwrap(); let trusted = Doc::from_json(root_blob.content()).unwrap();
let revision = history.len() as u32; let revision = history.len() as u32;
// The root hash must be equal to the id.
if root != *id {
return Err(IdentityError::MismatchedRoot(root));
}
let mut trusted = trusted.verified()?; let mut trusted = trusted.verified()?;
let mut current = *root; let mut current = root;
let mut signatures = Vec::new(); let mut signatures = Vec::new();
// Traverse the history chronologically. // Traverse the history chronologically.
@ -458,7 +468,6 @@ impl Identity<Untrusted> {
revision, revision,
doc: trusted, doc: trusted,
signatures: signatures.into_iter().collect(), signatures: signatures.into_iter().collect(),
verified: PhantomData,
})); }));
} }
Ok(None) Ok(None)
@ -558,8 +567,10 @@ mod test {
}) })
.unwrap(); .unwrap();
let identity = Identity::load(&id, alice.public_key(), &repo) let identity: Identity<Id> = Identity::load(alice.public_key(), &repo)
.unwrap() .unwrap()
.unwrap()
.verified(id.clone())
.unwrap(); .unwrap();
assert_eq!(identity.signatures.len(), 2); assert_eq!(identity.signatures.len(), 2);

View File

@ -7,9 +7,9 @@ use once_cell::sync::Lazy;
pub use radicle_git_ext::Oid; pub use radicle_git_ext::Oid;
use crate::crypto::{Signer, Verified}; use crate::crypto::{Signer, Unverified, Verified};
use crate::git; use crate::git;
use crate::identity; use crate::identity::{self, Doc};
use crate::identity::{Id, Project}; use crate::identity::{Id, Project};
use crate::storage::refs; use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs}; use crate::storage::refs::{Refs, SignedRefs};
@ -50,7 +50,7 @@ impl ReadStorage for Storage {
// Perhaps for checking we could have a `contains` method? // Perhaps for checking we could have a `contains` method?
let repo = self.repository(proj)?; let repo = self.repository(proj)?;
if let Some(doc) = repo.identity(remote)? { if let Some(doc) = repo.identity_of(remote)? {
let remotes = repo.remotes()?.collect::<Result<_, _>>()?; let remotes = repo.remotes()?.collect::<Result<_, _>>()?;
let path = repo.path().to_path_buf(); let path = repo.path().to_path_buf();
@ -252,7 +252,7 @@ impl Repository {
Ok(()) Ok(())
} }
pub fn identity( pub fn identity_of(
&self, &self,
remote: &RemoteId, remote: &RemoteId,
) -> Result<Option<identity::Doc<Verified>>, refs::Error> { ) -> Result<Option<identity::Doc<Verified>>, refs::Error> {
@ -262,6 +262,35 @@ impl Repository {
Ok(None) Ok(None)
} }
} }
pub fn identity(&self) -> Result<identity::Doc<Unverified>, refs::Error> {
let mut heads = Vec::new();
for remote in self.remote_ids()? {
let remote = remote?;
let oid = Doc::<Unverified>::head(&remote, self)?.unwrap();
heads.push(*oid);
}
let oid = self.raw().merge_base_many(&heads)?;
let (doc, _) = Doc::load_at(oid.into(), self)?.ok_or(refs::Error::NotFound)?;
Ok(doc)
}
pub fn remote_ids(
&self,
) -> Result<impl Iterator<Item = Result<RemoteId, refs::Error>> + '_, git2::Error> {
let iter = self.backend.references_glob(SIGNATURES_GLOB.as_str())?.map(
|reference| -> Result<RemoteId, refs::Error> {
let r = reference?;
let name = r.name().ok_or(refs::Error::InvalidRef)?;
let (id, _) = git::parse_ref::<RemoteId>(name)?;
Ok(id)
},
);
Ok(iter)
}
} }
impl<'r> ReadRepository<'r> for Repository { impl<'r> ReadRepository<'r> for Repository {