Browse Source

Implement rest based interactions. Added ED25519 checks. Updated summaries.

pull/1923/head
quin lynch 3 years ago
parent
commit
32f5661a94
100 changed files with 7423 additions and 54 deletions
  1. +4
    -3
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs
  2. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
  3. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs
  4. +6
    -8
      src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs
  5. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs
  6. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs
  7. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs
  8. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs
  9. +5
    -5
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
  10. +4
    -4
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs
  11. +6
    -6
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs
  12. +11
    -11
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
  13. +2
    -2
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs
  14. +6
    -10
      src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
  15. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs
  16. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs
  17. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs
  18. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
  19. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs
  20. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs
  21. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs
  22. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
  23. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs
  24. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
  25. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs
  26. +72
    -2
      src/Discord.Net.Rest/DiscordRestClient.cs
  27. +354
    -0
      src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
  28. +59
    -0
      src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs
  29. +92
    -0
      src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs
  30. +43
    -0
      src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs
  31. +38
    -0
      src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs
  32. +46
    -0
      src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs
  33. +36
    -0
      src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs
  34. +447
    -0
      src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
  35. +37
    -0
      src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs
  36. +222
    -0
      src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
  37. +43
    -0
      src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs
  38. +134
    -0
      src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs
  39. +76
    -0
      src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteractionData.cs
  40. +44
    -0
      src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs
  41. +32
    -0
      src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs
  42. +139
    -0
      src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs
  43. +16
    -0
      src/Discord.Net.Rest/Net/BadSignatureException.cs
  44. +1
    -1
      src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs
  45. +27
    -0
      src/Discord.Net.Rest/Net/ED25519/Array16.cs
  46. +18
    -0
      src/Discord.Net.Rest/Net/ED25519/Array8.cs
  47. +55
    -0
      src/Discord.Net.Rest/Net/ED25519/ByteIntegerConverter.cs
  48. +272
    -0
      src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs
  49. +67
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519.cs
  50. +45
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Operations.cs
  51. +23
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/FieldElement.cs
  52. +63
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/GroupElement.cs
  53. +1355
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base.cs
  54. +50
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base2.cs
  55. +9
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d.cs
  56. +9
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d2.cs
  57. +12
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_0.cs
  58. +13
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_1.cs
  59. +64
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_add.cs
  60. +71
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cmov.cs
  61. +79
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cswap.cs
  62. +102
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_frombytes.cs
  63. +128
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_invert.cs
  64. +22
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_isnegative.cs
  65. +37
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_isnonzero.cs
  66. +263
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul.cs
  67. +67
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul121666.cs
  68. +51
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_neg.cs
  69. +125
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_pow22523.cs
  70. +143
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq.cs
  71. +154
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq2.cs
  72. +66
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sub.cs
  73. +145
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_tobytes.cs
  74. +73
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_add.cs
  75. +115
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_double_scalarmult.cs
  76. +50
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_frombytes.cs
  77. +69
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_madd.cs
  78. +68
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_msub.cs
  79. +18
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p1p1_to_p2.cs
  80. +18
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p1p1_to_p3.cs
  81. +14
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p2_0.cs
  82. +64
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p2_dbl.cs
  83. +15
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_0.cs
  84. +17
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_dbl.cs
  85. +18
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_to_cached.cs
  86. +17
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_to_p2.cs
  87. +19
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_tobytes.cs
  88. +14
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_precomp_0.cs
  89. +113
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_scalarmult_base.cs
  90. +74
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_sub.cs
  91. +19
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_tobytes.cs
  92. +23
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/keypair.cs
  93. +80
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/open.cs
  94. +14
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_clamp.cs
  95. +374
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_mul_add.cs
  96. +264
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_reduce.cs
  97. +153
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/scalarmult.cs
  98. +44
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sign.cs
  99. +9
    -0
      src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sqrtm1.cs
  100. +155
    -0
      src/Discord.Net.Rest/Net/ED25519/Sha512.cs

+ 4
- 3
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs View File

@@ -9,8 +9,9 @@ namespace Discord
{
private string _name;
private object _value;

/// <summary>
/// The name of this choice.
/// Gets the name of this choice.
/// </summary>
public string Name
{
@@ -24,9 +25,9 @@ namespace Discord
}

/// <summary>
/// The value of this choice.
/// Gets the value of this choice.
/// <note type="warning">
/// Discord only accepts int and string as the input.
/// Discord only accepts int, double/floats, and string as the input.
/// </note>
/// </summary>
public object Value


+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs View File

@@ -13,7 +13,7 @@ namespace Discord
public Optional<string> Name { get; set; }

/// <summary>
/// Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>
/// Gets whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>.
/// </summary>
public Optional<bool> DefaultPermission { get; set; }



+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs View File

@@ -6,7 +6,7 @@ namespace Discord
public class AutocompleteOption
{
/// <summary>
/// Gets the type of this option
/// Gets the type of this option.
/// </summary>
public ApplicationCommandOptionType Type { get; }



+ 6
- 8
src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs View File

@@ -45,15 +45,13 @@ namespace Discord
public object Value
{
get => _value;
set => _value = value switch
set
{
string str => str,
int integer => integer,
long lng => lng,
double number => number,
null => throw new ArgumentNullException(nameof(value), $"{nameof(Value)} cannot be null."),
_ => throw new ArgumentException($"Type {value.GetType().Name} cannot be set as a value! Only string, int, and double allowed!")
};
if (value is not string || !value.IsNumericType())
throw new ArgumentException($"{nameof(value)} must be a numeric type or a string!");

_value = value;
}
}

/// <summary>


src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs → src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs View File


src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs → src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs View File


src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs → src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs View File


src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs → src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs View File


+ 5
- 5
src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs View File

@@ -15,27 +15,27 @@ namespace Discord
ulong ApplicationId { get; }

/// <summary>
/// The type of the command.
/// Gets the type of the command.
/// </summary>
ApplicationCommandType Type { get; }

/// <summary>
/// The name of the command.
/// Gets the name of the command.
/// </summary>
string Name { get; }

/// <summary>
/// The description of the command.
/// Gets the description of the command.
/// </summary>
string Description { get; }

/// <summary>
/// Whether the command is enabled by default when the app is added to a guild.
/// Gets whether the command is enabled by default when the app is added to a guild.
/// </summary>
bool IsDefaultPermission { get; }

/// <summary>
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
/// Gets a collection of options for this application command.
/// </summary>
IReadOnlyCollection<IApplicationCommandOption> Options { get; }



+ 4
- 4
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs View File

@@ -5,20 +5,20 @@ namespace Discord
/// <summary>
/// Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/>.
/// </summary>
public interface IApplicationCommandInteractionData
public interface IApplicationCommandInteractionData : IDiscordInteractionData
{
/// <summary>
/// The snowflake id of this command.
/// Gets the snowflake id of this command.
/// </summary>
ulong Id { get; }

/// <summary>
/// The name of this command.
/// Gets the name of this command.
/// </summary>
string Name { get; }

/// <summary>
/// The params + values from the user.
/// Gets the params + values from the user.
/// </summary>
IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; }
}


+ 6
- 6
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs View File

@@ -3,30 +3,30 @@ using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Represents a option group for a command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondataoption"/>.
/// Represents a option group for a command.
/// </summary>
public interface IApplicationCommandInteractionDataOption
{
/// <summary>
/// The name of the parameter.
/// Gets the name of the parameter.
/// </summary>
string Name { get; }

/// <summary>
/// The value of the pair.
/// Gets the value of the pair.
/// <note>
/// This objects type can be any one of the option types in <see cref="ApplicationCommandOptionType"/>
/// This objects type can be any one of the option types in <see cref="ApplicationCommandOptionType"/>.
/// </note>
/// </summary>
object Value { get; }

/// <summary>
/// The type of this data's option.
/// Gets the type of this data's option.
/// </summary>
ApplicationCommandOptionType Type { get; }

/// <summary>
/// Present if this option is a group or subcommand.
/// Gets the options for this command.
/// </summary>
IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; }
}


+ 11
- 11
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs View File

@@ -3,57 +3,57 @@ using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Options for the <see cref="IApplicationCommand"/>, see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoption">The docs</see>.
/// Options for the <see cref="IApplicationCommand"/>.
/// </summary>
public interface IApplicationCommandOption
{
/// <summary>
/// The type of this <see cref="IApplicationCommandOption"/>.
/// Gets the type of this <see cref="IApplicationCommandOption"/>.
/// </summary>
ApplicationCommandOptionType Type { get; }

/// <summary>
/// The name of this command option, 1-32 character name.
/// Gets the name of this command option.
/// </summary>
string Name { get; }

/// <summary>
/// The description of this command option, 1-100 character description.
/// Gets the description of this command option.
/// </summary>
string Description { get; }

/// <summary>
/// The first required option for the user to complete--only one option can be default.
/// Gets whether or not this is the first required option for the user to complete.
/// </summary>
bool? IsDefault { get; }

/// <summary>
/// If the parameter is required or optional, default is <see langword="false"/>.
/// Gets whether or not the parameter is required or optional.
/// </summary>
bool? IsRequired { get; }

/// <summary>
/// The smallest number value the user can input.
/// Gets the smallest number value the user can input.
/// </summary>
double? MinValue { get; }

/// <summary>
/// The largest number value the user can input.
/// Gets the largest number value the user can input.
/// </summary>
double? MaxValue { get; }

/// <summary>
/// Choices for string and int types for the user to pick from.
/// Gets the choices for string and int types for the user to pick from.
/// </summary>
IReadOnlyCollection<IApplicationCommandOptionChoice> Choices { get; }

/// <summary>
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
/// Gets the sub-options for this command option.
/// </summary>
IReadOnlyCollection<IApplicationCommandOption> Options { get; }

/// <summary>
/// The allowed channel types for this option.
/// Gets the allowed channel types for this option.
/// </summary>
IReadOnlyCollection<ChannelType> ChannelTypes { get; }
}


+ 2
- 2
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs View File

@@ -6,12 +6,12 @@ namespace Discord
public interface IApplicationCommandOptionChoice
{
/// <summary>
/// 1-100 character choice name.
/// Gets the choice name.
/// </summary>
string Name { get; }

/// <summary>
/// value of the choice.
/// Gets the value of the choice.
/// </summary>
object Value { get; }
}


+ 6
- 10
src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs View File

@@ -4,36 +4,32 @@ using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// Represents a discord interaction
/// <para>
/// An interaction is the base "thing" that is sent when a user invokes a command, and is the same for Slash Commands
/// and other future interaction types. see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction"/>.
/// </para>
/// Represents a discord interaction.
/// </summary>
public interface IDiscordInteraction : ISnowflakeEntity
{
/// <summary>
/// The id of the interaction.
/// Gets the id of the interaction.
/// </summary>
new ulong Id { get; }

/// <summary>
/// The type of this <see cref="IDiscordInteraction"/>.
/// Gets the type of this <see cref="IDiscordInteraction"/>.
/// </summary>
InteractionType Type { get; }

/// <summary>
/// Represents the data sent within this interaction.
/// Gets the data sent within this interaction.
/// </summary>
IDiscordInteractionData Data { get; }

/// <summary>
/// A continuation token for responding to the interaction.
/// Gets the continuation token for responding to the interaction.
/// </summary>
string Token { get; }

/// <summary>
/// read-only property, always 1.
/// Gets the version of the interaction, always 1.
/// </summary>
int Version { get; }



src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonStyle.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/IMessageComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs View File


src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandBuilder.cs → src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs View File


src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandProperties.cs → src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs View File


+ 72
- 2
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -1,8 +1,13 @@
//using Discord.Rest.Entities.Interactions;
using Discord.Net;
using Discord.Net.Converters;
using Discord.Net.ED25519;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Rest
@@ -14,12 +19,13 @@ namespace Discord.Rest
{
#region DiscordRestClient
private RestApplication _applicationInfo;
internal static JsonSerializer Serializer = new JsonSerializer() { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include };

/// <summary>
/// Gets the logged-in user.
/// </summary>
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; }
/// <inheritdoc />
public DiscordRestClient() : this(new DiscordRestConfig()) { }
/// <summary>
@@ -31,7 +37,7 @@ namespace Discord.Rest
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }

private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, useSystemClock: config.UseSystemClock);
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock);

internal override void Dispose(bool disposing)
{
@@ -60,6 +66,70 @@ namespace Discord.Rest
return Task.Delay(0);
}

#region Rest interactions

public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body)
=> IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body)
{
var key = HexConverter.HexToByteArray(publicKey);
var sig = HexConverter.HexToByteArray(signature);
var tsp = Encoding.UTF8.GetBytes(timestamp);

var message = new List<byte>();
message.AddRange(tsp);
message.AddRange(body);

return IsValidHttpInteraction(key, sig, message.ToArray());
}

private bool IsValidHttpInteraction(byte[] publicKey, byte[] signature, byte[] message)
{
return Ed25519.Verify(signature, message, publicKey);
}

/// <summary>
/// Creates a <see cref="RestInteraction"/> from a http message.
/// </summary>
/// <param name="publicKey">The public key of your application</param>
/// <param name="signature">The signature sent with the interaction.</param>
/// <param name="timestamp">The timestamp sent with the interaction.</param>
/// <param name="body">The body of the http message.</param>
/// <returns>
/// A <see cref="RestInteraction"/> that represents the incoming http interaction.
/// </returns>
/// <exception cref="BadSignatureException">Thrown when the signature doesn't match the public key.</exception>
public Task<RestInteraction> ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body)
=> ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));

/// <summary>
/// Creates a <see cref="RestInteraction"/> from a http message.
/// </summary>
/// <param name="publicKey">The public key of your application</param>
/// <param name="signature">The signature sent with the interaction.</param>
/// <param name="timestamp">The timestamp sent with the interaction.</param>
/// <param name="body">The body of the http message.</param>
/// <returns>
/// A <see cref="RestInteraction"/> that represents the incoming http interaction.
/// </returns>
/// <exception cref="BadSignatureException">Thrown when the signature doesn't match the public key.</exception>
public async Task<RestInteraction> ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body)
{
if (!IsValidHttpInteraction(publicKey, signature, timestamp, body))
{
throw new BadSignatureException();
}

using (var textReader = new StringReader(Encoding.UTF8.GetString(body)))
using (var jsonReader = new JsonTextReader(textReader))
{
var model = Serializer.Deserialize<API.Interaction>(jsonReader);
return await RestInteraction.CreateAsync(this, model);
}
}

#endregion

public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null)
{
return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false);


+ 354
- 0
src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs View File

@@ -0,0 +1,354 @@
using Discord.Net.Rest;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based base command interaction.
/// </summary>
public class RestCommandBase : RestInteraction
{
/// <summary>
/// Gets the name of the invoked command.
/// </summary>
public string CommandName
=> Data.Name;

/// <summary>
/// Gets the id of the invoked command.
/// </summary>
public ulong CommandId
=> Data.Id;

/// <summary>
/// The data associated with this interaction.
/// </summary>
internal new RestCommandBaseData Data { get; private set; }


internal override bool _hasResponded { get; set; }

private object _lock = new object();

internal RestCommandBase(DiscordRestClient client, Model model)
: base(client, model.Id)
{
}

internal new static async Task<RestCommandBase> CreateAsync(DiscordRestClient client, Model model)
{
var entity = new RestCommandBase(client, model);
await entity.UpdateAsync(client, model).ConfigureAwait(false);
return entity;
}

internal override async Task UpdateAsync(DiscordRestClient client, Model model)
{
await base.UpdateAsync(client, model);

var data = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;

if(Data == null)
{
Data = await RestCommandBaseData.CreateAsync(client, data, Guild, Channel).ConfigureAwait(false);
}
}

/// <summary>
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
/// <exception cref="InvalidOperationException">The parameters provided were invalid or the token was invalid.</exception>
/// <returns>
/// A string that contains json to write back to the incoming http request.
/// </returns>
public override string Respond(
string text = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,
AllowedMentions allowedMentions = null,
RequestOptions options = null,
MessageComponent component = null,
Embed embed = null)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");

// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}

if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}

var response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
TTS = isTTS,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified
}
};

lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond twice to the same interaction");
}
}

lock (_lock)
{
_hasResponded = true;
}

return SerializePayload(response);
}

/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// The sent message.
/// </returns>
public override async Task<RestFollowupMessage> FollowupAsync(
string text = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,
AllowedMentions allowedMentions = null,
RequestOptions options = null,
MessageComponent component = null,
Embed embed = null)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
};

if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}

/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="fileStream">The file to upload.</param>
/// <param name="fileName">The file name of the attachment.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// The sent message.
/// </returns>
public override async Task<RestFollowupMessage> FollowupWithFileAsync(
Stream fileStream,
string fileName,
string text = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,
AllowedMentions allowedMentions = null,
RequestOptions options = null,
MessageComponent component = null,
Embed embed = null)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data");
Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified
};

if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}

/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="filePath">The file to upload.</param>
/// <param name="fileName">The file name of the attachment.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// The sent message.
/// </returns>
public override async Task<RestFollowupMessage> FollowupWithFileAsync(
string filePath,
string text = null,
string fileName = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,
AllowedMentions allowedMentions = null,
RequestOptions options = null,
MessageComponent component = null,
Embed embed = null)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist");

fileName ??= Path.GetFileName(filePath);
Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional<MultipartFile>.Unspecified
};

if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}

/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
/// </summary>
/// <returns>
/// A string that contains json to write back to the incoming http request.
/// </returns>
public override string Defer(bool ephemeral = false, RequestOptions options = null)
{
if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified
}
};

lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond or defer twice to the same interaction");
}
}

lock (_lock)
{
_hasResponded = true;
}

return SerializePayload(response);
}
}
}

+ 59
- 0
src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.Rest
{
/// <summary>
/// Represents the base data tied with the <see cref="RestCommandBase"/> interaction.
/// </summary>
public class RestCommandBaseData<TOption> : RestEntity<ulong>, IApplicationCommandInteractionData where TOption : IApplicationCommandInteractionDataOption
{
/// <inheritdoc/>
public string Name { get; private set; }

/// <summary>
/// Gets a collection of <typeparamref name="TOption"/> received with this interaction.
/// </summary>
public virtual IReadOnlyCollection<TOption> Options { get; internal set; }

internal RestResolvableData<Model> ResolvableData;

internal RestCommandBaseData(BaseDiscordClient client, Model model)
: base(client, model.Id)
{
}

internal static async Task<RestCommandBaseData> CreateAsync(DiscordRestClient client, Model model, IGuild guild, IRestMessageChannel channel)
{
var entity = new RestCommandBaseData(client, model);
await entity.UpdateAsync(client, model, guild, channel);
return entity;
}

internal virtual async Task UpdateAsync(DiscordRestClient client, Model model, IGuild guild, IRestMessageChannel channel)
{
Name = model.Name;
if (model.Resolved.IsSpecified && ResolvableData == null)
{
ResolvableData = new RestResolvableData<Model>();
await ResolvableData.PopulateAsync(client, guild, channel, model).ConfigureAwait(false);
}
}

IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionData.Options
=> (IReadOnlyCollection<IApplicationCommandInteractionDataOption>)Options;
}

/// <summary>
/// Represents the base data tied with the <see cref="RestCommandBase"/> interaction.
/// </summary>
public class RestCommandBaseData : RestCommandBaseData<IApplicationCommandInteractionDataOption>
{
internal RestCommandBaseData(DiscordRestClient client, Model model)
: base(client, model) { }
}
}

+ 92
- 0
src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Rest
{
internal class RestResolvableData<T> where T : API.IResolvable
{
internal readonly Dictionary<ulong, RestGuildUser> GuildMembers
= new Dictionary<ulong, RestGuildUser>();
internal readonly Dictionary<ulong, RestUser> Users
= new Dictionary<ulong, RestUser>();
internal readonly Dictionary<ulong, RestChannel> Channels
= new Dictionary<ulong, RestChannel>();
internal readonly Dictionary<ulong, RestRole> Roles
= new Dictionary<ulong, RestRole>();
internal readonly Dictionary<ulong, RestMessage> Messages
= new Dictionary<ulong, RestMessage>();

internal async Task PopulateAsync(DiscordRestClient discord, IGuild guild, IRestMessageChannel channel, T model)
{
var resolved = model.Resolved.Value;

if (resolved.Users.IsSpecified)
{
foreach (var user in resolved.Users.Value)
{
var restUser = RestUser.Create(discord, user.Value);

Users.Add(ulong.Parse(user.Key), restUser);
}
}

if (resolved.Channels.IsSpecified)
{
//var channels = await guild.GetChannelsAsync().ConfigureAwait(false);

foreach (var channelModel in resolved.Channels.Value)
{
var restChannel = RestChannel.Create(discord, channelModel.Value);

Channels.Add(ulong.Parse(channelModel.Key), restChannel);
}
}

if (resolved.Members.IsSpecified)
{
foreach (var member in resolved.Members.Value)
{
var restMember = RestGuildUser.Create(discord, guild, member.Value);

GuildMembers.Add(ulong.Parse(member.Key), restMember);
}
}

if (resolved.Roles.IsSpecified)
{
foreach (var role in resolved.Roles.Value)
{
var restRole = RestRole.Create(discord, guild, role.Value);

Roles.Add(ulong.Parse(role.Key), restRole);
}
}

if (resolved.Messages.IsSpecified)
{
foreach (var msg in resolved.Messages.Value)
{
channel ??= (IRestMessageChannel)(Channels.FirstOrDefault(x => x.Key == msg.Value.ChannelId).Value ?? await discord.GetChannelAsync(msg.Value.ChannelId).ConfigureAwait(false));

RestUser author;

if (msg.Value.Author.IsSpecified)
{
author = RestUser.Create(discord, msg.Value.Author.Value);
}
else
{
author = RestGuildUser.Create(discord, guild, msg.Value.Member.Value);
}

var message = RestMessage.Create(discord, channel, author, msg.Value);

Messages.Add(message.Id, message);
}
}
}
}
}

+ 43
- 0
src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs View File

@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based message command interaction.
/// </summary>
public class RestMessageCommand : RestCommandBase, IDiscordInteraction
{
/// <summary>
/// The data associated with this interaction.
/// </summary>
public new RestMessageCommandData Data { get; private set; }

internal RestMessageCommand(DiscordRestClient client, Model model)
: base(client, model)
{
}

internal new static async Task<RestMessageCommand> CreateAsync(DiscordRestClient client, Model model)
{
var entity = new RestMessageCommand(client, model);
await entity.UpdateAsync(client, model).ConfigureAwait(false);
return entity;
}

internal override async Task UpdateAsync(DiscordRestClient client, Model model)
{
await base.UpdateAsync(client, model);

var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;
Data = await RestMessageCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false);
}

IDiscordInteractionData IDiscordInteraction.Data => Data;
}
}

+ 38
- 0
src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.Rest
{
/// <summary>
/// Represents the data for a <see cref="RestMessageCommand"/>.
/// </summary>
public class RestMessageCommandData : RestCommandBaseData, IDiscordInteractionData
{
/// <summary>
/// Gets the message associated with this message command.
/// </summary>
public RestMessage Message
=> ResolvableData?.Messages.FirstOrDefault().Value;

/// <inheritdoc/>
/// <remarks>
/// <b>Note</b> Not implemented for <see cref="SocketMessageCommandData"/>
/// </remarks>
public override IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options
=> throw new System.NotImplementedException();

internal RestMessageCommandData(DiscordRestClient client, Model model)
: base(client, model) { }

internal new static async Task<RestMessageCommandData> CreateAsync(DiscordRestClient client, Model model, IGuild guild, IRestMessageChannel channel)
{
var entity = new RestMessageCommandData(client, model);
await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
return entity;
}
}
}

