// Copyright (C) 2023 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.Security.Cryptography; using System.Runtime.InteropServices; using VNLib.Hashing; using VNLib.Utils; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; using static NVault.Crypto.Secp256k1.LibSecp256k1; namespace NVault.Crypto.Secp256k1 { public static unsafe class ContextExtensions { /// /// Creates a new from the current managed library /// /// /// The new object from the library /// public static Secp256k1Context CreateContext(this LibSecp256k1 Lib) { //Protect for released lib Lib.SafeLibHandle.ThrowIfClosed(); //Create new context IntPtr context = Lib._create(1); if (context == IntPtr.Zero) { throw new CryptographicException("Failed to create the new Secp256k1 context"); } return new Secp256k1Context(Lib, context); } /// /// Signs a 32byte message digest with the specified secret key on the current context and writes the signature to the specified buffer /// /// /// The 32byte secret key used to sign messages from the user /// The 32byte message digest to compute the signature of /// The buffer to write the signature output to, must be at-least 64 bytes /// The number of bytes written to the signature buffer, or less than 1 if the operation failed public static ERRNO SchnorSignDigest(this in Secp256k1Context context, ReadOnlySpan secretKey, ReadOnlySpan digest, Span signature) { //Check the signature buffer size if (signature.Length < SignatureSize) { return ERRNO.E_FAIL; } //Message digest must be exactly 32 bytes long if (digest.Length != (int)HashAlg.SHA256) { return ERRNO.E_FAIL; } //Stack allocated keypair KeyPair keyPair = new(); //Randomize the context and create the keypair if (!context.CreateKeyPair(&keyPair, secretKey)) { return ERRNO.E_FAIL; } //Create the random nonce byte* random = stackalloc byte[RandomBufferSize]; //Fill the buffer with random bytes context.Lib.GetRandomBytes(new Span(random, RandomBufferSize)); try { fixed (byte* sigPtr = &MemoryMarshal.GetReference(signature), digestPtr = &MemoryMarshal.GetReference(digest)) { //Sign the message hash and write the output to the signature buffer if (context.Lib._signHash(context.Context, sigPtr, digestPtr, &keyPair, random) != 1) { return ERRNO.E_FAIL; } } } finally { //Erase entropy MemoryUtil.InitializeBlock(random, RandomBufferSize); //Clear the keypair, contains the secret key, even if its stack allocated MemoryUtil.ZeroStruct(&keyPair); } //Signature size is always 64 bytes return SignatureSize; } /// /// Generates an x-only Schnor encoded public key from the specified secret key on the /// current context and writes it to the specified buffer. /// /// /// The 32byte secret key used to derrive the public key from /// The buffer to write the x-only Schnor encoded public key /// The number of bytes written to the output buffer, or 0 if the operation failed /// public static ERRNO GeneratePubKeyFromSecret(this in Secp256k1Context context, ReadOnlySpan secretKey, Span pubKeyBuffer) { if (secretKey.Length != SecretKeySize) { throw new CryptographicException($"Your secret key must be exactly {SecretKeySize} bytes long"); } if (pubKeyBuffer.Length < XOnlyPublicKeySize) { throw new CryptographicException($"Your public key buffer must be at least {XOnlyPublicKeySize} bytes long"); } //Protect for released lib context.Lib.SafeLibHandle.ThrowIfClosed(); //Stack allocated keypair and x-only public key XOnlyPubKey xOnlyPubKey = new(); KeyPair keyPair = new(); try { //Init context and keypair if (!context.CreateKeyPair(&keyPair, secretKey)) { return ERRNO.E_FAIL; } //X-only public key from the keypair if (context.Lib._createXonly(context.Context, &xOnlyPubKey, 0, &keyPair) != 1) { return ERRNO.E_FAIL; } fixed (byte* pubBuffer = &MemoryMarshal.GetReference(pubKeyBuffer)) { //Serialize the public key to the buffer as an X-only public key without leading status byte if (context.Lib._serializeXonly(context.Context, pubBuffer, &xOnlyPubKey) != 1) { return ERRNO.E_FAIL; } } } finally { //Clear the keypair, contains the secret key, even if its stack allocated MemoryUtil.ZeroStruct(&keyPair); } //PubKey length is constant return XOnlyPublicKeySize; } /// /// Verifies that a given secret key is valid using the current context /// /// /// The secret key to verify /// A boolean value that indicates if the secret key is valid or not /// public static bool VerifySecretKey(this in Secp256k1Context context, ReadOnlySpan secretKey) { if (secretKey.Length != SecretKeySize) { throw new CryptographicException($"Your secret key must be exactly {SecretKeySize} bytes long"); } context.Lib.SafeLibHandle.ThrowIfClosed(); //Get sec key ref and verify fixed(byte* ptr = &MemoryMarshal.GetReference(secretKey)) { return context.Lib._secKeyVerify.Invoke(context.Context, ptr) == 1; } } } }