Skip to content

Commit

Permalink
Merge pull request #72 from earthstar-project/meadowcap-ufotofu-codec
Browse files Browse the repository at this point in the history
Meadowcap + ufotofu_codec
  • Loading branch information
sgwilym authored Jan 17, 2025
2 parents c3db4ae + 396194f commit 662360c
Show file tree
Hide file tree
Showing 13 changed files with 1,069 additions and 496 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions fuzz/fuzz_targets/enc_mccapability_rel_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
use meadowcap::McCapability;
use ufotofu_codec::{fuzz_relative_all, Blame};
use willow_data_model::grouping::Area;
use willow_fuzz::{
placeholder_params::FakeSubspaceId,
silly_sigs::{SillyPublicKey, SillySig},
};
use willow_fuzz::silly_sigs::{SillyPublicKey, SillySig};

fuzz_relative_all!(McCapability<16, 16, 16, SillyPublicKey, SillySig, SillyPublicKey, SillySig>; Area<16, 16, 16, SillyPublicKey>; Blame; Blame);
fuzz_relative_all!(McCapability<16, 16, 16, SillyPublicKey, SillySig, SillyPublicKey, SillySig>; Area<16, 16, 16, SillyPublicKey>; Blame; Blame; |cap: &McCapability<16, 16, 16, SillyPublicKey, SillySig, SillyPublicKey, SillySig>, area: &Area<16, 16, 16, SillyPublicKey>| {
area.includes_area(&cap.granted_area())
});
8 changes: 3 additions & 5 deletions fuzz/fuzz_targets/mc_is_authorised_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
use libfuzzer_sys::fuzz_target;
use meadowcap::{AccessMode, McAuthorisationToken};
use signature::Signer;
use ufotofu::sync::consumer::IntoVec;
use ufotofu::consumer::IntoVec;
use ufotofu_codec::{Encodable, EncodableSync};
use willow_data_model::AuthorisationToken;
use willow_data_model::Entry;
use willow_encoding::sync::Encodable;
use willow_fuzz::{
placeholder_params::FakePayloadDigest,
silly_sigs::{SillyPublicKey, SillySig},
Expand All @@ -21,9 +21,7 @@ fuzz_target!(|data: (
let is_within_granted_area = token.capability.granted_area().includes_entry(&entry);
let is_write_cap = token.capability.access_mode() == AccessMode::Write;

let mut consumer = IntoVec::<u8>::new();
entry.encode(&mut consumer).unwrap();
let message = consumer.into_vec();
let message = entry.sync_encode_into_boxed_slice();

let expected_sig = token
.capability
Expand Down
4 changes: 3 additions & 1 deletion fuzz/src/placeholder_params.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use arbitrary::Arbitrary;
use ufotofu::{BulkConsumer, BulkProducer};
use ufotofu_codec::{
Blame, Decodable, DecodableCanonic, DecodeError, Encodable, EncodableKnownSize,
Blame, Decodable, DecodableCanonic, DecodeError, Encodable, EncodableKnownSize, EncodableSync,
};
use willow_data_model::{NamespaceId, PayloadDigest, SubspaceId};

Expand Down Expand Up @@ -205,4 +205,6 @@ impl EncodableKnownSize for FakePayloadDigest {
}
}

impl EncodableSync for FakePayloadDigest {}

impl PayloadDigest for FakePayloadDigest {}
20 changes: 19 additions & 1 deletion fuzz/src/silly_sigs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use arbitrary::Arbitrary;
use meadowcap::IsCommunal;
use signature::{Error as SignatureError, Signer, Verifier};
use ufotofu_codec::{Blame, Decodable, DecodableCanonic, Encodable};
use ufotofu_codec::{
Blame, Decodable, DecodableCanonic, Encodable, EncodableKnownSize, EncodableSync,
};
use willow_data_model::{NamespaceId, SubspaceId};

/// A silly, trivial, insecure public key for fuzz testing.
Expand Down Expand Up @@ -86,6 +88,14 @@ impl Encodable for SillyPublicKey {
}
}

impl EncodableKnownSize for SillyPublicKey {
fn len_of_encoding(&self) -> usize {
1
}
}

impl EncodableSync for SillyPublicKey {}

impl Decodable for SillyPublicKey {
type ErrorReason = Blame;

Expand Down Expand Up @@ -129,6 +139,14 @@ impl Encodable for SillySig {
}
}

impl EncodableKnownSize for SillySig {
fn len_of_encoding(&self) -> usize {
1
}
}

impl EncodableSync for SillySig {}

impl Decodable for SillySig {
type ErrorReason = Blame;

Expand Down
2 changes: 2 additions & 0 deletions meadowcap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ arbitrary = { version = "1.0.2", features = ["derive"], optional = true }
either = "1.13.0"
willow-encoding = { path = "../encoding", version = "0.1.0" }
willow-data-model = { path = "../data-model", version = "0.1.0" }
compact_u64 = { path = "../compact_u64", features = [ "std", "dev" ] }

[dependencies.ufotofu_codec]
path = "../ufotofu_codec"
version = "0.1.0"
features = ["alloc"]


[lints]
Expand Down
220 changes: 162 additions & 58 deletions meadowcap/src/communal_capability.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use signature::{Signer, Verifier};
use ufotofu_codec::Encodable;
use ufotofu_codec::{
Encodable, EncodableKnownSize, EncodableSync, RelativeEncodable, RelativeEncodableKnownSize,
};
use willow_data_model::{grouping::Area, NamespaceId, SubspaceId};

use crate::{AccessMode, Delegation, FailedDelegationError, InvalidDelegationError, IsCommunal};
Expand Down Expand Up @@ -34,9 +36,9 @@ pub struct CommunalCapability<
UserPublicKey,
UserSignature,
> where
NamespacePublicKey: NamespaceId + Encodable + IsCommunal,
UserPublicKey: SubspaceId + Encodable + Verifier<UserSignature>,
UserSignature: Encodable,
NamespacePublicKey: NamespaceId + EncodableSync + EncodableKnownSize + IsCommunal,
UserPublicKey: SubspaceId + EncodableSync + EncodableKnownSize + Verifier<UserSignature>,
UserSignature: EncodableSync + EncodableKnownSize,
{
access_mode: AccessMode,
namespace_key: NamespacePublicKey,
Expand All @@ -53,9 +55,9 @@ impl<
UserSignature,
> CommunalCapability<MCL, MCC, MPL, NamespacePublicKey, UserPublicKey, UserSignature>
where
NamespacePublicKey: NamespaceId + Encodable + IsCommunal,
UserPublicKey: SubspaceId + Encodable + Verifier<UserSignature>,
UserSignature: Encodable + Clone,
NamespacePublicKey: NamespaceId + EncodableSync + EncodableKnownSize + IsCommunal,
UserPublicKey: SubspaceId + EncodableSync + EncodableKnownSize + Verifier<UserSignature>,
UserSignature: EncodableSync + EncodableKnownSize + Clone,
{
/// Creates a new communal capability granting access to the [`SubspaceId`] corresponding to the given `UserPublicKey`.
pub fn new(
Expand Down Expand Up @@ -97,11 +99,18 @@ where

let prev_user = self.receiver();

let handover = self.handover(new_area, new_user);
let signature = secret_key.sign(&handover);
let handover = CommunalHandover {
cap: self,
area: new_area,
user: new_user,
};

let handover_enc = &handover.sync_encode_into_boxed_slice();

let signature = secret_key.sign(&handover_enc);

prev_user
.verify(&handover, &signature)
.verify(&handover_enc, &signature)
.map_err(|_| FailedDelegationError::WrongSecretForUser(self.receiver().clone()))?;

let mut new_delegations = self.delegations.clone();
Expand Down Expand Up @@ -136,17 +145,23 @@ where
});
}

let handover = self.handover(new_area, new_user);
let handover = CommunalHandover {
cap: self,
area: new_area,
user: new_user,
};

let prev_receiver = self.receiver();

prev_receiver.verify(&handover, new_sig).map_err(|_| {
InvalidDelegationError::InvalidSignature {
let handover_slice = handover.sync_encode_into_boxed_slice();

prev_receiver
.verify(&handover_slice, new_sig)
.map_err(|_| InvalidDelegationError::InvalidSignature {
claimed_receiver: new_user.clone(),
expected_signatory: prev_receiver.clone(),
signature: new_sig.clone(),
}
})?;
})?;

self.delegations.push(delegation);

Expand Down Expand Up @@ -220,50 +235,137 @@ where
pub fn progenitor(&self) -> &UserPublicKey {
&self.user_key
}
}

/// Returns a bytestring to be signed for a new [`Delegation`].
///
/// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#communal_handover)
fn handover(
&self,
new_area: &Area<MCL, MCC, MPL, UserPublicKey>,
new_user: &UserPublicKey,
) -> Box<[u8]> {
todo!("Implement with new Encoding trait that requires knowing the size upfront by computing the size of the handover first, allocating it as a boxed slice, and then using an ufotofu::into_slice encoder.");
// let mut consumer = IntoVec::<u8>::new();

// if self.delegations.is_empty() {
// let first_byte = match self.access_mode {
// AccessMode::Read => 0x00,
// AccessMode::Write => 0x01,
// };

// let prev_area =
// Area::<MCL, MCC, MPL, UserPublicKey>::new_subspace(self.user_key.clone());

// // We can safely unwrap all these encodings as IntoVec's error is the never type.
// consumer.consume(first_byte).unwrap();
// self.namespace_key.encode(&mut consumer).unwrap();
// new_area.relative_encode(&prev_area, &mut consumer).unwrap();
// new_user.encode(&mut consumer).unwrap();

// return consumer.into_vec().into();
// }

// // We can unwrap here because we know that self.delegations is not empty.
// let last_delegation = self.delegations.last().unwrap();
// let prev_area = last_delegation.area();
// let prev_signature = last_delegation.signature();

// // We can safely unwrap all these encodings as IntoVec's error is the never type.
// new_area.relative_encode(prev_area, &mut consumer).unwrap();
// prev_signature.encode(&mut consumer).unwrap();
// new_user.encode(&mut consumer).unwrap();

// consumer.into_vec().into()
/// Can be encoded to a bytestring to be signed for a new [`Delegation`] to a [`CommunalCapability`].
///
/// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#communal_handover)
pub struct CommunalHandover<
'a,
const MCL: usize,
const MCC: usize,
const MPL: usize,
NamespacePublicKey,
UserPublicKey,
UserSignature,
> where
NamespacePublicKey: NamespaceId + EncodableSync + EncodableKnownSize + IsCommunal,
UserPublicKey: SubspaceId + EncodableSync + EncodableKnownSize + Verifier<UserSignature>,
UserSignature: EncodableSync + EncodableKnownSize,
{
cap: &'a CommunalCapability<MCL, MCC, MPL, NamespacePublicKey, UserPublicKey, UserSignature>,
area: &'a Area<MCL, MCC, MPL, UserPublicKey>,
user: &'a UserPublicKey,
}

impl<
'a,
const MCL: usize,
const MCC: usize,
const MPL: usize,
NamespacePublicKey,
UserPublicKey,
UserSignature,
> Encodable
for CommunalHandover<'a, MCL, MCC, MPL, NamespacePublicKey, UserPublicKey, UserSignature>
where
NamespacePublicKey: NamespaceId + EncodableSync + EncodableKnownSize + IsCommunal,
UserPublicKey: SubspaceId + EncodableSync + EncodableKnownSize + Verifier<UserSignature>,
UserSignature: EncodableSync + EncodableKnownSize,
{
async fn encode<C>(&self, consumer: &mut C) -> Result<(), C::Error>
where
C: ufotofu::BulkConsumer<Item = u8>,
{
if self.cap.delegations.is_empty() {
let first_byte = match self.cap.access_mode {
AccessMode::Read => 0x00,
AccessMode::Write => 0x01,
};

let prev_area =
Area::<MCL, MCC, MPL, UserPublicKey>::new_subspace(self.cap.user_key.clone());

// We can safely unwrap all these encodings as IntoVec's error is the never type.
consumer.consume(first_byte).await?;
self.cap.namespace_key.encode(consumer).await?;
self.area.relative_encode(consumer, &prev_area).await?;
self.user.encode(consumer).await?;

return Ok(());
}

// We can unwrap here because we know that self.delegations is not empty.
let last_delegation = self.cap.delegations.last().unwrap();
let prev_area = last_delegation.area();
let prev_signature = last_delegation.signature();

// We can safely unwrap all these encodings as IntoVec's error is the never type.
self.area.relative_encode(consumer, prev_area).await?;
prev_signature.encode(consumer).await?;
self.user.encode(consumer).await?;

Ok(())
}
}

impl<
'a,
const MCL: usize,
const MCC: usize,
const MPL: usize,
NamespacePublicKey,
UserPublicKey,
UserSignature,
> EncodableKnownSize
for CommunalHandover<'a, MCL, MCC, MPL, NamespacePublicKey, UserPublicKey, UserSignature>
where
NamespacePublicKey: NamespaceId + EncodableSync + EncodableKnownSize + IsCommunal,
UserPublicKey: SubspaceId + EncodableSync + EncodableKnownSize + Verifier<UserSignature>,
UserSignature: EncodableSync + EncodableKnownSize,
{
fn len_of_encoding(&self) -> usize {
if self.cap.delegations.is_empty() {
let prev_area =
Area::<MCL, MCC, MPL, UserPublicKey>::new_subspace(self.cap.user_key.clone());

let namespace_len = self.cap.namespace_key.len_of_encoding();
let area_len = self.area.relative_len_of_encoding(&prev_area);
let user_len = self.user.len_of_encoding();

return 1 + namespace_len + area_len + user_len;
}

// We can unwrap here because we know that self.delegations is not empty.
let last_delegation = self.cap.delegations.last().unwrap();
let prev_area = last_delegation.area();
let prev_signature = last_delegation.signature();

let area_len = self.area.relative_len_of_encoding(prev_area);
let sig_len = prev_signature.len_of_encoding();
let user_len = self.user.len_of_encoding();

area_len + sig_len + user_len
}
}

impl<
'a,
const MCL: usize,
const MCC: usize,
const MPL: usize,
NamespacePublicKey,
UserPublicKey,
UserSignature,
> EncodableSync
for CommunalHandover<'a, MCL, MCC, MPL, NamespacePublicKey, UserPublicKey, UserSignature>
where
NamespacePublicKey: NamespaceId + EncodableSync + EncodableKnownSize + IsCommunal,
UserPublicKey: SubspaceId + EncodableSync + EncodableKnownSize + Verifier<UserSignature>,
UserSignature: EncodableSync + EncodableKnownSize,
{
}

#[cfg(feature = "dev")]
use arbitrary::{Arbitrary, Error as ArbitraryError};

Expand All @@ -279,9 +381,11 @@ impl<
> Arbitrary<'a>
for CommunalCapability<MCL, MCC, MPL, NamespacePublicKey, UserPublicKey, UserSignature>
where
NamespacePublicKey: NamespaceId + Encodable + IsCommunal + Arbitrary<'a>,
UserPublicKey: SubspaceId + Encodable + Verifier<UserSignature> + Arbitrary<'a>,
UserSignature: Encodable + Clone,
NamespacePublicKey:
NamespaceId + EncodableSync + EncodableKnownSize + IsCommunal + Arbitrary<'a>,
UserPublicKey:
SubspaceId + EncodableSync + EncodableKnownSize + Verifier<UserSignature> + Arbitrary<'a>,
UserSignature: EncodableSync + EncodableKnownSize + Clone,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let namespace_key: NamespacePublicKey = Arbitrary::arbitrary(u)?;
Expand Down
Loading

0 comments on commit 662360c

Please sign in to comment.