<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.0" />

SphincsPlusEngine

abstract class SphincsPlusEngine
using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Macs; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; using System; namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus { internal abstract class SphincsPlusEngine { internal class Sha2Engine : SphincsPlusEngine { private HMac treeHMac; private Mgf1BytesGenerator mgf1; private byte[] hmacBuf; private IDigest msgDigest; private byte[] msgDigestBuf; private int bl; private IDigest sha256; private byte[] sha256Buf; private IMemoable msgMemo; private IMemoable sha256Memo; public Sha2Engine(bool robust, int n, uint w, uint d, int a, int k, uint h) : base(robust, n, w, d, a, k, h) { sha256 = new Sha256Digest(); sha256Buf = new byte[sha256.GetDigestSize()]; if (n == 16) { msgDigest = new Sha256Digest(); treeHMac = new HMac(new Sha256Digest()); mgf1 = new Mgf1BytesGenerator(new Sha256Digest()); bl = 64; } else { msgDigest = new Sha512Digest(); treeHMac = new HMac(new Sha512Digest()); mgf1 = new Mgf1BytesGenerator(new Sha512Digest()); bl = 128; } hmacBuf = new byte[treeHMac.GetMacSize()]; msgDigestBuf = new byte[msgDigest.GetDigestSize()]; } public override void Init(byte[] pkSeed) { byte[] input = new byte[bl]; msgDigest.BlockUpdate(pkSeed, 0, pkSeed.Length); msgDigest.BlockUpdate(input, 0, bl - N); msgMemo = ((IMemoable)msgDigest).Copy(); msgDigest.Reset(); sha256.BlockUpdate(pkSeed, 0, pkSeed.Length); sha256.BlockUpdate(input, 0, 64 - N); sha256Memo = ((IMemoable)sha256).Copy(); sha256.Reset(); } public override byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1) { byte[] array = CompressedAdrs(adrs); if (robust) m1 = Bitmask256(Arrays.Concatenate(pkSeed, array), m1); ((IMemoable)sha256).Reset(sha256Memo); sha256.BlockUpdate(array, 0, array.Length); sha256.BlockUpdate(m1, 0, m1.Length); sha256.DoFinal(sha256Buf, 0); return Arrays.CopyOfRange(sha256Buf, 0, N); } public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1) { byte[] array = CompressedAdrs(adrs); ((IMemoable)sha256).Reset(sha256Memo); sha256.BlockUpdate(array); if (robust) sha256.BlockUpdate(Bitmask256(Arrays.Concatenate(pkSeed, array), m1)); else sha256.BlockUpdate(m1); sha256.DoFinal(sha256Buf); m1.CopyFrom(sha256Buf); } public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output) { byte[] array = CompressedAdrs(adrs); ((IMemoable)msgDigest).Reset(msgMemo); msgDigest.BlockUpdate(array); if (robust) { byte[] array2 = Bitmask(Arrays.Concatenate(pkSeed, array), m1, m2); msgDigest.BlockUpdate(array2); } else { msgDigest.BlockUpdate(m1); msgDigest.BlockUpdate(m2); } msgDigest.DoFinal(msgDigestBuf); output.Slice(0, N).CopyFrom(msgDigestBuf); } public override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] msg, int msgOff, int msgLen) { int num = (A * K + 7) / 8; uint num2 = FH / D; uint num3 = FH - num2; uint num4 = (num2 + 7) / 8; uint num5 = (num3 + 7) / 8; int num6 = num + (int)num5 + (int)num4; byte[] array = new byte[msgDigest.GetDigestSize()]; msgDigest.BlockUpdate(prf, 0, prf.Length); msgDigest.BlockUpdate(pkSeed, 0, pkSeed.Length); msgDigest.BlockUpdate(pkRoot, 0, pkRoot.Length); msgDigest.BlockUpdate(msg, msgOff, msgLen); msgDigest.DoFinal(array, 0); byte[] m = new byte[num6]; m = Bitmask(Arrays.ConcatenateAll(prf, pkSeed, array), m); ulong idx_tree = Pack.BE_To_UInt64_Low(m, num, (int)num5) & (ulong.MaxValue >> (int)(64 - num3)); uint idx_leaf = Pack.BE_To_UInt32_Low(m, num + (int)num5, (int)num4) & (uint.MaxValue >> (int)(32 - num2)); return new IndexedDigest(idx_tree, idx_leaf, Arrays.CopyOfRange(m, 0, num)); } public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output) { byte[] array = CompressedAdrs(adrs); if (robust) m = Bitmask(Arrays.Concatenate(pkSeed, array), m); ((IMemoable)msgDigest).Reset(msgMemo); msgDigest.BlockUpdate(array, 0, array.Length); msgDigest.BlockUpdate(m, 0, m.Length); msgDigest.DoFinal(msgDigestBuf, 0); output.Slice(0, N).CopyFrom(msgDigestBuf); } public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff) { int length = skSeed.Length; ((IMemoable)sha256).Reset(sha256Memo); byte[] array = CompressedAdrs(adrs); sha256.BlockUpdate(array, 0, array.Length); sha256.BlockUpdate(skSeed, 0, skSeed.Length); sha256.DoFinal(sha256Buf, 0); Array.Copy(sha256Buf, 0, prf, prfOff, length); } public override byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] msg, int msgOff, int msgLen) { treeHMac.Init(new KeyParameter(prf)); treeHMac.BlockUpdate(randomiser, 0, randomiser.Length); treeHMac.BlockUpdate(msg, msgOff, msgLen); treeHMac.DoFinal(hmacBuf, 0); return Arrays.CopyOfRange(hmacBuf, 0, N); } private byte[] CompressedAdrs(Adrs adrs) { byte[] array = new byte[22]; Array.Copy(adrs.value, 3, array, 0, 1); Array.Copy(adrs.value, 8, array, 1, 8); Array.Copy(adrs.value, 19, array, 9, 1); Array.Copy(adrs.value, 20, array, 10, 12); return array; } protected byte[] Bitmask(byte[] key, byte[] m) { byte[] array = new byte[m.Length]; mgf1.Init(new MgfParameters(key)); mgf1.GenerateBytes(array, 0, array.Length); Bytes.XorTo(m.Length, m, array); return array; } protected byte[] Bitmask(byte[] key, byte[] m1, byte[] m2) { byte[] array = new byte[m1.Length + m2.Length]; mgf1.Init(new MgfParameters(key)); mgf1.GenerateBytes(array, 0, array.Length); Bytes.XorTo(m1.Length, m1, array); Bytes.XorTo(m2.Length, m2, 0, array, m1.Length); return array; } protected byte[] Bitmask256(byte[] key, ReadOnlySpan<byte> m) { byte[] array = new byte[m.Length]; Mgf1BytesGenerator mgf1BytesGenerator = new Mgf1BytesGenerator(new Sha256Digest()); mgf1BytesGenerator.Init(new MgfParameters(key)); mgf1BytesGenerator.GenerateBytes(array, 0, array.Length); Bytes.XorTo(m.Length, m, array); return array; } } internal class Shake256Engine : SphincsPlusEngine { private IXof treeDigest; private IXof maskDigest; public Shake256Engine(bool robust, int n, uint w, uint d, int a, int k, uint h) : base(robust, n, w, d, a, k, h) { treeDigest = new ShakeDigest(256); maskDigest = new ShakeDigest(256); } public override void Init(byte[] pkSeed) { } public override byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1) { byte[] array = m1; if (robust) array = Bitmask(pkSeed, adrs, m1); byte[] array2 = new byte[N]; treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length); treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length); treeDigest.BlockUpdate(array, 0, array.Length); treeDigest.OutputFinal(array2, 0, array2.Length); return array2; } public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1) { if (robust) Bitmask(pkSeed, adrs, m1); treeDigest.BlockUpdate(pkSeed); treeDigest.BlockUpdate(adrs.value); treeDigest.BlockUpdate(m1); treeDigest.OutputFinal(m1); } public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output) { treeDigest.BlockUpdate(pkSeed); treeDigest.BlockUpdate(adrs.value); if (robust) { byte[] array = Bitmask(pkSeed, adrs, m1, m2); treeDigest.BlockUpdate(array); } else { treeDigest.BlockUpdate(m1); treeDigest.BlockUpdate(m2); } treeDigest.OutputFinal(output.Slice(0, N)); } public override IndexedDigest H_msg(byte[] R, byte[] pkSeed, byte[] pkRoot, byte[] msg, int msgOff, int msgLen) { int num = (A * K + 7) / 8; uint num2 = FH / D; uint num3 = FH - num2; uint num4 = (num2 + 7) / 8; uint num5 = (num3 + 7) / 8; byte[] array = new byte[(int)(num + num5 + num4)]; treeDigest.BlockUpdate(R, 0, R.Length); treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length); treeDigest.BlockUpdate(pkRoot, 0, pkRoot.Length); treeDigest.BlockUpdate(msg, msgOff, msgLen); treeDigest.OutputFinal(array, 0, array.Length); ulong idx_tree = Pack.BE_To_UInt64_Low(array, num, (int)num5) & (ulong.MaxValue >> (int)(64 - num3)); uint idx_leaf = Pack.BE_To_UInt32_Low(array, num + (int)num5, (int)num4) & (uint.MaxValue >> (int)(32 - num2)); return new IndexedDigest(idx_tree, idx_leaf, Arrays.CopyOfRange(array, 0, num)); } public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output) { byte[] array = m; if (robust) array = Bitmask(pkSeed, adrs, m); treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length); treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length); treeDigest.BlockUpdate(array, 0, array.Length); treeDigest.OutputFinal(output.Slice(0, N)); } public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff) { treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length); treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length); treeDigest.BlockUpdate(skSeed, 0, skSeed.Length); treeDigest.OutputFinal(prf, prfOff, N); } public override byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] msg, int msgOff, int msgLen) { treeDigest.BlockUpdate(prf, 0, prf.Length); treeDigest.BlockUpdate(randomiser, 0, randomiser.Length); treeDigest.BlockUpdate(msg, msgOff, msgLen); byte[] array = new byte[N]; treeDigest.OutputFinal(array, 0, array.Length); return array; } protected byte[] Bitmask(byte[] pkSeed, Adrs adrs, byte[] m) { byte[] array = new byte[m.Length]; maskDigest.BlockUpdate(pkSeed, 0, pkSeed.Length); maskDigest.BlockUpdate(adrs.value, 0, adrs.value.Length); maskDigest.OutputFinal(array, 0, array.Length); Bytes.XorTo(m.Length, m, array); return array; } protected unsafe void Bitmask(ReadOnlySpan<byte> pkSeed, Adrs adrs, Span<byte> m) { int length = m.Length; Span<byte> span = new Span<byte>(stackalloc byte[(int)(uint)length], length); maskDigest.BlockUpdate(pkSeed); maskDigest.BlockUpdate(adrs.value); maskDigest.OutputFinal(span); Bytes.XorTo(m.Length, span, m); } protected byte[] Bitmask(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2) { byte[] array = new byte[m1.Length + m2.Length]; maskDigest.BlockUpdate(pkSeed, 0, pkSeed.Length); maskDigest.BlockUpdate(adrs.value, 0, adrs.value.Length); maskDigest.OutputFinal(array, 0, array.Length); Bytes.XorTo(m1.Length, m1, array); Bytes.XorTo(m2.Length, m2, 0, array, m1.Length); return array; } } internal class HarakaSEngine : SphincsPlusEngine { public HarakaSXof harakaSXof; public HarakaS256Digest harakaS256Digest; public HarakaS512Digest harakaS512Digest; public HarakaSEngine(bool robust, int n, uint w, uint d, int a, int k, uint h) : base(robust, n, w, d, a, k, h) { } public override void Init(byte[] pkSeed) { harakaSXof = new HarakaSXof(pkSeed); harakaS256Digest = new HarakaS256Digest(harakaSXof); harakaS512Digest = new HarakaS512Digest(harakaSXof); } public override byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1) { byte[] array = new byte[32]; harakaS512Digest.BlockUpdate(adrs.value, 0, adrs.value.Length); if (robust) { harakaS256Digest.BlockUpdate(adrs.value, 0, adrs.value.Length); harakaS256Digest.DoFinal(array, 0); Bytes.XorTo(m1.Length, m1, array); harakaS512Digest.BlockUpdate(array, 0, m1.Length); } else harakaS512Digest.BlockUpdate(m1, 0, m1.Length); harakaS512Digest.DoFinal(array, 0); if (N != 32) return Arrays.CopyOfRange(array, 0, N); return array; } public unsafe override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1) { Span<byte> span = new Span<byte>(stackalloc byte[32], 32); if (robust) { harakaS256Digest.BlockUpdate(adrs.value); harakaS256Digest.DoFinal(span); Bytes.XorTo(m1.Length, span, m1); } harakaS512Digest.BlockUpdate(adrs.value); harakaS512Digest.BlockUpdate(m1); harakaS512Digest.DoFinal(span); m1.CopyFrom(span); } public unsafe override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output) { int num = m1.Length + m2.Length; Span<byte> span = new Span<byte>(stackalloc byte[(int)(uint)num], num); m1.CopyTo(span); num = m1.Length; m2.CopyTo(span.Slice(num, span.Length - num)); if (robust) Bitmask(adrs, span); harakaSXof.BlockUpdate(adrs.value); harakaSXof.BlockUpdate(span); harakaSXof.OutputFinal(output.Slice(0, N)); } public override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] msg, int msgOff, int msgLen) { int num = A * K + 7 >> 3; uint num2 = FH / D; uint num3 = FH - num2; uint num4 = num2 + 7 >> 3; uint num5 = num3 + 7 >> 3; byte[] array = new byte[num + num5 + num4]; harakaSXof.BlockUpdate(prf, 0, prf.Length); harakaSXof.BlockUpdate(pkRoot, 0, pkRoot.Length); harakaSXof.BlockUpdate(msg, msgOff, msgLen); harakaSXof.OutputFinal(array, 0, array.Length); ulong idx_tree = Pack.BE_To_UInt64_Low(array, num, (int)num5) & (ulong.MaxValue >> (int)(64 - num3)); uint idx_leaf = Pack.BE_To_UInt32_Low(array, num + (int)num5, (int)num4) & (uint.MaxValue >> (int)(32 - num2)); return new IndexedDigest(idx_tree, idx_leaf, Arrays.CopyOfRange(array, 0, num)); } public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output) { if (robust) Bitmask(adrs, m); harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length); harakaSXof.BlockUpdate(m, 0, m.Length); harakaSXof.OutputFinal(output.Slice(0, N)); } public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff) { byte[] array = new byte[32]; harakaS512Digest.BlockUpdate(adrs.value, 0, adrs.value.Length); harakaS512Digest.BlockUpdate(skSeed, 0, skSeed.Length); harakaS512Digest.DoFinal(array, 0); Array.Copy(array, 0, prf, prfOff, N); } public override byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] msg, int msgOff, int msgLen) { byte[] array = new byte[N]; harakaSXof.BlockUpdate(prf, 0, prf.Length); harakaSXof.BlockUpdate(randomiser, 0, randomiser.Length); harakaSXof.BlockUpdate(msg, msgOff, msgLen); harakaSXof.OutputFinal(array, 0, array.Length); return array; } protected void Bitmask(Adrs adrs, byte[] m) { byte[] array = new byte[m.Length]; harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length); harakaSXof.OutputFinal(array, 0, array.Length); Bytes.XorTo(m.Length, array, m); } protected unsafe void Bitmask(Adrs adrs, Span<byte> m) { int length = m.Length; Span<byte> span = new Span<byte>(stackalloc byte[(int)(uint)length], length); harakaSXof.BlockUpdate(adrs.value); harakaSXof.OutputFinal(span); Bytes.XorTo(m.Length, span, m); } } internal class HarakaSEngine_X86 : SphincsPlusEngine { private HarakaS_X86 m_harakaS; public static bool IsSupported { get { if (Haraka256_X86.IsSupported && Haraka512_X86.IsSupported) return HarakaS_X86.IsSupported; return false; } } public HarakaSEngine_X86(bool robust, int n, uint w, uint d, int a, int k, uint h) : base(robust, n, w, d, a, k, h) { } public override void Init(byte[] pkSeed) { m_harakaS = new HarakaS_X86(pkSeed); } public unsafe override byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1) { Span<byte> span = new Span<byte>(stackalloc byte[64], 64); adrs.value.CopyTo(span); if (robust) { Span<byte> span2 = new Span<byte>(stackalloc byte[32], 32); Haraka256_X86.Hash(adrs.value, span2, m_harakaS.RoundConstants); Bytes.Xor(m1.Length, m1, span2, span.Slice(32, span.Length - 32)); } else m1.CopyTo(span.Slice(32, span.Length - 32)); Haraka512_X86.Hash(span, span, m_harakaS.RoundConstants); return span.Slice(0, N).ToArray(); } public unsafe override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1) { Span<byte> span = new Span<byte>(stackalloc byte[64], 64); adrs.value.CopyTo(span); if (robust) { Span<byte> span2 = new Span<byte>(stackalloc byte[32], 32); Haraka256_X86.Hash(adrs.value, span2, m_harakaS.RoundConstants); Bytes.Xor(m1.Length, m1, span2, span.Slice(32, span.Length - 32)); } else m1.CopyTo(span.Slice(32, span.Length - 32)); Haraka512_X86.Hash(span, span, m_harakaS.RoundConstants); m1.CopyFrom(span); } public unsafe override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output) { int num = m1.Length + m2.Length; Span<byte> span = new Span<byte>(stackalloc byte[(int)(uint)num], num); m1.CopyTo(span); num = m1.Length; m2.CopyTo(span.Slice(num, span.Length - num)); if (robust) Bitmask(adrs, span); m_harakaS.BlockUpdate(adrs.value); m_harakaS.BlockUpdate(span); m_harakaS.OutputFinal(output.Slice(0, N)); } public unsafe override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] msg, int msgOff, int msgLen) { int num = A * K + 7 >> 3; int num2 = (int)(FH / D); int num3 = (int)FH - num2; int num4 = num2 + 7 >> 3; int num5 = num3 + 7 >> 3; byte[] array = new byte[num]; int num6 = num5 + num4; Span<byte> output = new Span<byte>(stackalloc byte[(int)(uint)num6], num6); m_harakaS.BlockUpdate(prf); m_harakaS.BlockUpdate(pkRoot); m_harakaS.BlockUpdate(msg.AsSpan(msgOff, msgLen)); m_harakaS.Output(array); m_harakaS.OutputFinal(output); ulong idx_tree = Pack.BE_To_UInt64_Low(output.Slice(0, num5)) & (ulong.MaxValue >> 64 - num3); num6 = num5; uint idx_leaf = Pack.BE_To_UInt32_Low(output.Slice(num6, output.Length - num6)) & (uint.MaxValue >> 32 - num2); return new IndexedDigest(idx_tree, idx_leaf, array); } public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output) { if (robust) Bitmask(adrs, m); m_harakaS.BlockUpdate(adrs.value); m_harakaS.BlockUpdate(m); m_harakaS.OutputFinal(output.Slice(0, N)); } public unsafe override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff) { Span<byte> span = new Span<byte>(stackalloc byte[64], 64); adrs.value.CopyTo(span); skSeed.CopyTo(span.Slice(32, span.Length - 32)); Haraka512_X86.Hash(span, span, m_harakaS.RoundConstants); span.Slice(0, N).CopyTo(prf.AsSpan(prfOff)); } public override byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] msg, int msgOff, int msgLen) { byte[] array = new byte[N]; m_harakaS.BlockUpdate(prf); m_harakaS.BlockUpdate(randomiser); m_harakaS.BlockUpdate(msg.AsSpan(msgOff, msgLen)); m_harakaS.OutputFinal(array); return array; } protected unsafe void Bitmask(Adrs adrs, Span<byte> m) { int length = m.Length; Span<byte> span = new Span<byte>(stackalloc byte[(int)(uint)length], length); m_harakaS.BlockUpdate(adrs.value); m_harakaS.OutputFinal(span); Bytes.XorTo(m.Length, span, m); } } internal readonly bool robust; internal readonly int N; internal readonly uint WOTS_W; internal readonly int WOTS_LOGW; internal readonly int WOTS_LEN; internal readonly int WOTS_LEN1; internal readonly int WOTS_LEN2; internal readonly uint D; internal readonly int A; internal readonly int K; internal readonly uint FH; internal readonly uint H_PRIME; internal readonly uint T; internal readonly int SignatureLength; internal SphincsPlusEngine(bool robust, int n, uint w, uint d, int a, int k, uint h) { N = n; switch (w) { case 16: WOTS_LOGW = 4; WOTS_LEN1 = 8 * N / WOTS_LOGW; if (N <= 8) WOTS_LEN2 = 2; else if (N <= 136) { WOTS_LEN2 = 3; } else { if (N > 256) throw new ArgumentException("cannot precompute SPX_WOTS_LEN2 for n outside {2, .., 256}"); WOTS_LEN2 = 4; } break; case 256: WOTS_LOGW = 8; WOTS_LEN1 = 8 * N / WOTS_LOGW; if (N <= 1) WOTS_LEN2 = 1; else { if (N > 256) throw new ArgumentException("cannot precompute SPX_WOTS_LEN2 for n outside {2, .., 256}"); WOTS_LEN2 = 2; } break; default: throw new ArgumentException("wots_w assumed 16 or 256"); } WOTS_W = w; WOTS_LEN = WOTS_LEN1 + WOTS_LEN2; this.robust = robust; D = d; A = a; K = k; FH = h; H_PRIME = h / d; T = (uint)(1 << a); SignatureLength = (1 + K * (1 + A) + (int)FH + (int)D * WOTS_LEN) * N; } public abstract void Init(byte[] pkSeed); public abstract byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1); public abstract void F(byte[] pkSeed, Adrs adrs, Span<byte> m1); public abstract void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output); public abstract IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] msg, int msgOff, int msgLen); public abstract void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output); public abstract void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff); public abstract byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] msg, int msgOff, int msgLen); } }