1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
//! Various accumulator variants of the DAPOL+ protocol.
//!
//! This is the top-most module in the hierarchy of the [dapol] crate. An
//! accumulator defines how the binary tree is built. There are different types
//! of accumulators, which can all be found under this module. Each accumulator
//! has different configuration requirements, which are detailed in each of the
//! sub-modules. The currently supported accumulator types are:
//! - [Non-Deterministic Mapping Sparse Merkle Tree]
//!
//! Accumulators can be constructed via the configuration parsers:
//! - [AccumulatorConfig] is used to deserialize config from a file (the
//! specific type of accumulator is determined from the config file). After
//! parsing the config the accumulator can be constructed.
//! - [NdmSmtConfigBuilder] is used to construct the
//! config for the NDM-SMT accumulator type using a builder pattern. The config
//! can then be parsed to construct an NDM-SMT.
//!
//! [Non-Deterministic Mapping Sparse Merkle Tree]: crate::accumulators::NdmSmt
use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::{
read_write_utils::{self, ReadWriteError},
utils::LogOnErr,
AggregationFactor, EntityId, InclusionProof,
};
mod config;
pub use config::{AccumulatorConfig, AccumulatorConfigError, AccumulatorParserError};
mod ndm_smt;
pub use ndm_smt::{
NdmSmt, NdmSmtConfig, NdmSmtConfigBuilder, NdmSmtError, NdmSmtConfigParserError, NdmSmtSecrets,
NdmSmtSecretsParser, RandomXCoordGenerator
};
const SERIALIZED_ACCUMULATOR_EXTENSION: &str = "dapoltree";
const SERIALIZED_ACCUMULATOR_FILE_PREFIX: &str = "accumulator_";
/// Various supported accumulator types.
///
/// Accumulators can be constructed via the configuration parsers:
/// - [AccumulatorConfig] is used to deserialize config from a file (the
/// specific type of accumulator is determined from the config file). After
/// parsing the config the accumulator can be constructed.
/// - [NdmSmtConfigBuilder] is used to construct the
/// config for the NDM-SMT accumulator type using a builder pattern. The config
/// can then be parsed to construct an NDM-SMT.
#[derive(Serialize, Deserialize)]
pub enum Accumulator {
NdmSmt(ndm_smt::NdmSmt),
// TODO other accumulators..
}
impl Accumulator {
/// Try deserialize an accumulator from the given file path.
///
/// The file is assumed to be in [bincode] format.
///
/// An error is logged and returned if
/// 1. The file cannot be opened.
/// 2. The [bincode] deserializer fails.
pub fn deserialize(path: PathBuf) -> Result<Accumulator, AccumulatorError> {
debug!(
"Deserializing accumulator from file {:?}",
path.clone().into_os_string()
);
match path.extension() {
Some(ext) => {
if ext != SERIALIZED_ACCUMULATOR_EXTENSION {
Err(ReadWriteError::UnsupportedFileExtension {
expected: SERIALIZED_ACCUMULATOR_EXTENSION.to_owned(),
actual: ext.to_os_string(),
})?;
}
}
None => Err(ReadWriteError::NotAFile(path.clone().into_os_string()))?,
}
let accumulator: Accumulator =
read_write_utils::deserialize_from_bin_file(path.clone()).log_on_err()?;
let root_hash = match &accumulator {
Accumulator::NdmSmt(ndm_smt) => ndm_smt.root_hash(),
};
info!(
"Successfully deserialized accumulator from file {:?} with root hash {:?}",
path.clone().into_os_string(),
root_hash
);
Ok(accumulator)
}
/// Parse `path` as one that points to a serialized dapol tree file.
///
/// `path` can be either of the following:
/// 1. Existing directory: in this case a default file name is appended to
/// `path`. 2. Non-existing directory: in this case all dirs in the path
/// are created, and a default file name is appended.
/// 3. File in existing dir: in this case the extension is checked to be
/// [SERIALIZED_ACCUMULATOR_EXTENSION], then `path` is returned.
/// 4. File in non-existing dir: dirs in the path are created and the file
/// extension is checked.
///
/// The file prefix is [SERIALIZED_ACCUMULATOR_FILE_PREFIX].
///
/// Example:
/// ```
/// use dapol::Accumulator;
/// use std::path::PathBuf;
///
/// let dir = PathBuf::from("./");
/// let path = Accumulator::parse_accumulator_serialization_path(dir).unwrap();
/// ```
pub fn parse_accumulator_serialization_path(path: PathBuf) -> Result<PathBuf, ReadWriteError> {
read_write_utils::parse_serialization_path(
path,
SERIALIZED_ACCUMULATOR_EXTENSION,
SERIALIZED_ACCUMULATOR_FILE_PREFIX,
)
}
/// Serialize to a file.
///
/// Serialization is done using [bincode]
///
/// An error is returned if
/// 1. [bincode] fails to serialize the file.
/// 2. There is an issue opening or writing the file.
pub fn serialize(&self, path: PathBuf) -> Result<(), AccumulatorError> {
info!(
"Serializing accumulator to file {:?}",
path.clone().into_os_string()
);
read_write_utils::serialize_to_bin_file(self, path).log_on_err()?;
Ok(())
}
/// Generate an inclusion proof for the given `entity_id`.
///
/// `aggregation_factor` is used to determine how many of the range proofs
/// are aggregated. Those that do not form part of the aggregated proof
/// are just proved individually. The aggregation is a feature of the
/// Bulletproofs protocol that improves efficiency.
///
/// `upper_bound_bit_length` is used to determine the upper bound for the
/// range proof, which is set to `2^upper_bound_bit_length` i.e. the
/// range proof shows `0 <= liability <= 2^upper_bound_bit_length` for
/// some liability. The type is set to `u8` because we are not expected
/// to require bounds higher than $2^256$. Note that if the value is set
/// to anything other than 8, 16, 32 or 64 the Bulletproofs code will return
/// an Err.
pub fn generate_inclusion_proof_with(
&self,
entity_id: &EntityId,
aggregation_factor: AggregationFactor,
upper_bound_bit_length: u8,
) -> Result<InclusionProof, NdmSmtError> {
match self {
Accumulator::NdmSmt(ndm_smt) => ndm_smt.generate_inclusion_proof_with(
entity_id,
aggregation_factor,
upper_bound_bit_length,
),
}
}
/// Generate an inclusion proof for the given `entity_id`.
pub fn generate_inclusion_proof(
&self,
entity_id: &EntityId,
) -> Result<InclusionProof, NdmSmtError> {
match self {
Accumulator::NdmSmt(ndm_smt) => ndm_smt.generate_inclusion_proof(entity_id),
}
}
}
/// Errors encountered when handling an [Accumulator].
#[derive(thiserror::Error, Debug)]
pub enum AccumulatorError {
#[error("Error serializing/deserializing file")]
SerdeError(#[from] ReadWriteError),
}
// NOTE no unit tests here because this code is tested in the integration tests.