// 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.LibNoscrypt;
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);
}
}
}
}