+ 46
- 0
src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based user command.
/// </summary>
internal class RestUserCommand : RestCommandBase, IDiscordInteraction
{
/// <summary>
/// Gets the data associated with this interaction.
/// </summary>
public new RestUserCommandData Data { get; private set; }

internal RestUserCommand(DiscordRestClient client, Model model)
: base(client, model)
{
}

internal new static async Task<RestUserCommand> CreateAsync(DiscordRestClient client, Model model)
{
var entity = new RestUserCommand(client, model);
await entity.UpdateAsync(client, model).ConfigureAwait(false);
return entity;
}

internal override async Task UpdateAsync(DiscordRestClient client, Model model)
{
await base.UpdateAsync(client, model).ConfigureAwait(false);

var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;

Data = await RestUserCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false);
}

IDiscordInteractionData IDiscordInteraction.Data => Data;
}
}

+ 36
- 0
src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.Rest
{
/// <summary>
/// Represents the data for a <see cref="RestUserCommand"/>.
/// </summary>
public class RestUserCommandData : RestCommandBaseData, IDiscordInteractionData
{
/// <summary>
/// Gets the user who this command targets.
/// </summary>
public RestUser Member
=> (RestUser)ResolvableData.GuildMembers.Values.FirstOrDefault() ?? ResolvableData.Users.Values.FirstOrDefault();

/// <inheritdoc/>
/// <remarks>
/// <b>Note</b> Not implemented for <see cref="SocketUserCommandData"/>
/// </remarks>
public override IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options
=> throw new System.NotImplementedException();

internal RestUserCommandData(DiscordRestClient client, Model model)
: base(client, model) { }

internal new static async Task<RestUserCommandData> CreateAsync(DiscordRestClient client, Model model, IGuild guild, IRestMessageChannel channel)
{
var entity = new RestUserCommandData(client, model);
await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
return entity;
}
}
}

+ 447
- 0
src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs View File

@@ -0,0 +1,447 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Interaction;
using DataModel = Discord.API.MessageComponentInteractionData;
using System.IO;
using Discord.Net.Rest;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based message component.
/// </summary>
internal class RestMessageComponent : RestInteraction, IDiscordInteraction
{
/// <summary>
/// Gets the data received with this interaction, contains the button that was clicked.
/// </summary>
public new RestMessageComponentData Data { get; }

/// <summary>
/// Gets the message that contained the trigger for this interaction.
/// </summary>
public RestUserMessage Message { get; private set; }

private object _lock = new object();
internal override bool _hasResponded { get; set; } = false;

internal RestMessageComponent(BaseDiscordClient client, Model model)
: base(client, model.Id)
{
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;

Data = new RestMessageComponentData(dataModel);
}

internal new static async Task<RestMessageComponent> CreateAsync(DiscordRestClient client, Model model)
{
var entity = new RestMessageComponent(client, model);
await entity.UpdateAsync(client, model);
return entity;
}
internal override async Task UpdateAsync(DiscordRestClient discord, Model model)
{
await base.UpdateAsync(discord, model);

if (model.Message.IsSpecified && model.ChannelId.IsSpecified)
{
if (Message == null)
{
Message = RestUserMessage.Create(Discord, Channel, User, model.Message.Value);
}
}
}
/// <summary>
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// A string that contains json to write back to the incoming http request.
/// </returns>
public override string Respond(
string text = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,
AllowedMentions allowedMentions = null,
RequestOptions options = null,
MessageComponent component = null,
Embed embed = null)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");

// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}

if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}

var response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Content = text ?? Optional<string>.Unspecified,
AllowedMentions = allowedMentions?.ToModel(),
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
TTS = isTTS,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
}
};

if (ephemeral)
response.Data.Value.Flags = MessageFlags.Ephemeral;

lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction");
}
}

lock (_lock)
{
_hasResponded = true;
}

return SerializePayload(response);
}

/// <summary>
/// Updates the message which this component resides in with the type <see cref="InteractionResponseType.UpdateMessage"/>
/// </summary>
/// <param name="func">A delegate containing the properties to modify the message with.</param>
/// <param name="options">The request options for this <see langword="async"/> request.</param>
/// <returns>A string that contains json to write back to the incoming http request.</returns>
public string Update(Action<MessageProperties> func, RequestOptions options = null)
{
var args = new MessageProperties();
func(args);

if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

if (args.AllowedMentions.IsSpecified)
{
var allowedMentions = args.AllowedMentions.Value;
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed.");
}

var embed = args.Embed;
var embeds = args.Embeds;

bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content);
bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || Message.Embeds.Any();

if (!hasText && !hasEmbeds)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));

var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List<API.Embed>() : null;

if (embed.IsSpecified && embed.Value != null)
{
apiEmbeds.Add(embed.Value.ToModel());
}

if (embeds.IsSpecified && embeds.Value != null)
{
apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
}

Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");

// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue)
{
var allowedMentions = args.AllowedMentions.Value;
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users)
&& allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(args.AllowedMentions));
}

if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles)
&& allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(args.AllowedMentions));
}
}

var response = new API.InteractionResponse
{
Type = InteractionResponseType.UpdateMessage,
Data = new API.InteractionCallbackData
{
Content = args.Content,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
}
};

lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction");
}
}

lock (_lock)
{
_hasResponded = true;
}

return SerializePayload(response);
}

/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// The sent message.
/// </returns>
public override async Task<RestFollowupMessage> FollowupAsync(
string text = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,
AllowedMentions allowedMentions = null,
RequestOptions options = null,
MessageComponent component = null,
Embed embed = null)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
};

if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false);
}

/// <inheritdoc/>
public override async Task<RestFollowupMessage> FollowupWithFileAsync(
Stream fileStream,
string fileName,
string text = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,
AllowedMentions allowedMentions = null,
RequestOptions options = null,
MessageComponent component = null,
Embed embed = null)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data");
Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified
};

if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false);
}

/// <inheritdoc/>
public override async Task<RestFollowupMessage> FollowupWithFileAsync(
string filePath,
string text = null,
string fileName = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,
AllowedMentions allowedMentions = null,
RequestOptions options = null,
MessageComponent component = null,
Embed embed = null)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional<MultipartFile>.Unspecified
};

if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false);
}

/// <summary>
/// Defers an interaction and responds with type 5 (<see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>)
/// </summary>
/// <param name="ephemeral"><see langword="true"/> to send this message ephemerally, otherwise <see langword="false"/>.</param>
/// <param name="options">The request options for this <see langword="async"/> request.</param>
/// <returns>
/// A string that contains json to write back to the incoming http request.
/// </returns>
public string DeferLoading(bool ephemeral = false, RequestOptions options = null)
{
if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement");

var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional<API.InteractionCallbackData>.Unspecified
};

lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond or defer twice to the same interaction");
}
}

lock (_lock)
{
_hasResponded = true;
}

return SerializePayload(response);
}

/// <summary>
///
/// </summary>
/// <param name="ephemeral"></param>
/// <param name="options"></param>
/// <returns>
/// A string that contains json to write back to the incoming http request.
/// </returns>
/// <exception cref="TimeoutException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public override string Defer(bool ephemeral = false, RequestOptions options = null)
{
if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement");

var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredUpdateMessage,
Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional<API.InteractionCallbackData>.Unspecified
};

lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond or defer twice to the same interaction");
}
}

lock (_lock)
{
_hasResponded = true;
}

return SerializePayload(response);
}

IDiscordInteractionData IDiscordInteraction.Data => Data;
}
}

+ 37
- 0
src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.MessageComponentInteractionData;

namespace Discord.Rest
{
/// <summary>
/// Represents data for a <see cref="RestMessageComponent"/>.
/// </summary>
public class RestMessageComponentData : IDiscordInteractionData
{
/// <summary>
/// Gets the components Custom Id that was clicked.
/// </summary>
public string CustomId { get; }

/// <summary>
/// Gets the type of the component clicked.
/// </summary>
public ComponentType Type { get; }

/// <summary>
/// Gets the value(s) of a <see cref="SelectMenuComponent"/> interaction response.
/// </summary>
public IReadOnlyCollection<string> Values { get; }

internal RestMessageComponentData(Model model)
{
CustomId = model.CustomId;
Type = model.ComponentType;
Values = model.Values.GetValueOrDefault();
}
}
}

+ 222
- 0
src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs View File

@@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Interaction;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Newtonsoft.Json;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based interaction.
/// </summary>
public abstract class RestInteraction : RestEntity<ulong>, IDiscordInteraction
{
/// <inheritdoc/>
public InteractionType Type { get; private set; }

/// <inheritdoc/>
public IDiscordInteractionData Data { get; private set; }

/// <inheritdoc/>
public string Token { get; private set; }

/// <inheritdoc/>
public int Version { get; private set; }

/// <summary>
/// Gets the user who invoked the interaction.
/// </summary>
public RestUser User { get; private set; }

/// <inheritdoc/>
public DateTimeOffset CreatedAt
=> SnowflakeUtils.FromSnowflake(Id);

internal abstract bool _hasResponded { get; set; }

/// <summary>
/// <see langword="true"/> if the token is valid for replying to, otherwise <see langword="false"/>.
/// </summary>
public bool IsValidToken
=> InteractionHelper.CanRespondOrFollowup(this);

/// <summary>
/// Gets the channel that this interaction was executed in.
/// </summary>
public IRestMessageChannel Channel { get; private set; }

/// <summary>
/// Gets the guild this interaction was executed in.
/// </summary>
public RestGuild Guild { get; private set; }

internal RestInteraction(BaseDiscordClient discord, ulong id)
: base(discord, id)
{
}

internal static async Task<RestInteraction> CreateAsync(DiscordRestClient client, Model model)
{
if(model.Type == InteractionType.Ping)
{
return await RestPingInteraction.CreateAsync(client, model);
}

if (model.Type == InteractionType.ApplicationCommand)
{
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;

if (dataModel == null)
return null;

return dataModel.Type switch
{
ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model).ConfigureAwait(false),
ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model).ConfigureAwait(false),
ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model).ConfigureAwait(false),
_ => null
};
}

if (model.Type == InteractionType.MessageComponent)
return await RestMessageComponent.CreateAsync(client, model).ConfigureAwait(false);

if (model.Type == InteractionType.ApplicationCommandAutocomplete)
return await RestAutocompleteInteraction.CreateAsync(client, model).ConfigureAwait(false);

return null;
}

internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model)
{
Data = model.Data.IsSpecified
? model.Data.Value
: null;
Token = model.Token;
Version = model.Version;
Type = model.Type;

if(Guild == null && model.GuildId.IsSpecified)
{
Guild = await discord.GetGuildAsync(model.GuildId.Value);
}

if (User == null)
{
if (model.Member.IsSpecified && model.GuildId.IsSpecified)
{
User = RestGuildUser.Create(Discord, Guild, model.Member.Value);
}
else
{
User = RestUser.Create(Discord, model.User.Value);
}
}

if(Channel == null && model.ChannelId.IsSpecified)
{
Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value);
}
}

internal string SerializePayload(object payload)
{
var json = new StringBuilder();
using (var text = new StringWriter(json))
using (var writer = new JsonTextWriter(text))
DiscordRestClient.Serializer.Serialize(writer, payload);

return json.ToString();
}

/// <inheritdoc/>
public abstract string Defer(bool ephemeral = false, RequestOptions options = null);
/// <inheritdoc/>
public abstract Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null);
/// <summary>
/// Gets the original response for this interaction.
/// </summary>
/// <param name="options">The request options for this <see langword="async"/> request.</param>
/// <returns>A <see cref="RestInteractionMessage"/> that represents the initial response.</returns>
public Task<RestInteractionMessage> GetOriginalResponseAsync(RequestOptions options = null)
=> InteractionHelper.GetOriginalResponseAsync(Discord, Channel, this, options);

/// <summary>
/// Edits original response for this interaction.
/// </summary>
/// <param name="func">A delegate containing the properties to modify the message with.</param>
/// <param name="options">The request options for this <see langword="async"/> request.</param>
/// <returns>A <see cref="RestInteractionMessage"/> that represents the initial response.</returns>
public async Task<RestInteractionMessage> ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options = null)
{
var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options);
return RestInteractionMessage.Create(Discord, model, Token, Channel);
}
/// <inheritdoc/>
public abstract string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null);
/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="fileStream">The file to upload.</param>
/// <param name="fileName">The file name of the attachment.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// The sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null);
/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="filePath">The file to upload.</param>
/// <param name="fileName">The file name of the attachment.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// The sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null);
#region IDiscordInteraction
/// <inheritdoc/>
Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, RequestOptions options, MessageComponent component, Embed embed)
=> Task.FromResult(Respond(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed));

Task IDiscordInteraction.DeferAsync(bool ephemeral, RequestOptions options)
=> Task.FromResult(Defer(ephemeral, options));

/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions,
RequestOptions options, MessageComponent component, Embed embed)
=> await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false);

/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options)
=> await GetOriginalResponseAsync(options).ConfigureAwait(false);

/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options)
=> await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false);
#endregion
}
}

+ 43
- 0
src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Interaction;

namespace Discord.Rest
{
public class RestPingInteraction : RestInteraction, IDiscordInteraction
{
internal override bool _hasResponded { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

public RestPingInteraction(BaseDiscordClient client, ulong id)
: base(client, id)
{
}

internal static new async Task<RestPingInteraction> CreateAsync(DiscordRestClient client, Model model)
{
var entity = new RestPingInteraction(client, model.Id);
await entity.UpdateAsync(client, model);
return entity;
}

public string AcknowledgePing()
{
var model = new API.InteractionResponse()
{
Type = InteractionResponseType.Pong
};

return SerializePayload(model);
}

public override string Defer(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException();
public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException();
}
}

+ 134
- 0
src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Interaction;
using DataModel = Discord.API.AutocompleteInteractionData;
using System.IO;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based autocomplete interaction.
/// </summary>
public class RestAutocompleteInteraction : RestInteraction, IDiscordInteraction
{
/// <summary>
/// Gets the autocomplete data of this interaction.
/// </summary>
public new RestAutocompleteInteractionData Data { get; }

internal override bool _hasResponded { get; set; }
private object _lock = new object();

internal RestAutocompleteInteraction(DiscordRestClient client, Model model)
: base(client, model.Id)
{
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;

if (dataModel != null)
Data = new RestAutocompleteInteractionData(dataModel);
}

internal new static async Task<RestAutocompleteInteraction> CreateAsync(DiscordRestClient client, Model model)
{
var entity = new RestAutocompleteInteraction(client, model);
await entity.UpdateAsync(client, model).ConfigureAwait(false);
return entity;
}

/// <summary>
/// Responds to this interaction with a set of choices.
/// </summary>
/// <param name="result">
/// The set of choices for the user to pick from.
/// <remarks>
/// A max of 20 choices are allowed. Passing <see langword="null"/> for this argument will show the executing user that
/// there is no choices for their autocompleted input.
/// </remarks>
/// </param>
/// <param name="options">The request options for this response.</param>
/// <returns>
/// A string that contains json to write back to the incoming http request.
/// </returns>
public string Respond(IEnumerable<AutocompleteResult> result, RequestOptions options = null)
{
if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond twice to the same interaction");
}
}

lock (_lock)
{
_hasResponded = true;
}

var model = new API.InteractionResponse
{
Type = InteractionResponseType.ApplicationCommandAutocompleteResult,
Data = new API.InteractionCallbackData
{
Choices = result.Any()
? result.Select(x => new API.ApplicationCommandOptionChoice { Name = x.Name, Value = x.Value }).ToArray()
: Array.Empty<API.ApplicationCommandOptionChoice>()
}
};

return SerializePayload(model);
}

/// <summary>
/// Responds to this interaction with a set of choices.
/// </summary>
/// <param name="options">The request options for this response.</param>
/// <param name="result">
/// The set of choices for the user to pick from.
/// <remarks>
/// A max of 20 choices are allowed. Passing <see langword="null"/> for this argument will show the executing user that
/// there is no choices for their autocompleted input.
/// </remarks>
/// </param>
/// <returns>
/// A string that contains json to write back to the incoming http request.
/// </returns>
public string Respond(RequestOptions options = null, params AutocompleteResult[] result)
=> Respond(result, options);

/// <inheritdoc/>
[Obsolete("Autocomplete interactions cannot be deferred!", true)]
public override string Defer(bool ephemeral = false, RequestOptions options = null)
=> throw new NotSupportedException("Autocomplete interactions cannot be deferred!");

/// <inheritdoc/>
[Obsolete("Autocomplete interactions cannot have followups!", true)]
public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null)
=> throw new NotSupportedException("Autocomplete interactions cannot be deferred!");

/// <inheritdoc/>
[Obsolete("Autocomplete interactions cannot have followups!", true)]
public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null)
=> throw new NotSupportedException("Autocomplete interactions cannot be deferred!");

/// <inheritdoc/>
[Obsolete("Autocomplete interactions cannot have followups!", true)]
public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null)
=> throw new NotSupportedException("Autocomplete interactions cannot be deferred!");

/// <inheritdoc/>
[Obsolete("Autocomplete interactions cannot have normal responses!", true)]
public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null)
=> throw new NotSupportedException("Autocomplete interactions cannot be deferred!");

IDiscordInteractionData IDiscordInteraction.Data => Data;

}
}

+ 76
- 0
src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteractionData.cs View File

@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataModel = Discord.API.AutocompleteInteractionData;

namespace Discord.Rest
{
/// <summary>
/// Represents the data for a <see cref="RestAutocompleteInteraction"/>.
/// </summary>
public class RestAutocompleteInteractionData : IDiscordInteractionData
{
/// <summary>
/// Gets the name of the invoked command.
/// </summary>
public string CommandName { get; }

/// <summary>
/// Gets the id of the invoked command.
/// </summary>
public ulong CommandId { get; }

/// <summary>
/// Gets the type of the invoked command.
/// </summary>
public ApplicationCommandType Type { get; }

/// <summary>
/// Gets the version of the invoked command.
/// </summary>
public ulong Version { get; }

/// <summary>
/// Gets the current autocomplete option that is actively being filled out.
/// </summary>
public AutocompleteOption Current { get; }

/// <summary>
/// Gets a collection of all the other options the executing users has filled out.
/// </summary>
public IReadOnlyCollection<AutocompleteOption> Options { get; }

internal RestAutocompleteInteractionData(DataModel model)
{
var options = model.Options.SelectMany(GetOptions);

Current = options.FirstOrDefault(x => x.Focused);
Options = options.ToImmutableArray();

if (Options.Count == 1 && Current == null)
Current = Options.FirstOrDefault();

CommandName = model.Name;
CommandId = model.Id;
Type = model.Type;
Version = model.Version;
}

private List<AutocompleteOption> GetOptions(API.AutocompleteInteractionDataOption model)
{
var options = new List<AutocompleteOption>();

options.Add(new AutocompleteOption(model.Type, model.Name, model.Value.GetValueOrDefault(null), model.Focused.GetValueOrDefault(false)));

if (model.Options.IsSpecified)
{
options.AddRange(model.Options.Value.SelectMany(GetOptions));
}

return options;
}
}
}

+ 44
- 0
src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based slash command.
/// </summary>
public class RestSlashCommand : RestCommandBase, IDiscordInteraction
{
/// <summary>
/// Gets the data associated with this interaction.
/// </summary>
public new RestSlashCommandData Data { get; private set; }

internal RestSlashCommand(DiscordRestClient client, Model model)
: base(client, model)
{
}

internal new static async Task<RestSlashCommand> CreateAsync(DiscordRestClient client, Model model)
{
var entity = new RestSlashCommand(client, model);
await entity.UpdateAsync(client, model);
return entity;
}

internal override async Task UpdateAsync(DiscordRestClient client, Model model)
{
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;

Data = await RestSlashCommandData.CreateAsync(client, dataModel, Guild, Channel);
}

IDiscordInteractionData IDiscordInteraction.Data => Data;
}
}

+ 32
- 0
src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.Rest
{

public class RestSlashCommandData : RestCommandBaseData<RestSlashCommandDataOption>, IDiscordInteractionData
{
internal RestSlashCommandData(DiscordRestClient client, Model model)
: base(client, model) { }

internal static new async Task<RestSlashCommandData> CreateAsync(DiscordRestClient client, Model model, IGuild guild, IRestMessageChannel channel)
{
var entity = new RestSlashCommandData(client, model);
await entity.UpdateAsync(client, model, guild, channel);
return entity;
}
internal override async Task UpdateAsync(DiscordRestClient client, Model model, IGuild guild, IRestMessageChannel channel)
{
await base.UpdateAsync(client, model, guild, channel);

Options = model.Options.IsSpecified
? model.Options.Value.Select(x => new RestSlashCommandDataOption(this, x)).ToImmutableArray()
: ImmutableArray.Create<RestSlashCommandDataOption>();
}
}
}

+ 139
- 0
src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.ApplicationCommandInteractionDataOption;


namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based option for a slash command.
/// </summary>
public class RestSlashCommandDataOption : IApplicationCommandInteractionDataOption
{
#region RestSlashCommandDataOption
/// <inheritdoc/>
public string Name { get; private set; }

/// <inheritdoc/>
public object Value { get; private set; }

/// <inheritdoc/>
public ApplicationCommandOptionType Type { get; private set; }

/// <summary>
/// Gets a collection of sub command options received for this sub command group.
/// </summary>
public IReadOnlyCollection<RestSlashCommandDataOption> Options { get; private set; }

internal RestSlashCommandDataOption() { }
internal RestSlashCommandDataOption(RestSlashCommandData data, Model model)
{
Name = model.Name;
Type = model.Type;

if (model.Value.IsSpecified)
{
switch (Type)
{
case ApplicationCommandOptionType.User:
case ApplicationCommandOptionType.Role:
case ApplicationCommandOptionType.Channel:
case ApplicationCommandOptionType.Mentionable:
if (ulong.TryParse($"{model.Value.Value}", out var valueId))
{
switch (Type)
{
case ApplicationCommandOptionType.User:
{
var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value;

if (guildUser != null)
Value = guildUser;
else
Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value;
}
break;
case ApplicationCommandOptionType.Channel:
Value = data.ResolvableData.Channels.FirstOrDefault(x => x.Key == valueId).Value;
break;
case ApplicationCommandOptionType.Role:
Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value;
break;
case ApplicationCommandOptionType.Mentionable:
{
if (data.ResolvableData.GuildMembers.Any(x => x.Key == valueId) || data.ResolvableData.Users.Any(x => x.Key == valueId))
{
var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value;

if (guildUser != null)
Value = guildUser;
else
Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value;
}
else if (data.ResolvableData.Roles.Any(x => x.Key == valueId))
{
Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value;
}
}
break;
default:
Value = model.Value.Value;
break;
}
}
break;
case ApplicationCommandOptionType.String:
Value = model.Value.ToString();
break;
case ApplicationCommandOptionType.Integer:
{
if (model.Value.Value is long val)
Value = val;
else if (long.TryParse(model.Value.Value.ToString(), out long res))
Value = res;
}
break;
case ApplicationCommandOptionType.Boolean:
{
if (model.Value.Value is bool val)
Value = val;
else if (bool.TryParse(model.Value.Value.ToString(), out bool res))
Value = res;
}
break;
case ApplicationCommandOptionType.Number:
{
if (model.Value.Value is int val)
Value = val;
else if (double.TryParse(model.Value.Value.ToString(), out double res))
Value = res;
}
break;
}
}

Options = model.Options.IsSpecified
? model.Options.Value.Select(x => new RestSlashCommandDataOption(data, x)).ToImmutableArray()
: ImmutableArray.Create<RestSlashCommandDataOption>();
}
#endregion

#region Converters
public static explicit operator bool(RestSlashCommandDataOption option)
=> (bool)option.Value;
public static explicit operator int(RestSlashCommandDataOption option)
=> (int)option.Value;
public static explicit operator string(RestSlashCommandDataOption option)
=> option.Value.ToString();
#endregion

#region IApplicationCommandInteractionDataOption
IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionDataOption.Options
=> Options;
#endregion
}
}

