use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::crypto::{PublicKey, Secret, SecretKey, SecretShare, Signature};
use crate::linked_data::{BlockEncoded, CodecError, DagCborCodec, Link};
use crate::version::Version;
use super::principal::{Principal, PrincipalRole};
#[derive(Debug, thiserror::Error)]
pub enum ManifestError {
#[error("codec error: {0}")]
Codec(#[from] CodecError),
#[error("signature verification failed")]
SignatureVerificationFailed,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Share {
principal: Principal,
share: Option<SecretShare>,
}
impl Share {
pub fn new_owner(share: SecretShare, public_key: PublicKey) -> Self {
Self {
principal: Principal {
role: PrincipalRole::Owner,
identity: public_key,
},
share: Some(share),
}
}
pub fn new_mirror(public_key: PublicKey) -> Self {
Self {
principal: Principal {
role: PrincipalRole::Mirror,
identity: public_key,
},
share: None,
}
}
pub fn principal(&self) -> &Principal {
&self.principal
}
pub fn share(&self) -> Option<&SecretShare> {
self.share.as_ref()
}
pub fn role(&self) -> &PrincipalRole {
&self.principal.role
}
pub fn set_share(&mut self, share: SecretShare) {
self.share = Some(share);
}
}
pub type Shares = BTreeMap<String, Share>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Manifest {
id: Uuid,
name: String,
height: u64,
version: Version,
shares: Shares,
entry: Link,
pins: Link,
previous: Option<Link>,
#[serde(default, skip_serializing_if = "Option::is_none")]
ops_log: Option<Link>,
#[serde(default, skip_serializing_if = "Option::is_none")]
public: Option<Secret>,
#[serde(default, skip_serializing_if = "Option::is_none")]
author: Option<PublicKey>,
#[serde(default, skip_serializing_if = "Option::is_none")]
signature: Option<Signature>,
}
impl BlockEncoded<DagCborCodec> for Manifest {}
impl Manifest {
pub fn new(
id: Uuid,
name: String,
owner: PublicKey,
share: SecretShare,
entry: Link,
pins: Link,
height: u64,
) -> Self {
Manifest {
id,
name,
shares: BTreeMap::from([(
owner.to_hex(),
Share {
principal: Principal {
role: PrincipalRole::Owner,
identity: owner,
},
share: Some(share),
},
)]),
entry,
pins,
previous: None,
height,
version: Version::default(),
ops_log: None,
public: None,
author: None,
signature: None,
}
}
pub fn id(&self) -> &Uuid {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn version(&self) -> &Version {
&self.version
}
pub fn entry(&self) -> &Link {
&self.entry
}
pub fn pins(&self) -> &Link {
&self.pins
}
pub fn previous(&self) -> &Option<Link> {
&self.previous
}
pub fn height(&self) -> u64 {
self.height
}
pub fn ops_log(&self) -> Option<&Link> {
self.ops_log.as_ref()
}
pub fn shares(&self) -> &BTreeMap<String, Share> {
&self.shares
}
pub fn shares_mut(&mut self) -> &mut BTreeMap<String, Share> {
&mut self.shares
}
pub fn get_share(&self, public_key: &PublicKey) -> Option<&Share> {
self.shares.get(&public_key.to_hex())
}
pub fn get_peer_ids(&self) -> Vec<PublicKey> {
self.shares
.iter()
.filter_map(|(key_hex, _)| PublicKey::from_hex(key_hex).ok())
.collect()
}
pub fn get_shares_by_role(&self, role: PrincipalRole) -> Vec<&Share> {
self.shares.values().filter(|s| *s.role() == role).collect()
}
pub fn is_published(&self) -> bool {
self.public.is_some()
}
pub fn public(&self) -> Option<&Secret> {
self.public.as_ref()
}
pub fn author(&self) -> Option<&PublicKey> {
self.author.as_ref()
}
pub fn signature(&self) -> Option<&Signature> {
self.signature.as_ref()
}
pub fn is_signed(&self) -> bool {
self.author.is_some() && self.signature.is_some()
}
pub fn set_entry(&mut self, entry: Link) {
self.entry = entry;
}
pub fn set_pins(&mut self, pins_link: Link) {
self.pins = pins_link;
}
pub fn set_previous(&mut self, previous: Link) {
self.previous = Some(previous);
}
pub fn set_height(&mut self, height: u64) {
self.height = height;
}
pub fn set_ops_log(&mut self, link: Link) {
self.ops_log = Some(link);
}
pub fn clear_ops_log(&mut self) {
self.ops_log = None;
}
pub fn add_share(&mut self, share: Share) {
let key = share.principal().identity.to_hex();
self.shares.insert(key, share);
}
pub fn publish(&mut self, secret: &Secret) {
self.public = Some(secret.clone());
}
pub fn unpublish(&mut self) {
self.public = None;
}
pub fn sign(&mut self, secret_key: &SecretKey) -> Result<(), ManifestError> {
self.author = Some(secret_key.public());
self.signature = None; let bytes = self.signable_bytes()?;
let signature = secret_key.sign(&bytes);
self.signature = Some(signature);
Ok(())
}
pub fn verify_signature(&self) -> Result<bool, ManifestError> {
let (author, signature) = match (self.author.as_ref(), self.signature.as_ref()) {
(Some(a), Some(s)) => (a, s),
_ => return Ok(false), };
let bytes = self.signable_bytes()?;
author
.verify(&bytes, signature)
.map_err(|_| ManifestError::SignatureVerificationFailed)?;
Ok(true)
}
fn signable_bytes(&self) -> Result<Vec<u8>, ManifestError> {
let mut signable = self.clone();
signable.signature = None; Ok(signable.encode()?) }
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)]
use crate::crypto::{PublicKey, Secret};
use crate::linked_data::Link;
fn create_test_manifest() -> Manifest {
let secret_key = SecretKey::generate();
let public_key = secret_key.public();
let share = SecretShare::default();
let entry_link = Link::default();
let pins_link = Link::default();
Manifest::new(
uuid::Uuid::new_v4(),
"test-bucket".to_string(),
public_key,
share,
entry_link,
pins_link,
0,
)
}
#[test]
fn test_share_serialize() {
use ipld_core::codec::Codec;
use serde_ipld_dagcbor::codec::DagCborCodec;
let share = SecretShare::default();
let encoded = DagCborCodec::encode_to_vec(&share).unwrap();
let decoded: SecretShare = DagCborCodec::decode_from_slice(&encoded).unwrap();
assert_eq!(share, decoded);
}
#[test]
fn test_principal_serialize() {
use ipld_core::codec::Codec;
use serde_ipld_dagcbor::codec::DagCborCodec;
let public_key = crate::crypto::SecretKey::generate().public();
let principal = Principal {
role: PrincipalRole::Owner,
identity: public_key,
};
let encoded = DagCborCodec::encode_to_vec(&principal).unwrap();
let decoded: Principal = DagCborCodec::decode_from_slice(&encoded).unwrap();
assert_eq!(principal, decoded);
}
#[test]
fn test_manifest_signing() {
let secret_key = SecretKey::generate();
let mut manifest = create_test_manifest();
assert!(!manifest.is_signed());
assert!(manifest.author().is_none());
assert!(manifest.signature().is_none());
manifest.sign(&secret_key).unwrap();
assert!(manifest.is_signed());
assert_eq!(manifest.author(), Some(&secret_key.public()));
assert!(manifest.signature().is_some());
assert!(manifest.verify_signature().unwrap());
}
#[test]
fn test_manifest_tamper_detection() {
let secret_key = SecretKey::generate();
let mut manifest = create_test_manifest();
manifest.sign(&secret_key).unwrap();
assert!(manifest.verify_signature().unwrap());
manifest.set_height(999);
let result = manifest.verify_signature();
assert!(result.is_err());
}
#[test]
fn test_unsigned_manifest_backwards_compatibility() {
use ipld_core::codec::Codec;
use serde_ipld_dagcbor::codec::DagCborCodec;
let manifest = create_test_manifest();
assert!(!manifest.is_signed());
let encoded = DagCborCodec::encode_to_vec(&manifest).unwrap();
let decoded: Manifest = DagCborCodec::decode_from_slice(&encoded).unwrap();
assert!(!decoded.is_signed());
assert!(decoded.author().is_none());
assert!(decoded.signature().is_none());
assert!(!decoded.verify_signature().unwrap());
}
#[test]
fn test_manifest_wrong_key_verification() {
let secret_key1 = SecretKey::generate();
let secret_key2 = SecretKey::generate();
let mut manifest = create_test_manifest();
manifest.sign(&secret_key1).unwrap();
assert!(manifest.verify_signature().unwrap());
assert_eq!(manifest.author(), Some(&secret_key1.public()));
assert_ne!(manifest.author(), Some(&secret_key2.public()));
}
}