use super::*;
use crate::artifact::Descriptor;
pub const PUBLICATION_BYTES: usize = 805;
#[derive(Clone, PartialEq, Eq)]
pub struct Publication {
record: Box<[u8; PUBLICATION_BYTES]>,
pub(crate) binding: PublicationBinding,
}
impl fmt::Debug for Publication {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Publication")
.field("commitment", &self.commitment())
.field("binding", &self.binding)
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PublicationBinding {
pub vault_id: [u8; 32],
pub genesis_commitment: [u8; 32],
pub bootstrap_id: [u8; 32],
pub stream_id: [u8; 32],
pub descriptor_commitment: [u8; 32],
pub manifest_commitment: [u8; 32],
pub prefix_count: u64,
}
impl PublicationBinding {
pub fn from_descriptor(genesis: &Genesis, descriptor: &[u8]) -> Result<Self> {
let d = Descriptor::decode(descriptor)?;
let b = &d;
ensure!(
b.vault == genesis.context().vault_id
&& b.generation == genesis.context().generation_id
&& b.membership == genesis.commitment(),
"error bootstrap-publication-context"
);
Ok(Self {
vault_id: b.vault,
genesis_commitment: b.membership,
bootstrap_id: b.bootstrap,
stream_id: b.stream,
descriptor_commitment: hash(descriptor),
manifest_commitment: d.manifest.aggregate,
prefix_count: d.prefix,
})
}
pub(super) fn tuple(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(136);
for value in [
self.bootstrap_id,
self.stream_id,
self.descriptor_commitment,
self.manifest_commitment,
] {
out.extend(value);
}
out.extend(self.prefix_count.to_be_bytes());
out
}
}
pub(super) fn components(
genesis: &Genesis,
binding: &PublicationBinding,
) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let (_, original, _, _) = codec::components(genesis.record()).expect("verified genesis");
let mut state = b"AVGS\0\x02\x01".to_vec();
state.extend(genesis.context().vault_id);
state.push(1);
state.extend(binding.tuple());
state.extend_from_slice(&original[40..]);
let mut body = b"AVPB\0\x01".to_vec();
body.extend(binding.tuple());
let core = membership_core(
&genesis.context().vault_id,
1,
&genesis.commitment(),
&genesis.device_id(),
2,
&body,
&state,
);
(core, state, b"AVGA\0\x02\0".to_vec())
}
impl Publication {
pub fn from_record(genesis: &Genesis, descriptor: &[u8], record: &[u8]) -> Result<Self> {
ensure!(
record.len() == PUBLICATION_BYTES,
"error bootstrap-publication-invalid"
);
let binding = PublicationBinding::from_descriptor(genesis, descriptor)?;
let (core, state, attachments) = components(genesis, &binding);
let mut r = Reader(record);
valid(r.take(1)? == [1])?;
valid(r.blob(301)? == core)?;
valid(r.blob(416)? == state)?;
valid(r.blob(7)? == attachments)?;
let signature = ed25519_dalek::Signature::from_slice(r.blob(64)?)?;
r.end()?;
ed25519_dalek::VerifyingKey::from_bytes(&genesis.signing_public())?
.verify_strict(&cce(MEMBERSHIP_SIGN, &[&core, &attachments]), &signature)
.map_err(|_| anyhow::anyhow!("error bootstrap-publication-signature"))?;
Ok(Self {
record: Box::new(record.try_into()?),
binding,
})
}
pub fn record(&self) -> &[u8; PUBLICATION_BYTES] {
&self.record
}
pub fn commitment(&self) -> [u8; 32] {
hash(self.record.as_slice())
}
pub fn binding(&self) -> &PublicationBinding {
&self.binding
}
}
impl SeedAuthority {
pub fn sign_authenticated_publication(
&self,
descriptor: &[u8],
key: &LocalSharedStatePackageKey,
) -> Result<Publication> {
self.validate(self.genesis().context(), key)?;
let binding = PublicationBinding::from_descriptor(self.genesis(), descriptor)?;
let (core, state, attachments) = components(self.genesis(), &binding);
let signature = SigningKey::from_bytes(self.protected_signing_seed().expose())
.sign(&cce(MEMBERSHIP_SIGN, &[&core, &attachments]));
let mut record = vec![1];
for part in [&core[..], &state, &attachments, &signature.to_bytes()] {
bytes(&mut record, part);
}
Publication::from_record(self.genesis(), descriptor, &record)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicationOutcome {
pub(crate) publication: Publication,
}
impl PublicationOutcome {
pub fn from_publication(publication: Publication) -> Self {
Self { publication }
}
pub fn from_response(genesis: &Genesis, descriptor: &[u8], record: &[u8]) -> Result<Self> {
Ok(Self {
publication: Publication::from_record(genesis, descriptor, record)?,
})
}
pub fn publication(&self) -> &Publication {
&self.publication
}
pub fn validate_expected(&self, genesis: &Genesis, expected_descriptor: &[u8]) -> Result<()> {
let b = &self.publication.binding;
ensure!(
b.genesis_commitment == genesis.commitment()
&& b.descriptor_commitment == hash(expected_descriptor),
"error bootstrap-publication-conflict"
);
Ok(())
}
}
#[cfg(any(test, feature = "test-support"))]
pub(super) mod fixture;
#[cfg(test)]
pub(super) mod tests;