// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use aes_kw::KekAes128; use aes_kw::KekAes192; use aes_kw::KekAes256; use base64::prelude::BASE64_URL_SAFE_NO_PAD; use base64::Engine; use deno_core::error::not_supported; use deno_core::op2; use deno_core::ToJsBuffer; use deno_core::unsync::spawn_blocking; use deno_core::JsBuffer; use deno_core::OpState; use serde::Deserialize; use p256::elliptic_curve::sec1::FromEncodedPoint; use p256::pkcs8::DecodePrivateKey; use rand::rngs::OsRng; use rand::rngs::StdRng; use rand::thread_rng; use rand::Rng; use rand::SeedableRng; use ring::digest; use ring::hkdf; use ring::hmac::Algorithm as HmacAlgorithm; use ring::hmac::Key as HmacKey; use ring::pbkdf2; use ring::rand as RingRand; use ring::signature::EcdsaKeyPair; use ring::signature::EcdsaSigningAlgorithm; use ring::signature::EcdsaVerificationAlgorithm; use ring::signature::KeyPair; use rsa::pkcs1::DecodeRsaPrivateKey; use rsa::pkcs1::DecodeRsaPublicKey; use rsa::signature::SignatureEncoding; use rsa::signature::Signer; use rsa::signature::Verifier; use rsa::traits::SignatureScheme; use rsa::Pss; use rsa::RsaPrivateKey; use rsa::RsaPublicKey; use sha1::Sha1; use sha2::Digest; use sha2::Sha256; use sha2::Sha384; use sha2::Sha512; use std::num::NonZeroU32; use std::path::PathBuf; pub use rand; // Re-export rand mod decrypt; mod ed25519; mod encrypt; mod export_key; mod generate_key; mod import_key; mod key; mod shared; mod x25519; mod x448; pub use crate::decrypt::op_crypto_decrypt; pub use crate::decrypt::DecryptError; pub use crate::ed25519::Ed25519Error; pub use crate::encrypt::op_crypto_encrypt; pub use crate::encrypt::EncryptError; pub use crate::export_key::op_crypto_export_key; pub use crate::export_key::ExportKeyError; pub use crate::generate_key::op_crypto_generate_key; pub use crate::generate_key::GenerateKeyError; pub use crate::import_key::op_crypto_import_key; pub use crate::import_key::ImportKeyError; use crate::key::Algorithm; use crate::key::CryptoHash; use crate::key::CryptoNamedCurve; use crate::key::HkdfOutput; pub use crate::shared::SharedError; use crate::shared::V8RawKeyData; pub use crate::x25519::X25519Error; pub use crate::x448::X448Error; deno_core::extension!(deno_crypto, deps = [ deno_webidl, deno_web ], ops = [ op_crypto_get_random_values, op_crypto_generate_key, op_crypto_sign_key, op_crypto_verify_key, op_crypto_derive_bits, op_crypto_import_key, op_crypto_export_key, op_crypto_encrypt, op_crypto_decrypt, op_crypto_subtle_digest, op_crypto_random_uuid, op_crypto_wrap_key, op_crypto_unwrap_key, op_crypto_base64url_decode, op_crypto_base64url_encode, x25519::op_crypto_generate_x25519_keypair, x25519::op_crypto_derive_bits_x25519, x25519::op_crypto_import_spki_x25519, x25519::op_crypto_import_pkcs8_x25519, x25519::op_crypto_export_spki_x25519, x25519::op_crypto_export_pkcs8_x25519, x448::op_crypto_generate_x448_keypair, x448::op_crypto_derive_bits_x448, x448::op_crypto_import_spki_x448, x448::op_crypto_import_pkcs8_x448, x448::op_crypto_export_spki_x448, x448::op_crypto_export_pkcs8_x448, ed25519::op_crypto_generate_ed25519_keypair, ed25519::op_crypto_import_spki_ed25519, ed25519::op_crypto_import_pkcs8_ed25519, ed25519::op_crypto_sign_ed25519, ed25519::op_crypto_verify_ed25519, ed25519::op_crypto_export_spki_ed25519, ed25519::op_crypto_export_pkcs8_ed25519, ed25519::op_crypto_jwk_x_ed25519, ], esm = [ "00_crypto.js" ], options = { maybe_seed: Option, }, state = |state, options| { if let Some(seed) = options.maybe_seed { state.put(StdRng::seed_from_u64(seed)); } }, ); #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] General(#[from] SharedError), #[error(transparent)] JoinError(#[from] tokio::task::JoinError), #[error(transparent)] Der(#[from] rsa::pkcs1::der::Error), #[error("Missing argument hash")] MissingArgumentHash, #[error("Missing argument saltLength")] MissingArgumentSaltLength, #[error("unsupported algorithm")] UnsupportedAlgorithm, #[error(transparent)] KeyRejected(#[from] ring::error::KeyRejected), #[error(transparent)] RSA(#[from] rsa::Error), #[error(transparent)] Pkcs1(#[from] rsa::pkcs1::Error), #[error(transparent)] Unspecified(#[from] ring::error::Unspecified), #[error("Invalid key format")] InvalidKeyFormat, #[error(transparent)] P256Ecdsa(#[from] p256::ecdsa::Error), #[error("Unexpected error decoding private key")] DecodePrivateKey, #[error("Missing argument publicKey")] MissingArgumentPublicKey, #[error("Missing argument namedCurve")] MissingArgumentNamedCurve, #[error("Missing argument info")] MissingArgumentInfo, #[error("The length provided for HKDF is too large")] HKDFLengthTooLarge, #[error(transparent)] Base64Decode(#[from] base64::DecodeError), #[error("Data must be multiple of 8 bytes")] DataInvalidSize, #[error("Invalid key length")] InvalidKeyLength, #[error("encryption error")] EncryptionError, #[error("decryption error - integrity check failed")] DecryptionError, #[error("The ArrayBufferView's byte length ({0}) exceeds the number of bytes of entropy available via this API (65536)")] ArrayBufferViewLengthExceeded(usize), #[error(transparent)] Other(deno_core::error::AnyError), } #[op2] #[serde] pub fn op_crypto_base64url_decode( #[string] data: String, ) -> Result { let data: Vec = BASE64_URL_SAFE_NO_PAD.decode(data)?; Ok(data.into()) } #[op2] #[string] pub fn op_crypto_base64url_encode(#[buffer] data: JsBuffer) -> String { let data: String = BASE64_URL_SAFE_NO_PAD.encode(data); data } #[op2(fast)] pub fn op_crypto_get_random_values( state: &mut OpState, #[buffer] out: &mut [u8], ) -> Result<(), Error> { if out.len() > 65536 { return Err(Error::ArrayBufferViewLengthExceeded(out.len())); } let maybe_seeded_rng = state.try_borrow_mut::(); if let Some(seeded_rng) = maybe_seeded_rng { seeded_rng.fill(out); } else { let mut rng = thread_rng(); rng.fill(out); } Ok(()) } #[derive(Deserialize)] #[serde(rename_all = "lowercase")] pub enum KeyFormat { Raw, Pkcs8, Spki, } #[derive(Deserialize)] #[serde(rename_all = "lowercase")] pub enum KeyType { Secret, Private, Public, } #[derive(Deserialize)] #[serde(rename_all = "lowercase")] pub struct KeyData { r#type: KeyType, data: JsBuffer, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct SignArg { key: KeyData, algorithm: Algorithm, salt_length: Option, hash: Option, named_curve: Option, } #[op2(async)] #[serde] pub async fn op_crypto_sign_key( #[serde] args: SignArg, #[buffer] zero_copy: JsBuffer, ) -> Result { deno_core::unsync::spawn_blocking(move || { let data = &*zero_copy; let algorithm = args.algorithm; let signature = match algorithm { Algorithm::RsassaPkcs1v15 => { use rsa::pkcs1v15::SigningKey; let private_key = RsaPrivateKey::from_pkcs1_der(&args.key.data)?; match args.hash.ok_or_else(|| Error::MissingArgumentHash)? { CryptoHash::Sha1 => { let signing_key = SigningKey::::new(private_key); signing_key.sign(data) } CryptoHash::Sha256 => { let signing_key = SigningKey::::new(private_key); signing_key.sign(data) } CryptoHash::Sha384 => { let signing_key = SigningKey::::new(private_key); signing_key.sign(data) } CryptoHash::Sha512 => { let signing_key = SigningKey::::new(private_key); signing_key.sign(data) } } .to_vec() } Algorithm::RsaPss => { let private_key = RsaPrivateKey::from_pkcs1_der(&args.key.data)?; let salt_len = args .salt_length .ok_or_else(|| Error::MissingArgumentSaltLength)? as usize; let mut rng = OsRng; match args.hash.ok_or_else(|| Error::MissingArgumentHash)? { CryptoHash::Sha1 => { let signing_key = Pss::new_with_salt::(salt_len); let hashed = Sha1::digest(data); signing_key.sign(Some(&mut rng), &private_key, &hashed)? } CryptoHash::Sha256 => { let signing_key = Pss::new_with_salt::(salt_len); let hashed = Sha256::digest(data); signing_key.sign(Some(&mut rng), &private_key, &hashed)? } CryptoHash::Sha384 => { let signing_key = Pss::new_with_salt::(salt_len); let hashed = Sha384::digest(data); signing_key.sign(Some(&mut rng), &private_key, &hashed)? } CryptoHash::Sha512 => { let signing_key = Pss::new_with_salt::(salt_len); let hashed = Sha512::digest(data); signing_key.sign(Some(&mut rng), &private_key, &hashed)? } } .to_vec() } Algorithm::Ecdsa => { let curve: &EcdsaSigningAlgorithm = args .named_curve .ok_or_else(|| Error::Other(not_supported()))? .into(); let rng = RingRand::SystemRandom::new(); let key_pair = EcdsaKeyPair::from_pkcs8(curve, &args.key.data, &rng)?; // We only support P256-SHA256 & P384-SHA384. These are recommended signature pairs. // https://briansmith.org/rustdoc/ring/signature/index.html#statics if let Some(hash) = args.hash { match hash { CryptoHash::Sha256 | CryptoHash::Sha384 => (), _ => return Err(Error::UnsupportedAlgorithm), } }; let signature = key_pair.sign(&rng, data)?; // Signature data as buffer. signature.as_ref().to_vec() } Algorithm::Hmac => { let hash: HmacAlgorithm = args .hash .ok_or_else(|| Error::Other(not_supported()))? .into(); let key = HmacKey::new(hash, &args.key.data); let signature = ring::hmac::sign(&key, data); signature.as_ref().to_vec() } _ => return Err(Error::UnsupportedAlgorithm), }; Ok(signature.into()) }) .await? } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct VerifyArg { key: KeyData, algorithm: Algorithm, salt_length: Option, hash: Option, signature: JsBuffer, named_curve: Option, } #[op2(async)] pub async fn op_crypto_verify_key( #[serde] args: VerifyArg, #[buffer] zero_copy: JsBuffer, ) -> Result { deno_core::unsync::spawn_blocking(move || { let data = &*zero_copy; let algorithm = args.algorithm; let verification = match algorithm { Algorithm::RsassaPkcs1v15 => { use rsa::pkcs1v15::Signature; use rsa::pkcs1v15::VerifyingKey; let public_key = read_rsa_public_key(args.key)?; let signature: Signature = args.signature.as_ref().try_into()?; match args.hash.ok_or_else(|| Error::MissingArgumentHash)? { CryptoHash::Sha1 => { let verifying_key = VerifyingKey::::new(public_key); verifying_key.verify(data, &signature).is_ok() } CryptoHash::Sha256 => { let verifying_key = VerifyingKey::::new(public_key); verifying_key.verify(data, &signature).is_ok() } CryptoHash::Sha384 => { let verifying_key = VerifyingKey::::new(public_key); verifying_key.verify(data, &signature).is_ok() } CryptoHash::Sha512 => { let verifying_key = VerifyingKey::::new(public_key); verifying_key.verify(data, &signature).is_ok() } } } Algorithm::RsaPss => { let public_key = read_rsa_public_key(args.key)?; let signature = args.signature.as_ref(); let salt_len = args .salt_length .ok_or_else(|| Error::MissingArgumentSaltLength)? as usize; match args.hash.ok_or_else(|| Error::MissingArgumentHash)? { CryptoHash::Sha1 => { let pss = Pss::new_with_salt::(salt_len); let hashed = Sha1::digest(data); pss.verify(&public_key, &hashed, signature).is_ok() } CryptoHash::Sha256 => { let pss = Pss::new_with_salt::(salt_len); let hashed = Sha256::digest(data); pss.verify(&public_key, &hashed, signature).is_ok() } CryptoHash::Sha384 => { let pss = Pss::new_with_salt::(salt_len); let hashed = Sha384::digest(data); pss.verify(&public_key, &hashed, signature).is_ok() } CryptoHash::Sha512 => { let pss = Pss::new_with_salt::(salt_len); let hashed = Sha512::digest(data); pss.verify(&public_key, &hashed, signature).is_ok() } } } Algorithm::Hmac => { let hash: HmacAlgorithm = args .hash .ok_or_else(|| Error::Other(not_supported()))? .into(); let key = HmacKey::new(hash, &args.key.data); ring::hmac::verify(&key, data, &args.signature).is_ok() } Algorithm::Ecdsa => { let signing_alg: &EcdsaSigningAlgorithm = args .named_curve .ok_or_else(|| Error::Other(not_supported()))? .into(); let verify_alg: &EcdsaVerificationAlgorithm = args .named_curve .ok_or_else(|| Error::Other(not_supported()))? .into(); let private_key; let public_key_bytes = match args.key.r#type { KeyType::Private => { let rng = RingRand::SystemRandom::new(); private_key = EcdsaKeyPair::from_pkcs8(signing_alg, &args.key.data, &rng)?; private_key.public_key().as_ref() } KeyType::Public => &*args.key.data, _ => return Err(Error::InvalidKeyFormat), }; let public_key = ring::signature::UnparsedPublicKey::new(verify_alg, public_key_bytes); public_key.verify(data, &args.signature).is_ok() } _ => return Err(Error::UnsupportedAlgorithm), }; Ok(verification) }) .await? } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct DeriveKeyArg { key: KeyData, algorithm: Algorithm, hash: Option, length: usize, iterations: Option, // ECDH public_key: Option, named_curve: Option, // HKDF info: Option, } #[op2(async)] #[serde] pub async fn op_crypto_derive_bits( #[serde] args: DeriveKeyArg, #[buffer] zero_copy: Option, ) -> Result { deno_core::unsync::spawn_blocking(move || { let algorithm = args.algorithm; match algorithm { Algorithm::Pbkdf2 => { let zero_copy = zero_copy.ok_or_else(|| Error::Other(not_supported()))?; let salt = &*zero_copy; // The caller must validate these cases. assert!(args.length > 0); assert!(args.length % 8 == 0); let algorithm = match args.hash.ok_or_else(|| Error::Other(not_supported()))? { CryptoHash::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1, CryptoHash::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256, CryptoHash::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384, CryptoHash::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512, }; // This will never panic. We have already checked length earlier. let iterations = NonZeroU32::new( args .iterations .ok_or_else(|| Error::Other(not_supported()))?, ) .unwrap(); let secret = args.key.data; let mut out = vec![0; args.length / 8]; pbkdf2::derive(algorithm, iterations, salt, &secret, &mut out); Ok(out.into()) } Algorithm::Ecdh => { let named_curve = args .named_curve .ok_or_else(|| Error::MissingArgumentNamedCurve)?; let public_key = args .public_key .ok_or_else(|| Error::MissingArgumentPublicKey)?; match named_curve { CryptoNamedCurve::P256 => { let secret_key = p256::SecretKey::from_pkcs8_der(&args.key.data) .map_err(|_| Error::DecodePrivateKey)?; let public_key = match public_key.r#type { KeyType::Private => { p256::SecretKey::from_pkcs8_der(&public_key.data) .map_err(|_| Error::DecodePrivateKey)? .public_key() } KeyType::Public => { let point = p256::EncodedPoint::from_bytes(public_key.data) .map_err(|_| Error::DecodePrivateKey)?; let pk = p256::PublicKey::from_encoded_point(&point); // pk is a constant time Option. if pk.is_some().into() { pk.unwrap() } else { return Err(Error::DecodePrivateKey); } } _ => unreachable!(), }; let shared_secret = p256::elliptic_curve::ecdh::diffie_hellman( secret_key.to_nonzero_scalar(), public_key.as_affine(), ); // raw serialized x-coordinate of the computed point Ok(shared_secret.raw_secret_bytes().to_vec().into()) } CryptoNamedCurve::P384 => { let secret_key = p384::SecretKey::from_pkcs8_der(&args.key.data) .map_err(|_| Error::DecodePrivateKey)?; let public_key = match public_key.r#type { KeyType::Private => { p384::SecretKey::from_pkcs8_der(&public_key.data) .map_err(|_| Error::DecodePrivateKey)? .public_key() } KeyType::Public => { let point = p384::EncodedPoint::from_bytes(public_key.data) .map_err(|_| Error::DecodePrivateKey)?; let pk = p384::PublicKey::from_encoded_point(&point); // pk is a constant time Option. if pk.is_some().into() { pk.unwrap() } else { return Err(Error::DecodePrivateKey); } } _ => unreachable!(), }; let shared_secret = p384::elliptic_curve::ecdh::diffie_hellman( secret_key.to_nonzero_scalar(), public_key.as_affine(), ); // raw serialized x-coordinate of the computed point Ok(shared_secret.raw_secret_bytes().to_vec().into()) } } } Algorithm::Hkdf => { let zero_copy = zero_copy.ok_or_else(|| Error::Other(not_supported()))?; let salt = &*zero_copy; let algorithm = match args.hash.ok_or_else(|| Error::Other(not_supported()))? { CryptoHash::Sha1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, CryptoHash::Sha256 => hkdf::HKDF_SHA256, CryptoHash::Sha384 => hkdf::HKDF_SHA384, CryptoHash::Sha512 => hkdf::HKDF_SHA512, }; let info = args.info.ok_or_else(|| Error::MissingArgumentInfo)?; // IKM let secret = args.key.data; // L let length = args.length / 8; let salt = hkdf::Salt::new(algorithm, salt); let prk = salt.extract(&secret); let info = &[&*info]; let okm = prk .expand(info, HkdfOutput(length)) .map_err(|_e| Error::HKDFLengthTooLarge)?; let mut r = vec![0u8; length]; okm.fill(&mut r)?; Ok(r.into()) } _ => Err(Error::UnsupportedAlgorithm), } }) .await? } fn read_rsa_public_key(key_data: KeyData) -> Result { let public_key = match key_data.r#type { KeyType::Private => { RsaPrivateKey::from_pkcs1_der(&key_data.data)?.to_public_key() } KeyType::Public => RsaPublicKey::from_pkcs1_der(&key_data.data)?, KeyType::Secret => unreachable!("unexpected KeyType::Secret"), }; Ok(public_key) } #[op2] #[string] pub fn op_crypto_random_uuid(state: &mut OpState) -> Result { let maybe_seeded_rng = state.try_borrow_mut::(); let uuid = if let Some(seeded_rng) = maybe_seeded_rng { let mut bytes = [0u8; 16]; seeded_rng.fill(&mut bytes); fast_uuid_v4(&mut bytes) } else { let mut rng = thread_rng(); let mut bytes = [0u8; 16]; rng.fill(&mut bytes); fast_uuid_v4(&mut bytes) }; Ok(uuid) } #[op2(async)] #[serde] pub async fn op_crypto_subtle_digest( #[serde] algorithm: CryptoHash, #[buffer] data: JsBuffer, ) -> Result { let output = spawn_blocking(move || { digest::digest(algorithm.into(), &data) .as_ref() .to_vec() .into() }) .await?; Ok(output) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct WrapUnwrapKeyArg { key: V8RawKeyData, algorithm: Algorithm, } #[op2] #[serde] pub fn op_crypto_wrap_key( #[serde] args: WrapUnwrapKeyArg, #[buffer] data: JsBuffer, ) -> Result { let algorithm = args.algorithm; match algorithm { Algorithm::AesKw => { let key = args.key.as_secret_key()?; if data.len() % 8 != 0 { return Err(Error::DataInvalidSize); } let wrapped_key = match key.len() { 16 => KekAes128::new(key.into()).wrap_vec(&data), 24 => KekAes192::new(key.into()).wrap_vec(&data), 32 => KekAes256::new(key.into()).wrap_vec(&data), _ => return Err(Error::InvalidKeyLength), } .map_err(|_| Error::EncryptionError)?; Ok(wrapped_key.into()) } _ => Err(Error::UnsupportedAlgorithm), } } #[op2] #[serde] pub fn op_crypto_unwrap_key( #[serde] args: WrapUnwrapKeyArg, #[buffer] data: JsBuffer, ) -> Result { let algorithm = args.algorithm; match algorithm { Algorithm::AesKw => { let key = args.key.as_secret_key()?; if data.len() % 8 != 0 { return Err(Error::DataInvalidSize); } let unwrapped_key = match key.len() { 16 => KekAes128::new(key.into()).unwrap_vec(&data), 24 => KekAes192::new(key.into()).unwrap_vec(&data), 32 => KekAes256::new(key.into()).unwrap_vec(&data), _ => return Err(Error::InvalidKeyLength), } .map_err(|_| Error::DecryptionError)?; Ok(unwrapped_key.into()) } _ => Err(Error::UnsupportedAlgorithm), } } pub fn get_declaration() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_crypto.d.ts") } const HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; fn fast_uuid_v4(bytes: &mut [u8; 16]) -> String { // Set UUID version to 4 and variant to 1. bytes[6] = (bytes[6] & 0x0f) | 0x40; bytes[8] = (bytes[8] & 0x3f) | 0x80; let buf = [ HEX_CHARS[(bytes[0] >> 4) as usize], HEX_CHARS[(bytes[0] & 0x0f) as usize], HEX_CHARS[(bytes[1] >> 4) as usize], HEX_CHARS[(bytes[1] & 0x0f) as usize], HEX_CHARS[(bytes[2] >> 4) as usize], HEX_CHARS[(bytes[2] & 0x0f) as usize], HEX_CHARS[(bytes[3] >> 4) as usize], HEX_CHARS[(bytes[3] & 0x0f) as usize], b'-', HEX_CHARS[(bytes[4] >> 4) as usize], HEX_CHARS[(bytes[4] & 0x0f) as usize], HEX_CHARS[(bytes[5] >> 4) as usize], HEX_CHARS[(bytes[5] & 0x0f) as usize], b'-', HEX_CHARS[(bytes[6] >> 4) as usize], HEX_CHARS[(bytes[6] & 0x0f) as usize], HEX_CHARS[(bytes[7] >> 4) as usize], HEX_CHARS[(bytes[7] & 0x0f) as usize], b'-', HEX_CHARS[(bytes[8] >> 4) as usize], HEX_CHARS[(bytes[8] & 0x0f) as usize], HEX_CHARS[(bytes[9] >> 4) as usize], HEX_CHARS[(bytes[9] & 0x0f) as usize], b'-', HEX_CHARS[(bytes[10] >> 4) as usize], HEX_CHARS[(bytes[10] & 0x0f) as usize], HEX_CHARS[(bytes[11] >> 4) as usize], HEX_CHARS[(bytes[11] & 0x0f) as usize], HEX_CHARS[(bytes[12] >> 4) as usize], HEX_CHARS[(bytes[12] & 0x0f) as usize], HEX_CHARS[(bytes[13] >> 4) as usize], HEX_CHARS[(bytes[13] & 0x0f) as usize], HEX_CHARS[(bytes[14] >> 4) as usize], HEX_CHARS[(bytes[14] & 0x0f) as usize], HEX_CHARS[(bytes[15] >> 4) as usize], HEX_CHARS[(bytes[15] & 0x0f) as usize], ]; // Safety: the buffer is all valid UTF-8. unsafe { String::from_utf8_unchecked(buf.to_vec()) } } #[test] fn test_fast_uuid_v4_correctness() { let mut rng = thread_rng(); let mut bytes = [0u8; 16]; rng.fill(&mut bytes); let uuid = fast_uuid_v4(&mut bytes.clone()); let uuid_lib = uuid::Builder::from_bytes(bytes) .set_variant(uuid::Variant::RFC4122) .set_version(uuid::Version::Random) .as_uuid() .to_string(); assert_eq!(uuid, uuid_lib); }