Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extension traits for exposing coordinates of curve points #49

Draft
wants to merge 1 commit into
base: curveaffine
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this library adheres to Rust's notion of
## [Unreleased]
### Added
- `group::CurveAffine`
- `group::coordinates` module, containing extension traits and structs that
provide generic access to the coordinates of elliptic curve points.

### Changed
- The curve-related traits have been refactored around the new `CurveAffine`
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ homepage = "https://github.com/zkcrypto/group"
repository = "https://github.com/zkcrypto/group"
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"]

[dependencies]
ff = { version = "0.13", default-features = false }
rand = { version = "0.8", optional = true, default-features = false }
Expand Down
15 changes: 15 additions & 0 deletions katex-header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.body, {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "\\(", right: "\\)", display: false},
{left: "$", right: "$", display: false},
{left: "\\[", right: "\\]", display: true}
]
});
});
</script>
206 changes: 206 additions & 0 deletions src/coordinates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
//! Extension traits and structs that provide generic access to the coordinates of
//! elliptic curve points.
//!
//! Coordinates are meaningless without the context of the curve equation that constrains
//! them. To safely expose them in a generic context, we use extension traits to restrict
//! the scope of the generic curve parameter; this ensures that the code can only be used
//! with curve implementations that explicitly expose their use of a specific curve model.

use subtle::{Choice, ConditionallySelectable, CtOption};

use crate::CurveAffine;

//
// Twisted Edwards curve
//

/// An affine elliptic curve point on a twisted Edwards curve
/// $a \cdot x^2 + y^2 = 1 + d \cdot x^2 \cdot y^2$.
pub trait TwistedEdwardsPoint: CurveAffine + Default + ConditionallySelectable {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, following the existing group crate conventions (given that we use the one trait to represent both the curve and points on the curve), we should probably name this:

Suggested change
pub trait TwistedEdwardsPoint: CurveAffine + Default + ConditionallySelectable {
pub trait TwistedEdwardsCurve: CurveAffine + Default + ConditionallySelectable {

/// Field element type used in the curve equation.
type Base: Copy + ConditionallySelectable;

/// The parameter $a$ in the twisted Edwards curve equation.
///
/// When $a = 1$, this reduces to an ordinary Edwards curve.
const A: Self::Base;

/// The parameter $d$ in the twisted Edwards curve equation.
const D: Self::Base;
Comment on lines +17 to +29
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not yet sure whether this trait should include the model parts as well, or whether that should be via an extension trait of the Curve trait. In general downstream users should use the Curve trait as the primary downstream interface, which would mean applying a bound of the form:

fn foo<C: Curve>() where C::Affine: TwistedEdwardsPoint {}

However, if we're not going to expose model-related APIs on the Curve type, then this is probably overall a simpler trait hierarchy, and a reasonable pathway to take (that we could always change track on later after we get some experience with it).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: is there a specific reason that MontgomeryPoint is necessarily an Affine subtype? It also has a projective representation. Would it make sense to separate the notion of curve equation and coordinate representation?

From an API perspective, I think that'd mean writing trait MontgomeryPoint as below, but without the methods. And then defining struct AffineMontgomeryPoint<Base> { u: Base, v: Base } and impl<F> MontgomeryPoint<Base=F> for AffineMontgomeryPoint<F> or something like that. Does that make sense?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: is there a specific reason that MontgomeryPoint is necessarily an Affine subtype? It also has a projective representation. Would it make sense to separate the notion of curve equation and coordinate representation?

This is somewhat an artifact of the trait approach we've taken in the group crate: the Group trait denotes both a cryptographic group, and elements of that group. So there is currently no single place where a curve-only trait could live.

What I was suggesting above was that we would effectively do this split, by way of having an extension trait of Curve where the curve model lives, but that trait would also be where any efficient-representation methods would need to live. Except that the efficient representation is not explicitly any specific representation; it's "whatever representation is best for this curve implementation", meaning that there's nothing to expose there.

So if we wanted to expose the projective representation, it would likely instead be an extension trait to the extension trait, which starts making the trait hierarchy quite complex, and we'd need a way to convey and manage that complexity. I'm not against

From an API perspective, I think that'd mean writing trait MontgomeryPoint as below, but without the methods. And then defining struct AffineMontgomeryPoint<Base> { u: Base, v: Base } and impl<F> MontgomeryPoint<Base=F> for AffineMontgomeryPoint<F> or something like that. Does that make sense?

The core method that the current trait needs is from_base_coordinates. That is necessary for safety: I want the coordinates struct to be guaranteed to represent a valid coordinate tuple, which requires having a validity-checked from-bare-coordinates constructor we can jump off to construct the coordinates struct.

So if we do split out a curve-only trait, we'd split this trait in the middle: have trait MontgomeryCurve: Curve, and then trait AffineMontgomeryPoint: CurveAffine where Self::Curve: MontgomeryPoint. Representing that trait bound in the type system is likely rather complex, but I'll have a go and see if it is workable.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand. I'm all for keeping it simple. Do you think there's an easy way to have to two representations for an Edwards point? This has affine but curve25519-dalek needs XYZT

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from_bare_coordinates can convert to extended twisted Edwards coordinates by setting Z=1 and T=X*Y (and calling is_valid on the resulting point)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat an artifact of the trait approach we've taken in the group crate: the Group trait denotes both a cryptographic group, and elements of that group. So there is currently no single place where a curve-only trait could live.

@str4d FWIW, in the elliptic-curve crate we have ZSTs which represent curve types and act as trait carriers (namely for traits that define the various associated element types)

Copy link
Member Author

@str4d str4d Jul 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think there's an easy way to have to two representations for an Edwards point? This has affine but curve25519-dalek needs XYZT

@rozbb to clarify, it needs to expose the coordinates of XYZT? If not and it just needs to use such points, that is already the case before this PR, and the idea behind the difference between the Curve and CurveAffine traits:

