frost_core/
identifier.rs

1//! FROST participant identifiers
2
3use core::{
4    fmt::{self, Debug},
5    hash::{Hash, Hasher},
6};
7
8use alloc::vec::Vec;
9
10use crate::{
11    serialization::SerializableScalar, Ciphersuite, Error, Field, FieldError, Group, Scalar,
12};
13
14/// A FROST participant identifier.
15///
16/// The identifier is a field element in the scalar field that the secret polynomial is defined
17/// over, corresponding to some x-coordinate for a polynomial f(x) = y.  MUST NOT be zero in the
18/// field, as f(0) = the shared secret.
19#[derive(Copy, Clone, PartialEq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
22// We use these to add a validation step since zero scalars should cause an
23// error when deserializing.
24#[cfg_attr(feature = "serde", serde(try_from = "SerializableScalar<C>"))]
25#[cfg_attr(feature = "serde", serde(into = "SerializableScalar<C>"))]
26pub struct Identifier<C: Ciphersuite>(SerializableScalar<C>);
27
28impl<C> Identifier<C>
29where
30    C: Ciphersuite,
31{
32    /// Create a new Identifier from a scalar. For internal use only.
33    #[cfg_attr(feature = "internals", visibility::make(pub))]
34    #[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
35    pub(crate) fn new(scalar: Scalar<C>) -> Result<Self, Error<C>> {
36        if scalar == <<C::Group as Group>::Field>::zero() {
37            Err(FieldError::InvalidZeroScalar.into())
38        } else {
39            Ok(Self(SerializableScalar(scalar)))
40        }
41    }
42
43    /// Get the inner scalar.
44    #[cfg_attr(feature = "internals", visibility::make(pub))]
45    #[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
46    pub(crate) fn to_scalar(&self) -> Scalar<C> {
47        self.0 .0
48    }
49
50    /// Derive an Identifier from an arbitrary byte string.
51    ///
52    /// This feature is not part of the specification and is just a convenient
53    /// way of creating identifiers.
54    ///
55    /// Each possible byte string will map to an uniformly random identifier.
56    /// Returns an error if the ciphersuite does not support identifier derivation,
57    /// or if the mapped identifier is zero (which is unpredictable, but should happen
58    /// with negligible probability).
59    pub fn derive(s: &[u8]) -> Result<Self, Error<C>> {
60        let scalar = C::HID(s).ok_or(Error::IdentifierDerivationNotSupported)?;
61        Self::new(scalar)
62    }
63
64    /// Serialize the identifier using the ciphersuite encoding.
65    pub fn serialize(&self) -> Vec<u8> {
66        self.0.serialize()
67    }
68
69    /// Deserialize an Identifier from a serialized buffer.
70    /// Returns an error if it attempts to deserialize zero.
71    pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
72        Self::new(SerializableScalar::deserialize(bytes)?.0)
73    }
74}
75
76#[cfg(feature = "serde")]
77impl<C> TryFrom<SerializableScalar<C>> for Identifier<C>
78where
79    C: Ciphersuite,
80{
81    type Error = Error<C>;
82
83    fn try_from(s: SerializableScalar<C>) -> Result<Self, Self::Error> {
84        Self::new(s.0)
85    }
86}
87
88#[cfg(feature = "serde")]
89impl<C> From<Identifier<C>> for SerializableScalar<C>
90where
91    C: Ciphersuite,
92{
93    fn from(i: Identifier<C>) -> Self {
94        i.0
95    }
96}
97
98impl<C> Eq for Identifier<C> where C: Ciphersuite {}
99
100impl<C> Debug for Identifier<C>
101where
102    C: Ciphersuite,
103{
104    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105        f.debug_tuple("Identifier")
106            .field(&hex::encode(self.serialize()))
107            .finish()
108    }
109}
110
111#[allow(clippy::derived_hash_with_manual_eq)]
112impl<C> Hash for Identifier<C>
113where
114    C: Ciphersuite,
115{
116    fn hash<H: Hasher>(&self, state: &mut H) {
117        self.serialize().hash(state)
118    }
119}
120
121impl<C> Ord for Identifier<C>
122where
123    C: Ciphersuite,
124{
125    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
126        let serialized_self =
127            <<C::Group as Group>::Field>::little_endian_serialize(&self.to_scalar());
128        let serialized_other =
129            <<C::Group as Group>::Field>::little_endian_serialize(&other.to_scalar());
130        // The default cmp uses lexicographic order; so we need the elements in big endian
131        serialized_self
132            .as_ref()
133            .iter()
134            .rev()
135            .cmp(serialized_other.as_ref().iter().rev())
136    }
137}
138
139impl<C> PartialOrd for Identifier<C>
140where
141    C: Ciphersuite,
142{
143    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
144        Some(self.cmp(other))
145    }
146}
147
148impl<C> TryFrom<u16> for Identifier<C>
149where
150    C: Ciphersuite,
151{
152    type Error = Error<C>;
153
154    fn try_from(n: u16) -> Result<Identifier<C>, Self::Error> {
155        if n == 0 {
156            Err(FieldError::InvalidZeroScalar.into())
157        } else {
158            // Classic left-to-right double-and-add algorithm that skips the first bit 1 (since
159            // identifiers are never zero, there is always a bit 1), thus `sum` starts with 1 too.
160            let one = <<C::Group as Group>::Field>::one();
161            let mut sum = <<C::Group as Group>::Field>::one();
162
163            let bits = (n.to_be_bytes().len() as u32) * 8;
164            for i in (0..(bits - n.leading_zeros() - 1)).rev() {
165                sum = sum + sum;
166                if n & (1 << i) != 0 {
167                    sum = sum + one;
168                }
169            }
170            Self::new(sum)
171        }
172    }
173}