Skip to content

Commit 7274aff

Browse files
authored
🧹 Cleanup Credentials Serialization (#440)
## What? Refactor the JWT serialization/deserialization to avoid unnecessary clone and/or conversions.
2 parents 4904ce6 + 38ca89e commit 7274aff

File tree

2 files changed

+27
-43
lines changed
  • polimec-common

2 files changed

+27
-43
lines changed

polimec-common/common/src/credentials/mod.rs

+20-36
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,15 @@ use pallet_timestamp::Now;
1919
use parity_scale_codec::{Decode, Encode};
2020
use scale_info::{prelude::string::String, TypeInfo};
2121
use serde::{de::Error, ser::SerializeStruct, Serializer};
22-
use sp_runtime::{traits::BadOrigin, DeserializeOwned, RuntimeDebug};
22+
use sp_runtime::{traits::BadOrigin, DeserializeOwned};
2323

2424
pub use jwt_compact::{
2525
alg::{Ed25519, VerifyingKey},
2626
Claims as StandardClaims, *,
2727
};
2828
use serde::Deserializer;
2929

30-
#[derive(
31-
Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, Deserialize, Serialize, MaxEncodedLen,
32-
)]
30+
#[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, TypeInfo, Deserialize, Serialize, MaxEncodedLen, RuntimeDebug)]
3331
#[serde(rename_all = "lowercase")]
3432
pub enum InvestorType {
3533
Retail,
@@ -54,8 +52,8 @@ parameter_types! {
5452
pub const Institutional: InvestorType = InvestorType::Institutional;
5553
}
5654

57-
#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, Deserialize)]
58-
pub struct SampleClaims<AccountId> {
55+
#[derive(Clone, Encode, Decode, Eq, PartialEq, TypeInfo, Deserialize)]
56+
pub struct PolimecPayload<AccountId> {
5957
#[serde(rename = "sub")]
6058
pub subject: AccountId,
6159
#[serde(rename = "iss")]
@@ -75,7 +73,7 @@ impl<T> EnsureOriginWithCredentials<T::RuntimeOrigin> for EnsureInvestor<T>
7573
where
7674
T: frame_system::Config + pallet_timestamp::Config,
7775
{
78-
type Claims = SampleClaims<T::AccountId>;
76+
type Claims = PolimecPayload<T::AccountId>;
7977
type Success = (T::AccountId, Did, InvestorType, Cid);
8078

8179
fn try_origin(
@@ -86,19 +84,14 @@ where
8684
let Some(who) = origin.clone().into_signer() else { return Err(origin) };
8785
let Ok(token) = Self::verify_token(token, verifying_key) else { return Err(origin) };
8886
let claims = token.claims();
89-
// Get the current timestamp from the pallet_timestamp. It is in milliseconds.
87+
// Get current timestamp from pallet_timestamp (milliseconds)
9088
let Ok(now) = Now::<T>::get().try_into() else { return Err(origin) };
9189
let Some(date_time) = claims.expiration else { return Err(origin) };
9290

9391
let timestamp: u64 = date_time.timestamp_millis().try_into().map_err(|_| origin.clone())?;
9492

9593
if claims.custom.subject == who && timestamp >= now {
96-
return Ok((
97-
who,
98-
claims.custom.did.clone(),
99-
claims.custom.investor_type.clone(),
100-
claims.custom.ipfs_cid.clone(),
101-
));
94+
return Ok((who, claims.custom.did.clone(), claims.custom.investor_type, claims.custom.ipfs_cid.clone()));
10295
}
10396

10497
Err(origin)
@@ -111,7 +104,7 @@ where
111104
OuterOrigin: OriginTrait,
112105
{
113106
type Success;
114-
type Claims: Clone + Encode + Decode + Eq + PartialEq + Ord + PartialOrd + TypeInfo + DeserializeOwned;
107+
type Claims: Clone + Encode + Decode + Eq + PartialEq + TypeInfo + DeserializeOwned;
115108

116109
fn try_origin(
117110
origin: OuterOrigin,
@@ -142,49 +135,40 @@ where
142135
D: Deserializer<'de>,
143136
{
144137
String::deserialize(deserializer)
145-
.map(|string| string.as_bytes().to_vec())
146-
.and_then(|vec| vec.try_into().map_err(|_| Error::custom("failed to deserialize")))
138+
.map(|string| string.into_bytes())
139+
.and_then(|vec| BoundedVec::try_from(vec).map_err(|_| Error::custom("DID exceeds length limit")))
147140
}
148141

149142
pub fn from_bounded_cid<'de, D>(deserializer: D) -> Result<Cid, D::Error>
150143
where
151144
D: Deserializer<'de>,
152145
{
153146
String::deserialize(deserializer)
154-
.map(|string| string.as_bytes().to_vec())
155-
.and_then(|vec| vec.try_into().map_err(|_| Error::custom("failed to deserialize")))
147+
.map(|string| string.into_bytes())
148+
.and_then(|vec| BoundedVec::try_from(vec).map_err(|_| Error::custom("CID exceeds length limit")))
156149
}
157150

158-
impl<AccountId> Serialize for SampleClaims<AccountId>
151+
// Key corrected serialization implementation
152+
impl<AccountId> Serialize for PolimecPayload<AccountId>
159153
where
160-
AccountId: Serialize, // Ensure AccountId can be serialized
154+
AccountId: Serialize,
161155
{
162156
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
163157
where
164158
S: Serializer,
165159
{
166160
// Define how many fields we are serializing.
167-
let mut state = serializer.serialize_struct("SampleClaims", 5)?;
161+
let mut state = serializer.serialize_struct("PolimecPayload", 5)?;
168162

169163
// Serialize each field.
170164
// Fields like `subject`, `issuer`, and `investor_type` can be serialized directly.
171165
state.serialize_field("sub", &self.subject)?;
172166
state.serialize_field("iss", &self.issuer)?;
173-
// For the `ipfs_cid_string` field, you'd use your custom logic to convert it to a string or another format suitable for serialization.
174-
// Assuming `cid` is a `BoundedVec<u8, ConstU32<96>>` and you're encoding it as a UTF-8 string.
175-
let ipfs_cid_bytes: scale_info::prelude::vec::Vec<u8> = self.ipfs_cid.clone().into(); // Convert BoundedVec to Vec<u8>
176-
let ipfs_cid_string = String::from_utf8_lossy(&ipfs_cid_bytes); // Convert Vec<u8> to String
177-
state.serialize_field("aud", &ipfs_cid_string)?;
178-
179167
state.serialize_field("investor_type", &self.investor_type)?;
180-
181-
// For the `did` field, you'd use your custom logic to convert it to a string or another format suitable for serialization.
182-
// Assuming `did` is a `BoundedVec<u8, ConstU32<57>>` and you're encoding it as a UTF-8 string.
183-
let did_bytes: scale_info::prelude::vec::Vec<u8> = self.did.clone().into(); // Convert BoundedVec to Vec<u8>
184-
let did_string = String::from_utf8_lossy(&did_bytes); // Convert Vec<u8> to String
185-
state.serialize_field("did", &did_string)?;
186-
187-
// End the serialization
168+
// Serialize the `ipfs_cid` and `did` fields as strings.
169+
state
170+
.serialize_field("aud", core::str::from_utf8(&self.ipfs_cid).map_err(|e| serde::ser::Error::custom(e))?)?;
171+
state.serialize_field("did", core::str::from_utf8(&self.did).map_err(|e| serde::ser::Error::custom(e))?)?;
188172
state.end()
189173
}
190174
}

polimec-common/test-utils/src/lib.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use alloc::{vec, vec::Vec};
2121
use frame_support::{sp_runtime::app_crypto::sp_core::bytes::to_hex, traits::ConstU32, BoundedVec, Parameter};
2222
use jwt_compact::{alg::Ed25519, AlgorithmExt, Header};
2323
use parity_scale_codec::alloc::string::ToString;
24-
use polimec_common::credentials::{Did, InvestorType, SampleClaims, UntrustedToken};
24+
use polimec_common::credentials::{Did, InvestorType, PolimecPayload, UntrustedToken};
2525
use xcm::{
2626
opaque::{v4::Xcm, VersionedXcm},
2727
v4::{Assets, Location, SendError, SendResult, SendXcm, XcmHash},
@@ -83,7 +83,7 @@ mod jwt_utils {
8383
// Handle optional IPFS CID
8484
let ipfs_cid = ipfs_cid.unwrap_or_else(|| BoundedVec::with_bounded_capacity(96));
8585
let custom_claims =
86-
SampleClaims { subject: account_id, investor_type, issuer: "verifier".to_string(), did, ipfs_cid };
86+
PolimecPayload { subject: account_id, investor_type, issuer: "verifier".to_string(), did, ipfs_cid };
8787

8888
let mut claims = Claims::new(custom_claims);
8989
claims.expiration = Some(Utc.with_ymd_and_hms(2030, 1, 1, 0, 0, 0).unwrap());
@@ -92,7 +92,7 @@ mod jwt_utils {
9292
UntrustedToken::new(&token_string).expect("Failed to parse the JWT")
9393
}
9494

95-
// The `Serialize` trait is needed to serialize the `account_id` into a `SampleClaims` struct.
95+
// The `Serialize` trait is needed to serialize the `account_id` into a `PolimecPayload` struct.
9696
pub fn get_mock_jwt<AccountId: frame_support::Serialize>(
9797
account_id: AccountId,
9898
investor_type: InvestorType,
@@ -101,7 +101,7 @@ mod jwt_utils {
101101
create_jwt(account_id, investor_type, did, None)
102102
}
103103

104-
// The `Serialize` trait is needed to serialize the `account_id` into a `SampleClaims` struct.
104+
// The `Serialize` trait is needed to serialize the `account_id` into a `PolimecPayload` struct.
105105
pub fn get_mock_jwt_with_cid<AccountId: frame_support::Serialize>(
106106
account_id: AccountId,
107107
investor_type: InvestorType,
@@ -189,7 +189,7 @@ mod tests {
189189
alg::{Ed25519, VerifyingKey},
190190
AlgorithmExt,
191191
};
192-
use polimec_common::credentials::{InvestorType, SampleClaims};
192+
use polimec_common::credentials::{InvestorType, PolimecPayload};
193193

194194
#[test]
195195
fn test_get_test_jwt() {
@@ -202,7 +202,7 @@ mod tests {
202202
)
203203
.unwrap();
204204
let token = get_mock_jwt("0x1234", InvestorType::Institutional, generate_did_from_account(40u64));
205-
let res = Ed25519.validator::<SampleClaims<String>>(&verifying_key).validate(&token);
205+
let res = Ed25519.validator::<PolimecPayload<String>>(&verifying_key).validate(&token);
206206
assert!(res.is_ok());
207207
}
208208

@@ -224,7 +224,7 @@ mod tests {
224224
generate_did_from_account(40u64),
225225
bounded_cid.clone(),
226226
);
227-
let res = Ed25519.validator::<SampleClaims<String>>(&verifying_key).validate(&token);
227+
let res = Ed25519.validator::<PolimecPayload<String>>(&verifying_key).validate(&token);
228228
assert!(res.is_ok());
229229
let validated_token = res.unwrap();
230230
let claims = validated_token.claims();

0 commit comments

Comments
 (0)