1use super::*;
21use crate::artifact::Descriptor;
22
23pub const PUBLICATION_BYTES: usize = 805;
24
25#[derive(Clone, PartialEq, Eq)]
27pub struct Publication {
28 record: Box<[u8; PUBLICATION_BYTES]>,
29 pub(crate) binding: PublicationBinding,
30}
31
32impl fmt::Debug for Publication {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 f.debug_struct("Publication")
35 .field("commitment", &self.commitment())
36 .field("binding", &self.binding)
37 .finish()
38 }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct PublicationBinding {
43 pub vault_id: [u8; 32],
44 pub genesis_commitment: [u8; 32],
45 pub bootstrap_id: [u8; 32],
46 pub stream_id: [u8; 32],
47 pub descriptor_commitment: [u8; 32],
48 pub manifest_commitment: [u8; 32],
49 pub prefix_count: u64,
50}
51
52impl PublicationBinding {
53 pub fn from_descriptor(genesis: &Genesis, descriptor: &[u8]) -> Result<Self> {
54 let d = Descriptor::decode(descriptor)?;
55 let b = &d;
56 ensure!(
57 b.vault == genesis.context().vault_id
58 && b.generation == genesis.context().generation_id
59 && b.membership == genesis.commitment(),
60 "error bootstrap-publication-context"
61 );
62 Ok(Self {
63 vault_id: b.vault,
64 genesis_commitment: b.membership,
65 bootstrap_id: b.bootstrap,
66 stream_id: b.stream,
67 descriptor_commitment: hash(descriptor),
68 manifest_commitment: d.manifest.aggregate,
69 prefix_count: d.prefix,
70 })
71 }
72
73 pub(super) fn tuple(&self) -> Vec<u8> {
74 let mut out = Vec::with_capacity(136);
75 for value in [
76 self.bootstrap_id,
77 self.stream_id,
78 self.descriptor_commitment,
79 self.manifest_commitment,
80 ] {
81 out.extend(value);
82 }
83 out.extend(self.prefix_count.to_be_bytes());
84 out
85 }
86}
87
88pub(super) fn components(
89 genesis: &Genesis,
90 binding: &PublicationBinding,
91) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
92 let (_, original, _, _) = codec::components(genesis.record()).expect("verified genesis");
93 let mut state = b"AVGS\0\x02\x01".to_vec();
94 state.extend(genesis.context().vault_id);
95 state.push(1);
96 state.extend(binding.tuple());
97 state.extend_from_slice(&original[40..]);
99 let mut body = b"AVPB\0\x01".to_vec();
100 body.extend(binding.tuple());
101 let core = membership_core(
102 &genesis.context().vault_id,
103 1,
104 &genesis.commitment(),
105 &genesis.device_id(),
106 2,
107 &body,
108 &state,
109 );
110 (core, state, b"AVGA\0\x02\0".to_vec())
111}
112
113impl Publication {
114 pub fn from_record(genesis: &Genesis, descriptor: &[u8], record: &[u8]) -> Result<Self> {
117 ensure!(
118 record.len() == PUBLICATION_BYTES,
119 "error bootstrap-publication-invalid"
120 );
121 let binding = PublicationBinding::from_descriptor(genesis, descriptor)?;
122 let (core, state, attachments) = components(genesis, &binding);
123 let mut r = Reader(record);
124 valid(r.take(1)? == [1])?;
125 valid(r.blob(301)? == core)?;
126 valid(r.blob(416)? == state)?;
127 valid(r.blob(7)? == attachments)?;
128 let signature = ed25519_dalek::Signature::from_slice(r.blob(64)?)?;
129 r.end()?;
130 ed25519_dalek::VerifyingKey::from_bytes(&genesis.signing_public())?
131 .verify_strict(&cce(MEMBERSHIP_SIGN, &[&core, &attachments]), &signature)
132 .map_err(|_| anyhow::anyhow!("error bootstrap-publication-signature"))?;
133 Ok(Self {
134 record: Box::new(record.try_into()?),
135 binding,
136 })
137 }
138
139 pub fn record(&self) -> &[u8; PUBLICATION_BYTES] {
140 &self.record
141 }
142 pub fn commitment(&self) -> [u8; 32] {
143 hash(self.record.as_slice())
144 }
145 pub fn binding(&self) -> &PublicationBinding {
146 &self.binding
147 }
148}
149
150impl SeedAuthority {
151 pub fn sign_authenticated_publication(
154 &self,
155 descriptor: &[u8],
156 key: &LocalSharedStatePackageKey,
157 ) -> Result<Publication> {
158 self.validate(self.genesis().context(), key)?;
159 let binding = PublicationBinding::from_descriptor(self.genesis(), descriptor)?;
160 let (core, state, attachments) = components(self.genesis(), &binding);
161 let signature = SigningKey::from_bytes(self.protected_signing_seed().expose())
162 .sign(&cce(MEMBERSHIP_SIGN, &[&core, &attachments]));
163 let mut record = vec![1];
164 for part in [&core[..], &state, &attachments, &signature.to_bytes()] {
165 bytes(&mut record, part);
166 }
167 Publication::from_record(self.genesis(), descriptor, &record)
168 }
169}
170
171#[derive(Clone, Debug, PartialEq, Eq)]
174pub struct PublicationOutcome {
175 pub(crate) publication: Publication,
176}
177
178impl PublicationOutcome {
179 pub fn from_publication(publication: Publication) -> Self {
181 Self { publication }
182 }
183
184 pub fn from_response(genesis: &Genesis, descriptor: &[u8], record: &[u8]) -> Result<Self> {
188 Ok(Self {
189 publication: Publication::from_record(genesis, descriptor, record)?,
190 })
191 }
192
193 pub fn publication(&self) -> &Publication {
194 &self.publication
195 }
196
197 pub fn validate_expected(&self, genesis: &Genesis, expected_descriptor: &[u8]) -> Result<()> {
202 let b = &self.publication.binding;
203 ensure!(
204 b.genesis_commitment == genesis.commitment()
205 && b.descriptor_commitment == hash(expected_descriptor),
206 "error bootstrap-publication-conflict"
207 );
208 Ok(())
209 }
210}
211
212#[cfg(any(test, feature = "test-support"))]
213pub(super) mod fixture;
214#[cfg(test)]
215pub(super) mod tests;