jax-common 0.1.11

Core data structures and cryptography for JaxBucket - end-to-end encrypted P2P storage
Documentation
use std::fmt::Display;

use iroh::NodeAddr;
use iroh_blobs::{ticket::BlobTicket, BlobFormat, Hash, HashAndFormat};
use serde::{Deserialize, Serialize};

use crate::crypto::PublicKey;

use super::ipld::{Cid, LinkedData, Multihash, BLAKE3_HASH_CODE, LD_CBOR_CODEC, LD_RAW_CODEC};

/// A Link is a wrapper around a CID (Content Identifier)
/// representing a reference to content stored via the iroh-blobs protocol.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Link(Cid);

impl Display for Link {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.hash())
    }
}

/**
 * Links
 * =====
 * A Link is a reference to a block of data stored
 *  by the iroh-blobs protocol.
 * Links are implemented as Blake3 only multihashes wrapped in CIDs.
 * CIDs encoding is used to communicate the expected encoding of
 *  the referenced content, while HashAndFormat is used for the
 *  retrieval of data over the iroh_blobs protocol.
 * For our purposes, we keep the following assumptions:
 *  - hashes are always blake3, and usually generated by iroh-blobs
 *  - links may either be RAW or DAG-CBOR encoded depending on whether they
 *   point to:
 *    - public raw data (RAW)
 *    - private raw data (RAW)
 *    - private structured data (RAW, but underlying format is DAG-CBOR)
 *    - public structured data (DAG-CBOR)
 */
#[allow(clippy::doc_overindented_list_items)]
#[allow(clippy::doc_lazy_continuation)]
impl Default for Link {
    fn default() -> Self {
        let hash = Hash::from_bytes([0; 32]);
        let mh = Multihash::wrap(BLAKE3_HASH_CODE, hash.as_bytes()).expect("valid blake3 hash");
        Link(Cid::new_v1(LD_RAW_CODEC, mh))
    }
}

impl From<Link> for Cid {
    fn from(val: Link) -> Self {
        val.0
    }
}

impl From<Cid> for Link {
    fn from(val: Cid) -> Self {
        let hash = val.hash();
        let code = hash.code();
        let codec = val.codec();
        // panic if we don't have a blake3 hash, this means
        //  it was not generated by our protocol
        if code != BLAKE3_HASH_CODE {
            panic!("invalid hash code");
        }
        // check we have one of our supported codecs
        if codec != LD_RAW_CODEC && codec != LD_CBOR_CODEC {
            panic!("unsupported codec");
        }

        Link(val)
    }
}

impl From<Link> for LinkedData {
    fn from(val: Link) -> Self {
        LinkedData::Link(val.0)
    }
}

impl From<Link> for HashAndFormat {
    fn from(val: Link) -> Self {
        HashAndFormat {
            hash: val.hash(),
            format: BlobFormat::Raw,
        }
    }
}

impl From<Link> for Hash {
    fn from(val: Link) -> Self {
        val.hash()
    }
}

impl Link {
    /// Create a new Link from a codec and hash
    pub fn new(codec: u64, hash: Hash) -> Self {
        let mh = Multihash::wrap(BLAKE3_HASH_CODE, hash.as_bytes()).expect("valid blake3 hash");
        Link(Cid::new_v1(codec, mh))
    }

    /// Get the codec of this link
    pub fn codec(&self) -> u64 {
        self.0.codec()
    }

    /// Get the hash of this link
    pub fn hash(&self) -> Hash {
        let hash_digest = self.0.hash().digest();
        let mut hash_bytes: [u8; 32] = [0; 32];
        hash_bytes.copy_from_slice(hash_digest);
        Hash::from_bytes(hash_bytes)
    }

    /// Get the underlying CID
    pub fn cid(&self) -> &Cid {
        &self.0
    }

    /// Create a ticket for this link with an optional blob format
    ///
    /// By default uses BlobFormat::Raw. For HashSeq collections, pass BlobFormat::HashSeq.
    pub fn ticket(&self, source: PublicKey, format: Option<BlobFormat>) -> BlobTicket {
        let node_addr = NodeAddr::new(*source);
        BlobTicket::new(node_addr, self.hash(), format.unwrap_or(BlobFormat::Raw))
    }
}