use crate::{error::AttributesError, AttrId, Error, Multikey};
use multi_codec::Codec;
use multi_util::Varuint;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ThresholdParticipant {
pub vlad: Vec<u8>,
pub public_share: Vec<u8>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ThresholdScheme {
ShamirSplit,
Dkg,
}
pub fn threshold_kind(mk: &Multikey) -> Option<ThresholdScheme> {
match mk.codec {
Codec::Bls12381G1PrivShare
| Codec::Bls12381G1PubShare
| Codec::Bls12381G2PrivShare
| Codec::Bls12381G2PubShare => Some(ThresholdScheme::ShamirSplit),
Codec::Ed25519ThreshPrivShare
| Codec::Ed25519ThreshPubShare
| Codec::P256ThreshPrivShare
| Codec::P256ThreshPubShare
| Codec::P384ThreshPrivShare
| Codec::P384ThreshPubShare
| Codec::Secp256K1ThreshPrivShare
| Codec::Secp256K1ThreshPubShare
| Codec::Bls12381ThreshPrivShare
| Codec::Bls12381ThreshPubShare
| Codec::Ed448ThreshPrivShare
| Codec::Ed448ThreshPubShare => Some(ThresholdScheme::Dkg),
_ => None,
}
}
pub fn threshold_params(mk: &Multikey) -> Option<(u16, u16)> {
match threshold_kind(mk)? {
ThresholdScheme::ShamirSplit => {
let t = mk.attributes.get(&AttrId::Threshold)?;
let n = mk.attributes.get(&AttrId::Limit)?;
let t = *Varuint::<usize>::try_from(t.as_slice()).ok()?;
let n = *Varuint::<usize>::try_from(n.as_slice()).ok()?;
Some((u16::try_from(t).ok()?, u16::try_from(n).ok()?))
}
ThresholdScheme::Dkg => {
let t = mk.attributes.get(&AttrId::DkgThreshold)?;
let n = mk.attributes.get(&AttrId::DkgLimit)?;
if t.len() < 2 || n.len() < 2 {
return None;
}
Some((
u16::from_le_bytes([t[0], t[1]]),
u16::from_le_bytes([n[0], n[1]]),
))
}
}
}
pub struct MarkerView<'a> {
mk: &'a Multikey,
}
impl<'a> MarkerView<'a> {
pub fn new(mk: &'a Multikey) -> Self {
Self { mk }
}
pub fn group_public_key(&self) -> Result<Vec<u8>, Error> {
let v = self
.mk
.attributes
.get(&AttrId::ThresholdGroupPublicKey)
.ok_or(AttributesError::MissingGroupPublicKey)?;
Ok(v.to_vec())
}
pub fn participants(&self) -> Result<BTreeMap<Vec<u8>, ThresholdParticipant>, Error> {
let v = self
.mk
.attributes
.get(&AttrId::ThresholdParticipants)
.ok_or(AttributesError::MissingThresholdParticipants)?;
let map: BTreeMap<Vec<u8>, ThresholdParticipant> = serde_cbor::from_slice(v.as_slice())
.map_err(|e| AttributesError::ThresholdMarkerCbor(e.to_string()))?;
const MAX_THRESHOLD_PARTICIPANTS: usize = 1024;
if map.len() > MAX_THRESHOLD_PARTICIPANTS {
return Err(AttributesError::ThresholdMarkerCbor(
"threshold participant registry exceeds maximum size".to_string(),
)
.into());
}
Ok(map)
}
pub fn group_pubkey(&self) -> Result<Vec<u8>, Error> {
self.group_public_key()
}
pub fn threshold(&self) -> Result<u16, Error> {
let v = self
.mk
.attributes
.get(&AttrId::Threshold)
.ok_or(AttributesError::MissingThreshold)?;
let t = *Varuint::<usize>::try_from(v.as_slice())?;
u16::try_from(t).map_err(|_| AttributesError::MissingThreshold.into())
}
pub fn participant_count(&self) -> Result<u16, Error> {
let v = self
.mk
.attributes
.get(&AttrId::Limit)
.ok_or(AttributesError::MissingLimit)?;
let n = *Varuint::<usize>::try_from(v.as_slice())?;
u16::try_from(n).map_err(|_| AttributesError::MissingLimit.into())
}
}
pub fn set_group_public_key(mk: &mut Multikey, group_pubkey: &[u8]) {
mk.attributes.insert(
AttrId::ThresholdGroupPublicKey,
group_pubkey.to_vec().into(),
);
}
pub fn set_participants(
mk: &mut Multikey,
participants: &BTreeMap<Vec<u8>, ThresholdParticipant>,
) -> Result<(), Error> {
let bytes = serde_cbor::to_vec(participants)
.map_err(|e| AttributesError::ThresholdMarkerCbor(e.to_string()))?;
mk.attributes
.insert(AttrId::ThresholdParticipants, bytes.into());
Ok(())
}
pub fn group_public_key(mk: &Multikey) -> Result<Vec<u8>, Error> {
MarkerView::new(mk).group_public_key()
}
pub fn participants(mk: &Multikey) -> Result<BTreeMap<Vec<u8>, ThresholdParticipant>, Error> {
MarkerView::new(mk).participants()
}
pub fn canonical_marker_bytes(mk: &Multikey) -> Result<Vec<u8>, Error> {
let mv = MarkerView::new(mk);
let group = mv.group_public_key()?;
let participants = mv.participants()?;
let threshold = mv.threshold()?;
let limit = mv.participant_count()?;
let payload = (group, participants, threshold, limit);
serde_cbor::to_vec(&payload)
.map_err(|e| AttributesError::ThresholdMarkerCbor(e.to_string()).into())
}
pub fn sign_marker(mk: &mut Multikey, signer: &Multikey, scheme: Option<u8>) -> Result<(), Error> {
use crate::Views;
let bytes = canonical_marker_bytes(mk)?;
let sig = signer.sign_view()?.sign(&bytes, false, scheme)?;
let sig_bytes: Vec<u8> = sig.into();
mk.attributes
.insert(AttrId::ThresholdMarkerSig, sig_bytes.into());
Ok(())
}
pub fn verify_marker(mk: &Multikey, verifier_pubkey: &Multikey) -> Result<(), Error> {
use crate::Views;
let sig_bytes = mk
.attributes
.get(&AttrId::ThresholdMarkerSig)
.ok_or(AttributesError::MissingThresholdMarkerSig)?;
let sig = multi_sig::Multisig::try_from(sig_bytes.as_slice())
.map_err(|_| AttributesError::ThresholdMarkerSigInvalid)?;
let bytes = canonical_marker_bytes(mk)?;
verifier_pubkey
.verify_view()?
.verify(&sig, Some(&bytes))
.map_err(|_| AttributesError::ThresholdMarkerSigInvalid.into())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Builder, Views};
fn g1_priv() -> Multikey {
Builder::new_from_random_bytes(Codec::Bls12381G1Priv, &mut rand::rng())
.unwrap()
.try_build()
.unwrap()
}
#[test]
fn classify_split_share_g1() {
let mk = g1_priv();
let shares = mk.threshold_view().unwrap().split(3, 5).unwrap();
let share = &shares[0];
assert_eq!(threshold_kind(share), Some(ThresholdScheme::ShamirSplit));
assert_eq!(threshold_params(share), Some((3, 5)));
}
#[test]
fn classify_split_pub_share_g1() {
let mk = g1_priv();
let shares = mk.threshold_view().unwrap().split(2, 4).unwrap();
let pub_share = shares[0].conv_view().unwrap().to_public_key().unwrap();
assert_eq!(pub_share.codec, Codec::Bls12381G1PubShare);
assert_eq!(
threshold_kind(&pub_share),
Some(ThresholdScheme::ShamirSplit)
);
assert_eq!(threshold_params(&pub_share), Some((2, 4)));
}
#[test]
fn classify_dkg_share() {
let mut mk = Builder::new(Codec::Bls12381ThreshPrivShare)
.try_build()
.unwrap();
mk.attributes
.insert(AttrId::DkgThreshold, 2u16.to_le_bytes().to_vec().into());
mk.attributes
.insert(AttrId::DkgLimit, 3u16.to_le_bytes().to_vec().into());
assert_eq!(threshold_kind(&mk), Some(ThresholdScheme::Dkg));
assert_eq!(threshold_params(&mk), Some((2, 3)));
}
#[test]
fn classify_non_threshold_none() {
let ed = Builder::new_from_random_bytes(Codec::Ed25519Priv, &mut rand::rng())
.unwrap()
.try_build()
.unwrap();
assert_eq!(threshold_kind(&ed), None);
assert_eq!(threshold_params(&ed), None);
let bls = g1_priv();
assert_eq!(threshold_kind(&bls), None);
assert_eq!(threshold_params(&bls), None);
}
#[test]
fn marker_roundtrip() {
let mut mk = g1_priv();
set_group_public_key(&mut mk, &[1, 2, 3, 4]);
assert_eq!(group_public_key(&mk).unwrap(), vec![1, 2, 3, 4]);
let mut map = BTreeMap::new();
map.insert(
vec![0u8; 32],
ThresholdParticipant {
vlad: vec![9, 9, 9],
public_share: vec![7; 48],
},
);
set_participants(&mut mk, &map).unwrap();
let got = participants(&mk).unwrap();
assert_eq!(got, map);
}
}