+ 16
- 0
src/Discord.Net.Rest/Net/BadSignatureException.cs View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Rest
{
public class BadSignatureException : Exception
{
internal BadSignatureException() : base("Failed to verify authenticity of message: public key doesnt match signature")
{

}
}
}

+ 1
- 1
src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs View File

@@ -22,7 +22,7 @@ namespace Discord.Net.Converters

// Remove the data property for manual deserialization
var result = obj.GetValue("data", StringComparison.OrdinalIgnoreCase);
result.Parent.Remove();
result?.Parent.Remove();

// Populate the remaining properties.
using (var subReader = obj.CreateReader())


+ 27
- 0
src/Discord.Net.Rest/Net/ED25519/Array16.cs View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;

namespace Discord.Net.ED25519
{
// Array16<UInt32> Salsa20 state
// Array16<UInt64> SHA-512 block
internal struct Array16<T>
{
public T x0;
public T x1;
public T x2;
public T x3;
public T x4;
public T x5;
public T x6;
public T x7;
public T x8;
public T x9;
public T x10;
public T x11;
public T x12;
public T x13;
public T x14;
public T x15;
}
}

+ 18
- 0
src/Discord.Net.Rest/Net/ED25519/Array8.cs View File

@@ -0,0 +1,18 @@
using System;

namespace Discord.Net.ED25519
{
// Array8<UInt32> Poly1305 key
// Array8<UInt64> SHA-512 state/output
internal struct Array8<T>
{
public T x0;
public T x1;
public T x2;
public T x3;
public T x4;
public T x5;
public T x6;
public T x7;
}
}

+ 55
- 0
src/Discord.Net.Rest/Net/ED25519/ByteIntegerConverter.cs View File

@@ -0,0 +1,55 @@
using System;

namespace Discord.Net.ED25519
{
// Loops? Arrays? Never heard of that stuff
// Library avoids unnecessary heap allocations and unsafe code
// so this ugly code becomes necessary :(
internal static class ByteIntegerConverter
{
public static ulong LoadBigEndian64(byte[] buf, int offset)
{
return
(ulong)(buf[offset + 7])
| (((ulong)(buf[offset + 6])) << 8)
| (((ulong)(buf[offset + 5])) << 16)
| (((ulong)(buf[offset + 4])) << 24)
| (((ulong)(buf[offset + 3])) << 32)
| (((ulong)(buf[offset + 2])) << 40)
| (((ulong)(buf[offset + 1])) << 48)
| (((ulong)(buf[offset + 0])) << 56);
}

public static void StoreBigEndian64(byte[] buf, int offset, ulong value)
{
buf[offset + 7] = unchecked((byte)value);
buf[offset + 6] = unchecked((byte)(value >> 8));
buf[offset + 5] = unchecked((byte)(value >> 16));
buf[offset + 4] = unchecked((byte)(value >> 24));
buf[offset + 3] = unchecked((byte)(value >> 32));
buf[offset + 2] = unchecked((byte)(value >> 40));
buf[offset + 1] = unchecked((byte)(value >> 48));
buf[offset + 0] = unchecked((byte)(value >> 56));
}

public static void Array16LoadBigEndian64(out Array16<ulong> output, byte[] input, int inputOffset)
{
output.x0 = LoadBigEndian64(input, inputOffset + 0);
output.x1 = LoadBigEndian64(input, inputOffset + 8);
output.x2 = LoadBigEndian64(input, inputOffset + 16);
output.x3 = LoadBigEndian64(input, inputOffset + 24);
output.x4 = LoadBigEndian64(input, inputOffset + 32);
output.x5 = LoadBigEndian64(input, inputOffset + 40);
output.x6 = LoadBigEndian64(input, inputOffset + 48);
output.x7 = LoadBigEndian64(input, inputOffset + 56);
output.x8 = LoadBigEndian64(input, inputOffset + 64);
output.x9 = LoadBigEndian64(input, inputOffset + 72);
output.x10 = LoadBigEndian64(input, inputOffset + 80);
output.x11 = LoadBigEndian64(input, inputOffset + 88);
output.x12 = LoadBigEndian64(input, inputOffset + 96);
output.x13 = LoadBigEndian64(input, inputOffset + 104);
output.x14 = LoadBigEndian64(input, inputOffset + 112);
output.x15 = LoadBigEndian64(input, inputOffset + 120);
}
}
}

+ 272
- 0
src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs View File

@@ -0,0 +1,272 @@
using System;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace Discord.Net.ED25519
{
internal class CryptoBytes
{
/// <summary>
/// Comparison of two arrays.
///
/// The runtime of this method does not depend on the contents of the arrays. Using constant time
/// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix.
///
/// It is important to use such a constant time comparison when verifying MACs.
/// </summary>
/// <param name="x">Byte array</param>
/// <param name="y">Byte array</param>
/// <returns>True if arrays are equal</returns>
public static bool ConstantTimeEquals(byte[] x, byte[] y)
{
if (x.Length != y.Length)
return false;
return InternalConstantTimeEquals(x, 0, y, 0, x.Length) != 0;
}

/// <summary>
/// Comparison of two array segments.
///
/// The runtime of this method does not depend on the contents of the arrays. Using constant time
/// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix.
///
/// It is important to use such a constant time comparison when verifying MACs.
/// </summary>
/// <param name="x">Byte array segment</param>
/// <param name="y">Byte array segment</param>
/// <returns>True if contents of x and y are equal</returns>
public static bool ConstantTimeEquals(ArraySegment<byte> x, ArraySegment<byte> y)
{
if (x.Count != y.Count)
return false;
return InternalConstantTimeEquals(x.Array, x.Offset, y.Array, y.Offset, x.Count) != 0;
}

/// <summary>
/// Comparison of two byte sequences.
///
/// The runtime of this method does not depend on the contents of the arrays. Using constant time
/// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix.
///
/// It is important to use such a constant time comparison when verifying MACs.
/// </summary>
/// <param name="x">Byte array</param>
/// <param name="xOffset">Offset of byte sequence in the x array</param>
/// <param name="y">Byte array</param>
/// <param name="yOffset">Offset of byte sequence in the y array</param>
/// <param name="length">Lengh of byte sequence</param>
/// <returns>True if sequences are equal</returns>
public static bool ConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int length)
{
return InternalConstantTimeEquals(x, xOffset, y, yOffset, length) != 0;
}

private static uint InternalConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int length)
{
int differentbits = 0;
for (int i = 0; i < length; i++)
differentbits |= x[xOffset + i] ^ y[yOffset + i];
return (1 & (unchecked((uint)differentbits - 1) >> 8));
}

/// <summary>
/// Overwrites the contents of the array, wiping the previous content.
/// </summary>
/// <param name="data">Byte array</param>
public static void Wipe(byte[] data)
{
InternalWipe(data, 0, data.Length);
}

/// <summary>
/// Overwrites the contents of the array, wiping the previous content.
/// </summary>
/// <param name="data">Byte array</param>
/// <param name="offset">Index of byte sequence</param>
/// <param name="length">Length of byte sequence</param>
public static void Wipe(byte[] data, int offset, int length)
{
InternalWipe(data, offset, length);
}

/// <summary>
/// Overwrites the contents of the array segment, wiping the previous content.
/// </summary>
/// <param name="data">Byte array segment</param>
public static void Wipe(ArraySegment<byte> data)
{
InternalWipe(data.Array, data.Offset, data.Count);
}

// Secure wiping is hard
// * the GC can move around and copy memory
// Perhaps this can be avoided by using unmanaged memory or by fixing the position of the array in memory
// * Swap files and error dumps can contain secret information
// It seems possible to lock memory in RAM, no idea about error dumps
// * Compiler could optimize out the wiping if it knows that data won't be read back
// I hope this is enough, suppressing inlining
// but perhaps `RtlSecureZeroMemory` is needed
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void InternalWipe(byte[] data, int offset, int count)
{
Array.Clear(data, offset, count);
}

// shallow wipe of structs
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void InternalWipe<T>(ref T data)
where T : struct
{
data = default(T);
}

/// <summary>
/// Constant-time conversion of the bytes array to an upper-case hex string.
/// Please see http://stackoverflow.com/a/14333437/445517 for the detailed explanation
/// </summary>
/// <param name="data">Byte array</param>
/// <returns>Hex representation of byte array</returns>
public static string ToHexStringUpper(byte[] data)
{
if (data == null)
return null;
char[] c = new char[data.Length * 2];
int b;
for (int i = 0; i < data.Length; i++)
{
b = data[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = data[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
return new string(c);
}

/// <summary>
/// Constant-time conversion of the bytes array to an lower-case hex string.
/// Please see http://stackoverflow.com/a/14333437/445517 for the detailed explanation.
/// </summary>
/// <param name="data">Byte array</param>
/// <returns>Hex representation of byte array</returns>
public static string ToHexStringLower(byte[] data)
{
if (data == null)
return null;
char[] c = new char[data.Length * 2];
int b;
for (int i = 0; i < data.Length; i++)
{
b = data[i] >> 4;
c[i * 2] = (char)(87 + b + (((b - 10) >> 31) & -39));
b = data[i] & 0xF;
c[i * 2 + 1] = (char)(87 + b + (((b - 10) >> 31) & -39));
}
return new string(c);
}

/// <summary>
/// Converts the hex string to bytes. Case insensitive.
/// </summary>
/// <param name="hexString">Hex encoded byte sequence</param>
/// <returns>Byte array</returns>
public static byte[] FromHexString(string hexString)
{
if (hexString == null)
return null;
if (hexString.Length % 2 != 0)
throw new FormatException("The hex string is invalid because it has an odd length");
var result = new byte[hexString.Length / 2];
for (int i = 0; i < result.Length; i++)
result[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
return result;
}

/// <summary>
/// Encodes the bytes with the Base64 encoding.
/// More compact than hex, but it is case-sensitive and uses the special characters `+`, `/` and `=`.
/// </summary>
/// <param name="data">Byte array</param>
/// <returns>Base 64 encoded data</returns>
public static string ToBase64String(byte[] data)
{
if (data == null)
return null;
return Convert.ToBase64String(data);
}

/// <summary>
/// Decodes a Base64 encoded string back to bytes.
/// </summary>
/// <param name="base64String">Base 64 encoded data</param>
/// <returns>Byte array</returns>
public static byte[] FromBase64String(string base64String)
{
if (base64String == null)
return null;
return Convert.FromBase64String(base64String);
}

private const string strDigits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

/// <summary>
/// Encode a byte sequence as a base58-encoded string
/// </summary>
/// <param name="input">Byte sequence</param>
/// <returns>Encoding result</returns>
public static string Base58Encode(byte[] input)
{
// Decode byte[] to BigInteger
BigInteger intData = 0;
for (int i = 0; i < input.Length; i++)
{
intData = intData * 256 + input[i];
}

// Encode BigInteger to Base58 string
string result = "";
while (intData > 0)
{
int remainder = (int)(intData % 58);
intData /= 58;
result = strDigits[remainder] + result;
}

// Append `1` for each leading 0 byte
for (int i = 0; i < input.Length && input[i] == 0; i++)
{
result = '1' + result;
}
return result;
}

/// <summary>
/// // Decode a base58-encoded string into byte array
/// </summary>
/// <param name="strBase58">Base58 data string</param>
/// <returns>Byte array</returns>
public static byte[] Base58Decode(string input)
{
// Decode Base58 string to BigInteger
BigInteger intData = 0;
for (int i = 0; i < input.Length; i++)
{
int digit = strDigits.IndexOf(input[i]); //Slow
if (digit < 0)
throw new FormatException(string.Format("Invalid Base58 character `{0}` at position {1}", input[i], i));
intData = intData * 58 + digit;
}

// Encode BigInteger to byte[]
// Leading zero bytes get encoded as leading `1` characters
int leadingZeroCount = input.TakeWhile(c => c == '1').Count();
var leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount);
var bytesWithoutLeadingZeros =
intData.ToByteArray()
.Reverse()// to big endian
.SkipWhile(b => b == 0);//strip sign byte
var result = leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray();
return result;
}
}
}

+ 67
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519.cs View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Net.ED25519
{
internal static class Ed25519
{
/// <summary>
/// Public Keys are 32 byte values. All possible values of this size a valid.
/// </summary>
public const int PublicKeySize = 32;
/// <summary>
/// Signatures are 64 byte values
/// </summary>
public const int SignatureSize = 64;
/// <summary>
/// Private key seeds are 32 byte arbitrary values. This is the form that should be generated and stored.
/// </summary>
public const int PrivateKeySeedSize = 32;
/// <summary>
/// A 64 byte expanded form of private key. This form is used internally to improve performance
/// </summary>
public const int ExpandedPrivateKeySize = 32 * 2;

/// <summary>
/// Verify Ed25519 signature
/// </summary>
/// <param name="signature">Signature bytes</param>
/// <param name="message">Message</param>
/// <param name="publicKey">Public key</param>
/// <returns>True if signature is valid, false if it's not</returns>
public static bool Verify(ArraySegment<byte> signature, ArraySegment<byte> message, ArraySegment<byte> publicKey)
{
if (signature.Count != SignatureSize)
throw new ArgumentException($"Sizeof signature doesnt match defined size of {SignatureSize}");

if (publicKey.Count != PublicKeySize)
throw new ArgumentException($"Sizeof public key doesnt match defined size of {PublicKeySize}");

return Ed25519Operations.crypto_sign_verify(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, publicKey.Array, publicKey.Offset);
}

/// <summary>
/// Verify Ed25519 signature
/// </summary>
/// <param name="signature">Signature bytes</param>
/// <param name="message">Message</param>
/// <param name="publicKey">Public key</param>
/// <returns>True if signature is valid, false if it's not</returns>
public static bool Verify(byte[] signature, byte[] message, byte[] publicKey)
{
Preconditions.NotNull(signature, nameof(signature));
Preconditions.NotNull(message, nameof(message));
Preconditions.NotNull(publicKey, nameof(publicKey));
if (signature.Length != SignatureSize)
throw new ArgumentException($"Sizeof signature doesnt match defined size of {SignatureSize}");

if (publicKey.Length != PublicKeySize)
throw new ArgumentException($"Sizeof public key doesnt match defined size of {PublicKeySize}");

return Ed25519Operations.crypto_sign_verify(signature, 0, message, 0, message.Length, publicKey, 0);
}
}
}

+ 45
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Operations.cs View File

@@ -0,0 +1,45 @@
using Discord.Net.ED25519.Ed25519Ref10;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Net.ED25519
{
internal class Ed25519Operations
{
public static bool crypto_sign_verify(
byte[] sig, int sigoffset,
byte[] m, int moffset, int mlen,
byte[] pk, int pkoffset)
{
byte[] h;
byte[] checkr = new byte[32];
GroupElementP3 A;
GroupElementP2 R;

if ((sig[sigoffset + 63] & 224) != 0)
return false;
if (GroupOperations.ge_frombytes_negate_vartime(out A, pk, pkoffset) != 0)
return false;

var hasher = new Sha512();
hasher.Update(sig, sigoffset, 32);
hasher.Update(pk, pkoffset, 32);
hasher.Update(m, moffset, mlen);
h = hasher.Finalize();

ScalarOperations.sc_reduce(h);

var sm32 = new byte[32];
Array.Copy(sig, sigoffset + 32, sm32, 0, 32);
GroupOperations.ge_double_scalarmult_vartime(out R, h, ref A, sm32);
GroupOperations.ge_tobytes(checkr, 0, ref R);
var result = CryptoBytes.ConstantTimeEquals(checkr, 0, sig, sigoffset, 32);
CryptoBytes.Wipe(h);
CryptoBytes.Wipe(checkr);
return result;
}
}
}

+ 23
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/FieldElement.cs View File

@@ -0,0 +1,23 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal struct FieldElement
{
internal int x0, x1, x2, x3, x4, x5, x6, x7, x8, x9;

internal FieldElement(params int[] elements)
{
x0 = elements[0];
x1 = elements[1];
x2 = elements[2];
x3 = elements[3];
x4 = elements[4];
x5 = elements[5];
x6 = elements[6];
x7 = elements[7];
x8 = elements[8];
x9 = elements[9];
}
}
}

+ 63
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/GroupElement.cs View File

@@ -0,0 +1,63 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
/*
ge means group element.

Here the group is the set of pairs (x,y) of field elements (see fe.h)
satisfying -x^2 + y^2 = 1 + d x^2y^2
where d = -121665/121666.

Representations:
ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
ge_precomp (Duif): (y+x,y-x,2dxy)
*/

internal struct GroupElementP2
{
public FieldElement X;
public FieldElement Y;
public FieldElement Z;
} ;

internal struct GroupElementP3
{
public FieldElement X;
public FieldElement Y;
public FieldElement Z;
public FieldElement T;
} ;

internal struct GroupElementP1P1
{
public FieldElement X;
public FieldElement Y;
public FieldElement Z;
public FieldElement T;
} ;

internal struct GroupElementPreComp
{
public FieldElement yplusx;
public FieldElement yminusx;
public FieldElement xy2d;

public GroupElementPreComp(FieldElement yplusx, FieldElement yminusx, FieldElement xy2d)
{
this.yplusx = yplusx;
this.yminusx = yminusx;
this.xy2d = xy2d;
}
} ;

internal struct GroupElementCached
{
public FieldElement YplusX;
public FieldElement YminusX;
public FieldElement Z;
public FieldElement T2d;
} ;
}

+ 1355
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base.cs
File diff suppressed because it is too large
View File


+ 50
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base2.cs View File

@@ -0,0 +1,50 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class LookupTables
{
internal static readonly GroupElementPreComp[] Base2 = new GroupElementPreComp[]{
new GroupElementPreComp(
new FieldElement( 25967493,-14356035,29566456,3660896,-12694345,4014787,27544626,-11754271,-6079156,2047605 ),
new FieldElement( -12545711,934262,-2722910,3049990,-727428,9406986,12720692,5043384,19500929,-15469378 ),
new FieldElement( -8738181,4489570,9688441,-14785194,10184609,-12363380,29287919,11864899,-24514362,-4438546 )
),
new GroupElementPreComp(
new FieldElement( 15636291,-9688557,24204773,-7912398,616977,-16685262,27787600,-14772189,28944400,-1550024 ),
new FieldElement( 16568933,4717097,-11556148,-1102322,15682896,-11807043,16354577,-11775962,7689662,11199574 ),
new FieldElement( 30464156,-5976125,-11779434,-15670865,23220365,15915852,7512774,10017326,-17749093,-9920357 )
),
new GroupElementPreComp(
new FieldElement( 10861363,11473154,27284546,1981175,-30064349,12577861,32867885,14515107,-15438304,10819380 ),
new FieldElement( 4708026,6336745,20377586,9066809,-11272109,6594696,-25653668,12483688,-12668491,5581306 ),
new FieldElement( 19563160,16186464,-29386857,4097519,10237984,-4348115,28542350,13850243,-23678021,-15815942 )
),
new GroupElementPreComp(
new FieldElement( 5153746,9909285,1723747,-2777874,30523605,5516873,19480852,5230134,-23952439,-15175766 ),
new FieldElement( -30269007,-3463509,7665486,10083793,28475525,1649722,20654025,16520125,30598449,7715701 ),
new FieldElement( 28881845,14381568,9657904,3680757,-20181635,7843316,-31400660,1370708,29794553,-1409300 )
),
new GroupElementPreComp(
new FieldElement( -22518993,-6692182,14201702,-8745502,-23510406,8844726,18474211,-1361450,-13062696,13821877 ),
new FieldElement( -6455177,-7839871,3374702,-4740862,-27098617,-10571707,31655028,-7212327,18853322,-14220951 ),
new FieldElement( 4566830,-12963868,-28974889,-12240689,-7602672,-2830569,-8514358,-10431137,2207753,-3209784 )
),
new GroupElementPreComp(
new FieldElement( -25154831,-4185821,29681144,7868801,-6854661,-9423865,-12437364,-663000,-31111463,-16132436 ),
new FieldElement( 25576264,-2703214,7349804,-11814844,16472782,9300885,3844789,15725684,171356,6466918 ),
new FieldElement( 23103977,13316479,9739013,-16149481,817875,-15038942,8965339,-14088058,-30714912,16193877 )
),
new GroupElementPreComp(
new FieldElement( -33521811,3180713,-2394130,14003687,-16903474,-16270840,17238398,4729455,-18074513,9256800 ),
new FieldElement( -25182317,-4174131,32336398,5036987,-21236817,11360617,22616405,9761698,-19827198,630305 ),
new FieldElement( -13720693,2639453,-24237460,-7406481,9494427,-5774029,-6554551,-15960994,-2449256,-14291300 )
),
new GroupElementPreComp(
new FieldElement( -3151181,-5046075,9282714,6866145,-31907062,-863023,-18940575,15033784,25105118,-7894876 ),
new FieldElement( -24326370,15950226,-31801215,-14592823,-11662737,-5090925,1573892,-2625887,2198790,-15804619 ),
new FieldElement( -3099351,10324967,-2241613,7453183,-5446979,-2735503,-13812022,-16236442,-32461234,-12290683 )
)
};
}
}

+ 9
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d.cs View File

@@ -0,0 +1,9 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class LookupTables
{
internal static FieldElement d = new FieldElement(-10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116);
}
}

+ 9
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d2.cs View File

@@ -0,0 +1,9 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class LookupTables
{
internal static FieldElement d2 = new FieldElement(-21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199);
}
}

+ 12
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_0.cs View File

@@ -0,0 +1,12 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
public static void fe_0(out FieldElement h)
{
h = default(FieldElement);
}
}
}

+ 13
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_1.cs View File

@@ -0,0 +1,13 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
public static void fe_1(out FieldElement h)
{
h = default(FieldElement);
h.x0 = 1;
}
}
}

+ 64
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_add.cs View File

@@ -0,0 +1,64 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
h = f + g
Can overlap h with f or g.

Preconditions:
|f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.

Postconditions:
|h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
*/
//void fe_add(fe h,const fe f,const fe g)
internal static void fe_add(out FieldElement h, ref FieldElement f, ref FieldElement g)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;
int g0 = g.x0;
int g1 = g.x1;
int g2 = g.x2;
int g3 = g.x3;
int g4 = g.x4;
int g5 = g.x5;
int g6 = g.x6;
int g7 = g.x7;
int g8 = g.x8;
int g9 = g.x9;
int h0 = f0 + g0;
int h1 = f1 + g1;
int h2 = f2 + g2;
int h3 = f3 + g3;
int h4 = f4 + g4;
int h5 = f5 + g5;
int h6 = f6 + g6;
int h7 = f7 + g7;
int h8 = f8 + g8;
int h9 = f9 + g9;

h.x0 = h0;
h.x1 = h1;
h.x2 = h2;
h.x3 = h3;
h.x4 = h4;
h.x5 = h5;
h.x6 = h6;
h.x7 = h7;
h.x8 = h8;
h.x9 = h9;
}
}
}

+ 71
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cmov.cs View File

@@ -0,0 +1,71 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
Replace (f,g) with (g,g) if b == 1;
replace (f,g) with (f,g) if b == 0.

Preconditions: b in {0,1}.
*/

