// Copyright (C) 2024 Vaughn Nugent // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . using System; using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; using VNLib.Utils.Cryptography.Noscrypt.@internal; using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; using NCResult = System.Int64; namespace VNLib.Utils.Cryptography.Noscrypt { /// /// A default implementation of the interface /// /// The initialized library context public unsafe class NostrCrypto(NCContext context, bool ownsContext) : VnDisposeable, INostrCrypto { /// /// Gets the underlying library context. /// public NCContext Context => context; private ref readonly FunctionTable Functions => ref context.Library.Functions; /// public void DecryptNip44( ref readonly NCSecretKey secretKey, ref readonly NCPublicKey publicKey, ref readonly byte nonce32, ref readonly byte cipherText, ref byte plainText, uint size ) { Check(); ThrowIfNullRef(in nonce32, nameof(nonce32)); fixed (NCSecretKey* pSecKey = &secretKey) fixed (NCPublicKey* pPubKey = &publicKey) fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pNonce = &nonce32) { NCEncryptionArgs data = new(); //Version set first otherwise errors will occur SetEncProperty(&data, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44); //Only the nonce must be set, the hmac key is not needed for decryption SetEncPropertyEx(&data, NC_ENC_SET_NIP44_NONCE, pNonce, NC_ENCRYPTION_NONCE_SIZE); SetEncData(&data, pTextPtr, pCipherText, size); NCResult result = Functions.NCDecrypt.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &data); NCUtil.CheckResult(result, true); } } /// public void EncryptNip44( ref readonly NCSecretKey secretKey, ref readonly NCPublicKey publicKey, ref readonly byte nonce32, ref readonly byte plainText, ref byte cipherText, uint size, ref byte hmackKeyOut32 ) { Check(); ThrowIfNullRef(in nonce32, nameof(nonce32)); fixed (NCSecretKey* pSecKey = &secretKey) fixed (NCPublicKey* pPubKey = &publicKey) fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pHmacKeyOut = &hmackKeyOut32, pNonce = &nonce32 ) { NCEncryptionArgs data = new(); /* * Use the extended api to set properties correctly and validate them. * * The version MUST be set before continuing to set properties * * Since pointers are used, they must be only be set/accessed inside * this fixed statement. */ SetEncProperty(&data, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44); SetEncPropertyEx(&data, NC_ENC_SET_NIP44_MAC_KEY, pHmacKeyOut, NC_HMAC_KEY_SIZE); SetEncPropertyEx(&data, NC_ENC_SET_NIP44_NONCE, pNonce, NC_ENCRYPTION_NONCE_SIZE); SetEncData(&data, pTextPtr, pCipherText, size); NCResult result = Functions.NCEncrypt.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &data); NCUtil.CheckResult(result, true); } } /// public void GetPublicKey(ref readonly NCSecretKey secretKey, ref NCPublicKey publicKey) { Check(); fixed (NCSecretKey* pSecKey = &secretKey) fixed (NCPublicKey* pPubKey = &publicKey) { NCResult result = Functions.NCGetPublicKey.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey); NCUtil.CheckResult(result, true); } } /// public void SignData( ref readonly NCSecretKey secretKey, ref readonly byte random32, ref readonly byte data, uint dataSize, ref byte sig64 ) { Check(); fixed (NCSecretKey* pSecKey = &secretKey) fixed (byte* pData = &data, pSig = &sig64, pRandom = &random32) { NCResult result = Functions.NCSignData.Invoke( ctx: context.DangerousGetHandle(), sk: pSecKey, random32: pRandom, data: pData, dataSize, sig64: pSig ); NCUtil.CheckResult(result, true); } } /// public bool ValidateSecretKey(ref readonly NCSecretKey secretKey) { Check(); IntPtr libCtx = context.DangerousGetHandle(); fixed (NCSecretKey* pSecKey = &secretKey) { /* * Validate should return a result of 1 if the secret key is valid * or a 0 if it is not. */ NCResult result = Functions.NCValidateSecretKey.Invoke(libCtx, pSecKey); NCUtil.CheckResult(result, false); return result == NC_SUCCESS; } } /// public bool VerifyData( ref readonly NCPublicKey pubKey, ref readonly byte data, uint dataSize, ref readonly byte sig64 ) { Check(); fixed(NCPublicKey* pPubKey = &pubKey) fixed (byte* pData = &data, pSig = &sig64) { NCResult result = Functions.NCVerifyData.Invoke(context.DangerousGetHandle(), pPubKey, pData, dataSize, pSig); NCUtil.CheckResult(result, false); return result == NC_SUCCESS; } } /// public bool VerifyMac( ref readonly NCSecretKey secretKey, ref readonly NCPublicKey publicKey, ref readonly byte nonce32, ref readonly byte mac32, ref readonly byte payload, uint payloadSize ) { Check(); //Check pointers we need to use ThrowIfNullRef(in nonce32, nameof(nonce32)); ThrowIfNullRef(in mac32, nameof(mac32)); ThrowIfNullRef(in payload, nameof(payload)); fixed (NCSecretKey* pSecKey = &secretKey) fixed (NCPublicKey* pPubKey = &publicKey) fixed (byte* pPayload = &payload, pMac = &mac32, pNonce = &nonce32) { NCMacVerifyArgs args = new() { payloadSize = payloadSize, payload = pPayload, mac32 = pMac, nonce32 = pNonce }; //Exec and bypass failure NCResult result = Functions.NCVerifyMac.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &args); NCUtil.CheckResult(result, false); //Result should be success if the hmac is valid return result == NC_SUCCESS; } } /// public void ComputeMac( ref readonly byte hmacKey32, ref readonly byte payload, uint payloadSize, ref byte hmacOut32 ) { Check(); //Library will check for null pointers, since they are all arguments fixed (byte* pKey = &hmacKey32, pPayload = &payload, pOut = &hmacOut32) { NCResult result = Functions.NCComputeMac.Invoke(context.DangerousGetHandle(), pKey, pPayload, payloadSize, pOut); NCUtil.CheckResult(result, true); } } #if DEBUG /// /// DEBUG ONLY: Gets the conversation key for the supplied secret key and public key /// /// The sender's private key /// The receiver's public key /// A pointer to the 32byte buffer to write the conversation key to public void GetConverstationKey( ref readonly NCSecretKey secretKey, ref readonly NCPublicKey publicKey, ref byte key32 ) { Check(); fixed (NCSecretKey* pSecKey = &secretKey) fixed (NCPublicKey* pPubKey = &publicKey) fixed (byte* pKey = &key32) { NCResult result = Functions.NCGetConversationKey.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, pKey); NCUtil.CheckResult(result, true); } } #endif private void SetEncPropertyEx(NCEncryptionArgs* args, uint prop, byte* value, uint valueLen) { NCResult result = Functions.NCSetEncryptionPropertyEx(args, prop, value, valueLen); NCUtil.CheckResult(result, true); } private void SetEncProperty(NCEncryptionArgs* args, uint prop, uint value) { NCResult result = Functions.NCSetEncryptionProperty(args, prop, value); NCUtil.CheckResult(result, true); } private void SetEncData(NCEncryptionArgs* args, byte* input, byte* output, uint dataLen) { /* * WARNING: * For now this a short-cut for setting the input and output data pointers * technically this still works and avoids the PInvoke call, but this may * change in the future. */ args->dataSize = dataLen; args->inputData = input; args->outputData = output; } /// protected override void Free() { if(ownsContext) { context.Dispose(); } } private static void ThrowIfNullRef([DoesNotReturnIf(false)] ref readonly byte value, string name) { if(Unsafe.IsNullRef(in value)) { throw new ArgumentNullException(name); } } } }