@@ -9,8 +9,9 @@ namespace Discord | |||||
{ | { | ||||
private string _name; | private string _name; | ||||
private object _value; | private object _value; | ||||
/// <summary> | /// <summary> | ||||
/// The name of this choice. | |||||
/// Gets the name of this choice. | |||||
/// </summary> | /// </summary> | ||||
public string Name | public string Name | ||||
{ | { | ||||
@@ -24,9 +25,9 @@ namespace Discord | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// The value of this choice. | |||||
/// Gets the value of this choice. | |||||
/// <note type="warning"> | /// <note type="warning"> | ||||
/// Discord only accepts int and string as the input. | |||||
/// Discord only accepts int, double/floats, and string as the input. | |||||
/// </note> | /// </note> | ||||
/// </summary> | /// </summary> | ||||
public object Value | public object Value | ||||
@@ -13,7 +13,7 @@ namespace Discord | |||||
public Optional<string> Name { get; set; } | public Optional<string> Name { get; set; } | ||||
/// <summary> | /// <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> | /// </summary> | ||||
public Optional<bool> DefaultPermission { get; set; } | public Optional<bool> DefaultPermission { get; set; } | ||||
@@ -6,7 +6,7 @@ namespace Discord | |||||
public class AutocompleteOption | public class AutocompleteOption | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// Gets the type of this option | |||||
/// Gets the type of this option. | |||||
/// </summary> | /// </summary> | ||||
public ApplicationCommandOptionType Type { get; } | public ApplicationCommandOptionType Type { get; } | ||||
@@ -45,15 +45,13 @@ namespace Discord | |||||
public object Value | public object Value | ||||
{ | { | ||||
get => _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> | /// <summary> | ||||
@@ -15,27 +15,27 @@ namespace Discord | |||||
ulong ApplicationId { get; } | ulong ApplicationId { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The type of the command. | |||||
/// Gets the type of the command. | |||||
/// </summary> | /// </summary> | ||||
ApplicationCommandType Type { get; } | ApplicationCommandType Type { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The name of the command. | |||||
/// Gets the name of the command. | |||||
/// </summary> | /// </summary> | ||||
string Name { get; } | string Name { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The description of the command. | |||||
/// Gets the description of the command. | |||||
/// </summary> | /// </summary> | ||||
string Description { get; } | string Description { get; } | ||||
/// <summary> | /// <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> | /// </summary> | ||||
bool IsDefaultPermission { get; } | bool IsDefaultPermission { get; } | ||||
/// <summary> | /// <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> | /// </summary> | ||||
IReadOnlyCollection<IApplicationCommandOption> Options { get; } | IReadOnlyCollection<IApplicationCommandOption> Options { get; } | ||||
@@ -5,20 +5,20 @@ namespace Discord | |||||
/// <summary> | /// <summary> | ||||
/// Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/>. | /// Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/>. | ||||
/// </summary> | /// </summary> | ||||
public interface IApplicationCommandInteractionData | |||||
public interface IApplicationCommandInteractionData : IDiscordInteractionData | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// The snowflake id of this command. | |||||
/// Gets the snowflake id of this command. | |||||
/// </summary> | /// </summary> | ||||
ulong Id { get; } | ulong Id { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The name of this command. | |||||
/// Gets the name of this command. | |||||
/// </summary> | /// </summary> | ||||
string Name { get; } | string Name { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The params + values from the user. | |||||
/// Gets the params + values from the user. | |||||
/// </summary> | /// </summary> | ||||
IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; } | IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; } | ||||
} | } | ||||
@@ -3,30 +3,30 @@ using System.Collections.Generic; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> | /// <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> | /// </summary> | ||||
public interface IApplicationCommandInteractionDataOption | public interface IApplicationCommandInteractionDataOption | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// The name of the parameter. | |||||
/// Gets the name of the parameter. | |||||
/// </summary> | /// </summary> | ||||
string Name { get; } | string Name { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The value of the pair. | |||||
/// Gets the value of the pair. | |||||
/// <note> | /// <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> | /// </note> | ||||
/// </summary> | /// </summary> | ||||
object Value { get; } | object Value { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The type of this data's option. | |||||
/// Gets the type of this data's option. | |||||
/// </summary> | /// </summary> | ||||
ApplicationCommandOptionType Type { get; } | ApplicationCommandOptionType Type { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Present if this option is a group or subcommand. | |||||
/// Gets the options for this command. | |||||
/// </summary> | /// </summary> | ||||
IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; } | IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; } | ||||
} | } | ||||
@@ -3,57 +3,57 @@ using System.Collections.Generic; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> | /// <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> | /// </summary> | ||||
public interface IApplicationCommandOption | public interface IApplicationCommandOption | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// The type of this <see cref="IApplicationCommandOption"/>. | |||||
/// Gets the type of this <see cref="IApplicationCommandOption"/>. | |||||
/// </summary> | /// </summary> | ||||
ApplicationCommandOptionType Type { get; } | ApplicationCommandOptionType Type { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The name of this command option, 1-32 character name. | |||||
/// Gets the name of this command option. | |||||
/// </summary> | /// </summary> | ||||
string Name { get; } | string Name { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The description of this command option, 1-100 character description. | |||||
/// Gets the description of this command option. | |||||
/// </summary> | /// </summary> | ||||
string Description { get; } | string Description { get; } | ||||
/// <summary> | /// <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> | /// </summary> | ||||
bool? IsDefault { get; } | bool? IsDefault { get; } | ||||
/// <summary> | /// <summary> | ||||
/// If the parameter is required or optional, default is <see langword="false"/>. | |||||
/// Gets whether or not the parameter is required or optional. | |||||
/// </summary> | /// </summary> | ||||
bool? IsRequired { get; } | bool? IsRequired { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The smallest number value the user can input. | |||||
/// Gets the smallest number value the user can input. | |||||
/// </summary> | /// </summary> | ||||
double? MinValue { get; } | double? MinValue { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The largest number value the user can input. | |||||
/// Gets the largest number value the user can input. | |||||
/// </summary> | /// </summary> | ||||
double? MaxValue { get; } | double? MaxValue { get; } | ||||
/// <summary> | /// <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> | /// </summary> | ||||
IReadOnlyCollection<IApplicationCommandOptionChoice> Choices { get; } | IReadOnlyCollection<IApplicationCommandOptionChoice> Choices { get; } | ||||
/// <summary> | /// <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> | /// </summary> | ||||
IReadOnlyCollection<IApplicationCommandOption> Options { get; } | IReadOnlyCollection<IApplicationCommandOption> Options { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The allowed channel types for this option. | |||||
/// Gets the allowed channel types for this option. | |||||
/// </summary> | /// </summary> | ||||
IReadOnlyCollection<ChannelType> ChannelTypes { get; } | IReadOnlyCollection<ChannelType> ChannelTypes { get; } | ||||
} | } | ||||
@@ -6,12 +6,12 @@ namespace Discord | |||||
public interface IApplicationCommandOptionChoice | public interface IApplicationCommandOptionChoice | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// 1-100 character choice name. | |||||
/// Gets the choice name. | |||||
/// </summary> | /// </summary> | ||||
string Name { get; } | string Name { get; } | ||||
/// <summary> | /// <summary> | ||||
/// value of the choice. | |||||
/// Gets the value of the choice. | |||||
/// </summary> | /// </summary> | ||||
object Value { get; } | object Value { get; } | ||||
} | } | ||||
@@ -4,36 +4,32 @@ using System.Threading.Tasks; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> | /// <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> | /// </summary> | ||||
public interface IDiscordInteraction : ISnowflakeEntity | public interface IDiscordInteraction : ISnowflakeEntity | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// The id of the interaction. | |||||
/// Gets the id of the interaction. | |||||
/// </summary> | /// </summary> | ||||
new ulong Id { get; } | new ulong Id { get; } | ||||
/// <summary> | /// <summary> | ||||
/// The type of this <see cref="IDiscordInteraction"/>. | |||||
/// Gets the type of this <see cref="IDiscordInteraction"/>. | |||||
/// </summary> | /// </summary> | ||||
InteractionType Type { get; } | InteractionType Type { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Represents the data sent within this interaction. | |||||
/// Gets the data sent within this interaction. | |||||
/// </summary> | /// </summary> | ||||
IDiscordInteractionData Data { get; } | IDiscordInteractionData Data { get; } | ||||
/// <summary> | /// <summary> | ||||
/// A continuation token for responding to the interaction. | |||||
/// Gets the continuation token for responding to the interaction. | |||||
/// </summary> | /// </summary> | ||||
string Token { get; } | string Token { get; } | ||||
/// <summary> | /// <summary> | ||||
/// read-only property, always 1. | |||||
/// Gets the version of the interaction, always 1. | |||||
/// </summary> | /// </summary> | ||||
int Version { get; } | int Version { get; } | ||||
@@ -1,8 +1,13 @@ | |||||
//using Discord.Rest.Entities.Interactions; | //using Discord.Rest.Entities.Interactions; | ||||
using Discord.Net; | |||||
using Discord.Net.Converters; | |||||
using Discord.Net.ED25519; | |||||
using Newtonsoft.Json; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.IO; | using System.IO; | ||||
using System.Text; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord.Rest | namespace Discord.Rest | ||||
@@ -14,12 +19,13 @@ namespace Discord.Rest | |||||
{ | { | ||||
#region DiscordRestClient | #region DiscordRestClient | ||||
private RestApplication _applicationInfo; | private RestApplication _applicationInfo; | ||||
internal static JsonSerializer Serializer = new JsonSerializer() { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include }; | |||||
/// <summary> | /// <summary> | ||||
/// Gets the logged-in user. | /// Gets the logged-in user. | ||||
/// </summary> | /// </summary> | ||||
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; } | public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public DiscordRestClient() : this(new DiscordRestConfig()) { } | public DiscordRestClient() : this(new DiscordRestConfig()) { } | ||||
/// <summary> | /// <summary> | ||||
@@ -31,7 +37,7 @@ namespace Discord.Rest | |||||
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } | internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } | ||||
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | 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) | internal override void Dispose(bool disposing) | ||||
{ | { | ||||
@@ -60,6 +66,70 @@ namespace Discord.Rest | |||||
return Task.Delay(0); | 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) | public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null) | ||||
{ | { | ||||
return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false); | return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false); | ||||
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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) { } | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} | |||||
} |
@@ -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 | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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>(); | |||||
} | |||||
} | |||||
} |
@@ -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 | |||||
} | |||||
} |
@@ -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") | |||||
{ | |||||
} | |||||
} | |||||
} |
@@ -22,7 +22,7 @@ namespace Discord.Net.Converters | |||||
// Remove the data property for manual deserialization | // Remove the data property for manual deserialization | ||||
var result = obj.GetValue("data", StringComparison.OrdinalIgnoreCase); | var result = obj.GetValue("data", StringComparison.OrdinalIgnoreCase); | ||||
result.Parent.Remove(); | |||||
result?.Parent.Remove(); | |||||
// Populate the remaining properties. | // Populate the remaining properties. | ||||
using (var subReader = obj.CreateReader()) | using (var subReader = obj.CreateReader()) | ||||
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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]; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} ; | |||||
} |
@@ -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 ) | |||||
) | |||||
}; | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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 */ | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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 */ | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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 */ | |||||
} | |||||
} | |||||
} |
@@ -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 */ | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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 */ | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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 */ | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} | |||||
} |