nym-node startup cleanup
This commit is contained in:
@@ -138,7 +138,7 @@ impl Carrier {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{carrier::Carrier, key_utils::generate_keypair_x25519};
|
||||
use crate::{carrier::Carrier, key_utils::generate_lp_keypair_x25519};
|
||||
use rand09::RngCore;
|
||||
|
||||
#[test]
|
||||
@@ -146,12 +146,12 @@ mod tests {
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
// generate responder x25519 keys
|
||||
let r_x25519 = generate_keypair_x25519(&mut rng);
|
||||
let r_x25519 = generate_lp_keypair_x25519(&mut rng);
|
||||
|
||||
let mut context: [u8; 32] = [0u8; 32];
|
||||
rng.fill_bytes(&mut context);
|
||||
|
||||
let ephemeral_keypair = generate_keypair_x25519(&mut rng);
|
||||
let ephemeral_keypair = generate_lp_keypair_x25519(&mut rng);
|
||||
|
||||
let i_shared_secret = ephemeral_keypair.sk().diffie_hellman(&r_x25519.pk).unwrap();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, HashFunction, KeyDigests};
|
||||
use rand09::{CryptoRng, RngCore};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn generate_keypair_x25519<R>(rng: &mut R) -> DHKeyPair
|
||||
pub fn generate_lp_keypair_x25519<R>(rng: &mut R) -> DHKeyPair
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ mod test {
|
||||
use crate::{
|
||||
initiator::KKTInitiator,
|
||||
key_utils::{
|
||||
generate_keypair_mceliece, generate_keypair_mlkem, generate_keypair_x25519,
|
||||
generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519,
|
||||
hash_encapsulation_key,
|
||||
},
|
||||
responder::KKTResponder,
|
||||
@@ -33,7 +33,7 @@ mod test {
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
// generate responder x25519 keys
|
||||
let responder_x25519_keypair = generate_keypair_x25519(&mut rng);
|
||||
let responder_x25519_keypair = generate_lp_keypair_x25519(&mut rng);
|
||||
|
||||
for hash_function in [
|
||||
HashFunction::Blake3,
|
||||
|
||||
@@ -153,7 +153,7 @@ pub fn mock_peer() -> LpLocalPeer {
|
||||
|
||||
#[cfg(any(feature = "mock", test))]
|
||||
pub fn random_peer<R: rand09::CryptoRng + rand09::RngCore>(rng: &mut R) -> LpLocalPeer {
|
||||
let x25519 = Arc::new(nym_kkt::key_utils::generate_keypair_x25519(rng));
|
||||
let x25519 = Arc::new(nym_kkt::key_utils::generate_lp_keypair_x25519(rng));
|
||||
|
||||
LpLocalPeer {
|
||||
ciphersuite: Ciphersuite::default(),
|
||||
|
||||
@@ -14,20 +14,12 @@
|
||||
//! LP(Sphinx) decrypt LP forward Sphinx
|
||||
//! ```
|
||||
//!
|
||||
//! # Wire Format
|
||||
//!
|
||||
//! Each UDP packet is a complete LP packet:
|
||||
//! - Header (8 bytes): receiver_idx (4) + counter (4)
|
||||
//! - Payload: Outer AEAD encrypted Sphinx packet
|
||||
//!
|
||||
//! The receiver_idx is used to look up the session established during LP registration.
|
||||
|
||||
use super::LpHandlerState;
|
||||
use crate::node::lp_listener::error::LpHandlerError;
|
||||
use crate::GatewayError;
|
||||
use nym_lp::state_machine::{LpAction, LpInput};
|
||||
use nym_lp::OuterHeader;
|
||||
use nym_metrics::inc;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UdpSocket;
|
||||
@@ -43,6 +35,7 @@ pub struct LpDataHandler {
|
||||
socket: Arc<UdpSocket>,
|
||||
|
||||
/// Shared state with TCP control plane
|
||||
#[allow(dead_code)]
|
||||
state: LpHandlerState,
|
||||
|
||||
/// Shutdown token
|
||||
@@ -127,8 +120,17 @@ impl LpDataHandler {
|
||||
packet: &[u8],
|
||||
src_addr: SocketAddr,
|
||||
) -> Result<(), LpHandlerError> {
|
||||
todo!()
|
||||
// inc!("lp_data_packets_received");
|
||||
inc!("lp_data_packets_received");
|
||||
|
||||
let _ = OuterHeader::parse(&packet)?;
|
||||
trace!(
|
||||
"received {} bytes from {src_addr} on the unimplemented LP Data endpoint",
|
||||
packet.len()
|
||||
);
|
||||
|
||||
Err(LpHandlerError::UnimplementedDataChannel)
|
||||
// leave old code for future reference
|
||||
|
||||
//
|
||||
// // Step 1: Parse LP header (always cleartext for routing)
|
||||
// let header = nym_lp::codec::parse_lp_header_only(packet).map_err(|e| {
|
||||
@@ -220,39 +222,6 @@ impl LpDataHandler {
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/// Parse Sphinx packet bytes and forward to mixnet
|
||||
///
|
||||
/// The decrypted LP payload contains a serialized MixPacket that includes:
|
||||
/// - Packet type (1 byte)
|
||||
/// - Key rotation (1 byte)
|
||||
/// - Next hop address (first mix node)
|
||||
/// - Sphinx packet data
|
||||
async fn forward_sphinx_packet(&self, sphinx_bytes: &[u8]) -> Result<(), LpHandlerError> {
|
||||
todo!()
|
||||
// // Parse as MixPacket v2 format (packet_type || key_rotation || next_hop || packet)
|
||||
// let mix_packet = MixPacket::try_from_v2_bytes(sphinx_bytes).map_err(|e| {
|
||||
// inc!("lp_data_sphinx_parse_errors");
|
||||
// LpHandlerError::LpProtocolError(format!("Failed to parse MixPacket: {e}"))
|
||||
// })?;
|
||||
//
|
||||
// trace!(
|
||||
// "Forwarding Sphinx packet to mixnet (next_hop={}, type={:?})",
|
||||
// mix_packet.next_hop(),
|
||||
// mix_packet.packet_type()
|
||||
// );
|
||||
//
|
||||
// // Forward to mixnet via the shared channel
|
||||
// if let Err(e) = self.state.outbound_mix_sender.forward_packet(mix_packet) {
|
||||
// error!("Failed to forward Sphinx packet to mixnet: {}", e);
|
||||
// inc!("lp_data_forward_errors");
|
||||
// return Err(LpHandlerError::InternalError(format!(
|
||||
// "Mix packet forwarding failed: {e}",
|
||||
// )));
|
||||
// }
|
||||
//
|
||||
// Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -49,6 +49,9 @@ pub enum LpHandlerError {
|
||||
#[error("timed out while attempting to send to/receive from the connection")]
|
||||
ConnectionTimeout,
|
||||
|
||||
#[error("data channel is not yet implemented")]
|
||||
UnimplementedDataChannel,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
@@ -597,10 +597,16 @@ where
|
||||
/// The caller should look up the session to get outer_aead_key, then call
|
||||
/// `parse_lp_packet()` with the key.
|
||||
async fn receive_raw_packet(&mut self) -> Result<EncryptedLpPacket, LpHandlerError> {
|
||||
Ok(self
|
||||
let packet = self
|
||||
.stream
|
||||
.receive_length_prefixed_transport_packet()
|
||||
.await?)
|
||||
.await?;
|
||||
|
||||
// Track bytes sent (4 byte header + packet data)
|
||||
self.stats
|
||||
.record_bytes_received(4 + packet.encoded_length());
|
||||
|
||||
Ok(packet)
|
||||
}
|
||||
|
||||
/// Send a serialised LP packet over the stream with proper length-prefixed framing.
|
||||
|
||||
@@ -552,7 +552,6 @@ impl LpListener {
|
||||
pub(crate) mod cleanup_task {
|
||||
use crate::node::lp_listener::{LpDebug, ReceiverIndex, TimestampedState};
|
||||
use dashmap::DashMap;
|
||||
use nym_lp::state_machine::LpStateBare;
|
||||
use nym_lp::LpStateMachine;
|
||||
use nym_metrics::inc_by;
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
|
||||
@@ -16,7 +16,7 @@ use nym_credential_verification::ecash::{
|
||||
use nym_credential_verification::upgrade_mode::{
|
||||
UpgradeModeCheckConfig, UpgradeModeDetails, UpgradeModeState,
|
||||
};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ip_packet_router::IpPacketRouter;
|
||||
use nym_lp::peer::{DHKeyPair, KEMKeys, LpLocalPeer};
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
|
||||
@@ -19,7 +19,7 @@ mod tests {
|
||||
use nym_gateway::node::wireguard::{PeerManager, PeerRegistrator};
|
||||
use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig};
|
||||
use nym_kkt::key_utils::{
|
||||
generate_keypair_mceliece, generate_keypair_mlkem, generate_keypair_x25519,
|
||||
generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519,
|
||||
};
|
||||
use nym_kkt_ciphersuite::Ciphersuite;
|
||||
use nym_registration_client::{LpClientError, LpRegistrationClient};
|
||||
@@ -75,7 +75,7 @@ mod tests {
|
||||
let ed25519_keys = ed25519::KeyPair::new(&mut rng08);
|
||||
let x25519_wg_keys = Arc::new(x25519::KeyPair::new(&mut rng08));
|
||||
|
||||
let lp_x25519_keys = Arc::new(generate_keypair_x25519(rng));
|
||||
let lp_x25519_keys = Arc::new(generate_lp_keypair_x25519(rng));
|
||||
let mlkem_keypair = generate_keypair_mlkem(rng);
|
||||
let mceliece_keypair = generate_keypair_mceliece(rng);
|
||||
let lp_kem_keys = KEMKeys::new(mceliece_keypair, mlkem_keypair);
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::config::old_configs::old_config_v12::{
|
||||
UpgradeModeWatcherV12, WireguardV12,
|
||||
};
|
||||
use crate::error::NymNodeError;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::read_config_from_toml_file;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
@@ -37,7 +37,7 @@ use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey;
|
||||
use nym_gateway::node::LpConfig;
|
||||
use nym_gateway::node::lp_listener::LpDebug;
|
||||
use nym_kkt::key_utils::{
|
||||
generate_keypair_mceliece, generate_keypair_mlkem, generate_keypair_x25519,
|
||||
generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519,
|
||||
};
|
||||
use rand09::SeedableRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -45,24 +45,25 @@ use std::env;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, error, info, instrument};
|
||||
pub use unchanged_v12_types::*;
|
||||
use tracing::{debug, info, instrument};
|
||||
use url::Url;
|
||||
|
||||
pub use unchanged_v12_types::*;
|
||||
|
||||
// (while some of those are technically unused, they might be needed in future migrations,
|
||||
// thus allow them to exist)
|
||||
#[allow(dead_code)]
|
||||
pub mod unchanged_v12_types {
|
||||
use crate::config::old_configs::old_config_v11::{
|
||||
AuthenticatorDebugV11, AuthenticatorPathsV11, AuthenticatorV11, ClientBandwidthDebugV11,
|
||||
DebugV11, GatewayTasksPathsV11, HostV11, HttpV11, IpPacketRouterDebugV11,
|
||||
IpPacketRouterPathsV11, IpPacketRouterV11, KeyRotationDebugV11, KeyRotationV11,
|
||||
KeysPathsV11, LoggingSettingsV11, MetricsConfigV11, MetricsDebugV11, MixnetDebugV11,
|
||||
MixnetV11, NetworkRequesterDebugV11, NetworkRequesterPathsV11, NetworkRequesterV11,
|
||||
NodeModeV11, NodeModesV11, NymNodePathsV11, ReplayProtectionDebugV11,
|
||||
ReplayProtectionPathsV11, ReplayProtectionV11, ServiceProvidersConfigDebugV11,
|
||||
ServiceProvidersConfigV11, ServiceProvidersPathsV11, StaleMessageDebugV11, VerlocDebugV11,
|
||||
VerlocV11, WireguardPathsV11, WireguardV11, ZkNymTicketHandlerDebugV11,
|
||||
GatewayTasksPathsV11, HostV11, HttpV11, IpPacketRouterDebugV11, IpPacketRouterPathsV11,
|
||||
IpPacketRouterV11, KeyRotationDebugV11, KeyRotationV11, KeysPathsV11, LoggingSettingsV11,
|
||||
MetricsConfigV11, MetricsDebugV11, MixnetDebugV11, MixnetV11, NetworkRequesterDebugV11,
|
||||
NetworkRequesterPathsV11, NetworkRequesterV11, NodeModeV11, NodeModesV11, NymNodePathsV11,
|
||||
ReplayProtectionDebugV11, ReplayProtectionPathsV11, ReplayProtectionV11,
|
||||
ServiceProvidersConfigDebugV11, ServiceProvidersConfigV11, ServiceProvidersPathsV11,
|
||||
StaleMessageDebugV11, VerlocDebugV11, VerlocV11, WireguardPathsV11,
|
||||
ZkNymTicketHandlerDebugV11,
|
||||
};
|
||||
|
||||
pub type WireguardPathsV12 = WireguardPathsV11;
|
||||
@@ -572,7 +573,7 @@ pub async fn try_upgrade_config_v12<P: AsRef<Path>>(
|
||||
|
||||
// generate new keys for LP
|
||||
info!("generating new LP x25519 DH keypair");
|
||||
let x25519 = generate_keypair_x25519(&mut rng);
|
||||
let x25519 = generate_lp_keypair_x25519(&mut rng);
|
||||
let paths = updated_keys.x25519_lp_key_paths();
|
||||
store_x25519_lp_keypair(&x25519, &paths)?;
|
||||
|
||||
|
||||
+55
-26
@@ -10,7 +10,9 @@ use crate::error::{EntryGatewayError, NymNodeError, ServiceProvidersError};
|
||||
use crate::node::description::{load_node_description, save_node_description};
|
||||
use crate::node::helpers::{
|
||||
DisplayDetails, get_current_rotation_id, load_ed25519_identity_keypair, load_key,
|
||||
load_mceliece_keypair, load_mlkem768_keypair, load_x25519_lp_keypair,
|
||||
load_x25519_noise_keypair, store_ed25519_identity_keypair, store_key, store_keypair,
|
||||
store_mceliece_keypair, store_mlkem768_keypair, store_x25519_lp_keypair,
|
||||
store_x25519_noise_keypair,
|
||||
};
|
||||
use crate::node::http::api::api_requests;
|
||||
@@ -43,6 +45,9 @@ use nym_credential_verification::UpgradeModeState;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_gateway::node::lp_listener::DHKeyPair;
|
||||
use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder, UpgradeModeCheckRequestSender};
|
||||
use nym_kkt::key_utils::{
|
||||
generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519,
|
||||
};
|
||||
use nym_kkt::keys::KEMKeys;
|
||||
use nym_mixnet_client::client::ActiveConnections;
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
@@ -64,6 +69,7 @@ use nym_verloc::{self, measurements::VerlocMeasurer};
|
||||
use nym_wireguard::{WireguardGatewayData, peer_controller::PeerControlRequest};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rand09::SeedableRng;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
@@ -404,10 +410,16 @@ impl NymNode {
|
||||
) -> Result<(), NymNodeError> {
|
||||
debug!("initialising nym-node with id: {}", config.id);
|
||||
let mut rng = OsRng;
|
||||
let mut rng09 = rand09::rngs::StdRng::from_os_rng();
|
||||
|
||||
// global initialisation
|
||||
let ed25519_identity_keys = ed25519::KeyPair::new(&mut rng);
|
||||
let x25519_noise_keys = x25519::KeyPair::new(&mut rng);
|
||||
|
||||
let x25519_lp_keys = generate_lp_keypair_x25519(&mut rng09);
|
||||
let mlkem = generate_keypair_mlkem(&mut rng09);
|
||||
let mceliece = generate_keypair_mceliece(&mut rng09);
|
||||
|
||||
let current_rotation_id =
|
||||
get_current_rotation_id(&config.mixnet.nym_api_urls, &config.mixnet.nyxd_urls).await?;
|
||||
let _ = SphinxKeyManager::initialise_new(
|
||||
@@ -429,6 +441,18 @@ impl NymNode {
|
||||
&config.storage_paths.keys.x25519_noise_storage_paths(),
|
||||
)?;
|
||||
|
||||
trace!("attempting to x25519 lp keypair");
|
||||
store_x25519_lp_keypair(
|
||||
&x25519_lp_keys,
|
||||
&config.storage_paths.keys.x25519_lp_key_paths(),
|
||||
)?;
|
||||
|
||||
trace!("attempting to mlkem768 keypair");
|
||||
store_mlkem768_keypair(&mlkem, &config.storage_paths.keys.mlkem768_key_paths())?;
|
||||
|
||||
trace!("attempting to mceliece keypair");
|
||||
store_mceliece_keypair(&mceliece, &config.storage_paths.keys.mceliece_key_paths())?;
|
||||
|
||||
trace!("creating description file");
|
||||
save_node_description(
|
||||
&config.storage_paths.description,
|
||||
@@ -460,33 +484,38 @@ impl NymNode {
|
||||
&config.storage_paths.keys.ed25519_identity_storage_paths(),
|
||||
)?;
|
||||
let entry_gateway = GatewayTasksData::new(&config.gateway_tasks).await?;
|
||||
let x25519_lp_keys =
|
||||
load_x25519_lp_keypair(&config.storage_paths.keys.x25519_lp_key_paths())?;
|
||||
let mlkem = load_mlkem768_keypair(&config.storage_paths.keys.mlkem768_key_paths())?;
|
||||
let mceliece = load_mceliece_keypair(&config.storage_paths.keys.mceliece_key_paths())?;
|
||||
let psq_kem_keys = KEMKeys::new(mceliece, mlkem);
|
||||
|
||||
todo!("deal with kem key migration path")
|
||||
//
|
||||
// Ok(NymNode {
|
||||
// ed25519_identity_keys: Arc::new(ed25519_identity_keys),
|
||||
// sphinx_key_manager: Some(SphinxKeyManager::try_load_or_regenerate(
|
||||
// current_rotation_id,
|
||||
// &config.storage_paths.keys.primary_x25519_sphinx_key_file,
|
||||
// &config.storage_paths.keys.secondary_x25519_sphinx_key_file,
|
||||
// )?),
|
||||
// x25519_noise_keys: Arc::new(load_x25519_noise_keypair(
|
||||
// &config.storage_paths.keys.x25519_noise_storage_paths(),
|
||||
// )?),
|
||||
// description: load_node_description(&config.storage_paths.description)?,
|
||||
// metrics: NymNodeMetrics::new(),
|
||||
// verloc_stats: Default::default(),
|
||||
// entry_gateway,
|
||||
// upgrade_mode_state: UpgradeModeState::new(
|
||||
// config.gateway_tasks.upgrade_mode.attester_public_key,
|
||||
// ),
|
||||
// service_providers: ServiceProvidersData::new(&config.service_providers)?,
|
||||
// wireguard: Some(wireguard_data),
|
||||
// config,
|
||||
// accepted_operator_terms_and_conditions: false,
|
||||
// shutdown_manager: ShutdownManager::build_new_default()
|
||||
// .map_err(|source| NymNodeError::ShutdownSignalFailure { source })?,
|
||||
// })
|
||||
Ok(NymNode {
|
||||
ed25519_identity_keys: Arc::new(ed25519_identity_keys),
|
||||
sphinx_key_manager: Some(SphinxKeyManager::try_load_or_regenerate(
|
||||
current_rotation_id,
|
||||
&config.storage_paths.keys.primary_x25519_sphinx_key_file,
|
||||
&config.storage_paths.keys.secondary_x25519_sphinx_key_file,
|
||||
)?),
|
||||
x25519_noise_keys: Arc::new(load_x25519_noise_keypair(
|
||||
&config.storage_paths.keys.x25519_noise_storage_paths(),
|
||||
)?),
|
||||
psq_kem_keys,
|
||||
description: load_node_description(&config.storage_paths.description)?,
|
||||
metrics: NymNodeMetrics::new(),
|
||||
verloc_stats: Default::default(),
|
||||
entry_gateway,
|
||||
upgrade_mode_state: UpgradeModeState::new(
|
||||
config.gateway_tasks.upgrade_mode.attester_public_key,
|
||||
),
|
||||
service_providers: ServiceProvidersData::new(&config.service_providers)?,
|
||||
wireguard: Some(wireguard_data),
|
||||
config,
|
||||
accepted_operator_terms_and_conditions: false,
|
||||
shutdown_manager: ShutdownManager::build_new_default()
|
||||
.map_err(|source| NymNodeError::ShutdownSignalFailure { source })?,
|
||||
x25519_lp_keys: Arc::new(x25519_lp_keys),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown_tracker(&self) -> &ShutdownTracker {
|
||||
|
||||
@@ -722,16 +722,16 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_kkt::key_utils::generate_keypair_x25519;
|
||||
use nym_kkt::key_utils::generate_lp_keypair_x25519;
|
||||
use nym_lp::packet::version;
|
||||
use nym_test_utils::helpers::deterministic_rng_09;
|
||||
|
||||
#[test]
|
||||
fn test_client_creation() {
|
||||
let mut rng09 = deterministic_rng_09();
|
||||
let keypair = Arc::new(generate_keypair_x25519(&mut rng09));
|
||||
let keypair = Arc::new(generate_lp_keypair_x25519(&mut rng09));
|
||||
|
||||
let gateway_x_keys = generate_keypair_x25519(&mut rng09);
|
||||
let gateway_x_keys = generate_lp_keypair_x25519(&mut rng09);
|
||||
let gateway_peer = LpRemotePeer::from(gateway_x_keys.pk);
|
||||
let address = "127.0.0.1:41264".parse().unwrap();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user