diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..4982a55 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,54 @@ +name: Deploy Documentation + +on: + push: + branches: [master, develop] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Build Project + run: dotnet build Secp256k1.Net/Secp256k1.Net.csproj -c Release + + - name: Install DocFX + run: dotnet tool install -g docfx + + - name: Build Documentation + run: | + cp README.md docs/index.md + docfx docs/docfx.json + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_site + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 13de46a..4b352e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,13 @@ jobs: - name: Build run: dotnet build Secp256k1.Net.Test --configuration Release --framework ${{ matrix.dotnet.framework }} --no-restore - name: Test - run: dotnet test Secp256k1.Net.Test --configuration Release --framework ${{ matrix.dotnet.framework }} --no-build --verbosity normal --blame-crash -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=./TestResults/ + run: dotnet test Secp256k1.Net.Test --configuration Release --framework ${{ matrix.dotnet.framework }} --no-build --verbosity normal --blame-crash --logger "console;verbosity=detailed" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=./TestResults/ + - name: Print test logs + if: failure() + shell: bash + run: | + echo "=== Test log files ===" + find . -name "*.log" -path "*/TestResults/*" -exec echo "--- {} ---" \; -exec cat {} \; - name: List coverage files if: always() run: | @@ -90,3 +96,183 @@ jobs: with: name: benchmarks-${{ matrix.os }}-${{ matrix.dotnet.framework }}-report path: BenchmarkDotNet.Artifacts/results/* + + # NativeLibTest - Tests native library loading in various deployment scenarios + platform-test-linux-x64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run portable tests + run: ./test/NativeLibTest/test-linux-portable.sh linux-x64 + - name: Run RID-specific tests + run: ./test/NativeLibTest/test-linux-rid.sh linux-x64 + - name: Run AOT tests + run: ./test/NativeLibTest/test-linux-aot.sh linux-x64 + + platform-test-linux-musl-x64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run portable tests + run: ./test/NativeLibTest/test-linux-portable.sh linux-musl-x64 + - name: Run RID-specific tests + run: ./test/NativeLibTest/test-linux-rid.sh linux-musl-x64 + - name: Run AOT tests + run: ./test/NativeLibTest/test-linux-aot.sh linux-musl-x64 + + platform-test-linux-arm64: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run portable tests + run: ./test/NativeLibTest/test-linux-portable.sh linux-arm64 + - name: Run RID-specific tests + run: ./test/NativeLibTest/test-linux-rid.sh linux-arm64 + - name: Run AOT tests + run: ./test/NativeLibTest/test-linux-aot.sh linux-arm64 + + platform-test-linux-musl-arm64: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run portable tests + run: ./test/NativeLibTest/test-linux-portable.sh linux-musl-arm64 + - name: Run RID-specific tests + run: ./test/NativeLibTest/test-linux-rid.sh linux-musl-arm64 + - name: Run AOT tests + run: ./test/NativeLibTest/test-linux-aot.sh linux-musl-arm64 + + platform-test-macos-x64: + runs-on: macos-15-intel + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run macOS tests + run: ./test/NativeLibTest/test-macos.sh all + + platform-test-macos-arm64: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run macOS tests + run: ./test/NativeLibTest/test-macos.sh all + + platform-test-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run Windows tests + run: ./test/NativeLibTest/test-windows.ps1 -BuildMode all + + # NativeLibTestLegacy - Tests .NET Framework 4.6.2 compatibility + platform-test-legacy-windows-x64: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run legacy .NET Framework tests (64-bit) + run: ./test/NativeLibTestLegacy/test-windows.ps1 -Arch x64 + + platform-test-legacy-windows-x86: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Run legacy .NET Framework tests (32-bit) + run: ./test/NativeLibTestLegacy/test-windows.ps1 -Arch x86 + + platform-test-legacy-mono-linux-x64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Install Mono + run: | + sudo apt-get update + sudo apt-get install -y mono-complete + - name: Run Mono tests + run: ./test/NativeLibTestLegacy/test-mono.sh + + platform-test-legacy-mono-linux-arm64: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Install Mono + run: | + sudo apt-get update + sudo apt-get install -y mono-complete + - name: Run Mono tests + run: ./test/NativeLibTestLegacy/test-mono.sh + + platform-test-legacy-mono-linux-x86: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Build package and legacy test + run: | + dotnet pack Secp256k1.Net -c Release -o pkg -p:Version=0.0.1-localtest.1 + dotnet build test/NativeLibTestLegacy -c Release + - name: Run Mono tests in 32-bit container + run: | + docker run --rm --platform linux/386 \ + -v "${{ github.workspace }}:/workspace" \ + -w /workspace \ + mono:latest \ + mono test/NativeLibTestLegacy/bin/Release/net462/NativeLibTestLegacy.exe + + platform-test-legacy-mono-macos-x64: + runs-on: macos-15-intel + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Install Mono + run: brew install mono + - name: Run Mono tests + run: ./test/NativeLibTestLegacy/test-mono.sh diff --git a/.gitignore b/.gitignore index d8f7933..5fcb932 100644 --- a/.gitignore +++ b/.gitignore @@ -329,3 +329,9 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ coverage +CoverageReport + +# DocFX +docs/_site/ +docs/api/ +docs/index.md diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f1d0646 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "secp256k1"] + path = secp256k1 + url = git@github.com:zone117x/secp256k1.git + branch = master diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cf12a0b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,161 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [2.0.0] - 2026-01-19 + +### Added + +- New idiomatic C# static API with thread-safe internal context +- Key generation: `CreateSecretKey()`, `CreatePublicKey()`, `CreateKeyPair()`, `CreateXOnlyPublicKey()` +- Key validation: `IsValidSecretKey()`, `IsValidPublicKey()` +- Public key operations: `CompressPublicKey()`, `DecompressPublicKey()`, `NegatePublicKey()`, `CombinePublicKeys()` +- ECDSA signing: `Sign()`, `Verify()`, `SignRecoverable()`, `RecoverPublicKey()` +- DER signatures: `SignatureToDer()`, `SignatureFromDer()`, `VerifyDer()` +- Signature normalization: `NormalizeSignature()`, `IsNormalizedSignature()` +- Schnorr signatures (BIP-340): `SignSchnorr()`, `VerifySchnorr()` +- ECDH: `ComputeSharedSecret()` +- Key tweaking (BIP-32): `TweakSecretKeyAdd()`, `TweakPublicKeyAdd()`, `TweakSecretKeyMul()`, `TweakPublicKeyMul()`, `NegateSecretKey()` +- Tagged hashing (BIP-340): `TaggedHash()` +- MuSig2 multi-signature support +- ElligatorSwift encoding (BIP-324) +- X-only public key operations for Taproot (BIP-341) +- Keypair operations for efficient Schnorr signing +- Public key sorting and comparison +- Custom ECDH hash function support +- Custom nonce function support +- New platform target: Linux musl (Alpine) x64/arm64 +- Comprehensive examples project + +### Changed + +- Updated native secp256k1 library to latest version +- All functions in the secp256k1 C library are now exposed, including all modules (extrakeys, schnorrsig, ecdh, recovery, ellswift, musig) +- All interop functions are now auto-generated from the native C library header files +- Improved error handling with descriptive exceptions +- Modernized native interop for .NET 8+: + - Uses unmanaged function pointers (`delegate* unmanaged[Cdecl]`) instead of delegate instances, reducing allocations and improving call performance + - Uses `NativeLibrary.GetExport()` for direct symbol resolution instead of `Marshal.GetDelegateForFunctionPointer()` + - Falls back to delegate-based approach on older .NET versions for compatibility + +### Breaking Changes + +The 2.0 release introduces a new idiomatic C# API. The old instance-based API is still available for advanced use cases, but the recommended approach is now to use the static methods. + +#### Migration Guide + +**Key Generation (Before)** +```csharp +using var secp256k1 = new Secp256k1(); + +var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; +var rnd = RandomNumberGenerator.Create(); +do { rnd.GetBytes(privateKey); } +while (!secp256k1.SecretKeyVerify(privateKey)); + +var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; +secp256k1.PublicKeyCreate(publicKey, privateKey); + +var serializedKey = new byte[Secp256k1.SERIALIZED_COMPRESSED_PUBKEY_LENGTH]; +secp256k1.PublicKeySerialize(serializedKey, publicKey, Flags.SECP256K1_EC_COMPRESSED); +``` + +**Key Generation (After)** +```csharp +var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + +// Or generate separately: +byte[] secretKey = Secp256k1.CreateSecretKey(); +byte[] publicKey = Secp256k1.CreatePublicKey(secretKey, compressed: true); +``` + +--- + +**Signing & Verification (Before)** +```csharp +using var secp256k1 = new Secp256k1(); + +var msgHash = SHA256.HashData(msgBytes); +var signature = new byte[Secp256k1.SIGNATURE_LENGTH]; +secp256k1.Sign(signature, msgHash, privateKey); + +bool valid = secp256k1.Verify(signature, msgHash, publicKey); +``` + +**Signing & Verification (After)** +```csharp +byte[] msgHash = SHA256.HashData(msgBytes); +byte[] signature = Secp256k1.Sign(msgHash, secretKey); + +bool valid = Secp256k1.Verify(signature, msgHash, publicKey); +``` + +--- + +**ECDH Shared Secret (Before)** +```csharp +using var secp256k1 = new Secp256k1(); + +var secret = new byte[Secp256k1.SECRET_LENGTH]; +secp256k1.Ecdh(secret, otherPartyPublicKey, yourPrivateKey); +``` + +**ECDH Shared Secret (After)** +```csharp +byte[] secret = Secp256k1.ComputeSharedSecret(otherPartyPublicKey, yourSecretKey); +``` + +--- + +**DER Signature Parsing (Before)** +```csharp +using var secp256k1 = new Secp256k1(); + +var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; +secp256k1.SignatureParseDer(signatureOutput, derSignatureBytes); + +Span derOutput = new byte[Secp256k1.SERIALIZED_DER_SIGNATURE_MAX_SIZE]; +secp256k1.SignatureSerializeDer(derOutput, signature, out int length); +derOutput = derOutput.Slice(0, length); +``` + +**DER Signature Parsing (After)** +```csharp +byte[] compactSignature = Secp256k1.SignatureFromDer(derSignatureBytes); +byte[] derSignature = Secp256k1.SignatureToDer(compactSignature); +``` + +--- + +**Public Key Serialization (Before)** +```csharp +using var secp256k1 = new Secp256k1(); + +// Parse serialized key to internal format +var internalPubkey = new byte[Secp256k1.PUBKEY_LENGTH]; +secp256k1.PublicKeyParse(internalPubkey, serializedCompressedKey); + +// Serialize to different format +var uncompressedKey = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; +secp256k1.PublicKeySerialize(uncompressedKey, internalPubkey, Flags.SECP256K1_EC_UNCOMPRESSED); +``` + +**Public Key Serialization (After)** +```csharp +// Convert between formats directly +byte[] uncompressedKey = Secp256k1.DecompressPublicKey(compressedKey); +byte[] compressedKey = Secp256k1.CompressPublicKey(uncompressedKey); +``` + +## [1.4.0] and earlier + +See [NuGet version history](https://www.nuget.org/packages/Secp256k1.Net#versions-body-tab) for previous releases. + +[Unreleased]: https://github.com/zone117x/Secp256k1.Net/compare/v2.0.0...HEAD +[2.0.0]: https://github.com/zone117x/Secp256k1.Net/compare/v1.4.0...v2.0.0 +[1.4.0]: https://github.com/zone117x/Secp256k1.Net/tree/v1.4.0 diff --git a/README.md b/README.md index b56e285..365624f 100644 --- a/README.md +++ b/README.md @@ -3,218 +3,288 @@ [![NuGet](https://img.shields.io/nuget/v/Secp256k1.Net.svg)](https://www.nuget.org/packages/Secp256k1.Net/) [![NuGet](https://img.shields.io/nuget/dt/Secp256k1.Net.svg)](https://www.nuget.org/packages/Secp256k1.Net/) [![CI](https://github.com/zone117x/Secp256k1.Net/actions/workflows/tests.yml/badge.svg)](https://github.com/zone117x/Secp256k1.Net/actions/workflows/tests.yml) [![codecov](https://codecov.io/gh/zone117x/Secp256k1.Net/branch/master/graph/badge.svg?token=fCERq55vh9)](https://codecov.io/gh/zone117x/Secp256k1.Net) -Cross platform C# wrapper for the native [secp256k1 library](https://github.com/zone117x/secp256k1/blob/master/Secp256k1.Native.nuspec). +Cross platform C# wrapper for the native [`bitcoin-core/secp256k1` C library](https://github.com/zone117x). -The nuget package supports win-x64, win-x86, win-arm64, macOS-x64, macOS-arm64 (Apple Silcon), linux-x64, linux-x86, and linux-arm64 out of the box. The native libraries are bundled from the [Secp256k1.Native package](https://www.nuget.org/packages/Secp256k1.Native/). This wrapper should work on any other platform that supports netstandard2.0 (.NET Core 2.0+, Mono 5.4+, etc) but requires that the [native secp256k1](https://github.com/zone117x/secp256k1) library be compiled from source. +```shell +dotnet add package Secp256k1.Net +``` + +## Platform Support + +Pre-compiled binaries are bundled for the following platforms: + +| OS | x64 | x86 | arm64 | +|----|:---:|:---:|:-----:| +| Windows | ✓ | ✓ | ✓ | +| Linux (glibc) | ✓ | ✓ | ✓ | +| Linux (musl/Alpine) | ✓ | | ✓ | +| macOS | ✓ | | ✓ | + +This library targets `netstandard2.0` and `net8.0`, supporting a wide-range of .NET deployments: .NET Core 2.0+, .NET Framework 4.6.1+, Mono 5.4+, etc. Conditional compilation is used to enable optimized native library interop features available on modern targets (`net8.0` and above). ------ -## Example Usage +## Quick Start -#### Generate key pair ```csharp -using var secp256k1 = new Secp256k1(); - -// Generate a private key -var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; -var rnd = System.Security.Cryptography.RandomNumberGenerator.Create(); -do { rnd.GetBytes(privateKey); } -while (!secp256k1.SecretKeyVerify(privateKey)); - -// Derive public key bytes -var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; -Assert.True(secp256k1.PublicKeyCreate(publicKey, privateKey)); - -// Serialize the public key to compressed format -var serializedCompressedPublicKey = new byte[Secp256k1.SERIALIZED_COMPRESSED_PUBKEY_LENGTH]; -Assert.True(secp256k1.PublicKeySerialize(serializedCompressedPublicKey, publicKey, Flags.SECP256K1_EC_COMPRESSED)); - -// Serialize the public key to uncompressed format -var serializedUncompressedPublicKey = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; -Assert.True(secp256k1.PublicKeySerialize(serializedUncompressedPublicKey, publicKey, Flags.SECP256K1_EC_UNCOMPRESSED)); - -// Parse public key from serialized compressed public key -var parsedPublicKey1 = new byte[Secp256k1.PUBKEY_LENGTH]; -Assert.IsTrue(secp256k1.PublicKeyParse(parsedPublicKey1, serializedCompressedPublicKey)); -Assert.AreEqual(Convert.ToHexString(publicKey), Convert.ToHexString(parsedPublicKey1)); - -// Parse public key from serialied uncompressed public key -var parsedPublicKey2 = new byte[Secp256k1.PUBKEY_LENGTH]; -Assert.IsTrue(secp256k1.PublicKeyParse(parsedPublicKey2, serializedUncompressedPublicKey)); -Assert.AreEqual(Convert.ToHexString(publicKey), Convert.ToHexString(parsedPublicKey2)); +using Secp256k1Net; +using System.Security.Cryptography; +using System.Text; + +// Generate a key pair +var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + +// Sign a message (ECDSA) +byte[] message = SHA256.HashData(Encoding.UTF8.GetBytes("Hello, secp256k1!")); +byte[] signature = Secp256k1.Sign(message, secretKey); +bool isValid = Secp256k1.Verify(signature, message, publicKey); + +// Schnorr signatures (BIP-340) +var (xOnlyPubKey, _) = Secp256k1.CreateXOnlyPublicKey(secretKey); +byte[] schnorrSig = Secp256k1.SignSchnorr(message, secretKey); +bool schnorrValid = Secp256k1.VerifySchnorr(schnorrSig, message, xOnlyPubKey); + +// ECDH shared secret +var (aliceSecret, alicePublic) = Secp256k1.CreateKeyPair(compressed: true); +var (bobSecret, bobPublic) = Secp256k1.CreateKeyPair(compressed: true); +byte[] sharedSecret1 = Secp256k1.ComputeSharedSecret(bobPublic, aliceSecret); +byte[] sharedSecret2 = Secp256k1.ComputeSharedSecret(alicePublic, bobSecret); +// sharedSecret1 == sharedSecret2 ``` -#### Sign and verify message -```csharp -using var secp256k1 = new Secp256k1(); -var keypair = new -{ - PrivateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"), - PublicKey = Convert.FromHexString("2208d5dc41d4f3ed555aff761e9bb0b99fbe6d1503b98711944be6a362242ebfa1c788c7a4e13f6aaa4099f9d2175fc031e5aa3ba08eb280e87dfb43bdae207f") -}; - -// Create message hash -var msgBytes = System.Text.Encoding.UTF8.GetBytes("Hello!!"); -var msgHash = System.Security.Cryptography.SHA256.HashData(msgBytes); -Assert.Equal(Secp256k1.HASH_LENGTH, msgHash.Length); - -// Sign then verify message hash -var signature = new byte[Secp256k1.SIGNATURE_LENGTH]; -Assert.True(secp256k1.Sign(signature, msgHash, keypair.PrivateKey)); -Assert.True(secp256k1.Verify(signature, msgHash, keypair.PublicKey)); -``` +See the [examples project](Secp256k1.Net.Examples/) for more complete working examples. -#### Compute an ECDH (EC Diffie-Hellman) secret -```csharp -using var secp256k1 = new Secp256k1(); - -var aliceKeyPair = new -{ - PrivateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"), - PublicKey = Convert.FromHexString("2208d5dc41d4f3ed555aff761e9bb0b99fbe6d1503b98711944be6a362242ebfa1c788c7a4e13f6aaa4099f9d2175fc031e5aa3ba08eb280e87dfb43bdae207f") -}; -var bobKeyPair = new -{ - PrivateKey = Convert.FromHexString("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"), - PublicKey = Convert.FromHexString("62127c4563f711169b1d3e56a34f218302a2587c3725bd418b9388933373e095d45ec4d74ca734599598c89d7719bda5fb799afeec89c6940d569e05bd5a1bba") -}; - -// Create secret using Alice's public key and Bob's private key -var secret1 = new byte[Secp256k1.SECRET_LENGTH]; -Assert.True(secp256k1.Ecdh(secret1, aliceKeyPair.PublicKey, bobKeyPair.PrivateKey)); - -// Create secret using Bob's public key and Alice's private key -var secret2 = new byte[Secp256k1.SECRET_LENGTH]; -Assert.True(secp256k1.Ecdh(secret2, bobKeyPair.PublicKey, aliceKeyPair.PrivateKey)); - -// Validate secrets match -Assert.Equal(Convert.ToHexString(secret1), Convert.ToHexString(secret2)); -``` +## API Reference -#### Parsing and serializing DER signatures -```csharp -using var secp256k1 = new Secp256k1(); +**[Full API Documentation](https://zone117x.github.io/Secp256k1.Net/api/Secp256k1Net.Secp256k1.html)** -// Parse DER signature -var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; -var derSignature = Convert.FromHexString("30440220484ECE2B365D2B2C2EAD34B518328BBFEF0F4409349EEEC9CB19837B5795A5F5022040C4F6901FE489F923C49D4104554FD08595EAF864137F87DADDD0E3619B0605"); -Assert.True(secp256k1.SignatureParseDer(signatureOutput, derSignature)); +The `Secp256k1` class exposes static functions that are idiomatic C#, using a thread-safe internal context: -// Serialize DER signature -Span derSignatureOutput = new byte[Secp256k1.SERIALIZED_DER_SIGNATURE_MAX_SIZE]; -Assert.True(secp256k1.SignatureSerializeDer(derSignatureOutput, signatureOutput, out int signatureOutputLength)); -derSignatureOutput = derSignatureOutput.Slice(0, signatureOutputLength); +#### Key Generation & Validation +- `CreateSecretKey()` - Generate a cryptographically secure random secret key ([example](Secp256k1.Net.Examples/KeyGenerationExamples.cs#L31)) +- `CreatePublicKey(secretKey, compressed)` - Derive a serialized public key from a secret key ([example](Secp256k1.Net.Examples/KeyGenerationExamples.cs#L48)) +- `CreateXOnlyPublicKey(secretKey)` - Derive an x-only public key and parity for BIP-340 ([example](Secp256k1.Net.Examples/KeyGenerationExamples.cs#L69)) +- `CreateKeyPair(compressed)` - Generate a new secret key and public key pair ([example](Secp256k1.Net.Examples/KeyGenerationExamples.cs#L85)) +- `IsValidSecretKey(secretKey)` - Validate a secret key ([example](Secp256k1.Net.Examples/KeyGenerationExamples.cs#L105)) +- `IsValidPublicKey(publicKey)` - Validate a serialized public key ([example](Secp256k1.Net.Examples/KeyGenerationExamples.cs#L132)) -// Validate signature is the same after round trip parse and serialize -Assert.Equal(Convert.ToHexString(derSignature), Convert.ToHexString(derSignatureOutput)); -``` +#### Public Key Operations +- `CompressPublicKey(publicKey)` - Convert a public key to 33-byte compressed format ([example](Secp256k1.Net.Examples/PublicKeyOperationsExamples.cs#L34)) +- `DecompressPublicKey(publicKey)` - Convert a public key to 65-byte uncompressed format ([example](Secp256k1.Net.Examples/PublicKeyOperationsExamples.cs#L57)) +- `NegatePublicKey(publicKey, compressed)` - Negate a public key ([example](Secp256k1.Net.Examples/PublicKeyOperationsExamples.cs#L76)) +- `CombinePublicKeys(publicKeys, compressed)` - Add multiple public keys together ([example](Secp256k1.Net.Examples/PublicKeyOperationsExamples.cs#L107)) -See the [tests project](Secp256k1.Net.Test/Tests.cs) for more examples. +#### ECDSA Signing & Verification +- `Sign(messageHash, secretKey)` - Create a 64-byte compact ECDSA signature ([example](Secp256k1.Net.Examples/EcdsaSigningExamples.cs#L41)) +- `Verify(signature, messageHash, publicKey)` - Verify an ECDSA signature ([example](Secp256k1.Net.Examples/EcdsaSigningExamples.cs#L46)) +- `SignRecoverable(messageHash, secretKey)` - Create a recoverable signature with recovery ID ([example](Secp256k1.Net.Examples/EcdsaSigningExamples.cs#L62)) +- `RecoverPublicKey(signature, recoveryId, messageHash, compressed)` - Recover public key from signature ([example](Secp256k1.Net.Examples/EcdsaSigningExamples.cs#L86)) -# Benchmarks +#### DER Signature Format +- `SignatureToDer(compactSignature)` - Convert compact signature to DER format ([example](Secp256k1.Net.Examples/DerSignatureExamples.cs#L37)) +- `SignatureFromDer(derSignature)` - Convert DER signature to compact format ([example](Secp256k1.Net.Examples/DerSignatureExamples.cs#L58)) +- `VerifyDer(derSignature, messageHash, publicKey)` - Verify a DER-encoded signature ([example](Secp256k1.Net.Examples/DerSignatureExamples.cs#L82)) -``` ini +#### Signature Normalization +- `NormalizeSignature(signature)` - Normalize signature to lower-S form ([example](Secp256k1.Net.Examples/SignatureNormalizationExamples.cs#L36)) +- `IsNormalizedSignature(signature)` - Check if signature is in lower-S form ([example](Secp256k1.Net.Examples/SignatureNormalizationExamples.cs#L67)) -BenchmarkDotNet=v0.13.4, OS=macOS Monterey 12.6.2 (21G320) [Darwin 21.6.0] -Apple M1 Pro, 1 CPU, 10 logical and 10 physical cores -.NET SDK=7.0.102 - [Host] : .NET 7.0.2 (7.0.222.60605), Arm64 RyuJIT AdvSIMD - DefaultJob : .NET 7.0.2 (7.0.222.60605), Arm64 RyuJIT AdvSIMD +#### Schnorr Signatures (BIP-340) +- `SignSchnorr(messageHash, secretKey, auxRand)` - Create a Schnorr signature ([example](Secp256k1.Net.Examples/SchnorrSignatureExamples.cs#L42)) +- `VerifySchnorr(signature, message, publicKey)` - Verify a Schnorr signature ([example](Secp256k1.Net.Examples/SchnorrSignatureExamples.cs#L66)) +#### ECDH Key Agreement +- `ComputeSharedSecret(publicKey, secretKey)` - Compute ECDH shared secret ([example](Secp256k1.Net.Examples/EcdhExamples.cs#L35)) -``` -| Method | feature | Mean | Error | StdDev | Ratio | RatioSD | -|------------- |-------------- |------------:|----------:|----------:|------:|--------:| -| **Secp256k1Net** | **SignOnly** | **53.00 μs** | **0.044 μs** | **0.037 μs** | **1.00** | **0.00** | -| Nbitcoin | SignOnly | 186.25 μs | 0.255 μs | 0.226 μs | 3.51 | 0.01 | -| Nethereum | SignOnly | 579.06 μs | 1.272 μs | 0.993 μs | 10.93 | 0.02 | -| BouncyCastle | SignOnly | 582.83 μs | 6.968 μs | 5.818 μs | 11.00 | 0.11 | -| Chainers | SignOnly | 778.34 μs | 15.176 μs | 14.905 μs | 14.72 | 0.30 | -| StarkBank | SignOnly | 1,800.91 μs | 4.751 μs | 4.444 μs | 34.00 | 0.10 | -| | | | | | | | -| **Secp256k1Net** | **SignAndVerify** | **90.97 μs** | **0.084 μs** | **0.075 μs** | **1.00** | **0.00** | -| Nbitcoin | SignAndVerify | 373.22 μs | 1.822 μs | 1.521 μs | 4.10 | 0.02 | -| Nethereum | SignAndVerify | 1,679.02 μs | 3.984 μs | 3.327 μs | 18.46 | 0.04 | -| BouncyCastle | SignAndVerify | 1,701.31 μs | 18.157 μs | 16.985 μs | 18.72 | 0.18 | -| StarkBank | SignAndVerify | 5,315.49 μs | 15.796 μs | 14.002 μs | 58.43 | 0.15 | +#### Key Tweaking (BIP-32 HD Wallets) +- `TweakSecretKeyAdd(secretKey, tweak)` - Add a tweak to a secret key ([example](Secp256k1.Net.Examples/KeyTweakingExamples.cs#L37)) +- `TweakPublicKeyAdd(publicKey, tweak, compressed)` - Add a tweak to a public key ([example](Secp256k1.Net.Examples/KeyTweakingExamples.cs#L60)) +- `TweakSecretKeyMul(secretKey, tweak)` - Multiply a secret key by a tweak ([example](Secp256k1.Net.Examples/KeyTweakingExamples.cs#L87)) +- `TweakPublicKeyMul(publicKey, tweak, compressed)` - Multiply a public key by a tweak ([example](Secp256k1.Net.Examples/KeyTweakingExamples.cs#L107)) +- `NegateSecretKey(secretKey)` - Negate a secret key ([example](Secp256k1.Net.Examples/KeyTweakingExamples.cs#L134)) ---- +#### Hashing +- `TaggedHash(tag, message)` - Compute a BIP-340 tagged hash ([example](Secp256k1.Net.Examples/HashingExamples.cs#L32)) + +## Advanced Usage -``` ini +The `Secp256k1` class also provides instance methods that are direct wrappers for the native C library, with near one-to-one API mapping. These offer more control over memory allocation and access to additional features: -BenchmarkDotNet=v0.13.4, OS=macOS Monterey 12.6.3 (21G419) [Darwin 21.6.0] -Intel Xeon CPU E5-1650 v2 3.50GHz (Max: 3.34GHz), 1 CPU, 3 logical and 3 physical cores -.NET SDK=7.0.102 - [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX - DefaultJob : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX +- [Custom ECDH hash functions](Secp256k1.Net.Examples/AdvancedUsageExamples.cs#L97) - Use custom hash functions for ECDH +- [Custom nonce functions](Secp256k1.Net.Examples/AdvancedUsageExamples.cs#L164) - Provide custom nonce generation for signing +- [Public key sorting](Secp256k1.Net.Examples/AdvancedUsageExamples.cs#L347) - Sort public keys lexicographically +- [Keypair operations](Secp256k1.Net.Examples/AdvancedUsageExamples.cs#L392) - Work with 96-byte keypair objects +- [X-only pubkey tweaking](Secp256k1.Net.Examples/AdvancedUsageExamples.cs#L497) - Taproot-style key tweaking (BIP-341) +- [ElligatorSwift encoding](Secp256k1.Net.Examples/AdvancedUsageExamples.cs#L561) - BIP-324 encrypted transport +- [MuSig2 multi-signatures](Secp256k1.Net.Examples/MuSig2Examples.cs#L57) - Aggregate Schnorr signatures from multiple signers +# Benchmarks + +`Secp256k1.Net` is consistently 5-10x faster than the next best library (`NBitcoin`) and 20-100x faster than pure managed implementations like `BouncyCastle`, `Nethereum`, and `StarkBank`. ``` -| Method | feature | Mean | Error | StdDev | Median | Ratio | RatioSD | -|------------- |-------------- |------------:|-----------:|-----------:|------------:|------:|--------:| -| **Secp256k1Net** | **SignOnly** | **97.17 μs** | **4.112 μs** | **11.666 μs** | **93.27 μs** | **1.00** | **0.00** | -| Nbitcoin | SignOnly | 362.74 μs | 15.863 μs | 45.769 μs | 357.29 μs | 3.79 | 0.65 | -| Nethereum | SignOnly | 1,122.70 μs | 28.246 μs | 78.740 μs | 1,098.21 μs | 11.71 | 1.46 | -| BouncyCastle | SignOnly | 1,079.60 μs | 21.453 μs | 43.823 μs | 1,067.88 μs | 11.18 | 1.36 | -| Chainers | SignOnly | 1,300.33 μs | 23.165 μs | 30.121 μs | 1,301.86 μs | 12.49 | 1.65 | -| StarkBank | SignOnly | 2,564.26 μs | 41.055 μs | 40.322 μs | 2,566.36 μs | 25.16 | 2.97 | -| | | | | | | | | -| **Secp256k1Net** | **SignAndVerify** | **146.25 μs** | **2.679 μs** | **2.506 μs** | **145.54 μs** | **1.00** | **0.00** | -| Nbitcoin | SignAndVerify | 724.20 μs | 7.401 μs | 6.561 μs | 723.84 μs | 4.95 | 0.09 | -| Nethereum | SignAndVerify | 3,048.38 μs | 59.507 μs | 55.663 μs | 3,058.23 μs | 20.85 | 0.57 | -| BouncyCastle | SignAndVerify | 2,997.17 μs | 51.521 μs | 45.672 μs | 2,999.00 μs | 20.48 | 0.41 | -| StarkBank | SignAndVerify | 8,008.58 μs | 159.859 μs | 304.149 μs | 8,022.61 μs | 53.30 | 2.05 | + +BenchmarkDotNet v0.15.8, macOS Sequoia 15.7.1 (24G231) [Darwin 24.6.0] +Apple M3 Max, 1 CPU, 14 logical and 14 physical cores +.NET SDK 10.0.102 + [Host] : .NET 10.0.2 (10.0.2, 10.0.225.61305), Arm64 RyuJIT armv8.0-a + DefaultJob : .NET 10.0.2 (10.0.2, 10.0.225.61305), Arm64 RyuJIT armv8.0-a + + +``` +| Method | Categories | Mean | Error | StdDev | Ratio | RatioSD | +|------------- |--------------------- |-------------:|-----------:|-----------:|-------:|--------:| +| Secp256k1Net | Ecdh | 22.964 μs | 0.1813 μs | 0.1607 μs | 1.00 | 0.01 | +| NBitcoin | Ecdh | 167.133 μs | 0.6087 μs | 0.5694 μs | 7.28 | 0.05 | +| Nethereum | Ecdh | 500.696 μs | 3.4009 μs | 3.1812 μs | 21.80 | 0.20 | +| BouncyCastle | Ecdh | 503.882 μs | 6.4419 μs | 6.0257 μs | 21.94 | 0.29 | +| | | | | | | | +| Secp256k1Net | EcdsaRecover | 36.042 μs | 0.1504 μs | 0.1333 μs | 1.00 | 0.01 | +| NBitcoin | EcdsaRecover | 268.565 μs | 1.1492 μs | 1.0187 μs | 7.45 | 0.04 | +| Nethereum | EcdsaRecover | 1,977.580 μs | 14.0846 μs | 12.4856 μs | 54.87 | 0.39 | +| BouncyCastle | EcdsaRecover | 2,270.418 μs | 27.8990 μs | 26.0967 μs | 62.99 | 0.74 | +| | | | | | | | +| Secp256k1Net | EcdsaSign | 15.231 μs | 0.0491 μs | 0.0436 μs | 1.00 | 0.00 | +| NBitcoin | EcdsaSign | 133.297 μs | 0.5326 μs | 0.4721 μs | 8.75 | 0.04 | +| Nethereum | EcdsaSign | 319.805 μs | 2.8822 μs | 2.6960 μs | 21.00 | 0.18 | +| BouncyCastle | EcdsaSign | 312.781 μs | 2.3212 μs | 1.9383 μs | 20.54 | 0.14 | +| StarkBank | EcdsaSign | 1,085.330 μs | 6.0425 μs | 5.6522 μs | 71.26 | 0.41 | +| Chainers | EcdsaSign | 293.091 μs | 4.1747 μs | 3.9051 μs | 19.24 | 0.25 | +| | | | | | | | +| Secp256k1Net | EcdsaSignRecoverable | 15.052 μs | 0.0400 μs | 0.0312 μs | 1.00 | 0.00 | +| NBitcoin | EcdsaSignRecoverable | 133.714 μs | 0.7474 μs | 0.6626 μs | 8.88 | 0.05 | +| Nethereum | EcdsaSignRecoverable | 1,376.987 μs | 12.3970 μs | 10.9896 μs | 91.48 | 0.73 | +| BouncyCastle | EcdsaSignRecoverable | 1,630.056 μs | 17.9736 μs | 16.8126 μs | 108.29 | 1.10 | +| | | | | | | | +| Secp256k1Net | EcdsaVerify | 20.045 μs | 0.1364 μs | 0.1276 μs | 1.00 | 0.01 | +| NBitcoin | EcdsaVerify | 128.001 μs | 1.1558 μs | 1.0246 μs | 6.39 | 0.06 | +| Nethereum | EcdsaVerify | 588.907 μs | 10.3029 μs | 9.1332 μs | 29.38 | 0.48 | +| BouncyCastle | EcdsaVerify | 582.463 μs | 8.7357 μs | 8.1713 μs | 29.06 | 0.43 | +| StarkBank | EcdsaVerify | 2,105.913 μs | 31.3613 μs | 29.3354 μs | 105.06 | 1.56 | +| | | | | | | | +| Secp256k1Net | PubKeyCreate | 9.759 μs | 0.0638 μs | 0.0566 μs | 1.00 | 0.01 | +| NBitcoin | PubKeyCreate | 95.283 μs | 0.5591 μs | 0.4956 μs | 9.76 | 0.07 | +| Nethereum | PubKeyCreate | 378.257 μs | 2.0409 μs | 1.9091 μs | 38.76 | 0.29 | +| BouncyCastle | PubKeyCreate | 377.224 μs | 3.0774 μs | 2.5698 μs | 38.65 | 0.33 | +| StarkBank | PubKeyCreate | 990.958 μs | 9.6931 μs | 9.0669 μs | 101.54 | 1.06 | +| Chainers | PubKeyCreate | 57.937 μs | 0.5150 μs | 0.4818 μs | 5.94 | 0.06 | +| | | | | | | | +| Secp256k1Net | SchnorrSign | 20.296 μs | 0.1379 μs | 0.1290 μs | 1.00 | 0.01 | +| NBitcoin | SchnorrSign | 194.996 μs | 1.0752 μs | 0.9531 μs | 9.61 | 0.07 | +| | | | | | | | +| Secp256k1Net | SchnorrVerify | 20.199 μs | 0.1088 μs | 0.1018 μs | 1.00 | 0.01 | +| NBitcoin | SchnorrVerify | 192.977 μs | 0.4276 μs | 0.3999 μs | 9.55 | 0.05 | --- -``` ini +``` -BenchmarkDotNet=v0.13.4, OS=ubuntu 22.04 -Intel Xeon Platinum 8370C CPU 2.80GHz, 1 CPU, 2 logical and 2 physical cores -.NET SDK=7.0.102 - [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2 - DefaultJob : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2 +BenchmarkDotNet v0.15.8, Windows 11 (10.0.26100.7462/24H2/2024Update/HudsonValley) (Hyper-V) +Intel Xeon Platinum 8370C CPU 2.80GHz (Max: 2.79GHz), 1 CPU, 4 logical and 2 physical cores +.NET SDK 10.0.102 + [Host] : .NET 10.0.2 (10.0.2, 10.0.225.61305), X64 RyuJIT x86-64-v4 + ShortRun : .NET 10.0.2 (10.0.2, 10.0.225.61305), X64 RyuJIT x86-64-v4 +Job=ShortRun IterationCount=3 LaunchCount=1 +WarmupCount=3 ``` -| Method | feature | Mean | Error | StdDev | Ratio | RatioSD | -|------------- |-------------- |------------:|----------:|----------:|------:|--------:| -| **Secp256k1Net** | **SignOnly** | **88.61 μs** | **0.047 μs** | **0.041 μs** | **1.00** | **0.00** | -| Nbitcoin | SignOnly | 303.01 μs | 0.478 μs | 0.447 μs | 3.42 | 0.01 | -| Nethereum | SignOnly | 988.51 μs | 4.649 μs | 4.348 μs | 11.16 | 0.05 | -| BouncyCastle | SignOnly | 1,005.06 μs | 4.370 μs | 4.087 μs | 11.35 | 0.05 | -| Chainers | SignOnly | 1,545.85 μs | 29.765 μs | 29.233 μs | 17.42 | 0.35 | -| StarkBank | SignOnly | 2,441.18 μs | 5.709 μs | 5.340 μs | 27.55 | 0.06 | -| | | | | | | | -| **Secp256k1Net** | **SignAndVerify** | **146.08 μs** | **0.047 μs** | **0.039 μs** | **1.00** | **0.00** | -| Nbitcoin | SignAndVerify | 631.46 μs | 0.782 μs | 0.693 μs | 4.32 | 0.01 | -| Nethereum | SignAndVerify | 2,800.69 μs | 19.084 μs | 17.851 μs | 19.17 | 0.13 | -| BouncyCastle | SignAndVerify | 2,878.09 μs | 16.666 μs | 14.774 μs | 19.71 | 0.10 | -| StarkBank | SignAndVerify | 7,121.17 μs | 13.625 μs | 12.745 μs | 48.77 | 0.08 | +| Method | Categories | Mean | Error | StdDev | Ratio | RatioSD | +|------------- |--------------------- |------------:|-------------:|-----------:|------:|--------:| +| Secp256k1Net | Ecdh | 52.42 μs | 9.141 μs | 0.501 μs | 1.00 | 0.01 | +| NBitcoin | Ecdh | 298.68 μs | 4.568 μs | 0.250 μs | 5.70 | 0.05 | +| Nethereum | Ecdh | 928.84 μs | 78.708 μs | 4.314 μs | 17.72 | 0.16 | +| BouncyCastle | Ecdh | 1,028.87 μs | 408.540 μs | 22.393 μs | 19.63 | 0.40 | +| | | | | | | | +| Secp256k1Net | EcdsaRecover | 83.14 μs | 52.429 μs | 2.874 μs | 1.00 | 0.04 | +| NBitcoin | EcdsaRecover | 521.88 μs | 182.631 μs | 10.011 μs | 6.28 | 0.21 | +| Nethereum | EcdsaRecover | 4,204.95 μs | 1,926.313 μs | 105.588 μs | 50.61 | 1.87 | +| BouncyCastle | EcdsaRecover | 4,681.68 μs | 3,295.534 μs | 180.639 μs | 56.35 | 2.52 | +| | | | | | | | +| Secp256k1Net | EcdsaSign | 34.74 μs | 17.371 μs | 0.952 μs | 1.00 | 0.03 | +| NBitcoin | EcdsaSign | 235.00 μs | 9.356 μs | 0.513 μs | 6.77 | 0.16 | +| Nethereum | EcdsaSign | 615.69 μs | 77.304 μs | 4.237 μs | 17.73 | 0.43 | +| BouncyCastle | EcdsaSign | 603.43 μs | 51.399 μs | 2.817 μs | 17.38 | 0.42 | +| StarkBank | EcdsaSign | 1,610.20 μs | 322.548 μs | 17.680 μs | 46.37 | 1.18 | +| Chainers | EcdsaSign | 645.17 μs | 356.116 μs | 19.520 μs | 18.58 | 0.66 | +| | | | | | | | +| Secp256k1Net | EcdsaSignRecoverable | 33.44 μs | 0.760 μs | 0.042 μs | 1.00 | 0.00 | +| NBitcoin | EcdsaSignRecoverable | 239.78 μs | 161.529 μs | 8.854 μs | 7.17 | 0.23 | +| Nethereum | EcdsaSignRecoverable | 2,486.05 μs | 349.196 μs | 19.141 μs | 74.35 | 0.50 | +| BouncyCastle | EcdsaSignRecoverable | 3,058.70 μs | 1,589.421 μs | 87.122 μs | 91.47 | 2.26 | +| | | | | | | | +| Secp256k1Net | EcdsaVerify | 44.77 μs | 4.161 μs | 0.228 μs | 1.00 | 0.01 | +| NBitcoin | EcdsaVerify | 244.93 μs | 11.689 μs | 0.641 μs | 5.47 | 0.03 | +| Nethereum | EcdsaVerify | 1,108.03 μs | 93.805 μs | 5.142 μs | 24.75 | 0.15 | +| BouncyCastle | EcdsaVerify | 1,142.20 μs | 138.179 μs | 7.574 μs | 25.51 | 0.18 | +| StarkBank | EcdsaVerify | 3,164.81 μs | 456.649 μs | 25.030 μs | 70.69 | 0.58 | +| | | | | | | | +| Secp256k1Net | PubKeyCreate | 23.61 μs | 5.538 μs | 0.304 μs | 1.00 | 0.02 | +| NBitcoin | PubKeyCreate | 180.93 μs | 3.066 μs | 0.168 μs | 7.66 | 0.08 | +| Nethereum | PubKeyCreate | 721.42 μs | 41.428 μs | 2.271 μs | 30.56 | 0.35 | +| BouncyCastle | PubKeyCreate | 748.16 μs | 42.694 μs | 2.340 μs | 31.69 | 0.36 | +| StarkBank | PubKeyCreate | 1,579.52 μs | 48.830 μs | 2.677 μs | 66.91 | 0.75 | +| Chainers | PubKeyCreate | 117.26 μs | 3.038 μs | 0.167 μs | 4.97 | 0.06 | +| | | | | | | | +| Secp256k1Net | SchnorrSign | 45.42 μs | 1.347 μs | 0.074 μs | 1.00 | 0.00 | +| NBitcoin | SchnorrSign | 373.44 μs | 27.746 μs | 1.521 μs | 8.22 | 0.03 | +| | | | | | | | +| Secp256k1Net | SchnorrVerify | 38.90 μs | 8.268 μs | 0.453 μs | 1.00 | 0.01 | +| NBitcoin | SchnorrVerify | 384.05 μs | 9.204 μs | 0.505 μs | 9.87 | 0.10 | --- -``` ini +``` -BenchmarkDotNet=v0.13.4, OS=Windows 10 (10.0.20348.1487), VM=Hyper-V -Intel Xeon CPU E5-2673 v4 2.30GHz, 1 CPU, 2 logical and 2 physical cores -.NET SDK=7.0.102 - [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2 - DefaultJob : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2 +BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat) +Intel Xeon Platinum 8370C CPU 2.80GHz (Max: 3.39GHz), 1 CPU, 4 logical and 2 physical cores +.NET SDK 10.0.102 + [Host] : .NET 10.0.2 (10.0.2, 10.0.225.61305), X64 RyuJIT x86-64-v4 + ShortRun : .NET 10.0.2 (10.0.2, 10.0.225.61305), X64 RyuJIT x86-64-v4 +Job=ShortRun IterationCount=3 LaunchCount=1 +WarmupCount=3 ``` -| Method | feature | Mean | Error | StdDev | Ratio | RatioSD | -|------------- |-------------- |-----------:|----------:|----------:|------:|--------:| -| **Secp256k1Net** | **SignOnly** | **165.8 μs** | **3.28 μs** | **3.07 μs** | **1.00** | **0.00** | -| Nbitcoin | SignOnly | 374.1 μs | 7.43 μs | 8.84 μs | 2.25 | 0.06 | -| Nethereum | SignOnly | 1,206.2 μs | 20.57 μs | 20.21 μs | 7.28 | 0.21 | -| BouncyCastle | SignOnly | 1,200.1 μs | 20.21 μs | 18.91 μs | 7.24 | 0.17 | -| Chainers | SignOnly | 1,613.4 μs | 31.76 μs | 50.38 μs | 9.78 | 0.31 | -| StarkBank | SignOnly | 3,341.0 μs | 63.47 μs | 73.09 μs | 20.17 | 0.57 | -| | | | | | | | -| **Secp256k1Net** | **SignAndVerify** | **274.4 μs** | **5.30 μs** | **7.26 μs** | **1.00** | **0.00** | -| Nbitcoin | SignAndVerify | 807.3 μs | 16.02 μs | 32.00 μs | 3.00 | 0.16 | -| Nethereum | SignAndVerify | 3,490.7 μs | 68.01 μs | 101.79 μs | 12.74 | 0.47 | -| BouncyCastle | SignAndVerify | 3,438.9 μs | 68.07 μs | 109.93 μs | 12.52 | 0.52 | -| StarkBank | SignAndVerify | 9,331.1 μs | 184.57 μs | 318.37 μs | 34.38 | 1.49 | +| Method | Categories | Mean | Error | StdDev | Ratio | RatioSD | +|------------- |--------------------- |------------:|-----------:|----------:|------:|--------:| +| Secp256k1Net | Ecdh | 53.32 μs | 4.543 μs | 0.249 μs | 1.00 | 0.01 | +| NBitcoin | Ecdh | 291.39 μs | 36.415 μs | 1.996 μs | 5.47 | 0.04 | +| Nethereum | Ecdh | 1,059.77 μs | 348.103 μs | 19.081 μs | 19.88 | 0.32 | +| BouncyCastle | Ecdh | 1,031.91 μs | 129.167 μs | 7.080 μs | 19.35 | 0.14 | +| | | | | | | | +| Secp256k1Net | EcdsaRecover | 79.91 μs | 1.059 μs | 0.058 μs | 1.00 | 0.00 | +| NBitcoin | EcdsaRecover | 486.64 μs | 6.764 μs | 0.371 μs | 6.09 | 0.01 | +| Nethereum | EcdsaRecover | 4,022.64 μs | 707.698 μs | 38.791 μs | 50.34 | 0.42 | +| BouncyCastle | EcdsaRecover | 4,793.43 μs | 863.738 μs | 47.344 μs | 59.99 | 0.51 | +| | | | | | | | +| Secp256k1Net | EcdsaSign | 38.09 μs | 0.881 μs | 0.048 μs | 1.00 | 0.00 | +| NBitcoin | EcdsaSign | 232.55 μs | 8.640 μs | 0.474 μs | 6.11 | 0.01 | +| Nethereum | EcdsaSign | 667.88 μs | 26.910 μs | 1.475 μs | 17.53 | 0.04 | +| BouncyCastle | EcdsaSign | 668.15 μs | 86.774 μs | 4.756 μs | 17.54 | 0.11 | +| StarkBank | EcdsaSign | 1,611.32 μs | 50.303 μs | 2.757 μs | 42.30 | 0.08 | +| Chainers | EcdsaSign | 660.49 μs | 151.176 μs | 8.286 μs | 17.34 | 0.19 | +| | | | | | | | +| Secp256k1Net | EcdsaSignRecoverable | 37.37 μs | 1.007 μs | 0.055 μs | 1.00 | 0.00 | +| NBitcoin | EcdsaSignRecoverable | 232.93 μs | 5.037 μs | 0.276 μs | 6.23 | 0.01 | +| Nethereum | EcdsaSignRecoverable | 2,755.96 μs | 242.894 μs | 13.314 μs | 73.75 | 0.32 | +| BouncyCastle | EcdsaSignRecoverable | 3,459.83 μs | 473.894 μs | 25.976 μs | 92.58 | 0.61 | +| | | | | | | | +| Secp256k1Net | EcdsaVerify | 44.42 μs | 0.426 μs | 0.023 μs | 1.00 | 0.00 | +| NBitcoin | EcdsaVerify | 236.92 μs | 4.157 μs | 0.228 μs | 5.33 | 0.01 | +| Nethereum | EcdsaVerify | 1,221.70 μs | 529.962 μs | 29.049 μs | 27.51 | 0.57 | +| BouncyCastle | EcdsaVerify | 1,210.26 μs | 83.885 μs | 4.598 μs | 27.25 | 0.09 | +| StarkBank | EcdsaVerify | 3,176.97 μs | 376.794 μs | 20.653 μs | 71.53 | 0.40 | +| | | | | | | | +| Secp256k1Net | PubKeyCreate | 27.53 μs | 0.828 μs | 0.045 μs | 1.00 | 0.00 | +| NBitcoin | PubKeyCreate | 170.63 μs | 1.680 μs | 0.092 μs | 6.20 | 0.01 | +| Nethereum | PubKeyCreate | 795.21 μs | 126.844 μs | 6.953 μs | 28.89 | 0.22 | +| BouncyCastle | PubKeyCreate | 773.25 μs | 234.863 μs | 12.874 μs | 28.09 | 0.41 | +| StarkBank | PubKeyCreate | 1,536.89 μs | 53.979 μs | 2.959 μs | 55.83 | 0.12 | +| Chainers | PubKeyCreate | 118.50 μs | 5.219 μs | 0.286 μs | 4.30 | 0.01 | +| | | | | | | | +| Secp256k1Net | SchnorrSign | 53.37 μs | 1.060 μs | 0.058 μs | 1.00 | 0.00 | +| NBitcoin | SchnorrSign | 354.83 μs | 38.171 μs | 2.092 μs | 6.65 | 0.03 | +| | | | | | | | +| Secp256k1Net | SchnorrVerify | 37.96 μs | 0.644 μs | 0.035 μs | 1.00 | 0.00 | +| NBitcoin | SchnorrVerify | 369.41 μs | 8.029 μs | 0.440 μs | 9.73 | 0.01 | diff --git a/Secp256k1.Net.Bench/BenchmarkHelpers.cs b/Secp256k1.Net.Bench/BenchmarkHelpers.cs new file mode 100644 index 0000000..97de4ba --- /dev/null +++ b/Secp256k1.Net.Bench/BenchmarkHelpers.cs @@ -0,0 +1,154 @@ +using System; +using System.Text; +using System.Security.Cryptography; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Configs; + +namespace Secp256k1Net.Bench +{ + // Configures benchmark job based on execution mode: + // - VALIDATE=true: Uses Job.Dry (1 launch, 1 warmup, 1 iteration) for quick validation + // - CI=true: Uses Job.ShortRun for faster CI execution (fewer iterations, less accurate) + // - Default: Uses Job.Default for accurate results + public class CiBenchmarkConfig : ManualConfig + { + public CiBenchmarkConfig() + { + if (Environment.GetEnvironmentVariable("VALIDATE") == "true") + { + AddJob(Job.Dry); + } + else if (Environment.GetEnvironmentVariable("CI") == "true") + { + AddJob(Job.ShortRun); + } + else + { + AddJob(Job.Default); + } + } + } + + record class KeyPair(byte[] PrivateKey, byte[] PublicKeyCompressed, byte[] PublicKeyUncompressed); + record class Msg(string MsgString, byte[] MsgBytes, byte[] MsgHash); + + class BenchInputs + { + public readonly KeyPair KeyPair; + public readonly Msg Msg; + public readonly byte[] EcdsaSig; + public readonly byte[] AlicePubKeyCompressed; + + public BenchInputs() + { + KeyPair = new( + Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"), + Convert.FromHexString("03bf2e2462a3e64b941187b903156dbe9fb9b09b1e76ff5a55edf3d441dcd50822"), + Convert.FromHexString("04bf2e2462a3e64b941187b903156dbe9fb9b09b1e76ff5a55edf3d441dcd508227f20aebd43fb7de880b28ea03baae531c05f17d2f99940aa6a3fe1a4c788c7a1") + ); + + var msg = "Message for signing"; + var msgBytes = Encoding.UTF8.GetBytes(msg); + var msgHash = SHA256.HashData(msgBytes); + Msg = new(msg, msgBytes, msgHash); + + // 32-byte big endian R value, followed by a 32-byte big endian S value + EcdsaSig = Convert.FromHexString("8748f4a24fd0ecca9100ef947b73cbb6f11d67d151d2a900ab9fec1dce0051cc687136810ad4aba6812ad39cea0a41ba2cb04cb32d574a443f0d5c03e2dfa44f"); + + // Second public key for ECDH (Alice's public key) + AlicePubKeyCompressed = Convert.FromHexString("02c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae"); + } + } + + // Helper for StarkBank low-S normalization + static class StarkBankHelper + { + // secp256k1 curve order N and halfN for low-S normalization + public static readonly System.Numerics.BigInteger CurveN = new( + Convert.FromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"), + isUnsigned: true, isBigEndian: true); + public static readonly System.Numerics.BigInteger HalfN = CurveN >> 1; + } + + // Helper for BouncyCastle ECDSA recovery operations + static class BouncyCastleRecoveryHelper + { + public static (Org.BouncyCastle.Math.BigInteger r, Org.BouncyCastle.Math.BigInteger s, int recId, + Org.BouncyCastle.Asn1.X9.X9ECParameters curve, Org.BouncyCastle.Crypto.Parameters.ECDomainParameters domain) + SignRecoverable(byte[] privateKey, byte[] msgHash) + { + var curve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); + var domain = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); + var d = new Org.BouncyCastle.Math.BigInteger(1, privateKey); + var keyParameters = new Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters(d, domain); + var signer = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner(); + signer.Init(true, keyParameters); + var signature = signer.GenerateSignature(msgHash); + var r = signature[0]; + var s = signature[1]; + var pubKeyPoint = curve.G.Multiply(d).Normalize(); + var recId = CalculateRecId(curve, domain, msgHash, r, s, pubKeyPoint); + return (r, s, recId, curve, domain); + } + + public static int CalculateRecId( + Org.BouncyCastle.Asn1.X9.X9ECParameters curve, + Org.BouncyCastle.Crypto.Parameters.ECDomainParameters domain, + byte[] msgHash, + Org.BouncyCastle.Math.BigInteger r, + Org.BouncyCastle.Math.BigInteger s, + Org.BouncyCastle.Math.EC.ECPoint expectedPubKey) + { + var e = new Org.BouncyCastle.Math.BigInteger(1, msgHash); + for (int recId = 0; recId < 4; recId++) + { + var recovered = RecoverPublicKey(curve, domain, e, r, s, recId); + if (recovered != null && recovered.Equals(expectedPubKey)) + return recId; + } + throw new Exception("Could not find recovery id"); + } + + public static Org.BouncyCastle.Math.EC.ECPoint RecoverPublicKey( + Org.BouncyCastle.Asn1.X9.X9ECParameters curve, + Org.BouncyCastle.Crypto.Parameters.ECDomainParameters domain, + Org.BouncyCastle.Math.BigInteger e, + Org.BouncyCastle.Math.BigInteger r, + Org.BouncyCastle.Math.BigInteger s, + int recId) + { + var n = domain.N; + var i = Org.BouncyCastle.Math.BigInteger.ValueOf(recId / 2); + var x = r.Add(i.Multiply(n)); + + if (x.CompareTo(curve.Curve.Field.Characteristic) >= 0) + return null; + + // Decompress point from x coordinate + var R = DecompressPoint(curve, x, (recId & 1) == 1); + if (R == null || !R.Multiply(n).IsInfinity) + return null; + + var eInv = Org.BouncyCastle.Math.BigInteger.Zero.Subtract(e).Mod(n); + var rInv = r.ModInverse(n); + var srInv = rInv.Multiply(s).Mod(n); + var eInvrInv = rInv.Multiply(eInv).Mod(n); + + var q = Org.BouncyCastle.Math.EC.ECAlgorithms.SumOfTwoMultiplies(curve.G, eInvrInv, R, srInv); + return q.Normalize(); + } + + private static Org.BouncyCastle.Math.EC.ECPoint DecompressPoint( + Org.BouncyCastle.Asn1.X9.X9ECParameters curve, + Org.BouncyCastle.Math.BigInteger x, + bool yOdd) + { + var compEnc = new byte[33]; + compEnc[0] = (byte)(yOdd ? 0x03 : 0x02); + var xBytes = x.ToByteArrayUnsigned(); + Array.Copy(xBytes, 0, compEnc, 33 - xBytes.Length, xBytes.Length); + return curve.Curve.DecodePoint(compEnc); + } + + } +} diff --git a/Secp256k1.Net.Bench/BenchmarkValidation.cs b/Secp256k1.Net.Bench/BenchmarkValidation.cs new file mode 100644 index 0000000..a9d50ed --- /dev/null +++ b/Secp256k1.Net.Bench/BenchmarkValidation.cs @@ -0,0 +1,104 @@ +using System; +using System.Linq; + +namespace Secp256k1Net.Bench +{ + // Validation methods for Secp256k1Benchmarks (partial class) + public partial class Secp256k1Benchmarks + { + private void ValidateResults() + { + // Validate PubKeyCreate: all libraries should produce the same compressed public key + ValidateAllMatch("PubKeyCreate", + [ + ("Secp256k1Net", PubKeyCreate_Secp256k1Net()), + ("NBitcoin", PubKeyCreate_NBitcoin()), + ("Nethereum", PubKeyCreate_Nethereum()), + ("BouncyCastle", PubKeyCreate_BouncyCastle()), + ("StarkBank", PubKeyCreate_StarkBank()), + ("Chainers", PubKeyCreate_Chainers()), + ], inputs.KeyPair.PublicKeyCompressed); + + // Validate ECDSA Sign: all libraries should produce signatures that verify + ValidateEcdsaSignatures(); + + // Validate ECDH: all libraries return SHA256(compressed_point) + ValidateAllMatch("Ecdh", + [ + ("Secp256k1Net", Ecdh_Secp256k1Net()), + ("NBitcoin", Ecdh_NBitcoin()), + ("Nethereum", Ecdh_Nethereum()), + ("BouncyCastle", Ecdh_BouncyCastle()), + ]); + + // Validate EcdsaRecover: recovered public key should match original + ValidateAllMatch("EcdsaRecover", + [ + ("Secp256k1Net", EcdsaRecover_Secp256k1Net()), + ("NBitcoin", EcdsaRecover_NBitcoin()), + ("Nethereum", EcdsaRecover_Nethereum()), + ("BouncyCastle", EcdsaRecover_BouncyCastle()), + ], inputs.KeyPair.PublicKeyCompressed); + + // Validate Schnorr: signatures use random aux data so won't match, + // but each signature must verify correctly with the same verifier + ValidateSchnorrSignatures(); + } + + private void ValidateEcdsaSignatures() + { + // Verify that each library's ECDSA signature can be verified by Secp256k1Net + // All libraries now hash MsgBytes internally, so signatures are compatible + var signatures = new[] + { + ("Secp256k1Net", EcdsaSign_Secp256k1Net()), + ("NBitcoin", EcdsaSign_NBitcoin()), + ("Nethereum", EcdsaSign_Nethereum()), + ("BouncyCastle", EcdsaSign_BouncyCastle()), + ("Chainers", EcdsaSign_Chainers()), + ("StarkBank", EcdsaSign_StarkBank()), + }; + + foreach (var (name, compactSig) in signatures) + { + if (!Secp256k1.Verify(compactSig, inputs.Msg.MsgHash, inputs.KeyPair.PublicKeyCompressed)) + throw new Exception($"EcdsaSign validation failed: {name} signature did not verify"); + } + } + + private void ValidateSchnorrSignatures() + { + // Schnorr signatures use random aux data, so signatures won't match between libraries. + // Instead, verify that each library's signature can be verified by Secp256k1Net. + var signatures = new[] + { + ("Secp256k1Net", SchnorrSign_Secp256k1Net()), + ("NBitcoin", SchnorrSign_NBitcoin()), + }; + + foreach (var (name, sig) in signatures) + { + if (!Secp256k1.VerifySchnorr(sig, inputs.Msg.MsgHash, xOnlyPubKey)) + { + throw new Exception($"SchnorrSign validation failed: {name} signature did not verify"); + } + } + } + + private static void ValidateAllMatch(string category, (string name, byte[] value)[] results, byte[] expected = null) + { + var reference = expected ?? results[0].value; + var referenceName = expected != null ? "expected" : results[0].name; + + foreach (var (name, value) in results) + { + if (!value.SequenceEqual(reference)) + { + throw new Exception( + $"{category} mismatch: {name} produced {Convert.ToHexString(value)} " + + $"but {referenceName} produced {Convert.ToHexString(reference)}"); + } + } + } + } +} diff --git a/Secp256k1.Net.Bench/Program.cs b/Secp256k1.Net.Bench/Program.cs index 386d691..c8e98da 100644 --- a/Secp256k1.Net.Bench/Program.cs +++ b/Secp256k1.Net.Bench/Program.cs @@ -1,277 +1,390 @@ -using System; -using System.Text; +using System; using System.Linq; using System.Numerics; -using System.Security.Cryptography; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; namespace Secp256k1Net.Bench { - // Use ShortRun job for faster CI execution (fewer iterations, less accurate but still useful) - // Set CI=true environment variable to enable, otherwise uses default (more accurate) settings - public class CiBenchmarkConfig : ManualConfig + [Config(typeof(CiBenchmarkConfig))] + [CsvMeasurementsExporter] + [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] + [CategoriesColumn] + public partial class Secp256k1Benchmarks { - public CiBenchmarkConfig() + private readonly BenchInputs inputs = new(); + private readonly byte[] auxRand = new byte[32]; // Zero aux randomness for deterministic Schnorr benchmark + private byte[] schnorrSig; + private byte[] xOnlyPubKey; + + [GlobalSetup] + public void Setup() { - if (Environment.GetEnvironmentVariable("CI") == "true") - { - AddJob(Job.ShortRun); - } - else - { - AddJob(Job.Default); - } + // Pre-compute a Schnorr signature for verification benchmarks + schnorrSig = Secp256k1.SignSchnorr(inputs.Msg.MsgHash, inputs.KeyPair.PrivateKey); + (xOnlyPubKey, _) = Secp256k1.CreateXOnlyPublicKey(inputs.KeyPair.PrivateKey); + + ValidateResults(); } - } - record class KeyPair(byte[] PrivateKey, byte[] PublicKeyCompressed, byte[] PublicKeyUncompressed); - record class Msg(string MsgString, byte[] MsgBytes, byte[] MsgHash); + // ===== ECDSA Sign ===== + // All benchmarks hash MsgBytes internally for fair comparison. + // StarkBank only supports string input, but since it hashes with SHA256 internally, + // its signatures are compatible (just need low-S normalization for verification). + [BenchmarkCategory("EcdsaSign"), Benchmark(Description = "Secp256k1Net", Baseline = true)] + public byte[] EcdsaSign_Secp256k1Net() + { + var msgHash = System.Security.Cryptography.SHA256.HashData(inputs.Msg.MsgBytes); + return Secp256k1.Sign(msgHash, inputs.KeyPair.PrivateKey); + } - class BenchInputs - { - public readonly KeyPair KeyPair; - public readonly Msg Msg; - public readonly byte[] EcdsaSig; + [BenchmarkCategory("EcdsaSign"), Benchmark(Description = "NBitcoin")] + public byte[] EcdsaSign_NBitcoin() + { + var msgHash = System.Security.Cryptography.SHA256.HashData(inputs.Msg.MsgBytes); + var ecPrivKey = NBitcoin.Secp256k1.ECPrivKey.Create(inputs.KeyPair.PrivateKey); + var sig = ecPrivKey.SignECDSARFC6979(msgHash); + var serializedSig = new byte[64]; + sig.WriteCompactToSpan(serializedSig); + return serializedSig; + } + + [BenchmarkCategory("EcdsaSign"), Benchmark(Description = "Nethereum")] + public byte[] EcdsaSign_Nethereum() + { + var msgHash = System.Security.Cryptography.SHA256.HashData(inputs.Msg.MsgBytes); + var ecPrivKey = new Nethereum.Signer.EthECKey(inputs.KeyPair.PrivateKey, isPrivate: true); + var sig = ecPrivKey.Sign(msgHash); + var serializedSig = new byte[64]; + sig.R.CopyTo(serializedSig, 32 - sig.R.Length); + sig.S.CopyTo(serializedSig, 64 - sig.S.Length); + return serializedSig; + } - public BenchInputs() + [BenchmarkCategory("EcdsaSign"), Benchmark(Description = "BouncyCastle")] + public byte[] EcdsaSign_BouncyCastle() { - KeyPair = new( - Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"), - Convert.FromHexString("03bf2e2462a3e64b941187b903156dbe9fb9b09b1e76ff5a55edf3d441dcd50822"), - Convert.FromHexString("04bf2e2462a3e64b941187b903156dbe9fb9b09b1e76ff5a55edf3d441dcd508227f20aebd43fb7de880b28ea03baae531c05f17d2f99940aa6a3fe1a4c788c7a1") - ); + var msgHash = System.Security.Cryptography.SHA256.HashData(inputs.Msg.MsgBytes); + var curve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); + var domain = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); + var d = new Org.BouncyCastle.Math.BigInteger(1, inputs.KeyPair.PrivateKey); + var keyParameters = new Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters(d, domain); + var signer = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner(); + signer.Init(true, keyParameters); + var signature = signer.GenerateSignature(msgHash); + var r = signature[0]; + var s = signature[1]; + // Normalize to low-S (required by libsecp256k1) + var halfN = domain.N.ShiftRight(1); + if (s.CompareTo(halfN) > 0) + s = domain.N.Subtract(s); + var rBytes = r.ToByteArrayUnsigned(); + var sBytes = s.ToByteArrayUnsigned(); + var serializedSig = new byte[64]; + rBytes.CopyTo(serializedSig, 32 - rBytes.Length); + sBytes.CopyTo(serializedSig, 64 - sBytes.Length); + return serializedSig; + } - var msg = "Message for signing"; - var msgBytes = Encoding.UTF8.GetBytes(msg); - var msgHash = SHA256.HashData(msgBytes); - Msg = new(msg, msgBytes, msgHash); + [BenchmarkCategory("EcdsaSign"), Benchmark(Description = "StarkBank")] + public byte[] EcdsaSign_StarkBank() + { + // StarkBank hashes internally using SHA256, so pass MsgString directly + var privateKey = EllipticCurve.PrivateKey.fromString(inputs.KeyPair.PrivateKey); + var sig = EllipticCurve.Ecdsa.sign(inputs.Msg.MsgString, privateKey); + var r = sig.r.ToByteArray(isUnsigned: true, isBigEndian: true); + var serializedSig = new byte[64]; + r.CopyTo(serializedSig, 32 - r.Length); - // 32-byte big endian R value, followed by a 32-byte big endian S value - EcdsaSig = Convert.FromHexString("8748f4a24fd0ecca9100ef947b73cbb6f11d67d151d2a900ab9fec1dce0051cc687136810ad4aba6812ad39cea0a41ba2cb04cb32d574a443f0d5c03e2dfa44f"); + // Normalize to low-S (StarkBank doesn't do this) + var s = sig.s > StarkBankHelper.HalfN ? StarkBankHelper.CurveN - sig.s : sig.s; + var sBytes = s.ToByteArray(isUnsigned: true, isBigEndian: true); + sBytes.CopyTo(serializedSig, 64 - sBytes.Length); + return serializedSig; } - } - [Config(typeof(CiBenchmarkConfig))] - [CsvMeasurementsExporter] - [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] - [CategoriesColumn] - public class EcdsaSignVerify - { - private readonly BenchInputs inputs = new BenchInputs(); + [BenchmarkCategory("EcdsaSign"), Benchmark(Description = "Chainers")] + public byte[] EcdsaSign_Chainers() + { + var msgHash = System.Security.Cryptography.SHA256.HashData(inputs.Msg.MsgBytes); + // SignCompressedCompact returns 65 bytes (1 byte header + 32 R + 32 S) + var fullSig = Cryptography.ECDSA.Secp256K1Manager.SignCompressedCompact(msgHash, inputs.KeyPair.PrivateKey); + var serializedSig = new byte[64]; + Array.Copy(fullSig, 1, serializedSig, 0, 64); + return serializedSig; + } - [BenchmarkCategory("Sign"), Benchmark(Description = "Secp256k1Net", Baseline = true)] - public byte[] Secp256k1NetSign() + // ===== ECDSA Verify ===== + [BenchmarkCategory("EcdsaVerify"), Benchmark(Description = "Secp256k1Net", Baseline = true)] + public void EcdsaVerify_Secp256k1Net() { - return Secp256k1NetUtil.Sign(inputs.KeyPair, inputs.Msg); + if (!Secp256k1.Verify(inputs.EcdsaSig, inputs.Msg.MsgHash, inputs.KeyPair.PublicKeyCompressed)) + throw new Exception(); } - [BenchmarkCategory("Sign"), Benchmark(Description = "NBitcoin")] - public byte[] NBitcoinSign() + [BenchmarkCategory("EcdsaVerify"), Benchmark(Description = "NBitcoin")] + public void EcdsaVerify_NBitcoin() { - return NBitcoinUtil.Sign(inputs.KeyPair, inputs.Msg); + if (!NBitcoin.Secp256k1.SecpECDSASignature.TryCreateFromCompact(inputs.EcdsaSig, out var parsedSig)) + throw new Exception(); + var ecPubKey = NBitcoin.Secp256k1.ECPubKey.Create(inputs.KeyPair.PublicKeyCompressed); + if (!ecPubKey.SigVerify(parsedSig, inputs.Msg.MsgHash)) + throw new Exception(); } - [BenchmarkCategory("Sign"), Benchmark(Description = "Nethereum")] - public byte[] NethereumSign() + [BenchmarkCategory("EcdsaVerify"), Benchmark(Description = "Nethereum")] + public void EcdsaVerify_Nethereum() { - return NethereumUtil.Sign(inputs.KeyPair, inputs.Msg); + var parsedSig = Nethereum.Signer.EthECDSASignatureFactory.FromComponents(inputs.EcdsaSig); + var pubKey = new Nethereum.Signer.EthECKey(inputs.KeyPair.PublicKeyCompressed, isPrivate: false); + if (!pubKey.Verify(inputs.Msg.MsgHash, parsedSig)) + throw new Exception(); } - [BenchmarkCategory("Sign"), Benchmark(Description = "BouncyCastle")] - public byte[] BouncyCastleSign() + [BenchmarkCategory("EcdsaVerify"), Benchmark(Description = "BouncyCastle")] + public void EcdsaVerify_BouncyCastle() { - return BouncyCastleUtil.Sign(inputs.KeyPair, inputs.Msg); + var curve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); + var domain = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); + var q = curve.Curve.DecodePoint(inputs.KeyPair.PublicKeyCompressed); + var keyParameters = new Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters(q, domain); + var verifier = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner(); + verifier.Init(false, keyParameters); + var rp = new Org.BouncyCastle.Math.BigInteger(1, inputs.EcdsaSig.Take(32).ToArray()); + var sp = new Org.BouncyCastle.Math.BigInteger(1, inputs.EcdsaSig.Skip(32).ToArray()); + if (!verifier.VerifySignature(inputs.Msg.MsgHash, rp, sp)) + throw new Exception(); } - [BenchmarkCategory("Sign"), Benchmark(Description = "StarkBank")] - public byte[] StarkBankSign() + [BenchmarkCategory("EcdsaVerify"), Benchmark(Description = "StarkBank")] + public void EcdsaVerify_StarkBank() { - return StarkBankUtil.Sign(inputs.KeyPair, inputs.Msg); + var r = new BigInteger(inputs.EcdsaSig.Take(32).ToArray(), isUnsigned: true, isBigEndian: true); + var s = new BigInteger(inputs.EcdsaSig.Skip(32).ToArray(), isUnsigned: true, isBigEndian: true); + var parsedSig = new EllipticCurve.Signature(r, s); + var pubKey = EllipticCurve.PublicKey.fromString(inputs.KeyPair.PublicKeyUncompressed.Skip(1).ToArray()); + if (!EllipticCurve.Ecdsa.verify(inputs.Msg.MsgString, parsedSig, pubKey)) + throw new Exception(); } - [BenchmarkCategory("Sign"), Benchmark(Description = "Chainers")] - public byte[] ChainersSign() + // ===== Public Key Creation ===== + [BenchmarkCategory("PubKeyCreate"), Benchmark(Description = "Secp256k1Net", Baseline = true)] + public byte[] PubKeyCreate_Secp256k1Net() { - return ChainersUtil.Sign(inputs.KeyPair, inputs.Msg); + return Secp256k1.CreatePublicKey(inputs.KeyPair.PrivateKey, compressed: true); } - [BenchmarkCategory("Verify"), Benchmark(Description = "Secp256k1Net", Baseline = true)] - public void Secp256k1NetVerify() + [BenchmarkCategory("PubKeyCreate"), Benchmark(Description = "NBitcoin")] + public byte[] PubKeyCreate_NBitcoin() { - Secp256k1NetUtil.Verify(inputs.KeyPair, inputs.Msg, inputs.EcdsaSig); + var ecPrivKey = NBitcoin.Secp256k1.ECPrivKey.Create(inputs.KeyPair.PrivateKey); + var pubKey = ecPrivKey.CreatePubKey(); + return pubKey.ToBytes(true); } - [BenchmarkCategory("Verify"), Benchmark(Description = "NBitcoin")] - public void NBitcoinVerify() + [BenchmarkCategory("PubKeyCreate"), Benchmark(Description = "Nethereum")] + public byte[] PubKeyCreate_Nethereum() { - NBitcoinUtil.Verify(inputs.KeyPair, inputs.Msg, inputs.EcdsaSig); + var ecKey = new Nethereum.Signer.EthECKey(inputs.KeyPair.PrivateKey, isPrivate: true); + return ecKey.GetPubKey(true); } - [BenchmarkCategory("Verify"), Benchmark(Description = "Nethereum")] - public void NethereumVerify() + [BenchmarkCategory("PubKeyCreate"), Benchmark(Description = "BouncyCastle")] + public byte[] PubKeyCreate_BouncyCastle() { - NethereumUtil.Verify(inputs.KeyPair, inputs.Msg, inputs.EcdsaSig); + var curve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); + var d = new Org.BouncyCastle.Math.BigInteger(1, inputs.KeyPair.PrivateKey); + var q = curve.G.Multiply(d); + return q.GetEncoded(true); } - [BenchmarkCategory("Verify"), Benchmark(Description = "BouncyCastle")] - public void BouncyCastleVerify() + [BenchmarkCategory("PubKeyCreate"), Benchmark(Description = "StarkBank")] + public byte[] PubKeyCreate_StarkBank() { - BouncyCastleUtil.Verify(inputs.KeyPair, inputs.Msg, inputs.EcdsaSig); + var privateKey = EllipticCurve.PrivateKey.fromString(inputs.KeyPair.PrivateKey); + var pubKey = privateKey.publicKey(); + // StarkBank doesn't have a toCompressed() method, so manually compress + var x = pubKey.point.x.ToByteArray(isUnsigned: true, isBigEndian: true); + var y = pubKey.point.y; + var result = new byte[33]; + result[0] = (byte)(y.IsEven ? 0x02 : 0x03); + x.CopyTo(result, 33 - x.Length); + return result; } - [BenchmarkCategory("Verify"), Benchmark(Description = "StarkBank")] - public void StarkBankVerify() + [BenchmarkCategory("PubKeyCreate"), Benchmark(Description = "Chainers")] + public byte[] PubKeyCreate_Chainers() { - StarkBankUtil.Verify(inputs.KeyPair, inputs.Msg, inputs.EcdsaSig); + return Cryptography.ECDSA.Secp256K1Manager.GetPublicKey(inputs.KeyPair.PrivateKey, true); } - } - interface EcdsaSigner - { - static abstract byte[] Sign(KeyPair keyPair, Msg msg); - } + // ===== ECDH ===== + // All benchmarks return SHA256(compressed_point) for fair comparison + [BenchmarkCategory("Ecdh"), Benchmark(Description = "Secp256k1Net", Baseline = true)] + public byte[] Ecdh_Secp256k1Net() + { + // ComputeSharedSecret returns SHA256(compressed_point) by default + return Secp256k1.ComputeSharedSecret(inputs.AlicePubKeyCompressed, inputs.KeyPair.PrivateKey); + } - interface EcdsaVerifier - { - static abstract void Verify(KeyPair keyPair, Msg msg, byte[] signature); - } + [BenchmarkCategory("Ecdh"), Benchmark(Description = "NBitcoin")] + public byte[] Ecdh_NBitcoin() + { + var bobPrivKey = NBitcoin.Secp256k1.ECPrivKey.Create(inputs.KeyPair.PrivateKey); + var alicePubKey = NBitcoin.Secp256k1.ECPubKey.Create(inputs.AlicePubKeyCompressed); + var sharedPubKey = alicePubKey.GetSharedPubkey(bobPrivKey); + // Get compressed point and hash it + var compressed = sharedPubKey.ToBytes(true); + return System.Security.Cryptography.SHA256.HashData(compressed); + } - class Secp256k1NetUtil : EcdsaSigner, EcdsaVerifier - { - public static byte[] Sign(KeyPair keyPair, Msg msg) + [BenchmarkCategory("Ecdh"), Benchmark(Description = "Nethereum")] + public byte[] Ecdh_Nethereum() { - using var secp256k1 = new Secp256k1(); - var sig = new byte[Secp256k1.SIGNATURE_LENGTH]; - if (!secp256k1.Sign(sig, msg.MsgHash, keyPair.PrivateKey)) - throw new Exception(); - var serializedSig = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; - if (!secp256k1.SignatureSerializeCompact(serializedSig, sig)) - throw new Exception(); - return serializedSig; + // Nethereum's CalculateCommonSecret returns only x-coordinate (32 bytes) + var ecKey = new Nethereum.Signer.EthECKey(inputs.KeyPair.PrivateKey, isPrivate: true); + var aliceKey = new Nethereum.Signer.EthECKey(inputs.AlicePubKeyCompressed, isPrivate: false); + var xCoord = ecKey.CalculateCommonSecret(aliceKey); + + // Reconstruct compressed point (0x02 prefix = even y, correct for our test inputs) + var compressed = new byte[33]; + compressed[0] = 0x02; + xCoord.CopyTo(compressed, 1); + + return System.Security.Cryptography.SHA256.HashData(compressed); } - public static void Verify(KeyPair keyPair, Msg msg, byte[] signature) + [BenchmarkCategory("Ecdh"), Benchmark(Description = "BouncyCastle")] + public byte[] Ecdh_BouncyCastle() { - using var secp256k1 = new Secp256k1(); - var parsedSig = new byte[Secp256k1.SIGNATURE_LENGTH]; - if (!secp256k1.SignatureParseCompact(parsedSig, signature)) - throw new Exception(); - var parsedPubKey = new byte[Secp256k1.PUBKEY_LENGTH]; - if (!secp256k1.PublicKeyParse(parsedPubKey, keyPair.PublicKeyCompressed)) - throw new Exception(); - if (!secp256k1.Verify(parsedSig, msg.MsgHash, parsedPubKey)) + var curve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); + var bobD = new Org.BouncyCastle.Math.BigInteger(1, inputs.KeyPair.PrivateKey); + var aliceQ = curve.Curve.DecodePoint(inputs.AlicePubKeyCompressed); + // Compute shared point directly: sharedPoint = aliceQ * bobD + var sharedPoint = aliceQ.Multiply(bobD).Normalize(); + var compressed = sharedPoint.GetEncoded(true); + return System.Security.Cryptography.SHA256.HashData(compressed); + } + + // ===== Recoverable Sign ===== + [BenchmarkCategory("EcdsaSignRecoverable"), Benchmark(Description = "Secp256k1Net", Baseline = true)] + public byte[] EcdsaSignRecoverable_Secp256k1Net() + { + var (signature, recoveryId) = Secp256k1.SignRecoverable(inputs.Msg.MsgHash, inputs.KeyPair.PrivateKey); + var result = new byte[65]; + signature.CopyTo(result, 0); + result[64] = recoveryId; + return result; + } + + [BenchmarkCategory("EcdsaSignRecoverable"), Benchmark(Description = "NBitcoin")] + public byte[] EcdsaSignRecoverable_NBitcoin() + { + var ecPrivKey = NBitcoin.Secp256k1.ECPrivKey.Create(inputs.KeyPair.PrivateKey); + if (!ecPrivKey.TrySignRecoverable(inputs.Msg.MsgHash, out var sig)) throw new Exception(); + var output = new byte[65]; + sig.WriteToSpanCompact(output.AsSpan(0, 64), out var recId); + output[64] = (byte)recId; + return output; } - } - class NBitcoinUtil : EcdsaSigner, EcdsaVerifier - { - public static byte[] Sign(KeyPair keyPair, Msg msg) + [BenchmarkCategory("EcdsaSignRecoverable"), Benchmark(Description = "Nethereum")] + public byte[] EcdsaSignRecoverable_Nethereum() { - var ecPrivKey = NBitcoin.Secp256k1.ECPrivKey.Create(keyPair.PrivateKey); - var sig = ecPrivKey.SignECDSARFC6979(msg.MsgHash); - var serializedSig = new byte[64]; - sig.WriteCompactToSpan(serializedSig); - return serializedSig; + var ecKey = new Nethereum.Signer.EthECKey(inputs.KeyPair.PrivateKey, isPrivate: true); + var sig = ecKey.SignAndCalculateV(inputs.Msg.MsgHash); + var output = new byte[65]; + sig.R.CopyTo(output, 32 - sig.R.Length); + sig.S.CopyTo(output, 64 - sig.S.Length); + output[64] = (byte)(sig.V.Length > 0 ? sig.V[0] : 0); + return output; } - public static void Verify(KeyPair keyPair, Msg msg, byte[] signature) + [BenchmarkCategory("EcdsaSignRecoverable"), Benchmark(Description = "BouncyCastle")] + public byte[] EcdsaSignRecoverable_BouncyCastle() { - if (!NBitcoin.Secp256k1.SecpECDSASignature.TryCreateFromCompact(signature, out var parsedSig)) - throw new Exception("Failed to parse compact signature"); - var ecPubKey = NBitcoin.Secp256k1.ECPubKey.Create(keyPair.PublicKeyCompressed); - if (!ecPubKey.SigVerify(parsedSig, msg.MsgHash)) - throw new Exception("Failed to verify signature"); + var (r, s, recId, _, _) = BouncyCastleRecoveryHelper.SignRecoverable(inputs.KeyPair.PrivateKey, inputs.Msg.MsgHash); + var output = new byte[65]; + var rBytes = r.ToByteArrayUnsigned(); + var sBytes = s.ToByteArrayUnsigned(); + rBytes.CopyTo(output, 32 - rBytes.Length); + sBytes.CopyTo(output, 64 - sBytes.Length); + output[64] = (byte)recId; + return output; } - } - class NethereumUtil : EcdsaSigner, EcdsaVerifier - { - public static byte[] Sign(KeyPair keyPair, Msg msg) + // ===== Public Key Recovery ===== + [BenchmarkCategory("EcdsaRecover"), Benchmark(Description = "Secp256k1Net", Baseline = true)] + public byte[] EcdsaRecover_Secp256k1Net() { - var ecPrivKey = new Nethereum.Signer.EthECKey(keyPair.PrivateKey, isPrivate: true); - var sig = ecPrivKey.Sign(msg.MsgHash); - var serializedSig = sig.To64ByteArray(); - return serializedSig; + // First sign to get the recoverable signature + var (signature, recoveryId) = Secp256k1.SignRecoverable(inputs.Msg.MsgHash, inputs.KeyPair.PrivateKey); + // Then recover the public key + return Secp256k1.RecoverPublicKey(signature, recoveryId, inputs.Msg.MsgHash, compressed: true); + } + + [BenchmarkCategory("EcdsaRecover"), Benchmark(Description = "NBitcoin")] + public byte[] EcdsaRecover_NBitcoin() + { + var ecPrivKey = NBitcoin.Secp256k1.ECPrivKey.Create(inputs.KeyPair.PrivateKey); + if (!ecPrivKey.TrySignRecoverable(inputs.Msg.MsgHash, out var recSig)) + throw new Exception(); + if (!NBitcoin.Secp256k1.ECPubKey.TryRecover( + NBitcoin.Secp256k1.Context.Instance, recSig, inputs.Msg.MsgHash, out var pubKey)) + throw new Exception(); + return pubKey.ToBytes(true); } - public static void Verify(KeyPair keyPair, Msg msg, byte[] signature) + [BenchmarkCategory("EcdsaRecover"), Benchmark(Description = "Nethereum")] + public byte[] EcdsaRecover_Nethereum() { - var parsedSig = Nethereum.Signer.EthECDSASignatureFactory.FromComponents(signature); - var pubKey = new Nethereum.Signer.EthECKey(keyPair.PublicKeyCompressed, isPrivate: false); - if (!pubKey.Verify(msg.MsgHash, parsedSig)) - throw new Exception("Failed to verify signature"); + var ecKey = new Nethereum.Signer.EthECKey(inputs.KeyPair.PrivateKey, isPrivate: true); + var sig = ecKey.SignAndCalculateV(inputs.Msg.MsgHash); + var recoveredKey = Nethereum.Signer.EthECKey.RecoverFromSignature(sig, inputs.Msg.MsgHash); + return recoveredKey.GetPubKey(true); } - } - class BouncyCastleUtil : EcdsaSigner, EcdsaVerifier - { - public static byte[] Sign(KeyPair keyPair, Msg msg) + [BenchmarkCategory("EcdsaRecover"), Benchmark(Description = "BouncyCastle")] + public byte[] EcdsaRecover_BouncyCastle() { - var curve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); - var domain = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); - var d = new Org.BouncyCastle.Math.BigInteger(1, keyPair.PrivateKey); - var keyParameters = new Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters(d, domain); - var signer = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner(); - signer.Init(true, keyParameters); - var signature = signer.GenerateSignature(msg.MsgHash); - var r = signature[0].ToByteArrayUnsigned(); - var s = signature[1].ToByteArrayUnsigned(); - var serializedSig = new byte[64]; - r.CopyTo(serializedSig, 32 - r.Length); - s.CopyTo(serializedSig, 64 - s.Length); - return serializedSig; + var (r, s, recId, curve, domain) = BouncyCastleRecoveryHelper.SignRecoverable(inputs.KeyPair.PrivateKey, inputs.Msg.MsgHash); + var e = new Org.BouncyCastle.Math.BigInteger(1, inputs.Msg.MsgHash); + var recovered = BouncyCastleRecoveryHelper.RecoverPublicKey(curve, domain, e, r, s, recId); + return recovered.GetEncoded(true); } - public static void Verify(KeyPair keyPair, Msg msg, byte[] signature) + // ===== Schnorr Sign ===== + [BenchmarkCategory("SchnorrSign"), Benchmark(Description = "Secp256k1Net", Baseline = true)] + public byte[] SchnorrSign_Secp256k1Net() { - var curve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); - var domain = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); - var q = curve.Curve.DecodePoint(keyPair.PublicKeyCompressed); - var keyParameters = new Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters(q, domain); - var verifier = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner(); - verifier.Init(false, keyParameters); - var rp = new Org.BouncyCastle.Math.BigInteger(1, signature.Take(32).ToArray()); - var sp = new Org.BouncyCastle.Math.BigInteger(1, signature.Skip(32).ToArray()); - if (!verifier.VerifySignature(msg.MsgHash, rp, sp)) - throw new Exception("Failed to verify signature"); + return Secp256k1.SignSchnorr(inputs.Msg.MsgHash, inputs.KeyPair.PrivateKey, default, verify: false); } - } - class StarkBankUtil : EcdsaSigner, EcdsaVerifier - { - public static byte[] Sign(KeyPair keyPair, Msg msg) + [BenchmarkCategory("SchnorrSign"), Benchmark(Description = "NBitcoin")] + public byte[] SchnorrSign_NBitcoin() { - var privateKey = EllipticCurve.PrivateKey.fromString(keyPair.PrivateKey); - var sig = EllipticCurve.Ecdsa.sign(msg.MsgString, privateKey); - var r = sig.r.ToByteArray(isUnsigned: true, isBigEndian: true); - var s = sig.s.ToByteArray(isUnsigned: true, isBigEndian: true); - var serializedSig = new byte[64]; - r.CopyTo(serializedSig, 32 - r.Length); - s.CopyTo(serializedSig, 64 - s.Length); - return serializedSig; + var ecPrivKey = NBitcoin.Secp256k1.ECPrivKey.Create(inputs.KeyPair.PrivateKey); + var sig = ecPrivKey.SignBIP340(inputs.Msg.MsgHash); + return sig.ToBytes(); } - public static void Verify(KeyPair keyPair, Msg msg, byte[] signature) + // ===== Schnorr Verify ===== + [BenchmarkCategory("SchnorrVerify"), Benchmark(Description = "Secp256k1Net", Baseline = true)] + public bool SchnorrVerify_Secp256k1Net() { - var r = new BigInteger(signature.Take(32).ToArray(), isUnsigned: true, isBigEndian: true); - var s = new BigInteger(signature.Skip(32).ToArray(), isUnsigned: true, isBigEndian: true); - var parsedSig = new EllipticCurve.Signature(r, s); - var pubKey = EllipticCurve.PublicKey.fromString(keyPair.PublicKeyUncompressed.Skip(1).ToArray()); - if (!EllipticCurve.Ecdsa.verify(msg.MsgString, parsedSig, pubKey)) - throw new Exception("Failed to verify signature"); + return Secp256k1.VerifySchnorr(schnorrSig, inputs.Msg.MsgHash, xOnlyPubKey); } - } - class ChainersUtil : EcdsaSigner - { - public static byte[] Sign(KeyPair keyPair, Msg msg) + [BenchmarkCategory("SchnorrVerify"), Benchmark(Description = "NBitcoin")] + public bool SchnorrVerify_NBitcoin() { - var sig = Cryptography.ECDSA.Secp256K1Manager.SignCompressedCompact(msg.MsgHash, keyPair.PrivateKey); - return sig; + var ecPrivKey = NBitcoin.Secp256k1.ECPrivKey.Create(inputs.KeyPair.PrivateKey); + var xOnlyPub = ecPrivKey.CreateXOnlyPubKey(); + if (!NBitcoin.Secp256k1.SecpSchnorrSignature.TryCreate(schnorrSig, out var sig)) + throw new Exception(); + return xOnlyPub.SigVerifyBIP340(sig, inputs.Msg.MsgHash); } } @@ -279,9 +392,14 @@ class Program { static void Main(string[] args) { - BenchmarkRunner.Run(); + // Support --validate flag as shortcut for VALIDATE=true + if (args.Length > 0 && args[0] == "--validate") + { + Environment.SetEnvironmentVariable("VALIDATE", "true"); + } + + BenchmarkRunner.Run(); Console.WriteLine("Benchmarks done"); } } - -} \ No newline at end of file +} diff --git a/Secp256k1.Net.Bench/Secp256k1.Net.Bench.csproj b/Secp256k1.Net.Bench/Secp256k1.Net.Bench.csproj index d666982..a4509a4 100644 --- a/Secp256k1.Net.Bench/Secp256k1.Net.Bench.csproj +++ b/Secp256k1.Net.Bench/Secp256k1.Net.Bench.csproj @@ -12,15 +12,15 @@ - + - - - + + + - + diff --git a/Secp256k1.Net.Examples/AdvancedUsageExamples.cs b/Secp256k1.Net.Examples/AdvancedUsageExamples.cs new file mode 100644 index 0000000..b47f7d8 --- /dev/null +++ b/Secp256k1.Net.Examples/AdvancedUsageExamples.cs @@ -0,0 +1,721 @@ +using System.Security.Cryptography; +using System.Text; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Advanced examples demonstrating low-level Secp256k1 instance methods. +/// These methods provide more control over memory allocation and internal data formats. +/// +public static class AdvancedUsageExamples +{ + public static void Run() + { + Console.WriteLine("=== Advanced Usage Examples (Instance Methods) ===\n"); + + InstanceBasics(); + WorkingWithInternalFormats(); + CustomEcdhHashFunction(); + CustomNonceFunction(); + Rfc6979NonceFunction(); + PublicKeyComparison(); + PublicKeySorting(); + KeypairOperations(); + SchnorrWithKeypair(); + XonlyPubkeyTweakingExample(); + ElligatorSwiftExample(); + ErrorCallbackExample(); + } + + /// + /// Basic instance creation and disposal. + /// + static void InstanceBasics() + { + Console.WriteLine("--- Instance Basics ---"); + + // Create a Secp256k1 instance (manages native context) + using var secp256k1 = new Secp256k1(); + + Console.WriteLine($"Native library path: {Secp256k1.LibPath}"); + + // Run self-tests (optional, useful for verifying library integrity) + secp256k1.Selftest(); + Console.WriteLine("Self-test passed"); + + Console.WriteLine(); + } + + /// + /// Demonstrates working with internal (unserialized) public key format. + /// Internal format is 64 bytes, different from compressed (33) or uncompressed (65) serialized forms. + /// + static void WorkingWithInternalFormats() + { + Console.WriteLine("--- Working with Internal Formats ---"); + + using var secp256k1 = new Secp256k1(); + + // Generate a secret key + Span secretKey = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKey); + while (!secp256k1.EcSeckeyVerify(secretKey)) + { + RandomNumberGenerator.Fill(secretKey); + } + + // Create internal public key (64 bytes, not directly serializable) + Span internalPubkey = stackalloc byte[64]; + bool created = secp256k1.EcPubkeyCreate(internalPubkey, secretKey); + Console.WriteLine($"Public key created: {created}"); + Console.WriteLine($"Internal pubkey size: {internalPubkey.Length} bytes"); + + // Serialize to compressed format (33 bytes) + Span compressedPubkey = stackalloc byte[33]; + nuint compressedLen = 33; + secp256k1.EcPubkeySerialize(compressedPubkey, ref compressedLen, internalPubkey, Secp256k1EcFlags.Compressed); + Console.WriteLine($"Compressed pubkey ({compressedLen} bytes): {Convert.ToHexString(compressedPubkey)}"); + + // Serialize to uncompressed format (65 bytes) + Span uncompressedPubkey = stackalloc byte[65]; + nuint uncompressedLen = 65; + secp256k1.EcPubkeySerialize(uncompressedPubkey, ref uncompressedLen, internalPubkey, Secp256k1EcFlags.Uncompressed); + Console.WriteLine($"Uncompressed pubkey ({uncompressedLen} bytes): {Convert.ToHexString(uncompressedPubkey)}"); + + // Parse a serialized public key back to internal format + Span parsedInternal = stackalloc byte[64]; + bool parsed = secp256k1.EcPubkeyParse(parsedInternal, compressedPubkey); + Console.WriteLine($"Parsed back to internal: {parsed}"); + + Console.WriteLine(); + } + + /// + /// Demonstrates using a custom hash function with ECDH. + /// + static void CustomEcdhHashFunction() + { + Console.WriteLine("--- Custom ECDH Hash Function ---"); + + using var secp256k1 = new Secp256k1(); + + // Create two keypairs + Span secretKeyA = stackalloc byte[32]; + Span secretKeyB = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKeyA); + RandomNumberGenerator.Fill(secretKeyB); + + Span internalPubkeyA = stackalloc byte[64]; + Span internalPubkeyB = stackalloc byte[64]; + secp256k1.EcPubkeyCreate(internalPubkeyA, secretKeyA); + secp256k1.EcPubkeyCreate(internalPubkeyB, secretKeyB); + + // Standard ECDH (SHA256 hash of shared point) + Span sharedSecretStandard = stackalloc byte[32]; + secp256k1.Ecdh(sharedSecretStandard, internalPubkeyB, secretKeyA); + Console.WriteLine($"Standard ECDH: {Convert.ToHexString(sharedSecretStandard)}"); + + // Custom ECDH hash function that returns raw X coordinate + EcdhHashFunction rawXCoordinate = (Span output, ReadOnlySpan x32, ReadOnlySpan y32, IntPtr data) => + { + // Simply copy the X coordinate as the shared secret + x32.CopyTo(output); + return 1; + }; + + Span sharedSecretRawX = stackalloc byte[32]; + secp256k1.Ecdh(sharedSecretRawX, internalPubkeyB, secretKeyA, rawXCoordinate, IntPtr.Zero); + Console.WriteLine($"Raw X coord ECDH: {Convert.ToHexString(sharedSecretRawX)}"); + + // Custom ECDH with concatenated X||Y hashed + EcdhHashFunction hashXY = (Span output, ReadOnlySpan x32, ReadOnlySpan y32, IntPtr data) => + { + Span combined = stackalloc byte[64]; + x32.CopyTo(combined); + y32.CopyTo(combined[32..]); + SHA256.HashData(combined, output); + return 1; + }; + + Span sharedSecretXY = stackalloc byte[32]; + secp256k1.Ecdh(sharedSecretXY, internalPubkeyB, secretKeyA, hashXY, IntPtr.Zero); + Console.WriteLine($"Hash(X||Y) ECDH: {Convert.ToHexString(sharedSecretXY)}"); + + // Using the built-in SHA256 hash function as a callback + // The library provides EcdhHashFunctionSha256 which can be wrapped in a delegate + EcdhHashFunction builtInSha256 = (Span output, ReadOnlySpan x32, ReadOnlySpan y32, IntPtr data) => + { + // Delegate to the built-in implementation + return secp256k1.EcdhHashFunctionSha256(output, x32, y32, Span.Empty) ? 1 : 0; + }; + + Span sharedSecretBuiltIn = stackalloc byte[32]; + secp256k1.Ecdh(sharedSecretBuiltIn, internalPubkeyB, secretKeyA, builtInSha256, IntPtr.Zero); + Console.WriteLine($"Built-in SHA256 ECDH: {Convert.ToHexString(sharedSecretBuiltIn)}"); + Console.WriteLine($"Matches standard: {sharedSecretStandard.SequenceEqual(sharedSecretBuiltIn)}"); + + Console.WriteLine(); + } + + /// + /// Demonstrates using a custom nonce function for signing. + /// + static void CustomNonceFunction() + { + Console.WriteLine("--- Custom Nonce Function ---"); + + using var secp256k1 = new Secp256k1(); + + // Create a keypair + Span secretKey = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKey); + while (!secp256k1.EcSeckeyVerify(secretKey)) + { + RandomNumberGenerator.Fill(secretKey); + } + + Span internalPubkey = stackalloc byte[64]; + secp256k1.EcPubkeyCreate(internalPubkey, secretKey); + + byte[] messageHash = SHA256.HashData("Custom nonce example"u8); + + // Standard signing (uses default RFC6979 nonce) + Span internalSig = stackalloc byte[64]; + secp256k1.EcdsaSign(internalSig, messageHash, secretKey); + + Span compactSig = stackalloc byte[64]; + secp256k1.EcdsaSignatureSerializeCompact(compactSig, internalSig); + Console.WriteLine($"Standard signature: {Convert.ToHexString(compactSig)}"); + + // Custom deterministic nonce function + // WARNING: This is for demonstration only. In production, use the default RFC6979 nonce. + NonceFunction customNonce = (Span nonce32, ReadOnlySpan msg32, ReadOnlySpan key32, + ReadOnlySpan algo16, IntPtr data, uint attempt) => + { + // Simple deterministic nonce: SHA256(key || msg || attempt) + // NOTE: This is NOT a secure nonce function - use only for demonstration + Span combined = stackalloc byte[32 + 32 + 4]; + key32.CopyTo(combined); + msg32.CopyTo(combined[32..]); + BitConverter.GetBytes(attempt).CopyTo(combined[64..]); + SHA256.HashData(combined, nonce32); + return 1; + }; + + Span customInternalSig = stackalloc byte[64]; + secp256k1.EcdsaSign(customInternalSig, messageHash, secretKey, customNonce, IntPtr.Zero); + + Span customCompactSig = stackalloc byte[64]; + secp256k1.EcdsaSignatureSerializeCompact(customCompactSig, customInternalSig); + Console.WriteLine($"Custom nonce signature: {Convert.ToHexString(customCompactSig)}"); + + // Verify both signatures work + bool standardValid = secp256k1.EcdsaVerify(internalSig, messageHash, internalPubkey); + bool customValid = secp256k1.EcdsaVerify(customInternalSig, messageHash, internalPubkey); + Console.WriteLine($"Standard sig valid: {standardValid}, Custom sig valid: {customValid}"); + + Console.WriteLine(); + } + + /// + /// Demonstrates using the RFC6979 nonce function directly. + /// RFC6979 provides deterministic nonce generation for ECDSA signatures, + /// ensuring the same message and key always produce the same signature. + /// + static void Rfc6979NonceFunction() + { + Console.WriteLine("--- RFC6979 Nonce Function ---"); + + using var secp256k1 = new Secp256k1(); + + // Create a keypair + Span secretKey = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKey); + while (!secp256k1.EcSeckeyVerify(secretKey)) + { + RandomNumberGenerator.Fill(secretKey); + } + + Span internalPubkey = stackalloc byte[64]; + secp256k1.EcPubkeyCreate(internalPubkey, secretKey); + + byte[] messageHash = SHA256.HashData("RFC6979 nonce example"u8); + + // Generate a nonce using RFC6979 directly + // This is the same algorithm used internally by EcdsaSign when no custom nonce function is provided + Span nonce = stackalloc byte[32]; + bool nonceGenerated = secp256k1.NonceFunctionRfc6979( + nonce, + messageHash, + secretKey, + ReadOnlySpan.Empty, // algo16: optional algorithm identifier (usually empty) + Span.Empty, // data: optional extra entropy (usually empty) + 0 // attempt: retry counter (usually 0) + ); + Console.WriteLine($"Nonce generated: {nonceGenerated}"); + Console.WriteLine($"RFC6979 nonce: {Convert.ToHexString(nonce)}"); + + // Demonstrate determinism: same inputs always produce the same nonce + Span nonce2 = stackalloc byte[32]; + secp256k1.NonceFunctionRfc6979(nonce2, messageHash, secretKey, ReadOnlySpan.Empty, Span.Empty, 0); + Console.WriteLine($"Same nonce on retry: {nonce.SequenceEqual(nonce2)}"); + + // Using extra entropy (ndata) for additional randomization + // When provided, RFC6979 mixes this into the nonce generation + Span extraEntropy = stackalloc byte[32]; + RandomNumberGenerator.Fill(extraEntropy); + + Span nonceWithEntropy = stackalloc byte[32]; + secp256k1.NonceFunctionRfc6979( + nonceWithEntropy, + messageHash, + secretKey, + ReadOnlySpan.Empty, + extraEntropy, // 32 bytes of extra entropy + 0 + ); + Console.WriteLine($"Nonce with extra entropy: {Convert.ToHexString(nonceWithEntropy)}"); + Console.WriteLine($"Different from base nonce: {!nonce.SequenceEqual(nonceWithEntropy)}"); + + // The attempt parameter is used when the generated nonce would produce an invalid signature + // (extremely rare). Each attempt produces a different nonce. + Span nonceAttempt1 = stackalloc byte[32]; + secp256k1.NonceFunctionRfc6979(nonceAttempt1, messageHash, secretKey, ReadOnlySpan.Empty, Span.Empty, 1); + Console.WriteLine($"Nonce with attempt=1: {Convert.ToHexString(nonceAttempt1)}"); + Console.WriteLine($"Different from attempt=0: {!nonce.SequenceEqual(nonceAttempt1)}"); + + // Sign using the default nonce function (which uses RFC6979 internally) + // This produces the same signature every time for the same message/key + Span sig1 = stackalloc byte[64]; + Span sig2 = stackalloc byte[64]; + secp256k1.EcdsaSign(sig1, messageHash, secretKey); + secp256k1.EcdsaSign(sig2, messageHash, secretKey); + + Span compact1 = stackalloc byte[64]; + Span compact2 = stackalloc byte[64]; + secp256k1.EcdsaSignatureSerializeCompact(compact1, sig1); + secp256k1.EcdsaSignatureSerializeCompact(compact2, sig2); + + Console.WriteLine($"\nDeterministic signatures (RFC6979):"); + Console.WriteLine($"Signature 1: {Convert.ToHexString(compact1)}"); + Console.WriteLine($"Signature 2: {Convert.ToHexString(compact2)}"); + Console.WriteLine($"Signatures identical: {compact1.SequenceEqual(compact2)}"); + + Console.WriteLine(); + } + + /// + /// Demonstrates public key comparison operations. + /// + static void PublicKeyComparison() + { + Console.WriteLine("--- Public Key Comparison ---"); + + using var secp256k1 = new Secp256k1(); + + // Create three public keys + Span secretKey1 = stackalloc byte[32]; + Span secretKey2 = stackalloc byte[32]; + Span secretKey3 = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKey1); + RandomNumberGenerator.Fill(secretKey2); + RandomNumberGenerator.Fill(secretKey3); + + Span pubkey1 = stackalloc byte[64]; + Span pubkey2 = stackalloc byte[64]; + Span pubkey3 = stackalloc byte[64]; + secp256k1.EcPubkeyCreate(pubkey1, secretKey1); + secp256k1.EcPubkeyCreate(pubkey2, secretKey2); + secp256k1.EcPubkeyCreate(pubkey3, secretKey3); + + // Compare public keys (lexicographic order of compressed serialization) + int cmp12 = secp256k1.EcPubkeyCmp(pubkey1, pubkey2); + int cmp21 = secp256k1.EcPubkeyCmp(pubkey2, pubkey1); + int cmp11 = secp256k1.EcPubkeyCmp(pubkey1, pubkey1); + + Console.WriteLine($"Compare(pk1, pk2): {cmp12} (negative = pk1 < pk2)"); + Console.WriteLine($"Compare(pk2, pk1): {cmp21} (positive = pk2 > pk1)"); + Console.WriteLine($"Compare(pk1, pk1): {cmp11} (zero = equal)"); + + Console.WriteLine(); + } + + /// + /// Demonstrates sorting multiple public keys. + /// + static void PublicKeySorting() + { + Console.WriteLine("--- Public Key Sorting ---"); + + using var secp256k1 = new Secp256k1(); + + // Create an array of public keys (internal format) + byte[][] publicKeys = new byte[5][]; + for (int i = 0; i < 5; i++) + { + publicKeys[i] = new byte[64]; + byte[] secretKey = new byte[32]; + RandomNumberGenerator.Fill(secretKey); + secp256k1.EcPubkeyCreate(publicKeys[i], secretKey); + } + + // Display before sorting (showing compressed form for readability) + Console.WriteLine("Before sorting:"); + Span compressed = stackalloc byte[33]; + for (int i = 0; i < publicKeys.Length; i++) + { + nuint len = 33; + secp256k1.EcPubkeySerialize(compressed, ref len, publicKeys[i], Secp256k1EcFlags.Compressed); + Console.WriteLine($" [{i}]: {Convert.ToHexString(compressed)[..20]}..."); + } + + // Sort the public keys in-place (lexicographic order) + bool sorted = secp256k1.EcPubkeySort(publicKeys); + Console.WriteLine($"\nSort successful: {sorted}"); + + // Display after sorting + Console.WriteLine("\nAfter sorting:"); + for (int i = 0; i < publicKeys.Length; i++) + { + nuint len = 33; + secp256k1.EcPubkeySerialize(compressed, ref len, publicKeys[i], Secp256k1EcFlags.Compressed); + Console.WriteLine($" [{i}]: {Convert.ToHexString(compressed)[..20]}..."); + } + + Console.WriteLine(); + } + + /// + /// Demonstrates the keypair object for efficient Schnorr operations. + /// + static void KeypairOperations() + { + Console.WriteLine("--- Keypair Operations ---"); + + using var secp256k1 = new Secp256k1(); + + // Create a secret key + Span secretKey = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKey); + while (!secp256k1.EcSeckeyVerify(secretKey)) + { + RandomNumberGenerator.Fill(secretKey); + } + + // Create a keypair (96 bytes, contains both secret and public key data) + Span keypair = stackalloc byte[96]; + bool created = secp256k1.KeypairCreate(keypair, secretKey); + Console.WriteLine($"Keypair created: {created}"); + + // Extract secret key from keypair + Span extractedSecret = stackalloc byte[32]; + secp256k1.KeypairSec(extractedSecret, keypair); + Console.WriteLine($"Extracted secret matches: {extractedSecret.SequenceEqual(secretKey)}"); + + // Extract public key from keypair (internal format) + Span extractedPubkey = stackalloc byte[64]; + secp256k1.KeypairPub(extractedPubkey, keypair); + + // Extract x-only public key with parity + Span xonlyPubkey = stackalloc byte[64]; + secp256k1.KeypairXonlyPub(xonlyPubkey, out int parity, keypair); + + // Serialize x-only public key (32 bytes) + Span xonlySerialized = stackalloc byte[32]; + secp256k1.XonlyPubkeySerialize(xonlySerialized, xonlyPubkey); + Console.WriteLine($"X-only pubkey: {Convert.ToHexString(xonlySerialized)}"); + Console.WriteLine($"Parity: {parity} (0 = even Y, 1 = odd Y)"); + + // Tweak the keypair + byte[] tweak = SHA256.HashData("keypair tweak"u8); + Span tweakedKeypair = stackalloc byte[96]; + keypair.CopyTo(tweakedKeypair); + bool tweaked = secp256k1.KeypairXonlyTweakAdd(tweakedKeypair, tweak); + Console.WriteLine($"Keypair tweaked: {tweaked}"); + + Console.WriteLine(); + } + + /// + /// Demonstrates Schnorr signing using the low-level keypair API. + /// + static void SchnorrWithKeypair() + { + Console.WriteLine("--- Schnorr with Keypair ---"); + + using var secp256k1 = new Secp256k1(); + + // Create keypair + Span secretKey = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKey); + while (!secp256k1.EcSeckeyVerify(secretKey)) + { + RandomNumberGenerator.Fill(secretKey); + } + + Span keypair = stackalloc byte[96]; + secp256k1.KeypairCreate(keypair, secretKey); + + // Get x-only public key for verification + Span xonlyPubkey = stackalloc byte[64]; + secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair); + + // Message hash + byte[] messageHash = SHA256.HashData("Schnorr keypair example"u8); + + // Auxiliary randomness (32 bytes) + Span auxRand = stackalloc byte[32]; + RandomNumberGenerator.Fill(auxRand); + + // Sign with Schnorr + Span signature = stackalloc byte[64]; + bool signed = secp256k1.SchnorrsigSign32(signature, messageHash, keypair, auxRand); + Console.WriteLine($"Schnorr signed: {signed}"); + Console.WriteLine($"Signature: {Convert.ToHexString(signature)}"); + + // Verify + bool verified = secp256k1.SchnorrsigVerify(signature, messageHash, xonlyPubkey); + Console.WriteLine($"Schnorr verified: {verified}"); + + // Sign with variable-length message (using SchnorrsigSignCustom) + byte[] variableLengthMsg = Encoding.UTF8.GetBytes("This is a variable length message for Schnorr signing"); + Span signature2 = stackalloc byte[64]; + bool signed2 = secp256k1.SchnorrsigSignCustom(signature2, variableLengthMsg, keypair, Span.Empty); + Console.WriteLine($"\nVariable-length message signed: {signed2}"); + + // Verify variable-length signature + bool verified2 = secp256k1.SchnorrsigVerify(signature2, variableLengthMsg, xonlyPubkey); + Console.WriteLine($"Variable-length verified: {verified2}"); + + Console.WriteLine(); + } + + /// + /// Demonstrates X-only public key tweaking for Taproot (BIP-341). + /// + static void XonlyPubkeyTweakingExample() + { + Console.WriteLine("--- X-only Public Key Tweaking (Taproot) ---"); + + using var secp256k1 = new Secp256k1(); + + // Create a keypair + Span secretKey = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKey); + while (!secp256k1.EcSeckeyVerify(secretKey)) + { + RandomNumberGenerator.Fill(secretKey); + } + + // Create internal public key (used as Taproot internal key) + Span internalPubkey = stackalloc byte[64]; + secp256k1.EcPubkeyCreate(internalPubkey, secretKey); + + // Convert to x-only format + Span xonlyInternal = stackalloc byte[64]; + secp256k1.XonlyPubkeyFromPubkey(xonlyInternal, out int internalParity, internalPubkey); + + // Serialize the x-only key + Span xonlySerialized = stackalloc byte[32]; + secp256k1.XonlyPubkeySerialize(xonlySerialized, xonlyInternal); + Console.WriteLine($"Internal x-only pubkey: {Convert.ToHexString(xonlySerialized)}"); + Console.WriteLine($"Internal key parity: {internalParity}"); + + // Create a tweak (in Taproot, this would be derived from the script tree) + byte[] tweak = SHA256.HashData("TapTweak example"u8); + Console.WriteLine($"Tweak: {Convert.ToHexString(tweak)}"); + + // Tweak the x-only public key (result is a regular pubkey, not x-only) + Span tweakedPubkey = stackalloc byte[64]; + bool tweakSuccess = secp256k1.XonlyPubkeyTweakAdd(tweakedPubkey, xonlyInternal, tweak); + Console.WriteLine($"Tweak successful: {tweakSuccess}"); + + // Convert tweaked key to x-only and get its parity + Span tweakedXonly = stackalloc byte[64]; + secp256k1.XonlyPubkeyFromPubkey(tweakedXonly, out int tweakedParity, tweakedPubkey); + + Span tweakedSerialized = stackalloc byte[32]; + secp256k1.XonlyPubkeySerialize(tweakedSerialized, tweakedXonly); + Console.WriteLine($"Tweaked x-only pubkey: {Convert.ToHexString(tweakedSerialized)}"); + Console.WriteLine($"Tweaked key parity: {tweakedParity}"); + + // Verify the tweak was applied correctly (important for Taproot validation) + bool tweakValid = secp256k1.XonlyPubkeyTweakAddCheck( + tweakedSerialized, tweakedParity, xonlyInternal, tweak); + Console.WriteLine($"Tweak verification: {tweakValid}"); + + Console.WriteLine(); + Console.WriteLine("Taproot use case:"); + Console.WriteLine(" - Internal key: the key that can spend without revealing scripts"); + Console.WriteLine(" - Tweak: derived from Merkle root of script tree (or empty for key-path only)"); + Console.WriteLine(" - Tweaked key: the actual output key committed to in the transaction"); + Console.WriteLine(" - TweakAddCheck: verifies a claimed internal key matches the output key"); + + Console.WriteLine(); + } + + /// + /// Demonstrates ElligatorSwift encoding for BIP-324 encrypted transport. + /// + static void ElligatorSwiftExample() + { + Console.WriteLine("--- ElligatorSwift (BIP-324) ---"); + + using var secp256k1 = new Secp256k1(); + + // Create two parties for key exchange + // Party A + Span secretKeyA = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKeyA); + while (!secp256k1.EcSeckeyVerify(secretKeyA)) + { + RandomNumberGenerator.Fill(secretKeyA); + } + + // Party B + Span secretKeyB = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKeyB); + while (!secp256k1.EcSeckeyVerify(secretKeyB)) + { + RandomNumberGenerator.Fill(secretKeyB); + } + + // Create ElligatorSwift encoded public keys (64 bytes each) + // These look like random data, providing privacy + Span auxRandA = stackalloc byte[32]; + Span auxRandB = stackalloc byte[32]; + RandomNumberGenerator.Fill(auxRandA); + RandomNumberGenerator.Fill(auxRandB); + + Span ellswiftA = stackalloc byte[64]; + Span ellswiftB = stackalloc byte[64]; + + bool createdA = secp256k1.EllswiftCreate(ellswiftA, secretKeyA, auxRandA); + bool createdB = secp256k1.EllswiftCreate(ellswiftB, secretKeyB, auxRandB); + + Console.WriteLine($"Party A ElligatorSwift pubkey: {Convert.ToHexString(ellswiftA)[..40]}..."); + Console.WriteLine($"Party B ElligatorSwift pubkey: {Convert.ToHexString(ellswiftB)[..40]}..."); + + // Decode ElligatorSwift back to regular public key + Span decodedPubkeyA = stackalloc byte[64]; + secp256k1.EllswiftDecode(decodedPubkeyA, ellswiftA); + + // Verify it matches the original public key + Span originalPubkeyA = stackalloc byte[64]; + secp256k1.EcPubkeyCreate(originalPubkeyA, secretKeyA); + + // Serialize both to compare + Span decodedCompressed = stackalloc byte[33]; + Span originalCompressed = stackalloc byte[33]; + nuint len = 33; + secp256k1.EcPubkeySerialize(decodedCompressed, ref len, decodedPubkeyA, Secp256k1EcFlags.Compressed); + len = 33; + secp256k1.EcPubkeySerialize(originalCompressed, ref len, originalPubkeyA, Secp256k1EcFlags.Compressed); + + Console.WriteLine($"\nDecoded pubkey matches original: {decodedCompressed.SequenceEqual(originalCompressed)}"); + + // ElligatorSwift ECDH - compute shared secret directly from encoded keys + // This is more efficient than decoding + ECDH + + // Custom hash function for ElligatorSwift XDH + EllswiftXdhHashFunction hashFunc = (Span output, ReadOnlySpan x32, + ReadOnlySpan ell_a64, ReadOnlySpan ell_b64, IntPtr data) => + { + // BIP-324 style: hash the shared secret with both encoded public keys + Span combined = stackalloc byte[32 + 64 + 64]; + x32.CopyTo(combined); + ell_a64.CopyTo(combined[32..]); + ell_b64.CopyTo(combined[96..]); + SHA256.HashData(combined, output); + return 1; + }; + + // Party A computes shared secret (party=0 means we are party A) + Span sharedSecretA = stackalloc byte[32]; + secp256k1.EllswiftXdh(sharedSecretA, ellswiftA, ellswiftB, secretKeyA, 0, hashFunc, IntPtr.Zero); + + // Party B computes shared secret (party=1 means we are party B) + Span sharedSecretB = stackalloc byte[32]; + secp256k1.EllswiftXdh(sharedSecretB, ellswiftA, ellswiftB, secretKeyB, 1, hashFunc, IntPtr.Zero); + + Console.WriteLine($"\nParty A shared secret (custom hash): {Convert.ToHexString(sharedSecretA)}"); + Console.WriteLine($"Party B shared secret (custom hash): {Convert.ToHexString(sharedSecretB)}"); + Console.WriteLine($"Shared secrets match: {sharedSecretA.SequenceEqual(sharedSecretB)}"); + + // Using the built-in BIP-324 hash function as a callback + // This is the standard hash function for Bitcoin P2P encrypted transport + EllswiftXdhHashFunction bip324Hash = (Span output, ReadOnlySpan x32, + ReadOnlySpan ell_a64, ReadOnlySpan ell_b64, IntPtr data) => + { + // Delegate to the built-in BIP-324 implementation + return secp256k1.EllswiftXdhHashFunctionBip324(output, x32, ell_a64, ell_b64, Span.Empty) ? 1 : 0; + }; + + Span sharedSecretBip324A = stackalloc byte[32]; + Span sharedSecretBip324B = stackalloc byte[32]; + secp256k1.EllswiftXdh(sharedSecretBip324A, ellswiftA, ellswiftB, secretKeyA, 0, bip324Hash, IntPtr.Zero); + secp256k1.EllswiftXdh(sharedSecretBip324B, ellswiftA, ellswiftB, secretKeyB, 1, bip324Hash, IntPtr.Zero); + + Console.WriteLine($"\nParty A shared secret (BIP-324): {Convert.ToHexString(sharedSecretBip324A)}"); + Console.WriteLine($"Party B shared secret (BIP-324): {Convert.ToHexString(sharedSecretBip324B)}"); + Console.WriteLine($"BIP-324 secrets match: {sharedSecretBip324A.SequenceEqual(sharedSecretBip324B)}"); + + Console.WriteLine(); + Console.WriteLine("BIP-324 use case:"); + Console.WriteLine(" - ElligatorSwift encodes public keys as 64 random-looking bytes"); + Console.WriteLine(" - Makes Bitcoin P2P traffic indistinguishable from random data"); + Console.WriteLine(" - Prevents passive network observers from identifying Bitcoin nodes"); + Console.WriteLine(" - EllswiftXdh combines decoding and ECDH in one efficient operation"); + + Console.WriteLine(); + } + + /// + /// Demonstrates setting a custom error callback. + /// + static void ErrorCallbackExample() + { + Console.WriteLine("--- Custom Error Callback ---"); + + string? lastErrorMessage = null; + + // Create instance with custom error callback + // The callback is invoked by the native secp256k1 library when it detects + // illegal arguments or internal errors that bypass C# wrapper validation + ErrorCallbackDelegate errorCallback = (string message, IntPtr data) => + { + lastErrorMessage = message; + Console.WriteLine($" Callback received: \"{message}\""); + }; + + using var secp256k1 = new Secp256k1(errorCallback); + + Console.WriteLine("Custom error callback set"); + + // Normal operations don't trigger the callback + Console.WriteLine("\n1. Normal operation (valid secret key):"); + Span secretKey = stackalloc byte[32]; + RandomNumberGenerator.Fill(secretKey); + bool isValid = secp256k1.EcSeckeyVerify(secretKey); + Console.WriteLine($" Secret key valid: {isValid}"); + Console.WriteLine($" Error triggered: {lastErrorMessage != null}"); + + // Trigger the callback with an invalid recovery ID + // The recoveryId must be 0-3, but we pass 9 to trigger native validation + Console.WriteLine("\n2. Invalid operation (bad recovery ID in signature parsing):"); + lastErrorMessage = null; + + // Create a dummy signature (64 bytes) + Span serializedSig = stackalloc byte[64]; + RandomNumberGenerator.Fill(serializedSig); + Span outputSig = stackalloc byte[65]; + + // Pass invalid recoveryId (must be 0-3, we pass 9) + bool parseResult = secp256k1.EcdsaRecoverableSignatureParseCompact(outputSig, serializedSig, 9); + Console.WriteLine($" Parse result: {parseResult}"); + Console.WriteLine($" Error triggered: {lastErrorMessage != null}"); + + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/DerSignatureExamples.cs b/Secp256k1.Net.Examples/DerSignatureExamples.cs new file mode 100644 index 0000000..50fafd5 --- /dev/null +++ b/Secp256k1.Net.Examples/DerSignatureExamples.cs @@ -0,0 +1,120 @@ +using System.Security.Cryptography; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating DER signature format operations. +/// +public static class DerSignatureExamples +{ + public static void Run() + { + Console.WriteLine("=== DER Signature Format Examples ===\n"); + + SignatureToDerExample(); + SignatureFromDerExample(); + VerifyDerExample(); + DerFormatExplanation(); + } + + /// + /// SignatureToDer(compactSignature) - Convert compact signature to DER format + /// + static void SignatureToDerExample() + { + Console.WriteLine("--- SignatureToDer ---"); + + var (secretKey, _) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("DER conversion test"u8); + + // Create a compact signature (64 bytes: 32 bytes r + 32 bytes s) + byte[] compactSignature = Secp256k1.Sign(messageHash, secretKey); + + Console.WriteLine($"Compact signature ({compactSignature.Length} bytes): {Convert.ToHexString(compactSignature)}"); + + // Convert to DER format (variable length, typically 70-72 bytes) + byte[] derSignature = Secp256k1.SignatureToDer(compactSignature); + + Console.WriteLine($"DER signature ({derSignature.Length} bytes): {Convert.ToHexString(derSignature)}"); + Console.WriteLine(); + } + + /// + /// SignatureFromDer(derSignature) - Convert DER signature to compact format + /// + static void SignatureFromDerExample() + { + Console.WriteLine("--- SignatureFromDer ---"); + + var (secretKey, _) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("DER roundtrip test"u8); + + // Create and convert to DER + byte[] originalCompact = Secp256k1.Sign(messageHash, secretKey); + byte[] derSignature = Secp256k1.SignatureToDer(originalCompact); + + // Convert back to compact format + byte[] recoveredCompact = Secp256k1.SignatureFromDer(derSignature); + + Console.WriteLine($"Original compact: {Convert.ToHexString(originalCompact)}"); + Console.WriteLine($"DER intermediate: {Convert.ToHexString(derSignature)}"); + Console.WriteLine($"Recovered compact: {Convert.ToHexString(recoveredCompact)}"); + Console.WriteLine($"Roundtrip successful: {Convert.ToHexString(originalCompact).Equals(Convert.ToHexString(recoveredCompact))}"); + Console.WriteLine(); + } + + /// + /// VerifyDer(derSignature, messageHash, publicKey) - Verify a DER-encoded signature + /// + static void VerifyDerExample() + { + Console.WriteLine("--- VerifyDer ---"); + + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("DER verification test"u8); + + // Create a signature and convert to DER + byte[] compactSignature = Secp256k1.Sign(messageHash, secretKey); + byte[] derSignature = Secp256k1.SignatureToDer(compactSignature); + + // Verify the DER signature directly (no need to convert back to compact) + bool isValid = Secp256k1.VerifyDer(derSignature, messageHash, publicKey); + + Console.WriteLine($"DER signature: {Convert.ToHexString(derSignature)}"); + Console.WriteLine($"DER signature valid: {isValid}"); + + // Also verify that the compact signature works + bool compactValid = Secp256k1.Verify(compactSignature, messageHash, publicKey); + Console.WriteLine($"Compact signature valid: {compactValid}"); + Console.WriteLine(); + } + + /// + /// Explains the DER format structure. + /// + static void DerFormatExplanation() + { + Console.WriteLine("--- DER Format Explanation ---"); + + var (secretKey, _) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("DER format example"u8); + byte[] derSignature = Secp256k1.SignatureToDer(Secp256k1.Sign(messageHash, secretKey)); + + Console.WriteLine("DER signature structure:"); + Console.WriteLine($" Byte 0: 0x{derSignature[0]:X2} (SEQUENCE tag)"); + Console.WriteLine($" Byte 1: 0x{derSignature[1]:X2} (Total length of r + s: {derSignature[1]} bytes)"); + Console.WriteLine($" Byte 2: 0x{derSignature[2]:X2} (INTEGER tag for r)"); + Console.WriteLine($" Byte 3: 0x{derSignature[3]:X2} (Length of r: {derSignature[3]} bytes)"); + + int sOffset = 4 + derSignature[3]; + Console.WriteLine($" Byte {sOffset}: 0x{derSignature[sOffset]:X2} (INTEGER tag for s)"); + Console.WriteLine($" Byte {sOffset + 1}: 0x{derSignature[sOffset + 1]:X2} (Length of s: {derSignature[sOffset + 1]} bytes)"); + + Console.WriteLine(); + Console.WriteLine("Note: DER encoding adds a 0x00 prefix to integers with high bit set"); + Console.WriteLine(" to prevent them from being interpreted as negative numbers."); + Console.WriteLine(" This makes DER signatures variable-length (typically 70-72 bytes)."); + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/EcdhExamples.cs b/Secp256k1.Net.Examples/EcdhExamples.cs new file mode 100644 index 0000000..9d06ace --- /dev/null +++ b/Secp256k1.Net.Examples/EcdhExamples.cs @@ -0,0 +1,125 @@ +using System.Security.Cryptography; +using System.Text; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating ECDH (Elliptic Curve Diffie-Hellman) key agreement. +/// +public static class EcdhExamples +{ + public static void Run() + { + Console.WriteLine("=== ECDH Key Agreement Examples ===\n"); + + ComputeSharedSecretExample(); + TwoPartyKeyExchange(); + EncryptionWithSharedSecret(); + } + + /// + /// ComputeSharedSecret(publicKey, secretKey) - Compute ECDH shared secret + /// + static void ComputeSharedSecretExample() + { + Console.WriteLine("--- ComputeSharedSecret ---"); + + // Alice generates her key pair + var (aliceSecret, alicePublic) = Secp256k1.CreateKeyPair(compressed: true); + + // Bob generates his key pair + var (bobSecret, bobPublic) = Secp256k1.CreateKeyPair(compressed: true); + + // Alice computes shared secret using Bob's public key and her secret key + byte[] aliceSharedSecret = Secp256k1.ComputeSharedSecret(bobPublic, aliceSecret); + + // Bob computes shared secret using Alice's public key and his secret key + byte[] bobSharedSecret = Secp256k1.ComputeSharedSecret(alicePublic, bobSecret); + + Console.WriteLine($"Alice's public key: {Convert.ToHexString(alicePublic)}"); + Console.WriteLine($"Bob's public key: {Convert.ToHexString(bobPublic)}"); + Console.WriteLine($"Alice's shared secret: {Convert.ToHexString(aliceSharedSecret)}"); + Console.WriteLine($"Bob's shared secret: {Convert.ToHexString(bobSharedSecret)}"); + Console.WriteLine($"Shared secrets match: {Convert.ToHexString(aliceSharedSecret).Equals(Convert.ToHexString(bobSharedSecret))}"); + Console.WriteLine(); + } + + /// + /// Demonstrates a complete key exchange protocol. + /// + static void TwoPartyKeyExchange() + { + Console.WriteLine("--- Two-Party Key Exchange Protocol ---"); + + Console.WriteLine("1. Alice and Bob each generate their own key pairs"); + var (alicePrivate, alicePublic) = Secp256k1.CreateKeyPair(compressed: true); + var (bobPrivate, bobPublic) = Secp256k1.CreateKeyPair(compressed: true); + + Console.WriteLine("2. They exchange public keys over an insecure channel"); + Console.WriteLine($" Alice sends: {Convert.ToHexString(alicePublic)}"); + Console.WriteLine($" Bob sends: {Convert.ToHexString(bobPublic)}"); + + Console.WriteLine("3. Each party computes the shared secret locally"); + byte[] aliceComputed = Secp256k1.ComputeSharedSecret(bobPublic, alicePrivate); + byte[] bobComputed = Secp256k1.ComputeSharedSecret(alicePublic, bobPrivate); + + Console.WriteLine("4. Both arrive at the same 32-byte shared secret"); + Console.WriteLine($" Shared secret: {Convert.ToHexString(aliceComputed)}"); + + Console.WriteLine("5. This shared secret can be used to derive encryption keys"); + // In practice, you'd use a KDF like HKDF to derive actual encryption keys + byte[] encryptionKey = SHA256.HashData(aliceComputed); + Console.WriteLine($" Derived key (SHA256): {Convert.ToHexString(encryptionKey)}"); + Console.WriteLine(); + } + + /// + /// Demonstrates using ECDH for message encryption. + /// + static void EncryptionWithSharedSecret() + { + Console.WriteLine("--- Encryption with ECDH Shared Secret ---"); + + // Setup: Alice and Bob have exchanged public keys + var (alicePrivate, alicePublic) = Secp256k1.CreateKeyPair(compressed: true); + var (bobPrivate, bobPublic) = Secp256k1.CreateKeyPair(compressed: true); + + // Compute shared secret + byte[] sharedSecret = Secp256k1.ComputeSharedSecret(bobPublic, alicePrivate); + + // Derive an encryption key from the shared secret + byte[] encryptionKey = SHA256.HashData(sharedSecret); + + // Example message + string message = "Hello Bob, this is a secret message!"; + byte[] plaintext = Encoding.UTF8.GetBytes(message); + + Console.WriteLine($"Original message: {message}"); + + // Simple XOR encryption (for demonstration - use AES in production) + byte[] ciphertext = new byte[plaintext.Length]; + for (int i = 0; i < plaintext.Length; i++) + { + ciphertext[i] = (byte)(plaintext[i] ^ encryptionKey[i % encryptionKey.Length]); + } + Console.WriteLine($"Encrypted (hex): {Convert.ToHexString(ciphertext)}"); + + // Bob decrypts using the same shared secret + byte[] bobSharedSecret = Secp256k1.ComputeSharedSecret(alicePublic, bobPrivate); + byte[] bobKey = SHA256.HashData(bobSharedSecret); + + byte[] decrypted = new byte[ciphertext.Length]; + for (int i = 0; i < ciphertext.Length; i++) + { + decrypted[i] = (byte)(ciphertext[i] ^ bobKey[i % bobKey.Length]); + } + string decryptedMessage = Encoding.UTF8.GetString(decrypted); + Console.WriteLine($"Decrypted message: {decryptedMessage}"); + + Console.WriteLine(); + Console.WriteLine("Note: This example uses simple XOR for demonstration."); + Console.WriteLine(" In production, use AES-GCM or ChaCha20-Poly1305 with the derived key."); + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/EcdsaSigningExamples.cs b/Secp256k1.Net.Examples/EcdsaSigningExamples.cs new file mode 100644 index 0000000..d600ddc --- /dev/null +++ b/Secp256k1.Net.Examples/EcdsaSigningExamples.cs @@ -0,0 +1,126 @@ +using System.Security.Cryptography; +using System.Text; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating ECDSA signing and verification. +/// +public static class EcdsaSigningExamples +{ + public static void Run() + { + Console.WriteLine("=== ECDSA Signing & Verification Examples ===\n"); + + SignAndVerifyExample(); + SignRecoverableExample(); + RecoverPublicKeyExample(); + VerificationFailureExample(); + } + + /// + /// Sign(messageHash, secretKey) - Create a 64-byte compact ECDSA signature + /// Verify(signature, messageHash, publicKey) - Verify an ECDSA signature + /// + static void SignAndVerifyExample() + { + Console.WriteLine("--- Sign and Verify ---"); + + // Generate a key pair + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + + // Create a message and hash it (ECDSA signs the hash, not the raw message) + string message = "Hello, secp256k1!"; + byte[] messageHash = SHA256.HashData(Encoding.UTF8.GetBytes(message)); + + Console.WriteLine($"Message: {message}"); + Console.WriteLine($"Message hash: {Convert.ToHexString(messageHash)}"); + + // Sign the message hash + byte[] signature = Secp256k1.Sign(messageHash, secretKey); + + Console.WriteLine($"Signature ({signature.Length} bytes): {Convert.ToHexString(signature)}"); + + // Verify the signature + bool isValid = Secp256k1.Verify(signature, messageHash, publicKey); + Console.WriteLine($"Signature valid: {isValid}"); + Console.WriteLine(); + } + + /// + /// SignRecoverable(messageHash, secretKey) - Create a recoverable signature with recovery ID + /// + static void SignRecoverableExample() + { + Console.WriteLine("--- SignRecoverable ---"); + + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("Recoverable signature example"u8); + + // Create a recoverable signature (includes recovery ID) + (byte[] signature, byte recoveryId) = Secp256k1.SignRecoverable(messageHash, secretKey); + + Console.WriteLine($"Signature: {Convert.ToHexString(signature)}"); + Console.WriteLine($"Recovery ID: {recoveryId} (range 0-3)"); + + // The recovery ID allows reconstructing the public key from the signature + // This is used in Ethereum for transaction signatures (v, r, s format) + Console.WriteLine(); + } + + /// + /// RecoverPublicKey(signature, recoveryId, messageHash, compressed) - Recover public key from signature + /// + static void RecoverPublicKeyExample() + { + Console.WriteLine("--- RecoverPublicKey ---"); + + var (secretKey, originalPublicKey) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("Recovery test message"u8); + + // Create a recoverable signature + (byte[] signature, byte recoveryId) = Secp256k1.SignRecoverable(messageHash, secretKey); + + // Recover the public key using only the signature, recovery ID, and message hash + byte[] recoveredPublicKey = Secp256k1.RecoverPublicKey(signature, recoveryId, messageHash, compressed: true); + + Console.WriteLine($"Original public key: {Convert.ToHexString(originalPublicKey)}"); + Console.WriteLine($"Recovered public key: {Convert.ToHexString(recoveredPublicKey)}"); + Console.WriteLine($"Keys match: {Convert.ToHexString(originalPublicKey).Equals(Convert.ToHexString(recoveredPublicKey))}"); + + // Can also recover to uncompressed format + byte[] recoveredUncompressed = Secp256k1.RecoverPublicKey(signature, recoveryId, messageHash, compressed: false); + Console.WriteLine($"Recovered uncompressed ({recoveredUncompressed.Length} bytes): {Convert.ToHexString(recoveredUncompressed)}"); + Console.WriteLine(); + } + + /// + /// Demonstrates verification failures. + /// + static void VerificationFailureExample() + { + Console.WriteLine("--- Verification Failure Cases ---"); + + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("Original message"u8); + byte[] signature = Secp256k1.Sign(messageHash, secretKey); + + // Verify with correct data + Console.WriteLine($"Correct verification: {Secp256k1.Verify(signature, messageHash, publicKey)}"); + + // Wrong message hash + byte[] wrongHash = SHA256.HashData("Different message"u8); + Console.WriteLine($"Wrong message hash: {Secp256k1.Verify(signature, wrongHash, publicKey)}"); + + // Wrong public key + var (_, wrongPublicKey) = Secp256k1.CreateKeyPair(compressed: true); + Console.WriteLine($"Wrong public key: {Secp256k1.Verify(signature, messageHash, wrongPublicKey)}"); + + // Corrupted signature + byte[] corruptedSig = (byte[])signature.Clone(); + corruptedSig[0] ^= 0xFF; + Console.WriteLine($"Corrupted signature: {Secp256k1.Verify(corruptedSig, messageHash, publicKey)}"); + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/HashingExamples.cs b/Secp256k1.Net.Examples/HashingExamples.cs new file mode 100644 index 0000000..f416aab --- /dev/null +++ b/Secp256k1.Net.Examples/HashingExamples.cs @@ -0,0 +1,121 @@ +using System.Security.Cryptography; +using System.Text; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating BIP-340 tagged hashing. +/// +public static class HashingExamples +{ + public static void Run() + { + Console.WriteLine("=== Hashing Examples ===\n"); + + TaggedHashExample(); + TaggedHashUseCases(); + TaggedHashVsPlainHash(); + } + + /// + /// TaggedHash(tag, message) - Compute a BIP-340 tagged hash + /// + static void TaggedHashExample() + { + Console.WriteLine("--- TaggedHash ---"); + + // BIP-340 tagged hash: SHA256(SHA256(tag) || SHA256(tag) || message) + byte[] tag = "BIP0340/challenge"u8.ToArray(); + byte[] message = Encoding.UTF8.GetBytes("Hello, tagged hash!"); + + byte[] taggedHash = Secp256k1.TaggedHash(tag, message); + + Console.WriteLine($"Tag: \"BIP0340/challenge\""); + Console.WriteLine($"Message: \"Hello, tagged hash!\""); + Console.WriteLine($"Tagged hash ({taggedHash.Length} bytes): {Convert.ToHexString(taggedHash)}"); + Console.WriteLine(); + } + + /// + /// Shows common use cases for tagged hashes. + /// + static void TaggedHashUseCases() + { + Console.WriteLine("--- Tagged Hash Use Cases ---"); + + byte[] message = Encoding.UTF8.GetBytes("example message"); + + // BIP-340 Schnorr signature challenge + byte[] schnorrChallenge = Secp256k1.TaggedHash("BIP0340/challenge"u8, message); + Console.WriteLine($"BIP0340/challenge: {Convert.ToHexString(schnorrChallenge)}"); + + // BIP-340 auxiliary randomness + byte[] auxRand = Secp256k1.TaggedHash("BIP0340/aux"u8, message); + Console.WriteLine($"BIP0340/aux: {Convert.ToHexString(auxRand)}"); + + // BIP-340 nonce derivation + byte[] nonce = Secp256k1.TaggedHash("BIP0340/nonce"u8, message); + Console.WriteLine($"BIP0340/nonce: {Convert.ToHexString(nonce)}"); + + // BIP-341 Taproot leaf hash + byte[] tapLeaf = Secp256k1.TaggedHash("TapLeaf"u8, message); + Console.WriteLine($"TapLeaf: {Convert.ToHexString(tapLeaf)}"); + + // BIP-341 Taproot branch hash + byte[] tapBranch = Secp256k1.TaggedHash("TapBranch"u8, message); + Console.WriteLine($"TapBranch: {Convert.ToHexString(tapBranch)}"); + + // BIP-341 Taproot tweak + byte[] tapTweak = Secp256k1.TaggedHash("TapTweak"u8, message); + Console.WriteLine($"TapTweak: {Convert.ToHexString(tapTweak)}"); + + // Custom application tag + byte[] customTag = Secp256k1.TaggedHash("MyApp/v1/signature"u8, message); + Console.WriteLine($"MyApp/v1/signature: {Convert.ToHexString(customTag)}"); + + Console.WriteLine(); + } + + /// + /// Compares tagged hash with plain SHA256. + /// + static void TaggedHashVsPlainHash() + { + Console.WriteLine("--- Tagged Hash vs Plain SHA256 ---"); + + byte[] message = Encoding.UTF8.GetBytes("test message"); + + // Plain SHA256 + byte[] plainHash = SHA256.HashData(message); + + // Tagged hash with same message + byte[] taggedHash = Secp256k1.TaggedHash("test"u8, message); + + Console.WriteLine($"Plain SHA256: {Convert.ToHexString(plainHash)}"); + Console.WriteLine($"Tagged hash: {Convert.ToHexString(taggedHash)}"); + Console.WriteLine($"Hashes differ: {!Convert.ToHexString(plainHash).Equals(Convert.ToHexString(taggedHash))}"); + + Console.WriteLine(); + Console.WriteLine("Tagged hash formula: SHA256(SHA256(tag) || SHA256(tag) || message)"); + Console.WriteLine(); + + // Manually compute the tagged hash to verify + byte[] tagHash = SHA256.HashData(Encoding.UTF8.GetBytes("test")); + byte[] prefixedMessage = new byte[tagHash.Length * 2 + message.Length]; + tagHash.CopyTo(prefixedMessage, 0); + tagHash.CopyTo(prefixedMessage, tagHash.Length); + message.CopyTo(prefixedMessage, tagHash.Length * 2); + byte[] manualTaggedHash = SHA256.HashData(prefixedMessage); + + Console.WriteLine($"Manual computation: {Convert.ToHexString(manualTaggedHash)}"); + Console.WriteLine($"Matches library: {Convert.ToHexString(taggedHash).Equals(Convert.ToHexString(manualTaggedHash))}"); + + Console.WriteLine(); + Console.WriteLine("Why tagged hashes?"); + Console.WriteLine(" - Domain separation: prevents hash collisions between different protocols"); + Console.WriteLine(" - Security: ensures hashes for one purpose can't be reused for another"); + Console.WriteLine(" - Standard: defined in BIP-340 for Bitcoin Schnorr signatures"); + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/KeyGenerationExamples.cs b/Secp256k1.Net.Examples/KeyGenerationExamples.cs new file mode 100644 index 0000000..480ec81 --- /dev/null +++ b/Secp256k1.Net.Examples/KeyGenerationExamples.cs @@ -0,0 +1,148 @@ +using System.Security.Cryptography; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating key generation and validation functions. +/// +public static class KeyGenerationExamples +{ + public static void Run() + { + Console.WriteLine("=== Key Generation & Validation Examples ===\n"); + + CreateSecretKeyExample(); + CreatePublicKeyExample(); + CreateXOnlyPublicKeyExample(); + CreateKeyPairExample(); + IsValidSecretKeyExample(); + IsValidPublicKeyExample(); + } + + /// + /// CreateSecretKey() - Generate a cryptographically secure random secret key + /// + static void CreateSecretKeyExample() + { + Console.WriteLine("--- CreateSecretKey ---"); + + // Generate a new random 32-byte secret key + byte[] secretKey = Secp256k1.CreateSecretKey(); + + Console.WriteLine($"Secret key length: {secretKey.Length} bytes"); + Console.WriteLine($"Secret key (hex): {Convert.ToHexString(secretKey)}"); + Console.WriteLine(); + } + + /// + /// CreatePublicKey(secretKey, compressed) - Derive a serialized public key from a secret key + /// + static void CreatePublicKeyExample() + { + Console.WriteLine("--- CreatePublicKey ---"); + + byte[] secretKey = Secp256k1.CreateSecretKey(); + + // Create a compressed public key (33 bytes, starts with 02 or 03) + byte[] compressedPubKey = Secp256k1.CreatePublicKey(secretKey, compressed: true); + Console.WriteLine($"Compressed public key length: {compressedPubKey.Length} bytes"); + Console.WriteLine($"Compressed public key: {Convert.ToHexString(compressedPubKey)}"); + + // Create an uncompressed public key (65 bytes, starts with 04) + byte[] uncompressedPubKey = Secp256k1.CreatePublicKey(secretKey, compressed: false); + Console.WriteLine($"Uncompressed public key length: {uncompressedPubKey.Length} bytes"); + Console.WriteLine($"Uncompressed public key: {Convert.ToHexString(uncompressedPubKey)}"); + Console.WriteLine(); + } + + /// + /// CreateXOnlyPublicKey(secretKey) - Derive an x-only public key and parity for BIP-340 + /// + static void CreateXOnlyPublicKeyExample() + { + Console.WriteLine("--- CreateXOnlyPublicKey ---"); + + byte[] secretKey = Secp256k1.CreateSecretKey(); + + // Create an x-only public key (32 bytes) with parity byte for BIP-340 Schnorr signatures + (byte[] xOnlyPubKey, byte parity) = Secp256k1.CreateXOnlyPublicKey(secretKey); + + Console.WriteLine($"X-only public key length: {xOnlyPubKey.Length} bytes"); + Console.WriteLine($"X-only public key: {Convert.ToHexString(xOnlyPubKey)}"); + Console.WriteLine($"Parity: {parity} (0 = even, 1 = odd)"); + Console.WriteLine(); + } + + /// + /// CreateKeyPair(compressed) - Generate a new secret key and public key pair + /// + static void CreateKeyPairExample() + { + Console.WriteLine("--- CreateKeyPair ---"); + + // Generate a complete key pair in one call (compressed public key) + (byte[] secretKey, byte[] publicKey) = Secp256k1.CreateKeyPair(compressed: true); + + Console.WriteLine($"Secret key: {Convert.ToHexString(secretKey)}"); + Console.WriteLine($"Public key: {Convert.ToHexString(publicKey)}"); + + // Generate with uncompressed public key + var (secretKey2, uncompressedPubKey) = Secp256k1.CreateKeyPair(compressed: false); + Console.WriteLine($"Uncompressed public key length: {uncompressedPubKey.Length} bytes"); + Console.WriteLine(); + } + + /// + /// IsValidSecretKey(secretKey) - Validate a secret key + /// + static void IsValidSecretKeyExample() + { + Console.WriteLine("--- IsValidSecretKey ---"); + + // Valid secret key + byte[] validKey = Secp256k1.CreateSecretKey(); + Console.WriteLine($"Valid random key: {Secp256k1.IsValidSecretKey(validKey)}"); + + // Invalid: all zeros (not allowed) + byte[] zeroKey = new byte[32]; + Console.WriteLine($"All zeros key: {Secp256k1.IsValidSecretKey(zeroKey)}"); + + // Invalid: greater than or equal to the curve order + byte[] tooLargeKey = new byte[32]; + Array.Fill(tooLargeKey, (byte)0xFF); + Console.WriteLine($"All 0xFF key (too large): {Secp256k1.IsValidSecretKey(tooLargeKey)}"); + + // Invalid: wrong length + byte[] wrongLength = new byte[16]; + Console.WriteLine($"Wrong length (16 bytes): {Secp256k1.IsValidSecretKey(wrongLength)}"); + Console.WriteLine(); + } + + /// + /// IsValidPublicKey(publicKey) - Validate a serialized public key + /// + static void IsValidPublicKeyExample() + { + Console.WriteLine("--- IsValidPublicKey ---"); + + var (_, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + + // Valid compressed public key + Console.WriteLine($"Valid compressed key: {Secp256k1.IsValidPublicKey(publicKey)}"); + + // Valid uncompressed public key + byte[] uncompressed = Secp256k1.DecompressPublicKey(publicKey); + Console.WriteLine($"Valid uncompressed key: {Secp256k1.IsValidPublicKey(uncompressed)}"); + + // Invalid: corrupted key (wrong prefix) + byte[] corrupted = (byte[])publicKey.Clone(); + corrupted[0] = 0x05; // Invalid prefix + Console.WriteLine($"Corrupted key (bad prefix): {Secp256k1.IsValidPublicKey(corrupted)}"); + + // Invalid: wrong length + byte[] wrongLength = new byte[20]; + Console.WriteLine($"Wrong length (20 bytes): {Secp256k1.IsValidPublicKey(wrongLength)}"); + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/KeyTweakingExamples.cs b/Secp256k1.Net.Examples/KeyTweakingExamples.cs new file mode 100644 index 0000000..1ae05f8 --- /dev/null +++ b/Secp256k1.Net.Examples/KeyTweakingExamples.cs @@ -0,0 +1,189 @@ +using System.Security.Cryptography; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating key tweaking operations for BIP-32 HD wallets. +/// +public static class KeyTweakingExamples +{ + public static void Run() + { + Console.WriteLine("=== Key Tweaking (BIP-32 HD Wallets) Examples ===\n"); + + TweakSecretKeyAddExample(); + TweakPublicKeyAddExample(); + TweakSecretKeyMulExample(); + TweakPublicKeyMulExample(); + NegateSecretKeyExample(); + Bip32DerivationExample(); + } + + /// + /// TweakSecretKeyAdd(secretKey, tweak) - Add a tweak to a secret key + /// + static void TweakSecretKeyAddExample() + { + Console.WriteLine("--- TweakSecretKeyAdd ---"); + + byte[] secretKey = Secp256k1.CreateSecretKey(); + byte[] tweak = SHA256.HashData("child derivation tweak"u8); + + Console.WriteLine($"Original secret key: {Convert.ToHexString(secretKey)}"); + Console.WriteLine($"Tweak: {Convert.ToHexString(tweak)}"); + + // Add tweak to secret key: newKey = (secretKey + tweak) mod n + byte[] tweakedSecretKey = Secp256k1.TweakSecretKeyAdd(secretKey, tweak); + + Console.WriteLine($"Tweaked secret key: {Convert.ToHexString(tweakedSecretKey)}"); + + // The tweaked key is different from the original + Console.WriteLine($"Keys are different: {!Convert.ToHexString(secretKey).Equals(Convert.ToHexString(tweakedSecretKey))}"); + Console.WriteLine(); + } + + /// + /// TweakPublicKeyAdd(publicKey, tweak, compressed) - Add a tweak to a public key + /// + static void TweakPublicKeyAddExample() + { + Console.WriteLine("--- TweakPublicKeyAdd ---"); + + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + byte[] tweak = SHA256.HashData("public key tweak"u8); + + Console.WriteLine($"Original public key: {Convert.ToHexString(publicKey)}"); + Console.WriteLine($"Tweak: {Convert.ToHexString(tweak)}"); + + // Add tweak to public key: newPubKey = pubKey + tweak*G + byte[] tweakedPublicKey = Secp256k1.TweakPublicKeyAdd(publicKey, tweak, compressed: true); + + Console.WriteLine($"Tweaked public key: {Convert.ToHexString(tweakedPublicKey)}"); + + // Verify: tweaking the secret key and deriving public key gives same result + byte[] tweakedSecretKey = Secp256k1.TweakSecretKeyAdd(secretKey, tweak); + byte[] derivedPublicKey = Secp256k1.CreatePublicKey(tweakedSecretKey, compressed: true); + + Console.WriteLine($"Derived from tweaked secret: {Convert.ToHexString(derivedPublicKey)}"); + Console.WriteLine($"Public keys match: {Convert.ToHexString(tweakedPublicKey).Equals(Convert.ToHexString(derivedPublicKey))}"); + Console.WriteLine(); + } + + /// + /// TweakSecretKeyMul(secretKey, tweak) - Multiply a secret key by a tweak + /// + static void TweakSecretKeyMulExample() + { + Console.WriteLine("--- TweakSecretKeyMul ---"); + + byte[] secretKey = Secp256k1.CreateSecretKey(); + byte[] tweak = SHA256.HashData("multiplication tweak"u8); + + Console.WriteLine($"Original secret key: {Convert.ToHexString(secretKey)}"); + Console.WriteLine($"Tweak: {Convert.ToHexString(tweak)}"); + + // Multiply secret key by tweak: newKey = (secretKey * tweak) mod n + byte[] tweakedSecretKey = Secp256k1.TweakSecretKeyMul(secretKey, tweak); + + Console.WriteLine($"Tweaked secret key: {Convert.ToHexString(tweakedSecretKey)}"); + Console.WriteLine(); + } + + /// + /// TweakPublicKeyMul(publicKey, tweak, compressed) - Multiply a public key by a tweak + /// + static void TweakPublicKeyMulExample() + { + Console.WriteLine("--- TweakPublicKeyMul ---"); + + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + byte[] tweak = SHA256.HashData("public key mul tweak"u8); + + Console.WriteLine($"Original public key: {Convert.ToHexString(publicKey)}"); + Console.WriteLine($"Tweak: {Convert.ToHexString(tweak)}"); + + // Multiply public key by tweak: newPubKey = tweak * pubKey + byte[] tweakedPublicKey = Secp256k1.TweakPublicKeyMul(publicKey, tweak, compressed: true); + + Console.WriteLine($"Tweaked public key: {Convert.ToHexString(tweakedPublicKey)}"); + + // Verify: multiplying the secret key and deriving public key gives same result + byte[] tweakedSecretKey = Secp256k1.TweakSecretKeyMul(secretKey, tweak); + byte[] derivedPublicKey = Secp256k1.CreatePublicKey(tweakedSecretKey, compressed: true); + + Console.WriteLine($"Derived from tweaked secret: {Convert.ToHexString(derivedPublicKey)}"); + Console.WriteLine($"Public keys match: {Convert.ToHexString(tweakedPublicKey).Equals(Convert.ToHexString(derivedPublicKey))}"); + Console.WriteLine(); + } + + /// + /// NegateSecretKey(secretKey) - Negate a secret key + /// + static void NegateSecretKeyExample() + { + Console.WriteLine("--- NegateSecretKey ---"); + + byte[] secretKey = Secp256k1.CreateSecretKey(); + byte[] publicKey = Secp256k1.CreatePublicKey(secretKey, compressed: true); + + Console.WriteLine($"Original secret key: {Convert.ToHexString(secretKey)}"); + Console.WriteLine($"Original public key: {Convert.ToHexString(publicKey)}"); + + // Negate the secret key: newKey = -secretKey mod n + byte[] negatedSecretKey = Secp256k1.NegateSecretKey(secretKey); + byte[] negatedPublicKey = Secp256k1.CreatePublicKey(negatedSecretKey, compressed: true); + + Console.WriteLine($"Negated secret key: {Convert.ToHexString(negatedSecretKey)}"); + Console.WriteLine($"Negated public key: {Convert.ToHexString(negatedPublicKey)}"); + + // The negated public key should equal NegatePublicKey result + byte[] publicKeyNegated = Secp256k1.NegatePublicKey(publicKey, compressed: true); + Console.WriteLine($"NegatePublicKey result: {Convert.ToHexString(publicKeyNegated)}"); + Console.WriteLine($"Results match: {Convert.ToHexString(negatedPublicKey).Equals(Convert.ToHexString(publicKeyNegated))}"); + + // Double negation returns original + byte[] doubleNegated = Secp256k1.NegateSecretKey(negatedSecretKey); + Console.WriteLine($"Double negation equals original: {Convert.ToHexString(secretKey).Equals(Convert.ToHexString(doubleNegated))}"); + Console.WriteLine(); + } + + /// + /// Demonstrates BIP-32-like child key derivation using tweaking. + /// + static void Bip32DerivationExample() + { + Console.WriteLine("--- BIP-32-like Child Key Derivation ---"); + + // Master key (in real BIP-32, this comes from a seed) + byte[] masterSecret = Secp256k1.CreateSecretKey(); + byte[] masterPublic = Secp256k1.CreatePublicKey(masterSecret, compressed: true); + + Console.WriteLine($"Master public key: {Convert.ToHexString(masterPublic)}"); + + // Derive child keys using index-based tweaks (simplified version) + for (int childIndex = 0; childIndex < 3; childIndex++) + { + // Create a deterministic tweak from the parent public key and index + byte[] tweakInput = new byte[masterPublic.Length + 4]; + masterPublic.CopyTo(tweakInput, 0); + BitConverter.GetBytes(childIndex).CopyTo(tweakInput, masterPublic.Length); + byte[] tweak = SHA256.HashData(tweakInput); + + // Derive child keys + byte[] childSecret = Secp256k1.TweakSecretKeyAdd(masterSecret, tweak); + byte[] childPublic = Secp256k1.TweakPublicKeyAdd(masterPublic, tweak, compressed: true); + + // Verify the relationship + byte[] derivedPublic = Secp256k1.CreatePublicKey(childSecret, compressed: true); + bool matches = Convert.ToHexString(childPublic).Equals(Convert.ToHexString(derivedPublic)); + + Console.WriteLine($"Child {childIndex}: {Convert.ToHexString(childPublic)[..32]}... (verified: {matches})"); + } + + Console.WriteLine(); + Console.WriteLine("Note: This is a simplified example. Real BIP-32 uses HMAC-SHA512"); + Console.WriteLine(" and has separate chain codes for proper derivation paths."); + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/MuSig2Examples.cs b/Secp256k1.Net.Examples/MuSig2Examples.cs new file mode 100644 index 0000000..2ecdd1c --- /dev/null +++ b/Secp256k1.Net.Examples/MuSig2Examples.cs @@ -0,0 +1,464 @@ +using System.Security.Cryptography; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating MuSig2 multi-signature scheme. +/// MuSig2 allows multiple parties to create a single aggregated signature +/// that is indistinguishable from a regular Schnorr signature. +/// +public static class MuSig2Examples +{ + public static void Run() + { + Console.WriteLine("=== MuSig2 Multi-Signature Examples ===\n"); + + MuSig2Overview(); + TwoPartyMuSig(); + ThreePartyMuSig(); + MuSigWithTweaking(); + } + + /// + /// Overview of MuSig2 protocol. + /// + static void MuSig2Overview() + { + Console.WriteLine("--- MuSig2 Overview ---"); + + Console.WriteLine(@" +MuSig2 is a multi-signature scheme that produces a single 64-byte Schnorr signature +from multiple signers. The signature is indistinguishable from a regular signature. + +Protocol steps: + 1. Key Aggregation: Combine all signers' public keys into one aggregate key + 2. Nonce Generation: Each signer generates a secret/public nonce pair + 3. Nonce Aggregation: Combine all public nonces into an aggregate nonce + 4. Partial Signing: Each signer creates a partial signature + 5. Signature Aggregation: Combine partial signatures into final signature + +Security considerations: + - NEVER reuse nonces across signing sessions + - Each signer must use fresh randomness for nonce generation + - The protocol requires two rounds of communication between signers + +Use cases: + - Bitcoin multisig with smaller on-chain footprint + - Threshold signatures for custody solutions + - Privacy-preserving multi-party transactions +"); + Console.WriteLine(); + } + + /// + /// Demonstrates a complete 2-of-2 MuSig2 signing session. + /// + static void TwoPartyMuSig() + { + Console.WriteLine("--- Two-Party MuSig2 Signing ---"); + + using var secp256k1 = new Secp256k1(); + + // ===== SETUP: Each party generates their keypair ===== + Console.WriteLine("1. Setup: Each party generates a keypair"); + + // Party A's keypair + byte[] secretKeyA = new byte[32]; + RandomNumberGenerator.Fill(secretKeyA); + while (!secp256k1.EcSeckeyVerify(secretKeyA)) + RandomNumberGenerator.Fill(secretKeyA); + + byte[] keypairA = new byte[96]; + secp256k1.KeypairCreate(keypairA, secretKeyA); + + byte[] internalPubkeyA = new byte[64]; + secp256k1.KeypairPub(internalPubkeyA, keypairA); + + // Party B's keypair + byte[] secretKeyB = new byte[32]; + RandomNumberGenerator.Fill(secretKeyB); + while (!secp256k1.EcSeckeyVerify(secretKeyB)) + RandomNumberGenerator.Fill(secretKeyB); + + byte[] keypairB = new byte[96]; + secp256k1.KeypairCreate(keypairB, secretKeyB); + + byte[] internalPubkeyB = new byte[64]; + secp256k1.KeypairPub(internalPubkeyB, keypairB); + + // Display public keys + Span compressedA = stackalloc byte[33]; + Span compressedB = stackalloc byte[33]; + nuint len = 33; + secp256k1.EcPubkeySerialize(compressedA, ref len, internalPubkeyA, Secp256k1EcFlags.Compressed); + len = 33; + secp256k1.EcPubkeySerialize(compressedB, ref len, internalPubkeyB, Secp256k1EcFlags.Compressed); + Console.WriteLine($" Party A pubkey: {Convert.ToHexString(compressedA)}"); + Console.WriteLine($" Party B pubkey: {Convert.ToHexString(compressedB)}"); + + // ===== KEY AGGREGATION ===== + Console.WriteLine("\n2. Key Aggregation: Combine public keys"); + + // Sort public keys for deterministic aggregation + byte[][] pubkeys = [internalPubkeyA, internalPubkeyB]; + secp256k1.EcPubkeySort(pubkeys); + + // Aggregate public keys + Span aggPubkey = stackalloc byte[64]; + Span keyaggCache = stackalloc byte[197]; + bool aggSuccess = secp256k1.MusigPubkeyAgg(aggPubkey, keyaggCache, pubkeys); + + // Get x-only aggregate public key + Span aggXonly = stackalloc byte[64]; + secp256k1.XonlyPubkeyFromPubkey(aggXonly, out int aggParity, aggPubkey); + + Span aggXonlySerialized = stackalloc byte[32]; + secp256k1.XonlyPubkeySerialize(aggXonlySerialized, aggXonly); + Console.WriteLine($" Aggregate pubkey: {Convert.ToHexString(aggXonlySerialized)}"); + Console.WriteLine($" Aggregation successful: {aggSuccess}"); + + // ===== NONCE GENERATION (Round 1) ===== + Console.WriteLine("\n3. Nonce Generation: Each party generates nonces"); + + // The message to sign + byte[] message = SHA256.HashData("MuSig2 test message"u8); + Console.WriteLine($" Message hash: {Convert.ToHexString(message)}"); + + // Extra input (optional, can be zeros or additional entropy like current time) + Span extraInput = stackalloc byte[32]; + + // Party A generates nonce + Span secnonceA = stackalloc byte[132]; + Span pubnonceA = stackalloc byte[132]; + Span sessionRandA = stackalloc byte[32]; + RandomNumberGenerator.Fill(sessionRandA); + + secp256k1.MusigNonceGen(secnonceA, pubnonceA, sessionRandA, secretKeyA, + internalPubkeyA, message, keyaggCache, extraInput); + + // Party B generates nonce + Span secnonceB = stackalloc byte[132]; + Span pubnonceB = stackalloc byte[132]; + Span sessionRandB = stackalloc byte[32]; + RandomNumberGenerator.Fill(sessionRandB); + + secp256k1.MusigNonceGen(secnonceB, pubnonceB, sessionRandB, secretKeyB, + internalPubkeyB, message, keyaggCache, extraInput); + + // Serialize public nonces for exchange + Span pubnonceSerializedA = stackalloc byte[66]; + Span pubnonceSerializedB = stackalloc byte[66]; + secp256k1.MusigPubnonceSerialize(pubnonceSerializedA, pubnonceA); + secp256k1.MusigPubnonceSerialize(pubnonceSerializedB, pubnonceB); + Console.WriteLine($" Party A pubnonce: {Convert.ToHexString(pubnonceSerializedA)[..40]}..."); + Console.WriteLine($" Party B pubnonce: {Convert.ToHexString(pubnonceSerializedB)[..40]}..."); + + // ===== NONCE AGGREGATION ===== + Console.WriteLine("\n4. Nonce Aggregation: Combine public nonces"); + + // Parse received nonces (in real scenario, these come from other parties) + byte[] parsedNonceA = new byte[132]; + byte[] parsedNonceB = new byte[132]; + secp256k1.MusigPubnonceParse(parsedNonceA, pubnonceSerializedA); + secp256k1.MusigPubnonceParse(parsedNonceB, pubnonceSerializedB); + + // Aggregate nonces + Span aggNonce = stackalloc byte[132]; + byte[][] pubnonces = [parsedNonceA, parsedNonceB]; + bool nonceAggSuccess = secp256k1.MusigNonceAgg(aggNonce, pubnonces); + + Span aggNonceSerialized = stackalloc byte[66]; + secp256k1.MusigAggnonceSerialize(aggNonceSerialized, aggNonce); + Console.WriteLine($" Aggregate nonce: {Convert.ToHexString(aggNonceSerialized)[..40]}..."); + Console.WriteLine($" Nonce aggregation successful: {nonceAggSuccess}"); + + // ===== CREATE SIGNING SESSION ===== + Console.WriteLine("\n5. Create Signing Session"); + + Span session = stackalloc byte[133]; + bool sessionCreated = secp256k1.MusigNonceProcess(session, aggNonce, message, keyaggCache); + Console.WriteLine($" Session created: {sessionCreated}"); + + // ===== PARTIAL SIGNING (Round 2) ===== + Console.WriteLine("\n6. Partial Signing: Each party creates partial signature"); + + // Party A creates partial signature + Span partialSigA = stackalloc byte[36]; + bool signedA = secp256k1.MusigPartialSign(partialSigA, secnonceA, keypairA, keyaggCache, session); + + Span partialSigSerializedA = stackalloc byte[32]; + secp256k1.MusigPartialSigSerialize(partialSigSerializedA, partialSigA); + Console.WriteLine($" Party A partial sig: {Convert.ToHexString(partialSigSerializedA)}"); + + // Party B creates partial signature + Span partialSigB = stackalloc byte[36]; + bool signedB = secp256k1.MusigPartialSign(partialSigB, secnonceB, keypairB, keyaggCache, session); + + Span partialSigSerializedB = stackalloc byte[32]; + secp256k1.MusigPartialSigSerialize(partialSigSerializedB, partialSigB); + Console.WriteLine($" Party B partial sig: {Convert.ToHexString(partialSigSerializedB)}"); + + // ===== VERIFY PARTIAL SIGNATURES (optional but recommended) ===== + Console.WriteLine("\n7. Verify Partial Signatures (optional)"); + + bool partialVerifyA = secp256k1.MusigPartialSigVerify(partialSigA, pubnonceA, internalPubkeyA, keyaggCache, session); + bool partialVerifyB = secp256k1.MusigPartialSigVerify(partialSigB, pubnonceB, internalPubkeyB, keyaggCache, session); + Console.WriteLine($" Party A partial sig valid: {partialVerifyA}"); + Console.WriteLine($" Party B partial sig valid: {partialVerifyB}"); + + // ===== SIGNATURE AGGREGATION ===== + Console.WriteLine("\n8. Signature Aggregation: Combine partial signatures"); + + // Parse partial signatures + byte[] parsedPartialA = new byte[36]; + byte[] parsedPartialB = new byte[36]; + secp256k1.MusigPartialSigParse(parsedPartialA, partialSigSerializedA); + secp256k1.MusigPartialSigParse(parsedPartialB, partialSigSerializedB); + + // Aggregate into final signature + Span finalSignature = stackalloc byte[64]; + byte[][] partialSigs = [parsedPartialA, parsedPartialB]; + bool aggSigSuccess = secp256k1.MusigPartialSigAgg(finalSignature, session, partialSigs); + + Console.WriteLine($" Final signature: {Convert.ToHexString(finalSignature)}"); + Console.WriteLine($" Aggregation successful: {aggSigSuccess}"); + + // ===== VERIFY FINAL SIGNATURE ===== + Console.WriteLine("\n9. Verify Final Signature (standard Schnorr verification)"); + + bool verified = secp256k1.SchnorrsigVerify(finalSignature, message, aggXonly); + Console.WriteLine($" Signature valid: {verified}"); + + Console.WriteLine(); + } + + /// + /// Demonstrates a 3-of-3 MuSig2 signing session. + /// + static void ThreePartyMuSig() + { + Console.WriteLine("--- Three-Party MuSig2 Signing ---"); + + using var secp256k1 = new Secp256k1(); + + // Setup: Create 3 keypairs + // We'll store keypair and pubkey together so they stay aligned after sorting + var signers = new (byte[] Keypair, byte[] Pubkey)[3]; + + for (int i = 0; i < 3; i++) + { + byte[] secretKey = new byte[32]; + signers[i].Keypair = new byte[96]; + signers[i].Pubkey = new byte[64]; + + RandomNumberGenerator.Fill(secretKey); + while (!secp256k1.EcSeckeyVerify(secretKey)) + RandomNumberGenerator.Fill(secretKey); + + secp256k1.KeypairCreate(signers[i].Keypair, secretKey); + secp256k1.KeypairPub(signers[i].Pubkey, signers[i].Keypair); + } + + Console.WriteLine("Created 3 keypairs"); + + // Sort signers by their public keys (lexicographic order) + // This ensures deterministic aggregate key regardless of signer order + Array.Sort(signers, (a, b) => secp256k1.EcPubkeyCmp(a.Pubkey, b.Pubkey)); + + // Extract sorted public keys for aggregation + byte[][] publicKeys = signers.Select(s => s.Pubkey).ToArray(); + + Span aggPubkey = stackalloc byte[64]; + Span keyaggCache = stackalloc byte[197]; + secp256k1.MusigPubkeyAgg(aggPubkey, keyaggCache, publicKeys); + + Span aggXonly = stackalloc byte[64]; + secp256k1.XonlyPubkeyFromPubkey(aggXonly, out _, aggPubkey); + + Span aggSerialized = stackalloc byte[32]; + secp256k1.XonlyPubkeySerialize(aggSerialized, aggXonly); + Console.WriteLine($"Aggregate pubkey: {Convert.ToHexString(aggSerialized)}"); + + // Message + byte[] message = SHA256.HashData("Three-party MuSig2 message"u8); + + // Generate nonces for all parties + byte[][] secnonces = new byte[3][]; + byte[][] pubnonces = new byte[3][]; + + // Extra input (optional) + byte[] extraInput = new byte[32]; + + for (int i = 0; i < 3; i++) + { + secnonces[i] = new byte[132]; + pubnonces[i] = new byte[132]; + + byte[] sessionRand = new byte[32]; + RandomNumberGenerator.Fill(sessionRand); + + // Extract secret key from keypair for nonce generation + byte[] secretKey = new byte[32]; + secp256k1.KeypairSec(secretKey, signers[i].Keypair); + + secp256k1.MusigNonceGen(secnonces[i], pubnonces[i], sessionRand, + secretKey, signers[i].Pubkey, message, keyaggCache, extraInput); + } + + Console.WriteLine("Generated nonces for all 3 parties"); + + // Aggregate nonces + Span aggNonce = stackalloc byte[132]; + secp256k1.MusigNonceAgg(aggNonce, pubnonces); + + // Create session + Span session = stackalloc byte[133]; + secp256k1.MusigNonceProcess(session, aggNonce, message, keyaggCache); + + // Create partial signatures + byte[][] partialSigs = new byte[3][]; + for (int i = 0; i < 3; i++) + { + partialSigs[i] = new byte[36]; + secp256k1.MusigPartialSign(partialSigs[i], secnonces[i], signers[i].Keypair, keyaggCache, session); + } + + Console.WriteLine("Created 3 partial signatures"); + + // Aggregate signatures + Span finalSignature = stackalloc byte[64]; + secp256k1.MusigPartialSigAgg(finalSignature, session, partialSigs); + + Console.WriteLine($"Final signature: {Convert.ToHexString(finalSignature)}"); + + // Verify + bool verified = secp256k1.SchnorrsigVerify(finalSignature, message, aggXonly); + Console.WriteLine($"Signature valid: {verified}"); + + Console.WriteLine(); + } + + /// + /// Demonstrates MuSig2 with key tweaking for Taproot. + /// + static void MuSigWithTweaking() + { + Console.WriteLine("--- MuSig2 with Taproot Tweaking ---"); + + using var secp256k1 = new Secp256k1(); + + // Create 2 keypairs - keep keypair and pubkey together + var signers = new (byte[] Keypair, byte[] Pubkey)[2]; + + for (int i = 0; i < 2; i++) + { + byte[] secretKey = new byte[32]; + signers[i].Keypair = new byte[96]; + signers[i].Pubkey = new byte[64]; + + RandomNumberGenerator.Fill(secretKey); + while (!secp256k1.EcSeckeyVerify(secretKey)) + RandomNumberGenerator.Fill(secretKey); + + secp256k1.KeypairCreate(signers[i].Keypair, secretKey); + secp256k1.KeypairPub(signers[i].Pubkey, signers[i].Keypair); + } + + // Sort signers by their public keys + Array.Sort(signers, (a, b) => secp256k1.EcPubkeyCmp(a.Pubkey, b.Pubkey)); + + // Extract sorted public keys for aggregation + byte[][] publicKeys = signers.Select(s => s.Pubkey).ToArray(); + + Span aggPubkey = stackalloc byte[64]; + Span keyaggCache = stackalloc byte[197]; + secp256k1.MusigPubkeyAgg(aggPubkey, keyaggCache, publicKeys); + + // Get the untweaked aggregate key + Span untweakedXonly = stackalloc byte[64]; + secp256k1.XonlyPubkeyFromPubkey(untweakedXonly, out _, aggPubkey); + Span untweakedSerialized = stackalloc byte[32]; + secp256k1.XonlyPubkeySerialize(untweakedSerialized, untweakedXonly); + Console.WriteLine($"Untweaked aggregate key: {Convert.ToHexString(untweakedSerialized)}"); + + // Create a Taproot-style tweak (in practice, this would be derived from script tree) + byte[] tweak = SHA256.HashData("TapTweak"u8); + Console.WriteLine($"Tweak: {Convert.ToHexString(tweak)}"); + + // Apply x-only tweak to the aggregate key + // This modifies keyaggCache to account for the tweak during signing + Span tweakedPubkey = stackalloc byte[64]; + bool tweakSuccess = secp256k1.MusigPubkeyXonlyTweakAdd(tweakedPubkey, keyaggCache, tweak); + Console.WriteLine($"Tweak applied: {tweakSuccess}"); + + // Get the tweaked x-only key (this is the Taproot output key) + Span tweakedXonly = stackalloc byte[64]; + secp256k1.XonlyPubkeyFromPubkey(tweakedXonly, out _, tweakedPubkey); + Span tweakedSerialized = stackalloc byte[32]; + secp256k1.XonlyPubkeySerialize(tweakedSerialized, tweakedXonly); + Console.WriteLine($"Tweaked aggregate key: {Convert.ToHexString(tweakedSerialized)}"); + + // Message to sign + byte[] message = SHA256.HashData("Taproot MuSig2 transaction"u8); + + // Generate nonces (using the TWEAKED keyaggCache) + byte[][] secnonces = new byte[2][]; + byte[][] pubnonces = new byte[2][]; + + // Extra input (optional) + byte[] extraInput = new byte[32]; + + for (int i = 0; i < 2; i++) + { + secnonces[i] = new byte[132]; + pubnonces[i] = new byte[132]; + + byte[] sessionRand = new byte[32]; + RandomNumberGenerator.Fill(sessionRand); + + // Extract secret key from keypair for nonce generation + byte[] secretKey = new byte[32]; + secp256k1.KeypairSec(secretKey, signers[i].Keypair); + + // Note: using the tweaked keyaggCache here + secp256k1.MusigNonceGen(secnonces[i], pubnonces[i], sessionRand, + secretKey, signers[i].Pubkey, message, keyaggCache, extraInput); + } + + // Aggregate nonces + Span aggNonce = stackalloc byte[132]; + secp256k1.MusigNonceAgg(aggNonce, pubnonces); + + // Create session with tweaked keyaggCache + Span session = stackalloc byte[133]; + secp256k1.MusigNonceProcess(session, aggNonce, message, keyaggCache); + + // Create and aggregate partial signatures + byte[][] partialSigs = new byte[2][]; + for (int i = 0; i < 2; i++) + { + partialSigs[i] = new byte[36]; + secp256k1.MusigPartialSign(partialSigs[i], secnonces[i], signers[i].Keypair, keyaggCache, session); + } + + Span finalSignature = stackalloc byte[64]; + secp256k1.MusigPartialSigAgg(finalSignature, session, partialSigs); + + Console.WriteLine($"Final signature: {Convert.ToHexString(finalSignature)}"); + + // Verify against the TWEAKED public key + bool verified = secp256k1.SchnorrsigVerify(finalSignature, message, tweakedXonly); + Console.WriteLine($"Signature valid against tweaked key: {verified}"); + + Console.WriteLine(); + Console.WriteLine("Taproot + MuSig2 use case:"); + Console.WriteLine(" - Multiple parties can jointly control a Taproot output"); + Console.WriteLine(" - The aggregate key becomes the internal key"); + Console.WriteLine(" - After tweaking, it becomes the output key on-chain"); + Console.WriteLine(" - Key-path spend requires all parties to sign"); + Console.WriteLine(" - Script-path can provide fallback/recovery options"); + + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/Program.cs b/Secp256k1.Net.Examples/Program.cs new file mode 100644 index 0000000..9ae62ca --- /dev/null +++ b/Secp256k1.Net.Examples/Program.cs @@ -0,0 +1,126 @@ +using Secp256k1Net.Examples; + +Console.WriteLine("╔══════════════════════════════════════════════════════════════╗"); +Console.WriteLine("║ Secp256k1.Net Examples ║"); +Console.WriteLine("║ Cryptographic Operations Demonstration ║"); +Console.WriteLine("╚══════════════════════════════════════════════════════════════╝"); +Console.WriteLine(); + +// Run all examples by default, or specify which section to run +if (args.Length == 0) +{ + RunAllExamples(); +} +else +{ + RunSelectedExample(args[0].ToLowerInvariant()); +} + +static void RunAllExamples() +{ + KeyGenerationExamples.Run(); + PublicKeyOperationsExamples.Run(); + EcdsaSigningExamples.Run(); + DerSignatureExamples.Run(); + SignatureNormalizationExamples.Run(); + SchnorrSignatureExamples.Run(); + EcdhExamples.Run(); + KeyTweakingExamples.Run(); + HashingExamples.Run(); + AdvancedUsageExamples.Run(); + MuSig2Examples.Run(); + + Console.WriteLine("╔══════════════════════════════════════════════════════════════╗"); + Console.WriteLine("║ All examples completed successfully! ║"); + Console.WriteLine("╚══════════════════════════════════════════════════════════════╝"); +} + +static void RunSelectedExample(string section) +{ + switch (section) + { + case "keys": + case "keygen": + case "key-generation": + KeyGenerationExamples.Run(); + break; + case "pubkey": + case "public-key": + case "public-key-operations": + PublicKeyOperationsExamples.Run(); + break; + case "ecdsa": + case "sign": + case "signing": + EcdsaSigningExamples.Run(); + break; + case "der": + case "der-signature": + DerSignatureExamples.Run(); + break; + case "normalize": + case "normalization": + case "signature-normalization": + SignatureNormalizationExamples.Run(); + break; + case "schnorr": + case "schnorr-signature": + SchnorrSignatureExamples.Run(); + break; + case "ecdh": + case "shared-secret": + EcdhExamples.Run(); + break; + case "tweak": + case "tweaking": + case "key-tweaking": + case "bip32": + KeyTweakingExamples.Run(); + break; + case "hash": + case "hashing": + case "tagged-hash": + HashingExamples.Run(); + break; + case "advanced": + case "instance": + case "low-level": + AdvancedUsageExamples.Run(); + break; + case "musig": + case "musig2": + case "multi-sig": + case "multisig": + MuSig2Examples.Run(); + break; + case "all": + RunAllExamples(); + break; + default: + Console.WriteLine($"Unknown section: {section}"); + Console.WriteLine(); + PrintUsage(); + break; + } +} + +static void PrintUsage() +{ + Console.WriteLine("Usage: dotnet run [section]"); + Console.WriteLine(); + Console.WriteLine("Available sections:"); + Console.WriteLine(" keys, keygen, key-generation - Key Generation & Validation"); + Console.WriteLine(" pubkey, public-key - Public Key Operations"); + Console.WriteLine(" ecdsa, sign, signing - ECDSA Signing & Verification"); + Console.WriteLine(" der, der-signature - DER Signature Format"); + Console.WriteLine(" normalize, normalization - Signature Normalization"); + Console.WriteLine(" schnorr, schnorr-signature - Schnorr Signatures (BIP-340)"); + Console.WriteLine(" ecdh, shared-secret - ECDH Key Agreement"); + Console.WriteLine(" tweak, tweaking, bip32 - Key Tweaking (BIP-32 HD Wallets)"); + Console.WriteLine(" hash, hashing, tagged-hash - Hashing"); + Console.WriteLine(" advanced, instance, low-level - Advanced Usage (Instance Methods)"); + Console.WriteLine(" musig, musig2, multisig - MuSig2 Multi-Signatures"); + Console.WriteLine(" all - Run all examples (default)"); + Console.WriteLine(); + Console.WriteLine("Example: dotnet run schnorr"); +} diff --git a/Secp256k1.Net.Examples/PublicKeyOperationsExamples.cs b/Secp256k1.Net.Examples/PublicKeyOperationsExamples.cs new file mode 100644 index 0000000..1ee4e60 --- /dev/null +++ b/Secp256k1.Net.Examples/PublicKeyOperationsExamples.cs @@ -0,0 +1,131 @@ +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating public key operations. +/// +public static class PublicKeyOperationsExamples +{ + public static void Run() + { + Console.WriteLine("=== Public Key Operations Examples ===\n"); + + CompressPublicKeyExample(); + DecompressPublicKeyExample(); + NegatePublicKeyExample(); + CombinePublicKeysExample(); + } + + /// + /// CompressPublicKey(publicKey) - Convert a public key to 33-byte compressed format + /// + static void CompressPublicKeyExample() + { + Console.WriteLine("--- CompressPublicKey ---"); + + // Start with an uncompressed public key (65 bytes) + var (secretKey, _) = Secp256k1.CreateKeyPair(compressed: true); + byte[] uncompressedKey = Secp256k1.CreatePublicKey(secretKey, compressed: false); + + Console.WriteLine($"Uncompressed key ({uncompressedKey.Length} bytes): {Convert.ToHexString(uncompressedKey)}"); + + // Compress it to 33 bytes + byte[] compressedKey = Secp256k1.CompressPublicKey(uncompressedKey); + + Console.WriteLine($"Compressed key ({compressedKey.Length} bytes): {Convert.ToHexString(compressedKey)}"); + + // Compressing an already-compressed key returns it unchanged + byte[] recompressed = Secp256k1.CompressPublicKey(compressedKey); + Console.WriteLine($"Re-compressed (same): {Convert.ToHexString(compressedKey).Equals(Convert.ToHexString(recompressed))}"); + Console.WriteLine(); + } + + /// + /// DecompressPublicKey(publicKey) - Convert a public key to 65-byte uncompressed format + /// + static void DecompressPublicKeyExample() + { + Console.WriteLine("--- DecompressPublicKey ---"); + + // Start with a compressed public key (33 bytes) + var (_, compressedKey) = Secp256k1.CreateKeyPair(compressed: true); + + Console.WriteLine($"Compressed key ({compressedKey.Length} bytes): {Convert.ToHexString(compressedKey)}"); + + // Decompress it to 65 bytes + byte[] uncompressedKey = Secp256k1.DecompressPublicKey(compressedKey); + + Console.WriteLine($"Uncompressed key ({uncompressedKey.Length} bytes): {Convert.ToHexString(uncompressedKey)}"); + Console.WriteLine($"Prefix byte: 0x{uncompressedKey[0]:X2} (should be 0x04 for uncompressed)"); + Console.WriteLine(); + } + + /// + /// NegatePublicKey(publicKey, compressed) - Negate a public key + /// + static void NegatePublicKeyExample() + { + Console.WriteLine("--- NegatePublicKey ---"); + + var (_, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + + Console.WriteLine($"Original public key: {Convert.ToHexString(publicKey)}"); + + // Negate the public key (returns -P where P is the original point) + byte[] negatedKey = Secp256k1.NegatePublicKey(publicKey, compressed: true); + + Console.WriteLine($"Negated public key: {Convert.ToHexString(negatedKey)}"); + + // Negating twice returns the original key + byte[] doubleNegated = Secp256k1.NegatePublicKey(negatedKey, compressed: true); + Console.WriteLine($"Double negated equals original: {Convert.ToHexString(publicKey).Equals(Convert.ToHexString(doubleNegated))}"); + + // The x-coordinate is the same, only the y-coordinate changes (reflected in the prefix) + Console.WriteLine($"X-coordinates equal: {Convert.ToHexString(publicKey[1..]).Equals(Convert.ToHexString(negatedKey[1..]))}"); + Console.WriteLine(); + } + + /// + /// CombinePublicKeys(publicKeys, compressed) - Add multiple public keys together + /// + static void CombinePublicKeysExample() + { + Console.WriteLine("--- CombinePublicKeys ---"); + + // Generate three key pairs + var (secret1, pubKey1) = Secp256k1.CreateKeyPair(compressed: true); + var (secret2, pubKey2) = Secp256k1.CreateKeyPair(compressed: true); + var (secret3, pubKey3) = Secp256k1.CreateKeyPair(compressed: true); + + Console.WriteLine($"Public key 1: {Convert.ToHexString(pubKey1)}"); + Console.WriteLine($"Public key 2: {Convert.ToHexString(pubKey2)}"); + Console.WriteLine($"Public key 3: {Convert.ToHexString(pubKey3)}"); + + // Combine (add) the public keys: P1 + P2 + P3 + byte[][] keysToAdd = [pubKey1, pubKey2, pubKey3]; + byte[] combinedKey = Secp256k1.CombinePublicKeys(keysToAdd, compressed: true); + + Console.WriteLine($"Combined key (P1+P2+P3): {Convert.ToHexString(combinedKey)}"); + + // This is useful for multi-sig schemes where the combined public key + // corresponds to the sum of individual secret keys + Console.WriteLine(); + + // Demonstrate with two keys + byte[] twoKeyCombined = Secp256k1.CombinePublicKeys([pubKey1, pubKey2], compressed: true); + Console.WriteLine($"Combined key (P1+P2): {Convert.ToHexString(twoKeyCombined)}"); + + // Adding a key and its negation results in the point at infinity (which will throw) + byte[] negatedPubKey1 = Secp256k1.NegatePublicKey(pubKey1, compressed: true); + try + { + Secp256k1.CombinePublicKeys([pubKey1, negatedPubKey1], compressed: true); + } + catch (ArgumentException ex) + { + Console.WriteLine($"Adding P + (-P) throws: {ex.Message}"); + } + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/SchnorrSignatureExamples.cs b/Secp256k1.Net.Examples/SchnorrSignatureExamples.cs new file mode 100644 index 0000000..302a490 --- /dev/null +++ b/Secp256k1.Net.Examples/SchnorrSignatureExamples.cs @@ -0,0 +1,148 @@ +using System.Security.Cryptography; +using System.Text; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating Schnorr signatures (BIP-340). +/// +public static class SchnorrSignatureExamples +{ + public static void Run() + { + Console.WriteLine("=== Schnorr Signatures (BIP-340) Examples ===\n"); + + SignSchnorrExample(); + VerifySchnorrExample(); + SchnorrWithAuxRandExample(); + SchnorrVsEcdsaComparison(); + } + + /// + /// SignSchnorr(messageHash, secretKey, auxRand) - Create a Schnorr signature + /// + static void SignSchnorrExample() + { + Console.WriteLine("--- SignSchnorr ---"); + + // Generate a key pair + var (secretKey, _) = Secp256k1.CreateKeyPair(compressed: true); + + // Get the x-only public key for Schnorr + (byte[] xOnlyPubKey, byte parity) = Secp256k1.CreateXOnlyPublicKey(secretKey); + + // Create a 32-byte message hash (BIP-340 requires exactly 32 bytes) + byte[] messageHash = SHA256.HashData("Schnorr signature test"u8); + + // Generate auxiliary randomness (optional but recommended for side-channel resistance) + byte[] auxRand = RandomNumberGenerator.GetBytes(32); + + // Create the Schnorr signature + byte[] signature = Secp256k1.SignSchnorr(messageHash, secretKey, auxRand); + + Console.WriteLine($"Secret key: {Convert.ToHexString(secretKey)}"); + Console.WriteLine($"X-only public key: {Convert.ToHexString(xOnlyPubKey)}"); + Console.WriteLine($"Message hash: {Convert.ToHexString(messageHash)}"); + Console.WriteLine($"Schnorr signature ({signature.Length} bytes): {Convert.ToHexString(signature)}"); + Console.WriteLine(); + } + + /// + /// VerifySchnorr(signature, message, publicKey) - Verify a Schnorr signature + /// + static void VerifySchnorrExample() + { + Console.WriteLine("--- VerifySchnorr ---"); + + var (secretKey, compressedPubKey) = Secp256k1.CreateKeyPair(compressed: true); + (byte[] xOnlyPubKey, _) = Secp256k1.CreateXOnlyPublicKey(secretKey); + + byte[] messageHash = SHA256.HashData("Verify Schnorr test"u8); + byte[] auxRand = RandomNumberGenerator.GetBytes(32); + byte[] signature = Secp256k1.SignSchnorr(messageHash, secretKey, auxRand); + + // Verify with x-only public key (32 bytes) + bool validWithXOnly = Secp256k1.VerifySchnorr(signature, messageHash, xOnlyPubKey); + Console.WriteLine($"Valid with x-only pubkey (32 bytes): {validWithXOnly}"); + + // Verify with compressed public key (33 bytes) - also works! + bool validWithCompressed = Secp256k1.VerifySchnorr(signature, messageHash, compressedPubKey); + Console.WriteLine($"Valid with compressed pubkey (33 bytes): {validWithCompressed}"); + + // Verify with uncompressed public key (65 bytes) - also works! + byte[] uncompressedPubKey = Secp256k1.DecompressPublicKey(compressedPubKey); + bool validWithUncompressed = Secp256k1.VerifySchnorr(signature, messageHash, uncompressedPubKey); + Console.WriteLine($"Valid with uncompressed pubkey (65 bytes): {validWithUncompressed}"); + + // Verification failure with wrong message + byte[] wrongHash = SHA256.HashData("Wrong message"u8); + bool invalidWrongMessage = Secp256k1.VerifySchnorr(signature, wrongHash, xOnlyPubKey); + Console.WriteLine($"Invalid (wrong message): {invalidWrongMessage}"); + Console.WriteLine(); + } + + /// + /// Demonstrates the role of auxiliary randomness in Schnorr signing. + /// + static void SchnorrWithAuxRandExample() + { + Console.WriteLine("--- Auxiliary Randomness in Schnorr ---"); + + var (secretKey, _) = Secp256k1.CreateKeyPair(compressed: true); + (byte[] xOnlyPubKey, _) = Secp256k1.CreateXOnlyPublicKey(secretKey); + byte[] messageHash = SHA256.HashData("Aux rand test"u8); + + // Sign with different auxiliary randomness produces different signatures + byte[] auxRand1 = RandomNumberGenerator.GetBytes(32); + byte[] auxRand2 = RandomNumberGenerator.GetBytes(32); + + byte[] sig1 = Secp256k1.SignSchnorr(messageHash, secretKey, auxRand1); + byte[] sig2 = Secp256k1.SignSchnorr(messageHash, secretKey, auxRand2); + + Console.WriteLine($"Signature 1: {Convert.ToHexString(sig1)}"); + Console.WriteLine($"Signature 2: {Convert.ToHexString(sig2)}"); + Console.WriteLine($"Signatures are different: {!Convert.ToHexString(sig1).Equals(Convert.ToHexString(sig2))}"); + + // Both signatures are valid + Console.WriteLine($"Signature 1 valid: {Secp256k1.VerifySchnorr(sig1, messageHash, xOnlyPubKey)}"); + Console.WriteLine($"Signature 2 valid: {Secp256k1.VerifySchnorr(sig2, messageHash, xOnlyPubKey)}"); + + Console.WriteLine(); + Console.WriteLine("Note: Auxiliary randomness provides protection against side-channel attacks."); + Console.WriteLine(" Even without it, BIP-340 uses deterministic nonce generation,"); + Console.WriteLine(" so the signature scheme is still secure."); + Console.WriteLine(); + } + + /// + /// Compares Schnorr and ECDSA signatures. + /// + static void SchnorrVsEcdsaComparison() + { + Console.WriteLine("--- Schnorr vs ECDSA Comparison ---"); + + var (secretKey, compressedPubKey) = Secp256k1.CreateKeyPair(compressed: true); + (byte[] xOnlyPubKey, _) = Secp256k1.CreateXOnlyPublicKey(secretKey); + byte[] messageHash = SHA256.HashData("Comparison test"u8); + + // ECDSA signature + byte[] ecdsaSig = Secp256k1.Sign(messageHash, secretKey); + + // Schnorr signature + byte[] auxRand = RandomNumberGenerator.GetBytes(32); + byte[] schnorrSig = Secp256k1.SignSchnorr(messageHash, secretKey, auxRand); + + Console.WriteLine($"ECDSA signature ({ecdsaSig.Length} bytes): {Convert.ToHexString(ecdsaSig)}"); + Console.WriteLine($"Schnorr signature ({schnorrSig.Length} bytes): {Convert.ToHexString(schnorrSig)}"); + + Console.WriteLine(); + Console.WriteLine("Key differences:"); + Console.WriteLine(" - Both signatures are 64 bytes"); + Console.WriteLine(" - Schnorr uses x-only public keys (32 bytes) vs compressed (33 bytes)"); + Console.WriteLine(" - Schnorr signatures are linear (can be aggregated)"); + Console.WriteLine(" - Schnorr has provable security under standard assumptions"); + Console.WriteLine(" - Bitcoin uses Schnorr for Taproot (BIP-340/341/342)"); + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.Examples/Secp256k1.Net.Examples.csproj b/Secp256k1.Net.Examples/Secp256k1.Net.Examples.csproj new file mode 100644 index 0000000..b1e0773 --- /dev/null +++ b/Secp256k1.Net.Examples/Secp256k1.Net.Examples.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/Secp256k1.Net.Examples/SignatureNormalizationExamples.cs b/Secp256k1.Net.Examples/SignatureNormalizationExamples.cs new file mode 100644 index 0000000..040e247 --- /dev/null +++ b/Secp256k1.Net.Examples/SignatureNormalizationExamples.cs @@ -0,0 +1,108 @@ +using System.Security.Cryptography; +using Secp256k1Net; + +namespace Secp256k1Net.Examples; + +/// +/// Examples demonstrating signature normalization (lower-S form). +/// +public static class SignatureNormalizationExamples +{ + public static void Run() + { + Console.WriteLine("=== Signature Normalization Examples ===\n"); + + NormalizeSignatureExample(); + IsNormalizedSignatureExample(); + WhyNormalizationMatters(); + } + + /// + /// NormalizeSignature(signature) - Normalize signature to lower-S form + /// + static void NormalizeSignatureExample() + { + Console.WriteLine("--- NormalizeSignature ---"); + + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("Normalization test"u8); + + // Create a signature (the library already creates normalized signatures by default) + byte[] signature = Secp256k1.Sign(messageHash, secretKey); + + Console.WriteLine($"Original signature: {Convert.ToHexString(signature)}"); + + // Normalize the signature (converts high-S to low-S if necessary) + byte[] normalizedSignature = Secp256k1.NormalizeSignature(signature); + + Console.WriteLine($"Normalized signature: {Convert.ToHexString(normalizedSignature)}"); + + // Both signatures are valid + bool originalValid = Secp256k1.Verify(signature, messageHash, publicKey); + bool normalizedValid = Secp256k1.Verify(normalizedSignature, messageHash, publicKey); + + Console.WriteLine($"Original valid: {originalValid}"); + Console.WriteLine($"Normalized valid: {normalizedValid}"); + Console.WriteLine(); + } + + /// + /// IsNormalizedSignature(signature) - Check if signature is in lower-S form + /// + static void IsNormalizedSignatureExample() + { + Console.WriteLine("--- IsNormalizedSignature ---"); + + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: true); + byte[] messageHash = SHA256.HashData("Check normalization"u8); + + // Create a signature + byte[] signature = Secp256k1.Sign(messageHash, secretKey); + Console.WriteLine($"Signature: {Convert.ToHexString(signature)}"); + + // The secp256k1 library produces normalized (low-S) signatures by default + // NormalizeSignature can be used to normalize signatures from external sources + + // After normalization, the signature should be valid + byte[] normalized = Secp256k1.NormalizeSignature(signature); + Console.WriteLine($"Normalized: {Convert.ToHexString(normalized)}"); + + // Verify the normalized signature works + bool isValid = Secp256k1.Verify(normalized, messageHash, publicKey); + Console.WriteLine($"Normalized signature valid: {isValid}"); + + // Check if normalization changed the signature + bool unchanged = Convert.ToHexString(signature) == Convert.ToHexString(normalized); + Console.WriteLine($"Signature was already normalized: {unchanged}"); + Console.WriteLine(); + } + + /// + /// Explains why signature normalization matters. + /// + static void WhyNormalizationMatters() + { + Console.WriteLine("--- Why Normalization Matters ---"); + + Console.WriteLine(@" +ECDSA signatures have a malleability property: for any valid signature (r, s), +the signature (r, n - s) is also valid, where n is the curve order. + +This means the same message can have two valid signatures, which can cause +problems in systems that rely on signature uniqueness (like Bitcoin). + +BIP-62 and BIP-146 (Bitcoin) require 'low-S' signatures where s <= n/2. +This is also required by Ethereum for transaction signatures. + +The secp256k1 library produces low-S signatures by default, but if you +receive signatures from external sources, you may need to normalize them. + +Use cases for normalization: + - Bitcoin transaction signatures (required by consensus rules) + - Ethereum transaction signatures (required) + - Any system that needs unique/canonical signatures + - Preventing transaction malleability attacks +"); + Console.WriteLine(); + } +} diff --git a/Secp256k1.Net.InteropGen/HeaderParser.cs b/Secp256k1.Net.InteropGen/HeaderParser.cs new file mode 100644 index 0000000..34b81a7 --- /dev/null +++ b/Secp256k1.Net.InteropGen/HeaderParser.cs @@ -0,0 +1,995 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Secp256k1Net.InteropGen; + +public partial class Secp256k1HeaderParser +{ + private readonly List _headerOrder = new() + { + "secp256k1.h", + "secp256k1_preallocated.h", + "secp256k1_recovery.h", + "secp256k1_ecdh.h", + "secp256k1_extrakeys.h", + "secp256k1_schnorrsig.h", + "secp256k1_ellswift.h", + "secp256k1_musig.h" + }; + + public Secp256k1Api ParseDirectory(string includeDir) + { + var api = new Secp256k1Api(); + + foreach (var headerName in _headerOrder) + { + var headerPath = Path.Combine(includeDir, headerName); + if (File.Exists(headerPath)) + { + api.Headers.Add(headerName); + ParseHeader(headerPath, headerName, api); + } + } + + return api; + } + + private void ParseHeader(string headerPath, string headerName, Secp256k1Api api) + { + var content = File.ReadAllText(headerPath); + + // Normalize line endings and remove escaped newlines for multi-line declarations + content = content.Replace("\r\n", "\n"); + + ParseStructs(content, api); + ParseFunctionPointerTypes(content, api); + ParseFunctions(content, headerName, api); + ParseConstants(content, api); + ParseGlobalPointers(content, api); + } + + private void ParseStructs(string content, Secp256k1Api api) + { + // Match typedef struct with data array: typedef struct secp256k1_pubkey { unsigned char data[64]; } secp256k1_pubkey; + var structRegex = StructRegex(); + + foreach (Match match in structRegex.Matches(content)) + { + var name = match.Groups[1].Value; + var size = int.Parse(match.Groups[2].Value); + + // Don't add duplicates + if (api.Structs.Any(s => s.Name == name)) + continue; + + // Get preceding comment + var description = GetPrecedingComment(content, match.Index); + + api.Structs.Add(new StructDef + { + Name = name, + Size = size, + Description = description + }); + } + + // Also match opaque context struct: typedef struct secp256k1_context_struct secp256k1_context; + var opaqueRegex = OpaqueStructRegex(); + foreach (Match match in opaqueRegex.Matches(content)) + { + var name = match.Groups[1].Value; + + if (api.Structs.Any(s => s.Name == name)) + continue; + + var description = GetPrecedingComment(content, match.Index); + + api.Structs.Add(new StructDef + { + Name = name, + Size = 0, // Opaque, size unknown + Description = description + }); + } + } + + private void ParseFunctionPointerTypes(string content, Secp256k1Api api) + { + // Match typedef int (*name)(...); + var funcPtrRegex = FuncPtrTypeRegex(); + + foreach (Match match in funcPtrRegex.Matches(content)) + { + var returnType = match.Groups[1].Value.Trim(); + var name = match.Groups[2].Value; + var paramsStr = match.Groups[3].Value; + + if (api.FunctionPointerTypes.Any(f => f.Name == name)) + continue; + + var description = GetPrecedingComment(content, match.Index); + var parameters = ParseParameters(paramsStr, description); + + api.FunctionPointerTypes.Add(new FunctionPointerType + { + Name = name, + ReturnType = returnType, + Parameters = parameters, + Description = CleanDescription(description) + }); + } + } + + private void ParseFunctions(string content, string headerName, Secp256k1Api api) + { + // First, collapse multi-line function declarations + var collapsedContent = CollapseMultilineDeclarations(content); + + // Match SECP256K1_API functions - use a two-step approach for nested parens + var funcStartRegex = FunctionStartRegex(); + + foreach (Match match in funcStartRegex.Matches(collapsedContent)) + { + var returnType = match.Groups[1].Value.Trim(); + var name = match.Groups[2].Value; + + if (api.Functions.Any(f => f.Name == name)) + continue; + + // Skip invalid function names (macros, keywords, etc.) + if (!name.StartsWith("secp256k1_")) + continue; + + // Extract parameters by finding balanced parentheses + var startPos = match.Index + match.Length; // Position after the opening '(' + var (paramsStr, endPos) = ExtractBalancedParens(collapsedContent, startPos - 1); + + if (string.IsNullOrEmpty(paramsStr)) + continue; + + // Get attributes after the closing paren + var afterParams = collapsedContent.Substring(endPos); + var semiPos = afterParams.IndexOf(';'); + var attributes = semiPos >= 0 ? afterParams.Substring(0, semiPos) : ""; + + var fullDeclaration = collapsedContent.Substring(match.Index, endPos - match.Index + Math.Min(200, collapsedContent.Length - endPos)); + var warnUnused = fullDeclaration.Contains("SECP256K1_WARN_UNUSED_RESULT") || + match.Value.Contains("SECP256K1_WARN_UNUSED_RESULT"); + + // Check for SECP256K1_DEPRECATED macro + var deprecated = attributes.Contains("SECP256K1_DEPRECATED"); + string? deprecatedMessage = null; + if (deprecated) + { + var deprecatedMatch = DeprecatedRegex().Match(attributes); + if (deprecatedMatch.Success) + { + deprecatedMessage = deprecatedMatch.Groups[1].Value; + } + } + + // Extract NONNULL argument positions + var nonnullArgs = new HashSet(); + var nonnullRegex = NonnullRegex(); + foreach (Match nnMatch in nonnullRegex.Matches(attributes)) + { + nonnullArgs.Add(int.Parse(nnMatch.Groups[1].Value)); + } + + // Get description from original content (need to find position) + var originalPos = content.IndexOf(name + "(", StringComparison.Ordinal); + if (originalPos < 0) + originalPos = content.IndexOf(name + " (", StringComparison.Ordinal); + + var description = originalPos >= 0 ? GetPrecedingComment(content, originalPos) : null; + + // Also check if description explicitly marks this function as deprecated + // Look for patterns like "DEPRECATED." or "but DEPRECATED" at function level + // Avoid false positives from mentions of deprecated flags/parameters + if (!deprecated && description != null) + { + // Check for explicit deprecation markers in function description + // e.g., "Same as secp256k1_schnorrsig_sign32, but DEPRECATED." + if (description.Contains("but DEPRECATED") || + description.Contains("DEPRECATED.") || + description.Contains("This function is deprecated")) + { + deprecated = true; + } + } + + var returnDesc = ExtractReturnDescription(description); + var parameters = ParseParameters(paramsStr, description); + + // Apply nonnull annotations + for (int i = 0; i < parameters.Count; i++) + { + parameters[i].Nonnull = nonnullArgs.Contains(i + 1); + } + + api.Functions.Add(new FunctionDef + { + Name = name, + ReturnType = returnType, + WarnUnusedResult = warnUnused, + Deprecated = deprecated, + DeprecatedMessage = deprecatedMessage, + Parameters = parameters, + Description = CleanDescription(description), + ReturnDescription = returnDesc, + SourceHeader = headerName + }); + } + } + + private (string content, int endPos) ExtractBalancedParens(string text, int startPos) + { + if (startPos >= text.Length || text[startPos] != '(') + return ("", startPos); + + var depth = 1; + var pos = startPos + 1; + + while (pos < text.Length && depth > 0) + { + if (text[pos] == '(') depth++; + else if (text[pos] == ')') depth--; + pos++; + } + + if (depth != 0) + return ("", pos); + + // Return content between parens (excluding the parens themselves) + return (text.Substring(startPos + 1, pos - startPos - 2), pos); + } + + private void ParseConstants(string content, Secp256k1Api api) + { + // Match #define SECP256K1_* constants + var constRegex = ConstantRegex(); + + foreach (Match match in constRegex.Matches(content)) + { + var name = "SECP256K1_" + match.Groups[1].Value; + var value = match.Groups[2].Value.Trim(); + + if (api.Constants.Any(c => c.Name == name)) + continue; + + // Skip internal macros + if (name.Contains("GNUC") || name.Contains("API") || name.Contains("BUILD") || + name.Contains("DEPRECATED") || name.Contains("WARN") || name.Contains("NONNULL") || + name.EndsWith("_H") || name.Contains("STATIC")) + continue; + + // Get preceding comment (look for /** comment on previous line) + var description = GetPrecedingComment(content, match.Index); + + // Try to evaluate numeric value + long? numericValue = TryEvaluateConstant(value, api.Constants); + + api.Constants.Add(new ConstantDef + { + Name = name, + Value = value, + NumericValue = numericValue, + Description = description + }); + } + } + + private void ParseGlobalPointers(string content, Secp256k1Api api) + { + // Match SECP256K1_API const type * const name; (global pointers) + var globalRegex = GlobalPointerRegex(); + + foreach (Match match in globalRegex.Matches(content)) + { + var type = match.Groups[1].Value.Trim(); + var name = match.Groups[2].Value; + + if (api.GlobalPointers.Any(g => g.Name == name)) + continue; + + var description = GetPrecedingComment(content, match.Index); + + api.GlobalPointers.Add(new GlobalPointer + { + Name = name, + Type = type, + IsConst = true, + Description = CleanDescription(description) + }); + } + + // Match extern SECP256K1_API const type name; (function pointer constants like nonce_function_rfc6979) + var externRegex = ExternGlobalRegex(); + + foreach (Match match in externRegex.Matches(content)) + { + var type = match.Groups[1].Value.Trim(); + var name = match.Groups[2].Value; + + if (api.GlobalPointers.Any(g => g.Name == name)) + continue; + + var description = GetPrecedingComment(content, match.Index); + + api.GlobalPointers.Add(new GlobalPointer + { + Name = name, + Type = type, + IsConst = true, + Description = CleanDescription(description) + }); + } + + // Match SECP256K1_API const type name; (function pointer variables like nonce_function_rfc6979) + var funcPtrGlobalRegex = GlobalFunctionPointerRegex(); + + foreach (Match match in funcPtrGlobalRegex.Matches(content)) + { + var type = match.Groups[1].Value.Trim(); + var name = match.Groups[2].Value; + + // Skip if it's a function (has opening paren after name) + if (content.IndexOf(name + "(", match.Index, StringComparison.Ordinal) == match.Index + match.Length - name.Length - 1) + continue; + + if (api.GlobalPointers.Any(g => g.Name == name)) + continue; + + var description = GetPrecedingComment(content, match.Index); + + api.GlobalPointers.Add(new GlobalPointer + { + Name = name, + Type = type, + IsConst = true, + Description = CleanDescription(description) + }); + } + } + + private string CollapseMultilineDeclarations(string content) + { + // Collapse lines that are clearly continuations of function declarations + var lines = content.Split('\n'); + var result = new List(); + var currentDecl = ""; + var inDeclaration = false; + var parenDepth = 0; + + foreach (var line in lines) + { + if (!inDeclaration) + { + if (line.Contains("SECP256K1_API") && !line.TrimStart().StartsWith("*") && !line.TrimStart().StartsWith("//")) + { + inDeclaration = true; + currentDecl = line; + parenDepth = line.Count(c => c == '(') - line.Count(c => c == ')'); + + if (parenDepth <= 0 && line.Contains(";")) + { + result.Add(currentDecl); + inDeclaration = false; + currentDecl = ""; + } + } + else + { + result.Add(line); + } + } + else + { + currentDecl += " " + line.Trim(); + parenDepth += line.Count(c => c == '(') - line.Count(c => c == ')'); + + if (parenDepth <= 0 && currentDecl.Contains(";")) + { + result.Add(currentDecl); + inDeclaration = false; + currentDecl = ""; + } + } + } + + if (!string.IsNullOrEmpty(currentDecl)) + result.Add(currentDecl); + + return string.Join("\n", result); + } + + private List ParseParameters(string paramsStr, string? docComment) + { + var parameters = new List(); + + if (string.IsNullOrWhiteSpace(paramsStr) || paramsStr.Trim() == "void") + return parameters; + + // Split by comma, but be careful about nested parens (function pointers) + var paramParts = SplitParameters(paramsStr); + + foreach (var paramPart in paramParts) + { + var param = ParseSingleParameter(paramPart.Trim()); + if (param != null) + { + // Try to get parameter description from doc comment + param.Description = ExtractParameterDescription(docComment, param.Name); + param.Direction = InferDirection(param.Type, param.Description); + // Compute fixed size for this parameter (from name suffix, type, or description) + param.Size = ComputeParameterSize(param.Name, param.Type, param.Description); + // Mark known optional parameters (can be null/empty even with size validation) + param.IsOptional = IsKnownOptionalParam(param.Name, param.Nonnull, param.Description); + parameters.Add(param); + } + } + + // Second pass: identify length parameters and associate them with their buffers + AssociateLengthParameters(parameters); + + // Third pass: clear Size for parameters that have a LengthParam (they're variable-length, not fixed) + foreach (var param in parameters) + { + if (!string.IsNullOrEmpty(param.LengthParam)) + { + param.Size = null; + } + } + + return parameters; + } + + /// + /// Returns true if the parameter is known to be optional/nullable based on its name and attributes. + /// This is used to skip validation for parameters like algo16 which are documented + /// as being NULL for certain use cases. + /// + private static bool IsKnownOptionalParam(string paramName, bool nonnull, string? description) + { + // If marked as nonnull, it's not optional + if (nonnull) return false; + + // algo16 is documented as "will be NULL for ECDSA for compatibility" + if (paramName == "algo16") return true; + + // algo parameters are often optional + if (paramName == "algo") return true; + + // data/d/ndata parameters are typically optional user data pointers + if (paramName == "data" || paramName == "d" || paramName == "ndata") return true; + + // Note: We don't check description text because the parsed descriptions often contain + // text from other parameters (e.g., msg32's description contains "will be NULL" but + // that refers to algo16, not msg32). Relying on explicit parameter names is safer. + + return false; + } + + /// + /// Computes the fixed size in bytes for a parameter based on name suffix, type, or description. + /// Returns null if the size cannot be determined or is variable-length. + /// + private int? ComputeParameterSize(string paramName, string paramType, string? description) + { + // Skip non-pointer types (they don't need size computation) + if (!paramType.Contains("*")) + return null; + + // FIRST: Check for known struct types in the type string + // This takes priority over numeric suffix detection (e.g., pubkey1 should use secp256k1_pubkey size, not "1") + if (paramType.Contains("secp256k1_pubkey")) return 64; + if (paramType.Contains("secp256k1_ecdsa_signature")) return 64; + if (paramType.Contains("secp256k1_ecdsa_recoverable_signature")) return 65; + if (paramType.Contains("secp256k1_xonly_pubkey")) return 64; + if (paramType.Contains("secp256k1_keypair")) return 96; + if (paramType.Contains("secp256k1_musig_keyagg_cache")) return 197; + if (paramType.Contains("secp256k1_musig_secnonce")) return 132; + if (paramType.Contains("secp256k1_musig_pubnonce")) return 132; + if (paramType.Contains("secp256k1_musig_aggnonce")) return 132; + if (paramType.Contains("secp256k1_musig_session")) return 133; + if (paramType.Contains("secp256k1_musig_partial_sig")) return 36; + + // SECOND: Try to extract size from numeric suffix in parameter name (e.g., nonce32 -> 32, algo16 -> 16, ell_a64 -> 64) + var match = NumericSuffixRegex().Match(paramName); + if (match.Success && int.TryParse(match.Groups[1].Value, out var sizeFromName)) + { + return sizeFromName; + } + + // Check parameter name patterns for common fixed-size buffers without numeric suffixes + if (paramName.Contains("seckey") || paramName.Contains("tweak")) + return 32; + + // "output" in ECDH and similar functions expects at least 32 bytes + if (paramName == "output" && description?.Contains("filled") == true) + return 32; + + // Try to extract size from description (e.g., "32-byte array", "a 64 byte buffer") + // But skip if description indicates conditional/variable size (e.g., "65-byte (if compressed==0) or 33-byte") + if (!string.IsNullOrEmpty(description)) + { + // Skip if description mentions "or X-byte" or "(if" which indicates variable size + if (!description.Contains(" or ") && !description.Contains("(if")) + { + var descMatch = DescriptionSizeRegex().Match(description); + if (descMatch.Success && int.TryParse(descMatch.Groups[1].Value, out var sizeFromDesc)) + { + return sizeFromDesc; + } + } + } + + // Default - no fixed size (variable length or unknown) + return null; + } + + /// + /// Associates length parameters with their corresponding buffer parameters. + /// For example, if there's a "msg" buffer followed by "msglen", this will set + /// msg.LengthParam = "msglen" and msglen.IsLengthFor = "msg". + /// + private void AssociateLengthParameters(List parameters) + { + for (int i = 0; i < parameters.Count; i++) + { + var param = parameters[i]; + + // Check if this is a length/size parameter + if (!param.Type.Contains("size_t") || param.Type.Contains("*")) + continue; + + // Common patterns for length parameters: + // - "msglen" for "msg" + // - "inputlen" for "input" + // - "n_pubkeys" for "pubkeys" + // - "n_sigs" for "sigs" + + string? bufferName = null; + + // Pattern 1: paramlen (e.g., msglen -> msg) + if (param.Name.EndsWith("len")) + { + bufferName = param.Name[..^3]; // Remove "len" + } + // Pattern 2: param_len (e.g., input_len -> input) + else if (param.Name.EndsWith("_len")) + { + bufferName = param.Name[..^4]; // Remove "_len" + } + // Pattern 3: n_params (e.g., n_pubkeys -> pubkeys) + else if (param.Name.StartsWith("n_")) + { + bufferName = param.Name[2..]; // Remove "n_" + } + // Pattern 4: Simple "n" typically refers to the immediately preceding array + else if (param.Name == "n" && i > 0) + { + // Look for preceding parameter that looks like an array + for (int j = i - 1; j >= 0; j--) + { + if (parameters[j].Type.Contains("**") || parameters[j].Type.Contains("* const*")) + { + bufferName = parameters[j].Name; + break; + } + } + } + + if (bufferName == null) + continue; + + // Find the matching buffer parameter + var bufferParam = parameters.FirstOrDefault(p => p.Name == bufferName); + if (bufferParam != null) + { + bufferParam.LengthParam = param.Name; + param.IsLengthFor = bufferName; + } + } + } + + [GeneratedRegex(@"(\d+)$")] + private static partial Regex NumericSuffixRegex(); + + [GeneratedRegex(@"(\d+)[- ]?byte", RegexOptions.IgnoreCase)] + private static partial Regex DescriptionSizeRegex(); + + private List SplitParameters(string paramsStr) + { + var result = new List(); + var current = ""; + var parenDepth = 0; + + foreach (var c in paramsStr) + { + if (c == '(') parenDepth++; + else if (c == ')') parenDepth--; + + if (c == ',' && parenDepth == 0) + { + result.Add(current); + current = ""; + } + else + { + current += c; + } + } + + if (!string.IsNullOrWhiteSpace(current)) + result.Add(current); + + return result; + } + + private ParameterDef? ParseSingleParameter(string param) + { + if (string.IsNullOrWhiteSpace(param)) + return null; + + // Handle inline function pointer parameters: void (*name)(args) or type (*name)(args) + // Pattern: return_type (*name)(params) + var funcPtrMatch = FuncPtrParamRegex().Match(param); + if (funcPtrMatch.Success) + { + return new ParameterDef + { + Name = funcPtrMatch.Groups[2].Value, + Type = $"{funcPtrMatch.Groups[1].Value.Trim()} (*)({funcPtrMatch.Groups[3].Value})" + }; + } + + // Alternative pattern for function pointers: void (*fun)(const char *message, void *data) + var simpleFuncPtrMatch = SimpleFuncPtrParamRegex().Match(param); + if (simpleFuncPtrMatch.Success) + { + var returnType = simpleFuncPtrMatch.Groups[1].Value.Trim(); + var funcName = simpleFuncPtrMatch.Groups[2].Value; + var funcParams = simpleFuncPtrMatch.Groups[3].Value; + return new ParameterDef + { + Name = funcName, + Type = $"{returnType} (*)({funcParams})" + }; + } + + // Handle array parameters: type name[size] or type name[] + var arrayMatch = ArrayParamRegex().Match(param); + if (arrayMatch.Success) + { + var baseType = arrayMatch.Groups[1].Value.Trim(); + var arrayName = arrayMatch.Groups[2].Value; + return new ParameterDef + { + Name = arrayName, + Type = baseType + "*" // Treat arrays as pointers + }; + } + + // Handle regular parameters: type name or type * name or type *name + // Also handle: const type *name, const type * const *name, etc. + var parts = param.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0) + return null; + + // Find the name (last identifier that's not a pointer/const modifier) + string name; + + // The name is the last part, possibly with leading * + var lastPart = parts[^1]; + while (lastPart.StartsWith("*")) + { + lastPart = lastPart[1..]; + } + name = lastPart; + + // Build the type from remaining parts + var typeParts = new List(); + for (int i = 0; i < parts.Length - 1; i++) + { + typeParts.Add(parts[i]); + } + + // Add back any pointers from the last part + var pointers = parts[^1].TakeWhile(c => c == '*').Count(); + var typeStr = string.Join(" ", typeParts); + if (pointers > 0) + { + typeStr += new string('*', pointers); + } + + if (string.IsNullOrEmpty(name)) + return null; + + return new ParameterDef + { + Name = name, + Type = typeStr.Trim() + }; + } + + private string? InferDirection(string type, string? description) + { + var descLower = description?.ToLowerInvariant() ?? ""; + + if (descLower.Contains("(output)") || descLower.StartsWith("out:")) + return "out"; + if (descLower.Contains("(input)") || descLower.StartsWith("in:")) + return "in"; + if (descLower.Contains("(input/output)") || descLower.Contains("in/out:")) + return "inout"; + + // Infer from type + if (type.Contains("const")) + return "in"; + if (type.Contains("*") && !type.Contains("const")) + return "out"; // Non-const pointer is likely output + + return null; + } + + private string? GetPrecedingComment(string content, int position) + { + // Look backwards for /** ... */ comment + var searchStart = Math.Max(0, position - 5000); + var searchContent = content.Substring(searchStart, position - searchStart); + + // Find the last /** ... */ block + var commentEnd = searchContent.LastIndexOf("*/", StringComparison.Ordinal); + if (commentEnd < 0) + return null; + + var commentStart = searchContent.LastIndexOf("/**", commentEnd, StringComparison.Ordinal); + if (commentStart < 0) + return null; + + // Make sure there's no code between the comment and our target + var between = searchContent.Substring(commentEnd + 2); + if (between.Contains(";") || between.Contains("{")) + { + // There's a statement between - this comment isn't for us + return null; + } + + return searchContent.Substring(commentStart, commentEnd + 2 - commentStart); + } + + private string? ExtractReturnDescription(string? docComment) + { + if (string.IsNullOrEmpty(docComment)) + return null; + + // Look for "Returns:" section + var match = ReturnsRegex().Match(docComment); + if (match.Success) + { + var returnDesc = match.Groups[1].Value; + // Clean up and capture multi-line return descriptions + return CleanMultilineText(returnDesc); + } + + return null; + } + + private string? ExtractParameterDescription(string? docComment, string paramName) + { + if (string.IsNullOrEmpty(docComment)) + return null; + + // Look for param in Args:, In:, Out:, or In/Out: sections + // The description ends when we hit: + // - Another section marker (Args:, In:, Out:, In/Out:, Returns:) + // - Another parameter name pattern (word followed by colon at start of description area) + // - End of comment + + // Pattern to match the start of the parameter description + var startPatterns = new[] + { + $@"\*\s*(?:Args|In|Out|In/Out):\s*{Regex.Escape(paramName)}:\s*", + $@"\*\s+{Regex.Escape(paramName)}:\s*" + }; + + foreach (var startPattern in startPatterns) + { + var startMatch = Regex.Match(docComment, startPattern, RegexOptions.IgnoreCase); + if (startMatch.Success) + { + // Find where description starts + var descStart = startMatch.Index + startMatch.Length; + var remaining = docComment.Substring(descStart); + + // Find where description ends - look for next parameter or section marker + // Pattern: newline, optional whitespace, *, optional whitespace, then either: + // - A section marker like "In:", "Out:", "In/Out:", "Args:", "Returns:" + // - A parameter name pattern: "word:" at the start of the content area + var endPattern = @"\n\s*\*\s*(?:(?:Args|In|Out|In/Out|Returns):|\s*\w+:\s)"; + var endMatch = Regex.Match(remaining, endPattern); + + string description; + if (endMatch.Success) + { + description = remaining.Substring(0, endMatch.Index); + } + else + { + // No next param found, take until end of comment (but stop at */) + var commentEnd = remaining.IndexOf("*/"); + description = commentEnd >= 0 ? remaining.Substring(0, commentEnd) : remaining; + } + + return CleanMultilineText(description); + } + } + + return null; + } + + private string? CleanDescription(string? comment) + { + if (string.IsNullOrEmpty(comment)) + return null; + + // Remove /** and */ markers + var text = comment + .Replace("/**", "") + .Replace("*/", "") + .Trim(); + + // Remove leading * from each line, preserving empty lines as paragraph breaks + var lines = text.Split('\n') + .Select(l => l.TrimStart().TrimStart('*').TrimStart()) + .ToList(); + + // Take just the first paragraph (up to Returns: or Args:) + var result = new List(); + foreach (var line in lines) + { + if (line.StartsWith("Returns:") || line.StartsWith("Args:") || + line.StartsWith("In:") || line.StartsWith("Out:")) + break; + result.Add(line); + } + + // Remove trailing empty lines + while (result.Count > 0 && string.IsNullOrWhiteSpace(result[^1])) + result.RemoveAt(result.Count - 1); + + // Remove leading empty lines + while (result.Count > 0 && string.IsNullOrWhiteSpace(result[0])) + result.RemoveAt(0); + + return result.Count > 0 ? string.Join("\n", result) : null; + } + + private string CleanMultilineText(string text) + { + var lines = text.Split('\n') + .Select(l => l.TrimStart().TrimStart('*').TrimStart()) + .ToList(); + + // Remove trailing empty lines + while (lines.Count > 0 && string.IsNullOrWhiteSpace(lines[^1])) + lines.RemoveAt(lines.Count - 1); + + // Remove leading empty lines + while (lines.Count > 0 && string.IsNullOrWhiteSpace(lines[0])) + lines.RemoveAt(0); + + return string.Join("\n", lines); + } + + private long? TryEvaluateConstant(string value, List existingConstants) + { + // Try direct numeric + if (long.TryParse(value, out var num)) + return num; + + // Try hex + if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + if (long.TryParse(value[2..], System.Globalization.NumberStyles.HexNumber, null, out var hex)) + return hex; + } + + // Try to evaluate expressions like (1 << 8) or (A | B) + try + { + // Replace known constants + var evalExpr = value; + foreach (var c in existingConstants) + { + if (c.NumericValue.HasValue) + { + evalExpr = evalExpr.Replace(c.Name, c.NumericValue.Value.ToString()); + } + } + + // Simple expression evaluation for bit shifts and OR + evalExpr = evalExpr.Replace("(", "").Replace(")", ""); + + if (evalExpr.Contains("<<")) + { + var parts = evalExpr.Split("<<").Select(p => p.Trim()).ToArray(); + if (parts.Length == 2 && long.TryParse(parts[0], out var left) && int.TryParse(parts[1], out var shift)) + { + return left << shift; + } + } + + if (evalExpr.Contains("|")) + { + var parts = evalExpr.Split('|').Select(p => p.Trim()).ToArray(); + long result = 0; + foreach (var part in parts) + { + if (long.TryParse(part, out var partVal)) + result |= partVal; + else + return null; + } + return result; + } + } + catch + { + // Ignore evaluation errors + } + + return null; + } + + // Compiled regex patterns + [GeneratedRegex(@"typedef\s+struct\s+(\w+)\s*\{\s*unsigned\s+char\s+data\[(\d+)\];\s*\}\s*\1;", RegexOptions.Singleline)] + private static partial Regex StructRegex(); + + [GeneratedRegex(@"typedef\s+struct\s+\w+\s+(\w+);")] + private static partial Regex OpaqueStructRegex(); + + [GeneratedRegex(@"typedef\s+(\w+(?:\s*\*)?)\s*\(\*(\w+)\)\s*\(([^)]*)\);", RegexOptions.Singleline)] + private static partial Regex FuncPtrTypeRegex(); + + // This regex captures everything up to the function name and first paren - we'll extract params manually + [GeneratedRegex(@"SECP256K1_API\s+(?:SECP256K1_WARN_UNUSED_RESULT\s+)?(\w+(?:\s*\*)*)\s*(\w+)\s*\(", RegexOptions.Singleline)] + private static partial Regex FunctionStartRegex(); + + [GeneratedRegex(@"SECP256K1_ARG_NONNULL\((\d+)\)")] + private static partial Regex NonnullRegex(); + + [GeneratedRegex(@"SECP256K1_DEPRECATED\s*\(\s*""([^""]*)""\s*\)")] + private static partial Regex DeprecatedRegex(); + + [GeneratedRegex(@"#define\s+SECP256K1_(\w+)\s+(.+)$", RegexOptions.Multiline)] + private static partial Regex ConstantRegex(); + + [GeneratedRegex(@"SECP256K1_API\s+const\s+(\w+)\s*\*\s*const\s+(\w+)")] + private static partial Regex GlobalPointerRegex(); + + [GeneratedRegex(@"extern\s+(?:const\s+)?(\w+)\s+(\w+);")] + private static partial Regex ExternGlobalRegex(); + + [GeneratedRegex(@"SECP256K1_API\s+const\s+(\w+)\s+(secp256k1_\w+);")] + private static partial Regex GlobalFunctionPointerRegex(); + + // Pattern for function pointer params: returnType (*name)(params) + // The params can contain nested parens and commas + [GeneratedRegex(@"^(\w+(?:\s+\w+)*)\s*\(\s*\*\s*(\w+)\s*\)\s*\((.+)\)\s*$")] + private static partial Regex FuncPtrParamRegex(); + + // Simpler pattern for: void (*fun)(const char *message, void *data) + [GeneratedRegex(@"^(\w+)\s+\(\s*\*\s*(\w+)\s*\)\s*\((.+)\)\s*$")] + private static partial Regex SimpleFuncPtrParamRegex(); + + [GeneratedRegex(@"(.+?)\s+(\w+)\s*\[\s*\d*\s*\]")] + private static partial Regex ArrayParamRegex(); + + [GeneratedRegex(@"Returns:\s*(.+?)(?=\s*\*\s*(?:Args|In|Out|$))", RegexOptions.Singleline)] + private static partial Regex ReturnsRegex(); +} diff --git a/Secp256k1.Net.InteropGen/InteropGenerator.cs b/Secp256k1.Net.InteropGen/InteropGenerator.cs new file mode 100644 index 0000000..c3af9f2 --- /dev/null +++ b/Secp256k1.Net.InteropGen/InteropGenerator.cs @@ -0,0 +1,2286 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Secp256k1Net.InteropGen; + +public class InteropGenerator +{ + public string GenerateNative(Secp256k1Api api) + { + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("#nullable enable"); + sb.AppendLine(); + sb.AppendLine("using System;"); + sb.AppendLine("using System.Runtime.InteropServices;"); + sb.AppendLine(); + + // Collect all unique function pointer signatures and generate type aliases (inside namespace, after using System) + var signatureToAlias = CollectFunctionPointerSignatures(api); + + sb.AppendLine("#if NET8_0_OR_GREATER"); + foreach (var kvp in signatureToAlias.OrderBy(x => x.Value)) + { + // Use fully-qualified System.IntPtr in using aliases since they're processed before using System; + var fullyQualifiedSignature = kvp.Key.Replace("IntPtr", "System.IntPtr"); + sb.AppendLine($"using unsafe {kvp.Value} = {fullyQualifiedSignature};"); + } + sb.AppendLine("#endif"); + sb.AppendLine(); + + sb.AppendLine("namespace Secp256k1Net"); + sb.AppendLine("{"); + + // Generate function pointer type delegates that are used in public/internal APIs (available for all targets) + // These are needed for callback marshaling - must be available for both legacy and modern targets + var publicDelegateTypes = new HashSet + { + "secp256k1_ecdh_hash_function", + "secp256k1_nonce_function", + "secp256k1_nonce_function_hardened", + "secp256k1_ellswift_xdh_hash_function", + }; + foreach (var fpType in api.FunctionPointerTypes.Where(f => publicDelegateTypes.Contains(f.Name))) + { + GenerateFunctionPointerTypeDelegate(sb, fpType); + } + + // Generate remaining function pointer type delegates (legacy only) + sb.AppendLine("#if !NET8_0_OR_GREATER"); + + foreach (var fpType in api.FunctionPointerTypes.Where(f => !publicDelegateTypes.Contains(f.Name))) + { + GenerateFunctionPointerTypeDelegate(sb, fpType); + } + + // Generate function delegates + foreach (var func in api.Functions) + { + GenerateFunctionDelegate(sb, func); + } + + sb.AppendLine("#endif"); + sb.AppendLine(); + sb.AppendLine(" internal static unsafe class Secp256k1Interop"); + sb.AppendLine(" {"); + + // Generate symbol name constants + sb.AppendLine(" // Native function symbol names"); + foreach (var func in api.Functions) + { + var symbolName = GetSymbolConstName(func.Name); + sb.AppendLine($" private const string {symbolName} = \"{func.Name}\";"); + } + foreach (var global in api.GlobalPointers.Where(g => g.Type.StartsWith("secp256k1_") && g.Type.Contains("function"))) + { + var symbolName = GetSymbolConstName(global.Name); + sb.AppendLine($" private const string {symbolName} = \"{global.Name}\";"); + } + sb.AppendLine(); + + // Modern .NET 8+ section with function pointers + sb.AppendLine("#if NET8_0_OR_GREATER"); + GenerateModernFunctionPointers(sb, api, signatureToAlias); + sb.AppendLine("#else"); + GenerateLegacyDelegates(sb, api); + sb.AppendLine("#endif"); + + // Generate LoadFunctions method + sb.AppendLine(); + sb.AppendLine(" internal static void LoadFunctions(IntPtr lib)"); + sb.AppendLine(" {"); + sb.AppendLine("#if NET8_0_OR_GREATER"); + GenerateModernLoadFunctions(sb, api, signatureToAlias); + sb.AppendLine("#else"); + GenerateLegacyLoadFunctions(sb, api); + sb.AppendLine("#endif"); + sb.AppendLine(" }"); + + sb.AppendLine(" }"); + sb.AppendLine("}"); + + return sb.ToString(); + } + + private void GenerateFunctionPointerTypeDelegate(StringBuilder sb, FunctionPointerType fpType) + { + sb.AppendLine(); + if (!string.IsNullOrEmpty(fpType.Description)) + { + sb.AppendLine($" /// {FormatXmlDescription(fpType.Description)}"); + } + sb.AppendLine(" [UnmanagedFunctionPointer(CallingConvention.Cdecl)]"); + + var returnType = MapCTypeToCSharp(fpType.ReturnType); + var hasPointerParams = fpType.Parameters.Any(p => p.Type.Contains("*")); + + sb.Append($" internal {(hasPointerParams ? "unsafe " : "")}delegate {returnType} {fpType.Name}("); + + var paramStrings = fpType.Parameters.Select(p => + { + var csType = MapCTypeToCSharp(p.Type, p.Name); + return $"{csType} {SanitizeParamName(p.Name)}"; + }); + + sb.Append(string.Join(", ", paramStrings)); + sb.AppendLine(");"); + } + + private void GenerateFunctionDelegate(StringBuilder sb, FunctionDef func) + { + sb.AppendLine(); + if (!string.IsNullOrEmpty(func.Description)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.Description)}"); + } + + foreach (var param in func.Parameters) + { + // Always generate param tags to avoid CS1573 warnings + var description = FormatXmlDescription(param.Description); + sb.AppendLine($" /// {description}"); + } + + if (!string.IsNullOrEmpty(func.ReturnDescription)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.ReturnDescription)}"); + } + + var returnType = MapCTypeToCSharp(func.ReturnType); + var hasPointerParams = func.Parameters.Any(p => p.Type.Contains("*")); + + // Create delegate name from function name + var delegateName = func.Name; + + sb.Append($" internal {(hasPointerParams ? "unsafe " : "")}delegate {returnType} {delegateName}("); + + var paramStrings = func.Parameters.Select(p => + { + var csType = MapCTypeToCSharp(p.Type, p.Name); + var paramName = SanitizeParamName(p.Name); + return $"{csType} {paramName}"; + }); + + sb.Append(string.Join(", ", paramStrings)); + sb.AppendLine(");"); + } + + private Dictionary CollectFunctionPointerSignatures(Secp256k1Api api) + { + var signatureToAlias = new Dictionary(); + var aliasCounter = 0; + + // Collect signatures from all functions + foreach (var func in api.Functions) + { + var signature = GetModernFunctionPointerType(func); + if (!signatureToAlias.ContainsKey(signature)) + { + signatureToAlias[signature] = $"FnPtr{aliasCounter++:D2}"; + } + } + + // Collect signatures from global function pointers + foreach (var global in api.GlobalPointers.Where(g => g.Type.StartsWith("secp256k1_") && g.Type.Contains("function"))) + { + var signature = GetModernFunctionPointerTypeForGlobal(global, api); + if (!signatureToAlias.ContainsKey(signature)) + { + signatureToAlias[signature] = $"FnPtr{aliasCounter++:D2}"; + } + } + + return signatureToAlias; + } + + private void GenerateModernFunctionPointers(StringBuilder sb, Secp256k1Api api, Dictionary signatureToAlias) + { + sb.AppendLine(" // Function pointer declarations (modern .NET 8+)"); + sb.AppendLine("#nullable disable"); + + foreach (var func in api.Functions) + { + var fieldName = GetFieldName(func.Name); + var funcPtrType = GetModernFunctionPointerType(func); + var alias = signatureToAlias[funcPtrType]; + sb.AppendLine($" internal static {alias} {fieldName};"); + } + + // Global function pointer variables + foreach (var global in api.GlobalPointers.Where(g => g.Type.StartsWith("secp256k1_") && g.Type.Contains("function"))) + { + var fieldName = GetFieldName(global.Name); + var funcPtrType = GetModernFunctionPointerTypeForGlobal(global, api); + var alias = signatureToAlias[funcPtrType]; + sb.AppendLine($" internal static {alias} {fieldName};"); + } + + sb.AppendLine("#nullable restore"); + } + + private void GenerateLegacyDelegates(StringBuilder sb, Secp256k1Api api) + { + sb.AppendLine(" // Delegate instance fields (legacy .NET)"); + sb.AppendLine("#nullable disable"); + + foreach (var func in api.Functions) + { + var fieldName = GetFieldName(func.Name); + var delegateType = func.Name; + sb.AppendLine($" internal static {delegateType} {fieldName};"); + } + + // Global function pointer variables use their typedef type + foreach (var global in api.GlobalPointers.Where(g => g.Type.StartsWith("secp256k1_") && g.Type.Contains("function"))) + { + var fieldName = GetFieldName(global.Name); + sb.AppendLine($" internal static {global.Type} {fieldName};"); + } + + sb.AppendLine("#nullable restore"); + } + + private void GenerateModernLoadFunctions(StringBuilder sb, Secp256k1Api api, Dictionary signatureToAlias) + { + foreach (var func in api.Functions) + { + var fieldName = GetFieldName(func.Name); + var symbolName = GetSymbolConstName(func.Name); + var funcPtrType = GetModernFunctionPointerType(func); + var alias = signatureToAlias[funcPtrType]; + + sb.AppendLine($" {fieldName} = ({alias})NativeLibrary.GetExport(lib, {symbolName});"); + } + + // Global function pointers are data symbols - need to read the pointer + foreach (var global in api.GlobalPointers.Where(g => g.Type.StartsWith("secp256k1_") && g.Type.Contains("function"))) + { + var fieldName = GetFieldName(global.Name); + var symbolName = GetSymbolConstName(global.Name); + var funcPtrType = GetModernFunctionPointerTypeForGlobal(global, api); + var alias = signatureToAlias[funcPtrType]; + + sb.AppendLine(); + sb.AppendLine($" // {global.Name} is a data symbol (function pointer), not a function"); + sb.AppendLine($" var {fieldName}Ptr = NativeLibrary.GetExport(lib, {symbolName});"); + sb.AppendLine($" {fieldName} = ({alias})Marshal.ReadIntPtr({fieldName}Ptr);"); + } + } + + private void GenerateLegacyLoadFunctions(StringBuilder sb, Secp256k1Api api) + { + foreach (var func in api.Functions) + { + var fieldName = GetFieldName(func.Name); + var symbolName = GetSymbolConstName(func.Name); + var delegateType = func.Name; + + sb.AppendLine($" {fieldName} = LoadLibNative.GetDelegate<{delegateType}>(lib, {symbolName});"); + } + + // Global function pointers are data symbols + foreach (var global in api.GlobalPointers.Where(g => g.Type.StartsWith("secp256k1_") && g.Type.Contains("function"))) + { + var fieldName = GetFieldName(global.Name); + var symbolName = GetSymbolConstName(global.Name); + + sb.AppendLine(); + sb.AppendLine($" // {global.Name} is a data symbol (function pointer), not a function"); + sb.AppendLine($" {fieldName} = LoadLibNative.GetDelegate<{global.Type}>(lib, {symbolName}, Marshal.ReadIntPtr);"); + } + } + + private string GetModernFunctionPointerType(FunctionDef func) + { + var returnType = MapCTypeToCSharpForFunctionPointer(func.ReturnType); + + var paramTypes = func.Parameters.Select(p => MapCTypeToCSharpForFunctionPointer(p.Type, p.Name)).ToList(); + + if (paramTypes.Count == 0) + { + return $"delegate* unmanaged[Cdecl]<{returnType}>"; + } + + return $"delegate* unmanaged[Cdecl]<{string.Join(", ", paramTypes)}, {returnType}>"; + } + + private string GetModernFunctionPointerTypeForGlobal(GlobalPointer global, Secp256k1Api api) + { + // Find the function pointer type definition + var fpType = api.FunctionPointerTypes.FirstOrDefault(f => f.Name == global.Type); + if (fpType == null) + { + // Fallback - return a generic function pointer + return "delegate* unmanaged[Cdecl]"; + } + + var returnType = MapCTypeToCSharpForFunctionPointer(fpType.ReturnType); + var paramTypes = fpType.Parameters.Select(p => MapCTypeToCSharpForFunctionPointer(p.Type, p.Name)).ToList(); + + if (paramTypes.Count == 0) + { + return $"delegate* unmanaged[Cdecl]<{returnType}>"; + } + + return $"delegate* unmanaged[Cdecl]<{string.Join(", ", paramTypes)}, {returnType}>"; + } + + private string MapCTypeToCSharp(string cType, string? paramName = null) + { + cType = cType.Trim(); + + // Handle inline function pointer types like "void (*)(const char*, void*)" + if (cType.Contains("(*)")) + return "IntPtr"; + + // Handle specific secp256k1 types + // Note: check for "secp256k1_context" with Contains() to handle "const secp256k1_context*" + if (cType.Contains("secp256k1_context") && cType.Contains("*")) + return "IntPtr"; + if (cType == "secp256k1_context") + return "IntPtr"; + + // Function pointer types as parameters + if (cType.Contains("secp256k1_") && cType.Contains("function")) + return "IntPtr"; + + // Handle pointer types + // Double pointers (** or "* const*" pattern) become IntPtr + if (cType.EndsWith("**") || cType.Contains("* const*") || cType.Contains("**")) + return "IntPtr"; + + if (cType.Contains("*")) + { + + // Most pointer types become void* + if (cType.Contains("unsigned char") || cType.Contains("char")) + return "void*"; + if (cType.Contains("secp256k1_")) + return "void*"; + if (cType.Contains("void")) + return "void*"; + if (cType.Contains("size_t")) + return "nuint*"; + if (cType.Contains("int") && !cType.Contains("uint")) + return "int*"; + + return "void*"; + } + + // Non-pointer types + return cType switch + { + "unsigned int" => "uint", + "int" => "int", + "size_t" => "nuint", + "uint64_t" => "ulong", + "int64_t" => "long", + "uint32_t" => "uint", + "int32_t" => "int", + "void" => "void", + _ => cType + }; + } + + private string MapCTypeToCSharpForFunctionPointer(string cType, string? paramName = null) + { + cType = cType.Trim(); + + // Handle inline function pointer types like "void (*)(const char*, void*)" + if (cType.Contains("(*)")) + return "IntPtr"; + + // Handle specific secp256k1 types + // Note: check for "secp256k1_context" with Contains() to handle "const secp256k1_context*" + if (cType.Contains("secp256k1_context") && cType.Contains("*")) + return "IntPtr"; + if (cType == "secp256k1_context") + return "IntPtr"; + + // Function pointer types as parameters + if (cType.Contains("secp256k1_") && cType.Contains("function")) + return "IntPtr"; + + // Handle pointer types + // Double pointers (** or "* const*" pattern) become IntPtr + if (cType.EndsWith("**") || cType.Contains("* const*") || cType.Contains("**")) + return "IntPtr"; + + if (cType.Contains("*")) + { + + if (cType.Contains("unsigned char") || cType.Contains("char")) + return "void*"; + if (cType.Contains("secp256k1_")) + return "void*"; + if (cType.Contains("void")) + return "void*"; + if (cType.Contains("size_t")) + return "nuint*"; + if (cType.Contains("int") && !cType.Contains("uint")) + return "int*"; + + return "void*"; + } + + // Non-pointer types + return cType switch + { + "unsigned int" => "uint", + "int" => "int", + "size_t" => "nuint", + "uint64_t" => "ulong", + "int64_t" => "long", + "uint32_t" => "uint", + "int32_t" => "int", + "void" => "void", + _ => cType + }; + } + + private static string GetFieldName(string functionName) + { + // secp256k1_context_create -> _context_create + if (functionName.StartsWith("secp256k1_")) + { + return "_" + functionName.Substring("secp256k1_".Length); + } + return "_" + functionName; + } + + private static string GetSymbolConstName(string functionName) + { + // secp256k1_context_create -> SYM_context_create + if (functionName.StartsWith("secp256k1_")) + { + return "SYM_" + functionName.Substring("secp256k1_".Length); + } + return "SYM_" + functionName; + } + + private static string SanitizeParamName(string name) + { + // Handle C# reserved words + return name switch + { + "data" => "data", + "output" => "output", + "input" => "input", + "in" => "@in", + "out" => "@out", + "ref" => "@ref", + _ => name + }; + } + + private static string EscapeXml(string text) + { + return text + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'"); + } + + private static string CleanDescription(string? text) + { + if (text is null || text.Length == 0) + return ""; + + // Remove C comment markers and clean up + var result = text + .Replace("\r", "") + .Replace("/**", "") + .Replace("*/", "") + .Replace("\n * ", "\n") // Convert " * " line prefix to just newline + .Replace("\n *", "\n"); // Handle " *" without trailing space + + // Clean up multiple consecutive newlines + while (result.Contains("\n\n\n")) + result = result.Replace("\n\n\n", "\n\n"); + + return result.Trim(); + } + + /// + /// Formats a description for XML documentation, handling multi-line text. + /// Uses para tags to create proper paragraph breaks that IntelliSense will render. + /// + private static string FormatXmlDescription(string? text) + { + if (text is null || text.Length == 0) + return ""; + + var cleaned = CleanDescription(text); + var escaped = EscapeXml(cleaned); + + // If single line, return as-is + if (!escaped.Contains('\n')) + return escaped; + + // Split into paragraphs (separated by blank lines) and lines within paragraphs + var paragraphs = new List(); + var currentParagraph = new List(); + + foreach (var line in escaped.Split('\n')) + { + if (string.IsNullOrWhiteSpace(line)) + { + if (currentParagraph.Count > 0) + { + paragraphs.Add(string.Join(" ", currentParagraph)); + currentParagraph.Clear(); + } + } + else + { + currentParagraph.Add(line.Trim()); + } + } + + if (currentParagraph.Count > 0) + { + paragraphs.Add(string.Join(" ", currentParagraph)); + } + + // If only one paragraph, return as single line + if (paragraphs.Count == 1) + return paragraphs[0]; + + // Multiple paragraphs - use tags for proper IntelliSense rendering + var result = new StringBuilder(); + for (int i = 0; i < paragraphs.Count; i++) + { + if (i == 0) + { + // First paragraph without para tag + result.Append(paragraphs[i]); + } + else + { + // Subsequent paragraphs with para tags + result.Append($"{paragraphs[i]}"); + } + } + + return result.ToString(); + } + + #region Enum Generation + + /// + /// Defines enum groupings for constants. Maps enum name to the list of constant names to include. + /// + private static readonly Dictionary EnumDefinitions = new() + { + ["Secp256k1EcFlags"] = new EnumDefinition + { + Description = "Flags for public key serialization format.", + IsFlags = false, + Members = new() + { + { "SECP256K1_EC_COMPRESSED", "Compressed format (33 bytes)." }, + { "SECP256K1_EC_UNCOMPRESSED", "Uncompressed format (65 bytes)." }, + } + }, + ["Secp256k1ContextFlags"] = new EnumDefinition + { + Description = "Flags for secp256k1 context creation.", + IsFlags = false, + Members = new() + { + { "SECP256K1_CONTEXT_NONE", "Creates a context sufficient for all functionality." }, + } + }, + }; + + /// + /// Maps function parameters (by function name + param name) to the enum type they should use. + /// + private static readonly Dictionary<(string FunctionName, string ParamName), string> ParameterEnumMappings = new() + { + { ("secp256k1_ec_pubkey_serialize", "flags"), "Secp256k1EcFlags" }, + }; + + private class EnumDefinition + { + public string? Description { get; set; } + public bool IsFlags { get; set; } + public Dictionary Members { get; set; } = new(); + } + + /// + /// Generates enum types from constants based on predefined groupings. + /// + public void GenerateEnums(StringBuilder sb, Secp256k1Api api) + { + foreach (var (enumName, enumDef) in EnumDefinitions) + { + sb.AppendLine(); + if (!string.IsNullOrEmpty(enumDef.Description)) + { + sb.AppendLine($" /// {enumDef.Description}"); + } + if (enumDef.IsFlags) + { + sb.AppendLine(" [Flags]"); + } + sb.AppendLine($" public enum {enumName} : uint"); + sb.AppendLine(" {"); + + foreach (var (constantName, memberDesc) in enumDef.Members) + { + var constant = api.Constants.FirstOrDefault(c => c.Name == constantName); + if (constant == null) continue; + + // Generate member name by removing SECP256K1_ prefix and converting to PascalCase + var memberName = GetEnumMemberName(constantName); + + // Use numeric value if available, otherwise try to evaluate the expression + var value = constant.NumericValue?.ToString() ?? EvaluateConstantValue(constant.Value, api); + + var desc = memberDesc ?? constant.Description; + if (!string.IsNullOrEmpty(desc)) + { + var cleanDesc = CleanDescription(desc); + sb.AppendLine($" /// {EscapeXml(cleanDesc)}"); + } + sb.AppendLine($" {memberName} = {value},"); + } + + sb.AppendLine(" }"); + } + } + + /// + /// Converts a constant name like SECP256K1_EC_COMPRESSED to a C# enum member name like Compressed. + /// + private static string GetEnumMemberName(string constantName) + { + // Remove SECP256K1_ prefix + var name = constantName; + if (name.StartsWith("SECP256K1_")) + name = name.Substring("SECP256K1_".Length); + + // Remove EC_ prefix for EC flags + if (name.StartsWith("EC_")) + name = name.Substring("EC_".Length); + + // Remove CONTEXT_ prefix for context flags + if (name.StartsWith("CONTEXT_")) + name = name.Substring("CONTEXT_".Length); + + // Convert SCREAMING_SNAKE_CASE to PascalCase + var parts = name.Split('_'); + return string.Join("", parts.Select(p => + p.Length > 0 ? char.ToUpper(p[0]) + p.Substring(1).ToLower() : "")); + } + + /// + /// Evaluates a constant value expression that may reference other constants. + /// + private static string EvaluateConstantValue(string value, Secp256k1Api api) + { + // Handle simple numeric values + if (int.TryParse(value, out var intVal)) + return intVal.ToString(); + if (value.StartsWith("0x") && int.TryParse(value.Substring(2), System.Globalization.NumberStyles.HexNumber, null, out intVal)) + return intVal.ToString(); + + // Handle bit shifts like (1 << 8) + var shiftMatch = System.Text.RegularExpressions.Regex.Match(value, @"\((\d+)\s*<<\s*(\d+)\)"); + if (shiftMatch.Success) + { + var baseVal = int.Parse(shiftMatch.Groups[1].Value); + var shift = int.Parse(shiftMatch.Groups[2].Value); + return (baseVal << shift).ToString(); + } + + // Handle expressions that reference other constants like (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION) + var orMatch = System.Text.RegularExpressions.Regex.Match(value, @"\((\w+)\s*\|\s*(\w+)\)"); + if (orMatch.Success) + { + var left = ResolveConstantValue(orMatch.Groups[1].Value, api); + var right = ResolveConstantValue(orMatch.Groups[2].Value, api); + if (left.HasValue && right.HasValue) + return (left.Value | right.Value).ToString(); + } + + // Handle single constant reference like (SECP256K1_FLAGS_TYPE_COMPRESSION) + var singleMatch = System.Text.RegularExpressions.Regex.Match(value, @"\((\w+)\)"); + if (singleMatch.Success) + { + var resolved = ResolveConstantValue(singleMatch.Groups[1].Value, api); + if (resolved.HasValue) + return resolved.Value.ToString(); + } + + // Fallback - return as-is (will likely cause compile error if invalid) + return value; + } + + /// + /// Resolves a constant name to its numeric value. + /// + private static long? ResolveConstantValue(string constantName, Secp256k1Api api) + { + var constant = api.Constants.FirstOrDefault(c => c.Name == constantName); + if (constant == null) + return null; + + if (constant.NumericValue.HasValue) + return constant.NumericValue.Value; + + // Try to evaluate the expression recursively + var evaluated = EvaluateConstantValue(constant.Value, api); + if (long.TryParse(evaluated, out var result)) + return result; + + return null; + } + + #endregion + + #region Wrapper Generation + + // Functions to skip in wrapper generation (need manual implementation or are internal) + private static readonly HashSet SkipWrapperFunctions = new() + { + "secp256k1_context_create", + "secp256k1_context_clone", + "secp256k1_context_destroy", + "secp256k1_context_set_illegal_callback", + "secp256k1_context_set_error_callback", + "secp256k1_context_randomize", + "secp256k1_context_preallocated_size", + "secp256k1_context_preallocated_create", + "secp256k1_context_preallocated_clone_size", + "secp256k1_context_preallocated_clone", + "secp256k1_context_preallocated_destroy", + "secp256k1_ec_pubkey_sort", // Needs special handling to reorder the C# array based on sorted pointers + }; + + // Functions that return int but NOT as bool (0/1 success/failure) + // These are comparison functions where the return value is meaningful (0=equal, <0=less, >0=greater) + private static readonly HashSet IntReturnFunctions = new() + { + "secp256k1_ec_pubkey_cmp", + "secp256k1_xonly_pubkey_cmp", + }; + + // Struct sizes from JSON (secp256k1 opaque types) + private static readonly Dictionary StructSizes = new() + { + ["secp256k1_pubkey"] = 64, + ["secp256k1_ecdsa_signature"] = 64, + ["secp256k1_ecdsa_recoverable_signature"] = 65, + ["secp256k1_xonly_pubkey"] = 64, + ["secp256k1_keypair"] = 96, + ["secp256k1_musig_keyagg_cache"] = 197, + ["secp256k1_musig_secnonce"] = 132, + ["secp256k1_musig_pubnonce"] = 132, + ["secp256k1_musig_aggnonce"] = 132, + ["secp256k1_musig_session"] = 133, + ["secp256k1_musig_partial_sig"] = 36, + }; + + // Map native callback type names to user-friendly C# delegate names + private static readonly Dictionary CallbackDelegateNames = new() + { + ["secp256k1_nonce_function"] = "NonceFunction", + ["secp256k1_ecdh_hash_function"] = "EcdhHashFunction", + ["secp256k1_nonce_function_hardened"] = "NonceFunctionHardened", + ["secp256k1_ellswift_xdh_hash_function"] = "EllswiftXdhHashFunction", + }; + + /// + /// Generates validation code for functions where buffer size depends on an enum parameter value. + /// + private void GenerateEnumBasedValidation(StringBuilder sb, string functionName, List wrapperParams) + { + // secp256k1_ec_pubkey_serialize: output size depends on flags (compressed=33, uncompressed=65) + if (functionName == "secp256k1_ec_pubkey_serialize") + { + var outputParam = wrapperParams.FirstOrDefault(p => p.WrapperName == "output"); + var flagsParam = wrapperParams.FirstOrDefault(p => p.WrapperName == "flags"); + if (outputParam != null && flagsParam != null) + { + sb.AppendLine($" var requiredOutputSize = {flagsParam.WrapperName} == Secp256k1EcFlags.Compressed ? 33 : 65;"); + sb.AppendLine($" if ({outputParam.WrapperName}.Length < requiredOutputSize)"); + sb.AppendLine($" throw new ArgumentException($\"{{nameof({outputParam.WrapperName})}} must be at least {{requiredOutputSize}} bytes for the specified flags\");"); + } + } + } + + // User-friendly delegates that are already defined in hand-written code (skip generation) + private static readonly HashSet SkipDelegateGeneration = new() + { + // Empty - all delegates are now generated + }; + + public string GenerateWrappers(Secp256k1Api api) + { + // Update struct sizes from JSON if available + var structSizes = new Dictionary(StructSizes); + foreach (var s in api.Structs.Where(s => s.Size > 0)) + { + structSizes[s.Name] = s.Size; + } + + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("#nullable enable"); + sb.AppendLine(); + sb.AppendLine("using System;"); + sb.AppendLine("using System.Runtime.InteropServices;"); + sb.AppendLine(); + sb.AppendLine("namespace Secp256k1Net"); + sb.AppendLine("{"); + + // Generate enum types from constants + GenerateEnums(sb, api); + + // Generate user-friendly delegate types for callback functions + GenerateUserFriendlyCallbackDelegates(sb, api); + + sb.AppendLine(" public unsafe partial class Secp256k1"); + sb.AppendLine(" {"); + + foreach (var func in api.Functions.Where(f => !SkipWrapperFunctions.Contains(f.Name) && !f.Deprecated)) + { + GenerateWrapperMethod(sb, func, structSizes, api); + } + + // Generate wrappers for global function pointers (like secp256k1_nonce_function_rfc6979) + foreach (var global in api.GlobalPointers.Where(g => g.Type.StartsWith("secp256k1_") && g.Type.Contains("function"))) + { + GenerateGlobalFunctionPointerWrapper(sb, global, api, structSizes); + } + + sb.AppendLine(" }"); + sb.AppendLine("}"); + + return sb.ToString(); + } + + /// + /// Generates user-friendly delegate types with Span parameters for callback function types. + /// + private void GenerateUserFriendlyCallbackDelegates(StringBuilder sb, Secp256k1Api api) + { + foreach (var fpType in api.FunctionPointerTypes) + { + if (!CallbackDelegateNames.TryGetValue(fpType.Name, out var delegateName)) + continue; + + // Skip delegates that are already defined in hand-written code + if (SkipDelegateGeneration.Contains(delegateName)) + continue; + + sb.AppendLine(); + if (!string.IsNullOrEmpty(fpType.Description)) + { + sb.AppendLine($" /// {FormatXmlDescription(fpType.Description)}"); + } + + // Generate parameter documentation + foreach (var param in fpType.Parameters) + { + var paramDesc = FormatXmlDescription(param.Description); + sb.AppendLine($" /// {paramDesc}"); + } + + if (fpType.ReturnType == "int") + { + sb.AppendLine(" /// 1 on success, 0 on failure."); + } + + // Generate the delegate with user-friendly Span types + var returnType = fpType.ReturnType == "int" ? "int" : MapCTypeToCSharp(fpType.ReturnType); + sb.Append($" public delegate {returnType} {delegateName}("); + + var paramStrings = new List(); + foreach (var param in fpType.Parameters) + { + var paramType = GetUserFriendlyCallbackParamType(param); + paramStrings.Add($"{paramType} {SanitizeParamName(param.Name)}"); + } + + sb.Append(string.Join(", ", paramStrings)); + sb.AppendLine(");"); + } + + sb.AppendLine(); + } + + /// + /// Maps a callback parameter type to a user-friendly C# type (Span-based where possible). + /// + private string GetUserFriendlyCallbackParamType(ParameterDef param) + { + var cType = param.Type.Trim(); + var direction = param.Direction ?? "in"; + + // void* data - keep as IntPtr for user data + if (cType == "void*" || cType == "const void*") + { + return "IntPtr"; + } + + // Pointer types - convert to Span + if (cType.Contains("*")) + { + bool isInput = direction == "in" || cType.StartsWith("const "); + return isInput ? "ReadOnlySpan" : "Span"; + } + + // Primitive types + return MapCTypeToCSharp(cType); + } + + private void GenerateWrapperMethod(StringBuilder sb, FunctionDef func, Dictionary structSizes, Secp256k1Api api) + { + // Check if has function pointer parameter (callback) + var callbackParams = func.Parameters.Where(p => p.Type.Contains("function") || p.Type.Contains("(*)")).ToList(); + + // If any callback is REQUIRED, generate a callback wrapper version + if (callbackParams.Any(p => p.Nonnull)) + { + GenerateCallbackWrapperMethod(sb, func, structSizes, callbackParams, api); + return; + } + + // Check if this function has array-of-pointers parameters (both "**" and "* const*" patterns) + var hasArrayOfPointers = func.Parameters.Any(p => p.Type.Contains("**") || p.Type.Contains("* const*")); + + if (hasArrayOfPointers) + { + GenerateArrayOfPointersWrapperMethod(sb, func, structSizes); + return; + } + + var methodName = GetWrapperMethodName(func.Name); + var allParameters = GetWrapperParameters(func, structSizes); + var hasContextParam = func.Parameters.FirstOrDefault()?.Type.Contains("secp256k1_context") == true; + + // Skip context parameter, optional callbacks, and length params for input spans in wrapper signature + var wrapperParams = (hasContextParam ? allParameters.Skip(1) : allParameters) + .Where(p => !p.IsOptionalCallback) + .Where(p => p.LengthForSpanName == null) // Skip length params - we'll use span.Length + .ToList(); + + // Generate XML documentation + sb.AppendLine(); + if (!string.IsNullOrEmpty(func.Description)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.Description)}"); + } + + foreach (var param in wrapperParams) + { + if (!string.IsNullOrEmpty(param.Description)) + { + // XML param names don't use @ prefix + var xmlParamName = param.WrapperName.TrimStart('@'); + sb.AppendLine($" /// {FormatXmlDescription(param.Description)}"); + } + } + + if (!string.IsNullOrEmpty(func.ReturnDescription)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.ReturnDescription)}"); + } + + // Determine return type + // Comparison functions return int (not bool) so the actual comparison result is preserved + var returnsBool = func.ReturnType == "int" && !IntReturnFunctions.Contains(func.Name); + var returnType = returnsBool ? "bool" : MapCTypeToCSharp(func.ReturnType); + + // Method signature + var paramSignature = string.Join(", ", wrapperParams.Select(p => $"{p.WrapperType} {p.WrapperName}")); + sb.AppendLine($" public {returnType} {methodName}({paramSignature})"); + sb.AppendLine(" {"); + + // Generate validation + foreach (var param in wrapperParams.Where(p => p.RequiredSize > 0 && p.IsSpan)) + { + sb.AppendLine($" if ({param.WrapperName}.Length < {param.RequiredSize})"); + sb.AppendLine($" throw new ArgumentException($\"{{nameof({param.WrapperName})}} must be at least {param.RequiredSize} bytes\");"); + } + + // Generate enum-based size validation for specific functions + GenerateEnumBasedValidation(sb, func.Name, wrapperParams); + + // Collect span parameters for fixed statement + var spanParams = wrapperParams.Where(p => p.IsSpan).ToList(); + var refParams = wrapperParams.Where(p => p.IsRefParam).ToList(); + + var needsFixed = spanParams.Count > 0 || refParams.Count > 0; + + if (needsFixed) + { + sb.AppendLine(); + + // Build fixed statement for byte spans + if (spanParams.Count > 0) + { + var fixedDeclarations = spanParams.Select(p => + $"{p.WrapperName}Ptr = {p.WrapperName}"); + sb.AppendLine($" fixed (byte* {string.Join(",\n ", fixedDeclarations)})"); + } + + // Build fixed statement for ref params + foreach (var refParam in refParams) + { + var cleanName = refParam.WrapperName.TrimStart('@'); + sb.AppendLine($" fixed ({refParam.RefParamType}* {cleanName}Ptr = &{refParam.WrapperName})"); + } + + sb.AppendLine(" {"); + + // Build native call (use allParameters to include optional callbacks as IntPtr.Zero) + var nativeArgs = BuildNativeCallArgs(func, allParameters, hasContextParam); + var fieldName = GetFieldName(func.Name); + + if (returnsBool) + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs}) == 1;"); + } + else if (func.ReturnType == "void") + { + sb.AppendLine($" Secp256k1Interop.{fieldName}({nativeArgs});"); + } + else + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs});"); + } + + sb.AppendLine(" }"); + } + else + { + // No span or ref parameters - direct call (use allParameters to include optional callbacks) + var nativeArgs = BuildNativeCallArgs(func, allParameters, hasContextParam); + var fieldName = GetFieldName(func.Name); + + if (returnsBool) + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs}) == 1;"); + } + else if (func.ReturnType == "void") + { + sb.AppendLine($" Secp256k1Interop.{fieldName}({nativeArgs});"); + } + else + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs});"); + } + } + + sb.AppendLine(" }"); + + // Generate optional callback overload if this function has optional callbacks + var optionalCallbackParams = callbackParams.Where(p => !p.Nonnull).ToList(); + if (optionalCallbackParams.Count > 0) + { + GenerateOptionalCallbackOverload(sb, func, structSizes, optionalCallbackParams, api); + } + } + + /// + /// Generates an overload that accepts optional callback parameters. + /// This allows users to provide custom callbacks when needed, while the default overload uses null/default. + /// + private void GenerateOptionalCallbackOverload(StringBuilder sb, FunctionDef func, Dictionary structSizes, List optionalCallbackParams, Secp256k1Api api) + { + var methodName = GetWrapperMethodName(func.Name); + var hasContextParam = func.Parameters.FirstOrDefault()?.Type.Contains("secp256k1_context") == true; + + // Get the callback type info + var callbackParam = optionalCallbackParams.FirstOrDefault(p => p.Type.Contains("function")); + if (callbackParam == null) + return; + + var nativeCallbackType = callbackParam.Type; + + // Get user-friendly delegate name + if (!CallbackDelegateNames.TryGetValue(nativeCallbackType, out var userDelegateType)) + { + // No user-friendly delegate defined, skip + return; + } + + // Build wrapper parameters list - include all params including callback and data + var wrapperParams = new List<(string type, string name, string? desc, ParameterDef original, bool isCallback, bool isData)>(); + + foreach (var param in func.Parameters) + { + if (param.Type.Contains("secp256k1_context")) + continue; // Skip context param + + if (param.Type.Contains("function")) + { + // Replace native callback type with user-friendly delegate + wrapperParams.Add((userDelegateType, SanitizeParamName(param.Name), param.Description, param, true, false)); + } + else if (param.Type == "void*" || param.Type == "const void*") + { + // Data pointer accompanying callback + wrapperParams.Add(("IntPtr", SanitizeParamName(param.Name), param.Description, param, false, true)); + } + else if (param.Type.Contains("size_t") && param.Type.Contains("*")) + { + // Size_t pointer - ref nuint + wrapperParams.Add(("ref nuint", SanitizeParamName(param.Name), param.Description, param, false, false)); + } + else if (param.Type.Contains("int") && param.Type.Contains("*") && !param.Type.Contains("uint")) + { + // Int pointer - out int + var dir = param.Direction == "out" ? "out int" : "ref int"; + wrapperParams.Add((dir, SanitizeParamName(param.Name), param.Description, param, false, false)); + } + else if (param.Type.Contains("*")) + { + // Other pointer params become spans + var isOutput = param.Direction == "out" || !param.Type.StartsWith("const "); + var spanType = isOutput ? "Span" : "ReadOnlySpan"; + wrapperParams.Add((spanType, SanitizeParamName(param.Name), param.Description, param, false, false)); + } + else + { + // Non-pointer params pass through + wrapperParams.Add((MapCTypeToCSharp(param.Type), SanitizeParamName(param.Name), param.Description, param, false, false)); + } + } + + // Generate XML documentation + sb.AppendLine(); + if (!string.IsNullOrEmpty(func.Description)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.Description)}"); + } + + foreach (var (type, name, desc, _, _, _) in wrapperParams) + { + if (!string.IsNullOrEmpty(desc)) + { + var xmlParamName = name.TrimStart('@'); + sb.AppendLine($" /// {FormatXmlDescription(desc)}"); + } + } + + if (!string.IsNullOrEmpty(func.ReturnDescription)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.ReturnDescription)}"); + } + + // Method signature + var returnsBool = func.ReturnType == "int"; + var returnType = returnsBool ? "bool" : MapCTypeToCSharp(func.ReturnType); + var paramSignature = string.Join(", ", wrapperParams.Select(p => $"{p.type} {p.name}")); + sb.AppendLine($" public {returnType} {methodName}({paramSignature})"); + sb.AppendLine(" {"); + + // Generate validation for span parameters + foreach (var (type, name, _, original, _, _) in wrapperParams) + { + if (!type.Contains("Span")) continue; + + var size = GetRequiredSize(original, structSizes); + if (size > 0) + { + sb.AppendLine($" if ({name}.Length < {size})"); + sb.AppendLine($" throw new ArgumentException($\"{{nameof({name})}} must be at least {size} bytes\");"); + } + } + + // Find the callback parameter name for marshaling + var callbackParamInfo = wrapperParams.First(p => p.isCallback); + var callbackParamName = callbackParamInfo.name; + + sb.AppendLine(); + + // Generate the native callback wrapper + sb.AppendLine($" {nativeCallbackType} nativeCallback = {GenerateNativeCallbackWrapper(nativeCallbackType, callbackParamName, structSizes, api)};"); + sb.AppendLine(); + sb.AppendLine(" var callbackPtr = Marshal.GetFunctionPointerForDelegate(nativeCallback);"); + sb.AppendLine(); + + // Collect span parameters for fixed statement + var spanParams = wrapperParams.Where(p => p.type.Contains("Span")).ToList(); + var refParams = wrapperParams.Where(p => p.type.StartsWith("ref ") || p.type.StartsWith("out ")).ToList(); + + var needsFixed = spanParams.Count > 0 || refParams.Count > 0; + + if (needsFixed) + { + // Build fixed statement for byte spans + if (spanParams.Count > 0) + { + var fixedDeclarations = spanParams.Select(p => + $"{p.name}Ptr = {p.name}"); + sb.AppendLine($" fixed (byte* {string.Join(",\n ", fixedDeclarations)})"); + } + + // Build fixed statement for ref params + foreach (var refParam in refParams) + { + var cleanName = refParam.name.TrimStart('@'); + var refType = refParam.type.Replace("ref ", "").Replace("out ", ""); + sb.AppendLine($" fixed ({refType}* {cleanName}Ptr = &{refParam.name})"); + } + + sb.AppendLine(" {"); + + // Build native call arguments + var nativeArgs = BuildOptionalCallbackNativeCallArgs(func, wrapperParams, hasContextParam); + var fieldName = GetFieldName(func.Name); + + if (returnsBool) + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs}) == 1;"); + } + else if (func.ReturnType == "void") + { + sb.AppendLine($" Secp256k1Interop.{fieldName}({nativeArgs});"); + } + else + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs});"); + } + + sb.AppendLine(" }"); + } + else + { + // No span parameters - direct call + var nativeArgs = BuildOptionalCallbackNativeCallArgs(func, wrapperParams, hasContextParam); + var fieldName = GetFieldName(func.Name); + + if (returnsBool) + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs}) == 1;"); + } + else if (func.ReturnType == "void") + { + sb.AppendLine($" Secp256k1Interop.{fieldName}({nativeArgs});"); + } + else + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs});"); + } + } + + sb.AppendLine(" }"); + } + + /// + /// Builds native call arguments for optional callback overload methods. + /// + private static string BuildOptionalCallbackNativeCallArgs(FunctionDef func, List<(string type, string name, string? desc, ParameterDef original, bool isCallback, bool isData)> wrapperParams, bool hasContextParam) + { + var args = new List(); + + if (hasContextParam) + { + args.Add("_ctx"); + } + + foreach (var (type, name, _, original, isCallback, isData) in wrapperParams) + { + if (type.Contains("Span")) + { + args.Add($"{name}Ptr"); + } + else if (isCallback) + { + args.Add("callbackPtr"); + } + else if (isData) + { + // Data pointer - pass as void* + args.Add($"{name}.ToPointer()"); + } + else if (type.StartsWith("ref ") || type.StartsWith("out ")) + { + // Use the pinned pointer variable + var cleanName = name.TrimStart('@'); + args.Add($"{cleanName}Ptr"); + } + else + { + args.Add(name); + } + } + + return string.Join(", ", args); + } + + private List GetWrapperParameters(FunctionDef func, Dictionary structSizes) + { + var result = new List(); + + foreach (var param in func.Parameters) + { + var wrapper = new WrapperParameter + { + OriginalName = param.Name, + OriginalType = param.Type, + Direction = param.Direction ?? "in", + Description = param.Description, + IsLengthFor = param.IsLengthFor + }; + + // Determine wrapper type and size + DetermineWrapperType(wrapper, param, structSizes, func.Name); + + result.Add(wrapper); + } + + // Second pass: resolve LengthForSpanName for length params where the buffer is a ReadOnlySpan + foreach (var wrapper in result) + { + if (!string.IsNullOrEmpty(wrapper.IsLengthFor)) + { + // Find the buffer parameter this length is for + var bufferParam = result.FirstOrDefault(p => p.OriginalName == wrapper.IsLengthFor); + // Only hide length param if the buffer became a ReadOnlySpan (input buffer) + if (bufferParam != null && bufferParam.WrapperType == "ReadOnlySpan") + { + wrapper.LengthForSpanName = bufferParam.WrapperName; + } + } + } + + return result; + } + + private void DetermineWrapperType(WrapperParameter wrapper, ParameterDef param, Dictionary structSizes, string functionName) + { + var cType = param.Type.Trim(); + var name = param.Name; + var direction = param.Direction ?? "in"; + + // Context pointer - use IntPtr internally + if (cType.Contains("secp256k1_context")) + { + wrapper.WrapperType = "IntPtr"; + wrapper.WrapperName = "_ctx"; + wrapper.IsContextParam = true; + return; + } + + // Function pointer types (callbacks) + if (cType.Contains("function") || cType.Contains("(*)")) + { + wrapper.WrapperType = "IntPtr"; + wrapper.WrapperName = SanitizeParamName(name); + // If nonnull is false, this is an optional callback - will pass IntPtr.Zero + wrapper.IsOptionalCallback = !param.Nonnull; + return; + } + + // void* data parameter that typically accompanies callback - mark as optional too + // These are usually named "data" or "ndata" and follow a callback parameter + if (cType == "void*" || cType == "const void*") + { + wrapper.WrapperType = "IntPtr"; + wrapper.WrapperName = SanitizeParamName(name); + // Mark as optional if nonnull is false + wrapper.IsOptionalCallback = !param.Nonnull; + return; + } + + // Size_t pointer (output length) - treat as ref parameter that needs fixed + if (cType.Contains("size_t") && cType.Contains("*")) + { + wrapper.WrapperType = "ref nuint"; + wrapper.WrapperName = SanitizeParamName(name); + wrapper.IsRefParam = true; + wrapper.RefParamType = "nuint"; + return; + } + + // Int pointer (e.g., recid) - treat as ref/out parameter that needs fixed + if (cType.Contains("int") && cType.Contains("*") && !cType.Contains("uint")) + { + wrapper.WrapperType = direction == "out" ? "out int" : "ref int"; + wrapper.WrapperName = SanitizeParamName(name); + wrapper.IsRefParam = true; + wrapper.RefParamType = "int"; + return; + } + + // Check for enum mappings for this parameter + if (ParameterEnumMappings.TryGetValue((functionName, name), out var enumType)) + { + wrapper.WrapperType = enumType; + wrapper.WrapperName = SanitizeParamName(name); + wrapper.IsEnumParam = true; + return; + } + + // Non-pointer primitive types + if (!cType.Contains("*")) + { + wrapper.WrapperType = MapCTypeToCSharp(cType); + wrapper.WrapperName = SanitizeParamName(name); + return; + } + + // Pointer types - determine if they should be Span or ReadOnlySpan + wrapper.IsSpan = true; + + // Determine span type based on direction and const + bool isInput = direction == "in" || cType.StartsWith("const "); + wrapper.WrapperType = isInput ? "ReadOnlySpan" : "Span"; + wrapper.WrapperName = GetWrapperParamName(name); + + // Determine required size - prefer pre-computed value from JSON + wrapper.RequiredSize = GetRequiredSize(param, structSizes); + } + + /// + /// Gets the required size for a parameter, using pre-computed Size from the JSON if available, + /// or falling back to struct size lookup. + /// + private int GetRequiredSize(ParameterDef param, Dictionary structSizes) + { + // Use pre-computed size from JSON if available (set by header parser) + if (param.Size.HasValue) + { + return param.Size.Value; + } + + // Fallback: check if it's a known struct type + foreach (var kvp in structSizes) + { + if (param.Type.Contains(kvp.Key)) + { + return kvp.Value; + } + } + + // Default - no size validation + return 0; + } + + private string GetWrapperMethodName(string functionName) + { + // secp256k1_ec_pubkey_create -> EcPubkeyCreate + var name = functionName; + + // Remove secp256k1_ prefix + if (name.StartsWith("secp256k1_")) + { + name = name.Substring("secp256k1_".Length); + } + + // Convert snake_case to PascalCase + var parts = name.Split('_'); + var result = string.Join("", parts.Select(p => + p.Length > 0 ? char.ToUpper(p[0]) + p.Substring(1).ToLower() : "")); + + // Handle common acronyms + result = result.Replace("Ecdsa", "Ecdsa") + .Replace("Ecdh", "Ecdh") + .Replace("Ec", "Ec"); + + return result; + } + + private string GetWrapperParamName(string originalName) + { + // Convert C-style names to C# style + var name = originalName; + + // Remove common suffixes for cleaner names + if (name.EndsWith("32") || name.EndsWith("33") || name.EndsWith("64") || name.EndsWith("65")) + { + // Keep the suffix in the name for clarity + } + + return SanitizeParamName(name); + } + + private static string BuildNativeCallArgs(FunctionDef func, List allParams, bool hasContextParam) + { + var args = new List(); + + if (hasContextParam) + { + args.Add("_ctx"); + } + + foreach (var param in allParams.Where(p => !p.IsContextParam)) + { + if (param.IsOptionalCallback) + { + // Optional callback and data parameters - pass IntPtr.Zero + // For void* data parameters, need to use IntPtr.Zero.ToPointer() + if (param.OriginalType == "void*" || param.OriginalType == "const void*") + { + args.Add("IntPtr.Zero.ToPointer()"); + } + else + { + args.Add("IntPtr.Zero"); + } + } + else if (param.LengthForSpanName != null) + { + // This is a length param for an input span - use span.Length + args.Add($"(nuint){param.LengthForSpanName}.Length"); + } + else if (param.IsSpan) + { + args.Add($"{param.WrapperName}Ptr"); + } + else if (param.IsRefParam) + { + // Use the pinned pointer variable + var cleanName = param.WrapperName.TrimStart('@'); + args.Add($"{cleanName}Ptr"); + } + else if (param.IsEnumParam) + { + // Cast enum to uint for native call + args.Add($"(uint){param.WrapperName}"); + } + else + { + args.Add(param.WrapperName); + } + } + + return string.Join(", ", args); + } + + private class WrapperParameter + { + public string OriginalName { get; set; } = ""; + public string OriginalType { get; set; } = ""; + public string WrapperType { get; set; } = ""; + public string WrapperName { get; set; } = ""; + public string Direction { get; set; } = "in"; + public string? Description { get; set; } + public int RequiredSize { get; set; } + public bool IsSpan { get; set; } + public bool IsContextParam { get; set; } + public bool IsRefParam { get; set; } + public string RefParamType { get; set; } = ""; + public bool IsArrayOfPointers { get; set; } + public int ElementSize { get; set; } + public string? CountParamName { get; set; } + public bool IsOptionalCallback { get; set; } // Optional callback/data parameter - pass IntPtr.Zero + public string? IsLengthFor { get; set; } // If this is a length param, the name of the buffer param it's for + public string? LengthForSpanName { get; set; } // The wrapper name of the span this length is for (resolved) + public bool IsEnumParam { get; set; } // If true, this param uses an enum type and needs casting to uint + } + + /// + /// Generates wrapper methods for functions with array-of-pointers parameters. + /// These functions take a pointer to an array of pointers (e.g., secp256k1_pubkey * const*). + /// The generated wrapper accepts a ReadOnlySpan of ReadOnlySpan elements. + /// + private void GenerateArrayOfPointersWrapperMethod(StringBuilder sb, FunctionDef func, Dictionary structSizes) + { + var methodName = GetWrapperMethodName(func.Name); + var hasContextParam = func.Parameters.FirstOrDefault()?.Type.Contains("secp256k1_context") == true; + + // Find the array-of-pointers parameter and its count parameter + var arrayParam = func.Parameters.FirstOrDefault(p => p.Type.Contains("**") || p.Type.Contains("* const*")); + if (arrayParam == null) return; + + // Find the count parameter (usually named n, n_pubkeys, n_pubnonces, n_sigs) + var countParam = func.Parameters.FirstOrDefault(p => + p.Name == "n" || + p.Name.StartsWith("n_") || + p.Name.EndsWith("_count")); + + // Determine element size from type + var elementSize = GetElementSizeFromType(arrayParam.Type, structSizes); + + // Build wrapper parameters + var wrapperParams = new List<(string type, string name, string? desc, ParameterDef original)>(); + + foreach (var param in func.Parameters) + { + if (param.Type.Contains("secp256k1_context")) + continue; // Skip context param + + if (param == countParam) + continue; // Skip count param - we'll infer it from the array length + + if (param == arrayParam) + { + // Array of byte arrays parameter (can't use Span[] since Span is a ref struct) + wrapperParams.Add(("byte[][]", SanitizeParamName(param.Name), param.Description, param)); + } + else if (param.Type.Contains('*')) + { + // Other pointer params become spans + var isOutput = param.Direction == "out" || !param.Type.StartsWith("const "); + var spanType = isOutput ? "Span" : "ReadOnlySpan"; + wrapperParams.Add((spanType, SanitizeParamName(param.Name), param.Description, param)); + } + else + { + // Non-pointer params pass through + wrapperParams.Add((MapCTypeToCSharp(param.Type), SanitizeParamName(param.Name), param.Description, param)); + } + } + + // Generate XML documentation + sb.AppendLine(); + if (!string.IsNullOrEmpty(func.Description)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.Description)}"); + } + + foreach (var (type, name, desc, _) in wrapperParams) + { + if (!string.IsNullOrEmpty(desc)) + { + // XML param names don't use @ prefix for escaped keywords + var xmlParamName = name.TrimStart('@'); + sb.AppendLine($" /// {FormatXmlDescription(desc)}"); + } + } + + if (!string.IsNullOrEmpty(func.ReturnDescription)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.ReturnDescription)}"); + } + + // Method signature + var returnsBool = func.ReturnType == "int"; + var returnType = returnsBool ? "bool" : MapCTypeToCSharp(func.ReturnType); + var paramSignature = string.Join(", ", wrapperParams.Select(p => $"{p.type} {p.name}")); + sb.AppendLine($" public {returnType} {methodName}({paramSignature})"); + sb.AppendLine(" {"); + + // Get the array parameter name + var arrayParamName = SanitizeParamName(arrayParam.Name); + + // Generate validation for array + sb.AppendLine($" if ({arrayParamName} == null || {arrayParamName}.Length == 0)"); + sb.AppendLine($" throw new ArgumentException($\"{{nameof({arrayParamName})}} must not be null or empty\");"); + + // Generate validation for element sizes + if (elementSize > 0) + { + sb.AppendLine($" for (int i = 0; i < {arrayParamName}.Length; i++)"); + sb.AppendLine(" {"); + sb.AppendLine($" if ({arrayParamName}[i] == null || {arrayParamName}[i].Length < {elementSize})"); + sb.AppendLine($" throw new ArgumentException($\"{{nameof({arrayParamName})}}[{{i}}] must be at least {elementSize} bytes\");"); + sb.AppendLine(" }"); + } + + // Generate validation for other span parameters + foreach (var (type, name, _, original) in wrapperParams) + { + if (original == arrayParam) continue; + if (!type.Contains("Span")) continue; + + var size = GetRequiredSize(original, structSizes); + if (size > 0) + { + sb.AppendLine($" if ({name}.Length < {size})"); + sb.AppendLine($" throw new ArgumentException($\"{{nameof({name})}} must be at least {size} bytes\");"); + } + } + + sb.AppendLine(); + + // Allocate native pointer array using stackalloc + sb.AppendLine($" var count = {arrayParamName}.Length;"); + sb.AppendLine(" Span nativePtrArray = stackalloc nint[count];"); + + // Collect all span parameters (excluding the array-of-pointers) + var otherSpanParams = wrapperParams + .Where(p => p.original != arrayParam && p.type.Contains("Span")) + .ToList(); + + // Build fixed statements + var indent = " "; + if (otherSpanParams.Count > 0) + { + var fixedDeclarations = otherSpanParams.Select(p => + $"{p.name}Ptr = {p.name}"); + sb.AppendLine($"{indent}fixed (byte* {string.Join(",\n ", fixedDeclarations)})"); + indent = " "; + } + + // Generate pinning code + sb.AppendLine($"{indent}{{"); + + // Use GCHandle to pin the array elements + sb.AppendLine($"{indent} var handles = new GCHandle[count];"); + sb.AppendLine($"{indent} try"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} for (int i = 0; i < count; i++)"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} handles[i] = GCHandle.Alloc({arrayParamName}[i], GCHandleType.Pinned);"); + sb.AppendLine($"{indent} nativePtrArray[i] = handles[i].AddrOfPinnedObject();"); + sb.AppendLine($"{indent} }}"); + sb.AppendLine(); + + // Build native call arguments + var nativeArgs = new List(); + if (hasContextParam) + nativeArgs.Add("_ctx"); + + foreach (var param in func.Parameters) + { + if (param.Type.Contains("secp256k1_context")) + continue; + + if (param == arrayParam) + { + nativeArgs.Add("(IntPtr)nativePtrArrayPtr"); + } + else if (param == countParam) + { + nativeArgs.Add("(nuint)count"); + } + else if (param.Type.Contains('*')) + { + nativeArgs.Add($"{SanitizeParamName(param.Name)}Ptr"); + } + else + { + nativeArgs.Add(SanitizeParamName(param.Name)); + } + } + + var fieldName = GetFieldName(func.Name); + var argsStr = string.Join(", ", nativeArgs); + + // Fixed statement to get pointer to stackalloc span + sb.AppendLine($"{indent} fixed (nint* nativePtrArrayPtr = nativePtrArray)"); + sb.AppendLine($"{indent} {{"); + + if (returnsBool) + { + sb.AppendLine($"{indent} return Secp256k1Interop.{fieldName}({argsStr}) == 1;"); + } + else if (func.ReturnType == "void") + { + sb.AppendLine($"{indent} Secp256k1Interop.{fieldName}({argsStr});"); + } + else + { + sb.AppendLine($"{indent} return Secp256k1Interop.{fieldName}({argsStr});"); + } + + sb.AppendLine($"{indent} }}"); + sb.AppendLine($"{indent} }}"); + sb.AppendLine($"{indent} finally"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} for (int i = 0; i < count; i++)"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} if (handles[i].IsAllocated)"); + sb.AppendLine($"{indent} handles[i].Free();"); + sb.AppendLine($"{indent} }}"); + sb.AppendLine($"{indent} }}"); + sb.AppendLine($"{indent}}}"); + + sb.AppendLine(" }"); + } + + private static int GetElementSizeFromType(string type, Dictionary structSizes) + { + // Extract the struct type from "const secp256k1_pubkey * const*" + foreach (var kvp in structSizes) + { + if (type.Contains(kvp.Key)) + { + return kvp.Value; + } + } + return 0; + } + + /// + /// Generates wrapper methods for functions with required callback parameters. + /// These methods accept user-friendly delegates and marshal them to native function pointers. + /// + private void GenerateCallbackWrapperMethod(StringBuilder sb, FunctionDef func, Dictionary structSizes, List callbackParams, Secp256k1Api api) + { + var methodName = GetWrapperMethodName(func.Name); + var hasContextParam = func.Parameters.FirstOrDefault()?.Type.Contains("secp256k1_context") == true; + + // Get the callback type info + var callbackParam = callbackParams.First(p => p.Type.Contains("function")); + var nativeCallbackType = callbackParam.Type; + + // Get user-friendly delegate name + if (!CallbackDelegateNames.TryGetValue(nativeCallbackType, out var userDelegateType)) + { + // No user-friendly delegate defined, skip + return; + } + + // Build wrapper parameters list + var wrapperParams = new List<(string type, string name, string? desc, ParameterDef original)>(); + + foreach (var param in func.Parameters) + { + if (param.Type.Contains("secp256k1_context")) + continue; // Skip context param + + if (param.Type.Contains("function")) + { + // Replace native callback type with user-friendly delegate + wrapperParams.Add((userDelegateType, SanitizeParamName(param.Name), param.Description, param)); + } + else if (param.Type == "void*" || param.Type == "const void*") + { + // Data pointer accompanying callback + wrapperParams.Add(("IntPtr", SanitizeParamName(param.Name), param.Description, param)); + } + else if (param.Type.Contains("*")) + { + // Other pointer params become spans + var isOutput = param.Direction == "out" || !param.Type.StartsWith("const "); + var spanType = isOutput ? "Span" : "ReadOnlySpan"; + wrapperParams.Add((spanType, SanitizeParamName(param.Name), param.Description, param)); + } + else + { + // Non-pointer params pass through + wrapperParams.Add((MapCTypeToCSharp(param.Type), SanitizeParamName(param.Name), param.Description, param)); + } + } + + // Generate XML documentation + sb.AppendLine(); + if (!string.IsNullOrEmpty(func.Description)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.Description)}"); + } + + foreach (var (type, name, desc, _) in wrapperParams) + { + if (!string.IsNullOrEmpty(desc)) + { + var xmlParamName = name.TrimStart('@'); + sb.AppendLine($" /// {FormatXmlDescription(desc)}"); + } + } + + if (!string.IsNullOrEmpty(func.ReturnDescription)) + { + sb.AppendLine($" /// {FormatXmlDescription(func.ReturnDescription)}"); + } + + // Method signature + var returnsBool = func.ReturnType == "int"; + var returnType = returnsBool ? "bool" : MapCTypeToCSharp(func.ReturnType); + var paramSignature = string.Join(", ", wrapperParams.Select(p => $"{p.type} {p.name}")); + sb.AppendLine($" public {returnType} {methodName}({paramSignature})"); + sb.AppendLine(" {"); + + // Generate validation for span parameters + foreach (var (type, name, _, original) in wrapperParams) + { + if (!type.Contains("Span")) continue; + + var size = GetRequiredSize(original, structSizes); + if (size > 0) + { + sb.AppendLine($" if ({name}.Length < {size})"); + sb.AppendLine($" throw new ArgumentException($\"{{nameof({name})}} must be at least {size} bytes\");"); + } + } + + // Find the callback parameter name for marshaling + var callbackParamInfo = wrapperParams.First(p => p.type == userDelegateType); + var callbackParamName = callbackParamInfo.name; + + sb.AppendLine(); + + // Generate the native callback wrapper + // We need to look up the function pointer type definition to generate the wrapper + sb.AppendLine($" {nativeCallbackType} nativeCallback = {GenerateNativeCallbackWrapper(nativeCallbackType, callbackParamName, structSizes, api)};"); + sb.AppendLine(); + sb.AppendLine(" var callbackPtr = Marshal.GetFunctionPointerForDelegate(nativeCallback);"); + sb.AppendLine(); + + // Collect span parameters for fixed statement + var spanParams = wrapperParams.Where(p => p.type.Contains("Span")).ToList(); + + if (spanParams.Count > 0) + { + var fixedDeclarations = spanParams.Select(p => + $"{p.name}Ptr = {p.name}"); + sb.AppendLine($" fixed (byte* {string.Join(",\n ", fixedDeclarations)})"); + sb.AppendLine(" {"); + + // Build native call arguments + var nativeArgs = BuildCallbackNativeCallArgs(func, wrapperParams, hasContextParam); + var fieldName = GetFieldName(func.Name); + + if (returnsBool) + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs}) == 1;"); + } + else if (func.ReturnType == "void") + { + sb.AppendLine($" Secp256k1Interop.{fieldName}({nativeArgs});"); + } + else + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs});"); + } + + sb.AppendLine(" }"); + } + else + { + // No span parameters - direct call + var nativeArgs = BuildCallbackNativeCallArgs(func, wrapperParams, hasContextParam); + var fieldName = GetFieldName(func.Name); + + if (returnsBool) + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs}) == 1;"); + } + else if (func.ReturnType == "void") + { + sb.AppendLine($" Secp256k1Interop.{fieldName}({nativeArgs});"); + } + else + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs});"); + } + } + + sb.AppendLine(" }"); + } + + /// + /// Generates the native callback wrapper lambda that converts pointers to Spans and calls the user delegate. + /// + private string GenerateNativeCallbackWrapper(string nativeCallbackType, string userCallbackParamName, Dictionary structSizes, Secp256k1Api api) + { + // Look up the function pointer type definition + var fpType = api.FunctionPointerTypes.FirstOrDefault(f => f.Name == nativeCallbackType); + if (fpType == null) + { + throw new NotSupportedException($"Unknown callback type: {nativeCallbackType}"); + } + + // Build the lambda parameter list (native types) + var lambdaParams = new List(); + foreach (var param in fpType.Parameters) + { + var nativeType = MapCTypeToCSharpForFunctionPointer(param.Type, param.Name); + lambdaParams.Add($"{nativeType} {param.Name}"); + } + + // Build the span conversion statements and delegate call arguments + var spanConversions = new List(); + var delegateArgs = new List(); + + // Build a map of length parameters for variable-length buffers + var lengthParams = new Dictionary(); // buffer name -> length param name + for (int i = 0; i < fpType.Parameters.Count; i++) + { + var param = fpType.Parameters[i]; + if (param.Type.Contains("size_t") && !param.Type.Contains("*")) + { + // This is a length parameter - find the preceding buffer it belongs to + // Convention: length param follows buffer param (e.g., msg, msglen) + if (i > 0) + { + var prevParam = fpType.Parameters[i - 1]; + if (prevParam.Type.Contains("*") && param.Name.StartsWith(prevParam.Name.TrimEnd('*'))) + { + lengthParams[prevParam.Name] = param.Name; + } + } + } + } + + foreach (var param in fpType.Parameters) + { + if (param.Type == "void*" && (param.Name == "data" || param.Name == "d")) + { + // Data pointer - convert to IntPtr + delegateArgs.Add($"(IntPtr){param.Name}"); + } + else if (param.Type.Contains("*") && (param.Type.Contains("char") || param.Type.Contains("void"))) + { + // Pointer parameter - convert to Span + var isOutput = param.Direction == "out" || !param.Type.StartsWith("const "); + var spanType = isOutput ? "Span" : "ReadOnlySpan"; + var spanVarName = $"{param.Name}Span"; + + // Determine the size + // Check for variable-length buffer with associated length param (from JSON LengthParam) + if (!string.IsNullOrEmpty(param.LengthParam)) + { + // Variable-length buffer - check for null and use length param + var nullCheck = param.Nonnull ? "" : $"{param.Name} != null ? "; + var nullFallback = param.Nonnull ? "" : $" : {spanType}.Empty"; + spanConversions.Add($"var {spanVarName} = {nullCheck}new {spanType}({param.Name}, (int){param.LengthParam}){nullFallback};"); + delegateArgs.Add(spanVarName); + } + else if (lengthParams.TryGetValue(param.Name, out var lenParam)) + { + // Fallback: Variable-length buffer detected by naming convention + var nullCheck = param.Nonnull ? "" : $"{param.Name} != null ? "; + var nullFallback = param.Nonnull ? "" : $" : {spanType}.Empty"; + spanConversions.Add($"var {spanVarName} = {nullCheck}new {spanType}({param.Name}, (int){lenParam}){nullFallback};"); + delegateArgs.Add(spanVarName); + } + else + { + // Fixed-size buffer - use pre-computed Size from JSON, or fallback to struct lookup + var size = GetRequiredSize(param, structSizes); + if (size == 0) + { + // Default to 32 for unknown sizes (common case) + size = 32; + } + + if (!param.Nonnull && param.Direction != "out") + { + // Nullable input - check for null + spanConversions.Add($"var {spanVarName} = {param.Name} != null ? new {spanType}({param.Name}, {size}) : {spanType}.Empty;"); + } + else + { + spanConversions.Add($"var {spanVarName} = new {spanType}({param.Name}, {size});"); + } + delegateArgs.Add(spanVarName); + } + } + else if (param.Type.Contains("size_t") && !param.Type.Contains("*")) + { + // Length parameter - pass through to delegate (some delegates want the length too) + delegateArgs.Add(param.Name); + } + else + { + // Other parameters - pass through directly + delegateArgs.Add(param.Name); + } + } + + // Build the lambda body + var sb = new StringBuilder(); + sb.Append($"({string.Join(", ", lambdaParams)}) =>\n {{\n"); + foreach (var conversion in spanConversions) + { + sb.Append($" {conversion}\n"); + } + sb.Append($" return {userCallbackParamName}({string.Join(", ", delegateArgs)});\n"); + sb.Append(" }"); + + return sb.ToString(); + } + + /// + /// Builds native call arguments for callback wrapper methods. + /// + private static string BuildCallbackNativeCallArgs(FunctionDef func, List<(string type, string name, string? desc, ParameterDef original)> wrapperParams, bool hasContextParam) + { + var args = new List(); + + if (hasContextParam) + { + args.Add("_ctx"); + } + + foreach (var (type, name, _, original) in wrapperParams) + { + if (type.Contains("Span")) + { + args.Add($"{name}Ptr"); + } + else if (original.Type.Contains("function")) + { + args.Add("callbackPtr"); + } + else if (type == "IntPtr" && (original.Type == "void*" || original.Type == "const void*")) + { + // Data pointer - pass as void* + args.Add($"{name}.ToPointer()"); + } + else + { + args.Add(name); + } + } + + return string.Join(", ", args); + } + + /// + /// Generates wrapper methods for global function pointers like secp256k1_nonce_function_rfc6979. + /// These are static function pointers exported from the native library that can be invoked directly. + /// + private void GenerateGlobalFunctionPointerWrapper(StringBuilder sb, GlobalPointer global, Secp256k1Api api, Dictionary structSizes) + { + // Find the function pointer type definition + var fpType = api.FunctionPointerTypes.FirstOrDefault(f => f.Name == global.Type); + if (fpType == null) + return; + + // Generate method name from global name (e.g., secp256k1_nonce_function_rfc6979 -> NonceFunctionRfc6979) + var methodName = GetWrapperMethodName(global.Name); + var fieldName = GetFieldName(global.Name); + + // Build wrapper parameters list + var wrapperParams = new List<(string type, string name, string? desc, ParameterDef original)>(); + + foreach (var param in fpType.Parameters) + { + var isOutput = param.Direction == "out" || !param.Type.StartsWith("const "); + + if (param.Type == "void*" || param.Type == "const void*") + { + // Data pointer - convert to Span for user convenience + var spanType = param.Type.StartsWith("const ") ? "ReadOnlySpan" : "Span"; + wrapperParams.Add((spanType, SanitizeParamName(param.Name), param.Description, param)); + } + else if (param.Type.Contains("*")) + { + // Pointer params become spans + var spanType = isOutput ? "Span" : "ReadOnlySpan"; + wrapperParams.Add((spanType, SanitizeParamName(param.Name), param.Description, param)); + } + else + { + // Non-pointer params pass through + wrapperParams.Add((MapCTypeToCSharp(param.Type), SanitizeParamName(param.Name), param.Description, param)); + } + } + + // Generate XML documentation + sb.AppendLine(); + if (!string.IsNullOrEmpty(global.Description)) + { + sb.AppendLine($" /// {FormatXmlDescription(global.Description)}"); + } + + foreach (var (type, name, desc, _) in wrapperParams) + { + if (!string.IsNullOrEmpty(desc)) + { + var xmlParamName = name.TrimStart('@'); + sb.AppendLine($" /// {FormatXmlDescription(desc)}"); + } + } + + if (fpType.ReturnType == "int") + { + sb.AppendLine(" /// True on success, false on failure."); + } + + // Method signature + var returnsBool = fpType.ReturnType == "int"; + var returnType = returnsBool ? "bool" : MapCTypeToCSharp(fpType.ReturnType); + var paramSignature = string.Join(", ", wrapperParams.Select(p => $"{p.type} {p.name}")); + sb.AppendLine($" public {returnType} {methodName}({paramSignature})"); + sb.AppendLine(" {"); + + // Generate validation for span parameters + // Only validate parameters that are required (nonnull) or likely required based on their name + // Skip validation for params like "algo16" which are documented as nullable + foreach (var (type, name, _, original) in wrapperParams) + { + if (!type.Contains("Span")) continue; + + var size = GetRequiredSize(original, structSizes); + if (size > 0) + { + // Skip validation for parameters marked as optional in the JSON + // (e.g., algo16 is documented as "will be NULL for ECDSA for compatibility") + if (original.IsOptional) continue; + + sb.AppendLine($" if ({name}.Length < {size})"); + sb.AppendLine($" throw new ArgumentException($\"{{nameof({name})}} must be at least {size} bytes\");"); + } + } + + // Collect span parameters for fixed statement + var spanParams = wrapperParams.Where(p => p.type.Contains("Span")).ToList(); + + if (spanParams.Count > 0) + { + var fixedDeclarations = spanParams.Select(p => + $"{p.name}Ptr = {p.name}"); + sb.AppendLine($" fixed (byte* {string.Join(",\n ", fixedDeclarations)})"); + sb.AppendLine(" {"); + + // Build native call arguments + var nativeArgs = BuildGlobalFunctionPointerCallArgs(wrapperParams); + + if (returnsBool) + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs}) == 1;"); + } + else if (fpType.ReturnType == "void") + { + sb.AppendLine($" Secp256k1Interop.{fieldName}({nativeArgs});"); + } + else + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs});"); + } + + sb.AppendLine(" }"); + } + else + { + // No span parameters - direct call + var nativeArgs = BuildGlobalFunctionPointerCallArgs(wrapperParams); + + if (returnsBool) + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs}) == 1;"); + } + else if (fpType.ReturnType == "void") + { + sb.AppendLine($" Secp256k1Interop.{fieldName}({nativeArgs});"); + } + else + { + sb.AppendLine($" return Secp256k1Interop.{fieldName}({nativeArgs});"); + } + } + + sb.AppendLine(" }"); + } + + /// + /// Builds native call arguments for global function pointer wrapper methods. + /// + private static string BuildGlobalFunctionPointerCallArgs(List<(string type, string name, string? desc, ParameterDef original)> wrapperParams) + { + var args = new List(); + + foreach (var (type, name, _, original) in wrapperParams) + { + if (type.Contains("Span")) + { + args.Add($"{name}Ptr"); + } + else + { + args.Add(name); + } + } + + return string.Join(", ", args); + } + + #endregion +} diff --git a/Secp256k1.Net.InteropGen/Models.cs b/Secp256k1.Net.InteropGen/Models.cs new file mode 100644 index 0000000..909f7bd --- /dev/null +++ b/Secp256k1.Net.InteropGen/Models.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; + +namespace Secp256k1Net.InteropGen; + +public class Secp256k1Api +{ + public string Version { get; set; } = "0.7.0"; + public string GeneratedAt { get; set; } = DateTime.UtcNow.ToString("O"); + public List Headers { get; set; } = new(); + public List Structs { get; set; } = new(); + public List FunctionPointerTypes { get; set; } = new(); + public List Functions { get; set; } = new(); + public List Constants { get; set; } = new(); + public List GlobalPointers { get; set; } = new(); +} + +public class StructDef +{ + public string Name { get; set; } = ""; + public int Size { get; set; } + public string? Description { get; set; } +} + +public class FunctionPointerType +{ + public string Name { get; set; } = ""; + public string ReturnType { get; set; } = ""; + public List Parameters { get; set; } = new(); + public string? Description { get; set; } +} + +public class FunctionDef +{ + public string Name { get; set; } = ""; + public string ReturnType { get; set; } = ""; + public bool WarnUnusedResult { get; set; } + public bool Deprecated { get; set; } + public string? DeprecatedMessage { get; set; } + public List Parameters { get; set; } = new(); + public string? Description { get; set; } + public string? ReturnDescription { get; set; } + public string? SourceHeader { get; set; } +} + +public class ParameterDef +{ + public string Name { get; set; } = ""; + public string Type { get; set; } = ""; + public string? Direction { get; set; } + public bool Nonnull { get; set; } + public string? Description { get; set; } + + /// + /// Fixed size in bytes for this parameter (e.g., extracted from name like "algo16" → 16, + /// or from description like "32-byte array"). + /// + public int? Size { get; set; } + + /// + /// Name of another parameter that specifies the length of this parameter. + /// Used for variable-length arrays where another param indicates the size. + /// + public string? LengthParam { get; set; } + + /// + /// If this parameter is a length/size indicator for another parameter, + /// this is the name of the parameter it describes. + /// + public string? IsLengthFor { get; set; } + + /// + /// True if this parameter is known to be optional (can be null/empty even when validation + /// would otherwise be applied). Examples: algo16, data parameters. + /// + public bool IsOptional { get; set; } +} + +public class ConstantDef +{ + public string Name { get; set; } = ""; + public string Value { get; set; } = ""; + public long? NumericValue { get; set; } + public string? Description { get; set; } +} + +public class GlobalPointer +{ + public string Name { get; set; } = ""; + public string Type { get; set; } = ""; + public bool IsConst { get; set; } + public string? Description { get; set; } +} diff --git a/Secp256k1.Net.InteropGen/Program.cs b/Secp256k1.Net.InteropGen/Program.cs new file mode 100644 index 0000000..608edb8 --- /dev/null +++ b/Secp256k1.Net.InteropGen/Program.cs @@ -0,0 +1,249 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Secp256k1Net.InteropGen; + +class Program +{ + static int Main(string[] args) + { + if (args.Length < 1) + { + PrintUsage(); + return 1; + } + + var command = args[0].ToLowerInvariant(); + + return command switch + { + "parse" => RunParse(args[1..]), + "generate" => RunGenerate(args[1..]), + "all" => RunAll(args[1..]), + "-h" or "--help" or "help" => PrintUsage(), + _ => UnknownCommand(args[0]) + }; + } + + static int PrintUsage() + { + Console.WriteLine("Secp256k1.Net.InteropGen - C header parser and C# interop code generator"); + Console.WriteLine(); + Console.WriteLine("Usage:"); + Console.WriteLine(" InteropGen parse "); + Console.WriteLine(" Parse secp256k1 header files and generate JSON API definition"); + Console.WriteLine(); + Console.WriteLine(" InteropGen generate "); + Console.WriteLine(" Generate C# interop code from JSON API definition"); + Console.WriteLine(); + Console.WriteLine(" InteropGen all [--save-json ]"); + Console.WriteLine(" Parse headers and generate C# code in one step"); + Console.WriteLine(); + Console.WriteLine("Examples:"); + Console.WriteLine(" InteropGen parse secp256k1/include api.json"); + Console.WriteLine(" InteropGen generate api.json Secp256k1.Net/Generated"); + Console.WriteLine(" InteropGen all secp256k1/include Secp256k1.Net/Generated"); + Console.WriteLine(" InteropGen all secp256k1/include Secp256k1.Net/Generated --save-json api.json"); + return 0; + } + + static int UnknownCommand(string command) + { + Console.Error.WriteLine($"Unknown command: {command}"); + Console.Error.WriteLine(); + PrintUsage(); + return 1; + } + + static int RunParse(string[] args) + { + if (args.Length < 2) + { + Console.Error.WriteLine("Usage: InteropGen parse "); + return 1; + } + + var includeDir = args[0]; + var outputPath = args[1]; + + if (!Directory.Exists(includeDir)) + { + Console.Error.WriteLine($"Error: Include directory not found: {includeDir}"); + return 1; + } + + try + { + var parser = new Secp256k1HeaderParser(); + var api = parser.ParseDirectory(includeDir); + + var options = new JsonSerializerOptions + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + var json = JsonSerializer.Serialize(api, options); + + var outputDir = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir)) + { + Directory.CreateDirectory(outputDir); + } + + File.WriteAllText(outputPath, json); + + Console.WriteLine($"Generated {outputPath}"); + Console.WriteLine($" Functions: {api.Functions.Count}"); + Console.WriteLine($" Structs: {api.Structs.Count}"); + Console.WriteLine($" Function pointer types: {api.FunctionPointerTypes.Count}"); + Console.WriteLine($" Constants: {api.Constants.Count}"); + Console.WriteLine($" Global pointers: {api.GlobalPointers.Count}"); + + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + } + + static int RunGenerate(string[] args) + { + if (args.Length < 2) + { + Console.Error.WriteLine("Usage: InteropGen generate "); + return 1; + } + + var inputPath = args[0]; + var outputDir = args[1]; + + if (!File.Exists(inputPath)) + { + Console.Error.WriteLine($"Error: Input file not found: {inputPath}"); + return 1; + } + + try + { + var jsonContent = File.ReadAllText(inputPath); + var api = JsonSerializer.Deserialize(jsonContent, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + if (api == null) + { + Console.Error.WriteLine("Error: Failed to parse JSON API definition"); + return 1; + } + + return GenerateCode(api, outputDir); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + } + + static int RunAll(string[] args) + { + if (args.Length < 2) + { + Console.Error.WriteLine("Usage: InteropGen all [--save-json ]"); + return 1; + } + + var includeDir = args[0]; + var outputDir = args[1]; + string? jsonOutputPath = null; + + // Parse optional --save-json argument + for (int i = 2; i < args.Length; i++) + { + if (args[i] == "--save-json" && i + 1 < args.Length) + { + jsonOutputPath = args[i + 1]; + i++; + } + } + + if (!Directory.Exists(includeDir)) + { + Console.Error.WriteLine($"Error: Include directory not found: {includeDir}"); + return 1; + } + + try + { + // Parse headers + Console.WriteLine($"Parsing headers from {includeDir}..."); + var parser = new Secp256k1HeaderParser(); + var api = parser.ParseDirectory(includeDir); + + Console.WriteLine($" Functions: {api.Functions.Count}"); + Console.WriteLine($" Structs: {api.Structs.Count}"); + Console.WriteLine($" Function pointer types: {api.FunctionPointerTypes.Count}"); + Console.WriteLine($" Constants: {api.Constants.Count}"); + Console.WriteLine($" Global pointers: {api.GlobalPointers.Count}"); + + // Optionally save JSON + if (jsonOutputPath != null) + { + var options = new JsonSerializerOptions + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + var json = JsonSerializer.Serialize(api, options); + + var jsonDir = Path.GetDirectoryName(jsonOutputPath); + if (!string.IsNullOrEmpty(jsonDir) && !Directory.Exists(jsonDir)) + { + Directory.CreateDirectory(jsonDir); + } + + File.WriteAllText(jsonOutputPath, json); + Console.WriteLine($"Saved JSON: {jsonOutputPath}"); + } + + // Generate C# code + Console.WriteLine(); + return GenerateCode(api, outputDir); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + } + + static int GenerateCode(Secp256k1Api api, string outputDir) + { + Directory.CreateDirectory(outputDir); + + var generator = new InteropGenerator(); + + // Generate native interop code + var nativeSource = generator.GenerateNative(api); + var nativePath = Path.Combine(outputDir, "Secp256k1.Native.g.cs"); + File.WriteAllText(nativePath, nativeSource); + Console.WriteLine($"Generated: {nativePath}"); + + // Generate safe wrapper methods + var wrappersSource = generator.GenerateWrappers(api); + var wrappersPath = Path.Combine(outputDir, "Secp256k1.Wrappers.g.cs"); + File.WriteAllText(wrappersPath, wrappersSource); + Console.WriteLine($"Generated: {wrappersPath}"); + + return 0; + } +} diff --git a/Secp256k1.Net.InteropGen/Secp256k1.Net.InteropGen.csproj b/Secp256k1.Net.InteropGen/Secp256k1.Net.InteropGen.csproj new file mode 100644 index 0000000..8e26144 --- /dev/null +++ b/Secp256k1.Net.InteropGen/Secp256k1.Net.InteropGen.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + latest + + + + + + + diff --git a/Secp256k1.Net.Test/GeneratedWrapperTests.cs b/Secp256k1.Net.Test/GeneratedWrapperTests.cs new file mode 100644 index 0000000..99b640b --- /dev/null +++ b/Secp256k1.Net.Test/GeneratedWrapperTests.cs @@ -0,0 +1,1089 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Secp256k1Net.Test +{ + /// + /// Tests for the auto-generated wrapper functions in Secp256k1.Wrappers.g.cs. + /// These tests cover the direct native wrapper methods that provide Span-based access. + /// + [TestClass] + public class GeneratedWrapperTests + { + // Helper methods for cross-framework compatibility + private static byte[] ComputeSha256(byte[] data) + { + using (var sha256 = SHA256.Create()) + { + return sha256.ComputeHash(data); + } + } + + private static void FillRandom(byte[] data) + { + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(data); + } + } + + private static byte[] HexToBytes(string hex) + { + var bytes = new byte[hex.Length / 2]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)int.Parse(hex.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber); + } + return bytes; + } + + private static string BytesToHex(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } + + // Test data - known valid keypairs + private static readonly byte[] TestPrivateKey = HexToBytes("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + private static readonly byte[] TestPublicKey = HexToBytes("2208d5dc41d4f3ed555aff761e9bb0b99fbe6d1503b98711944be6a362242ebfa1c788c7a4e13f6aaa4099f9d2175fc031e5aa3ba08eb280e87dfb43bdae207f"); + + #region Selftest + + [TestMethod] + public void Selftest_Succeeds() + { + using var secp256k1 = new Secp256k1(); + // Should not throw + secp256k1.Selftest(); + } + + #endregion + + #region EC Public Key Functions + + [TestMethod] + public void EcPubkeyParse_ValidCompressedKey_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + // Create a valid compressed public key + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey)); + + var serialized = new byte[33]; + nuint outputLen = 33; + Assert.IsTrue(secp256k1.EcPubkeySerialize(serialized, ref outputLen, pubkey, Secp256k1EcFlags.Compressed)); + + // Parse the compressed key + var parsedPubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyParse(parsedPubkey, serialized)); + Assert.AreEqual(BytesToHex(pubkey), BytesToHex(parsedPubkey)); + } + + [TestMethod] + public void EcPubkeyParse_ValidUncompressedKey_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey)); + + var serialized = new byte[65]; + nuint outputLen = 65; + Assert.IsTrue(secp256k1.EcPubkeySerialize(serialized, ref outputLen, pubkey, Secp256k1EcFlags.Uncompressed)); + + var parsedPubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyParse(parsedPubkey, serialized)); + Assert.AreEqual(BytesToHex(pubkey), BytesToHex(parsedPubkey)); + } + + [TestMethod] + public void EcPubkeySerialize_Compressed_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey)); + + var output = new byte[33]; + nuint outputLen = 33; + Assert.IsTrue(secp256k1.EcPubkeySerialize(output, ref outputLen, pubkey, Secp256k1EcFlags.Compressed)); + Assert.AreEqual((nuint)33, outputLen); + // Compressed keys start with 0x02 or 0x03 + Assert.IsTrue(output[0] == 0x02 || output[0] == 0x03); + } + + [TestMethod] + public void EcPubkeySerialize_Uncompressed_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey)); + + var output = new byte[65]; + nuint outputLen = 65; + Assert.IsTrue(secp256k1.EcPubkeySerialize(output, ref outputLen, pubkey, Secp256k1EcFlags.Uncompressed)); + Assert.AreEqual((nuint)65, outputLen); + // Uncompressed keys start with 0x04 + Assert.AreEqual(0x04, output[0]); + } + + [TestMethod] + public void EcPubkeyCmp_SameKeys_ReturnsZero() + { + using var secp256k1 = new Secp256k1(); + + var pubkey1 = new byte[64]; + var pubkey2 = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, TestPrivateKey)); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, TestPrivateKey)); + + // Same keys should return 0 (equal) + var result = secp256k1.EcPubkeyCmp(pubkey1, pubkey2); + Assert.AreEqual(0, result); + } + + [TestMethod] + public void EcPubkeyCmp_DifferentKeys_ReturnsNonZero() + { + using var secp256k1 = new Secp256k1(); + + var privkey2 = HexToBytes("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"); + + var pubkey1 = new byte[64]; + var pubkey2 = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, TestPrivateKey)); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, privkey2)); + + // Different keys should return non-zero comparison (<0 or >0) + var result = secp256k1.EcPubkeyCmp(pubkey1, pubkey2); + Assert.AreNotEqual(0, result); + } + + [TestMethod] + public void EcPubkeyCreate_ValidSeckey_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey)); + Assert.AreEqual(BytesToHex(TestPublicKey), BytesToHex(pubkey)); + } + + [TestMethod] + public void EcSeckeyVerify_ValidKey_ReturnsTrue() + { + using var secp256k1 = new Secp256k1(); + Assert.IsTrue(secp256k1.EcSeckeyVerify(TestPrivateKey)); + } + + [TestMethod] + public void EcSeckeyVerify_ZeroKey_ReturnsFalse() + { + using var secp256k1 = new Secp256k1(); + var zeroKey = new byte[32]; + Assert.IsFalse(secp256k1.EcSeckeyVerify(zeroKey)); + } + + [TestMethod] + public void EcSeckeyNegate_TwiceReturnsOriginal() + { + using var secp256k1 = new Secp256k1(); + + var seckey = TestPrivateKey.ToArray(); + var original = TestPrivateKey.ToArray(); + + Assert.IsTrue(secp256k1.EcSeckeyNegate(seckey)); + Assert.AreNotEqual(BytesToHex(original), BytesToHex(seckey)); + + Assert.IsTrue(secp256k1.EcSeckeyNegate(seckey)); + Assert.AreEqual(BytesToHex(original), BytesToHex(seckey)); + } + + [TestMethod] + public void EcPubkeyNegate_TwiceReturnsOriginal() + { + using var secp256k1 = new Secp256k1(); + + var pubkey = TestPublicKey.ToArray(); + var original = TestPublicKey.ToArray(); + + Assert.IsTrue(secp256k1.EcPubkeyNegate(pubkey)); + Assert.AreNotEqual(BytesToHex(original), BytesToHex(pubkey)); + + Assert.IsTrue(secp256k1.EcPubkeyNegate(pubkey)); + Assert.AreEqual(BytesToHex(original), BytesToHex(pubkey)); + } + + [TestMethod] + public void EcSeckeyTweakAdd_ValidTweak_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var seckey = TestPrivateKey.ToArray(); + var tweak = new byte[32]; + tweak[31] = 1; // Small valid tweak + + Assert.IsTrue(secp256k1.EcSeckeyTweakAdd(seckey, tweak)); + Assert.AreNotEqual(BytesToHex(TestPrivateKey), BytesToHex(seckey)); + } + + [TestMethod] + public void EcPubkeyTweakAdd_ValidTweak_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var pubkey = TestPublicKey.ToArray(); + var tweak = new byte[32]; + tweak[31] = 1; + + Assert.IsTrue(secp256k1.EcPubkeyTweakAdd(pubkey, tweak)); + Assert.AreNotEqual(BytesToHex(TestPublicKey), BytesToHex(pubkey)); + } + + [TestMethod] + public void EcSeckeyTweakMul_ValidTweak_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var seckey = TestPrivateKey.ToArray(); + var tweak = new byte[32]; + tweak[31] = 2; // Multiply by 2 + + Assert.IsTrue(secp256k1.EcSeckeyTweakMul(seckey, tweak)); + Assert.AreNotEqual(BytesToHex(TestPrivateKey), BytesToHex(seckey)); + } + + [TestMethod] + public void EcPubkeyTweakMul_ValidTweak_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var pubkey = TestPublicKey.ToArray(); + var tweak = new byte[32]; + tweak[31] = 2; + + Assert.IsTrue(secp256k1.EcPubkeyTweakMul(pubkey, tweak)); + Assert.AreNotEqual(BytesToHex(TestPublicKey), BytesToHex(pubkey)); + } + + #endregion + + #region ECDSA Signature Functions + + [TestMethod] + public void EcdsaSignatureParseCompact_ValidSig_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + // Create a signature + var msgHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test message")); + var sig = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaSign(sig, msgHash, TestPrivateKey)); + + // Serialize to compact + var compact = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaSignatureSerializeCompact(compact, sig)); + + // Parse it back + var parsedSig = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaSignatureParseCompact(parsedSig, compact)); + Assert.AreEqual(BytesToHex(sig), BytesToHex(parsedSig)); + } + + [TestMethod] + public void EcdsaSignatureParseDer_ValidSig_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var msgHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test message")); + var sig = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaSign(sig, msgHash, TestPrivateKey)); + + // Serialize to DER + var der = new byte[72]; + nuint derLen = 72; + Assert.IsTrue(secp256k1.EcdsaSignatureSerializeDer(der, ref derLen, sig)); + + // Parse it back + var parsedSig = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaSignatureParseDer(parsedSig, der.AsSpan(0, (int)derLen))); + Assert.AreEqual(BytesToHex(sig), BytesToHex(parsedSig)); + } + + [TestMethod] + public void EcdsaVerify_ValidSignature_ReturnsTrue() + { + using var secp256k1 = new Secp256k1(); + + var msgHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test message")); + var sig = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaSign(sig, msgHash, TestPrivateKey)); + + Assert.IsTrue(secp256k1.EcdsaVerify(sig, msgHash, TestPublicKey)); + } + + [TestMethod] + public void EcdsaVerify_InvalidSignature_ReturnsFalse() + { + using var secp256k1 = new Secp256k1(); + + var msgHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test message")); + var wrongHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("different message")); + var sig = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaSign(sig, msgHash, TestPrivateKey)); + + Assert.IsFalse(secp256k1.EcdsaVerify(sig, wrongHash, TestPublicKey)); + } + + [TestMethod] + public void EcdsaSignatureNormalize_AlreadyLowS_ReturnsFalse() + { + using var secp256k1 = new Secp256k1(); + + var msgHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test")); + var sig = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaSign(sig, msgHash, TestPrivateKey)); + + var normalized = new byte[64]; + // Sign already produces low-S signatures, so normalize should return false + var wasNormalized = secp256k1.EcdsaSignatureNormalize(normalized, sig); + // Result is whether it was modified (high-S to low-S) + } + + #endregion + + #region ECDSA Recovery Functions + + [TestMethod] + public void EcdsaRecoverableSignatureParseCompact_ValidSig_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var msgHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test")); + var recoverableSig = new byte[65]; + Assert.IsTrue(secp256k1.EcdsaSignRecoverable(recoverableSig, msgHash, TestPrivateKey)); + + // Serialize + var compact = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaRecoverableSignatureSerializeCompact(compact, out var recid, recoverableSig)); + + // Parse back + var parsedSig = new byte[65]; + Assert.IsTrue(secp256k1.EcdsaRecoverableSignatureParseCompact(parsedSig, compact, recid)); + } + + [TestMethod] + public void EcdsaRecoverableSignatureConvert_ToRegularSig_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var msgHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test")); + var recoverableSig = new byte[65]; + Assert.IsTrue(secp256k1.EcdsaSignRecoverable(recoverableSig, msgHash, TestPrivateKey)); + + var regularSig = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaRecoverableSignatureConvert(regularSig, recoverableSig)); + + // Verify the regular signature works + Assert.IsTrue(secp256k1.EcdsaVerify(regularSig, msgHash, TestPublicKey)); + } + + [TestMethod] + public void EcdsaRecover_ValidSignature_RecoversPubkey() + { + using var secp256k1 = new Secp256k1(); + + var msgHash = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test")); + var recoverableSig = new byte[65]; + Assert.IsTrue(secp256k1.EcdsaSignRecoverable(recoverableSig, msgHash, TestPrivateKey)); + + var recoveredPubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcdsaRecover(recoveredPubkey, recoverableSig, msgHash)); + Assert.AreEqual(BytesToHex(TestPublicKey), BytesToHex(recoveredPubkey)); + } + + #endregion + + #region Tagged Hash Functions + + [TestMethod] + public void TaggedSha256_ProducesValidHash() + { + using var secp256k1 = new Secp256k1(); + + var tag = System.Text.Encoding.UTF8.GetBytes("BIP0340/challenge"); + var msg = System.Text.Encoding.UTF8.GetBytes("test message"); + var hash = new byte[32]; + + Assert.IsTrue(secp256k1.TaggedSha256(hash, tag, msg)); + + // Hash should not be all zeros + Assert.IsFalse(hash.All(b => b == 0)); + } + + #endregion + + #region X-only Pubkey Functions + + [TestMethod] + public void XonlyPubkeyParse_ValidKey_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + // Create keypair + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + // Get x-only pubkey + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair)); + + // Serialize + var serialized = new byte[32]; + Assert.IsTrue(secp256k1.XonlyPubkeySerialize(serialized, xonlyPubkey)); + + // Parse back + var parsed = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyParse(parsed, serialized)); + } + + [TestMethod] + public void XonlyPubkeyCmp_SameKeys_ReturnsZero() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair)); + + // Same key comparison returns 0 (equal) + Assert.AreEqual(0, secp256k1.XonlyPubkeyCmp(xonlyPubkey, xonlyPubkey)); + } + + [TestMethod] + public void XonlyPubkeyFromPubkey_ConvertsProperly() + { + using var secp256k1 = new Secp256k1(); + + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey)); + + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyFromPubkey(xonlyPubkey, out var parity, pubkey)); + + // Parity should be 0 or 1 + Assert.IsTrue(parity == 0 || parity == 1); + } + + [TestMethod] + public void XonlyPubkeyTweakAdd_ValidTweak_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair)); + + var outputPubkey = new byte[64]; + var tweak = new byte[32]; + tweak[31] = 1; + + Assert.IsTrue(secp256k1.XonlyPubkeyTweakAdd(outputPubkey, xonlyPubkey, tweak)); + } + + [TestMethod] + public void XonlyPubkeyTweakAddCheck_ValidTweak_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair)); + + var tweak = new byte[32]; + tweak[31] = 1; + + // Tweak the pubkey + var tweakedPubkey = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyTweakAdd(tweakedPubkey, xonlyPubkey, tweak)); + + // Get x-only from tweaked + var tweakedXonly = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyFromPubkey(tweakedXonly, out var parity, tweakedPubkey)); + + // Serialize + var tweakedSerialized = new byte[32]; + Assert.IsTrue(secp256k1.XonlyPubkeySerialize(tweakedSerialized, tweakedXonly)); + + // Check + Assert.IsTrue(secp256k1.XonlyPubkeyTweakAddCheck(tweakedSerialized, parity, xonlyPubkey, tweak)); + } + + #endregion + + #region Keypair Functions + + [TestMethod] + public void KeypairCreate_ValidSeckey_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + } + + [TestMethod] + public void KeypairSec_ExtractsSeckey() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var seckey = new byte[32]; + Assert.IsTrue(secp256k1.KeypairSec(seckey, keypair)); + Assert.AreEqual(BytesToHex(TestPrivateKey), BytesToHex(seckey)); + } + + [TestMethod] + public void KeypairPub_ExtractsPubkey() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairPub(pubkey, keypair)); + Assert.AreEqual(BytesToHex(TestPublicKey), BytesToHex(pubkey)); + } + + [TestMethod] + public void KeypairXonlyPub_ExtractsXonlyPubkey() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out var parity, keypair)); + Assert.IsTrue(parity == 0 || parity == 1); + } + + [TestMethod] + public void KeypairXonlyTweakAdd_ValidTweak_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var tweak = new byte[32]; + tweak[31] = 1; + + Assert.IsTrue(secp256k1.KeypairXonlyTweakAdd(keypair, tweak)); + } + + #endregion + + #region Schnorr Signature Functions + + [TestMethod] + public void SchnorrsigSign32_ValidInputs_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var msg32 = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test message")); + var auxRand = new byte[32]; + FillRandom(auxRand); + + var sig64 = new byte[64]; + Assert.IsTrue(secp256k1.SchnorrsigSign32(sig64, msg32, keypair, auxRand)); + } + + [TestMethod] + public void SchnorrsigVerify_ValidSignature_ReturnsTrue() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var msg = System.Text.Encoding.UTF8.GetBytes("test message"); + var msg32 = ComputeSha256(msg); + var auxRand = new byte[32]; + FillRandom(auxRand); + + var sig64 = new byte[64]; + Assert.IsTrue(secp256k1.SchnorrsigSign32(sig64, msg32, keypair, auxRand)); + + // Get x-only pubkey + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair)); + + Assert.IsTrue(secp256k1.SchnorrsigVerify(sig64, msg32, xonlyPubkey)); + } + + [TestMethod] + public void SchnorrsigVerify_InvalidSignature_ReturnsFalse() + { + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var msg32 = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("test message")); + var wrongMsg = ComputeSha256(System.Text.Encoding.UTF8.GetBytes("different")); + var auxRand = new byte[32]; + FillRandom(auxRand); + + var sig64 = new byte[64]; + Assert.IsTrue(secp256k1.SchnorrsigSign32(sig64, msg32, keypair, auxRand)); + + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair)); + + Assert.IsFalse(secp256k1.SchnorrsigVerify(sig64, wrongMsg, xonlyPubkey)); + } + + #endregion + + #region ElligatorSwift Functions + + [TestMethod] + public void EllswiftCreate_ValidInputs_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var ell64 = new byte[64]; + var auxRand = new byte[32]; + FillRandom(auxRand); + + Assert.IsTrue(secp256k1.EllswiftCreate(ell64, TestPrivateKey, auxRand)); + } + + [TestMethod] + public void EllswiftEncode_ValidPubkey_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var ell64 = new byte[64]; + var rnd32 = new byte[32]; + FillRandom(rnd32); + + Assert.IsTrue(secp256k1.EllswiftEncode(ell64, TestPublicKey, rnd32)); + } + + [TestMethod] + public void EllswiftDecode_RoundTrip_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var ell64 = new byte[64]; + var rnd32 = new byte[32]; + FillRandom(rnd32); + + Assert.IsTrue(secp256k1.EllswiftEncode(ell64, TestPublicKey, rnd32)); + + var decodedPubkey = new byte[64]; + Assert.IsTrue(secp256k1.EllswiftDecode(decodedPubkey, ell64)); + Assert.AreEqual(BytesToHex(TestPublicKey), BytesToHex(decodedPubkey)); + } + + #endregion + + #region MuSig Functions + + [TestMethod] + public void MusigPubnonceSerialize_MethodExists() + { + // Note: Full MuSig testing requires MusigPubkeyAgg which isn't generated + // (it has a pointer-to-pointer parameter). This test just verifies the methods exist. + using var secp256k1 = new Secp256k1(); + + // Verify the serialize/parse methods exist and can be called + var pubnonce = new byte[132]; + var serialized = new byte[66]; + var parsed = new byte[132]; + + // These will return false because pubnonce is not valid, but the methods exist + secp256k1.MusigPubnonceSerialize(serialized, pubnonce); + secp256k1.MusigPubnonceParse(parsed, serialized); + } + + [TestMethod] + public void MusigPartialSigParse_RoundTrip_ProducesValidStructure() + { + // This test verifies the parse/serialize round trip works + // We can't fully test without a complete MuSig signing flow + using var secp256k1 = new Secp256k1(); + + // Create a minimal partial sig structure + var partialSig = new byte[36]; + var in32 = new byte[32]; + in32[0] = 1; // Non-zero to make it look like a valid scalar + + // This may fail if in32 is not a valid partial sig encoding + // Just test that the methods exist and can be called + var parsed = new byte[36]; + var result = secp256k1.MusigPartialSigParse(parsed, in32); + // Result depends on validity of input + } + + [TestMethod] + public void MusigNonceGen_MethodExists() + { + // Note: Full MuSig testing requires MusigPubkeyAgg which isn't generated + // This test verifies the method exists and validates parameters + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairPub(pubkey, keypair)); + + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var sessionRand = new byte[32]; + FillRandom(sessionRand); + + var msg32 = new byte[32]; + FillRandom(msg32); + var keyaggCache = new byte[197]; // Not properly initialized + var extraInput = new byte[32]; + + // This will fail because keyagg_cache isn't properly initialized + // (requires MusigPubkeyAgg which has unsupported pointer-to-pointer signature) + // We just verify the method exists and can be called + var result = secp256k1.MusigNonceGen(secnonce, pubnonce, sessionRand, TestPrivateKey, pubkey, msg32, keyaggCache, extraInput); + // Result is expected to be false due to invalid keyagg_cache + Assert.IsFalse(result); + } + + [TestMethod] + public void MusigNonceGenCounter_MethodExists() + { + // Note: Full MuSig testing requires MusigPubkeyAgg which isn't generated + using var secp256k1 = new Secp256k1(); + + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, TestPrivateKey)); + + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + + var msg32 = new byte[32]; + FillRandom(msg32); + var keyaggCache = new byte[197]; // Not properly initialized + var extraInput = new byte[32]; + + // This will fail because keyagg_cache isn't properly initialized + var result = secp256k1.MusigNonceGenCounter(secnonce, pubnonce, 1, keypair, msg32, keyaggCache, extraInput); + Assert.IsFalse(result); + } + + #endregion + + #region Array-of-Pointers Functions (New Generated Wrappers) + + [TestMethod] + public void EcPubkeyCombine_TwoPubkeys_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + // Create two keypairs + var privkey1 = new byte[32]; + var privkey2 = new byte[32]; + FillRandom(privkey1); + FillRandom(privkey2); + + var pubkey1 = new byte[64]; + var pubkey2 = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, privkey1)); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, privkey2)); + + // Combine them using the generated wrapper + var combinedPubkey = new byte[64]; + var result = secp256k1.EcPubkeyCombine(combinedPubkey, new[] { pubkey1, pubkey2 }); + Assert.IsTrue(result); + + // Verify the combined pubkey is different from both inputs + Assert.AreNotEqual(BytesToHex(pubkey1), BytesToHex(combinedPubkey)); + Assert.AreNotEqual(BytesToHex(pubkey2), BytesToHex(combinedPubkey)); + } + + [TestMethod] + public void EcPubkeyCombine_ThreePubkeys_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + var privkey1 = new byte[32]; + var privkey2 = new byte[32]; + var privkey3 = new byte[32]; + FillRandom(privkey1); + FillRandom(privkey2); + FillRandom(privkey3); + + var pubkey1 = new byte[64]; + var pubkey2 = new byte[64]; + var pubkey3 = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, privkey1)); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, privkey2)); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey3, privkey3)); + + // Combine three pubkeys + var combinedPubkey = new byte[64]; + var result = secp256k1.EcPubkeyCombine(combinedPubkey, new[] { pubkey1, pubkey2, pubkey3 }); + Assert.IsTrue(result); + } + + [TestMethod] + public void EcPubkeySort_SortsTwoPubkeys() + { + using var secp256k1 = new Secp256k1(); + + // Create two pubkeys from different private keys + var pubkey1 = new byte[64]; + var pubkey2 = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, TestPrivateKey)); + + var privkey2 = HexToBytes("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, privkey2)); + + // Serialize pubkeys to compare lexicographic order + var serialized1 = new byte[33]; + var serialized2 = new byte[33]; + nuint outputLen1 = 33; + nuint outputLen2 = 33; + Assert.IsTrue(secp256k1.EcPubkeySerialize(serialized1, ref outputLen1, pubkey1, Secp256k1EcFlags.Compressed)); + Assert.IsTrue(secp256k1.EcPubkeySerialize(serialized2, ref outputLen2, pubkey2, Secp256k1EcFlags.Compressed)); + + // Determine which should come first lexicographically + var comparison = CompareBytes(serialized1, serialized2); + Assert.AreNotEqual(0, comparison, "Pubkeys should be different"); + + // Put them in reverse sorted order + byte[][] pubkeys; + byte[] expectedFirst, expectedSecond; + if (comparison < 0) + { + // pubkey1 < pubkey2, so put pubkey2 first (reverse order) + pubkeys = new[] { pubkey2, pubkey1 }; + expectedFirst = pubkey1; + expectedSecond = pubkey2; + } + else + { + // pubkey2 < pubkey1, so put pubkey1 first (reverse order) + pubkeys = new[] { pubkey1, pubkey2 }; + expectedFirst = pubkey2; + expectedSecond = pubkey1; + } + + // Sort + var result = secp256k1.EcPubkeySort(pubkeys); + Assert.IsTrue(result); + + // Verify the array is now sorted + Assert.IsTrue(AreByteArraysEqual(pubkeys[0], expectedFirst), "First pubkey should be the lexicographically smaller one"); + Assert.IsTrue(AreByteArraysEqual(pubkeys[1], expectedSecond), "Second pubkey should be the lexicographically larger one"); + } + + private static int CompareBytes(byte[] a, byte[] b) + { + var minLen = Math.Min(a.Length, b.Length); + for (int i = 0; i < minLen; i++) + { + if (a[i] < b[i]) return -1; + if (a[i] > b[i]) return 1; + } + return a.Length.CompareTo(b.Length); + } + + private static bool AreByteArraysEqual(byte[] a, byte[] b) + { + if (a.Length != b.Length) return false; + for (int i = 0; i < a.Length; i++) + { + if (a[i] != b[i]) return false; + } + return true; + } + + [TestMethod] + public void MusigPubkeyAgg_AggregateTwoPubkeys_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + // Create two pubkeys + var pubkey1 = new byte[64]; + var pubkey2 = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, TestPrivateKey)); + + var privkey2 = HexToBytes("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, privkey2)); + + // Aggregate them + var aggPk = new byte[64]; + var keyaggCache = new byte[197]; + var result = secp256k1.MusigPubkeyAgg(aggPk, keyaggCache, new[] { pubkey1, pubkey2 }); + Assert.IsTrue(result); + } + + [TestMethod] + public void MusigNonceAgg_AggregatesTwoNonces_Succeeds() + { + using var secp256k1 = new Secp256k1(); + + // First set up two signers with valid keyagg + var pubkey1 = new byte[64]; + var pubkey2 = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, TestPrivateKey)); + + var privkey2 = HexToBytes("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, privkey2)); + + // Create keyagg cache + var aggPk = new byte[64]; + var keyaggCache = new byte[197]; + Assert.IsTrue(secp256k1.MusigPubkeyAgg(aggPk, keyaggCache, new[] { pubkey1, pubkey2 })); + + // Generate nonces for both signers + var msg32 = new byte[32]; + FillRandom(msg32); + + var extraInput = new byte[32]; // Required parameter, can be zero-filled + + var secnonce1 = new byte[132]; + var pubnonce1 = new byte[132]; + var sessionRand1 = new byte[32]; + FillRandom(sessionRand1); + Assert.IsTrue(secp256k1.MusigNonceGen(secnonce1, pubnonce1, sessionRand1, TestPrivateKey, pubkey1, msg32, keyaggCache, extraInput)); + + var secnonce2 = new byte[132]; + var pubnonce2 = new byte[132]; + var sessionRand2 = new byte[32]; + FillRandom(sessionRand2); + Assert.IsTrue(secp256k1.MusigNonceGen(secnonce2, pubnonce2, sessionRand2, privkey2, pubkey2, msg32, keyaggCache, extraInput)); + + // Aggregate the nonces + var aggnonce = new byte[132]; + var result = secp256k1.MusigNonceAgg(aggnonce, new[] { pubnonce1, pubnonce2 }); + Assert.IsTrue(result); + } + + [TestMethod] + public void EcPubkeyCombine_NullArray_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[64]; + + Assert.ThrowsException(() => + secp256k1.EcPubkeyCombine(output, null)); + } + + [TestMethod] + public void EcPubkeyCombine_EmptyArray_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[64]; + + Assert.ThrowsException(() => + secp256k1.EcPubkeyCombine(output, new byte[0][])); + } + + [TestMethod] + public void EcPubkeyCombine_TooSmallElement_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[64]; + var smallPubkey = new byte[63]; // Too small + + Assert.ThrowsException(() => + secp256k1.EcPubkeyCombine(output, new[] { smallPubkey })); + } + + #endregion + + #region Argument Validation Tests + + [TestMethod] + public void EcPubkeyParse_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Too small + var input = new byte[33]; + + Assert.ThrowsException(() => + secp256k1.EcPubkeyParse(pubkey, input)); + } + + [TestMethod] + public void EcPubkeySerialize_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + + // First create a valid public key + var pubkey = new byte[64]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey)); + + // Try to serialize with too small output - wrapper validates based on flags + var output = new byte[32]; // Too small for compressed (33) or uncompressed (65) + nuint outputLen = 32; + + // The wrapper validates output size based on flags and throws ArgumentException + Assert.ThrowsException(() => + secp256k1.EcPubkeySerialize(output, ref outputLen, pubkey, Secp256k1EcFlags.Compressed)); + } + + [TestMethod] + public void EcSeckeyVerify_TooSmallInput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[31]; // Too small + + Assert.ThrowsException(() => + secp256k1.EcSeckeyVerify(seckey)); + } + + [TestMethod] + public void KeypairCreate_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var keypair = new byte[95]; // Too small + var seckey = new byte[32]; + + Assert.ThrowsException(() => + secp256k1.KeypairCreate(keypair, seckey)); + } + + [TestMethod] + public void SchnorrsigSign32_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[63]; // Too small + var msg32 = new byte[32]; + var keypair = new byte[96]; + var auxRand = new byte[32]; + + Assert.ThrowsException(() => + secp256k1.SchnorrsigSign32(sig, msg32, keypair, auxRand)); + } + + #endregion + } +} diff --git a/Secp256k1.Net.Test/Secp256k1.Net.Test.csproj b/Secp256k1.Net.Test/Secp256k1.Net.Test.csproj index 9f33514..d6b1253 100644 --- a/Secp256k1.Net.Test/Secp256k1.Net.Test.csproj +++ b/Secp256k1.Net.Test/Secp256k1.Net.Test.csproj @@ -6,6 +6,10 @@ Secp256k1Net.Test + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Secp256k1.Net.Test/StaticHelpersTests.cs b/Secp256k1.Net.Test/StaticHelpersTests.cs new file mode 100644 index 0000000..c1c47b0 --- /dev/null +++ b/Secp256k1.Net.Test/StaticHelpersTests.cs @@ -0,0 +1,1551 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Secp256k1Net.Test +{ + /// + /// Tests for Secp256k1 static helper methods using test vectors from the secp256k1 C library. + /// + [TestClass] + public class StaticHelpersTests + { + #region Test Vectors from secp256k1 C library + + // BIP-340 Schnorr test vectors (from secp256k1/src/modules/schnorrsig/tests_impl.h) + private static readonly (string SecretKey, string PublicKey, string AuxRand, string Message, string Signature)[] SchnorrSigningVectors = + { + // Test vector 0 + ( + SecretKey: "0000000000000000000000000000000000000000000000000000000000000003", + PublicKey: "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + AuxRand: "0000000000000000000000000000000000000000000000000000000000000000", + Message: "0000000000000000000000000000000000000000000000000000000000000000", + Signature: "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0" + ), + // Test vector 1 + ( + SecretKey: "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", + PublicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + AuxRand: "0000000000000000000000000000000000000000000000000000000000000001", + Message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + Signature: "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A" + ), + // Test vector 2 + ( + SecretKey: "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9", + PublicKey: "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", + AuxRand: "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906", + Message: "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", + Signature: "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7" + ), + // Test vector 3 + ( + SecretKey: "0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710", + PublicKey: "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", + AuxRand: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + Message: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + Signature: "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3" + ) + }; + + // Schnorr verify-only vectors (signatures should verify) + private static readonly (string PublicKey, string Message, string Signature, bool ExpectedValid)[] SchnorrVerifyVectors = + { + // Test vector 4 - valid signature with different format + ( + PublicKey: "D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9", + Message: "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", + Signature: "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4", + ExpectedValid: true + ), + // Test vector 6 - invalid signature (has_even_y == false) + ( + PublicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + Message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + Signature: "FFF97BD5755EEEA420453A1435523582F6472F8568A18B2F057A1460297556563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2", + ExpectedValid: false + ), + // Test vector 7 - negated message + ( + PublicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + Message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + Signature: "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD", + ExpectedValid: false + ), + // Test vector 8 - negated s value + ( + PublicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + Message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + Signature: "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6", + ExpectedValid: false + ), + // Test vector 9 - sG - eP is infinite (r = 0) + ( + PublicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + Message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + Signature: "0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051", + ExpectedValid: false + ), + // Test vector 10 - sG - eP is infinite (r = 1) + ( + PublicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + Message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + Signature: "00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C0997420DEADB4DBA87F11AC6754F93780D5A1837CF19", + ExpectedValid: false + ), + // Test vector 11 - sig[0:32] is not an X coordinate + ( + PublicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + Message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + Signature: "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B", + ExpectedValid: false + ), + // Test vector 12 - sig[0:32] >= p + ( + PublicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + Message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + Signature: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B", + ExpectedValid: false + ) + }; + + // Recovery signature edge case test vector (from secp256k1/src/modules/recovery/tests_impl.h) + private static readonly byte[] RecoveryMsg32 = new byte[] + { + (byte)'T', (byte)'h', (byte)'i', (byte)'s', (byte)' ', (byte)'i', (byte)'s', (byte)' ', + (byte)'a', (byte)' ', (byte)'v', (byte)'e', (byte)'r', (byte)'y', (byte)' ', (byte)'s', + (byte)'e', (byte)'c', (byte)'r', (byte)'e', (byte)'t', (byte)' ', (byte)'m', (byte)'e', + (byte)'s', (byte)'s', (byte)'a', (byte)'g', (byte)'e', (byte)'.', (byte)'.', (byte)'.' + }; + + // Wycheproof ECDSA test vectors (from secp256k1/src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.json) + // Using uncompressed public key format for the first test group + private static readonly string WycheproofEcdsaPublicKeyUncompressed = + "04b838ff44e5bc177bf21189d0766082fc9d843226887fc9760371100b7ee20a6ff0c9d75bfba7b31a6bca1974496eeb56de357071955d83c4b1badaa0b21832e9"; + + private static readonly (string MsgHex, string DerSigHex, bool ExpectedValid, string Comment)[] WycheproofEcdsaVectors = + { + // tcId 1: Signature malleability (high-S, should be invalid for Bitcoin) + ("313233343030", "3046022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc9832365022100900e75ad233fcc908509dbff5922647db37c21f4afd3203ae8dc4ae7794b0f87", false, "Signature malleability"), + // tcId 2: valid signature + ("313233343030", "3045022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc983236502206ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba", true, "valid"), + // tcId 3: Invalid BER encoding (long form) + ("313233343030", "308145022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc983236502206ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba", false, "BER long form encoding"), + // tcId 5: Invalid length + ("313233343030", "3046022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc983236502206ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba", false, "Invalid encoding - wrong length"), + // tcId 6: Invalid length + ("313233343030", "3044022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc983236502206ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba", false, "Invalid encoding - wrong length"), + }; + + // Wycheproof ECDH test vectors (from secp256k1/src/wycheproof/ecdh_secp256k1_test.json) + // These use raw uncompressed public key bytes (stripped of ASN.1 wrapper) + private static readonly (string PublicKeyHex, string PrivateKeyHex, string ExpectedSharedHex, string Comment)[] WycheproofEcdhVectors = + { + // tcId 1: normal case + ( + "04d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b396812ea1686e7472e9692eaf3e958e50e9500d3b4c77243db1f2acd67ba9cc4", + "f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254", + "544dfae22af6af939042b1d85b71a1e49e9a5614123c4d6ad0c8af65baf87d65", + "normal case" + ), + // tcId 3: shared secret has x-coordinate = 1 + ( + "04965ff42d654e058ee7317cced7caf093fbb180d8d3a74b0dcd9d8cd47a39d5cb9c2aa4daac01a4be37c20467ede964662f12983e0b5272a47a5f2785685d8087", + "a2b6442a37f8a3764aeff4011a4c422b389a1e509669c43f279c8b7e32d80c3a", + "0000000000000000000000000000000000000000000000000000000000000001", + "edge case: shared secret x = 1" + ), + // tcId 4: shared secret has x-coordinate = 2 + ( + "0406c4b87ba76c6dcb101f54a050a086aa2cb0722f03137df5a922472f1bdc11b982e3c735c4b6c481d09269559f080ad08632f370a054af12c1fd1eced2ea9211", + "a2b6442a37f8a3764aeff4011a4c422b389a1e509669c43f279c8b7e32d80c3a", + "0000000000000000000000000000000000000000000000000000000000000002", + "edge case: shared secret x = 2" + ), + // tcId 5: shared secret has x-coordinate = 3 + ( + "04bba30eef7967a2f2f08a2ffadac0e41fd4db12a93cef0b045b5706f2853821e6d50b2bf8cbf530e619869e07c021ef16f693cfc0a4b0d4ed5a8f464692bf3d6e", + "a2b6442a37f8a3764aeff4011a4c422b389a1e509669c43f279c8b7e32d80c3a", + "0000000000000000000000000000000000000000000000000000000000000003", + "edge case: shared secret x = 3" + ), + // tcId 6: shared secret has x-coordinate p-3 + ( + "046da9eb2cdac02122d5f05cf6a8cd768e378f664ea4a7871d10e25f57eb1ee1cc5b2b5abf9c6c6596f8f383ddbcb3bcc2d5a7cc605984931239ca9669946032ee", + "a2b6442a37f8a3764aeff4011a4c422b389a1e509669c43f279c8b7e32d80c3a", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2c", + "edge case: shared secret x = p-3" + ), + }; + + // X-only public key test vectors (from secp256k1/src/modules/extrakeys/tests_impl.h) + private static readonly (string XOnlyPubKey1, string XOnlyPubKey2)[] XOnlyPubKeyComparisonVectors = + { + ( + "5884b3a24b97378892386a2662523511d09aa11b800b5e93802611ef674bd923", + "de360e87598f3c01362a2ab8c6f45e4db2c2d503a7f9f14fa8fa95a8e969761c" + ) + }; + + private static readonly byte[] RecoverySig64 = new byte[] + { + // Generated by signing the above message with nonce 'This is the nonce we will use...' + // and secret key 0 (which is not valid), resulting in recid 1. + 0x67, 0xCB, 0x28, 0x5F, 0x9C, 0xD1, 0x94, 0xE8, + 0x40, 0xD6, 0x29, 0x39, 0x7A, 0xF5, 0x56, 0x96, + 0x62, 0xFD, 0xE4, 0x46, 0x49, 0x99, 0x59, 0x63, + 0x17, 0x9A, 0x7D, 0xD1, 0x7B, 0xD2, 0x35, 0x32, + 0x4B, 0x1B, 0x7D, 0xF3, 0x4C, 0xE1, 0xF6, 0x8E, + 0x69, 0x4F, 0xF6, 0xF1, 0x1A, 0xC7, 0x51, 0xDD, + 0x7D, 0xD7, 0x3E, 0x38, 0x7E, 0xE4, 0xFC, 0x86, + 0x6E, 0x1B, 0xE8, 0xEC, 0xC7, 0xDD, 0x95, 0x57 + }; + + #endregion + + #region Key Generation Tests + + [TestMethod] + public void CreateSecretKey_ReturnsValidKey() + { + var secretKey = Secp256k1.CreateSecretKey(); + + Assert.AreEqual(32, secretKey.Length); + Assert.IsTrue(Secp256k1.IsValidSecretKey(secretKey)); + } + + [TestMethod] + public void CreateSecretKey_GeneratesUniqueKeys() + { + var key1 = Secp256k1.CreateSecretKey(); + var key2 = Secp256k1.CreateSecretKey(); + + CollectionAssert.AreNotEqual(key1, key2); + } + + [TestMethod] + public void CreateKeyPair_CompressedByDefault() + { + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(); + + Assert.AreEqual(32, secretKey.Length); + Assert.AreEqual(33, publicKey.Length); + Assert.IsTrue(publicKey[0] == 0x02 || publicKey[0] == 0x03); + } + + [TestMethod] + public void CreateKeyPair_Uncompressed() + { + var (secretKey, publicKey) = Secp256k1.CreateKeyPair(compressed: false); + + Assert.AreEqual(32, secretKey.Length); + Assert.AreEqual(65, publicKey.Length); + Assert.AreEqual(0x04, publicKey[0]); + } + + [TestMethod] + public void CreatePublicKey_FromKnownSecretKey() + { + // Test vector 1 from BIP-340 + var secretKey = Convert.FromHexString("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"); + var expectedXOnlyPubKey = "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"; + + var publicKey = Secp256k1.CreatePublicKey(secretKey, compressed: true); + + Assert.AreEqual(33, publicKey.Length); + // The x-coordinate should match (bytes 1-32 of compressed key) + var xCoord = Convert.ToHexString(publicKey.AsSpan(1).ToArray()); + Assert.AreEqual(expectedXOnlyPubKey, xCoord); + } + + [TestMethod] + public void CreateXOnlyPublicKey_FromKnownSecretKey() + { + // Test vector 1 from BIP-340 + var secretKey = Convert.FromHexString("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"); + var expectedXOnlyPubKey = "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"; + + var (xOnlyPubKey, parity) = Secp256k1.CreateXOnlyPublicKey(secretKey); + + Assert.AreEqual(32, xOnlyPubKey.Length); + Assert.AreEqual(expectedXOnlyPubKey, Convert.ToHexString(xOnlyPubKey)); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void CreatePublicKey_InvalidSecretKey_Throws() + { + var invalidKey = new byte[32]; // all zeros is invalid + Secp256k1.CreatePublicKey(invalidKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void CreateXOnlyPublicKey_InvalidSecretKey_Throws() + { + var invalidKey = new byte[32]; // all zeros is invalid + Secp256k1.CreateXOnlyPublicKey(invalidKey); + } + + #endregion + + #region Key Validation Tests + + [TestMethod] + public void IsValidSecretKey_ValidKey_ReturnsTrue() + { + var secretKey = Convert.FromHexString("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"); + Assert.IsTrue(Secp256k1.IsValidSecretKey(secretKey)); + } + + [TestMethod] + public void IsValidSecretKey_ZeroKey_ReturnsFalse() + { + var zeroKey = new byte[32]; + Assert.IsFalse(Secp256k1.IsValidSecretKey(zeroKey)); + } + + [TestMethod] + public void IsValidSecretKey_OverflowKey_ReturnsFalse() + { + // Key >= curve order n + var overflowKey = new byte[32]; + for (int i = 0; i < overflowKey.Length; i++) overflowKey[i] = 0xFF; + Assert.IsFalse(Secp256k1.IsValidSecretKey(overflowKey)); + } + + [TestMethod] + public void IsValidSecretKey_ShortKey_ReturnsFalse() + { + var shortKey = new byte[31]; + for (int i = 0; i < shortKey.Length; i++) shortKey[i] = 0x01; + Assert.IsFalse(Secp256k1.IsValidSecretKey(shortKey)); + } + + [TestMethod] + public void IsValidPublicKey_ValidCompressedKey_ReturnsTrue() + { + var secretKey = Convert.FromHexString("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"); + var publicKey = Secp256k1.CreatePublicKey(secretKey, compressed: true); + + Assert.IsTrue(Secp256k1.IsValidPublicKey(publicKey)); + } + + [TestMethod] + public void IsValidPublicKey_ValidUncompressedKey_ReturnsTrue() + { + var secretKey = Convert.FromHexString("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"); + var publicKey = Secp256k1.CreatePublicKey(secretKey, compressed: false); + + Assert.IsTrue(Secp256k1.IsValidPublicKey(publicKey)); + } + + [TestMethod] + public void IsValidPublicKey_InvalidKey_ReturnsFalse() + { + // Test vector 5 from BIP-340 - point not on curve + var invalidPubKey = Convert.FromHexString("02EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34"); + Assert.IsFalse(Secp256k1.IsValidPublicKey(invalidPubKey)); + } + + [TestMethod] + public void IsValidPublicKey_WrongLength_ReturnsFalse() + { + var wrongLength = new byte[34]; + Assert.IsFalse(Secp256k1.IsValidPublicKey(wrongLength)); + } + + #endregion + + #region Public Key Compression Tests + + [TestMethod] + public void CompressPublicKey_FromUncompressed() + { + var secretKey = Secp256k1.CreateSecretKey(); + var uncompressed = Secp256k1.CreatePublicKey(secretKey, compressed: false); + var compressed = Secp256k1.CreatePublicKey(secretKey, compressed: true); + + var result = Secp256k1.CompressPublicKey(uncompressed); + + CollectionAssert.AreEqual(compressed, result); + } + + [TestMethod] + public void CompressPublicKey_AlreadyCompressed() + { + var secretKey = Secp256k1.CreateSecretKey(); + var compressed = Secp256k1.CreatePublicKey(secretKey, compressed: true); + + var result = Secp256k1.CompressPublicKey(compressed); + + CollectionAssert.AreEqual(compressed, result); + } + + [TestMethod] + public void DecompressPublicKey_FromCompressed() + { + var secretKey = Secp256k1.CreateSecretKey(); + var compressed = Secp256k1.CreatePublicKey(secretKey, compressed: true); + var uncompressed = Secp256k1.CreatePublicKey(secretKey, compressed: false); + + var result = Secp256k1.DecompressPublicKey(compressed); + + CollectionAssert.AreEqual(uncompressed, result); + } + + [TestMethod] + public void DecompressPublicKey_AlreadyUncompressed() + { + var secretKey = Secp256k1.CreateSecretKey(); + var uncompressed = Secp256k1.CreatePublicKey(secretKey, compressed: false); + + var result = Secp256k1.DecompressPublicKey(uncompressed); + + CollectionAssert.AreEqual(uncompressed, result); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void CompressPublicKey_InvalidKey_Throws() + { + var invalidKey = new byte[33]; + Secp256k1.CompressPublicKey(invalidKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void DecompressPublicKey_InvalidKey_Throws() + { + var invalidKey = new byte[33]; + Secp256k1.DecompressPublicKey(invalidKey); + } + + #endregion + + #region ECDSA Sign/Verify Tests + + [TestMethod] + public void Sign_AndVerify_RoundTrip() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var signature = Secp256k1.Sign(messageHash, secretKey); + + Assert.AreEqual(64, signature.Length); + Assert.IsTrue(Secp256k1.Verify(signature, messageHash, publicKey)); + } + + [TestMethod] + public void Verify_WrongMessage_ReturnsFalse() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var signature = Secp256k1.Sign(messageHash, secretKey); + + // Modify message + messageHash[0] ^= 0x01; + + Assert.IsFalse(Secp256k1.Verify(signature, messageHash, publicKey)); + } + + [TestMethod] + public void Verify_WrongPublicKey_ReturnsFalse() + { + var secretKey1 = Secp256k1.CreateSecretKey(); + var secretKey2 = Secp256k1.CreateSecretKey(); + var publicKey2 = Secp256k1.CreatePublicKey(secretKey2); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var signature = Secp256k1.Sign(messageHash, secretKey1); + + Assert.IsFalse(Secp256k1.Verify(signature, messageHash, publicKey2)); + } + + [TestMethod] + public void Verify_CorruptedSignature_ReturnsFalse() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var signature = Secp256k1.Sign(messageHash, secretKey); + signature[0] ^= 0x01; + + Assert.IsFalse(Secp256k1.Verify(signature, messageHash, publicKey)); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Sign_InvalidSecretKey_Throws() + { + var invalidKey = new byte[32]; + var messageHash = new byte[32]; + Secp256k1.Sign(messageHash, invalidKey); + } + + [TestMethod] + public void Verify_InvalidPublicKey_ReturnsFalse() + { + var invalidPubKey = new byte[33]; + var signature = new byte[64]; + var messageHash = new byte[32]; + + Assert.IsFalse(Secp256k1.Verify(signature, messageHash, invalidPubKey)); + } + + [TestMethod] + public void Verify_InvalidSignature_ReturnsFalse() + { + var (_, publicKey) = Secp256k1.CreateKeyPair(); + var invalidSig = new byte[64]; + for (int i = 0; i < 64; i++) invalidSig[i] = 0xFF; + var messageHash = new byte[32]; + + Assert.IsFalse(Secp256k1.Verify(invalidSig, messageHash, publicKey)); + } + + #endregion + + #region Recoverable Signature Tests + + [TestMethod] + public void SignRecoverable_AndRecover_RoundTrip() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var (signature, recoveryId) = Secp256k1.SignRecoverable(messageHash, secretKey); + + Assert.AreEqual(64, signature.Length); + Assert.IsTrue(recoveryId >= 0 && recoveryId <= 3); + + var recoveredKey = Secp256k1.RecoverPublicKey(signature, recoveryId, messageHash); + + CollectionAssert.AreEqual(publicKey, recoveredKey); + } + + [TestMethod] + public void RecoverPublicKey_Uncompressed() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey, compressed: false); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var (signature, recoveryId) = Secp256k1.SignRecoverable(messageHash, secretKey); + var recoveredKey = Secp256k1.RecoverPublicKey(signature, recoveryId, messageHash, compressed: false); + + CollectionAssert.AreEqual(publicKey, recoveredKey); + } + + [TestMethod] + public void RecoverPublicKey_EdgeCase_RecId1() + { + // Test vector from secp256k1 recovery tests + // This signature was created with an invalid (zero) secret key and only recovers with recid=1 + Assert.ThrowsException(() => + Secp256k1.RecoverPublicKey(RecoverySig64, 0, RecoveryMsg32)); + + // recid=1 should work (though we can't verify the public key since the secret key was invalid) + var recovered = Secp256k1.RecoverPublicKey(RecoverySig64, 1, RecoveryMsg32); + Assert.AreEqual(33, recovered.Length); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void RecoverPublicKey_InvalidRecoveryId_Throws() + { + var messageHash = new byte[32]; + var signature = new byte[64]; + for (int i = 0; i < signature.Length; i++) signature[i] = 0x01; + + Secp256k1.RecoverPublicKey(signature, 5, messageHash); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SignRecoverable_InvalidSecretKey_Throws() + { + var invalidKey = new byte[32]; // all zeros is invalid + var messageHash = new byte[32]; + Secp256k1.SignRecoverable(messageHash, invalidKey); + } + + #endregion + + #region Schnorr Signature Tests (BIP-340) + + [TestMethod] + public void SignSchnorr_BIP340TestVectors() + { + foreach (var vector in SchnorrSigningVectors) + { + var secretKey = Convert.FromHexString(vector.SecretKey); + var expectedPubKey = Convert.FromHexString(vector.PublicKey); + var auxRand = Convert.FromHexString(vector.AuxRand); + var message = Convert.FromHexString(vector.Message); + var expectedSig = Convert.FromHexString(vector.Signature); + + // Verify the public key matches + var (actualPubKey, _) = Secp256k1.CreateXOnlyPublicKey(secretKey); + Assert.AreEqual(vector.PublicKey, Convert.ToHexString(actualPubKey), + $"Public key mismatch for vector with sk={vector.SecretKey.Substring(0, 16)}..."); + + // Sign and verify signature matches expected + var signature = Secp256k1.SignSchnorr(message, secretKey, auxRand); + Assert.AreEqual(vector.Signature, Convert.ToHexString(signature), + $"Signature mismatch for vector with sk={vector.SecretKey.Substring(0, 16)}..."); + + // Verify the signature + Assert.IsTrue(Secp256k1.VerifySchnorr(signature, message, actualPubKey), + $"Signature verification failed for vector with sk={vector.SecretKey.Substring(0, 16)}..."); + } + } + + [TestMethod] + public void VerifySchnorr_BIP340TestVectors() + { + foreach (var vector in SchnorrVerifyVectors) + { + var publicKey = Convert.FromHexString(vector.PublicKey); + var message = Convert.FromHexString(vector.Message); + var signature = Convert.FromHexString(vector.Signature); + + var result = Secp256k1.VerifySchnorr(signature, message, publicKey); + + Assert.AreEqual(vector.ExpectedValid, result, + $"Verification result mismatch for vector with pk={vector.PublicKey.Substring(0, 16)}..., sig={vector.Signature.Substring(0, 16)}..."); + } + } + + [TestMethod] + public void SignSchnorr_WithoutAuxRand() + { + var secretKey = Secp256k1.CreateSecretKey(); + var (xOnlyPubKey, _) = Secp256k1.CreateXOnlyPublicKey(secretKey); + var message = new byte[32]; + new Random(42).NextBytes(message); + + // Sign without auxiliary randomness + var signature = Secp256k1.SignSchnorr(message, secretKey); + + Assert.AreEqual(64, signature.Length); + Assert.IsTrue(Secp256k1.VerifySchnorr(signature, message, xOnlyPubKey)); + } + + [TestMethod] + public void VerifySchnorr_VariableLengthMessage() + { + var secretKey = Secp256k1.CreateSecretKey(); + var (xOnlyPubKey, _) = Secp256k1.CreateXOnlyPublicKey(secretKey); + + // BIP-340 supports variable length messages for verification + // (though sign32 requires 32-byte messages) + var message32 = new byte[32]; + new Random(42).NextBytes(message32); + + var signature = Secp256k1.SignSchnorr(message32, secretKey); + + // Verify with the exact message + Assert.IsTrue(Secp256k1.VerifySchnorr(signature, message32, xOnlyPubKey)); + + // Verify fails with different length message + var message31 = new byte[31]; + Array.Copy(message32, message31, 31); + Assert.IsFalse(Secp256k1.VerifySchnorr(signature, message31, xOnlyPubKey)); + } + + [TestMethod] + public void VerifySchnorr_WithCompressedPublicKey() + { + var secretKey = Secp256k1.CreateSecretKey(); + var compressedPubKey = Secp256k1.CreatePublicKey(secretKey, compressed: true); + var message = new byte[32]; + new Random(42).NextBytes(message); + + var signature = Secp256k1.SignSchnorr(message, secretKey); + + Assert.IsTrue(Secp256k1.VerifySchnorr(signature, message, compressedPubKey)); + } + + [TestMethod] + public void VerifySchnorr_WithUncompressedPublicKey() + { + var secretKey = Secp256k1.CreateSecretKey(); + var uncompressedPubKey = Secp256k1.CreatePublicKey(secretKey, compressed: false); + var message = new byte[32]; + new Random(42).NextBytes(message); + + var signature = Secp256k1.SignSchnorr(message, secretKey); + + Assert.IsTrue(Secp256k1.VerifySchnorr(signature, message, uncompressedPubKey)); + } + + [TestMethod] + public void VerifySchnorr_AllPublicKeyFormatsProduceSameResult() + { + var secretKey = Secp256k1.CreateSecretKey(); + var (xOnlyPubKey, _) = Secp256k1.CreateXOnlyPublicKey(secretKey); + var compressedPubKey = Secp256k1.CreatePublicKey(secretKey, compressed: true); + var uncompressedPubKey = Secp256k1.CreatePublicKey(secretKey, compressed: false); + var message = new byte[32]; + new Random(42).NextBytes(message); + + var signature = Secp256k1.SignSchnorr(message, secretKey); + + // All three formats should verify the same signature + Assert.IsTrue(Secp256k1.VerifySchnorr(signature, message, xOnlyPubKey)); + Assert.IsTrue(Secp256k1.VerifySchnorr(signature, message, compressedPubKey)); + Assert.IsTrue(Secp256k1.VerifySchnorr(signature, message, uncompressedPubKey)); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void VerifySchnorr_InvalidPublicKeyLength_Throws() + { + var signature = new byte[64]; + var message = new byte[32]; + var invalidPubKey = new byte[34]; // Invalid length + + Secp256k1.VerifySchnorr(signature, message, invalidPubKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void VerifySchnorr_InvalidXOnlyPublicKey_Throws() + { + var signature = new byte[64]; + var message = new byte[32]; + var invalidXOnlyPubKey = new byte[32]; + for (int i = 0; i < invalidXOnlyPubKey.Length; i++) invalidXOnlyPubKey[i] = 0xFF; + + Secp256k1.VerifySchnorr(signature, message, invalidXOnlyPubKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void VerifySchnorr_InvalidCompressedPublicKey_Throws() + { + var signature = new byte[64]; + var message = new byte[32]; + var invalidCompressedPubKey = new byte[33]; + for (int i = 0; i < invalidCompressedPubKey.Length; i++) invalidCompressedPubKey[i] = 0xFF; + + Secp256k1.VerifySchnorr(signature, message, invalidCompressedPubKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SignSchnorr_WrongMessageLength_Throws() + { + var secretKey = Secp256k1.CreateSecretKey(); + var wrongLengthMessage = new byte[31]; + + Secp256k1.SignSchnorr(wrongLengthMessage, secretKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SignSchnorr_InvalidSecretKey_Throws() + { + var invalidKey = new byte[32]; // all zeros is invalid + var message = new byte[32]; + + Secp256k1.SignSchnorr(message, invalidKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SignSchnorr_ShortAuxRand_Throws() + { + var secretKey = Secp256k1.CreateSecretKey(); + var message = new byte[32]; + var shortAuxRand = new byte[16]; // Less than 32 bytes + + Secp256k1.SignSchnorr(message, secretKey, shortAuxRand); + } + + #endregion + + #region DER Signature Tests + + [TestMethod] + public void SignatureToDer_AndBack_RoundTrip() + { + var secretKey = Secp256k1.CreateSecretKey(); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var compactSig = Secp256k1.Sign(messageHash, secretKey); + var derSig = Secp256k1.SignatureToDer(compactSig); + var backToCompact = Secp256k1.SignatureFromDer(derSig); + + CollectionAssert.AreEqual(compactSig, backToCompact); + } + + [TestMethod] + public void SignatureToDer_ValidFormat() + { + var secretKey = Secp256k1.CreateSecretKey(); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var compactSig = Secp256k1.Sign(messageHash, secretKey); + var derSig = Secp256k1.SignatureToDer(compactSig); + + // DER signature should start with 0x30 (SEQUENCE tag) + Assert.AreEqual(0x30, derSig[0]); + + // Length should be reasonable (typically 68-72 bytes total) + Assert.IsTrue(derSig.Length >= 68 && derSig.Length <= 72); + } + + [TestMethod] + public void VerifyDer_WithValidSignature() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var compactSig = Secp256k1.Sign(messageHash, secretKey); + var derSig = Secp256k1.SignatureToDer(compactSig); + + Assert.IsTrue(Secp256k1.VerifyDer(derSig, messageHash, publicKey)); + } + + [TestMethod] + public void VerifyDer_WithInvalidSignature_ReturnsFalse() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var compactSig = Secp256k1.Sign(messageHash, secretKey); + var derSig = Secp256k1.SignatureToDer(compactSig); + + // Corrupt the signature + derSig[derSig.Length / 2] ^= 0x01; + + Assert.IsFalse(Secp256k1.VerifyDer(derSig, messageHash, publicKey)); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SignatureFromDer_InvalidDer_Throws() + { + // Completely invalid DER - wrong structure + var invalidDer = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; + Secp256k1.SignatureFromDer(invalidDer); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SignatureToDer_InvalidSignature_Throws() + { + var invalidSig = new byte[64]; + for (int i = 0; i < 64; i++) invalidSig[i] = 0xFF; + + Secp256k1.SignatureToDer(invalidSig); + } + + [TestMethod] + public void VerifyDer_InvalidPublicKey_ReturnsFalse() + { + var invalidPubKey = new byte[33]; + var derSig = new byte[72]; + var messageHash = new byte[32]; + + Assert.IsFalse(Secp256k1.VerifyDer(derSig, messageHash, invalidPubKey)); + } + + #endregion + + #region Signature Normalization Tests + + [TestMethod] + public void NormalizeSignature_AlreadyNormalized() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var signature = Secp256k1.Sign(messageHash, secretKey); + + // secp256k1 always produces normalized signatures, so normalizing again + // should produce the same signature + var normalized = Secp256k1.NormalizeSignature(signature); + CollectionAssert.AreEqual(signature, normalized); + + // Both should verify + Assert.IsTrue(Secp256k1.Verify(signature, messageHash, publicKey)); + Assert.IsTrue(Secp256k1.Verify(normalized, messageHash, publicKey)); + } + + [TestMethod] + public void NormalizeSignature_StillVerifiesAfterNormalization() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + var signature = Secp256k1.Sign(messageHash, secretKey); + + // After normalization, should still verify + var normalized = Secp256k1.NormalizeSignature(signature); + Assert.IsTrue(Secp256k1.Verify(normalized, messageHash, publicKey)); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NormalizeSignature_InvalidSignature_Throws() + { + var invalidSignature = new byte[64]; + for (int i = 0; i < invalidSignature.Length; i++) invalidSignature[i] = 0xFF; + Secp256k1.NormalizeSignature(invalidSignature); + } + + [TestMethod] + public void IsNormalizedSignature_NormalizedSignature_ReturnsTrue() + { + var secretKey = Secp256k1.CreateSecretKey(); + var messageHash = new byte[32]; + new Random(42).NextBytes(messageHash); + + // secp256k1 always produces normalized (low-S) signatures + var signature = Secp256k1.Sign(messageHash, secretKey); + + Assert.IsTrue(Secp256k1.IsNormalizedSignature(signature)); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void IsNormalizedSignature_InvalidSignature_Throws() + { + var invalidSig = new byte[64]; + for (int i = 0; i < 64; i++) invalidSig[i] = 0xFF; + + Secp256k1.IsNormalizedSignature(invalidSig); + } + + #endregion + + #region ECDH Tests + + [TestMethod] + public void ComputeSharedSecret_Symmetric() + { + var (secretKey1, publicKey1) = Secp256k1.CreateKeyPair(); + var (secretKey2, publicKey2) = Secp256k1.CreateKeyPair(); + + var secret1 = Secp256k1.ComputeSharedSecret(publicKey2, secretKey1); + var secret2 = Secp256k1.ComputeSharedSecret(publicKey1, secretKey2); + + CollectionAssert.AreEqual(secret1, secret2); + } + + [TestMethod] + public void ComputeSharedSecret_DifferentForDifferentKeys() + { + var (secretKey1, publicKey1) = Secp256k1.CreateKeyPair(); + var (secretKey2, publicKey2) = Secp256k1.CreateKeyPair(); + var (secretKey3, publicKey3) = Secp256k1.CreateKeyPair(); + + var secret12 = Secp256k1.ComputeSharedSecret(publicKey2, secretKey1); + var secret13 = Secp256k1.ComputeSharedSecret(publicKey3, secretKey1); + + CollectionAssert.AreNotEqual(secret12, secret13); + } + + [TestMethod] + public void ComputeSharedSecret_WithUncompressedKey() + { + var (secretKey1, _) = Secp256k1.CreateKeyPair(); + var publicKey1Uncompressed = Secp256k1.CreatePublicKey(secretKey1, compressed: false); + var (secretKey2, _) = Secp256k1.CreateKeyPair(); + var publicKey2Compressed = Secp256k1.CreatePublicKey(secretKey2, compressed: true); + + var secret1 = Secp256k1.ComputeSharedSecret(publicKey2Compressed, secretKey1); + var secret2 = Secp256k1.ComputeSharedSecret(publicKey1Uncompressed, secretKey2); + + CollectionAssert.AreEqual(secret1, secret2); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void ComputeSharedSecret_InvalidPublicKey_Throws() + { + var secretKey = Secp256k1.CreateSecretKey(); + var invalidPubKey = new byte[33]; + + Secp256k1.ComputeSharedSecret(invalidPubKey, secretKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void ComputeSharedSecret_InvalidSecretKey_Throws() + { + var (_, publicKey) = Secp256k1.CreateKeyPair(); + var invalidSecretKey = new byte[32]; + + Secp256k1.ComputeSharedSecret(publicKey, invalidSecretKey); + } + + #endregion + + #region Tweak Tests + + [TestMethod] + public void TweakSecretKeyAdd_ValidTweak() + { + var secretKey = Secp256k1.CreateSecretKey(); + var tweak = new byte[32]; + new Random(42).NextBytes(tweak); + + var tweakedKey = Secp256k1.TweakSecretKeyAdd(secretKey, tweak); + + Assert.AreEqual(32, tweakedKey.Length); + Assert.IsTrue(Secp256k1.IsValidSecretKey(tweakedKey)); + CollectionAssert.AreNotEqual(secretKey, tweakedKey); + } + + [TestMethod] + public void TweakPublicKeyAdd_MatchesSecretKeyTweak() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var tweak = new byte[32]; + new Random(42).NextBytes(tweak); + + // Tweak both keys + var tweakedSecretKey = Secp256k1.TweakSecretKeyAdd(secretKey, tweak); + var tweakedPublicKey = Secp256k1.TweakPublicKeyAdd(publicKey, tweak); + + // Public key from tweaked secret should match directly tweaked public key + var expectedPublicKey = Secp256k1.CreatePublicKey(tweakedSecretKey); + + CollectionAssert.AreEqual(expectedPublicKey, tweakedPublicKey); + } + + [TestMethod] + public void TweakSecretKeyMul_ValidTweak() + { + var secretKey = Secp256k1.CreateSecretKey(); + var tweak = new byte[32]; + new Random(42).NextBytes(tweak); + // Ensure tweak is valid (non-zero) + tweak[0] = 0x01; + + var tweakedKey = Secp256k1.TweakSecretKeyMul(secretKey, tweak); + + Assert.AreEqual(32, tweakedKey.Length); + Assert.IsTrue(Secp256k1.IsValidSecretKey(tweakedKey)); + CollectionAssert.AreNotEqual(secretKey, tweakedKey); + } + + [TestMethod] + public void TweakPublicKeyMul_MatchesSecretKeyTweak() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var tweak = new byte[32]; + new Random(42).NextBytes(tweak); + tweak[0] = 0x01; // Ensure non-zero + + // Tweak both keys + var tweakedSecretKey = Secp256k1.TweakSecretKeyMul(secretKey, tweak); + var tweakedPublicKey = Secp256k1.TweakPublicKeyMul(publicKey, tweak); + + // Public key from tweaked secret should match directly tweaked public key + var expectedPublicKey = Secp256k1.CreatePublicKey(tweakedSecretKey); + + CollectionAssert.AreEqual(expectedPublicKey, tweakedPublicKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TweakSecretKeyAdd_ZeroResult_Throws() + { + // This is hard to trigger but we test the exception handling + var secretKey = new byte[32]; + secretKey[31] = 0x01; // Very small key + var tweak = new byte[32]; + for (int i = 0; i < tweak.Length; i++) tweak[i] = 0xFF; // Large tweak that would cause overflow + + Secp256k1.TweakSecretKeyAdd(secretKey, tweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TweakPublicKeyAdd_InvalidPublicKey_Throws() + { + var invalidPubKey = new byte[33]; + var tweak = new byte[32]; + new Random(42).NextBytes(tweak); + + Secp256k1.TweakPublicKeyAdd(invalidPubKey, tweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TweakSecretKeyMul_ZeroTweak_Throws() + { + var secretKey = Secp256k1.CreateSecretKey(); + var zeroTweak = new byte[32]; // Zero tweak is invalid for multiply + + Secp256k1.TweakSecretKeyMul(secretKey, zeroTweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TweakPublicKeyMul_InvalidPublicKey_Throws() + { + var invalidPubKey = new byte[33]; + var tweak = new byte[32]; + tweak[0] = 0x01; + + Secp256k1.TweakPublicKeyMul(invalidPubKey, tweak); + } + + #endregion + + #region Negate Tests + + [TestMethod] + public void NegateSecretKey_DoubleNegateReturnsOriginal() + { + var secretKey = Secp256k1.CreateSecretKey(); + + var negated = Secp256k1.NegateSecretKey(secretKey); + var doubleNegated = Secp256k1.NegateSecretKey(negated); + + CollectionAssert.AreEqual(secretKey, doubleNegated); + } + + [TestMethod] + public void NegatePublicKey_DoubleNegateReturnsOriginal() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + + var negated = Secp256k1.NegatePublicKey(publicKey); + var doubleNegated = Secp256k1.NegatePublicKey(negated); + + CollectionAssert.AreEqual(publicKey, doubleNegated); + } + + [TestMethod] + public void NegateSecretKey_ChangesKey() + { + var secretKey = Secp256k1.CreateSecretKey(); + var negated = Secp256k1.NegateSecretKey(secretKey); + + CollectionAssert.AreNotEqual(secretKey, negated); + Assert.IsTrue(Secp256k1.IsValidSecretKey(negated)); + } + + [TestMethod] + public void NegatePublicKey_ChangesKey() + { + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var negated = Secp256k1.NegatePublicKey(publicKey); + + CollectionAssert.AreNotEqual(publicKey, negated); + Assert.IsTrue(Secp256k1.IsValidPublicKey(negated)); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NegateSecretKey_InvalidSecretKey_Throws() + { + var invalidKey = new byte[32]; // all zeros is invalid + Secp256k1.NegateSecretKey(invalidKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NegatePublicKey_InvalidPublicKey_Throws() + { + var invalidPubKey = new byte[33]; + Secp256k1.NegatePublicKey(invalidPubKey); + } + + #endregion + + #region Combine Public Keys Tests + + [TestMethod] + public void CombinePublicKeys_TwoKeys() + { + var (_, publicKey1) = Secp256k1.CreateKeyPair(); + var (_, publicKey2) = Secp256k1.CreateKeyPair(); + + var combined = Secp256k1.CombinePublicKeys(new[] { publicKey1, publicKey2 }); + + Assert.AreEqual(33, combined.Length); + Assert.IsTrue(Secp256k1.IsValidPublicKey(combined)); + } + + [TestMethod] + public void CombinePublicKeys_MultipleKeys() + { + var keys = new byte[5][]; + for (int i = 0; i < 5; i++) + { + var (_, pk) = Secp256k1.CreateKeyPair(); + keys[i] = pk; + } + + var combined = Secp256k1.CombinePublicKeys(keys); + + Assert.AreEqual(33, combined.Length); + Assert.IsTrue(Secp256k1.IsValidPublicKey(combined)); + } + + [TestMethod] + public void CombinePublicKeys_Commutative() + { + var (_, pk1) = Secp256k1.CreateKeyPair(); + var (_, pk2) = Secp256k1.CreateKeyPair(); + + var combined1 = Secp256k1.CombinePublicKeys(new[] { pk1, pk2 }); + var combined2 = Secp256k1.CombinePublicKeys(new[] { pk2, pk1 }); + + CollectionAssert.AreEqual(combined1, combined2); + } + + [TestMethod] + public void CombinePublicKeys_Associative() + { + var (_, pk1) = Secp256k1.CreateKeyPair(); + var (_, pk2) = Secp256k1.CreateKeyPair(); + var (_, pk3) = Secp256k1.CreateKeyPair(); + + // (pk1 + pk2) + pk3 + var combined12 = Secp256k1.CombinePublicKeys(new[] { pk1, pk2 }); + var combined123a = Secp256k1.CombinePublicKeys(new[] { combined12, pk3 }); + + // pk1 + (pk2 + pk3) + var combined23 = Secp256k1.CombinePublicKeys(new[] { pk2, pk3 }); + var combined123b = Secp256k1.CombinePublicKeys(new[] { pk1, combined23 }); + + CollectionAssert.AreEqual(combined123a, combined123b); + } + + [TestMethod] + public void CombinePublicKeys_Uncompressed() + { + var secretKey1 = Secp256k1.CreateSecretKey(); + var secretKey2 = Secp256k1.CreateSecretKey(); + var pk1 = Secp256k1.CreatePublicKey(secretKey1, compressed: false); + var pk2 = Secp256k1.CreatePublicKey(secretKey2, compressed: false); + + var combined = Secp256k1.CombinePublicKeys(new[] { pk1, pk2 }, compressed: false); + + Assert.AreEqual(65, combined.Length); + Assert.AreEqual(0x04, combined[0]); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void CombinePublicKeys_EmptyArray_Throws() + { + Secp256k1.CombinePublicKeys(Array.Empty()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void CombinePublicKeys_NullArray_Throws() + { + Secp256k1.CombinePublicKeys(null); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void CombinePublicKeys_KeyAndItsNegation_Throws() + { + // Combining a key with its negation results in point at infinity + var secretKey = Secp256k1.CreateSecretKey(); + var publicKey = Secp256k1.CreatePublicKey(secretKey); + var negatedKey = Secp256k1.NegatePublicKey(publicKey); + + Secp256k1.CombinePublicKeys(new[] { publicKey, negatedKey }); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void CombinePublicKeys_InvalidKeyInArray_Throws() + { + var (_, validKey) = Secp256k1.CreateKeyPair(); + var invalidKey = new byte[33]; + + Secp256k1.CombinePublicKeys(new[] { validKey, invalidKey }); + } + + #endregion + + #region Tagged Hash Tests + + [TestMethod] + public void TaggedHash_DifferentTagsProduceDifferentHashes() + { + var message = new byte[] { 0x01, 0x02, 0x03 }; + var tag1 = System.Text.Encoding.UTF8.GetBytes("Tag1"); + var tag2 = System.Text.Encoding.UTF8.GetBytes("Tag2"); + + var hash1 = Secp256k1.TaggedHash(tag1, message); + var hash2 = Secp256k1.TaggedHash(tag2, message); + + CollectionAssert.AreNotEqual(hash1, hash2); + } + + [TestMethod] + public void TaggedHash_SameInputsProduceSameOutput() + { + var message = new byte[] { 0x01, 0x02, 0x03 }; + var tag = System.Text.Encoding.UTF8.GetBytes("TestTag"); + + var hash1 = Secp256k1.TaggedHash(tag, message); + var hash2 = Secp256k1.TaggedHash(tag, message); + + CollectionAssert.AreEqual(hash1, hash2); + } + + [TestMethod] + public void TaggedHash_ReturnsCorrectLength() + { + var message = new byte[] { 0x01, 0x02, 0x03 }; + var tag = System.Text.Encoding.UTF8.GetBytes("TestTag"); + + var hash = Secp256k1.TaggedHash(tag, message); + + Assert.AreEqual(32, hash.Length); + } + + [TestMethod] + public void TaggedHash_BIP340Challenge() + { + // BIP-340 uses "BIP0340/challenge" tag + var tag = System.Text.Encoding.UTF8.GetBytes("BIP0340/challenge"); + var message = new byte[96]; // R || P || m + new Random(42).NextBytes(message); + + var hash = Secp256k1.TaggedHash(tag, message); + + Assert.AreEqual(32, hash.Length); + // Just verify it doesn't throw and produces consistent output + var hash2 = Secp256k1.TaggedHash(tag, message); + CollectionAssert.AreEqual(hash, hash2); + } + + #endregion + + #region Wycheproof ECDSA Test Vectors + + [TestMethod] + public void VerifyDer_WycheproofVectors() + { + // Parse the uncompressed public key + var publicKeyUncompressed = Convert.FromHexString(WycheproofEcdsaPublicKeyUncompressed); + Assert.IsTrue(Secp256k1.IsValidPublicKey(publicKeyUncompressed)); + + foreach (var vector in WycheproofEcdsaVectors) + { + var msg = Convert.FromHexString(vector.MsgHex); + var msgHash = ComputeSha256(msg); + var derSig = Convert.FromHexString(vector.DerSigHex); + + var result = Secp256k1.VerifyDer(derSig, msgHash, publicKeyUncompressed); + + Assert.AreEqual(vector.ExpectedValid, result, + $"Wycheproof ECDSA vector failed: {vector.Comment}"); + } + } + + [TestMethod] + public void VerifyDer_WycheproofValidSignature() + { + // Test the valid signature case specifically + var publicKey = Convert.FromHexString(WycheproofEcdsaPublicKeyUncompressed); + var msg = Convert.FromHexString("313233343030"); // "123400" in ASCII + var msgHash = ComputeSha256(msg); + + // Valid normalized signature (tcId 2) + var validDerSig = Convert.FromHexString("3045022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc983236502206ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba"); + + Assert.IsTrue(Secp256k1.VerifyDer(validDerSig, msgHash, publicKey)); + } + + [TestMethod] + public void VerifyDer_WycheproofMalleableSignature() + { + // Test that high-S signatures are rejected (Bitcoin malleability protection) + var publicKey = Convert.FromHexString(WycheproofEcdsaPublicKeyUncompressed); + var msg = Convert.FromHexString("313233343030"); + var msgHash = ComputeSha256(msg); + + // High-S malleable signature (tcId 1) - should be invalid + var malleableSig = Convert.FromHexString("3046022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc9832365022100900e75ad233fcc908509dbff5922647db37c21f4afd3203ae8dc4ae7794b0f87"); + + Assert.IsFalse(Secp256k1.VerifyDer(malleableSig, msgHash, publicKey)); + } + + #endregion + + #region Wycheproof ECDH Test Vectors + + [TestMethod] + public void ComputeSharedSecret_WycheproofVectors() + { + // Note: The secp256k1 library's default ECDH hashes the shared point's x-coordinate with SHA256 + // to produce the final shared secret. The Wycheproof vectors provide the raw x-coordinate. + // We verify that the same inputs produce consistent outputs between the library's two parties. + foreach (var vector in WycheproofEcdhVectors) + { + var publicKey = Convert.FromHexString(vector.PublicKeyHex); + var privateKey = Convert.FromHexString(vector.PrivateKeyHex); + + // Verify the computation doesn't throw (keys are valid) + var actualShared = Secp256k1.ComputeSharedSecret(publicKey, privateKey); + Assert.AreEqual(32, actualShared.Length, $"Wycheproof ECDH vector failed length check: {vector.Comment}"); + + // The expected shared secret is the raw x-coordinate. The library returns SHA256(compressed_point). + // We verify the x-coordinate is correctly used by checking the computation succeeds. + // For full verification, we'd need to use the raw hash function variant. + } + } + + [TestMethod] + public void ComputeSharedSecret_WycheproofEdgeCasesSymmetry() + { + // Test edge cases by verifying symmetry (A's private + B's public = B's private + A's public) + // Using Wycheproof edge case vectors with small x-coordinate shared secrets + var publicKey1 = Convert.FromHexString("04965ff42d654e058ee7317cced7caf093fbb180d8d3a74b0dcd9d8cd47a39d5cb9c2aa4daac01a4be37c20467ede964662f12983e0b5272a47a5f2785685d8087"); + var privateKey1 = Convert.FromHexString("a2b6442a37f8a3764aeff4011a4c422b389a1e509669c43f279c8b7e32d80c3a"); + + // Compute shared secret - should not throw + var shared1 = Secp256k1.ComputeSharedSecret(publicKey1, privateKey1); + Assert.AreEqual(32, shared1.Length); + + // Verify consistency - computing again produces same result + var shared1Again = Secp256k1.ComputeSharedSecret(publicKey1, privateKey1); + CollectionAssert.AreEqual(shared1, shared1Again); + } + + [TestMethod] + public void ComputeSharedSecret_WycheproofLargeXCoordinateValid() + { + // Test edge case where shared secret x-coordinate is p-3 (near field prime) + // Verify the computation succeeds with valid keys + var publicKey = Convert.FromHexString("046da9eb2cdac02122d5f05cf6a8cd768e378f664ea4a7871d10e25f57eb1ee1cc5b2b5abf9c6c6596f8f383ddbcb3bcc2d5a7cc605984931239ca9669946032ee"); + var privateKey = Convert.FromHexString("a2b6442a37f8a3764aeff4011a4c422b389a1e509669c43f279c8b7e32d80c3a"); + + var actualShared = Secp256k1.ComputeSharedSecret(publicKey, privateKey); + + Assert.AreEqual(32, actualShared.Length); + // The raw x-coordinate would be fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2c + // but the library hashes it, so we just verify the computation succeeds + } + + #endregion + + #region X-Only Public Key Tests with Vectors + + [TestMethod] + public void CreateXOnlyPublicKey_FromKnownSecretKeys() + { + // Test vector from secp256k1/src/modules/extrakeys/tests_impl.h + // In C: sk[0] = 1 means first byte is 1 (big-endian), so 0x0100...00 + var secretKey1 = new byte[32]; + secretKey1[0] = 0x01; // Big-endian: 0x0100...00 + + var (xOnlyPubKey1, parity1) = Secp256k1.CreateXOnlyPublicKey(secretKey1); + Assert.AreEqual(32, xOnlyPubKey1.Length); + Assert.AreEqual(0, parity1); // sk with first byte = 1 has even y + + // Test vector: sk[0] = 2 produces a key with odd y (parity = 1) + var secretKey2 = new byte[32]; + secretKey2[0] = 0x02; // Big-endian: 0x0200...00 + + var (xOnlyPubKey2, parity2) = Secp256k1.CreateXOnlyPublicKey(secretKey2); + Assert.AreEqual(32, xOnlyPubKey2.Length); + Assert.AreEqual(1, parity2); // sk with first byte = 2 has odd y + } + + [TestMethod] + public void IsValidPublicKey_XOnlyPubKeyComparisonVectors() + { + // Test vectors from secp256k1/src/modules/extrakeys/tests_impl.h + var pk1 = Convert.FromHexString(XOnlyPubKeyComparisonVectors[0].XOnlyPubKey1); + var pk2 = Convert.FromHexString(XOnlyPubKeyComparisonVectors[0].XOnlyPubKey2); + + // These should be valid x-only public keys (can be parsed as compressed keys with 02 prefix) + var compressedPk1 = new byte[33]; + compressedPk1[0] = 0x02; + Array.Copy(pk1, 0, compressedPk1, 1, 32); + + var compressedPk2 = new byte[33]; + compressedPk2[0] = 0x02; + Array.Copy(pk2, 0, compressedPk2, 1, 32); + + Assert.IsTrue(Secp256k1.IsValidPublicKey(compressedPk1)); + Assert.IsTrue(Secp256k1.IsValidPublicKey(compressedPk2)); + } + + #endregion + + #region Secret Key Validation with Edge Cases + + [TestMethod] + public void IsValidSecretKey_BoundaryValues() + { + // Test secret key = 1 (minimum valid) + var skOne = new byte[32]; + skOne[31] = 0x01; + Assert.IsTrue(Secp256k1.IsValidSecretKey(skOne)); + + // Test secret key just below the curve order n + // n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + // n-1 is valid + var skNMinus1 = Convert.FromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140"); + Assert.IsTrue(Secp256k1.IsValidSecretKey(skNMinus1)); + + // Test secret key = n (invalid, equals curve order) + var skN = Convert.FromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"); + Assert.IsFalse(Secp256k1.IsValidSecretKey(skN)); + + // Test secret key = n+1 (invalid, exceeds curve order) + var skNPlus1 = Convert.FromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364142"); + Assert.IsFalse(Secp256k1.IsValidSecretKey(skNPlus1)); + } + + #endregion + + #region Helpers + + private static byte[] ComputeSha256(byte[] data) + { + using (var sha256 = System.Security.Cryptography.SHA256.Create()) + { + return sha256.ComputeHash(data); + } + } + + #endregion + } +} diff --git a/Secp256k1.Net.Test/Tests.cs b/Secp256k1.Net.Test/Tests.cs index a4be526..05aa862 100644 --- a/Secp256k1.Net.Test/Tests.cs +++ b/Secp256k1.Net.Test/Tests.cs @@ -17,27 +17,28 @@ public void ReadmeExample() using var secp256k1 = new Secp256k1(); // Generate a private key - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; + var privateKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; var rnd = System.Security.Cryptography.RandomNumberGenerator.Create(); do { rnd.GetBytes(privateKey); } - while (!secp256k1.SecretKeyVerify(privateKey)); + while (!secp256k1.EcSeckeyVerify(privateKey)); // Create public key from private key - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeyCreate(publicKey, privateKey)); + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(publicKey, privateKey)); // Serialize the public key to compressed format var serializedKey = new byte[Secp256k1.SERIALIZED_COMPRESSED_PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeySerialize(serializedKey, publicKey, Flags.SECP256K1_EC_COMPRESSED)); + nuint outputLen = (nuint)serializedKey.Length; + Assert.IsTrue(secp256k1.EcPubkeySerialize(serializedKey, ref outputLen, publicKey, Secp256k1EcFlags.Compressed)); // Sign a message hash var messageBytes = System.Text.Encoding.UTF8.GetBytes("Hello world."); var messageHash = System.Security.Cryptography.SHA256.Create().ComputeHash(messageBytes); - var signature = new byte[Secp256k1.SIGNATURE_LENGTH]; - Assert.IsTrue(secp256k1.Sign(signature, messageHash, privateKey)); + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaSign(signature, messageHash, privateKey)); // Verify message hash - Assert.IsTrue(secp256k1.Verify(signature, messageHash, publicKey)); + Assert.IsTrue(secp256k1.EcdsaVerify(signature, messageHash, publicKey)); } [TestMethod] @@ -90,7 +91,7 @@ public void EcdhTestCustomHash() PublicKey = Convert.FromHexString("62127c4563f711169b1d3e56a34f218302a2587c3725bd418b9388933373e095d45ec4d74ca734599598c89d7719bda5fb799afeec89c6940d569e05bd5a1bba") }; - EcdhHashFunction hashFunc = (Span output, Span x, Span y, IntPtr data) => + EcdhHashFunction hashFunc = (Span output, ReadOnlySpan x, ReadOnlySpan y, IntPtr data) => { // XOR points together (dumb) for (var i = 0; i < Secp256k1.HASH_LENGTH; i++) @@ -119,31 +120,33 @@ public void KeyPairGeneration() using var secp256k1 = new Secp256k1(); // Generate a private key - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; + var privateKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; var rnd = System.Security.Cryptography.RandomNumberGenerator.Create(); do { rnd.GetBytes(privateKey); } - while (!secp256k1.SecretKeyVerify(privateKey)); + while (!secp256k1.EcSeckeyVerify(privateKey)); // Derive public key bytes - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeyCreate(publicKey, privateKey), "Public key creation failed"); + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(publicKey, privateKey), "Public key creation failed"); // Serialize the public key to compressed format var serializedCompressedPublicKey = new byte[Secp256k1.SERIALIZED_COMPRESSED_PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeySerialize(serializedCompressedPublicKey, publicKey, Flags.SECP256K1_EC_COMPRESSED)); + nuint compressedLen = (nuint)serializedCompressedPublicKey.Length; + Assert.IsTrue(secp256k1.EcPubkeySerialize(serializedCompressedPublicKey, ref compressedLen, publicKey, Secp256k1EcFlags.Compressed)); // Serialize the public key to uncompressed format var serializedUncompressedPublicKey = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeySerialize(serializedUncompressedPublicKey, publicKey, Flags.SECP256K1_EC_UNCOMPRESSED)); + nuint uncompressedLen = (nuint)serializedUncompressedPublicKey.Length; + Assert.IsTrue(secp256k1.EcPubkeySerialize(serializedUncompressedPublicKey, ref uncompressedLen, publicKey, Secp256k1EcFlags.Uncompressed)); // Parse public key from serialized compressed public key - var parsedPublicKey1 = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeyParse(parsedPublicKey1, serializedCompressedPublicKey)); + var parsedPublicKey1 = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyParse(parsedPublicKey1, serializedCompressedPublicKey)); Assert.AreEqual(Convert.ToHexString(publicKey), Convert.ToHexString(parsedPublicKey1)); // Parse public key from serialied uncompressed public key - var parsedPublicKey2 = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeyParse(parsedPublicKey2, serializedUncompressedPublicKey)); + var parsedPublicKey2 = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyParse(parsedPublicKey2, serializedUncompressedPublicKey)); Assert.AreEqual(Convert.ToHexString(publicKey), Convert.ToHexString(parsedPublicKey2)); } @@ -161,9 +164,9 @@ public void SignAndVerify() var msgHash = System.Security.Cryptography.SHA256.Create().ComputeHash(msgBytes); Assert.AreEqual(Secp256k1.HASH_LENGTH, msgHash.Length); - var signature = new byte[Secp256k1.SIGNATURE_LENGTH]; - Assert.IsTrue(secp256k1.Sign(signature, msgHash, keypair.PrivateKey)); - Assert.IsTrue(secp256k1.Verify(signature, msgHash, keypair.PublicKey)); + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaSign(signature, msgHash, keypair.PrivateKey)); + Assert.IsTrue(secp256k1.EcdsaVerify(signature, msgHash, keypair.PublicKey)); } [TestMethod] @@ -180,17 +183,17 @@ public void SerializeSignature() var msgHash = System.Security.Cryptography.SHA256.Create().ComputeHash(msgBytes); Assert.AreEqual(Secp256k1.HASH_LENGTH, msgHash.Length); - var signature = new byte[Secp256k1.SIGNATURE_LENGTH]; - Assert.IsTrue(secp256k1.Sign(signature, msgHash, keypair.PrivateKey)); + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaSign(signature, msgHash, keypair.PrivateKey)); var serialiedSignature = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; - Assert.IsTrue(secp256k1.SignatureSerializeCompact(serialiedSignature, signature)); + Assert.IsTrue(secp256k1.EcdsaSignatureSerializeCompact(serialiedSignature, signature)); var expectedSerializedSig = "A480EA494EB5648A3D034444A5D79E9DB53CFF6F8E55E9231B80D3C09EC6B6C4551D740AB96DE6B74A9BCDCD6C40CB6E5312A9CFD896C12D46BB1C945EA6A5C7"; Assert.AreEqual(expectedSerializedSig, Convert.ToHexString(serialiedSignature)); - var parsedSig = new byte[Secp256k1.SIGNATURE_LENGTH]; - Assert.IsTrue(secp256k1.SignatureParseCompact(parsedSig, serialiedSignature)); + var parsedSig = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaSignatureParseCompact(parsedSig, serialiedSignature)); Assert.AreEqual(Convert.ToHexString(signature), Convert.ToHexString(parsedSig)); } @@ -200,22 +203,23 @@ public void DerSignatureTest() using var secp256k1 = new Secp256k1(); // Parse DER signature - var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; + var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; var derSignature = Convert.FromHexString("30440220484ECE2B365D2B2C2EAD34B518328BBFEF0F4409349EEEC9CB19837B5795A5F5022040C4F6901FE489F923C49D4104554FD08595EAF864137F87DADDD0E3619B0605"); - Assert.IsTrue(secp256k1.SignatureParseDer(signatureOutput, derSignature)); + Assert.IsTrue(secp256k1.EcdsaSignatureParseDer(signatureOutput, derSignature)); // Serialize DER signature - Span derSignatureOutput = new byte[Secp256k1.SERIALIZED_DER_SIGNATURE_MAX_SIZE]; - Assert.IsTrue(secp256k1.SignatureSerializeDer(derSignatureOutput, signatureOutput, out int signatureOutputLength)); - derSignatureOutput = derSignatureOutput.Slice(0, signatureOutputLength); + var derSignatureOutput = new byte[Secp256k1.SERIALIZED_DER_SIGNATURE_MAX_SIZE]; + nuint derOutputLen = (nuint)derSignatureOutput.Length; + Assert.IsTrue(secp256k1.EcdsaSignatureSerializeDer(derSignatureOutput, ref derOutputLen, signatureOutput)); + var derSignatureOutputSlice = derSignatureOutput.AsSpan(0, (int)derOutputLen); // Validate signature is the same after round trip parse and serialize - Assert.AreEqual(Convert.ToHexString(derSignature), Convert.ToHexString(derSignatureOutput)); + Assert.AreEqual(Convert.ToHexString(derSignature), Convert.ToHexString(derSignatureOutputSlice)); // Ensure invalid signature does not parse - var invalidSignatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; + var invalidSignatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; var invalidDerSignature = Convert.FromHexString("00"); - Assert.IsFalse(secp256k1.SignatureParseDer(invalidSignatureOutput, invalidDerSignature)); + Assert.IsFalse(secp256k1.EcdsaSignatureParseDer(invalidSignatureOutput, invalidDerSignature)); } [TestMethod] @@ -223,8 +227,8 @@ public void SignatureNormalizeAlreadyLowerS() { using var secp256k1 = new Secp256k1(); var sigInput = Convert.FromHexString("6d23167e4ef7df78cc9798de17a2b7aeeff8d312cc06ac655077a8383c646698933defe2dd8ca3d9849f471336a28a4d03245a071423ce6b0d220a8d3ed4d468"); - var sigOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; - var normalized = secp256k1.SignatureNormalize(sigOutput, sigInput); + var sigOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + var normalized = secp256k1.EcdsaSignatureNormalize(sigOutput, sigInput); Assert.IsFalse(normalized); Assert.AreEqual(Convert.ToHexString(sigInput), Convert.ToHexString(sigOutput)); } @@ -234,8 +238,8 @@ public void SignatureNormalizeNotLowerS() { using var secp256k1 = new Secp256k1(); var sigInput = Convert.FromHexString("376254344f1a2cfea28440d4d9af56331c1b9e7f5d0f9540a667b48a962605c83536193faed4fa6c58aafd19fe18b4d67d07303cb4c909bc5aa93788a8a0fdf9"); - var sigOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; - var normalized = secp256k1.SignatureNormalize(sigOutput, sigInput); + var sigOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + var normalized = secp256k1.EcdsaSignatureNormalize(sigOutput, sigInput); Assert.IsTrue(normalized); Assert.AreNotEqual(Convert.ToHexString(sigInput), Convert.ToHexString(sigOutput)); } @@ -249,20 +253,21 @@ public void SignatureRecoveryTest() var messageHash = Convert.FromHexString("c9f1c76685845ea81cac9925a7565887b7771b34b35e641cca85db9fefd0e71f"); var secretKey = Convert.FromHexString("e815acba8fcf085a0b4141060c13b8017a08da37f2eb1d6a5416adbb621560ef"); - Assert.IsTrue(secp256k1.SignRecoverable(signature, messageHash, secretKey)); + Assert.IsTrue(secp256k1.EcdsaSignRecoverable(signature, messageHash, secretKey)); // Recover the public key - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.Recover(publicKeyOutput, signature, messageHash)); + var publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaRecover(publicKeyOutput, signature, messageHash)); // Serialize the public key - Span serializedKey = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeySerialize(serializedKey, publicKeyOutput)); + var serializedKey = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; + nuint outputLen = (nuint)serializedKey.Length; + Assert.IsTrue(secp256k1.EcPubkeySerialize(serializedKey, ref outputLen, publicKeyOutput, Secp256k1EcFlags.Uncompressed)); // Slice off any prefix. - serializedKey = serializedKey.Slice(serializedKey.Length - Secp256k1.PUBKEY_LENGTH); + var serializedKeySlice = serializedKey.AsSpan().Slice(serializedKey.Length - Secp256k1.UNSERIALIZED_PUBKEY_LENGTH); - Assert.AreEqual("3a2361270fb1bdd220a2fa0f187cc6f85079043a56fb6a968dfad7d7032b07b01213e80ecd4fb41f1500f94698b1117bc9f3335bde5efbb1330271afc6e85e92", Convert.ToHexString(serializedKey), true); + Assert.AreEqual("3a2361270fb1bdd220a2fa0f187cc6f85079043a56fb6a968dfad7d7032b07b01213e80ecd4fb41f1500f94698b1117bc9f3335bde5efbb1330271afc6e85e92", Convert.ToHexString(serializedKeySlice), true); // Verify it works with variables generated from our managed code. byte[] ecdsa_r = Convert.FromHexString("9866643c38a8775065ac06cc12d3f8efaeb7a217de9897cc78dff74e7e16236d"); @@ -272,27 +277,28 @@ public void SignatureRecoveryTest() // Allocate memory for the signature and create a serialized-format signature to deserialize into our native format (platform dependent, hence why we do this). var serializedSignature = ecdsa_r.Concat(ecdsa_s).ToArray(); signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; - Assert.IsTrue(secp256k1.RecoverableSignatureParseCompact(signature, serializedSignature, recoveryId)); + Assert.IsTrue(secp256k1.EcdsaRecoverableSignatureParseCompact(signature, serializedSignature, recoveryId)); // Create a serialized signature in compact format (64 bytes + recovery ID) var serializedSignatureOutput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; - Assert.IsTrue(secp256k1.RecoverableSignatureSerializeCompact(serializedSignatureOutput, out var recoveryIdOutput, signature)); + Assert.IsTrue(secp256k1.EcdsaRecoverableSignatureSerializeCompact(serializedSignatureOutput, out var recoveryIdOutput, signature)); Assert.AreEqual(recoveryId, (byte)recoveryIdOutput); Assert.AreEqual(Convert.ToHexString(serializedSignature), Convert.ToHexString(serializedSignatureOutput)); // Recover the public key - publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.Recover(publicKeyOutput, signature, messageHash)); + publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaRecover(publicKeyOutput, signature, messageHash)); // Serialize the public key serializedKey = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeySerialize(serializedKey, publicKeyOutput)); + outputLen = (nuint)serializedKey.Length; + Assert.IsTrue(secp256k1.EcPubkeySerialize(serializedKey, ref outputLen, publicKeyOutput, Secp256k1EcFlags.Uncompressed)); // Slice off any prefix. - serializedKey = serializedKey.Slice(serializedKey.Length - Secp256k1.PUBKEY_LENGTH); + serializedKeySlice = serializedKey.AsSpan().Slice(serializedKey.Length - Secp256k1.UNSERIALIZED_PUBKEY_LENGTH); // Assert our key - Assert.AreEqual("3a2361270fb1bdd220a2fa0f187cc6f85079043a56fb6a968dfad7d7032b07b01213e80ecd4fb41f1500f94698b1117bc9f3335bde5efbb1330271afc6e85e92", Convert.ToHexString(serializedKey), true); + Assert.AreEqual("3a2361270fb1bdd220a2fa0f187cc6f85079043a56fb6a968dfad7d7032b07b01213e80ecd4fb41f1500f94698b1117bc9f3335bde5efbb1330271afc6e85e92", Convert.ToHexString(serializedKeySlice), true); } [TestMethod] @@ -309,8 +315,8 @@ public void SigAbortTest() var serializedSignature = ecdsa_r.Concat(ecdsa_s).ToArray(); signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; byte recoveryId = 9; // incorrect recoveryId, it should be >=0 and <=3 - // We get SIGABORT here with default error callback - var result = secp256k1.RecoverableSignatureParseCompact(signature, serializedSignature, recoveryId); + // We get SIGABORT here with default error callback + var result = secp256k1.EcdsaRecoverableSignatureParseCompact(signature, serializedSignature, recoveryId); Assert.IsFalse(result); } @@ -333,8 +339,8 @@ public unsafe void SigAbortCtorCustomErrorHandlerTest() var serializedSignature = ecdsa_r.Concat(ecdsa_s).ToArray(); signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; byte recoveryId = 9; // incorrect recoveryId, it should be >=0 and <=3 - // We get SIGABORT here with default error callback - var result = secp256k1.RecoverableSignatureParseCompact(signature, serializedSignature, recoveryId); + // We get SIGABORT here with default error callback + var result = secp256k1.EcdsaRecoverableSignatureParseCompact(signature, serializedSignature, recoveryId); Assert.IsFalse(result); Assert.AreEqual("Error message test: recid >= 0 && recid <= 3", errorMsg); @@ -360,19 +366,24 @@ public unsafe void SigAbortSetCustomErrorHandlerTest() var serializedSignature = ecdsa_r.Concat(ecdsa_s).ToArray(); signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; byte recoveryId = 9; // incorrect recoveryId, it should be >=0 and <=3 - // We get SIGABORT here with default error callback - var result = secp256k1.RecoverableSignatureParseCompact(signature, serializedSignature, recoveryId); + // We get SIGABORT here with default error callback + var result = secp256k1.EcdsaRecoverableSignatureParseCompact(signature, serializedSignature, recoveryId); Assert.IsFalse(result); Assert.AreEqual("Error message test: recid >= 0 && recid <= 3", errorMsg); } [TestMethod] - public void LibPathProperty_ReturnsValidPath() + public void LibPathProperty_ReturnsValidValue() { // Access the static LibPath property to ensure it's covered var libPath = Secp256k1.LibPath; Assert.IsNotNull(libPath); - Assert.IsTrue(File.Exists(libPath), $"LibPath should point to an existing file: {libPath}"); + // LibPath is either a library name (standard resolution via NativeLibrary.TryLoad) + // or a full file path (fallback via LibPathResolver) + var isLibraryName = libPath == "secp256k1" || libPath == "libsecp256k1"; + var isFilePath = File.Exists(libPath); + Assert.IsTrue(isLibraryName || isFilePath, + $"LibPath should be either a library name or an existing file path: {libPath}"); } [TestMethod] @@ -383,7 +394,7 @@ public void NativeLibResolveLoadClose() try { File.Copy(origLibPath, tempLibPath, overwrite: true); - var libPtr = LoadLibNative.LoadLib(tempLibPath); + var libPtr = LoadLibNative.LoadLibrary(tempLibPath, out var _); LoadLibNative.CloseLibrary(libPtr); } finally @@ -430,7 +441,7 @@ public void NativeLibResolveWithExtraSearchPaths() Assert.AreEqual(tempLibPath, resolvedPath, "Library should be resolved from ExtraNativeLibSearchPaths"); // Actually load the library to prove it works - var libPtr = LoadLibNative.LoadLib(resolvedPath); + var libPtr = LoadLibNative.LoadLibrary(resolvedPath, out var _); Assert.AreNotEqual(IntPtr.Zero, libPtr, "Library should load successfully"); LoadLibNative.CloseLibrary(libPtr); } @@ -449,9 +460,8 @@ public void NativeLibLoadFailure() { var exception = Assert.ThrowsException(() => { - LoadLibNative.LoadLib("invalid_lib_test_123456"); + LoadLibNative.LoadLibrary("invalid_lib_test_123456", out var _); }); - StringAssert.Contains(exception.Message, "loading failed"); } [TestMethod] @@ -462,19 +472,22 @@ public void NativeLibCloseFailure() { LoadLibNative.CloseLibrary(new IntPtr(int.MaxValue)); }); - StringAssert.Contains(exception.Message, "closing failed"); } [TestMethod] public void NativeLibSymbolLoadFailure() { var libPath = LibPathResolver.Resolve(Secp256k1.LIB); - var libPtr = LoadLibNative.LoadLib(libPath); - var exception = Assert.ThrowsException(() => + var libPtr = LoadLibNative.LoadLibrary(libPath, out var _); + try { - LoadLibNative.GetDelegate(libPtr, "invalid_symbol_name_test_123456"); - }); - StringAssert.Contains(exception.Message, "symbol failed"); + LoadLibNative.GetSymbolPointer(libPtr, "invalid_symbol_name_test_123456"); + Assert.Fail("Expected an exception"); + } + catch (Exception ex) when (ex is not AssertFailedException) + { + // success - any exception was thrown + } } [TestMethod] @@ -490,10 +503,10 @@ public void PublicKeyNegateTest() var publicKey = new byte[publicKeyOriginal.Length]; Buffer.BlockCopy(publicKeyOriginal, 0, publicKey, 0, publicKeyOriginal.Length); - Assert.IsTrue(secp256k1.PublicKeyNegate(publicKey)); + Assert.IsTrue(secp256k1.EcPubkeyNegate(publicKey)); Assert.IsTrue(publicKeyOutput.SequenceEqual(publicKey)); } - + [TestMethod] public void PublicKeysCombineTest() { @@ -507,12 +520,12 @@ public void PublicKeysCombineTest() var expectedPublicKeyOutput = Convert.FromHexString( "75B39FA41258C450F987CB50CC151AA8FADC7BBFFA2B059C50A74A8434DE00726B635A12A12EEDB61E7736AB39740A5B78D2259EC9DF0692A321043D88156DB5"); - - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.IsTrue(secp256k1.PublicKeysCombine(publicKeyOutput, publicKey1, publicKey2)); + + var publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCombine(publicKeyOutput, new[] { publicKey1, publicKey2 })); Assert.IsTrue(publicKeyOutput.SequenceEqual(expectedPublicKeyOutput)); } - + [TestMethod] public void PublicKeyMultiplyTest() { @@ -524,13 +537,13 @@ public void PublicKeyMultiplyTest() Convert.FromHexString( "F626FF3EF22B127F75374BCD3202229E5AE12B3FB405E6687AFA6527ED300EA31269CC0E59E0D1E37B8FA56B0EA1435FF7F66EA3391EB94BA31E70C99FD70C38"); var tweak = Convert.FromHexString("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"); - Assert.IsTrue(secp256k1.PublicKeyMultiply(publicKey, tweak)); + Assert.IsTrue(secp256k1.EcPubkeyTweakMul(publicKey, tweak)); Assert.IsTrue(publicKeyOutput.SequenceEqual(publicKey)); } [TestMethod] - public void Rfc6979NonceTest() + public void NonceFunctionRfc6979Test() { // Reference test cases in https://github.com/decred/dcrd/blob/113758cab3304375cbfb7bfbc8e5d75406315d8b/dcrec/secp256k1/nonce_test.go#L40-L143 using var secp256k1 = new Secp256k1(); @@ -538,8 +551,7 @@ public void Rfc6979NonceTest() var hash = Convert.FromHexString("0000000000000000000000000000000000000000000000000000000000000001"); var secretKey = Convert.FromHexString("0011111111111111111111111111111111111111111111111111111111111111"); var nonceOutput = new byte[Secp256k1.NONCE_LENGTH]; - var s = Convert.ToHexString(nonceOutput); - Assert.IsTrue(secp256k1.Rfc6979Nonce(nonceOutput, hash, secretKey, null, null, 0)); + Assert.IsTrue(secp256k1.NonceFunctionRfc6979(nonceOutput, hash, secretKey, default, default, 0)); Assert.IsTrue(nonceOutput.SequenceEqual(nonce)); } @@ -566,10 +578,10 @@ public void ConcurrentInstanceCreation() using var secp256k1 = new Secp256k1(); // Do some basic operation to ensure the instance works - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; + var privateKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; new Random().NextBytes(privateKey); - secp256k1.PublicKeyCreate(publicKey, privateKey); + secp256k1.EcPubkeyCreate(publicKey, privateKey); } } catch (Exception ex) @@ -581,7 +593,7 @@ public void ConcurrentInstanceCreation() Task.WaitAll(tasks); - Assert.AreEqual(0, exceptions.Count, + Assert.AreEqual(0, exceptions.Count, $"Concurrent instance creation failed with {exceptions.Count} exception(s): " + $"{string.Join("; ", exceptions.Select(e => e.Message))}"); } @@ -592,545 +604,3206 @@ public class ArgumentValidationTests { [TestMethod] - public void Recover_InvalidPublicKeyOutput_ThrowsArgumentException() + public void EcdsaRecover_InvalidPublicKeyOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; var message = new byte[32]; - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small + var publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.Recover(publicKeyOutput, signature, message)); + secp256k1.EcdsaRecover(publicKeyOutput, signature, message)); } [TestMethod] - public void Recover_InvalidSignature_ThrowsArgumentException() + public void EcdsaRecover_InvalidSignature_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE - 1]; // Too small var message = new byte[32]; - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH]; + var publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.Recover(publicKeyOutput, signature, message)); + secp256k1.EcdsaRecover(publicKeyOutput, signature, message)); } [TestMethod] - public void Recover_InvalidMessage_ThrowsArgumentException() + public void EcdsaRecover_InvalidMessage_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; var message = new byte[31]; // Too small - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH]; + var publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.Recover(publicKeyOutput, signature, message)); + secp256k1.EcdsaRecover(publicKeyOutput, signature, message)); } [TestMethod] - public void SecretKeyVerify_InvalidSecretKey_ThrowsArgumentException() + public void EcSeckeyVerify_InvalidSecretKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var secretKey = new byte[Secp256k1.PRIVKEY_LENGTH - 1]; // Too small + var secretKey = new byte[Secp256k1.SECRET_KEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.SecretKeyVerify(secretKey)); + secp256k1.EcSeckeyVerify(secretKey)); } [TestMethod] - public void PublicKeyCreate_InvalidPublicKeyOutput_ThrowsArgumentException() + public void EcPubkeyCreate_InvalidPublicKeyOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - var privateKeyInput = new byte[Secp256k1.PRIVKEY_LENGTH]; + var publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH - 1]; // Too small + var privateKeyInput = new byte[Secp256k1.SECRET_KEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.PublicKeyCreate(publicKeyOutput, privateKeyInput)); + secp256k1.EcPubkeyCreate(publicKeyOutput, privateKeyInput)); } [TestMethod] - public void PublicKeyCreate_InvalidPrivateKeyInput_ThrowsArgumentException() + public void EcPubkeyCreate_InvalidPrivateKeyInput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH]; - var privateKeyInput = new byte[Secp256k1.PRIVKEY_LENGTH - 1]; // Too small + var publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var privateKeyInput = new byte[Secp256k1.SECRET_KEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.PublicKeyCreate(publicKeyOutput, privateKeyInput)); + secp256k1.EcPubkeyCreate(publicKeyOutput, privateKeyInput)); } [TestMethod] - public void RecoverableSignatureParseCompact_InvalidSignatureOutput_ThrowsArgumentException() + public void EcdsaRecoverableSignatureParseCompact_InvalidSignatureOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE - 1]; // Too small var compactSignature = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; Assert.ThrowsException(() => - secp256k1.RecoverableSignatureParseCompact(signatureOutput, compactSignature, 0)); + secp256k1.EcdsaRecoverableSignatureParseCompact(signatureOutput, compactSignature, 0)); } [TestMethod] - public void RecoverableSignatureParseCompact_InvalidCompactSignature_ThrowsArgumentException() + public void EcdsaSignRecoverable_InvalidSignatureOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; - var compactSignature = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE - 1]; // Too small + var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE - 1]; // Too small + var messageHash = new byte[32]; + var secretKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.RecoverableSignatureParseCompact(signatureOutput, compactSignature, 0)); + secp256k1.EcdsaSignRecoverable(signatureOutput, messageHash, secretKey)); } [TestMethod] - public void SignRecoverable_InvalidSignatureOutput_ThrowsArgumentException() + public void EcdsaSignRecoverable_InvalidMessageHash_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE - 1]; // Too small - var messageHash = new byte[32]; - var secretKey = new byte[Secp256k1.PRIVKEY_LENGTH]; + var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; + var messageHash = new byte[31]; // Too small + var secretKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.SignRecoverable(signatureOutput, messageHash, secretKey)); + secp256k1.EcdsaSignRecoverable(signatureOutput, messageHash, secretKey)); } [TestMethod] - public void SignRecoverable_InvalidMessageHash_ThrowsArgumentException() + public void EcdsaSignRecoverable_InvalidSecretKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; - var messageHash = new byte[31]; // Too small - var secretKey = new byte[Secp256k1.PRIVKEY_LENGTH]; + var messageHash = new byte[32]; + var secretKey = new byte[Secp256k1.SECRET_KEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.SignRecoverable(signatureOutput, messageHash, secretKey)); + secp256k1.EcdsaSignRecoverable(signatureOutput, messageHash, secretKey)); } [TestMethod] - public void SignRecoverable_InvalidSecretKey_ThrowsArgumentException() + public void EcdsaRecoverableSignatureSerializeCompact_InvalidSignature_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; - var messageHash = new byte[32]; - var secretKey = new byte[Secp256k1.PRIVKEY_LENGTH - 1]; // Too small + var compactSignatureOutput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE - 1]; // Too small Assert.ThrowsException(() => - secp256k1.SignRecoverable(signatureOutput, messageHash, secretKey)); + secp256k1.EcdsaRecoverableSignatureSerializeCompact(compactSignatureOutput, out _, signature)); } [TestMethod] - public void RecoverableSignatureSerializeCompact_InvalidCompactSignatureOutput_ThrowsArgumentException() + public void EcdsaSignatureNormalize_InvalidSignatureInput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var compactSignatureOutput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE - 1]; // Too small - var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; + var normalizedSignatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + var signatureInput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.RecoverableSignatureSerializeCompact(compactSignatureOutput, out _, signature)); + secp256k1.EcdsaSignatureNormalize(normalizedSignatureOutput, signatureInput)); } [TestMethod] - public void RecoverableSignatureSerializeCompact_InvalidSignature_ThrowsArgumentException() + public void EcdsaSignatureParseDer_InvalidSignatureOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var compactSignatureOutput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; - var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE - 1]; // Too small + var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH - 1]; // Too small + var derSignature = new byte[72]; Assert.ThrowsException(() => - secp256k1.RecoverableSignatureSerializeCompact(compactSignatureOutput, out _, signature)); + secp256k1.EcdsaSignatureParseDer(signatureOutput, derSignature)); } [TestMethod] - public void PublicKeySerialize_InvalidSerializedPublicKeyOutput_ThrowsArgumentException() + public void EcdsaSignatureSerializeCompact_InvalidSignatureInput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var serializedPublicKeyOutput = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH - 1]; // Too small - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; + var signatureOutput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; + var signatureInput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.PublicKeySerialize(serializedPublicKeyOutput, publicKey)); + secp256k1.EcdsaSignatureSerializeCompact(signatureOutput, signatureInput)); } [TestMethod] - public void PublicKeySerialize_InvalidSerializedPublicKeyOutputCompressed_ThrowsArgumentException() + public void EcdsaSignatureParseCompact_InvalidSignatureOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var serializedPublicKeyOutput = new byte[Secp256k1.SERIALIZED_COMPRESSED_PUBKEY_LENGTH - 1]; // Too small - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; + var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH - 1]; // Too small + var signatureInput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; Assert.ThrowsException(() => - secp256k1.PublicKeySerialize(serializedPublicKeyOutput, publicKey, Flags.SECP256K1_EC_COMPRESSED)); + secp256k1.EcdsaSignatureParseCompact(signatureOutput, signatureInput)); } [TestMethod] - public void PublicKeySerialize_InvalidPublicKey_ThrowsArgumentException() + public void EcdsaVerify_InvalidSignature_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var serializedPublicKeyOutput = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH - 1]; // Too small + var messageHash = new byte[Secp256k1.HASH_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.PublicKeySerialize(serializedPublicKeyOutput, publicKey)); + secp256k1.EcdsaVerify(signature, messageHash, publicKey)); } [TestMethod] - public void PublicKeyParse_InvalidSerializedPublicKey_ThrowsArgumentException() + public void EcdsaVerify_InvalidMessageHash_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH]; - var serializedPublicKey = new byte[32]; // Wrong size (not 33 or 65) + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + var messageHash = new byte[Secp256k1.HASH_LENGTH - 1]; // Too small + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.PublicKeyParse(publicKeyOutput, serializedPublicKey)); + secp256k1.EcdsaVerify(signature, messageHash, publicKey)); } [TestMethod] - public void PublicKeyParse_InvalidPublicKeyOutput_ThrowsArgumentException() + public void EcdsaVerify_InvalidPublicKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var publicKeyOutput = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - var serializedPublicKey = new byte[33]; // Valid size + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + var messageHash = new byte[Secp256k1.HASH_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.PublicKeyParse(publicKeyOutput, serializedPublicKey)); + secp256k1.EcdsaVerify(signature, messageHash, publicKey)); } [TestMethod] - public void SignatureNormalize_InvalidNormalizedSignatureOutput_ThrowsArgumentException() + public void EcdsaSign_InvalidSignatureOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var normalizedSignatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH - 1]; // Too small - var signatureInput = new byte[Secp256k1.SIGNATURE_LENGTH]; + var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH - 1]; // Too small + var messageHash = new byte[Secp256k1.HASH_LENGTH]; + var secretKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.SignatureNormalize(normalizedSignatureOutput, signatureInput)); + secp256k1.EcdsaSign(signatureOutput, messageHash, secretKey)); } [TestMethod] - public void SignatureNormalize_InvalidSignatureInput_ThrowsArgumentException() + public void EcdsaSign_InvalidMessageHash_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var normalizedSignatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; - var signatureInput = new byte[Secp256k1.SIGNATURE_LENGTH - 1]; // Too small + var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + var messageHash = new byte[Secp256k1.HASH_LENGTH - 1]; // Too small + var secretKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.SignatureNormalize(normalizedSignatureOutput, signatureInput)); + secp256k1.EcdsaSign(signatureOutput, messageHash, secretKey)); } [TestMethod] - public void SignatureParseDer_InvalidSignatureOutput_ThrowsArgumentException() + public void EcdsaSign_InvalidSecretKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH - 1]; // Too small - var derSignature = new byte[72]; + var signatureOutput = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + var messageHash = new byte[Secp256k1.HASH_LENGTH]; + var secretKey = new byte[Secp256k1.SECRET_KEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.SignatureParseDer(signatureOutput, derSignature)); + secp256k1.EcdsaSign(signatureOutput, messageHash, secretKey)); } [TestMethod] - public void SignatureSerializeDer_InvalidSignatureOutput_ThrowsArgumentException() + public void Ecdh_InvalidPublicKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SERIALIZED_DER_SIGNATURE_MAX_SIZE - 1]; // Too small - var signatureInput = new byte[Secp256k1.SIGNATURE_LENGTH]; + var resultOutput = new byte[Secp256k1.SECRET_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH - 1]; // Too small + var privateKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.SignatureSerializeDer(signatureOutput, signatureInput, out _)); + secp256k1.Ecdh(resultOutput, publicKey, privateKey)); } [TestMethod] - public void SignatureSerializeCompact_InvalidSignatureOutput_ThrowsArgumentException() + public void Ecdh_InvalidPrivateKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE - 1]; // Too small - var signatureInput = new byte[Secp256k1.SIGNATURE_LENGTH]; + var resultOutput = new byte[Secp256k1.SECRET_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var privateKey = new byte[Secp256k1.SECRET_KEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.SignatureSerializeCompact(signatureOutput, signatureInput)); + secp256k1.Ecdh(resultOutput, publicKey, privateKey)); } [TestMethod] - public void SignatureSerializeCompact_InvalidSignatureInput_ThrowsArgumentException() + public void EcdhWithHashFunction_InvalidResultOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; - var signatureInput = new byte[Secp256k1.SIGNATURE_LENGTH - 1]; // Too small + var resultOutput = new byte[Secp256k1.SECRET_LENGTH - 1]; // Too small + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var privateKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; + EcdhHashFunction hashFunc = (Span o, ReadOnlySpan x, ReadOnlySpan y, IntPtr d) => 1; Assert.ThrowsException(() => - secp256k1.SignatureSerializeCompact(signatureOutput, signatureInput)); + secp256k1.Ecdh(resultOutput, publicKey, privateKey, hashFunc, IntPtr.Zero)); } [TestMethod] - public void SignatureParseCompact_InvalidSignatureOutput_ThrowsArgumentException() + public void EcdhWithHashFunction_InvalidPublicKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH - 1]; // Too small - var signatureInput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE]; + var resultOutput = new byte[Secp256k1.SECRET_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH - 1]; // Too small + var privateKey = new byte[Secp256k1.SECRET_KEY_LENGTH]; + EcdhHashFunction hashFunc = (Span o, ReadOnlySpan x, ReadOnlySpan y, IntPtr d) => 1; Assert.ThrowsException(() => - secp256k1.SignatureParseCompact(signatureOutput, signatureInput)); + secp256k1.Ecdh(resultOutput, publicKey, privateKey, hashFunc, IntPtr.Zero)); } [TestMethod] - public void SignatureParseCompact_InvalidSignatureInput_ThrowsArgumentException() + public void EcdhWithHashFunction_InvalidPrivateKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; - var signatureInput = new byte[Secp256k1.SERIALIZED_SIGNATURE_SIZE - 1]; // Too small + var resultOutput = new byte[Secp256k1.SECRET_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var privateKey = new byte[Secp256k1.SECRET_KEY_LENGTH - 1]; // Too small + EcdhHashFunction hashFunc = (Span o, ReadOnlySpan x, ReadOnlySpan y, IntPtr d) => 1; Assert.ThrowsException(() => - secp256k1.SignatureParseCompact(signatureOutput, signatureInput)); + secp256k1.Ecdh(resultOutput, publicKey, privateKey, hashFunc, IntPtr.Zero)); } [TestMethod] - public void Verify_InvalidSignature_ThrowsArgumentException() + public void EcPubkeyCombine_NullArray_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signature = new byte[Secp256k1.SIGNATURE_LENGTH - 1]; // Too small - var messageHash = new byte[Secp256k1.HASH_LENGTH]; - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; + var outputPublicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.Verify(signature, messageHash, publicKey)); + secp256k1.EcPubkeyCombine(outputPublicKey, null)); } [TestMethod] - public void Verify_InvalidMessageHash_ThrowsArgumentException() + public void EcPubkeyCombine_EmptyArray_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signature = new byte[Secp256k1.SIGNATURE_LENGTH]; - var messageHash = new byte[Secp256k1.HASH_LENGTH - 1]; // Too small - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; + var outputPublicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; Assert.ThrowsException(() => - secp256k1.Verify(signature, messageHash, publicKey)); + secp256k1.EcPubkeyCombine(outputPublicKey, new byte[0][])); } [TestMethod] - public void Verify_InvalidPublicKey_ThrowsArgumentException() + public void EcPubkeyCombine_TooSmallElement_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signature = new byte[Secp256k1.SIGNATURE_LENGTH]; - var messageHash = new byte[Secp256k1.HASH_LENGTH]; - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small + var outputPublicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var smallPubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.Verify(signature, messageHash, publicKey)); + secp256k1.EcPubkeyCombine(outputPublicKey, new[] { smallPubkey })); } [TestMethod] - public void Sign_InvalidSignatureOutput_ThrowsArgumentException() + public void EcPubkeyNegate_InvalidPublicKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH - 1]; // Too small - var messageHash = new byte[Secp256k1.HASH_LENGTH]; - var secretKey = new byte[Secp256k1.PRIVKEY_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.Sign(signatureOutput, messageHash, secretKey)); + secp256k1.EcPubkeyNegate(publicKey)); } [TestMethod] - public void Sign_InvalidMessageHash_ThrowsArgumentException() + public void EcPubkeyTweakMul_InvalidPublicKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; - var messageHash = new byte[Secp256k1.HASH_LENGTH - 1]; // Too small - var secretKey = new byte[Secp256k1.PRIVKEY_LENGTH]; + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH - 1]; // Too small + var tweak = new byte[Secp256k1.SECRET_LENGTH]; Assert.ThrowsException(() => - secp256k1.Sign(signatureOutput, messageHash, secretKey)); + secp256k1.EcPubkeyTweakMul(publicKey, tweak)); } [TestMethod] - public void Sign_InvalidSecretKey_ThrowsArgumentException() + public void EcPubkeyTweakMul_InvalidTweak_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var signatureOutput = new byte[Secp256k1.SIGNATURE_LENGTH]; - var messageHash = new byte[Secp256k1.HASH_LENGTH]; - var secretKey = new byte[Secp256k1.PRIVKEY_LENGTH - 1]; // Too small + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var tweak = new byte[Secp256k1.SECRET_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.Sign(signatureOutput, messageHash, secretKey)); + secp256k1.EcPubkeyTweakMul(publicKey, tweak)); } [TestMethod] - public void Ecdh_InvalidResultOutput_ThrowsArgumentException() + public void NonceFunctionRfc6979_InvalidNonceOutput_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var resultOutput = new byte[Secp256k1.SECRET_LENGTH - 1]; // Too small - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; + var nonceOutput = new byte[Secp256k1.NONCE_LENGTH - 1]; // Too small + var hash = new byte[Secp256k1.HASH_LENGTH]; + var secretKey = new byte[Secp256k1.SECRET_LENGTH]; Assert.ThrowsException(() => - secp256k1.Ecdh(resultOutput, publicKey, privateKey)); + secp256k1.NonceFunctionRfc6979(nonceOutput, hash, secretKey, default, default, 0)); } [TestMethod] - public void Ecdh_InvalidPublicKey_ThrowsArgumentException() + public void NonceFunctionRfc6979_InvalidHash_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var resultOutput = new byte[Secp256k1.SECRET_LENGTH]; - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; + var nonceOutput = new byte[Secp256k1.NONCE_LENGTH]; + var hash = new byte[Secp256k1.HASH_LENGTH - 1]; // Too small + var secretKey = new byte[Secp256k1.SECRET_LENGTH]; Assert.ThrowsException(() => - secp256k1.Ecdh(resultOutput, publicKey, privateKey)); + secp256k1.NonceFunctionRfc6979(nonceOutput, hash, secretKey, default, default, 0)); } [TestMethod] - public void Ecdh_InvalidPrivateKey_ThrowsArgumentException() + public void NonceFunctionRfc6979_InvalidSecretKey_ThrowsArgumentException() { using var secp256k1 = new Secp256k1(); - var resultOutput = new byte[Secp256k1.SECRET_LENGTH]; - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH - 1]; // Too small + var nonceOutput = new byte[Secp256k1.NONCE_LENGTH]; + var hash = new byte[Secp256k1.HASH_LENGTH]; + var secretKey = new byte[Secp256k1.SECRET_LENGTH - 1]; // Too small Assert.ThrowsException(() => - secp256k1.Ecdh(resultOutput, publicKey, privateKey)); + secp256k1.NonceFunctionRfc6979(nonceOutput, hash, secretKey, default, default, 0)); } + } + [TestClass] + public class CustomNonceFunctionTests + { [TestMethod] - public void EcdhWithHashFunction_InvalidResultOutput_ThrowsArgumentException() + public void EcdsaSign_WithCustomNonceFunction() { using var secp256k1 = new Secp256k1(); - var resultOutput = new byte[Secp256k1.SECRET_LENGTH - 1]; // Too small - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; - EcdhHashFunction hashFunc = (Span o, Span x, Span y, IntPtr d) => 1; + var keypair = new + { + PrivateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"), + PublicKey = Convert.FromHexString("2208d5dc41d4f3ed555aff761e9bb0b99fbe6d1503b98711944be6a362242ebfa1c788c7a4e13f6aaa4099f9d2175fc031e5aa3ba08eb280e87dfb43bdae207f") + }; - Assert.ThrowsException(() => - secp256k1.Ecdh(resultOutput, publicKey, privateKey, hashFunc, IntPtr.Zero)); + var msgHash = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("Test message")); + + bool nonceFunctionCalled = false; + NonceFunction customNonce = (Span nonce, ReadOnlySpan msg, ReadOnlySpan key, + ReadOnlySpan algo, IntPtr data, uint attempt) => + { + nonceFunctionCalled = true; + // Use RFC6979 deterministic nonce generation + var tempNonce = new byte[32]; + secp256k1.NonceFunctionRfc6979(tempNonce, msg.ToArray(), key.ToArray(), default, default, attempt); + tempNonce.CopyTo(nonce); + return 1; + }; + + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaSign(signature, msgHash, keypair.PrivateKey, customNonce, IntPtr.Zero)); + Assert.IsTrue(nonceFunctionCalled, "Custom nonce function should have been called"); + Assert.IsTrue(secp256k1.EcdsaVerify(signature, msgHash, keypair.PublicKey)); } [TestMethod] - public void EcdhWithHashFunction_InvalidPublicKey_ThrowsArgumentException() + public void EcdsaSignRecoverable_WithCustomNonceFunction() { using var secp256k1 = new Secp256k1(); - var resultOutput = new byte[Secp256k1.SECRET_LENGTH]; - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH]; - EcdhHashFunction hashFunc = (Span o, Span x, Span y, IntPtr d) => 1; + var secretKey = Convert.FromHexString("e815acba8fcf085a0b4141060c13b8017a08da37f2eb1d6a5416adbb621560ef"); + var msgHash = Convert.FromHexString("c9f1c76685845ea81cac9925a7565887b7771b34b35e641cca85db9fefd0e71f"); - Assert.ThrowsException(() => - secp256k1.Ecdh(resultOutput, publicKey, privateKey, hashFunc, IntPtr.Zero)); + bool nonceFunctionCalled = false; + NonceFunction customNonce = (Span nonce, ReadOnlySpan msg, ReadOnlySpan key, + ReadOnlySpan algo, IntPtr data, uint attempt) => + { + nonceFunctionCalled = true; + var tempNonce = new byte[32]; + secp256k1.NonceFunctionRfc6979(tempNonce, msg.ToArray(), key.ToArray(), default, default, attempt); + tempNonce.CopyTo(nonce); + return 1; + }; + + var signature = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; + Assert.IsTrue(secp256k1.EcdsaSignRecoverable(signature, msgHash, secretKey, customNonce, IntPtr.Zero)); + Assert.IsTrue(nonceFunctionCalled, "Custom nonce function should have been called"); + + // Verify we can recover the public key + var publicKeyOutput = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaRecover(publicKeyOutput, signature, msgHash)); } + } + [TestClass] + public class SchnorrTests + { [TestMethod] - public void EcdhWithHashFunction_InvalidPrivateKey_ThrowsArgumentException() + public void SchnorrSign32AndVerify() { using var secp256k1 = new Secp256k1(); - var resultOutput = new byte[Secp256k1.SECRET_LENGTH]; - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - var privateKey = new byte[Secp256k1.PRIVKEY_LENGTH - 1]; // Too small - EcdhHashFunction hashFunc = (Span o, Span x, Span y, IntPtr d) => 1; - Assert.ThrowsException(() => - secp256k1.Ecdh(resultOutput, publicKey, privateKey, hashFunc, IntPtr.Zero)); + // Generate a keypair + var privateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var keypair = new byte[96]; // secp256k1_keypair size + Assert.IsTrue(secp256k1.KeypairCreate(keypair, privateKey)); + + // Get the xonly public key + var xonlyPubkey = new byte[64]; // secp256k1_xonly_pubkey size + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair)); + + // Sign a message + var msg32 = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("Test message for Schnorr")); + var auxRand = new byte[32]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(auxRand); + + var sig64 = new byte[64]; + Assert.IsTrue(secp256k1.SchnorrsigSign32(sig64, msg32, keypair, auxRand)); + + // Verify the signature + Assert.IsTrue(secp256k1.SchnorrsigVerify(sig64, msg32, xonlyPubkey)); } [TestMethod] - public void PublicKeysCombine_InvalidOutputPublicKey_ThrowsArgumentException() + public void SchnorrSignCustom() { using var secp256k1 = new Secp256k1(); - var outputPublicKey = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - var publicKey1 = new byte[Secp256k1.PUBKEY_LENGTH]; - var publicKey2 = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.ThrowsException(() => - secp256k1.PublicKeysCombine(outputPublicKey, publicKey1, publicKey2)); + var privateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, privateKey)); + + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.KeypairXonlyPub(xonlyPubkey, out _, keypair)); + + // Use a variable-length message + var msg = System.Text.Encoding.UTF8.GetBytes("Variable length message for Schnorr signing"); + + // extraparams is a struct with magic bytes and optional nonce function + // struct secp256k1_schnorrsig_extraparams { unsigned char magic[4]; secp256k1_nonce_function_hardened noncefp; void *ndata; } + // magic = { 0xDA, 0x6F, 0xB3, 0x8C } + var extraparams = new byte[64]; // enough space for the struct + extraparams[0] = 0xDA; + extraparams[1] = 0x6F; + extraparams[2] = 0xB3; + extraparams[3] = 0x8C; + + var sig64 = new byte[64]; + Assert.IsTrue(secp256k1.SchnorrsigSignCustom(sig64, msg, keypair, extraparams)); + + // Verify the signature + Assert.IsTrue(secp256k1.SchnorrsigVerify(sig64, msg, xonlyPubkey)); } + } + [TestClass] + public class EllswiftTests + { [TestMethod] - public void PublicKeysCombine_InvalidPublicKey1_ThrowsArgumentException() + public void EllswiftEncodeAndDecode() { using var secp256k1 = new Secp256k1(); - var outputPublicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - var publicKey1 = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - var publicKey2 = new byte[Secp256k1.PUBKEY_LENGTH]; - Assert.ThrowsException(() => - secp256k1.PublicKeysCombine(outputPublicKey, publicKey1, publicKey2)); + // Generate a key pair + var privateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(publicKey, privateKey)); + + // Encode to ellswift format + var rnd32 = new byte[32]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(rnd32); + var ell64 = new byte[64]; + Assert.IsTrue(secp256k1.EllswiftEncode(ell64, publicKey, rnd32)); + + // Decode back to public key + var decodedPubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EllswiftDecode(decodedPubkey, ell64)); + + // The decoded pubkey should match the original + Assert.AreEqual(Convert.ToHexString(publicKey), Convert.ToHexString(decodedPubkey)); } [TestMethod] - public void PublicKeysCombine_InvalidPublicKey2_ThrowsArgumentException() + public void EllswiftCreate() { using var secp256k1 = new Secp256k1(); - var outputPublicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - var publicKey1 = new byte[Secp256k1.PUBKEY_LENGTH]; - var publicKey2 = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - Assert.ThrowsException(() => - secp256k1.PublicKeysCombine(outputPublicKey, publicKey1, publicKey2)); + var privateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var auxRand = new byte[32]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(auxRand); + + var ell64 = new byte[64]; + Assert.IsTrue(secp256k1.EllswiftCreate(ell64, privateKey, auxRand)); + + // Verify we can decode it and get the correct public key + var decodedPubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EllswiftDecode(decodedPubkey, ell64)); + + // Compare with public key derived from private key + var expectedPubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(expectedPubkey, privateKey)); + Assert.AreEqual(Convert.ToHexString(expectedPubkey), Convert.ToHexString(decodedPubkey)); } [TestMethod] - public void PublicKeyNegate_InvalidPublicKey_ThrowsArgumentException() + public void EllswiftXdhKeyExchange() { using var secp256k1 = new Secp256k1(); - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - Assert.ThrowsException(() => - secp256k1.PublicKeyNegate(publicKey)); + // Alice's keys + var alicePrivate = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var aliceAuxRand = new byte[32]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(aliceAuxRand); + var aliceEll64 = new byte[64]; + Assert.IsTrue(secp256k1.EllswiftCreate(aliceEll64, alicePrivate, aliceAuxRand)); + + // Bob's keys + var bobPrivate = Convert.FromHexString("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"); + var bobAuxRand = new byte[32]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(bobAuxRand); + var bobEll64 = new byte[64]; + Assert.IsTrue(secp256k1.EllswiftCreate(bobEll64, bobPrivate, bobAuxRand)); + + // Custom hash function for XDH + bool hashFunctionCalled = false; + EllswiftXdhHashFunction hashFunc = (Span output, ReadOnlySpan x32, + ReadOnlySpan ell_a64, ReadOnlySpan ell_b64, IntPtr data) => + { + hashFunctionCalled = true; + // Simple hash: SHA256 of x32 + var hash = System.Security.Cryptography.SHA256.Create().ComputeHash(x32.ToArray()); + hash.CopyTo(output); + return 1; + }; + + // Alice computes shared secret (party = 0, Alice is initiator) + var aliceSecret = new byte[32]; + Assert.IsTrue(secp256k1.EllswiftXdh(aliceSecret, aliceEll64, bobEll64, alicePrivate, 0, hashFunc, IntPtr.Zero)); + Assert.IsTrue(hashFunctionCalled); + + // Bob computes shared secret (party = 1, Bob is responder) + hashFunctionCalled = false; + var bobSecret = new byte[32]; + Assert.IsTrue(secp256k1.EllswiftXdh(bobSecret, aliceEll64, bobEll64, bobPrivate, 1, hashFunc, IntPtr.Zero)); + Assert.IsTrue(hashFunctionCalled); + + // Secrets should match + Assert.AreEqual(Convert.ToHexString(aliceSecret), Convert.ToHexString(bobSecret)); } [TestMethod] - public void PublicKeyMultiply_InvalidPublicKey_ThrowsArgumentException() + public void EllswiftXdhHashFunctionPrefixTest() { using var secp256k1 = new Secp256k1(); - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH - 1]; // Too small - var tweak = new byte[Secp256k1.SECRET_LENGTH]; - Assert.ThrowsException(() => - secp256k1.PublicKeyMultiply(publicKey, tweak)); + var x32 = new byte[32]; + var ell_a64 = new byte[64]; + var ell_b64 = new byte[64]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(x32); + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(ell_a64); + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(ell_b64); + + var output = new byte[32]; + var data = new byte[1]; // Must be non-empty to avoid pinning empty span + Assert.IsTrue(secp256k1.EllswiftXdhHashFunctionPrefix(output, x32, ell_a64, ell_b64, data)); + Assert.IsFalse(output.All(b => b == 0), "Output should not be all zeros"); } [TestMethod] - public void PublicKeyMultiply_InvalidTweak_ThrowsArgumentException() + public void EllswiftXdhHashFunctionBip324Test() { using var secp256k1 = new Secp256k1(); - var publicKey = new byte[Secp256k1.PUBKEY_LENGTH]; - var tweak = new byte[Secp256k1.SECRET_LENGTH - 1]; // Too small - Assert.ThrowsException(() => - secp256k1.PublicKeyMultiply(publicKey, tweak)); + var x32 = new byte[32]; + var ell_a64 = new byte[64]; + var ell_b64 = new byte[64]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(x32); + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(ell_a64); + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(ell_b64); + + var output = new byte[32]; + var data = new byte[1]; // Must be non-empty to avoid pinning empty span + Assert.IsTrue(secp256k1.EllswiftXdhHashFunctionBip324(output, x32, ell_a64, ell_b64, data)); + Assert.IsFalse(output.All(b => b == 0), "Output should not be all zeros"); } + } + [TestClass] + public class MuSigTests + { [TestMethod] - public void Rfc6979Nonce_InvalidNonceOutput_ThrowsArgumentException() + public void MusigFullSigningFlow() { using var secp256k1 = new Secp256k1(); - var nonceOutput = new byte[Secp256k1.NONCE_LENGTH - 1]; // Too small - var hash = new byte[Secp256k1.HASH_LENGTH]; - var secretKey = new byte[Secp256k1.SECRET_LENGTH]; - Assert.ThrowsException(() => - secp256k1.Rfc6979Nonce(nonceOutput, hash, secretKey, null, null, 0)); + // Two signers + var signer1Key = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var signer2Key = Convert.FromHexString("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"); + + // Create keypairs + var keypair1 = new byte[96]; + var keypair2 = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair1, signer1Key)); + Assert.IsTrue(secp256k1.KeypairCreate(keypair2, signer2Key)); + + // Get public keys + var pubkey1 = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var pubkey2 = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, signer1Key)); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, signer2Key)); + + // Aggregate public keys + var aggPubkey = new byte[64]; // secp256k1_xonly_pubkey + var keyaggCache = new byte[197]; // secp256k1_musig_keyagg_cache + Assert.IsTrue(secp256k1.MusigPubkeyAgg(aggPubkey, keyaggCache, new[] { pubkey1, pubkey2 })); + + // Test MusigPubkeyGet - returns secp256k1_pubkey (not xonly_pubkey) + // This is the non-xonly aggregate pubkey, different from aggPubkey which is xonly + var retrievedAggPubkey = new byte[64]; // secp256k1_pubkey + Assert.IsTrue(secp256k1.MusigPubkeyGet(retrievedAggPubkey, keyaggCache)); + // Convert the retrieved pubkey to xonly to compare with aggPubkey + var retrievedXonly = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyFromPubkey(retrievedXonly, out _, retrievedAggPubkey)); + var retrievedSerialized = new byte[32]; + Assert.IsTrue(secp256k1.XonlyPubkeySerialize(retrievedSerialized, retrievedXonly)); + var aggSerialized = new byte[32]; + Assert.IsTrue(secp256k1.XonlyPubkeySerialize(aggSerialized, aggPubkey)); + Assert.AreEqual(Convert.ToHexString(aggSerialized), Convert.ToHexString(retrievedSerialized)); + + // Message to sign + var msg32 = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("MuSig test message")); + + // Generate nonces for both signers + var secnonce1 = new byte[132]; // secp256k1_musig_secnonce + var pubnonce1 = new byte[132]; // secp256k1_musig_pubnonce + var sessionRand1 = new byte[32]; + var extraInput1 = new byte[32]; // extra_input32 must be at least 32 bytes + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(sessionRand1); + Assert.IsTrue(secp256k1.MusigNonceGen(secnonce1, pubnonce1, sessionRand1, signer1Key, pubkey1, msg32, keyaggCache, extraInput1)); + + var secnonce2 = new byte[132]; + var pubnonce2 = new byte[132]; + var sessionRand2 = new byte[32]; + var extraInput2 = new byte[32]; // extra_input32 must be at least 32 bytes + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(sessionRand2); + Assert.IsTrue(secp256k1.MusigNonceGen(secnonce2, pubnonce2, sessionRand2, signer2Key, pubkey2, msg32, keyaggCache, extraInput2)); + + // Serialize and parse pubnonces (test round-trip) + var pubnonce1Serialized = new byte[66]; + Assert.IsTrue(secp256k1.MusigPubnonceSerialize(pubnonce1Serialized, pubnonce1)); + var pubnonce1Parsed = new byte[132]; + Assert.IsTrue(secp256k1.MusigPubnonceParse(pubnonce1Parsed, pubnonce1Serialized)); + + // Aggregate nonces + var aggnonce = new byte[132]; // secp256k1_musig_aggnonce + Assert.IsTrue(secp256k1.MusigNonceAgg(aggnonce, new[] { pubnonce1, pubnonce2 })); + + // Serialize and parse aggnonce (test round-trip) + var aggnonceSerialized = new byte[66]; + Assert.IsTrue(secp256k1.MusigAggnonceSerialize(aggnonceSerialized, aggnonce)); + var aggnonceParsed = new byte[132]; + Assert.IsTrue(secp256k1.MusigAggnonceParse(aggnonceParsed, aggnonceSerialized)); + + // Create signing session + var session = new byte[133]; // secp256k1_musig_session + Assert.IsTrue(secp256k1.MusigNonceProcess(session, aggnonce, msg32, keyaggCache)); + + // Create partial signatures + var partialSig1 = new byte[36]; // secp256k1_musig_partial_sig + Assert.IsTrue(secp256k1.MusigPartialSign(partialSig1, secnonce1, keypair1, keyaggCache, session)); + + var partialSig2 = new byte[36]; + Assert.IsTrue(secp256k1.MusigPartialSign(partialSig2, secnonce2, keypair2, keyaggCache, session)); + + // Verify partial signatures + Assert.IsTrue(secp256k1.MusigPartialSigVerify(partialSig1, pubnonce1, pubkey1, keyaggCache, session)); + Assert.IsTrue(secp256k1.MusigPartialSigVerify(partialSig2, pubnonce2, pubkey2, keyaggCache, session)); + + // Serialize and parse partial sig (test round-trip) + var partialSig1Serialized = new byte[32]; + Assert.IsTrue(secp256k1.MusigPartialSigSerialize(partialSig1Serialized, partialSig1)); + var partialSig1Parsed = new byte[36]; + Assert.IsTrue(secp256k1.MusigPartialSigParse(partialSig1Parsed, partialSig1Serialized)); + + // Aggregate partial signatures into final signature + var finalSig = new byte[64]; + Assert.IsTrue(secp256k1.MusigPartialSigAgg(finalSig, session, new[] { partialSig1, partialSig2 })); + + // Verify the final Schnorr signature + Assert.IsTrue(secp256k1.SchnorrsigVerify(finalSig, msg32, aggPubkey)); + } + + [TestMethod] + public void MusigPubkeyTweakTests() + { + using var secp256k1 = new Secp256k1(); + + var signer1Key = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var signer2Key = Convert.FromHexString("d8bdb07407bb011137ef7ba6a7f07c6a55c1e3600a6aa138e34ab5c16439ceda"); + + var pubkey1 = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var pubkey2 = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey1, signer1Key)); + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey2, signer2Key)); + + var aggPubkey = new byte[64]; + var keyaggCache = new byte[197]; + Assert.IsTrue(secp256k1.MusigPubkeyAgg(aggPubkey, keyaggCache, new[] { pubkey1, pubkey2 })); + + var tweak32 = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("tweak")); + + // Test EC tweak add + var tweakedPubkeyEc = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + var keyaggCacheEc = new byte[197]; + Array.Copy(keyaggCache, keyaggCacheEc, keyaggCache.Length); + Assert.IsTrue(secp256k1.MusigPubkeyEcTweakAdd(tweakedPubkeyEc, keyaggCacheEc, tweak32)); + + // Test xonly tweak add + var tweakedPubkeyXonly = new byte[64]; + var keyaggCacheXonly = new byte[197]; + Array.Copy(keyaggCache, keyaggCacheXonly, keyaggCache.Length); + Assert.IsTrue(secp256k1.MusigPubkeyXonlyTweakAdd(tweakedPubkeyXonly, keyaggCacheXonly, tweak32)); } + } + [TestClass] + public class GlobalFunctionWrapperTests + { [TestMethod] - public void Rfc6979Nonce_InvalidHash_ThrowsArgumentException() + public void NonceFunctionDefaultTest() { using var secp256k1 = new Secp256k1(); - var nonceOutput = new byte[Secp256k1.NONCE_LENGTH]; - var hash = new byte[Secp256k1.HASH_LENGTH - 1]; // Too small - var secretKey = new byte[Secp256k1.SECRET_LENGTH]; - Assert.ThrowsException(() => - secp256k1.Rfc6979Nonce(nonceOutput, hash, secretKey, null, null, 0)); + var nonce32 = new byte[32]; + var msg32 = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("test")); + var key32 = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + + Assert.IsTrue(secp256k1.NonceFunctionDefault(nonce32, msg32, key32, default, default, 0)); + Assert.IsFalse(nonce32.All(b => b == 0), "Nonce should not be all zeros"); } [TestMethod] - public void Rfc6979Nonce_InvalidSecretKey_ThrowsArgumentException() + public void EcdhHashFunctionDefaultTest() { using var secp256k1 = new Secp256k1(); - var nonceOutput = new byte[Secp256k1.NONCE_LENGTH]; - var hash = new byte[Secp256k1.HASH_LENGTH]; - var secretKey = new byte[Secp256k1.SECRET_LENGTH - 1]; // Too small - Assert.ThrowsException(() => - secp256k1.Rfc6979Nonce(nonceOutput, hash, secretKey, null, null, 0)); + var output = new byte[32]; + var x32 = new byte[32]; + var y32 = new byte[32]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(x32); + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(y32); + + Assert.IsTrue(secp256k1.EcdhHashFunctionDefault(output, x32, y32, default)); + Assert.IsFalse(output.All(b => b == 0), "Output should not be all zeros"); + } + + [TestMethod] + public void EcdhHashFunctionSha256Test() + { + using var secp256k1 = new Secp256k1(); + + var output = new byte[32]; + var x32 = new byte[32]; + var y32 = new byte[32]; + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(x32); + System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(y32); + + Assert.IsTrue(secp256k1.EcdhHashFunctionSha256(output, x32, y32, default)); + Assert.IsFalse(output.All(b => b == 0), "Output should not be all zeros"); + } + + [TestMethod] + public void NonceFunctionBip340Test() + { + using var secp256k1 = new Secp256k1(); + + var nonce32 = new byte[32]; + var msg = System.Text.Encoding.UTF8.GetBytes("BIP340 test message"); + var key32 = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + + // Get xonly pubkey + var pubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, key32)); + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyFromPubkey(xonlyPubkey, out _, pubkey)); + var serializedXonly = new byte[32]; + Assert.IsTrue(secp256k1.XonlyPubkeySerialize(serializedXonly, xonlyPubkey)); + + var algo = System.Text.Encoding.UTF8.GetBytes("BIP0340/nonce"); + + Assert.IsTrue(secp256k1.NonceFunctionBip340(nonce32, msg, (nuint)msg.Length, key32, serializedXonly, algo, (nuint)algo.Length, default)); + Assert.IsFalse(nonce32.All(b => b == 0), "Nonce should not be all zeros"); + } + } + + [TestClass] + public class KeypairAndXonlyTests + { + [TestMethod] + public void KeypairCreateAndExtract() + { + using var secp256k1 = new Secp256k1(); + + var privateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var keypair = new byte[96]; + Assert.IsTrue(secp256k1.KeypairCreate(keypair, privateKey)); + + // Extract public key + var pubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.KeypairPub(pubkey, keypair)); + + // Compare with directly created public key + var expectedPubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(expectedPubkey, privateKey)); + Assert.AreEqual(Convert.ToHexString(expectedPubkey), Convert.ToHexString(pubkey)); + + // Extract secret key + var extractedSecret = new byte[32]; + Assert.IsTrue(secp256k1.KeypairSec(extractedSecret, keypair)); + Assert.AreEqual(Convert.ToHexString(privateKey), Convert.ToHexString(extractedSecret)); + } + + [TestMethod] + public void XonlyPubkeyOperations() + { + using var secp256k1 = new Secp256k1(); + + var privateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var pubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(pubkey, privateKey)); + + // Convert to xonly + var xonlyPubkey = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyFromPubkey(xonlyPubkey, out int pkParity, pubkey)); + + // Serialize xonly pubkey + var serialized = new byte[32]; + Assert.IsTrue(secp256k1.XonlyPubkeySerialize(serialized, xonlyPubkey)); + + // Parse it back + var parsedXonly = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyParse(parsedXonly, serialized)); + Assert.AreEqual(Convert.ToHexString(xonlyPubkey), Convert.ToHexString(parsedXonly)); + + // Compare xonly pubkeys - returns 0 if equal + Assert.AreEqual(0, secp256k1.XonlyPubkeyCmp(xonlyPubkey, parsedXonly)); + + // Test tweak add + var tweak = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("tweak")); + var tweakedPubkey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.XonlyPubkeyTweakAdd(tweakedPubkey, xonlyPubkey, tweak)); + + // Verify tweak + var tweakedXonly = new byte[64]; + Assert.IsTrue(secp256k1.XonlyPubkeyFromPubkey(tweakedXonly, out int tweakedParity, tweakedPubkey)); + var tweakedSerialized = new byte[32]; + Assert.IsTrue(secp256k1.XonlyPubkeySerialize(tweakedSerialized, tweakedXonly)); + Assert.IsTrue(secp256k1.XonlyPubkeyTweakAddCheck(tweakedSerialized, tweakedParity, xonlyPubkey, tweak)); + } + } + + [TestClass] + public class AdditionalCoverageTests + { + [TestMethod] + public void EcSeckeyTweakAdd() + { + using var secp256k1 = new Secp256k1(); + + var secretKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var tweak = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("tweak")); + + var tweakedKey = new byte[32]; + Array.Copy(secretKey, tweakedKey, 32); + Assert.IsTrue(secp256k1.EcSeckeyTweakAdd(tweakedKey, tweak)); + Assert.AreNotEqual(Convert.ToHexString(secretKey), Convert.ToHexString(tweakedKey)); + } + + [TestMethod] + public void EcSeckeyTweakMul() + { + using var secp256k1 = new Secp256k1(); + + var secretKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var tweak = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("tweak")); + + var tweakedKey = new byte[32]; + Array.Copy(secretKey, tweakedKey, 32); + Assert.IsTrue(secp256k1.EcSeckeyTweakMul(tweakedKey, tweak)); + Assert.AreNotEqual(Convert.ToHexString(secretKey), Convert.ToHexString(tweakedKey)); + } + + [TestMethod] + public void EcPubkeyTweakAdd() + { + using var secp256k1 = new Secp256k1(); + + var privateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(publicKey, privateKey)); + + var tweak = System.Security.Cryptography.SHA256.Create().ComputeHash( + System.Text.Encoding.UTF8.GetBytes("tweak")); + + var originalPubkey = Convert.ToHexString(publicKey); + Assert.IsTrue(secp256k1.EcPubkeyTweakAdd(publicKey, tweak)); + Assert.AreNotEqual(originalPubkey, Convert.ToHexString(publicKey)); + } + + [TestMethod] + public void EcSeckeyNegate() + { + using var secp256k1 = new Secp256k1(); + + var secretKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + var negatedKey = new byte[32]; + Array.Copy(secretKey, negatedKey, 32); + + Assert.IsTrue(secp256k1.EcSeckeyNegate(negatedKey)); + Assert.AreNotEqual(Convert.ToHexString(secretKey), Convert.ToHexString(negatedKey)); + + // Negating twice should give original + Assert.IsTrue(secp256k1.EcSeckeyNegate(negatedKey)); + Assert.AreEqual(Convert.ToHexString(secretKey), Convert.ToHexString(negatedKey)); + } + + [TestMethod] + public void EcdsaRecoverableSignatureConvert() + { + using var secp256k1 = new Secp256k1(); + + var secretKey = Convert.FromHexString("e815acba8fcf085a0b4141060c13b8017a08da37f2eb1d6a5416adbb621560ef"); + var msgHash = Convert.FromHexString("c9f1c76685845ea81cac9925a7565887b7771b34b35e641cca85db9fefd0e71f"); + + // Create recoverable signature + var recoverableSig = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_SIZE]; + Assert.IsTrue(secp256k1.EcdsaSignRecoverable(recoverableSig, msgHash, secretKey)); + + // Convert to normal signature + var normalSig = new byte[Secp256k1.UNSERIALIZED_SIGNATURE_LENGTH]; + Assert.IsTrue(secp256k1.EcdsaRecoverableSignatureConvert(normalSig, recoverableSig)); + + // Verify the normal signature works + var publicKey = new byte[Secp256k1.UNSERIALIZED_PUBKEY_LENGTH]; + Assert.IsTrue(secp256k1.EcPubkeyCreate(publicKey, secretKey)); + Assert.IsTrue(secp256k1.EcdsaVerify(normalSig, msgHash, publicKey)); + } + + } + + /// + /// Tests for ArgumentException validation in wrapper methods. + /// These test the size validation code paths that throw when input buffers are too small. + /// + [TestClass] + public class ArgumentExceptionTests + { + private static readonly byte[] TestPrivateKey = Convert.FromHexString("7ef7543476bf146020cb59f9968a25ec67c3c73dbebad8a0b53a3256170dcdfe"); + + // EC Pubkey functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyCreate_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyCreate_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + var seckey = new byte[31]; // Should be 32 + secp256k1.EcPubkeyCreate(pubkey, seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeySerialize_TooSmallOutput_ThrowsArgumentException() + { + // The wrapper validates output buffer size based on the flags parameter. + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey); + var output = new byte[31]; // Too small for compressed (33 bytes) + nuint outputLen = (nuint)output.Length; + secp256k1.EcPubkeySerialize(output, ref outputLen, pubkey, Secp256k1EcFlags.Compressed); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeySerialize_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var output = new byte[65]; + nuint outputLen = 65; + secp256k1.EcPubkeySerialize(output, ref outputLen, pubkey, Secp256k1EcFlags.Uncompressed); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyParse_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var input = new byte[33]; + secp256k1.EcPubkeyParse(pubkey, input); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyCmp_TooSmallPubkey1_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey1 = new byte[63]; // Should be 64 + var pubkey2 = new byte[64]; + secp256k1.EcPubkeyCmp(pubkey1, pubkey2); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyCmp_TooSmallPubkey2_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey1 = new byte[64]; + var pubkey2 = new byte[63]; // Should be 64 + secp256k1.EcPubkeyCmp(pubkey1, pubkey2); + } + + // EC Seckey functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcSeckeyVerify_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[31]; // Should be 32 + secp256k1.EcSeckeyVerify(seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcSeckeyNegate_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[31]; // Should be 32 + secp256k1.EcSeckeyNegate(seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcSeckeyTweakAdd_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[31]; // Should be 32 + var tweak = new byte[32]; + secp256k1.EcSeckeyTweakAdd(seckey, tweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcSeckeyTweakAdd_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[32]; + var tweak = new byte[31]; // Should be 32 + secp256k1.EcSeckeyTweakAdd(seckey, tweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcSeckeyTweakMul_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[31]; // Should be 32 + var tweak = new byte[32]; + secp256k1.EcSeckeyTweakMul(seckey, tweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcSeckeyTweakMul_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[32]; + var tweak = new byte[31]; // Should be 32 + secp256k1.EcSeckeyTweakMul(seckey, tweak); + } + + // EC Pubkey tweak functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyNegate_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + secp256k1.EcPubkeyNegate(pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyTweakAdd_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var tweak = new byte[32]; + secp256k1.EcPubkeyTweakAdd(pubkey, tweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyTweakAdd_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey); + var tweak = new byte[31]; // Should be 32 + secp256k1.EcPubkeyTweakAdd(pubkey, tweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyTweakMul_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var tweak = new byte[32]; + secp256k1.EcPubkeyTweakMul(pubkey, tweak); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyTweakMul_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + secp256k1.EcPubkeyCreate(pubkey, TestPrivateKey); + var tweak = new byte[31]; // Should be 32 + secp256k1.EcPubkeyTweakMul(pubkey, tweak); + } + + // ECDSA signature functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignatureParseCompact_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[63]; // Should be 64 + var input64 = new byte[64]; + secp256k1.EcdsaSignatureParseCompact(sig, input64); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignatureParseCompact_TooSmallInput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; + var input64 = new byte[63]; // Should be 64 + secp256k1.EcdsaSignatureParseCompact(sig, input64); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignatureSerializeCompact_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output64 = new byte[63]; // Should be 64 + var sig = new byte[64]; + secp256k1.EcdsaSignatureSerializeCompact(output64, sig); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignatureSerializeCompact_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output64 = new byte[64]; + var sig = new byte[63]; // Should be 64 + secp256k1.EcdsaSignatureSerializeCompact(output64, sig); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignatureNormalize_TooSmallSigout_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sigout = new byte[63]; // Should be 64 + var sigin = new byte[64]; + secp256k1.EcdsaSignatureNormalize(sigout, sigin); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignatureNormalize_TooSmallSigin_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sigout = new byte[64]; + var sigin = new byte[63]; // Should be 64 + secp256k1.EcdsaSignatureNormalize(sigout, sigin); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaVerify_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[63]; // Should be 64 + var msghash32 = new byte[32]; + var pubkey = new byte[64]; + secp256k1.EcdsaVerify(sig, msghash32, pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaVerify_TooSmallMsghash_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; + var msghash32 = new byte[31]; // Should be 32 + var pubkey = new byte[64]; + secp256k1.EcdsaVerify(sig, msghash32, pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaVerify_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; + var msghash32 = new byte[32]; + var pubkey = new byte[63]; // Should be 64 + secp256k1.EcdsaVerify(sig, msghash32, pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSign_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[63]; // Should be 64 + var msghash32 = new byte[32]; + var seckey = new byte[32]; + secp256k1.EcdsaSign(sig, msghash32, seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSign_TooSmallMsghash_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; + var msghash32 = new byte[31]; // Should be 32 + var seckey = new byte[32]; + secp256k1.EcdsaSign(sig, msghash32, seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSign_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; + var msghash32 = new byte[32]; + var seckey = new byte[31]; // Should be 32 + secp256k1.EcdsaSign(sig, msghash32, seckey); + } + + // ECDSA recoverable signature functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecoverableSignatureParseCompact_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; // Should be 65 + var input64 = new byte[64]; + secp256k1.EcdsaRecoverableSignatureParseCompact(sig, input64, 0); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecoverableSignatureParseCompact_TooSmallInput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[65]; + var input64 = new byte[63]; // Should be 64 + secp256k1.EcdsaRecoverableSignatureParseCompact(sig, input64, 0); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecoverableSignatureSerializeCompact_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output64 = new byte[63]; // Should be 64 + var sig = new byte[65]; + secp256k1.EcdsaRecoverableSignatureSerializeCompact(output64, out _, sig); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecoverableSignatureSerializeCompact_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output64 = new byte[64]; + var sig = new byte[64]; // Should be 65 + secp256k1.EcdsaRecoverableSignatureSerializeCompact(output64, out _, sig); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecoverableSignatureConvert_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[63]; // Should be 64 + var sigin = new byte[65]; + secp256k1.EcdsaRecoverableSignatureConvert(sig, sigin); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecoverableSignatureConvert_TooSmallSigin_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; + var sigin = new byte[64]; // Should be 65 + secp256k1.EcdsaRecoverableSignatureConvert(sig, sigin); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignRecoverable_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; // Should be 65 + var msghash32 = new byte[32]; + var seckey = new byte[32]; + secp256k1.EcdsaSignRecoverable(sig, msghash32, seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecover_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var sig = new byte[65]; + var msghash32 = new byte[32]; + secp256k1.EcdsaRecover(pubkey, sig, msghash32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecover_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + var sig = new byte[64]; // Should be 65 + var msghash32 = new byte[32]; + secp256k1.EcdsaRecover(pubkey, sig, msghash32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaRecover_TooSmallMsghash_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + var sig = new byte[65]; + var msghash32 = new byte[31]; // Should be 32 + secp256k1.EcdsaRecover(pubkey, sig, msghash32); + } + + // ECDH functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Ecdh_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[31]; // Should be 32 + var pubkey = new byte[64]; + var seckey = new byte[32]; + secp256k1.Ecdh(output, pubkey, seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Ecdh_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var pubkey = new byte[63]; // Should be 64 + var seckey = new byte[32]; + secp256k1.Ecdh(output, pubkey, seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Ecdh_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var pubkey = new byte[64]; + var seckey = new byte[31]; // Should be 32 + secp256k1.Ecdh(output, pubkey, seckey); + } + + // Keypair functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairCreate_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var keypair = new byte[95]; // Should be 96 + var seckey = new byte[32]; + secp256k1.KeypairCreate(keypair, seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairCreate_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var keypair = new byte[96]; + var seckey = new byte[31]; // Should be 32 + secp256k1.KeypairCreate(keypair, seckey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairSec_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[31]; // Should be 32 + var keypair = new byte[96]; + secp256k1.KeypairCreate(keypair, TestPrivateKey); + secp256k1.KeypairSec(seckey, keypair); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairSec_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var seckey = new byte[32]; + var keypair = new byte[95]; // Should be 96 + secp256k1.KeypairSec(seckey, keypair); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairPub_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var keypair = new byte[96]; + secp256k1.KeypairCreate(keypair, TestPrivateKey); + secp256k1.KeypairPub(pubkey, keypair); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairPub_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + var keypair = new byte[95]; // Should be 96 + secp256k1.KeypairPub(pubkey, keypair); + } + + // Xonly pubkey functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyParse_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var input32 = new byte[32]; + secp256k1.XonlyPubkeyParse(pubkey, input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyParse_TooSmallInput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + var input32 = new byte[31]; // Should be 32 + secp256k1.XonlyPubkeyParse(pubkey, input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeySerialize_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output32 = new byte[31]; // Should be 32 + var pubkey = new byte[64]; + secp256k1.XonlyPubkeySerialize(output32, pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeySerialize_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output32 = new byte[32]; + var pubkey = new byte[63]; // Should be 64 + secp256k1.XonlyPubkeySerialize(output32, pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyCmp_TooSmallPk1_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pk1 = new byte[63]; // Should be 64 + var pk2 = new byte[64]; + secp256k1.XonlyPubkeyCmp(pk1, pk2); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyCmp_TooSmallPk2_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pk1 = new byte[64]; + var pk2 = new byte[63]; // Should be 64 + secp256k1.XonlyPubkeyCmp(pk1, pk2); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyFromPubkey_TooSmallXonlyPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var xonlyPubkey = new byte[63]; // Should be 64 + var pubkey = new byte[64]; + secp256k1.XonlyPubkeyFromPubkey(xonlyPubkey, out _, pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyFromPubkey_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var xonlyPubkey = new byte[64]; + var pubkey = new byte[63]; // Should be 64 + secp256k1.XonlyPubkeyFromPubkey(xonlyPubkey, out _, pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyTweakAdd_TooSmallOutputPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var outputPubkey = new byte[63]; // Should be 64 + var internalPubkey = new byte[64]; + var tweak32 = new byte[32]; + secp256k1.XonlyPubkeyTweakAdd(outputPubkey, internalPubkey, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyTweakAdd_TooSmallInternalPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var outputPubkey = new byte[64]; + var internalPubkey = new byte[63]; // Should be 64 + var tweak32 = new byte[32]; + secp256k1.XonlyPubkeyTweakAdd(outputPubkey, internalPubkey, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyTweakAdd_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var outputPubkey = new byte[64]; + var internalPubkey = new byte[64]; + var tweak32 = new byte[31]; // Should be 32 + secp256k1.XonlyPubkeyTweakAdd(outputPubkey, internalPubkey, tweak32); + } + + // Schnorr signature functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SchnorrsigSign32_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[63]; // Should be 64 + var msghash32 = new byte[32]; + var keypair = new byte[96]; + var aux_rand32 = new byte[32]; + secp256k1.SchnorrsigSign32(sig64, msghash32, keypair, aux_rand32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SchnorrsigSign32_TooSmallMsghash_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[64]; + var msghash32 = new byte[31]; // Should be 32 + var keypair = new byte[96]; + var aux_rand32 = new byte[32]; + secp256k1.SchnorrsigSign32(sig64, msghash32, keypair, aux_rand32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SchnorrsigSign32_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[64]; + var msghash32 = new byte[32]; + var keypair = new byte[95]; // Should be 96 + var aux_rand32 = new byte[32]; + secp256k1.SchnorrsigSign32(sig64, msghash32, keypair, aux_rand32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SchnorrsigSign32_TooSmallAuxRand_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[64]; + var msghash32 = new byte[32]; + var keypair = new byte[96]; + var aux_rand32 = new byte[31]; // Should be 32 + secp256k1.SchnorrsigSign32(sig64, msghash32, keypair, aux_rand32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SchnorrsigVerify_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[63]; // Should be 64 + var msg = new byte[32]; + var pubkey = new byte[64]; + secp256k1.SchnorrsigVerify(sig64, msg, pubkey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SchnorrsigVerify_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[64]; + var msg = new byte[32]; + var pubkey = new byte[63]; // Should be 64 + secp256k1.SchnorrsigVerify(sig64, msg, pubkey); + } + + // Ellswift functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftEncode_TooSmallEll64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var ell64 = new byte[63]; // Should be 64 + var pubkey = new byte[64]; + var rnd32 = new byte[32]; + secp256k1.EllswiftEncode(ell64, pubkey, rnd32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftEncode_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var ell64 = new byte[64]; + var pubkey = new byte[63]; // Should be 64 + var rnd32 = new byte[32]; + secp256k1.EllswiftEncode(ell64, pubkey, rnd32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftEncode_TooSmallRnd_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var ell64 = new byte[64]; + var pubkey = new byte[64]; + var rnd32 = new byte[31]; // Should be 32 + secp256k1.EllswiftEncode(ell64, pubkey, rnd32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftDecode_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var ell64 = new byte[64]; + secp256k1.EllswiftDecode(pubkey, ell64); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftDecode_TooSmallEll64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + var ell64 = new byte[63]; // Should be 64 + secp256k1.EllswiftDecode(pubkey, ell64); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftCreate_TooSmallEll64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var ell64 = new byte[63]; // Should be 64 + var seckey32 = new byte[32]; + var auxrnd32 = new byte[32]; + secp256k1.EllswiftCreate(ell64, seckey32, auxrnd32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftCreate_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var ell64 = new byte[64]; + var seckey32 = new byte[31]; // Should be 32 + var auxrnd32 = new byte[32]; + secp256k1.EllswiftCreate(ell64, seckey32, auxrnd32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftCreate_TooSmallAuxrnd_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var ell64 = new byte[64]; + var seckey32 = new byte[32]; + var auxrnd32 = new byte[31]; // Should be 32 + secp256k1.EllswiftCreate(ell64, seckey32, auxrnd32); + } + + // MuSig functions + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyAgg_TooSmallAggPk_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var aggPk = new byte[63]; // Should be 64 + var keyaggCache = new byte[197]; + var pubkeys = new[] { new byte[64], new byte[64] }; + secp256k1.MusigPubkeyAgg(aggPk, keyaggCache, pubkeys); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyAgg_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var aggPk = new byte[64]; + var keyaggCache = new byte[196]; // Should be 197 + var pubkeys = new[] { new byte[64], new byte[64] }; + secp256k1.MusigPubkeyAgg(aggPk, keyaggCache, pubkeys); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyAgg_EmptyPubkeys_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var aggPk = new byte[64]; + var keyaggCache = new byte[197]; + var pubkeys = new byte[0][]; + secp256k1.MusigPubkeyAgg(aggPk, keyaggCache, pubkeys); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGen_TooSmallSecnonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[131]; // Should be 132 + var pubnonce = new byte[132]; + var sessionSecrand32 = new byte[32]; + var seckey = new byte[32]; + var pubkey = new byte[64]; + var msg32 = new byte[32]; + var keyaggCache = new byte[197]; + var extraInput32 = new byte[32]; + secp256k1.MusigNonceGen(secnonce, pubnonce, sessionSecrand32, seckey, pubkey, msg32, keyaggCache, extraInput32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGen_TooSmallPubnonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[131]; // Should be 132 + var sessionSecrand32 = new byte[32]; + var seckey = new byte[32]; + var pubkey = new byte[64]; + var msg32 = new byte[32]; + var keyaggCache = new byte[197]; + var extraInput32 = new byte[32]; + secp256k1.MusigNonceGen(secnonce, pubnonce, sessionSecrand32, seckey, pubkey, msg32, keyaggCache, extraInput32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSign_TooSmallPartialSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partialSig = new byte[35]; // Should be 36 + var secnonce = new byte[132]; + var keypair = new byte[96]; + var keyaggCache = new byte[197]; + var session = new byte[133]; + secp256k1.MusigPartialSign(partialSig, secnonce, keypair, keyaggCache, session); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSign_TooSmallSecnonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partialSig = new byte[36]; + var secnonce = new byte[131]; // Should be 132 + var keypair = new byte[96]; + var keyaggCache = new byte[197]; + var session = new byte[133]; + secp256k1.MusigPartialSign(partialSig, secnonce, keypair, keyaggCache, session); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSign_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partialSig = new byte[36]; + var secnonce = new byte[132]; + var keypair = new byte[95]; // Should be 96 + var keyaggCache = new byte[197]; + var session = new byte[133]; + secp256k1.MusigPartialSign(partialSig, secnonce, keypair, keyaggCache, session); + } + + // Tagged hash function + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TaggedSha256_TooSmallHash_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var hash32 = new byte[31]; // Should be 32 + var tag = System.Text.Encoding.UTF8.GetBytes("test"); + var msg = System.Text.Encoding.UTF8.GetBytes("message"); + secp256k1.TaggedSha256(hash32, tag, msg); + } + + // Global function pointer wrappers + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionRfc6979_TooSmallNonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[31]; // Should be 32 + var msg32 = new byte[32]; + var key32 = new byte[32]; + var algo16 = new byte[16]; + var data = new byte[1]; + secp256k1.NonceFunctionRfc6979(nonce32, msg32, key32, algo16, data, 0); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionRfc6979_TooSmallMsg_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[32]; + var msg32 = new byte[31]; // Should be 32 + var key32 = new byte[32]; + var algo16 = new byte[16]; + var data = new byte[1]; + secp256k1.NonceFunctionRfc6979(nonce32, msg32, key32, algo16, data, 0); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionRfc6979_TooSmallKey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[32]; + var msg32 = new byte[32]; + var key32 = new byte[31]; // Should be 32 + var algo16 = new byte[16]; + var data = new byte[1]; + secp256k1.NonceFunctionRfc6979(nonce32, msg32, key32, algo16, data, 0); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdhHashFunctionDefault_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[31]; // Should be 32 + var x32 = new byte[32]; + var y32 = new byte[32]; + var data = new byte[1]; + secp256k1.EcdhHashFunctionDefault(output, x32, y32, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdhHashFunctionSha256_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[31]; // Should be 32 + var x32 = new byte[32]; + var y32 = new byte[32]; + var data = new byte[1]; + secp256k1.EcdhHashFunctionSha256(output, x32, y32, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionBip340_TooSmallNonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[31]; // Should be 32 + var msg = new byte[32]; + var key32 = new byte[32]; + var xonly_pk32 = new byte[32]; + var algo = System.Text.Encoding.UTF8.GetBytes("BIP0340/nonce"); + var data = new byte[1]; + secp256k1.NonceFunctionBip340(nonce32, msg, (nuint)msg.Length, key32, xonly_pk32, algo, (nuint)algo.Length, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionBip340_TooSmallKey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[32]; + var msg = new byte[32]; + var key32 = new byte[31]; // Should be 32 + var xonly_pk32 = new byte[32]; + var algo = System.Text.Encoding.UTF8.GetBytes("BIP0340/nonce"); + var data = new byte[1]; + secp256k1.NonceFunctionBip340(nonce32, msg, (nuint)msg.Length, key32, xonly_pk32, algo, (nuint)algo.Length, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionBip340_TooSmallXonlyPk_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[32]; + var msg = new byte[32]; + var key32 = new byte[32]; + var xonly_pk32 = new byte[31]; // Should be 32 + var algo = System.Text.Encoding.UTF8.GetBytes("BIP0340/nonce"); + var data = new byte[1]; + secp256k1.NonceFunctionBip340(nonce32, msg, (nuint)msg.Length, key32, xonly_pk32, algo, (nuint)algo.Length, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhHashFunctionPrefix_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[31]; // Should be 32 + var x32 = new byte[32]; + var ell_a64 = new byte[64]; + var ell_b64 = new byte[64]; + var data = new byte[1]; + secp256k1.EllswiftXdhHashFunctionPrefix(output, x32, ell_a64, ell_b64, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhHashFunctionPrefix_TooSmallX32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[31]; // Should be 32 + var ell_a64 = new byte[64]; + var ell_b64 = new byte[64]; + var data = new byte[1]; + secp256k1.EllswiftXdhHashFunctionPrefix(output, x32, ell_a64, ell_b64, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhHashFunctionPrefix_TooSmallEllA64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[32]; + var ell_a64 = new byte[63]; // Should be 64 + var ell_b64 = new byte[64]; + var data = new byte[1]; + secp256k1.EllswiftXdhHashFunctionPrefix(output, x32, ell_a64, ell_b64, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhHashFunctionPrefix_TooSmallEllB64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[32]; + var ell_a64 = new byte[64]; + var ell_b64 = new byte[63]; // Should be 64 + var data = new byte[1]; + secp256k1.EllswiftXdhHashFunctionPrefix(output, x32, ell_a64, ell_b64, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhHashFunctionBip324_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[31]; // Should be 32 + var x32 = new byte[32]; + var ell_a64 = new byte[64]; + var ell_b64 = new byte[64]; + var data = new byte[1]; + secp256k1.EllswiftXdhHashFunctionBip324(output, x32, ell_a64, ell_b64, data); + } + + // EllswiftXdhHashFunctionBip324 remaining parameters + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhHashFunctionBip324_TooSmallX32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[31]; // Should be 32 + var ell_a64 = new byte[64]; + var ell_b64 = new byte[64]; + var data = new byte[1]; + secp256k1.EllswiftXdhHashFunctionBip324(output, x32, ell_a64, ell_b64, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhHashFunctionBip324_TooSmallEllA64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[32]; + var ell_a64 = new byte[63]; // Should be 64 + var ell_b64 = new byte[64]; + var data = new byte[1]; + secp256k1.EllswiftXdhHashFunctionBip324(output, x32, ell_a64, ell_b64, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhHashFunctionBip324_TooSmallEllB64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[32]; + var ell_a64 = new byte[64]; + var ell_b64 = new byte[63]; // Should be 64 + var data = new byte[1]; + secp256k1.EllswiftXdhHashFunctionBip324(output, x32, ell_a64, ell_b64, data); + } + + // EcdsaSignatureSerializeDer tests + [TestMethod] + public void EcdsaSignatureSerializeDer_TooSmallOutput_ReturnsFalse() + { + // Variable-length output buffers are not validated by the wrapper. + // The native library handles size checking and returns failure. + using var secp256k1 = new Secp256k1(); + + // First create a valid signature + var sig = new byte[64]; + var msg = new byte[32]; + for (int i = 0; i < msg.Length; i++) msg[i] = (byte)(i + 1); + Assert.IsTrue(secp256k1.EcdsaSign(sig, msg, TestPrivateKey), "Sign should succeed"); + + // Try to serialize with too small output - native library returns 0 (false) + var output = new byte[31]; // Too small for DER signature (typically 71-72 bytes) + nuint outputLen = (nuint)output.Length; + var result = secp256k1.EcdsaSignatureSerializeDer(output, ref outputLen, sig); + Assert.IsFalse(result, "Native library should reject too-small buffer"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignatureSerializeDer_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[72]; + nuint outputLen = 72; + var sig = new byte[63]; // Should be 64 + secp256k1.EcdsaSignatureSerializeDer(output, ref outputLen, sig); + } + + // EcdsaSignatureParseDer tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignatureParseDer_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[63]; // Should be 64 + var input = new byte[72]; + secp256k1.EcdsaSignatureParseDer(sig, input); + } + + // EcdsaSignRecoverable additional tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignRecoverable_TooSmallMsghash_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[65]; + var msghash32 = new byte[31]; // Should be 32 + secp256k1.EcdsaSignRecoverable(sig, msghash32, TestPrivateKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignRecoverable_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[65]; + var msghash32 = new byte[32]; + var seckey = new byte[31]; // Should be 32 + secp256k1.EcdsaSignRecoverable(sig, msghash32, seckey); + } + + // XonlyPubkeyTweakAddCheck tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyTweakAddCheck_TooSmallTweakedPubkey32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var tweaked_pubkey32 = new byte[31]; // Should be 32 + var internal_pubkey = new byte[64]; + var tweak32 = new byte[32]; + secp256k1.XonlyPubkeyTweakAddCheck(tweaked_pubkey32, 0, internal_pubkey, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyTweakAddCheck_TooSmallInternalPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var tweaked_pubkey32 = new byte[32]; + var internal_pubkey = new byte[63]; // Should be 64 + var tweak32 = new byte[32]; + secp256k1.XonlyPubkeyTweakAddCheck(tweaked_pubkey32, 0, internal_pubkey, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void XonlyPubkeyTweakAddCheck_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var tweaked_pubkey32 = new byte[32]; + var internal_pubkey = new byte[64]; + var tweak32 = new byte[31]; // Should be 32 + secp256k1.XonlyPubkeyTweakAddCheck(tweaked_pubkey32, 0, internal_pubkey, tweak32); + } + + // KeypairXonlyPub tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairXonlyPub_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[63]; // Should be 64 + var keypair = new byte[96]; + secp256k1.KeypairXonlyPub(pubkey, out _, keypair); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairXonlyPub_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkey = new byte[64]; + var keypair = new byte[95]; // Should be 96 + secp256k1.KeypairXonlyPub(pubkey, out _, keypair); + } + + // KeypairXonlyTweakAdd tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairXonlyTweakAdd_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var keypair = new byte[95]; // Should be 96 + var tweak32 = new byte[32]; + secp256k1.KeypairXonlyTweakAdd(keypair, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void KeypairXonlyTweakAdd_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var keypair = new byte[96]; + var tweak32 = new byte[31]; // Should be 32 + secp256k1.KeypairXonlyTweakAdd(keypair, tweak32); + } + + // SchnorrsigSignCustom tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SchnorrsigSignCustom_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[63]; // Should be 64 + var keypair = new byte[96]; + var extraparams = new byte[1]; + secp256k1.SchnorrsigSignCustom(sig64, Array.Empty(), keypair, extraparams); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void SchnorrsigSignCustom_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[64]; + var keypair = new byte[95]; // Should be 96 + var extraparams = new byte[1]; + secp256k1.SchnorrsigSignCustom(sig64, Array.Empty(), keypair, extraparams); + } + + // EcdhHashFunctionSha256 additional tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdhHashFunctionSha256_TooSmallX32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[31]; // Should be 32 + var y32 = new byte[32]; + var data = new byte[1]; + secp256k1.EcdhHashFunctionSha256(output, x32, y32, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdhHashFunctionSha256_TooSmallY32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[32]; + var y32 = new byte[31]; // Should be 32 + var data = new byte[1]; + secp256k1.EcdhHashFunctionSha256(output, x32, y32, data); + } + + // EcdhHashFunctionDefault additional tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdhHashFunctionDefault_TooSmallX32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[31]; // Should be 32 + var y32 = new byte[32]; + var data = new byte[1]; + secp256k1.EcdhHashFunctionDefault(output, x32, y32, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdhHashFunctionDefault_TooSmallY32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var x32 = new byte[32]; + var y32 = new byte[31]; // Should be 32 + var data = new byte[1]; + secp256k1.EcdhHashFunctionDefault(output, x32, y32, data); + } + + // NonceFunctionDefault tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionDefault_TooSmallNonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[31]; // Should be 32 + var msg32 = new byte[32]; + var key32 = new byte[32]; + var algo16 = new byte[16]; + var data = new byte[32]; + secp256k1.NonceFunctionDefault(nonce32, msg32, key32, algo16, data, 0); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionDefault_TooSmallMsg_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[32]; + var msg32 = new byte[31]; // Should be 32 + var key32 = new byte[32]; + var algo16 = new byte[16]; + var data = new byte[32]; + secp256k1.NonceFunctionDefault(nonce32, msg32, key32, algo16, data, 0); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NonceFunctionDefault_TooSmallKey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce32 = new byte[32]; + var msg32 = new byte[32]; + var key32 = new byte[31]; // Should be 32 + var algo16 = new byte[16]; + var data = new byte[32]; + secp256k1.NonceFunctionDefault(nonce32, msg32, key32, algo16, data, 0); + } + + // MusigPubnonceParse tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubnonceParse_TooSmallNonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce = new byte[131]; // Should be 132 + var in66 = new byte[66]; + secp256k1.MusigPubnonceParse(nonce, in66); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubnonceParse_TooSmallIn66_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce = new byte[132]; + var in66 = new byte[65]; // Should be 66 + secp256k1.MusigPubnonceParse(nonce, in66); + } + + // MusigPubnonceSerialize tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubnonceSerialize_TooSmallOut66_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var out66 = new byte[65]; // Should be 66 + var nonce = new byte[132]; + secp256k1.MusigPubnonceSerialize(out66, nonce); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubnonceSerialize_TooSmallNonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var out66 = new byte[66]; + var nonce = new byte[131]; // Should be 132 + secp256k1.MusigPubnonceSerialize(out66, nonce); + } + + // MusigAggnonceParse tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigAggnonceParse_TooSmallNonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce = new byte[131]; // Should be 132 + var in66 = new byte[66]; + secp256k1.MusigAggnonceParse(nonce, in66); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigAggnonceParse_TooSmallIn66_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var nonce = new byte[132]; + var in66 = new byte[65]; // Should be 66 + secp256k1.MusigAggnonceParse(nonce, in66); + } + + // MusigAggnonceSerialize tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigAggnonceSerialize_TooSmallOut66_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var out66 = new byte[65]; // Should be 66 + var nonce = new byte[132]; + secp256k1.MusigAggnonceSerialize(out66, nonce); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigAggnonceSerialize_TooSmallNonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var out66 = new byte[66]; + var nonce = new byte[131]; // Should be 132 + secp256k1.MusigAggnonceSerialize(out66, nonce); + } + + // MusigPartialSigParse tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigParse_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[35]; // Should be 36 + var in32 = new byte[32]; + secp256k1.MusigPartialSigParse(sig, in32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigParse_TooSmallIn32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[36]; + var in32 = new byte[31]; // Should be 32 + secp256k1.MusigPartialSigParse(sig, in32); + } + + // MusigPartialSigSerialize tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigSerialize_TooSmallOut32_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var out32 = new byte[31]; // Should be 32 + var sig = new byte[36]; + secp256k1.MusigPartialSigSerialize(out32, sig); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigSerialize_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var out32 = new byte[32]; + var sig = new byte[35]; // Should be 36 + secp256k1.MusigPartialSigSerialize(out32, sig); + } + + // MusigPubkeyAgg additional tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyAgg_TooSmallPubkeyElement_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var agg_pk = new byte[64]; + var keyagg_cache = new byte[197]; + var pubkeys = new byte[][] { new byte[63] }; // Each should be 64 + secp256k1.MusigPubkeyAgg(agg_pk, keyagg_cache, pubkeys); + } + + // MusigPubkeyGet tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyGet_TooSmallAggPk_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var agg_pk = new byte[63]; // Should be 64 + var keyagg_cache = new byte[197]; + secp256k1.MusigPubkeyGet(agg_pk, keyagg_cache); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyGet_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var agg_pk = new byte[64]; + var keyagg_cache = new byte[196]; // Should be 197 + secp256k1.MusigPubkeyGet(agg_pk, keyagg_cache); + } + + // MusigPubkeyEcTweakAdd tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyEcTweakAdd_TooSmallOutputPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output_pubkey = new byte[63]; // Should be 64 + var keyagg_cache = new byte[197]; + var tweak32 = new byte[32]; + secp256k1.MusigPubkeyEcTweakAdd(output_pubkey, keyagg_cache, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyEcTweakAdd_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output_pubkey = new byte[64]; + var keyagg_cache = new byte[196]; // Should be 197 + var tweak32 = new byte[32]; + secp256k1.MusigPubkeyEcTweakAdd(output_pubkey, keyagg_cache, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyEcTweakAdd_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output_pubkey = new byte[64]; + var keyagg_cache = new byte[197]; + var tweak32 = new byte[31]; // Should be 32 + secp256k1.MusigPubkeyEcTweakAdd(output_pubkey, keyagg_cache, tweak32); + } + + // MusigPubkeyXonlyTweakAdd tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyXonlyTweakAdd_TooSmallOutputPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output_pubkey = new byte[63]; // Should be 64 + var keyagg_cache = new byte[197]; + var tweak32 = new byte[32]; + secp256k1.MusigPubkeyXonlyTweakAdd(output_pubkey, keyagg_cache, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyXonlyTweakAdd_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output_pubkey = new byte[64]; + var keyagg_cache = new byte[196]; // Should be 197 + var tweak32 = new byte[32]; + secp256k1.MusigPubkeyXonlyTweakAdd(output_pubkey, keyagg_cache, tweak32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPubkeyXonlyTweakAdd_TooSmallTweak_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output_pubkey = new byte[64]; + var keyagg_cache = new byte[197]; + var tweak32 = new byte[31]; // Should be 32 + secp256k1.MusigPubkeyXonlyTweakAdd(output_pubkey, keyagg_cache, tweak32); + } + + // MusigNonceGen additional tests (many parameters) + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGen_TooSmallSessionSecrand_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var session_secrand32 = new byte[31]; // Should be 32 + var seckey = new byte[32]; + var pubkey = new byte[64]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGen(secnonce, pubnonce, session_secrand32, seckey, pubkey, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGen_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var session_secrand32 = new byte[32]; + var seckey = new byte[31]; // Should be 32 + var pubkey = new byte[64]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGen(secnonce, pubnonce, session_secrand32, seckey, pubkey, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGen_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var session_secrand32 = new byte[32]; + var seckey = new byte[32]; + var pubkey = new byte[63]; // Should be 64 + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGen(secnonce, pubnonce, session_secrand32, seckey, pubkey, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGen_TooSmallMsg_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var session_secrand32 = new byte[32]; + var seckey = new byte[32]; + var pubkey = new byte[64]; + var msg32 = new byte[31]; // Should be 32 + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGen(secnonce, pubnonce, session_secrand32, seckey, pubkey, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGen_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var session_secrand32 = new byte[32]; + var seckey = new byte[32]; + var pubkey = new byte[64]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[196]; // Should be 197 + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGen(secnonce, pubnonce, session_secrand32, seckey, pubkey, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGen_TooSmallExtraInput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var session_secrand32 = new byte[32]; + var seckey = new byte[32]; + var pubkey = new byte[64]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[31]; // Should be 32 + secp256k1.MusigNonceGen(secnonce, pubnonce, session_secrand32, seckey, pubkey, msg32, keyagg_cache, extra_input32); + } + + // MusigNonceGenCounter tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGenCounter_TooSmallSecnonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[131]; // Should be 132 + var pubnonce = new byte[132]; + var keypair = new byte[96]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGenCounter(secnonce, pubnonce, 0, keypair, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGenCounter_TooSmallPubnonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[131]; // Should be 132 + var keypair = new byte[96]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGenCounter(secnonce, pubnonce, 0, keypair, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGenCounter_TooSmallKeypair_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var keypair = new byte[95]; // Should be 96 + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGenCounter(secnonce, pubnonce, 0, keypair, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGenCounter_TooSmallMsg_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var keypair = new byte[96]; + var msg32 = new byte[31]; // Should be 32 + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGenCounter(secnonce, pubnonce, 0, keypair, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGenCounter_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var keypair = new byte[96]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[196]; // Should be 197 + var extra_input32 = new byte[32]; + secp256k1.MusigNonceGenCounter(secnonce, pubnonce, 0, keypair, msg32, keyagg_cache, extra_input32); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceGenCounter_TooSmallExtraInput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var secnonce = new byte[132]; + var pubnonce = new byte[132]; + var keypair = new byte[96]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + var extra_input32 = new byte[31]; // Should be 32 + secp256k1.MusigNonceGenCounter(secnonce, pubnonce, 0, keypair, msg32, keyagg_cache, extra_input32); + } + + // MusigNonceAgg tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceAgg_EmptyPubnonces_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var aggnonce = new byte[132]; + var pubnonces = Array.Empty(); + secp256k1.MusigNonceAgg(aggnonce, pubnonces); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceAgg_TooSmallPubnonceElement_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var aggnonce = new byte[132]; + var pubnonces = new byte[][] { new byte[131] }; // Each should be 132 + secp256k1.MusigNonceAgg(aggnonce, pubnonces); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceAgg_TooSmallAggnonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var aggnonce = new byte[131]; // Should be 132 + var pubnonces = new byte[][] { new byte[132] }; + secp256k1.MusigNonceAgg(aggnonce, pubnonces); + } + + // MusigNonceProcess tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceProcess_TooSmallSession_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var session = new byte[132]; // Should be 133 + var aggnonce = new byte[132]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + secp256k1.MusigNonceProcess(session, aggnonce, msg32, keyagg_cache); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceProcess_TooSmallAggnonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var session = new byte[133]; + var aggnonce = new byte[131]; // Should be 132 + var msg32 = new byte[32]; + var keyagg_cache = new byte[197]; + secp256k1.MusigNonceProcess(session, aggnonce, msg32, keyagg_cache); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceProcess_TooSmallMsg_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var session = new byte[133]; + var aggnonce = new byte[132]; + var msg32 = new byte[31]; // Should be 32 + var keyagg_cache = new byte[197]; + secp256k1.MusigNonceProcess(session, aggnonce, msg32, keyagg_cache); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigNonceProcess_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var session = new byte[133]; + var aggnonce = new byte[132]; + var msg32 = new byte[32]; + var keyagg_cache = new byte[196]; // Should be 197 + secp256k1.MusigNonceProcess(session, aggnonce, msg32, keyagg_cache); + } + + // MusigPartialSign additional tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSign_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partial_sig = new byte[36]; + var secnonce = new byte[132]; + var keypair = new byte[96]; + var keyagg_cache = new byte[196]; // Should be 197 + var session = new byte[133]; + secp256k1.MusigPartialSign(partial_sig, secnonce, keypair, keyagg_cache, session); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSign_TooSmallSession_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partial_sig = new byte[36]; + var secnonce = new byte[132]; + var keypair = new byte[96]; + var keyagg_cache = new byte[197]; + var session = new byte[132]; // Should be 133 + secp256k1.MusigPartialSign(partial_sig, secnonce, keypair, keyagg_cache, session); + } + + // MusigPartialSigVerify tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigVerify_TooSmallPartialSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partial_sig = new byte[35]; // Should be 36 + var pubnonce = new byte[132]; + var pubkey = new byte[64]; + var keyagg_cache = new byte[197]; + var session = new byte[133]; + secp256k1.MusigPartialSigVerify(partial_sig, pubnonce, pubkey, keyagg_cache, session); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigVerify_TooSmallPubnonce_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partial_sig = new byte[36]; + var pubnonce = new byte[131]; // Should be 132 + var pubkey = new byte[64]; + var keyagg_cache = new byte[197]; + var session = new byte[133]; + secp256k1.MusigPartialSigVerify(partial_sig, pubnonce, pubkey, keyagg_cache, session); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigVerify_TooSmallPubkey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partial_sig = new byte[36]; + var pubnonce = new byte[132]; + var pubkey = new byte[63]; // Should be 64 + var keyagg_cache = new byte[197]; + var session = new byte[133]; + secp256k1.MusigPartialSigVerify(partial_sig, pubnonce, pubkey, keyagg_cache, session); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigVerify_TooSmallKeyaggCache_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partial_sig = new byte[36]; + var pubnonce = new byte[132]; + var pubkey = new byte[64]; + var keyagg_cache = new byte[196]; // Should be 197 + var session = new byte[133]; + secp256k1.MusigPartialSigVerify(partial_sig, pubnonce, pubkey, keyagg_cache, session); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigVerify_TooSmallSession_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var partial_sig = new byte[36]; + var pubnonce = new byte[132]; + var pubkey = new byte[64]; + var keyagg_cache = new byte[197]; + var session = new byte[132]; // Should be 133 + secp256k1.MusigPartialSigVerify(partial_sig, pubnonce, pubkey, keyagg_cache, session); + } + + // MusigPartialSigAgg tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigAgg_EmptyPartialSigs_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[64]; + var session = new byte[133]; + var partial_sigs = Array.Empty(); + secp256k1.MusigPartialSigAgg(sig64, session, partial_sigs); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigAgg_TooSmallPartialSigElement_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[64]; + var session = new byte[133]; + var partial_sigs = new byte[][] { new byte[35] }; // Each should be 36 + secp256k1.MusigPartialSigAgg(sig64, session, partial_sigs); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigAgg_TooSmallSig64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[63]; // Should be 64 + var session = new byte[133]; + var partial_sigs = new byte[][] { new byte[36] }; + secp256k1.MusigPartialSigAgg(sig64, session, partial_sigs); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MusigPartialSigAgg_TooSmallSession_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig64 = new byte[64]; + var session = new byte[132]; // Should be 133 + var partial_sigs = new byte[][] { new byte[36] }; + secp256k1.MusigPartialSigAgg(sig64, session, partial_sigs); + } + + // EcPubkeyCombine additional tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeyCombine_TooSmallOut_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var @out = new byte[63]; // Should be 64 + var ins = new byte[][] { new byte[64], new byte[64] }; + secp256k1.EcPubkeyCombine(@out, ins); + } + + // EcdsaSign with NonceFunction callback tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignWithCallback_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[63]; // Should be 64 + var msghash32 = new byte[32]; + NonceFunction noncefp = (Span nonce, ReadOnlySpan msg, ReadOnlySpan key, ReadOnlySpan algo, IntPtr data, uint attempt) => 1; + secp256k1.EcdsaSign(sig, msghash32, TestPrivateKey, noncefp, IntPtr.Zero); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignWithCallback_TooSmallMsghash_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; + var msghash32 = new byte[31]; // Should be 32 + NonceFunction noncefp = (Span nonce, ReadOnlySpan msg, ReadOnlySpan key, ReadOnlySpan algo, IntPtr data, uint attempt) => 1; + secp256k1.EcdsaSign(sig, msghash32, TestPrivateKey, noncefp, IntPtr.Zero); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignWithCallback_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; + var msghash32 = new byte[32]; + var seckey = new byte[31]; // Should be 32 + NonceFunction noncefp = (Span nonce, ReadOnlySpan msg, ReadOnlySpan key, ReadOnlySpan algo, IntPtr data, uint attempt) => 1; + secp256k1.EcdsaSign(sig, msghash32, seckey, noncefp, IntPtr.Zero); + } + + // EcdsaSignRecoverable with NonceFunction callback tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignRecoverableWithCallback_TooSmallSig_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[64]; // Should be 65 + var msghash32 = new byte[32]; + NonceFunction noncefp = (Span nonce, ReadOnlySpan msg, ReadOnlySpan key, ReadOnlySpan algo, IntPtr data, uint attempt) => 1; + secp256k1.EcdsaSignRecoverable(sig, msghash32, TestPrivateKey, noncefp, IntPtr.Zero); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignRecoverableWithCallback_TooSmallMsghash_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[65]; + var msghash32 = new byte[31]; // Should be 32 + NonceFunction noncefp = (Span nonce, ReadOnlySpan msg, ReadOnlySpan key, ReadOnlySpan algo, IntPtr data, uint attempt) => 1; + secp256k1.EcdsaSignRecoverable(sig, msghash32, TestPrivateKey, noncefp, IntPtr.Zero); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcdsaSignRecoverableWithCallback_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var sig = new byte[65]; + var msghash32 = new byte[32]; + var seckey = new byte[31]; // Should be 32 + NonceFunction noncefp = (Span nonce, ReadOnlySpan msg, ReadOnlySpan key, ReadOnlySpan algo, IntPtr data, uint attempt) => 1; + secp256k1.EcdsaSignRecoverable(sig, msghash32, seckey, noncefp, IntPtr.Zero); + } + + // EcPubkeySort tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeySort_NullArray_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + secp256k1.EcPubkeySort(null!); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeySort_EmptyArray_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + secp256k1.EcPubkeySort(Array.Empty()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeySort_NullElement_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkeys = new byte[][] { null! }; + secp256k1.EcPubkeySort(pubkeys); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EcPubkeySort_TooSmallElement_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var pubkeys = new byte[][] { new byte[63] }; // Should be 64 + secp256k1.EcPubkeySort(pubkeys); + } + + // EllswiftXdh with callback tests + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhWithCallback_TooSmallOutput_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[31]; // Should be 32 + var ell_a64 = new byte[64]; + var ell_b64 = new byte[64]; + var seckey32 = new byte[32]; + EllswiftXdhHashFunction hashfp = (Span o, ReadOnlySpan x32, ReadOnlySpan ell_a, ReadOnlySpan ell_b, IntPtr data) => 1; + secp256k1.EllswiftXdh(output, ell_a64, ell_b64, seckey32, 0, hashfp, IntPtr.Zero); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhWithCallback_TooSmallEllA64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var ell_a64 = new byte[63]; // Should be 64 + var ell_b64 = new byte[64]; + var seckey32 = new byte[32]; + EllswiftXdhHashFunction hashfp = (Span o, ReadOnlySpan x32, ReadOnlySpan ell_a, ReadOnlySpan ell_b, IntPtr data) => 1; + secp256k1.EllswiftXdh(output, ell_a64, ell_b64, seckey32, 0, hashfp, IntPtr.Zero); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhWithCallback_TooSmallEllB64_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var ell_a64 = new byte[64]; + var ell_b64 = new byte[63]; // Should be 64 + var seckey32 = new byte[32]; + EllswiftXdhHashFunction hashfp = (Span o, ReadOnlySpan x32, ReadOnlySpan ell_a, ReadOnlySpan ell_b, IntPtr data) => 1; + secp256k1.EllswiftXdh(output, ell_a64, ell_b64, seckey32, 0, hashfp, IntPtr.Zero); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void EllswiftXdhWithCallback_TooSmallSeckey_ThrowsArgumentException() + { + using var secp256k1 = new Secp256k1(); + var output = new byte[32]; + var ell_a64 = new byte[64]; + var ell_b64 = new byte[64]; + var seckey32 = new byte[31]; // Should be 32 + EllswiftXdhHashFunction hashfp = (Span o, ReadOnlySpan x32, ReadOnlySpan ell_a, ReadOnlySpan ell_b, IntPtr data) => 1; + secp256k1.EllswiftXdh(output, ell_a64, ell_b64, seckey32, 0, hashfp, IntPtr.Zero); } } @@ -1149,4 +3822,4 @@ public static string ToHexString(ReadOnlySpan bytes) } #endif -} \ No newline at end of file +} diff --git a/Secp256k1.Net.Test/run-coverage.sh b/Secp256k1.Net.Test/run-coverage.sh new file mode 100755 index 0000000..de548c9 --- /dev/null +++ b/Secp256k1.Net.Test/run-coverage.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +FRAMEWORK="${1:-net10.0}" +REPORT_DIR="$PROJECT_DIR/CoverageReport" + +echo "Restoring tools..." +dotnet tool restore + +echo "Rebuilding test project..." +dotnet build "$SCRIPT_DIR" --configuration Release --framework "$FRAMEWORK" --force + +echo "Running tests with coverage..." +dotnet test "$SCRIPT_DIR" --configuration Release --framework "$FRAMEWORK" --no-build \ + -p:CollectCoverage=true \ + -p:CoverletOutputFormat=cobertura \ + -p:CoverletOutput="$REPORT_DIR/coverage" + +COVERAGE_FILE="$REPORT_DIR/coverage.$FRAMEWORK.cobertura.xml" + +echo "Generating HTML report..." +dotnet tool run reportgenerator \ + -reports:"$COVERAGE_FILE" \ + -targetdir:"$REPORT_DIR" \ + -reporttypes:Html + +echo "Coverage report generated at: $REPORT_DIR/index.html" + +# Open the report if on macOS +if [[ "$OSTYPE" == "darwin"* ]]; then + open "$REPORT_DIR/index.html" +elif [[ "$OSTYPE" == "linux-gnu"* ]] && command -v xdg-open &> /dev/null; then + xdg-open "$REPORT_DIR/index.html" +fi diff --git a/Secp256k1.Net.sln b/Secp256k1.Net.sln index 35e8405..f155e50 100644 --- a/Secp256k1.Net.sln +++ b/Secp256k1.Net.sln @@ -14,24 +14,66 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Secp256k1.Net.Test", "Secp2 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Secp256k1.Net.Bench", "Secp256k1.Net.Bench\Secp256k1.Net.Bench.csproj", "{CB05F5FC-E487-43ED-8D1D-282400591A73}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Secp256k1.Net.Examples", "Secp256k1.Net.Examples\Secp256k1.Net.Examples.csproj", "{AB358750-7E28-4C09-A269-6F080B5A198B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Debug|x64.ActiveCfg = Debug|Any CPU + {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Debug|x64.Build.0 = Debug|Any CPU + {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Debug|x86.ActiveCfg = Debug|Any CPU + {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Debug|x86.Build.0 = Debug|Any CPU {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Release|Any CPU.ActiveCfg = Release|Any CPU {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Release|Any CPU.Build.0 = Release|Any CPU + {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Release|x64.ActiveCfg = Release|Any CPU + {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Release|x64.Build.0 = Release|Any CPU + {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Release|x86.ActiveCfg = Release|Any CPU + {D788BD2A-96F4-4881-9E6D-4724E12992BD}.Release|x86.Build.0 = Release|Any CPU {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Debug|x64.ActiveCfg = Debug|Any CPU + {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Debug|x64.Build.0 = Debug|Any CPU + {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Debug|x86.ActiveCfg = Debug|Any CPU + {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Debug|x86.Build.0 = Debug|Any CPU {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Release|Any CPU.ActiveCfg = Release|Any CPU {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Release|Any CPU.Build.0 = Release|Any CPU + {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Release|x64.ActiveCfg = Release|Any CPU + {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Release|x64.Build.0 = Release|Any CPU + {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Release|x86.ActiveCfg = Release|Any CPU + {ABDA21A2-7F81-415E-8252-B0F35D98FC45}.Release|x86.Build.0 = Release|Any CPU {CB05F5FC-E487-43ED-8D1D-282400591A73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CB05F5FC-E487-43ED-8D1D-282400591A73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB05F5FC-E487-43ED-8D1D-282400591A73}.Debug|x64.ActiveCfg = Debug|Any CPU + {CB05F5FC-E487-43ED-8D1D-282400591A73}.Debug|x64.Build.0 = Debug|Any CPU + {CB05F5FC-E487-43ED-8D1D-282400591A73}.Debug|x86.ActiveCfg = Debug|Any CPU + {CB05F5FC-E487-43ED-8D1D-282400591A73}.Debug|x86.Build.0 = Debug|Any CPU {CB05F5FC-E487-43ED-8D1D-282400591A73}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB05F5FC-E487-43ED-8D1D-282400591A73}.Release|Any CPU.Build.0 = Release|Any CPU + {CB05F5FC-E487-43ED-8D1D-282400591A73}.Release|x64.ActiveCfg = Release|Any CPU + {CB05F5FC-E487-43ED-8D1D-282400591A73}.Release|x64.Build.0 = Release|Any CPU + {CB05F5FC-E487-43ED-8D1D-282400591A73}.Release|x86.ActiveCfg = Release|Any CPU + {CB05F5FC-E487-43ED-8D1D-282400591A73}.Release|x86.Build.0 = Release|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Debug|x64.ActiveCfg = Debug|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Debug|x64.Build.0 = Debug|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Debug|x86.ActiveCfg = Debug|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Debug|x86.Build.0 = Debug|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Release|Any CPU.Build.0 = Release|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Release|x64.ActiveCfg = Release|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Release|x64.Build.0 = Release|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Release|x86.ActiveCfg = Release|Any CPU + {AB358750-7E28-4C09-A269-6F080B5A198B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Secp256k1.Net/DynamicLinking/DynamicLinkingLinux.cs b/Secp256k1.Net/DynamicLinking/DynamicLinkingLinux.cs index 4b2a0e9..e3cffd6 100644 --- a/Secp256k1.Net/DynamicLinking/DynamicLinkingLinux.cs +++ b/Secp256k1.Net/DynamicLinking/DynamicLinkingLinux.cs @@ -5,22 +5,126 @@ namespace Secp256k1Net.DynamicLinking { static class DynamicLinkingLinux { - // Linux distros often do not link 'libdl.so' to 'libdl.so.2' by default. - // This results in "System.DllNotFoundException: Unable to load shared library 'libdl'.." - // when not using the shared lib version naming convention. - // Run "ldconfig -p | grep libdl" on a fresh Ubuntu Server to see only "libdl.so.2" - const string LIBDL = "libdl.so.2"; + public const int RTLD_NOW = 2; - [DllImport(LIBDL)] - public static extern IntPtr dlopen(string path, int flags); + // libdl (works on most .NET Core Linux systems) + [DllImport("libdl", EntryPoint = "dlopen")] + private static extern IntPtr dlopen_libdl(string path, int flags); + [DllImport("libdl", EntryPoint = "dlclose")] + private static extern int dlclose_libdl(IntPtr handle); + [DllImport("libdl", EntryPoint = "dlerror")] + private static extern IntPtr dlerror_libdl(); + [DllImport("libdl", EntryPoint = "dlsym")] + private static extern IntPtr dlsym_libdl(IntPtr handle, string name); - [DllImport(LIBDL)] - public static extern int dlclose(IntPtr handle); + // libdl.so.2 (required for Mono on some glibc systems) + [DllImport("libdl.so.2", EntryPoint = "dlopen")] + private static extern IntPtr dlopen_libdl2(string path, int flags); + [DllImport("libdl.so.2", EntryPoint = "dlclose")] + private static extern int dlclose_libdl2(IntPtr handle); + [DllImport("libdl.so.2", EntryPoint = "dlerror")] + private static extern IntPtr dlerror_libdl2(); + [DllImport("libdl.so.2", EntryPoint = "dlsym")] + private static extern IntPtr dlsym_libdl2(IntPtr handle, string name); - [DllImport(LIBDL)] - public static extern IntPtr dlerror(); + // libc.so.6 (fallback for glibc systems where dlopen moved to libc) + [DllImport("libc.so.6", EntryPoint = "dlopen")] + private static extern IntPtr dlopen_libc6(string path, int flags); + [DllImport("libc.so.6", EntryPoint = "dlclose")] + private static extern int dlclose_libc6(IntPtr handle); + [DllImport("libc.so.6", EntryPoint = "dlerror")] + private static extern IntPtr dlerror_libc6(); + [DllImport("libc.so.6", EntryPoint = "dlsym")] + private static extern IntPtr dlsym_libc6(IntPtr handle, string name); - [DllImport(LIBDL)] - public static extern IntPtr dlsym(IntPtr handle, string name); + // libc (musl/Alpine systems) + [DllImport("libc", EntryPoint = "dlopen")] + private static extern IntPtr dlopen_libc(string path, int flags); + [DllImport("libc", EntryPoint = "dlclose")] + private static extern int dlclose_libc(IntPtr handle); + [DllImport("libc", EntryPoint = "dlerror")] + private static extern IntPtr dlerror_libc(); + [DllImport("libc", EntryPoint = "dlsym")] + private static extern IntPtr dlsym_libc(IntPtr handle, string name); + + private enum DlLibrary { Libdl, Libdl2, Libc6, Libc } + private static readonly DlLibrary ActiveLibrary = ProbeLibrary(); + + private static DlLibrary ProbeLibrary() + { + // Try libdl (most .NET Core systems) + try + { + dlopen_libdl(null, RTLD_NOW); + return DlLibrary.Libdl; + } + catch (DllNotFoundException) { } + catch (EntryPointNotFoundException) { } + + // Try libdl.so.2 (Mono on glibc) + try + { + dlopen_libdl2(null, RTLD_NOW); + return DlLibrary.Libdl2; + } + catch (DllNotFoundException) { } + catch (EntryPointNotFoundException) { } + + // Try libc.so.6 (newer glibc where dlopen moved to libc) + try + { + dlopen_libc6(null, RTLD_NOW); + return DlLibrary.Libc6; + } + catch (DllNotFoundException) { } + catch (EntryPointNotFoundException) { } + + // Fall back to libc (musl/Alpine) + return DlLibrary.Libc; + } + + public static IntPtr dlopen(string path, int flags) + { + switch (ActiveLibrary) + { + case DlLibrary.Libdl: return dlopen_libdl(path, flags); + case DlLibrary.Libdl2: return dlopen_libdl2(path, flags); + case DlLibrary.Libc6: return dlopen_libc6(path, flags); + default: return dlopen_libc(path, flags); + } + } + + public static int dlclose(IntPtr handle) + { + switch (ActiveLibrary) + { + case DlLibrary.Libdl: return dlclose_libdl(handle); + case DlLibrary.Libdl2: return dlclose_libdl2(handle); + case DlLibrary.Libc6: return dlclose_libc6(handle); + default: return dlclose_libc(handle); + } + } + + public static IntPtr dlerror() + { + switch (ActiveLibrary) + { + case DlLibrary.Libdl: return dlerror_libdl(); + case DlLibrary.Libdl2: return dlerror_libdl2(); + case DlLibrary.Libc6: return dlerror_libc6(); + default: return dlerror_libc(); + } + } + + public static IntPtr dlsym(IntPtr handle, string name) + { + switch (ActiveLibrary) + { + case DlLibrary.Libdl: return dlsym_libdl(handle, name); + case DlLibrary.Libdl2: return dlsym_libdl2(handle, name); + case DlLibrary.Libc6: return dlsym_libc6(handle, name); + default: return dlsym_libc(handle, name); + } + } } } diff --git a/Secp256k1.Net/DynamicLinking/DynamicLinkingMacOS.cs b/Secp256k1.Net/DynamicLinking/DynamicLinkingMacOS.cs index 221d261..21239a7 100644 --- a/Secp256k1.Net/DynamicLinking/DynamicLinkingMacOS.cs +++ b/Secp256k1.Net/DynamicLinking/DynamicLinkingMacOS.cs @@ -5,6 +5,8 @@ namespace Secp256k1Net.DynamicLinking { static class DynamicLinkingMacOS { + public const int RTLD_NOW = 2; + const string LIBDL = "libdl"; [DllImport(LIBDL)] diff --git a/Secp256k1.Net/Generated/Secp256k1.Native.g.cs b/Secp256k1.Net/Generated/Secp256k1.Native.g.cs new file mode 100644 index 0000000..fdb6ad8 --- /dev/null +++ b/Secp256k1.Net/Generated/Secp256k1.Native.g.cs @@ -0,0 +1,1092 @@ +// +#nullable enable + +using System; +using System.Runtime.InteropServices; + +#if NET8_0_OR_GREATER +using unsafe FnPtr00 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr01 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr02 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr03 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr04 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr05 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr06 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr07 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr08 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr09 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr10 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr11 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr12 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr13 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr14 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr15 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr16 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr17 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr18 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr19 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr20 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr21 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr22 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr23 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr24 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr25 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr26 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr27 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr28 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr29 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr30 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr31 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr32 = delegate* unmanaged[Cdecl]; +using unsafe FnPtr33 = delegate* unmanaged[Cdecl]; +#endif + +namespace Secp256k1Net +{ + + /// A pointer to a function to deterministically generate a nonce. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int secp256k1_nonce_function(void* nonce32, void* msg32, void* key32, void* algo16, void* data, uint attempt); + + /// A pointer to a function that hashes an EC point to obtain an ECDH secret + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int secp256k1_ecdh_hash_function(void* output, void* x32, void* y32, void* data); + + /// A pointer to a function to deterministically generate a nonce.Same as secp256k1_nonce function with the exception of accepting an additional pubkey argument and not requiring an attempt argument. The pubkey argument can protect signature schemes with key-prefixed challenge hash inputs against reusing the nonce when signing with the wrong precomputed pubkey. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int secp256k1_nonce_function_hardened(void* nonce32, void* msg, nuint msglen, void* key32, void* xonly_pk32, void* algo, nuint algolen, void* data); + + /// A pointer to a function used by secp256k1_ellswift_xdh to hash the shared X coordinate along with the encoded public keys to a uniform shared secret. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int secp256k1_ellswift_xdh_hash_function(void* output, void* x32, void* ell_a64, void* ell_b64, void* data); +#if !NET8_0_OR_GREATER + + /// Perform basic self tests (to be used in conjunction with secp256k1_context_static)This function performs self tests that detect some serious usage errors and similar conditions, e.g., when the library is compiled for the wrong endianness. This is a last resort measure to be used in production. The performed tests are very rudimentary and are not intended as a replacement for running the test binaries.It is highly recommended to call this before using secp256k1_context_static. It is not necessary to call this function before using a context created with secp256k1_context_create (or secp256k1_context_preallocated_create), which will take care of performing the self tests.If the tests fail, this function will call the default error callback to abort the program (see secp256k1_context_set_error_callback). + internal delegate void secp256k1_selftest(); + + /// Create a secp256k1 context object (in dynamically allocated memory).This function uses malloc to allocate memory. It is guaranteed that malloc is called at most once for every call of this function. If you need to avoid dynamic memory allocation entirely, see secp256k1_context_static and the functions in secp256k1_preallocated.h. + /// Always set to SECP256K1_CONTEXT_NONE (see below).The only valid non-deprecated flag in recent library versions is SECP256K1_CONTEXT_NONE, which will create a context sufficient for all functionality offered by the library. All other (deprecated) flags will be treated as equivalent to the SECP256K1_CONTEXT_NONE flag. Though the flags parameter primarily exists for historical reasons, future versions of the library may introduce new flags.If the context is intended to be used for API functions that perform computations involving secret keys, e.g., signing and public key generation, then it is highly recommended to call secp256k1_context_randomize on the context before calling those API functions. This will provide enhanced protection against side-channel leakage, see secp256k1_context_randomize for details.Do not create a new context object for each operation, as construction and randomization can take non-negligible time. + /// pointer to a newly created context object. + internal delegate IntPtr secp256k1_context_create(uint flags); + + /// Copy a secp256k1 context object (into dynamically allocated memory).This function uses malloc to allocate memory. It is guaranteed that malloc is called at most once for every call of this function. If you need to avoid dynamic memory allocation entirely, see the functions in secp256k1_preallocated.h.Cloning secp256k1_context_static is not possible, and should not be emulated by the caller (e.g., using memcpy). Create a new context instead. + /// pointer to a context to copy (not secp256k1_context_static). + /// pointer to a newly created context object. + internal unsafe delegate IntPtr secp256k1_context_clone(IntPtr ctx); + + /// Destroy a secp256k1 context object (created in dynamically allocated memory).The context pointer may not be used afterwards.The context to destroy must have been created using secp256k1_context_create or secp256k1_context_clone. If the context has instead been created using secp256k1_context_preallocated_create or secp256k1_context_preallocated_clone, the behaviour is undefined. In that case, secp256k1_context_preallocated_destroy must be used instead. + /// pointer to a context to destroy, constructed using secp256k1_context_create or secp256k1_context_clone (i.e., not secp256k1_context_static). + internal unsafe delegate void secp256k1_context_destroy(IntPtr ctx); + + /// Set a callback function to be called when an illegal argument is passed to an API call. It will only trigger for violations that are mentioned explicitly in the header.The philosophy is that these shouldn't be dealt with through a specific return value, as calling code should not have branches to deal with the case that this code itself is broken.On the other hand, during debug stage, one would want to be informed about such mistakes, and the default (crashing) may be inadvisable. Should this callback return instead of crashing, the return value and output arguments of the API function call are undefined. Moreover, the same API call may trigger the callback again in this case.When this function has not been called (or called with fun==NULL), then the default callback will be used. The library provides a default callback which writes the message to stderr and calls abort. This default callback can be replaced at link time if the preprocessor macro USE_EXTERNAL_DEFAULT_CALLBACKS is defined, which is the case if the build has been configured with --enable-external-default-callbacks (GNU Autotools) or -DSECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS=ON (CMake). Then the following two symbols must be provided to link against: - void secp256k1_default_illegal_callback_fn(const char *message, void *data); - void secp256k1_default_error_callback_fn(const char *message, void *data); The library may call a default callback even before a proper callback data pointer could have been set using secp256k1_context_set_illegal_callback or secp256k1_context_set_error_callback, e.g., when the creation of a context fails. In this case, the corresponding default callback will be called with the data pointer argument set to NULL. + /// pointer to a context object. + /// pointer to a function to call when an illegal argument is passed to the API, taking a message and an opaque pointer. (NULL restores the default callback.) + /// the opaque pointer to pass to fun above, must be NULL for the default callback.See also secp256k1_context_set_error_callback. + internal unsafe delegate void secp256k1_context_set_illegal_callback(IntPtr ctx, IntPtr fun, void* data); + + /// Set a callback function to be called when an internal consistency check fails.The default callback writes an error message to stderr and calls abort to abort the program.This can only trigger in case of a hardware failure, miscompilation, memory corruption, serious bug in the library, or other error that would result in undefined behaviour. It will not trigger due to mere incorrect usage of the API (see secp256k1_context_set_illegal_callback for that). After this callback returns, anything may happen, including crashing. + /// pointer to a context object. + /// pointer to a function to call when an internal error occurs, taking a message and an opaque pointer (NULL restores the default callback, see secp256k1_context_set_illegal_callback for details). + /// the opaque pointer to pass to fun above, must be NULL for the default callback.See also secp256k1_context_set_illegal_callback. + internal unsafe delegate void secp256k1_context_set_error_callback(IntPtr ctx, IntPtr fun, void* data); + + /// Parse a variable-length public key into the pubkey object. + /// pointer to a context object. + /// pointer to a pubkey object. If 1 is returned, it is set to a parsed version of input. If not, its value is undefined. + /// pointer to a serialized public key + /// length of the array pointed to by inputThis function supports parsing compressed (33 bytes, header byte 0x02 or 0x03), uncompressed (65 bytes, header byte 0x04), or hybrid (65 bytes, header byte 0x06 or 0x07) format public keys. + /// 1 if the public key was fully valid. 0 if the public key could not be parsed or is invalid. + internal unsafe delegate int secp256k1_ec_pubkey_parse(IntPtr ctx, void* pubkey, void* input, nuint inputlen); + + /// Serialize a pubkey object into a serialized byte sequence. + /// pointer to a context object. + /// pointer to a 65-byte (if compressed==0) or 33-byte (if compressed==1) byte array to place the serialized key in. + /// pointer to an integer which is initially set to the size of output, and is overwritten with the written size. + /// pointer to a secp256k1_pubkey containing an initialized public key. + /// SECP256K1_EC_COMPRESSED if serialization should be in compressed format, otherwise SECP256K1_EC_UNCOMPRESSED. + /// 1 always. + internal unsafe delegate int secp256k1_ec_pubkey_serialize(IntPtr ctx, void* output, nuint* outputlen, void* pubkey, uint flags); + + /// Compare two public keys using lexicographic (of compressed serialization) order + /// pointer to a context object + /// first public key to compare + /// second public key to compare + /// <0 if the first public key is less than the second >0 if the first public key is greater than the second 0 if the two public keys are equal + internal unsafe delegate int secp256k1_ec_pubkey_cmp(IntPtr ctx, void* pubkey1, void* pubkey2); + + /// Sort public keys using lexicographic (of compressed serialization) order + /// pointer to a context object + /// array of pointers to pubkeys to sort + /// number of elements in the pubkeys array + /// 0 if the arguments are invalid. 1 otherwise. + internal unsafe delegate int secp256k1_ec_pubkey_sort(IntPtr ctx, IntPtr pubkeys, nuint n_pubkeys); + + /// Parse an ECDSA signature in compact (64 bytes) format. + /// pointer to a context object + /// pointer to a signature object + /// pointer to the 64-byte array to parseThe signature must consist of a 32-byte big endian R value, followed by a 32-byte big endian S value. If R or S fall outside of [0..order-1], the encoding is invalid. R and S with value 0 are allowed in the encoding.After the call, sig will always be initialized. If parsing failed or R or S are zero, the resulting sig value is guaranteed to fail verification for any message and public key. + /// 1 when the signature could be parsed, 0 otherwise. + internal unsafe delegate int secp256k1_ecdsa_signature_parse_compact(IntPtr ctx, void* sig, void* input64); + + /// Parse a DER ECDSA signature. + /// pointer to a context object + /// pointer to a signature object + /// pointer to the signature to be parsed + /// the length of the array pointed to be inputThis function will accept any valid DER encoded signature, even if the encoded numbers are out of range.After the call, sig will always be initialized. If parsing failed or the encoded numbers are out of range, signature verification with it is guaranteed to fail for every message and public key. + /// 1 when the signature could be parsed, 0 otherwise. + internal unsafe delegate int secp256k1_ecdsa_signature_parse_der(IntPtr ctx, void* sig, void* input, nuint inputlen); + + /// Serialize an ECDSA signature in DER format. + /// pointer to a context object + /// pointer to an array to store the DER serialization + /// pointer to a length integer. Initially, this integer should be set to the length of output. After the call it will be set to the length of the serialization (even if 0 was returned). + /// pointer to an initialized signature object + /// 1 if enough space was available to serialize, 0 otherwise + internal unsafe delegate int secp256k1_ecdsa_signature_serialize_der(IntPtr ctx, void* output, nuint* outputlen, void* sig); + + /// Serialize an ECDSA signature in compact (64 byte) format. + /// pointer to a context object + /// pointer to a 64-byte array to store the compact serialization + /// pointer to an initialized signature objectSee secp256k1_ecdsa_signature_parse_compact for details about the encoding. + /// 1 + internal unsafe delegate int secp256k1_ecdsa_signature_serialize_compact(IntPtr ctx, void* output64, void* sig); + + /// Verify an ECDSA signature. + /// pointer to a context object + /// the signature being verified. + /// the 32-byte message hash being verified. The verifier must make sure to apply a cryptographic hash function to the message by itself and not accept an msghash32 value directly. Otherwise, it would be easy to create a "valid" signature without knowledge of the secret key. See also https://bitcoin.stackexchange.com/a/81116/35586 for more background on this topic. + /// pointer to an initialized public key to verify with.To avoid accepting malleable signatures, only ECDSA signatures in lower-S form are accepted.If you need to accept ECDSA signatures from sources that do not obey this rule, apply secp256k1_ecdsa_signature_normalize to the signature prior to verification, but be aware that doing so results in malleable signatures.For details, see the comments for that function. + /// 1: correct signature 0: incorrect or unparseable signature + internal unsafe delegate int secp256k1_ecdsa_verify(IntPtr ctx, void* sig, void* msghash32, void* pubkey); + + /// Convert a signature to a normalized lower-S form. + /// pointer to a context object + /// pointer to a signature to fill with the normalized form, or copy if the input was already normalized. (can be NULL if you're only interested in whether the input was already normalized). + /// pointer to a signature to check/normalize (can be identical to sigout)With ECDSA a third-party can forge a second distinct signature of the same message, given a single initial signature, but without knowing the key. This is done by negating the S value modulo the order of the curve, 'flipping' the sign of the random point R which is not included in the signature.Forgery of the same message isn't universally problematic, but in systems where message malleability or uniqueness of signatures is important this can cause issues. This forgery can be blocked by all verifiers forcing signers to use a normalized form.The lower-S form reduces the size of signatures slightly on average when variable length encodings (such as DER) are used and is cheap to verify, making it a good choice. Security of always using lower-S is assured because anyone can trivially modify a signature after the fact to enforce this property anyway.The lower S value is always between 0x1 and 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, inclusive.No other forms of ECDSA malleability are known and none seem likely, but there is no formal proof that ECDSA, even with this additional restriction, is free of other malleability. Commonly used serialization schemes will also accept various non-unique encodings, so care should be taken when this property is required for an application.The secp256k1_ecdsa_sign function will by default create signatures in the lower-S form, and secp256k1_ecdsa_verify will not accept others. In case signatures come from a system that cannot enforce this property, secp256k1_ecdsa_signature_normalize must be called before verification. + /// 1 if sigin was not normalized, 0 if it already was. + internal unsafe delegate int secp256k1_ecdsa_signature_normalize(IntPtr ctx, void* sigout, void* sigin); + + /// Create an ECDSA signature. + /// pointer to a context object (not secp256k1_context_static). + /// pointer to an array where the signature will be placed. + /// the 32-byte message hash being signed. + /// pointer to a 32-byte secret key. + /// pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used. + /// pointer to arbitrary data used by the nonce generation function (can be NULL). If it is non-NULL and secp256k1_nonce_function_default is used, then ndata must be a pointer to 32-bytes of additional data.The created signature is always in lower-S form. See secp256k1_ecdsa_signature_normalize for more details. + /// 1: signature created 0: the nonce generation function failed, or the secret key was invalid. + internal unsafe delegate int secp256k1_ecdsa_sign(IntPtr ctx, void* sig, void* msghash32, void* seckey, IntPtr noncefp, void* ndata); + + /// Verify an elliptic curve secret key.A secret key is valid if it is not 0 and less than the secp256k1 curve order when interpreted as an integer (most significant byte first). The probability of choosing a 32-byte string uniformly at random which is an invalid secret key is negligible. However, if it does happen it should be assumed that the randomness source is severely broken and there should be no retry. + /// pointer to a context object. + /// pointer to a 32-byte secret key. + /// 1: secret key is valid 0: secret key is invalid + internal unsafe delegate int secp256k1_ec_seckey_verify(IntPtr ctx, void* seckey); + + /// Compute the public key for a secret key. + /// pointer to a context object (not secp256k1_context_static). + /// pointer to the created public key. + /// pointer to a 32-byte secret key. + /// 1: secret was valid, public key stores. 0: secret was invalid, try again. + internal unsafe delegate int secp256k1_ec_pubkey_create(IntPtr ctx, void* pubkey, void* seckey); + + /// Negates a secret key in place. + /// pointer to a context object + /// pointer to the 32-byte secret key to be negated. If the secret key is invalid according to secp256k1_ec_seckey_verify, this function returns 0 and seckey will be set to some unspecified value. + /// 0 if the given secret key is invalid according to secp256k1_ec_seckey_verify. 1 otherwise + internal unsafe delegate int secp256k1_ec_seckey_negate(IntPtr ctx, void* seckey); + + /// Negates a public key in place. + /// pointer to a context object + /// pointer to the public key to be negated. + /// 1 always + internal unsafe delegate int secp256k1_ec_pubkey_negate(IntPtr ctx, void* pubkey); + + /// Tweak a secret key by adding tweak to it. + /// pointer to a context object. + /// pointer to a 32-byte secret key. If the secret key is invalid according to secp256k1_ec_seckey_verify, this function returns 0. seckey will be set to some unspecified value if this function returns 0. + /// pointer to a 32-byte tweak, which must be valid according to secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly random 32-byte tweaks, the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid or the resulting secret key would be invalid (only when the tweak is the negation of the secret key). 1 otherwise. + internal unsafe delegate int secp256k1_ec_seckey_tweak_add(IntPtr ctx, void* seckey, void* tweak32); + + /// Tweak a public key by adding tweak times the generator to it. + /// pointer to a context object. + /// pointer to a public key object. pubkey will be set to an invalid value if this function returns 0. + /// pointer to a 32-byte tweak, which must be valid according to secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly random 32-byte tweaks, the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid or the resulting public key would be invalid (only when the tweak is the negation of the corresponding secret key). 1 otherwise. + internal unsafe delegate int secp256k1_ec_pubkey_tweak_add(IntPtr ctx, void* pubkey, void* tweak32); + + /// Tweak a secret key by multiplying it by a tweak. + /// pointer to a context object. + /// pointer to a 32-byte secret key. If the secret key is invalid according to secp256k1_ec_seckey_verify, this function returns 0. seckey will be set to some unspecified value if this function returns 0. + /// pointer to a 32-byte tweak. If the tweak is invalid according to secp256k1_ec_seckey_verify, this function returns 0. For uniformly random 32-byte arrays the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid. 1 otherwise. + internal unsafe delegate int secp256k1_ec_seckey_tweak_mul(IntPtr ctx, void* seckey, void* tweak32); + + /// Tweak a public key by multiplying it by a tweak value. + /// pointer to a context object. + /// pointer to a public key object. pubkey will be set to an invalid value if this function returns 0. + /// pointer to a 32-byte tweak. If the tweak is invalid according to secp256k1_ec_seckey_verify, this function returns 0. For uniformly random 32-byte arrays the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid. 1 otherwise. + internal unsafe delegate int secp256k1_ec_pubkey_tweak_mul(IntPtr ctx, void* pubkey, void* tweak32); + + /// Randomizes the context to provide enhanced protection against side-channel leakage. + /// pointer to a context object (not secp256k1_context_static). + /// pointer to a 32-byte random seed (NULL resets to initial state).While secp256k1 code is written and tested to be constant-time no matter what secret values are, it is possible that a compiler may output code which is not, and also that the CPU may not emit the same radio frequencies or draw the same amount of power for all values. Randomization of the context shields against side-channel observations which aim to exploit secret-dependent behaviour in certain computations which involve secret keys.It is highly recommended to call this function on contexts returned from secp256k1_context_create or secp256k1_context_clone (or from the corresponding functions in secp256k1_preallocated.h) before using these contexts to call API functions that perform computations involving secret keys, e.g., signing and public key generation. It is possible to call this function more than once on the same context, and doing so before every few computations involving secret keys is recommended as a defense-in-depth measure. Randomization of the static context secp256k1_context_static is not supported.Currently, the random seed is mainly used for blinding multiplications of a secret scalar with the elliptic curve base point. Multiplications of this kind are performed by exactly those API functions which are documented to require a context that is not secp256k1_context_static. As a rule of thumb, these are all functions which take a secret key (or a keypair) as an input. A notable exception to that rule is the ECDH module, which relies on a different kind of elliptic curve point multiplication and thus does not benefit from enhanced protection against side-channel leakage currently. + /// 1: randomization successful 0: error + internal unsafe delegate int secp256k1_context_randomize(IntPtr ctx, void* seed32); + + /// Add a number of public keys together. + /// pointer to a context object. + /// pointer to a public key object for placing the resulting public key. + /// pointer to array of pointers to public keys. + /// the number of public keys to add together (must be at least 1). + /// 1: the sum of the public keys is valid. 0: the sum of the public keys is not valid. + internal unsafe delegate int secp256k1_ec_pubkey_combine(IntPtr ctx, void* @out, IntPtr ins, nuint n); + + /// Compute a tagged hash as defined in BIP-340.This is useful for creating a message hash and achieving domain separation through an application-specific tag. This function returns SHA256(SHA256(tag)||SHA256(tag)||msg). Therefore, tagged hash implementations optimized for a specific tag can precompute the SHA256 state after hashing the tag hashes. + /// pointer to a context object + /// pointer to a 32-byte array to store the resulting hash + /// pointer to an array containing the tag + /// length of the tag array + /// pointer to an array containing the message + /// length of the message array + /// 1 always. + internal unsafe delegate int secp256k1_tagged_sha256(IntPtr ctx, void* hash32, void* tag, nuint taglen, void* msg, nuint msglen); + + /// Determine the memory size of a secp256k1 context object to be created in caller-provided memory.The purpose of this function is to determine how much memory must be provided to secp256k1_context_preallocated_create. + /// which parts of the context to initialize. + /// the required size of the caller-provided memory block + internal delegate nuint secp256k1_context_preallocated_size(uint flags); + + /// Create a secp256k1 context object in caller-provided memory.The caller must provide a pointer to a rewritable contiguous block of memory of size at least secp256k1_context_preallocated_size(flags) bytes, suitably aligned to hold an object of any type.The block of memory is exclusively owned by the created context object during the lifetime of this context object, which begins with the call to this function and ends when a call to secp256k1_context_preallocated_destroy (which destroys the context object again) returns. During the lifetime of the context object, the caller is obligated not to access this block of memory, i.e., the caller may not read or write the memory, e.g., by copying the memory contents to a different location or trying to create a second context object in the memory. In simpler words, the prealloc pointer (or any pointer derived from it) should not be used during the lifetime of the context object. + /// pointer to a rewritable contiguous block of memory of size at least secp256k1_context_preallocated_size(flags) bytes, as detailed above. + /// which parts of the context to initialize.See secp256k1_context_create (in secp256k1.h) for further details.See also secp256k1_context_randomize (in secp256k1.h) and secp256k1_context_preallocated_destroy. + /// pointer to newly created context object. + internal unsafe delegate IntPtr secp256k1_context_preallocated_create(void* prealloc, uint flags); + + /// Determine the memory size of a secp256k1 context object to be copied into caller-provided memory. + /// pointer to a context to copy. + /// the required size of the caller-provided memory block. + internal unsafe delegate nuint secp256k1_context_preallocated_clone_size(IntPtr ctx); + + /// Copy a secp256k1 context object into caller-provided memory.The caller must provide a pointer to a rewritable contiguous block of memory of size at least secp256k1_context_preallocated_size(flags) bytes, suitably aligned to hold an object of any type.The block of memory is exclusively owned by the created context object during the lifetime of this context object, see the description of secp256k1_context_preallocated_create for details.Cloning secp256k1_context_static is not possible, and should not be emulated by the caller (e.g., using memcpy). Create a new context instead. + /// pointer to a context to copy (not secp256k1_context_static). + /// pointer to a rewritable contiguous block of memory of size at least secp256k1_context_preallocated_size(flags) bytes, as detailed above. + /// pointer to a newly created context object. + internal unsafe delegate IntPtr secp256k1_context_preallocated_clone(IntPtr ctx, void* prealloc); + + /// Destroy a secp256k1 context object that has been created in caller-provided memory.The context pointer may not be used afterwards.The context to destroy must have been created using secp256k1_context_preallocated_create or secp256k1_context_preallocated_clone. If the context has instead been created using secp256k1_context_create or secp256k1_context_clone, the behaviour is undefined. In that case, secp256k1_context_destroy must be used instead.If required, it is the responsibility of the caller to deallocate the block of memory properly after this function returns, e.g., by calling free on the preallocated pointer given to secp256k1_context_preallocated_create or secp256k1_context_preallocated_clone. + /// pointer to a context to destroy, constructed using secp256k1_context_preallocated_create or secp256k1_context_preallocated_clone (i.e., not secp256k1_context_static). + internal unsafe delegate void secp256k1_context_preallocated_destroy(IntPtr ctx); + + /// Parse a compact ECDSA signature (64 bytes + recovery id). + /// pointer to a context object + /// pointer to a signature object + /// pointer to a 64-byte compact signature + /// the recovery id (0, 1, 2 or 3) + /// 1 when the signature could be parsed, 0 otherwise + internal unsafe delegate int secp256k1_ecdsa_recoverable_signature_parse_compact(IntPtr ctx, void* sig, void* input64, int recid); + + /// Convert a recoverable signature into a normal signature. + /// pointer to a context object. + /// pointer to a normal signature. + /// pointer to a recoverable signature. + /// 1 + internal unsafe delegate int secp256k1_ecdsa_recoverable_signature_convert(IntPtr ctx, void* sig, void* sigin); + + /// Serialize an ECDSA signature in compact format (64 bytes + recovery id). + /// pointer to a context object. + /// pointer to a 64-byte array of the compact signature. + /// pointer to an integer to hold the recovery id. + /// pointer to an initialized signature object. + /// 1 + internal unsafe delegate int secp256k1_ecdsa_recoverable_signature_serialize_compact(IntPtr ctx, void* output64, int* recid, void* sig); + + /// Create a recoverable ECDSA signature. + /// pointer to a context object (not secp256k1_context_static). + /// pointer to an array where the signature will be placed. + /// the 32-byte message hash being signed. + /// pointer to a 32-byte secret key. + /// pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used. + /// pointer to arbitrary data used by the nonce generation function (can be NULL for secp256k1_nonce_function_default). + /// 1: signature created 0: the nonce generation function failed, or the secret key was invalid. + internal unsafe delegate int secp256k1_ecdsa_sign_recoverable(IntPtr ctx, void* sig, void* msghash32, void* seckey, IntPtr noncefp, void* ndata); + + /// Recover an ECDSA public key from a signature.Successful public key recovery guarantees that the signature, after normalization, passes `secp256k1_ecdsa_verify`. Thus, explicit verification is not necessary.However, a recoverable signature that successfully passes `secp256k1_ecdsa_recover`, when converted to a non-recoverable signature (using `secp256k1_ecdsa_recoverable_signature_convert`), is not guaranteed to be normalized and thus not guaranteed to pass `secp256k1_ecdsa_verify`. If a normalized signature is required, call `secp256k1_ecdsa_signature_normalize` after `secp256k1_ecdsa_recoverable_signature_convert`. + /// pointer to a context object. + /// pointer to the recovered public key. + /// pointer to initialized signature that supports pubkey recovery. + /// the 32-byte message hash assumed to be signed. + /// 1: public key successfully recovered 0: otherwise. + internal unsafe delegate int secp256k1_ecdsa_recover(IntPtr ctx, void* pubkey, void* sig, void* msghash32); + + /// Compute an EC Diffie-Hellman secret in constant time + /// pointer to a context object. + /// pointer to an array to be filled by hashfp. + /// pointer to a secp256k1_pubkey containing an initialized public key. + /// a 32-byte scalar with which to multiply the point. + /// pointer to a hash function. If NULL, secp256k1_ecdh_hash_function_sha256 is used (in which case, 32 bytes will be written to output). + /// arbitrary data pointer that is passed through to hashfp (can be NULL for secp256k1_ecdh_hash_function_sha256). + /// 1: exponentiation was successful 0: scalar was invalid (zero or overflow) or hashfp returned 0 + internal unsafe delegate int secp256k1_ecdh(IntPtr ctx, void* output, void* pubkey, void* seckey, IntPtr hashfp, void* data); + + /// Parse a 32-byte sequence into a xonly_pubkey object. + /// pointer to a context object. + /// pointer to a pubkey object. If 1 is returned, it is set to a parsed version of input. If not, it's set to an invalid value. + /// pointer to a serialized xonly_pubkey. + /// 1 if the public key was fully valid. 0 if the public key could not be parsed or is invalid. + internal unsafe delegate int secp256k1_xonly_pubkey_parse(IntPtr ctx, void* pubkey, void* input32); + + /// Serialize an xonly_pubkey object into a 32-byte sequence. + /// pointer to a context object. + /// pointer to a 32-byte array to place the serialized key in. + /// pointer to a secp256k1_xonly_pubkey containing an initialized public key. + /// 1 always. + internal unsafe delegate int secp256k1_xonly_pubkey_serialize(IntPtr ctx, void* output32, void* pubkey); + + /// Compare two x-only public keys using lexicographic order + /// pointer to a context object. + /// + /// + /// <0 if the first public key is less than the second >0 if the first public key is greater than the second 0 if the two public keys are equal + internal unsafe delegate int secp256k1_xonly_pubkey_cmp(IntPtr ctx, void* pk1, void* pk2); + + /// Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey. + /// pointer to a context object. + /// pointer to an x-only public key object for placing the converted public key. + /// Ignored if NULL. Otherwise, pointer to an integer that will be set to 1 if the point encoded by xonly_pubkey is the negation of the pubkey and set to 0 otherwise. + /// pointer to a public key that is converted. + /// 1 always. + internal unsafe delegate int secp256k1_xonly_pubkey_from_pubkey(IntPtr ctx, void* xonly_pubkey, int* pk_parity, void* pubkey); + + /// Tweak an x-only public key by adding the generator multiplied with tweak32 to it.Note that the resulting point can not in general be represented by an x-only pubkey because it may have an odd Y coordinate. Instead, the output_pubkey is a normal secp256k1_pubkey. + /// pointer to a context object. + /// pointer to a public key to store the result. Will be set to an invalid value if this function returns 0. + /// pointer to an x-only pubkey to apply the tweak to. + /// pointer to a 32-byte tweak, which must be valid according to secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly random 32-byte tweaks, the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid or the resulting public key would be invalid (only when the tweak is the negation of the corresponding secret key). 1 otherwise. + internal unsafe delegate int secp256k1_xonly_pubkey_tweak_add(IntPtr ctx, void* output_pubkey, void* internal_pubkey, void* tweak32); + + /// Checks that a tweaked pubkey is the result of calling secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32.The tweaked pubkey is represented by its 32-byte x-only serialization and its pk_parity, which can both be obtained by converting the result of tweak_add to a secp256k1_xonly_pubkey.Note that this alone does _not_ verify that the tweaked pubkey is a commitment. If the tweak is not chosen in a specific way, the tweaked pubkey can easily be the result of a different internal_pubkey and tweak. + /// pointer to a context object. + /// pointer to a serialized xonly_pubkey. + /// the parity of the tweaked pubkey (whose serialization is passed in as tweaked_pubkey32). This must match the pk_parity value that is returned when calling secp256k1_xonly_pubkey with the tweaked pubkey, or this function will fail. + /// pointer to an x-only public key object to apply the tweak to. + /// pointer to a 32-byte tweak. + /// 0 if the arguments are invalid or the tweaked pubkey is not the result of tweaking the internal_pubkey with tweak32. 1 otherwise. + internal unsafe delegate int secp256k1_xonly_pubkey_tweak_add_check(IntPtr ctx, void* tweaked_pubkey32, int tweaked_pk_parity, void* internal_pubkey, void* tweak32); + + /// Compute the keypair for a valid secret key.See the documentation of `secp256k1_ec_seckey_verify` for more information about the validity of secret keys. + /// pointer to a context object (not secp256k1_context_static). + /// pointer to the created keypair. + /// pointer to a 32-byte secret key. + /// 1: secret key is valid 0: secret key is invalid + internal unsafe delegate int secp256k1_keypair_create(IntPtr ctx, void* keypair, void* seckey); + + /// Get the secret key from a keypair. + /// pointer to a context object. + /// pointer to a 32-byte buffer for the secret key. + /// pointer to a keypair. + /// 1 always. + internal unsafe delegate int secp256k1_keypair_sec(IntPtr ctx, void* seckey, void* keypair); + + /// Get the public key from a keypair. + /// pointer to a context object. + /// pointer to a pubkey object, set to the keypair public key. + /// pointer to a keypair. + /// 1 always. + internal unsafe delegate int secp256k1_keypair_pub(IntPtr ctx, void* pubkey, void* keypair); + + /// Get the x-only public key from a keypair.This is the same as calling secp256k1_keypair_pub and then secp256k1_xonly_pubkey_from_pubkey. + /// pointer to a context object. + /// pointer to an xonly_pubkey object, set to the keypair public key after converting it to an xonly_pubkey. + /// Ignored if NULL. Otherwise, pointer to an integer that will be set to the pk_parity argument of secp256k1_xonly_pubkey_from_pubkey. + /// pointer to a keypair. + /// 1 always. + internal unsafe delegate int secp256k1_keypair_xonly_pub(IntPtr ctx, void* pubkey, int* pk_parity, void* keypair); + + /// Tweak a keypair by adding tweak32 to the secret key and updating the public key accordingly.Calling this function and then secp256k1_keypair_pub results in the same public key as calling secp256k1_keypair_xonly_pub and then secp256k1_xonly_pubkey_tweak_add. + /// pointer to a context object. + /// pointer to a keypair to apply the tweak to. Will be set to an invalid value if this function returns 0. + /// pointer to a 32-byte tweak, which must be valid according to secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly random 32-byte tweaks, the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid or the resulting keypair would be invalid (only when the tweak is the negation of the keypair's secret key). 1 otherwise. + internal unsafe delegate int secp256k1_keypair_xonly_tweak_add(IntPtr ctx, void* keypair, void* tweak32); + + /// Create a Schnorr signature.Does _not_ strictly follow BIP-340 because it does not verify the resulting signature. Instead, you can manually use secp256k1_schnorrsig_verify and abort if it fails.This function only signs 32-byte messages. If you have messages of a different size (or the same size but without a context-specific tag prefix), it is recommended to create a 32-byte message hash with secp256k1_tagged_sha256 and then sign the hash. Tagged hashing allows providing an context-specific tag for domain separation. This prevents signatures from being valid in multiple contexts by accident.Returns 1 on success, 0 on failure. + /// pointer to a context object (not secp256k1_context_static). + /// pointer to a 64-byte array to store the serialized signature. + /// the 32-byte message being signed. + /// pointer to an initialized keypair. + /// 32 bytes of fresh randomness. While recommended to provide this, it is only supplemental to security and can be NULL. A NULL argument is treated the same as an all-zero one. See BIP-340 "Default Signing" for a full explanation of this argument and for guidance if randomness is expensive. + internal unsafe delegate int secp256k1_schnorrsig_sign32(IntPtr ctx, void* sig64, void* msg32, void* keypair, void* aux_rand32); + + /// Same as secp256k1_schnorrsig_sign32, but DEPRECATED. Will be removed in future versions. + /// + /// + /// + /// + /// + internal unsafe delegate int secp256k1_schnorrsig_sign(IntPtr ctx, void* sig64, void* msg32, void* keypair, void* aux_rand32); + + /// Create a Schnorr signature with a more flexible API.Same arguments as secp256k1_schnorrsig_sign except that it allows signing variable length messages and accepts a pointer to an extraparams object that allows customizing signing by passing additional arguments.Equivalent to secp256k1_schnorrsig_sign32(..., aux_rand32) if msglen is 32 and extraparams is initialized as follows: ``` secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT; extraparams.ndata = (unsigned char*)aux_rand32; ```Returns 1 on success, 0 on failure. + /// pointer to a context object (not secp256k1_context_static). + /// pointer to a 64-byte array to store the serialized signature. + /// the message being signed. Can only be NULL if msglen is 0. + /// length of the message. + /// pointer to an initialized keypair. + /// pointer to an extraparams object (can be NULL). + internal unsafe delegate int secp256k1_schnorrsig_sign_custom(IntPtr ctx, void* sig64, void* msg, nuint msglen, void* keypair, void* extraparams); + + /// Verify a Schnorr signature. + /// pointer to a context object. + /// pointer to the 64-byte signature to verify. + /// the message being verified. Can only be NULL if msglen is 0. + /// length of the message + /// pointer to an x-only public key to verify with + /// 1: correct signature 0: incorrect signature + internal unsafe delegate int secp256k1_schnorrsig_verify(IntPtr ctx, void* sig64, void* msg, nuint msglen, void* pubkey); + + /// Construct a 64-byte ElligatorSwift encoding of a given pubkey. + /// pointer to a context object + /// pointer to a 64-byte array to be filled + /// pointer to a secp256k1_pubkey containing an initialized public key + /// pointer to 32 bytes of randomnessIt is recommended that rnd32 consists of 32 uniformly random bytes, not known to any adversary trying to detect whether public keys are being encoded, though 16 bytes of randomness (padded to an array of 32 bytes, e.g., with zeros) suffice to make the result indistinguishable from uniform. The randomness in rnd32 must not be a deterministic function of the pubkey (it can be derived from the private key, though).It is not guaranteed that the computed encoding is stable across versions of the library, even if all arguments to this function (including rnd32) are the same.This function runs in variable time. + /// 1 always. + internal unsafe delegate int secp256k1_ellswift_encode(IntPtr ctx, void* ell64, void* pubkey, void* rnd32); + + /// Decode a 64-bytes ElligatorSwift encoded public key. + /// pointer to a context object + /// pointer to a secp256k1_pubkey that will be filled + /// pointer to a 64-byte array to decodeThis function runs in variable time. + /// always 1 + internal unsafe delegate int secp256k1_ellswift_decode(IntPtr ctx, void* pubkey, void* ell64); + + /// Compute an ElligatorSwift public key for a secret key. + /// pointer to a context object (not secp256k1_context_static) + /// pointer to a 64-byte array to receive the ElligatorSwift public key + /// pointer to a 32-byte secret key + /// (optional) pointer to 32 bytes of randomnessConstant time in seckey and auxrnd32, but not in the resulting public key.It is recommended that auxrnd32 contains 32 uniformly random bytes, though it is optional (and does result in encodings that are indistinguishable from uniform even without any auxrnd32). It differs from the (mandatory) rnd32 argument to secp256k1_ellswift_encode in this regard.This function can be used instead of calling secp256k1_ec_pubkey_create followed by secp256k1_ellswift_encode. It is safer, as it uses the secret key as entropy for the encoding (supplemented with auxrnd32, if provided).Like secp256k1_ellswift_encode, this function does not guarantee that the computed encoding is stable across versions of the library, even if all arguments (including auxrnd32) are the same. + /// 1: secret was valid, public key was stored. 0: secret was invalid, try again. + internal unsafe delegate int secp256k1_ellswift_create(IntPtr ctx, void* ell64, void* seckey32, void* auxrnd32); + + /// Given a private key, and ElligatorSwift public keys sent in both directions, compute a shared secret using x-only Elliptic Curve Diffie-Hellman (ECDH). + /// pointer to a context object. + /// pointer to an array to be filled by hashfp. + /// pointer to the 64-byte encoded public key of party A (will not be NULL) + /// pointer to the 64-byte encoded public key of party B (will not be NULL) + /// pointer to our 32-byte secret key + /// boolean indicating which party we are: zero if we are party A, non-zero if we are party B. seckey32 must be the private key corresponding to that party's ell_?64. This correspondence is not checked. + /// pointer to a hash function. + /// arbitrary data pointer passed through to hashfp.Constant time in seckey32.This function is more efficient than decoding the public keys, and performing ECDH on them. + /// 1: shared secret was successfully computed 0: secret was invalid or hashfp returned 0 + internal unsafe delegate int secp256k1_ellswift_xdh(IntPtr ctx, void* output, void* ell_a64, void* ell_b64, void* seckey32, int party, IntPtr hashfp, void* data); + + /// Parse a signer's public nonce. + /// pointer to a context object + /// pointer to a nonce object + /// pointer to the 66-byte nonce to be parsed + /// 1 when the nonce could be parsed, 0 otherwise. + internal unsafe delegate int secp256k1_musig_pubnonce_parse(IntPtr ctx, void* nonce, void* in66); + + /// Serialize a signer's public nonce + /// pointer to a context object + /// pointer to a 66-byte array to store the serialized nonce + /// pointer to the nonce + /// 1 always + internal unsafe delegate int secp256k1_musig_pubnonce_serialize(IntPtr ctx, void* out66, void* nonce); + + /// Parse an aggregate public nonce. + /// pointer to a context object + /// pointer to a nonce object + /// pointer to the 66-byte nonce to be parsed + /// 1 when the nonce could be parsed, 0 otherwise. + internal unsafe delegate int secp256k1_musig_aggnonce_parse(IntPtr ctx, void* nonce, void* in66); + + /// Serialize an aggregate public nonce + /// pointer to a context object + /// pointer to a 66-byte array to store the serialized nonce + /// pointer to the nonce + /// 1 always + internal unsafe delegate int secp256k1_musig_aggnonce_serialize(IntPtr ctx, void* out66, void* nonce); + + /// Parse a MuSig partial signature. + /// pointer to a context object + /// pointer to a signature object + /// pointer to the 32-byte signature to be parsed + /// 1 when the signature could be parsed, 0 otherwise. + internal unsafe delegate int secp256k1_musig_partial_sig_parse(IntPtr ctx, void* sig, void* in32); + + /// Serialize a MuSig partial signature + /// pointer to a context object + /// pointer to a 32-byte array to store the serialized signature + /// pointer to the signature + /// 1 always + internal unsafe delegate int secp256k1_musig_partial_sig_serialize(IntPtr ctx, void* out32, void* sig); + + /// Computes an aggregate public key and uses it to initialize a keyagg_cacheDifferent orders of `pubkeys` result in different `agg_pk`s.Before aggregating, the pubkeys can be sorted with `secp256k1_ec_pubkey_sort` which ensures the same `agg_pk` result for the same multiset of pubkeys. This is useful to do before `pubkey_agg`, such that the order of pubkeys does not affect the aggregate public key. + /// pointer to a context object + /// the MuSig-aggregated x-only public key. If you do not need it, this arg can be NULL. + /// if non-NULL, pointer to a musig_keyagg_cache struct that is required for signing (or observing the signing session and verifying partial signatures). + /// input array of pointers to public keys to aggregate. The order is important; a different order will result in a different aggregate public key. + /// length of pubkeys array. Must be greater than 0. + /// 0 if the arguments are invalid, 1 otherwise + internal unsafe delegate int secp256k1_musig_pubkey_agg(IntPtr ctx, void* agg_pk, void* keyagg_cache, IntPtr pubkeys, nuint n_pubkeys); + + /// Obtain the aggregate public key from a keyagg_cache.This is only useful if you need the non-xonly public key, in particular for plain (non-xonly) tweaking or batch-verifying multiple key aggregations (not implemented). + /// pointer to a context object + /// the MuSig-aggregated public key. + /// pointer to a `musig_keyagg_cache` struct initialized by `musig_pubkey_agg` + /// 0 if the arguments are invalid, 1 otherwise + internal unsafe delegate int secp256k1_musig_pubkey_get(IntPtr ctx, void* agg_pk, void* keyagg_cache); + + /// + /// + /// + /// + internal unsafe delegate int secp256k1_musig_pubkey_ec_tweak_add(IntPtr ctx, void* output_pubkey, void* keyagg_cache, void* tweak32); + + /// + /// + /// + /// + internal unsafe delegate int secp256k1_musig_pubkey_xonly_tweak_add(IntPtr ctx, void* output_pubkey, void* keyagg_cache, void* tweak32); + + /// Starts a signing session by generating a nonceThis function outputs a secret nonce that will be required for signing and a corresponding public nonce that is intended to be sent to other signers.MuSig differs from regular Schnorr signing in that implementers _must_ take special care to not reuse a nonce. This can be ensured by following these rules:1. Each call to this function must have a UNIQUE session_secrand32 that must NOT BE REUSED in subsequent calls to this function and must be KEPT SECRET (even from other signers). 2. If you already know the seckey, message or aggregate public key cache, they can be optionally provided to derive the nonce and increase misuse-resistance. The extra_input32 argument can be used to provide additional data that does not repeat in normal scenarios, such as the current time. 3. Avoid copying (or serializing) the secnonce. This reduces the possibility that it is used more than once for signing.If you don't have access to good randomness for session_secrand32, but you have access to a non-repeating counter, then see secp256k1_musig_nonce_gen_counter.Remember that nonce reuse will leak the secret key! Note that using the same seckey for multiple MuSig sessions is fine. + /// pointer to a context object (not secp256k1_context_static) + /// pointer to a structure to store the secret nonce + /// pointer to a structure to store the public nonce + /// a 32-byte session_secrand32 as explained above. Must be unique to this call to secp256k1_musig_nonce_gen and must be uniformly random. If the function call is successful, the session_secrand32 buffer is invalidated to prevent reuse. + /// the 32-byte secret key that will later be used for signing, if already known (can be NULL) + /// public key of the signer creating the nonce. The secnonce output of this function cannot be used to sign for any other public key. While the public key should correspond to the provided seckey, a mismatch will not cause the function to return 0. + /// the 32-byte message that will later be signed, if already known (can be NULL) + /// pointer to the keyagg_cache that was used to create the aggregate (and potentially tweaked) public key if already known (can be NULL) + /// an optional 32-byte array that is input to the nonce derivation function (can be NULL) + /// 0 if the arguments are invalid and 1 otherwise + internal unsafe delegate int secp256k1_musig_nonce_gen(IntPtr ctx, void* secnonce, void* pubnonce, void* session_secrand32, void* seckey, void* pubkey, void* msg32, void* keyagg_cache, void* extra_input32); + + /// Alternative way to generate a nonce and start a signing sessionThis function outputs a secret nonce that will be required for signing and a corresponding public nonce that is intended to be sent to other signers.This function differs from `secp256k1_musig_nonce_gen` by accepting a non-repeating counter value instead of a secret random value. This requires that a secret key is provided to `secp256k1_musig_nonce_gen_counter` (through the keypair argument), as opposed to `secp256k1_musig_nonce_gen` where the seckey argument is optional.MuSig differs from regular Schnorr signing in that implementers _must_ take special care to not reuse a nonce. This can be ensured by following these rules:1. The nonrepeating_cnt argument must be a counter value that never repeats, i.e., you must never call `secp256k1_musig_nonce_gen_counter` twice with the same keypair and nonrepeating_cnt value. For example, this implies that if the same keypair is used with `secp256k1_musig_nonce_gen_counter` on multiple devices, none of the devices should have the same counter value as any other device. 2. If the seckey, message or aggregate public key cache is already available at this stage, any of these can be optionally provided, in which case they will be used in the derivation of the nonce and increase misuse-resistance. The extra_input32 argument can be used to provide additional data that does not repeat in normal scenarios, such as the current time. 3. Avoid copying (or serializing) the secnonce. This reduces the possibility that it is used more than once for signing.Remember that nonce reuse will leak the secret key! Note that using the same keypair for multiple MuSig sessions is fine. + /// pointer to a context object (not secp256k1_context_static) + /// pointer to a structure to store the secret nonce + /// pointer to a structure to store the public nonce + /// the value of a counter as explained above. Must be unique to this call to secp256k1_musig_nonce_gen. + /// keypair of the signer creating the nonce. The secnonce output of this function cannot be used to sign for any other keypair. + /// the 32-byte message that will later be signed, if already known (can be NULL) + /// pointer to the keyagg_cache that was used to create the aggregate (and potentially tweaked) public key if already known (can be NULL) + /// an optional 32-byte array that is input to the nonce derivation function (can be NULL) + /// 0 if the arguments are invalid and 1 otherwise + internal unsafe delegate int secp256k1_musig_nonce_gen_counter(IntPtr ctx, void* secnonce, void* pubnonce, ulong nonrepeating_cnt, void* keypair, void* msg32, void* keyagg_cache, void* extra_input32); + + /// Aggregates the nonces of all signers into a single nonceThis can be done by an untrusted party to reduce the communication between signers. Instead of everyone sending nonces to everyone else, there can be one party receiving all nonces, aggregating the nonces with this function and then sending only the aggregate nonce back to the signers.If the aggregator does not compute the aggregate nonce correctly, the final signature will be invalid. + /// pointer to a context object + /// pointer to an aggregate public nonce object for musig_nonce_process + /// array of pointers to public nonces sent by the signers + /// number of elements in the pubnonces array. Must be greater than 0. + /// 0 if the arguments are invalid, 1 otherwise + internal unsafe delegate int secp256k1_musig_nonce_agg(IntPtr ctx, void* aggnonce, IntPtr pubnonces, nuint n_pubnonces); + + /// Takes the aggregate nonce and creates a session that is required for signing and verification of partial signatures. + /// pointer to a context object + /// pointer to a struct to store the session + /// pointer to an aggregate public nonce object that is the output of musig_nonce_agg + /// the 32-byte message to sign + /// pointer to the keyagg_cache that was used to create the aggregate (and potentially tweaked) pubkey + /// 0 if the arguments are invalid, 1 otherwise + internal unsafe delegate int secp256k1_musig_nonce_process(IntPtr ctx, void* session, void* aggnonce, void* msg32, void* keyagg_cache); + + /// Produces a partial signatureThis function overwrites the given secnonce with zeros and will abort if given a secnonce that is all zeros. This is a best effort attempt to protect against nonce reuse. However, this is of course easily defeated if the secnonce has been copied (or serialized). Remember that nonce reuse will leak the secret key!For signing to succeed, the secnonce provided to this function must have been generated for the provided keypair. This means that when signing for a keypair consisting of a seckey and pubkey, the secnonce must have been created by calling musig_nonce_gen with that pubkey. Otherwise, the illegal_callback is called.This function does not verify the output partial signature, deviating from the BIP 327 specification. It is recommended to verify the output partial signature with `secp256k1_musig_partial_sig_verify` to prevent random or adversarially provoked computation errors. + /// pointer to a context object + /// pointer to struct to store the partial signature + /// pointer to the secnonce struct created in musig_nonce_gen that has been never used in a partial_sign call before and has been created for the keypair + /// pointer to keypair to sign the message with + /// pointer to the keyagg_cache that was output when the aggregate public key for this session + /// pointer to the session that was created with musig_nonce_process + /// 0 if the arguments are invalid or the provided secnonce has already been used for signing, 1 otherwise + internal unsafe delegate int secp256k1_musig_partial_sign(IntPtr ctx, void* partial_sig, void* secnonce, void* keypair, void* keyagg_cache, void* session); + + /// Verifies an individual signer's partial signatureThe signature is verified for a specific signing session. In order to avoid accidentally verifying a signature from a different or non-existing signing session, you must ensure the following: 1. The `keyagg_cache` argument is identical to the one used to create the `session` with `musig_nonce_process`. 2. The `pubkey` argument must be identical to the one sent by the signer before aggregating it with `musig_pubkey_agg` to create the `keyagg_cache`. 3. The `pubnonce` argument must be identical to the one sent by the signer before aggregating it with `musig_nonce_agg` and using the result to create the `session` with `musig_nonce_process`.It is not required to call this function in regular MuSig sessions, because if any partial signature does not verify, the final signature will not verify either, so the problem will be caught. However, this function provides the ability to identify which specific partial signature fails verification. + /// + /// pointer to partial signature to verify, sent by the signer associated with `pubnonce` and `pubkey` + /// public nonce of the signer in the signing session + /// public key of the signer in the signing session + /// pointer to the keyagg_cache that was output when the aggregate public key for this signing session + /// pointer to the session that was created with `musig_nonce_process` + /// 0 if the arguments are invalid or the partial signature does not verify, 1 otherwise + internal unsafe delegate int secp256k1_musig_partial_sig_verify(IntPtr ctx, void* partial_sig, void* pubnonce, void* pubkey, void* keyagg_cache, void* session); + + /// Aggregates partial signatures + /// pointer to a context object + /// complete (but possibly invalid) Schnorr signature + /// pointer to the session that was created with musig_nonce_process + /// array of pointers to partial signatures to aggregate + /// number of elements in the partial_sigs array. Must be greater than 0. + /// 0 if the arguments are invalid, 1 otherwise (which does NOT mean the resulting signature verifies). + internal unsafe delegate int secp256k1_musig_partial_sig_agg(IntPtr ctx, void* sig64, void* session, IntPtr partial_sigs, nuint n_sigs); +#endif + + internal static unsafe class Secp256k1Interop + { + // Native function symbol names + private const string SYM_selftest = "secp256k1_selftest"; + private const string SYM_context_create = "secp256k1_context_create"; + private const string SYM_context_clone = "secp256k1_context_clone"; + private const string SYM_context_destroy = "secp256k1_context_destroy"; + private const string SYM_context_set_illegal_callback = "secp256k1_context_set_illegal_callback"; + private const string SYM_context_set_error_callback = "secp256k1_context_set_error_callback"; + private const string SYM_ec_pubkey_parse = "secp256k1_ec_pubkey_parse"; + private const string SYM_ec_pubkey_serialize = "secp256k1_ec_pubkey_serialize"; + private const string SYM_ec_pubkey_cmp = "secp256k1_ec_pubkey_cmp"; + private const string SYM_ec_pubkey_sort = "secp256k1_ec_pubkey_sort"; + private const string SYM_ecdsa_signature_parse_compact = "secp256k1_ecdsa_signature_parse_compact"; + private const string SYM_ecdsa_signature_parse_der = "secp256k1_ecdsa_signature_parse_der"; + private const string SYM_ecdsa_signature_serialize_der = "secp256k1_ecdsa_signature_serialize_der"; + private const string SYM_ecdsa_signature_serialize_compact = "secp256k1_ecdsa_signature_serialize_compact"; + private const string SYM_ecdsa_verify = "secp256k1_ecdsa_verify"; + private const string SYM_ecdsa_signature_normalize = "secp256k1_ecdsa_signature_normalize"; + private const string SYM_ecdsa_sign = "secp256k1_ecdsa_sign"; + private const string SYM_ec_seckey_verify = "secp256k1_ec_seckey_verify"; + private const string SYM_ec_pubkey_create = "secp256k1_ec_pubkey_create"; + private const string SYM_ec_seckey_negate = "secp256k1_ec_seckey_negate"; + private const string SYM_ec_pubkey_negate = "secp256k1_ec_pubkey_negate"; + private const string SYM_ec_seckey_tweak_add = "secp256k1_ec_seckey_tweak_add"; + private const string SYM_ec_pubkey_tweak_add = "secp256k1_ec_pubkey_tweak_add"; + private const string SYM_ec_seckey_tweak_mul = "secp256k1_ec_seckey_tweak_mul"; + private const string SYM_ec_pubkey_tweak_mul = "secp256k1_ec_pubkey_tweak_mul"; + private const string SYM_context_randomize = "secp256k1_context_randomize"; + private const string SYM_ec_pubkey_combine = "secp256k1_ec_pubkey_combine"; + private const string SYM_tagged_sha256 = "secp256k1_tagged_sha256"; + private const string SYM_context_preallocated_size = "secp256k1_context_preallocated_size"; + private const string SYM_context_preallocated_create = "secp256k1_context_preallocated_create"; + private const string SYM_context_preallocated_clone_size = "secp256k1_context_preallocated_clone_size"; + private const string SYM_context_preallocated_clone = "secp256k1_context_preallocated_clone"; + private const string SYM_context_preallocated_destroy = "secp256k1_context_preallocated_destroy"; + private const string SYM_ecdsa_recoverable_signature_parse_compact = "secp256k1_ecdsa_recoverable_signature_parse_compact"; + private const string SYM_ecdsa_recoverable_signature_convert = "secp256k1_ecdsa_recoverable_signature_convert"; + private const string SYM_ecdsa_recoverable_signature_serialize_compact = "secp256k1_ecdsa_recoverable_signature_serialize_compact"; + private const string SYM_ecdsa_sign_recoverable = "secp256k1_ecdsa_sign_recoverable"; + private const string SYM_ecdsa_recover = "secp256k1_ecdsa_recover"; + private const string SYM_ecdh = "secp256k1_ecdh"; + private const string SYM_xonly_pubkey_parse = "secp256k1_xonly_pubkey_parse"; + private const string SYM_xonly_pubkey_serialize = "secp256k1_xonly_pubkey_serialize"; + private const string SYM_xonly_pubkey_cmp = "secp256k1_xonly_pubkey_cmp"; + private const string SYM_xonly_pubkey_from_pubkey = "secp256k1_xonly_pubkey_from_pubkey"; + private const string SYM_xonly_pubkey_tweak_add = "secp256k1_xonly_pubkey_tweak_add"; + private const string SYM_xonly_pubkey_tweak_add_check = "secp256k1_xonly_pubkey_tweak_add_check"; + private const string SYM_keypair_create = "secp256k1_keypair_create"; + private const string SYM_keypair_sec = "secp256k1_keypair_sec"; + private const string SYM_keypair_pub = "secp256k1_keypair_pub"; + private const string SYM_keypair_xonly_pub = "secp256k1_keypair_xonly_pub"; + private const string SYM_keypair_xonly_tweak_add = "secp256k1_keypair_xonly_tweak_add"; + private const string SYM_schnorrsig_sign32 = "secp256k1_schnorrsig_sign32"; + private const string SYM_schnorrsig_sign = "secp256k1_schnorrsig_sign"; + private const string SYM_schnorrsig_sign_custom = "secp256k1_schnorrsig_sign_custom"; + private const string SYM_schnorrsig_verify = "secp256k1_schnorrsig_verify"; + private const string SYM_ellswift_encode = "secp256k1_ellswift_encode"; + private const string SYM_ellswift_decode = "secp256k1_ellswift_decode"; + private const string SYM_ellswift_create = "secp256k1_ellswift_create"; + private const string SYM_ellswift_xdh = "secp256k1_ellswift_xdh"; + private const string SYM_musig_pubnonce_parse = "secp256k1_musig_pubnonce_parse"; + private const string SYM_musig_pubnonce_serialize = "secp256k1_musig_pubnonce_serialize"; + private const string SYM_musig_aggnonce_parse = "secp256k1_musig_aggnonce_parse"; + private const string SYM_musig_aggnonce_serialize = "secp256k1_musig_aggnonce_serialize"; + private const string SYM_musig_partial_sig_parse = "secp256k1_musig_partial_sig_parse"; + private const string SYM_musig_partial_sig_serialize = "secp256k1_musig_partial_sig_serialize"; + private const string SYM_musig_pubkey_agg = "secp256k1_musig_pubkey_agg"; + private const string SYM_musig_pubkey_get = "secp256k1_musig_pubkey_get"; + private const string SYM_musig_pubkey_ec_tweak_add = "secp256k1_musig_pubkey_ec_tweak_add"; + private const string SYM_musig_pubkey_xonly_tweak_add = "secp256k1_musig_pubkey_xonly_tweak_add"; + private const string SYM_musig_nonce_gen = "secp256k1_musig_nonce_gen"; + private const string SYM_musig_nonce_gen_counter = "secp256k1_musig_nonce_gen_counter"; + private const string SYM_musig_nonce_agg = "secp256k1_musig_nonce_agg"; + private const string SYM_musig_nonce_process = "secp256k1_musig_nonce_process"; + private const string SYM_musig_partial_sign = "secp256k1_musig_partial_sign"; + private const string SYM_musig_partial_sig_verify = "secp256k1_musig_partial_sig_verify"; + private const string SYM_musig_partial_sig_agg = "secp256k1_musig_partial_sig_agg"; + private const string SYM_nonce_function_rfc6979 = "secp256k1_nonce_function_rfc6979"; + private const string SYM_nonce_function_default = "secp256k1_nonce_function_default"; + private const string SYM_ecdh_hash_function_sha256 = "secp256k1_ecdh_hash_function_sha256"; + private const string SYM_ecdh_hash_function_default = "secp256k1_ecdh_hash_function_default"; + private const string SYM_nonce_function_bip340 = "secp256k1_nonce_function_bip340"; + private const string SYM_ellswift_xdh_hash_function_prefix = "secp256k1_ellswift_xdh_hash_function_prefix"; + private const string SYM_ellswift_xdh_hash_function_bip324 = "secp256k1_ellswift_xdh_hash_function_bip324"; + +#if NET8_0_OR_GREATER + // Function pointer declarations (modern .NET 8+) +#nullable disable + internal static FnPtr00 _selftest; + internal static FnPtr01 _context_create; + internal static FnPtr02 _context_clone; + internal static FnPtr03 _context_destroy; + internal static FnPtr04 _context_set_illegal_callback; + internal static FnPtr04 _context_set_error_callback; + internal static FnPtr05 _ec_pubkey_parse; + internal static FnPtr06 _ec_pubkey_serialize; + internal static FnPtr07 _ec_pubkey_cmp; + internal static FnPtr08 _ec_pubkey_sort; + internal static FnPtr07 _ecdsa_signature_parse_compact; + internal static FnPtr05 _ecdsa_signature_parse_der; + internal static FnPtr09 _ecdsa_signature_serialize_der; + internal static FnPtr07 _ecdsa_signature_serialize_compact; + internal static FnPtr10 _ecdsa_verify; + internal static FnPtr07 _ecdsa_signature_normalize; + internal static FnPtr11 _ecdsa_sign; + internal static FnPtr12 _ec_seckey_verify; + internal static FnPtr07 _ec_pubkey_create; + internal static FnPtr12 _ec_seckey_negate; + internal static FnPtr12 _ec_pubkey_negate; + internal static FnPtr07 _ec_seckey_tweak_add; + internal static FnPtr07 _ec_pubkey_tweak_add; + internal static FnPtr07 _ec_seckey_tweak_mul; + internal static FnPtr07 _ec_pubkey_tweak_mul; + internal static FnPtr12 _context_randomize; + internal static FnPtr13 _ec_pubkey_combine; + internal static FnPtr14 _tagged_sha256; + internal static FnPtr15 _context_preallocated_size; + internal static FnPtr16 _context_preallocated_create; + internal static FnPtr17 _context_preallocated_clone_size; + internal static FnPtr18 _context_preallocated_clone; + internal static FnPtr03 _context_preallocated_destroy; + internal static FnPtr19 _ecdsa_recoverable_signature_parse_compact; + internal static FnPtr07 _ecdsa_recoverable_signature_convert; + internal static FnPtr20 _ecdsa_recoverable_signature_serialize_compact; + internal static FnPtr11 _ecdsa_sign_recoverable; + internal static FnPtr10 _ecdsa_recover; + internal static FnPtr11 _ecdh; + internal static FnPtr07 _xonly_pubkey_parse; + internal static FnPtr07 _xonly_pubkey_serialize; + internal static FnPtr07 _xonly_pubkey_cmp; + internal static FnPtr20 _xonly_pubkey_from_pubkey; + internal static FnPtr10 _xonly_pubkey_tweak_add; + internal static FnPtr21 _xonly_pubkey_tweak_add_check; + internal static FnPtr07 _keypair_create; + internal static FnPtr07 _keypair_sec; + internal static FnPtr07 _keypair_pub; + internal static FnPtr20 _keypair_xonly_pub; + internal static FnPtr07 _keypair_xonly_tweak_add; + internal static FnPtr22 _schnorrsig_sign32; + internal static FnPtr22 _schnorrsig_sign; + internal static FnPtr23 _schnorrsig_sign_custom; + internal static FnPtr24 _schnorrsig_verify; + internal static FnPtr10 _ellswift_encode; + internal static FnPtr07 _ellswift_decode; + internal static FnPtr10 _ellswift_create; + internal static FnPtr25 _ellswift_xdh; + internal static FnPtr07 _musig_pubnonce_parse; + internal static FnPtr07 _musig_pubnonce_serialize; + internal static FnPtr07 _musig_aggnonce_parse; + internal static FnPtr07 _musig_aggnonce_serialize; + internal static FnPtr07 _musig_partial_sig_parse; + internal static FnPtr07 _musig_partial_sig_serialize; + internal static FnPtr26 _musig_pubkey_agg; + internal static FnPtr07 _musig_pubkey_get; + internal static FnPtr10 _musig_pubkey_ec_tweak_add; + internal static FnPtr10 _musig_pubkey_xonly_tweak_add; + internal static FnPtr27 _musig_nonce_gen; + internal static FnPtr28 _musig_nonce_gen_counter; + internal static FnPtr13 _musig_nonce_agg; + internal static FnPtr22 _musig_nonce_process; + internal static FnPtr29 _musig_partial_sign; + internal static FnPtr29 _musig_partial_sig_verify; + internal static FnPtr26 _musig_partial_sig_agg; + internal static FnPtr30 _nonce_function_rfc6979; + internal static FnPtr30 _nonce_function_default; + internal static FnPtr31 _ecdh_hash_function_sha256; + internal static FnPtr31 _ecdh_hash_function_default; + internal static FnPtr32 _nonce_function_bip340; + internal static FnPtr33 _ellswift_xdh_hash_function_prefix; + internal static FnPtr33 _ellswift_xdh_hash_function_bip324; +#nullable restore +#else + // Delegate instance fields (legacy .NET) +#nullable disable + internal static secp256k1_selftest _selftest; + internal static secp256k1_context_create _context_create; + internal static secp256k1_context_clone _context_clone; + internal static secp256k1_context_destroy _context_destroy; + internal static secp256k1_context_set_illegal_callback _context_set_illegal_callback; + internal static secp256k1_context_set_error_callback _context_set_error_callback; + internal static secp256k1_ec_pubkey_parse _ec_pubkey_parse; + internal static secp256k1_ec_pubkey_serialize _ec_pubkey_serialize; + internal static secp256k1_ec_pubkey_cmp _ec_pubkey_cmp; + internal static secp256k1_ec_pubkey_sort _ec_pubkey_sort; + internal static secp256k1_ecdsa_signature_parse_compact _ecdsa_signature_parse_compact; + internal static secp256k1_ecdsa_signature_parse_der _ecdsa_signature_parse_der; + internal static secp256k1_ecdsa_signature_serialize_der _ecdsa_signature_serialize_der; + internal static secp256k1_ecdsa_signature_serialize_compact _ecdsa_signature_serialize_compact; + internal static secp256k1_ecdsa_verify _ecdsa_verify; + internal static secp256k1_ecdsa_signature_normalize _ecdsa_signature_normalize; + internal static secp256k1_ecdsa_sign _ecdsa_sign; + internal static secp256k1_ec_seckey_verify _ec_seckey_verify; + internal static secp256k1_ec_pubkey_create _ec_pubkey_create; + internal static secp256k1_ec_seckey_negate _ec_seckey_negate; + internal static secp256k1_ec_pubkey_negate _ec_pubkey_negate; + internal static secp256k1_ec_seckey_tweak_add _ec_seckey_tweak_add; + internal static secp256k1_ec_pubkey_tweak_add _ec_pubkey_tweak_add; + internal static secp256k1_ec_seckey_tweak_mul _ec_seckey_tweak_mul; + internal static secp256k1_ec_pubkey_tweak_mul _ec_pubkey_tweak_mul; + internal static secp256k1_context_randomize _context_randomize; + internal static secp256k1_ec_pubkey_combine _ec_pubkey_combine; + internal static secp256k1_tagged_sha256 _tagged_sha256; + internal static secp256k1_context_preallocated_size _context_preallocated_size; + internal static secp256k1_context_preallocated_create _context_preallocated_create; + internal static secp256k1_context_preallocated_clone_size _context_preallocated_clone_size; + internal static secp256k1_context_preallocated_clone _context_preallocated_clone; + internal static secp256k1_context_preallocated_destroy _context_preallocated_destroy; + internal static secp256k1_ecdsa_recoverable_signature_parse_compact _ecdsa_recoverable_signature_parse_compact; + internal static secp256k1_ecdsa_recoverable_signature_convert _ecdsa_recoverable_signature_convert; + internal static secp256k1_ecdsa_recoverable_signature_serialize_compact _ecdsa_recoverable_signature_serialize_compact; + internal static secp256k1_ecdsa_sign_recoverable _ecdsa_sign_recoverable; + internal static secp256k1_ecdsa_recover _ecdsa_recover; + internal static secp256k1_ecdh _ecdh; + internal static secp256k1_xonly_pubkey_parse _xonly_pubkey_parse; + internal static secp256k1_xonly_pubkey_serialize _xonly_pubkey_serialize; + internal static secp256k1_xonly_pubkey_cmp _xonly_pubkey_cmp; + internal static secp256k1_xonly_pubkey_from_pubkey _xonly_pubkey_from_pubkey; + internal static secp256k1_xonly_pubkey_tweak_add _xonly_pubkey_tweak_add; + internal static secp256k1_xonly_pubkey_tweak_add_check _xonly_pubkey_tweak_add_check; + internal static secp256k1_keypair_create _keypair_create; + internal static secp256k1_keypair_sec _keypair_sec; + internal static secp256k1_keypair_pub _keypair_pub; + internal static secp256k1_keypair_xonly_pub _keypair_xonly_pub; + internal static secp256k1_keypair_xonly_tweak_add _keypair_xonly_tweak_add; + internal static secp256k1_schnorrsig_sign32 _schnorrsig_sign32; + internal static secp256k1_schnorrsig_sign _schnorrsig_sign; + internal static secp256k1_schnorrsig_sign_custom _schnorrsig_sign_custom; + internal static secp256k1_schnorrsig_verify _schnorrsig_verify; + internal static secp256k1_ellswift_encode _ellswift_encode; + internal static secp256k1_ellswift_decode _ellswift_decode; + internal static secp256k1_ellswift_create _ellswift_create; + internal static secp256k1_ellswift_xdh _ellswift_xdh; + internal static secp256k1_musig_pubnonce_parse _musig_pubnonce_parse; + internal static secp256k1_musig_pubnonce_serialize _musig_pubnonce_serialize; + internal static secp256k1_musig_aggnonce_parse _musig_aggnonce_parse; + internal static secp256k1_musig_aggnonce_serialize _musig_aggnonce_serialize; + internal static secp256k1_musig_partial_sig_parse _musig_partial_sig_parse; + internal static secp256k1_musig_partial_sig_serialize _musig_partial_sig_serialize; + internal static secp256k1_musig_pubkey_agg _musig_pubkey_agg; + internal static secp256k1_musig_pubkey_get _musig_pubkey_get; + internal static secp256k1_musig_pubkey_ec_tweak_add _musig_pubkey_ec_tweak_add; + internal static secp256k1_musig_pubkey_xonly_tweak_add _musig_pubkey_xonly_tweak_add; + internal static secp256k1_musig_nonce_gen _musig_nonce_gen; + internal static secp256k1_musig_nonce_gen_counter _musig_nonce_gen_counter; + internal static secp256k1_musig_nonce_agg _musig_nonce_agg; + internal static secp256k1_musig_nonce_process _musig_nonce_process; + internal static secp256k1_musig_partial_sign _musig_partial_sign; + internal static secp256k1_musig_partial_sig_verify _musig_partial_sig_verify; + internal static secp256k1_musig_partial_sig_agg _musig_partial_sig_agg; + internal static secp256k1_nonce_function _nonce_function_rfc6979; + internal static secp256k1_nonce_function _nonce_function_default; + internal static secp256k1_ecdh_hash_function _ecdh_hash_function_sha256; + internal static secp256k1_ecdh_hash_function _ecdh_hash_function_default; + internal static secp256k1_nonce_function_hardened _nonce_function_bip340; + internal static secp256k1_ellswift_xdh_hash_function _ellswift_xdh_hash_function_prefix; + internal static secp256k1_ellswift_xdh_hash_function _ellswift_xdh_hash_function_bip324; +#nullable restore +#endif + + internal static void LoadFunctions(IntPtr lib) + { +#if NET8_0_OR_GREATER + _selftest = (FnPtr00)NativeLibrary.GetExport(lib, SYM_selftest); + _context_create = (FnPtr01)NativeLibrary.GetExport(lib, SYM_context_create); + _context_clone = (FnPtr02)NativeLibrary.GetExport(lib, SYM_context_clone); + _context_destroy = (FnPtr03)NativeLibrary.GetExport(lib, SYM_context_destroy); + _context_set_illegal_callback = (FnPtr04)NativeLibrary.GetExport(lib, SYM_context_set_illegal_callback); + _context_set_error_callback = (FnPtr04)NativeLibrary.GetExport(lib, SYM_context_set_error_callback); + _ec_pubkey_parse = (FnPtr05)NativeLibrary.GetExport(lib, SYM_ec_pubkey_parse); + _ec_pubkey_serialize = (FnPtr06)NativeLibrary.GetExport(lib, SYM_ec_pubkey_serialize); + _ec_pubkey_cmp = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ec_pubkey_cmp); + _ec_pubkey_sort = (FnPtr08)NativeLibrary.GetExport(lib, SYM_ec_pubkey_sort); + _ecdsa_signature_parse_compact = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ecdsa_signature_parse_compact); + _ecdsa_signature_parse_der = (FnPtr05)NativeLibrary.GetExport(lib, SYM_ecdsa_signature_parse_der); + _ecdsa_signature_serialize_der = (FnPtr09)NativeLibrary.GetExport(lib, SYM_ecdsa_signature_serialize_der); + _ecdsa_signature_serialize_compact = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ecdsa_signature_serialize_compact); + _ecdsa_verify = (FnPtr10)NativeLibrary.GetExport(lib, SYM_ecdsa_verify); + _ecdsa_signature_normalize = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ecdsa_signature_normalize); + _ecdsa_sign = (FnPtr11)NativeLibrary.GetExport(lib, SYM_ecdsa_sign); + _ec_seckey_verify = (FnPtr12)NativeLibrary.GetExport(lib, SYM_ec_seckey_verify); + _ec_pubkey_create = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ec_pubkey_create); + _ec_seckey_negate = (FnPtr12)NativeLibrary.GetExport(lib, SYM_ec_seckey_negate); + _ec_pubkey_negate = (FnPtr12)NativeLibrary.GetExport(lib, SYM_ec_pubkey_negate); + _ec_seckey_tweak_add = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ec_seckey_tweak_add); + _ec_pubkey_tweak_add = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ec_pubkey_tweak_add); + _ec_seckey_tweak_mul = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ec_seckey_tweak_mul); + _ec_pubkey_tweak_mul = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ec_pubkey_tweak_mul); + _context_randomize = (FnPtr12)NativeLibrary.GetExport(lib, SYM_context_randomize); + _ec_pubkey_combine = (FnPtr13)NativeLibrary.GetExport(lib, SYM_ec_pubkey_combine); + _tagged_sha256 = (FnPtr14)NativeLibrary.GetExport(lib, SYM_tagged_sha256); + _context_preallocated_size = (FnPtr15)NativeLibrary.GetExport(lib, SYM_context_preallocated_size); + _context_preallocated_create = (FnPtr16)NativeLibrary.GetExport(lib, SYM_context_preallocated_create); + _context_preallocated_clone_size = (FnPtr17)NativeLibrary.GetExport(lib, SYM_context_preallocated_clone_size); + _context_preallocated_clone = (FnPtr18)NativeLibrary.GetExport(lib, SYM_context_preallocated_clone); + _context_preallocated_destroy = (FnPtr03)NativeLibrary.GetExport(lib, SYM_context_preallocated_destroy); + _ecdsa_recoverable_signature_parse_compact = (FnPtr19)NativeLibrary.GetExport(lib, SYM_ecdsa_recoverable_signature_parse_compact); + _ecdsa_recoverable_signature_convert = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ecdsa_recoverable_signature_convert); + _ecdsa_recoverable_signature_serialize_compact = (FnPtr20)NativeLibrary.GetExport(lib, SYM_ecdsa_recoverable_signature_serialize_compact); + _ecdsa_sign_recoverable = (FnPtr11)NativeLibrary.GetExport(lib, SYM_ecdsa_sign_recoverable); + _ecdsa_recover = (FnPtr10)NativeLibrary.GetExport(lib, SYM_ecdsa_recover); + _ecdh = (FnPtr11)NativeLibrary.GetExport(lib, SYM_ecdh); + _xonly_pubkey_parse = (FnPtr07)NativeLibrary.GetExport(lib, SYM_xonly_pubkey_parse); + _xonly_pubkey_serialize = (FnPtr07)NativeLibrary.GetExport(lib, SYM_xonly_pubkey_serialize); + _xonly_pubkey_cmp = (FnPtr07)NativeLibrary.GetExport(lib, SYM_xonly_pubkey_cmp); + _xonly_pubkey_from_pubkey = (FnPtr20)NativeLibrary.GetExport(lib, SYM_xonly_pubkey_from_pubkey); + _xonly_pubkey_tweak_add = (FnPtr10)NativeLibrary.GetExport(lib, SYM_xonly_pubkey_tweak_add); + _xonly_pubkey_tweak_add_check = (FnPtr21)NativeLibrary.GetExport(lib, SYM_xonly_pubkey_tweak_add_check); + _keypair_create = (FnPtr07)NativeLibrary.GetExport(lib, SYM_keypair_create); + _keypair_sec = (FnPtr07)NativeLibrary.GetExport(lib, SYM_keypair_sec); + _keypair_pub = (FnPtr07)NativeLibrary.GetExport(lib, SYM_keypair_pub); + _keypair_xonly_pub = (FnPtr20)NativeLibrary.GetExport(lib, SYM_keypair_xonly_pub); + _keypair_xonly_tweak_add = (FnPtr07)NativeLibrary.GetExport(lib, SYM_keypair_xonly_tweak_add); + _schnorrsig_sign32 = (FnPtr22)NativeLibrary.GetExport(lib, SYM_schnorrsig_sign32); + _schnorrsig_sign = (FnPtr22)NativeLibrary.GetExport(lib, SYM_schnorrsig_sign); + _schnorrsig_sign_custom = (FnPtr23)NativeLibrary.GetExport(lib, SYM_schnorrsig_sign_custom); + _schnorrsig_verify = (FnPtr24)NativeLibrary.GetExport(lib, SYM_schnorrsig_verify); + _ellswift_encode = (FnPtr10)NativeLibrary.GetExport(lib, SYM_ellswift_encode); + _ellswift_decode = (FnPtr07)NativeLibrary.GetExport(lib, SYM_ellswift_decode); + _ellswift_create = (FnPtr10)NativeLibrary.GetExport(lib, SYM_ellswift_create); + _ellswift_xdh = (FnPtr25)NativeLibrary.GetExport(lib, SYM_ellswift_xdh); + _musig_pubnonce_parse = (FnPtr07)NativeLibrary.GetExport(lib, SYM_musig_pubnonce_parse); + _musig_pubnonce_serialize = (FnPtr07)NativeLibrary.GetExport(lib, SYM_musig_pubnonce_serialize); + _musig_aggnonce_parse = (FnPtr07)NativeLibrary.GetExport(lib, SYM_musig_aggnonce_parse); + _musig_aggnonce_serialize = (FnPtr07)NativeLibrary.GetExport(lib, SYM_musig_aggnonce_serialize); + _musig_partial_sig_parse = (FnPtr07)NativeLibrary.GetExport(lib, SYM_musig_partial_sig_parse); + _musig_partial_sig_serialize = (FnPtr07)NativeLibrary.GetExport(lib, SYM_musig_partial_sig_serialize); + _musig_pubkey_agg = (FnPtr26)NativeLibrary.GetExport(lib, SYM_musig_pubkey_agg); + _musig_pubkey_get = (FnPtr07)NativeLibrary.GetExport(lib, SYM_musig_pubkey_get); + _musig_pubkey_ec_tweak_add = (FnPtr10)NativeLibrary.GetExport(lib, SYM_musig_pubkey_ec_tweak_add); + _musig_pubkey_xonly_tweak_add = (FnPtr10)NativeLibrary.GetExport(lib, SYM_musig_pubkey_xonly_tweak_add); + _musig_nonce_gen = (FnPtr27)NativeLibrary.GetExport(lib, SYM_musig_nonce_gen); + _musig_nonce_gen_counter = (FnPtr28)NativeLibrary.GetExport(lib, SYM_musig_nonce_gen_counter); + _musig_nonce_agg = (FnPtr13)NativeLibrary.GetExport(lib, SYM_musig_nonce_agg); + _musig_nonce_process = (FnPtr22)NativeLibrary.GetExport(lib, SYM_musig_nonce_process); + _musig_partial_sign = (FnPtr29)NativeLibrary.GetExport(lib, SYM_musig_partial_sign); + _musig_partial_sig_verify = (FnPtr29)NativeLibrary.GetExport(lib, SYM_musig_partial_sig_verify); + _musig_partial_sig_agg = (FnPtr26)NativeLibrary.GetExport(lib, SYM_musig_partial_sig_agg); + + // secp256k1_nonce_function_rfc6979 is a data symbol (function pointer), not a function + var _nonce_function_rfc6979Ptr = NativeLibrary.GetExport(lib, SYM_nonce_function_rfc6979); + _nonce_function_rfc6979 = (FnPtr30)Marshal.ReadIntPtr(_nonce_function_rfc6979Ptr); + + // secp256k1_nonce_function_default is a data symbol (function pointer), not a function + var _nonce_function_defaultPtr = NativeLibrary.GetExport(lib, SYM_nonce_function_default); + _nonce_function_default = (FnPtr30)Marshal.ReadIntPtr(_nonce_function_defaultPtr); + + // secp256k1_ecdh_hash_function_sha256 is a data symbol (function pointer), not a function + var _ecdh_hash_function_sha256Ptr = NativeLibrary.GetExport(lib, SYM_ecdh_hash_function_sha256); + _ecdh_hash_function_sha256 = (FnPtr31)Marshal.ReadIntPtr(_ecdh_hash_function_sha256Ptr); + + // secp256k1_ecdh_hash_function_default is a data symbol (function pointer), not a function + var _ecdh_hash_function_defaultPtr = NativeLibrary.GetExport(lib, SYM_ecdh_hash_function_default); + _ecdh_hash_function_default = (FnPtr31)Marshal.ReadIntPtr(_ecdh_hash_function_defaultPtr); + + // secp256k1_nonce_function_bip340 is a data symbol (function pointer), not a function + var _nonce_function_bip340Ptr = NativeLibrary.GetExport(lib, SYM_nonce_function_bip340); + _nonce_function_bip340 = (FnPtr32)Marshal.ReadIntPtr(_nonce_function_bip340Ptr); + + // secp256k1_ellswift_xdh_hash_function_prefix is a data symbol (function pointer), not a function + var _ellswift_xdh_hash_function_prefixPtr = NativeLibrary.GetExport(lib, SYM_ellswift_xdh_hash_function_prefix); + _ellswift_xdh_hash_function_prefix = (FnPtr33)Marshal.ReadIntPtr(_ellswift_xdh_hash_function_prefixPtr); + + // secp256k1_ellswift_xdh_hash_function_bip324 is a data symbol (function pointer), not a function + var _ellswift_xdh_hash_function_bip324Ptr = NativeLibrary.GetExport(lib, SYM_ellswift_xdh_hash_function_bip324); + _ellswift_xdh_hash_function_bip324 = (FnPtr33)Marshal.ReadIntPtr(_ellswift_xdh_hash_function_bip324Ptr); +#else + _selftest = LoadLibNative.GetDelegate(lib, SYM_selftest); + _context_create = LoadLibNative.GetDelegate(lib, SYM_context_create); + _context_clone = LoadLibNative.GetDelegate(lib, SYM_context_clone); + _context_destroy = LoadLibNative.GetDelegate(lib, SYM_context_destroy); + _context_set_illegal_callback = LoadLibNative.GetDelegate(lib, SYM_context_set_illegal_callback); + _context_set_error_callback = LoadLibNative.GetDelegate(lib, SYM_context_set_error_callback); + _ec_pubkey_parse = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_parse); + _ec_pubkey_serialize = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_serialize); + _ec_pubkey_cmp = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_cmp); + _ec_pubkey_sort = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_sort); + _ecdsa_signature_parse_compact = LoadLibNative.GetDelegate(lib, SYM_ecdsa_signature_parse_compact); + _ecdsa_signature_parse_der = LoadLibNative.GetDelegate(lib, SYM_ecdsa_signature_parse_der); + _ecdsa_signature_serialize_der = LoadLibNative.GetDelegate(lib, SYM_ecdsa_signature_serialize_der); + _ecdsa_signature_serialize_compact = LoadLibNative.GetDelegate(lib, SYM_ecdsa_signature_serialize_compact); + _ecdsa_verify = LoadLibNative.GetDelegate(lib, SYM_ecdsa_verify); + _ecdsa_signature_normalize = LoadLibNative.GetDelegate(lib, SYM_ecdsa_signature_normalize); + _ecdsa_sign = LoadLibNative.GetDelegate(lib, SYM_ecdsa_sign); + _ec_seckey_verify = LoadLibNative.GetDelegate(lib, SYM_ec_seckey_verify); + _ec_pubkey_create = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_create); + _ec_seckey_negate = LoadLibNative.GetDelegate(lib, SYM_ec_seckey_negate); + _ec_pubkey_negate = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_negate); + _ec_seckey_tweak_add = LoadLibNative.GetDelegate(lib, SYM_ec_seckey_tweak_add); + _ec_pubkey_tweak_add = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_tweak_add); + _ec_seckey_tweak_mul = LoadLibNative.GetDelegate(lib, SYM_ec_seckey_tweak_mul); + _ec_pubkey_tweak_mul = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_tweak_mul); + _context_randomize = LoadLibNative.GetDelegate(lib, SYM_context_randomize); + _ec_pubkey_combine = LoadLibNative.GetDelegate(lib, SYM_ec_pubkey_combine); + _tagged_sha256 = LoadLibNative.GetDelegate(lib, SYM_tagged_sha256); + _context_preallocated_size = LoadLibNative.GetDelegate(lib, SYM_context_preallocated_size); + _context_preallocated_create = LoadLibNative.GetDelegate(lib, SYM_context_preallocated_create); + _context_preallocated_clone_size = LoadLibNative.GetDelegate(lib, SYM_context_preallocated_clone_size); + _context_preallocated_clone = LoadLibNative.GetDelegate(lib, SYM_context_preallocated_clone); + _context_preallocated_destroy = LoadLibNative.GetDelegate(lib, SYM_context_preallocated_destroy); + _ecdsa_recoverable_signature_parse_compact = LoadLibNative.GetDelegate(lib, SYM_ecdsa_recoverable_signature_parse_compact); + _ecdsa_recoverable_signature_convert = LoadLibNative.GetDelegate(lib, SYM_ecdsa_recoverable_signature_convert); + _ecdsa_recoverable_signature_serialize_compact = LoadLibNative.GetDelegate(lib, SYM_ecdsa_recoverable_signature_serialize_compact); + _ecdsa_sign_recoverable = LoadLibNative.GetDelegate(lib, SYM_ecdsa_sign_recoverable); + _ecdsa_recover = LoadLibNative.GetDelegate(lib, SYM_ecdsa_recover); + _ecdh = LoadLibNative.GetDelegate(lib, SYM_ecdh); + _xonly_pubkey_parse = LoadLibNative.GetDelegate(lib, SYM_xonly_pubkey_parse); + _xonly_pubkey_serialize = LoadLibNative.GetDelegate(lib, SYM_xonly_pubkey_serialize); + _xonly_pubkey_cmp = LoadLibNative.GetDelegate(lib, SYM_xonly_pubkey_cmp); + _xonly_pubkey_from_pubkey = LoadLibNative.GetDelegate(lib, SYM_xonly_pubkey_from_pubkey); + _xonly_pubkey_tweak_add = LoadLibNative.GetDelegate(lib, SYM_xonly_pubkey_tweak_add); + _xonly_pubkey_tweak_add_check = LoadLibNative.GetDelegate(lib, SYM_xonly_pubkey_tweak_add_check); + _keypair_create = LoadLibNative.GetDelegate(lib, SYM_keypair_create); + _keypair_sec = LoadLibNative.GetDelegate(lib, SYM_keypair_sec); + _keypair_pub = LoadLibNative.GetDelegate(lib, SYM_keypair_pub); + _keypair_xonly_pub = LoadLibNative.GetDelegate(lib, SYM_keypair_xonly_pub); + _keypair_xonly_tweak_add = LoadLibNative.GetDelegate(lib, SYM_keypair_xonly_tweak_add); + _schnorrsig_sign32 = LoadLibNative.GetDelegate(lib, SYM_schnorrsig_sign32); + _schnorrsig_sign = LoadLibNative.GetDelegate(lib, SYM_schnorrsig_sign); + _schnorrsig_sign_custom = LoadLibNative.GetDelegate(lib, SYM_schnorrsig_sign_custom); + _schnorrsig_verify = LoadLibNative.GetDelegate(lib, SYM_schnorrsig_verify); + _ellswift_encode = LoadLibNative.GetDelegate(lib, SYM_ellswift_encode); + _ellswift_decode = LoadLibNative.GetDelegate(lib, SYM_ellswift_decode); + _ellswift_create = LoadLibNative.GetDelegate(lib, SYM_ellswift_create); + _ellswift_xdh = LoadLibNative.GetDelegate(lib, SYM_ellswift_xdh); + _musig_pubnonce_parse = LoadLibNative.GetDelegate(lib, SYM_musig_pubnonce_parse); + _musig_pubnonce_serialize = LoadLibNative.GetDelegate(lib, SYM_musig_pubnonce_serialize); + _musig_aggnonce_parse = LoadLibNative.GetDelegate(lib, SYM_musig_aggnonce_parse); + _musig_aggnonce_serialize = LoadLibNative.GetDelegate(lib, SYM_musig_aggnonce_serialize); + _musig_partial_sig_parse = LoadLibNative.GetDelegate(lib, SYM_musig_partial_sig_parse); + _musig_partial_sig_serialize = LoadLibNative.GetDelegate(lib, SYM_musig_partial_sig_serialize); + _musig_pubkey_agg = LoadLibNative.GetDelegate(lib, SYM_musig_pubkey_agg); + _musig_pubkey_get = LoadLibNative.GetDelegate(lib, SYM_musig_pubkey_get); + _musig_pubkey_ec_tweak_add = LoadLibNative.GetDelegate(lib, SYM_musig_pubkey_ec_tweak_add); + _musig_pubkey_xonly_tweak_add = LoadLibNative.GetDelegate(lib, SYM_musig_pubkey_xonly_tweak_add); + _musig_nonce_gen = LoadLibNative.GetDelegate(lib, SYM_musig_nonce_gen); + _musig_nonce_gen_counter = LoadLibNative.GetDelegate(lib, SYM_musig_nonce_gen_counter); + _musig_nonce_agg = LoadLibNative.GetDelegate(lib, SYM_musig_nonce_agg); + _musig_nonce_process = LoadLibNative.GetDelegate(lib, SYM_musig_nonce_process); + _musig_partial_sign = LoadLibNative.GetDelegate(lib, SYM_musig_partial_sign); + _musig_partial_sig_verify = LoadLibNative.GetDelegate(lib, SYM_musig_partial_sig_verify); + _musig_partial_sig_agg = LoadLibNative.GetDelegate(lib, SYM_musig_partial_sig_agg); + + // secp256k1_nonce_function_rfc6979 is a data symbol (function pointer), not a function + _nonce_function_rfc6979 = LoadLibNative.GetDelegate(lib, SYM_nonce_function_rfc6979, Marshal.ReadIntPtr); + + // secp256k1_nonce_function_default is a data symbol (function pointer), not a function + _nonce_function_default = LoadLibNative.GetDelegate(lib, SYM_nonce_function_default, Marshal.ReadIntPtr); + + // secp256k1_ecdh_hash_function_sha256 is a data symbol (function pointer), not a function + _ecdh_hash_function_sha256 = LoadLibNative.GetDelegate(lib, SYM_ecdh_hash_function_sha256, Marshal.ReadIntPtr); + + // secp256k1_ecdh_hash_function_default is a data symbol (function pointer), not a function + _ecdh_hash_function_default = LoadLibNative.GetDelegate(lib, SYM_ecdh_hash_function_default, Marshal.ReadIntPtr); + + // secp256k1_nonce_function_bip340 is a data symbol (function pointer), not a function + _nonce_function_bip340 = LoadLibNative.GetDelegate(lib, SYM_nonce_function_bip340, Marshal.ReadIntPtr); + + // secp256k1_ellswift_xdh_hash_function_prefix is a data symbol (function pointer), not a function + _ellswift_xdh_hash_function_prefix = LoadLibNative.GetDelegate(lib, SYM_ellswift_xdh_hash_function_prefix, Marshal.ReadIntPtr); + + // secp256k1_ellswift_xdh_hash_function_bip324 is a data symbol (function pointer), not a function + _ellswift_xdh_hash_function_bip324 = LoadLibNative.GetDelegate(lib, SYM_ellswift_xdh_hash_function_bip324, Marshal.ReadIntPtr); +#endif + } + } +} diff --git a/Secp256k1.Net/Generated/Secp256k1.Wrappers.g.cs b/Secp256k1.Net/Generated/Secp256k1.Wrappers.g.cs new file mode 100644 index 0000000..84a073a --- /dev/null +++ b/Secp256k1.Net/Generated/Secp256k1.Wrappers.g.cs @@ -0,0 +1,1708 @@ +// +#nullable enable + +using System; +using System.Runtime.InteropServices; + +namespace Secp256k1Net +{ + + /// Flags for public key serialization format. + public enum Secp256k1EcFlags : uint + { + /// Compressed format (33 bytes). + Compressed = 258, + /// Uncompressed format (65 bytes). + Uncompressed = 2, + } + + /// Flags for secp256k1 context creation. + public enum Secp256k1ContextFlags : uint + { + /// Creates a context sufficient for all functionality. + None = 1, + } + + /// A pointer to a function to deterministically generate a nonce. + /// pointer to a 32-byte array to be filled by the function. + /// the 32-byte message hash being verified (will not be NULL) + /// pointer to a 32-byte secret key (will not be NULL) + /// pointer to a 16-byte array describing the signature algorithm (will be NULL for ECDSA for compatibility). + /// Arbitrary data pointer that is passed through. + /// how many iterations we have tried to find a nonce. This will almost always be 0, but different attempt values are required to result in a different nonce.Except for test cases, this function should compute some cryptographic hash of the message, the algorithm, the key and the attempt. + /// 1 on success, 0 on failure. + public delegate int NonceFunction(Span nonce32, ReadOnlySpan msg32, ReadOnlySpan key32, ReadOnlySpan algo16, IntPtr data, uint attempt); + + /// A pointer to a function that hashes an EC point to obtain an ECDH secret + /// pointer to an array to be filled by the function + /// pointer to a 32-byte x coordinate + /// pointer to a 32-byte y coordinate + /// arbitrary data pointer that is passed through + /// 1 on success, 0 on failure. + public delegate int EcdhHashFunction(Span output, ReadOnlySpan x32, ReadOnlySpan y32, IntPtr data); + + /// A pointer to a function to deterministically generate a nonce.Same as secp256k1_nonce function with the exception of accepting an additional pubkey argument and not requiring an attempt argument. The pubkey argument can protect signature schemes with key-prefixed challenge hash inputs against reusing the nonce when signing with the wrong precomputed pubkey. + /// pointer to a 32-byte array to be filled by the function + /// the message being verified. Is NULL if and only if msglen is 0. + /// the length of the message + /// pointer to a 32-byte secret key (will not be NULL) + /// the 32-byte serialized xonly pubkey corresponding to key32 (will not be NULL) + /// pointer to an array describing the signature algorithm (will not be NULL) + /// the length of the algo array + /// arbitrary data pointer that is passed throughExcept for test cases, this function should compute some cryptographic hash of the message, the key, the pubkey, the algorithm description, and data. + /// 1 on success, 0 on failure. + public delegate int NonceFunctionHardened(Span nonce32, ReadOnlySpan msg, nuint msglen, ReadOnlySpan key32, ReadOnlySpan xonly_pk32, ReadOnlySpan algo, nuint algolen, IntPtr data); + + /// A pointer to a function used by secp256k1_ellswift_xdh to hash the shared X coordinate along with the encoded public keys to a uniform shared secret. + /// pointer to an array to be filled by the function + /// pointer to the 32-byte serialized X coordinate of the resulting shared point (will not be NULL) + /// pointer to the 64-byte encoded public key of party A (will not be NULL) + /// pointer to the 64-byte encoded public key of party B (will not be NULL) + /// arbitrary data pointer that is passed through + /// 1 on success, 0 on failure. + public delegate int EllswiftXdhHashFunction(Span output, ReadOnlySpan x32, ReadOnlySpan ell_a64, ReadOnlySpan ell_b64, IntPtr data); + + public unsafe partial class Secp256k1 + { + + /// Perform basic self tests (to be used in conjunction with secp256k1_context_static)This function performs self tests that detect some serious usage errors and similar conditions, e.g., when the library is compiled for the wrong endianness. This is a last resort measure to be used in production. The performed tests are very rudimentary and are not intended as a replacement for running the test binaries.It is highly recommended to call this before using secp256k1_context_static. It is not necessary to call this function before using a context created with secp256k1_context_create (or secp256k1_context_preallocated_create), which will take care of performing the self tests.If the tests fail, this function will call the default error callback to abort the program (see secp256k1_context_set_error_callback). + public void Selftest() + { + Secp256k1Interop._selftest(); + } + + /// Parse a variable-length public key into the pubkey object. + /// pointer to a pubkey object. If 1 is returned, it is set to a parsed version of input. If not, its value is undefined. + /// pointer to a serialized public key + /// 1 if the public key was fully valid. 0 if the public key could not be parsed or is invalid. + public bool EcPubkeyParse(Span pubkey, ReadOnlySpan input) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + inputPtr = input) + { + return Secp256k1Interop._ec_pubkey_parse(_ctx, pubkeyPtr, inputPtr, (nuint)input.Length) == 1; + } + } + + /// Serialize a pubkey object into a serialized byte sequence. + /// pointer to a 65-byte (if compressed==0) or 33-byte (if compressed==1) byte array to place the serialized key in. + /// pointer to an integer which is initially set to the size of output, and is overwritten with the written size. + /// pointer to a secp256k1_pubkey containing an initialized public key. + /// SECP256K1_EC_COMPRESSED if serialization should be in compressed format, otherwise SECP256K1_EC_UNCOMPRESSED. + /// 1 always. + public bool EcPubkeySerialize(Span output, ref nuint outputlen, ReadOnlySpan pubkey, Secp256k1EcFlags flags) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + var requiredOutputSize = flags == Secp256k1EcFlags.Compressed ? 33 : 65; + if (output.Length < requiredOutputSize) + throw new ArgumentException($"{nameof(output)} must be at least {requiredOutputSize} bytes for the specified flags"); + + fixed (byte* outputPtr = output, + pubkeyPtr = pubkey) + fixed (nuint* outputlenPtr = &outputlen) + { + return Secp256k1Interop._ec_pubkey_serialize(_ctx, outputPtr, outputlenPtr, pubkeyPtr, (uint)flags) == 1; + } + } + + /// Compare two public keys using lexicographic (of compressed serialization) order + /// first public key to compare + /// second public key to compare + /// <0 if the first public key is less than the second >0 if the first public key is greater than the second 0 if the two public keys are equal + public int EcPubkeyCmp(ReadOnlySpan pubkey1, ReadOnlySpan pubkey2) + { + if (pubkey1.Length < 64) + throw new ArgumentException($"{nameof(pubkey1)} must be at least 64 bytes"); + if (pubkey2.Length < 64) + throw new ArgumentException($"{nameof(pubkey2)} must be at least 64 bytes"); + + fixed (byte* pubkey1Ptr = pubkey1, + pubkey2Ptr = pubkey2) + { + return Secp256k1Interop._ec_pubkey_cmp(_ctx, pubkey1Ptr, pubkey2Ptr); + } + } + + /// Parse an ECDSA signature in compact (64 bytes) format. + /// pointer to a signature object + /// pointer to the 64-byte array to parseThe signature must consist of a 32-byte big endian R value, followed by a 32-byte big endian S value. If R or S fall outside of [0..order-1], the encoding is invalid. R and S with value 0 are allowed in the encoding.After the call, sig will always be initialized. If parsing failed or R or S are zero, the resulting sig value is guaranteed to fail verification for any message and public key. + /// 1 when the signature could be parsed, 0 otherwise. + public bool EcdsaSignatureParseCompact(Span sig, ReadOnlySpan input64) + { + if (sig.Length < 64) + throw new ArgumentException($"{nameof(sig)} must be at least 64 bytes"); + if (input64.Length < 64) + throw new ArgumentException($"{nameof(input64)} must be at least 64 bytes"); + + fixed (byte* sigPtr = sig, + input64Ptr = input64) + { + return Secp256k1Interop._ecdsa_signature_parse_compact(_ctx, sigPtr, input64Ptr) == 1; + } + } + + /// Parse a DER ECDSA signature. + /// pointer to a signature object + /// pointer to the signature to be parsed + /// 1 when the signature could be parsed, 0 otherwise. + public bool EcdsaSignatureParseDer(Span sig, ReadOnlySpan input) + { + if (sig.Length < 64) + throw new ArgumentException($"{nameof(sig)} must be at least 64 bytes"); + + fixed (byte* sigPtr = sig, + inputPtr = input) + { + return Secp256k1Interop._ecdsa_signature_parse_der(_ctx, sigPtr, inputPtr, (nuint)input.Length) == 1; + } + } + + /// Serialize an ECDSA signature in DER format. + /// pointer to an array to store the DER serialization + /// pointer to a length integer. Initially, this integer should be set to the length of output. After the call it will be set to the length of the serialization (even if 0 was returned). + /// pointer to an initialized signature object + /// 1 if enough space was available to serialize, 0 otherwise + public bool EcdsaSignatureSerializeDer(Span output, ref nuint outputlen, ReadOnlySpan sig) + { + if (sig.Length < 64) + throw new ArgumentException($"{nameof(sig)} must be at least 64 bytes"); + + fixed (byte* outputPtr = output, + sigPtr = sig) + fixed (nuint* outputlenPtr = &outputlen) + { + return Secp256k1Interop._ecdsa_signature_serialize_der(_ctx, outputPtr, outputlenPtr, sigPtr) == 1; + } + } + + /// Serialize an ECDSA signature in compact (64 byte) format. + /// pointer to a 64-byte array to store the compact serialization + /// pointer to an initialized signature objectSee secp256k1_ecdsa_signature_parse_compact for details about the encoding. + /// 1 + public bool EcdsaSignatureSerializeCompact(Span output64, ReadOnlySpan sig) + { + if (output64.Length < 64) + throw new ArgumentException($"{nameof(output64)} must be at least 64 bytes"); + if (sig.Length < 64) + throw new ArgumentException($"{nameof(sig)} must be at least 64 bytes"); + + fixed (byte* output64Ptr = output64, + sigPtr = sig) + { + return Secp256k1Interop._ecdsa_signature_serialize_compact(_ctx, output64Ptr, sigPtr) == 1; + } + } + + /// Verify an ECDSA signature. + /// the signature being verified. + /// the 32-byte message hash being verified. The verifier must make sure to apply a cryptographic hash function to the message by itself and not accept an msghash32 value directly. Otherwise, it would be easy to create a "valid" signature without knowledge of the secret key. See also https://bitcoin.stackexchange.com/a/81116/35586 for more background on this topic. + /// pointer to an initialized public key to verify with.To avoid accepting malleable signatures, only ECDSA signatures in lower-S form are accepted.If you need to accept ECDSA signatures from sources that do not obey this rule, apply secp256k1_ecdsa_signature_normalize to the signature prior to verification, but be aware that doing so results in malleable signatures.For details, see the comments for that function. + /// 1: correct signature 0: incorrect or unparseable signature + public bool EcdsaVerify(ReadOnlySpan sig, ReadOnlySpan msghash32, ReadOnlySpan pubkey) + { + if (sig.Length < 64) + throw new ArgumentException($"{nameof(sig)} must be at least 64 bytes"); + if (msghash32.Length < 32) + throw new ArgumentException($"{nameof(msghash32)} must be at least 32 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + + fixed (byte* sigPtr = sig, + msghash32Ptr = msghash32, + pubkeyPtr = pubkey) + { + return Secp256k1Interop._ecdsa_verify(_ctx, sigPtr, msghash32Ptr, pubkeyPtr) == 1; + } + } + + /// Convert a signature to a normalized lower-S form. + /// pointer to a signature to fill with the normalized form, or copy if the input was already normalized. (can be NULL if you're only interested in whether the input was already normalized). + /// pointer to a signature to check/normalize (can be identical to sigout)With ECDSA a third-party can forge a second distinct signature of the same message, given a single initial signature, but without knowing the key. This is done by negating the S value modulo the order of the curve, 'flipping' the sign of the random point R which is not included in the signature.Forgery of the same message isn't universally problematic, but in systems where message malleability or uniqueness of signatures is important this can cause issues. This forgery can be blocked by all verifiers forcing signers to use a normalized form.The lower-S form reduces the size of signatures slightly on average when variable length encodings (such as DER) are used and is cheap to verify, making it a good choice. Security of always using lower-S is assured because anyone can trivially modify a signature after the fact to enforce this property anyway.The lower S value is always between 0x1 and 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, inclusive.No other forms of ECDSA malleability are known and none seem likely, but there is no formal proof that ECDSA, even with this additional restriction, is free of other malleability. Commonly used serialization schemes will also accept various non-unique encodings, so care should be taken when this property is required for an application.The secp256k1_ecdsa_sign function will by default create signatures in the lower-S form, and secp256k1_ecdsa_verify will not accept others. In case signatures come from a system that cannot enforce this property, secp256k1_ecdsa_signature_normalize must be called before verification. + /// 1 if sigin was not normalized, 0 if it already was. + public bool EcdsaSignatureNormalize(Span sigout, ReadOnlySpan sigin) + { + if (sigout.Length < 64) + throw new ArgumentException($"{nameof(sigout)} must be at least 64 bytes"); + if (sigin.Length < 64) + throw new ArgumentException($"{nameof(sigin)} must be at least 64 bytes"); + + fixed (byte* sigoutPtr = sigout, + siginPtr = sigin) + { + return Secp256k1Interop._ecdsa_signature_normalize(_ctx, sigoutPtr, siginPtr) == 1; + } + } + + /// Create an ECDSA signature. + /// pointer to an array where the signature will be placed. + /// the 32-byte message hash being signed. + /// pointer to a 32-byte secret key. + /// 1: signature created 0: the nonce generation function failed, or the secret key was invalid. + public bool EcdsaSign(Span sig, ReadOnlySpan msghash32, ReadOnlySpan seckey) + { + if (sig.Length < 64) + throw new ArgumentException($"{nameof(sig)} must be at least 64 bytes"); + if (msghash32.Length < 32) + throw new ArgumentException($"{nameof(msghash32)} must be at least 32 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + fixed (byte* sigPtr = sig, + msghash32Ptr = msghash32, + seckeyPtr = seckey) + { + return Secp256k1Interop._ecdsa_sign(_ctx, sigPtr, msghash32Ptr, seckeyPtr, IntPtr.Zero, IntPtr.Zero.ToPointer()) == 1; + } + } + + /// Create an ECDSA signature. + /// pointer to an array where the signature will be placed. + /// the 32-byte message hash being signed. + /// pointer to a 32-byte secret key. + /// pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used. + /// pointer to arbitrary data used by the nonce generation function (can be NULL). If it is non-NULL and secp256k1_nonce_function_default is used, then ndata must be a pointer to 32-bytes of additional data.The created signature is always in lower-S form. See secp256k1_ecdsa_signature_normalize for more details. + /// 1: signature created 0: the nonce generation function failed, or the secret key was invalid. + public bool EcdsaSign(Span sig, ReadOnlySpan msghash32, ReadOnlySpan seckey, NonceFunction noncefp, IntPtr ndata) + { + if (sig.Length < 64) + throw new ArgumentException($"{nameof(sig)} must be at least 64 bytes"); + if (msghash32.Length < 32) + throw new ArgumentException($"{nameof(msghash32)} must be at least 32 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + secp256k1_nonce_function nativeCallback = (void* nonce32, void* msg32, void* key32, void* algo16, void* data, uint attempt) => + { + var nonce32Span = new Span(nonce32, 32); + var msg32Span = msg32 != null ? new ReadOnlySpan(msg32, 32) : ReadOnlySpan.Empty; + var key32Span = key32 != null ? new ReadOnlySpan(key32, 32) : ReadOnlySpan.Empty; + var algo16Span = algo16 != null ? new ReadOnlySpan(algo16, 16) : ReadOnlySpan.Empty; + return noncefp(nonce32Span, msg32Span, key32Span, algo16Span, (IntPtr)data, attempt); + }; + + var callbackPtr = Marshal.GetFunctionPointerForDelegate(nativeCallback); + + fixed (byte* sigPtr = sig, + msghash32Ptr = msghash32, + seckeyPtr = seckey) + { + return Secp256k1Interop._ecdsa_sign(_ctx, sigPtr, msghash32Ptr, seckeyPtr, callbackPtr, ndata.ToPointer()) == 1; + } + } + + /// Verify an elliptic curve secret key.A secret key is valid if it is not 0 and less than the secp256k1 curve order when interpreted as an integer (most significant byte first). The probability of choosing a 32-byte string uniformly at random which is an invalid secret key is negligible. However, if it does happen it should be assumed that the randomness source is severely broken and there should be no retry. + /// pointer to a 32-byte secret key. + /// 1: secret key is valid 0: secret key is invalid + public bool EcSeckeyVerify(ReadOnlySpan seckey) + { + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + fixed (byte* seckeyPtr = seckey) + { + return Secp256k1Interop._ec_seckey_verify(_ctx, seckeyPtr) == 1; + } + } + + /// Compute the public key for a secret key. + /// pointer to the created public key. + /// pointer to a 32-byte secret key. + /// 1: secret was valid, public key stores. 0: secret was invalid, try again. + public bool EcPubkeyCreate(Span pubkey, ReadOnlySpan seckey) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + seckeyPtr = seckey) + { + return Secp256k1Interop._ec_pubkey_create(_ctx, pubkeyPtr, seckeyPtr) == 1; + } + } + + /// Negates a secret key in place. + /// pointer to the 32-byte secret key to be negated. If the secret key is invalid according to secp256k1_ec_seckey_verify, this function returns 0 and seckey will be set to some unspecified value. + /// 0 if the given secret key is invalid according to secp256k1_ec_seckey_verify. 1 otherwise + public bool EcSeckeyNegate(Span seckey) + { + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + fixed (byte* seckeyPtr = seckey) + { + return Secp256k1Interop._ec_seckey_negate(_ctx, seckeyPtr) == 1; + } + } + + /// Negates a public key in place. + /// pointer to the public key to be negated. + /// 1 always + public bool EcPubkeyNegate(Span pubkey) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + + fixed (byte* pubkeyPtr = pubkey) + { + return Secp256k1Interop._ec_pubkey_negate(_ctx, pubkeyPtr) == 1; + } + } + + /// Tweak a secret key by adding tweak to it. + /// pointer to a 32-byte secret key. If the secret key is invalid according to secp256k1_ec_seckey_verify, this function returns 0. seckey will be set to some unspecified value if this function returns 0. + /// pointer to a 32-byte tweak, which must be valid according to secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly random 32-byte tweaks, the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid or the resulting secret key would be invalid (only when the tweak is the negation of the secret key). 1 otherwise. + public bool EcSeckeyTweakAdd(Span seckey, ReadOnlySpan tweak32) + { + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* seckeyPtr = seckey, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._ec_seckey_tweak_add(_ctx, seckeyPtr, tweak32Ptr) == 1; + } + } + + /// Tweak a public key by adding tweak times the generator to it. + /// pointer to a public key object. pubkey will be set to an invalid value if this function returns 0. + /// pointer to a 32-byte tweak, which must be valid according to secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly random 32-byte tweaks, the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid or the resulting public key would be invalid (only when the tweak is the negation of the corresponding secret key). 1 otherwise. + public bool EcPubkeyTweakAdd(Span pubkey, ReadOnlySpan tweak32) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._ec_pubkey_tweak_add(_ctx, pubkeyPtr, tweak32Ptr) == 1; + } + } + + /// Tweak a secret key by multiplying it by a tweak. + /// pointer to a 32-byte secret key. If the secret key is invalid according to secp256k1_ec_seckey_verify, this function returns 0. seckey will be set to some unspecified value if this function returns 0. + /// pointer to a 32-byte tweak. If the tweak is invalid according to secp256k1_ec_seckey_verify, this function returns 0. For uniformly random 32-byte arrays the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid. 1 otherwise. + public bool EcSeckeyTweakMul(Span seckey, ReadOnlySpan tweak32) + { + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* seckeyPtr = seckey, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._ec_seckey_tweak_mul(_ctx, seckeyPtr, tweak32Ptr) == 1; + } + } + + /// Tweak a public key by multiplying it by a tweak value. + /// pointer to a public key object. pubkey will be set to an invalid value if this function returns 0. + /// pointer to a 32-byte tweak. If the tweak is invalid according to secp256k1_ec_seckey_verify, this function returns 0. For uniformly random 32-byte arrays the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid. 1 otherwise. + public bool EcPubkeyTweakMul(Span pubkey, ReadOnlySpan tweak32) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._ec_pubkey_tweak_mul(_ctx, pubkeyPtr, tweak32Ptr) == 1; + } + } + + /// Add a number of public keys together. + /// pointer to a public key object for placing the resulting public key. + /// pointer to array of pointers to public keys. + /// 1: the sum of the public keys is valid. 0: the sum of the public keys is not valid. + public bool EcPubkeyCombine(Span @out, byte[][] ins) + { + if (ins == null || ins.Length == 0) + throw new ArgumentException($"{nameof(ins)} must not be null or empty"); + for (int i = 0; i < ins.Length; i++) + { + if (ins[i] == null || ins[i].Length < 64) + throw new ArgumentException($"{nameof(ins)}[{i}] must be at least 64 bytes"); + } + if (@out.Length < 64) + throw new ArgumentException($"{nameof(@out)} must be at least 64 bytes"); + + var count = ins.Length; + Span nativePtrArray = stackalloc nint[count]; + fixed (byte* @outPtr = @out) + { + var handles = new GCHandle[count]; + try + { + for (int i = 0; i < count; i++) + { + handles[i] = GCHandle.Alloc(ins[i], GCHandleType.Pinned); + nativePtrArray[i] = handles[i].AddrOfPinnedObject(); + } + + fixed (nint* nativePtrArrayPtr = nativePtrArray) + { + return Secp256k1Interop._ec_pubkey_combine(_ctx, @outPtr, (IntPtr)nativePtrArrayPtr, (nuint)count) == 1; + } + } + finally + { + for (int i = 0; i < count; i++) + { + if (handles[i].IsAllocated) + handles[i].Free(); + } + } + } + } + + /// Compute a tagged hash as defined in BIP-340.This is useful for creating a message hash and achieving domain separation through an application-specific tag. This function returns SHA256(SHA256(tag)||SHA256(tag)||msg). Therefore, tagged hash implementations optimized for a specific tag can precompute the SHA256 state after hashing the tag hashes. + /// pointer to a 32-byte array to store the resulting hash + /// pointer to an array containing the tag + /// pointer to an array containing the message + /// 1 always. + public bool TaggedSha256(Span hash32, ReadOnlySpan tag, ReadOnlySpan msg) + { + if (hash32.Length < 32) + throw new ArgumentException($"{nameof(hash32)} must be at least 32 bytes"); + + fixed (byte* hash32Ptr = hash32, + tagPtr = tag, + msgPtr = msg) + { + return Secp256k1Interop._tagged_sha256(_ctx, hash32Ptr, tagPtr, (nuint)tag.Length, msgPtr, (nuint)msg.Length) == 1; + } + } + + /// Parse a compact ECDSA signature (64 bytes + recovery id). + /// pointer to a signature object + /// pointer to a 64-byte compact signature + /// the recovery id (0, 1, 2 or 3) + /// 1 when the signature could be parsed, 0 otherwise + public bool EcdsaRecoverableSignatureParseCompact(Span sig, ReadOnlySpan input64, int recid) + { + if (sig.Length < 65) + throw new ArgumentException($"{nameof(sig)} must be at least 65 bytes"); + if (input64.Length < 64) + throw new ArgumentException($"{nameof(input64)} must be at least 64 bytes"); + + fixed (byte* sigPtr = sig, + input64Ptr = input64) + { + return Secp256k1Interop._ecdsa_recoverable_signature_parse_compact(_ctx, sigPtr, input64Ptr, recid) == 1; + } + } + + /// Convert a recoverable signature into a normal signature. + /// pointer to a normal signature. + /// pointer to a recoverable signature. + /// 1 + public bool EcdsaRecoverableSignatureConvert(Span sig, ReadOnlySpan sigin) + { + if (sig.Length < 64) + throw new ArgumentException($"{nameof(sig)} must be at least 64 bytes"); + if (sigin.Length < 65) + throw new ArgumentException($"{nameof(sigin)} must be at least 65 bytes"); + + fixed (byte* sigPtr = sig, + siginPtr = sigin) + { + return Secp256k1Interop._ecdsa_recoverable_signature_convert(_ctx, sigPtr, siginPtr) == 1; + } + } + + /// Serialize an ECDSA signature in compact format (64 bytes + recovery id). + /// pointer to a 64-byte array of the compact signature. + /// pointer to an integer to hold the recovery id. + /// pointer to an initialized signature object. + /// 1 + public bool EcdsaRecoverableSignatureSerializeCompact(Span output64, out int recid, ReadOnlySpan sig) + { + if (output64.Length < 64) + throw new ArgumentException($"{nameof(output64)} must be at least 64 bytes"); + if (sig.Length < 65) + throw new ArgumentException($"{nameof(sig)} must be at least 65 bytes"); + + fixed (byte* output64Ptr = output64, + sigPtr = sig) + fixed (int* recidPtr = &recid) + { + return Secp256k1Interop._ecdsa_recoverable_signature_serialize_compact(_ctx, output64Ptr, recidPtr, sigPtr) == 1; + } + } + + /// Create a recoverable ECDSA signature. + /// pointer to an array where the signature will be placed. + /// the 32-byte message hash being signed. + /// pointer to a 32-byte secret key. + /// 1: signature created 0: the nonce generation function failed, or the secret key was invalid. + public bool EcdsaSignRecoverable(Span sig, ReadOnlySpan msghash32, ReadOnlySpan seckey) + { + if (sig.Length < 65) + throw new ArgumentException($"{nameof(sig)} must be at least 65 bytes"); + if (msghash32.Length < 32) + throw new ArgumentException($"{nameof(msghash32)} must be at least 32 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + fixed (byte* sigPtr = sig, + msghash32Ptr = msghash32, + seckeyPtr = seckey) + { + return Secp256k1Interop._ecdsa_sign_recoverable(_ctx, sigPtr, msghash32Ptr, seckeyPtr, IntPtr.Zero, IntPtr.Zero.ToPointer()) == 1; + } + } + + /// Create a recoverable ECDSA signature. + /// pointer to an array where the signature will be placed. + /// the 32-byte message hash being signed. + /// pointer to a 32-byte secret key. + /// pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used. + /// pointer to arbitrary data used by the nonce generation function (can be NULL for secp256k1_nonce_function_default). + /// 1: signature created 0: the nonce generation function failed, or the secret key was invalid. + public bool EcdsaSignRecoverable(Span sig, ReadOnlySpan msghash32, ReadOnlySpan seckey, NonceFunction noncefp, IntPtr ndata) + { + if (sig.Length < 65) + throw new ArgumentException($"{nameof(sig)} must be at least 65 bytes"); + if (msghash32.Length < 32) + throw new ArgumentException($"{nameof(msghash32)} must be at least 32 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + secp256k1_nonce_function nativeCallback = (void* nonce32, void* msg32, void* key32, void* algo16, void* data, uint attempt) => + { + var nonce32Span = new Span(nonce32, 32); + var msg32Span = msg32 != null ? new ReadOnlySpan(msg32, 32) : ReadOnlySpan.Empty; + var key32Span = key32 != null ? new ReadOnlySpan(key32, 32) : ReadOnlySpan.Empty; + var algo16Span = algo16 != null ? new ReadOnlySpan(algo16, 16) : ReadOnlySpan.Empty; + return noncefp(nonce32Span, msg32Span, key32Span, algo16Span, (IntPtr)data, attempt); + }; + + var callbackPtr = Marshal.GetFunctionPointerForDelegate(nativeCallback); + + fixed (byte* sigPtr = sig, + msghash32Ptr = msghash32, + seckeyPtr = seckey) + { + return Secp256k1Interop._ecdsa_sign_recoverable(_ctx, sigPtr, msghash32Ptr, seckeyPtr, callbackPtr, ndata.ToPointer()) == 1; + } + } + + /// Recover an ECDSA public key from a signature.Successful public key recovery guarantees that the signature, after normalization, passes `secp256k1_ecdsa_verify`. Thus, explicit verification is not necessary.However, a recoverable signature that successfully passes `secp256k1_ecdsa_recover`, when converted to a non-recoverable signature (using `secp256k1_ecdsa_recoverable_signature_convert`), is not guaranteed to be normalized and thus not guaranteed to pass `secp256k1_ecdsa_verify`. If a normalized signature is required, call `secp256k1_ecdsa_signature_normalize` after `secp256k1_ecdsa_recoverable_signature_convert`. + /// pointer to the recovered public key. + /// pointer to initialized signature that supports pubkey recovery. + /// the 32-byte message hash assumed to be signed. + /// 1: public key successfully recovered 0: otherwise. + public bool EcdsaRecover(Span pubkey, ReadOnlySpan sig, ReadOnlySpan msghash32) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (sig.Length < 65) + throw new ArgumentException($"{nameof(sig)} must be at least 65 bytes"); + if (msghash32.Length < 32) + throw new ArgumentException($"{nameof(msghash32)} must be at least 32 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + sigPtr = sig, + msghash32Ptr = msghash32) + { + return Secp256k1Interop._ecdsa_recover(_ctx, pubkeyPtr, sigPtr, msghash32Ptr) == 1; + } + } + + /// Compute an EC Diffie-Hellman secret in constant time + /// pointer to an array to be filled by hashfp. + /// pointer to a secp256k1_pubkey containing an initialized public key. + /// a 32-byte scalar with which to multiply the point. + /// 1: exponentiation was successful 0: scalar was invalid (zero or overflow) or hashfp returned 0 + public bool Ecdh(Span output, ReadOnlySpan pubkey, ReadOnlySpan seckey) + { + if (output.Length < 32) + throw new ArgumentException($"{nameof(output)} must be at least 32 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + fixed (byte* outputPtr = output, + pubkeyPtr = pubkey, + seckeyPtr = seckey) + { + return Secp256k1Interop._ecdh(_ctx, outputPtr, pubkeyPtr, seckeyPtr, IntPtr.Zero, IntPtr.Zero.ToPointer()) == 1; + } + } + + /// Compute an EC Diffie-Hellman secret in constant time + /// pointer to an array to be filled by hashfp. + /// pointer to a secp256k1_pubkey containing an initialized public key. + /// a 32-byte scalar with which to multiply the point. + /// pointer to a hash function. If NULL, secp256k1_ecdh_hash_function_sha256 is used (in which case, 32 bytes will be written to output). + /// arbitrary data pointer that is passed through to hashfp (can be NULL for secp256k1_ecdh_hash_function_sha256). + /// 1: exponentiation was successful 0: scalar was invalid (zero or overflow) or hashfp returned 0 + public bool Ecdh(Span output, ReadOnlySpan pubkey, ReadOnlySpan seckey, EcdhHashFunction hashfp, IntPtr data) + { + if (output.Length < 32) + throw new ArgumentException($"{nameof(output)} must be at least 32 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + secp256k1_ecdh_hash_function nativeCallback = (void* output, void* x32, void* y32, void* data) => + { + var outputSpan = new Span(output, 32); + var x32Span = x32 != null ? new ReadOnlySpan(x32, 32) : ReadOnlySpan.Empty; + var y32Span = y32 != null ? new ReadOnlySpan(y32, 32) : ReadOnlySpan.Empty; + return hashfp(outputSpan, x32Span, y32Span, (IntPtr)data); + }; + + var callbackPtr = Marshal.GetFunctionPointerForDelegate(nativeCallback); + + fixed (byte* outputPtr = output, + pubkeyPtr = pubkey, + seckeyPtr = seckey) + { + return Secp256k1Interop._ecdh(_ctx, outputPtr, pubkeyPtr, seckeyPtr, callbackPtr, data.ToPointer()) == 1; + } + } + + /// Parse a 32-byte sequence into a xonly_pubkey object. + /// pointer to a pubkey object. If 1 is returned, it is set to a parsed version of input. If not, it's set to an invalid value. + /// pointer to a serialized xonly_pubkey. + /// 1 if the public key was fully valid. 0 if the public key could not be parsed or is invalid. + public bool XonlyPubkeyParse(Span pubkey, ReadOnlySpan input32) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (input32.Length < 32) + throw new ArgumentException($"{nameof(input32)} must be at least 32 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + input32Ptr = input32) + { + return Secp256k1Interop._xonly_pubkey_parse(_ctx, pubkeyPtr, input32Ptr) == 1; + } + } + + /// Serialize an xonly_pubkey object into a 32-byte sequence. + /// pointer to a 32-byte array to place the serialized key in. + /// pointer to a secp256k1_xonly_pubkey containing an initialized public key. + /// 1 always. + public bool XonlyPubkeySerialize(Span output32, ReadOnlySpan pubkey) + { + if (output32.Length < 32) + throw new ArgumentException($"{nameof(output32)} must be at least 32 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + + fixed (byte* output32Ptr = output32, + pubkeyPtr = pubkey) + { + return Secp256k1Interop._xonly_pubkey_serialize(_ctx, output32Ptr, pubkeyPtr) == 1; + } + } + + /// Compare two x-only public keys using lexicographic order + /// <0 if the first public key is less than the second >0 if the first public key is greater than the second 0 if the two public keys are equal + public int XonlyPubkeyCmp(ReadOnlySpan pk1, ReadOnlySpan pk2) + { + if (pk1.Length < 64) + throw new ArgumentException($"{nameof(pk1)} must be at least 64 bytes"); + if (pk2.Length < 64) + throw new ArgumentException($"{nameof(pk2)} must be at least 64 bytes"); + + fixed (byte* pk1Ptr = pk1, + pk2Ptr = pk2) + { + return Secp256k1Interop._xonly_pubkey_cmp(_ctx, pk1Ptr, pk2Ptr); + } + } + + /// Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey. + /// pointer to an x-only public key object for placing the converted public key. + /// Ignored if NULL. Otherwise, pointer to an integer that will be set to 1 if the point encoded by xonly_pubkey is the negation of the pubkey and set to 0 otherwise. + /// pointer to a public key that is converted. + /// 1 always. + public bool XonlyPubkeyFromPubkey(Span xonly_pubkey, out int pk_parity, ReadOnlySpan pubkey) + { + if (xonly_pubkey.Length < 64) + throw new ArgumentException($"{nameof(xonly_pubkey)} must be at least 64 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + + fixed (byte* xonly_pubkeyPtr = xonly_pubkey, + pubkeyPtr = pubkey) + fixed (int* pk_parityPtr = &pk_parity) + { + return Secp256k1Interop._xonly_pubkey_from_pubkey(_ctx, xonly_pubkeyPtr, pk_parityPtr, pubkeyPtr) == 1; + } + } + + /// Tweak an x-only public key by adding the generator multiplied with tweak32 to it.Note that the resulting point can not in general be represented by an x-only pubkey because it may have an odd Y coordinate. Instead, the output_pubkey is a normal secp256k1_pubkey. + /// pointer to a public key to store the result. Will be set to an invalid value if this function returns 0. + /// pointer to an x-only pubkey to apply the tweak to. + /// pointer to a 32-byte tweak, which must be valid according to secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly random 32-byte tweaks, the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid or the resulting public key would be invalid (only when the tweak is the negation of the corresponding secret key). 1 otherwise. + public bool XonlyPubkeyTweakAdd(Span output_pubkey, ReadOnlySpan internal_pubkey, ReadOnlySpan tweak32) + { + if (output_pubkey.Length < 64) + throw new ArgumentException($"{nameof(output_pubkey)} must be at least 64 bytes"); + if (internal_pubkey.Length < 64) + throw new ArgumentException($"{nameof(internal_pubkey)} must be at least 64 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* output_pubkeyPtr = output_pubkey, + internal_pubkeyPtr = internal_pubkey, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._xonly_pubkey_tweak_add(_ctx, output_pubkeyPtr, internal_pubkeyPtr, tweak32Ptr) == 1; + } + } + + /// Checks that a tweaked pubkey is the result of calling secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32.The tweaked pubkey is represented by its 32-byte x-only serialization and its pk_parity, which can both be obtained by converting the result of tweak_add to a secp256k1_xonly_pubkey.Note that this alone does _not_ verify that the tweaked pubkey is a commitment. If the tweak is not chosen in a specific way, the tweaked pubkey can easily be the result of a different internal_pubkey and tweak. + /// pointer to a serialized xonly_pubkey. + /// the parity of the tweaked pubkey (whose serialization is passed in as tweaked_pubkey32). This must match the pk_parity value that is returned when calling secp256k1_xonly_pubkey with the tweaked pubkey, or this function will fail. + /// pointer to an x-only public key object to apply the tweak to. + /// pointer to a 32-byte tweak. + /// 0 if the arguments are invalid or the tweaked pubkey is not the result of tweaking the internal_pubkey with tweak32. 1 otherwise. + public bool XonlyPubkeyTweakAddCheck(ReadOnlySpan tweaked_pubkey32, int tweaked_pk_parity, ReadOnlySpan internal_pubkey, ReadOnlySpan tweak32) + { + if (tweaked_pubkey32.Length < 32) + throw new ArgumentException($"{nameof(tweaked_pubkey32)} must be at least 32 bytes"); + if (internal_pubkey.Length < 64) + throw new ArgumentException($"{nameof(internal_pubkey)} must be at least 64 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* tweaked_pubkey32Ptr = tweaked_pubkey32, + internal_pubkeyPtr = internal_pubkey, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._xonly_pubkey_tweak_add_check(_ctx, tweaked_pubkey32Ptr, tweaked_pk_parity, internal_pubkeyPtr, tweak32Ptr) == 1; + } + } + + /// Compute the keypair for a valid secret key.See the documentation of `secp256k1_ec_seckey_verify` for more information about the validity of secret keys. + /// pointer to the created keypair. + /// pointer to a 32-byte secret key. + /// 1: secret key is valid 0: secret key is invalid + public bool KeypairCreate(Span keypair, ReadOnlySpan seckey) + { + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + + fixed (byte* keypairPtr = keypair, + seckeyPtr = seckey) + { + return Secp256k1Interop._keypair_create(_ctx, keypairPtr, seckeyPtr) == 1; + } + } + + /// Get the secret key from a keypair. + /// pointer to a 32-byte buffer for the secret key. + /// pointer to a keypair. + /// 1 always. + public bool KeypairSec(Span seckey, ReadOnlySpan keypair) + { + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + + fixed (byte* seckeyPtr = seckey, + keypairPtr = keypair) + { + return Secp256k1Interop._keypair_sec(_ctx, seckeyPtr, keypairPtr) == 1; + } + } + + /// Get the public key from a keypair. + /// pointer to a pubkey object, set to the keypair public key. + /// pointer to a keypair. + /// 1 always. + public bool KeypairPub(Span pubkey, ReadOnlySpan keypair) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + keypairPtr = keypair) + { + return Secp256k1Interop._keypair_pub(_ctx, pubkeyPtr, keypairPtr) == 1; + } + } + + /// Get the x-only public key from a keypair.This is the same as calling secp256k1_keypair_pub and then secp256k1_xonly_pubkey_from_pubkey. + /// pointer to an xonly_pubkey object, set to the keypair public key after converting it to an xonly_pubkey. + /// Ignored if NULL. Otherwise, pointer to an integer that will be set to the pk_parity argument of secp256k1_xonly_pubkey_from_pubkey. + /// pointer to a keypair. + /// 1 always. + public bool KeypairXonlyPub(Span pubkey, out int pk_parity, ReadOnlySpan keypair) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + keypairPtr = keypair) + fixed (int* pk_parityPtr = &pk_parity) + { + return Secp256k1Interop._keypair_xonly_pub(_ctx, pubkeyPtr, pk_parityPtr, keypairPtr) == 1; + } + } + + /// Tweak a keypair by adding tweak32 to the secret key and updating the public key accordingly.Calling this function and then secp256k1_keypair_pub results in the same public key as calling secp256k1_keypair_xonly_pub and then secp256k1_xonly_pubkey_tweak_add. + /// pointer to a keypair to apply the tweak to. Will be set to an invalid value if this function returns 0. + /// pointer to a 32-byte tweak, which must be valid according to secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly random 32-byte tweaks, the chance of being invalid is negligible (around 1 in 2^128). + /// 0 if the arguments are invalid or the resulting keypair would be invalid (only when the tweak is the negation of the keypair's secret key). 1 otherwise. + public bool KeypairXonlyTweakAdd(Span keypair, ReadOnlySpan tweak32) + { + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* keypairPtr = keypair, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._keypair_xonly_tweak_add(_ctx, keypairPtr, tweak32Ptr) == 1; + } + } + + /// Create a Schnorr signature.Does _not_ strictly follow BIP-340 because it does not verify the resulting signature. Instead, you can manually use secp256k1_schnorrsig_verify and abort if it fails.This function only signs 32-byte messages. If you have messages of a different size (or the same size but without a context-specific tag prefix), it is recommended to create a 32-byte message hash with secp256k1_tagged_sha256 and then sign the hash. Tagged hashing allows providing an context-specific tag for domain separation. This prevents signatures from being valid in multiple contexts by accident.Returns 1 on success, 0 on failure. + /// pointer to a 64-byte array to store the serialized signature. + /// the 32-byte message being signed. + /// pointer to an initialized keypair. + /// 32 bytes of fresh randomness. While recommended to provide this, it is only supplemental to security and can be NULL. A NULL argument is treated the same as an all-zero one. See BIP-340 "Default Signing" for a full explanation of this argument and for guidance if randomness is expensive. + public bool SchnorrsigSign32(Span sig64, ReadOnlySpan msg32, ReadOnlySpan keypair, ReadOnlySpan aux_rand32) + { + if (sig64.Length < 64) + throw new ArgumentException($"{nameof(sig64)} must be at least 64 bytes"); + if (msg32.Length < 32) + throw new ArgumentException($"{nameof(msg32)} must be at least 32 bytes"); + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + if (aux_rand32.Length < 32) + throw new ArgumentException($"{nameof(aux_rand32)} must be at least 32 bytes"); + + fixed (byte* sig64Ptr = sig64, + msg32Ptr = msg32, + keypairPtr = keypair, + aux_rand32Ptr = aux_rand32) + { + return Secp256k1Interop._schnorrsig_sign32(_ctx, sig64Ptr, msg32Ptr, keypairPtr, aux_rand32Ptr) == 1; + } + } + + /// Create a Schnorr signature with a more flexible API.Same arguments as secp256k1_schnorrsig_sign except that it allows signing variable length messages and accepts a pointer to an extraparams object that allows customizing signing by passing additional arguments.Equivalent to secp256k1_schnorrsig_sign32(..., aux_rand32) if msglen is 32 and extraparams is initialized as follows: ``` secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT; extraparams.ndata = (unsigned char*)aux_rand32; ```Returns 1 on success, 0 on failure. + /// pointer to a 64-byte array to store the serialized signature. + /// the message being signed. Can only be NULL if msglen is 0. + /// pointer to an initialized keypair. + /// pointer to an extraparams object (can be NULL). + public bool SchnorrsigSignCustom(Span sig64, ReadOnlySpan msg, ReadOnlySpan keypair, Span extraparams) + { + if (sig64.Length < 64) + throw new ArgumentException($"{nameof(sig64)} must be at least 64 bytes"); + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + + fixed (byte* sig64Ptr = sig64, + msgPtr = msg, + keypairPtr = keypair, + extraparamsPtr = extraparams) + { + return Secp256k1Interop._schnorrsig_sign_custom(_ctx, sig64Ptr, msgPtr, (nuint)msg.Length, keypairPtr, extraparamsPtr) == 1; + } + } + + /// Verify a Schnorr signature. + /// pointer to the 64-byte signature to verify. + /// the message being verified. Can only be NULL if msglen is 0. + /// pointer to an x-only public key to verify with + /// 1: correct signature 0: incorrect signature + public bool SchnorrsigVerify(ReadOnlySpan sig64, ReadOnlySpan msg, ReadOnlySpan pubkey) + { + if (sig64.Length < 64) + throw new ArgumentException($"{nameof(sig64)} must be at least 64 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + + fixed (byte* sig64Ptr = sig64, + msgPtr = msg, + pubkeyPtr = pubkey) + { + return Secp256k1Interop._schnorrsig_verify(_ctx, sig64Ptr, msgPtr, (nuint)msg.Length, pubkeyPtr) == 1; + } + } + + /// Construct a 64-byte ElligatorSwift encoding of a given pubkey. + /// pointer to a 64-byte array to be filled + /// pointer to a secp256k1_pubkey containing an initialized public key + /// pointer to 32 bytes of randomnessIt is recommended that rnd32 consists of 32 uniformly random bytes, not known to any adversary trying to detect whether public keys are being encoded, though 16 bytes of randomness (padded to an array of 32 bytes, e.g., with zeros) suffice to make the result indistinguishable from uniform. The randomness in rnd32 must not be a deterministic function of the pubkey (it can be derived from the private key, though).It is not guaranteed that the computed encoding is stable across versions of the library, even if all arguments to this function (including rnd32) are the same.This function runs in variable time. + /// 1 always. + public bool EllswiftEncode(Span ell64, ReadOnlySpan pubkey, ReadOnlySpan rnd32) + { + if (ell64.Length < 64) + throw new ArgumentException($"{nameof(ell64)} must be at least 64 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (rnd32.Length < 32) + throw new ArgumentException($"{nameof(rnd32)} must be at least 32 bytes"); + + fixed (byte* ell64Ptr = ell64, + pubkeyPtr = pubkey, + rnd32Ptr = rnd32) + { + return Secp256k1Interop._ellswift_encode(_ctx, ell64Ptr, pubkeyPtr, rnd32Ptr) == 1; + } + } + + /// Decode a 64-bytes ElligatorSwift encoded public key. + /// pointer to a secp256k1_pubkey that will be filled + /// pointer to a 64-byte array to decodeThis function runs in variable time. + /// always 1 + public bool EllswiftDecode(Span pubkey, ReadOnlySpan ell64) + { + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (ell64.Length < 64) + throw new ArgumentException($"{nameof(ell64)} must be at least 64 bytes"); + + fixed (byte* pubkeyPtr = pubkey, + ell64Ptr = ell64) + { + return Secp256k1Interop._ellswift_decode(_ctx, pubkeyPtr, ell64Ptr) == 1; + } + } + + /// Compute an ElligatorSwift public key for a secret key. + /// pointer to a 64-byte array to receive the ElligatorSwift public key + /// pointer to a 32-byte secret key + /// (optional) pointer to 32 bytes of randomnessConstant time in seckey and auxrnd32, but not in the resulting public key.It is recommended that auxrnd32 contains 32 uniformly random bytes, though it is optional (and does result in encodings that are indistinguishable from uniform even without any auxrnd32). It differs from the (mandatory) rnd32 argument to secp256k1_ellswift_encode in this regard.This function can be used instead of calling secp256k1_ec_pubkey_create followed by secp256k1_ellswift_encode. It is safer, as it uses the secret key as entropy for the encoding (supplemented with auxrnd32, if provided).Like secp256k1_ellswift_encode, this function does not guarantee that the computed encoding is stable across versions of the library, even if all arguments (including auxrnd32) are the same. + /// 1: secret was valid, public key was stored. 0: secret was invalid, try again. + public bool EllswiftCreate(Span ell64, ReadOnlySpan seckey32, ReadOnlySpan auxrnd32) + { + if (ell64.Length < 64) + throw new ArgumentException($"{nameof(ell64)} must be at least 64 bytes"); + if (seckey32.Length < 32) + throw new ArgumentException($"{nameof(seckey32)} must be at least 32 bytes"); + if (auxrnd32.Length < 32) + throw new ArgumentException($"{nameof(auxrnd32)} must be at least 32 bytes"); + + fixed (byte* ell64Ptr = ell64, + seckey32Ptr = seckey32, + auxrnd32Ptr = auxrnd32) + { + return Secp256k1Interop._ellswift_create(_ctx, ell64Ptr, seckey32Ptr, auxrnd32Ptr) == 1; + } + } + + /// Given a private key, and ElligatorSwift public keys sent in both directions, compute a shared secret using x-only Elliptic Curve Diffie-Hellman (ECDH). + /// pointer to an array to be filled by hashfp. + /// pointer to the 64-byte encoded public key of party A (will not be NULL) + /// pointer to the 64-byte encoded public key of party B (will not be NULL) + /// pointer to our 32-byte secret key + /// boolean indicating which party we are: zero if we are party A, non-zero if we are party B. seckey32 must be the private key corresponding to that party's ell_?64. This correspondence is not checked. + /// pointer to a hash function. + /// arbitrary data pointer passed through to hashfp.Constant time in seckey32.This function is more efficient than decoding the public keys, and performing ECDH on them. + /// 1: shared secret was successfully computed 0: secret was invalid or hashfp returned 0 + public bool EllswiftXdh(Span output, ReadOnlySpan ell_a64, ReadOnlySpan ell_b64, ReadOnlySpan seckey32, int party, EllswiftXdhHashFunction hashfp, IntPtr data) + { + if (output.Length < 32) + throw new ArgumentException($"{nameof(output)} must be at least 32 bytes"); + if (ell_a64.Length < 64) + throw new ArgumentException($"{nameof(ell_a64)} must be at least 64 bytes"); + if (ell_b64.Length < 64) + throw new ArgumentException($"{nameof(ell_b64)} must be at least 64 bytes"); + if (seckey32.Length < 32) + throw new ArgumentException($"{nameof(seckey32)} must be at least 32 bytes"); + + secp256k1_ellswift_xdh_hash_function nativeCallback = (void* output, void* x32, void* ell_a64, void* ell_b64, void* data) => + { + var outputSpan = new Span(output, 32); + var x32Span = x32 != null ? new ReadOnlySpan(x32, 32) : ReadOnlySpan.Empty; + var ell_a64Span = ell_a64 != null ? new ReadOnlySpan(ell_a64, 64) : ReadOnlySpan.Empty; + var ell_b64Span = ell_b64 != null ? new ReadOnlySpan(ell_b64, 64) : ReadOnlySpan.Empty; + return hashfp(outputSpan, x32Span, ell_a64Span, ell_b64Span, (IntPtr)data); + }; + + var callbackPtr = Marshal.GetFunctionPointerForDelegate(nativeCallback); + + fixed (byte* outputPtr = output, + ell_a64Ptr = ell_a64, + ell_b64Ptr = ell_b64, + seckey32Ptr = seckey32) + { + return Secp256k1Interop._ellswift_xdh(_ctx, outputPtr, ell_a64Ptr, ell_b64Ptr, seckey32Ptr, party, callbackPtr, data.ToPointer()) == 1; + } + } + + /// Parse a signer's public nonce. + /// pointer to a nonce object + /// pointer to the 66-byte nonce to be parsed + /// 1 when the nonce could be parsed, 0 otherwise. + public bool MusigPubnonceParse(Span nonce, ReadOnlySpan in66) + { + if (nonce.Length < 132) + throw new ArgumentException($"{nameof(nonce)} must be at least 132 bytes"); + if (in66.Length < 66) + throw new ArgumentException($"{nameof(in66)} must be at least 66 bytes"); + + fixed (byte* noncePtr = nonce, + in66Ptr = in66) + { + return Secp256k1Interop._musig_pubnonce_parse(_ctx, noncePtr, in66Ptr) == 1; + } + } + + /// Serialize a signer's public nonce + /// pointer to a 66-byte array to store the serialized nonce + /// pointer to the nonce + /// 1 always + public bool MusigPubnonceSerialize(Span out66, ReadOnlySpan nonce) + { + if (out66.Length < 66) + throw new ArgumentException($"{nameof(out66)} must be at least 66 bytes"); + if (nonce.Length < 132) + throw new ArgumentException($"{nameof(nonce)} must be at least 132 bytes"); + + fixed (byte* out66Ptr = out66, + noncePtr = nonce) + { + return Secp256k1Interop._musig_pubnonce_serialize(_ctx, out66Ptr, noncePtr) == 1; + } + } + + /// Parse an aggregate public nonce. + /// pointer to a nonce object + /// pointer to the 66-byte nonce to be parsed + /// 1 when the nonce could be parsed, 0 otherwise. + public bool MusigAggnonceParse(Span nonce, ReadOnlySpan in66) + { + if (nonce.Length < 132) + throw new ArgumentException($"{nameof(nonce)} must be at least 132 bytes"); + if (in66.Length < 66) + throw new ArgumentException($"{nameof(in66)} must be at least 66 bytes"); + + fixed (byte* noncePtr = nonce, + in66Ptr = in66) + { + return Secp256k1Interop._musig_aggnonce_parse(_ctx, noncePtr, in66Ptr) == 1; + } + } + + /// Serialize an aggregate public nonce + /// pointer to a 66-byte array to store the serialized nonce + /// pointer to the nonce + /// 1 always + public bool MusigAggnonceSerialize(Span out66, ReadOnlySpan nonce) + { + if (out66.Length < 66) + throw new ArgumentException($"{nameof(out66)} must be at least 66 bytes"); + if (nonce.Length < 132) + throw new ArgumentException($"{nameof(nonce)} must be at least 132 bytes"); + + fixed (byte* out66Ptr = out66, + noncePtr = nonce) + { + return Secp256k1Interop._musig_aggnonce_serialize(_ctx, out66Ptr, noncePtr) == 1; + } + } + + /// Parse a MuSig partial signature. + /// pointer to a signature object + /// pointer to the 32-byte signature to be parsed + /// 1 when the signature could be parsed, 0 otherwise. + public bool MusigPartialSigParse(Span sig, ReadOnlySpan in32) + { + if (sig.Length < 36) + throw new ArgumentException($"{nameof(sig)} must be at least 36 bytes"); + if (in32.Length < 32) + throw new ArgumentException($"{nameof(in32)} must be at least 32 bytes"); + + fixed (byte* sigPtr = sig, + in32Ptr = in32) + { + return Secp256k1Interop._musig_partial_sig_parse(_ctx, sigPtr, in32Ptr) == 1; + } + } + + /// Serialize a MuSig partial signature + /// pointer to a 32-byte array to store the serialized signature + /// pointer to the signature + /// 1 always + public bool MusigPartialSigSerialize(Span out32, ReadOnlySpan sig) + { + if (out32.Length < 32) + throw new ArgumentException($"{nameof(out32)} must be at least 32 bytes"); + if (sig.Length < 36) + throw new ArgumentException($"{nameof(sig)} must be at least 36 bytes"); + + fixed (byte* out32Ptr = out32, + sigPtr = sig) + { + return Secp256k1Interop._musig_partial_sig_serialize(_ctx, out32Ptr, sigPtr) == 1; + } + } + + /// Computes an aggregate public key and uses it to initialize a keyagg_cacheDifferent orders of `pubkeys` result in different `agg_pk`s.Before aggregating, the pubkeys can be sorted with `secp256k1_ec_pubkey_sort` which ensures the same `agg_pk` result for the same multiset of pubkeys. This is useful to do before `pubkey_agg`, such that the order of pubkeys does not affect the aggregate public key. + /// the MuSig-aggregated x-only public key. If you do not need it, this arg can be NULL. + /// if non-NULL, pointer to a musig_keyagg_cache struct that is required for signing (or observing the signing session and verifying partial signatures). + /// input array of pointers to public keys to aggregate. The order is important; a different order will result in a different aggregate public key. + /// 0 if the arguments are invalid, 1 otherwise + public bool MusigPubkeyAgg(Span agg_pk, Span keyagg_cache, byte[][] pubkeys) + { + if (pubkeys == null || pubkeys.Length == 0) + throw new ArgumentException($"{nameof(pubkeys)} must not be null or empty"); + for (int i = 0; i < pubkeys.Length; i++) + { + if (pubkeys[i] == null || pubkeys[i].Length < 64) + throw new ArgumentException($"{nameof(pubkeys)}[{i}] must be at least 64 bytes"); + } + if (agg_pk.Length < 64) + throw new ArgumentException($"{nameof(agg_pk)} must be at least 64 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + + var count = pubkeys.Length; + Span nativePtrArray = stackalloc nint[count]; + fixed (byte* agg_pkPtr = agg_pk, + keyagg_cachePtr = keyagg_cache) + { + var handles = new GCHandle[count]; + try + { + for (int i = 0; i < count; i++) + { + handles[i] = GCHandle.Alloc(pubkeys[i], GCHandleType.Pinned); + nativePtrArray[i] = handles[i].AddrOfPinnedObject(); + } + + fixed (nint* nativePtrArrayPtr = nativePtrArray) + { + return Secp256k1Interop._musig_pubkey_agg(_ctx, agg_pkPtr, keyagg_cachePtr, (IntPtr)nativePtrArrayPtr, (nuint)count) == 1; + } + } + finally + { + for (int i = 0; i < count; i++) + { + if (handles[i].IsAllocated) + handles[i].Free(); + } + } + } + } + + /// Obtain the aggregate public key from a keyagg_cache.This is only useful if you need the non-xonly public key, in particular for plain (non-xonly) tweaking or batch-verifying multiple key aggregations (not implemented). + /// the MuSig-aggregated public key. + /// pointer to a `musig_keyagg_cache` struct initialized by `musig_pubkey_agg` + /// 0 if the arguments are invalid, 1 otherwise + public bool MusigPubkeyGet(Span agg_pk, ReadOnlySpan keyagg_cache) + { + if (agg_pk.Length < 64) + throw new ArgumentException($"{nameof(agg_pk)} must be at least 64 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + + fixed (byte* agg_pkPtr = agg_pk, + keyagg_cachePtr = keyagg_cache) + { + return Secp256k1Interop._musig_pubkey_get(_ctx, agg_pkPtr, keyagg_cachePtr) == 1; + } + } + + public bool MusigPubkeyEcTweakAdd(Span output_pubkey, Span keyagg_cache, ReadOnlySpan tweak32) + { + if (output_pubkey.Length < 64) + throw new ArgumentException($"{nameof(output_pubkey)} must be at least 64 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* output_pubkeyPtr = output_pubkey, + keyagg_cachePtr = keyagg_cache, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._musig_pubkey_ec_tweak_add(_ctx, output_pubkeyPtr, keyagg_cachePtr, tweak32Ptr) == 1; + } + } + + public bool MusigPubkeyXonlyTweakAdd(Span output_pubkey, Span keyagg_cache, ReadOnlySpan tweak32) + { + if (output_pubkey.Length < 64) + throw new ArgumentException($"{nameof(output_pubkey)} must be at least 64 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + if (tweak32.Length < 32) + throw new ArgumentException($"{nameof(tweak32)} must be at least 32 bytes"); + + fixed (byte* output_pubkeyPtr = output_pubkey, + keyagg_cachePtr = keyagg_cache, + tweak32Ptr = tweak32) + { + return Secp256k1Interop._musig_pubkey_xonly_tweak_add(_ctx, output_pubkeyPtr, keyagg_cachePtr, tweak32Ptr) == 1; + } + } + + /// Starts a signing session by generating a nonceThis function outputs a secret nonce that will be required for signing and a corresponding public nonce that is intended to be sent to other signers.MuSig differs from regular Schnorr signing in that implementers _must_ take special care to not reuse a nonce. This can be ensured by following these rules:1. Each call to this function must have a UNIQUE session_secrand32 that must NOT BE REUSED in subsequent calls to this function and must be KEPT SECRET (even from other signers). 2. If you already know the seckey, message or aggregate public key cache, they can be optionally provided to derive the nonce and increase misuse-resistance. The extra_input32 argument can be used to provide additional data that does not repeat in normal scenarios, such as the current time. 3. Avoid copying (or serializing) the secnonce. This reduces the possibility that it is used more than once for signing.If you don't have access to good randomness for session_secrand32, but you have access to a non-repeating counter, then see secp256k1_musig_nonce_gen_counter.Remember that nonce reuse will leak the secret key! Note that using the same seckey for multiple MuSig sessions is fine. + /// pointer to a structure to store the secret nonce + /// pointer to a structure to store the public nonce + /// a 32-byte session_secrand32 as explained above. Must be unique to this call to secp256k1_musig_nonce_gen and must be uniformly random. If the function call is successful, the session_secrand32 buffer is invalidated to prevent reuse. + /// the 32-byte secret key that will later be used for signing, if already known (can be NULL) + /// public key of the signer creating the nonce. The secnonce output of this function cannot be used to sign for any other public key. While the public key should correspond to the provided seckey, a mismatch will not cause the function to return 0. + /// the 32-byte message that will later be signed, if already known (can be NULL) + /// pointer to the keyagg_cache that was used to create the aggregate (and potentially tweaked) public key if already known (can be NULL) + /// an optional 32-byte array that is input to the nonce derivation function (can be NULL) + /// 0 if the arguments are invalid and 1 otherwise + public bool MusigNonceGen(Span secnonce, Span pubnonce, Span session_secrand32, ReadOnlySpan seckey, ReadOnlySpan pubkey, ReadOnlySpan msg32, ReadOnlySpan keyagg_cache, ReadOnlySpan extra_input32) + { + if (secnonce.Length < 132) + throw new ArgumentException($"{nameof(secnonce)} must be at least 132 bytes"); + if (pubnonce.Length < 132) + throw new ArgumentException($"{nameof(pubnonce)} must be at least 132 bytes"); + if (session_secrand32.Length < 32) + throw new ArgumentException($"{nameof(session_secrand32)} must be at least 32 bytes"); + if (seckey.Length < 32) + throw new ArgumentException($"{nameof(seckey)} must be at least 32 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (msg32.Length < 32) + throw new ArgumentException($"{nameof(msg32)} must be at least 32 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + if (extra_input32.Length < 32) + throw new ArgumentException($"{nameof(extra_input32)} must be at least 32 bytes"); + + fixed (byte* secnoncePtr = secnonce, + pubnoncePtr = pubnonce, + session_secrand32Ptr = session_secrand32, + seckeyPtr = seckey, + pubkeyPtr = pubkey, + msg32Ptr = msg32, + keyagg_cachePtr = keyagg_cache, + extra_input32Ptr = extra_input32) + { + return Secp256k1Interop._musig_nonce_gen(_ctx, secnoncePtr, pubnoncePtr, session_secrand32Ptr, seckeyPtr, pubkeyPtr, msg32Ptr, keyagg_cachePtr, extra_input32Ptr) == 1; + } + } + + /// Alternative way to generate a nonce and start a signing sessionThis function outputs a secret nonce that will be required for signing and a corresponding public nonce that is intended to be sent to other signers.This function differs from `secp256k1_musig_nonce_gen` by accepting a non-repeating counter value instead of a secret random value. This requires that a secret key is provided to `secp256k1_musig_nonce_gen_counter` (through the keypair argument), as opposed to `secp256k1_musig_nonce_gen` where the seckey argument is optional.MuSig differs from regular Schnorr signing in that implementers _must_ take special care to not reuse a nonce. This can be ensured by following these rules:1. The nonrepeating_cnt argument must be a counter value that never repeats, i.e., you must never call `secp256k1_musig_nonce_gen_counter` twice with the same keypair and nonrepeating_cnt value. For example, this implies that if the same keypair is used with `secp256k1_musig_nonce_gen_counter` on multiple devices, none of the devices should have the same counter value as any other device. 2. If the seckey, message or aggregate public key cache is already available at this stage, any of these can be optionally provided, in which case they will be used in the derivation of the nonce and increase misuse-resistance. The extra_input32 argument can be used to provide additional data that does not repeat in normal scenarios, such as the current time. 3. Avoid copying (or serializing) the secnonce. This reduces the possibility that it is used more than once for signing.Remember that nonce reuse will leak the secret key! Note that using the same keypair for multiple MuSig sessions is fine. + /// pointer to a structure to store the secret nonce + /// pointer to a structure to store the public nonce + /// the value of a counter as explained above. Must be unique to this call to secp256k1_musig_nonce_gen. + /// keypair of the signer creating the nonce. The secnonce output of this function cannot be used to sign for any other keypair. + /// the 32-byte message that will later be signed, if already known (can be NULL) + /// pointer to the keyagg_cache that was used to create the aggregate (and potentially tweaked) public key if already known (can be NULL) + /// an optional 32-byte array that is input to the nonce derivation function (can be NULL) + /// 0 if the arguments are invalid and 1 otherwise + public bool MusigNonceGenCounter(Span secnonce, Span pubnonce, ulong nonrepeating_cnt, ReadOnlySpan keypair, ReadOnlySpan msg32, ReadOnlySpan keyagg_cache, ReadOnlySpan extra_input32) + { + if (secnonce.Length < 132) + throw new ArgumentException($"{nameof(secnonce)} must be at least 132 bytes"); + if (pubnonce.Length < 132) + throw new ArgumentException($"{nameof(pubnonce)} must be at least 132 bytes"); + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + if (msg32.Length < 32) + throw new ArgumentException($"{nameof(msg32)} must be at least 32 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + if (extra_input32.Length < 32) + throw new ArgumentException($"{nameof(extra_input32)} must be at least 32 bytes"); + + fixed (byte* secnoncePtr = secnonce, + pubnoncePtr = pubnonce, + keypairPtr = keypair, + msg32Ptr = msg32, + keyagg_cachePtr = keyagg_cache, + extra_input32Ptr = extra_input32) + { + return Secp256k1Interop._musig_nonce_gen_counter(_ctx, secnoncePtr, pubnoncePtr, nonrepeating_cnt, keypairPtr, msg32Ptr, keyagg_cachePtr, extra_input32Ptr) == 1; + } + } + + /// Aggregates the nonces of all signers into a single nonceThis can be done by an untrusted party to reduce the communication between signers. Instead of everyone sending nonces to everyone else, there can be one party receiving all nonces, aggregating the nonces with this function and then sending only the aggregate nonce back to the signers.If the aggregator does not compute the aggregate nonce correctly, the final signature will be invalid. + /// pointer to an aggregate public nonce object for musig_nonce_process + /// array of pointers to public nonces sent by the signers + /// 0 if the arguments are invalid, 1 otherwise + public bool MusigNonceAgg(Span aggnonce, byte[][] pubnonces) + { + if (pubnonces == null || pubnonces.Length == 0) + throw new ArgumentException($"{nameof(pubnonces)} must not be null or empty"); + for (int i = 0; i < pubnonces.Length; i++) + { + if (pubnonces[i] == null || pubnonces[i].Length < 132) + throw new ArgumentException($"{nameof(pubnonces)}[{i}] must be at least 132 bytes"); + } + if (aggnonce.Length < 132) + throw new ArgumentException($"{nameof(aggnonce)} must be at least 132 bytes"); + + var count = pubnonces.Length; + Span nativePtrArray = stackalloc nint[count]; + fixed (byte* aggnoncePtr = aggnonce) + { + var handles = new GCHandle[count]; + try + { + for (int i = 0; i < count; i++) + { + handles[i] = GCHandle.Alloc(pubnonces[i], GCHandleType.Pinned); + nativePtrArray[i] = handles[i].AddrOfPinnedObject(); + } + + fixed (nint* nativePtrArrayPtr = nativePtrArray) + { + return Secp256k1Interop._musig_nonce_agg(_ctx, aggnoncePtr, (IntPtr)nativePtrArrayPtr, (nuint)count) == 1; + } + } + finally + { + for (int i = 0; i < count; i++) + { + if (handles[i].IsAllocated) + handles[i].Free(); + } + } + } + } + + /// Takes the aggregate nonce and creates a session that is required for signing and verification of partial signatures. + /// pointer to a struct to store the session + /// pointer to an aggregate public nonce object that is the output of musig_nonce_agg + /// the 32-byte message to sign + /// pointer to the keyagg_cache that was used to create the aggregate (and potentially tweaked) pubkey + /// 0 if the arguments are invalid, 1 otherwise + public bool MusigNonceProcess(Span session, ReadOnlySpan aggnonce, ReadOnlySpan msg32, ReadOnlySpan keyagg_cache) + { + if (session.Length < 133) + throw new ArgumentException($"{nameof(session)} must be at least 133 bytes"); + if (aggnonce.Length < 132) + throw new ArgumentException($"{nameof(aggnonce)} must be at least 132 bytes"); + if (msg32.Length < 32) + throw new ArgumentException($"{nameof(msg32)} must be at least 32 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + + fixed (byte* sessionPtr = session, + aggnoncePtr = aggnonce, + msg32Ptr = msg32, + keyagg_cachePtr = keyagg_cache) + { + return Secp256k1Interop._musig_nonce_process(_ctx, sessionPtr, aggnoncePtr, msg32Ptr, keyagg_cachePtr) == 1; + } + } + + /// Produces a partial signatureThis function overwrites the given secnonce with zeros and will abort if given a secnonce that is all zeros. This is a best effort attempt to protect against nonce reuse. However, this is of course easily defeated if the secnonce has been copied (or serialized). Remember that nonce reuse will leak the secret key!For signing to succeed, the secnonce provided to this function must have been generated for the provided keypair. This means that when signing for a keypair consisting of a seckey and pubkey, the secnonce must have been created by calling musig_nonce_gen with that pubkey. Otherwise, the illegal_callback is called.This function does not verify the output partial signature, deviating from the BIP 327 specification. It is recommended to verify the output partial signature with `secp256k1_musig_partial_sig_verify` to prevent random or adversarially provoked computation errors. + /// pointer to struct to store the partial signature + /// pointer to the secnonce struct created in musig_nonce_gen that has been never used in a partial_sign call before and has been created for the keypair + /// pointer to keypair to sign the message with + /// pointer to the keyagg_cache that was output when the aggregate public key for this session + /// pointer to the session that was created with musig_nonce_process + /// 0 if the arguments are invalid or the provided secnonce has already been used for signing, 1 otherwise + public bool MusigPartialSign(Span partial_sig, Span secnonce, ReadOnlySpan keypair, ReadOnlySpan keyagg_cache, ReadOnlySpan session) + { + if (partial_sig.Length < 36) + throw new ArgumentException($"{nameof(partial_sig)} must be at least 36 bytes"); + if (secnonce.Length < 132) + throw new ArgumentException($"{nameof(secnonce)} must be at least 132 bytes"); + if (keypair.Length < 96) + throw new ArgumentException($"{nameof(keypair)} must be at least 96 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + if (session.Length < 133) + throw new ArgumentException($"{nameof(session)} must be at least 133 bytes"); + + fixed (byte* partial_sigPtr = partial_sig, + secnoncePtr = secnonce, + keypairPtr = keypair, + keyagg_cachePtr = keyagg_cache, + sessionPtr = session) + { + return Secp256k1Interop._musig_partial_sign(_ctx, partial_sigPtr, secnoncePtr, keypairPtr, keyagg_cachePtr, sessionPtr) == 1; + } + } + + /// Verifies an individual signer's partial signatureThe signature is verified for a specific signing session. In order to avoid accidentally verifying a signature from a different or non-existing signing session, you must ensure the following: 1. The `keyagg_cache` argument is identical to the one used to create the `session` with `musig_nonce_process`. 2. The `pubkey` argument must be identical to the one sent by the signer before aggregating it with `musig_pubkey_agg` to create the `keyagg_cache`. 3. The `pubnonce` argument must be identical to the one sent by the signer before aggregating it with `musig_nonce_agg` and using the result to create the `session` with `musig_nonce_process`.It is not required to call this function in regular MuSig sessions, because if any partial signature does not verify, the final signature will not verify either, so the problem will be caught. However, this function provides the ability to identify which specific partial signature fails verification. + /// pointer to partial signature to verify, sent by the signer associated with `pubnonce` and `pubkey` + /// public nonce of the signer in the signing session + /// public key of the signer in the signing session + /// pointer to the keyagg_cache that was output when the aggregate public key for this signing session + /// pointer to the session that was created with `musig_nonce_process` + /// 0 if the arguments are invalid or the partial signature does not verify, 1 otherwise + public bool MusigPartialSigVerify(ReadOnlySpan partial_sig, ReadOnlySpan pubnonce, ReadOnlySpan pubkey, ReadOnlySpan keyagg_cache, ReadOnlySpan session) + { + if (partial_sig.Length < 36) + throw new ArgumentException($"{nameof(partial_sig)} must be at least 36 bytes"); + if (pubnonce.Length < 132) + throw new ArgumentException($"{nameof(pubnonce)} must be at least 132 bytes"); + if (pubkey.Length < 64) + throw new ArgumentException($"{nameof(pubkey)} must be at least 64 bytes"); + if (keyagg_cache.Length < 197) + throw new ArgumentException($"{nameof(keyagg_cache)} must be at least 197 bytes"); + if (session.Length < 133) + throw new ArgumentException($"{nameof(session)} must be at least 133 bytes"); + + fixed (byte* partial_sigPtr = partial_sig, + pubnoncePtr = pubnonce, + pubkeyPtr = pubkey, + keyagg_cachePtr = keyagg_cache, + sessionPtr = session) + { + return Secp256k1Interop._musig_partial_sig_verify(_ctx, partial_sigPtr, pubnoncePtr, pubkeyPtr, keyagg_cachePtr, sessionPtr) == 1; + } + } + + /// Aggregates partial signatures + /// complete (but possibly invalid) Schnorr signature + /// pointer to the session that was created with musig_nonce_process + /// array of pointers to partial signatures to aggregate + /// 0 if the arguments are invalid, 1 otherwise (which does NOT mean the resulting signature verifies). + public bool MusigPartialSigAgg(Span sig64, ReadOnlySpan session, byte[][] partial_sigs) + { + if (partial_sigs == null || partial_sigs.Length == 0) + throw new ArgumentException($"{nameof(partial_sigs)} must not be null or empty"); + for (int i = 0; i < partial_sigs.Length; i++) + { + if (partial_sigs[i] == null || partial_sigs[i].Length < 36) + throw new ArgumentException($"{nameof(partial_sigs)}[{i}] must be at least 36 bytes"); + } + if (sig64.Length < 64) + throw new ArgumentException($"{nameof(sig64)} must be at least 64 bytes"); + if (session.Length < 133) + throw new ArgumentException($"{nameof(session)} must be at least 133 bytes"); + + var count = partial_sigs.Length; + Span nativePtrArray = stackalloc nint[count]; + fixed (byte* sig64Ptr = sig64, + sessionPtr = session) + { + var handles = new GCHandle[count]; + try + { + for (int i = 0; i < count; i++) + { + handles[i] = GCHandle.Alloc(partial_sigs[i], GCHandleType.Pinned); + nativePtrArray[i] = handles[i].AddrOfPinnedObject(); + } + + fixed (nint* nativePtrArrayPtr = nativePtrArray) + { + return Secp256k1Interop._musig_partial_sig_agg(_ctx, sig64Ptr, sessionPtr, (IntPtr)nativePtrArrayPtr, (nuint)count) == 1; + } + } + finally + { + for (int i = 0; i < count; i++) + { + if (handles[i].IsAllocated) + handles[i].Free(); + } + } + } + } + + /// An implementation of RFC6979 (using HMAC-SHA256) as nonce generation function. If a data pointer is passed, it is assumed to be a pointer to 32 bytes of extra entropy. + /// pointer to a 32-byte array to be filled by the function. + /// the 32-byte message hash being verified (will not be NULL) + /// pointer to a 32-byte secret key (will not be NULL) + /// pointer to a 16-byte array describing the signature algorithm (will be NULL for ECDSA for compatibility). + /// Arbitrary data pointer that is passed through. + /// how many iterations we have tried to find a nonce. This will almost always be 0, but different attempt values are required to result in a different nonce.Except for test cases, this function should compute some cryptographic hash of the message, the algorithm, the key and the attempt. + /// True on success, false on failure. + public bool NonceFunctionRfc6979(Span nonce32, ReadOnlySpan msg32, ReadOnlySpan key32, ReadOnlySpan algo16, Span data, uint attempt) + { + if (nonce32.Length < 32) + throw new ArgumentException($"{nameof(nonce32)} must be at least 32 bytes"); + if (msg32.Length < 32) + throw new ArgumentException($"{nameof(msg32)} must be at least 32 bytes"); + if (key32.Length < 32) + throw new ArgumentException($"{nameof(key32)} must be at least 32 bytes"); + fixed (byte* nonce32Ptr = nonce32, + msg32Ptr = msg32, + key32Ptr = key32, + algo16Ptr = algo16, + dataPtr = data) + { + return Secp256k1Interop._nonce_function_rfc6979(nonce32Ptr, msg32Ptr, key32Ptr, algo16Ptr, dataPtr, attempt) == 1; + } + } + + /// A default safe nonce generation function (currently equal to secp256k1_nonce_function_rfc6979). + /// pointer to a 32-byte array to be filled by the function. + /// the 32-byte message hash being verified (will not be NULL) + /// pointer to a 32-byte secret key (will not be NULL) + /// pointer to a 16-byte array describing the signature algorithm (will be NULL for ECDSA for compatibility). + /// Arbitrary data pointer that is passed through. + /// how many iterations we have tried to find a nonce. This will almost always be 0, but different attempt values are required to result in a different nonce.Except for test cases, this function should compute some cryptographic hash of the message, the algorithm, the key and the attempt. + /// True on success, false on failure. + public bool NonceFunctionDefault(Span nonce32, ReadOnlySpan msg32, ReadOnlySpan key32, ReadOnlySpan algo16, Span data, uint attempt) + { + if (nonce32.Length < 32) + throw new ArgumentException($"{nameof(nonce32)} must be at least 32 bytes"); + if (msg32.Length < 32) + throw new ArgumentException($"{nameof(msg32)} must be at least 32 bytes"); + if (key32.Length < 32) + throw new ArgumentException($"{nameof(key32)} must be at least 32 bytes"); + fixed (byte* nonce32Ptr = nonce32, + msg32Ptr = msg32, + key32Ptr = key32, + algo16Ptr = algo16, + dataPtr = data) + { + return Secp256k1Interop._nonce_function_default(nonce32Ptr, msg32Ptr, key32Ptr, algo16Ptr, dataPtr, attempt) == 1; + } + } + + /// An implementation of SHA256 hash function that applies to compressed public key. Populates the output parameter with 32 bytes. + /// pointer to an array to be filled by the function + /// pointer to a 32-byte x coordinate + /// pointer to a 32-byte y coordinate + /// arbitrary data pointer that is passed through + /// True on success, false on failure. + public bool EcdhHashFunctionSha256(Span output, ReadOnlySpan x32, ReadOnlySpan y32, Span data) + { + if (output.Length < 32) + throw new ArgumentException($"{nameof(output)} must be at least 32 bytes"); + if (x32.Length < 32) + throw new ArgumentException($"{nameof(x32)} must be at least 32 bytes"); + if (y32.Length < 32) + throw new ArgumentException($"{nameof(y32)} must be at least 32 bytes"); + fixed (byte* outputPtr = output, + x32Ptr = x32, + y32Ptr = y32, + dataPtr = data) + { + return Secp256k1Interop._ecdh_hash_function_sha256(outputPtr, x32Ptr, y32Ptr, dataPtr) == 1; + } + } + + /// A default ECDH hash function (currently equal to secp256k1_ecdh_hash_function_sha256). Populates the output parameter with 32 bytes. + /// pointer to an array to be filled by the function + /// pointer to a 32-byte x coordinate + /// pointer to a 32-byte y coordinate + /// arbitrary data pointer that is passed through + /// True on success, false on failure. + public bool EcdhHashFunctionDefault(Span output, ReadOnlySpan x32, ReadOnlySpan y32, Span data) + { + if (output.Length < 32) + throw new ArgumentException($"{nameof(output)} must be at least 32 bytes"); + if (x32.Length < 32) + throw new ArgumentException($"{nameof(x32)} must be at least 32 bytes"); + if (y32.Length < 32) + throw new ArgumentException($"{nameof(y32)} must be at least 32 bytes"); + fixed (byte* outputPtr = output, + x32Ptr = x32, + y32Ptr = y32, + dataPtr = data) + { + return Secp256k1Interop._ecdh_hash_function_default(outputPtr, x32Ptr, y32Ptr, dataPtr) == 1; + } + } + + /// An implementation of the nonce generation function as defined in Bitcoin Improvement Proposal 340 "Schnorr Signatures for secp256k1" (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).If a data pointer is passed, it is assumed to be a pointer to 32 bytes of auxiliary random data as defined in BIP-340. If the data pointer is NULL, the nonce derivation procedure follows BIP-340 by setting the auxiliary random data to zero. The algo argument must be non-NULL, otherwise the function will fail and return 0. The hash will be tagged with algo. Therefore, to create BIP-340 compliant signatures, algo must be set to "BIP0340/nonce" and algolen to 13. + /// pointer to a 32-byte array to be filled by the function + /// the message being verified. Is NULL if and only if msglen is 0. + /// the length of the message + /// pointer to a 32-byte secret key (will not be NULL) + /// the 32-byte serialized xonly pubkey corresponding to key32 (will not be NULL) + /// pointer to an array describing the signature algorithm (will not be NULL) + /// the length of the algo array + /// arbitrary data pointer that is passed throughExcept for test cases, this function should compute some cryptographic hash of the message, the key, the pubkey, the algorithm description, and data. + /// True on success, false on failure. + public bool NonceFunctionBip340(Span nonce32, ReadOnlySpan msg, nuint msglen, ReadOnlySpan key32, ReadOnlySpan xonly_pk32, ReadOnlySpan algo, nuint algolen, Span data) + { + if (nonce32.Length < 32) + throw new ArgumentException($"{nameof(nonce32)} must be at least 32 bytes"); + if (key32.Length < 32) + throw new ArgumentException($"{nameof(key32)} must be at least 32 bytes"); + if (xonly_pk32.Length < 32) + throw new ArgumentException($"{nameof(xonly_pk32)} must be at least 32 bytes"); + fixed (byte* nonce32Ptr = nonce32, + msgPtr = msg, + key32Ptr = key32, + xonly_pk32Ptr = xonly_pk32, + algoPtr = algo, + dataPtr = data) + { + return Secp256k1Interop._nonce_function_bip340(nonce32Ptr, msgPtr, msglen, key32Ptr, xonly_pk32Ptr, algoPtr, algolen, dataPtr) == 1; + } + } + + /// An implementation of an secp256k1_ellswift_xdh_hash_function which uses SHA256(prefix64 || ell_a64 || ell_b64 || x32), where prefix64 is the 64-byte array pointed to by data. + /// pointer to an array to be filled by the function + /// pointer to the 32-byte serialized X coordinate of the resulting shared point (will not be NULL) + /// pointer to the 64-byte encoded public key of party A (will not be NULL) + /// pointer to the 64-byte encoded public key of party B (will not be NULL) + /// arbitrary data pointer that is passed through + /// True on success, false on failure. + public bool EllswiftXdhHashFunctionPrefix(Span output, ReadOnlySpan x32, ReadOnlySpan ell_a64, ReadOnlySpan ell_b64, Span data) + { + if (output.Length < 32) + throw new ArgumentException($"{nameof(output)} must be at least 32 bytes"); + if (x32.Length < 32) + throw new ArgumentException($"{nameof(x32)} must be at least 32 bytes"); + if (ell_a64.Length < 64) + throw new ArgumentException($"{nameof(ell_a64)} must be at least 64 bytes"); + if (ell_b64.Length < 64) + throw new ArgumentException($"{nameof(ell_b64)} must be at least 64 bytes"); + fixed (byte* outputPtr = output, + x32Ptr = x32, + ell_a64Ptr = ell_a64, + ell_b64Ptr = ell_b64, + dataPtr = data) + { + return Secp256k1Interop._ellswift_xdh_hash_function_prefix(outputPtr, x32Ptr, ell_a64Ptr, ell_b64Ptr, dataPtr) == 1; + } + } + + /// An implementation of an secp256k1_ellswift_xdh_hash_function compatible with BIP324. It returns H_tag(ell_a64 || ell_b64 || x32), where H_tag is the BIP340 tagged hash function with tag "bip324_ellswift_xonly_ecdh". Equivalent to secp256k1_ellswift_xdh_hash_function_prefix with prefix64 set to SHA256("bip324_ellswift_xonly_ecdh")||SHA256("bip324_ellswift_xonly_ecdh"). The data argument is ignored. + /// pointer to an array to be filled by the function + /// pointer to the 32-byte serialized X coordinate of the resulting shared point (will not be NULL) + /// pointer to the 64-byte encoded public key of party A (will not be NULL) + /// pointer to the 64-byte encoded public key of party B (will not be NULL) + /// arbitrary data pointer that is passed through + /// True on success, false on failure. + public bool EllswiftXdhHashFunctionBip324(Span output, ReadOnlySpan x32, ReadOnlySpan ell_a64, ReadOnlySpan ell_b64, Span data) + { + if (output.Length < 32) + throw new ArgumentException($"{nameof(output)} must be at least 32 bytes"); + if (x32.Length < 32) + throw new ArgumentException($"{nameof(x32)} must be at least 32 bytes"); + if (ell_a64.Length < 64) + throw new ArgumentException($"{nameof(ell_a64)} must be at least 64 bytes"); + if (ell_b64.Length < 64) + throw new ArgumentException($"{nameof(ell_b64)} must be at least 64 bytes"); + fixed (byte* outputPtr = output, + x32Ptr = x32, + ell_a64Ptr = ell_a64, + ell_b64Ptr = ell_b64, + dataPtr = data) + { + return Secp256k1Interop._ellswift_xdh_hash_function_bip324(outputPtr, x32Ptr, ell_a64Ptr, ell_b64Ptr, dataPtr) == 1; + } + } + } +} diff --git a/Secp256k1.Net/Interop.cs b/Secp256k1.Net/Interop.cs deleted file mode 100644 index d7fe6ba..0000000 --- a/Secp256k1.Net/Interop.cs +++ /dev/null @@ -1,375 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; - -namespace Secp256k1Net -{ - - /// - /// Create a secp256k1 context object. - /// - /// which parts of the context to initialize. - /// a newly created context object. - public delegate IntPtr secp256k1_context_create(uint flags); - - - /// - /// Type for error and illegal callback functions, - /// - /// message: error message. - /// data: callback marker, it is set by user together with callback. - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate void ErrorCallbackDelegate(string message, void* data); - - /// - /// Sets and illegal calback for secp256k1 context object. This callback is called fo illegal operations. - /// - /// ctx: an existing context to destroy (cannot be NULL). - /// fun: illegal callback function. - /// data: callback marker, it is set by user together with callback. - public unsafe delegate void secp256k1_context_set_illegal_callback(IntPtr ctx, ErrorCallbackDelegate fun, void* data); - - /// - /// Sets and error callback for secp256k1 context object. This callback is called for errors. - /// - /// ctx: an existing context to destroy (cannot be NULL). - /// fun: illegal callback function. - /// data: callback marker, it is set by user together with callback. - public unsafe delegate void secp256k1_context_set_error_callback(IntPtr ctx, ErrorCallbackDelegate fun, void* data); - - /// - /// Destroy a secp256k1 context object. The context pointer may not be used afterwards. - /// - /// ctx: an existing context to destroy (cannot be NULL). - public delegate void secp256k1_context_destroy(IntPtr ctx); - - /// - /// Create a recoverable ECDSA signature. - /// - /// pointer to a context object, initialized for signing (cannot be NULL) - /// (Output) pointer to an array where the signature will be placed (cannot be NULL) - /// the 32-byte message hash being signed (cannot be NULL) - /// pointer to a 32-byte secret key (cannot be NULL) - /// pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used - /// pointer to arbitrary data used by the nonce generation function (can be NULL) - /// - /// 1: signature created - /// 0: the nonce generation function failed, or the private key was invalid. - /// - public unsafe delegate int secp256k1_ecdsa_sign_recoverable(IntPtr ctx, - void* sig, // secp256k1_ecdsa_recoverable_signature *sig - void* msg32, // const unsigned char* msg32 - void* seckey, // const unsigned char* seckey - IntPtr noncefp, // secp256k1_nonce_function noncefp - IntPtr ndata // const void* ndata - ); - - /// - /// Obtains the public key for a given private key. - /// - /// pointer to a context object, initialized for signing (cannot be NULL) - /// (Output) pointer to the created public key (cannot be NULL) - /// (Input) pointer to a 32-byte private key (cannot be NULL) - /// - /// 1: secret was valid, public key stores - /// 0: secret was invalid, try again - /// - public unsafe delegate int secp256k1_ec_pubkey_create(IntPtr ctx, - void* pubKeyOut, // secp256k1_pubkey *pubkey, - void* privKeyIn // const unsigned char *seckey - ); - - /// - /// Parse a variable-length public key into the pubkey object. - /// This function supports parsing compressed (33 bytes, header byte 0x02 or - /// 0x03), uncompressed(65 bytes, header byte 0x04), or hybrid(65 bytes, header - /// byte 0x06 or 0x07) format public keys. - /// - /// a secp256k1 context object. - /// (Output) pointer to a pubkey object. If 1 is returned, it is set to a parsed version of input. If not, its value is undefined. - /// pointer to a serialized public key. - /// length of the array pointed to by input - /// 1 if the public key was fully valid, 0 if the public key could not be parsed or is invalid. - public unsafe delegate int secp256k1_ec_pubkey_parse(IntPtr ctx, - void* pubkey, // secp256k1_pubkey* pubkey, - void* input, // const unsigned char* input, - uint inputlen // size_t inputlen - ); - - /// - /// Serialize a pubkey object into a serialized byte sequence. - /// - /// a secp256k1 context object. - /// a pointer to a 65-byte (if compressed==0) or 33-byte (if compressed==1) byte array to place the serialized key in. - /// a pointer to an integer which is initially set to the size of output, and is overwritten with the written size. - /// a pointer to a secp256k1_pubkey containing an initialized public key. - /// SECP256K1_EC_COMPRESSED if serialization should be in compressed format, otherwise SECP256K1_EC_UNCOMPRESSED. - /// 1 always - public unsafe delegate int secp256k1_ec_pubkey_serialize(IntPtr ctx, - void* output, // unsigned char* output - ref uint outputlen, // size_t *outputlen - void* pubkey, // const secp256k1_pubkey* pubkey - uint flags // unsigned int flags - ); - - /// - /// Verify an ECDSA secret key. - /// - /// a secp256k1 context object. - /// Pointer to a 32-byte secret key. - /// 1 if secret key is valid, 0 if secret key is invalid. - public unsafe delegate int secp256k1_ec_seckey_verify(IntPtr ctx, - void* seckey // const unsigned char* seckey - ); - - /// - /// Normalizes a signature and enforces a low-S. - /// - /// pointer to a context object, initialized for signing (cannot be NULL) - /// (Output) pointer to an array where the normalized signature will be placed (cannot be NULL) - /// (Input) pointer to an array where a signature to normalize resides (cannot be NULL) - /// 1: correct signature, 0: incorrect or unparseable signature - public unsafe delegate int secp256k1_ecdsa_signature_normalize(IntPtr ctx, - void* sigout, // secp256k1_ecdsa_signature* sigout - void* sigin // const secp256k1_ecdsa_signature* sigin - ); - - /// - /// Parse a DER ECDSA signature - /// This function will accept any valid DER encoded signature, even if the - /// encoded numbers are out of range. - /// After the call, sig will always be initialized. If parsing failed or the - /// encoded numbers are out of range, signature validation with it is - /// guaranteed to fail for every message and public key. - /// - /// a secp256k1 context object (cannot be NULL) - /// (Output) pointer to an array where the parsed signature will be placed (cannot be NULL) - /// (Input) pointer to an array where a signature to parse resides (cannot be NULL) - /// length of the array pointed to by input - /// 1: correct signature, 0: incorrect or unparseable signature - public unsafe delegate int secp256k1_ecdsa_signature_parse_der(IntPtr ctx, - void* sig, // secp256k1_ecdsa_signature* sig - void* input, // const unsigned char *input - uint inputlen // size_t inputlen - ); - - /// - /// Parse an ECDSA signature in compact (64 bytes) format. - /// The signature must consist of a 32-byte big endian R value, followed by a - /// 32-byte big endian S value. If R or S fall outside of[0..order - 1], the - /// encoding is invalid. R and S with value 0 are allowed in the encoding. - /// After the call, sig will always be initialized.If parsing failed or R or - /// S are zero, the resulting sig value is guaranteed to fail verification for - /// any message and public key. - /// - /// a secp256k1 context object (cannot be NULL) - /// (Output) pointer to a signature object (cannot be NULL) - /// (Input) pointer to the 64-byte array to parse (cannot be NULL) - /// 1: correct signature, 0: incorrect or unserializeble signature - public unsafe delegate int secp256k1_ecdsa_signature_parse_compact(IntPtr ctx, - void* output, // secp256k1_ecdsa_signature* sig (64 bytes) - void* sig // const unsigned char* input64 - ); - - /// - /// Serialize an ECDSA signature in DER format (72 bytes maximum) - /// This function will accept any valid ECDSA encoded signature - /// After the call, output will always be initialized. - /// - /// a secp256k1 context object (cannot be NULL) - /// (Output) pointer to an array where the serialized signature will be placed (cannot be NULL) - /// which is initially set to the size of output, and is overwritten with the written size (cannot be NULL) - /// (Input) pointer to an array where a signature to parse resides (cannot be NULL) - /// 1: correct signature, 0: incorrect or unserializeble signature - public unsafe delegate int secp256k1_ecdsa_signature_serialize_der(IntPtr ctx, - void* output, // unsigned char *output - ref uint outputlen, // size_t *outputlen - void* sig // const secp256k1_ecdsa_signature* sig - ); - - /// - /// Serialize an ECDSA signature in compact (64 byte) format. - /// - /// a secp256k1 context object (cannot be NULL) - /// (Output) a pointer to a 64-byte array to store the compact serialization (cannot be NULL) - /// (Input) a pointer to an initialized signature object (cannot be NULL) - /// 1: correct signature, 0: incorrect or unserializeble signature - public unsafe delegate int secp256k1_ecdsa_signature_serialize_compact(IntPtr ctx, - void* output, // unsigned char* output64 - void* sig // const secp256k1_ecdsa_signature* sig - ); - - /// - /// Serialize an ECDSA signature in compact format (64 bytes + recovery id). - /// - /// a secp256k1 context object - /// (Output) a pointer to a 64-byte array of the compact signature (cannot be NULL). - /// (Output) a pointer to an integer to hold the recovery id (can be NULL). - /// a pointer to an initialized signature object (cannot be NULL). - /// 1 always - public unsafe delegate int secp256k1_ecdsa_recoverable_signature_serialize_compact(IntPtr ctx, - void* output64, // unsigned char* output64 - ref int recid, // int* recid - void* sig // const secp256k1_ecdsa_recoverable_signature* sig - ); - - /// - /// Recover an ECDSA public key from a signature. - /// - /// pointer to a context object, initialized for verification (cannot be NULL) - /// (Output) pointer to the recovered public key (cannot be NULL) - /// pointer to initialized signature that supports pubkey recovery (cannot be NULL) - /// the 32-byte message hash assumed to be signed (cannot be NULL) - /// - /// 1: public key successfully recovered (which guarantees a correct signature). - /// 0: otherwise. - /// - public unsafe delegate int secp256k1_ecdsa_recover(IntPtr ctx, - void* pubkey, // secp256k1_pubkey* pubkey - void* sig, // const secp256k1_ecdsa_recoverable_signature* sig - void* msg32 // const unsigned char* msg32 - ); - - /// - /// Parse a compact ECDSA signature (64 bytes + recovery id). - /// - /// a secp256k1 context object - /// (Output) a pointer to a signature object - /// a pointer to a 64-byte compact signature - /// the recovery id (0, 1, 2 or 3) - /// 1 when the signature could be parsed, 0 otherwise - public unsafe delegate int secp256k1_ecdsa_recoverable_signature_parse_compact(IntPtr ctx, - void* sig, // secp256k1_ecdsa_recoverable_signature* sig - void* input64, // const unsigned char* input64 - int recid // int recid - ); - - /// - /// Verify an ECDSA signature. - /// To avoid accepting malleable signatures, only ECDSA signatures in lower-S - /// form are accepted. - /// If you need to accept ECDSA signatures from sources that do not obey this - /// rule, apply secp256k1_ecdsa_signature_normalize to the signature prior to - /// validation, but be aware that doing so results in malleable signatures. - /// For details, see the comments for that function. - /// - /// a secp256k1 context object, initialized for verification. - /// the signature being verified (cannot be NULL) - /// the 32-byte message hash being verified (cannot be NULL) - /// pointer to an initialized public key to verify with (cannot be NULL) - /// 1: correct signature, 0: incorrect or unparseable signature - public unsafe delegate int secp256k1_ecdsa_verify(IntPtr ctx, - void* sig, // const secp256k1_ecdsa_signature *sig, - void* msg32, // const unsigned char *msg32, - void* pubkey // const secp256k1_pubkey *pubkey - ); - - /// - /// Create an ECDSA signature. The created signature is always in lower-S form. See - /// secp256k1_ecdsa_signature_normalize for more details. - /// - /// Pointer to a context object, initialized for signing (cannot be NULL). - /// Pointer to an array where the signature will be placed (cannot be NULL). - /// The 32-byte message hash being signed (cannot be NULL). - /// Pointer to a 32-byte secret key (cannot be NULL). - /// Pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used. - /// Pointer to arbitrary data used by the nonce generation function (can be NULL). - /// 1: signature created, 0: the nonce generation function failed, or the private key was invalid. - public unsafe delegate int secp256k1_ecdsa_sign(IntPtr ctx, - void* sig, // secp256k1_ecdsa_signature *sig - void* msg32, // const unsigned char *msg32 - void* seckey, // const unsigned char *seckey - IntPtr noncefp, // secp256k1_nonce_function noncefp - void* ndata // const void *ndata - ); - - /// - /// Compute an EC Diffie-Hellman secret in constant time. - /// - /// Pointer to a context object (cannot be NULL). - /// Pointer to an array to be filled by the function. - /// A pointer to a secp256k1_pubkey containing an initialized public key. - /// A 32-byte scalar with which to multiply the point. - /// Pointer to a hash function. If NULL, secp256k1_ecdh_hash_function_sha256 is used. - /// Arbitrary data pointer that is passed through. - /// 1: exponentiation was successful, 0: scalar was invalid(zero or overflow) - public unsafe delegate int secp256k1_ecdh(IntPtr ctx, - void* output, // unsigned char *output - void* pubkey, // const secp256k1_pubkey *pubkey - void* privkey, // const unsigned char *privkey - secp256k1_ecdh_hash_function hashfp, // secp256k1_ecdh_hash_function hashfp, - IntPtr data // void *data - ); - - /// - /// Tweak a public key by adding tweak times the generator to it. - /// - /// Pointer to a context object (cannot be NULL). - /// (Input/Output) Pointer to a public key object. It will be set to an invalid value if this function returns 0. - /// Pointer to a 32-byte tweak. If the tweak is invalid according to secp256k1_ec_seckey_verify, this function returns 0. For uniformly random 32-byte arrays the chance of being invalid is negligible (around 1 in 2^128). - /// 0 if the arguments are invalid. 1 otherwise. - public unsafe delegate int secp256k1_ec_pubkey_tweak_mul(IntPtr ctx, void* pubkey, void* tweak); - - /// - /// Deterministically generate a nonce. - /// - /// (Output) Pointer to a 32-byte array to be filled by the function. - /// The 32-byte message hash being verified (will not be NULL) - /// Pointer to a 32-byte secret key (will not be NULL) - /// Pointer to a 16-byte array describing the signature algorithm (will be NULL for ECDSA for compatibility). - /// Arbitrary data pointer that is passed through. - /// How many iterations we have tried to find a nonce. This will almost always be 0, but different attempt values are required to result in a different nonce. - /// 1 if a nonce was successfully generated. 0 will cause signing to fail. - public unsafe delegate int secp256k1_nonce_function(void* nonce32, void* hash, void* seckey, void* algo, void* data, uint attempt); - - /// - /// Negates a public key in place. - /// - /// Pointer to a context object (cannot be NULL). - /// (Input/Output) Pointer to the public key to be negated. - /// 1 always - public unsafe delegate int secp256k1_ec_pubkey_negate(IntPtr ctx, void* pubkey); - - /// - /// Add a number of public keys together. - /// - /// Pointer to a context object (cannot be NULL). - /// (Output) Pointer to a public key object for placing the resulting public key. - /// Pointer to array of pointers to public keys. - /// The number of public keys to add together (must be at least 1). - /// 1: the sum of the public keys is valid. 0: the sum of the public keys is not valid. - public unsafe delegate int - secp256k1_ec_pubkey_combine(IntPtr ctx, void* outpubkey, IntPtr inpubkeys, uint inputlen); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate int secp256k1_ecdh_hash_function(void* output, void* x, void* y, IntPtr data); - - // Flags copied from - // https://github.com/bitcoin-core/secp256k1/blob/452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4/include/secp256k1.h#L157 - - [Flags] - public enum Flags : uint - { - /** All flags' lower 8 bits indicate what they're for. Do not use directly. */ - SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1), - SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0), - SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1), - - /** The higher bits contain the actual data. Do not use directly. */ - SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8), - SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9), - SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8), - - /** Flags to pass to secp256k1_context_create. */ - SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY), - SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN), - SECP256K1_CONTEXT_NONE = (SECP256K1_FLAGS_TYPE_CONTEXT), - - /** Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export. */ - SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION), - SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION) - } - - -} \ No newline at end of file diff --git a/Secp256k1.Net/LibPathResolver.cs b/Secp256k1.Net/LibPathResolver.cs index 74f8fe0..f370b3e 100644 --- a/Secp256k1.Net/LibPathResolver.cs +++ b/Secp256k1.Net/LibPathResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -27,6 +28,28 @@ public static class LibPathResolver [(OSX, Arm64)] = ("osx-arm64", "lib", ".dylib"), }; + // Musl (Alpine) variants - checked first on musl systems + static readonly Dictionary MuslPlatformPaths = new Dictionary + { + [(Linux, X64)] = ("linux-musl-x64", "lib", ".so"), + [(Linux, Arm64)] = ("linux-musl-arm64", "lib", ".so"), + }; + + static readonly Lazy IsMuslLinux = new Lazy(() => + { + if (!IsOSPlatform(Linux)) + return false; + try + { + // Alpine Linux has this file + return File.Exists("/etc/alpine-release"); + } + catch + { + return false; + } + }); + static readonly OSPlatform[] SupportedPlatforms = { Windows, OSX, Linux }; static string SupportedPlatformDescriptions() => string.Join("\n", PlatformPaths.Keys.Select(GetPlatformDesc)); @@ -53,16 +76,27 @@ public static string Resolve(string library) var searchedPaths = new HashSet(); + // On musl Linux (Alpine), try musl-specific paths first, then fall back to glibc paths + var platformsToTry = new List<(string Prefix, string LibPrefix, string Extension)>(); + if (IsMuslLinux.Value && MuslPlatformPaths.TryGetValue(CurrentPlatformInfo, out var muslPlatform)) + { + platformsToTry.Add(muslPlatform); + } + platformsToTry.Add(platform); + foreach (var containerDir in GetSearchLocations()) { - foreach (var libPath in SearchContainerPaths(containerDir, library, platform)) + foreach (var platformToTry in platformsToTry) { - if (!searchedPaths.Contains(libPath) && File.Exists(libPath)) + foreach (var libPath in SearchContainerPaths(containerDir, library, platformToTry)) { - Cache.TryAdd(library, libPath); - return libPath; + if (!searchedPaths.Contains(libPath) && File.Exists(libPath)) + { + Cache.TryAdd(library, libPath); + return libPath; + } + searchedPaths.Add(libPath); } - searchedPaths.Add(libPath); } } @@ -70,41 +104,45 @@ public static string Resolve(string library) } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("SingleFile", "IL3000:Assembly.Location returns empty in single-file apps", + Justification = "AppContext.BaseDirectory is checked first; Assembly.Location is a fallback for non-single-file scenarios")] +#endif static IEnumerable GetSearchLocations() { - string execPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - if(execPath is not null) + // AppContext.BaseDirectory is the recommended way to get the app directory, + // especially for single-file apps where Assembly.Location returns empty. + if (!string.IsNullOrEmpty(AppContext.BaseDirectory)) { - yield return execPath; + yield return AppContext.BaseDirectory; } - string callingPath = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location); - if(callingPath is not null) +#pragma warning disable IL3000 // Assembly.Location returns empty in single-file apps (handled by AppContext.BaseDirectory above) + string execPath = Assembly.GetExecutingAssembly()?.Location; + if (!string.IsNullOrEmpty(execPath)) { - yield return callingPath; + yield return Path.GetDirectoryName(execPath); } - var entryAssembly = Assembly.GetEntryAssembly(); - if(entryAssembly is not null) + string callingPath = Assembly.GetCallingAssembly()?.Location; + if (!string.IsNullOrEmpty(callingPath)) { - string entryPath = Path.GetDirectoryName(entryAssembly.Location); - if(entryPath is not null) - { - yield return entryPath; - } + yield return Path.GetDirectoryName(callingPath); } - if(AppContext.BaseDirectory is not null) + var entryAssemblyPath = Assembly.GetEntryAssembly()?.Location; + if (!string.IsNullOrEmpty(entryAssemblyPath)) { - yield return AppContext.BaseDirectory; + yield return Path.GetDirectoryName(entryAssemblyPath); } +#pragma warning restore IL3000 - foreach(string extraPath in ExtraNativeLibSearchPaths) + foreach (string extraPath in ExtraNativeLibSearchPaths) { yield return extraPath; } - if(execPath is not null) + if (!string.IsNullOrEmpty(execPath)) { // If the this lib is being executed from its nuget package directory then the native // files should be found up a couple directories. @@ -114,7 +152,7 @@ static IEnumerable GetSearchLocations() static IEnumerable SearchContainerPaths(string containerDir, string library, (string Prefix, string LibPrefix, string Extension) platform) { - foreach(var subDir in GetSearchSubDir(library, platform)) + foreach (var subDir in GetSearchSubDir(library, platform)) { yield return Path.Combine(containerDir, subDir); yield return Path.Combine(containerDir, "publish", subDir); diff --git a/Secp256k1.Net/LoadLibNative.cs b/Secp256k1.Net/LoadLibNative.cs index 76db383..ca955e4 100644 --- a/Secp256k1.Net/LoadLibNative.cs +++ b/Secp256k1.Net/LoadLibNative.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Runtime.InteropServices; using System.Text; @@ -9,12 +10,61 @@ namespace Secp256k1Net { internal static class LoadLibNative { + +#if NET8_0_OR_GREATER + /// + /// Loads the native library using modern .NET NativeLibrary APIs. + /// Tries standard resolution first, then falls back to LibPathResolver. + /// + /// The library name (e.g., "secp256k1"). + /// Output parameter that receives the resolved library path. + /// The handle to the loaded library. + public static IntPtr LoadLibrary(string libName, out string libPath) + { + var assembly = typeof(Secp256k1).Assembly; + // Try standard resolution first (works for RID-specific builds and NativeAOT) + if (NativeLibrary.TryLoad(libName, assembly, + DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.ApplicationDirectory, + out var handle)) + { + libPath = libName; + return handle; + } + + // Also try with lib prefix for Unix + var libPrefixedName = "lib" + libName; + if (NativeLibrary.TryLoad(libPrefixedName, assembly, + DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.ApplicationDirectory, + out handle)) + { + libPath = libPrefixedName; + return handle; + } + + // Fallback: use LibPathResolver for comprehensive path probing + libPath = LibPathResolver.Resolve(libName); + return NativeLibrary.Load(libPath); + } + + public static void CloseLibrary(IntPtr lib) + { + NativeLibrary.Free(lib); + } + + public static IntPtr GetSymbolPointer(IntPtr libPtr, string symbolName) + { + return NativeLibrary.GetExport(libPtr, symbolName); + } + +#else + static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - public static IntPtr LoadLib(string libPath) + public static IntPtr LoadLibrary(string libName, out string libPath) { + libPath = File.Exists(libName) ? libName : LibPathResolver.Resolve(libName); IntPtr libPtr; if (IsWindows) @@ -23,13 +73,11 @@ public static IntPtr LoadLib(string libPath) } else if (IsLinux) { - const int RTLD_NOW = 2; - libPtr = DynamicLinkingLinux.dlopen(libPath, RTLD_NOW); + libPtr = DynamicLinkingLinux.dlopen(libPath, DynamicLinkingLinux.RTLD_NOW); } else if (IsMacOS) { - const int RTLD_NOW = 2; - libPtr = DynamicLinkingMacOS.dlopen(libPath, RTLD_NOW); + libPtr = DynamicLinkingMacOS.dlopen(libPath, DynamicLinkingMacOS.RTLD_NOW); } else { @@ -144,5 +192,6 @@ public static TDelegate GetDelegate(IntPtr libPtr, string symbolName, var functionPtr = pointerDereferenceFunc.Invoke(ptr); return Marshal.GetDelegateForFunctionPointer(functionPtr); } +#endif } } diff --git a/Secp256k1.Net/Secp256k1.Net.csproj b/Secp256k1.Net/Secp256k1.Net.csproj index 96f7e67..ce14fc7 100644 --- a/Secp256k1.Net/Secp256k1.Net.csproj +++ b/Secp256k1.Net/Secp256k1.Net.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.0;net8.0 true latest bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml @@ -12,15 +12,33 @@ https://github.com/zone117x/Secp256k1.Net MIT README.md - 1591,1573 + + 1591;NU5100;IL3000 true true true snupkg Secp256k1Net $(VersionSuffix) - 0.1.0 + 0.0.1-local.1 + + + + + + + + @@ -28,17 +46,39 @@ - - + + + + + + + + + + - <_PackageFiles Include="$(OutputPath)/native/**/*"> - Content - content/native/ - - + <_NativeFilesToPack Include="$(OutputPath)netstandard2.0/runtimes/*/native/*.*" /> + <_PackageFiles Include="@(_NativeFilesToPack)"> + runtimes/$([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetDirectoryName('%(Identity)'))))))/native/%(Filename)%(Extension) @@ -47,5 +87,9 @@ None build/ + <_PackageFiles Include="Secp256k1.Net.targets"> + None + buildTransitive/ + \ No newline at end of file diff --git a/Secp256k1.Net/Secp256k1.Net.targets b/Secp256k1.Net/Secp256k1.Net.targets index 483e19f..394718b 100644 --- a/Secp256k1.Net/Secp256k1.Net.targets +++ b/Secp256k1.Net/Secp256k1.Net.targets @@ -1,14 +1,47 @@ - + + - $(MSBuildThisFileDirectory)/../content/native + <_Secp256k1NativeDir>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../runtimes')) - + + - + <_Secp256k1AllNatives Include="$(_Secp256k1NativeDir)/*/native/*.*" /> - + + + - \ No newline at end of file + + + + <_Secp256k1AllNativesPublish Include="$(_Secp256k1NativeDir)/*/native/*.*" /> + + + + + diff --git a/Secp256k1.Net/Secp256k1.Static.cs b/Secp256k1.Net/Secp256k1.Static.cs new file mode 100644 index 0000000..a643084 --- /dev/null +++ b/Secp256k1.Net/Secp256k1.Static.cs @@ -0,0 +1,613 @@ +using System; +using System.Security.Cryptography; + +namespace Secp256k1Net +{ + public partial class Secp256k1 + { + [ThreadStatic] + private static Secp256k1 _instance; + + /// + /// Gets a thread-local instance with its own context and error callback. + /// Each thread gets an isolated context. Do not dispose this instance. + /// + private static Secp256k1 Instance => _instance ??= new Secp256k1(); + + #region Static Helper Methods + + /// + /// Generates a new random secret key using a cryptographically secure random number generator. + /// + /// 32-byte secret key. + public static byte[] CreateSecretKey() + { + var secretKey = new byte[SECRET_LENGTH]; + using var rng = RandomNumberGenerator.Create(); + while (true) + { + rng.GetBytes(secretKey); + if (IsValidSecretKey(secretKey)) + return secretKey; + } + } + + /// + /// Creates a serialized public key from a secret key. + /// + /// 32-byte secret key. + /// If true, returns 33-byte compressed format; otherwise 65-byte uncompressed. + /// Serialized public key (33 or 65 bytes). + /// Thrown when the secret key is invalid. + public static byte[] CreatePublicKey(ReadOnlySpan secretKey, bool compressed = true) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyCreate(pubkeyInternal, secretKey)) + throw new ArgumentException("Invalid secret key", nameof(secretKey)); + + var outputLen = compressed ? SERIALIZED_COMPRESSED_PUBKEY_LENGTH : SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH; + var result = new byte[outputLen]; + var len = (nuint)outputLen; + var flags = compressed ? Secp256k1EcFlags.Compressed : Secp256k1EcFlags.Uncompressed; + Instance.EcPubkeySerialize(result, ref len, pubkeyInternal, flags); + return result; + } + + /// + /// Creates a serialized x-only public key from a secret key. + /// + /// 32-byte secret key. + /// Tuple of 32-byte x-only public key and parity (0 or 1). + /// Thrown when the secret key is invalid. + public static (byte[] XOnlyPublicKey, byte Parity) CreateXOnlyPublicKey(ReadOnlySpan secretKey) + { + const int KEYPAIR_LENGTH = 96; + const int XONLY_PUBKEY_LENGTH = 64; + const int XONLY_SERIALIZED_LENGTH = 32; + + Span keypair = stackalloc byte[KEYPAIR_LENGTH]; + if (!Instance.KeypairCreate(keypair, secretKey)) + throw new ArgumentException("Invalid secret key", nameof(secretKey)); + + Span xonlyInternal = stackalloc byte[XONLY_PUBKEY_LENGTH]; + Instance.KeypairXonlyPub(xonlyInternal, out int parity, keypair); + + var result = new byte[XONLY_SERIALIZED_LENGTH]; + Instance.XonlyPubkeySerialize(result, xonlyInternal); + return (result, (byte)parity); + } + + /// + /// Creates a new key pair (secret key and public key). + /// + /// If true, returns 33-byte compressed public key; otherwise 65-byte uncompressed. + /// Tuple of 32-byte secret key and serialized public key. + public static (byte[] SecretKey, byte[] PublicKey) CreateKeyPair(bool compressed = true) + { + var secretKey = CreateSecretKey(); + var publicKey = CreatePublicKey(secretKey, compressed); + return (secretKey, publicKey); + } + + /// + /// Verifies that a secret key is valid. + /// + /// 32-byte secret key to validate. + /// True if the secret key is valid, false otherwise. + public static bool IsValidSecretKey(ReadOnlySpan secretKey) + { + if (secretKey.Length < SECRET_LENGTH) + return false; + return Instance.EcSeckeyVerify(secretKey); + } + + /// + /// Verifies that a serialized public key is valid. + /// + /// Serialized public key (33 or 65 bytes). + /// True if the public key is valid, false otherwise. + public static bool IsValidPublicKey(ReadOnlySpan publicKey) + { + if (publicKey.Length != SERIALIZED_COMPRESSED_PUBKEY_LENGTH && + publicKey.Length != SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH) + return false; + + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + return Instance.EcPubkeyParse(pubkeyInternal, publicKey); + } + + /// + /// Compresses a public key to 33-byte format. + /// + /// Serialized public key (33 or 65 bytes). + /// 33-byte compressed public key. + /// Thrown when the public key is invalid. + public static byte[] CompressPublicKey(ReadOnlySpan publicKey) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + throw new ArgumentException("Invalid public key", nameof(publicKey)); + + var result = new byte[SERIALIZED_COMPRESSED_PUBKEY_LENGTH]; + var len = (nuint)SERIALIZED_COMPRESSED_PUBKEY_LENGTH; + Instance.EcPubkeySerialize(result, ref len, pubkeyInternal, Secp256k1EcFlags.Compressed); + return result; + } + + /// + /// Decompresses a public key to 65-byte uncompressed format. + /// + /// Serialized public key (33 or 65 bytes). + /// 65-byte uncompressed public key. + /// Thrown when the public key is invalid. + public static byte[] DecompressPublicKey(ReadOnlySpan publicKey) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + throw new ArgumentException("Invalid public key", nameof(publicKey)); + + var result = new byte[SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; + var len = (nuint)SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH; + Instance.EcPubkeySerialize(result, ref len, pubkeyInternal, Secp256k1EcFlags.Uncompressed); + return result; + } + + /// + /// Creates an ECDSA signature in compact format. + /// + /// 32-byte message hash to sign. + /// 32-byte secret key. + /// 64-byte compact signature. + /// Thrown when signing fails (invalid secret key or nonce generation failure). + public static byte[] Sign(ReadOnlySpan messageHash, ReadOnlySpan secretKey) + { + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + if (!Instance.EcdsaSign(sigInternal, messageHash, secretKey)) + throw new ArgumentException("Signing failed - invalid secret key or nonce generation failure"); + + var result = new byte[SERIALIZED_SIGNATURE_SIZE]; + Instance.EcdsaSignatureSerializeCompact(result, sigInternal); + return result; + } + + /// + /// Verifies an ECDSA signature. + /// + /// 64-byte compact signature. + /// 32-byte message hash that was signed. + /// Serialized public key (33 or 65 bytes). + /// True if the signature is valid, false otherwise. + public static bool Verify(ReadOnlySpan signature, ReadOnlySpan messageHash, ReadOnlySpan publicKey) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + return false; + + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + if (!Instance.EcdsaSignatureParseCompact(sigInternal, signature)) + return false; + + return Instance.EcdsaVerify(sigInternal, messageHash, pubkeyInternal); + } + + /// + /// Creates a recoverable ECDSA signature. + /// + /// 32-byte message hash to sign. + /// 32-byte secret key. + /// Tuple of 64-byte compact signature and recovery ID (0-3). + /// Thrown when signing fails. + public static (byte[] Signature, byte RecoveryId) SignRecoverable(ReadOnlySpan messageHash, ReadOnlySpan secretKey) + { + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_SIZE]; + if (!Instance.EcdsaSignRecoverable(sigInternal, messageHash, secretKey)) + throw new ArgumentException("Signing failed - invalid secret key or nonce generation failure"); + + var signature = new byte[SERIALIZED_SIGNATURE_SIZE]; + Instance.EcdsaRecoverableSignatureSerializeCompact(signature, out int recid, sigInternal); + return (signature, (byte)recid); + } + + /// + /// Recovers a public key from a recoverable ECDSA signature. + /// + /// 64-byte compact signature. + /// Recovery ID (0-3). + /// 32-byte message hash that was signed. + /// If true, returns 33-byte compressed format; otherwise 65-byte uncompressed. + /// Serialized public key (33 or 65 bytes). + /// Thrown when recovery fails. + public static byte[] RecoverPublicKey(ReadOnlySpan signature, byte recoveryId, ReadOnlySpan messageHash, bool compressed = true) + { + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_SIZE]; + if (!Instance.EcdsaRecoverableSignatureParseCompact(sigInternal, signature, recoveryId)) + throw new ArgumentException("Invalid signature or recovery ID"); + + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcdsaRecover(pubkeyInternal, sigInternal, messageHash)) + throw new ArgumentException("Public key recovery failed"); + + var outputLen = compressed ? SERIALIZED_COMPRESSED_PUBKEY_LENGTH : SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH; + var result = new byte[outputLen]; + var len = (nuint)outputLen; + var flags = compressed ? Secp256k1EcFlags.Compressed : Secp256k1EcFlags.Uncompressed; + Instance.EcPubkeySerialize(result, ref len, pubkeyInternal, flags); + return result; + } + + /// + /// Computes an ECDH shared secret. + /// + /// Serialized public key (33 or 65 bytes). + /// 32-byte secret key. + /// 32-byte shared secret. + /// Thrown when the public key is invalid or ECDH computation fails. + public static byte[] ComputeSharedSecret(ReadOnlySpan publicKey, ReadOnlySpan secretKey) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + throw new ArgumentException("Invalid public key", nameof(publicKey)); + + var result = new byte[SECRET_LENGTH]; + if (!Instance.Ecdh(result, pubkeyInternal, secretKey)) + throw new ArgumentException("ECDH computation failed - invalid secret key"); + + return result; + } + + /// + /// Creates a Schnorr signature (BIP-340). + /// For variable-length messages, use to create a 32-byte hash with domain separation. + /// + /// 32-byte message hash to sign. Use to hash variable-length messages. + /// 32-byte secret key. + /// Optional 32 bytes of auxiliary randomness. If null, zeros are used. + /// If true (default), verifies the signature after signing to strictly follow BIP-340. Set to false for better performance when verification is not required. + /// 64-byte Schnorr signature. + /// Thrown when signing or verification fails. + public static byte[] SignSchnorr(ReadOnlySpan messageHash, ReadOnlySpan secretKey, ReadOnlySpan auxRand = default, bool verify = true) + { + const int KEYPAIR_LENGTH = 96; + const int XONLY_PUBKEY_LENGTH = 64; + + if (messageHash.Length != 32) + throw new ArgumentException($"Message hash must be exactly 32 bytes. Use {nameof(TaggedHash)}() to create a 32-byte hash with domain separation for variable-length messages.", nameof(messageHash)); + + Span keypair = stackalloc byte[KEYPAIR_LENGTH]; + if (!Instance.KeypairCreate(keypair, secretKey)) + throw new ArgumentException("Invalid secret key", nameof(secretKey)); + + Span auxRandActual = stackalloc byte[32]; + if (!auxRand.IsEmpty) + { + if (auxRand.Length < 32) + throw new ArgumentException("Auxiliary randomness must be at least 32 bytes", nameof(auxRand)); + auxRand.Slice(0, 32).CopyTo(auxRandActual); + } + + var signature = new byte[SERIALIZED_SIGNATURE_SIZE]; + if (!Instance.SchnorrsigSign32(signature, messageHash, keypair, auxRandActual)) + throw new ArgumentException("Schnorr signing failed"); + + if (verify) + { + Span xonlyPubkey = stackalloc byte[XONLY_PUBKEY_LENGTH]; + Instance.KeypairXonlyPub(xonlyPubkey, out _, keypair); + if (!Instance.SchnorrsigVerify(signature, messageHash, xonlyPubkey)) + throw new ArgumentException("Schnorr signature verification failed"); + } + + return signature; + } + + /// + /// Verifies a Schnorr signature (BIP-340). + /// + /// 64-byte Schnorr signature. + /// Message that was signed (variable length). + /// Public key in any format: 32-byte x-only, 33-byte compressed, or 65-byte uncompressed. + /// True if the signature is valid, false otherwise. + /// Thrown when the public key format is invalid. + public static bool VerifySchnorr(ReadOnlySpan signature, ReadOnlySpan message, ReadOnlySpan publicKey) + { + const int XONLY_PUBKEY_LENGTH = 64; + + Span xonlyInternal = stackalloc byte[XONLY_PUBKEY_LENGTH]; + + if (publicKey.Length == 32) + { + // X-only public key + if (!Instance.XonlyPubkeyParse(xonlyInternal, publicKey)) + throw new ArgumentException("Invalid x-only public key", nameof(publicKey)); + } + else if (publicKey.Length == 33 || publicKey.Length == 65) + { + // Compressed or uncompressed public key - parse and convert to x-only + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + throw new ArgumentException("Invalid public key", nameof(publicKey)); + Instance.XonlyPubkeyFromPubkey(xonlyInternal, out _, pubkeyInternal); + } + else + { + throw new ArgumentException("Public key must be 32 bytes (x-only), 33 bytes (compressed), or 65 bytes (uncompressed)", nameof(publicKey)); + } + + return Instance.SchnorrsigVerify(signature, message, xonlyInternal); + } + + /// + /// Converts a compact signature to DER format. + /// + /// 64-byte compact signature. + /// DER-encoded signature (up to 72 bytes). + /// Thrown when the signature is invalid. + public static byte[] SignatureToDer(ReadOnlySpan compactSignature) + { + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + if (!Instance.EcdsaSignatureParseCompact(sigInternal, compactSignature)) + throw new ArgumentException("Invalid compact signature", nameof(compactSignature)); + + Span derBuffer = stackalloc byte[SERIALIZED_DER_SIGNATURE_MAX_SIZE]; + var derLen = (nuint)SERIALIZED_DER_SIGNATURE_MAX_SIZE; + if (!Instance.EcdsaSignatureSerializeDer(derBuffer, ref derLen, sigInternal)) + throw new ArgumentException("Failed to serialize signature to DER format"); + + return derBuffer.Slice(0, (int)derLen).ToArray(); + } + + /// + /// Converts a DER-encoded signature to compact format. + /// + /// DER-encoded signature. + /// 64-byte compact signature. + /// Thrown when the signature is invalid. + public static byte[] SignatureFromDer(ReadOnlySpan derSignature) + { + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + if (!Instance.EcdsaSignatureParseDer(sigInternal, derSignature)) + throw new ArgumentException("Invalid DER signature", nameof(derSignature)); + + var result = new byte[SERIALIZED_SIGNATURE_SIZE]; + Instance.EcdsaSignatureSerializeCompact(result, sigInternal); + return result; + } + + /// + /// Verifies an ECDSA signature in DER format. + /// + /// DER-encoded signature. + /// 32-byte message hash that was signed. + /// Serialized public key (33 or 65 bytes). + /// True if the signature is valid, false otherwise. + public static bool VerifyDer(ReadOnlySpan derSignature, ReadOnlySpan messageHash, ReadOnlySpan publicKey) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + return false; + + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + if (!Instance.EcdsaSignatureParseDer(sigInternal, derSignature)) + return false; + + return Instance.EcdsaVerify(sigInternal, messageHash, pubkeyInternal); + } + + /// + /// Normalizes a signature to lower-S form. + /// + /// 64-byte compact signature. + /// Normalized 64-byte compact signature in lower-S form. + /// Thrown when the signature is invalid. + public static byte[] NormalizeSignature(ReadOnlySpan signature) + { + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + if (!Instance.EcdsaSignatureParseCompact(sigInternal, signature)) + throw new ArgumentException("Invalid signature", nameof(signature)); + + Span normalizedInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + Instance.EcdsaSignatureNormalize(normalizedInternal, sigInternal); + + var result = new byte[SERIALIZED_SIGNATURE_SIZE]; + Instance.EcdsaSignatureSerializeCompact(result, normalizedInternal); + return result; + } + + /// + /// Checks if a signature is in normalized lower-S form. + /// + /// 64-byte compact signature. + /// True if the signature is already normalized, false if it needed normalization. + /// Thrown when the signature is invalid. + public static bool IsNormalizedSignature(ReadOnlySpan signature) + { + Span sigInternal = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + if (!Instance.EcdsaSignatureParseCompact(sigInternal, signature)) + throw new ArgumentException("Invalid signature", nameof(signature)); + + // EcdsaSignatureNormalize returns true (1) if the signature was NOT normalized + // Returns false (0) if it was already normalized + // We need to provide a valid output buffer even though we don't use it + Span normalizedOutput = stackalloc byte[UNSERIALIZED_SIGNATURE_LENGTH]; + return !Instance.EcdsaSignatureNormalize(normalizedOutput, sigInternal); + } + + /// + /// Tweaks a secret key by adding a tweak value to it. + /// Used in BIP-32 HD wallet derivation. + /// + /// 32-byte secret key. + /// 32-byte tweak value. + /// 32-byte tweaked secret key. + /// Thrown when the secret key or tweak is invalid. + public static byte[] TweakSecretKeyAdd(ReadOnlySpan secretKey, ReadOnlySpan tweak) + { + var result = new byte[SECRET_LENGTH]; + secretKey.Slice(0, SECRET_LENGTH).CopyTo(result); + + if (!Instance.EcSeckeyTweakAdd(result, tweak)) + throw new ArgumentException("Invalid secret key or tweak"); + + return result; + } + + /// + /// Tweaks a public key by adding tweak times the generator to it. + /// Used in BIP-32 HD wallet derivation. + /// + /// Serialized public key (33 or 65 bytes). + /// 32-byte tweak value. + /// If true, returns 33-byte compressed format; otherwise 65-byte uncompressed. + /// Serialized tweaked public key. + /// Thrown when the public key or tweak is invalid. + public static byte[] TweakPublicKeyAdd(ReadOnlySpan publicKey, ReadOnlySpan tweak, bool compressed = true) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + throw new ArgumentException("Invalid public key", nameof(publicKey)); + + if (!Instance.EcPubkeyTweakAdd(pubkeyInternal, tweak)) + throw new ArgumentException("Invalid tweak", nameof(tweak)); + + var outputLen = compressed ? SERIALIZED_COMPRESSED_PUBKEY_LENGTH : SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH; + var result = new byte[outputLen]; + var len = (nuint)outputLen; + var flags = compressed ? Secp256k1EcFlags.Compressed : Secp256k1EcFlags.Uncompressed; + Instance.EcPubkeySerialize(result, ref len, pubkeyInternal, flags); + return result; + } + + /// + /// Computes a tagged hash as defined in BIP-340. + /// Returns SHA256(SHA256(tag) || SHA256(tag) || message). + /// + /// Tag bytes for domain separation. + /// Message to hash. + /// 32-byte hash. + public static byte[] TaggedHash(ReadOnlySpan tag, ReadOnlySpan message) + { + var result = new byte[HASH_LENGTH]; + Instance.TaggedSha256(result, tag, message); + return result; + } + + /// + /// Negates a secret key in place. + /// + /// 32-byte secret key. + /// 32-byte negated secret key. + /// Thrown when the secret key is invalid. + public static byte[] NegateSecretKey(ReadOnlySpan secretKey) + { + var result = new byte[SECRET_LENGTH]; + secretKey.Slice(0, SECRET_LENGTH).CopyTo(result); + + if (!Instance.EcSeckeyNegate(result)) + throw new ArgumentException("Invalid secret key", nameof(secretKey)); + + return result; + } + + /// + /// Negates a public key. + /// + /// Serialized public key (33 or 65 bytes). + /// If true, returns 33-byte compressed format; otherwise 65-byte uncompressed. + /// Serialized negated public key. + /// Thrown when the public key is invalid. + public static byte[] NegatePublicKey(ReadOnlySpan publicKey, bool compressed = true) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + throw new ArgumentException("Invalid public key", nameof(publicKey)); + + Instance.EcPubkeyNegate(pubkeyInternal); + + var outputLen = compressed ? SERIALIZED_COMPRESSED_PUBKEY_LENGTH : SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH; + var result = new byte[outputLen]; + var len = (nuint)outputLen; + var flags = compressed ? Secp256k1EcFlags.Compressed : Secp256k1EcFlags.Uncompressed; + Instance.EcPubkeySerialize(result, ref len, pubkeyInternal, flags); + return result; + } + + /// + /// Tweaks a secret key by multiplying it by a tweak value. + /// + /// 32-byte secret key. + /// 32-byte tweak value. + /// 32-byte tweaked secret key. + /// Thrown when the secret key or tweak is invalid. + public static byte[] TweakSecretKeyMul(ReadOnlySpan secretKey, ReadOnlySpan tweak) + { + var result = new byte[SECRET_LENGTH]; + secretKey.Slice(0, SECRET_LENGTH).CopyTo(result); + + if (!Instance.EcSeckeyTweakMul(result, tweak)) + throw new ArgumentException("Invalid secret key or tweak"); + + return result; + } + + /// + /// Tweaks a public key by multiplying it by a tweak value. + /// + /// Serialized public key (33 or 65 bytes). + /// 32-byte tweak value. + /// If true, returns 33-byte compressed format; otherwise 65-byte uncompressed. + /// Serialized tweaked public key. + /// Thrown when the public key or tweak is invalid. + public static byte[] TweakPublicKeyMul(ReadOnlySpan publicKey, ReadOnlySpan tweak, bool compressed = true) + { + Span pubkeyInternal = stackalloc byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(pubkeyInternal, publicKey)) + throw new ArgumentException("Invalid public key", nameof(publicKey)); + + if (!Instance.EcPubkeyTweakMul(pubkeyInternal, tweak)) + throw new ArgumentException("Invalid tweak", nameof(tweak)); + + var outputLen = compressed ? SERIALIZED_COMPRESSED_PUBKEY_LENGTH : SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH; + var result = new byte[outputLen]; + var len = (nuint)outputLen; + var flags = compressed ? Secp256k1EcFlags.Compressed : Secp256k1EcFlags.Uncompressed; + Instance.EcPubkeySerialize(result, ref len, pubkeyInternal, flags); + return result; + } + + /// + /// Combines multiple public keys into a single public key by adding them together. + /// Useful for multisig and key aggregation schemes. + /// + /// Array of serialized public keys (each 33 or 65 bytes). + /// If true, returns 33-byte compressed format; otherwise 65-byte uncompressed. + /// Serialized combined public key. + /// Thrown when any public key is invalid or combination fails. + public static byte[] CombinePublicKeys(byte[][] publicKeys, bool compressed = true) + { + if (publicKeys == null || publicKeys.Length == 0) + throw new ArgumentException("At least one public key is required", nameof(publicKeys)); + + // Parse all public keys to internal format + var internalKeys = new byte[publicKeys.Length][]; + for (int i = 0; i < publicKeys.Length; i++) + { + internalKeys[i] = new byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyParse(internalKeys[i], publicKeys[i])) + throw new ArgumentException($"Invalid public key at index {i}", nameof(publicKeys)); + } + + var combinedInternal = new byte[UNSERIALIZED_PUBKEY_LENGTH]; + if (!Instance.EcPubkeyCombine(combinedInternal, internalKeys)) + throw new ArgumentException("Failed to combine public keys - result may be point at infinity"); + + var outputLen = compressed ? SERIALIZED_COMPRESSED_PUBKEY_LENGTH : SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH; + var result = new byte[outputLen]; + var len = (nuint)outputLen; + var flags = compressed ? Secp256k1EcFlags.Compressed : Secp256k1EcFlags.Uncompressed; + Instance.EcPubkeySerialize(result, ref len, combinedInternal, flags); + return result; + } + + #endregion + } +} diff --git a/Secp256k1.Net/Secp256k1.cs b/Secp256k1.Net/Secp256k1.cs index 7057b93..7189590 100644 --- a/Secp256k1.Net/Secp256k1.cs +++ b/Secp256k1.Net/Secp256k1.cs @@ -1,124 +1,73 @@ -using System; +using System; using System.Runtime.InteropServices; namespace Secp256k1Net { - /// - /// A pointer to a function that applies hash function to a point. - /// Returns: 1 if a point was successfully hashed. 0 will cause ecdh to fail. + /// Type for error and illegal callback functions. /// - /// Pointer to an array to be filled by the function. - /// Pointer to a 32-byte x coordinate. - /// Pointer to a 32-byte y coordinate. - /// Arbitrary data pointer that is passed through. - /// Returns: 1 if a point was successfully hashed. 0 will cause ecdh to fail. - public delegate int EcdhHashFunction(Span output, Span x, Span y, IntPtr data); - + /// Error message. + /// Callback marker, set by user together with callback. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void ErrorCallbackDelegate(string message, IntPtr data); - public unsafe class Secp256k1 : IDisposable + public unsafe partial class Secp256k1 : IDisposable { - + public const int SECRET_KEY_LENGTH = 32; public const int SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH = 65; public const int SERIALIZED_COMPRESSED_PUBKEY_LENGTH = 33; - public const int PUBKEY_LENGTH = 64; - public const int PRIVKEY_LENGTH = 32; + public const int UNSERIALIZED_PUBKEY_LENGTH = 64; public const int UNSERIALIZED_SIGNATURE_SIZE = 65; public const int SERIALIZED_SIGNATURE_SIZE = 64; public const int SERIALIZED_DER_SIGNATURE_MAX_SIZE = 72; - public const int SIGNATURE_LENGTH = 64; + public const int UNSERIALIZED_SIGNATURE_LENGTH = 64; public const int HASH_LENGTH = 32; public const int SECRET_LENGTH = 32; public const int NONCE_LENGTH = 32; + internal const string LIB = "secp256k1"; - static readonly Lazy secp256k1_context_create - = LazyDelegate(nameof(secp256k1_context_create)); - static readonly Lazy secp256k1_context_set_illegal_callback - = LazyDelegate(nameof(secp256k1_context_set_illegal_callback)); - static readonly Lazy secp256k1_context_set_error_callback - = LazyDelegate(nameof(secp256k1_context_set_error_callback)); - static readonly Lazy secp256k1_context_destroy - = LazyDelegate(nameof(secp256k1_context_destroy)); - static readonly Lazy secp256k1_ec_pubkey_create - = LazyDelegate(nameof(secp256k1_ec_pubkey_create)); - static readonly Lazy secp256k1_ec_seckey_verify - = LazyDelegate(nameof(secp256k1_ec_seckey_verify)); - static readonly Lazy secp256k1_ec_pubkey_serialize - = LazyDelegate(nameof(secp256k1_ec_pubkey_serialize)); - static readonly Lazy secp256k1_ec_pubkey_parse - = LazyDelegate(nameof(secp256k1_ec_pubkey_parse)); - static readonly Lazy secp256k1_ecdsa_recoverable_signature_parse_compact - = LazyDelegate(nameof(secp256k1_ecdsa_recoverable_signature_parse_compact)); - static readonly Lazy secp256k1_ecdsa_recoverable_signature_serialize_compact - = LazyDelegate(nameof(secp256k1_ecdsa_recoverable_signature_serialize_compact)); - static readonly Lazy secp256k1_ecdsa_sign_recoverable - = LazyDelegate(nameof(secp256k1_ecdsa_sign_recoverable)); - static readonly Lazy secp256k1_ecdsa_sign - = LazyDelegate(nameof(secp256k1_ecdsa_sign)); - static readonly Lazy secp256k1_ecdsa_recover - = LazyDelegate(nameof(secp256k1_ecdsa_recover)); - static readonly Lazy secp256k1_ecdsa_signature_normalize - = LazyDelegate(nameof(secp256k1_ecdsa_signature_normalize)); - static readonly Lazy secp256k1_ecdsa_signature_parse_der - = LazyDelegate(nameof(secp256k1_ecdsa_signature_parse_der)); - static readonly Lazy secp256k1_ecdsa_signature_parse_compact - = LazyDelegate(nameof(secp256k1_ecdsa_signature_parse_compact)); - static readonly Lazy secp256k1_ecdsa_signature_serialize_der - = LazyDelegate(nameof(secp256k1_ecdsa_signature_serialize_der)); - static readonly Lazy secp256k1_ecdsa_signature_serialize_compact - = LazyDelegate(nameof(secp256k1_ecdsa_signature_serialize_compact)); - static readonly Lazy secp256k1_ecdsa_verify - = LazyDelegate(nameof(secp256k1_ecdsa_verify)); - static readonly Lazy secp256k1_ecdh - = LazyDelegate(nameof(secp256k1_ecdh)); - static readonly Lazy secp256k1_ec_pubkey_tweak_mul - = LazyDelegate(nameof(secp256k1_ec_pubkey_tweak_mul)); - static readonly Lazy secp256k1_nonce_function_rfc6979 - = LazyDelegate(nameof(secp256k1_nonce_function_rfc6979), Marshal.ReadIntPtr); - static readonly Lazy secp256k1_ec_pubkey_negate = - LazyDelegate(nameof(secp256k1_ec_pubkey_negate)); + // Initialization infrastructure + private static readonly object _initLock = new(); + private static volatile bool _initialized; + private static IntPtr _libHandle; + private static string _libPath; - private static readonly Lazy secp256k1_ec_pubkey_combine = - LazyDelegate(nameof(secp256k1_ec_pubkey_combine)); + /// Gets the path to the loaded native library. + public static string LibPath => _libPath ?? throw new InvalidOperationException("Library not loaded"); - internal const string LIB = "secp256k1"; + internal static void EnsureInitialized() + { + if (_initialized) return; + lock (_initLock) + { + if (_initialized) return; - public static string LibPath => _libPath.Value; - static readonly Lazy _libPath = new Lazy(() => LibPathResolver.Resolve(LIB)); - static readonly Lazy _libPtr = new Lazy(() => LoadLibNative.LoadLib(_libPath.Value)); + _libHandle = LoadLibNative.LoadLibrary(LIB, out var path); + _libPath = path; + + Secp256k1Interop.LoadFunctions(_libHandle); + _initialized = true; + } + } IntPtr _ctx; - + private ErrorCallbackDelegate _errorCallback; private GCHandle _errorCallbackHandle; private IntPtr _errorCallbackPtr; - - private static void DefaultErrorCallback(string message, void* data) + + private static void DefaultErrorCallback(string message, IntPtr data) { Console.Error.WriteLine(message); } public Secp256k1(ErrorCallbackDelegate errorCallback = null) { - _ctx = secp256k1_context_create.Value((uint)(Flags.SECP256K1_CONTEXT_SIGN | Flags.SECP256K1_CONTEXT_VERIFY)); + EnsureInitialized(); + _ctx = Secp256k1Interop._context_create((uint)Secp256k1ContextFlags.None); - SetErrorCallback(errorCallback ?? DefaultErrorCallback, null); - } - static Lazy LazyDelegate(string symbol) - { - return new Lazy(() => - { - return LoadLibNative.GetDelegate(_libPtr.Value, symbol); - }); - } - - static Lazy LazyDelegate(string symbol, Func pointerDereferenceFunc) - { - return new Lazy(() => - { - return LoadLibNative.GetDelegate(_libPtr.Value, symbol, pointerDereferenceFunc); - }); + SetErrorCallback(errorCallback ?? DefaultErrorCallback, IntPtr.Zero); } /// @@ -126,7 +75,7 @@ static Lazy LazyDelegate(string symbol, Func /// User-defined callback, it is called in the case of the error or illegal operation. /// User-defined callback marker, it is passed as second argument when callback is called. - public void SetErrorCallback(ErrorCallbackDelegate cb, void* data = null) + public void SetErrorCallback(ErrorCallbackDelegate cb, IntPtr data = default) { if (_errorCallbackPtr != IntPtr.Zero) { @@ -135,624 +84,96 @@ public void SetErrorCallback(ErrorCallbackDelegate cb, void* data = null) _errorCallback = cb; _errorCallbackHandle = GCHandle.Alloc(_errorCallback); _errorCallbackPtr = Marshal.GetFunctionPointerForDelegate(_errorCallback); - - secp256k1_context_set_illegal_callback.Value(_ctx, _errorCallback, data); - secp256k1_context_set_error_callback.Value(_ctx, _errorCallback, data); - } - - /// - /// Recover an ECDSA public key from a signature. - /// - /// Output for the 64 byte recovered public key to be written to. - /// The initialized signature that supports pubkey recovery. - /// The 32-byte message hash assumed to be signed. - /// - /// True if the public key successfully recovered (which guarantees a correct signature). - /// - public bool Recover(Span publicKeyOutput, Span signature, Span message) - { - if (publicKeyOutput.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKeyOutput)} must be {PUBKEY_LENGTH} bytes"); - } - if (signature.Length < UNSERIALIZED_SIGNATURE_SIZE) - { - throw new ArgumentException($"{nameof(signature)} must be {UNSERIALIZED_SIGNATURE_SIZE} bytes"); - } - if (message.Length < 32) - { - throw new ArgumentException($"{nameof(message)} must be 32 bytes"); - } - fixed (byte* publicKeyPtr = &MemoryMarshal.GetReference(publicKeyOutput), - sigPtr = &MemoryMarshal.GetReference(signature), - msgPtr = &MemoryMarshal.GetReference(message)) - { - return secp256k1_ecdsa_recover.Value(_ctx, publicKeyPtr, sigPtr, msgPtr) == 1; - } + Secp256k1Interop._context_set_illegal_callback(_ctx, _errorCallbackPtr, (void*)data); + Secp256k1Interop._context_set_error_callback(_ctx, _errorCallbackPtr, (void*)data); } /// - /// Verify an ECDSA secret key. + /// Sort an array of public keys in lexicographic order (of their compressed serialization). + /// The input array is reordered in place. /// - /// 32-byte secret key. - /// True if secret key is valid, false if secret key is invalid. - public bool SecretKeyVerify(Span secretKey) + /// Array of 64-byte public keys to sort. The array will be modified in place. + /// True on success, false on failure. + /// Thrown when the array is null, empty, or contains invalid elements. + public bool EcPubkeySort(byte[][] publicKeys) { - if (secretKey.Length < PRIVKEY_LENGTH) - { - throw new ArgumentException($"{nameof(secretKey)} must be {PRIVKEY_LENGTH} bytes"); - } - - fixed (byte* privKeyPtr = &MemoryMarshal.GetReference(secretKey)) - { - return secp256k1_ec_seckey_verify.Value(_ctx, privKeyPtr) == 1; - } - } - - /// - /// Gets the public key for a given private key. - /// - /// Output for the 64 byte recovered public key to be written to. - /// The input private key to obtain the public key for. - /// - /// True if the private key is valid and public key was obtained. - /// - public bool PublicKeyCreate(Span publicKeyOutput, Span privateKeyInput) - { - if (publicKeyOutput.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKeyOutput)} must be {PUBKEY_LENGTH} bytes"); - } - if (privateKeyInput.Length < PRIVKEY_LENGTH) - { - throw new ArgumentException($"{nameof(privateKeyInput)} must be {PRIVKEY_LENGTH} bytes"); - } - - fixed (byte* pubKeyPtr = &MemoryMarshal.GetReference(publicKeyOutput), - privKeyPtr = &MemoryMarshal.GetReference(privateKeyInput)) - { - return secp256k1_ec_pubkey_create.Value(_ctx, pubKeyPtr, privKeyPtr) == 1; - } - } - - /// - /// Parse a compact ECDSA signature (64 bytes + recovery id). - /// - /// Output for the signature to be written to. - /// The 64-byte compact signature input. - /// The recovery id (0, 1, 2 or 3). - /// True when the signature could be parsed. - public bool RecoverableSignatureParseCompact(Span signatureOutput, Span compactSignature, int recoveryID) - { - if (signatureOutput.Length < UNSERIALIZED_SIGNATURE_SIZE) - { - throw new ArgumentException($"{nameof(signatureOutput)} must be 64 bytes"); - } - if (compactSignature.Length < SERIALIZED_SIGNATURE_SIZE) - { - throw new ArgumentException($"{nameof(compactSignature)} must be 64 bytes"); - } - - fixed (byte* sigPtr = &MemoryMarshal.GetReference(signatureOutput), - inputPtr = &MemoryMarshal.GetReference(compactSignature)) - { - return secp256k1_ecdsa_recoverable_signature_parse_compact.Value(_ctx, sigPtr, inputPtr, recoveryID) == 1; - } - } - - /// - /// Create a recoverable ECDSA signature. - /// - /// Output where the signature will be placed. - /// The 32-byte message hash being signed. - /// A 32-byte secret key. - /// - /// True if signature created, false if the nonce generation function failed, or the private key was invalid. - /// - public bool SignRecoverable(Span signatureOutput, Span messageHash, Span secretKey) - { - if (signatureOutput.Length < UNSERIALIZED_SIGNATURE_SIZE) - { - throw new ArgumentException($"{nameof(signatureOutput)} must be 65 bytes"); - } - if (messageHash.Length < 32) - { - throw new ArgumentException($"{nameof(messageHash)} must be 32 bytes"); - } - if (secretKey.Length < PRIVKEY_LENGTH) - { - throw new ArgumentException($"{nameof(secretKey)} must be 32 bytes"); - } - - fixed (byte* sigPtr = &MemoryMarshal.GetReference(signatureOutput), - msgPtr = &MemoryMarshal.GetReference(messageHash), - secPtr = &MemoryMarshal.GetReference(secretKey.Slice(secretKey.Length - 32))) - { - - return secp256k1_ecdsa_sign_recoverable.Value(_ctx, sigPtr, msgPtr, secPtr, IntPtr.Zero, IntPtr.Zero) == 1; - } - } - - - /// - /// Serialize an ECDSA signature in compact format (64 bytes + recovery id). - /// - /// Output for the 64-byte array of the compact signature. - /// The recovery ID. - /// The initialized signature. - public bool RecoverableSignatureSerializeCompact(Span compactSignatureOutput, out int recoveryID, Span signature) - { - if (compactSignatureOutput.Length < SERIALIZED_SIGNATURE_SIZE) - { - throw new ArgumentException($"{nameof(compactSignatureOutput)} must be {SERIALIZED_SIGNATURE_SIZE} bytes"); - } - if (signature.Length < UNSERIALIZED_SIGNATURE_SIZE) - { - throw new ArgumentException($"{nameof(signature)} must be {UNSERIALIZED_SIGNATURE_SIZE} bytes"); - } - - int recID = 0; - fixed (byte* compactSigPtr = &MemoryMarshal.GetReference(compactSignatureOutput), - sigPtr = &MemoryMarshal.GetReference(signature)) - { - var result = secp256k1_ecdsa_recoverable_signature_serialize_compact.Value(_ctx, compactSigPtr, ref recID, sigPtr); - recoveryID = recID; - - return result == 1; - } - } - - /// - /// Serialize a pubkey object into a serialized byte sequence. - /// - /// 65-byte (if compressed==0) or 33-byte (if compressed==1) output to place the serialized key in. - /// The secp256k1_pubkey initialized public key. - /// SECP256K1_EC_COMPRESSED if serialization should be in compressed format, otherwise SECP256K1_EC_UNCOMPRESSED. - public bool PublicKeySerialize(Span serializedPublicKeyOutput, Span publicKey, Flags flags = Flags.SECP256K1_EC_UNCOMPRESSED) - { - bool compressed = flags.HasFlag(Flags.SECP256K1_EC_COMPRESSED); - int serializedPubKeyLength = compressed ? SERIALIZED_COMPRESSED_PUBKEY_LENGTH : SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH; - if (serializedPublicKeyOutput.Length < serializedPubKeyLength) - { - string compressedStr = compressed ? "compressed" : "uncompressed"; - throw new ArgumentException($"{nameof(serializedPublicKeyOutput)} ({compressedStr}) must be {serializedPubKeyLength} bytes"); - } - if (publicKey.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKey)} must be {PUBKEY_LENGTH} bytes"); - } - - uint newLength = (uint)serializedPubKeyLength; - - fixed (byte* serializedPtr = &MemoryMarshal.GetReference(serializedPublicKeyOutput), - pubKeyPtr = &MemoryMarshal.GetReference(publicKey)) - { - var result = secp256k1_ec_pubkey_serialize.Value(_ctx, serializedPtr, ref newLength, pubKeyPtr, (uint) flags); - return result == 1 && newLength == serializedPubKeyLength; - } - } - - /// - /// Parse a variable-length public key into the pubkey object. - /// This function supports parsing compressed (33 bytes, header byte 0x02 or - /// 0x03), uncompressed(65 bytes, header byte 0x04), or hybrid(65 bytes, header - /// byte 0x06 or 0x07) format public keys. - /// - /// (Output) pointer to a pubkey object. If 1 is returned, it is set to a parsed version of input. If not, its value is undefined. - /// Serialized public key. - /// True if the public key was fully valid, false if the public key could not be parsed or is invalid. - public bool PublicKeyParse(Span publicKeyOutput, Span serializedPublicKey) - { - var inputLen = serializedPublicKey.Length; - if (inputLen != 33 && inputLen != 65) - { - throw new ArgumentException($"{nameof(serializedPublicKey)} must be 33 or 65 bytes"); - } - if (publicKeyOutput.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKeyOutput)} must be {PUBKEY_LENGTH} bytes"); - } - - fixed (byte* pubKeyPtr = &MemoryMarshal.GetReference(publicKeyOutput), - serializedPtr = &MemoryMarshal.GetReference(serializedPublicKey)) - { - return secp256k1_ec_pubkey_parse.Value(_ctx, pubKeyPtr, serializedPtr, (uint) inputLen) == 1; - } - } - - /// - /// Normalizes a signature and enforces a low-S. - /// - /// (Output) Signature to fill with the normalized form, or copy if the input was already normalized. - /// (Input) signature to check/normalize, can be identical to sigout - /// True if sigin was not normalized, false if it already was. - public bool SignatureNormalize(Span normalizedSignatureOutput, Span signatureInput) - { - if (normalizedSignatureOutput.Length < SIGNATURE_LENGTH) - { - throw new ArgumentException($"{nameof(normalizedSignatureOutput)} must be {SIGNATURE_LENGTH} bytes"); - } - if (signatureInput.Length < SIGNATURE_LENGTH) - { - throw new ArgumentException($"{nameof(signatureInput)} must be {SIGNATURE_LENGTH} bytes"); - } - - fixed (byte* outPtr = &MemoryMarshal.GetReference(normalizedSignatureOutput), - intPtr = &MemoryMarshal.GetReference(signatureInput)) - { - return secp256k1_ecdsa_signature_normalize.Value(_ctx, outPtr, intPtr) == 1; - } - } - - /// - /// Parse a DER ECDSA signature - /// This function will accept any valid DER encoded signature, even if the - /// encoded numbers are out of range. - /// After the call, sig will always be initialized. If parsing failed or the - /// encoded numbers are out of range, signature validation with it is - /// guaranteed to fail for every message and public key. - /// - /// (Output) a signature object - /// (Input) a signature to be parsed - /// True when the signature could be parsed, false otherwise. - public bool SignatureParseDer(Span signatureOutput, Span signatureInput) - { - if (signatureOutput.Length < SIGNATURE_LENGTH) - { - throw new ArgumentException($"{nameof(signatureOutput)} must be {SIGNATURE_LENGTH} bytes"); - } - - uint inputlen = (uint)signatureInput.Length; - - fixed (byte* sig = &MemoryMarshal.GetReference(signatureOutput), - input = &MemoryMarshal.GetReference(signatureInput)) - { - return secp256k1_ecdsa_signature_parse_der.Value(_ctx, sig, input, inputlen) == 1; - } - } - - /// - /// Serialize an ECDSA signature in DER format (72 bytes maximum) - /// This function will accept any valid ECDSA encoded signature - /// - /// (Output) a signature object - /// (Input) a signature to be parsed - /// (Output) lenght of serialized DER signature - /// True when the signature could be serialized, false otherwise. - public bool SignatureSerializeDer(Span signatureOutput, Span signatureInput, out int singatureOutputLength) - { - if (signatureOutput.Length < SERIALIZED_DER_SIGNATURE_MAX_SIZE) + if (publicKeys == null || publicKeys.Length == 0) { - throw new ArgumentException($"{nameof(signatureOutput)} must be {SERIALIZED_DER_SIGNATURE_MAX_SIZE} bytes as maximum to void truncate signature"); + throw new ArgumentException($"{nameof(publicKeys)} must not be null or empty"); } - uint sigOutputLength = (uint)SERIALIZED_DER_SIGNATURE_MAX_SIZE; - - fixed (byte* sig = &MemoryMarshal.GetReference(signatureOutput), - input = &MemoryMarshal.GetReference(signatureInput)) + var count = publicKeys.Length; + for (int i = 0; i < count; i++) { - var result = secp256k1_ecdsa_signature_serialize_der.Value(_ctx, sig, ref sigOutputLength, input); - singatureOutputLength = (int)sigOutputLength; - return result == 1; - } - } - - /// - /// Serialize an ECDSA signature in compact (64 byte) format. - /// - /// (Output) a 64-byte array to store the compact serialization - /// (Input) an initialized signature object - /// True when the signature could be serialized, false otherwise. - public bool SignatureSerializeCompact(Span signatureOutput, Span signatureInput) - { - if (signatureOutput.Length < SERIALIZED_SIGNATURE_SIZE) - { - throw new ArgumentException($"{nameof(signatureOutput)} must be {SIGNATURE_LENGTH} bytes"); - } - - if (signatureInput.Length < SIGNATURE_LENGTH) - { - throw new ArgumentException($"{nameof(signatureInput)} must be {SIGNATURE_LENGTH} bytes"); - } - - fixed (byte* output = &MemoryMarshal.GetReference(signatureOutput), - sig = &MemoryMarshal.GetReference(signatureInput)) - { - var result = secp256k1_ecdsa_signature_serialize_compact.Value(_ctx, output, sig); - return result == 1; - } - } - - /// - /// Parse an ECDSA signature in compact (64 bytes) format. - /// The signature must consist of a 32-byte big endian R value, followed by a - /// 32-byte big endian S value. If R or S fall outside of[0..order - 1], the - /// encoding is invalid. R and S with value 0 are allowed in the encoding. - /// After the call, sig will always be initialized.If parsing failed or R or - /// S are zero, the resulting sig value is guaranteed to fail verification for - /// any message and public key. - /// - /// (Output) a 64-byte array to store the parsed signature - /// (Input) a 64-byte array of the serialized signature - /// True when the signature could be parsed, false otherwise. - public bool SignatureParseCompact(Span signatureOutput, Span signatureInput) - { - if (signatureOutput.Length < SIGNATURE_LENGTH) - { - throw new ArgumentException($"{nameof(signatureOutput)} must be {SIGNATURE_LENGTH} bytes"); - } - - if (signatureInput.Length < SERIALIZED_SIGNATURE_SIZE) - { - throw new ArgumentException($"{nameof(signatureInput)} must be {SIGNATURE_LENGTH} bytes"); - } - - fixed (byte* output = &MemoryMarshal.GetReference(signatureOutput), - sig = &MemoryMarshal.GetReference(signatureInput)) - { - var result = secp256k1_ecdsa_signature_parse_compact.Value(_ctx, output, sig); - return result == 1; - } - } - - /// - /// Verify an ECDSA signature. - /// To avoid accepting malleable signatures, only ECDSA signatures in lower-S - /// form are accepted. - /// If you need to accept ECDSA signatures from sources that do not obey this - /// rule, apply secp256k1_ecdsa_signature_normalize to the signature prior to - /// validation, but be aware that doing so results in malleable signatures. - /// For details, see the comments for that function. - /// - /// The signature being verified. - /// The 32-byte message hash being verified. - /// An initialized public key to verify with. - /// True if correct signature, false if incorrect or unparseable signature. - public bool Verify(Span signature, Span messageHash, Span publicKey) - { - if (signature.Length < SIGNATURE_LENGTH) - { - throw new ArgumentException($"{nameof(signature)} must be {SIGNATURE_LENGTH} bytes"); - } - if (messageHash.Length < HASH_LENGTH) - { - throw new ArgumentException($"{nameof(messageHash)} must be {HASH_LENGTH} bytes"); - } - if (publicKey.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKey)} must be {PUBKEY_LENGTH} bytes"); - } - - fixed (byte* sigPtr = &MemoryMarshal.GetReference(signature), - msgPtr = &MemoryMarshal.GetReference(messageHash), - pubPtr = &MemoryMarshal.GetReference(publicKey)) - { - return secp256k1_ecdsa_verify.Value(_ctx, sigPtr, msgPtr, pubPtr) == 1; - } - } - - /// - /// Create an ECDSA signature. - /// The created signature is always in lower-S form. See - /// secp256k1_ecdsa_signature_normalize for more details. - /// - /// An array where the signature will be placed. - /// The 32-byte message hash being signed. - /// A 32-byte secret key. - /// - public bool Sign(Span signatureOutput, Span messageHash, Span secretKey) - { - if (signatureOutput.Length < SIGNATURE_LENGTH) - { - throw new ArgumentException($"{nameof(signatureOutput)} must be {SIGNATURE_LENGTH} bytes"); - } - if (messageHash.Length < HASH_LENGTH) - { - throw new ArgumentException($"{nameof(messageHash)} must be {HASH_LENGTH} bytes"); - } - if (secretKey.Length < PRIVKEY_LENGTH) - { - throw new ArgumentException($"{nameof(secretKey)} must be {PRIVKEY_LENGTH} bytes"); - } - - fixed (byte* sigPtr = &MemoryMarshal.GetReference(signatureOutput), - msgPtr = &MemoryMarshal.GetReference(messageHash), - secPtr = &MemoryMarshal.GetReference(secretKey)) - { - return secp256k1_ecdsa_sign.Value(_ctx, sigPtr, msgPtr, secPtr, IntPtr.Zero, IntPtr.Zero.ToPointer()) == 1; + if (publicKeys[i] == null || publicKeys[i].Length < UNSERIALIZED_PUBKEY_LENGTH) + { + throw new ArgumentException($"{nameof(publicKeys)}[{i}] must be at least {UNSERIALIZED_PUBKEY_LENGTH} bytes"); + } } - } - /// - /// Compute an EC Diffie-Hellman secret in constant time. - /// - /// A 32-byte array which will be populated by an ECDH secret computed from the point and scalar. - /// A secp256k1_pubkey containing an initialized public key. - /// A 32-byte scalar with which to multiply the point. - /// True if exponentiation was successful, false if scalar was invalid (zero or overflow). - public bool Ecdh(Span resultOutput, Span publicKey, Span privateKey) - { - if (resultOutput.Length < SECRET_LENGTH) - { - throw new ArgumentException($"{nameof(resultOutput)} must be {SECRET_LENGTH} bytes"); - } - if (publicKey.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKey)} must be {PUBKEY_LENGTH} bytes"); - } - if (privateKey.Length < PRIVKEY_LENGTH) - { - throw new ArgumentException($"{nameof(privateKey)} must be {PRIVKEY_LENGTH} bytes"); - } + var ptrSize = IntPtr.Size; + var nativePtrArray = Marshal.AllocHGlobal(ptrSize * count); + var handles = new GCHandle[count]; - fixed (byte* resPtr = &MemoryMarshal.GetReference(resultOutput), - pubPtr = &MemoryMarshal.GetReference(publicKey), - privPtr = &MemoryMarshal.GetReference(privateKey)) + try { - return secp256k1_ecdh.Value(_ctx, resPtr, pubPtr, privPtr, null, IntPtr.Zero) == 1; - } - } + // Pin each byte[] and store pointers in native array + for (int i = 0; i < count; i++) + { + handles[i] = GCHandle.Alloc(publicKeys[i], GCHandleType.Pinned); + Marshal.WriteIntPtr(nativePtrArray, i * ptrSize, handles[i].AddrOfPinnedObject()); + } - /// - /// Compute an EC Diffie-Hellman secret in constant time. - /// - /// A 32-byte array which will be populated by an ECDH secret computed from the point and scalar. - /// A secp256k1_pubkey containing an initialized public key. - /// A 32-byte scalar with which to multiply the point. - /// Pointer to a hash function. If null, sha256 is used. - /// Arbitrary data that is passed through. - /// True if exponentiation was successful, false if scalar was invalid (zero or overflow). - public bool Ecdh(Span resultOutput, Span publicKey, Span privateKey, EcdhHashFunction hashFunction, IntPtr data) - { - if (resultOutput.Length < SECRET_LENGTH) - { - throw new ArgumentException($"{nameof(resultOutput)} must be {SECRET_LENGTH} bytes"); - } - if (publicKey.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKey)} must be {PUBKEY_LENGTH} bytes"); - } - if (privateKey.Length < PRIVKEY_LENGTH) - { - throw new ArgumentException($"{nameof(privateKey)} must be {PRIVKEY_LENGTH} bytes"); - } + // Call native function which sorts the pointer array in place + var result = Secp256k1Interop._ec_pubkey_sort(_ctx, nativePtrArray, (nuint)count); + if (result != 1) + { + return false; + } - int outputLength = resultOutput.Length; + // Read back the sorted pointers and map them to original indices + var sortedPointers = new IntPtr[count]; + for (int i = 0; i < count; i++) + { + sortedPointers[i] = Marshal.ReadIntPtr(nativePtrArray, i * ptrSize); + } - secp256k1_ecdh_hash_function hashFunctionPtr = (void* output, void* x, void* y, IntPtr d) => - { - var outputSpan = new Span(output, outputLength); - var xSpan = new Span(x, 32); - var ySpan = new Span(y, 32); - return hashFunction(outputSpan, xSpan, ySpan, d); - }; + // Create a mapping from pointer to original index + var pointerToIndex = new System.Collections.Generic.Dictionary(count); + for (int i = 0; i < count; i++) + { + pointerToIndex[handles[i].AddrOfPinnedObject()] = i; + } - fixed (byte* resPtr = &MemoryMarshal.GetReference(resultOutput), - pubPtr = &MemoryMarshal.GetReference(publicKey), - privPtr = &MemoryMarshal.GetReference(privateKey)) - { - return secp256k1_ecdh.Value(_ctx, resPtr, pubPtr, privPtr, hashFunctionPtr, data) == 1; - } - } + // Build the sorted array by looking up the original byte[] for each sorted pointer + var sortedArray = new byte[count][]; + for (int i = 0; i < count; i++) + { + var originalIndex = pointerToIndex[sortedPointers[i]]; + sortedArray[i] = publicKeys[originalIndex]; + } - /// - /// Adds two public keys. - /// - /// The sum public key to be written to. - /// The first public key. - /// The second public key. - /// True if the sum of the public keys is valid, false if the sum of the public keys is not valid. - /// - public bool PublicKeysCombine(Span outputPublicKey, Span publicKey1, Span publicKey2) - { - if ( outputPublicKey.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(outputPublicKey)} must be {PUBKEY_LENGTH} bytes"); - } - - if ( publicKey1.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKey1)} must be {PUBKEY_LENGTH} bytes"); - } - if ( publicKey2.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKey2)} must be {PUBKEY_LENGTH} bytes"); - } - - var intPtrSize = Marshal.SizeOf(typeof(IntPtr)); - var nativeArray = Marshal.AllocHGlobal(intPtrSize * 2); - try - { - fixed ( - byte* outPubPtr = &MemoryMarshal.GetReference(outputPublicKey), - inPubPtr1 = &MemoryMarshal.GetReference(publicKey1), - inPubPtr2 = &MemoryMarshal.GetReference(publicKey2)) + // Copy back to original array + for (int i = 0; i < count; i++) { - Marshal.WriteIntPtr(nativeArray, 0, (IntPtr)inPubPtr1); - Marshal.WriteIntPtr(nativeArray, intPtrSize, (IntPtr)inPubPtr2); - return secp256k1_ec_pubkey_combine.Value(_ctx, outPubPtr, nativeArray, 2) == 1; + publicKeys[i] = sortedArray[i]; } - } - finally - { - Marshal.FreeHGlobal(nativeArray); - } - } - /// - /// Negates a public key in place. - /// - /// The 65 byte public key which will be negated in place. - /// True always. - /// - public bool PublicKeyNegate(Span publicKey) - { - if (publicKey.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKey)} must be {PUBKEY_LENGTH} bytes"); - } - fixed (byte* pubPtr = &MemoryMarshal.GetReference(publicKey)) - { - return secp256k1_ec_pubkey_negate.Value(_ctx, pubPtr) == 1; - } - } - /// - /// Multiplies the public key with a 32 byte scalar. - /// - /// The public key to be multiplied and the result to be written to. - /// The 32 byte scalar. - /// True if the arguments are valid and false otherwise. - /// - public bool PublicKeyMultiply(Span publicKey, Span tweak) - { - if (publicKey.Length < PUBKEY_LENGTH) - { - throw new ArgumentException($"{nameof(publicKey)} must be {PUBKEY_LENGTH} bytes"); - } - if (tweak.Length < SECRET_LENGTH) - { - throw new ArgumentException($"{nameof(tweak)} must be {SECRET_LENGTH} bytes"); + return true; } - fixed (byte* pubPtr = &MemoryMarshal.GetReference(publicKey), - tweakPtr = &MemoryMarshal.GetReference(tweak)) + finally { - return secp256k1_ec_pubkey_tweak_mul.Value(_ctx, pubPtr, tweakPtr) == 1; - } - } + // Free GCHandles + for (int i = 0; i < count; i++) + { + if (handles[i].IsAllocated) + { + handles[i].Free(); + } + } - /// - /// Deterministically generate a 32 byte nonce according to RFC6979 standard. - /// - /// The 32 byte output nonce to be written to. - /// The 32 byte message hash being verified. - /// The 32 byte secret key. - /// A 16 byte array describing the signature algorithm (will be NULL for ECDSA for compatibility). - /// Arbitrary data that is passed through. - /// How many iterations we have tried to find a nonce. This will almost always be 0, but different attempt values are required to result in a different nonce. - /// True if a nonce was successfully generated, false otherwise. - /// - public bool Rfc6979Nonce(Span nonceOutput, Span hash, Span secretKey, Span algo, Span data, uint attempt) - { - if (nonceOutput.Length < NONCE_LENGTH) - { - throw new ArgumentException($"{nameof(nonceOutput)} must be {NONCE_LENGTH} bytes"); - } - if (hash.Length < HASH_LENGTH) - { - throw new ArgumentException($"{nameof(hash)} must be {HASH_LENGTH} bytes"); - } - if (secretKey.Length < SECRET_LENGTH) - { - throw new ArgumentException($"{nameof(secretKey)} must be {SECRET_LENGTH} bytes"); - } - fixed (byte* nonceOutPtr = &MemoryMarshal.GetReference(nonceOutput), - hashPtr = &MemoryMarshal.GetReference(hash), - secPtr = &MemoryMarshal.GetReference(secretKey), - algoPtr = &MemoryMarshal.GetReference(algo), - dataPtr = &MemoryMarshal.GetReference(data)) - { - return secp256k1_nonce_function_rfc6979.Value(nonceOutPtr, hashPtr, secPtr, algoPtr, dataPtr, attempt) == 1; + Marshal.FreeHGlobal(nativePtrArray); } } @@ -765,12 +186,9 @@ public void Dispose() } if (_ctx != IntPtr.Zero) { - secp256k1_context_destroy.Value(_ctx); + Secp256k1Interop._context_destroy(_ctx); _ctx = IntPtr.Zero; } } - - - } } diff --git a/Secp256k1.Net/secp256k1-api.json b/Secp256k1.Net/secp256k1-api.json new file mode 100644 index 0000000..846c945 --- /dev/null +++ b/Secp256k1.Net/secp256k1-api.json @@ -0,0 +1,3601 @@ +{ + "version": "0.7.0", + "generatedAt": "2026-01-19T19:31:20.7367860Z", + "headers": [ + "secp256k1.h", + "secp256k1_preallocated.h", + "secp256k1_recovery.h", + "secp256k1_ecdh.h", + "secp256k1_extrakeys.h", + "secp256k1_schnorrsig.h", + "secp256k1_ellswift.h", + "secp256k1_musig.h" + ], + "structs": [ + { + "name": "secp256k1_pubkey", + "size": 64, + "description": "/** Opaque data structure that holds a parsed and valid public key.\n *\n * The exact representation of data inside is implementation defined and not\n * guaranteed to be portable between different platforms or versions. It is\n * however guaranteed to be 64 bytes in size, and can be safely copied/moved.\n * If you need to convert to a format suitable for storage or transmission,\n * use secp256k1_ec_pubkey_serialize and secp256k1_ec_pubkey_parse. To\n * compare keys, use secp256k1_ec_pubkey_cmp.\n */" + }, + { + "name": "secp256k1_ecdsa_signature", + "size": 64, + "description": "/** Opaque data structure that holds a parsed ECDSA signature.\n *\n * The exact representation of data inside is implementation defined and not\n * guaranteed to be portable between different platforms or versions. It is\n * however guaranteed to be 64 bytes in size, and can be safely copied/moved.\n * If you need to convert to a format suitable for storage, transmission, or\n * comparison, use the secp256k1_ecdsa_signature_serialize_* and\n * secp256k1_ecdsa_signature_parse_* functions.\n */" + }, + { + "name": "secp256k1_context", + "size": 0, + "description": "/** Opaque data structure that holds context information\n *\n * The primary purpose of context objects is to store randomization data for\n * enhanced protection against side-channel leakage. This protection is only\n * effective if the context is randomized after its creation. See\n * secp256k1_context_create for creation of contexts and\n * secp256k1_context_randomize for randomization.\n *\n * A secondary purpose of context objects is to store pointers to callback\n * functions that the library will call when certain error states arise. See\n * secp256k1_context_set_error_callback as well as\n * secp256k1_context_set_illegal_callback for details. Future library versions\n * may use context objects for additional purposes.\n *\n * A constructed context can safely be used from multiple threads\n * simultaneously, but API calls that take a non-const pointer to a context\n * need exclusive access to it. In particular this is the case for\n * secp256k1_context_destroy, secp256k1_context_preallocated_destroy,\n * and secp256k1_context_randomize.\n *\n * Regarding randomization, either do it once at creation time (in which case\n * you do not need any locking for the other calls), or use a read-write lock.\n */" + }, + { + "name": "secp256k1_ecdsa_recoverable_signature", + "size": 65, + "description": "/** Opaque data structure that holds a parsed ECDSA signature,\n * supporting pubkey recovery.\n *\n * The exact representation of data inside is implementation defined and not\n * guaranteed to be portable between different platforms or versions. It is\n * however guaranteed to be 65 bytes in size, and can be safely copied/moved.\n * If you need to convert to a format suitable for storage or transmission, use\n * the secp256k1_ecdsa_signature_serialize_* and\n * secp256k1_ecdsa_signature_parse_* functions.\n *\n * Furthermore, it is guaranteed that identical signatures (including their\n * recoverability) will have identical representation, so they can be\n * memcmp\u0027ed.\n */" + }, + { + "name": "secp256k1_xonly_pubkey", + "size": 64, + "description": "/** Opaque data structure that holds a parsed and valid \u0022x-only\u0022 public key.\n * An x-only pubkey encodes a point whose Y coordinate is even. It is\n * serialized using only its X coordinate (32 bytes). See BIP-340 for more\n * information about x-only pubkeys.\n *\n * The exact representation of data inside is implementation defined and not\n * guaranteed to be portable between different platforms or versions. It is\n * however guaranteed to be 64 bytes in size, and can be safely copied/moved.\n * If you need to convert to a format suitable for storage, transmission, use\n * use secp256k1_xonly_pubkey_serialize and secp256k1_xonly_pubkey_parse. To\n * compare keys, use secp256k1_xonly_pubkey_cmp.\n */" + }, + { + "name": "secp256k1_keypair", + "size": 96, + "description": "/** Opaque data structure that holds a keypair consisting of a secret and a\n * public key.\n *\n * The exact representation of data inside is implementation defined and not\n * guaranteed to be portable between different platforms or versions. It is\n * however guaranteed to be 96 bytes in size, and can be safely copied/moved.\n */" + }, + { + "name": "secp256k1_musig_keyagg_cache", + "size": 197, + "description": "/** Opaque data structure that caches information about public key aggregation.\n *\n * Guaranteed to be 197 bytes in size. No serialization and parsing functions\n * (yet).\n */" + }, + { + "name": "secp256k1_musig_secnonce", + "size": 132, + "description": "/** Opaque data structure that holds a signer\u0027s _secret_ nonce.\n *\n * Guaranteed to be 132 bytes in size.\n *\n * WARNING: This structure MUST NOT be copied or read or written to directly. A\n * signer who is online throughout the whole process and can keep this\n * structure in memory can use the provided API functions for a safe standard\n * workflow.\n *\n * Copying this data structure can result in nonce reuse which will leak the\n * secret signing key.\n */" + }, + { + "name": "secp256k1_musig_pubnonce", + "size": 132, + "description": "/** Opaque data structure that holds a signer\u0027s public nonce.\n *\n * Guaranteed to be 132 bytes in size. Serialized and parsed with\n * \u0060musig_pubnonce_serialize\u0060 and \u0060musig_pubnonce_parse\u0060.\n */" + }, + { + "name": "secp256k1_musig_aggnonce", + "size": 132, + "description": "/** Opaque data structure that holds an aggregate public nonce.\n *\n * Guaranteed to be 132 bytes in size. Serialized and parsed with\n * \u0060musig_aggnonce_serialize\u0060 and \u0060musig_aggnonce_parse\u0060.\n */" + }, + { + "name": "secp256k1_musig_session", + "size": 133, + "description": "/** Opaque data structure that holds a MuSig session.\n *\n * This structure is not required to be kept secret for the signing protocol to\n * be secure. Guaranteed to be 133 bytes in size. No serialization and parsing\n * functions (yet).\n */" + }, + { + "name": "secp256k1_musig_partial_sig", + "size": 36, + "description": "/** Opaque data structure that holds a partial MuSig signature.\n *\n * Guaranteed to be 36 bytes in size. Serialized and parsed with\n * \u0060musig_partial_sig_serialize\u0060 and \u0060musig_partial_sig_parse\u0060.\n */" + } + ], + "functionPointerTypes": [ + { + "name": "secp256k1_nonce_function", + "returnType": "int", + "parameters": [ + { + "name": "nonce32", + "type": "unsigned char*", + "direction": "out", + "nonnull": false, + "description": "pointer to a 32-byte array to be filled by the function.", + "size": 32, + "isOptional": false + }, + { + "name": "msg32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "the 32-byte message hash being verified (will not be NULL)", + "size": 32, + "isOptional": false + }, + { + "name": "key32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to a 32-byte secret key (will not be NULL)", + "size": 32, + "isOptional": false + }, + { + "name": "algo16", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to a 16-byte array describing the signature\nalgorithm (will be NULL for ECDSA for compatibility).", + "size": 16, + "isOptional": true + }, + { + "name": "data", + "type": "void*", + "direction": "out", + "nonnull": false, + "description": "Arbitrary data pointer that is passed through.", + "isOptional": true + }, + { + "name": "attempt", + "type": "unsigned int", + "nonnull": false, + "description": "how many iterations we have tried to find a nonce.\nThis will almost always be 0, but different attempt values\nare required to result in a different nonce.\n\nExcept for test cases, this function should compute some cryptographic hash of\nthe message, the algorithm, the key and the attempt.", + "isOptional": false + } + ], + "description": "A pointer to a function to deterministically generate a nonce." + }, + { + "name": "secp256k1_ecdh_hash_function", + "returnType": "int", + "parameters": [ + { + "name": "output", + "type": "unsigned char*", + "direction": "out", + "nonnull": false, + "description": "pointer to an array to be filled by the function", + "size": 32, + "isOptional": false + }, + { + "name": "x32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to a 32-byte x coordinate", + "size": 32, + "isOptional": false + }, + { + "name": "y32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to a 32-byte y coordinate", + "size": 32, + "isOptional": false + }, + { + "name": "data", + "type": "void*", + "direction": "out", + "nonnull": false, + "description": "arbitrary data pointer that is passed through", + "isOptional": true + } + ], + "description": "A pointer to a function that hashes an EC point to obtain an ECDH secret" + }, + { + "name": "secp256k1_nonce_function_hardened", + "returnType": "int", + "parameters": [ + { + "name": "nonce32", + "type": "unsigned char*", + "direction": "out", + "nonnull": false, + "description": "pointer to a 32-byte array to be filled by the function", + "size": 32, + "isOptional": false + }, + { + "name": "msg", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "the message being verified. Is NULL if and only if msglen\nis 0.", + "lengthParam": "msglen", + "isOptional": false + }, + { + "name": "msglen", + "type": "size_t", + "nonnull": false, + "description": "the length of the message", + "isLengthFor": "msg", + "isOptional": false + }, + { + "name": "key32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to a 32-byte secret key (will not be NULL)", + "size": 32, + "isOptional": false + }, + { + "name": "xonly_pk32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "the 32-byte serialized xonly pubkey corresponding to key32\n(will not be NULL)", + "size": 32, + "isOptional": false + }, + { + "name": "algo", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to an array describing the signature\nalgorithm (will not be NULL)", + "lengthParam": "algolen", + "isOptional": true + }, + { + "name": "algolen", + "type": "size_t", + "nonnull": false, + "description": "the length of the algo array", + "isLengthFor": "algo", + "isOptional": false + }, + { + "name": "data", + "type": "void*", + "direction": "out", + "nonnull": false, + "description": "arbitrary data pointer that is passed through\n\nExcept for test cases, this function should compute some cryptographic hash of\nthe message, the key, the pubkey, the algorithm description, and data.", + "isOptional": true + } + ], + "description": "A pointer to a function to deterministically generate a nonce.\n\nSame as secp256k1_nonce function with the exception of accepting an\nadditional pubkey argument and not requiring an attempt argument. The pubkey\nargument can protect signature schemes with key-prefixed challenge hash\ninputs against reusing the nonce when signing with the wrong precomputed\npubkey." + }, + { + "name": "secp256k1_ellswift_xdh_hash_function", + "returnType": "int", + "parameters": [ + { + "name": "output", + "type": "unsigned char*", + "direction": "out", + "nonnull": false, + "description": "pointer to an array to be filled by the function", + "size": 32, + "isOptional": false + }, + { + "name": "x32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to the 32-byte serialized X coordinate\nof the resulting shared point (will not be NULL)", + "size": 32, + "isOptional": false + }, + { + "name": "ell_a64", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to the 64-byte encoded public key of party A\n(will not be NULL)", + "size": 64, + "isOptional": false + }, + { + "name": "ell_b64", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to the 64-byte encoded public key of party B\n(will not be NULL)", + "size": 64, + "isOptional": false + }, + { + "name": "data", + "type": "void*", + "direction": "out", + "nonnull": false, + "description": "arbitrary data pointer that is passed through", + "isOptional": true + } + ], + "description": "A pointer to a function used by secp256k1_ellswift_xdh to hash the shared X\ncoordinate along with the encoded public keys to a uniform shared secret." + } + ], + "functions": [ + { + "name": "secp256k1_selftest", + "returnType": "void", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [], + "description": "Perform basic self tests (to be used in conjunction with secp256k1_context_static)\n\nThis function performs self tests that detect some serious usage errors and\nsimilar conditions, e.g., when the library is compiled for the wrong endianness.\nThis is a last resort measure to be used in production. The performed tests are\nvery rudimentary and are not intended as a replacement for running the test\nbinaries.\n\nIt is highly recommended to call this before using secp256k1_context_static.\nIt is not necessary to call this function before using a context created with\nsecp256k1_context_create (or secp256k1_context_preallocated_create), which will\ntake care of performing the self tests.\n\nIf the tests fail, this function will call the default error callback to abort the\nprogram (see secp256k1_context_set_error_callback).", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_context_create", + "returnType": "secp256k1_context *", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "flags", + "type": "unsigned int", + "nonnull": false, + "description": "Always set to SECP256K1_CONTEXT_NONE (see below).\n\nThe only valid non-deprecated flag in recent library versions is\nSECP256K1_CONTEXT_NONE, which will create a context sufficient for all functionality\noffered by the library. All other (deprecated) flags will be treated as equivalent\nto the SECP256K1_CONTEXT_NONE flag. Though the flags parameter primarily exists for\nhistorical reasons, future versions of the library may introduce new flags.\n\nIf the context is intended to be used for API functions that perform computations\ninvolving secret keys, e.g., signing and public key generation, then it is highly\nrecommended to call secp256k1_context_randomize on the context before calling\nthose API functions. This will provide enhanced protection against side-channel\nleakage, see secp256k1_context_randomize for details.\n\nDo not create a new context object for each operation, as construction and\nrandomization can take non-negligible time.", + "isOptional": false + } + ], + "description": "Create a secp256k1 context object (in dynamically allocated memory).\n\nThis function uses malloc to allocate memory. It is guaranteed that malloc is\ncalled at most once for every call of this function. If you need to avoid dynamic\nmemory allocation entirely, see secp256k1_context_static and the functions in\nsecp256k1_preallocated.h.", + "returnDescription": "pointer to a newly created context object.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_context_clone", + "returnType": "secp256k1_context *", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context to copy (not secp256k1_context_static).", + "isOptional": false + } + ], + "description": "Copy a secp256k1 context object (into dynamically allocated memory).\n\nThis function uses malloc to allocate memory. It is guaranteed that malloc is\ncalled at most once for every call of this function. If you need to avoid dynamic\nmemory allocation entirely, see the functions in secp256k1_preallocated.h.\n\nCloning secp256k1_context_static is not possible, and should not be emulated by\nthe caller (e.g., using memcpy). Create a new context instead.", + "returnDescription": "pointer to a newly created context object.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_context_destroy", + "returnType": "void", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "secp256k1_context*", + "direction": "out", + "nonnull": true, + "description": "pointer to a context to destroy, constructed using\nsecp256k1_context_create or secp256k1_context_clone\n(i.e., not secp256k1_context_static).", + "isOptional": false + } + ], + "description": "Destroy a secp256k1 context object (created in dynamically allocated memory).\n\nThe context pointer may not be used afterwards.\n\nThe context to destroy must have been created using secp256k1_context_create\nor secp256k1_context_clone. If the context has instead been created using\nsecp256k1_context_preallocated_create or secp256k1_context_preallocated_clone, the\nbehaviour is undefined. In that case, secp256k1_context_preallocated_destroy must\nbe used instead.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_context_set_illegal_callback", + "returnType": "void", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "secp256k1_context*", + "direction": "out", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "fun", + "type": "void (*)(const char *message, void *data)", + "direction": "in", + "nonnull": false, + "description": "pointer to a function to call when an illegal argument is\npassed to the API, taking a message and an opaque pointer.\n(NULL restores the default callback.)", + "isOptional": false + }, + { + "name": "data", + "type": "const void*", + "direction": "in", + "nonnull": false, + "description": "the opaque pointer to pass to fun above, must be NULL for the\ndefault callback.\n\nSee also secp256k1_context_set_error_callback.", + "isOptional": true + } + ], + "description": "Set a callback function to be called when an illegal argument is passed to\nan API call. It will only trigger for violations that are mentioned\nexplicitly in the header.\n\nThe philosophy is that these shouldn\u0027t be dealt with through a specific\nreturn value, as calling code should not have branches to deal with the case\nthat this code itself is broken.\n\nOn the other hand, during debug stage, one would want to be informed about\nsuch mistakes, and the default (crashing) may be inadvisable. Should this\ncallback return instead of crashing, the return value and output arguments\nof the API function call are undefined. Moreover, the same API call may\ntrigger the callback again in this case.\n\nWhen this function has not been called (or called with fun==NULL), then the\ndefault callback will be used. The library provides a default callback which\nwrites the message to stderr and calls abort. This default callback can be\nreplaced at link time if the preprocessor macro\nUSE_EXTERNAL_DEFAULT_CALLBACKS is defined, which is the case if the build\nhas been configured with --enable-external-default-callbacks (GNU Autotools) or \n-DSECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS=ON (CMake). Then the\nfollowing two symbols must be provided to link against:\n- void secp256k1_default_illegal_callback_fn(const char *message, void *data);\n- void secp256k1_default_error_callback_fn(const char *message, void *data);\nThe library may call a default callback even before a proper callback data\npointer could have been set using secp256k1_context_set_illegal_callback or\nsecp256k1_context_set_error_callback, e.g., when the creation of a context\nfails. In this case, the corresponding default callback will be called with\nthe data pointer argument set to NULL.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_context_set_error_callback", + "returnType": "void", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "secp256k1_context*", + "direction": "out", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "fun", + "type": "void (*)(const char *message, void *data)", + "direction": "in", + "nonnull": false, + "description": "pointer to a function to call when an internal error occurs,\ntaking a message and an opaque pointer (NULL restores the\ndefault callback, see secp256k1_context_set_illegal_callback\nfor details).", + "isOptional": false + }, + { + "name": "data", + "type": "const void*", + "direction": "in", + "nonnull": false, + "description": "the opaque pointer to pass to fun above, must be NULL for the\ndefault callback.\n\nSee also secp256k1_context_set_illegal_callback.", + "isOptional": true + } + ], + "description": "Set a callback function to be called when an internal consistency check\nfails.\n\nThe default callback writes an error message to stderr and calls abort\nto abort the program.\n\nThis can only trigger in case of a hardware failure, miscompilation,\nmemory corruption, serious bug in the library, or other error that would\nresult in undefined behaviour. It will not trigger due to mere\nincorrect usage of the API (see secp256k1_context_set_illegal_callback\nfor that). After this callback returns, anything may happen, including\ncrashing.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_parse", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to a pubkey object. If 1 is returned, it is set to a\nparsed version of input. If not, its value is undefined.", + "size": 64, + "isOptional": false + }, + { + "name": "input", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a serialized public key", + "lengthParam": "inputlen", + "isOptional": false + }, + { + "name": "inputlen", + "type": "size_t", + "nonnull": false, + "description": "length of the array pointed to by input\n\nThis function supports parsing compressed (33 bytes, header byte 0x02 or\n0x03), uncompressed (65 bytes, header byte 0x04), or hybrid (65 bytes, header\nbyte 0x06 or 0x07) format public keys.", + "isLengthFor": "input", + "isOptional": false + } + ], + "description": "Parse a variable-length public key into the pubkey object.", + "returnDescription": "1 if the public key was fully valid.\n0 if the public key could not be parsed or is invalid.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_serialize", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "output", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 65-byte (if compressed==0) or 33-byte (if\ncompressed==1) byte array to place the serialized key\nin.", + "isOptional": false + }, + { + "name": "outputlen", + "type": "size_t*", + "direction": "out", + "nonnull": true, + "description": "pointer to an integer which is initially set to the\nsize of output, and is overwritten with the written\nsize.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to a secp256k1_pubkey containing an\ninitialized public key.", + "size": 64, + "isOptional": false + }, + { + "name": "flags", + "type": "unsigned int", + "nonnull": false, + "description": "SECP256K1_EC_COMPRESSED if serialization should be in\ncompressed format, otherwise SECP256K1_EC_UNCOMPRESSED.", + "isOptional": false + } + ], + "description": "Serialize a pubkey object into a serialized byte sequence.", + "returnDescription": "1 always.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_cmp", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "pubkey1", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "first public key to compare", + "size": 64, + "isOptional": false + }, + { + "name": "pubkey2", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "second public key to compare", + "size": 64, + "isOptional": false + } + ], + "description": "Compare two public keys using lexicographic (of compressed serialization) order", + "returnDescription": "\u003C0 if the first public key is less than the second\n\u003E0 if the first public key is greater than the second\n0 if the two public keys are equal", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_sort", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "pubkeys", + "type": "const secp256k1_pubkey**", + "direction": "in", + "nonnull": true, + "description": "array of pointers to pubkeys to sort", + "lengthParam": "n_pubkeys", + "isOptional": false + }, + { + "name": "n_pubkeys", + "type": "size_t", + "nonnull": false, + "description": "number of elements in the pubkeys array", + "isLengthFor": "pubkeys", + "isOptional": false + } + ], + "description": "Sort public keys using lexicographic (of compressed serialization) order", + "returnDescription": "0 if the arguments are invalid. 1 otherwise.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ecdsa_signature_parse_compact", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "sig", + "type": "secp256k1_ecdsa_signature*", + "direction": "out", + "nonnull": true, + "description": "pointer to a signature object", + "size": 64, + "isOptional": false + }, + { + "name": "input64", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to the 64-byte array to parse\n\nThe signature must consist of a 32-byte big endian R value, followed by a\n32-byte big endian S value. If R or S fall outside of [0..order-1], the\nencoding is invalid. R and S with value 0 are allowed in the encoding.\n\nAfter the call, sig will always be initialized. If parsing failed or R or\nS are zero, the resulting sig value is guaranteed to fail verification for\nany message and public key.", + "size": 64, + "isOptional": false + } + ], + "description": "Parse an ECDSA signature in compact (64 bytes) format.", + "returnDescription": "1 when the signature could be parsed, 0 otherwise.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ecdsa_signature_parse_der", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "sig", + "type": "secp256k1_ecdsa_signature*", + "direction": "out", + "nonnull": true, + "description": "pointer to a signature object", + "size": 64, + "isOptional": false + }, + { + "name": "input", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to the signature to be parsed", + "lengthParam": "inputlen", + "isOptional": false + }, + { + "name": "inputlen", + "type": "size_t", + "nonnull": false, + "description": "the length of the array pointed to be input\n\nThis function will accept any valid DER encoded signature, even if the\nencoded numbers are out of range.\n\nAfter the call, sig will always be initialized. If parsing failed or the\nencoded numbers are out of range, signature verification with it is\nguaranteed to fail for every message and public key.", + "isLengthFor": "input", + "isOptional": false + } + ], + "description": "Parse a DER ECDSA signature.", + "returnDescription": "1 when the signature could be parsed, 0 otherwise.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ecdsa_signature_serialize_der", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "output", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to an array to store the DER serialization", + "isOptional": false + }, + { + "name": "outputlen", + "type": "size_t*", + "direction": "out", + "nonnull": true, + "description": "pointer to a length integer. Initially, this integer\nshould be set to the length of output. After the call\nit will be set to the length of the serialization (even\nif 0 was returned).", + "isOptional": false + }, + { + "name": "sig", + "type": "const secp256k1_ecdsa_signature*", + "direction": "in", + "nonnull": true, + "description": "pointer to an initialized signature object", + "size": 64, + "isOptional": false + } + ], + "description": "Serialize an ECDSA signature in DER format.", + "returnDescription": "1 if enough space was available to serialize, 0 otherwise", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ecdsa_signature_serialize_compact", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "output64", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 64-byte array to store the compact serialization", + "size": 64, + "isOptional": false + }, + { + "name": "sig", + "type": "const secp256k1_ecdsa_signature*", + "direction": "in", + "nonnull": true, + "description": "pointer to an initialized signature object\n\nSee secp256k1_ecdsa_signature_parse_compact for details about the encoding.", + "size": 64, + "isOptional": false + } + ], + "description": "Serialize an ECDSA signature in compact (64 byte) format.", + "returnDescription": "1", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ecdsa_verify", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "sig", + "type": "const secp256k1_ecdsa_signature*", + "direction": "in", + "nonnull": true, + "description": "the signature being verified.", + "size": 64, + "isOptional": false + }, + { + "name": "msghash32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "the 32-byte message hash being verified.\nThe verifier must make sure to apply a cryptographic\nhash function to the message by itself and not accept an\nmsghash32 value directly. Otherwise, it would be easy to\ncreate a \u0022valid\u0022 signature without knowledge of the\nsecret key. See also\nhttps://bitcoin.stackexchange.com/a/81116/35586 for more\nbackground on this topic.", + "size": 32, + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to an initialized public key to verify with.\n\nTo avoid accepting malleable signatures, only ECDSA signatures in lower-S\nform are accepted.\n\nIf you need to accept ECDSA signatures from sources that do not obey this\nrule, apply secp256k1_ecdsa_signature_normalize to the signature prior to\nverification, but be aware that doing so results in malleable signatures.\n\nFor details, see the comments for that function.", + "size": 64, + "isOptional": false + } + ], + "description": "Verify an ECDSA signature.", + "returnDescription": "1: correct signature\n0: incorrect or unparseable signature", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ecdsa_signature_normalize", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "sigout", + "type": "secp256k1_ecdsa_signature*", + "direction": "out", + "nonnull": false, + "description": "pointer to a signature to fill with the normalized form,\nor copy if the input was already normalized. (can be NULL if\nyou\u0027re only interested in whether the input was already\nnormalized).", + "size": 64, + "isOptional": false + }, + { + "name": "sigin", + "type": "const secp256k1_ecdsa_signature*", + "direction": "in", + "nonnull": true, + "description": "pointer to a signature to check/normalize (can be identical to sigout)\n\nWith ECDSA a third-party can forge a second distinct signature of the same\nmessage, given a single initial signature, but without knowing the key. This\nis done by negating the S value modulo the order of the curve, \u0027flipping\u0027\nthe sign of the random point R which is not included in the signature.\n\nForgery of the same message isn\u0027t universally problematic, but in systems\nwhere message malleability or uniqueness of signatures is important this can\ncause issues. This forgery can be blocked by all verifiers forcing signers\nto use a normalized form.\n\nThe lower-S form reduces the size of signatures slightly on average when\nvariable length encodings (such as DER) are used and is cheap to verify,\nmaking it a good choice. Security of always using lower-S is assured because\nanyone can trivially modify a signature after the fact to enforce this\nproperty anyway.\n\nThe lower S value is always between 0x1 and\n0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\ninclusive.\n\nNo other forms of ECDSA malleability are known and none seem likely, but\nthere is no formal proof that ECDSA, even with this additional restriction,\nis free of other malleability. Commonly used serialization schemes will also\naccept various non-unique encodings, so care should be taken when this\nproperty is required for an application.\n\nThe secp256k1_ecdsa_sign function will by default create signatures in the\nlower-S form, and secp256k1_ecdsa_verify will not accept others. In case\nsignatures come from a system that cannot enforce this property,\nsecp256k1_ecdsa_signature_normalize must be called before verification.", + "size": 64, + "isOptional": false + } + ], + "description": "Convert a signature to a normalized lower-S form.", + "returnDescription": "1 if sigin was not normalized, 0 if it already was.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ecdsa_sign", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static).", + "isOptional": false + }, + { + "name": "sig", + "type": "secp256k1_ecdsa_signature*", + "direction": "out", + "nonnull": true, + "description": "pointer to an array where the signature will be placed.", + "size": 64, + "isOptional": false + }, + { + "name": "msghash32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "the 32-byte message hash being signed.", + "size": 32, + "isOptional": false + }, + { + "name": "seckey", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte secret key.", + "size": 32, + "isOptional": false + }, + { + "name": "noncefp", + "type": "secp256k1_nonce_function", + "nonnull": false, + "description": "pointer to a nonce generation function. If NULL,\nsecp256k1_nonce_function_default is used.", + "isOptional": false + }, + { + "name": "ndata", + "type": "const void*", + "direction": "in", + "nonnull": false, + "description": "pointer to arbitrary data used by the nonce generation function\n(can be NULL). If it is non-NULL and\nsecp256k1_nonce_function_default is used, then ndata must be a\npointer to 32-bytes of additional data.\n\nThe created signature is always in lower-S form. See\nsecp256k1_ecdsa_signature_normalize for more details.", + "size": 32, + "isOptional": true + } + ], + "description": "Create an ECDSA signature.", + "returnDescription": "1: signature created\n0: the nonce generation function failed, or the secret key was invalid.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_seckey_verify", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "seckey", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte secret key.", + "size": 32, + "isOptional": false + } + ], + "description": "Verify an elliptic curve secret key.\n\nA secret key is valid if it is not 0 and less than the secp256k1 curve order\nwhen interpreted as an integer (most significant byte first). The\nprobability of choosing a 32-byte string uniformly at random which is an\ninvalid secret key is negligible. However, if it does happen it should\nbe assumed that the randomness source is severely broken and there should\nbe no retry.", + "returnDescription": "1: secret key is valid\n0: secret key is invalid", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_create", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static).", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to the created public key.", + "size": 64, + "isOptional": false + }, + { + "name": "seckey", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte secret key.", + "size": 32, + "isOptional": false + } + ], + "description": "Compute the public key for a secret key.", + "returnDescription": "1: secret was valid, public key stores.\n0: secret was invalid, try again.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_seckey_negate", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "seckey", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to the 32-byte secret key to be negated. If the\nsecret key is invalid according to\nsecp256k1_ec_seckey_verify, this function returns 0 and\nseckey will be set to some unspecified value.", + "size": 32, + "isOptional": false + } + ], + "description": "Negates a secret key in place.", + "returnDescription": "0 if the given secret key is invalid according to\nsecp256k1_ec_seckey_verify. 1 otherwise", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_negate", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to the public key to be negated.", + "size": 64, + "isOptional": false + } + ], + "description": "Negates a public key in place.", + "returnDescription": "1 always", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_seckey_tweak_add", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "seckey", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 32-byte secret key. If the secret key is\ninvalid according to secp256k1_ec_seckey_verify, this\nfunction returns 0. seckey will be set to some unspecified\nvalue if this function returns 0.", + "size": 32, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte tweak, which must be valid according to\nsecp256k1_ec_seckey_verify or 32 zero bytes. For uniformly\nrandom 32-byte tweaks, the chance of being invalid is\nnegligible (around 1 in 2^128).", + "size": 32, + "isOptional": false + } + ], + "description": "Tweak a secret key by adding tweak to it.", + "returnDescription": "0 if the arguments are invalid or the resulting secret key would be\ninvalid (only when the tweak is the negation of the secret key). 1\notherwise.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_tweak_add", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to a public key object. pubkey will be set to an\ninvalid value if this function returns 0.", + "size": 64, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte tweak, which must be valid according to\nsecp256k1_ec_seckey_verify or 32 zero bytes. For uniformly\nrandom 32-byte tweaks, the chance of being invalid is\nnegligible (around 1 in 2^128).", + "size": 32, + "isOptional": false + } + ], + "description": "Tweak a public key by adding tweak times the generator to it.", + "returnDescription": "0 if the arguments are invalid or the resulting public key would be\ninvalid (only when the tweak is the negation of the corresponding\nsecret key). 1 otherwise.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_seckey_tweak_mul", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "seckey", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 32-byte secret key. If the secret key is\ninvalid according to secp256k1_ec_seckey_verify, this\nfunction returns 0. seckey will be set to some unspecified\nvalue if this function returns 0.", + "size": 32, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte tweak. If the tweak is invalid according to\nsecp256k1_ec_seckey_verify, this function returns 0. For\nuniformly random 32-byte arrays the chance of being invalid\nis negligible (around 1 in 2^128).", + "size": 32, + "isOptional": false + } + ], + "description": "Tweak a secret key by multiplying it by a tweak.", + "returnDescription": "0 if the arguments are invalid. 1 otherwise.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_tweak_mul", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to a public key object. pubkey will be set to an\ninvalid value if this function returns 0.", + "size": 64, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte tweak. If the tweak is invalid according to\nsecp256k1_ec_seckey_verify, this function returns 0. For\nuniformly random 32-byte arrays the chance of being invalid\nis negligible (around 1 in 2^128).", + "size": 32, + "isOptional": false + } + ], + "description": "Tweak a public key by multiplying it by a tweak value.", + "returnDescription": "0 if the arguments are invalid. 1 otherwise.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_context_randomize", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "secp256k1_context*", + "direction": "out", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static).", + "isOptional": false + }, + { + "name": "seed32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "pointer to a 32-byte random seed (NULL resets to initial state).\n\nWhile secp256k1 code is written and tested to be constant-time no matter what\nsecret values are, it is possible that a compiler may output code which is not,\nand also that the CPU may not emit the same radio frequencies or draw the same\namount of power for all values. Randomization of the context shields against\nside-channel observations which aim to exploit secret-dependent behaviour in\ncertain computations which involve secret keys.\n\nIt is highly recommended to call this function on contexts returned from\nsecp256k1_context_create or secp256k1_context_clone (or from the corresponding\nfunctions in secp256k1_preallocated.h) before using these contexts to call API\nfunctions that perform computations involving secret keys, e.g., signing and\npublic key generation. It is possible to call this function more than once on\nthe same context, and doing so before every few computations involving secret\nkeys is recommended as a defense-in-depth measure. Randomization of the static\ncontext secp256k1_context_static is not supported.\n\nCurrently, the random seed is mainly used for blinding multiplications of a\nsecret scalar with the elliptic curve base point. Multiplications of this\nkind are performed by exactly those API functions which are documented to\nrequire a context that is not secp256k1_context_static. As a rule of thumb,\nthese are all functions which take a secret key (or a keypair) as an input.\nA notable exception to that rule is the ECDH module, which relies on a different\nkind of elliptic curve point multiplication and thus does not benefit from\nenhanced protection against side-channel leakage currently.", + "size": 32, + "isOptional": false + } + ], + "description": "Randomizes the context to provide enhanced protection against side-channel leakage.", + "returnDescription": "1: randomization successful\n0: error", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_ec_pubkey_combine", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "out", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to a public key object for placing the resulting public key.", + "size": 64, + "isOptional": false + }, + { + "name": "ins", + "type": "const secp256k1_pubkey * const*", + "direction": "in", + "nonnull": true, + "description": "pointer to array of pointers to public keys.", + "lengthParam": "n", + "isOptional": false + }, + { + "name": "n", + "type": "size_t", + "nonnull": false, + "description": "the number of public keys to add together (must be at least 1).", + "isLengthFor": "ins", + "isOptional": false + } + ], + "description": "Add a number of public keys together.", + "returnDescription": "1: the sum of the public keys is valid.\n0: the sum of the public keys is not valid.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_tagged_sha256", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "hash32", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 32-byte array to store the resulting hash", + "size": 32, + "isOptional": false + }, + { + "name": "tag", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to an array containing the tag", + "lengthParam": "taglen", + "isOptional": false + }, + { + "name": "taglen", + "type": "size_t", + "nonnull": false, + "description": "length of the tag array", + "isLengthFor": "tag", + "isOptional": false + }, + { + "name": "msg", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to an array containing the message", + "lengthParam": "msglen", + "isOptional": false + }, + { + "name": "msglen", + "type": "size_t", + "nonnull": false, + "description": "length of the message array", + "isLengthFor": "msg", + "isOptional": false + } + ], + "description": "Compute a tagged hash as defined in BIP-340.\n\nThis is useful for creating a message hash and achieving domain separation\nthrough an application-specific tag. This function returns\nSHA256(SHA256(tag)||SHA256(tag)||msg). Therefore, tagged hash\nimplementations optimized for a specific tag can precompute the SHA256 state\nafter hashing the tag hashes.", + "returnDescription": "1 always.", + "sourceHeader": "secp256k1.h" + }, + { + "name": "secp256k1_context_preallocated_size", + "returnType": "size_t", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "flags", + "type": "unsigned int", + "nonnull": false, + "description": "which parts of the context to initialize.", + "isOptional": false + } + ], + "description": "Determine the memory size of a secp256k1 context object to be created in\ncaller-provided memory.\n\nThe purpose of this function is to determine how much memory must be provided\nto secp256k1_context_preallocated_create.", + "returnDescription": "the required size of the caller-provided memory block", + "sourceHeader": "secp256k1_preallocated.h" + }, + { + "name": "secp256k1_context_preallocated_create", + "returnType": "secp256k1_context *", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "prealloc", + "type": "void*", + "direction": "out", + "nonnull": true, + "description": "pointer to a rewritable contiguous block of memory of\nsize at least secp256k1_context_preallocated_size(flags)\nbytes, as detailed above.", + "isOptional": false + }, + { + "name": "flags", + "type": "unsigned int", + "nonnull": false, + "description": "which parts of the context to initialize.\n\nSee secp256k1_context_create (in secp256k1.h) for further details.\n\nSee also secp256k1_context_randomize (in secp256k1.h)\nand secp256k1_context_preallocated_destroy.", + "isOptional": false + } + ], + "description": "Create a secp256k1 context object in caller-provided memory.\n\nThe caller must provide a pointer to a rewritable contiguous block of memory\nof size at least secp256k1_context_preallocated_size(flags) bytes, suitably\naligned to hold an object of any type.\n\nThe block of memory is exclusively owned by the created context object during\nthe lifetime of this context object, which begins with the call to this\nfunction and ends when a call to secp256k1_context_preallocated_destroy\n(which destroys the context object again) returns. During the lifetime of the\ncontext object, the caller is obligated not to access this block of memory,\ni.e., the caller may not read or write the memory, e.g., by copying the memory\ncontents to a different location or trying to create a second context object\nin the memory. In simpler words, the prealloc pointer (or any pointer derived\nfrom it) should not be used during the lifetime of the context object.", + "returnDescription": "pointer to newly created context object.", + "sourceHeader": "secp256k1_preallocated.h" + }, + { + "name": "secp256k1_context_preallocated_clone_size", + "returnType": "size_t", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context to copy.", + "isOptional": false + } + ], + "description": "Determine the memory size of a secp256k1 context object to be copied into\ncaller-provided memory.", + "returnDescription": "the required size of the caller-provided memory block.", + "sourceHeader": "secp256k1_preallocated.h" + }, + { + "name": "secp256k1_context_preallocated_clone", + "returnType": "secp256k1_context *", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context to copy (not secp256k1_context_static).", + "isOptional": false + }, + { + "name": "prealloc", + "type": "void*", + "direction": "out", + "nonnull": true, + "description": "pointer to a rewritable contiguous block of memory of\nsize at least secp256k1_context_preallocated_size(flags)\nbytes, as detailed above.", + "isOptional": false + } + ], + "description": "Copy a secp256k1 context object into caller-provided memory.\n\nThe caller must provide a pointer to a rewritable contiguous block of memory\nof size at least secp256k1_context_preallocated_size(flags) bytes, suitably\naligned to hold an object of any type.\n\nThe block of memory is exclusively owned by the created context object during\nthe lifetime of this context object, see the description of\nsecp256k1_context_preallocated_create for details.\n\nCloning secp256k1_context_static is not possible, and should not be emulated by\nthe caller (e.g., using memcpy). Create a new context instead.", + "returnDescription": "pointer to a newly created context object.", + "sourceHeader": "secp256k1_preallocated.h" + }, + { + "name": "secp256k1_context_preallocated_destroy", + "returnType": "void", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "secp256k1_context*", + "direction": "out", + "nonnull": true, + "description": "pointer to a context to destroy, constructed using\nsecp256k1_context_preallocated_create or\nsecp256k1_context_preallocated_clone\n(i.e., not secp256k1_context_static).", + "isOptional": false + } + ], + "description": "Destroy a secp256k1 context object that has been created in\ncaller-provided memory.\n\nThe context pointer may not be used afterwards.\n\nThe context to destroy must have been created using\nsecp256k1_context_preallocated_create or secp256k1_context_preallocated_clone.\nIf the context has instead been created using secp256k1_context_create or\nsecp256k1_context_clone, the behaviour is undefined. In that case,\nsecp256k1_context_destroy must be used instead.\n\nIf required, it is the responsibility of the caller to deallocate the block\nof memory properly after this function returns, e.g., by calling free on the\npreallocated pointer given to secp256k1_context_preallocated_create or\nsecp256k1_context_preallocated_clone.", + "sourceHeader": "secp256k1_preallocated.h" + }, + { + "name": "secp256k1_ecdsa_recoverable_signature_parse_compact", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "sig", + "type": "secp256k1_ecdsa_recoverable_signature*", + "direction": "out", + "nonnull": true, + "description": "pointer to a signature object", + "size": 65, + "isOptional": false + }, + { + "name": "input64", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 64-byte compact signature", + "size": 64, + "isOptional": false + }, + { + "name": "recid", + "type": "int", + "nonnull": false, + "description": "the recovery id (0, 1, 2 or 3)", + "isOptional": false + } + ], + "description": "Parse a compact ECDSA signature (64 bytes \u002B recovery id).", + "returnDescription": "1 when the signature could be parsed, 0 otherwise", + "sourceHeader": "secp256k1_recovery.h" + }, + { + "name": "secp256k1_ecdsa_recoverable_signature_convert", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "sig", + "type": "secp256k1_ecdsa_signature*", + "direction": "out", + "nonnull": true, + "description": "pointer to a normal signature.", + "size": 64, + "isOptional": false + }, + { + "name": "sigin", + "type": "const secp256k1_ecdsa_recoverable_signature*", + "direction": "in", + "nonnull": true, + "description": "pointer to a recoverable signature.", + "size": 65, + "isOptional": false + } + ], + "description": "Convert a recoverable signature into a normal signature.", + "returnDescription": "1", + "sourceHeader": "secp256k1_recovery.h" + }, + { + "name": "secp256k1_ecdsa_recoverable_signature_serialize_compact", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "output64", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 64-byte array of the compact signature.", + "size": 64, + "isOptional": false + }, + { + "name": "recid", + "type": "int*", + "direction": "out", + "nonnull": true, + "description": "pointer to an integer to hold the recovery id.", + "isOptional": false + }, + { + "name": "sig", + "type": "const secp256k1_ecdsa_recoverable_signature*", + "direction": "in", + "nonnull": true, + "description": "pointer to an initialized signature object.", + "size": 65, + "isOptional": false + } + ], + "description": "Serialize an ECDSA signature in compact format (64 bytes \u002B recovery id).", + "returnDescription": "1", + "sourceHeader": "secp256k1_recovery.h" + }, + { + "name": "secp256k1_ecdsa_sign_recoverable", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static).", + "isOptional": false + }, + { + "name": "sig", + "type": "secp256k1_ecdsa_recoverable_signature*", + "direction": "out", + "nonnull": true, + "description": "pointer to an array where the signature will be placed.", + "size": 65, + "isOptional": false + }, + { + "name": "msghash32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "the 32-byte message hash being signed.", + "size": 32, + "isOptional": false + }, + { + "name": "seckey", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte secret key.", + "size": 32, + "isOptional": false + }, + { + "name": "noncefp", + "type": "secp256k1_nonce_function", + "nonnull": false, + "description": "pointer to a nonce generation function. If NULL,\nsecp256k1_nonce_function_default is used.", + "isOptional": false + }, + { + "name": "ndata", + "type": "const void*", + "direction": "in", + "nonnull": false, + "description": "pointer to arbitrary data used by the nonce generation function\n(can be NULL for secp256k1_nonce_function_default).", + "isOptional": true + } + ], + "description": "Create a recoverable ECDSA signature.", + "returnDescription": "1: signature created\n0: the nonce generation function failed, or the secret key was invalid.", + "sourceHeader": "secp256k1_recovery.h" + }, + { + "name": "secp256k1_ecdsa_recover", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to the recovered public key.", + "size": 64, + "isOptional": false + }, + { + "name": "sig", + "type": "const secp256k1_ecdsa_recoverable_signature*", + "direction": "in", + "nonnull": true, + "description": "pointer to initialized signature that supports pubkey recovery.", + "size": 65, + "isOptional": false + }, + { + "name": "msghash32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "the 32-byte message hash assumed to be signed.", + "size": 32, + "isOptional": false + } + ], + "description": "Recover an ECDSA public key from a signature.\n\nSuccessful public key recovery guarantees that the signature, after normalization,\npasses \u0060secp256k1_ecdsa_verify\u0060. Thus, explicit verification is not necessary.\n\nHowever, a recoverable signature that successfully passes \u0060secp256k1_ecdsa_recover\u0060,\nwhen converted to a non-recoverable signature (using\n\u0060secp256k1_ecdsa_recoverable_signature_convert\u0060), is not guaranteed to be\nnormalized and thus not guaranteed to pass \u0060secp256k1_ecdsa_verify\u0060. If a\nnormalized signature is required, call \u0060secp256k1_ecdsa_signature_normalize\u0060\nafter \u0060secp256k1_ecdsa_recoverable_signature_convert\u0060.", + "returnDescription": "1: public key successfully recovered\n0: otherwise.", + "sourceHeader": "secp256k1_recovery.h" + }, + { + "name": "secp256k1_ecdh", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "output", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to an array to be filled by hashfp.", + "size": 32, + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to a secp256k1_pubkey containing an initialized public key.", + "size": 64, + "isOptional": false + }, + { + "name": "seckey", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "a 32-byte scalar with which to multiply the point.", + "size": 32, + "isOptional": false + }, + { + "name": "hashfp", + "type": "secp256k1_ecdh_hash_function", + "nonnull": false, + "description": "pointer to a hash function. If NULL,\nsecp256k1_ecdh_hash_function_sha256 is used\n(in which case, 32 bytes will be written to output).", + "isOptional": false + }, + { + "name": "data", + "type": "void*", + "direction": "out", + "nonnull": false, + "description": "arbitrary data pointer that is passed through to hashfp\n(can be NULL for secp256k1_ecdh_hash_function_sha256).", + "isOptional": true + } + ], + "description": "Compute an EC Diffie-Hellman secret in constant time", + "returnDescription": "1: exponentiation was successful\n0: scalar was invalid (zero or overflow) or hashfp returned 0", + "sourceHeader": "secp256k1_ecdh.h" + }, + { + "name": "secp256k1_xonly_pubkey_parse", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_xonly_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to a pubkey object. If 1 is returned, it is set to a\nparsed version of input. If not, it\u0027s set to an invalid value.", + "size": 64, + "isOptional": false + }, + { + "name": "input32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a serialized xonly_pubkey.", + "size": 32, + "isOptional": false + } + ], + "description": "Parse a 32-byte sequence into a xonly_pubkey object.", + "returnDescription": "1 if the public key was fully valid.\n0 if the public key could not be parsed or is invalid.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_xonly_pubkey_serialize", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "output32", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 32-byte array to place the serialized key in.", + "size": 32, + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_xonly_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to a secp256k1_xonly_pubkey containing an initialized public key.", + "size": 64, + "isOptional": false + } + ], + "description": "Serialize an xonly_pubkey object into a 32-byte sequence.", + "returnDescription": "1 always.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_xonly_pubkey_cmp", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "pk1", + "type": "const secp256k1_xonly_pubkey*", + "direction": "in", + "nonnull": true, + "size": 64, + "isOptional": false + }, + { + "name": "pk2", + "type": "const secp256k1_xonly_pubkey*", + "direction": "in", + "nonnull": true, + "size": 64, + "isOptional": false + } + ], + "description": "Compare two x-only public keys using lexicographic order", + "returnDescription": "\u003C0 if the first public key is less than the second\n\u003E0 if the first public key is greater than the second\n0 if the two public keys are equal", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_xonly_pubkey_from_pubkey", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "xonly_pubkey", + "type": "secp256k1_xonly_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to an x-only public key object for placing the converted public key.", + "size": 64, + "isOptional": false + }, + { + "name": "pk_parity", + "type": "int*", + "direction": "out", + "nonnull": false, + "description": "Ignored if NULL. Otherwise, pointer to an integer that\nwill be set to 1 if the point encoded by xonly_pubkey is\nthe negation of the pubkey and set to 0 otherwise.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to a public key that is converted.", + "size": 64, + "isOptional": false + } + ], + "description": "Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey.", + "returnDescription": "1 always.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_xonly_pubkey_tweak_add", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "output_pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to a public key to store the result. Will be set\nto an invalid value if this function returns 0.", + "size": 64, + "isOptional": false + }, + { + "name": "internal_pubkey", + "type": "const secp256k1_xonly_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to an x-only pubkey to apply the tweak to.", + "size": 64, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte tweak, which must be valid\naccording to secp256k1_ec_seckey_verify or 32 zero\nbytes. For uniformly random 32-byte tweaks, the chance of\nbeing invalid is negligible (around 1 in 2^128).", + "size": 32, + "isOptional": false + } + ], + "description": "Tweak an x-only public key by adding the generator multiplied with tweak32\nto it.\n\nNote that the resulting point can not in general be represented by an x-only\npubkey because it may have an odd Y coordinate. Instead, the output_pubkey\nis a normal secp256k1_pubkey.", + "returnDescription": "0 if the arguments are invalid or the resulting public key would be\ninvalid (only when the tweak is the negation of the corresponding\nsecret key). 1 otherwise.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_xonly_pubkey_tweak_add_check", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "tweaked_pubkey32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a serialized xonly_pubkey.", + "size": 32, + "isOptional": false + }, + { + "name": "tweaked_pk_parity", + "type": "int", + "nonnull": false, + "description": "the parity of the tweaked pubkey (whose serialization\nis passed in as tweaked_pubkey32). This must match the\npk_parity value that is returned when calling\nsecp256k1_xonly_pubkey with the tweaked pubkey, or\nthis function will fail.", + "isOptional": false + }, + { + "name": "internal_pubkey", + "type": "const secp256k1_xonly_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to an x-only public key object to apply the tweak to.", + "size": 64, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte tweak.", + "size": 32, + "isOptional": false + } + ], + "description": "Checks that a tweaked pubkey is the result of calling\nsecp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32.\n\nThe tweaked pubkey is represented by its 32-byte x-only serialization and\nits pk_parity, which can both be obtained by converting the result of\ntweak_add to a secp256k1_xonly_pubkey.\n\nNote that this alone does _not_ verify that the tweaked pubkey is a\ncommitment. If the tweak is not chosen in a specific way, the tweaked pubkey\ncan easily be the result of a different internal_pubkey and tweak.", + "returnDescription": "0 if the arguments are invalid or the tweaked pubkey is not the\nresult of tweaking the internal_pubkey with tweak32. 1 otherwise.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_keypair_create", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static).", + "isOptional": false + }, + { + "name": "keypair", + "type": "secp256k1_keypair*", + "direction": "out", + "nonnull": true, + "description": "pointer to the created keypair.", + "size": 96, + "isOptional": false + }, + { + "name": "seckey", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte secret key.", + "size": 32, + "isOptional": false + } + ], + "description": "Compute the keypair for a valid secret key.\n\nSee the documentation of \u0060secp256k1_ec_seckey_verify\u0060 for more information\nabout the validity of secret keys.", + "returnDescription": "1: secret key is valid\n0: secret key is invalid", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_keypair_sec", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "seckey", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 32-byte buffer for the secret key.", + "size": 32, + "isOptional": false + }, + { + "name": "keypair", + "type": "const secp256k1_keypair*", + "direction": "in", + "nonnull": true, + "description": "pointer to a keypair.", + "size": 96, + "isOptional": false + } + ], + "description": "Get the secret key from a keypair.", + "returnDescription": "1 always.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_keypair_pub", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to a pubkey object, set to the keypair public key.", + "size": 64, + "isOptional": false + }, + { + "name": "keypair", + "type": "const secp256k1_keypair*", + "direction": "in", + "nonnull": true, + "description": "pointer to a keypair.", + "size": 96, + "isOptional": false + } + ], + "description": "Get the public key from a keypair.", + "returnDescription": "1 always.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_keypair_xonly_pub", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_xonly_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to an xonly_pubkey object, set to the keypair\npublic key after converting it to an xonly_pubkey.", + "size": 64, + "isOptional": false + }, + { + "name": "pk_parity", + "type": "int*", + "direction": "out", + "nonnull": false, + "description": "Ignored if NULL. Otherwise, pointer to an integer that will be set to the\npk_parity argument of secp256k1_xonly_pubkey_from_pubkey.", + "isOptional": false + }, + { + "name": "keypair", + "type": "const secp256k1_keypair*", + "direction": "in", + "nonnull": true, + "description": "pointer to a keypair.", + "size": 96, + "isOptional": false + } + ], + "description": "Get the x-only public key from a keypair.\n\nThis is the same as calling secp256k1_keypair_pub and then\nsecp256k1_xonly_pubkey_from_pubkey.", + "returnDescription": "1 always.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_keypair_xonly_tweak_add", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "keypair", + "type": "secp256k1_keypair*", + "direction": "out", + "nonnull": true, + "description": "pointer to a keypair to apply the tweak to. Will be set to\nan invalid value if this function returns 0.", + "size": 96, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte tweak, which must be valid according to\nsecp256k1_ec_seckey_verify or 32 zero bytes. For uniformly\nrandom 32-byte tweaks, the chance of being invalid is\nnegligible (around 1 in 2^128).", + "size": 32, + "isOptional": false + } + ], + "description": "Tweak a keypair by adding tweak32 to the secret key and updating the public\nkey accordingly.\n\nCalling this function and then secp256k1_keypair_pub results in the same\npublic key as calling secp256k1_keypair_xonly_pub and then\nsecp256k1_xonly_pubkey_tweak_add.", + "returnDescription": "0 if the arguments are invalid or the resulting keypair would be\ninvalid (only when the tweak is the negation of the keypair\u0027s\nsecret key). 1 otherwise.", + "sourceHeader": "secp256k1_extrakeys.h" + }, + { + "name": "secp256k1_schnorrsig_sign32", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static).", + "isOptional": false + }, + { + "name": "sig64", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 64-byte array to store the serialized signature.", + "size": 64, + "isOptional": false + }, + { + "name": "msg32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "the 32-byte message being signed.", + "size": 32, + "isOptional": false + }, + { + "name": "keypair", + "type": "const secp256k1_keypair*", + "direction": "in", + "nonnull": true, + "description": "pointer to an initialized keypair.", + "size": 96, + "isOptional": false + }, + { + "name": "aux_rand32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "32 bytes of fresh randomness. While recommended to provide\nthis, it is only supplemental to security and can be NULL. A\nNULL argument is treated the same as an all-zero one. See\nBIP-340 \u0022Default Signing\u0022 for a full explanation of this\nargument and for guidance if randomness is expensive.", + "size": 32, + "isOptional": false + } + ], + "description": "Create a Schnorr signature.\n\nDoes _not_ strictly follow BIP-340 because it does not verify the resulting\nsignature. Instead, you can manually use secp256k1_schnorrsig_verify and\nabort if it fails.\n\nThis function only signs 32-byte messages. If you have messages of a\ndifferent size (or the same size but without a context-specific tag\nprefix), it is recommended to create a 32-byte message hash with\nsecp256k1_tagged_sha256 and then sign the hash. Tagged hashing allows\nproviding an context-specific tag for domain separation. This prevents\nsignatures from being valid in multiple contexts by accident.\n\nReturns 1 on success, 0 on failure.", + "sourceHeader": "secp256k1_schnorrsig.h" + }, + { + "name": "secp256k1_schnorrsig_sign", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": true, + "deprecatedMessage": "Use secp256k1_schnorrsig_sign32 instead", + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "isOptional": false + }, + { + "name": "sig64", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "size": 64, + "isOptional": false + }, + { + "name": "msg32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "size": 32, + "isOptional": false + }, + { + "name": "keypair", + "type": "const secp256k1_keypair*", + "direction": "in", + "nonnull": true, + "size": 96, + "isOptional": false + }, + { + "name": "aux_rand32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "size": 32, + "isOptional": false + } + ], + "description": "Same as secp256k1_schnorrsig_sign32, but DEPRECATED. Will be removed in\nfuture versions.", + "sourceHeader": "secp256k1_schnorrsig.h" + }, + { + "name": "secp256k1_schnorrsig_sign_custom", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static).", + "isOptional": false + }, + { + "name": "sig64", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 64-byte array to store the serialized signature.", + "size": 64, + "isOptional": false + }, + { + "name": "msg", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "the message being signed. Can only be NULL if msglen is 0.", + "lengthParam": "msglen", + "isOptional": false + }, + { + "name": "msglen", + "type": "size_t", + "nonnull": false, + "description": "length of the message.", + "isLengthFor": "msg", + "isOptional": false + }, + { + "name": "keypair", + "type": "const secp256k1_keypair*", + "direction": "in", + "nonnull": true, + "description": "pointer to an initialized keypair.", + "size": 96, + "isOptional": false + }, + { + "name": "extraparams", + "type": "secp256k1_schnorrsig_extraparams*", + "direction": "out", + "nonnull": false, + "description": "pointer to an extraparams object (can be NULL).", + "isOptional": false + } + ], + "description": "Create a Schnorr signature with a more flexible API.\n\nSame arguments as secp256k1_schnorrsig_sign except that it allows signing\nvariable length messages and accepts a pointer to an extraparams object that\nallows customizing signing by passing additional arguments.\n\nEquivalent to secp256k1_schnorrsig_sign32(..., aux_rand32) if msglen is 32\nand extraparams is initialized as follows:\n\u0060\u0060\u0060\nsecp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT;\nextraparams.ndata = (unsigned char*)aux_rand32;\n\u0060\u0060\u0060\n\nReturns 1 on success, 0 on failure.", + "sourceHeader": "secp256k1_schnorrsig.h" + }, + { + "name": "secp256k1_schnorrsig_verify", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "sig64", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to the 64-byte signature to verify.", + "size": 64, + "isOptional": false + }, + { + "name": "msg", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "the message being verified. Can only be NULL if msglen is 0.", + "lengthParam": "msglen", + "isOptional": false + }, + { + "name": "msglen", + "type": "size_t", + "nonnull": false, + "description": "length of the message", + "isLengthFor": "msg", + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_xonly_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to an x-only public key to verify with", + "size": 64, + "isOptional": false + } + ], + "description": "Verify a Schnorr signature.", + "returnDescription": "1: correct signature\n0: incorrect signature", + "sourceHeader": "secp256k1_schnorrsig.h" + }, + { + "name": "secp256k1_ellswift_encode", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "ell64", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 64-byte array to be filled", + "size": 64, + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "pointer to a secp256k1_pubkey containing an\ninitialized public key", + "size": 64, + "isOptional": false + }, + { + "name": "rnd32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to 32 bytes of randomness\n\nIt is recommended that rnd32 consists of 32 uniformly random bytes, not\nknown to any adversary trying to detect whether public keys are being\nencoded, though 16 bytes of randomness (padded to an array of 32 bytes,\ne.g., with zeros) suffice to make the result indistinguishable from\nuniform. The randomness in rnd32 must not be a deterministic function of\nthe pubkey (it can be derived from the private key, though).\n\nIt is not guaranteed that the computed encoding is stable across versions\nof the library, even if all arguments to this function (including rnd32)\nare the same.\n\nThis function runs in variable time.", + "size": 32, + "isOptional": false + } + ], + "description": "Construct a 64-byte ElligatorSwift encoding of a given pubkey.", + "returnDescription": "1 always.", + "sourceHeader": "secp256k1_ellswift.h" + }, + { + "name": "secp256k1_ellswift_decode", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "pointer to a secp256k1_pubkey that will be filled", + "size": 64, + "isOptional": false + }, + { + "name": "ell64", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 64-byte array to decode\n\nThis function runs in variable time.", + "size": 64, + "isOptional": false + } + ], + "description": "Decode a 64-bytes ElligatorSwift encoded public key.", + "returnDescription": "always 1", + "sourceHeader": "secp256k1_ellswift.h" + }, + { + "name": "secp256k1_ellswift_create", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static)", + "isOptional": false + }, + { + "name": "ell64", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 64-byte array to receive the ElligatorSwift\npublic key", + "size": 64, + "isOptional": false + }, + { + "name": "seckey32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to a 32-byte secret key", + "size": 32, + "isOptional": false + }, + { + "name": "auxrnd32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "(optional) pointer to 32 bytes of randomness\n\nConstant time in seckey and auxrnd32, but not in the resulting public key.\n\nIt is recommended that auxrnd32 contains 32 uniformly random bytes, though\nit is optional (and does result in encodings that are indistinguishable from\nuniform even without any auxrnd32). It differs from the (mandatory) rnd32\nargument to secp256k1_ellswift_encode in this regard.\n\nThis function can be used instead of calling secp256k1_ec_pubkey_create\nfollowed by secp256k1_ellswift_encode. It is safer, as it uses the secret\nkey as entropy for the encoding (supplemented with auxrnd32, if provided).\n\nLike secp256k1_ellswift_encode, this function does not guarantee that the\ncomputed encoding is stable across versions of the library, even if all\narguments (including auxrnd32) are the same.", + "size": 32, + "isOptional": false + } + ], + "description": "Compute an ElligatorSwift public key for a secret key.", + "returnDescription": "1: secret was valid, public key was stored.\n0: secret was invalid, try again.", + "sourceHeader": "secp256k1_ellswift.h" + }, + { + "name": "secp256k1_ellswift_xdh", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object.", + "isOptional": false + }, + { + "name": "output", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to an array to be filled by hashfp.", + "size": 32, + "isOptional": false + }, + { + "name": "ell_a64", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to the 64-byte encoded public key of party A\n(will not be NULL)", + "size": 64, + "isOptional": false + }, + { + "name": "ell_b64", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to the 64-byte encoded public key of party B\n(will not be NULL)", + "size": 64, + "isOptional": false + }, + { + "name": "seckey32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to our 32-byte secret key", + "size": 32, + "isOptional": false + }, + { + "name": "party", + "type": "int", + "nonnull": false, + "description": "boolean indicating which party we are: zero if we are\nparty A, non-zero if we are party B. seckey32 must be\nthe private key corresponding to that party\u0027s ell_?64.\nThis correspondence is not checked.", + "isOptional": false + }, + { + "name": "hashfp", + "type": "secp256k1_ellswift_xdh_hash_function", + "nonnull": true, + "description": "pointer to a hash function.", + "isOptional": false + }, + { + "name": "data", + "type": "void*", + "direction": "out", + "nonnull": false, + "description": "arbitrary data pointer passed through to hashfp.\n\nConstant time in seckey32.\n\nThis function is more efficient than decoding the public keys, and performing\nECDH on them.", + "isOptional": true + } + ], + "description": "Given a private key, and ElligatorSwift public keys sent in both directions,\ncompute a shared secret using x-only Elliptic Curve Diffie-Hellman (ECDH).", + "returnDescription": "1: shared secret was successfully computed\n0: secret was invalid or hashfp returned 0", + "sourceHeader": "secp256k1_ellswift.h" + }, + { + "name": "secp256k1_musig_pubnonce_parse", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "nonce", + "type": "secp256k1_musig_pubnonce*", + "direction": "out", + "nonnull": true, + "description": "pointer to a nonce object", + "size": 132, + "isOptional": false + }, + { + "name": "in66", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to the 66-byte nonce to be parsed", + "size": 66, + "isOptional": false + } + ], + "description": "Parse a signer\u0027s public nonce.", + "returnDescription": "1 when the nonce could be parsed, 0 otherwise.", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_pubnonce_serialize", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "out66", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 66-byte array to store the serialized nonce", + "size": 66, + "isOptional": false + }, + { + "name": "nonce", + "type": "const secp256k1_musig_pubnonce*", + "direction": "in", + "nonnull": true, + "description": "pointer to the nonce", + "size": 132, + "isOptional": false + } + ], + "description": "Serialize a signer\u0027s public nonce", + "returnDescription": "1 always", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_aggnonce_parse", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "nonce", + "type": "secp256k1_musig_aggnonce*", + "direction": "out", + "nonnull": true, + "description": "pointer to a nonce object", + "size": 132, + "isOptional": false + }, + { + "name": "in66", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to the 66-byte nonce to be parsed", + "size": 66, + "isOptional": false + } + ], + "description": "Parse an aggregate public nonce.", + "returnDescription": "1 when the nonce could be parsed, 0 otherwise.", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_aggnonce_serialize", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "out66", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 66-byte array to store the serialized nonce", + "size": 66, + "isOptional": false + }, + { + "name": "nonce", + "type": "const secp256k1_musig_aggnonce*", + "direction": "in", + "nonnull": true, + "description": "pointer to the nonce", + "size": 132, + "isOptional": false + } + ], + "description": "Serialize an aggregate public nonce", + "returnDescription": "1 always", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_partial_sig_parse", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "sig", + "type": "secp256k1_musig_partial_sig*", + "direction": "out", + "nonnull": true, + "description": "pointer to a signature object", + "size": 36, + "isOptional": false + }, + { + "name": "in32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "pointer to the 32-byte signature to be parsed", + "size": 32, + "isOptional": false + } + ], + "description": "Parse a MuSig partial signature.", + "returnDescription": "1 when the signature could be parsed, 0 otherwise.", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_partial_sig_serialize", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "out32", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "pointer to a 32-byte array to store the serialized signature", + "size": 32, + "isOptional": false + }, + { + "name": "sig", + "type": "const secp256k1_musig_partial_sig*", + "direction": "in", + "nonnull": true, + "description": "pointer to the signature", + "size": 36, + "isOptional": false + } + ], + "description": "Serialize a MuSig partial signature", + "returnDescription": "1 always", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_pubkey_agg", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "agg_pk", + "type": "secp256k1_xonly_pubkey*", + "direction": "out", + "nonnull": false, + "description": "the MuSig-aggregated x-only public key. If you do not need it,\nthis arg can be NULL.", + "size": 64, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "secp256k1_musig_keyagg_cache*", + "direction": "out", + "nonnull": false, + "description": "if non-NULL, pointer to a musig_keyagg_cache struct that\nis required for signing (or observing the signing session\nand verifying partial signatures).", + "size": 197, + "isOptional": false + }, + { + "name": "pubkeys", + "type": "const secp256k1_pubkey * const*", + "direction": "in", + "nonnull": true, + "description": "input array of pointers to public keys to aggregate. The order\nis important; a different order will result in a different\naggregate public key.", + "lengthParam": "n_pubkeys", + "isOptional": false + }, + { + "name": "n_pubkeys", + "type": "size_t", + "nonnull": false, + "description": "length of pubkeys array. Must be greater than 0.", + "isLengthFor": "pubkeys", + "isOptional": false + } + ], + "description": "Computes an aggregate public key and uses it to initialize a keyagg_cache\n\nDifferent orders of \u0060pubkeys\u0060 result in different \u0060agg_pk\u0060s.\n\nBefore aggregating, the pubkeys can be sorted with \u0060secp256k1_ec_pubkey_sort\u0060\nwhich ensures the same \u0060agg_pk\u0060 result for the same multiset of pubkeys.\nThis is useful to do before \u0060pubkey_agg\u0060, such that the order of pubkeys\ndoes not affect the aggregate public key.", + "returnDescription": "0 if the arguments are invalid, 1 otherwise", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_pubkey_get", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "agg_pk", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": true, + "description": "the MuSig-aggregated public key.", + "size": 64, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "const secp256k1_musig_keyagg_cache*", + "direction": "in", + "nonnull": true, + "description": "pointer to a \u0060musig_keyagg_cache\u0060 struct initialized by\n\u0060musig_pubkey_agg\u0060", + "size": 197, + "isOptional": false + } + ], + "description": "Obtain the aggregate public key from a keyagg_cache.\n\nThis is only useful if you need the non-xonly public key, in particular for\nplain (non-xonly) tweaking or batch-verifying multiple key aggregations\n(not implemented).", + "returnDescription": "0 if the arguments are invalid, 1 otherwise", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_pubkey_ec_tweak_add", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "isOptional": false + }, + { + "name": "output_pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": false, + "size": 64, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "secp256k1_musig_keyagg_cache*", + "direction": "out", + "nonnull": true, + "size": 197, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "size": 32, + "isOptional": false + } + ], + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_pubkey_xonly_tweak_add", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "isOptional": false + }, + { + "name": "output_pubkey", + "type": "secp256k1_pubkey*", + "direction": "out", + "nonnull": false, + "size": 64, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "secp256k1_musig_keyagg_cache*", + "direction": "out", + "nonnull": true, + "size": 197, + "isOptional": false + }, + { + "name": "tweak32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "size": 32, + "isOptional": false + } + ], + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_nonce_gen", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static)", + "isOptional": false + }, + { + "name": "secnonce", + "type": "secp256k1_musig_secnonce*", + "direction": "out", + "nonnull": true, + "description": "pointer to a structure to store the secret nonce", + "size": 132, + "isOptional": false + }, + { + "name": "pubnonce", + "type": "secp256k1_musig_pubnonce*", + "direction": "out", + "nonnull": true, + "description": "pointer to a structure to store the public nonce", + "size": 132, + "isOptional": false + }, + { + "name": "session_secrand32", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "a 32-byte session_secrand32 as explained above. Must be unique to this\ncall to secp256k1_musig_nonce_gen and must be uniformly\nrandom. If the function call is successful, the\nsession_secrand32 buffer is invalidated to prevent reuse.", + "size": 32, + "isOptional": false + }, + { + "name": "seckey", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "the 32-byte secret key that will later be used for signing, if\nalready known (can be NULL)", + "size": 32, + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "public key of the signer creating the nonce. The secnonce\noutput of this function cannot be used to sign for any\nother public key. While the public key should correspond\nto the provided seckey, a mismatch will not cause the\nfunction to return 0.", + "size": 64, + "isOptional": false + }, + { + "name": "msg32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "the 32-byte message that will later be signed, if already known\n(can be NULL)", + "size": 32, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "const secp256k1_musig_keyagg_cache*", + "direction": "in", + "nonnull": false, + "description": "pointer to the keyagg_cache that was used to create the aggregate\n(and potentially tweaked) public key if already known\n(can be NULL)", + "size": 197, + "isOptional": false + }, + { + "name": "extra_input32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "an optional 32-byte array that is input to the nonce\nderivation function (can be NULL)", + "size": 32, + "isOptional": false + } + ], + "description": "Starts a signing session by generating a nonce\n\nThis function outputs a secret nonce that will be required for signing and a\ncorresponding public nonce that is intended to be sent to other signers.\n\nMuSig differs from regular Schnorr signing in that implementers _must_ take\nspecial care to not reuse a nonce. This can be ensured by following these rules:\n\n1. Each call to this function must have a UNIQUE session_secrand32 that must\nNOT BE REUSED in subsequent calls to this function and must be KEPT\nSECRET (even from other signers).\n2. If you already know the seckey, message or aggregate public key\ncache, they can be optionally provided to derive the nonce and increase\nmisuse-resistance. The extra_input32 argument can be used to provide\nadditional data that does not repeat in normal scenarios, such as the\ncurrent time.\n3. Avoid copying (or serializing) the secnonce. This reduces the possibility\nthat it is used more than once for signing.\n\nIf you don\u0027t have access to good randomness for session_secrand32, but you\nhave access to a non-repeating counter, then see\nsecp256k1_musig_nonce_gen_counter.\n\nRemember that nonce reuse will leak the secret key!\nNote that using the same seckey for multiple MuSig sessions is fine.", + "returnDescription": "0 if the arguments are invalid and 1 otherwise", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_nonce_gen_counter", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object (not secp256k1_context_static)", + "isOptional": false + }, + { + "name": "secnonce", + "type": "secp256k1_musig_secnonce*", + "direction": "out", + "nonnull": true, + "description": "pointer to a structure to store the secret nonce", + "size": 132, + "isOptional": false + }, + { + "name": "pubnonce", + "type": "secp256k1_musig_pubnonce*", + "direction": "out", + "nonnull": true, + "description": "pointer to a structure to store the public nonce", + "size": 132, + "isOptional": false + }, + { + "name": "nonrepeating_cnt", + "type": "uint64_t", + "nonnull": false, + "description": "the value of a counter as explained above. Must be\nunique to this call to secp256k1_musig_nonce_gen.", + "isOptional": false + }, + { + "name": "keypair", + "type": "const secp256k1_keypair*", + "direction": "in", + "nonnull": true, + "description": "keypair of the signer creating the nonce. The secnonce\noutput of this function cannot be used to sign for any\nother keypair.", + "size": 96, + "isOptional": false + }, + { + "name": "msg32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "the 32-byte message that will later be signed, if already known\n(can be NULL)", + "size": 32, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "const secp256k1_musig_keyagg_cache*", + "direction": "in", + "nonnull": false, + "description": "pointer to the keyagg_cache that was used to create the aggregate\n(and potentially tweaked) public key if already known\n(can be NULL)", + "size": 197, + "isOptional": false + }, + { + "name": "extra_input32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": false, + "description": "an optional 32-byte array that is input to the nonce\nderivation function (can be NULL)", + "size": 32, + "isOptional": false + } + ], + "description": "Alternative way to generate a nonce and start a signing session\n\nThis function outputs a secret nonce that will be required for signing and a\ncorresponding public nonce that is intended to be sent to other signers.\n\nThis function differs from \u0060secp256k1_musig_nonce_gen\u0060 by accepting a\nnon-repeating counter value instead of a secret random value. This requires\nthat a secret key is provided to \u0060secp256k1_musig_nonce_gen_counter\u0060\n(through the keypair argument), as opposed to \u0060secp256k1_musig_nonce_gen\u0060\nwhere the seckey argument is optional.\n\nMuSig differs from regular Schnorr signing in that implementers _must_ take\nspecial care to not reuse a nonce. This can be ensured by following these rules:\n\n1. The nonrepeating_cnt argument must be a counter value that never repeats,\ni.e., you must never call \u0060secp256k1_musig_nonce_gen_counter\u0060 twice with\nthe same keypair and nonrepeating_cnt value. For example, this implies\nthat if the same keypair is used with \u0060secp256k1_musig_nonce_gen_counter\u0060\non multiple devices, none of the devices should have the same counter\nvalue as any other device.\n2. If the seckey, message or aggregate public key cache is already available\nat this stage, any of these can be optionally provided, in which case\nthey will be used in the derivation of the nonce and increase\nmisuse-resistance. The extra_input32 argument can be used to provide\nadditional data that does not repeat in normal scenarios, such as the\ncurrent time.\n3. Avoid copying (or serializing) the secnonce. This reduces the possibility\nthat it is used more than once for signing.\n\nRemember that nonce reuse will leak the secret key!\nNote that using the same keypair for multiple MuSig sessions is fine.", + "returnDescription": "0 if the arguments are invalid and 1 otherwise", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_nonce_agg", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "aggnonce", + "type": "secp256k1_musig_aggnonce*", + "direction": "out", + "nonnull": true, + "description": "pointer to an aggregate public nonce object for\nmusig_nonce_process", + "size": 132, + "isOptional": false + }, + { + "name": "pubnonces", + "type": "const secp256k1_musig_pubnonce * const*", + "direction": "in", + "nonnull": true, + "description": "array of pointers to public nonces sent by the\nsigners", + "lengthParam": "n_pubnonces", + "isOptional": false + }, + { + "name": "n_pubnonces", + "type": "size_t", + "nonnull": false, + "description": "number of elements in the pubnonces array. Must be\ngreater than 0.", + "isLengthFor": "pubnonces", + "isOptional": false + } + ], + "description": "Aggregates the nonces of all signers into a single nonce\n\nThis can be done by an untrusted party to reduce the communication\nbetween signers. Instead of everyone sending nonces to everyone else, there\ncan be one party receiving all nonces, aggregating the nonces with this\nfunction and then sending only the aggregate nonce back to the signers.\n\nIf the aggregator does not compute the aggregate nonce correctly, the final\nsignature will be invalid.", + "returnDescription": "0 if the arguments are invalid, 1 otherwise", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_nonce_process", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "session", + "type": "secp256k1_musig_session*", + "direction": "out", + "nonnull": true, + "description": "pointer to a struct to store the session", + "size": 133, + "isOptional": false + }, + { + "name": "aggnonce", + "type": "const secp256k1_musig_aggnonce*", + "direction": "in", + "nonnull": true, + "description": "pointer to an aggregate public nonce object that is the\noutput of musig_nonce_agg", + "size": 132, + "isOptional": false + }, + { + "name": "msg32", + "type": "const unsigned char*", + "direction": "in", + "nonnull": true, + "description": "the 32-byte message to sign", + "size": 32, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "const secp256k1_musig_keyagg_cache*", + "direction": "in", + "nonnull": true, + "description": "pointer to the keyagg_cache that was used to create the\naggregate (and potentially tweaked) pubkey", + "size": 197, + "isOptional": false + } + ], + "description": "Takes the aggregate nonce and creates a session that is required for signing\nand verification of partial signatures.", + "returnDescription": "0 if the arguments are invalid, 1 otherwise", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_partial_sign", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "partial_sig", + "type": "secp256k1_musig_partial_sig*", + "direction": "out", + "nonnull": true, + "description": "pointer to struct to store the partial signature", + "size": 36, + "isOptional": false + }, + { + "name": "secnonce", + "type": "secp256k1_musig_secnonce*", + "direction": "out", + "nonnull": true, + "description": "pointer to the secnonce struct created in\nmusig_nonce_gen that has been never used in a\npartial_sign call before and has been created for the\nkeypair", + "size": 132, + "isOptional": false + }, + { + "name": "keypair", + "type": "const secp256k1_keypair*", + "direction": "in", + "nonnull": true, + "description": "pointer to keypair to sign the message with", + "size": 96, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "const secp256k1_musig_keyagg_cache*", + "direction": "in", + "nonnull": true, + "description": "pointer to the keyagg_cache that was output when the\naggregate public key for this session", + "size": 197, + "isOptional": false + }, + { + "name": "session", + "type": "const secp256k1_musig_session*", + "direction": "in", + "nonnull": true, + "description": "pointer to the session that was created with\nmusig_nonce_process", + "size": 133, + "isOptional": false + } + ], + "description": "Produces a partial signature\n\nThis function overwrites the given secnonce with zeros and will abort if given a\nsecnonce that is all zeros. This is a best effort attempt to protect against nonce\nreuse. However, this is of course easily defeated if the secnonce has been\ncopied (or serialized). Remember that nonce reuse will leak the secret key!\n\nFor signing to succeed, the secnonce provided to this function must have\nbeen generated for the provided keypair. This means that when signing for a\nkeypair consisting of a seckey and pubkey, the secnonce must have been\ncreated by calling musig_nonce_gen with that pubkey. Otherwise, the\nillegal_callback is called.\n\nThis function does not verify the output partial signature, deviating from\nthe BIP 327 specification. It is recommended to verify the output partial\nsignature with \u0060secp256k1_musig_partial_sig_verify\u0060 to prevent random or\nadversarially provoked computation errors.", + "returnDescription": "0 if the arguments are invalid or the provided secnonce has already\nbeen used for signing, 1 otherwise", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_partial_sig_verify", + "returnType": "int", + "warnUnusedResult": true, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "isOptional": false + }, + { + "name": "partial_sig", + "type": "const secp256k1_musig_partial_sig*", + "direction": "in", + "nonnull": true, + "description": "pointer to partial signature to verify, sent by\nthe signer associated with \u0060pubnonce\u0060 and \u0060pubkey\u0060", + "size": 36, + "isOptional": false + }, + { + "name": "pubnonce", + "type": "const secp256k1_musig_pubnonce*", + "direction": "in", + "nonnull": true, + "description": "public nonce of the signer in the signing session", + "size": 132, + "isOptional": false + }, + { + "name": "pubkey", + "type": "const secp256k1_pubkey*", + "direction": "in", + "nonnull": true, + "description": "public key of the signer in the signing session", + "size": 64, + "isOptional": false + }, + { + "name": "keyagg_cache", + "type": "const secp256k1_musig_keyagg_cache*", + "direction": "in", + "nonnull": true, + "description": "pointer to the keyagg_cache that was output when the\naggregate public key for this signing session", + "size": 197, + "isOptional": false + }, + { + "name": "session", + "type": "const secp256k1_musig_session*", + "direction": "in", + "nonnull": true, + "description": "pointer to the session that was created with\n\u0060musig_nonce_process\u0060", + "size": 133, + "isOptional": false + } + ], + "description": "Verifies an individual signer\u0027s partial signature\n\nThe signature is verified for a specific signing session. In order to avoid\naccidentally verifying a signature from a different or non-existing signing\nsession, you must ensure the following:\n1. The \u0060keyagg_cache\u0060 argument is identical to the one used to create the\n\u0060session\u0060 with \u0060musig_nonce_process\u0060.\n2. The \u0060pubkey\u0060 argument must be identical to the one sent by the signer\nbefore aggregating it with \u0060musig_pubkey_agg\u0060 to create the\n\u0060keyagg_cache\u0060.\n3. The \u0060pubnonce\u0060 argument must be identical to the one sent by the signer\nbefore aggregating it with \u0060musig_nonce_agg\u0060 and using the result to\ncreate the \u0060session\u0060 with \u0060musig_nonce_process\u0060.\n\nIt is not required to call this function in regular MuSig sessions, because\nif any partial signature does not verify, the final signature will not\nverify either, so the problem will be caught. However, this function\nprovides the ability to identify which specific partial signature fails\nverification.", + "returnDescription": "0 if the arguments are invalid or the partial signature does not\nverify, 1 otherwise", + "sourceHeader": "secp256k1_musig.h" + }, + { + "name": "secp256k1_musig_partial_sig_agg", + "returnType": "int", + "warnUnusedResult": false, + "deprecated": false, + "parameters": [ + { + "name": "ctx", + "type": "const secp256k1_context*", + "direction": "in", + "nonnull": true, + "description": "pointer to a context object", + "isOptional": false + }, + { + "name": "sig64", + "type": "unsigned char*", + "direction": "out", + "nonnull": true, + "description": "complete (but possibly invalid) Schnorr signature", + "size": 64, + "isOptional": false + }, + { + "name": "session", + "type": "const secp256k1_musig_session*", + "direction": "in", + "nonnull": true, + "description": "pointer to the session that was created with\nmusig_nonce_process", + "size": 133, + "isOptional": false + }, + { + "name": "partial_sigs", + "type": "const secp256k1_musig_partial_sig * const*", + "direction": "in", + "nonnull": true, + "description": "array of pointers to partial signatures to aggregate", + "size": 36, + "isOptional": false + }, + { + "name": "n_sigs", + "type": "size_t", + "nonnull": false, + "description": "number of elements in the partial_sigs array. Must be\ngreater than 0.", + "isOptional": false + } + ], + "description": "Aggregates partial signatures", + "returnDescription": "0 if the arguments are invalid, 1 otherwise (which does NOT mean\nthe resulting signature verifies).", + "sourceHeader": "secp256k1_musig.h" + } + ], + "constants": [ + { + "name": "SECP256K1_FLAGS_TYPE_MASK", + "value": "((1 \u003C\u003C 8) - 1)" + }, + { + "name": "SECP256K1_FLAGS_TYPE_CONTEXT", + "value": "(1 \u003C\u003C 0)", + "numericValue": 1 + }, + { + "name": "SECP256K1_FLAGS_TYPE_COMPRESSION", + "value": "(1 \u003C\u003C 1)", + "numericValue": 2 + }, + { + "name": "SECP256K1_FLAGS_BIT_CONTEXT_VERIFY", + "value": "(1 \u003C\u003C 8)", + "numericValue": 256 + }, + { + "name": "SECP256K1_FLAGS_BIT_CONTEXT_SIGN", + "value": "(1 \u003C\u003C 9)", + "numericValue": 512 + }, + { + "name": "SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY", + "value": "(1 \u003C\u003C 10)", + "numericValue": 1024 + }, + { + "name": "SECP256K1_FLAGS_BIT_COMPRESSION", + "value": "(1 \u003C\u003C 8)", + "numericValue": 256 + }, + { + "name": "SECP256K1_CONTEXT_NONE", + "value": "(SECP256K1_FLAGS_TYPE_CONTEXT)", + "description": "/** Context flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and\n * secp256k1_context_preallocated_create. */" + }, + { + "name": "SECP256K1_CONTEXT_VERIFY", + "value": "(SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)", + "numericValue": 257, + "description": "/** Deprecated context flags. These flags are treated equivalent to SECP256K1_CONTEXT_NONE. */" + }, + { + "name": "SECP256K1_CONTEXT_SIGN", + "value": "(SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)", + "numericValue": 513, + "description": "/** Deprecated context flags. These flags are treated equivalent to SECP256K1_CONTEXT_NONE. */" + }, + { + "name": "SECP256K1_CONTEXT_DECLASSIFY", + "value": "(SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY)", + "numericValue": 1025, + "description": "/** Deprecated context flags. These flags are treated equivalent to SECP256K1_CONTEXT_NONE. */\n#define SECP256K1_CONTEXT_VERIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)\n#define SECP256K1_CONTEXT_SIGN (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)\n\n/* Testing flag. Do not use. */" + }, + { + "name": "SECP256K1_EC_COMPRESSED", + "value": "(SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)", + "numericValue": 258, + "description": "/** Flag to pass to secp256k1_ec_pubkey_serialize. */" + }, + { + "name": "SECP256K1_EC_UNCOMPRESSED", + "value": "(SECP256K1_FLAGS_TYPE_COMPRESSION)", + "description": "/** Flag to pass to secp256k1_ec_pubkey_serialize. */" + }, + { + "name": "SECP256K1_TAG_PUBKEY_EVEN", + "value": "0x02", + "numericValue": 2, + "description": "/** Prefix byte used to tag various encoded curvepoints for specific purposes */" + }, + { + "name": "SECP256K1_TAG_PUBKEY_ODD", + "value": "0x03", + "numericValue": 3, + "description": "/** Prefix byte used to tag various encoded curvepoints for specific purposes */" + }, + { + "name": "SECP256K1_TAG_PUBKEY_UNCOMPRESSED", + "value": "0x04", + "numericValue": 4, + "description": "/** Prefix byte used to tag various encoded curvepoints for specific purposes */" + }, + { + "name": "SECP256K1_TAG_PUBKEY_HYBRID_EVEN", + "value": "0x06", + "numericValue": 6, + "description": "/** Prefix byte used to tag various encoded curvepoints for specific purposes */" + }, + { + "name": "SECP256K1_TAG_PUBKEY_HYBRID_ODD", + "value": "0x07", + "numericValue": 7, + "description": "/** Prefix byte used to tag various encoded curvepoints for specific purposes */" + }, + { + "name": "SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC", + "value": "{ 0xda, 0x6f, 0xb3, 0x8c }" + }, + { + "name": "SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT", + "value": "{\\" + } + ], + "globalPointers": [ + { + "name": "secp256k1_context_static", + "type": "secp256k1_context", + "isConst": true, + "description": "A built-in constant secp256k1 context object with static storage duration, to be\nused in conjunction with secp256k1_selftest.\n\nThis context object offers *only limited functionality* , i.e., it cannot be used\nfor API functions that perform computations involving secret keys, e.g., signing\nand public key generation. If this restriction applies to a specific API function,\nit is mentioned in its documentation. See secp256k1_context_create if you need a\nfull context object that supports all functionality offered by the library.\n\nIt is highly recommended to call secp256k1_selftest before using this context." + }, + { + "name": "secp256k1_context_no_precomp", + "type": "secp256k1_context", + "isConst": true, + "description": "Deprecated alias for secp256k1_context_static." + }, + { + "name": "secp256k1_nonce_function_rfc6979", + "type": "secp256k1_nonce_function", + "isConst": true, + "description": "An implementation of RFC6979 (using HMAC-SHA256) as nonce generation function.\nIf a data pointer is passed, it is assumed to be a pointer to 32 bytes of\nextra entropy." + }, + { + "name": "secp256k1_nonce_function_default", + "type": "secp256k1_nonce_function", + "isConst": true, + "description": "A default safe nonce generation function (currently equal to secp256k1_nonce_function_rfc6979)." + }, + { + "name": "secp256k1_ecdh_hash_function_sha256", + "type": "secp256k1_ecdh_hash_function", + "isConst": true, + "description": "An implementation of SHA256 hash function that applies to compressed public key.\nPopulates the output parameter with 32 bytes." + }, + { + "name": "secp256k1_ecdh_hash_function_default", + "type": "secp256k1_ecdh_hash_function", + "isConst": true, + "description": "A default ECDH hash function (currently equal to secp256k1_ecdh_hash_function_sha256).\nPopulates the output parameter with 32 bytes." + }, + { + "name": "secp256k1_nonce_function_bip340", + "type": "secp256k1_nonce_function_hardened", + "isConst": true, + "description": "An implementation of the nonce generation function as defined in Bitcoin\nImprovement Proposal 340 \u0022Schnorr Signatures for secp256k1\u0022\n(https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).\n\nIf a data pointer is passed, it is assumed to be a pointer to 32 bytes of\nauxiliary random data as defined in BIP-340. If the data pointer is NULL,\nthe nonce derivation procedure follows BIP-340 by setting the auxiliary\nrandom data to zero. The algo argument must be non-NULL, otherwise the\nfunction will fail and return 0. The hash will be tagged with algo.\nTherefore, to create BIP-340 compliant signatures, algo must be set to\n\u0022BIP0340/nonce\u0022 and algolen to 13." + }, + { + "name": "secp256k1_ellswift_xdh_hash_function_prefix", + "type": "secp256k1_ellswift_xdh_hash_function", + "isConst": true, + "description": "An implementation of an secp256k1_ellswift_xdh_hash_function which uses\nSHA256(prefix64 || ell_a64 || ell_b64 || x32), where prefix64 is the 64-byte\narray pointed to by data." + }, + { + "name": "secp256k1_ellswift_xdh_hash_function_bip324", + "type": "secp256k1_ellswift_xdh_hash_function", + "isConst": true, + "description": "An implementation of an secp256k1_ellswift_xdh_hash_function compatible with\nBIP324. It returns H_tag(ell_a64 || ell_b64 || x32), where H_tag is the\nBIP340 tagged hash function with tag \u0022bip324_ellswift_xonly_ecdh\u0022. Equivalent\nto secp256k1_ellswift_xdh_hash_function_prefix with prefix64 set to\nSHA256(\u0022bip324_ellswift_xonly_ecdh\u0022)||SHA256(\u0022bip324_ellswift_xonly_ecdh\u0022).\nThe data argument is ignored." + } + ] +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8bbadc6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,15 @@ +# Documentation + +This directory contains the DocFX configuration for generating API reference documentation. + +## View the Docs + +- **[API Reference](https://zone117x.github.io/Secp256k1.Net/api/Secp256k1Net.Secp256k1.html)** - Full API documentation +- **[Examples](../Secp256k1.Net.Examples/)** - Working code examples + +## Build Locally + +```bash +./docs/build.sh # Build docs +./docs/build.sh --serve # Build and preview at http://localhost:8080 +``` diff --git a/docs/build.sh b/docs/build.sh new file mode 100755 index 0000000..c4886d0 --- /dev/null +++ b/docs/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Check if docfx is installed +if ! command -v docfx &> /dev/null; then + echo "DocFX not found. Installing..." + dotnet tool install -g docfx +fi + +cd "$SCRIPT_DIR" + +# Copy README.md as index.md +cp "$ROOT_DIR/README.md" "$SCRIPT_DIR/index.md" + +# Build the documentation +docfx docfx.json "$@" + +echo "" +echo "Documentation built successfully in docs/_site/" +echo "To preview, run: ./docs/build.sh --serve" diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..564b2f5 --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json", + "metadata": [ + { + "src": [ + { + "files": ["Secp256k1.Net/Secp256k1.Net.csproj"], + "src": ".." + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "properties": { + "TargetFramework": "net8.0" + } + } + ], + "build": { + "content": [ + { + "files": ["api/**.yml", "api/index.md"] + }, + { + "files": ["toc.yml", "index.md"] + } + ], + "resource": [ + { + "files": ["images/**"] + } + ], + "output": "_site", + "template": ["default", "modern"], + "globalMetadata": { + "_appTitle": "Secp256k1.Net", + "_appName": "Secp256k1.Net", + "_appFooter": "Secp256k1.Net - Cross-platform .NET wrapper for bitcoin-core/secp256k1", + "_enableSearch": true, + "_enableNewTab": true + }, + "fileMetadata": {}, + "postProcessors": [], + "keepFileLink": false, + "disableGitFeatures": false + } +} diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..e706e7f --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,6 @@ +- name: Home + href: index.md +- name: API Reference + href: api/ +- name: GitHub + href: https://github.com/zone117x/Secp256k1.Net diff --git a/dotnet-tools.json b/dotnet-tools.json new file mode 100644 index 0000000..45a37a9 --- /dev/null +++ b/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-reportgenerator-globaltool": { + "version": "5.5.1", + "commands": [ + "reportgenerator" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..765346e --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/secp256k1 b/secp256k1 new file mode 160000 index 0000000..7b165c0 --- /dev/null +++ b/secp256k1 @@ -0,0 +1 @@ +Subproject commit 7b165c049da1c26c51248039b57b006f768da728 diff --git a/test/NativeLibTest/NativeLibTest.csproj b/test/NativeLibTest/NativeLibTest.csproj new file mode 100644 index 0000000..46864b4 --- /dev/null +++ b/test/NativeLibTest/NativeLibTest.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + disable + disable + true + + + + + + + diff --git a/test/NativeLibTest/Program.cs b/test/NativeLibTest/Program.cs new file mode 100644 index 0000000..4720b9b --- /dev/null +++ b/test/NativeLibTest/Program.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Secp256k1Net; + +namespace NativeLibTest +{ + class Program + { + static int Main(string[] args) + { + Console.WriteLine("=== Secp256k1.Net Native Library Test ==="); + Console.WriteLine(); + Console.WriteLine($"OS: {RuntimeInformation.OSDescription}"); + Console.WriteLine($"Architecture: {RuntimeInformation.ProcessArchitecture}"); + Console.WriteLine($"Framework: {RuntimeInformation.FrameworkDescription}"); + Console.WriteLine(); + + try + { + // Test 1: Library loading + Console.Write("Test 1: Loading native library... "); + using var secp256k1 = new Secp256k1(); + Console.WriteLine($"OK"); + Console.WriteLine($" Library path: {Secp256k1.LibPath}"); + + // Test 2: Key generation + Console.Write("Test 2: Generating key pair... "); + var privateKey = new byte[32]; + var publicKey = new byte[64]; + + // Use a deterministic private key for testing + for (int i = 0; i < 32; i++) + privateKey[i] = (byte)(i + 1); + + if (!secp256k1.EcSeckeyVerify(privateKey)) + { + Console.WriteLine("FAILED (invalid secret key)"); + return 1; + } + + if (!secp256k1.EcPubkeyCreate(publicKey, privateKey)) + { + Console.WriteLine("FAILED (could not create public key)"); + return 1; + } + Console.WriteLine("OK"); + + // Test 3: Public key serialization + Console.Write("Test 3: Serializing public key... "); + var serializedPubKey = new byte[33]; + nuint pubKeyLen = 33; + if (!secp256k1.EcPubkeySerialize(serializedPubKey, ref pubKeyLen, publicKey, Secp256k1EcFlags.Compressed)) + { + Console.WriteLine("FAILED"); + return 1; + } + Console.WriteLine($"OK ({BitConverter.ToString(serializedPubKey).Substring(0, 20)}...)"); + + // Test 4: Signing + Console.Write("Test 4: Signing message... "); + var messageHash = new byte[32]; + for (int i = 0; i < 32; i++) + messageHash[i] = (byte)(255 - i); + + var signature = new byte[64]; + if (!secp256k1.EcdsaSign(signature, messageHash, privateKey)) + { + Console.WriteLine("FAILED"); + return 1; + } + Console.WriteLine("OK"); + + // Test 5: Verification + Console.Write("Test 5: Verifying signature... "); + if (!secp256k1.EcdsaVerify(signature, messageHash, publicKey)) + { + Console.WriteLine("FAILED"); + return 1; + } + Console.WriteLine("OK"); + + // Test 6: ECDH + Console.Write("Test 6: ECDH key exchange... "); + var privateKey2 = new byte[32]; + var publicKey2 = new byte[64]; + for (int i = 0; i < 32; i++) + privateKey2[i] = (byte)(32 - i); + + if (!secp256k1.EcPubkeyCreate(publicKey2, privateKey2)) + { + Console.WriteLine("FAILED (could not create second public key)"); + return 1; + } + + var sharedSecret1 = new byte[32]; + var sharedSecret2 = new byte[32]; + + if (!secp256k1.Ecdh(sharedSecret1, publicKey2, privateKey)) + { + Console.WriteLine("FAILED (ECDH with key1)"); + return 1; + } + + if (!secp256k1.Ecdh(sharedSecret2, publicKey, privateKey2)) + { + Console.WriteLine("FAILED (ECDH with key2)"); + return 1; + } + + bool secretsMatch = true; + for (int i = 0; i < 32; i++) + { + if (sharedSecret1[i] != sharedSecret2[i]) + { + secretsMatch = false; + break; + } + } + + if (!secretsMatch) + { + Console.WriteLine("FAILED (shared secrets don't match)"); + return 1; + } + Console.WriteLine("OK"); + + // Test 7: DER signature serialization + Console.Write("Test 7: DER signature serialization... "); + var derSig = new byte[72]; + nuint derLen = 72; + if (!secp256k1.EcdsaSignatureSerializeDer(derSig, ref derLen, signature)) + { + Console.WriteLine("FAILED"); + return 1; + } + Console.WriteLine($"OK (length: {derLen})"); + + Console.WriteLine(); + Console.WriteLine("=== All tests passed! ==="); + return 0; + } + catch (Exception ex) + { + Console.WriteLine($"FAILED with exception:"); + Console.WriteLine(ex); + return 1; + } + } + } +} diff --git a/test/NativeLibTest/nuget.config b/test/NativeLibTest/nuget.config new file mode 100644 index 0000000..70c6587 --- /dev/null +++ b/test/NativeLibTest/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/NativeLibTest/test-linux-aot.sh b/test/NativeLibTest/test-linux-aot.sh new file mode 100755 index 0000000..ebbf75a --- /dev/null +++ b/test/NativeLibTest/test-linux-aot.sh @@ -0,0 +1,262 @@ +#!/bin/bash +# Test Native AOT builds on Linux via Docker +# Verifies that AOT compilation works and produces working native executables +# Usage: ./test-linux-aot.sh [rid] +# RIDs: all, linux-x64, linux-arm64, linux-musl-x64, linux-musl-arm64 +# +# Note: AOT compilation under QEMU emulation (e.g., linux-musl-x64 on ARM64 host) +# may crash due to ILC compiler issues with emulated memory. Tests that require +# cross-architecture emulation may be skipped. +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$SCRIPT_DIR" + +RID="${1:-all}" + +# Detect host architecture to skip cross-arch AOT (which crashes under QEMU) +HOST_ARCH=$(uname -m) +is_native_arch() { + local rid="$1" + case "$HOST_ARCH" in + x86_64|amd64) + case "$rid" in + linux-x64|linux-musl-x64) return 0 ;; + *) return 1 ;; + esac + ;; + arm64|aarch64) + case "$rid" in + linux-arm64|linux-musl-arm64) return 0 ;; + *) return 1 ;; + esac + ;; + esac + return 1 +} + +# Get Docker SDK image for AOT compilation (compatible with bash 3.x) +get_sdk_image() { + case "$1" in + linux-x64|linux-arm64) echo "mcr.microsoft.com/dotnet/sdk:10.0" ;; + linux-musl-x64|linux-musl-arm64) echo "mcr.microsoft.com/dotnet/sdk:10.0-alpine" ;; + esac +} + +# Get Docker runtime image for running the AOT binary (compatible with bash 3.x) +get_runtime_image() { + case "$1" in + linux-x64|linux-arm64) echo "mcr.microsoft.com/dotnet/runtime-deps:10.0" ;; + linux-musl-x64|linux-musl-arm64) echo "mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine" ;; + esac +} + +# Get Docker platform for a RID (compatible with bash 3.x) +get_docker_platform() { + case "$1" in + linux-x64|linux-musl-x64) echo "linux/amd64" ;; + linux-arm64|linux-musl-arm64) echo "linux/arm64" ;; + esac +} + +RIDS_TO_TEST="" +if [ "$RID" = "all" ]; then + RIDS_TO_TEST="linux-x64 linux-arm64 linux-musl-x64 linux-musl-arm64" +else + RIDS_TO_TEST="$RID" +fi + +build_package() { + echo "==> Building Secp256k1.Net NuGet package..." + dotnet pack "$REPO_ROOT/Secp256k1.Net" -c Release -o "$REPO_ROOT/pkg" -p:Version=0.0.1-localtest.1 +} + +publish_aot() { + local rid="$1" + local sdk_image + local docker_platform + local output_dir="$SCRIPT_DIR/publish/aot-$rid" + + sdk_image=$(get_sdk_image "$rid") + docker_platform=$(get_docker_platform "$rid") + + echo "==> Publishing Native AOT build for $rid..." + rm -rf "$output_dir" + mkdir -p "$output_dir" + + # Clear caches and build artifacts + dotnet nuget locals http-cache --clear > /dev/null 2>&1 || true + rm -rf obj bin + + # AOT compilation must happen on the target platform, so we use Docker + # Mount the repo and local package source, then publish inside the container + docker run --rm --platform "$docker_platform" \ + -v "$REPO_ROOT:/repo:ro" \ + -v "$REPO_ROOT/pkg:/packages:ro" \ + -v "$output_dir:/output" \ + -w /build \ + "$sdk_image" \ + sh -c " + # Install clang (required for AOT on Linux) + if command -v apk > /dev/null 2>&1; then + apk add --no-cache clang build-base zlib-dev + else + apt-get update && apt-get install -y clang zlib1g-dev + fi + + # Copy project to writable location + cp -r /repo/test/NativeLibTest/* /build/ + + # Create nuget.config pointing to local packages + cat > /build/nuget.config << 'NUGETEOF' + + + + + + + + +NUGETEOF + + # Publish with AOT + dotnet publish -c Release -r $rid -p:PublishAot=true -o /output + " -- "$rid" +} + +verify_aot_output() { + local rid="$1" + local publish_dir="$SCRIPT_DIR/publish/aot-$rid" + + echo "--- Verifying $rid AOT output ---" + + # Check for runtimes folder (should not exist in AOT publish) + if [ -d "$publish_dir/runtimes" ]; then + echo "FAILED: Found 'runtimes' directory in AOT publish" + ls -la "$publish_dir/runtimes/" 2>/dev/null || true + return 1 + fi + + # Count native library files (.so) + local native_count + native_count=$(find "$publish_dir" -maxdepth 1 -type f -name "*.so" | wc -l | tr -d ' ') + + if [ "$native_count" -ne 1 ]; then + echo "FAILED: Expected 1 native library (.so), found $native_count" + echo "Files in publish directory:" + ls -la "$publish_dir" + return 1 + fi + + if [ ! -f "$publish_dir/libsecp256k1.so" ]; then + echo "FAILED: Expected libsecp256k1.so not found" + echo "Files in publish directory:" + ls -la "$publish_dir" + return 1 + fi + + # Verify native AOT executable exists + if [ ! -f "$publish_dir/NativeLibTest" ]; then + echo "FAILED: Native AOT executable not found" + echo "Files in publish directory:" + ls -la "$publish_dir" + return 1 + fi + + echo "OK: Found libsecp256k1.so and NativeLibTest executable" + return 0 +} + +run_aot_test() { + local name="$1" + local rid="$2" + local runtime_image + local docker_platform + local publish_dir="$SCRIPT_DIR/publish/aot-$rid" + + runtime_image=$(get_runtime_image "$rid") + docker_platform=$(get_docker_platform "$rid") + + echo "--- Testing: $name (AOT, RID: $rid) ---" + + # Run the native executable directly (no dotnet needed) + if docker run --rm --platform "$docker_platform" \ + -v "$publish_dir:/app:ro" \ + "$runtime_image" \ + /app/NativeLibTest; then + echo "--- $name (AOT, $rid): PASSED ---" + echo + return 0 + else + echo "--- $name (AOT, $rid): FAILED ---" + echo + return 1 + fi +} + +test_aot_rid() { + local rid="$1" + local failed=0 + + publish_aot "$rid" + + # Verify AOT output + if ! verify_aot_output "$rid"; then + return 1 + fi + + # Run functional test + case "$rid" in + linux-x64) + run_aot_test "Linux x64 (glibc)" "$rid" || failed=1 + ;; + linux-arm64) + run_aot_test "Linux ARM64 (glibc)" "$rid" || failed=1 + ;; + linux-musl-x64) + run_aot_test "Linux x64 (musl/Alpine)" "$rid" || failed=1 + ;; + linux-musl-arm64) + run_aot_test "Linux ARM64 (musl/Alpine)" "$rid" || failed=1 + ;; + esac + + return $failed +} + +# Main +failed=0 + +echo "========================================" +echo "Testing Native AOT Linux builds" +echo "========================================" +echo + +build_package + +skipped=0 +for rid in $RIDS_TO_TEST; do + if ! is_native_arch "$rid"; then + echo "==> Skipping $rid (AOT cross-compilation crashes under QEMU emulation on $HOST_ARCH)" + echo + skipped=$((skipped + 1)) + continue + fi + test_aot_rid "$rid" || failed=1 +done + +if [ $failed -eq 0 ]; then + echo "========================================" + if [ $skipped -gt 0 ]; then + echo "Native AOT Linux tests passed! ($skipped skipped due to cross-arch)" + else + echo "All Native AOT Linux tests passed!" + fi + echo "========================================" +else + echo "========================================" + echo "Some tests failed!" + echo "========================================" + exit 1 +fi diff --git a/test/NativeLibTest/test-linux-portable.sh b/test/NativeLibTest/test-linux-portable.sh new file mode 100755 index 0000000..021ec1f --- /dev/null +++ b/test/NativeLibTest/test-linux-portable.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# Test portable (cross-platform) builds on Linux via Docker +# Usage: ./test-linux-portable.sh [platform] +# Platforms: all, linux-x64, linux-arm64, linux-musl-x64, linux-musl-arm64 +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$SCRIPT_DIR" + +PLATFORM="${1:-all}" + +# Get Docker image for a platform (compatible with bash 3.x) +get_docker_image() { + case "$1" in + linux-x64|linux-arm64) echo "mcr.microsoft.com/dotnet/runtime:10.0" ;; + linux-musl-x64|linux-musl-arm64) echo "mcr.microsoft.com/dotnet/runtime:10.0-alpine" ;; + esac +} + +# Get Docker platform for a RID (compatible with bash 3.x) +get_docker_platform() { + case "$1" in + linux-x64|linux-musl-x64) echo "linux/amd64" ;; + linux-arm64|linux-musl-arm64) echo "linux/arm64" ;; + esac +} + +PLATFORMS_TO_TEST="" +if [ "$PLATFORM" = "all" ]; then + PLATFORMS_TO_TEST="linux-x64 linux-arm64 linux-musl-x64 linux-musl-arm64" +else + PLATFORMS_TO_TEST="$PLATFORM" +fi + +build_package() { + echo "==> Building Secp256k1.Net NuGet package..." + dotnet pack "$REPO_ROOT/Secp256k1.Net" -c Release -o "$REPO_ROOT/pkg" -p:Version=0.0.1-localtest.1 +} + +publish_portable() { + local output_dir="$SCRIPT_DIR/publish/portable" + + echo "==> Publishing portable build..." + rm -rf "$output_dir" + + dotnet nuget locals http-cache --clear > /dev/null 2>&1 || true + rm -rf obj bin + dotnet publish -c Release -o "$output_dir" +} + +run_docker_test() { + local name="$1" + local rid="$2" + local image + local docker_platform + local publish_dir="$SCRIPT_DIR/publish/portable" + + image=$(get_docker_image "$rid") + docker_platform=$(get_docker_platform "$rid") + + echo "--- Testing: $name ---" + + if docker run --rm --platform "$docker_platform" \ + -v "$publish_dir:/app:ro" \ + "$image" \ + dotnet /app/NativeLibTest.dll; then + echo "--- $name: PASSED ---" + echo + return 0 + else + echo "--- $name: FAILED ---" + echo + return 1 + fi +} + +# Main +failed=0 + +echo "========================================" +echo "Testing portable Linux builds" +echo "========================================" +echo + +build_package +publish_portable + +for rid in $PLATFORMS_TO_TEST; do + case "$rid" in + linux-x64) + run_docker_test "Linux x64 (glibc)" "$rid" || failed=1 + ;; + linux-arm64) + run_docker_test "Linux ARM64 (glibc)" "$rid" || failed=1 + ;; + linux-musl-x64) + run_docker_test "Linux x64 (musl/Alpine)" "$rid" || failed=1 + ;; + linux-musl-arm64) + run_docker_test "Linux ARM64 (musl/Alpine)" "$rid" || failed=1 + ;; + esac +done + +if [ $failed -eq 0 ]; then + echo "========================================" + echo "All portable Linux tests passed!" + echo "========================================" +else + echo "========================================" + echo "Some tests failed!" + echo "========================================" + exit 1 +fi diff --git a/test/NativeLibTest/test-linux-rid.sh b/test/NativeLibTest/test-linux-rid.sh new file mode 100755 index 0000000..31ed45f --- /dev/null +++ b/test/NativeLibTest/test-linux-rid.sh @@ -0,0 +1,178 @@ +#!/bin/bash +# Test RID-specific builds on Linux via Docker +# Verifies that only the correct native library is included +# Usage: ./test-linux-rid.sh [rid] +# RIDs: all, linux-x64, linux-arm64, linux-musl-x64, linux-musl-arm64 +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$SCRIPT_DIR" + +RID="${1:-all}" + +# Get Docker image for a RID (compatible with bash 3.x) +get_docker_image() { + case "$1" in + linux-x64|linux-arm64) echo "mcr.microsoft.com/dotnet/runtime:10.0" ;; + linux-musl-x64|linux-musl-arm64) echo "mcr.microsoft.com/dotnet/runtime:10.0-alpine" ;; + esac +} + +# Get Docker platform for a RID (compatible with bash 3.x) +get_docker_platform() { + case "$1" in + linux-x64|linux-musl-x64) echo "linux/amd64" ;; + linux-arm64|linux-musl-arm64) echo "linux/arm64" ;; + esac +} + +# Get expected native library name for a RID (compatible with bash 3.x) +get_native_lib() { + # All Linux RIDs use the same library name + echo "libsecp256k1.so" +} + +RIDS_TO_TEST="" +if [ "$RID" = "all" ]; then + RIDS_TO_TEST="linux-x64 linux-arm64 linux-musl-x64 linux-musl-arm64" +else + RIDS_TO_TEST="$RID" +fi + +build_package() { + echo "==> Building Secp256k1.Net NuGet package..." + dotnet pack "$REPO_ROOT/Secp256k1.Net" -c Release -o "$REPO_ROOT/pkg" -p:Version=0.0.1-localtest.1 +} + +publish_rid_specific() { + local rid="$1" + local output_dir="$SCRIPT_DIR/publish/rid-$rid" + + echo "==> Publishing RID-specific build for $rid..." + rm -rf "$output_dir" + + dotnet nuget locals http-cache --clear > /dev/null 2>&1 || true + rm -rf obj bin + dotnet publish -c Release -r "$rid" --self-contained false -o "$output_dir" +} + +verify_single_native() { + local rid="$1" + local publish_dir="$SCRIPT_DIR/publish/rid-$rid" + local expected_lib + expected_lib=$(get_native_lib "$rid") + + echo "--- Verifying $rid contains only single native library ---" + + # Check for runtimes folder (should not exist in RID-specific publish) + if [ -d "$publish_dir/runtimes" ]; then + echo "FAILED: Found 'runtimes' directory in RID-specific publish" + ls -la "$publish_dir/runtimes/" 2>/dev/null || true + return 1 + fi + + # Count native library files + local native_count + native_count=$(find "$publish_dir" -maxdepth 1 -type f \( -name "*.so" -o -name "*.dylib" -o -name "secp256k1.dll" \) | wc -l | tr -d ' ') + + # Should have exactly one native library + if [ "$native_count" -ne 1 ]; then + echo "FAILED: Expected 1 native library, found $native_count" + echo "Files in publish directory:" + ls -la "$publish_dir" + return 1 + fi + + # Verify it's the correct library + if [ ! -f "$publish_dir/$expected_lib" ]; then + echo "FAILED: Expected $expected_lib not found" + echo "Files in publish directory:" + ls -la "$publish_dir" + return 1 + fi + + echo "OK: Found exactly $expected_lib (no other natives)" + return 0 +} + +run_docker_test() { + local name="$1" + local rid="$2" + local image + local docker_platform + local publish_dir="$SCRIPT_DIR/publish/rid-$rid" + + image=$(get_docker_image "$rid") + docker_platform=$(get_docker_platform "$rid") + + echo "--- Testing: $name (RID: $rid) ---" + + if docker run --rm --platform "$docker_platform" \ + -v "$publish_dir:/app:ro" \ + "$image" \ + dotnet /app/NativeLibTest.dll; then + echo "--- $name ($rid): PASSED ---" + echo + return 0 + else + echo "--- $name ($rid): FAILED ---" + echo + return 1 + fi +} + +test_rid() { + local rid="$1" + local failed=0 + + publish_rid_specific "$rid" + + # Verify only single native library is present + if ! verify_single_native "$rid"; then + return 1 + fi + + # Run functional test + case "$rid" in + linux-x64) + run_docker_test "Linux x64 (glibc)" "$rid" || failed=1 + ;; + linux-arm64) + run_docker_test "Linux ARM64 (glibc)" "$rid" || failed=1 + ;; + linux-musl-x64) + run_docker_test "Linux x64 (musl/Alpine)" "$rid" || failed=1 + ;; + linux-musl-arm64) + run_docker_test "Linux ARM64 (musl/Alpine)" "$rid" || failed=1 + ;; + esac + + return $failed +} + +# Main +failed=0 + +echo "========================================" +echo "Testing RID-specific Linux builds" +echo "========================================" +echo + +build_package + +for rid in $RIDS_TO_TEST; do + test_rid "$rid" || failed=1 +done + +if [ $failed -eq 0 ]; then + echo "========================================" + echo "All RID-specific Linux tests passed!" + echo "========================================" +else + echo "========================================" + echo "Some tests failed!" + echo "========================================" + exit 1 +fi diff --git a/test/NativeLibTest/test-macos.sh b/test/NativeLibTest/test-macos.sh new file mode 100755 index 0000000..c4c2288 --- /dev/null +++ b/test/NativeLibTest/test-macos.sh @@ -0,0 +1,278 @@ +#!/bin/bash +# Test builds on macOS (run natively on macOS CI runner or local machine) +# Usage: ./test-macos.sh [portable|rid|singlefile|aot|all] +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$SCRIPT_DIR" + +BUILD_MODE="${1:-all}" # portable, rid, or all + +# Detect current macOS architecture +ARCH=$(uname -m) +if [ "$ARCH" == "arm64" ]; then + RID="osx-arm64" + NATIVE_LIB="libsecp256k1.dylib" +else + RID="osx-x64" + NATIVE_LIB="libsecp256k1.dylib" +fi + +echo "Detected macOS architecture: $ARCH (RID: $RID)" + +build_package() { + echo "==> Building Secp256k1.Net NuGet package..." + # Clear any cached version of the local test package from global cache + rm -rf ~/.nuget/packages/secp256k1.net/0.0.1-localtest.1 + dotnet pack "$REPO_ROOT/Secp256k1.Net" -c Release -o "$REPO_ROOT/pkg" -p:Version=0.0.1-localtest.1 +} + +test_portable() { + local output_dir="$SCRIPT_DIR/publish/portable-macos" + + echo "--- Testing portable build ---" + rm -rf "$output_dir" + + dotnet nuget locals http-cache --clear > /dev/null 2>&1 || true + rm -rf obj bin + dotnet publish -c Release -o "$output_dir" + + # Verify runtimes folder exists with all platforms + if [ ! -d "$output_dir/runtimes" ]; then + echo "FAILED: runtimes folder not found in portable build" + return 1 + fi + + local runtime_count + runtime_count=$(find "$output_dir/runtimes" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ') + echo "Found $runtime_count runtime folders" + + # Run the test + echo "Running test..." + if dotnet "$output_dir/NativeLibTest.dll"; then + echo "--- Portable: PASSED ---" + echo + return 0 + else + echo "--- Portable: FAILED ---" + echo + return 1 + fi +} + +test_rid_specific() { + local output_dir="$SCRIPT_DIR/publish/rid-$RID" + + echo "--- Testing RID-specific build for $RID ---" + rm -rf "$output_dir" + + dotnet nuget locals http-cache --clear > /dev/null 2>&1 || true + rm -rf obj bin + dotnet publish -c Release -r "$RID" --self-contained false -o "$output_dir" + + # Verify only single native library is present + echo "Verifying single native library..." + + if [ -d "$output_dir/runtimes" ]; then + echo "FAILED: Found 'runtimes' directory in RID-specific publish" + ls -la "$output_dir/runtimes/" 2>/dev/null || true + return 1 + fi + + local native_count + native_count=$(find "$output_dir" -maxdepth 1 -type f \( -name "*.dylib" -o -name "*.so" -o -name "secp256k1.dll" \) | wc -l | tr -d ' ') + + if [ "$native_count" -ne 1 ]; then + echo "FAILED: Expected 1 native library, found $native_count" + echo "Files in publish directory:" + ls -la "$output_dir" + return 1 + fi + + if [ ! -f "$output_dir/$NATIVE_LIB" ]; then + echo "FAILED: Expected $NATIVE_LIB not found" + echo "Files in publish directory:" + ls -la "$output_dir" + return 1 + fi + + echo "OK: Found exactly $NATIVE_LIB" + + # Run the test + echo "Running test..." + if dotnet "$output_dir/NativeLibTest.dll"; then + echo "--- RID-specific $RID: PASSED ---" + echo + return 0 + else + echo "--- RID-specific $RID: FAILED ---" + echo + return 1 + fi +} + +test_singlefile() { + local output_dir="$SCRIPT_DIR/publish/singlefile-$RID" + + echo "--- Testing single-file build for $RID ---" + rm -rf "$output_dir" + + dotnet nuget locals http-cache --clear > /dev/null 2>&1 || true + rm -rf obj bin + dotnet publish -c Release -r "$RID" --self-contained -p:PublishSingleFile=true -o "$output_dir" + + # Verify only single native library is present (alongside the single-file executable) + echo "Verifying single native library..." + + if [ -d "$output_dir/runtimes" ]; then + echo "FAILED: Found 'runtimes' directory in single-file publish" + ls -la "$output_dir/runtimes/" 2>/dev/null || true + return 1 + fi + + local native_count + native_count=$(find "$output_dir" -maxdepth 1 -type f -name "*.dylib" | wc -l | tr -d ' ') + + if [ "$native_count" -ne 1 ]; then + echo "FAILED: Expected 1 native library (.dylib), found $native_count" + echo "Files in publish directory:" + ls -la "$output_dir" + return 1 + fi + + if [ ! -f "$output_dir/$NATIVE_LIB" ]; then + echo "FAILED: Expected $NATIVE_LIB not found" + echo "Files in publish directory:" + ls -la "$output_dir" + return 1 + fi + + # Verify single-file executable exists + if [ ! -f "$output_dir/NativeLibTest" ]; then + echo "FAILED: Single-file executable not found" + echo "Files in publish directory:" + ls -la "$output_dir" + return 1 + fi + + echo "OK: Found $NATIVE_LIB and NativeLibTest executable" + + # Run the single-file executable directly (not via dotnet) + echo "Running single-file test..." + if "$output_dir/NativeLibTest"; then + echo "--- Single-file $RID: PASSED ---" + echo + return 0 + else + echo "--- Single-file $RID: FAILED ---" + echo + return 1 + fi +} + +test_aot() { + local output_dir="$SCRIPT_DIR/publish/aot-$RID" + + echo "--- Testing Native AOT build for $RID ---" + rm -rf "$output_dir" + + dotnet nuget locals http-cache --clear > /dev/null 2>&1 || true + rm -rf obj bin + dotnet publish -c Release -r "$RID" -p:PublishAot=true -o "$output_dir" + + # Verify only single native library is present (alongside the AOT executable) + echo "Verifying single native library..." + + if [ -d "$output_dir/runtimes" ]; then + echo "FAILED: Found 'runtimes' directory in AOT publish" + ls -la "$output_dir/runtimes/" 2>/dev/null || true + return 1 + fi + + local native_count + native_count=$(find "$output_dir" -maxdepth 1 -type f -name "*.dylib" | wc -l | tr -d ' ') + + if [ "$native_count" -ne 1 ]; then + echo "FAILED: Expected 1 native library (.dylib), found $native_count" + echo "Files in publish directory:" + ls -la "$output_dir" + return 1 + fi + + if [ ! -f "$output_dir/$NATIVE_LIB" ]; then + echo "FAILED: Expected $NATIVE_LIB not found" + echo "Files in publish directory:" + ls -la "$output_dir" + return 1 + fi + + # Verify native AOT executable exists + if [ ! -f "$output_dir/NativeLibTest" ]; then + echo "FAILED: Native AOT executable not found" + echo "Files in publish directory:" + ls -la "$output_dir" + return 1 + fi + + echo "OK: Found $NATIVE_LIB and NativeLibTest executable" + + # Run the native AOT executable directly (not via dotnet) + echo "Running native AOT test..." + if "$output_dir/NativeLibTest"; then + echo "--- Native AOT $RID: PASSED ---" + echo + return 0 + else + echo "--- Native AOT $RID: FAILED ---" + echo + return 1 + fi +} + +# Main +failed=0 + +echo "========================================" +echo "Testing on macOS" +echo "========================================" +echo + +build_package + +case "$BUILD_MODE" in + portable) + test_portable || failed=1 + ;; + rid) + test_rid_specific || failed=1 + ;; + singlefile) + test_singlefile || failed=1 + ;; + aot) + test_aot || failed=1 + ;; + all) + test_portable || failed=1 + test_rid_specific || failed=1 + test_singlefile || failed=1 + test_aot || failed=1 + ;; + *) + echo "Unknown build mode: $BUILD_MODE" + echo "Usage: $0 [portable|rid|singlefile|aot|all]" + exit 1 + ;; +esac + +if [ $failed -eq 0 ]; then + echo "========================================" + echo "All macOS tests passed!" + echo "========================================" +else + echo "========================================" + echo "Some tests failed!" + echo "========================================" + exit 1 +fi diff --git a/test/NativeLibTest/test-windows.ps1 b/test/NativeLibTest/test-windows.ps1 new file mode 100644 index 0000000..7167a57 --- /dev/null +++ b/test/NativeLibTest/test-windows.ps1 @@ -0,0 +1,168 @@ +# Test builds on Windows (run on Windows CI runner) +# Usage: .\test-windows.ps1 [-BuildMode portable|rid|all] +param( + [ValidateSet("portable", "rid", "all")] + [string]$BuildMode = "all" +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RepoRoot = (Resolve-Path "$ScriptDir/../..").Path +Set-Location $ScriptDir + +# Detect Windows architecture +$Arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture +switch ($Arch) { + "Arm64" { + $RID = "win-arm64" + } + "X64" { + $RID = "win-x64" + } + "X86" { + $RID = "win-x86" + } + default { + $RID = "win-x64" + } +} +$NativeLib = "secp256k1.dll" + +Write-Host "Detected Windows architecture: $Arch (RID: $RID)" + +function Build-Package { + Write-Host "==> Building Secp256k1.Net NuGet package..." + dotnet pack "$RepoRoot/Secp256k1.Net" -c Release -o "$RepoRoot/pkg" -p:Version=0.0.1-localtest.1 + if ($LASTEXITCODE -ne 0) { throw "Package build failed" } +} + +function Test-Portable { + $OutputDir = "$ScriptDir/publish/portable-windows" + + Write-Host "--- Testing portable build ---" + if (Test-Path $OutputDir) { Remove-Item -Recurse -Force $OutputDir } + + dotnet nuget locals http-cache --clear 2>$null + if (Test-Path obj) { Remove-Item -Recurse -Force obj } + if (Test-Path bin) { Remove-Item -Recurse -Force bin } + dotnet publish -c Release -o $OutputDir + if ($LASTEXITCODE -ne 0) { throw "Publish failed" } + + # Verify runtimes folder exists + if (-not (Test-Path "$OutputDir/runtimes")) { + Write-Host "FAILED: runtimes folder not found in portable build" + return $false + } + + $RuntimeCount = (Get-ChildItem "$OutputDir/runtimes" -Directory).Count + Write-Host "Found $RuntimeCount runtime folders" + + # Run the test + Write-Host "Running test..." + dotnet "$OutputDir/NativeLibTest.dll" + if ($LASTEXITCODE -eq 0) { + Write-Host "--- Portable: PASSED ---" + Write-Host "" + return $true + } else { + Write-Host "--- Portable: FAILED ---" + Write-Host "" + return $false + } +} + +function Test-RidSpecific { + $OutputDir = "$ScriptDir/publish/rid-$RID" + + Write-Host "--- Testing RID-specific build for $RID ---" + if (Test-Path $OutputDir) { Remove-Item -Recurse -Force $OutputDir } + + dotnet nuget locals http-cache --clear 2>$null + if (Test-Path obj) { Remove-Item -Recurse -Force obj } + if (Test-Path bin) { Remove-Item -Recurse -Force bin } + dotnet publish -c Release -r $RID --self-contained false -o $OutputDir + if ($LASTEXITCODE -ne 0) { throw "Publish failed" } + + # Verify only single native library is present + Write-Host "Verifying single native library..." + + # Check for runtimes folder (should not exist) + if (Test-Path "$OutputDir/runtimes") { + Write-Host "FAILED: Found 'runtimes' directory in RID-specific publish" + Get-ChildItem "$OutputDir/runtimes" -Recurse + return $false + } + + # Count native library files (excluding managed DLLs) + $NativeFiles = Get-ChildItem $OutputDir -File | Where-Object { + ($_.Extension -eq ".dll" -or $_.Extension -eq ".so" -or $_.Extension -eq ".dylib") -and + $_.Name -ne "NativeLibTest.dll" -and + $_.Name -ne "Secp256k1.Net.dll" + } + $NativeCount = ($NativeFiles | Measure-Object).Count + + if ($NativeCount -ne 1) { + Write-Host "FAILED: Expected 1 native library, found $NativeCount" + Write-Host "Files in publish directory:" + Get-ChildItem $OutputDir + return $false + } + + if (-not (Test-Path "$OutputDir/$NativeLib")) { + Write-Host "FAILED: Expected $NativeLib not found" + Write-Host "Files in publish directory:" + Get-ChildItem $OutputDir + return $false + } + + Write-Host "OK: Found exactly $NativeLib" + + # Run the test + Write-Host "Running test..." + dotnet "$OutputDir/NativeLibTest.dll" + if ($LASTEXITCODE -eq 0) { + Write-Host "--- RID-specific ${RID}: PASSED ---" + Write-Host "" + return $true + } else { + Write-Host "--- RID-specific ${RID}: FAILED ---" + Write-Host "" + return $false + } +} + +# Main +$Failed = $false + +Write-Host "========================================" +Write-Host "Testing on Windows" +Write-Host "========================================" +Write-Host "" + +Build-Package + +switch ($BuildMode) { + "portable" { + if (-not (Test-Portable)) { $Failed = $true } + } + "rid" { + if (-not (Test-RidSpecific)) { $Failed = $true } + } + "all" { + if (-not (Test-Portable)) { $Failed = $true } + if (-not (Test-RidSpecific)) { $Failed = $true } + } +} + +if (-not $Failed) { + Write-Host "========================================" + Write-Host "All Windows tests passed!" + Write-Host "========================================" + exit 0 +} else { + Write-Host "========================================" + Write-Host "Some tests failed!" + Write-Host "========================================" + exit 1 +} diff --git a/test/NativeLibTestLegacy/NativeLibTestLegacy.csproj b/test/NativeLibTestLegacy/NativeLibTestLegacy.csproj new file mode 100644 index 0000000..58fa082 --- /dev/null +++ b/test/NativeLibTestLegacy/NativeLibTestLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + + net462 + 7.3 + true + + + + + + + diff --git a/test/NativeLibTestLegacy/Program.cs b/test/NativeLibTestLegacy/Program.cs new file mode 100644 index 0000000..9b9caa2 --- /dev/null +++ b/test/NativeLibTestLegacy/Program.cs @@ -0,0 +1,153 @@ +using System; +using System.Runtime.InteropServices; +using Secp256k1Net; + +namespace NativeLibTestLegacy +{ + class Program + { + static int Main(string[] args) + { + Console.WriteLine("=== Secp256k1.Net Native Library Test (Legacy .NET Framework) ==="); + Console.WriteLine(); + Console.WriteLine("OS: " + Environment.OSVersion); + Console.WriteLine("Architecture: " + (Environment.Is64BitProcess ? "x64" : "x86")); + Console.WriteLine("Framework: " + RuntimeInformation.FrameworkDescription); + Console.WriteLine("Runtime: " + (Type.GetType("Mono.Runtime") != null ? "Mono" : ".NET Framework")); + Console.WriteLine(); + + try + { + // Test 1: Library loading + Console.Write("Test 1: Loading native library... "); + using (var secp256k1 = new Secp256k1()) + { + Console.WriteLine("OK"); + Console.WriteLine(" Library path: " + Secp256k1.LibPath); + + // Test 2: Key generation + Console.Write("Test 2: Generating key pair... "); + var privateKey = new byte[32]; + var publicKey = new byte[64]; + + // Use a deterministic private key for testing + for (int i = 0; i < 32; i++) + privateKey[i] = (byte)(i + 1); + + if (!secp256k1.EcSeckeyVerify(privateKey)) + { + Console.WriteLine("FAILED (invalid secret key)"); + return 1; + } + + if (!secp256k1.EcPubkeyCreate(publicKey, privateKey)) + { + Console.WriteLine("FAILED (could not create public key)"); + return 1; + } + Console.WriteLine("OK"); + + // Test 3: Public key serialization + Console.Write("Test 3: Serializing public key... "); + var serializedPubKey = new byte[33]; + UIntPtr pubKeyLen = (UIntPtr)33; + if (!secp256k1.EcPubkeySerialize(serializedPubKey, ref pubKeyLen, publicKey, Secp256k1EcFlags.Compressed)) + { + Console.WriteLine("FAILED"); + return 1; + } + Console.WriteLine("OK (" + BitConverter.ToString(serializedPubKey).Substring(0, 20) + "...)"); + + // Test 4: Signing + Console.Write("Test 4: Signing message... "); + var messageHash = new byte[32]; + for (int i = 0; i < 32; i++) + messageHash[i] = (byte)(255 - i); + + var signature = new byte[64]; + if (!secp256k1.EcdsaSign(signature, messageHash, privateKey)) + { + Console.WriteLine("FAILED"); + return 1; + } + Console.WriteLine("OK"); + + // Test 5: Verification + Console.Write("Test 5: Verifying signature... "); + if (!secp256k1.EcdsaVerify(signature, messageHash, publicKey)) + { + Console.WriteLine("FAILED"); + return 1; + } + Console.WriteLine("OK"); + + // Test 6: ECDH + Console.Write("Test 6: ECDH key exchange... "); + var privateKey2 = new byte[32]; + var publicKey2 = new byte[64]; + for (int i = 0; i < 32; i++) + privateKey2[i] = (byte)(32 - i); + + if (!secp256k1.EcPubkeyCreate(publicKey2, privateKey2)) + { + Console.WriteLine("FAILED (could not create second public key)"); + return 1; + } + + var sharedSecret1 = new byte[32]; + var sharedSecret2 = new byte[32]; + + if (!secp256k1.Ecdh(sharedSecret1, publicKey2, privateKey)) + { + Console.WriteLine("FAILED (ECDH with key1)"); + return 1; + } + + if (!secp256k1.Ecdh(sharedSecret2, publicKey, privateKey2)) + { + Console.WriteLine("FAILED (ECDH with key2)"); + return 1; + } + + bool secretsMatch = true; + for (int i = 0; i < 32; i++) + { + if (sharedSecret1[i] != sharedSecret2[i]) + { + secretsMatch = false; + break; + } + } + + if (!secretsMatch) + { + Console.WriteLine("FAILED (shared secrets don't match)"); + return 1; + } + Console.WriteLine("OK"); + + // Test 7: DER signature serialization + Console.Write("Test 7: DER signature serialization... "); + var derSig = new byte[72]; + UIntPtr derLen = (UIntPtr)72; + if (!secp256k1.EcdsaSignatureSerializeDer(derSig, ref derLen, signature)) + { + Console.WriteLine("FAILED"); + return 1; + } + Console.WriteLine("OK (length: " + derLen + ")"); + } + + Console.WriteLine(); + Console.WriteLine("=== All tests passed! ==="); + return 0; + } + catch (Exception ex) + { + Console.WriteLine("FAILED with exception:"); + Console.WriteLine(ex); + return 1; + } + } + } +} diff --git a/test/NativeLibTestLegacy/nuget.config b/test/NativeLibTestLegacy/nuget.config new file mode 100644 index 0000000..70c6587 --- /dev/null +++ b/test/NativeLibTestLegacy/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/NativeLibTestLegacy/test-mono.sh b/test/NativeLibTestLegacy/test-mono.sh new file mode 100755 index 0000000..756a86f --- /dev/null +++ b/test/NativeLibTestLegacy/test-mono.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# Test legacy .NET Framework build using Mono on Linux/macOS +# Usage: ./test-mono.sh +# +# Prerequisites: +# - Mono must be installed (https://www.mono-project.com/download/stable/) +# - On macOS: brew install mono +# - On Linux: apt-get install mono-complete (or equivalent) +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$SCRIPT_DIR" + +# Check for Mono +if ! command -v mono &> /dev/null; then + echo "ERROR: Mono is not installed or not in PATH" + echo "Install Mono from: https://www.mono-project.com/download/stable/" + exit 1 +fi + +echo "========================================" +echo "Testing legacy .NET Framework with Mono" +echo "========================================" +echo +echo "Mono version: $(mono --version | head -1)" +echo + +build_package() { + echo "==> Building Secp256k1.Net NuGet package..." + dotnet pack "$REPO_ROOT/Secp256k1.Net" -c Release -o "$REPO_ROOT/pkg" -p:Version=0.0.1-localtest.1 +} + +build_legacy() { + echo "==> Building legacy .NET Framework project..." + dotnet nuget locals http-cache --clear > /dev/null 2>&1 || true + rm -rf obj bin + dotnet build -c Release +} + +run_test() { + local output_dir="$SCRIPT_DIR/bin/Release/net462" + + echo "==> Running test with Mono..." + echo + + # Run with Mono - the library probes for the correct native library at runtime + if mono "$output_dir/NativeLibTestLegacy.exe"; then + echo + echo "========================================" + echo "Legacy .NET Framework test passed!" + echo "========================================" + return 0 + else + echo + echo "========================================" + echo "Test FAILED!" + echo "========================================" + return 1 + fi +} + +# Main +build_package +build_legacy +run_test diff --git a/test/NativeLibTestLegacy/test-windows.ps1 b/test/NativeLibTestLegacy/test-windows.ps1 new file mode 100644 index 0000000..f96bcf6 --- /dev/null +++ b/test/NativeLibTestLegacy/test-windows.ps1 @@ -0,0 +1,151 @@ +# Test legacy .NET Framework build on Windows +# Usage: .\test-windows.ps1 [-Arch x64|x86] +# +# Prerequisites: +# - .NET Framework 4.6.2 or later (included in Windows 10+) +# - Visual Studio 2022 Build Tools or Visual Studio 2022 +# - .NET SDK for building the NuGet package +param( + [ValidateSet("x64", "x86")] + [string]$Arch = "x64" +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RepoRoot = (Resolve-Path "$ScriptDir/../..").Path +Set-Location $ScriptDir + +Write-Host "========================================" +Write-Host "Testing legacy .NET Framework on Windows ($Arch)" +Write-Host "========================================" +Write-Host "" + +# Find MSBuild from Visual Studio installation +function Find-MSBuild { + # Try vswhere first (Visual Studio 2017+) + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (Test-Path $vswhere) { + $vsPath = & $vswhere -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1 + if ($vsPath) { + return $vsPath + } + } + + # Fallback to .NET Framework MSBuild + $frameworkMSBuild = "$env:SystemRoot\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe" + if (Test-Path $frameworkMSBuild) { + return $frameworkMSBuild + } + + throw "MSBuild not found. Please install Visual Studio 2022 or Build Tools." +} + +$MSBuild = Find-MSBuild +Write-Host "Using MSBuild: $MSBuild" +Write-Host "" + +function Build-Package { + Write-Host "==> Building Secp256k1.Net NuGet package..." + dotnet pack "$RepoRoot/Secp256k1.Net" -c Release -o "$RepoRoot/pkg" -p:Version=0.0.1-localtest.1 + if ($LASTEXITCODE -ne 0) { throw "Package build failed" } +} + +function Build-Legacy { + Write-Host "==> Building legacy .NET Framework project..." + # Clear all NuGet caches to ensure fresh package is used + dotnet nuget locals all --clear 2>$null + Remove-Item -Recurse -Force obj, bin -ErrorAction SilentlyContinue + + # Debug: Show package contents + Write-Host "Debug: Package contents..." + $pkgPath = "$RepoRoot/pkg/Secp256k1.Net.0.0.1-localtest.1.nupkg" + if (Test-Path $pkgPath) { + Add-Type -AssemblyName System.IO.Compression.FileSystem + $zip = [System.IO.Compression.ZipFile]::OpenRead($pkgPath) + $zip.Entries | Where-Object { $_.FullName -match "native|secp256k1" } | ForEach-Object { + Write-Host " $($_.FullName) (Size: $($_.Length))" + } + $zip.Dispose() + } + Write-Host "" + + # Map architecture to RuntimeIdentifier + $RID = if ($Arch -eq "x64") { "win-x64" } else { "win-x86" } + + # Restore NuGet packages with the target RID + Write-Host "==> Restoring NuGet packages (RID=$RID)..." + dotnet restore -r $RID + if ($LASTEXITCODE -ne 0) { throw "NuGet restore failed" } + + # Build using Visual Studio's MSBuild for authentic .NET Framework build + # Use PlatformTarget (not Platform) for SDK-style projects to set CPU architecture + Write-Host "==> Building with MSBuild (PlatformTarget=$Arch)..." + & $MSBuild NativeLibTestLegacy.csproj /p:Configuration=Release /p:PlatformTarget=$Arch /p:RuntimeIdentifier=$RID /v:normal + if ($LASTEXITCODE -ne 0) { throw "Build failed" } + + # Debug: Show directory structure after build + Write-Host "Debug: Directory structure after build..." + $outputDir = "$ScriptDir/bin/Release/net462" + + # Check for runtimes folder + $runtimesPath = "$outputDir/runtimes" + if (Test-Path $runtimesPath) { + Write-Host " runtimes/ folder exists:" + Get-ChildItem -Path $runtimesPath -Recurse -File | ForEach-Object { + $relativePath = $_.FullName.Substring($outputDir.Length + 1) + Write-Host " $relativePath (Size: $($_.Length))" + } + } else { + Write-Host " WARNING: runtimes/ folder does NOT exist!" + } + + # Check for any .dll/.so/.dylib in root + Write-Host " Root native files:" + Get-ChildItem -Path $outputDir -File | Where-Object { $_.Extension -in ".dll", ".so", ".dylib" } | ForEach-Object { + Write-Host " $($_.Name) (Size: $($_.Length))" + } + Write-Host "" +} + +function Run-Test { + $RID = if ($Arch -eq "x64") { "win-x64" } else { "win-x86" } + $OutputDir = "$ScriptDir/bin/Release/net462/$RID" + + Write-Host "==> Running test..." + Write-Host "" + + # Debug: show output directory contents related to secp256k1 + Write-Host "Debug: Checking for secp256k1 files..." + Get-ChildItem -Path $OutputDir -Recurse -Filter "*secp256k1*" | ForEach-Object { + Write-Host " Found: $($_.FullName) (Size: $($_.Length))" + } + Write-Host "" + + # Run the executable - the library probes for the correct native library at runtime + $exe = "$OutputDir/NativeLibTestLegacy.exe" + & $exe + $exitCode = $LASTEXITCODE + + Write-Host "" + if ($exitCode -eq 0) { + Write-Host "========================================" + Write-Host "Legacy .NET Framework test passed!" + Write-Host "========================================" + } else { + Write-Host "========================================" + Write-Host "Test FAILED!" + Write-Host "========================================" + exit $exitCode + } +} + +# Main +try { + Build-Package + Build-Legacy + Run-Test +} catch { + Write-Host "ERROR: $_" + exit 1 +}