  • CurveAffine represents specifically affine coordinates that are both closer to the encoding, and can be a type used for mixed addition or scalar mul.
  • Curve represents the efficient type of a curve point for the given implementation; in the case of curve25519-dalek the XYZT representation.

If you want to expose the XYZT coordinates at all, then you can do that with concrete methods in curve25519-dalek and don't need changes in group. If you want to expose them generically, then we'd need to discuss in what context this needs to be used (which should probably go into #30, or if sufficiently different then a new issue):

  • If the intent is to write generic code for the subset of efficient point impls that use XYZT coords, then extension traits on Curve that constrain to a given efficient model may be sufficient.
  • If the intent is to obtain XYZT coords for given points, regardless of what coords they currently are in, then we'd need to define a trait or struct space that forms a state space of compatible coordinate models, along with an extension trait that allows converting the efficient Curve type from whatever its model is to the target model (which might be a no-op, or might not).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@str4d FWIW, in the elliptic-curve crate we have ZSTs which represent curve types and act as trait carriers (namely for traits that define the various associated element types)

@tarcieri Can you point me to these? I couldn't find them in the public API for the elliptic-curve crate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main one (and also one of the main integration points with the group crate, as it were): https://docs.rs/elliptic-curve/latest/elliptic_curve/trait.CurveArithmetic.html

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@str4d you're right, we don't need to expose our specific representation. Any equivalent one should do, at least for a first draft


/// Obtains a point given $(x, y)$, failing if it is not on the curve.
fn from_bare_coordinates(x: Self::Base, y: Self::Base) -> CtOption<Self>;

/// Obtains a point given its coordinates.
fn from_coordinates(coords: TwistedEdwardsCoordinates<Self>) -> Self;

/// Returns the coordinates of this point.
///
/// For twisted Edwards curves, the identity has valid coordinates on the curve, so
/// this method is infallible.
fn coordinates(&self) -> TwistedEdwardsCoordinates<Self>;
}

/// The affine coordinates for a [`TwistedEdwardsPoint`].
#[derive(Clone, Copy, Debug, Default)]
pub struct TwistedEdwardsCoordinates<P: TwistedEdwardsPoint> {
x: P::Base,
y: P::Base,
}

impl<P: TwistedEdwardsPoint> TwistedEdwardsCoordinates<P> {
/// Obtains a `TwistedEdwardsCoordinates` value given $(x, y)$, failing if it is not
/// on the curve.
pub fn from_coordinates(x: P::Base, y: P::Base) -> CtOption<Self> {
// We use `P::from_bare_coordinates` to validate the coordinates.
P::from_bare_coordinates(x, y).map(|_| TwistedEdwardsCoordinates { x, y })
}

/// Returns the x-coordinate.
pub fn x(&self) -> P::Base {
self.x
}

/// Returns the y-coordinate.
pub fn y(&self) -> P::Base {
self.y
}
}

impl<P: TwistedEdwardsPoint> ConditionallySelectable for TwistedEdwardsCoordinates<P> {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
TwistedEdwardsCoordinates {
x: P::Base::conditional_select(&a.x, &b.x, choice),
y: P::Base::conditional_select(&a.y, &b.y, choice),
}
}
}

//
// Montgomery curve
//

/// An affine elliptic curve point on a Montgomery curve
/// $B \cdot v^2 = u^3 + A \cdot u^2 + u$.
///
/// For these curves, it is required that $B \cdot (A^2 - 4) ≠ 0$, which implies that
/// $A ≠ ±2$ and $B ≠ 0$.
pub trait MontgomeryPoint: CurveAffine + Default + ConditionallySelectable {
/// Field element type used in the curve equation.
type Base: Copy + ConditionallySelectable;

/// The parameter $A$ in the Montgomery curve equation.
const A: Self::Base;

/// The parameter $B$ in the Montgomery curve equation.
const B: Self::Base;

/// Obtains a point given $(u, v)$, failing if it is not on the curve.
fn from_bare_coordinates(u: Self::Base, v: Self::Base) -> CtOption<Self>;

/// Obtains a point given its coordinates.
fn from_coordinates(coords: MontgomeryCoordinates<Self>) -> Self;

/// Returns the coordinates of this point.
///
/// Returns `None` if this is the identity.
fn coordinates(&self) -> CtOption<MontgomeryCoordinates<Self>>;
}

/// The affine coordinates for a [`MontgomeryCoordinates`].
#[derive(Clone, Copy, Debug, Default)]
pub struct MontgomeryCoordinates<P: MontgomeryPoint> {
u: P::Base,
v: P::Base,
}

impl<P: MontgomeryPoint> MontgomeryCoordinates<P> {
/// Obtains a `MontgomeryCoordinates` value given $(u, v)$, failing if it is not on
/// the curve.
pub fn from_coordinates(u: P::Base, v: P::Base) -> CtOption<Self> {
// We use `P::from_bare_coordinates` to validate the coordinates.
P::from_bare_coordinates(u, v).map(|_| MontgomeryCoordinates { u, v })
}

/// Returns the u-coordinate.
pub fn u(&self) -> P::Base {
self.u
}

/// Returns the v-coordinate.
pub fn v(&self) -> P::Base {
self.v
}
}

impl<P: MontgomeryPoint> ConditionallySelectable for MontgomeryCoordinates<P> {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
MontgomeryCoordinates {
u: P::Base::conditional_select(&a.u, &b.u, choice),
v: P::Base::conditional_select(&a.v, &b.v, choice),
}
}
}

//
// Short Weierstrass curve
//

/// An affine elliptic curve point on a short Weierstrass curve
/// $y^2 = x^3 + a \cdot x + b$.
pub trait ShortWeierstrassPoint: CurveAffine + Default + ConditionallySelectable {
/// Field element type used in the curve equation.
type Base: Copy + ConditionallySelectable;

/// The parameter $a$ in the short Weierstrass curve equation.
const A: Self::Base;

/// The parameter $b$ in the short Weierstrass curve equation.
const B: Self::Base;

/// Obtains a point given $(x, y)$, failing if it is not on the curve.
fn from_bare_coordinates(x: Self::Base, y: Self::Base) -> CtOption<Self>;

/// Obtains a point given its coordinates.
fn from_coordinates(coords: ShortWeierstrassCoordinates<Self>) -> Self;

/// Returns the coordinates of this point.
///
/// Returns `None` if this is the identity.
fn coordinates(&self) -> CtOption<ShortWeierstrassCoordinates<Self>>;
}

/// The affine coordinates for a [`ShortWeierstrassCoordinates`].
#[derive(Clone, Copy, Debug, Default)]
pub struct ShortWeierstrassCoordinates<P: ShortWeierstrassPoint> {
x: P::Base,
y: P::Base,
}

impl<P: ShortWeierstrassPoint> ShortWeierstrassCoordinates<P> {
/// Obtains a `ShortWeierstrassCoordinates` value given $(x, y)$, failing if it is not
/// on the curve.
pub fn from_coordinates(x: P::Base, y: P::Base) -> CtOption<Self> {
// We use `P::from_bare_coordinates` to validate the coordinates.
P::from_bare_coordinates(x, y).map(|_| ShortWeierstrassCoordinates { x, y })
}

/// Returns the x-coordinate.
pub fn x(&self) -> P::Base {
self.x
}

/// Returns the y-coordinate.
pub fn y(&self) -> P::Base {
self.y
}
}

impl<P: ShortWeierstrassPoint> ConditionallySelectable for ShortWeierstrassCoordinates<P> {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
ShortWeierstrassCoordinates {
x: P::Base::conditional_select(&a.x, &b.x, choice),
y: P::Base::conditional_select(&a.y, &b.y, choice),
}
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub mod prime;
#[cfg(feature = "tests")]
pub mod tests;

pub mod coordinates;

#[cfg(feature = "alloc")]
mod wnaf;
#[cfg(feature = "alloc")]
Expand Down
Loading