From 7a4c728085e4d347fb946ee9705c162fa1007b76 Mon Sep 17 00:00:00 2001 From: Michael Lodder Date: Fri, 30 Jan 2026 13:08:31 -0700 Subject: [PATCH] update to blsful 3 Signed-off-by: Michael Lodder --- Cargo.toml | 1 + crates/multikey/Cargo.toml | 2 +- crates/multikey/src/mk.rs | 34 ++- crates/multikey/src/views.rs | 2 +- crates/multikey/src/views/bls12381.rs | 270 +++++++++++++++++------ crates/multisig/Cargo.toml | 2 +- crates/multisig/src/ms.rs | 21 +- crates/multisig/src/views.rs | 2 +- crates/multisig/src/views/bls12381.rs | 97 ++++++-- crates/multitrait/src/try_decode_from.rs | 13 ++ 10 files changed, 334 insertions(+), 110 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cc5aba0..b15fa5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ wacc = { path = "crates/wacc" } # Core dependencies best-practices = { version = "0.1.0", git = "https://github.com/cryptidtech/best-practices.git" } +blsful = "3.0.0" criterion = "0.5.1" elliptic-curve = "0.13.8" hex = "0.4.3" diff --git a/crates/multikey/Cargo.toml b/crates/multikey/Cargo.toml index 9de6f03..27a7b95 100644 --- a/crates/multikey/Cargo.toml +++ b/crates/multikey/Cargo.toml @@ -13,7 +13,7 @@ wasm = ["getrandom/wasm_js"] # needed for CI testing on wasm32-unknown-unknown [dependencies] bcrypt-pbkdf = "0.10" -blsful = "2.5" +blsful.workspace = true chacha20poly1305 = "0.10.1" ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } elliptic-curve.workspace = true diff --git a/crates/multikey/src/mk.rs b/crates/multikey/src/mk.rs index da15b2d..a2f009b 100644 --- a/crates/multikey/src/mk.rs +++ b/crates/multikey/src/mk.rs @@ -628,14 +628,17 @@ impl Builder { } }; let key_share = bls12381::KeyShare::try_from(key_bytes.as_ref())?; - let identifier: Vec = Varuint(key_share.0).into(); + let identifier: Vec = key_share.0 .0.to_be_bytes().to_vec(); let threshold: Vec = Varuint(key_share.1).into(); let limit: Vec = Varuint(key_share.2).into(); let mut attributes = Attributes::new(); attributes.insert(AttrId::ShareIdentifier, identifier.into()); attributes.insert(AttrId::Threshold, threshold.into()); attributes.insert(AttrId::Limit, limit.into()); - attributes.insert(AttrId::KeyData, key_share.3.into()); + attributes.insert( + AttrId::KeyData, + key_share.3 .0.to_be_bytes().to_vec().into(), + ); Ok(Builder { codec: Codec::Bls12381G1PubShare, comment: Some(sshkey.comment().to_string()), @@ -673,16 +676,19 @@ impl Builder { } }; let key_share = bls12381::KeyShare::try_from(key_bytes.as_ref())?; - let identifier: Vec = Varuint(key_share.0).into(); + let identifier: Vec = key_share.0 .0.to_be_bytes().to_vec(); let threshold: Vec = Varuint(key_share.1).into(); let limit: Vec = Varuint(key_share.2).into(); let mut attributes = Attributes::new(); attributes.insert(AttrId::ShareIdentifier, identifier.into()); attributes.insert(AttrId::Threshold, threshold.into()); attributes.insert(AttrId::Limit, limit.into()); - attributes.insert(AttrId::KeyData, key_share.3.into()); + attributes.insert( + AttrId::KeyData, + key_share.3 .0.to_be_bytes().to_vec().into(), + ); Ok(Builder { - codec: Codec::Bls12381G1PubShare, + codec: Codec::Bls12381G2PubShare, comment: Some(sshkey.comment().to_string()), attributes: Some(attributes), ..Default::default() @@ -843,14 +849,17 @@ impl Builder { } }; let key_share = bls12381::KeyShare::try_from(key_bytes.as_ref())?; - let identifier: Vec = Varuint(key_share.0).into(); + let identifier: Vec = key_share.0 .0.to_be_bytes().to_vec(); let threshold: Vec = Varuint(key_share.1).into(); let limit: Vec = Varuint(key_share.2).into(); let mut attributes = Attributes::new(); attributes.insert(AttrId::ShareIdentifier, identifier.into()); attributes.insert(AttrId::Threshold, threshold.into()); attributes.insert(AttrId::Limit, limit.into()); - attributes.insert(AttrId::KeyData, key_share.3.into()); + attributes.insert( + AttrId::KeyData, + key_share.3 .0.to_be_bytes().to_vec().into(), + ); Ok(Builder { codec: Codec::Bls12381G1PrivShare, comment: Some(sshkey.comment().to_string()), @@ -888,14 +897,17 @@ impl Builder { } }; let key_share = bls12381::KeyShare::try_from(key_bytes.as_ref())?; - let identifier: Vec = Varuint(key_share.0).into(); + let identifier: Vec = key_share.0 .0.to_be_bytes().to_vec(); let threshold: Vec = Varuint(key_share.1).into(); let limit: Vec = Varuint(key_share.2).into(); let mut attributes = Attributes::new(); attributes.insert(AttrId::ShareIdentifier, identifier.into()); attributes.insert(AttrId::Threshold, threshold.into()); attributes.insert(AttrId::Limit, limit.into()); - attributes.insert(AttrId::KeyData, key_share.3.into()); + attributes.insert( + AttrId::KeyData, + key_share.3 .0.to_be_bytes().to_vec().into(), + ); Ok(Builder { codec: Codec::Bls12381G2PrivShare, comment: Some(sshkey.comment().to_string()), @@ -1005,8 +1017,8 @@ impl Builder { } /// add in the share identifier value - pub fn with_identifier(self, identifier: u8) -> Self { - self.with_attribute(AttrId::ShareIdentifier, &Varuint(identifier).into()) + pub fn with_identifier(self, identifier: impl AsRef<[u8]>) -> Self { + self.with_attribute(AttrId::ShareIdentifier, &identifier.as_ref().to_vec()) } /// add in the threshold data diff --git a/crates/multikey/src/views.rs b/crates/multikey/src/views.rs index 36f42f1..71a7cee 100644 --- a/crates/multikey/src/views.rs +++ b/crates/multikey/src/views.rs @@ -68,7 +68,7 @@ pub trait ThresholdAttrView { /// get the limit value for the multikey fn limit(&self) -> Result; /// get the share identifier for the multikey - fn identifier(&self) -> Result; + fn identifier(&self) -> Result<&[u8], Error>; /// get the codec-specific threshold data fn threshold_data(&self) -> Result<&[u8], Error>; } diff --git a/crates/multikey/src/views/bls12381.rs b/crates/multikey/src/views/bls12381.rs index 3938d25..8b79b46 100644 --- a/crates/multikey/src/views/bls12381.rs +++ b/crates/multikey/src/views/bls12381.rs @@ -8,8 +8,8 @@ use crate::{ KdfAttrView, Multikey, SignView, ThresholdAttrView, ThresholdView, VerifyView, Views, }; use blsful::{ - inner_types::{G1Projective, G2Projective}, - vsss_rs::Share, + inner_types::{G1Projective, G2Projective, Scalar}, + vsss_rs::{Share, ValueGroup}, Bls12381G1Impl, Bls12381G2Impl, PublicKey, PublicKeyShare, SecretKey, SecretKeyShare, Signature, SignatureSchemes, SignatureShare, SECRET_KEY_BYTES, }; @@ -18,10 +18,11 @@ use multicodec::Codec; use multihash::{mh, Multihash}; use multisig::{ms, views::bls12381::SchemeTypeId, Multisig, Views as SigViews}; use multitrait::TryDecodeFrom; -use multiutil::{Varbytes, Varuint}; +use multiutil::Varuint; use ssh_encoding::{Decode, Encode}; use std::{array::TryFromSliceError, collections::BTreeMap}; -use zeroize::Zeroizing; +use vsss_rs::{IdentifierPrimeField, ValuePrimeField}; +use zeroize::{Zeroize, Zeroizing}; /// the RFC 4251 algorithm name for SSH compatibility pub const ALGORITHM_NAME_G1: &str = "bls12_381-g1@multikey"; @@ -33,30 +34,58 @@ pub const ALGORITHM_NAME_G2_SHARE: &str = "bls12_381-g2-share@multikey"; pub const G1_PUBLIC_KEY_BYTES: usize = 48; pub const G2_PUBLIC_KEY_BYTES: usize = 96; +/// Parse 32 bytes to identifier for BLS key/sig shares. +fn bytes_to_identifier(bytes: &[u8]) -> Result, Error> { + let arr: [u8; SECRET_KEY_BYTES] = bytes.try_into().map_err(|_| { + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid share identifier length".to_string(), + )) + })?; + Ok(IdentifierPrimeField( + Option::from(Scalar::from_be_bytes(&arr)).ok_or(Error::Conversions( + ConversionsError::SecretKeyFailure("Invalid share identifier bytes".to_string()), + ))?, + )) +} + +/// Parse 32 bytes to value (scalar) for BLS key shares. +fn bytes_to_value(bytes: &[u8]) -> Result, Error> { + let arr: [u8; SECRET_KEY_BYTES] = bytes.try_into().map_err(|_| { + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid share value length".to_string(), + )) + })?; + Ok(IdentifierPrimeField( + Option::from(Scalar::from_be_bytes(&arr)).ok_or(Error::Conversions( + ConversionsError::SecretKeyFailure("Invalid share value bytes".to_string()), + ))?, + )) +} + /// tuple of the key share data with threshold attributes #[derive(Clone)] pub struct KeyShare( /// identifier - pub u8, + pub IdentifierPrimeField, /// threshold, pub usize, /// limit pub usize, /// key bytes - pub Vec, + pub ValuePrimeField, ); impl From for Vec { fn from(val: KeyShare) -> Self { let mut v = Vec::default(); // add in the share identifier - v.append(&mut Varuint(val.0).into()); + v.extend_from_slice(&val.0 .0.to_be_bytes()); // add in the threshold v.append(&mut Varuint(val.1).into()); // add in the limit v.append(&mut Varuint(val.2).into()); // add in the key share data - v.append(&mut Varbytes(val.3.clone()).into()); + v.extend_from_slice(&val.3 .0.to_be_bytes()); v } } @@ -75,27 +104,36 @@ impl<'a> TryDecodeFrom<'a> for KeyShare { fn try_decode_from(bytes: &'a [u8]) -> Result<(Self, &'a [u8]), Self::Error> { // try to decode the identifier - let (id, ptr) = Varuint::::try_decode_from(bytes)?; + let (id, ptr) = Varuint::<[u8; 32]>::try_decode_from(bytes)?; // try to decode the threshold let (threshold, ptr) = Varuint::::try_decode_from(ptr)?; // try to decode the limit let (limit, ptr) = Varuint::::try_decode_from(ptr)?; // try to decode the key share data - let (key_data, ptr) = Varbytes::try_decode_from(ptr)?; + let (key_data, ptr) = Varuint::<[u8; 32]>::try_decode_from(ptr)?; + + let identifier = IdentifierPrimeField( + Option::::from(Scalar::from_be_bytes(&id.0)).ok_or(Error::Conversions( + ConversionsError::SecretKeyFailure("Invalid share identifier bytes".to_string()), + ))?, + ); + let value = IdentifierPrimeField( + Option::::from(Scalar::from_be_bytes(&key_data.0)).ok_or( + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid share value bytes".to_string(), + )), + )?, + ); + Ok(( - Self( - id.to_inner(), - threshold.to_inner(), - limit.to_inner(), - key_data.to_inner(), - ), + Self(identifier, threshold.to_inner(), limit.to_inner(), value), ptr, )) } } #[derive(Clone, Default)] -pub(crate) struct ThresholdData(pub(crate) BTreeMap); +pub(crate) struct ThresholdData(pub(crate) BTreeMap, KeyShare>); impl From for Vec { fn from(val: ThresholdData) -> Self { @@ -208,13 +246,13 @@ impl ThresholdAttrView for View<'_> { Ok(*Varuint::::try_from(v.as_slice())?) } /// get the share identifier for the multikey - fn identifier(&self) -> Result { + fn identifier(&self) -> Result<&[u8], Error> { let v = self .mk .attributes .get(&AttrId::ShareIdentifier) .ok_or(AttributesError::MissingShareIdentifier)?; - Ok(*Varuint::::try_from(v.as_slice())?) + Ok(v.as_slice()) } /// get the threshold data fn threshold_data(&self) -> Result<&[u8], Error> { @@ -374,23 +412,61 @@ impl ConvView for View<'_> { let av = self.mk.threshold_attr_view()?; let threshold = av.threshold()?; let limit = av.limit()?; - let identifier = av.identifier()?; + let identifier_bytes = av.identifier()?; + if identifier_bytes.len() != SECRET_KEY_BYTES { + return Err(Error::Conversions(ConversionsError::SecretKeyFailure( + "Insufficient number of bytes for secret share identifier".to_string(), + ))); + } + let identifier_array = <[u8; SECRET_KEY_BYTES]>::try_from(identifier_bytes) + .map_err(|_| { + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid bytes for secret share identifier".to_string(), + )) + })?; + let identifier = IdentifierPrimeField( + Option::::from(Scalar::from_be_bytes(&identifier_array)).ok_or( + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid bytes for secret share identifier".to_string(), + )), + )?, + ); - let secret_key: SecretKeyShare = SecretKeyShare( - Share::with_identifier_and_value(identifier, secret_bytes.as_slice()), + if secret_bytes.len() != SECRET_KEY_BYTES { + return Err(Error::Conversions(ConversionsError::SecretKeyFailure( + "Insufficient number of bytes for secret share".to_string(), + ))); + } + let mut secret_array = <[u8; SECRET_KEY_BYTES]>::try_from(secret_bytes.as_slice()) + .map_err(|_| { + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid bytes for secret share".to_string(), + )) + })?; + + let secret = IdentifierPrimeField( + Option::::from(Scalar::from_be_bytes(&secret_array)).ok_or( + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid bytes for secret share".to_string(), + )), + )?, ); + secret_array.zeroize(); + + let secret_key: SecretKeyShare = + SecretKeyShare(Share::with_identifier_and_value(identifier, secret)); // get the public key and build a Multikey out of it let public_key = secret_key .public_key() .map_err(|e| ConversionsError::PublicKeyFailure(e.to_string()))?; - let key_bytes = public_key.0 .0.value_vec(); + let key_bytes = public_key.0 .0.value.0.to_compressed(); Builder::new(Codec::Bls12381G1PubShare) .with_comment(&self.mk.comment) .with_key_bytes(&key_bytes) .with_threshold(threshold) .with_limit(limit) - .with_identifier(identifier) + .with_identifier(public_key.0 .0.identifier.0.to_be_bytes()) .try_build() } Codec::Bls12381G2Priv => { @@ -419,23 +495,56 @@ impl ConvView for View<'_> { let av = self.mk.threshold_attr_view()?; let threshold = av.threshold()?; let limit = av.limit()?; - let identifier = av.identifier()?; + let identifier_bytes = av.identifier()?; + let identifier_array = <[u8; SECRET_KEY_BYTES]>::try_from(identifier_bytes) + .map_err(|_| { + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid bytes for secret share identifier".to_string(), + )) + })?; + let identifier = IdentifierPrimeField( + Option::::from(Scalar::from_be_bytes(&identifier_array)).ok_or( + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid bytes for secret share identifier".to_string(), + )), + )?, + ); + + if secret_bytes.len() != SECRET_KEY_BYTES { + return Err(Error::Conversions(ConversionsError::SecretKeyFailure( + "Insufficient number of bytes for secret share".to_string(), + ))); + } + let mut secret_array = <[u8; SECRET_KEY_BYTES]>::try_from(secret_bytes.as_slice()) + .map_err(|_| { + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid bytes for secret share".to_string(), + )) + })?; - let secret_key: SecretKeyShare = SecretKeyShare( - Share::with_identifier_and_value(identifier, secret_bytes.as_slice()), + let secret = IdentifierPrimeField( + Option::::from(Scalar::from_be_bytes(&secret_array)).ok_or( + Error::Conversions(ConversionsError::SecretKeyFailure( + "Invalid bytes for secret share".to_string(), + )), + )?, ); + secret_array.zeroize(); + + let secret_key: SecretKeyShare = + SecretKeyShare(Share::with_identifier_and_value(identifier, secret)); // get the public key and build a Multikey out of it let public_key = secret_key .public_key() .map_err(|e| ConversionsError::PublicKeyFailure(e.to_string()))?; - let key_bytes = public_key.0 .0.value_vec(); + let key_bytes = public_key.0 .0.value.0.to_compressed(); Builder::new(Codec::Bls12381G1PubShare) .with_comment(&self.mk.comment) .with_key_bytes(&key_bytes) .with_threshold(threshold) .with_limit(limit) - .with_identifier(identifier) + .with_identifier(public_key.0 .0.identifier.0.to_be_bytes()) .try_build() } _ => Err(ConversionsError::UnsupportedCodec(self.mk.codec).into()), @@ -467,10 +576,10 @@ impl ConvView for View<'_> { Codec::Bls12381G1PubShare => { let tav = pk.threshold_attr_view()?; let key_share: Vec = KeyShare( - tav.identifier()?, + bytes_to_identifier(tav.identifier()?)?, tav.threshold()?, tav.limit()?, - key_bytes.to_vec(), + bytes_to_value(key_bytes.as_slice())?, ) .into(); key_share @@ -487,10 +596,10 @@ impl ConvView for View<'_> { Codec::Bls12381G2PubShare => { let tav = pk.threshold_attr_view()?; let key_share: Vec = KeyShare( - tav.identifier()?, + bytes_to_identifier(tav.identifier()?)?, tav.threshold()?, tav.limit()?, - key_bytes.to_vec(), + bytes_to_value(key_bytes.as_slice())?, ) .into(); key_share @@ -547,18 +656,18 @@ impl ConvView for View<'_> { Codec::Bls12381G1PrivShare => { let sav = self.mk.threshold_attr_view()?; let secret_key_share: Vec = KeyShare( - sav.identifier()?, + bytes_to_identifier(sav.identifier()?)?, sav.threshold()?, sav.limit()?, - secret_bytes.to_vec(), + bytes_to_value(secret_bytes.as_slice())?, ) .into(); let pav = pk.threshold_attr_view()?; let public_key_share: Vec = KeyShare( - pav.identifier()?, + bytes_to_identifier(pav.identifier()?)?, pav.threshold()?, pav.limit()?, - key_bytes.to_vec(), + bytes_to_value(key_bytes.as_slice())?, ) .into(); secret_key_share @@ -581,18 +690,18 @@ impl ConvView for View<'_> { Codec::Bls12381G2PrivShare => { let sav = self.mk.threshold_attr_view()?; let secret_key_share: Vec = KeyShare( - sav.identifier()?, + bytes_to_identifier(sav.identifier()?)?, sav.threshold()?, sav.limit()?, - secret_bytes.to_vec(), + bytes_to_value(secret_bytes.as_slice())?, ) .into(); let pav = pk.threshold_attr_view()?; let public_key_share: Vec = KeyShare( - pav.identifier()?, + bytes_to_identifier(pav.identifier()?)?, pav.threshold()?, pav.limit()?, - key_bytes.to_vec(), + bytes_to_value(key_bytes.as_slice())?, ) .into(); secret_key_share @@ -681,11 +790,11 @@ impl SignView for View<'_> { let av = self.mk.threshold_attr_view()?; let threshold = av.threshold()?; let limit = av.limit()?; - let identifier = av.identifier()?; + let identifier = bytes_to_identifier(av.identifier()?)?; + let secret_value = bytes_to_value(secret_bytes.as_slice())?; - let secret_key: SecretKeyShare = SecretKeyShare( - Share::with_identifier_and_value(identifier, secret_bytes.as_slice()), - ); + let secret_key: SecretKeyShare = + SecretKeyShare(Share::with_identifier_and_value(identifier, secret_value)); // sign the data let signature = secret_key @@ -728,11 +837,11 @@ impl SignView for View<'_> { let av = self.mk.threshold_attr_view()?; let threshold = av.threshold()?; let limit = av.limit()?; - let identifier = av.identifier()?; + let identifier = bytes_to_identifier(av.identifier()?)?; + let secret_value = bytes_to_value(secret_bytes.as_slice())?; - let secret_key: SecretKeyShare = SecretKeyShare( - Share::with_identifier_and_value(identifier, secret_bytes.as_slice()), - ); + let secret_key: SecretKeyShare = + SecretKeyShare(Share::with_identifier_and_value(identifier, secret_value)); // sign the data let signature = secret_key @@ -793,8 +902,9 @@ impl ThresholdView for View<'_> { key_shares .iter() .try_for_each(|share| -> Result<(), Error> { - let key_bytes = share.as_raw_value().value_vec(); - let identifier = share.as_raw_value().identifier(); + let raw = share.as_raw_value(); + let key_bytes = raw.value().0.to_be_bytes(); + let identifier = raw.identifier().0.to_be_bytes(); let mk = Builder::new(Codec::Bls12381G1PrivShare) .with_comment(&self.mk.comment) @@ -833,8 +943,9 @@ impl ThresholdView for View<'_> { key_shares .iter() .try_for_each(|share| -> Result<(), Error> { - let key_bytes = share.as_raw_value().value_vec(); - let identifier = share.as_raw_value().identifier(); + let raw = share.as_raw_value(); + let key_bytes = raw.value().0.to_be_bytes(); + let identifier = raw.identifier().0.to_be_bytes(); let mk = Builder::new(Codec::Bls12381G2PrivShare) .with_comment(&self.mk.comment) @@ -871,7 +982,7 @@ impl ThresholdView for View<'_> { let (key_share, identifier, threshold, limit) = { // get the share attributes let av = share.threshold_attr_view()?; - let identifier = av.identifier()?; + let identifier = bytes_to_identifier(av.identifier()?)?; let threshold = av.threshold()?; let limit = av.limit()?; // get the key data @@ -879,7 +990,12 @@ impl ThresholdView for View<'_> { let key_bytes = dv.key_bytes()?; // return the data ( - KeyShare(identifier, threshold, limit, key_bytes.to_vec()), + KeyShare( + identifier, + threshold, + limit, + bytes_to_value(key_bytes.as_slice())?, + ), identifier, threshold, limit, @@ -943,7 +1059,7 @@ impl ThresholdView for View<'_> { .0 .iter() .try_for_each(|(id, share)| -> Result<(), Error> { - let vsss = Share::with_identifier_and_value(*id, share.3.as_slice()); + let vsss = Share::with_identifier_and_value(*id, share.3); shares.push(SecretKeyShare::(vsss)); Ok(()) })?; @@ -961,7 +1077,7 @@ impl ThresholdView for View<'_> { .0 .iter() .try_for_each(|(id, share)| -> Result<(), Error> { - let vsss = Share::with_identifier_and_value(*id, share.3.as_slice()); + let vsss = Share::with_identifier_and_value(*id, share.3); shares.push(SecretKeyShare::(vsss)); Ok(()) })?; @@ -1051,13 +1167,24 @@ impl VerifyView for View<'_> { // get the share identifier let av = multisig.threshold_attr_view()?; - let identifier = av.identifier()?; + let identifier = bytes_to_identifier(av.identifier()?)?; // get the signature data let sv = multisig.data_view()?; - let value = sv.sig_bytes().map_err(|_| VerifyError::MissingSignature)?; + let value_bytes = sv.sig_bytes().map_err(|_| VerifyError::MissingSignature)?; + let value = { + let arr: [u8; G1_PUBLIC_KEY_BYTES] = value_bytes + .as_slice() + .try_into() + .map_err(|e: TryFromSliceError| VerifyError::BadSignature(e.to_string()))?; + Option::from(G1Projective::from_compressed(&arr)).ok_or( + VerifyError::BadSignature( + "failed to deserialize signature share".to_string(), + ), + )? + }; - let share = Share::with_identifier_and_value(identifier, &value); + let share = Share::with_identifier_and_value(identifier, ValueGroup(value)); let sig = match sig_scheme { SchemeTypeId::Basic => SignatureShare::::Basic(share), @@ -1131,21 +1258,32 @@ impl VerifyView for View<'_> { // get the share identifier let av = multisig.threshold_attr_view()?; - let identifier = av.identifier()?; + let identifier = bytes_to_identifier(av.identifier()?)?; // get the signature data let sv = multisig.data_view()?; - let value = sv.sig_bytes().map_err(|_| VerifyError::MissingSignature)?; + let value_bytes = sv.sig_bytes().map_err(|_| VerifyError::MissingSignature)?; + let value = { + let arr: [u8; G2_PUBLIC_KEY_BYTES] = value_bytes + .as_slice() + .try_into() + .map_err(|e: TryFromSliceError| VerifyError::BadSignature(e.to_string()))?; + Option::from(G2Projective::from_compressed(&arr)).ok_or( + VerifyError::BadSignature( + "failed to deserialize signature share".to_string(), + ), + )? + }; - let share = Share::with_identifier_and_value(identifier, &value); + let share = Share::with_identifier_and_value(identifier, ValueGroup(value)); let sig = match sig_scheme { - SchemeTypeId::Basic => SignatureShare::::Basic(share), + SchemeTypeId::Basic => SignatureShare::::Basic(share), SchemeTypeId::MessageAugmentation => { - SignatureShare::::MessageAugmentation(share) + SignatureShare::::MessageAugmentation(share) } SchemeTypeId::ProofOfPossession => { - SignatureShare::::ProofOfPossession(share) + SignatureShare::::ProofOfPossession(share) } }; diff --git a/crates/multisig/Cargo.toml b/crates/multisig/Cargo.toml index fc98424..f3e05f2 100644 --- a/crates/multisig/Cargo.toml +++ b/crates/multisig/Cargo.toml @@ -11,7 +11,7 @@ license = "Apache-2.0" default = ["serde"] [dependencies] -blsful = { version = "2.5.7" } +blsful.workspace = true elliptic-curve.workspace = true multibase.workspace = true multicodec.workspace = true diff --git a/crates/multisig/src/ms.rs b/crates/multisig/src/ms.rs index 6fe1380..f19bd0d 100644 --- a/crates/multisig/src/ms.rs +++ b/crates/multisig/src/ms.rs @@ -7,7 +7,11 @@ use crate::{ }, AttrId, AttrView, ConvView, DataView, Error, ThresholdAttrView, ThresholdView, Views, }; -use blsful::{inner_types::GroupEncoding, vsss_rs::Share, Signature, SignatureShare}; +use blsful::{ + inner_types::{GroupEncoding, PrimeField}, + vsss_rs::Share, + Signature, SignatureShare, +}; use multibase::Base; use multicodec::Codec; use multitrait::{Null, TryDecodeFrom}; @@ -297,7 +301,7 @@ impl Builder { } bls12381::ALGORITHM_NAME_G1_SHARE => { let sig_share = bls12381::SigShare::try_from(sig.as_bytes())?; - attributes.insert(AttrId::ShareIdentifier, Varuint(sig_share.0).into()); + attributes.insert(AttrId::ShareIdentifier, sig_share.0 .0.to_be_bytes().into()); attributes.insert(AttrId::Threshold, Varuint(sig_share.1).into()); attributes.insert(AttrId::Limit, Varuint(sig_share.2).into()); attributes.insert(AttrId::Scheme, sig_share.3.into()); @@ -310,7 +314,7 @@ impl Builder { } bls12381::ALGORITHM_NAME_G2_SHARE => { let sig_share = bls12381::SigShare::try_from(sig.as_bytes())?; - attributes.insert(AttrId::ShareIdentifier, Varuint(sig_share.0).into()); + attributes.insert(AttrId::ShareIdentifier, sig_share.0 .0.to_be_bytes().into()); attributes.insert(AttrId::Threshold, Varuint(sig_share.1).into()); attributes.insert(AttrId::Limit, Varuint(sig_share.2).into()); attributes.insert(AttrId::Scheme, sig_share.3.into()); @@ -334,7 +338,6 @@ impl Builder { { let scheme_type_id = SchemeTypeId::from(sig); let sig_bytes: Vec = sig.as_raw_value().to_bytes().as_ref().to_vec(); - println!("signature length: {}", sig_bytes.len()); let codec = match sig_bytes.len() { 48 => Codec::Bls12381G1Msig, // G1Projective::to_compressed() 96 => Codec::Bls12381G2Msig, // G2Projective::to_compressed() @@ -365,8 +368,8 @@ impl Builder { { let scheme_type_id = SchemeTypeId::from(sigshare); let sigshare = sigshare.as_raw_value(); - let identifier = sigshare.identifier(); - let value = sigshare.value_vec(); + let identifier = sigshare.identifier().0.to_repr().as_ref().to_vec(); + let value = sigshare.value().0.to_bytes().as_ref().to_vec(); let codec = match value.len() { 48 => Codec::Bls12381G1ShareMsig, // large pubkeys, small signatures 96 => Codec::Bls12381G2ShareMsig, // small pubkeys, large signatures @@ -380,7 +383,7 @@ impl Builder { attributes.insert(AttrId::SigData, value); attributes.insert(AttrId::Threshold, Varuint(threshold).into()); attributes.insert(AttrId::Limit, Varuint(limit).into()); - attributes.insert(AttrId::ShareIdentifier, Varuint(identifier).into()); + attributes.insert(AttrId::ShareIdentifier, identifier); attributes.insert(AttrId::Scheme, scheme_type_id.into()); Ok(Self { codec, @@ -435,8 +438,8 @@ impl Builder { } /// add the threshold signature identifier - pub fn with_identifier(self, identifier: u8) -> Self { - self.with_attribute(AttrId::ShareIdentifier, &Varuint(identifier).into()) + pub fn with_identifier(self, identifier: &impl AsRef<[u8]>) -> Self { + self.with_attribute(AttrId::ShareIdentifier, &identifier.as_ref().to_vec()) } /// add the threshold data diff --git a/crates/multisig/src/views.rs b/crates/multisig/src/views.rs index 4679c6a..a363487 100644 --- a/crates/multisig/src/views.rs +++ b/crates/multisig/src/views.rs @@ -39,7 +39,7 @@ pub trait ThresholdAttrView { /// get the limit value for this multisig share fn limit(&self) -> Result; /// get the identifier value for this multisig share - fn identifier(&self) -> Result; + fn identifier(&self) -> Result<&[u8], Error>; /// get the threshold data associated with the signature fn threshold_data(&self) -> Result<&[u8], Error>; } diff --git a/crates/multisig/src/views/bls12381.rs b/crates/multisig/src/views/bls12381.rs index f476402..e6534db 100644 --- a/crates/multisig/src/views/bls12381.rs +++ b/crates/multisig/src/views/bls12381.rs @@ -5,7 +5,9 @@ use crate::{ ThresholdView, Views, }; use blsful::{ - vsss_rs::Share, Bls12381G1Impl, Bls12381G2Impl, Signature, SignatureSchemes, SignatureShare, + inner_types::{G1Projective, G2Projective, Scalar}, + vsss_rs::{IdentifierPrimeField, Share, ValueGroup}, + Bls12381G1Impl, Bls12381G2Impl, Signature, SignatureSchemes, SignatureShare, }; use multicodec::Codec; use multitrait::{EncodeInto, TryDecodeFrom}; @@ -203,7 +205,7 @@ impl<'a> TryDecodeFrom<'a> for SigCombined { #[derive(Clone)] pub struct SigShare( /// identifier - pub u8, + pub IdentifierPrimeField, /// threshold pub usize, /// limit @@ -218,7 +220,7 @@ impl From for Vec { fn from(val: SigShare) -> Self { let mut v = Vec::default(); // add in the share identifier - v.append(&mut Varuint(val.0).into()); + v.append(&mut val.0 .0.to_be_bytes().into()); // add in the share threshold v.append(&mut Varuint(val.1).into()); // add in the share limit @@ -245,7 +247,10 @@ impl<'a> TryDecodeFrom<'a> for SigShare { fn try_decode_from(bytes: &'a [u8]) -> Result<(Self, &'a [u8]), Self::Error> { // try to decode the identifier - let (id, ptr) = Varuint::::try_decode_from(bytes)?; + let (id_bytes, ptr) = Varuint::<[u8; 32]>::try_decode_from(bytes)?; + let id = Option::::from(Scalar::from_be_bytes(&id_bytes)).ok_or( + Error::FailedConversion("Can't convert identifier to scalar".to_string()), + )?; // try to decode the threshold let (threshold, ptr) = Varuint::::try_decode_from(ptr)?; // try to decode the limit @@ -256,7 +261,7 @@ impl<'a> TryDecodeFrom<'a> for SigShare { let (share_data, ptr) = Varbytes::try_decode_from(ptr)?; Ok(( Self( - id.to_inner(), + IdentifierPrimeField(id), threshold.to_inner(), limit.to_inner(), share_type, @@ -268,7 +273,7 @@ impl<'a> TryDecodeFrom<'a> for SigShare { } #[derive(Clone, Default)] -pub(crate) struct ThresholdData(pub(crate) BTreeMap); +pub(crate) struct ThresholdData(pub(crate) BTreeMap, SigShare>); impl From for Vec { fn from(val: ThresholdData) -> Self { @@ -410,11 +415,28 @@ impl ConvView for View<'_> { let av = self.ms.threshold_attr_view()?; let threshold = av.threshold()?; let limit = av.limit()?; - let identifier = av.identifier()?; + let identifier_bytes = av.identifier()?; + if identifier_bytes.len() != 32 { + return Err(Error::FailedConversion( + "Insufficient identifier bytes".to_string(), + )); + } + let identifier_array = <[u8; 32]>::try_from(identifier_bytes) + .map_err(|_| Error::FailedConversion("Invalid bytes".to_string()))?; + let id_scalar = Option::::from(Scalar::from_be_bytes(&identifier_array)) + .ok_or(Error::FailedConversion( + "Invalid share identifier bytes".to_string(), + ))?; // create the sig share tuple - let sig_data: Vec = - SigShare(identifier, threshold, limit, scheme_type, sig_bytes).into(); + let sig_data: Vec = SigShare( + IdentifierPrimeField(id_scalar), + threshold, + limit, + scheme_type, + sig_bytes, + ) + .into(); Ok(ssh_key::Signature::new( ssh_key::Algorithm::Other( @@ -430,11 +452,28 @@ impl ConvView for View<'_> { let av = self.ms.threshold_attr_view()?; let threshold = av.threshold()?; let limit = av.limit()?; - let identifier = av.identifier()?; + let identifier_bytes = av.identifier()?; + if identifier_bytes.len() != 32 { + return Err(Error::FailedConversion( + "Insufficient identifier bytes".to_string(), + )); + } + let identifier_array = <[u8; 32]>::try_from(identifier_bytes) + .map_err(|_| Error::FailedConversion("Invalid bytes".to_string()))?; + let id_scalar = Option::::from(Scalar::from_be_bytes(&identifier_array)) + .ok_or(Error::FailedConversion( + "Invalid share identifier bytes".to_string(), + ))?; // create the sig share tuple - let sig_data: Vec = - SigShare(identifier, threshold, limit, scheme_type, sig_bytes).into(); + let sig_data: Vec = SigShare( + IdentifierPrimeField(id_scalar), + threshold, + limit, + scheme_type, + sig_bytes, + ) + .into(); Ok(ssh_key::Signature::new( ssh_key::Algorithm::Other( @@ -470,7 +509,7 @@ impl ThresholdAttrView for View<'_> { Ok(Varuint::::try_from(limit.as_slice())?.to_inner()) } /// get the share identifier - fn identifier(&self) -> Result { + fn identifier(&self) -> Result<&[u8], Error> { match self.ms.codec { Codec::Bls12381G1ShareMsig | Codec::Bls12381G2ShareMsig => { let identifier = self @@ -478,7 +517,7 @@ impl ThresholdAttrView for View<'_> { .attributes .get(&AttrId::ShareIdentifier) .ok_or(AttributesError::MissingIdentifier)?; - Ok(Varuint::::try_from(identifier.as_slice())?.to_inner()) + Ok(identifier.as_slice()) } _ => Err(SharesError::NotASignatureShare.into()), } @@ -533,7 +572,7 @@ impl ThresholdView for View<'_> { // and the payload encoding value let share = Builder::new(codec) .with_message_bytes(&self.ms.message.as_slice()) - .with_identifier(share.0) + .with_identifier(&share.0 .0.to_be_bytes()) .with_threshold(share.1) .with_limit(share.2) .with_signature_bytes(&share.4) @@ -566,7 +605,19 @@ impl ThresholdView for View<'_> { let av = share.threshold_attr_view()?; let threshold = av.threshold()?; let limit = av.limit()?; - let identifier = av.identifier()?; + let identifier_bytes = av.identifier()?; + if identifier_bytes.len() != 32 { + return Err(Error::FailedConversion( + "Insufficient number of identifier bytes".to_string(), + )); + } + let identifier_array = <[u8; 32]>::try_from(identifier_bytes) + .map_err(|_| Error::FailedConversion("Incorrect identifier bytes".to_string()))?; + let identifier = IdentifierPrimeField( + Option::::from(Scalar::from_be_bytes(&identifier_array)).ok_or( + Error::FailedConversion("Incorrect identifier bytes".to_string()), + )?, + ); // get the share's signature data let dv = share.data_view()?; @@ -654,8 +705,11 @@ impl ThresholdView for View<'_> { .0 .iter() .try_for_each(|(id, share)| -> Result<(), Error> { - let vsss = Share::with_identifier_and_value(*id, share.4.as_slice()); - // check to make sure all of the shares are of the same type + let inner = G1Projective::try_from(&share.4).map_err(|_| { + Error::FailedConversion("Invalid signature share bytes".to_string()) + })?; + let vsss = Share::with_identifier_and_value(*id, ValueGroup(inner)); + // check to make sure all shares are of the same type if let Some(sti) = share_type_id { if sti != share.3 { return Err(SharesError::ShareTypeMismatch.into()); @@ -694,8 +748,11 @@ impl ThresholdView for View<'_> { .0 .iter() .try_for_each(|(id, share)| -> Result<(), Error> { - let vsss = Share::with_identifier_and_value(*id, share.4.as_slice()); - // check to make sure all of the shares are of the same type + let inner = G2Projective::try_from(&share.4).map_err(|_| { + Error::FailedConversion("Invalid signature share bytes".to_string()) + })?; + let vsss = Share::with_identifier_and_value(*id, ValueGroup(inner)); + // check to make sure all shares are of the same type if let Some(sti) = share_type_id { if sti != share.3 { return Err(SharesError::ShareTypeMismatch.into()); diff --git a/crates/multitrait/src/try_decode_from.rs b/crates/multitrait/src/try_decode_from.rs index 343fb9f..58c9268 100644 --- a/crates/multitrait/src/try_decode_from.rs +++ b/crates/multitrait/src/try_decode_from.rs @@ -76,3 +76,16 @@ impl<'a> TryDecodeFrom<'a> for usize { decode::usize(bytes).map_err(Self::Error::UnsignedVarintDecode) } } + +impl TryDecodeFrom<'_> for [u8; N] { + type Error = Error; + + fn try_decode_from(bytes: &'_ [u8]) -> Result<([u8; N], &'_ [u8]), Self::Error> { + if bytes.len() < 32 { + return Err(Error::UnsignedVarintDecode(decode::Error::Overflow)); + } + let a = <[u8; N]>::try_from(bytes) + .map_err(|_| Self::Error::UnsignedVarintDecode(decode::Error::Insufficient))?; + Ok((a, &bytes[32..])) + } +}