Merge branch 'onion-netdir-apis' into 'main'

Add onion service APIs to tor-netdir

See merge request tpo/core/arti!966
This commit is contained in:
Nick Mathewson 2023-01-11 16:08:18 +00:00
commit 7030cbe460
11 changed files with 346 additions and 33 deletions

1
Cargo.lock generated
View File

@ -3995,6 +3995,7 @@ dependencies = [
"tor-checkable",
"tor-config",
"tor-error",
"tor-hscrypto",
"tor-linkspec",
"tor-llcrypto",
"tor-netdoc",

View File

@ -11,6 +11,7 @@ pub mod time;
/// The information that a client needs to know about an onion service in
/// order to connect to it.
#[derive(Copy, Clone, Debug)]
pub struct Credential {
/// Representation for the onion service's public ID.
///
@ -26,6 +27,7 @@ pub struct Credential {
/// the current time period.
///
/// Given this piece of information, the original credential cannot be re-derived.
#[derive(Copy, Clone, Debug)]
pub struct Subcredential([u8; 32]);
/// Counts which revision of an onion service descriptor is which, within a
@ -33,13 +35,10 @@ pub struct Subcredential([u8; 32]);
///
/// There can be gaps in this numbering. A descriptor with a higher-valued
/// revision counter supersedes one with a lower revision counter.
#[derive(Copy, Clone, Debug)]
pub struct RevisionCounter(u64);
/// An opaque value used by an onion service
// TODO hs: these values should only permit constant-time comparison.
#[derive(Clone, Debug)]
#[derive(Copy, Clone, Debug)]
pub struct RendCookie([u8; 20]);
/// A position within the onion service directory hash ring.
// TODO: these should move to tor-netdir, I think?
pub struct HsRingIndex([u8; 32]);

View File

@ -23,6 +23,7 @@ use crate::time::TimePeriod;
/// This is the decoded and validated ed25519 public key that is encoded as a
/// `${base32}.onion` address. When expanded, it is a public key whose
/// corresponding secret key is controlled by the onion service.
#[derive(Copy, Clone, Debug)]
pub struct OnionId([u8; 32]);
/// The identity of a v3 onion service, expanded into a public key.
@ -38,6 +39,7 @@ pub struct OnionId([u8; 32]);
//
// NOTE: This is a separate type from OnionId because it is about 6x larger. It
// is an expanded form, used for doing actual cryptography.
#[derive(Clone, Debug)]
pub struct OnionIdKey(ed25519::PublicKey);
// TODO hs: implement TryFrom<OnionId> for OnionIdKey, and From<OnionIdKey> for OnionId.
@ -59,9 +61,11 @@ impl OnionIdKey {
///
/// It is used for two purposes: first, to compute an index into the HSDir
/// ring, and second, to sign a `DescSigningKey`.
#[derive(Clone, Debug)]
pub struct BlindedOnionIdKey(ed25519::PublicKey);
/// A blinded onion service identity, repreesented in a compact format.
#[derive(Copy, Clone, Debug)]
pub struct BlindedOnionId([u8; 32]);
// TODO hs: implement TryFrom<BlindedOnionId> for BlinedOnionIdKey, and
@ -80,6 +84,7 @@ pub struct BlindedOnionId([u8; 32]);
/// Note: we use a separate signing key here, rather than using the
/// BlidedOnionIdKey directly, so that the secret key for the BlindedOnionIdKey
/// can be kept offline.
#[derive(Clone, Debug)]
pub struct DescSigningKey(ed25519::PublicKey);
/// A key used to identify and authenticate an onion service at a single
@ -89,6 +94,7 @@ pub struct DescSigningKey(ed25519::PublicKey);
/// used at each introduction point. Introduction points don't know the
/// relation of this key to the onion service: they only recognize the same key
/// when they see it again.
#[derive(Clone, Debug)]
pub struct IntroPtAuthKey(ed25519::PublicKey);
/// A key used in the HsNtor handshake between the client and the onion service.
@ -96,12 +102,14 @@ pub struct IntroPtAuthKey(ed25519::PublicKey);
/// The onion service chooses a different one of these to use with each
/// introduction point, though it does not need to tell the introduction points
/// about these keys.
#[derive(Clone, Debug)]
pub struct IntroPtEncKey(curve25519::PublicKey);
/// First type of client authorization key, used for the introduction protocol.
///
/// This is used to sign a nonce included in an extension in the encrypted
/// portion of an introduce cell.
#[derive(Clone, Debug)]
pub struct ClientIntroAuthKey(ed25519::PublicKey);
/// Second type of client authorization key, used for onion descryptor
@ -109,6 +117,7 @@ pub struct ClientIntroAuthKey(ed25519::PublicKey);
///
/// Any client who knows the secret key corresponding to this key can decrypt
/// the inner layer of the onion service descriptor.
#[derive(Clone, Debug)]
pub struct ClientDescAuthKey(curve25519::PublicKey);
// TODO hs: For each of the above key types, we should have a correspondingly

View File

@ -6,6 +6,7 @@ use std::time::{Duration, SystemTime};
///
/// These time periods are used to derive a different `BlindedOnionIdKey`
/// during each period from each `OnionIdKey`.
#[derive(Copy, Clone, Debug)]
pub struct TimePeriod {
/// Index of the time periods that have passed since the unix epoch.
interval_num: u64,

View File

@ -14,13 +14,16 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = []
experimental = ["experimental-api"]
experimental = ["experimental-api", "onion-service", "onion-client"]
# Enable experimental APIs that are not yet officially supported.
#
# These APIs are not covered by semantic versioning. Using this
# feature voids your "semver warrantee".
experimental-api = []
onion-client = ["onion-common"]
onion-service = ["onion-common"]
onion-common = ["tor-hscrypto"]
# Enable testing-only APIs. APIs under this feature are not
# covered by semver.
@ -42,6 +45,7 @@ thiserror = "1"
tor-checkable = { path = "../tor-checkable", version = "0.4.0" }
tor-config = { path = "../tor-config", version = "0.7.0" }
tor-error = { path = "../tor-error", version = "0.4.0" }
tor-hscrypto = { path = "../tor-hscrypto", version = "0.1.0", optional = true }
tor-linkspec = { path = "../tor-linkspec", version = "0.6.0" }
tor-llcrypto = { path = "../tor-llcrypto", version = "0.4.0" }
tor-netdoc = { path = "../tor-netdoc", version = "0.6.0" }

View File

@ -32,3 +32,26 @@ impl HasKind for Error {
}
}
}
/// An error returned when looking up onion service directories.
#[derive(Error, Clone, Debug)]
#[cfg(feature = "onion-common")]
#[cfg_attr(docsrs, doc(cfg(feature = "onion-common")))]
#[non_exhaustive]
pub enum OnionDirLookupError {
/// We tried to look up an onion service directory for a time period that
/// did not correspond to one of our hash rings.
#[error("Tried to look up an onion service directory for an invalid time period.")]
WrongTimePeriod,
}
#[cfg(feature = "onion-common")]
impl HasKind for OnionDirLookupError {
fn kind(&self) -> tor_error::ErrorKind {
use tor_error::ErrorKind as EK;
use OnionDirLookupError as E;
match self {
E::WrongTimePeriod => EK::BadApiUsage,
}
}
}

View File

@ -0,0 +1,116 @@
//! Functions and type for implementing the onion service directory ring.
//!
//! The onion service directory ring is an ordered ring of the all of relays in
//! the consensus with the HsDir flag. The HSDirs change their position in this
//! index every [`TimePeriod`], and every time that the shared random value in
//! the consensus changes. (These events are typically synchronized, for
//! reasonable network configurations.)
//!
//! Each onion service is also (semi-privately) associated with "N" positions on
//! the ring based on its blinded ID and the current time period. When upload or
//! downloading an onion service descriptor descriptor, we look at the ring at
//! each of these positions, and consider the "S" relays that fall at that
//! position or later. ("N" is a "number of replicas" parameter, and "S" is a
//! "Spread" parameter.)
#![allow(unused_variables, dead_code)] //TODO hs: remove
use tor_hscrypto::{pk::BlindedOnionId, time::TimePeriod};
use tor_llcrypto::pk::ed25519::Ed25519Identity;
use tor_netdoc::doc::netstatus::SharedRandVal;
/// A sort key determining a position in the onion service directory ring.
///
/// This is either the sort key of a given relay at a given time period, or the
/// sort key for a probing position for a given onion service id at a given
/// time.
///
/// The specification calls this an "index" but `HsDirIndex` is a key-length
/// sized, apparently-random, value, which determines the ordering of relays on
/// the ring. It is not the position number (ie, not a dense index starting at
/// 0).
///
/// Note that this is _not_ an index into any array; it is instead an index into
/// a space of possible values in a (virtual!) ring of 2^256 elements.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) struct HsDirIndex([u8; 32]);
/// A hash ring as used in `NetDir`.
///
/// This type is immutable once constructed: entries cannot be added, changed,
/// or removed. It can be interpreted only in the context of a given consensus
/// document.
#[derive(Clone, Debug)]
pub(crate) struct HsDirRing {
/// The time period for which the ring is valid.
period: TimePeriod,
/// The shared random value that applies to the ring.
shared_rand: SharedRandVal,
/// The ring itself.
///
/// The first element of each tuple is a 32-byte hash representing a
/// position on the ring; the second is the index for the corresponding
/// relay within self.consensus.relays().
///
/// This vector is empty in a partial netdir; it is filled in when we
/// convert to a complete netdir.
ring: Vec<(HsDirIndex, usize)>,
}
/// Compute the [`HsDirIndex`] for a given relay.
pub(crate) fn relay_index(
id: Ed25519Identity,
rand: SharedRandVal,
period: TimePeriod,
) -> HsDirIndex {
// TODO hs implement this.
//
// hsdir_index(node) = H("node-idx" | node_identity |
// shared_random_value |
// INT_8(period_num) |
// INT_8(period_length) )
//
// Note that INT_8 means "u64" and H is sha3-256.
todo!()
}
/// Compute the starting [`HsDirIndex`] for a given descriptor replica.
pub(crate) fn service_index(
id: BlindedOnionId,
replica: u8,
rand: SharedRandVal,
period: TimePeriod,
) -> HsDirIndex {
// TODO hs implement this
//
// hs_index(replicanum) = H("store-at-idx" |
// blinded_public_key |
// INT_8(replicanum) |
// INT_8(period_length) |
// INT_8(period_num) )
//
// Note that INT_8 means "u64" and H is sha3-256
todo!()
}
impl HsDirRing {
/// Find the location or (notional) insertion point for `idx` within `ring`.
fn find_pos(&self, idx: HsDirIndex) -> usize {
// TODO hs implement this
todo!()
}
/// Yield items from `ring` starting with `idx`, wrapping around once when we
/// reach the end, and yielding no element more than once.
pub(crate) fn ring_items_at(
&self,
idx: HsDirIndex,
) -> impl Iterator<Item = &(HsDirIndex, usize)> {
let idx = self.find_pos(idx);
self.ring[idx..].iter().chain(&self.ring[..idx])
}
}

View File

@ -38,6 +38,8 @@
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
mod err;
#[cfg(feature = "onion-common")]
mod hsdir_ring;
pub mod params;
mod weight;
@ -46,6 +48,8 @@ pub mod testnet;
#[cfg(feature = "testing")]
pub mod testprovider;
#[cfg(feature = "onion-common")]
use hsdir_ring::HsDirRing;
use static_assertions::const_assert;
use tor_linkspec::{
ChanTarget, DirectChanMethodsHelper, HasAddrs, HasRelayIds, RelayIdRef, RelayIdType,
@ -66,11 +70,17 @@ use std::sync::Arc;
use strum::{EnumCount, EnumIter};
use tracing::warn;
#[cfg(feature = "onion-common")]
use tor_hscrypto::{pk::BlindedOnionId, time::TimePeriod};
pub use err::Error;
pub use weight::WeightRole;
/// A Result using the Error type from the tor-netdir crate
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(feature = "onion-common")]
pub use err::OnionDirLookupError;
use params::NetParameters;
/// Configuration for determining when two relays have addresses "too close" in
@ -203,6 +213,16 @@ impl From<u64> for RelayWeight {
}
}
/// An operation for which we might be requesting an onion service directory.
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub enum OnionServiceDirOp {
/// Uploading an onion service descriptor.
Upload,
/// Downloading an onion service descriptor.
Download,
}
/// A view of the Tor directory, suitable for use in building circuits.
///
/// Abstractly, a [`NetDir`] is a set of usable public [`Relay`]s, each of which
@ -259,6 +279,54 @@ pub struct NetDir {
/// can be immutable.
rs_idx_by_rsa: Arc<HashMap<RsaIdentity, usize>>,
/// A hash ring describing the onion service directory.
///
/// This is empty in a PartialNetDir, and is filled in before the NetDir is
/// built.
///
/// It corresponds to the time period containing the `valid-after` time in
/// the consensus. Its SRV is whatever SRV was most current at the time when
/// that time period began.
///
/// This is the hash ring that we should use whenever we are fetching an
/// onion service descriptor.
//
// TODO hs: It is ugly to have this be Option.
#[cfg(feature = "onion-common")]
#[allow(dead_code)]
hsdir_ring: Option<HsDirRing>,
/// A hash ring describing the onion service directory based on the
/// parameters for the previous and next time periods.
///
/// Onion services upload to positions on these ring as well, based on how
/// far into the current time period this directory is, so that
/// not-synchronized clients can still find their descriptor.
///
/// Each of these rings is None in a PartialNetDir, and None if this ring
/// should not be used.
///
/// Note that with the current (2023) network parameters, with
/// `hsdir_interval = SRV lifetime = 24 hours` at most one of these
/// secondary rings will be active at a time. We have two here in order
/// to conform with a more flexible regime in proposal 342.
//
// TODO hs: It is sort of ugly to have these be Option.
//
// TODO hs: hs clients never need this; so I've made it not-present for thm.
// But does that risk too much with respect to side channels?
//
// TODO hs: Perhaps we should refactor this so that there is just one
// Vec<HsDirRing> (or SmallVec<>); or so that there are `current`, `next`,
// and `previous`.
//
// TODO hs: Perhaps we should refactor this so that it is clear that these
// are immutable? On the other hand, the documentation for this type
// declares that it is immutable, so we are likely okay.
#[cfg(feature = "onion-service")]
#[allow(dead_code)]
hsdir_secondary_rings: (Option<HsDirRing>, Option<HsDirRing>),
/// Weight values to apply to a given relay when deciding how frequently
/// to choose it for a given role.
weights: weight::WeightSet,
@ -494,6 +562,10 @@ impl PartialNetDir {
rs_idx_by_missing,
rs_idx_by_rsa: Arc::new(rs_idx_by_rsa),
rs_idx_by_ed: HashMap::with_capacity(n_relays),
#[cfg(feature = "onion-common")]
hsdir_ring: None,
#[cfg(feature = "onion-service")]
hsdir_secondary_rings: (None, None),
weights,
};
@ -514,9 +586,32 @@ impl PartialNetDir {
loaded.push(md.digest());
}
}
#[cfg(feature = "hs-common")]
{
// TODO hs: cache the values for the hash rings if possible, maybe
// in the PartialNetDir, since we will want to use them in computing
// hash indices for the new hash ring. This can let us save some
// computation? Alternatively, we could compute the new rings at
// this point, but that could make this operation a bit expensive.
}
loaded
}
/// Compute the hash ring(s) for this NetDir, if one is not already computed.
#[cfg(feature = "hs-common")]
#[allow(clippy::missing_panics_doc)]
pub fn compute_ring(&mut self) {
// TODO hs: compute the ring based on the time period and shared random
// value of the consensus.
//
// The ring itself can be a bit expensive to compute, so maybe we should
// make sure this happens in a separate task or something, and expose a
// way to do that?
todo!()
}
/// Return true if this are enough information in this directory
/// to build multihop paths.
pub fn have_enough_paths(&self) -> bool {
@ -526,6 +621,7 @@ impl PartialNetDir {
/// circuits, return it.
pub fn unwrap_if_sufficient(self) -> std::result::Result<NetDir, PartialNetDir> {
if self.netdir.have_enough_paths() {
// self.compute_ring(); // TODO hs
Ok(self.netdir)
} else {
Err(self)
@ -1000,6 +1096,60 @@ impl NetDir {
.filter(|other_relay| other_relay.md.family().contains(relay_rsa_id))
})
}
/// Return the current onion service directory "time period".
///
/// Specifically, this returns the time period that contains the beginning
/// of the validity period of this `NetDir`'s consensus. That time period
/// is the one we use when acting as an onion service client.
#[cfg(feature = "onion-common")]
#[allow(unused, clippy::missing_panics_doc)] // TODO hs: remove.
pub fn onion_service_time_period(&self) -> TimePeriod {
todo!() // TODO hs
}
/// Return the secondary onion service directory "time periods".
///
/// These are additional time periods that we publish descriptors for when we are
/// acting as an onion service.
#[cfg(feature = "onion-service")]
#[allow(unused, clippy::missing_panics_doc)] // TODO hs: remove.
pub fn onion_service_secondary_time_periods(&self) -> Vec<TimePeriod> {
todo!()
}
/// Return the relays in this network directory that will be used to store a
/// given onion service's descriptor at a given time period.
///
/// Return an error if the time period is not one returned by
/// `onion_service_time_period` or `onion_service_secondary_time_periods`.
#[cfg(feature = "onion-common")]
#[allow(unused, clippy::missing_panics_doc)] // TODO hs: remove.
pub fn onion_service_dirs(
&self,
id: BlindedOnionId,
op: OnionServiceDirOp,
when: TimePeriod,
) -> std::result::Result<Vec<Relay<'_>>, OnionDirLookupError> {
// Algorithm:
//
// 1. Determine which HsDirRing to use, based on the time period.
// 2. Find the shared random value that's associated with that HsDirRing.
// 3. Choose spread = the parameter `hsdir_spread_store` or
// `hsdir_spread_fetch` based on `op`.
// 4. Let n_replicas = the parameter `hsdir_n_replicas`.
// 5. Initialize Dirs = []
// 6. for idx in 0..n_replicas:
// - let H = hsdir_ring::onion_service_index(id, replica, rand,
// period).
// - Find the position of H within hsdir_ring.
// - Take elements from hsdir_ring starting at that position,
// adding them to Dirs until we have added `spread` new elements
// that were not there before.
// 7. return Dirs.
todo!() // TODO hs
}
}
impl MdReceiver for NetDir {

View File

@ -292,10 +292,13 @@ pub struct SignatureGroup {
signatures: Vec<Signature>,
}
// TODO hs: Lower this type to tor-llcrypto: It is relied upon by various crypto
// things in onion services, and may later be used elsewhere too.
//
/// A shared-random value produced by the directory authorities.
/// A shared random value produced by the directory authorities.
#[derive(Debug, Clone, Copy)]
// TODO: needs accessors.
pub struct SharedRandVal([u8; 32]);
/// A shared-random value produced by the directory authorities,
/// along with meta-information about that value.
#[allow(dead_code)]
// TODO hs: This should have real accessors, not this 'visible/visibility' hack.
#[cfg_attr(
@ -305,7 +308,7 @@ pub struct SignatureGroup {
non_exhaustive
)]
#[derive(Debug, Clone)]
struct SharedRandVal {
struct SharedRandStatus {
/// How many authorities revealed shares that contributed to this value.
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
n_reveals: u8,
@ -315,10 +318,8 @@ struct SharedRandVal {
/// that this value isn't predictable before it first becomes
/// live, and that a hostile party could not have forced it to
/// have any more than a small number of possible random values.
//
// TODO hs-client: This should become [u8; 32] if we get approval to nail it down in the spec.
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
value: Vec<u8>,
value: SharedRandVal,
}
/// Parts of the networkstatus header that are present in every networkstatus.
@ -385,10 +386,10 @@ struct ConsensusHeader {
consensus_method: u32,
/// Global shared-random value for the previous shared-random period.
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
shared_rand_prev: Option<SharedRandVal>,
shared_rand_prev: Option<SharedRandStatus>,
/// Global shared-random value for the current shared-random period.
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
shared_rand_cur: Option<SharedRandVal>,
shared_rand_cur: Option<SharedRandStatus>,
}
/// Description of an authority's identity and address.
@ -985,7 +986,7 @@ impl CommonHeader {
}
}
impl SharedRandVal {
impl SharedRandStatus {
/// Parse a current or previous shared rand value from a given
/// SharedRandPreviousValue or SharedRandCurrentValue.
fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
@ -1001,8 +1002,8 @@ impl SharedRandVal {
}
let n_reveals: u8 = item.parse_arg(0)?;
let val: B64 = item.parse_arg(1)?;
let value = val.into();
Ok(SharedRandVal { n_reveals, value })
let value = SharedRandVal(val.into_array()?);
Ok(SharedRandStatus { n_reveals, value })
}
}
@ -1024,12 +1025,12 @@ impl ConsensusHeader {
let shared_rand_prev = sec
.get(SHARED_RAND_PREVIOUS_VALUE)
.map(SharedRandVal::from_item)
.map(SharedRandStatus::from_item)
.transpose()?;
let shared_rand_cur = sec
.get(SHARED_RAND_CURRENT_VALUE)
.map(SharedRandVal::from_item)
.map(SharedRandStatus::from_item)
.transpose()?;
Ok(ConsensusHeader {
@ -1939,16 +1940,16 @@ mod test {
let sr =
gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
.unwrap();
let sr = SharedRandVal::from_item(&sr).unwrap();
let sr = SharedRandStatus::from_item(&sr).unwrap();
assert_eq!(sr.n_reveals, 9);
assert_eq!(
sr.value,
sr.value.0,
hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
);
let sr = gettok("foo bar\n").unwrap();
let sr = SharedRandVal::from_item(&sr);
let sr = SharedRandStatus::from_item(&sr);
assert!(sr.is_err());
}
}

View File

@ -6,7 +6,7 @@
use super::rs::build::RouterStatusBuilder;
use super::{
CommonHeader, Consensus, ConsensusFlavor, ConsensusHeader, ConsensusVoterInfo, DirSource,
Footer, Lifetime, NetParams, ProtoStatus, RouterStatus, SharedRandVal,
Footer, Lifetime, NetParams, ProtoStatus, RouterStatus, SharedRandStatus, SharedRandVal,
};
use crate::{BuildError as Error, BuildResult as Result};
@ -42,9 +42,9 @@ pub struct ConsensusBuilder<RS> {
/// See [`ConsensusHeader::consensus_method`]
consensus_method: Option<u32>,
/// See [`ConsensusHeader::shared_rand_prev`]
shared_rand_prev: Option<SharedRandVal>,
shared_rand_prev: Option<SharedRandStatus>,
/// See [`ConsensusHeader::shared_rand_cur`]
shared_rand_cur: Option<SharedRandVal>,
shared_rand_cur: Option<SharedRandStatus>,
/// See [`Consensus::voters`]
voters: Vec<ConsensusVoterInfo>,
/// See [`Consensus::relays`]
@ -147,15 +147,15 @@ impl<RS> ConsensusBuilder<RS> {
/// Set the previous day's shared-random value for this consensus.
///
/// This value is optional.
pub fn shared_rand_prev(&mut self, n_reveals: u8, value: Vec<u8>) -> &mut Self {
self.shared_rand_prev = Some(SharedRandVal { n_reveals, value });
pub fn shared_rand_prev(&mut self, n_reveals: u8, value: SharedRandVal) -> &mut Self {
self.shared_rand_prev = Some(SharedRandStatus { n_reveals, value });
self
}
/// Set the current day's shared-random value for this consensus.
///
/// This value is optional.
pub fn shared_rand_cur(&mut self, n_reveals: u8, value: Vec<u8>) -> &mut Self {
self.shared_rand_cur = Some(SharedRandVal { n_reveals, value });
pub fn shared_rand_cur(&mut self, n_reveals: u8, value: SharedRandVal) -> &mut Self {
self.shared_rand_cur = Some(SharedRandStatus { n_reveals, value });
self
}
/// Set a named weight parameter for this consensus.
@ -402,8 +402,8 @@ mod test {
.param("knish", 1212)
.voting_delay(7, 8)
.consensus_method(32)
.shared_rand_prev(1, (*b"").into())
.shared_rand_cur(1, (*b"hi there").into())
.shared_rand_prev(1, SharedRandVal([b'x'; 32]))
.shared_rand_cur(1, SharedRandVal([b'y'; 32]))
.weight("Wxy", 303)
.weight("Wow", 999);

View File

@ -72,6 +72,15 @@ mod b64impl {
Err(EK::BadObjectVal.with_msg("Invalid length on base64 data"))
}
}
/// Try to convert this object into an array of N bytes.
///
/// Return an error if the length is wrong.
pub(crate) fn into_array<const N: usize>(self) -> Result<[u8; N]> {
self.0
.try_into()
.map_err(|_| EK::BadObjectVal.with_msg("Invalid length on base64 data"))
}
}
impl From<B64> for Vec<u8> {