Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c80e327
x448: Initial import
baloo Jun 9, 2025
1e35298
x448: api fixups
baloo Jun 9, 2025
0c69dd6
Change license to Apache-2.0 OR MIT with permission
tarcieri Jul 19, 2025
e58d2c5
Rename to `MontgomeryPoint` to `MontgomeryXpoint`
daxpedda Jul 18, 2025
34e7ded
Move `MontgomeryXpoint` to its own module
daxpedda Jul 18, 2025
595faff
Implement `Eq` for `ProjectiveMontgomeryXpoint`
daxpedda Jul 18, 2025
59fbd56
Change `MontgomeryPoint` scalar multiplication output to `ProjectiveM…
daxpedda Jul 18, 2025
a3e64cc
Add y-coordinate recovery
daxpedda Jul 18, 2025
171b266
Make `ProjectiveMontgomeryXpoint::identity()` an associated const
daxpedda Jul 18, 2025
d1c9ea2
Add `ProjectiveMontgomeryXpoint::GENERATOR`
daxpedda Jul 18, 2025
3adb2f6
Document source of `montgomery::differential_add_and_double`
daxpedda Jul 18, 2025
7d864b8
Expose Montgomery ladder with additional output internally
daxpedda Jul 18, 2025
c95e7af
Add `ProjectiveMontgomeryXpoint::double()`
daxpedda Jul 18, 2025
94c9d87
Add full-coordinate `MontgomeryPoint` skeleton
daxpedda Jul 18, 2025
2a86734
Drop `CurveArithmetic` requirement from `CurveWithScalar`
daxpedda Jul 18, 2025
2f5f221
Add `MontgomeryScalar`
daxpedda Jul 18, 2025
1f20b19
Add arithmetic operations to Montgomery
daxpedda Jul 18, 2025
8c6327a
Add Montgomery <-> Edwards conversions
daxpedda Jul 18, 2025
7bd2571
Implement `CurveArithmetic` for Curve448
daxpedda Jul 18, 2025
ad1b7c5
Implement `FromOkm` for `MontgomeryScalar`
daxpedda Jul 19, 2025
e580745
Implement hash2curve for `Curve448`
daxpedda Jul 19, 2025
1685098
Add hash2curve to `ProjectiveMontgomeryXpoint`
daxpedda Jul 19, 2025
0975cdf
Test Montgomery -> Edwards through hash2curve
daxpedda Jul 19, 2025
2a8b634
merge daxpedda's branch
baloo Jul 20, 2025
f799e52
fixup scalars
baloo Jul 20, 2025
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
111 changes: 111 additions & 0 deletions .github/workflows/x448.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: x448

on:
pull_request:
paths:
- ".github/workflows/x448.yml"
- "ed448-goldilocks/**"
- "x448/**"
- "Cargo.*"
push:
branches: master

defaults:
run:
working-directory: x448

env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-Dwarnings"
RUSTDOCFLAGS: "-Dwarnings"

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- 1.85.0 # MSRV
- stable
target:
- thumbv7em-none-eabi
- wasm32-unknown-unknown
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
- run: cargo build --target ${{ matrix.target }} --release

test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
# 32-bit Linux
- target: i686-unknown-linux-gnu
rust: 1.85.0 # MSRV
deps: sudo apt update && sudo apt install gcc-multilib
- target: i686-unknown-linux-gnu
rust: stable
deps: sudo apt update && sudo apt install gcc-multilib

# 64-bit Linux
- target: x86_64-unknown-linux-gnu
rust: 1.85.0 # MSRV
- target: x86_64-unknown-linux-gnu
rust: stable

steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
- uses: RustCrypto/actions/cargo-hack-install@master
- run: ${{ matrix.deps }}
- run: cargo test --release --target ${{ matrix.target }}

cross:
strategy:
matrix:
include:
# ARM32
- target: armv7-unknown-linux-gnueabihf
rust: 1.85.0 # MSRV (cross)
- target: armv7-unknown-linux-gnueabihf
rust: stable

# ARM64
- target: aarch64-unknown-linux-gnu
rust: 1.85.0 # MSRV (cross)
- target: aarch64-unknown-linux-gnu
rust: stable

# PPC32
- target: powerpc-unknown-linux-gnu
rust: 1.85.0 # MSRV (cross)
- target: powerpc-unknown-linux-gnu
rust: stable

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ${{ matrix.deps }}
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
- uses: RustCrypto/actions/cross-install@master
- run: cross test --release --target ${{ matrix.target }}

doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: RustCrypto/actions/cargo-cache@master
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- run: cargo doc
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ members = [
"p521",
"primefield",
"primeorder",
"sm2"
"sm2",
"x448"
]

[profile.dev]
opt-level = 2

[patch.crates-io]
ed448-goldilocks = { path = "ed448-goldilocks" }
elliptic-curve = { git = "https://github.com/RustCrypto/traits.git" }
hash2curve = { path = "hash2curve" }
primefield = { path = "primefield" }
Expand Down
31 changes: 0 additions & 31 deletions ed448-goldilocks/src/edwards/affine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,37 +69,6 @@ impl AffinePoint {
y: FieldElement::ONE,
};

pub(crate) fn isogeny(&self) -> Self {
let x = self.x;
let y = self.y;
let mut t0 = x.square(); // x^2
let t1 = t0 + FieldElement::ONE; // x^2+1
t0 -= FieldElement::ONE; // x^2-1
let mut t2 = y.square(); // y^2
t2 = t2.double(); // 2y^2
let t3 = x.double(); // 2x

let mut t4 = t0 * y; // y(x^2-1)
t4 = t4.double(); // 2y(x^2-1)
let xNum = t4.double(); // xNum = 4y(x^2-1)

let mut t5 = t0.square(); // x^4-2x^2+1
t4 = t5 + t2; // x^4-2x^2+1+2y^2
let xDen = t4 + t2; // xDen = x^4-2x^2+1+4y^2

t5 *= x; // x^5-2x^3+x
t4 = t2 * t3; // 4xy^2
let yNum = t4 - t5; // yNum = -(x^5-2x^3+x-4xy^2)

t4 = t1 * t2; // 2x^2y^2+2y^2
let yDen = t5 - t4; // yDen = x^5-2x^3+x-2x^2y^2-2y^2

Self {
x: xNum * xDen.invert(),
y: yNum * yDen.invert(),
}
}

/// Convert to edwards extended point
pub fn to_edwards(&self) -> EdwardsPoint {
EdwardsPoint {
Expand Down
52 changes: 46 additions & 6 deletions ed448-goldilocks/src/edwards/extended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ impl EdwardsPoint {
T: FieldElement::ZERO,
};

/// Convert this point to [`MontgomeryPoint`]
pub fn to_montgomery(&self) -> MontgomeryPoint {
/// Convert this point to [`MontgomeryXpoint`]
pub fn to_montgomery_x(&self) -> MontgomeryXpoint {
// u = y^2 * [(1-dy^2)/(1-y^2)]

let affine = self.to_affine();
Expand All @@ -538,7 +538,29 @@ impl EdwardsPoint {

let u = yy * (FieldElement::ONE - dyy) * (FieldElement::ONE - yy).invert();

MontgomeryPoint(u.to_bytes())
MontgomeryXpoint(u.to_bytes())
}

/// Convert this point to [`MontgomeryPoint`]
// See https://www.rfc-editor.org/rfc/rfc7748#section-4.2 4-isogeny maps
pub fn to_montgomery(&self) -> MontgomeryPoint {
// u = y^2/x^2
// v = (2 - x^2 - y^2)*y/x^3

let affine = self.to_affine();

// TODO: optimize to a single inversion.
let xx = affine.x.square();
let yy = affine.y.square();

let u = yy * xx.invert();
let v = (FieldElement::TWO - xx - yy) * affine.y * (xx * affine.x).invert();

MontgomeryPoint::conditional_select(
&MontgomeryPoint::new(u, v),
&MontgomeryPoint::IDENTITY,
self.ct_eq(&Self::IDENTITY),
)
}

/// Generic scalar multiplication to compute s*P
Expand Down Expand Up @@ -973,6 +995,7 @@ mod tests {
use elliptic_curve::Field;
use hex_literal::hex;
use rand_core::TryRngCore;
use sha3::Shake256;

fn hex_to_field(hex: &'static str) -> FieldElement {
assert_eq!(hex.len(), 56 * 2);
Expand Down Expand Up @@ -1133,7 +1156,7 @@ mod tests {
];

for (msg, x, y) in MSGS {
let p = Ed448::hash_from_bytes::<ExpandMsgXof<sha3::Shake256>>(&[msg], &[DST]).unwrap();
let p = Ed448::hash_from_bytes::<ExpandMsgXof<Shake256>>(&[msg], &[DST]).unwrap();
assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
let p = p.to_affine();
let mut xx = [0u8; 56];
Expand Down Expand Up @@ -1170,8 +1193,7 @@ mod tests {
];

for (msg, x, y) in MSGS {
let p =
Ed448::encode_from_bytes::<ExpandMsgXof<sha3::Shake256>>(&[msg], &[DST]).unwrap();
let p = Ed448::encode_from_bytes::<ExpandMsgXof<Shake256>>(&[msg], &[DST]).unwrap();
assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
let p = p.to_affine();
let mut xx = [0u8; 56];
Expand All @@ -1182,6 +1204,24 @@ mod tests {
yy.reverse();
assert_eq!(p.x.to_bytes(), xx);
assert_eq!(p.y.to_bytes(), yy);

// Test Montgomery to Edwards conversion.
// See https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/664b13592116cecc9e52fb192dcde0ade36f904e/poc/ell2_opt_3mod4.sage#L243-L245.
let conv_p =
ProjectiveMontgomeryXpoint::encode::<ExpandMsgXof<Shake256>>(&[msg], &[DST])
.to_affine();
let conv_p1 = conv_p.to_edwards(Choice::from(0));
let conv_p2 = conv_p.to_edwards(Choice::from(1));
assert!(conv_p1.x == p.x || conv_p2.x == p.x);
assert!(conv_p1.y == p.y || conv_p2.y == p.y);

let conv_p = AffinePoint::from(
Curve448::encode_from_bytes::<ExpandMsgXof<Shake256>>(&[msg], &[DST])
.unwrap()
.to_affine(),
);
assert_eq!(conv_p.x, p.x);
assert_eq!(conv_p.y, p.y);
}
}

Expand Down
16 changes: 3 additions & 13 deletions ed448-goldilocks/src/edwards/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::field::{CurveWithScalar, NZ_ORDER, Scalar, ScalarBytes, WideScalarByt
use crate::{Ed448, ORDER};

use elliptic_curve::array::Array;
use elliptic_curve::bigint::{Limb, NonZero, U448, U704};
use elliptic_curve::consts::{U57, U84, U88};
use elliptic_curve::bigint::{Limb, U448};
use elliptic_curve::consts::{U57, U84};
use elliptic_curve::scalar::FromUintUnchecked;
use hash2curve::FromOkm;
use subtle::{Choice, CtOption};
Expand Down Expand Up @@ -86,17 +86,7 @@ impl FromOkm for EdwardsScalar {
type Length = U84;

fn from_okm(data: &Array<u8, Self::Length>) -> Self {
const SEMI_WIDE_MODULUS: NonZero<U704> = NonZero::<U704>::new_unwrap(U704::from_be_hex(
"00000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3",
));
let mut tmp = Array::<u8, U88>::default();
tmp[4..].copy_from_slice(&data[..]);

let mut num = U704::from_be_slice(&tmp[..]);
num %= SEMI_WIDE_MODULUS;
let mut words = [0; U448::LIMBS];
words.copy_from_slice(&num.to_words()[..U448::LIMBS]);
Scalar::new(U448::from_words(words))
Self::from_okm_u84(data)
}
}

Expand Down
Loading