//void fe_cmov(fe f,const fe g,unsigned int b)
internal static void fe_cmov(ref FieldElement f, ref FieldElement g, int b)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;
int g0 = g.x0;
int g1 = g.x1;
int g2 = g.x2;
int g3 = g.x3;
int g4 = g.x4;
int g5 = g.x5;
int g6 = g.x6;
int g7 = g.x7;
int g8 = g.x8;
int g9 = g.x9;
int x0 = f0 ^ g0;
int x1 = f1 ^ g1;
int x2 = f2 ^ g2;
int x3 = f3 ^ g3;
int x4 = f4 ^ g4;
int x5 = f5 ^ g5;
int x6 = f6 ^ g6;
int x7 = f7 ^ g7;
int x8 = f8 ^ g8;
int x9 = f9 ^ g9;

b = -b;
x0 &= b;
x1 &= b;
x2 &= b;
x3 &= b;
x4 &= b;
x5 &= b;
x6 &= b;
x7 &= b;
x8 &= b;
x9 &= b;
f.x0 = f0 ^ x0;
f.x1 = f1 ^ x1;
f.x2 = f2 ^ x2;
f.x3 = f3 ^ x3;
f.x4 = f4 ^ x4;
f.x5 = f5 ^ x5;
f.x6 = f6 ^ x6;
f.x7 = f7 ^ x7;
f.x8 = f8 ^ x8;
f.x9 = f9 ^ x9;
}
}
}

+ 79
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cswap.cs View File

@@ -0,0 +1,79 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
Replace (f,g) with (g,f) if b == 1;
replace (f,g) with (f,g) if b == 0.

Preconditions: b in {0,1}.
*/
public static void fe_cswap(ref FieldElement f, ref FieldElement g, uint b)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;
int g0 = g.x0;
int g1 = g.x1;
int g2 = g.x2;
int g3 = g.x3;
int g4 = g.x4;
int g5 = g.x5;
int g6 = g.x6;
int g7 = g.x7;
int g8 = g.x8;
int g9 = g.x9;
int x0 = f0 ^ g0;
int x1 = f1 ^ g1;
int x2 = f2 ^ g2;
int x3 = f3 ^ g3;
int x4 = f4 ^ g4;
int x5 = f5 ^ g5;
int x6 = f6 ^ g6;
int x7 = f7 ^ g7;
int x8 = f8 ^ g8;
int x9 = f9 ^ g9;

int negb = unchecked((int)-b);
x0 &= negb;
x1 &= negb;
x2 &= negb;
x3 &= negb;
x4 &= negb;
x5 &= negb;
x6 &= negb;
x7 &= negb;
x8 &= negb;
x9 &= negb;
f.x0 = f0 ^ x0;
f.x1 = f1 ^ x1;
f.x2 = f2 ^ x2;
f.x3 = f3 ^ x3;
f.x4 = f4 ^ x4;
f.x5 = f5 ^ x5;
f.x6 = f6 ^ x6;
f.x7 = f7 ^ x7;
f.x8 = f8 ^ x8;
f.x9 = f9 ^ x9;
g.x0 = g0 ^ x0;
g.x1 = g1 ^ x1;
g.x2 = g2 ^ x2;
g.x3 = g3 ^ x3;
g.x4 = g4 ^ x4;
g.x5 = g5 ^ x5;
g.x6 = g6 ^ x6;
g.x7 = g7 ^ x7;
g.x8 = g8 ^ x8;
g.x9 = g9 ^ x9;
}
}
}

+ 102
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_frombytes.cs View File

@@ -0,0 +1,102 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
private static long load_3(byte[] data, int offset)
{
uint result;
result = data[offset + 0];
result |= (uint)data[offset + 1] << 8;
result |= (uint)data[offset + 2] << 16;
return (long)(ulong)result;
}

private static long load_4(byte[] data, int offset)
{
uint result;
result = data[offset + 0];
result |= (uint)data[offset + 1] << 8;
result |= (uint)data[offset + 2] << 16;
result |= (uint)data[offset + 3] << 24;
return (long)(ulong)result;
}

// Ignores top bit of h.
internal static void fe_frombytes(out FieldElement h, byte[] data, int offset)
{
var h0 = load_4(data, offset);
var h1 = load_3(data, offset + 4) << 6;
var h2 = load_3(data, offset + 7) << 5;
var h3 = load_3(data, offset + 10) << 3;
var h4 = load_3(data, offset + 13) << 2;
var h5 = load_4(data, offset + 16);
var h6 = load_3(data, offset + 20) << 7;
var h7 = load_3(data, offset + 23) << 5;
var h8 = load_3(data, offset + 26) << 4;
var h9 = (load_3(data, offset + 29) & 8388607) << 2;

var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25;

var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26;

h.x0 = (int)h0;
h.x1 = (int)h1;
h.x2 = (int)h2;
h.x3 = (int)h3;
h.x4 = (int)h4;
h.x5 = (int)h5;
h.x6 = (int)h6;
h.x7 = (int)h7;
h.x8 = (int)h8;
h.x9 = (int)h9;
}

// does NOT ignore top bit
internal static void fe_frombytes2(out FieldElement h, byte[] data, int offset)
{
var h0 = load_4(data, offset);
var h1 = load_3(data, offset + 4) << 6;
var h2 = load_3(data, offset + 7) << 5;
var h3 = load_3(data, offset + 10) << 3;
var h4 = load_3(data, offset + 13) << 2;
var h5 = load_4(data, offset + 16);
var h6 = load_3(data, offset + 20) << 7;
var h7 = load_3(data, offset + 23) << 5;
var h8 = load_3(data, offset + 26) << 4;
var h9 = load_3(data, offset + 29) << 2;

var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25;

var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26;

h.x0 = (int)h0;
h.x1 = (int)h1;
h.x2 = (int)h2;
h.x3 = (int)h3;
h.x4 = (int)h4;
h.x5 = (int)h5;
h.x6 = (int)h6;
h.x7 = (int)h7;
h.x8 = (int)h8;
h.x9 = (int)h9;
}
}
}

+ 128
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_invert.cs View File

@@ -0,0 +1,128 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
internal static void fe_invert(out FieldElement result, ref FieldElement z)
{
FieldElement t0, t1, t2, t3;
int i;

/* qhasm: z2 = z1^2^1 */
/* asm 1: fe_sq(>z2=fe#1,<z1=fe#11); for (i = 1;i < 1;++i) fe_sq(>z2=fe#1,>z2=fe#1); */
/* asm 2: fe_sq(>z2=t0,<z1=z); for (i = 1;i < 1;++i) fe_sq(>z2=t0,>z2=t0); */
fe_sq(out t0, ref z); //for (i = 1; i < 1; ++i) fe_sq(out t0, ref t0);

/* qhasm: z8 = z2^2^2 */
/* asm 1: fe_sq(>z8=fe#2,<z2=fe#1); for (i = 1;i < 2;++i) fe_sq(>z8=fe#2,>z8=fe#2); */
/* asm 2: fe_sq(>z8=t1,<z2=t0); for (i = 1;i < 2;++i) fe_sq(>z8=t1,>z8=t1); */
fe_sq(out t1, ref t0); for (i = 1; i < 2; ++i) fe_sq(out t1, ref t1);

/* qhasm: z9 = z1*z8 */
/* asm 1: fe_mul(>z9=fe#2,<z1=fe#11,<z8=fe#2); */
/* asm 2: fe_mul(>z9=t1,<z1=z,<z8=t1); */
fe_mul(out t1, ref z, ref t1);

/* qhasm: z11 = z2*z9 */
/* asm 1: fe_mul(>z11=fe#1,<z2=fe#1,<z9=fe#2); */
/* asm 2: fe_mul(>z11=t0,<z2=t0,<z9=t1); */
fe_mul(out t0, ref t0, ref t1);

/* qhasm: z22 = z11^2^1 */
/* asm 1: fe_sq(>z22=fe#3,<z11=fe#1); for (i = 1;i < 1;++i) fe_sq(>z22=fe#3,>z22=fe#3); */
/* asm 2: fe_sq(>z22=t2,<z11=t0); for (i = 1;i < 1;++i) fe_sq(>z22=t2,>z22=t2); */
fe_sq(out t2, ref t0); //for (i = 1; i < 1; ++i) fe_sq(out t2, ref t2);

/* qhasm: z_5_0 = z9*z22 */
/* asm 1: fe_mul(>z_5_0=fe#2,<z9=fe#2,<z22=fe#3); */
/* asm 2: fe_mul(>z_5_0=t1,<z9=t1,<z22=t2); */
fe_mul(out t1, ref t1, ref t2);

/* qhasm: z_10_5 = z_5_0^2^5 */
/* asm 1: fe_sq(>z_10_5=fe#3,<z_5_0=fe#2); for (i = 1;i < 5;++i) fe_sq(>z_10_5=fe#3,>z_10_5=fe#3); */
/* asm 2: fe_sq(>z_10_5=t2,<z_5_0=t1); for (i = 1;i < 5;++i) fe_sq(>z_10_5=t2,>z_10_5=t2); */
fe_sq(out t2, ref t1); for (i = 1; i < 5; ++i) fe_sq(out t2, ref t2);

/* qhasm: z_10_0 = z_10_5*z_5_0 */
/* asm 1: fe_mul(>z_10_0=fe#2,<z_10_5=fe#3,<z_5_0=fe#2); */
/* asm 2: fe_mul(>z_10_0=t1,<z_10_5=t2,<z_5_0=t1); */
fe_mul(out t1, ref t2, ref t1);

/* qhasm: z_20_10 = z_10_0^2^10 */
/* asm 1: fe_sq(>z_20_10=fe#3,<z_10_0=fe#2); for (i = 1;i < 10;++i) fe_sq(>z_20_10=fe#3,>z_20_10=fe#3); */
/* asm 2: fe_sq(>z_20_10=t2,<z_10_0=t1); for (i = 1;i < 10;++i) fe_sq(>z_20_10=t2,>z_20_10=t2); */
fe_sq(out t2, ref t1); for (i = 1; i < 10; ++i) fe_sq(out t2, ref t2);

/* qhasm: z_20_0 = z_20_10*z_10_0 */
/* asm 1: fe_mul(>z_20_0=fe#3,<z_20_10=fe#3,<z_10_0=fe#2); */
/* asm 2: fe_mul(>z_20_0=t2,<z_20_10=t2,<z_10_0=t1); */
fe_mul(out t2, ref t2, ref t1);

/* qhasm: z_40_20 = z_20_0^2^20 */
/* asm 1: fe_sq(>z_40_20=fe#4,<z_20_0=fe#3); for (i = 1;i < 20;++i) fe_sq(>z_40_20=fe#4,>z_40_20=fe#4); */
/* asm 2: fe_sq(>z_40_20=t3,<z_20_0=t2); for (i = 1;i < 20;++i) fe_sq(>z_40_20=t3,>z_40_20=t3); */
fe_sq(out t3, ref t2); for (i = 1; i < 20; ++i) fe_sq(out t3, ref t3);

/* qhasm: z_40_0 = z_40_20*z_20_0 */
/* asm 1: fe_mul(>z_40_0=fe#3,<z_40_20=fe#4,<z_20_0=fe#3); */
/* asm 2: fe_mul(>z_40_0=t2,<z_40_20=t3,<z_20_0=t2); */
fe_mul(out t2, ref t3, ref t2);

/* qhasm: z_50_10 = z_40_0^2^10 */
/* asm 1: fe_sq(>z_50_10=fe#3,<z_40_0=fe#3); for (i = 1;i < 10;++i) fe_sq(>z_50_10=fe#3,>z_50_10=fe#3); */
/* asm 2: fe_sq(>z_50_10=t2,<z_40_0=t2); for (i = 1;i < 10;++i) fe_sq(>z_50_10=t2,>z_50_10=t2); */
fe_sq(out t2, ref t2); for (i = 1; i < 10; ++i) fe_sq(out t2, ref t2);

/* qhasm: z_50_0 = z_50_10*z_10_0 */
/* asm 1: fe_mul(>z_50_0=fe#2,<z_50_10=fe#3,<z_10_0=fe#2); */
/* asm 2: fe_mul(>z_50_0=t1,<z_50_10=t2,<z_10_0=t1); */
fe_mul(out t1, ref t2, ref t1);

/* qhasm: z_100_50 = z_50_0^2^50 */
/* asm 1: fe_sq(>z_100_50=fe#3,<z_50_0=fe#2); for (i = 1;i < 50;++i) fe_sq(>z_100_50=fe#3,>z_100_50=fe#3); */
/* asm 2: fe_sq(>z_100_50=t2,<z_50_0=t1); for (i = 1;i < 50;++i) fe_sq(>z_100_50=t2,>z_100_50=t2); */
fe_sq(out t2, ref t1); for (i = 1; i < 50; ++i) fe_sq(out t2, ref t2);

/* qhasm: z_100_0 = z_100_50*z_50_0 */
/* asm 1: fe_mul(>z_100_0=fe#3,<z_100_50=fe#3,<z_50_0=fe#2); */
/* asm 2: fe_mul(>z_100_0=t2,<z_100_50=t2,<z_50_0=t1); */
fe_mul(out t2, ref t2, ref t1);

/* qhasm: z_200_100 = z_100_0^2^100 */
/* asm 1: fe_sq(>z_200_100=fe#4,<z_100_0=fe#3); for (i = 1;i < 100;++i) fe_sq(>z_200_100=fe#4,>z_200_100=fe#4); */
/* asm 2: fe_sq(>z_200_100=t3,<z_100_0=t2); for (i = 1;i < 100;++i) fe_sq(>z_200_100=t3,>z_200_100=t3); */
fe_sq(out t3, ref t2); for (i = 1; i < 100; ++i) fe_sq(out t3, ref t3);

/* qhasm: z_200_0 = z_200_100*z_100_0 */
/* asm 1: fe_mul(>z_200_0=fe#3,<z_200_100=fe#4,<z_100_0=fe#3); */
/* asm 2: fe_mul(>z_200_0=t2,<z_200_100=t3,<z_100_0=t2); */
fe_mul(out t2, ref t3, ref t2);

/* qhasm: z_250_50 = z_200_0^2^50 */
/* asm 1: fe_sq(>z_250_50=fe#3,<z_200_0=fe#3); for (i = 1;i < 50;++i) fe_sq(>z_250_50=fe#3,>z_250_50=fe#3); */
/* asm 2: fe_sq(>z_250_50=t2,<z_200_0=t2); for (i = 1;i < 50;++i) fe_sq(>z_250_50=t2,>z_250_50=t2); */
fe_sq(out t2, ref t2); for (i = 1; i < 50; ++i) fe_sq(out t2, ref t2);

/* qhasm: z_250_0 = z_250_50*z_50_0 */
/* asm 1: fe_mul(>z_250_0=fe#2,<z_250_50=fe#3,<z_50_0=fe#2); */
/* asm 2: fe_mul(>z_250_0=t1,<z_250_50=t2,<z_50_0=t1); */
fe_mul(out t1, ref t2, ref t1);

/* qhasm: z_255_5 = z_250_0^2^5 */
/* asm 1: fe_sq(>z_255_5=fe#2,<z_250_0=fe#2); for (i = 1;i < 5;++i) fe_sq(>z_255_5=fe#2,>z_255_5=fe#2); */
/* asm 2: fe_sq(>z_255_5=t1,<z_250_0=t1); for (i = 1;i < 5;++i) fe_sq(>z_255_5=t1,>z_255_5=t1); */
fe_sq(out t1, ref t1); for (i = 1; i < 5; ++i) fe_sq(out t1, ref t1);

/* qhasm: z_255_21 = z_255_5*z11 */
/* asm 1: fe_mul(>z_255_21=fe#12,<z_255_5=fe#2,<z11=fe#1); */
/* asm 2: fe_mul(>z_255_21=out,<z_255_5=t1,<z11=t0); */
fe_mul(out result, ref t1, ref t0);

/* qhasm: return */


return;
}
}
}

+ 22
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_isnegative.cs View File

@@ -0,0 +1,22 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
return 1 if f is in {1,3,5,...,q-2}
return 0 if f is in {0,2,4,...,q-1}

Preconditions:
|f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
*/
//int fe_isnegative(const fe f)
public static int fe_isnegative(ref FieldElement f)
{
FieldElement fr;
fe_reduce(out fr, ref f);
return fr.x0 & 1;
}
}
}

+ 37
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_isnonzero.cs View File

@@ -0,0 +1,37 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
return 1 if f == 0
return 0 if f != 0

Preconditions:
|f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
*/
// Todo: Discuss this with upstream
// Above comment is from the original code. But I believe the original code returned
// 0 if f == 0
// -1 if f != 0
// This code actually returns 0 if f==0 and 1 if f != 0
internal static int fe_isnonzero(ref FieldElement f)
{
FieldElement fr;
fe_reduce(out fr, ref f);
int differentBits = 0;
differentBits |= fr.x0;
differentBits |= fr.x1;
differentBits |= fr.x2;
differentBits |= fr.x3;
differentBits |= fr.x4;
differentBits |= fr.x5;
differentBits |= fr.x6;
differentBits |= fr.x7;
differentBits |= fr.x8;
differentBits |= fr.x9;
return (int)((unchecked((uint)differentBits - 1) >> 31) ^ 1);
}
}
}

+ 263
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul.cs View File

@@ -0,0 +1,263 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
h = f * g
Can overlap h with f or g.

Preconditions:
|f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
|g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.

Postconditions:
|h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
*/

/*
Notes on implementation strategy:

Using schoolbook multiplication.
Karatsuba would save a little in some cost models.

Most multiplications by 2 and 19 are 32-bit precomputations;
cheaper than 64-bit postcomputations.

There is one remaining multiplication by 19 in the carry chain;
one *19 precomputation can be merged into this,
but the resulting data flow is considerably less clean.

There are 12 carries below.
10 of them are 2-way parallelizable and vectorizable.
Can get away with 11 carries, but then data flow is much deeper.

With tighter constraints on inputs can squeeze carries into int32.
*/

internal static void fe_mul(out FieldElement h, ref FieldElement f, ref FieldElement g)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;
int g0 = g.x0;
int g1 = g.x1;
int g2 = g.x2;
int g3 = g.x3;
int g4 = g.x4;
int g5 = g.x5;
int g6 = g.x6;
int g7 = g.x7;
int g8 = g.x8;
int g9 = g.x9;

int g1_19 = 19 * g1; /* 1.959375*2^29 */
int g2_19 = 19 * g2; /* 1.959375*2^30; still ok */
int g3_19 = 19 * g3;
int g4_19 = 19 * g4;
int g5_19 = 19 * g5;
int g6_19 = 19 * g6;
int g7_19 = 19 * g7;
int g8_19 = 19 * g8;
int g9_19 = 19 * g9;

int f1_2 = 2 * f1;
int f3_2 = 2 * f3;
int f5_2 = 2 * f5;
int f7_2 = 2 * f7;
int f9_2 = 2 * f9;

long f0g0 = f0 * (long)g0;
long f0g1 = f0 * (long)g1;
long f0g2 = f0 * (long)g2;
long f0g3 = f0 * (long)g3;
long f0g4 = f0 * (long)g4;
long f0g5 = f0 * (long)g5;
long f0g6 = f0 * (long)g6;
long f0g7 = f0 * (long)g7;
long f0g8 = f0 * (long)g8;
long f0g9 = f0 * (long)g9;
long f1g0 = f1 * (long)g0;
long f1g1_2 = f1_2 * (long)g1;
long f1g2 = f1 * (long)g2;
long f1g3_2 = f1_2 * (long)g3;
long f1g4 = f1 * (long)g4;
long f1g5_2 = f1_2 * (long)g5;
long f1g6 = f1 * (long)g6;
long f1g7_2 = f1_2 * (long)g7;
long f1g8 = f1 * (long)g8;
long f1g9_38 = f1_2 * (long)g9_19;
long f2g0 = f2 * (long)g0;
long f2g1 = f2 * (long)g1;
long f2g2 = f2 * (long)g2;
long f2g3 = f2 * (long)g3;
long f2g4 = f2 * (long)g4;
long f2g5 = f2 * (long)g5;
long f2g6 = f2 * (long)g6;
long f2g7 = f2 * (long)g7;
long f2g8_19 = f2 * (long)g8_19;
long f2g9_19 = f2 * (long)g9_19;
long f3g0 = f3 * (long)g0;
long f3g1_2 = f3_2 * (long)g1;
long f3g2 = f3 * (long)g2;
long f3g3_2 = f3_2 * (long)g3;
long f3g4 = f3 * (long)g4;
long f3g5_2 = f3_2 * (long)g5;
long f3g6 = f3 * (long)g6;
long f3g7_38 = f3_2 * (long)g7_19;
long f3g8_19 = f3 * (long)g8_19;
long f3g9_38 = f3_2 * (long)g9_19;
long f4g0 = f4 * (long)g0;
long f4g1 = f4 * (long)g1;
long f4g2 = f4 * (long)g2;
long f4g3 = f4 * (long)g3;
long f4g4 = f4 * (long)g4;
long f4g5 = f4 * (long)g5;
long f4g6_19 = f4 * (long)g6_19;
long f4g7_19 = f4 * (long)g7_19;
long f4g8_19 = f4 * (long)g8_19;
long f4g9_19 = f4 * (long)g9_19;
long f5g0 = f5 * (long)g0;
long f5g1_2 = f5_2 * (long)g1;
long f5g2 = f5 * (long)g2;
long f5g3_2 = f5_2 * (long)g3;
long f5g4 = f5 * (long)g4;
long f5g5_38 = f5_2 * (long)g5_19;
long f5g6_19 = f5 * (long)g6_19;
long f5g7_38 = f5_2 * (long)g7_19;
long f5g8_19 = f5 * (long)g8_19;
long f5g9_38 = f5_2 * (long)g9_19;
long f6g0 = f6 * (long)g0;
long f6g1 = f6 * (long)g1;
long f6g2 = f6 * (long)g2;
long f6g3 = f6 * (long)g3;
long f6g4_19 = f6 * (long)g4_19;
long f6g5_19 = f6 * (long)g5_19;
long f6g6_19 = f6 * (long)g6_19;
long f6g7_19 = f6 * (long)g7_19;
long f6g8_19 = f6 * (long)g8_19;
long f6g9_19 = f6 * (long)g9_19;
long f7g0 = f7 * (long)g0;
long f7g1_2 = f7_2 * (long)g1;
long f7g2 = f7 * (long)g2;
long f7g3_38 = f7_2 * (long)g3_19;
long f7g4_19 = f7 * (long)g4_19;
long f7g5_38 = f7_2 * (long)g5_19;
long f7g6_19 = f7 * (long)g6_19;
long f7g7_38 = f7_2 * (long)g7_19;
long f7g8_19 = f7 * (long)g8_19;
long f7g9_38 = f7_2 * (long)g9_19;
long f8g0 = f8 * (long)g0;
long f8g1 = f8 * (long)g1;
long f8g2_19 = f8 * (long)g2_19;
long f8g3_19 = f8 * (long)g3_19;
long f8g4_19 = f8 * (long)g4_19;
long f8g5_19 = f8 * (long)g5_19;
long f8g6_19 = f8 * (long)g6_19;
long f8g7_19 = f8 * (long)g7_19;
long f8g8_19 = f8 * (long)g8_19;
long f8g9_19 = f8 * (long)g9_19;
long f9g0 = f9 * (long)g0;
long f9g1_38 = f9_2 * (long)g1_19;
long f9g2_19 = f9 * (long)g2_19;
long f9g3_38 = f9_2 * (long)g3_19;
long f9g4_19 = f9 * (long)g4_19;
long f9g5_38 = f9_2 * (long)g5_19;
long f9g6_19 = f9 * (long)g6_19;
long f9g7_38 = f9_2 * (long)g7_19;
long f9g8_19 = f9 * (long)g8_19;
long f9g9_38 = f9_2 * (long)g9_19;

