diff --git a/CHANGELOG.md b/CHANGELOG.md index ca55f6e4..c7966c71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a typo in the Python type stubs for `ferveo.Keypair.secure_randomness_size()`. ([#61]) +### Added + +- Added `equals` method to protocol objects in WASM bindings ([#56]) [ferveo#127]: https://github.com/nucypher/nucypher-core/pull/127 [#61]: https://github.com/nucypher/nucypher-core/pull/61 @@ -39,8 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#53]: https://github.com/nucypher/nucypher-core/pull/53 -[#58]: https://github.com/nucypher/nucypher-core/pull/58 [#54]: https://github.com/nucypher/nucypher-core/pull/54 +[#56]: https://github.com/nucypher/nucypher-core/pull/56 +[#58]: https://github.com/nucypher/nucypher-core/pull/58 ## [0.8.0] - 2023-05-23 diff --git a/Cargo.lock b/Cargo.lock index f0bc1e8d..247cd093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -912,6 +912,7 @@ dependencies = [ "console_error_panic_hook", "derive_more", "ferveo-pre-release", + "hex", "js-sys", "nucypher-core", "umbral-pre", diff --git a/nucypher-core-python/src/lib.rs b/nucypher-core-python/src/lib.rs index 5aa09c55..bbf277a5 100644 --- a/nucypher-core-python/src/lib.rs +++ b/nucypher-core-python/src/lib.rs @@ -768,7 +768,7 @@ impl ThresholdDecryptionRequest { _ => { return Err(PyValueError::new_err( "Invalid ThresholdDecryptionRequest variant", - )) + )); } }; diff --git a/nucypher-core-wasm/Cargo.toml b/nucypher-core-wasm/Cargo.toml index 83d7fe18..1f527a1f 100644 --- a/nucypher-core-wasm/Cargo.toml +++ b/nucypher-core-wasm/Cargo.toml @@ -31,4 +31,5 @@ x25519-dalek = "2.0.0-rc.2" [dev-dependencies] console_error_panic_hook = "0.1" +hex = "0.4.3" wasm-bindgen-test = "0.3.36" diff --git a/nucypher-core-wasm/src/lib.rs b/nucypher-core-wasm/src/lib.rs index a84f4fc3..f570bd69 100644 --- a/nucypher-core-wasm/src/lib.rs +++ b/nucypher-core-wasm/src/lib.rs @@ -105,6 +105,55 @@ where .unchecked_into::() } +macro_rules! generate_from_bytes { + ($struct_name:ident) => { + #[wasm_bindgen] + impl $struct_name { + #[wasm_bindgen(js_name = "fromBytes")] + pub fn from_bytes(bytes: &[u8]) -> Result<$struct_name, Error> { + from_bytes(bytes).map(Self) + } + } + }; +} + +macro_rules! generate_to_bytes { + ($struct_name:ident) => { + #[wasm_bindgen] + impl $struct_name { + #[wasm_bindgen(js_name = "toBytes")] + pub fn to_bytes(&self) -> Box<[u8]> { + to_bytes(self) + } + } + }; +} + +macro_rules! generate_equals { + ($struct_name:ident) => { + #[wasm_bindgen] + impl $struct_name { + #[wasm_bindgen] + pub fn equals(&self, other: &$struct_name) -> bool { + self.0 == other.0 + } + } + }; +} + +macro_rules! generate_to_string { + ($struct_name:ident) => { + #[wasm_bindgen] + impl $struct_name { + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = toString)] + pub fn to_string(&self) -> String { + format!("{}", self.0) + } + } + }; +} + #[wasm_bindgen] extern "C" { #[wasm_bindgen(typescript_type = "VerifiedCapsuleFrag[]")] @@ -155,6 +204,9 @@ extern "C" { #[wasm_bindgen] pub struct Conditions(nucypher_core::Conditions); +generate_to_string!(Conditions); +generate_equals!(Conditions); + #[wasm_bindgen] impl Conditions { #[wasm_bindgen(constructor)] @@ -162,17 +214,12 @@ impl Conditions { Self(nucypher_core::Conditions::new(conditions)) } + // TODO: Why is this called `from_bytes` and not `from_string`? #[wasm_bindgen(js_name = fromBytes)] pub fn from_bytes(data: &str) -> Self { let data_owned: String = data.into(); Self(nucypher_core::Conditions::from(data_owned)) } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - self.0.as_ref().into() - } } #[derive(TryFromJsValue)] @@ -180,6 +227,9 @@ impl Conditions { #[derive(Clone)] pub struct Context(nucypher_core::Context); +generate_to_string!(Context); +generate_equals!(Context); + #[wasm_bindgen] impl Context { #[wasm_bindgen(constructor)] @@ -187,17 +237,12 @@ impl Context { Self(nucypher_core::Context::new(context)) } + // TODO: Why is this called `from_bytes` and not `from_string`? #[wasm_bindgen(js_name = fromBytes)] pub fn from_bytes(data: &str) -> Self { let data_owned: String = data.into(); Self(nucypher_core::Context::from(data_owned)) } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - self.0.as_ref().into() - } } // @@ -209,6 +254,8 @@ impl Context { #[derive(Clone, derive_more::AsRef, derive_more::From)] pub struct Address(nucypher_core::Address); +generate_equals!(Address); + #[wasm_bindgen] impl Address { #[wasm_bindgen(constructor)] @@ -230,10 +277,6 @@ impl Address { pub fn to_bytes(&self) -> Box<[u8]> { self.0.as_ref().to_vec().into_boxed_slice() } - - pub fn equals(&self, other: &Address) -> bool { - self.0 == other.0 - } } // @@ -244,6 +287,10 @@ impl Address { #[derive(PartialEq, Debug, derive_more::From, derive_more::AsRef)] pub struct MessageKit(nucypher_core::MessageKit); +generate_equals!(MessageKit); +generate_from_bytes!(MessageKit); +generate_to_bytes!(MessageKit); + #[wasm_bindgen] impl MessageKit { #[wasm_bindgen(constructor)] @@ -275,16 +322,6 @@ impl MessageKit { self.0.conditions.clone().map(Conditions) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::MessageKit>(data) - } - - #[wasm_bindgen(js_name = toBytes)] - pub fn to_bytes(&self) -> Box<[u8]> { - to_bytes(self) - } - #[wasm_bindgen(js_name = decryptReencrypted)] pub fn decrypt_reencrypted( &self, @@ -313,6 +350,9 @@ impl MessageKit { #[derive(PartialEq, Eq)] pub struct HRAC(nucypher_core::HRAC); +generate_to_string!(HRAC); +generate_equals!(HRAC); + #[wasm_bindgen] impl HRAC { #[wasm_bindgen(constructor)] @@ -338,12 +378,6 @@ impl HRAC { pub fn to_bytes(&self) -> Box<[u8]> { self.0.as_ref().to_vec().into_boxed_slice() } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - format!("{}", self.0) - } } // @@ -355,6 +389,8 @@ impl HRAC { #[derive(Clone, PartialEq, Debug, derive_more::From, derive_more::AsRef)] pub struct EncryptedKeyFrag(nucypher_core::EncryptedKeyFrag); +generate_from_bytes!(EncryptedKeyFrag); + #[wasm_bindgen] impl EncryptedKeyFrag { #[wasm_bindgen(constructor)] @@ -384,11 +420,6 @@ impl EncryptedKeyFrag { .map(VerifiedKeyFrag::from) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::EncryptedKeyFrag>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -403,6 +434,9 @@ impl EncryptedKeyFrag { #[derive(Clone, PartialEq, Debug, derive_more::From, derive_more::AsRef)] pub struct TreasureMap(nucypher_core::TreasureMap); +generate_equals!(TreasureMap); +generate_from_bytes!(TreasureMap); + #[wasm_bindgen] impl TreasureMap { #[wasm_bindgen(constructor)] @@ -499,11 +533,6 @@ impl TreasureMap { PublicKey::from(self.0.publisher_verifying_key) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::TreasureMap>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -518,6 +547,9 @@ impl TreasureMap { #[derive(PartialEq, Debug, derive_more::From, derive_more::AsRef)] pub struct EncryptedTreasureMap(nucypher_core::EncryptedTreasureMap); +generate_equals!(EncryptedTreasureMap); +generate_from_bytes!(EncryptedTreasureMap); + #[wasm_bindgen] impl EncryptedTreasureMap { pub fn decrypt( @@ -531,11 +563,6 @@ impl EncryptedTreasureMap { .map(TreasureMap) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::EncryptedTreasureMap>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -554,33 +581,24 @@ pub struct SessionSharedSecret(nucypher_core::SessionSharedSecret); #[derive(PartialEq, Eq, Debug, derive_more::From, derive_more::AsRef)] pub struct SessionStaticKey(nucypher_core::SessionStaticKey); +generate_equals!(SessionStaticKey); +generate_from_bytes!(SessionStaticKey); +generate_to_string!(SessionStaticKey); + #[wasm_bindgen] impl SessionStaticKey { - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::SessionStaticKey>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - format!("{}", self.0) - } - - pub fn equals(&self, other: &SessionStaticKey) -> bool { - self.0 == other.0 - } } #[wasm_bindgen] #[derive(derive_more::From, derive_more::AsRef)] pub struct SessionStaticSecret(nucypher_core::SessionStaticSecret); +generate_to_string!(SessionStaticSecret); + #[wasm_bindgen] impl SessionStaticSecret { /// Generates a secret key using the default RNG and returns it. @@ -598,17 +616,13 @@ impl SessionStaticSecret { pub fn derive_shared_secret(&self, their_public_key: &SessionStaticKey) -> SessionSharedSecret { SessionSharedSecret(self.0.derive_shared_secret(their_public_key.as_ref())) } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - format!("{}", self.0) - } } #[wasm_bindgen] pub struct SessionSecretFactory(nucypher_core::SessionSecretFactory); +generate_to_string!(SessionSecretFactory); + #[wasm_bindgen] impl SessionSecretFactory { /// Generates a secret key factory using the default RNG and returns it. @@ -632,12 +646,6 @@ impl SessionSecretFactory { pub fn make_key(&self, label: &[u8]) -> SessionStaticSecret { SessionStaticSecret(self.0.make_key(label)) } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - format!("{}", self.0) - } } // @@ -648,6 +656,9 @@ impl SessionSecretFactory { #[derive(PartialEq, Eq, Debug, derive_more::From, derive_more::AsRef)] pub struct ThresholdDecryptionRequest(nucypher_core::ThresholdDecryptionRequest); +generate_from_bytes!(ThresholdDecryptionRequest); +generate_equals!(ThresholdDecryptionRequest); + #[wasm_bindgen] impl ThresholdDecryptionRequest { #[wasm_bindgen(constructor)] @@ -705,11 +716,6 @@ impl ThresholdDecryptionRequest { ) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::ThresholdDecryptionRequest>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -724,6 +730,8 @@ impl ThresholdDecryptionRequest { #[derive(PartialEq, Debug, derive_more::From, derive_more::AsRef)] pub struct EncryptedThresholdDecryptionRequest(nucypher_core::EncryptedThresholdDecryptionRequest); +generate_from_bytes!(EncryptedThresholdDecryptionRequest); + #[wasm_bindgen] impl EncryptedThresholdDecryptionRequest { #[wasm_bindgen(getter, js_name = ritualId)] @@ -746,11 +754,6 @@ impl EncryptedThresholdDecryptionRequest { .map(ThresholdDecryptionRequest) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::EncryptedThresholdDecryptionRequest>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -765,6 +768,8 @@ impl EncryptedThresholdDecryptionRequest { #[derive(PartialEq, Eq, Debug, derive_more::From, derive_more::AsRef)] pub struct ThresholdDecryptionResponse(nucypher_core::ThresholdDecryptionResponse); +generate_from_bytes!(ThresholdDecryptionResponse); + #[wasm_bindgen] impl ThresholdDecryptionResponse { #[wasm_bindgen(constructor)] @@ -795,11 +800,6 @@ impl ThresholdDecryptionResponse { EncryptedThresholdDecryptionResponse(self.0.encrypt(shared_secret.as_ref())) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::ThresholdDecryptionResponse>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -816,6 +816,8 @@ pub struct EncryptedThresholdDecryptionResponse( nucypher_core::EncryptedThresholdDecryptionResponse, ); +generate_from_bytes!(EncryptedThresholdDecryptionResponse); + #[wasm_bindgen] impl EncryptedThresholdDecryptionResponse { #[wasm_bindgen(getter, js_name = ritualId)] @@ -833,11 +835,6 @@ impl EncryptedThresholdDecryptionResponse { .map(ThresholdDecryptionResponse) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::EncryptedThresholdDecryptionResponse>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -852,6 +849,8 @@ impl EncryptedThresholdDecryptionResponse { #[derive(PartialEq, Debug, derive_more::From, derive_more::AsRef)] pub struct ReencryptionRequest(nucypher_core::ReencryptionRequest); +generate_from_bytes!(ReencryptionRequest); + #[wasm_bindgen] impl ReencryptionRequest { #[wasm_bindgen(constructor)] @@ -907,11 +906,6 @@ impl ReencryptionRequest { into_js_array(self.0.capsules.iter().cloned().map(Capsule::from)) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::ReencryptionRequest>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -936,6 +930,8 @@ impl ReencryptionRequest { #[derive(derive_more::From, derive_more::AsRef)] pub struct ReencryptionResponse(nucypher_core::ReencryptionResponse); +generate_from_bytes!(ReencryptionResponse); + #[wasm_bindgen] impl ReencryptionResponse { #[wasm_bindgen(constructor)] @@ -976,11 +972,6 @@ impl ReencryptionResponse { ))) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::ReencryptionResponse>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -1031,6 +1022,8 @@ impl ReencryptionResponse { #[derive(derive_more::From, derive_more::AsRef)] pub struct RetrievalKit(nucypher_core::RetrievalKit); +generate_from_bytes!(RetrievalKit); + #[wasm_bindgen] impl RetrievalKit { #[wasm_bindgen(constructor)] @@ -1069,11 +1062,6 @@ impl RetrievalKit { into_js_array(self.0.queried_addresses.iter().cloned().map(Address::from)) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::RetrievalKit>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -1093,6 +1081,8 @@ impl RetrievalKit { #[derive(PartialEq, Debug, derive_more::From, derive_more::AsRef)] pub struct RevocationOrder(nucypher_core::RevocationOrder); +generate_from_bytes!(RevocationOrder); + #[wasm_bindgen] impl RevocationOrder { #[wasm_bindgen(constructor)] @@ -1124,11 +1114,6 @@ impl RevocationOrder { ])) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::RevocationOrder>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -1235,6 +1220,8 @@ impl NodeMetadataPayload { #[derive(Clone, PartialEq, Eq, Debug, derive_more::From, derive_more::AsRef)] pub struct NodeMetadata(nucypher_core::NodeMetadata); +generate_from_bytes!(NodeMetadata); + #[wasm_bindgen] impl NodeMetadata { #[wasm_bindgen(constructor)] @@ -1254,11 +1241,6 @@ impl NodeMetadata { NodeMetadataPayload(self.0.payload.clone()) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::NodeMetadata>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -1273,6 +1255,8 @@ impl NodeMetadata { #[derive(Clone, derive_more::AsRef)] pub struct FleetStateChecksum(nucypher_core::FleetStateChecksum); +generate_to_string!(FleetStateChecksum); + #[wasm_bindgen] impl FleetStateChecksum { #[wasm_bindgen(constructor)] @@ -1296,12 +1280,6 @@ impl FleetStateChecksum { pub fn to_bytes(&self) -> Box<[u8]> { self.0.as_ref().to_vec().into_boxed_slice() } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - format!("{}", self.0) - } } // @@ -1312,6 +1290,8 @@ impl FleetStateChecksum { #[derive(derive_more::From, derive_more::AsRef)] pub struct MetadataRequest(nucypher_core::MetadataRequest); +generate_from_bytes!(MetadataRequest); + #[wasm_bindgen] impl MetadataRequest { #[wasm_bindgen(constructor)] @@ -1340,11 +1320,6 @@ impl MetadataRequest { into_js_array(self.0.announce_nodes.iter().cloned().map(NodeMetadata)) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::MetadataRequest>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) @@ -1395,6 +1370,8 @@ impl MetadataResponsePayload { #[derive(derive_more::From, derive_more::AsRef)] pub struct MetadataResponse(nucypher_core::MetadataResponse); +generate_from_bytes!(MetadataResponse); + #[wasm_bindgen] impl MetadataResponse { #[wasm_bindgen(constructor)] @@ -1414,11 +1391,6 @@ impl MetadataResponse { .map(MetadataResponsePayload) } - #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { - from_bytes::<_, nucypher_core::MetadataResponse>(data) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { to_bytes(self) diff --git a/nucypher-core/Cargo.toml b/nucypher-core/Cargo.toml index f52c8bc4..a5c61b70 100644 --- a/nucypher-core/Cargo.toml +++ b/nucypher-core/Cargo.toml @@ -13,16 +13,16 @@ categories = ["cryptography", "no-std"] umbral-pre = { version = "0.10.0", features = ["serde"] } ferveo = { package = "ferveo-pre-release", version = "0.1.0-alpha.8", git = "https://github.com/nucypher/ferveo.git", rev = "ccdc20990ed3ad6ed8267e5dc54745a3a500b730" } serde = { version = "1", default-features = false, features = ["derive"] } -generic-array = { version="0.14", features = ["zeroize"] } +generic-array = { version = "0.14", features = ["zeroize"] } sha3 = "0.10" rmp-serde = "1" serde_with = "1.14" hex = "0.4" hkdf = "0.12.3" sha2 = "0.10.6" -x25519-dalek = { version="2.0.0-rc.2", features = ["serde", "static_secrets"] } +x25519-dalek = { version = "2.0.0-rc.2", features = ["serde", "static_secrets"] } chacha20poly1305 = "0.10.1" -zeroize = { version="1.6.0", features = ["derive"] } +zeroize = { version = "1.6.0", features = ["derive"] } rand_core = "0.6.4" rand_chacha = "0.3.1" rand = "0.8.5" diff --git a/nucypher-core/src/conditions.rs b/nucypher-core/src/conditions.rs index d053ff78..8330daf2 100644 --- a/nucypher-core/src/conditions.rs +++ b/nucypher-core/src/conditions.rs @@ -1,4 +1,5 @@ use alloc::string::String; +use core::fmt; use serde::{Deserialize, Serialize}; @@ -25,6 +26,12 @@ impl From for Conditions { } } +impl fmt::Display for Conditions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Conditions({})", self.0) + } +} + /// Context for reencryption conditions. #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct Context(String); @@ -47,3 +54,9 @@ impl From for Context { Self(source) } } + +impl fmt::Display for Context { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Context({})", self.0) + } +}