use super::*;
use bounded_collections::{BoundedVec, ConstU32, Get};
use core::ops::{Deref, DerefMut};
use sha2::{Digest, Sha256};
pub const MAX_MEMBERS: u32 = 1024;
#[derive(Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo)]
#[scale_info(skip_type_params(N))]
pub struct MockMembers<T, N: Get<u32> = ConstU32<MAX_MEMBERS>>(pub BoundedVec<T, N>);
impl<T: Decode, N: Get<u32>> DecodeUnchecked for MockMembers<T, N> {}
impl DecodeUnchecked for () {}
impl<T: Clone, N: Get<u32>> Clone for MockMembers<T, N> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: core::fmt::Debug, N: Get<u32>> core::fmt::Debug for MockMembers<T, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("MockMembers").field(&self.0).finish()
}
}
impl<T, N: Get<u32>> Default for MockMembers<T, N> {
fn default() -> Self {
Self(BoundedVec::new())
}
}
impl<T: PartialEq, N: Get<u32>> PartialEq for MockMembers<T, N> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T: Eq, N: Get<u32>> Eq for MockMembers<T, N> {}
impl<T, N: Get<u32>> Deref for MockMembers<T, N> {
type Target = BoundedVec<T, N>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, N: Get<u32>> DerefMut for MockMembers<T, N> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T, N: Get<u32>> TryFrom<Vec<T>> for MockMembers<T, N> {
type Error = ();
fn try_from(v: Vec<T>) -> Result<Self, Self::Error> {
BoundedVec::try_from(v).map(Self).map_err(|_| ())
}
}
const TAG_ALIAS: &[u8] = b"verifiable-mock:v1:alias";
const TAG_SIG: &[u8] = b"verifiable-mock:v1:sig";
const TAG_PROOF: &[u8] = b"verifiable-mock:v1:proof";
fn h(domain: &[u8], parts: &[&[u8]]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update((domain.len() as u32).to_le_bytes());
hasher.update(domain);
hasher.update((parts.len() as u32).to_le_bytes());
for p in parts {
hasher.update((p.len() as u32).to_le_bytes());
hasher.update(p);
}
hasher.finalize().into()
}
fn make_alias(member: &[u8; 32], context: &[u8]) -> Alias {
h(TAG_ALIAS, &[member, context])
}
fn make_signature(member: &[u8; 32], message: &[u8]) -> [u8; 32] {
h(TAG_SIG, &[member, message])
}
fn make_proof_tag(
member: &[u8; 32],
contexts: &[&[u8]],
aliases: &[Alias],
message: &[u8],
) -> [u8; 32] {
let n = (contexts.len() as u32).to_le_bytes();
let mut parts: Vec<&[u8]> = Vec::with_capacity(3 + contexts.len() + aliases.len());
parts.push(member);
parts.push(&n);
parts.extend(contexts.iter().copied());
parts.extend(aliases.iter().map(|a| a.as_slice()));
parts.push(message);
h(TAG_PROOF, &parts)
}
#[derive(Default, Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
pub struct MockProof {
pub tag: [u8; 32],
pub member: [u8; 32],
pub aliases: BoundedVec<Alias, ConstU32<{ MAX_CONTEXTS as u32 }>>,
}
#[derive(Debug)]
pub struct Mock;
impl GenerateVerifiable for Mock {
type Members = MockMembers<Self::Member>;
type Intermediate = MockMembers<Self::Member>;
type Member = [u8; 32];
type Secret = [u8; 32];
type Commitment = (Self::Member, Vec<Self::Member>);
type Proof = MockProof;
type Signature = [u8; 32];
type StaticChunk = ();
type Config = ();
fn start_members(_config: Self::Config) -> Self::Intermediate {
MockMembers::default()
}
fn push_members(
inter: &mut Self::Intermediate,
members: impl Iterator<Item = Self::Member>,
_lookup: impl Fn(Range<usize>) -> Result<Vec<Self::StaticChunk>, ()>,
) -> Result<(), Error> {
for member in members {
inter.try_push(member).map_err(|_| Error::SetFull)?;
}
Ok(())
}
fn finish_members(inter: Self::Intermediate) -> Self::Members {
inter
}
fn new_secret(entropy: Entropy) -> Self::Secret {
entropy
}
fn member_from_secret(secret: &Self::Secret) -> Self::Member {
*secret
}
#[cfg(feature = "prover")]
fn open(
_config: Self::Config,
member: &Self::Member,
members: impl Iterator<Item = Self::Member>,
) -> Result<Self::Commitment, Error> {
let set = members.collect::<Vec<_>>();
if !set.contains(member) {
return Err(Error::NotInRing);
}
Ok((*member, set))
}
#[cfg(feature = "prover")]
fn create_multi_context(
(member, _): Self::Commitment,
secret: &Self::Secret,
contexts: &[&[u8]],
message: &[u8],
) -> Result<(Self::Proof, AliasVec), Error> {
if &member != secret {
return Err(Error::NotInRing);
}
if contexts.len() > MAX_CONTEXTS {
return Err(Error::TooManyContexts);
}
let aliases: AliasVec = contexts.iter().map(|ctx| make_alias(secret, ctx)).collect();
let bounded = BoundedVec::try_from(aliases.to_vec()).map_err(|_| Error::TooManyContexts)?;
let tag = make_proof_tag(secret, contexts, &aliases, message);
let proof = MockProof {
tag,
member,
aliases: bounded,
};
Ok((proof, aliases))
}
fn validate_multi_context(
_config: Self::Config,
proof: &Self::Proof,
members: &Self::Members,
contexts: &[&[u8]],
message: &[u8],
) -> Result<AliasVec, Error> {
let MockProof {
tag,
member,
aliases,
} = proof;
if !members.contains(member) {
return Err(Error::VerificationFailed);
}
if aliases.len() != contexts.len() {
return Err(Error::ContextCountMismatch);
}
for (alias, ctx) in aliases.iter().zip(contexts.iter()) {
if alias != &make_alias(member, ctx) {
return Err(Error::VerificationFailed);
}
}
let expected_tag = make_proof_tag(member, contexts, aliases, message);
if tag != &expected_tag {
return Err(Error::VerificationFailed);
}
Ok(aliases.iter().copied().collect())
}
fn alias_in_context(secret: &Self::Secret, context: &[u8]) -> Result<Alias, Error> {
Ok(make_alias(secret, context))
}
fn is_member_valid(_member: &Self::Member) -> bool {
true
}
fn sign(secret: &Self::Secret, message: &[u8]) -> Result<Self::Signature, Error> {
Ok(make_signature(secret, message))
}
fn verify_signature(
signature: &Self::Signature,
message: &[u8],
member: &Self::Member,
) -> bool {
signature == &make_signature(member, message)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_members(secrets: &[[u8; 32]]) -> <Mock as GenerateVerifiable>::Members {
let mut inter = Mock::start_members(());
let members = secrets.iter().map(Mock::member_from_secret);
Mock::push_members(&mut inter, members, |_| Ok(vec![()])).unwrap();
Mock::finish_members(inter)
}
#[cfg(feature = "prover")]
#[test]
fn create_and_verify() {
let alice_sec = Mock::new_secret([0u8; 32]);
let bob_sec = Mock::new_secret([1u8; 32]);
let charlie_sec = Mock::new_secret([2u8; 32]);
let members = make_members(&[alice_sec, bob_sec]);
let context = b"ctx";
let message = b"hello";
type MockReceipt = Receipt<Mock>;
let receipt = MockReceipt::create(
(),
&alice_sec,
members.iter().cloned(),
context,
message.to_vec(),
)
.unwrap();
let (alias, msg) = receipt.verify((), &members, context).unwrap();
assert_eq!(&msg, message);
assert_eq!(alias, make_alias(&alice_sec, context));
assert!(
MockReceipt::create(
(),
&charlie_sec,
members.iter().cloned(),
context,
message.to_vec(),
)
.is_err()
);
}
#[cfg(feature = "prover")]
#[test]
fn proof_binds_to_message() {
let secret = Mock::new_secret([7u8; 32]);
let member = Mock::member_from_secret(&secret);
let members = make_members(&[secret]);
let commitment = Mock::open((), &member, members.iter().cloned()).unwrap();
let (proof, _) =
Mock::create_multi_context(commitment, &secret, &[b"ctx"], b"msg").unwrap();
assert!(Mock::validate((), &proof, &members, b"ctx", b"msg").is_ok());
assert!(Mock::validate((), &proof, &members, b"ctx", b"other").is_err());
}
#[cfg(feature = "prover")]
#[test]
fn proof_binds_to_context() {
let secret = Mock::new_secret([7u8; 32]);
let member = Mock::member_from_secret(&secret);
let members = make_members(&[secret]);
let commitment = Mock::open((), &member, members.iter().cloned()).unwrap();
let (proof, _) = Mock::create_multi_context(commitment, &secret, &[b"a"], b"msg").unwrap();
assert!(Mock::validate((), &proof, &members, b"a", b"msg").is_ok());
assert!(Mock::validate((), &proof, &members, b"b", b"msg").is_err());
}
#[cfg(feature = "prover")]
#[test]
fn proof_rejected_for_non_member() {
let prover_sec = Mock::new_secret([7u8; 32]);
let prover = Mock::member_from_secret(&prover_sec);
let prover_members = make_members(&[prover_sec]);
let commitment = Mock::open((), &prover, prover_members.iter().cloned()).unwrap();
let (proof, _) =
Mock::create_multi_context(commitment, &prover_sec, &[b"ctx"], b"msg").unwrap();
let other = make_members(&[Mock::new_secret([8u8; 32])]);
assert!(Mock::validate((), &proof, &other, b"ctx", b"msg").is_err());
}
#[cfg(feature = "prover")]
#[test]
fn multi_context() {
let secret = Mock::new_secret([0u8; 32]);
let member = Mock::member_from_secret(&secret);
let members = make_members(&[secret]);
let contexts: Vec<&[u8]> = vec![b"ctx1", b"ctx2"];
let commitment = Mock::open((), &member, members.iter().cloned()).unwrap();
let (proof, aliases) =
Mock::create_multi_context(commitment, &secret, &contexts, b"msg").unwrap();
assert_eq!(aliases.len(), 2);
assert_ne!(aliases[0], aliases[1]);
assert!(Mock::is_valid_multi_context(
(),
&proof,
&members,
&contexts,
&aliases,
b"msg"
));
let swapped = vec![aliases[1], aliases[0]];
assert!(!Mock::is_valid_multi_context(
(),
&proof,
&members,
&contexts,
&swapped,
b"msg"
));
}
#[test]
fn signature_binds_to_message() {
let secret = Mock::new_secret([42u8; 32]);
let member = Mock::member_from_secret(&secret);
let sig = Mock::sign(&secret, b"msg").unwrap();
assert!(Mock::verify_signature(&sig, b"msg", &member));
assert!(!Mock::verify_signature(&sig, b"other", &member));
let other = Mock::member_from_secret(&Mock::new_secret([43u8; 32]));
assert!(!Mock::verify_signature(&sig, b"msg", &other));
}
#[test]
fn alias_is_collision_resistant_per_context() {
let secret = Mock::new_secret([1u8; 32]);
let a1 = Mock::alias_in_context(&secret, b"ctx1").unwrap();
let a2 = Mock::alias_in_context(&secret, b"ctx2").unwrap();
assert_ne!(a1, a2);
let a3 = Mock::alias_in_context(&secret, b"ctx1\x00").unwrap();
assert_ne!(a1, a3);
}
#[test]
fn duplicate_member_accepted() {
let secret = Mock::new_secret([0u8; 32]);
let member = Mock::member_from_secret(&secret);
let mut inter = Mock::start_members(());
assert!(Mock::push_members(&mut inter, [member].into_iter(), |_| Ok(vec![()])).is_ok());
assert!(Mock::push_members(&mut inter, [member].into_iter(), |_| Ok(vec![()])).is_ok());
}
}