long h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38;
long h1 = f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19;
long h2 = f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38;
long h3 = f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19;
long h4 = f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38;
long h5 = f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19;
long h6 = f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38;
long h7 = f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19;
long h8 = f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38;
long h9 = f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0;

long carry0;
long carry1;
long carry2;
long carry3;
long carry4;
long carry5;
long carry6;
long carry7;
long carry8;
long carry9;

/*
|h0| <= (1.65*1.65*2^52*(1+19+19+19+19)+1.65*1.65*2^50*(38+38+38+38+38))
i.e. |h0| <= 1.4*2^60; narrower ranges for h2, h4, h6, h8
|h1| <= (1.65*1.65*2^51*(1+1+19+19+19+19+19+19+19+19))
i.e. |h1| <= 1.7*2^59; narrower ranges for h3, h5, h7, h9
*/

carry0 = (h0 + (long)(1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
carry4 = (h4 + (long)(1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
/* |h0| <= 2^25 */
/* |h4| <= 2^25 */
/* |h1| <= 1.71*2^59 */
/* |h5| <= 1.71*2^59 */

carry1 = (h1 + (long)(1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
carry5 = (h5 + (long)(1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
/* |h1| <= 2^24; from now on fits into int32 */
/* |h5| <= 2^24; from now on fits into int32 */
/* |h2| <= 1.41*2^60 */
/* |h6| <= 1.41*2^60 */

carry2 = (h2 + (long)(1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
carry6 = (h6 + (long)(1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
/* |h2| <= 2^25; from now on fits into int32 unchanged */
/* |h6| <= 2^25; from now on fits into int32 unchanged */
/* |h3| <= 1.71*2^59 */
/* |h7| <= 1.71*2^59 */

carry3 = (h3 + (long)(1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
carry7 = (h7 + (long)(1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25;
/* |h3| <= 2^24; from now on fits into int32 unchanged */
/* |h7| <= 2^24; from now on fits into int32 unchanged */
/* |h4| <= 1.72*2^34 */
/* |h8| <= 1.41*2^60 */

carry4 = (h4 + (long)(1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
carry8 = (h8 + (long)(1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26;
/* |h4| <= 2^25; from now on fits into int32 unchanged */
/* |h8| <= 2^25; from now on fits into int32 unchanged */
/* |h5| <= 1.01*2^24 */
/* |h9| <= 1.71*2^59 */

carry9 = (h9 + (long)(1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
/* |h9| <= 2^24; from now on fits into int32 unchanged */
/* |h0| <= 1.1*2^39 */

carry0 = (h0 + (long)(1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
/* |h0| <= 2^25; from now on fits into int32 unchanged */
/* |h1| <= 1.01*2^24 */

h.x0 = (int)h0;
h.x1 = (int)h1;
h.x2 = (int)h2;
h.x3 = (int)h3;
h.x4 = (int)h4;
h.x5 = (int)h5;
h.x6 = (int)h6;
h.x7 = (int)h7;
h.x8 = (int)h8;
h.x9 = (int)h9;
}
}
}

+ 67
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul121666.cs View File

@@ -0,0 +1,67 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{

/*
h = f * 121666
Can overlap h with f.

Preconditions:
|f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.

Postconditions:
|h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
*/

public static void fe_mul121666(out FieldElement h, ref FieldElement f)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;

var h0 = f0 * 121666L;
var h1 = f1 * 121666L;
var h2 = f2 * 121666L;
var h3 = f3 * 121666L;
var h4 = f4 * 121666L;
var h5 = f5 * 121666L;
var h6 = f6 * 121666L;
var h7 = f7 * 121666L;
var h8 = f8 * 121666L;
var h9 = f9 * 121666L;

var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25;

var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26;

h.x0 = (int)h0;
h.x1 = (int)h1;
h.x2 = (int)h2;
h.x3 = (int)h3;
h.x4 = (int)h4;
h.x5 = (int)h5;
h.x6 = (int)h6;
h.x7 = (int)h7;
h.x8 = (int)h8;
h.x9 = (int)h9;
}
}
}

+ 51
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_neg.cs View File

@@ -0,0 +1,51 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
h = -f

Preconditions:
|f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.

Postconditions:
|h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
*/
internal static void fe_neg(out FieldElement h, ref FieldElement f)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;
int h0 = -f0;
int h1 = -f1;
int h2 = -f2;
int h3 = -f3;
int h4 = -f4;
int h5 = -f5;
int h6 = -f6;
int h7 = -f7;
int h8 = -f8;
int h9 = -f9;

h.x0 = h0;
h.x1 = h1;
h.x2 = h2;
h.x3 = h3;
h.x4 = h4;
h.x5 = h5;
h.x6 = h6;
h.x7 = h7;
h.x8 = h8;
h.x9 = h9;
}
}
}

+ 125
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_pow22523.cs View File

@@ -0,0 +1,125 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
internal static void fe_pow22523(out FieldElement result, ref FieldElement z)
{
FieldElement t0, t1, t2;
int i;

/* qhasm: z2 = z1^2^1 */
/* asm 1: fe_sq(>z2=fe#1,<z1=fe#11); for (i = 1;i < 1;++i) fe_sq(>z2=fe#1,>z2=fe#1); */
/* asm 2: fe_sq(>z2=t0,<z1=z); for (i = 1;i < 1;++i) fe_sq(>z2=t0,>z2=t0); */
fe_sq(out t0, ref z); //for (i = 1; i < 1; ++i) fe_sq(out t0, ref t0);

/* qhasm: z8 = z2^2^2 */
/* asm 1: fe_sq(>z8=fe#2,<z2=fe#1); for (i = 1;i < 2;++i) fe_sq(>z8=fe#2,>z8=fe#2); */
/* asm 2: fe_sq(>z8=t1,<z2=t0); for (i = 1;i < 2;++i) fe_sq(>z8=t1,>z8=t1); */
fe_sq(out t1, ref t0); for (i = 1; i < 2; ++i) fe_sq(out t1, ref t1);

/* qhasm: z9 = z1*z8 */
/* asm 1: fe_mul(>z9=fe#2,<z1=fe#11,<z8=fe#2); */
/* asm 2: fe_mul(>z9=t1,<z1=z,<z8=t1); */
fe_mul(out t1, ref z, ref t1);

/* qhasm: z11 = z2*z9 */
/* asm 1: fe_mul(>z11=fe#1,<z2=fe#1,<z9=fe#2); */
/* asm 2: fe_mul(>z11=t0,<z2=t0,<z9=t1); */
fe_mul(out t0, ref t0, ref t1);

/* qhasm: z22 = z11^2^1 */
/* asm 1: fe_sq(>z22=fe#1,<z11=fe#1); for (i = 1;i < 1;++i) fe_sq(>z22=fe#1,>z22=fe#1); */
/* asm 2: fe_sq(>z22=t0,<z11=t0); for (i = 1;i < 1;++i) fe_sq(>z22=t0,>z22=t0); */
fe_sq(out t0, ref t0); //for (i = 1; i < 1; ++i) fe_sq(out t0, ref t0);

/* qhasm: z_5_0 = z9*z22 */
/* asm 1: fe_mul(>z_5_0=fe#1,<z9=fe#2,<z22=fe#1); */
/* asm 2: fe_mul(>z_5_0=t0,<z9=t1,<z22=t0); */
fe_mul(out t0, ref t1, ref t0);

/* qhasm: z_10_5 = z_5_0^2^5 */
/* asm 1: fe_sq(>z_10_5=fe#2,<z_5_0=fe#1); for (i = 1;i < 5;++i) fe_sq(>z_10_5=fe#2,>z_10_5=fe#2); */
/* asm 2: fe_sq(>z_10_5=t1,<z_5_0=t0); for (i = 1;i < 5;++i) fe_sq(>z_10_5=t1,>z_10_5=t1); */
fe_sq(out t1, ref t0); for (i = 1; i < 5; ++i) fe_sq(out t1, ref t1);

/* qhasm: z_10_0 = z_10_5*z_5_0 */
/* asm 1: fe_mul(>z_10_0=fe#1,<z_10_5=fe#2,<z_5_0=fe#1); */
/* asm 2: fe_mul(>z_10_0=t0,<z_10_5=t1,<z_5_0=t0); */
fe_mul(out t0, ref t1, ref t0);

/* qhasm: z_20_10 = z_10_0^2^10 */
/* asm 1: fe_sq(>z_20_10=fe#2,<z_10_0=fe#1); for (i = 1;i < 10;++i) fe_sq(>z_20_10=fe#2,>z_20_10=fe#2); */
/* asm 2: fe_sq(>z_20_10=t1,<z_10_0=t0); for (i = 1;i < 10;++i) fe_sq(>z_20_10=t1,>z_20_10=t1); */
fe_sq(out t1, ref t0); for (i = 1; i < 10; ++i) fe_sq(out t1, ref t1);

/* qhasm: z_20_0 = z_20_10*z_10_0 */
/* asm 1: fe_mul(>z_20_0=fe#2,<z_20_10=fe#2,<z_10_0=fe#1); */
/* asm 2: fe_mul(>z_20_0=t1,<z_20_10=t1,<z_10_0=t0); */
fe_mul(out t1, ref t1, ref t0);

/* qhasm: z_40_20 = z_20_0^2^20 */
/* asm 1: fe_sq(>z_40_20=fe#3,<z_20_0=fe#2); for (i = 1;i < 20;++i) fe_sq(>z_40_20=fe#3,>z_40_20=fe#3); */
/* asm 2: fe_sq(>z_40_20=t2,<z_20_0=t1); for (i = 1;i < 20;++i) fe_sq(>z_40_20=t2,>z_40_20=t2); */
fe_sq(out t2, ref t1); for (i = 1; i < 20; ++i) fe_sq(out t2, ref t2);

/* qhasm: z_40_0 = z_40_20*z_20_0 */
/* asm 1: fe_mul(>z_40_0=fe#2,<z_40_20=fe#3,<z_20_0=fe#2); */
/* asm 2: fe_mul(>z_40_0=t1,<z_40_20=t2,<z_20_0=t1); */
fe_mul(out t1, ref t2, ref t1);

/* qhasm: z_50_10 = z_40_0^2^10 */
/* asm 1: fe_sq(>z_50_10=fe#2,<z_40_0=fe#2); for (i = 1;i < 10;++i) fe_sq(>z_50_10=fe#2,>z_50_10=fe#2); */
/* asm 2: fe_sq(>z_50_10=t1,<z_40_0=t1); for (i = 1;i < 10;++i) fe_sq(>z_50_10=t1,>z_50_10=t1); */
fe_sq(out t1, ref t1); for (i = 1; i < 10; ++i) fe_sq(out t1, ref t1);

/* qhasm: z_50_0 = z_50_10*z_10_0 */
/* asm 1: fe_mul(>z_50_0=fe#1,<z_50_10=fe#2,<z_10_0=fe#1); */
/* asm 2: fe_mul(>z_50_0=t0,<z_50_10=t1,<z_10_0=t0); */
fe_mul(out t0, ref t1, ref t0);

/* qhasm: z_100_50 = z_50_0^2^50 */
/* asm 1: fe_sq(>z_100_50=fe#2,<z_50_0=fe#1); for (i = 1;i < 50;++i) fe_sq(>z_100_50=fe#2,>z_100_50=fe#2); */
/* asm 2: fe_sq(>z_100_50=t1,<z_50_0=t0); for (i = 1;i < 50;++i) fe_sq(>z_100_50=t1,>z_100_50=t1); */
fe_sq(out t1, ref t0); for (i = 1; i < 50; ++i) fe_sq(out t1, ref t1);

/* qhasm: z_100_0 = z_100_50*z_50_0 */
/* asm 1: fe_mul(>z_100_0=fe#2,<z_100_50=fe#2,<z_50_0=fe#1); */
/* asm 2: fe_mul(>z_100_0=t1,<z_100_50=t1,<z_50_0=t0); */
fe_mul(out t1, ref t1, ref t0);

/* qhasm: z_200_100 = z_100_0^2^100 */
/* asm 1: fe_sq(>z_200_100=fe#3,<z_100_0=fe#2); for (i = 1;i < 100;++i) fe_sq(>z_200_100=fe#3,>z_200_100=fe#3); */
/* asm 2: fe_sq(>z_200_100=t2,<z_100_0=t1); for (i = 1;i < 100;++i) fe_sq(>z_200_100=t2,>z_200_100=t2); */
fe_sq(out t2, ref t1); for (i = 1; i < 100; ++i) fe_sq(out t2, ref t2);

/* qhasm: z_200_0 = z_200_100*z_100_0 */
/* asm 1: fe_mul(>z_200_0=fe#2,<z_200_100=fe#3,<z_100_0=fe#2); */
/* asm 2: fe_mul(>z_200_0=t1,<z_200_100=t2,<z_100_0=t1); */
fe_mul(out t1, ref t2, ref t1);

/* qhasm: z_250_50 = z_200_0^2^50 */
/* asm 1: fe_sq(>z_250_50=fe#2,<z_200_0=fe#2); for (i = 1;i < 50;++i) fe_sq(>z_250_50=fe#2,>z_250_50=fe#2); */
/* asm 2: fe_sq(>z_250_50=t1,<z_200_0=t1); for (i = 1;i < 50;++i) fe_sq(>z_250_50=t1,>z_250_50=t1); */
fe_sq(out t1, ref t1); for (i = 1; i < 50; ++i) fe_sq(out t1, ref t1);

/* qhasm: z_250_0 = z_250_50*z_50_0 */
/* asm 1: fe_mul(>z_250_0=fe#1,<z_250_50=fe#2,<z_50_0=fe#1); */
/* asm 2: fe_mul(>z_250_0=t0,<z_250_50=t1,<z_50_0=t0); */
fe_mul(out t0, ref t1, ref t0);

/* qhasm: z_252_2 = z_250_0^2^2 */
/* asm 1: fe_sq(>z_252_2=fe#1,<z_250_0=fe#1); for (i = 1;i < 2;++i) fe_sq(>z_252_2=fe#1,>z_252_2=fe#1); */
/* asm 2: fe_sq(>z_252_2=t0,<z_250_0=t0); for (i = 1;i < 2;++i) fe_sq(>z_252_2=t0,>z_252_2=t0); */
fe_sq(out t0, ref t0); for (i = 1; i < 2; ++i) fe_sq(out t0, ref t0);

/* qhasm: z_252_3 = z_252_2*z1 */
/* asm 1: fe_mul(>z_252_3=fe#12,<z_252_2=fe#1,<z1=fe#11); */
/* asm 2: fe_mul(>z_252_3=out,<z_252_2=t0,<z1=z); */
fe_mul(out result, ref t0, ref z);

/* qhasm: return */
}
}
}

+ 143
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq.cs View File

@@ -0,0 +1,143 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
h = f * f
Can overlap h with f.

Preconditions:
|f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.

Postconditions:
|h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
*/

/*
See fe_mul.c for discussion of implementation strategy.
*/
internal static void fe_sq(out FieldElement h, ref FieldElement f)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;

int f0_2 = 2 * f0;
int f1_2 = 2 * f1;
int f2_2 = 2 * f2;
int f3_2 = 2 * f3;
int f4_2 = 2 * f4;
int f5_2 = 2 * f5;
int f6_2 = 2 * f6;
int f7_2 = 2 * f7;
int f5_38 = 38 * f5; /* 1.959375*2^30 */
int f6_19 = 19 * f6; /* 1.959375*2^30 */
int f7_38 = 38 * f7; /* 1.959375*2^30 */
int f8_19 = 19 * f8; /* 1.959375*2^30 */
int f9_38 = 38 * f9; /* 1.959375*2^30 */

var f0f0 = f0 * (long)f0;
var f0f1_2 = f0_2 * (long)f1;
var f0f2_2 = f0_2 * (long)f2;
var f0f3_2 = f0_2 * (long)f3;
var f0f4_2 = f0_2 * (long)f4;
var f0f5_2 = f0_2 * (long)f5;
var f0f6_2 = f0_2 * (long)f6;
var f0f7_2 = f0_2 * (long)f7;
var f0f8_2 = f0_2 * (long)f8;
var f0f9_2 = f0_2 * (long)f9;
var f1f1_2 = f1_2 * (long)f1;
var f1f2_2 = f1_2 * (long)f2;
var f1f3_4 = f1_2 * (long)f3_2;
var f1f4_2 = f1_2 * (long)f4;
var f1f5_4 = f1_2 * (long)f5_2;
var f1f6_2 = f1_2 * (long)f6;
var f1f7_4 = f1_2 * (long)f7_2;
var f1f8_2 = f1_2 * (long)f8;
var f1f9_76 = f1_2 * (long)f9_38;
var f2f2 = f2 * (long)f2;
var f2f3_2 = f2_2 * (long)f3;
var f2f4_2 = f2_2 * (long)f4;
var f2f5_2 = f2_2 * (long)f5;
var f2f6_2 = f2_2 * (long)f6;
var f2f7_2 = f2_2 * (long)f7;
var f2f8_38 = f2_2 * (long)f8_19;
var f2f9_38 = f2 * (long)f9_38;
var f3f3_2 = f3_2 * (long)f3;
var f3f4_2 = f3_2 * (long)f4;
var f3f5_4 = f3_2 * (long)f5_2;
var f3f6_2 = f3_2 * (long)f6;
var f3f7_76 = f3_2 * (long)f7_38;
var f3f8_38 = f3_2 * (long)f8_19;
var f3f9_76 = f3_2 * (long)f9_38;
var f4f4 = f4 * (long)f4;
var f4f5_2 = f4_2 * (long)f5;
var f4f6_38 = f4_2 * (long)f6_19;
var f4f7_38 = f4 * (long)f7_38;
var f4f8_38 = f4_2 * (long)f8_19;
var f4f9_38 = f4 * (long)f9_38;
var f5f5_38 = f5 * (long)f5_38;
var f5f6_38 = f5_2 * (long)f6_19;
var f5f7_76 = f5_2 * (long)f7_38;
var f5f8_38 = f5_2 * (long)f8_19;
var f5f9_76 = f5_2 * (long)f9_38;
var f6f6_19 = f6 * (long)f6_19;
var f6f7_38 = f6 * (long)f7_38;
var f6f8_38 = f6_2 * (long)f8_19;
var f6f9_38 = f6 * (long)f9_38;
var f7f7_38 = f7 * (long)f7_38;
var f7f8_38 = f7_2 * (long)f8_19;
var f7f9_76 = f7_2 * (long)f9_38;
var f8f8_19 = f8 * (long)f8_19;
var f8f9_38 = f8 * (long)f9_38;
var f9f9_38 = f9 * (long)f9_38;

var h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38;
var h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38;
var h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19;
var h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38;
var h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38;
var h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38;
var h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19;
var h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38;
var h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38;
var h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2;

var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25;

carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;

var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26;
var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;

carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;

h.x0 = (int)h0;
h.x1 = (int)h1;
h.x2 = (int)h2;
h.x3 = (int)h3;
h.x4 = (int)h4;
h.x5 = (int)h5;
h.x6 = (int)h6;
h.x7 = (int)h7;
h.x8 = (int)h8;
h.x9 = (int)h9;
}
}
}

+ 154
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq2.cs View File

@@ -0,0 +1,154 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
h = 2 * f * f
Can overlap h with f.

Preconditions:
|f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.

Postconditions:
|h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
*/

/*
See fe_mul.c for discussion of implementation strategy.
*/
internal static void fe_sq2(out FieldElement h, ref FieldElement f)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;

int f0_2 = 2 * f0;
int f1_2 = 2 * f1;
int f2_2 = 2 * f2;
int f3_2 = 2 * f3;
int f4_2 = 2 * f4;
int f5_2 = 2 * f5;
int f6_2 = 2 * f6;
int f7_2 = 2 * f7;
int f5_38 = 38 * f5; /* 1.959375*2^30 */
int f6_19 = 19 * f6; /* 1.959375*2^30 */
int f7_38 = 38 * f7; /* 1.959375*2^30 */
int f8_19 = 19 * f8; /* 1.959375*2^30 */
int f9_38 = 38 * f9; /* 1.959375*2^30 */

var f0f0 = f0 * (long)f0;
var f0f1_2 = f0_2 * (long)f1;
var f0f2_2 = f0_2 * (long)f2;
var f0f3_2 = f0_2 * (long)f3;
var f0f4_2 = f0_2 * (long)f4;
var f0f5_2 = f0_2 * (long)f5;
var f0f6_2 = f0_2 * (long)f6;
var f0f7_2 = f0_2 * (long)f7;
var f0f8_2 = f0_2 * (long)f8;
var f0f9_2 = f0_2 * (long)f9;
var f1f1_2 = f1_2 * (long)f1;
var f1f2_2 = f1_2 * (long)f2;
var f1f3_4 = f1_2 * (long)f3_2;
var f1f4_2 = f1_2 * (long)f4;
var f1f5_4 = f1_2 * (long)f5_2;
var f1f6_2 = f1_2 * (long)f6;
var f1f7_4 = f1_2 * (long)f7_2;
var f1f8_2 = f1_2 * (long)f8;
var f1f9_76 = f1_2 * (long)f9_38;
var f2f2 = f2 * (long)f2;
var f2f3_2 = f2_2 * (long)f3;
var f2f4_2 = f2_2 * (long)f4;
var f2f5_2 = f2_2 * (long)f5;
var f2f6_2 = f2_2 * (long)f6;
var f2f7_2 = f2_2 * (long)f7;
var f2f8_38 = f2_2 * (long)f8_19;
var f2f9_38 = f2 * (long)f9_38;
var f3f3_2 = f3_2 * (long)f3;
var f3f4_2 = f3_2 * (long)f4;
var f3f5_4 = f3_2 * (long)f5_2;
var f3f6_2 = f3_2 * (long)f6;
var f3f7_76 = f3_2 * (long)f7_38;
var f3f8_38 = f3_2 * (long)f8_19;
var f3f9_76 = f3_2 * (long)f9_38;
var f4f4 = f4 * (long)f4;
var f4f5_2 = f4_2 * (long)f5;
var f4f6_38 = f4_2 * (long)f6_19;
var f4f7_38 = f4 * (long)f7_38;
var f4f8_38 = f4_2 * (long)f8_19;
var f4f9_38 = f4 * (long)f9_38;
var f5f5_38 = f5 * (long)f5_38;
var f5f6_38 = f5_2 * (long)f6_19;
var f5f7_76 = f5_2 * (long)f7_38;
var f5f8_38 = f5_2 * (long)f8_19;
var f5f9_76 = f5_2 * (long)f9_38;
var f6f6_19 = f6 * (long)f6_19;
var f6f7_38 = f6 * (long)f7_38;
var f6f8_38 = f6_2 * (long)f8_19;
var f6f9_38 = f6 * (long)f9_38;
var f7f7_38 = f7 * (long)f7_38;
var f7f8_38 = f7_2 * (long)f8_19;
var f7f9_76 = f7_2 * (long)f9_38;
var f8f8_19 = f8 * (long)f8_19;
var f8f9_38 = f8 * (long)f9_38;
var f9f9_38 = f9 * (long)f9_38;

var h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38;
var h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38;
var h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19;
var h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38;
var h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38;
var h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38;
var h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19;
var h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38;
var h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38;
var h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2;

h0 += h0;
h1 += h1;
h2 += h2;
h3 += h3;
h4 += h4;
h5 += h5;
h6 += h6;
h7 += h7;
h8 += h8;
h9 += h9;

var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25;

carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26;

var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26;
var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;

carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26;

h.x0 = (int)h0;
h.x1 = (int)h1;
h.x2 = (int)h2;
h.x3 = (int)h3;
h.x4 = (int)h4;
h.x5 = (int)h5;
h.x6 = (int)h6;
h.x7 = (int)h7;
h.x8 = (int)h8;
h.x9 = (int)h9;
}
}
}

+ 66
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sub.cs View File

@@ -0,0 +1,66 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
h = f - g
Can overlap h with f or g.

Preconditions:
|f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.

Postconditions:
|h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
*/

internal static void fe_sub(out FieldElement h, ref FieldElement f, ref FieldElement g)
{
int f0 = f.x0;
int f1 = f.x1;
int f2 = f.x2;
int f3 = f.x3;
int f4 = f.x4;
int f5 = f.x5;
int f6 = f.x6;
int f7 = f.x7;
int f8 = f.x8;
int f9 = f.x9;

int g0 = g.x0;
int g1 = g.x1;
int g2 = g.x2;
int g3 = g.x3;
int g4 = g.x4;
int g5 = g.x5;
int g6 = g.x6;
int g7 = g.x7;
int g8 = g.x8;
int g9 = g.x9;

int h0 = f0 - g0;
int h1 = f1 - g1;
int h2 = f2 - g2;
int h3 = f3 - g3;
int h4 = f4 - g4;
int h5 = f5 - g5;
int h6 = f6 - g6;
int h7 = f7 - g7;
int h8 = f8 - g8;
int h9 = f9 - g9;

h.x0 = h0;
h.x1 = h1;
h.x2 = h2;
h.x3 = h3;
h.x4 = h4;
h.x5 = h5;
h.x6 = h6;
h.x7 = h7;
h.x8 = h8;
h.x9 = h9;
}
}
}

+ 145
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_tobytes.cs View File

@@ -0,0 +1,145 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class FieldOperations
{
/*
Preconditions:
|h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.

Write p=2^255-19; q=floor(h/p).
Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).

Proof:
Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4.

Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
Then 0<y<1.

Write r=h-pq.
Have 0<=r<=p-1=2^255-20.
Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1.

Write x=r+19(2^-255)r+y.
Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.

Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))
so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
*/
internal static void fe_tobytes(byte[] s, int offset, ref FieldElement h)
{
FieldElement hr;
fe_reduce(out hr, ref h);

int h0 = hr.x0;
int h1 = hr.x1;
int h2 = hr.x2;
int h3 = hr.x3;
int h4 = hr.x4;
int h5 = hr.x5;
int h6 = hr.x6;
int h7 = hr.x7;
int h8 = hr.x8;
int h9 = hr.x9;

/*
Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
Have h0+...+2^230 h9 between 0 and 2^255-1;
evidently 2^255 h10-2^255 q = 0.
Goal: Output h0+...+2^230 h9.
*/
unchecked
{
s[offset + 0] = (byte) (h0 >> 0);
s[offset + 1] = (byte) (h0 >> 8);
s[offset + 2] = (byte) (h0 >> 16);
s[offset + 3] = (byte) ((h0 >> 24) | (h1 << 2));
s[offset + 4] = (byte) (h1 >> 6);
s[offset + 5] = (byte) (h1 >> 14);
s[offset + 6] = (byte) ((h1 >> 22) | (h2 << 3));
s[offset + 7] = (byte) (h2 >> 5);
s[offset + 8] = (byte) (h2 >> 13);
s[offset + 9] = (byte) ((h2 >> 21) | (h3 << 5));
s[offset + 10] = (byte) (h3 >> 3);
s[offset + 11] = (byte) (h3 >> 11);
s[offset + 12] = (byte) ((h3 >> 19) | (h4 << 6));
s[offset + 13] = (byte) (h4 >> 2);
s[offset + 14] = (byte) (h4 >> 10);
s[offset + 15] = (byte) (h4 >> 18);
s[offset + 16] = (byte) (h5 >> 0);
s[offset + 17] = (byte) (h5 >> 8);
s[offset + 18] = (byte) (h5 >> 16);
s[offset + 19] = (byte) ((h5 >> 24) | (h6 << 1));
s[offset + 20] = (byte) (h6 >> 7);
s[offset + 21] = (byte) (h6 >> 15);
s[offset + 22] = (byte) ((h6 >> 23) | (h7 << 3));
s[offset + 23] = (byte) (h7 >> 5);
s[offset + 24] = (byte) (h7 >> 13);
s[offset + 25] = (byte) ((h7 >> 21) | (h8 << 4));
s[offset + 26] = (byte) (h8 >> 4);
s[offset + 27] = (byte) (h8 >> 12);
s[offset + 28] = (byte) ((h8 >> 20) | (h9 << 6));
s[offset + 29] = (byte) (h9 >> 2);
s[offset + 30] = (byte) (h9 >> 10);
s[offset + 31] = (byte) (h9 >> 18);
}
}

internal static void fe_reduce(out FieldElement hr, ref FieldElement h)
{
int h0 = h.x0;
int h1 = h.x1;
int h2 = h.x2;
int h3 = h.x3;
int h4 = h.x4;
int h5 = h.x5;
int h6 = h.x6;
int h7 = h.x7;
int h8 = h.x8;
int h9 = h.x9;

int q;

q = (19 * h9 + (1 << 24)) >> 25;
q = (h0 + q) >> 26;
q = (h1 + q) >> 25;
q = (h2 + q) >> 26;
q = (h3 + q) >> 25;
q = (h4 + q) >> 26;
q = (h5 + q) >> 25;
q = (h6 + q) >> 26;
q = (h7 + q) >> 25;
q = (h8 + q) >> 26;
q = (h9 + q) >> 25;

/* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */
h0 += 19 * q;
/* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */

var carry0 = h0 >> 26; h1 += carry0; h0 -= carry0 << 26;
var carry1 = h1 >> 25; h2 += carry1; h1 -= carry1 << 25;
var carry2 = h2 >> 26; h3 += carry2; h2 -= carry2 << 26;
var carry3 = h3 >> 25; h4 += carry3; h3 -= carry3 << 25;
var carry4 = h4 >> 26; h5 += carry4; h4 -= carry4 << 26;
var carry5 = h5 >> 25; h6 += carry5; h5 -= carry5 << 25;
var carry6 = h6 >> 26; h7 += carry6; h6 -= carry6 << 26;
var carry7 = h7 >> 25; h8 += carry7; h7 -= carry7 << 25;
var carry8 = h8 >> 26; h9 += carry8; h8 -= carry8 << 26;
var carry9 = h9 >> 25; h9 -= carry9 << 25;
/* h10 = carry9 */

hr.x0 = h0;
hr.x1 = h1;
hr.x2 = h2;
hr.x3 = h3;
hr.x4 = h4;
hr.x5 = h5;
hr.x6 = h6;
hr.x7 = h7;
hr.x8 = h8;
hr.x9 = h9;
}
}
}

+ 73
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_add.cs View File

@@ -0,0 +1,73 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = p + q
*/

internal static void ge_add(out GroupElementP1P1 r, ref GroupElementP3 p, ref GroupElementCached q)
{
FieldElement t0;

/* qhasm: YpX1 = Y1+X1 */
/* asm 1: fe_add(>YpX1=fe#1,<Y1=fe#12,<X1=fe#11); */
/* asm 2: fe_add(>YpX1=r.X,<Y1=p.Y,<X1=p.X); */
FieldOperations.fe_add(out r.X, ref p.Y, ref p.X);

/* qhasm: YmX1 = Y1-X1 */
/* asm 1: fe_sub(>YmX1=fe#2,<Y1=fe#12,<X1=fe#11); */
/* asm 2: fe_sub(>YmX1=r.Y,<Y1=p.Y,<X1=p.X); */
FieldOperations.fe_sub(out r.Y, ref p.Y, ref p.X);

/* qhasm: A = YpX1*YpX2 */
/* asm 1: fe_mul(>A=fe#3,<YpX1=fe#1,<YpX2=fe#15); */
/* asm 2: fe_mul(>A=r.Z,<YpX1=r.X,<YpX2=q.YplusX); */
FieldOperations.fe_mul(out r.Z, ref r.X, ref q.YplusX);

/* qhasm: B = YmX1*YmX2 */
/* asm 1: fe_mul(>B=fe#2,<YmX1=fe#2,<YmX2=fe#16); */
/* asm 2: fe_mul(>B=r.Y,<YmX1=r.Y,<YmX2=q.YminusX); */
FieldOperations.fe_mul(out r.Y, ref r.Y, ref q.YminusX);

/* qhasm: C = T2d2*T1 */
/* asm 1: fe_mul(>C=fe#4,<T2d2=fe#18,<T1=fe#14); */
/* asm 2: fe_mul(>C=r.T,<T2d2=q.T2d,<T1=p.T); */
FieldOperations.fe_mul(out r.T, ref q.T2d, ref p.T);

/* qhasm: ZZ = Z1*Z2 */
/* asm 1: fe_mul(>ZZ=fe#1,<Z1=fe#13,<Z2=fe#17); */
/* asm 2: fe_mul(>ZZ=r.X,<Z1=p.Z,<Z2=q.Z); */
FieldOperations.fe_mul(out r.X, ref p.Z, ref q.Z);

/* qhasm: D = 2*ZZ */
/* asm 1: fe_add(>D=fe#5,<ZZ=fe#1,<ZZ=fe#1); */
/* asm 2: fe_add(>D=t0,<ZZ=r.X,<ZZ=r.X); */
FieldOperations.fe_add(out t0, ref r.X, ref r.X);

/* qhasm: X3 = A-B */
/* asm 1: fe_sub(>X3=fe#1,<A=fe#3,<B=fe#2); */
/* asm 2: fe_sub(>X3=r.X,<A=r.Z,<B=r.Y); */
FieldOperations.fe_sub(out r.X, ref r.Z, ref r.Y);

/* qhasm: Y3 = A+B */
/* asm 1: fe_add(>Y3=fe#2,<A=fe#3,<B=fe#2); */
/* asm 2: fe_add(>Y3=r.Y,<A=r.Z,<B=r.Y); */
FieldOperations.fe_add(out r.Y, ref r.Z, ref r.Y);

/* qhasm: Z3 = D+C */
/* asm 1: fe_add(>Z3=fe#3,<D=fe#5,<C=fe#4); */
/* asm 2: fe_add(>Z3=r.Z,<D=t0,<C=r.T); */
FieldOperations.fe_add(out r.Z, ref t0, ref r.T);

/* qhasm: T3 = D-C */
/* asm 1: fe_sub(>T3=fe#4,<D=fe#5,<C=fe#4); */
/* asm 2: fe_sub(>T3=r.T,<D=t0,<C=r.T); */
FieldOperations.fe_sub(out r.T, ref t0, ref r.T);

/* qhasm: return */
}
}
}

+ 115
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_double_scalarmult.cs View File

@@ -0,0 +1,115 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
private static void slide(sbyte[] r, byte[] a)
{
for (int i = 0; i < 256; ++i)
r[i] = (sbyte)(1 & (a[i >> 3] >> (i & 7)));

for (int i = 0; i < 256; ++i)
{
if (r[i] != 0)
{
for (int b = 1; b <= 6 && (i + b) < 256; ++b)
{
if (r[i + b] != 0)
{
if (r[i] + (r[i + b] << b) <= 15)
{
r[i] += (sbyte)(r[i + b] << b); r[i + b] = 0;
}
else if (r[i] - (r[i + b] << b) >= -15)
{
r[i] -= (sbyte)(r[i + b] << b);
for (int k = i + b; k < 256; ++k)
{
if (r[k] == 0)
{
r[k] = 1;
break;
}
r[k] = 0;
}
}
else
break;
}
}
}
}
}

/*
r = a * A + b * B
where a = a[0]+256*a[1]+...+256^31 a[31].
and b = b[0]+256*b[1]+...+256^31 b[31].
B is the Ed25519 base point (x,4/5) with x positive.
*/

public static void ge_double_scalarmult_vartime(out GroupElementP2 r, byte[] a, ref GroupElementP3 A, byte[] b)
{
GroupElementPreComp[] Bi = LookupTables.Base2;
// todo: Perhaps remove these allocations?
sbyte[] aslide = new sbyte[256];
sbyte[] bslide = new sbyte[256];
GroupElementCached[] Ai = new GroupElementCached[8]; /* A,3A,5A,7A,9A,11A,13A,15A */
GroupElementP1P1 t;
GroupElementP3 u;
GroupElementP3 A2;
int i;

slide(aslide, a);
slide(bslide, b);

ge_p3_to_cached(out Ai[0], ref A);
ge_p3_dbl(out t, ref A); ge_p1p1_to_p3(out A2, ref t);
ge_add(out t, ref A2, ref Ai[0]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[1], ref u);
ge_add(out t, ref A2, ref Ai[1]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[2], ref u);
ge_add(out t, ref A2, ref Ai[2]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[3], ref u);
ge_add(out t, ref A2, ref Ai[3]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[4], ref u);
ge_add(out t, ref A2, ref Ai[4]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[5], ref u);
ge_add(out t, ref A2, ref Ai[5]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[6], ref u);
ge_add(out t, ref A2, ref Ai[6]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[7], ref u);

ge_p2_0(out r);

for (i = 255; i >= 0; --i)
{
if ((aslide[i] != 0) || (bslide[i] != 0)) break;
}

for (; i >= 0; --i)
{
ge_p2_dbl(out t, ref r);

if (aslide[i] > 0)
{
ge_p1p1_to_p3(out u, ref t);
ge_add(out t, ref u, ref Ai[aslide[i] / 2]);
}
else if (aslide[i] < 0)
{
ge_p1p1_to_p3(out u, ref t);
ge_sub(out t, ref u, ref Ai[(-aslide[i]) / 2]);
}

if (bslide[i] > 0)
{
ge_p1p1_to_p3(out u, ref t);
ge_madd(out t, ref u, ref Bi[bslide[i] / 2]);
}
else if (bslide[i] < 0)
{
ge_p1p1_to_p3(out u, ref t);
ge_msub(out t, ref u, ref Bi[(-bslide[i]) / 2]);
}

ge_p1p1_to_p2(out r, ref t);
}
}

}
}

+ 50
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_frombytes.cs View File

@@ -0,0 +1,50 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
public static int ge_frombytes_negate_vartime(out GroupElementP3 h, byte[] data, int offset)
{
FieldElement u, v, v3, vxx, check;

FieldOperations.fe_frombytes(out h.Y, data, offset);
FieldOperations.fe_1(out h.Z);
FieldOperations.fe_sq(out u, ref h.Y);
FieldOperations.fe_mul(out v, ref u, ref LookupTables.d);
FieldOperations.fe_sub(out u, ref u, ref h.Z); /* u = y^2-1 */
FieldOperations.fe_add(out v, ref v, ref h.Z); /* v = dy^2+1 */

FieldOperations.fe_sq(out v3, ref v);
FieldOperations.fe_mul(out v3, ref v3, ref v); /* v3 = v^3 */
FieldOperations.fe_sq(out h.X, ref v3);
FieldOperations.fe_mul(out h.X, ref h.X, ref v);
FieldOperations.fe_mul(out h.X, ref h.X, ref u); /* x = uv^7 */

FieldOperations.fe_pow22523(out h.X, ref h.X); /* x = (uv^7)^((q-5)/8) */
FieldOperations.fe_mul(out h.X, ref h.X, ref v3);
FieldOperations.fe_mul(out h.X, ref h.X, ref u); /* x = uv^3(uv^7)^((q-5)/8) */

FieldOperations.fe_sq(out vxx, ref h.X);
FieldOperations.fe_mul(out vxx, ref vxx, ref v);
FieldOperations.fe_sub(out check, ref vxx, ref u); /* vx^2-u */
if (FieldOperations.fe_isnonzero(ref check) != 0)
{
FieldOperations.fe_add(out check, ref vxx, ref u); /* vx^2+u */
if (FieldOperations.fe_isnonzero(ref check) != 0)
{
h = default(GroupElementP3);
return -1;
}
FieldOperations.fe_mul(out h.X, ref h.X, ref LookupTables.sqrtm1);
}

if (FieldOperations.fe_isnegative(ref h.X) == (data[offset + 31] >> 7))
FieldOperations.fe_neg(out h.X, ref h.X);

FieldOperations.fe_mul(out h.T, ref h.X, ref h.Y);
return 0;
}

}
}

