diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e4dbf255..8020c7ab 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,17 +11,17 @@ jobs:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
steps:
- name: Set up .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
- 7.0.x
+ 8.0.x
- run: dotnet --info
- - uses: actions/checkout@v3
- - name: Test (.NET 7.0/Debug)
- run: dotnet test tests -f net7.0 -c Debug
- - name: Test (.NET 7.0/Release)
- run: dotnet test tests -f net7.0 -c Release
+ - uses: actions/checkout@v4
+ - name: Test (.NET 8.0/Debug)
+ run: dotnet test tests -f net8.0 -c Debug
+ - name: Test (.NET 8.0/Release)
+ run: dotnet test tests -f net8.0 -c Release
- name: Test (.NET 6.0/Debug)
run: dotnet test tests -f net6.0 -c Debug
- name: Test (.NET 6.0/Release)
@@ -29,7 +29,7 @@ jobs:
- name: Pack
run: dotnet pack -c Release
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
path: '**/*.nupkg'
@@ -39,26 +39,26 @@ jobs:
fail-fast: false
matrix:
include:
- - os: macos-11
- os: macos-12
- os: macos-13
+ - os: macos-14
runs-on: ${{ matrix.os }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
steps:
- name: Set up .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
- 7.0.x
+ 8.0.x
- run: dotnet --info
- - uses: actions/checkout@v3
- - name: Test (.NET 7.0/Debug)
- run: dotnet test tests -f net7.0 -c Debug
- - name: Test (.NET 7.0/Release)
- run: dotnet test tests -f net7.0 -c Release
+ - uses: actions/checkout@v4
+ - name: Test (.NET 8.0/Debug)
+ run: dotnet test tests -f net8.0 -c Debug
+ - name: Test (.NET 8.0/Release)
+ run: dotnet test tests -f net8.0 -c Release
- name: Test (.NET 6.0/Debug)
run: dotnet test tests -f net6.0 -c Debug
- name: Test (.NET 6.0/Release)
@@ -70,15 +70,15 @@ jobs:
fail-fast: false
matrix:
include:
- - os: centos:7
- os: debian:10
- os: debian:11
- - os: fedora:37
+ - os: debian:12
- os: fedora:38
- - os: ubuntu:16.04
- - os: ubuntu:18.04
+ - os: fedora:39
+ - os: fedora:40
- os: ubuntu:20.04
- os: ubuntu:22.04
+ - os: ubuntu:24.04
runs-on: ubuntu-latest
container:
image: ${{ matrix.os }}
@@ -99,17 +99,17 @@ jobs:
run: apt-get -qq update && apt-get -qq install --yes --no-install-recommends curl ca-certificates gettext
if: ${{ startsWith(matrix.os, 'ubuntu') }}
- name: Set up .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
- 7.0.x
+ 8.0.x
- run: dotnet --info
- - uses: actions/checkout@v3
- - name: Test (.NET 7.0/Debug)
- run: dotnet test tests -f net7.0 -c Debug
- - name: Test (.NET 7.0/Release)
- run: dotnet test tests -f net7.0 -c Release
+ - uses: actions/checkout@v4
+ - name: Test (.NET 8.0/Debug)
+ run: dotnet test tests -f net8.0 -c Debug
+ - name: Test (.NET 8.0/Release)
+ run: dotnet test tests -f net8.0 -c Release
- name: Test (.NET 6.0/Debug)
run: dotnet test tests -f net6.0 -c Debug
- name: Test (.NET 6.0/Release)
@@ -120,17 +120,18 @@ jobs:
fail-fast: false
matrix:
include:
- - os: alpine3.17
+ - os: alpine3.18
+ - os: alpine3.19
runs-on: ubuntu-latest
container:
- image: mcr.microsoft.com/dotnet/sdk:7.0-${{ matrix.os }}
+ image: mcr.microsoft.com/dotnet/sdk:8.0-${{ matrix.os }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
steps:
- run: dotnet --info
- - uses: actions/checkout@v3
- - name: Test (.NET 7.0/Debug)
- run: dotnet test tests -f net7.0 -c Debug
- - name: Test (.NET 7.0/Release)
- run: dotnet test tests -f net7.0 -c Release
+ - uses: actions/checkout@v4
+ - name: Test (.NET 8.0/Debug)
+ run: dotnet test tests -f net8.0 -c Debug
+ - name: Test (.NET 8.0/Release)
+ run: dotnet test tests -f net8.0 -c Release
diff --git a/LICENSE b/LICENSE
index f53d1507..33f80545 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023 Klaus Hartke
+Copyright (c) 2024 Klaus Hartke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/NOTICE b/NOTICE
index d5ed078d..054451aa 100644
--- a/NOTICE
+++ b/NOTICE
@@ -4,7 +4,7 @@ License Notice for libsodium
This software is based on and contains code derived from libsodium, which is
available under the ISC license.
-Copyright (c) 2013-2023 Frank Denis
+Copyright (c) 2013-2024 Frank Denis
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
diff --git a/NSec.sln b/NSec.sln
index 937219c0..90d2b87c 100644
--- a/NSec.sln
+++ b/NSec.sln
@@ -62,6 +62,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{F058E702-CA9
docs\api\nsec.cryptography.hashalgorithm.md = docs\api\nsec.cryptography.hashalgorithm.md
docs\api\nsec.cryptography.incrementalhash.md = docs\api\nsec.cryptography.incrementalhash.md
docs\api\nsec.cryptography.incrementalmac.md = docs\api\nsec.cryptography.incrementalmac.md
+ docs\api\nsec.cryptography.incrementalsignature.md = docs\api\nsec.cryptography.incrementalsignature.md
+ docs\api\nsec.cryptography.incrementalsignatureverification.md = docs\api\nsec.cryptography.incrementalsignatureverification.md
docs\api\nsec.cryptography.key.md = docs\api\nsec.cryptography.key.md
docs\api\nsec.cryptography.keyagreementalgorithm.md = docs\api\nsec.cryptography.keyagreementalgorithm.md
docs\api\nsec.cryptography.keyblobformat.md = docs\api\nsec.cryptography.keyblobformat.md
@@ -78,6 +80,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{F058E702-CA9
docs\api\nsec.cryptography.sharedsecretblobformat.md = docs\api\nsec.cryptography.sharedsecretblobformat.md
docs\api\nsec.cryptography.sharedsecretcreationparameters.md = docs\api\nsec.cryptography.sharedsecretcreationparameters.md
docs\api\nsec.cryptography.signaturealgorithm.md = docs\api\nsec.cryptography.signaturealgorithm.md
+ docs\api\nsec.cryptography.signaturealgorithm2.md = docs\api\nsec.cryptography.signaturealgorithm2.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{030978CE-A451-4F42-887C-1B200DF7211C}"
diff --git a/README.md b/README.md
index 53fb1eac..8bc90aea 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# NSec
-[![Maintenance](https://img.shields.io/maintenance/yes/2023)](https://github.com/ektrah/nsec)
+[![Maintenance](https://img.shields.io/maintenance/yes/2024)](https://github.com/ektrah/nsec)
[![License](https://img.shields.io/github/license/ektrah/nsec)](https://nsec.rocks/license)
-[![NuGet](https://img.shields.io/nuget/vpre/NSec.Cryptography)](https://www.nuget.org/packages/NSec.Cryptography/23.5.0-preview.1)
+[![NuGet](https://img.shields.io/nuget/vpre/NSec.Cryptography)](https://www.nuget.org/packages/NSec.Cryptography/24.4.0)
[NSec](https://nsec.rocks/) is a cryptographic library for
[.NET 6+](https://dotnet.microsoft.com/) based on
diff --git a/build/Package.props b/build/Package.props
index ca6179cc..e6dcde00 100644
--- a/build/Package.props
+++ b/build/Package.props
@@ -6,8 +6,8 @@
-
- $([System.DateTime]::UtcNow.ToString(y.M.0))-preview.2-$(BuildNumberMajor)-$(BuildNumberMinor)
+
+ $([System.DateTime]::UtcNow.ToString(y.M.0))-preview.1-$(BuildNumberMajor)-$(BuildNumberMinor)
diff --git a/docs/api/nsec.cryptography.aeadalgorithm.md b/docs/api/nsec.cryptography.aeadalgorithm.md
index 479e31ab..40b79076 100644
--- a/docs/api/nsec.cryptography.aeadalgorithm.md
+++ b/docs/api/nsec.cryptography.aeadalgorithm.md
@@ -9,6 +9,8 @@ Represents an authenticated encryption with associated data (AEAD) algorithm.
* [[Algorithm|Algorithm Class]]
* **AeadAlgorithm**
+ * Aegis128L
+ * Aegis256
* Aes256Gcm
* ChaCha20Poly1305
* XChaCha20Poly1305
@@ -20,6 +22,20 @@ Represents an authenticated encryption with associated data (AEAD) algorithm.
## Static Properties
+### Aegis128L
+
+Gets the AEGIS-128L AEAD algorithm.
+
+ public static Aegis128L Aegis128L { get; }
+
+
+### Aegis256
+
+Gets the AEGIS-256 AEAD algorithm.
+
+ public static Aegis256 Aegis256 { get; }
+
+
### Aes256Gcm
Gets the AES256-GCM AEAD algorithm.
@@ -33,10 +49,9 @@ PlatformNotSupportedException
#### Remarks
-The implementation of AES-GCM in NSec is hardware-accelerated and requires an
-x64 processor with the AES-NI extension. The availability of this extension can
-be determined at runtime using the static `IsSupported` property of the
-`NSec.Cryptography.Aes256Gcm` class.
+The AES-GCM implementation in NSec is hardware-accelerated and may not be
+available on all architectures. Support can be determined at runtime using
+the static `IsSupported` property of the `NSec.Cryptography.Aes256Gcm` class.
### ChaCha20Poly1305
@@ -121,8 +136,9 @@ nonce
catastrophic loss of security.
: To prevent nonce reuse when encrypting multiple plaintexts with the same key,
- it is recommended to increment the previous nonce; a randomly generated
- nonce is not suitable.
+ it is recommended to increment the previous nonce. A randomly generated
+ nonce is unsafe unless the [[nonce size|AeadAlgorithm Class#NonceSize]]
+ is at least 24 bytes.
associatedData
: Optional additional data to be authenticated during decryption.
@@ -184,8 +200,9 @@ nonce
catastrophic loss of security.
: To prevent nonce reuse when encrypting multiple plaintexts with the same key,
- it is recommended to increment the previous nonce; a randomly generated
- nonce is not suitable.
+ it is recommended to increment the previous nonce. A randomly generated
+ nonce is unsafe unless the [[nonce size|AeadAlgorithm Class#NonceSize]]
+ is at least 24 bytes.
associatedData
: Optional additional data to be authenticated during decryption.
diff --git a/docs/api/nsec.cryptography.algorithm.md b/docs/api/nsec.cryptography.algorithm.md
index e7e7eff4..96a076bc 100644
--- a/docs/api/nsec.cryptography.algorithm.md
+++ b/docs/api/nsec.cryptography.algorithm.md
@@ -11,8 +11,11 @@ This class has no public members.
* **Algorithm**
* [[AeadAlgorithm|AeadAlgorithm Class]]
+ * Aegis128L
+ * Aegis256
* Aes256Gcm
* ChaCha20Poly1305
+ * XChaCha20Poly1305
* [[HashAlgorithm|HashAlgorithm Class]]
* Blake2b
* Sha256
diff --git a/docs/api/nsec.cryptography.incrementalsignature.md b/docs/api/nsec.cryptography.incrementalsignature.md
new file mode 100644
index 00000000..1ab1be68
--- /dev/null
+++ b/docs/api/nsec.cryptography.incrementalsignature.md
@@ -0,0 +1,163 @@
+# IncrementalSignature Struct
+
+Represents the state of a signature algorithm that can be incrementally updated with
+segments of data.
+
+ public readonly struct IncrementalSignature
+
+The type provides an "init, update, final" interface for computing a signature:
+First, a state needs to be initialized with the signing key to be used. The state can
+then be updated zero or more times with segments of data. Finalizing the state
+yields a result that is identical to the signature of the concatenated segments.
+
+!!! Note
+ [[IncrementalSignature|IncrementalSignature Struct]] instances have
+ value-type semantics: Passing an instance to a method or assigning it to a
+ variable creates a copy of the state. It is therefore recommended to always
+ pass instances using `ref`, `in`, or `out`.
+
+
+## Example
+
+The following C# example shows how to compute a signature from multiple segments of
+data:
+
+ {{Incremental Signing}}
+
+
+## [TOC] Summary
+
+
+## Properties
+
+
+### Algorithm
+
+Gets the algorithm that was used to initialize the state.
+
+ public SignatureAlgorithm2? Algorithm { get; }
+
+#### Property Value
+
+An instance of the [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] class, or `null` if the
+current instance has not been initialized yet or if it has been finalized.
+
+
+## Static Methods
+
+
+### Initialize(Key, out IncrementalSignature)
+
+Initializes the [[IncrementalSignature|IncrementalSignature Struct]] state with the
+specified private key.
+
+ public static void Initialize(
+ Key privateKey,
+ out IncrementalSignature state)
+
+#### Parameters
+
+privateKey
+: The private key to use for computing the signature.
+
+state
+: When this method returns, contains the initialized state.
+
+#### Exceptions
+
+ArgumentNullException
+: `privateKey` is `null`.
+
+ArgumentException
+: `privateKey.Algorithm` is not an instance of the
+ [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] class.
+
+
+### Update(ref IncrementalSignature, ReadOnlySpan)
+
+Updates the [[IncrementalSignature|IncrementalSignature Struct]] state with the specified
+span of bytes.
+
+ public static void Update(
+ ref IncrementalSignature state,
+ ReadOnlySpan data)
+
+#### Parameters
+
+state
+: The state to be updated with `data`.
+
+data
+: A segment of the data to sign.
+
+#### Exceptions
+
+InvalidOperationException
+: `state` has not been initialized yet or has already been finalized.
+
+
+### Finalize(ref IncrementalSignature)
+
+Completes the signature computation and returns the result as an array of bytes.
+
+ public static byte[] Finalize(
+ ref IncrementalSignature state)
+
+#### Parameters
+
+state
+: The state to be finalized.
+
+#### Return Value
+
+The computed signature.
+
+#### Exceptions
+
+InvalidOperationException
+: `state` has not been initialized yet or has already been finalized.
+
+
+### Finalize(ref IncrementalSignature, Span)
+
+Completes the signature computation and fills the specified span of bytes with the
+result.
+
+ public static void Finalize(
+ ref IncrementalSignature state,
+ Span signature)
+
+#### Parameters
+
+state
+: The state to be finalized.
+
+signature
+: The span to fill with the computed signature.
+
+#### Exceptions
+
+InvalidOperationException
+: `state` has not been initialized yet or has already been finalized.
+
+ArgumentException
+: `signature.Length` is not equal to [[SignatureSize|SignatureAlgorithm2 Class#SignatureSize]].
+
+ObjectDisposedException
+: The private key used to initialize the state has been disposed.
+
+
+## Thread Safety
+
+Any public static members of this type are thread safe. Any instance members are
+not guaranteed to be thread safe. As with any other type, reading and writing to
+a shared variable that contains an instance of this type must be protected by a
+lock to guarantee thread safety.
+
+
+## See Also
+
+* API Reference
+ * [[IncrementalSignatureVerification Struct]]
+ * [[Key Class]]
+ * [[SignatureAlgorithm2 Class]]
diff --git a/docs/api/nsec.cryptography.incrementalsignatureverification.md b/docs/api/nsec.cryptography.incrementalsignatureverification.md
new file mode 100644
index 00000000..b24db4f3
--- /dev/null
+++ b/docs/api/nsec.cryptography.incrementalsignatureverification.md
@@ -0,0 +1,140 @@
+# IncrementalSignatureVerification Struct
+
+Represents the state of a signature verification algorithm that can be incrementally updated with
+segments of data.
+
+ public readonly struct IncrementalSignatureVerification
+
+The type provides an "init, update, final" interface for verifying data given a
+public key and a signature. First, a state needs to be initialized with the
+public key. The state can then be updated zero or more times with segments of
+data. Finalizing the state gives a result as to whether verification of the
+concatenated segments was successful.
+
+!!! Note
+ [[IncrementalSignatureVerification|IncrementalSignatureVerification Struct]]
+ instances have value-type semantics: Passing an instance to a method or
+ assigning it to a variable creates a copy of the state. It is therefore
+ recommended to always pass instances using `ref`, `in`, or `out`.
+
+
+## Example
+
+The following C# example shows how to verify multiple segments of data given a
+public key and a signature:
+
+ {{Incremental Signature Verification}}
+
+
+## [TOC] Summary
+
+
+## Properties
+
+
+### Algorithm
+
+Gets the algorithm that was used to initialize the state.
+
+ public SignatureAlgorithm2? Algorithm { get; }
+
+#### Property Value
+
+An instance of the [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] class, or `null` if the
+current instance has not been initialized yet or if it has been finalized.
+
+
+## Static Methods
+
+
+### Initialize(PublicKey, out IncrementalSignatureVerification)
+
+Initializes the
+[[IncrementalSignatureVerification|IncrementalSignatureVerification Struct]]
+state with the specified public key.
+
+ public static void Initialize(
+ PublicKey publicKey,
+ out IncrementalSignatureVerification state)
+
+#### Parameters
+
+publicKey
+: The public key to use for verifying the data.
+
+state
+: When this method returns, contains the initialized state.
+
+#### Exceptions
+
+ArgumentNullException
+: `publicKey` is `null`.
+
+ArgumentException
+: `publicKey.Algorithm` is not an instance of the
+ [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] class.
+
+
+### Update(ref IncrementalSignatureVerification, ReadOnlySpan)
+
+Updates the [[IncrementalSignatureVerification|IncrementalSignatureVerification Struct]] state with the specified
+span of bytes.
+
+ public static void Update(
+ ref IncrementalSignatureVerification state,
+ ReadOnlySpan data)
+
+#### Parameters
+
+state
+: The state to be updated with `data`.
+
+data
+: A segment of the data to verify.
+
+#### Exceptions
+
+InvalidOperationException
+: `state` has not been initialized yet or has already been finalized.
+
+
+### FinalizeAndVerify(ref IncrementalSignatureVerification, ReadOnlySpan)
+
+Completes the verification.
+
+ public static bool FinalizeAndVerify(
+ ref IncrementalSignatureVerification state,
+ ReadOnlySpan signature)
+
+#### Parameters
+
+state
+: The state to be finalized.
+
+signature
+: The signature of the data to verify.
+
+#### Return Value
+
+`true` if verification succeeds; otherwise, `false`.
+
+#### Exceptions
+
+InvalidOperationException
+: `state` has not been initialized yet or has already been finalized.
+
+
+## Thread Safety
+
+Any public static members of this type are thread safe. Any instance members are
+not guaranteed to be thread safe. As with any other type, reading and writing to
+a shared variable that contains an instance of this type must be protected by a
+lock to guarantee thread safety.
+
+
+## See Also
+
+* API Reference
+ * [[IncrementalSignature Struct]]
+ * [[PublicKey Class]]
+ * [[SignatureAlgorithm2 Class]]
diff --git a/docs/api/nsec.cryptography.md b/docs/api/nsec.cryptography.md
index 3279a4ee..7d30471f 100644
--- a/docs/api/nsec.cryptography.md
+++ b/docs/api/nsec.cryptography.md
@@ -2,6 +2,8 @@
* [[Algorithm Class]]
* [[AeadAlgorithm Class]]
+ * Aegis128L Class
+ * Aegis256 Class
* Aes256Gcm Class
* ChaCha20Poly1305 Class
* XChaCha20Poly1305 Class
@@ -26,8 +28,12 @@
* [[ScryptParameters Struct]]
* [[SignatureAlgorithm Class]]
* Ed25519 Class
+ * [[SignatureAlgorithm2 Class]]
+ * Ed25519ph Class
* [[IncrementalHash Struct]]
* [[IncrementalMac Struct]]
+* [[IncrementalSignature Struct]]
+* [[IncrementalSignatureVerification Struct]]
* [[Key Class]]
* [[KeyCreationParameters Struct]]
* [[KeyExportPolicies Enum]]
diff --git a/docs/api/nsec.cryptography.signaturealgorithm.md b/docs/api/nsec.cryptography.signaturealgorithm.md
index f0463174..12dbfe8a 100644
--- a/docs/api/nsec.cryptography.signaturealgorithm.md
+++ b/docs/api/nsec.cryptography.signaturealgorithm.md
@@ -10,6 +10,8 @@ Represents a digital signature algorithm.
* [[Algorithm|Algorithm Class]]
* **SignatureAlgorithm**
* Ed25519
+ * [[SignatureAlgorithm2|SignatureAlgorithm2 Class]]
+ * Ed25519ph
## [TOC] Summary
@@ -25,6 +27,19 @@ Gets the Ed25519 signature algorithm.
public static Ed25519 Ed25519 { get; }
+### Ed25519ph
+
+Gets the Ed25519ph signature algorithm.
+
+ public static Ed25519ph Ed25519ph { get; }
+
+!!! Note
+ Ed25519ph incorporates a pre-hashing step to support [[incremental
+ signing|IncrementalSignature Struct]]. As a result, the outputs of Ed25519
+ and Ed25519ph are not the same. For most cryptographic applications, it is
+ recommended to use Ed25519 due to its broader adoption.
+
+
## Properties
@@ -189,3 +204,4 @@ All methods yield the same result for the same arguments.
* [[Algorithm Class]]
* [[Key Class]]
* [[PublicKey Class]]
+ * [[SignatureAlgorithm2 Class]]
diff --git a/docs/api/nsec.cryptography.signaturealgorithm2.md b/docs/api/nsec.cryptography.signaturealgorithm2.md
new file mode 100644
index 00000000..fc210163
--- /dev/null
+++ b/docs/api/nsec.cryptography.signaturealgorithm2.md
@@ -0,0 +1,202 @@
+# SignatureAlgorithm2 Class
+
+Represents a digital signature algorithm that supports [[incremental
+signing|IncrementalSignature Struct]] and [[incremental signature
+verification|IncrementalSignatureVerification Struct]].
+
+ public abstract class SignatureAlgorithm2 : SignatureAlgorithm
+
+
+## Inheritance Hierarchy
+
+* [[Algorithm|Algorithm Class]]
+ * [[SignatureAlgorithm|SignatureAlgorithm Class]]
+ * Ed25519
+ * **SignatureAlgorithm2**
+ * Ed25519ph
+
+
+## [TOC] Summary
+
+
+## Static Properties
+
+
+### Ed25519ph
+
+Gets the Ed25519ph signature algorithm.
+
+ public static Ed25519ph Ed25519ph { get; }
+
+!!! Note
+ Ed25519ph incorporates a pre-hashing step to support [[incremental
+ signing|IncrementalSignature Struct]]. As a result, the outputs of Ed25519
+ and Ed25519ph are not the same. For most cryptographic applications, it is
+ recommended to use Ed25519 due to its broader adoption.
+
+
+## Properties
+
+
+### PrivateKeySize
+
+Gets the size of private keys.
+
+ public int PrivateKeySize { get; }
+
+#### Property Value
+
+The private key size, in bytes.
+
+
+### PublicKeySize
+
+Gets the size of public keys.
+
+ public int PublicKeySize { get; }
+
+#### Property Value
+
+The public key size, in bytes.
+
+
+### SignatureSize
+
+Gets the size of a signature.
+
+ public int SignatureSize { get; }
+
+#### Property Value
+
+The signature size, in bytes.
+
+
+## Methods
+
+
+### Sign(Key, ReadOnlySpan)
+
+Signs the specified input data using the specified key and returns the signature
+as an array of bytes.
+
+ public byte[] Sign(
+ Key key,
+ ReadOnlySpan data)
+
+#### Parameters
+
+key
+: The [[Key|Key Class]] to use for signing.
+
+data
+: The data to sign.
+
+#### Return Value
+
+The signature for the data.
+
+#### Exceptions
+
+ArgumentNullException
+: `key` is `null`.
+
+ArgumentException
+: `key.Algorithm` is not the same object as the current
+ [[SignatureAlgorithm|SignatureAlgorithm Class]] object.
+
+ObjectDisposedException
+: `key` has been disposed.
+
+
+### Sign(Key, ReadOnlySpan, Span)
+
+Signs the specified input data using the specified key and fills the specified
+span of bytes with the signature.
+
+ public void Sign(
+ Key key,
+ ReadOnlySpan data,
+ Span signature)
+
+#### Parameters
+
+key
+: The [[Key|Key Class]] to use for signing.
+
+data
+: The data to sign.
+
+signature
+: The span to fill with the signature for the data.
+
+#### Exceptions
+
+ArgumentNullException
+: `key` is `null`.
+
+ArgumentException
+: `key.Algorithm` is not the same object as the current
+ [[SignatureAlgorithm|SignatureAlgorithm Class]] object.
+
+ArgumentException
+: `signature.Length` is not equal to
+ [[SignatureSize|SignatureAlgorithm Class#SignatureSize]].
+
+ObjectDisposedException
+: `key` has been disposed.
+
+
+### Verify(PublicKey, ReadOnlySpan, ReadOnlySpan)
+
+Verifies specified input data using the specified public key and signature.
+
+ public bool Verify(
+ PublicKey publicKey,
+ ReadOnlySpan data,
+ ReadOnlySpan signature)
+
+#### Parameters
+
+publicKey
+: The [[PublicKey|PublicKey Class]] to use for verification.
+ Verification fails if this is not the public key for the private key used
+ when signing the data.
+
+data
+: The data to verify.
+ Verification fails if the integrity of the data was compromised.
+
+signature
+: The signature of the data to verify.
+
+#### Return Value
+
+`true` if verification succeeds; otherwise, `false`.
+
+#### Exceptions
+
+ArgumentNullException
+: `publicKey` is `null`.
+
+ArgumentException
+: `publicKey.Algorithm` is not the same object as the current
+ [[SignatureAlgorithm|SignatureAlgorithm Class]] object.
+
+
+## Thread Safety
+
+All members of this type are thread safe.
+
+
+## Purity
+
+All methods yield the same result for the same arguments.
+
+
+## See Also
+
+* API Reference
+ * [[Algorithm Class]]
+ * [[Key Class]]
+ * [[PublicKey Class]]
+ * [[SignatureAlgorithm Class]]
diff --git a/docs/install.md b/docs/install.md
index e3d9d90f..a4267f83 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -1,27 +1,15 @@
# Installation
-NSec is available as
-[a NuGet package from nuget.org](https://www.nuget.org/packages/NSec.Cryptography/23.5.0-preview.1).
-It can be added to a project in a number of ways, depending on the project type
-and tools used:
+Use the following command to install the
+[NSec.Cryptography NuGet package](https://www.nuget.org/packages/NSec.Cryptography/24.4.0):
-
-#### dotnet CLI
-
- $ dotnet add package NSec.Cryptography --version 23.5.0-preview.1
-
-#### Visual Studio
-
- PM> Install-Package NSec.Cryptography -Version 23.5.0-preview.1
-
-#### .csproj
-
-
+ $ dotnet add package NSec.Cryptography --version 24.4.0
## Supported Platforms
-NSec is intended to run on
+[NSec 24.4.0](https://www.nuget.org/packages/NSec.Cryptography/24.4.0)
+is intended to run on all
[supported versions of .NET](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core)
on the following platforms:
@@ -34,38 +22,56 @@ on the following platforms:
| **`ios-`** | | | | |
| **`android-`** | | | | |
-Specifically,
-[NSec 23.5.0-preview.1](https://www.nuget.org/packages/NSec.Cryptography/23.5.0-preview.1)
+
+Please note:
+
+1. For Windows, the
+ [Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022](https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist)
+ is required. This is part of the .NET SDK but might not be present on a
+ clean Windows installation.
+
+2. The AES-GCM implementation in NSec is hardware-accelerated and may not be
+ available on all architectures. Support can be determined at runtime using
+ the static `IsSupported` property of the `NSec.Cryptography.Aes256Gcm` class.
+
+
+## Tested Platforms
+
+[NSec 24.4.0](https://www.nuget.org/packages/NSec.Cryptography/24.4.0)
has been tested to run on the following platforms and .NET versions:
-| OS | Version | Architectures | .NET |
-|:-------------------- |:-------- |:------------- |:--------------- |
-| Windows 11 | 22H2 | x64 | 7.0.5 / 6.0.16 |
-| Windows Server | 2022 | x64 | 7.0.5 / 6.0.16 |
-| | | | |
-| macOS | 11.7 | x64 | 7.0.5 / 6.0.16 |
-| | 12.6 | x64 | 7.0.5 / 6.0.16 |
-| | 13.3 | x64 | 7.0.5 / 6.0.16 |
-| | | | |
-| Alpine | 3.17 | x64 | 7.0.4 |
-| CentOS | 7.9.2009 | x64 | 7.0.5 / 6.0.16 |
-| Debian | 10.13 | x64 | 7.0.5 / 6.0.16 |
-| | 11.7 | x64 | 7.0.5 / 6.0.16 |
-| Fedora | 37 | x64 | 7.0.5 / 6.0.16 |
-| | 38 | x64 | 7.0.5 / 6.0.16 |
-| Ubuntu | 16.04 | x64 | 7.0.5 / 6.0.16 |
-| | 18.04 | x64 | 7.0.5 / 6.0.16 |
-| | 20.04 | x64 | 7.0.5 / 6.0.16 |
-| | 22.04 | x64 | 7.0.5 / 6.0.16 |
-
-Other, similar platforms supported by .NET should work as well but have not been tested.
-
-Using NSec on Windows requires the
-[Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads).
-This dependency is included in the .NET SDK but might
-not be present, for example, when deploying a self-contained application.
-
-The implementation of AES-GCM in NSec is hardware-accelerated and requires an
-x64 processor with the AES-NI extension. The availability of this extension can
-be determined at runtime using the static `IsSupported` property of the
-`NSec.Cryptography.Aes256Gcm` class.
+| OS | Version | Architecture | .NET |
+|:-------------------- |:-------- |:------------- |:-------------- |
+| Windows 11 | 23H2 | x64 | 8.0.4 / 6.0.29 |
+| Windows Server | 2022 | x64 | 8.0.4 / 6.0.29 |
+| | | | |
+| macOS | 12.7 | x64 | 8.0.4 / 6.0.29 |
+| | 13.6 | x64 | 8.0.4 / 6.0.29 |
+| | 14.4 | arm64 | 8.0.4 / 6.0.29 |
+| | | | |
+| Alpine Linux | 3.18 | x64 | 8.0.4 |
+| | 3.19 | x64 | 8.0.4 |
+| Debian | 10 | x64 | 8.0.4 / 6.0.29 |
+| | 11 | x64 | 8.0.4 / 6.0.29 |
+| | 12 | x64 | 8.0.4 / 6.0.29 |
+| Fedora | 38 | x64 | 8.0.4 / 6.0.29 |
+| | 39 | x64 | 8.0.4 / 6.0.29 |
+| | 40 | x64 | 8.0.4 / 6.0.29 |
+| Ubuntu | 20.04 | x64 | 8.0.4 / 6.0.29 |
+| | 22.04 | x64 | 8.0.4 / 6.0.29 |
+| | 24.04 | x64 | 8.0.4 / 6.0.29 |
+
+The other supported platforms should work as well, but haven't been tested.
+
+
+## Frequently Asked Questions
+
+Below are some frequently asked questions:
+
+**Q**: What causes a *System.DllNotFoundException: Unable to load shared
+library 'libsodium' or one of its dependencies.* when using the
+NSec.Cryptography NuGet package?
+**A**: This exception can occur if the operating system or architecture is not
+supported, or if the Visual C++ Redistributable has not been installed on a
+Windows system. Please refer to the [Supported Platforms](#supported-platforms)
+section above.
diff --git a/index.md b/index.md
index 0e7cef50..004a8a34 100644
--- a/index.md
+++ b/index.md
@@ -42,7 +42,7 @@ and verify the signature:
## Installation
- $ dotnet add package NSec.Cryptography --version 23.5.0-preview.1
+ $ dotnet add package NSec.Cryptography --version 24.4.0
NSec works with .NET 6 and later on Windows, Linux and macOS.
@@ -53,21 +53,24 @@ See [[Installation]] for more details.
| Class | Algorithms |
|:----------------------------------------------- |:------------------------- |
-| [[AeadAlgorithm Class]] | AES256-GCM |
+| [[AeadAlgorithm Class]] | AEGIS-128L |
+| | AEGIS-256 |
+| | AES256-GCM |
| | ChaCha20-Poly1305 |
| | XChaCha20-Poly1305 |
-| [[HashAlgorithm Class]] | BLAKE2b *-- unkeyed* |
+| [[HashAlgorithm Class]] | BLAKE2b *(unkeyed)* |
| | SHA-256 |
| | SHA-512 |
| [[KeyAgreementAlgorithm Class]] | X25519 |
| [[KeyDerivationAlgorithm2 Class]] | HKDF-SHA-256 |
| | HKDF-SHA-512 |
-| [[MacAlgorithm Class]] | BLAKE2b *-- keyed* |
+| [[MacAlgorithm Class]] | BLAKE2b *(keyed)* |
| | HMAC-SHA-256 |
| | HMAC-SHA-512 |
| [[PasswordBasedKeyDerivationAlgorithm Class]] | Argon2id |
| | scrypt |
| [[SignatureAlgorithm Class]] | Ed25519 |
+| [[SignatureAlgorithm2 Class]] | Ed25519ph |
See [[API Reference|NSec.Cryptography Namespace]] for detailed information.
diff --git a/notice.md b/notice.md
index f86f4892..c6e16a22 100644
--- a/notice.md
+++ b/notice.md
@@ -12,7 +12,7 @@ permission except when reproducing the unmodified NSec documentation.
[NSec](https://github.com/ektrah/nsec) is licensed under the MIT license.
-Copyright © 2023 Klaus Hartke
+Copyright © 2024 Klaus Hartke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -39,7 +39,7 @@ NSec is based on and contains code derived from
[libsodium](https://github.com/jedisct1/libsodium),
which is available under the ISC license.
-Copyright © 2013-2023 Frank Denis
+Copyright © 2013-2024 Frank Denis
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
diff --git a/src/Cryptography/AeadAlgorithm.cs b/src/Cryptography/AeadAlgorithm.cs
index 03fcde95..b4eccde3 100644
--- a/src/Cryptography/AeadAlgorithm.cs
+++ b/src/Cryptography/AeadAlgorithm.cs
@@ -23,6 +23,8 @@ namespace NSec.Cryptography
//
public abstract class AeadAlgorithm : Algorithm
{
+ private static Aegis128L? s_Aegis128L;
+ private static Aegis256? s_Aegis256;
private static Aes256Gcm? s_Aes256Gcm;
private static ChaCha20Poly1305? s_ChaCha20Poly1305;
private static XChaCha20Poly1305? s_XChaCha20Poly1305;
@@ -37,7 +39,7 @@ private protected AeadAlgorithm(
int tagSize)
{
Debug.Assert(keySize > 0);
- Debug.Assert(nonceSize >= 0 && nonceSize <= 24);
+ Debug.Assert(nonceSize >= 0 && nonceSize <= 32);
Debug.Assert(tagSize >= 0 && tagSize <= 255);
_keySize = keySize;
@@ -45,6 +47,34 @@ private protected AeadAlgorithm(
_tagSize = tagSize;
}
+ public static Aegis128L Aegis128L
+ {
+ get
+ {
+ Aegis128L? instance = s_Aegis128L;
+ if (instance == null)
+ {
+ Interlocked.CompareExchange(ref s_Aegis128L, new Aegis128L(), null);
+ instance = s_Aegis128L;
+ }
+ return instance;
+ }
+ }
+
+ public static Aegis256 Aegis256
+ {
+ get
+ {
+ Aegis256? instance = s_Aegis256;
+ if (instance == null)
+ {
+ Interlocked.CompareExchange(ref s_Aegis256, new Aegis256(), null);
+ instance = s_Aegis256;
+ }
+ return instance;
+ }
+ }
+
public static Aes256Gcm Aes256Gcm
{
get
diff --git a/src/Cryptography/Aegis128L.cs b/src/Cryptography/Aegis128L.cs
new file mode 100644
index 00000000..fbeeed96
--- /dev/null
+++ b/src/Cryptography/Aegis128L.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using NSec.Cryptography.Formatting;
+using static Interop.Libsodium;
+
+namespace NSec.Cryptography
+{
+ //
+ // AEGIS-128L
+ //
+ // The AEGIS-128L authenticated encryption with associated data (AEAD)
+ // algorithm
+ //
+ // References:
+ //
+ // draft-irtf-cfrg-aegis-aead-04 - The AEGIS Family of Authenticated
+ // Encryption Algorithms
+ //
+ // RFC 5116 - An Interface and Algorithms for Authenticated Encryption
+ //
+ // Parameters:
+ //
+ // Key Size - 16 bytes.
+ //
+ // Nonce Size - 16 bytes.
+ //
+ // Tag Size - 32 bytes.
+ //
+ // Plaintext Size - Between 0 and 2^61-1 bytes. (A Span can hold
+ // only up to 2^31-1 bytes.)
+ //
+ // Associated Data Size - Between 0 and 2^64-1 bytes.
+ //
+ // Ciphertext Size - The ciphertext always has the size of the
+ // plaintext plus the tag size.
+ //
+ public sealed class Aegis128L : AeadAlgorithm
+ {
+ private const uint NSecBlobHeader = 0xDE614ADE;
+
+ private static int s_selfTest;
+
+ public Aegis128L() : base(
+ keySize: crypto_aead_aegis128l_KEYBYTES,
+ nonceSize: crypto_aead_aegis128l_NPUBBYTES,
+ tagSize: crypto_aead_aegis128l_ABYTES)
+ {
+ if (s_selfTest == 0)
+ {
+ SelfTest();
+ Interlocked.Exchange(ref s_selfTest, 1);
+ }
+ }
+
+ internal override void CreateKey(
+ ReadOnlySpan seed,
+ out SecureMemoryHandle keyHandle,
+ out PublicKey? publicKey)
+ {
+ Debug.Assert(seed.Length == crypto_aead_aegis128l_KEYBYTES);
+
+ publicKey = null;
+ keyHandle = SecureMemoryHandle.CreateFrom(seed);
+ }
+
+ private protected unsafe override void EncryptCore(
+ SecureMemoryHandle keyHandle,
+ ReadOnlySpan nonce,
+ ReadOnlySpan associatedData,
+ ReadOnlySpan plaintext,
+ Span ciphertext)
+ {
+ Debug.Assert(keyHandle.Size == crypto_aead_aegis128l_KEYBYTES);
+ Debug.Assert(nonce.Length == crypto_aead_aegis128l_NPUBBYTES);
+ Debug.Assert(ciphertext.Length == plaintext.Length + crypto_aead_aegis128l_ABYTES);
+
+ fixed (byte* c = ciphertext)
+ fixed (byte* m = plaintext)
+ fixed (byte* ad = associatedData)
+ fixed (byte* n = nonce)
+ {
+ int error = crypto_aead_aegis128l_encrypt(
+ c,
+ out ulong clen_p,
+ m,
+ (ulong)plaintext.Length,
+ ad,
+ (ulong)associatedData.Length,
+ null,
+ n,
+ keyHandle);
+
+ Debug.Assert(error == 0);
+ Debug.Assert((ulong)ciphertext.Length == clen_p);
+ }
+ }
+
+ internal override int GetSeedSize()
+ {
+ return crypto_aead_aegis128l_KEYBYTES;
+ }
+
+ private protected unsafe override bool DecryptCore(
+ SecureMemoryHandle keyHandle,
+ ReadOnlySpan nonce,
+ ReadOnlySpan associatedData,
+ ReadOnlySpan ciphertext,
+ Span plaintext)
+ {
+ Debug.Assert(keyHandle.Size == crypto_aead_aegis128l_KEYBYTES);
+ Debug.Assert(nonce.Length == crypto_aead_aegis128l_NPUBBYTES);
+ Debug.Assert(plaintext.Length == ciphertext.Length - crypto_aead_aegis128l_ABYTES);
+
+ fixed (byte* m = plaintext)
+ fixed (byte* c = ciphertext)
+ fixed (byte* ad = associatedData)
+ fixed (byte* n = nonce)
+ {
+ int error = crypto_aead_aegis128l_decrypt(
+ m,
+ out ulong mlen_p,
+ null,
+ c,
+ (ulong)ciphertext.Length,
+ ad,
+ (ulong)associatedData.Length,
+ n,
+ keyHandle);
+
+ // libsodium clears plaintext if decryption fails
+
+ Debug.Assert(error != 0 || (ulong)plaintext.Length == mlen_p);
+ return error == 0;
+ }
+ }
+
+ internal override bool TryExportKey(
+ SecureMemoryHandle keyHandle,
+ KeyBlobFormat format,
+ Span blob,
+ out int blobSize)
+ {
+ return format switch
+ {
+ KeyBlobFormat.RawSymmetricKey => RawKeyFormatter.TryExport(keyHandle, blob, out blobSize),
+ KeyBlobFormat.NSecSymmetricKey => NSecKeyFormatter.TryExport(NSecBlobHeader, crypto_aead_aegis128l_KEYBYTES, crypto_aead_aegis128l_ABYTES, keyHandle, blob, out blobSize),
+ _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
+ };
+ }
+
+ internal override bool TryImportKey(
+ ReadOnlySpan blob,
+ KeyBlobFormat format,
+ out SecureMemoryHandle? keyHandle,
+ out PublicKey? publicKey)
+ {
+ publicKey = null;
+
+ return format switch
+ {
+ KeyBlobFormat.RawSymmetricKey => RawKeyFormatter.TryImport(crypto_aead_aegis128l_KEYBYTES, blob, out keyHandle),
+ KeyBlobFormat.NSecSymmetricKey => NSecKeyFormatter.TryImport(NSecBlobHeader, crypto_aead_aegis128l_KEYBYTES, crypto_aead_aegis128l_ABYTES, blob, out keyHandle),
+ _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
+ };
+ }
+
+ private static void SelfTest()
+ {
+ if ((crypto_aead_aegis128l_abytes() != crypto_aead_aegis128l_ABYTES) ||
+ (crypto_aead_aegis128l_keybytes() != crypto_aead_aegis128l_KEYBYTES) ||
+ (crypto_aead_aegis128l_npubbytes() != crypto_aead_aegis128l_NPUBBYTES) ||
+ (crypto_aead_aegis128l_nsecbytes() != crypto_aead_aegis128l_NSECBYTES))
+ {
+ throw Error.InvalidOperation_InitializationFailed();
+ }
+ }
+ }
+}
diff --git a/src/Cryptography/Aegis256.cs b/src/Cryptography/Aegis256.cs
new file mode 100644
index 00000000..1a8acfa8
--- /dev/null
+++ b/src/Cryptography/Aegis256.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using NSec.Cryptography.Formatting;
+using static Interop.Libsodium;
+
+namespace NSec.Cryptography
+{
+ //
+ // AEGIS-256
+ //
+ // The AEGIS-256 authenticated encryption with associated data (AEAD)
+ // algorithm
+ //
+ // References:
+ //
+ // draft-irtf-cfrg-aegis-aead-04 - The AEGIS Family of Authenticated
+ // Encryption Algorithms
+ //
+ // RFC 5116 - An Interface and Algorithms for Authenticated Encryption
+ //
+ // Parameters:
+ //
+ // Key Size - 32 bytes.
+ //
+ // Nonce Size - 32 bytes.
+ //
+ // Tag Size - 32 bytes.
+ //
+ // Plaintext Size - Between 0 and 2^61-1 bytes. (A Span can hold
+ // only up to 2^31-1 bytes.)
+ //
+ // Associated Data Size - Between 0 and 2^64-1 bytes.
+ //
+ // Ciphertext Size - The ciphertext always has the size of the
+ // plaintext plus the tag size.
+ //
+ public sealed class Aegis256 : AeadAlgorithm
+ {
+ private const uint NSecBlobHeader = 0xDE614BDE;
+
+ private static int s_selfTest;
+
+ public Aegis256() : base(
+ keySize: crypto_aead_aegis256_KEYBYTES,
+ nonceSize: crypto_aead_aegis256_NPUBBYTES,
+ tagSize: crypto_aead_aegis256_ABYTES)
+ {
+ if (s_selfTest == 0)
+ {
+ SelfTest();
+ Interlocked.Exchange(ref s_selfTest, 1);
+ }
+ }
+
+ internal override void CreateKey(
+ ReadOnlySpan seed,
+ out SecureMemoryHandle keyHandle,
+ out PublicKey? publicKey)
+ {
+ Debug.Assert(seed.Length == crypto_aead_aegis256_KEYBYTES);
+
+ publicKey = null;
+ keyHandle = SecureMemoryHandle.CreateFrom(seed);
+ }
+
+ private protected unsafe override void EncryptCore(
+ SecureMemoryHandle keyHandle,
+ ReadOnlySpan nonce,
+ ReadOnlySpan associatedData,
+ ReadOnlySpan plaintext,
+ Span ciphertext)
+ {
+ Debug.Assert(keyHandle.Size == crypto_aead_aegis256_KEYBYTES);
+ Debug.Assert(nonce.Length == crypto_aead_aegis256_NPUBBYTES);
+ Debug.Assert(ciphertext.Length == plaintext.Length + crypto_aead_aegis256_ABYTES);
+
+ fixed (byte* c = ciphertext)
+ fixed (byte* m = plaintext)
+ fixed (byte* ad = associatedData)
+ fixed (byte* n = nonce)
+ {
+ int error = crypto_aead_aegis256_encrypt(
+ c,
+ out ulong clen_p,
+ m,
+ (ulong)plaintext.Length,
+ ad,
+ (ulong)associatedData.Length,
+ null,
+ n,
+ keyHandle);
+
+ Debug.Assert(error == 0);
+ Debug.Assert((ulong)ciphertext.Length == clen_p);
+ }
+ }
+
+ internal override int GetSeedSize()
+ {
+ return crypto_aead_aegis256_KEYBYTES;
+ }
+
+ private protected unsafe override bool DecryptCore(
+ SecureMemoryHandle keyHandle,
+ ReadOnlySpan nonce,
+ ReadOnlySpan associatedData,
+ ReadOnlySpan ciphertext,
+ Span plaintext)
+ {
+ Debug.Assert(keyHandle.Size == crypto_aead_aegis256_KEYBYTES);
+ Debug.Assert(nonce.Length == crypto_aead_aegis256_NPUBBYTES);
+ Debug.Assert(plaintext.Length == ciphertext.Length - crypto_aead_aegis256_ABYTES);
+
+ fixed (byte* m = plaintext)
+ fixed (byte* c = ciphertext)
+ fixed (byte* ad = associatedData)
+ fixed (byte* n = nonce)
+ {
+ int error = crypto_aead_aegis256_decrypt(
+ m,
+ out ulong mlen_p,
+ null,
+ c,
+ (ulong)ciphertext.Length,
+ ad,
+ (ulong)associatedData.Length,
+ n,
+ keyHandle);
+
+ // libsodium clears plaintext if decryption fails
+
+ Debug.Assert(error != 0 || (ulong)plaintext.Length == mlen_p);
+ return error == 0;
+ }
+ }
+
+ internal override bool TryExportKey(
+ SecureMemoryHandle keyHandle,
+ KeyBlobFormat format,
+ Span blob,
+ out int blobSize)
+ {
+ return format switch
+ {
+ KeyBlobFormat.RawSymmetricKey => RawKeyFormatter.TryExport(keyHandle, blob, out blobSize),
+ KeyBlobFormat.NSecSymmetricKey => NSecKeyFormatter.TryExport(NSecBlobHeader, crypto_aead_aegis256_KEYBYTES, crypto_aead_aegis256_ABYTES, keyHandle, blob, out blobSize),
+ _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
+ };
+ }
+
+ internal override bool TryImportKey(
+ ReadOnlySpan blob,
+ KeyBlobFormat format,
+ out SecureMemoryHandle? keyHandle,
+ out PublicKey? publicKey)
+ {
+ publicKey = null;
+
+ return format switch
+ {
+ KeyBlobFormat.RawSymmetricKey => RawKeyFormatter.TryImport(crypto_aead_aegis256_KEYBYTES, blob, out keyHandle),
+ KeyBlobFormat.NSecSymmetricKey => NSecKeyFormatter.TryImport(NSecBlobHeader, crypto_aead_aegis256_KEYBYTES, crypto_aead_aegis256_ABYTES, blob, out keyHandle),
+ _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
+ };
+ }
+
+ private static void SelfTest()
+ {
+ if ((crypto_aead_aegis256_abytes() != crypto_aead_aegis256_ABYTES) ||
+ (crypto_aead_aegis256_keybytes() != crypto_aead_aegis256_KEYBYTES) ||
+ (crypto_aead_aegis256_npubbytes() != crypto_aead_aegis256_NPUBBYTES) ||
+ (crypto_aead_aegis256_nsecbytes() != crypto_aead_aegis256_NSECBYTES))
+ {
+ throw Error.InvalidOperation_InitializationFailed();
+ }
+ }
+ }
+}
diff --git a/src/Cryptography/Ed25519.cs b/src/Cryptography/Ed25519.cs
index dc7fac40..795e713a 100644
--- a/src/Cryptography/Ed25519.cs
+++ b/src/Cryptography/Ed25519.cs
@@ -10,7 +10,8 @@ namespace NSec.Cryptography
//
// Ed25519
//
- // Digital Signature Algorithm (EdDSA) based on the edwards25519 curve
+ // Digital Signature Algorithm based on the edwards25519 curve in
+ // pure mode (PureEdDSA)
//
// References:
//
diff --git a/src/Cryptography/Ed25519ph.cs b/src/Cryptography/Ed25519ph.cs
new file mode 100644
index 00000000..2f3c4aa1
--- /dev/null
+++ b/src/Cryptography/Ed25519ph.cs
@@ -0,0 +1,296 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using NSec.Cryptography.Formatting;
+using static Interop.Libsodium;
+
+namespace NSec.Cryptography
+{
+ //
+ // Ed25519ph
+ //
+ // Digital Signature Algorithm based on the edwards25519 curve in
+ // pre-hash mode (HashEdDSA)
+ //
+ // References:
+ //
+ // RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)
+ //
+ // Parameters:
+ //
+ // Private Key Size - The private key is 32 bytes (256 bits). However,
+ // the libsodium representation of a private key is 64 bytes. We
+ // expose private keys as 32-byte byte strings and internally
+ // convert from/to the libsodium format as necessary.
+ //
+ // Public Key Size - 32 bytes.
+ //
+ // Signature Size - 64 bytes.
+ //
+ public sealed class Ed25519ph : SignatureAlgorithm2
+ {
+ private static readonly PrivateKeyFormatter s_nsecPrivateKeyFormatter = new Ed25519PrivateKeyFormatter(new byte[] { 0xDE, 0x64, 0x48, 0xDE, crypto_sign_ed25519_SEEDBYTES, 0, crypto_sign_ed25519_BYTES, 0 });
+
+ private static readonly PublicKeyFormatter s_nsecPublicKeyFormatter = new Ed25519PublicKeyFormatter(new byte[] { 0xDE, 0x65, 0x48, 0xDE, crypto_sign_ed25519_PUBLICKEYBYTES, 0, crypto_sign_ed25519_BYTES, 0 });
+
+ private static readonly PrivateKeyFormatter s_rawPrivateKeyFormatter = new Ed25519PrivateKeyFormatter(Array.Empty());
+
+ private static readonly PublicKeyFormatter s_rawPublicKeyFormatter = new Ed25519PublicKeyFormatter(Array.Empty());
+
+ private static int s_selfTest;
+
+ public Ed25519ph() : base(
+ privateKeySize: crypto_sign_ed25519_SEEDBYTES,
+ publicKeySize: crypto_sign_ed25519_PUBLICKEYBYTES,
+ signatureSize: crypto_sign_ed25519_BYTES)
+ {
+ if (s_selfTest == 0)
+ {
+ SelfTest();
+ Interlocked.Exchange(ref s_selfTest, 1);
+ }
+ }
+
+ internal override unsafe void CreateKey(
+ ReadOnlySpan seed,
+ out SecureMemoryHandle keyHandle,
+ out PublicKey? publicKey)
+ {
+ if (Unsafe.SizeOf() != crypto_sign_ed25519_PUBLICKEYBYTES)
+ {
+ throw Error.InvalidOperation_InternalError();
+ }
+
+ Debug.Assert(seed.Length == crypto_sign_ed25519_SEEDBYTES);
+
+ publicKey = new PublicKey(this);
+ keyHandle = SecureMemoryHandle.Create(crypto_sign_ed25519_SECRETKEYBYTES);
+
+ fixed (PublicKeyBytes* pk = publicKey)
+ fixed (byte* seed_ = seed)
+ {
+ int error = crypto_sign_ed25519_seed_keypair(pk, keyHandle, seed_);
+
+ Debug.Assert(error == 0);
+ }
+ }
+
+ internal override int GetSeedSize()
+ {
+ return crypto_sign_ed25519_SEEDBYTES;
+ }
+
+ private protected unsafe override void SignCore(
+ SecureMemoryHandle keyHandle,
+ ReadOnlySpan data,
+ Span signature)
+ {
+ Debug.Assert(keyHandle.Size == crypto_sign_ed25519_SECRETKEYBYTES);
+ Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES);
+
+ fixed (byte* sig = signature)
+ fixed (byte* m = data)
+ {
+ crypto_sign_ed25519ph_state state;
+
+ crypto_sign_ed25519ph_init(
+ &state);
+
+ crypto_sign_ed25519ph_update(
+ &state,
+ m,
+ (ulong)data.Length);
+
+ int error = crypto_sign_ed25519ph_final_create(
+ &state,
+ sig,
+ out ulong signatureLength,
+ keyHandle);
+
+ Debug.Assert(error == 0);
+ Debug.Assert((ulong)signature.Length == signatureLength);
+ }
+ }
+
+ private protected unsafe override bool VerifyCore(
+ in PublicKeyBytes publicKeyBytes,
+ ReadOnlySpan data,
+ ReadOnlySpan signature)
+ {
+ if (Unsafe.SizeOf() != crypto_sign_ed25519_PUBLICKEYBYTES)
+ {
+ throw Error.InvalidOperation_InternalError();
+ }
+
+ Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES);
+
+ fixed (byte* sig = signature)
+ fixed (byte* m = data)
+ fixed (PublicKeyBytes* pk = &publicKeyBytes)
+ {
+ crypto_sign_ed25519ph_state state;
+
+ crypto_sign_ed25519ph_init(
+ &state);
+
+ crypto_sign_ed25519ph_update(
+ &state,
+ m,
+ (ulong)data.Length);
+
+ int error = crypto_sign_ed25519ph_final_verify(
+ &state,
+ sig,
+ pk);
+
+ return error == 0;
+ }
+ }
+
+ internal unsafe override void InitializeCore(
+ out IncrementalSignatureState state)
+ {
+ fixed (crypto_sign_ed25519ph_state* state_ = &state.ed25519ph)
+ {
+ int error = crypto_sign_ed25519ph_init(state_);
+
+ Debug.Assert(error == 0);
+ }
+ }
+
+ internal unsafe override void UpdateCore(
+ ref IncrementalSignatureState state,
+ ReadOnlySpan data)
+ {
+ fixed (crypto_sign_ed25519ph_state* state_ = &state.ed25519ph)
+ fixed (byte* @in = data)
+ {
+ int error = crypto_sign_ed25519ph_update(
+ state_,
+ @in,
+ (ulong)data.Length);
+
+ Debug.Assert(error == 0);
+ }
+ }
+
+ internal unsafe override void FinalSignCore(
+ ref IncrementalSignatureState state,
+ SecureMemoryHandle keyHandle,
+ Span signature)
+ {
+ Debug.Assert(keyHandle.Size == crypto_sign_ed25519_SECRETKEYBYTES);
+ Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES);
+
+ fixed (crypto_sign_ed25519ph_state* state_ = &state.ed25519ph)
+ fixed (byte* sig = signature)
+ {
+ int error = crypto_sign_ed25519ph_final_create(
+ state_,
+ sig,
+ out ulong signatureLength,
+ keyHandle);
+
+ Debug.Assert(error == 0);
+ Debug.Assert((ulong)signature.Length == signatureLength);
+ }
+ }
+
+ internal unsafe override bool FinalVerifyCore(
+ ref IncrementalSignatureState state,
+ in PublicKeyBytes publicKeyBytes,
+ ReadOnlySpan signature)
+ {
+ if (Unsafe.SizeOf() != crypto_sign_ed25519_PUBLICKEYBYTES)
+ {
+ throw Error.InvalidOperation_InternalError();
+ }
+
+ Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES);
+
+ fixed (crypto_sign_ed25519ph_state* state_ = &state.ed25519ph)
+ fixed (byte* sig = signature)
+ fixed (PublicKeyBytes* pk = &publicKeyBytes)
+ {
+ int error = crypto_sign_ed25519ph_final_verify(
+ state_,
+ sig,
+ pk);
+
+ return error == 0;
+ }
+ }
+
+ internal override bool TryExportKey(
+ SecureMemoryHandle keyHandle,
+ KeyBlobFormat format,
+ Span blob,
+ out int blobSize)
+ {
+ return format switch
+ {
+ KeyBlobFormat.RawPrivateKey => s_rawPrivateKeyFormatter.TryExport(keyHandle, blob, out blobSize),
+ KeyBlobFormat.NSecPrivateKey => s_nsecPrivateKeyFormatter.TryExport(keyHandle, blob, out blobSize),
+ _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
+ };
+ }
+
+ internal override bool TryExportPublicKey(
+ PublicKey publicKey,
+ KeyBlobFormat format,
+ Span blob,
+ out int blobSize)
+ {
+ return format switch
+ {
+ KeyBlobFormat.RawPublicKey => s_rawPublicKeyFormatter.TryExport(in publicKey.GetPinnableReference(), blob, out blobSize),
+ KeyBlobFormat.NSecPublicKey => s_nsecPublicKeyFormatter.TryExport(in publicKey.GetPinnableReference(), blob, out blobSize),
+ _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
+ };
+ }
+
+ internal override bool TryImportKey(
+ ReadOnlySpan blob,
+ KeyBlobFormat format,
+ out SecureMemoryHandle? keyHandle,
+ out PublicKey? publicKey)
+ {
+ publicKey = new PublicKey(this);
+
+ return format switch
+ {
+ KeyBlobFormat.RawPrivateKey => s_rawPrivateKeyFormatter.TryImport(blob, out keyHandle, out publicKey.GetPinnableReference()),
+ KeyBlobFormat.NSecPrivateKey => s_nsecPrivateKeyFormatter.TryImport(blob, out keyHandle, out publicKey.GetPinnableReference()),
+ _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
+ };
+ }
+
+ internal override bool TryImportPublicKey(
+ ReadOnlySpan blob,
+ KeyBlobFormat format,
+ out PublicKey publicKey)
+ {
+ publicKey = new PublicKey(this);
+
+ return format switch
+ {
+ KeyBlobFormat.RawPublicKey => s_rawPublicKeyFormatter.TryImport(blob, out publicKey.GetPinnableReference()),
+ KeyBlobFormat.NSecPublicKey => s_nsecPublicKeyFormatter.TryImport(blob, out publicKey.GetPinnableReference()),
+ _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
+ };
+ }
+
+ private static void SelfTest()
+ {
+ if ((crypto_sign_ed25519_bytes() != crypto_sign_ed25519_BYTES) ||
+ (crypto_sign_ed25519_publickeybytes() != crypto_sign_ed25519_PUBLICKEYBYTES) ||
+ (crypto_sign_ed25519_secretkeybytes() != crypto_sign_ed25519_SECRETKEYBYTES) ||
+ (crypto_sign_ed25519_seedbytes() != crypto_sign_ed25519_SEEDBYTES) ||
+ (crypto_sign_ed25519ph_statebytes() != (nuint)Unsafe.SizeOf()))
+ {
+ throw Error.InvalidOperation_InitializationFailed();
+ }
+ }
+ }
+}
diff --git a/src/Cryptography/Error.cs b/src/Cryptography/Error.cs
index d988ac92..f12f3418 100644
--- a/src/Cryptography/Error.cs
+++ b/src/Cryptography/Error.cs
@@ -221,6 +221,12 @@ internal static ArgumentException Argument_SharedSecretLength(
return new ArgumentException(string.Format(ResourceManager.GetString(nameof(Argument_SharedSecretLength))!, arg0), paramName);
}
+ internal static ArgumentException Argument_SignatureKeyRequired(
+ string paramName)
+ {
+ return new ArgumentException(ResourceManager.GetString(nameof(Argument_SignatureKeyRequired)), paramName);
+ }
+
internal static ArgumentException Argument_SignatureLength(
string paramName,
object? arg0)
diff --git a/src/Cryptography/Error.resx b/src/Cryptography/Error.resx
index ad728a24..2b04a665 100644
--- a/src/Cryptography/Error.resx
+++ b/src/Cryptography/Error.resx
@@ -258,6 +258,9 @@
The length of the shared secret must be less than or equal to {0}.
+
+ Cannot use a key for an incremental signature that is not a signature key.
+
The signature must have a length of {0} bytes.
diff --git a/src/Cryptography/IncrementalSignature.cs b/src/Cryptography/IncrementalSignature.cs
new file mode 100644
index 00000000..a4523aa7
--- /dev/null
+++ b/src/Cryptography/IncrementalSignature.cs
@@ -0,0 +1,127 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace NSec.Cryptography
+{
+ [StructLayout(LayoutKind.Sequential)]
+ public readonly struct IncrementalSignature
+ {
+ private readonly IncrementalSignatureState _state;
+ private readonly SignatureAlgorithm2? _algorithm;
+ private readonly Key? _privateKey;
+
+ public SignatureAlgorithm2? Algorithm => _algorithm;
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static new bool Equals(
+ object? objA,
+ object? objB)
+ {
+ return object.Equals(objA, objB);
+ }
+
+ public static void Initialize(
+ Key privateKey,
+ out IncrementalSignature state)
+ {
+ if (privateKey == null)
+ {
+ throw Error.ArgumentNull_Key(nameof(privateKey));
+ }
+ if (privateKey.Algorithm is not SignatureAlgorithm2 algorithm)
+ {
+ throw Error.Argument_SignatureKeyRequired(nameof(privateKey));
+ }
+
+ state = default;
+ algorithm.InitializeCore(out Unsafe.AsRef(in state._state));
+ Unsafe.AsRef(in state._algorithm) = algorithm;
+ Unsafe.AsRef(in state._privateKey) = privateKey;
+ }
+
+ public static void Update(
+ ref IncrementalSignature state,
+ ReadOnlySpan data)
+ {
+ if (state._algorithm == null)
+ {
+ throw Error.InvalidOperation_UninitializedState();
+ }
+
+ state._algorithm.UpdateCore(ref Unsafe.AsRef(in state._state), data);
+ }
+
+ public static byte[] Finalize(
+ ref IncrementalSignature state)
+ {
+ if (state._algorithm == null || state._privateKey == null)
+ {
+ throw Error.InvalidOperation_UninitializedState();
+ }
+
+ try
+ {
+ byte[] signature = new byte[state._algorithm.SignatureSize];
+ state._algorithm.FinalSignCore(ref Unsafe.AsRef(in state._state), state._privateKey.Handle, signature);
+ return signature;
+ }
+ finally
+ {
+ Unsafe.AsRef(in state._algorithm) = null;
+ Unsafe.AsRef(in state._privateKey) = null;
+ }
+ }
+
+ public static void Finalize(
+ ref IncrementalSignature state,
+ Span signature)
+ {
+ if (state._algorithm == null || state._privateKey == null)
+ {
+ throw Error.InvalidOperation_UninitializedState();
+ }
+ if (signature.Length != state._algorithm.SignatureSize)
+ {
+ throw Error.Argument_SignatureLength(nameof(signature), state._algorithm.SignatureSize);
+ }
+
+ try
+ {
+ state._algorithm.FinalSignCore(ref Unsafe.AsRef(in state._state), state._privateKey.Handle, signature);
+ }
+ finally
+ {
+ Unsafe.AsRef(in state._algorithm) = null;
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static new bool ReferenceEquals(
+ object? objA,
+ object? objB)
+ {
+ return object.ReferenceEquals(objA, objB);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(
+ object? obj)
+ {
+ throw Error.NotSupported_Operation();
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode()
+ {
+ throw Error.NotSupported_Operation();
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString()
+ {
+ return typeof(IncrementalSignature).ToString();
+ }
+ }
+}
diff --git a/src/Cryptography/IncrementalSignatureState.cs b/src/Cryptography/IncrementalSignatureState.cs
new file mode 100644
index 00000000..b8eaf5d8
--- /dev/null
+++ b/src/Cryptography/IncrementalSignatureState.cs
@@ -0,0 +1,11 @@
+namespace NSec.Cryptography
+{
+ using System.Runtime.InteropServices;
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct IncrementalSignatureState
+ {
+ [FieldOffset(0)]
+ internal Interop.Libsodium.crypto_sign_ed25519ph_state ed25519ph;
+ }
+}
diff --git a/src/Cryptography/IncrementalSignatureVerification.cs b/src/Cryptography/IncrementalSignatureVerification.cs
new file mode 100644
index 00000000..fec4597f
--- /dev/null
+++ b/src/Cryptography/IncrementalSignatureVerification.cs
@@ -0,0 +1,104 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace NSec.Cryptography
+{
+ [StructLayout(LayoutKind.Sequential)]
+ public readonly struct IncrementalSignatureVerification
+ {
+ private readonly IncrementalSignatureState _state;
+ private readonly SignatureAlgorithm2? _algorithm;
+ private readonly PublicKey? _publicKey;
+
+ public SignatureAlgorithm2? Algorithm => _algorithm;
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static new bool Equals(
+ object? objA,
+ object? objB)
+ {
+ return object.Equals(objA, objB);
+ }
+
+ public static void Initialize(
+ PublicKey publicKey,
+ out IncrementalSignatureVerification state)
+ {
+ if (publicKey == null)
+ {
+ throw Error.ArgumentNull_Key(nameof(publicKey));
+ }
+ if (publicKey.Algorithm is not SignatureAlgorithm2 algorithm)
+ {
+ throw Error.Argument_SignatureKeyRequired(nameof(publicKey));
+ }
+
+ state = default;
+ algorithm.InitializeCore(out Unsafe.AsRef(in state._state));
+ Unsafe.AsRef(in state._algorithm) = algorithm;
+ Unsafe.AsRef(in state._publicKey) = publicKey;
+ }
+
+ public static void Update(
+ ref IncrementalSignatureVerification state,
+ ReadOnlySpan data)
+ {
+ if (state._algorithm == null)
+ {
+ throw Error.InvalidOperation_UninitializedState();
+ }
+
+ state._algorithm.UpdateCore(ref Unsafe.AsRef(in state._state), data);
+ }
+
+ public static bool FinalizeAndVerify(
+ ref IncrementalSignatureVerification state,
+ ReadOnlySpan signature)
+ {
+ if (state._algorithm == null || state._publicKey == null)
+ {
+ throw Error.InvalidOperation_UninitializedState();
+ }
+
+ try
+ {
+ return signature.Length == state._algorithm.SignatureSize &&
+ state._algorithm.FinalVerifyCore(ref Unsafe.AsRef(in state._state), state._publicKey.GetPinnableReference(), signature);
+ }
+ finally
+ {
+ Unsafe.AsRef(in state._algorithm) = null;
+ Unsafe.AsRef(in state._publicKey) = null;
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static new bool ReferenceEquals(
+ object? objA,
+ object? objB)
+ {
+ return object.ReferenceEquals(objA, objB);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(
+ object? obj)
+ {
+ throw Error.NotSupported_Operation();
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode()
+ {
+ throw Error.NotSupported_Operation();
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString()
+ {
+ return typeof(IncrementalSignatureVerification).ToString();
+ }
+ }
+}
diff --git a/src/Cryptography/NSec.Cryptography.csproj b/src/Cryptography/NSec.Cryptography.csproj
index 52138822..77bd7149 100644
--- a/src/Cryptography/NSec.Cryptography.csproj
+++ b/src/Cryptography/NSec.Cryptography.csproj
@@ -19,7 +19,7 @@ NSec.Cryptography.X25519
-
+
diff --git a/src/Cryptography/SignatureAlgorithm.cs b/src/Cryptography/SignatureAlgorithm.cs
index 0bd0a583..967a05b8 100644
--- a/src/Cryptography/SignatureAlgorithm.cs
+++ b/src/Cryptography/SignatureAlgorithm.cs
@@ -20,6 +20,7 @@ namespace NSec.Cryptography
public abstract class SignatureAlgorithm : Algorithm
{
private static Ed25519? s_Ed25519;
+ private static Ed25519ph? s_Ed25519ph;
private readonly int _privateKeySize;
private readonly int _publicKeySize;
@@ -53,6 +54,20 @@ public static Ed25519 Ed25519
}
}
+ public static Ed25519ph Ed25519ph
+ {
+ get
+ {
+ Ed25519ph? instance = s_Ed25519ph;
+ if (instance == null)
+ {
+ Interlocked.CompareExchange(ref s_Ed25519ph, new Ed25519ph(), null);
+ instance = s_Ed25519ph;
+ }
+ return instance;
+ }
+ }
+
public int PrivateKeySize => _privateKeySize;
public int PublicKeySize => _publicKeySize;
diff --git a/src/Cryptography/SignatureAlgorithm2.cs b/src/Cryptography/SignatureAlgorithm2.cs
new file mode 100644
index 00000000..851aa2ce
--- /dev/null
+++ b/src/Cryptography/SignatureAlgorithm2.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using static Interop.Libsodium;
+
+namespace NSec.Cryptography
+{
+ //
+ // A digital signature algorithm supporting the "init, update, final" interface
+ //
+ // Candidates
+ //
+ // | Algorithm | Reference | Key Size | Signature Size |
+ // | ----------- | ---------- | -------- | -------------- |
+ // | Ed25519ph | RFC 8032 | 32 | 64 |
+ // | Ed448ph | RFC 8032 | 57 | 114 |
+ //
+ public abstract class SignatureAlgorithm2 : SignatureAlgorithm
+ {
+ private protected SignatureAlgorithm2(
+ int privateKeySize,
+ int publicKeySize,
+ int signatureSize)
+ : base(privateKeySize, publicKeySize, signatureSize)
+ {
+ }
+
+ internal abstract void InitializeCore(
+ out IncrementalSignatureState state);
+
+ internal abstract void UpdateCore(
+ ref IncrementalSignatureState state,
+ ReadOnlySpan data);
+
+ internal abstract void FinalSignCore(
+ ref IncrementalSignatureState state,
+ SecureMemoryHandle keyHandle,
+ Span signature);
+
+ internal abstract bool FinalVerifyCore(
+ ref IncrementalSignatureState state,
+ in PublicKeyBytes publicKeyBytes,
+ ReadOnlySpan signature);
+ }
+}
diff --git a/src/Experimental/PasswordBased/PasswordBasedKeyExporter.cs b/src/Experimental/PasswordBased/PasswordBasedKeyExporter.cs
index 40f964e2..520a9d49 100644
--- a/src/Experimental/PasswordBased/PasswordBasedKeyExporter.cs
+++ b/src/Experimental/PasswordBased/PasswordBasedKeyExporter.cs
@@ -189,6 +189,16 @@ private static void ReadEncryptionParameters(
Read(ref reader, out nonce);
break;
+ case Aegis128L _:
+ Read(ref reader, 0x2004);
+ Read(ref reader, out nonce);
+ break;
+
+ case Aegis256 _:
+ Read(ref reader, 0x2005);
+ Read(ref reader, out nonce);
+ break;
+
default:
throw new NotSupportedException();
}
@@ -275,6 +285,16 @@ private static void WriteEncryptionParameters(
Write(ref writer, nonce);
break;
+ case Aegis128L _:
+ Write(ref writer, 0x2004);
+ Write(ref writer, nonce);
+ break;
+
+ case Aegis256 _:
+ Write(ref writer, 0x2005);
+ Write(ref writer, nonce);
+ break;
+
default:
throw new NotSupportedException();
}
diff --git a/src/Interop/Interop.Aead.Aegis128L.cs b/src/Interop/Interop.Aead.Aegis128L.cs
new file mode 100644
index 00000000..f19e9b33
--- /dev/null
+++ b/src/Interop/Interop.Aead.Aegis128L.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Libsodium
+ {
+ internal const int crypto_aead_aegis128l_ABYTES = 32;
+ internal const int crypto_aead_aegis128l_KEYBYTES = 16;
+ internal const int crypto_aead_aegis128l_NPUBBYTES = 16;
+ internal const int crypto_aead_aegis128l_NSECBYTES = 0;
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_aead_aegis128l_abytes();
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static unsafe extern int crypto_aead_aegis128l_decrypt(
+ byte* m,
+ out ulong mlen_p,
+ byte* nsec,
+ byte* c,
+ ulong clen,
+ byte* ad,
+ ulong adlen,
+ byte* npub,
+ SecureMemoryHandle k);
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static unsafe extern int crypto_aead_aegis128l_encrypt(
+ byte* c,
+ out ulong clen_p,
+ byte* m,
+ ulong mlen,
+ byte* ad,
+ ulong adlen,
+ byte* nsec,
+ byte* npub,
+ SecureMemoryHandle k);
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_aead_aegis128l_keybytes();
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_aead_aegis128l_npubbytes();
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_aead_aegis128l_nsecbytes();
+ }
+}
diff --git a/src/Interop/Interop.Aead.Aegis256.cs b/src/Interop/Interop.Aead.Aegis256.cs
new file mode 100644
index 00000000..4b22d0bb
--- /dev/null
+++ b/src/Interop/Interop.Aead.Aegis256.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Libsodium
+ {
+ internal const int crypto_aead_aegis256_ABYTES = 32;
+ internal const int crypto_aead_aegis256_KEYBYTES = 32;
+ internal const int crypto_aead_aegis256_NPUBBYTES = 32;
+ internal const int crypto_aead_aegis256_NSECBYTES = 0;
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_aead_aegis256_abytes();
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static unsafe extern int crypto_aead_aegis256_decrypt(
+ byte* m,
+ out ulong mlen_p,
+ byte* nsec,
+ byte* c,
+ ulong clen,
+ byte* ad,
+ ulong adlen,
+ byte* npub,
+ SecureMemoryHandle k);
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static unsafe extern int crypto_aead_aegis256_encrypt(
+ byte* c,
+ out ulong clen_p,
+ byte* m,
+ ulong mlen,
+ byte* ad,
+ ulong adlen,
+ byte* nsec,
+ byte* npub,
+ SecureMemoryHandle k);
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_aead_aegis256_keybytes();
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_aead_aegis256_npubbytes();
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_aead_aegis256_nsecbytes();
+ }
+}
diff --git a/src/Interop/Interop.Sign.Ed25519.cs b/src/Interop/Interop.Sign.Ed25519.cs
index 4a5afca2..e576e250 100644
--- a/src/Interop/Interop.Sign.Ed25519.cs
+++ b/src/Interop/Interop.Sign.Ed25519.cs
@@ -57,5 +57,36 @@ internal static unsafe extern int crypto_sign_ed25519_verify_detached(
byte* m,
ulong mlen,
PublicKeyBytes* pk);
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static unsafe extern int crypto_sign_ed25519ph_final_create(
+ crypto_sign_ed25519ph_state* state,
+ byte* sig,
+ out ulong siglen_p,
+ SecureMemoryHandle sk);
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static unsafe extern int crypto_sign_ed25519ph_final_verify(
+ crypto_sign_ed25519ph_state* state,
+ byte* sig,
+ PublicKeyBytes* pk);
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static unsafe extern int crypto_sign_ed25519ph_init(
+ crypto_sign_ed25519ph_state* state);
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern nuint crypto_sign_ed25519ph_statebytes();
+
+ [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
+ internal static unsafe extern int crypto_sign_ed25519ph_update(
+ crypto_sign_ed25519ph_state* state,
+ byte* m,
+ ulong mlen);
+
+ [StructLayout(LayoutKind.Explicit, Size = 208)]
+ internal struct crypto_sign_ed25519ph_state
+ {
+ }
}
}
diff --git a/src/Interop/Interop.Version.cs b/src/Interop/Interop.Version.cs
index 143f420e..2bc5c7d3 100644
--- a/src/Interop/Interop.Version.cs
+++ b/src/Interop/Interop.Version.cs
@@ -5,9 +5,9 @@ internal static partial class Interop
{
internal static partial class Libsodium
{
- internal const int SODIUM_LIBRARY_VERSION_MAJOR = 10;
- internal const int SODIUM_LIBRARY_VERSION_MINOR = 3;
- internal const string SODIUM_VERSION_STRING = "1.0.18";
+ internal const int SODIUM_LIBRARY_VERSION_MAJOR = 26;
+ internal const int SODIUM_LIBRARY_VERSION_MINOR = 1;
+ internal const string SODIUM_VERSION_STRING = "1.0.19";
[DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)]
internal static extern int sodium_library_version_major();
diff --git a/src/Interop/Interop.projitems b/src/Interop/Interop.projitems
index 20eb0f2f..dad831ee 100644
--- a/src/Interop/Interop.projitems
+++ b/src/Interop/Interop.projitems
@@ -5,6 +5,8 @@
true
+
+
diff --git a/src/Interop/Interop.yaml b/src/Interop/Interop.yaml
index 5d04e90c..fd4f6148 100644
--- a/src/Interop/Interop.yaml
+++ b/src/Interop/Interop.yaml
@@ -6,6 +6,36 @@ Interop.Core.cs:
- sodium_init
- sodium_set_misuse_handler
+Interop.Aead.Aegis128L.cs:
+ include: include/sodium/crypto_aead_aegis128l.h
+ constants:
+ - crypto_aead_aegis128l_ABYTES
+ - crypto_aead_aegis128l_KEYBYTES
+ - crypto_aead_aegis128l_NPUBBYTES
+ - crypto_aead_aegis128l_NSECBYTES
+ functions:
+ - crypto_aead_aegis128l_abytes
+ - crypto_aead_aegis128l_decrypt (out ulong mlen_p, SecureMemoryHandle k)
+ - crypto_aead_aegis128l_encrypt (out ulong clen_p, SecureMemoryHandle k)
+ - crypto_aead_aegis128l_keybytes
+ - crypto_aead_aegis128l_npubbytes
+ - crypto_aead_aegis128l_nsecbytes
+
+Interop.Aead.Aegis256.cs:
+ include: include/sodium/crypto_aead_aegis256.h
+ constants:
+ - crypto_aead_aegis256_ABYTES
+ - crypto_aead_aegis256_KEYBYTES
+ - crypto_aead_aegis256_NPUBBYTES
+ - crypto_aead_aegis256_NSECBYTES
+ functions:
+ - crypto_aead_aegis256_abytes
+ - crypto_aead_aegis256_decrypt (out ulong mlen_p, SecureMemoryHandle k)
+ - crypto_aead_aegis256_encrypt (out ulong clen_p, SecureMemoryHandle k)
+ - crypto_aead_aegis256_keybytes
+ - crypto_aead_aegis256_npubbytes
+ - crypto_aead_aegis256_nsecbytes
+
Interop.Aead.Aes256Gcm.cs:
include: include/sodium/crypto_aead_aes256gcm.h
constants:
@@ -288,11 +318,18 @@ Interop.Sign.Ed25519.cs:
- crypto_sign_ed25519_pk_to_curve25519 (PublicKeyBytes* curve25519_pk, PublicKeyBytes* ed25519_pk)
- crypto_sign_ed25519_publickeybytes
- crypto_sign_ed25519_secretkeybytes
- - crypto_sign_ed25519_sk_to_curve25519 (SecureMemoryHandle ed25519_sk)
- - crypto_sign_ed25519_sk_to_seed (SecureMemoryHandle sk)
- crypto_sign_ed25519_seed_keypair (PublicKeyBytes* pk, SecureMemoryHandle sk)
- crypto_sign_ed25519_seedbytes
+ - crypto_sign_ed25519_sk_to_curve25519 (SecureMemoryHandle ed25519_sk)
+ - crypto_sign_ed25519_sk_to_seed (SecureMemoryHandle sk)
- crypto_sign_ed25519_verify_detached (PublicKeyBytes* pk)
+ - crypto_sign_ed25519ph_final_create (out ulong siglen_p, SecureMemoryHandle sk)
+ - crypto_sign_ed25519ph_final_verify (PublicKeyBytes* pk)
+ - crypto_sign_ed25519ph_init
+ - crypto_sign_ed25519ph_statebytes
+ - crypto_sign_ed25519ph_update
+ structs:
+ - crypto_sign_ed25519ph_state
#Interop.Sign.Edwards25519Sha512Batch.cs:
# include: include/sodium/crypto_sign_edwards25519sha512batch.h
diff --git a/tests/Algorithms/Aegis128LTests.cs b/tests/Algorithms/Aegis128LTests.cs
new file mode 100644
index 00000000..f199c06e
--- /dev/null
+++ b/tests/Algorithms/Aegis128LTests.cs
@@ -0,0 +1,50 @@
+using System;
+using NSec.Cryptography;
+using Xunit;
+
+namespace NSec.Tests.Algorithms
+{
+ public static class Aegis128LTests
+ {
+ public static readonly TheoryData PlaintextLengths = Utilities.Primes;
+
+ #region Properties
+
+ [Fact]
+ public static void Properties()
+ {
+ var a = AeadAlgorithm.Aegis128L;
+
+ Assert.Equal(16, a.KeySize);
+ Assert.Equal(16, a.NonceSize);
+ Assert.Equal(32, a.TagSize);
+ }
+
+ #endregion
+
+ #region Encrypt/Decrypt
+
+ [Theory]
+ [MemberData(nameof(PlaintextLengths))]
+ public static void EncryptDecrypt(int length)
+ {
+ var a = AeadAlgorithm.Aegis128L;
+
+ using var k = new Key(a);
+ var n = Utilities.RandomBytes.Slice(0, a.NonceSize);
+ var ad = Utilities.RandomBytes.Slice(0, 100);
+
+ var expected = Utilities.RandomBytes.Slice(0, length).ToArray();
+
+ var ciphertext = a.Encrypt(k, n, ad, expected);
+ Assert.NotNull(ciphertext);
+ Assert.Equal(length + a.TagSize, ciphertext.Length);
+
+ var actual = a.Decrypt(k, n, ad, ciphertext);
+ Assert.NotNull(actual);
+ Assert.Equal(expected, actual);
+ }
+
+ #endregion
+ }
+}
diff --git a/tests/Algorithms/Aegis256Tests.cs b/tests/Algorithms/Aegis256Tests.cs
new file mode 100644
index 00000000..1659fab8
--- /dev/null
+++ b/tests/Algorithms/Aegis256Tests.cs
@@ -0,0 +1,50 @@
+using System;
+using NSec.Cryptography;
+using Xunit;
+
+namespace NSec.Tests.Algorithms
+{
+ public static class Aegis256Tests
+ {
+ public static readonly TheoryData PlaintextLengths = Utilities.Primes;
+
+ #region Properties
+
+ [Fact]
+ public static void Properties()
+ {
+ var a = AeadAlgorithm.Aegis256;
+
+ Assert.Equal(32, a.KeySize);
+ Assert.Equal(32, a.NonceSize);
+ Assert.Equal(32, a.TagSize);
+ }
+
+ #endregion
+
+ #region Encrypt/Decrypt
+
+ [Theory]
+ [MemberData(nameof(PlaintextLengths))]
+ public static void EncryptDecrypt(int length)
+ {
+ var a = AeadAlgorithm.Aegis256;
+
+ using var k = new Key(a);
+ var n = Utilities.RandomBytes.Slice(0, a.NonceSize);
+ var ad = Utilities.RandomBytes.Slice(0, 100);
+
+ var expected = Utilities.RandomBytes.Slice(0, length).ToArray();
+
+ var ciphertext = a.Encrypt(k, n, ad, expected);
+ Assert.NotNull(ciphertext);
+ Assert.Equal(length + a.TagSize, ciphertext.Length);
+
+ var actual = a.Decrypt(k, n, ad, ciphertext);
+ Assert.NotNull(actual);
+ Assert.Equal(expected, actual);
+ }
+
+ #endregion
+ }
+}
diff --git a/tests/Base/AeadAlgorithmTests.cs b/tests/Base/AeadAlgorithmTests.cs
index 7a724dda..7911cc17 100644
--- a/tests/Base/AeadAlgorithmTests.cs
+++ b/tests/Base/AeadAlgorithmTests.cs
@@ -17,7 +17,7 @@ public static class AeadAlgorithmTests
public static void Properties(AeadAlgorithm a)
{
Assert.True(a.KeySize > 0);
- Assert.InRange(a.NonceSize, 0, 24);
+ Assert.InRange(a.NonceSize, 0, 32);
Assert.InRange(a.TagSize, 0, 255);
}
diff --git a/tests/Base/IncrementalSignatureTests.cs b/tests/Base/IncrementalSignatureTests.cs
new file mode 100644
index 00000000..bf33da09
--- /dev/null
+++ b/tests/Base/IncrementalSignatureTests.cs
@@ -0,0 +1,156 @@
+using System;
+using NSec.Cryptography;
+using Xunit;
+
+namespace NSec.Tests.Base
+{
+ public static class IncrementalSignatureTests
+ {
+ public static readonly TheoryData IncrementalSignatureAlgorithms = Registry.IncrementalSignatureAlgorithms;
+
+ #region Initialize
+
+ [Fact]
+ public static void InitializeWithNullKey()
+ {
+ Assert.Throws("privateKey", () => IncrementalSignature.Initialize(null!, out _));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void InitializeWithWrongKey(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(SignatureAlgorithm.Ed25519);
+
+ Assert.Throws("privateKey", () => IncrementalSignature.Initialize(k, out var state));
+ }
+
+ #endregion
+
+ #region Finalize #1
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeInvalid(SignatureAlgorithm2 a)
+ {
+ var state = default(IncrementalSignature);
+
+ Assert.Throws(() => IncrementalSignature.Finalize(ref state));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeSuccess(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(a);
+ var state = default(IncrementalSignature);
+
+ Assert.Null(state.Algorithm);
+
+ IncrementalSignature.Initialize(k, out state);
+
+ Assert.Same(a, state.Algorithm);
+
+ IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(0, 100));
+ IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(100, 100));
+ IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(200, 100));
+
+ var actual = IncrementalSignature.Finalize(ref state);
+
+ Assert.Null(state.Algorithm);
+
+ var expected = a.Sign(k, Utilities.RandomBytes.Slice(0, 300));
+
+ Assert.Equal(expected, actual);
+ }
+
+ #endregion
+
+ #region Finalize #2
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeWithSpanInvalid(SignatureAlgorithm2 a)
+ {
+ var state = default(IncrementalSignature);
+
+ Assert.Throws(() => IncrementalSignature.Finalize(ref state, new byte[a.SignatureSize]));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeWithSpanTooSmall(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(a);
+
+ IncrementalSignature.Initialize(k, out var state);
+
+ Assert.Throws("signature", () => IncrementalSignature.Finalize(ref state, new byte[a.SignatureSize - 1]));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeWithSpanTooLarge(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(a);
+
+ IncrementalSignature.Initialize(k, out var state);
+
+ Assert.Throws("signature", () => IncrementalSignature.Finalize(ref state, new byte[a.SignatureSize + 1]));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeWithSpanSuccess(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(a);
+ var state = default(IncrementalSignature);
+
+ Assert.Null(state.Algorithm);
+
+ IncrementalSignature.Initialize(k, out state);
+
+ Assert.Same(a, state.Algorithm);
+
+ IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(0, 100));
+ IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(100, 100));
+ IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(200, 100));
+
+ var actual = new byte[a.SignatureSize];
+
+ IncrementalSignature.Finalize(ref state, actual);
+
+ Assert.Null(state.Algorithm);
+
+ var expected = a.Sign(k, Utilities.RandomBytes.Slice(0, 300));
+
+ Assert.Equal(expected, actual);
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeWithSpanSuccessNoUpdate(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(a);
+ var state = default(IncrementalSignature);
+
+ Assert.Null(state.Algorithm);
+
+ IncrementalSignature.Initialize(k, out state);
+
+ Assert.Same(a, state.Algorithm);
+
+ var actual = new byte[a.SignatureSize];
+
+ IncrementalSignature.Finalize(ref state, actual);
+
+ Assert.Null(state.Algorithm);
+
+ var expected = a.Sign(k, ReadOnlySpan.Empty);
+
+ Assert.Equal(expected, actual);
+ }
+
+ #endregion
+ }
+}
diff --git a/tests/Base/IncrementalSignatureVerificationTests.cs b/tests/Base/IncrementalSignatureVerificationTests.cs
new file mode 100644
index 00000000..f1bf7792
--- /dev/null
+++ b/tests/Base/IncrementalSignatureVerificationTests.cs
@@ -0,0 +1,69 @@
+using System;
+using NSec.Cryptography;
+using Xunit;
+
+namespace NSec.Tests.Base
+{
+ public static class IncrementalSignatureVerificationTests
+ {
+ public static readonly TheoryData IncrementalSignatureAlgorithms = Registry.IncrementalSignatureAlgorithms;
+
+ #region Initialize
+
+ [Fact]
+ public static void InitializeWithNullKey()
+ {
+ Assert.Throws("publicKey", () => IncrementalSignatureVerification.Initialize(null!, out _));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void InitializeWithWrongKey(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(SignatureAlgorithm.Ed25519);
+
+ Assert.Throws("publicKey", () => IncrementalSignatureVerification.Initialize(k.PublicKey, out var state));
+ }
+
+ #endregion
+
+ #region FinalizeAndVerify
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeAndVerifyInvalid(SignatureAlgorithm2 a)
+ {
+ var state = default(IncrementalSignatureVerification);
+
+ Assert.Throws(() => IncrementalSignatureVerification.FinalizeAndVerify(ref state, new byte[a.SignatureSize]));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeAndVerifyFail(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(a);
+
+ IncrementalSignatureVerification.Initialize(k.PublicKey, out var state);
+
+ Assert.False(IncrementalSignatureVerification.FinalizeAndVerify(ref state, new byte[a.SignatureSize]));
+
+ Assert.Null(state.Algorithm);
+ }
+
+ [Theory]
+ [MemberData(nameof(IncrementalSignatureAlgorithms))]
+ public static void FinalizeAndVerifySuccess(SignatureAlgorithm2 a)
+ {
+ using var k = new Key(a);
+
+ IncrementalSignatureVerification.Initialize(k.PublicKey, out var state);
+
+ Assert.True(IncrementalSignatureVerification.FinalizeAndVerify(ref state, a.Sign(k, ReadOnlySpan.Empty)));
+
+ Assert.Null(state.Algorithm);
+ }
+
+ #endregion
+ }
+}
diff --git a/tests/Examples/Incremental.cs b/tests/Examples/Incremental.cs
index e798a492..549fa201 100644
--- a/tests/Examples/Incremental.cs
+++ b/tests/Examples/Incremental.cs
@@ -77,5 +77,90 @@ public static void Mac()
Assert.Equal(algorithm.Mac(key, Encoding.UTF8.GetBytes(string.Concat(lines))), mac);
}
+
+ [Fact]
+ public static void Signature()
+ {
+ #region Incremental Signing
+
+ // define some data to be signed
+ var lines = new[]
+ {
+ "Luke Skywalker has returned to\n",
+ "his home planet of Tatooine in\n",
+ "an attempt to rescue his\n",
+ "friend Han Solo from the\n",
+ "clutches of the vile gangster\n",
+ "Jabba the Hutt.\n",
+ };
+
+ // select the Ed25519ph algorithm
+ var algorithm = SignatureAlgorithm.Ed25519ph;
+
+ // create a new key pair
+ using var key = Key.Create(algorithm);
+
+ // initialize the state with the private key
+ IncrementalSignature.Initialize(key, out var state);
+
+ // incrementally update the state with the data
+ foreach (var line in lines)
+ {
+ IncrementalSignature.Update(ref state, Encoding.UTF8.GetBytes(line));
+ }
+
+ // finalize the computation and get the result
+ var signature = IncrementalSignature.Finalize(ref state);
+
+ #endregion
+
+ Assert.Equal(algorithm.Sign(key, Encoding.UTF8.GetBytes(string.Concat(lines))), signature);
+ }
+
+ [Fact]
+ public static void SignatureVerification()
+ {
+ using var key = Key.Create(SignatureAlgorithm.Ed25519ph);
+
+ #region Incremental Signature Verification
+
+ // define some data to be verified
+ var lines = new[]
+ {
+ "Luke Skywalker has returned to\n",
+ "his home planet of Tatooine in\n",
+ "an attempt to rescue his\n",
+ "friend Han Solo from the\n",
+ "clutches of the vile gangster\n",
+ "Jabba the Hutt.\n",
+ };
+
+ // select the Ed25519ph algorithm
+ var algorithm = SignatureAlgorithm.Ed25519ph;
+
+ // obtain the public key
+ var publicKey = /*{*/key.PublicKey;/*}*/
+
+ // obtain the signature
+ var signature = /*{*/algorithm.Sign(key, Encoding.UTF8.GetBytes(string.Concat(lines)));/*}*/
+
+ // initialize the state with the public key
+ IncrementalSignatureVerification.Initialize(publicKey, out var state);
+
+ // incrementally update the state with the data
+ foreach (var line in lines)
+ {
+ IncrementalSignatureVerification.Update(ref state, Encoding.UTF8.GetBytes(line));
+ }
+
+ // verify the data using the signature
+ if (IncrementalSignatureVerification.FinalizeAndVerify(ref state, signature))
+ {
+ // verified!
+ /*{*//*}*/
+ }
+
+ #endregion
+ }
}
}
diff --git a/tests/Formatting/NSecTests.cs b/tests/Formatting/NSecTests.cs
index 12ae4be0..6d67a8f9 100644
--- a/tests/Formatting/NSecTests.cs
+++ b/tests/Formatting/NSecTests.cs
@@ -7,6 +7,8 @@ namespace NSec.Tests.Formatting
public static class NSecTests
{
[Theory]
+ [InlineData(typeof(Aegis128L), new byte[] { 0xDE, 0x61, 0x4A, 0xDE })]
+ [InlineData(typeof(Aegis256), new byte[] { 0xDE, 0x61, 0x4B, 0xDE })]
[InlineData(typeof(Aes256Gcm), new byte[] { 0xDE, 0x61, 0x44, 0xDE })]
[InlineData(typeof(ChaCha20Poly1305), new byte[] { 0xDE, 0x61, 0x43, 0xDE })]
public static void Aead(Type algorithmType, byte[] blobHeader)
@@ -43,6 +45,22 @@ public static void Ed25519Public()
Test(a, a.PrivateKeySize, KeyBlobFormat.RawPrivateKey, a.PublicKeySize, a.SignatureSize, KeyBlobFormat.NSecPublicKey, new byte[] { 0xDE, 0x65, 0x42, 0xDE });
}
+ [Fact]
+ public static void Ed25519phPrivate()
+ {
+ var a = SignatureAlgorithm.Ed25519ph;
+
+ Test(a, a.PrivateKeySize, KeyBlobFormat.RawPrivateKey, a.PrivateKeySize, a.SignatureSize, KeyBlobFormat.NSecPrivateKey, new byte[] { 0xDE, 0x64, 0x48, 0xDE });
+ }
+
+ [Fact]
+ public static void Ed25519phPublic()
+ {
+ var a = SignatureAlgorithm.Ed25519ph;
+
+ Test(a, a.PrivateKeySize, KeyBlobFormat.RawPrivateKey, a.PublicKeySize, a.SignatureSize, KeyBlobFormat.NSecPublicKey, new byte[] { 0xDE, 0x65, 0x48, 0xDE });
+ }
+
[Fact]
public static void X25519Private()
{
diff --git a/tests/Registry.cs b/tests/Registry.cs
index 04bbbc52..8fe26fd5 100644
--- a/tests/Registry.cs
+++ b/tests/Registry.cs
@@ -12,6 +12,8 @@ internal static class Registry
public static readonly TheoryData AeadAlgorithms = new()
{
+ AeadAlgorithm.Aegis128L,
+ AeadAlgorithm.Aegis256,
AeadAlgorithm.Aes256Gcm,
AeadAlgorithm.ChaCha20Poly1305,
AeadAlgorithm.XChaCha20Poly1305,
@@ -80,6 +82,12 @@ internal static class Registry
public static readonly TheoryData SignatureAlgorithms = new()
{
SignatureAlgorithm.Ed25519,
+ SignatureAlgorithm.Ed25519ph,
+ };
+
+ public static readonly TheoryData IncrementalSignatureAlgorithms = new()
+ {
+ SignatureAlgorithm.Ed25519ph,
};
#endregion
@@ -90,10 +98,13 @@ internal static class Registry
{
KeyAgreementAlgorithm.X25519,
SignatureAlgorithm.Ed25519,
+ SignatureAlgorithm.Ed25519ph,
};
public static readonly TheoryData SymmetricAlgorithms = new()
{
+ AeadAlgorithm.Aegis128L,
+ AeadAlgorithm.Aegis256,
AeadAlgorithm.Aes256Gcm,
AeadAlgorithm.ChaCha20Poly1305,
AeadAlgorithm.XChaCha20Poly1305,
@@ -140,6 +151,8 @@ internal static class Registry
{ SignatureAlgorithm.Ed25519, KeyBlobFormat.NSecPublicKey },
{ SignatureAlgorithm.Ed25519, KeyBlobFormat.PkixPublicKey },
{ SignatureAlgorithm.Ed25519, KeyBlobFormat.PkixPublicKeyText },
+ { SignatureAlgorithm.Ed25519ph, KeyBlobFormat.RawPublicKey },
+ { SignatureAlgorithm.Ed25519ph, KeyBlobFormat.NSecPublicKey },
};
public static readonly TheoryData PrivateKeyBlobFormats = new()
@@ -152,10 +165,16 @@ internal static class Registry
{ SignatureAlgorithm.Ed25519, KeyBlobFormat.NSecPrivateKey },
{ SignatureAlgorithm.Ed25519, KeyBlobFormat.PkixPrivateKey },
{ SignatureAlgorithm.Ed25519, KeyBlobFormat.PkixPrivateKeyText },
+ { SignatureAlgorithm.Ed25519ph, KeyBlobFormat.RawPrivateKey },
+ { SignatureAlgorithm.Ed25519ph, KeyBlobFormat.NSecPrivateKey },
};
public static readonly TheoryData SymmetricKeyBlobFormats = new()
{
+ { AeadAlgorithm.Aegis128L, KeyBlobFormat.RawSymmetricKey },
+ { AeadAlgorithm.Aegis128L, KeyBlobFormat.NSecSymmetricKey },
+ { AeadAlgorithm.Aegis256, KeyBlobFormat.RawSymmetricKey },
+ { AeadAlgorithm.Aegis256, KeyBlobFormat.NSecSymmetricKey },
{ AeadAlgorithm.Aes256Gcm, KeyBlobFormat.RawSymmetricKey },
{ AeadAlgorithm.Aes256Gcm, KeyBlobFormat.NSecSymmetricKey },
{ MacAlgorithm.Blake2b_128, KeyBlobFormat.RawSymmetricKey },
diff --git a/tests/Tests.csproj b/tests/Tests.csproj
index 0f36c842..bfaaf4ce 100644
--- a/tests/Tests.csproj
+++ b/tests/Tests.csproj
@@ -1,7 +1,7 @@
- net7.0;net6.0
+ net8.0;net7.0;net6.0
@@ -15,9 +15,9 @@
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers