use super::{SignatureAggregator, Signed, SignedShare};
use crate::{error::Result, messages::PlainMessageUtils};
use serde::{Serialize, Serializer};
use sn_messaging::node::Proposal;
use thiserror::Error;
pub trait ProposalUtils {
fn prove(
&self,
public_key_set: bls::PublicKeySet,
index: usize,
secret_key_share: &bls::SecretKeyShare,
) -> Result<SignedShare>;
fn as_signable(&self) -> SignableView;
}
impl ProposalUtils for Proposal {
fn prove(
&self,
public_key_set: bls::PublicKeySet,
index: usize,
secret_key_share: &bls::SecretKeyShare,
) -> Result<SignedShare> {
Ok(SignedShare::new(
public_key_set,
index,
secret_key_share,
&bincode::serialize(&self.as_signable()).map_err(|_| ProposalError::Invalid)?,
))
}
fn as_signable(&self) -> SignableView {
SignableView(self)
}
}
pub struct SignableView<'a>(pub &'a Proposal);
impl<'a> Serialize for SignableView<'a> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self.0 {
Proposal::Online { node_state, .. } => node_state.serialize(serializer),
Proposal::Offline(node_state) => node_state.serialize(serializer),
Proposal::SectionInfo(info) => info.serialize(serializer),
Proposal::OurElders(info) => info.signed.public_key.serialize(serializer),
Proposal::AccumulateAtSrc { message, .. } => {
message.as_signable().serialize(serializer)
}
Proposal::JoinsAllowed(joins_allowed) => joins_allowed.serialize(serializer),
}
}
}
#[derive(Default)]
pub(crate) struct ProposalAggregator(SignatureAggregator);
impl ProposalAggregator {
pub fn add(
&mut self,
proposal: Proposal,
signed_share: SignedShare,
) -> Result<(Proposal, Signed), ProposalError> {
let bytes =
bincode::serialize(&SignableView(&proposal)).map_err(|_| ProposalError::Invalid)?;
let signed = self.0.add(&bytes, signed_share)?;
Ok((proposal, signed))
}
}
#[derive(Debug, Error)]
pub enum ProposalError {
#[error("failed to aggregate signature shares: {0}")]
Aggregation(#[from] sn_messaging::node::Error),
#[error("invalid proposal")]
Invalid,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{dkg, section};
use anyhow::Result;
use std::fmt::Debug;
use xor_name::Prefix;
#[test]
fn serialize_for_signing() -> Result<()> {
let (section_auth, _, _) =
section::test_utils::gen_section_authority_provider(Prefix::default(), 4);
let proposal = Proposal::SectionInfo(section_auth.clone());
verify_serialize_for_signing(&proposal, §ion_auth)?;
let new_sk = bls::SecretKey::random();
let new_pk = new_sk.public_key();
let section_signed_auth = dkg::test_utils::section_signed(&new_sk, section_auth)?;
let proposal = Proposal::OurElders(section_signed_auth);
verify_serialize_for_signing(&proposal, &new_pk)?;
Ok(())
}
fn verify_serialize_for_signing<T>(proposal: &Proposal, should_serialize_as: &T) -> Result<()>
where
T: Serialize + Debug,
{
let actual = bincode::serialize(&SignableView(proposal))?;
let expected = bincode::serialize(should_serialize_as)?;
assert_eq!(
actual, expected,
"expected SignableView({:?}) to serialize same as {:?}, but didn't",
proposal, should_serialize_as
);
Ok(())
}
}