+ 69
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_madd.cs View File

@@ -0,0 +1,69 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = p + q
*/
public static void ge_madd(out GroupElementP1P1 r, ref GroupElementP3 p, ref GroupElementPreComp q)
{
FieldElement t0;

/* qhasm: YpX1 = Y1+X1 */
/* asm 1: fe_add(>YpX1=fe#1,<Y1=fe#12,<X1=fe#11); */
/* asm 2: fe_add(>YpX1=r.X,<Y1=p.Y,<X1=p.X); */
FieldOperations.fe_add(out r.X, ref p.Y, ref p.X);

/* qhasm: YmX1 = Y1-X1 */
/* asm 1: fe_sub(>YmX1=fe#2,<Y1=fe#12,<X1=fe#11); */
/* asm 2: fe_sub(>YmX1=r.Y,<Y1=p.Y,<X1=p.X); */
FieldOperations.fe_sub(out r.Y, ref p.Y, ref p.X);

/* qhasm: A = YpX1*ypx2 */
/* asm 1: fe_mul(>A=fe#3,<YpX1=fe#1,<ypx2=fe#15); */
/* asm 2: fe_mul(>A=r.Z,<YpX1=r.X,<ypx2=q.yplusx); */
FieldOperations.fe_mul(out r.Z, ref r.X, ref q.yplusx);

/* qhasm: B = YmX1*ymx2 */
/* asm 1: fe_mul(>B=fe#2,<YmX1=fe#2,<ymx2=fe#16); */
/* asm 2: fe_mul(>B=r.Y,<YmX1=r.Y,<ymx2=q.yminusx); */
FieldOperations.fe_mul(out r.Y, ref r.Y, ref q.yminusx);

/* qhasm: C = xy2d2*T1 */
/* asm 1: fe_mul(>C=fe#4,<xy2d2=fe#17,<T1=fe#14); */
/* asm 2: fe_mul(>C=r.T,<xy2d2=q.xy2d,<T1=p.T); */
FieldOperations.fe_mul(out r.T, ref q.xy2d, ref p.T);

/* qhasm: D = 2*Z1 */
/* asm 1: fe_add(>D=fe#5,<Z1=fe#13,<Z1=fe#13); */
/* asm 2: fe_add(>D=t0,<Z1=p.Z,<Z1=p.Z); */
FieldOperations.fe_add(out t0, ref p.Z, ref p.Z);

/* qhasm: X3 = A-B */
/* asm 1: fe_sub(>X3=fe#1,<A=fe#3,<B=fe#2); */
/* asm 2: fe_sub(>X3=r.X,<A=r.Z,<B=r.Y); */
FieldOperations.fe_sub(out r.X, ref r.Z, ref r.Y);

/* qhasm: Y3 = A+B */
/* asm 1: fe_add(>Y3=fe#2,<A=fe#3,<B=fe#2); */
/* asm 2: fe_add(>Y3=r.Y,<A=r.Z,<B=r.Y); */
FieldOperations.fe_add(out r.Y, ref r.Z, ref r.Y);

/* qhasm: Z3 = D+C */
/* asm 1: fe_add(>Z3=fe#3,<D=fe#5,<C=fe#4); */
/* asm 2: fe_add(>Z3=r.Z,<D=t0,<C=r.T); */
FieldOperations.fe_add(out r.Z, ref t0, ref r.T);

/* qhasm: T3 = D-C */
/* asm 1: fe_sub(>T3=fe#4,<D=fe#5,<C=fe#4); */
/* asm 2: fe_sub(>T3=r.T,<D=t0,<C=r.T); */
FieldOperations.fe_sub(out r.T, ref t0, ref r.T);

/* qhasm: return */

}

}
}

+ 68
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_msub.cs View File

@@ -0,0 +1,68 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = p - q
*/
public static void ge_msub(out GroupElementP1P1 r, ref GroupElementP3 p, ref GroupElementPreComp q)
{
FieldElement t0;

/* qhasm: YpX1 = Y1+X1 */
/* asm 1: fe_add(>YpX1=fe#1,<Y1=fe#12,<X1=fe#11); */
/* asm 2: fe_add(>YpX1=r.X,<Y1=p.Y,<X1=p.X); */
FieldOperations.fe_add(out r.X, ref p.Y, ref p.X);

/* qhasm: YmX1 = Y1-X1 */
/* asm 1: fe_sub(>YmX1=fe#2,<Y1=fe#12,<X1=fe#11); */
/* asm 2: fe_sub(>YmX1=r.Y,<Y1=p.Y,<X1=p.X); */
FieldOperations.fe_sub(out r.Y, ref p.Y, ref p.X);

/* qhasm: A = YpX1*ymx2 */
/* asm 1: fe_mul(>A=fe#3,<YpX1=fe#1,<ymx2=fe#16); */
/* asm 2: fe_mul(>A=r.Z,<YpX1=r.X,<ymx2=q.yminusx); */
FieldOperations.fe_mul(out r.Z, ref r.X, ref q.yminusx);

/* qhasm: B = YmX1*ypx2 */
/* asm 1: fe_mul(>B=fe#2,<YmX1=fe#2,<ypx2=fe#15); */
/* asm 2: fe_mul(>B=r.Y,<YmX1=r.Y,<ypx2=q.yplusx); */
FieldOperations.fe_mul(out r.Y, ref r.Y, ref q.yplusx);

/* qhasm: C = xy2d2*T1 */
/* asm 1: fe_mul(>C=fe#4,<xy2d2=fe#17,<T1=fe#14); */
/* asm 2: fe_mul(>C=r.T,<xy2d2=q.xy2d,<T1=p.T); */
FieldOperations.fe_mul(out r.T, ref q.xy2d, ref p.T);

/* qhasm: D = 2*Z1 */
/* asm 1: fe_add(>D=fe#5,<Z1=fe#13,<Z1=fe#13); */
/* asm 2: fe_add(>D=t0,<Z1=p.Z,<Z1=p.Z); */
FieldOperations.fe_add(out t0, ref p.Z, ref p.Z);

/* qhasm: X3 = A-B */
/* asm 1: fe_sub(>X3=fe#1,<A=fe#3,<B=fe#2); */
/* asm 2: fe_sub(>X3=r.X,<A=r.Z,<B=r.Y); */
FieldOperations.fe_sub(out r.X, ref r.Z, ref r.Y);

/* qhasm: Y3 = A+B */
/* asm 1: fe_add(>Y3=fe#2,<A=fe#3,<B=fe#2); */
/* asm 2: fe_add(>Y3=r.Y,<A=r.Z,<B=r.Y); */
FieldOperations.fe_add(out r.Y, ref r.Z, ref r.Y);

/* qhasm: Z3 = D-C */
/* asm 1: fe_sub(>Z3=fe#3,<D=fe#5,<C=fe#4); */
/* asm 2: fe_sub(>Z3=r.Z,<D=t0,<C=r.T); */
FieldOperations.fe_sub(out r.Z, ref t0, ref r.T);

/* qhasm: T3 = D+C */
/* asm 1: fe_add(>T3=fe#4,<D=fe#5,<C=fe#4); */
/* asm 2: fe_add(>T3=r.T,<D=t0,<C=r.T); */
FieldOperations.fe_add(out r.T, ref t0, ref r.T);

/* qhasm: return */

}
}
}

