// 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.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using VNLib.Utils.Extensions; using VNLib.Utils.Memory; using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; namespace VNLib.Utils.Cryptography.Noscrypt { public sealed class NostrEncryptedMessage(IEncryptionVersion version, INostrCrypto lib) : VnDisposeable { private readonly INostrCrypto library = lib; private NCSecretKey _fromKey; private NCPublicKey _toKey; private Buffer32 _nonce32; /// /// The message encryption version used by this instance /// public uint Version { get; } = version.Version; /// /// The message nonce created during encryption event /// public unsafe Span Nonce { get { Debug.Assert(NC_ENCRYPTION_NONCE_SIZE == sizeof(Buffer32)); return MemoryMarshal.CreateSpan(ref GetNonceRef(), sizeof(Buffer32)); } } /// /// Gets the size of the buffer required to encrypt the specified data size /// /// The size of the message raw plaintext message to send /// The minimum number of bytes required for message encryption output /// public int GetOutputBufferSize(int dataSize) => version.CalcBufferSize(dataSize); /// /// Sets the encryption secret key for the message /// /// The secret key buffer /// The current instance for chaining /// public NostrEncryptedMessage SetSecretKey(ReadOnlySpan secKey) => SetSecretKey(in NCUtil.AsSecretKey(secKey)); /// /// Sets the encryption secret key for the message /// /// The secret key structure to copy /// The current instance for chaining /// public NostrEncryptedMessage SetSecretKey(ref readonly NCSecretKey secKey) { MemoryUtil.CloneStruct(in secKey, ref _fromKey); return this; } /// /// Assigns the public key used to encrypt the message as the /// receiver of the message /// /// The user's public key receiving the message /// The current instance for chaining /// public NostrEncryptedMessage SetPublicKey(ReadOnlySpan pubKey) => SetPublicKey(in NCUtil.AsPublicKey(pubKey)); /// /// Assigns the public key used to encrypt the message as the /// receiver of the message /// /// The user's public key receiving the message /// The current instance for chaining /// public NostrEncryptedMessage SetPublicKey(ref readonly NCPublicKey pubKey) { MemoryUtil.CloneStruct(in pubKey, ref _toKey); return this; } /// /// Assigns the nonce to the message. Must be /// in length /// /// The nonce value to copy /// The current instance for chaining /// public NostrEncryptedMessage SetNonce(ReadOnlySpan nonce) { MemoryUtil.CopyStruct(nonce, ref _nonce32); return this; } /// /// Assigns a random nonce using the specified random source /// /// The random source to genrate a random nonce from /// The current instance for chaining public NostrEncryptedMessage SetRandomNonce(IRandomSource rng) { rng.GetRandomBytes(Nonce); return this; } /// /// Encrypts the plaintext message and writes the encrypted message to the /// specified buffer, along with a 32 byte mac of the message /// /// The plaintext data to encrypt /// The message output buffer to write encrypted data to /// A buffer to write the computed message mac to /// The number of bytes writtn to the message output buffer /// /// The message buffer must be at-least the size of the output buffer, and it is not /// initialized before the encryption operation. /// /// public int EncryptMessage(ReadOnlySpan plaintext, Span message, Span macOut32) { return Version switch { NC_ENC_VERSION_NIP44 => EncryptNip44(plaintext, message, macOut32), _ => throw new NotSupportedException("NIP04 encryption is not supported"), }; } private int EncryptNip44(ReadOnlySpan plaintext, Span message, Span macOut32) { int payloadSize = GetOutputBufferSize(plaintext.Length); ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); ArgumentOutOfRangeException.ThrowIfZero(message.Length, nameof(message)); ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, payloadSize, nameof(message)); ArgumentOutOfRangeException.ThrowIfLessThan(macOut32.Length, NC_ENCRYPTION_MAC_SIZE, nameof(macOut32)); /* * Alloc temp buffer to copy formatted payload to data to for the encryption * operation. Encryption will write directly to the message buffer */ using UnsafeMemoryHandle ptPayloadBuf = MemoryUtil.UnsafeAllocNearestPage(payloadSize, true); using UnsafeMemoryHandle hmacKeyBuf = MemoryUtil.UnsafeAlloc(NC_HMAC_KEY_SIZE, true); Debug.Assert(hmacKeyBuf.Length == NC_HMAC_KEY_SIZE); Nip44Util.FormatBuffer(plaintext, ptPayloadBuf.Span, false); library.EncryptNip44( in _fromKey, in _toKey, in GetNonceRef(), in ptPayloadBuf.GetReference(), ref MemoryMarshal.GetReference(message), (uint)payloadSize, //IMPORTANT: Format buffer will pad the buffer to the exact size ref hmacKeyBuf.GetReference() //Must set the hmac key buffer ); //Safe to clear the plain text copy buffer MemoryUtil.InitializeBlock( ref ptPayloadBuf.GetReference(), ptPayloadBuf.GetIntLength() ); //Compute message mac, key should be set by the encryption operation library.ComputeMac( in hmacKeyBuf.GetReference(), in MemoryMarshal.GetReference(message), (uint)payloadSize, //Again set exact playload size ref MemoryMarshal.GetReference(macOut32) ); //Safe to clear the hmac key buffer MemoryUtil.InitializeBlock( ref hmacKeyBuf.GetReference(), hmacKeyBuf.GetIntLength() ); return payloadSize; } private ref byte GetNonceRef() => ref Unsafe.As(ref _nonce32); protected override void Free() { //Zero all internal memory MemoryUtil.ZeroStruct(ref _fromKey); MemoryUtil.ZeroStruct(ref _toKey); MemoryUtil.ZeroStruct(ref _nonce32); } /// /// Initializes a new with the nip44 encryption /// method. /// /// The nostr crypto implementation instance to use /// The intialzied message instance public static NostrEncryptedMessage CreateNip44Cipher(INostrCrypto lib) => new(NCNip44EncryptionVersion.Instance, lib); [StructLayout(LayoutKind.Sequential, Size = 32)] unsafe struct Buffer32 { fixed byte value[32]; } } }