+ 18
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p1p1_to_p2.cs View File

@@ -0,0 +1,18 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = p
*/
public static void ge_p1p1_to_p2(out GroupElementP2 r, ref GroupElementP1P1 p)
{
FieldOperations.fe_mul(out r.X, ref p.X, ref p.T);
FieldOperations.fe_mul(out r.Y, ref p.Y, ref p.Z);
FieldOperations.fe_mul(out r.Z, ref p.Z, ref p.T);
}

}
}

+ 18
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p1p1_to_p3.cs View File

@@ -0,0 +1,18 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = p
*/
public static void ge_p1p1_to_p3(out GroupElementP3 r, ref GroupElementP1P1 p)
{
FieldOperations.fe_mul(out r.X, ref p.X, ref p.T);
FieldOperations.fe_mul(out r.Y, ref p.Y, ref p.Z);
FieldOperations.fe_mul(out r.Z, ref p.Z, ref p.T);
FieldOperations.fe_mul(out r.T, ref p.X, ref p.Y);
}
}
}

+ 14
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p2_0.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
public static void ge_p2_0(out GroupElementP2 h)
{
FieldOperations.fe_0(out h.X);
FieldOperations.fe_1(out h.Y);
FieldOperations.fe_1(out h.Z);
}
}
}

+ 64
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p2_dbl.cs View File

@@ -0,0 +1,64 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = 2 * p
*/

public static void ge_p2_dbl(out GroupElementP1P1 r, ref GroupElementP2 p)
{
FieldElement t0;

/* qhasm: XX=X1^2 */
/* asm 1: fe_sq(>XX=fe#1,<X1=fe#11); */
/* asm 2: fe_sq(>XX=r.X,<X1=p.X); */
FieldOperations.fe_sq(out r.X, ref p.X);

/* qhasm: YY=Y1^2 */
/* asm 1: fe_sq(>YY=fe#3,<Y1=fe#12); */
/* asm 2: fe_sq(>YY=r.Z,<Y1=p.Y); */
FieldOperations.fe_sq(out r.Z, ref p.Y);

/* qhasm: B=2*Z1^2 */
/* asm 1: fe_sq2(>B=fe#4,<Z1=fe#13); */
/* asm 2: fe_sq2(>B=r.T,<Z1=p.Z); */
FieldOperations.fe_sq2(out r.T, ref p.Z);

/* qhasm: A=X1+Y1 */
/* asm 1: fe_add(>A=fe#2,<X1=fe#11,<Y1=fe#12); */
/* asm 2: fe_add(>A=r.Y,<X1=p.X,<Y1=p.Y); */
FieldOperations.fe_add(out r.Y, ref p.X, ref p.Y);

/* qhasm: AA=A^2 */
/* asm 1: fe_sq(>AA=fe#5,<A=fe#2); */
/* asm 2: fe_sq(>AA=t0,<A=r.Y); */
FieldOperations.fe_sq(out t0, ref r.Y);

/* qhasm: Y3=YY+XX */
/* asm 1: fe_add(>Y3=fe#2,<YY=fe#3,<XX=fe#1); */
/* asm 2: fe_add(>Y3=r.Y,<YY=r.Z,<XX=r.X); */
FieldOperations.fe_add(out r.Y, ref r.Z, ref r.X);

/* qhasm: Z3=YY-XX */
/* asm 1: fe_sub(>Z3=fe#3,<YY=fe#3,<XX=fe#1); */
/* asm 2: fe_sub(>Z3=r.Z,<YY=r.Z,<XX=r.X); */
FieldOperations.fe_sub(out r.Z, ref r.Z, ref r.X);

/* qhasm: X3=AA-Y3 */
/* asm 1: fe_sub(>X3=fe#1,<AA=fe#5,<Y3=fe#2); */
/* asm 2: fe_sub(>X3=r.X,<AA=t0,<Y3=r.Y); */
FieldOperations.fe_sub(out r.X, ref t0, ref r.Y);

/* qhasm: T3=B-Z3 */
/* asm 1: fe_sub(>T3=fe#4,<B=fe#4,<Z3=fe#3); */
/* asm 2: fe_sub(>T3=r.T,<B=r.T,<Z3=r.Z); */
FieldOperations.fe_sub(out r.T, ref r.T, ref r.Z);

/* qhasm: return */

}
}
}

+ 15
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_0.cs View File

@@ -0,0 +1,15 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
public static void ge_p3_0(out GroupElementP3 h)
{
FieldOperations.fe_0(out h.X);
FieldOperations.fe_1(out h.Y);
FieldOperations.fe_1(out h.Z);
FieldOperations.fe_0(out h.T);
}
}
}

+ 17
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_dbl.cs View File

@@ -0,0 +1,17 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = 2 * p
*/
public static void ge_p3_dbl(out GroupElementP1P1 r, ref GroupElementP3 p)
{
GroupElementP2 q;
ge_p3_to_p2(out q, ref p);
ge_p2_dbl(out r, ref q);
}
}
}

+ 18
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_to_cached.cs View File

@@ -0,0 +1,18 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = p
*/
public static void ge_p3_to_cached(out GroupElementCached r, ref GroupElementP3 p)
{
FieldOperations.fe_add(out r.YplusX, ref p.Y, ref p.X);
FieldOperations.fe_sub(out r.YminusX, ref p.Y, ref p.X);
r.Z = p.Z;
FieldOperations.fe_mul(out r.T2d, ref p.T, ref LookupTables.d2);
}
}
}

+ 17
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_to_p2.cs View File

@@ -0,0 +1,17 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = p
*/
public static void ge_p3_to_p2(out GroupElementP2 r, ref GroupElementP3 p)
{
r.X = p.X;
r.Y = p.Y;
r.Z = p.Z;
}
}
}

+ 19
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_tobytes.cs View File

@@ -0,0 +1,19 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
public static void ge_p3_tobytes(byte[] s, int offset, ref GroupElementP3 h)
{
FieldElement recip;
FieldElement x, y;

FieldOperations.fe_invert(out recip, ref h.Z);
FieldOperations.fe_mul(out x, ref h.X, ref recip);
FieldOperations.fe_mul(out y, ref h.Y, ref recip);
FieldOperations.fe_tobytes(s, offset, ref y);
s[offset + 31] ^= (byte)(FieldOperations.fe_isnegative(ref x) << 7);
}
}
}

+ 14
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_precomp_0.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
public static void ge_precomp_0(out GroupElementPreComp h)
{
FieldOperations.fe_1(out h.yplusx);
FieldOperations.fe_1(out h.yminusx);
FieldOperations.fe_0(out h.xy2d);
}
}
}

+ 113
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_scalarmult_base.cs View File

@@ -0,0 +1,113 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
static byte equal(byte b, byte c)
{

byte ub = b;
byte uc = c;
byte x = (byte)(ub ^ uc); /* 0: yes; 1..255: no */
uint y = x; /* 0: yes; 1..255: no */
unchecked { y -= 1; } /* 4294967295: yes; 0..254: no */
y >>= 31; /* 1: yes; 0: no */
return (byte)y;
}

static byte negative(sbyte b)
{
var x = unchecked((ulong)b); /* 18446744073709551361..18446744073709551615: yes; 0..255: no */
x >>= 63; /* 1: yes; 0: no */
return (byte)x;
}

static void cmov(ref GroupElementPreComp t, ref GroupElementPreComp u, byte b)
{
FieldOperations.fe_cmov(ref t.yplusx, ref u.yplusx, b);
FieldOperations.fe_cmov(ref t.yminusx, ref u.yminusx, b);
FieldOperations.fe_cmov(ref t.xy2d, ref u.xy2d, b);
}

static void select(out GroupElementPreComp t, int pos, sbyte b)
{
GroupElementPreComp minust;
var bnegative = negative(b);
var babs = (byte)(b - (((-bnegative) & b) << 1));

ge_precomp_0(out t);
var table = LookupTables.Base[pos];
cmov(ref t, ref table[0], equal(babs, 1));
cmov(ref t, ref table[1], equal(babs, 2));
cmov(ref t, ref table[2], equal(babs, 3));
cmov(ref t, ref table[3], equal(babs, 4));
cmov(ref t, ref table[4], equal(babs, 5));
cmov(ref t, ref table[5], equal(babs, 6));
cmov(ref t, ref table[6], equal(babs, 7));
cmov(ref t, ref table[7], equal(babs, 8));
minust.yplusx = t.yminusx;
minust.yminusx = t.yplusx;
FieldOperations.fe_neg(out minust.xy2d, ref t.xy2d);
cmov(ref t, ref minust, bnegative);
}

/*
h = a * B
where a = a[0]+256*a[1]+...+256^31 a[31]
B is the Ed25519 base point (x,4/5) with x positive.

Preconditions:
a[31] <= 127
*/

public static void ge_scalarmult_base(out GroupElementP3 h, byte[] a, int offset)
{
// todo: Perhaps remove this allocation
var e = new sbyte[64];
sbyte carry;

GroupElementP1P1 r;
GroupElementP2 s;
GroupElementPreComp t;

for (int i = 0; i < 32; ++i)
{
e[2 * i + 0] = (sbyte)((a[offset + i] >> 0) & 15);
e[2 * i + 1] = (sbyte)((a[offset + i] >> 4) & 15);
}
/* each e[i] is between 0 and 15 */
/* e[63] is between 0 and 7 */

carry = 0;
for (int i = 0; i < 63; ++i)
{
e[i] += carry;
carry = (sbyte)(e[i] + 8);
carry >>= 4;
e[i] -= (sbyte)(carry << 4);
}
e[63] += carry;
/* each e[i] is between -8 and 8 */

ge_p3_0(out h);
for (int i = 1; i < 64; i += 2)
{
select(out t, i / 2, e[i]);
ge_madd(out r, ref h, ref t); ge_p1p1_to_p3(out h, ref r);
}

ge_p3_dbl(out r, ref h); ge_p1p1_to_p2(out s, ref r);
ge_p2_dbl(out r, ref s); ge_p1p1_to_p2(out s, ref r);
ge_p2_dbl(out r, ref s); ge_p1p1_to_p2(out s, ref r);
ge_p2_dbl(out r, ref s); ge_p1p1_to_p3(out h, ref r);

for (int i = 0; i < 64; i += 2)
{
select(out t, i / 2, e[i]);
ge_madd(out r, ref h, ref t); ge_p1p1_to_p3(out h, ref r);
}
}

}
}

+ 74
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_sub.cs View File

@@ -0,0 +1,74 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
/*
r = p - q
*/

public static void ge_sub(out GroupElementP1P1 r, ref GroupElementP3 p, ref GroupElementCached q)
{
FieldElement t0;

/* qhasm: YpX1 = Y1+X1 */
/* asm 1: fe_add(>YpX1=fe#1,<Y1=fe#12,<X1=fe#11); */
/* asm 2: fe_add(>YpX1=r.X,<Y1=p.Y,<X1=p.X); */
FieldOperations.fe_add(out r.X, ref p.Y, ref p.X);

/* qhasm: YmX1 = Y1-X1 */
/* asm 1: fe_sub(>YmX1=fe#2,<Y1=fe#12,<X1=fe#11); */
/* asm 2: fe_sub(>YmX1=r.Y,<Y1=p.Y,<X1=p.X); */
FieldOperations.fe_sub(out r.Y, ref p.Y, ref p.X);

/* qhasm: A = YpX1*YmX2 */
/* asm 1: fe_mul(>A=fe#3,<YpX1=fe#1,<YmX2=fe#16); */
/* asm 2: fe_mul(>A=r.Z,<YpX1=r.X,<YmX2=q.YminusX); */
FieldOperations.fe_mul(out r.Z, ref r.X, ref q.YminusX);

/* qhasm: B = YmX1*YpX2 */
/* asm 1: fe_mul(>B=fe#2,<YmX1=fe#2,<YpX2=fe#15); */
/* asm 2: fe_mul(>B=r.Y,<YmX1=r.Y,<YpX2=q.YplusX); */
FieldOperations.fe_mul(out r.Y, ref r.Y, ref q.YplusX);

/* qhasm: C = T2d2*T1 */
/* asm 1: fe_mul(>C=fe#4,<T2d2=fe#18,<T1=fe#14); */
/* asm 2: fe_mul(>C=r.T,<T2d2=q.T2d,<T1=p.T); */
FieldOperations.fe_mul(out r.T, ref q.T2d, ref p.T);

/* qhasm: ZZ = Z1*Z2 */
/* asm 1: fe_mul(>ZZ=fe#1,<Z1=fe#13,<Z2=fe#17); */
/* asm 2: fe_mul(>ZZ=r.X,<Z1=p.Z,<Z2=q.Z); */
FieldOperations.fe_mul(out r.X, ref p.Z, ref q.Z);

/* qhasm: D = 2*ZZ */
/* asm 1: fe_add(>D=fe#5,<ZZ=fe#1,<ZZ=fe#1); */
/* asm 2: fe_add(>D=t0,<ZZ=r.X,<ZZ=r.X); */
FieldOperations.fe_add(out t0, ref r.X, ref r.X);

/* qhasm: X3 = A-B */
/* asm 1: fe_sub(>X3=fe#1,<A=fe#3,<B=fe#2); */
/* asm 2: fe_sub(>X3=r.X,<A=r.Z,<B=r.Y); */
FieldOperations.fe_sub(out r.X, ref r.Z, ref r.Y);

/* qhasm: Y3 = A+B */
/* asm 1: fe_add(>Y3=fe#2,<A=fe#3,<B=fe#2); */
/* asm 2: fe_add(>Y3=r.Y,<A=r.Z,<B=r.Y); */
FieldOperations.fe_add(out r.Y, ref r.Z, ref r.Y);

/* qhasm: Z3 = D-C */
/* asm 1: fe_sub(>Z3=fe#3,<D=fe#5,<C=fe#4); */
/* asm 2: fe_sub(>Z3=r.Z,<D=t0,<C=r.T); */
FieldOperations.fe_sub(out r.Z, ref t0, ref r.T);

/* qhasm: T3 = D+C */
/* asm 1: fe_add(>T3=fe#4,<D=fe#5,<C=fe#4); */
/* asm 2: fe_add(>T3=r.T,<D=t0,<C=r.T); */
FieldOperations.fe_add(out r.T, ref t0, ref r.T);

/* qhasm: return */
}

}
}

+ 19
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_tobytes.cs View File

@@ -0,0 +1,19 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class GroupOperations
{
public static void ge_tobytes(byte[] s, int offset, ref GroupElementP2 h)
{
FieldElement recip;
FieldElement x, y;

FieldOperations.fe_invert(out recip, ref h.Z);
FieldOperations.fe_mul(out x, ref h.X, ref recip);
FieldOperations.fe_mul(out y, ref h.Y, ref recip);
FieldOperations.fe_tobytes(s, offset, ref y);
s[offset + 31] ^= (byte)(FieldOperations.fe_isnegative(ref x) << 7);
}
}
}

+ 23
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/keypair.cs View File

@@ -0,0 +1,23 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class Ed25519Operations
{
public static void crypto_sign_keypair(byte[] pk, int pkoffset, byte[] sk, int skoffset, byte[] seed, int seedoffset)
{
GroupElementP3 A;
int i;

Array.Copy(seed, seedoffset, sk, skoffset, 32);
byte[] h = Sha512.Hash(sk, skoffset, 32);//ToDo: Remove alloc
ScalarOperations.sc_clamp(h, 0);

GroupOperations.ge_scalarmult_base(out A, h, 0);
GroupOperations.ge_p3_tobytes(pk, pkoffset, ref A);

for (i = 0; i < 32; ++i) sk[skoffset + 32 + i] = pk[pkoffset + i];
CryptoBytes.Wipe(h);
}
}
}

+ 80
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/open.cs View File

@@ -0,0 +1,80 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class Ed25519Operations
{
// Original crypto_sign_open, for reference only
/*public static int crypto_sign_open(
byte[] m, out int mlen,
byte[] sm, int smlen,
byte[] pk)
{
byte[] h = new byte[64];
byte[] checkr = new byte[32];
GroupElementP3 A;
GroupElementP2 R;
int i;

mlen = -1;
if (smlen < 64) return -1;
if ((sm[63] & 224) != 0) return -1;
if (GroupOperations.ge_frombytes_negate_vartime(out A, pk, 0) != 0) return -1;

for (i = 0; i < smlen; ++i) m[i] = sm[i];
for (i = 0; i < 32; ++i) m[32 + i] = pk[i];
Sha512BclWrapper.crypto_hash_sha512(h, m, 0, smlen);
ScalarOperations.sc_reduce(h);

var sm32 = new byte[32];
Array.Copy(sm, 32, sm32, 0, 32);
GroupOperations.ge_double_scalarmult_vartime(out R, h, ref A, sm32);
GroupOperations.ge_tobytes(checkr, 0, ref R);
if (Helpers.crypto_verify_32(checkr, sm) != 0)
{
for (i = 0; i < smlen; ++i)
m[i] = 0;
return -1;
}

for (i = 0; i < smlen - 64; ++i)
m[i] = sm[64 + i];
for (i = smlen - 64; i < smlen; ++i)
m[i] = 0;
mlen = smlen - 64;
return 0;
}*/

public static bool crypto_sign_verify(
byte[] sig, int sigoffset,
byte[] m, int moffset, int mlen,
byte[] pk, int pkoffset)
{
byte[] h;
byte[] checkr = new byte[32];
GroupElementP3 A;
GroupElementP2 R;

if ((sig[sigoffset + 63] & 224) != 0) return false;
if (GroupOperations.ge_frombytes_negate_vartime(out A, pk, pkoffset) != 0)
return false;

var hasher = new Sha512();
hasher.Update(sig, sigoffset, 32);
hasher.Update(pk, pkoffset, 32);
hasher.Update(m, moffset, mlen);
h = hasher.Finalize();

ScalarOperations.sc_reduce(h);

var sm32 = new byte[32];//todo: remove allocation
Array.Copy(sig, sigoffset + 32, sm32, 0, 32);
GroupOperations.ge_double_scalarmult_vartime(out R, h, ref A, sm32);
GroupOperations.ge_tobytes(checkr, 0, ref R);
var result = CryptoBytes.ConstantTimeEquals(checkr, 0, sig, sigoffset, 32);
CryptoBytes.Wipe(h);
CryptoBytes.Wipe(checkr);
return result;
}
}
}

+ 14
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_clamp.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class ScalarOperations
{
public static void sc_clamp(byte[] s, int offset)
{
s[offset + 0] &= 248;
s[offset + 31] &= 127;
s[offset + 31] |= 64;
}
}
}

+ 374
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_mul_add.cs View File

@@ -0,0 +1,374 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class ScalarOperations
{
static long load_3(byte[] input, int offset)
{
long result;
result = (long)input[offset + 0];
result |= ((long)input[offset + 1]) << 8;
result |= ((long)input[offset + 2]) << 16;
return result;
}

static long load_4(byte[] input, int offset)
{
long result;
result = (long)input[offset + 0];
result |= ((long)input[offset + 1]) << 8;
result |= ((long)input[offset + 2]) << 16;
result |= ((long)input[offset + 3]) << 24;
return result;
}

/*
Input:
a[0]+256*a[1]+...+256^31*a[31] = a
b[0]+256*b[1]+...+256^31*b[31] = b
c[0]+256*c[1]+...+256^31*c[31] = c

Output:
s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l
where l = 2^252 + 27742317777372353535851937790883648493.
*/

public static void sc_muladd(byte[] s, byte[] a, byte[] b, byte[] c)
{
long a0 = 2097151 & load_3(a, 0);
long a1 = 2097151 & (load_4(a, 2) >> 5);
long a2 = 2097151 & (load_3(a, 5) >> 2);
long a3 = 2097151 & (load_4(a, 7) >> 7);
long a4 = 2097151 & (load_4(a, 10) >> 4);
long a5 = 2097151 & (load_3(a, 13) >> 1);
long a6 = 2097151 & (load_4(a, 15) >> 6);
long a7 = 2097151 & (load_3(a, 18) >> 3);
long a8 = 2097151 & load_3(a, 21);
long a9 = 2097151 & (load_4(a, 23) >> 5);
long a10 = 2097151 & (load_3(a, 26) >> 2);
long a11 = (load_4(a, 28) >> 7);
long b0 = 2097151 & load_3(b, 0);
long b1 = 2097151 & (load_4(b, 2) >> 5);
long b2 = 2097151 & (load_3(b, 5) >> 2);
long b3 = 2097151 & (load_4(b, 7) >> 7);
long b4 = 2097151 & (load_4(b, 10) >> 4);
long b5 = 2097151 & (load_3(b, 13) >> 1);
long b6 = 2097151 & (load_4(b, 15) >> 6);
long b7 = 2097151 & (load_3(b, 18) >> 3);
long b8 = 2097151 & load_3(b, 21);
long b9 = 2097151 & (load_4(b, 23) >> 5);
long b10 = 2097151 & (load_3(b, 26) >> 2);
long b11 = (load_4(b, 28) >> 7);
long c0 = 2097151 & load_3(c, 0);
long c1 = 2097151 & (load_4(c, 2) >> 5);
long c2 = 2097151 & (load_3(c, 5) >> 2);
long c3 = 2097151 & (load_4(c, 7) >> 7);
long c4 = 2097151 & (load_4(c, 10) >> 4);
long c5 = 2097151 & (load_3(c, 13) >> 1);
long c6 = 2097151 & (load_4(c, 15) >> 6);
long c7 = 2097151 & (load_3(c, 18) >> 3);
long c8 = 2097151 & load_3(c, 21);
long c9 = 2097151 & (load_4(c, 23) >> 5);
long c10 = 2097151 & (load_3(c, 26) >> 2);
long c11 = (load_4(c, 28) >> 7);
long s0;
long s1;
long s2;
long s3;
long s4;
long s5;
long s6;
long s7;
long s8;
long s9;
long s10;
long s11;
long s12;
long s13;
long s14;
long s15;
long s16;
long s17;
long s18;
long s19;
long s20;
long s21;
long s22;
long s23;
long carry0;
long carry1;
long carry2;
long carry3;
long carry4;
long carry5;
long carry6;
long carry7;
long carry8;
long carry9;
long carry10;
long carry11;
long carry12;
long carry13;
long carry14;
long carry15;
long carry16;
long carry17;
long carry18;
long carry19;
long carry20;
long carry21;
long carry22;

s0 = c0 + a0 * b0;
s1 = c1 + a0 * b1 + a1 * b0;
s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0;
s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0;
s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0;
s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0;
s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0;
s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0;
s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1;
s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2;
s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3;
s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4;
s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
s20 = a9 * b11 + a10 * b10 + a11 * b9;
s21 = a10 * b11 + a11 * b10;
s22 = a11 * b11;
s23 = 0;

carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21;
carry18 = (s18 + (1 << 20)) >> 21; s19 += carry18; s18 -= carry18 << 21;
carry20 = (s20 + (1 << 20)) >> 21; s21 += carry20; s20 -= carry20 << 21;
carry22 = (s22 + (1 << 20)) >> 21; s23 += carry22; s22 -= carry22 << 21;

carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21;
carry17 = (s17 + (1 << 20)) >> 21; s18 += carry17; s17 -= carry17 << 21;
carry19 = (s19 + (1 << 20)) >> 21; s20 += carry19; s19 -= carry19 << 21;
carry21 = (s21 + (1 << 20)) >> 21; s22 += carry21; s21 -= carry21 << 21;

s11 += s23 * 666643;
s12 += s23 * 470296;
s13 += s23 * 654183;
s14 -= s23 * 997805;
s15 += s23 * 136657;
s16 -= s23 * 683901;
s23 = 0;

s10 += s22 * 666643;
s11 += s22 * 470296;
s12 += s22 * 654183;
s13 -= s22 * 997805;
s14 += s22 * 136657;
s15 -= s22 * 683901;
s22 = 0;

s9 += s21 * 666643;
s10 += s21 * 470296;
s11 += s21 * 654183;
s12 -= s21 * 997805;
s13 += s21 * 136657;
s14 -= s21 * 683901;
s21 = 0;

s8 += s20 * 666643;
s9 += s20 * 470296;
s10 += s20 * 654183;
s11 -= s20 * 997805;
s12 += s20 * 136657;
s13 -= s20 * 683901;
s20 = 0;

s7 += s19 * 666643;
s8 += s19 * 470296;
s9 += s19 * 654183;
s10 -= s19 * 997805;
s11 += s19 * 136657;
s12 -= s19 * 683901;
s19 = 0;

s6 += s18 * 666643;
s7 += s18 * 470296;
s8 += s18 * 654183;
s9 -= s18 * 997805;
s10 += s18 * 136657;
s11 -= s18 * 683901;
s18 = 0;

carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21;

carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21;

s5 += s17 * 666643;
s6 += s17 * 470296;
s7 += s17 * 654183;
s8 -= s17 * 997805;
s9 += s17 * 136657;
s10 -= s17 * 683901;
s17 = 0;

s4 += s16 * 666643;
s5 += s16 * 470296;
s6 += s16 * 654183;
s7 -= s16 * 997805;
s8 += s16 * 136657;
s9 -= s16 * 683901;
s16 = 0;

s3 += s15 * 666643;
s4 += s15 * 470296;
s5 += s15 * 654183;
s6 -= s15 * 997805;
s7 += s15 * 136657;
s8 -= s15 * 683901;
s15 = 0;

s2 += s14 * 666643;
s3 += s14 * 470296;
s4 += s14 * 654183;
s5 -= s14 * 997805;
s6 += s14 * 136657;
s7 -= s14 * 683901;
s14 = 0;

s1 += s13 * 666643;
s2 += s13 * 470296;
s3 += s13 * 654183;
s4 -= s13 * 997805;
s5 += s13 * 136657;
s6 -= s13 * 683901;
s13 = 0;

s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;

carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21;

carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21;

s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;

carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;
carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21;

s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;

carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;

unchecked
{
s[0] = (byte)(s0 >> 0);
s[1] = (byte)(s0 >> 8);
s[2] = (byte)((s0 >> 16) | (s1 << 5));
s[3] = (byte)(s1 >> 3);
s[4] = (byte)(s1 >> 11);
s[5] = (byte)((s1 >> 19) | (s2 << 2));
s[6] = (byte)(s2 >> 6);
s[7] = (byte)((s2 >> 14) | (s3 << 7));
s[8] = (byte)(s3 >> 1);
s[9] = (byte)(s3 >> 9);
s[10] = (byte)((s3 >> 17) | (s4 << 4));
s[11] = (byte)(s4 >> 4);
s[12] = (byte)(s4 >> 12);
s[13] = (byte)((s4 >> 20) | (s5 << 1));
s[14] = (byte)(s5 >> 7);
s[15] = (byte)((s5 >> 15) | (s6 << 6));
s[16] = (byte)(s6 >> 2);
s[17] = (byte)(s6 >> 10);
s[18] = (byte)((s6 >> 18) | (s7 << 3));
s[19] = (byte)(s7 >> 5);
s[20] = (byte)(s7 >> 13);
s[21] = (byte)(s8 >> 0);
s[22] = (byte)(s8 >> 8);
s[23] = (byte)((s8 >> 16) | (s9 << 5));
s[24] = (byte)(s9 >> 3);
s[25] = (byte)(s9 >> 11);
s[26] = (byte)((s9 >> 19) | (s10 << 2));
s[27] = (byte)(s10 >> 6);
s[28] = (byte)((s10 >> 14) | (s11 << 7));
s[29] = (byte)(s11 >> 1);
s[30] = (byte)(s11 >> 9);
s[31] = (byte)(s11 >> 17);
}
}
}
}

+ 264
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_reduce.cs View File

@@ -0,0 +1,264 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class ScalarOperations
{
/*
Input:
s[0]+256*s[1]+...+256^63*s[63] = s

Output:
s[0]+256*s[1]+...+256^31*s[31] = s mod l
where l = 2^252 + 27742317777372353535851937790883648493.
Overwrites s in place.
*/

public static void sc_reduce(byte[] s)
{
long s0 = 2097151 & load_3(s, 0);
long s1 = 2097151 & (load_4(s, 2) >> 5);
long s2 = 2097151 & (load_3(s, 5) >> 2);
long s3 = 2097151 & (load_4(s, 7) >> 7);
long s4 = 2097151 & (load_4(s, 10) >> 4);
long s5 = 2097151 & (load_3(s, 13) >> 1);
long s6 = 2097151 & (load_4(s, 15) >> 6);
long s7 = 2097151 & (load_3(s, 18) >> 3);
long s8 = 2097151 & load_3(s, 21);
long s9 = 2097151 & (load_4(s, 23) >> 5);
long s10 = 2097151 & (load_3(s, 26) >> 2);
long s11 = 2097151 & (load_4(s, 28) >> 7);
long s12 = 2097151 & (load_4(s, 31) >> 4);
long s13 = 2097151 & (load_3(s, 34) >> 1);
long s14 = 2097151 & (load_4(s, 36) >> 6);
long s15 = 2097151 & (load_3(s, 39) >> 3);
long s16 = 2097151 & load_3(s, 42);
long s17 = 2097151 & (load_4(s, 44) >> 5);
long s18 = 2097151 & (load_3(s, 47) >> 2);
long s19 = 2097151 & (load_4(s, 49) >> 7);
long s20 = 2097151 & (load_4(s, 52) >> 4);
long s21 = 2097151 & (load_3(s, 55) >> 1);
long s22 = 2097151 & (load_4(s, 57) >> 6);
long s23 = (load_4(s, 60) >> 3);

long carry0;
long carry1;
long carry2;
long carry3;
long carry4;
long carry5;
long carry6;
long carry7;
long carry8;
long carry9;
long carry10;
long carry11;
long carry12;
long carry13;
long carry14;
long carry15;
long carry16;

s11 += s23 * 666643;
s12 += s23 * 470296;
s13 += s23 * 654183;
s14 -= s23 * 997805;
s15 += s23 * 136657;
s16 -= s23 * 683901;
s23 = 0;

s10 += s22 * 666643;
s11 += s22 * 470296;
s12 += s22 * 654183;
s13 -= s22 * 997805;
s14 += s22 * 136657;
s15 -= s22 * 683901;
s22 = 0;

s9 += s21 * 666643;
s10 += s21 * 470296;
s11 += s21 * 654183;
s12 -= s21 * 997805;
s13 += s21 * 136657;
s14 -= s21 * 683901;
s21 = 0;

s8 += s20 * 666643;
s9 += s20 * 470296;
s10 += s20 * 654183;
s11 -= s20 * 997805;
s12 += s20 * 136657;
s13 -= s20 * 683901;
s20 = 0;

s7 += s19 * 666643;
s8 += s19 * 470296;
s9 += s19 * 654183;
s10 -= s19 * 997805;
s11 += s19 * 136657;
s12 -= s19 * 683901;
s19 = 0;

s6 += s18 * 666643;
s7 += s18 * 470296;
s8 += s18 * 654183;
s9 -= s18 * 997805;
s10 += s18 * 136657;
s11 -= s18 * 683901;
s18 = 0;

carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21;

carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21;

s5 += s17 * 666643;
s6 += s17 * 470296;
s7 += s17 * 654183;
s8 -= s17 * 997805;
s9 += s17 * 136657;
s10 -= s17 * 683901;
s17 = 0;

s4 += s16 * 666643;
s5 += s16 * 470296;
s6 += s16 * 654183;
s7 -= s16 * 997805;
s8 += s16 * 136657;
s9 -= s16 * 683901;
s16 = 0;

s3 += s15 * 666643;
s4 += s15 * 470296;
s5 += s15 * 654183;
s6 -= s15 * 997805;
s7 += s15 * 136657;
s8 -= s15 * 683901;
s15 = 0;

s2 += s14 * 666643;
s3 += s14 * 470296;
s4 += s14 * 654183;
s5 -= s14 * 997805;
s6 += s14 * 136657;
s7 -= s14 * 683901;
s14 = 0;

s1 += s13 * 666643;
s2 += s13 * 470296;
s3 += s13 * 654183;
s4 -= s13 * 997805;
s5 += s13 * 136657;
s6 -= s13 * 683901;
s13 = 0;

s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;

carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21;

carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21;

s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;

carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;
carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21;

s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;

carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;

unchecked
{
s[0] = (byte)(s0 >> 0);
s[1] = (byte)(s0 >> 8);
s[2] = (byte)((s0 >> 16) | (s1 << 5));
s[3] = (byte)(s1 >> 3);
s[4] = (byte)(s1 >> 11);
s[5] = (byte)((s1 >> 19) | (s2 << 2));
s[6] = (byte)(s2 >> 6);
s[7] = (byte)((s2 >> 14) | (s3 << 7));
s[8] = (byte)(s3 >> 1);
s[9] = (byte)(s3 >> 9);
s[10] = (byte)((s3 >> 17) | (s4 << 4));
s[11] = (byte)(s4 >> 4);
s[12] = (byte)(s4 >> 12);
s[13] = (byte)((s4 >> 20) | (s5 << 1));
s[14] = (byte)(s5 >> 7);
s[15] = (byte)((s5 >> 15) | (s6 << 6));
s[16] = (byte)(s6 >> 2);
s[17] = (byte)(s6 >> 10);
s[18] = (byte)((s6 >> 18) | (s7 << 3));
s[19] = (byte)(s7 >> 5);
s[20] = (byte)(s7 >> 13);
s[21] = (byte)(s8 >> 0);
s[22] = (byte)(s8 >> 8);
s[23] = (byte)((s8 >> 16) | (s9 << 5));
s[24] = (byte)(s9 >> 3);
s[25] = (byte)(s9 >> 11);
s[26] = (byte)((s9 >> 19) | (s10 << 2));
s[27] = (byte)(s10 >> 6);
s[28] = (byte)((s10 >> 14) | (s11 << 7));
s[29] = (byte)(s11 >> 1);
s[30] = (byte)(s11 >> 9);
s[31] = (byte)(s11 >> 17);
}
}

}
}

+ 153
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/scalarmult.cs View File

@@ -0,0 +1,153 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
public static class MontgomeryOperations
{
public static void scalarmult(
byte[] q, int qoffset,
byte[] n, int noffset,
byte[] p, int poffset)
{
FieldElement p0, q0;
FieldOperations.fe_frombytes2(out p0, p, poffset);
scalarmult(out q0, n, noffset, ref p0);
FieldOperations.fe_tobytes(q, qoffset, ref q0);
}

internal static void scalarmult(
out FieldElement q,
byte[] n, int noffset,
ref FieldElement p)
{
byte[] e = new byte[32];//ToDo: remove allocation
FieldElement x1, x2, x3;
FieldElement z2, z3;
FieldElement tmp0, tmp1;

for (int i = 0; i < 32; ++i)
e[i] = n[noffset + i];
ScalarOperations.sc_clamp(e, 0);
x1 = p;
FieldOperations.fe_1(out x2);
FieldOperations.fe_0(out z2);
x3 = x1;
FieldOperations.fe_1(out z3);

uint swap = 0;
for (int pos = 254; pos >= 0; --pos)
{
uint b = (uint)(e[pos / 8] >> (pos & 7));
b &= 1;
swap ^= b;
FieldOperations.fe_cswap(ref x2, ref x3, swap);
FieldOperations.fe_cswap(ref z2, ref z3, swap);
swap = b;

/* qhasm: enter ladder */

/* qhasm: D = X3-Z3 */
/* asm 1: fe_sub(>D=fe#5,<X3=fe#3,<Z3=fe#4); */
/* asm 2: fe_sub(>D=tmp0,<X3=x3,<Z3=z3); */
FieldOperations.fe_sub(out tmp0, ref x3, ref z3);

/* qhasm: B = X2-Z2 */
/* asm 1: fe_sub(>B=fe#6,<X2=fe#1,<Z2=fe#2); */
/* asm 2: fe_sub(>B=tmp1,<X2=x2,<Z2=z2); */
FieldOperations.fe_sub(out tmp1, ref x2, ref z2);

/* qhasm: A = X2+Z2 */
/* asm 1: fe_add(>A=fe#1,<X2=fe#1,<Z2=fe#2); */
/* asm 2: fe_add(>A=x2,<X2=x2,<Z2=z2); */
FieldOperations.fe_add(out x2, ref x2, ref z2);

/* qhasm: C = X3+Z3 */
/* asm 1: fe_add(>C=fe#2,<X3=fe#3,<Z3=fe#4); */
/* asm 2: fe_add(>C=z2,<X3=x3,<Z3=z3); */
FieldOperations.fe_add(out z2, ref x3, ref z3);

/* qhasm: DA = D*A */
/* asm 1: fe_mul(>DA=fe#4,<D=fe#5,<A=fe#1); */
/* asm 2: fe_mul(>DA=z3,<D=tmp0,<A=x2); */
FieldOperations.fe_mul(out z3, ref tmp0, ref x2);

/* qhasm: CB = C*B */
/* asm 1: fe_mul(>CB=fe#2,<C=fe#2,<B=fe#6); */
/* asm 2: fe_mul(>CB=z2,<C=z2,<B=tmp1); */
FieldOperations.fe_mul(out z2, ref z2, ref tmp1);

/* qhasm: BB = B^2 */
/* asm 1: fe_sq(>BB=fe#5,<B=fe#6); */
/* asm 2: fe_sq(>BB=tmp0,<B=tmp1); */
FieldOperations.fe_sq(out tmp0, ref tmp1);

/* qhasm: AA = A^2 */
/* asm 1: fe_sq(>AA=fe#6,<A=fe#1); */
/* asm 2: fe_sq(>AA=tmp1,<A=x2); */
FieldOperations.fe_sq(out tmp1, ref x2);

/* qhasm: t0 = DA+CB */
/* asm 1: fe_add(>t0=fe#3,<DA=fe#4,<CB=fe#2); */
/* asm 2: fe_add(>t0=x3,<DA=z3,<CB=z2); */
FieldOperations.fe_add(out x3, ref z3, ref z2);

/* qhasm: assign x3 to t0 */

/* qhasm: t1 = DA-CB */
/* asm 1: fe_sub(>t1=fe#2,<DA=fe#4,<CB=fe#2); */
/* asm 2: fe_sub(>t1=z2,<DA=z3,<CB=z2); */
FieldOperations.fe_sub(out z2, ref z3, ref z2);

/* qhasm: X4 = AA*BB */
/* asm 1: fe_mul(>X4=fe#1,<AA=fe#6,<BB=fe#5); */
/* asm 2: fe_mul(>X4=x2,<AA=tmp1,<BB=tmp0); */
FieldOperations.fe_mul(out x2, ref tmp1, ref tmp0);

/* qhasm: E = AA-BB */
/* asm 1: fe_sub(>E=fe#6,<AA=fe#6,<BB=fe#5); */
/* asm 2: fe_sub(>E=tmp1,<AA=tmp1,<BB=tmp0); */
FieldOperations.fe_sub(out tmp1, ref tmp1, ref tmp0);

/* qhasm: t2 = t1^2 */
/* asm 1: fe_sq(>t2=fe#2,<t1=fe#2); */
/* asm 2: fe_sq(>t2=z2,<t1=z2); */
FieldOperations.fe_sq(out z2, ref z2);

/* qhasm: t3 = a24*E */
/* asm 1: fe_mul121666(>t3=fe#4,<E=fe#6); */
/* asm 2: fe_mul121666(>t3=z3,<E=tmp1); */
FieldOperations.fe_mul121666(out z3, ref tmp1);

/* qhasm: X5 = t0^2 */
/* asm 1: fe_sq(>X5=fe#3,<t0=fe#3); */
/* asm 2: fe_sq(>X5=x3,<t0=x3); */
FieldOperations.fe_sq(out x3, ref x3);

/* qhasm: t4 = BB+t3 */
/* asm 1: fe_add(>t4=fe#5,<BB=fe#5,<t3=fe#4); */
/* asm 2: fe_add(>t4=tmp0,<BB=tmp0,<t3=z3); */
FieldOperations.fe_add(out tmp0, ref tmp0, ref z3);

/* qhasm: Z5 = X1*t2 */
/* asm 1: fe_mul(>Z5=fe#4,x1,<t2=fe#2); */
/* asm 2: fe_mul(>Z5=z3,x1,<t2=z2); */
FieldOperations.fe_mul(out z3, ref x1, ref z2);

/* qhasm: Z4 = E*t4 */
/* asm 1: fe_mul(>Z4=fe#2,<E=fe#6,<t4=fe#5); */
/* asm 2: fe_mul(>Z4=z2,<E=tmp1,<t4=tmp0); */
FieldOperations.fe_mul(out z2, ref tmp1, ref tmp0);

/* qhasm: return */

}
FieldOperations.fe_cswap(ref x2, ref x3, swap);
FieldOperations.fe_cswap(ref z2, ref z3, swap);

FieldOperations.fe_invert(out z2, ref z2);
FieldOperations.fe_mul(out x2, ref x2, ref z2);
q = x2;
CryptoBytes.Wipe(e);
}
}
}

+ 44
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sign.cs View File

@@ -0,0 +1,44 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class Ed25519Operations
{
public static void crypto_sign(
byte[] sig, int sigoffset,
byte[] m, int moffset, int mlen,
byte[] sk, int skoffset)
{
byte[] az, r, hram;
GroupElementP3 R;
var hasher = new Sha512();
{
hasher.Update(sk, skoffset, 32);
az = hasher.Finalize();
ScalarOperations.sc_clamp(az, 0);

hasher.Init();
hasher.Update(az, 32, 32);
hasher.Update(m, moffset, mlen);
r = hasher.Finalize();

ScalarOperations.sc_reduce(r);
GroupOperations.ge_scalarmult_base(out R, r, 0);
GroupOperations.ge_p3_tobytes(sig, sigoffset, ref R);

hasher.Init();
hasher.Update(sig, sigoffset, 32);
hasher.Update(sk, skoffset + 32, 32);
hasher.Update(m, moffset, mlen);
hram = hasher.Finalize();

ScalarOperations.sc_reduce(hram);
var s = new byte[32];//todo: remove allocation
Array.Copy(sig, sigoffset + 32, s, 0, 32);
ScalarOperations.sc_muladd(s, hram, az, r);
Array.Copy(s, 0, sig, sigoffset + 32, 32);
CryptoBytes.Wipe(s);
}
}
}
}

+ 9
- 0
src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sqrtm1.cs View File

@@ -0,0 +1,9 @@
using System;

namespace Discord.Net.ED25519.Ed25519Ref10
{
internal static partial class LookupTables
{
internal static FieldElement sqrtm1 = new FieldElement(-32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482);
}
}

+ 155
- 0
src/Discord.Net.Rest/Net/ED25519/Sha512.cs View File

@@ -0,0 +1,155 @@
using System;

namespace Discord.Net.ED25519
{
internal class Sha512
{
private Array8<ulong> _state;
private readonly byte[] _buffer;
private ulong _totalBytes;
public const int BlockSize = 128;
private static readonly byte[] _padding = new byte[] { 0x80 };

/// <summary>
/// Allocation and initialization of the new SHA-512 object.
/// </summary>
public Sha512()
{
_buffer = new byte[BlockSize];//todo: remove allocation
Init();
}

/// <summary>
/// Performs an initialization of internal SHA-512 state.
/// </summary>
public void Init()
{
Sha512Internal.Sha512Init(out _state);
_totalBytes = 0;
}

/// <summary>
/// Updates internal state with data from the provided array segment.
/// </summary>
/// <param name="data">Array segment</param>
public void Update(ArraySegment<byte> data)
{
Update(data.Array, data.Offset, data.Count);
}

/// <summary>
/// Updates internal state with data from the provided array.
/// </summary>
/// <param name="data">Array of bytes</param>
/// <param name="index">Offset of byte sequence</param>
/// <param name="length">Sequence length</param>
public void Update(byte[] data, int index, int length)
{
Array16<ulong> block;
int bytesInBuffer = (int)_totalBytes & (BlockSize - 1);
_totalBytes += (uint)length;

if (_totalBytes >= ulong.MaxValue / 8)
throw new InvalidOperationException("Too much data");
// Fill existing buffer
if (bytesInBuffer != 0)
{
var toCopy = Math.Min(BlockSize - bytesInBuffer, length);
Buffer.BlockCopy(data, index, _buffer, bytesInBuffer, toCopy);
index += toCopy;
length -= toCopy;
bytesInBuffer += toCopy;
if (bytesInBuffer == BlockSize)
{
ByteIntegerConverter.Array16LoadBigEndian64(out block, _buffer, 0);
Sha512Internal.Core(out _state, ref _state, ref block);
CryptoBytes.InternalWipe(_buffer, 0, _buffer.Length);
bytesInBuffer = 0;
}
}
// Hash complete blocks without copying
while (length >= BlockSize)
{
ByteIntegerConverter.Array16LoadBigEndian64(out block, data, index);
Sha512Internal.Core(out _state, ref _state, ref block);
index += BlockSize;
length -= BlockSize;
}
// Copy remainder into buffer
if (length > 0)
{
Buffer.BlockCopy(data, index, _buffer, bytesInBuffer, length);
}
}

/// <summary>
/// Finalizes SHA-512 hashing
/// </summary>
/// <param name="output">Output buffer</param>
public void Finalize(ArraySegment<byte> output)
{
Preconditions.NotNull(output.Array, nameof(output));
if (output.Count != 64)
throw new ArgumentException("Output should be 64 in length");

Update(_padding, 0, _padding.Length);
Array16<ulong> block;
ByteIntegerConverter.Array16LoadBigEndian64(out block, _buffer, 0);
CryptoBytes.InternalWipe(_buffer, 0, _buffer.Length);
int bytesInBuffer = (int)_totalBytes & (BlockSize - 1);
if (bytesInBuffer > BlockSize - 16)
{
Sha512Internal.Core(out _state, ref _state, ref block);
block = default(Array16<ulong>);
}
block.x15 = (_totalBytes - 1) * 8;
Sha512Internal.Core(out _state, ref _state, ref block);

ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 0, _state.x0);
ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 8, _state.x1);
ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 16, _state.x2);
ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 24, _state.x3);
ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 32, _state.x4);
ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 40, _state.x5);
ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 48, _state.x6);
ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 56, _state.x7);
_state = default(Array8<ulong>);
}

/// <summary>
/// Finalizes SHA-512 hashing.
/// </summary>
/// <returns>Hash bytes</returns>
public byte[] Finalize()
{
var result = new byte[64];
Finalize(new ArraySegment<byte>(result));
return result;
}

/// <summary>
/// Calculates SHA-512 hash value for the given bytes array.
/// </summary>
/// <param name="data">Data bytes array</param>
/// <returns>Hash bytes</returns>
public static byte[] Hash(byte[] data)
{
return Hash(data, 0, data.Length);
}

/// <summary>
/// Calculates SHA-512 hash value for the given bytes array.
/// </summary>
/// <param name="data">Data bytes array</param>
/// <param name="index">Offset of byte sequence</param>
/// <param name="length">Sequence length</param>
/// <returns>Hash bytes</returns>
public static byte[] Hash(byte[] data, int index, int length)
{
var hasher = new Sha512();
hasher.Update(data, index, length);
return hasher.Finalize();
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save