Skip to content

Add ed448 #1121

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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open

Add ed448 #1121

wants to merge 9 commits into from

Conversation

mikelodder7
Copy link

This adds the Ed448-Goldilocks curve and signing implementation from the latest commit at Mikelodder7/Ed448-Goldilocks.

// The original file was a part of curve25519-dalek.
// Copyright (c) 2016-2019 Isis Lovecruft, Henry de Valence
// Copyright (c) 2020 Kevaundray Wedderburn
// See LICENSE for licensing information.
Copy link
Member

Choose a reason for hiding this comment

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

lets add a reference to the actual licnes file from curve25519-dalek

@dignifiedquire
Copy link
Member

@tarcieri @mikelodder7 I am not seeing much in terms of test vectors or the likes, do you happen to know where to find some?

@dignifiedquire
Copy link
Member

working on a pr to update to the current set of rustcrypto deps

@mikelodder7
Copy link
Author

@mikelodder7
Copy link
Author

@tarcieri @mikelodder7 I am not seeing much in terms of test vectors or the likes, do you happen to know where to find some?

Here's the Ed448 test vectors

https://github.com/RustCrypto/elliptic-curves/pull/1121/files#diff-16bd59631ab39d33a42cd634a7603565919c4243d9d2b21b3d975d194457f22bR351

@dignifiedquire
Copy link
Member

created mikelodder7#1

@mikelodder7
Copy link
Author

created mikelodder7#1

👀

@dkg
Copy link

dkg commented Mar 4, 2025

what's the status on this? It would be great to see ed448 under the RustCrypto umbrella.

@tarcieri tarcieri self-requested a review March 4, 2025 22:46
@tarcieri
Copy link
Member

@dkg waiting on @mikelodder7 to rebase and get the tests passing

Signed-off-by: Michael Lodder <[email protected]>
Signed-off-by: Michael Lodder <[email protected]>
Signed-off-by: Michael Lodder <[email protected]>
Signed-off-by: Michael Lodder <[email protected]>
Signed-off-by: Michael Lodder <[email protected]>
Signed-off-by: Michael Lodder <[email protected]>
Signed-off-by: Michael Lodder <[email protected]>
@stackinspector
Copy link

There's a little stuff about my previous contribution that I feel the need to renegotiate before the code gets into a serious place like RustCrypto.
The hex_to_field (example) in test cases, which I originally implemented with macro_rules, passes string literial as a macro argument to hex-literial::hex!. But the maintainer of the original repository preferred hex_to_field as a function. But the problem is that hex-literial::hex! can only accept string literials, and the function hex-literial::decode, which can accept &'static str directly, is an internal API, even though we can do the checks that should be done, the API is theoretically unstable! Some of the other drawbacks I explained in the comment at the time. At final the function version was merged, but I still prefer to switch back to the macro_rules implementation and pass literals.

Signed-off-by: Michael Lodder <[email protected]>
Signed-off-by: Michael Lodder <[email protected]>
homepage = "https://docs.rs/ed448-goldilocks/"
keywords = ["cryptography", "decaf", "ed448", "ed448-goldilocks"]
license = "BSD-3-Clause"
name = "ed448"
Copy link
Member

@tarcieri tarcieri Mar 17, 2025

Choose a reason for hiding this comment

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

If we keep the name as ed448-goldilocks, it will allow the ed448 name to be used for the signature abstraction crate, which would be consistent with the ed25519 crate

(and that also ensures existing users get updates automatically without having to change to a new name)

Copy link
Member

@tarcieri tarcieri Mar 17, 2025

Choose a reason for hiding this comment

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

Another option for a name is curve448. We could extract the EdDSA implementation/instantiation into ed448 at that point, which would follow how curve25519-dalek/ed25519-dalek are structured.

@dignifiedquire
Copy link
Member

@tarcieri what's the blockers for moving this forward, given CI is green now?

@dignifiedquire
Copy link
Member

@tarcieri what would be the best place for me to put a x448 implementation based on this?

@kevaundray
Copy link

@tarcieri what would be the best place for me to put a x448 implementation based on this?

I have an impl here as a reference btw: https://github.com/crate-crypto/x448

@dignifiedquire
Copy link
Member

@kevaundray very nice, so I guess we just need to update that to the latest version of this 448 crate. Do you want to keep that crate under your management and update it, or should we move it into rustcrypto as well?

@kevaundray
Copy link

@kevaundray very nice, so I guess we just need to update that to the latest version of this 448 crate. Do you want to keep that crate under your management and update it, or should we move it into rustcrypto as well?

I think it would make sense to move it to rustcrypto as well for completeness if you haven't made an implementation

@tarcieri
Copy link
Member

tarcieri commented Apr 7, 2025

@dignifiedquire I'd like to do a final review and won't have time to do that for about a week


[dependencies]
crypto-bigint = { version = "0.6.0-rc.3", features = ["hybrid-array"], default-features = false }
crypto_signature = { version = "2.3.0-pre.6", default-features = false, features = ["digest", "rand_core"], optional = true, package = "signature" }
Copy link
Member

Choose a reason for hiding this comment

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

if we put this on the pre release for signature and [email protected] we will run into the issue that this is going to be unusable as in RustCrypto/signatures#930

Copy link
Member

Choose a reason for hiding this comment

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

Let’s get this merged as-is and follow up on that

Copy link
Member

Choose a reason for hiding this comment

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

It’s also not the same problem as this crate has existing releases with older deps whereas ml-dsa/slh-dsa do not

Copy link
Member

Choose a reason for hiding this comment

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

fyi I made https://github.com/dignifiedquire/cx448/ based on this code, which I will publish for now, so I can use a more up to date version

elliptic-curve = { version = "0.14.0-rc.0", features = ["arithmetic", "bits", "hash2curve", "jwk", "pkcs8", "pem", "sec1"] }
pkcs8 = { version = "0.10", features = ["alloc"], optional = true }
rand_core = { version = "0.9", default-features = false }
serdect = { version = "0.3.0", optional = true }
Copy link
Member

Choose a reason for hiding this comment

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

looks like this needs to be a prerelease version to compile

Copy link
Member

Choose a reason for hiding this comment

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

huh?

Copy link
Member

Choose a reason for hiding this comment

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

I tried updating x448 to use this version and am getting this

error: failed to select a version for `serdect`.
    ... required by package `elliptic-curve v0.14.0-rc.0`
    ... which satisfies dependency `elliptic-curve = "^0.14.0-rc.0"` of package `ed448 v0.17.0-pre.0 (https://github.com/mikelodder7/elliptic-curves#363ed9dc)`
    ... which satisfies git dependency `ed448` of package `x448 v0.6.0 (/Users/dignified/opensource/x448)`
versions that meet the requirements `=0.3.0-pre.0` are: 0.3.0-pre.0

all possible versions conflict with previously selected packages.

  previously selected package `serdect v0.3.0`
    ... which satisfies dependency `serdect = "^0.3.0"` of package `ed448 v0.17.0-pre.0 (https://github.com/mikelodder7/elliptic-curves#363ed9dc)`
    ... which satisfies git dependency `ed448` of package `x448 v0.6.0 (/Users/dignified/opensource/x448)`

with these dependencies

ed448 = { git = "https://github.com/mikelodder7/elliptic-curves", features = ["zeroize"] }
rand_core = { version = "0.9", default-features = false, features = ["getrandom"] }
zeroize = { version = "1.8", default-features = false, optional = true }

Copy link
Member

Choose a reason for hiding this comment

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

For now you'll also need:

[patch.crates-io]
elliptic-curve = { git = "https://github.com/RustCrypto/traits.git" }

(also rand_core v0.9 doesn't have a getrandom feature, it's os_rng)

Choose a reason for hiding this comment

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

Why not use rand_core 0.8, rand_core introduced more dependencies.

Copy link
Contributor

@daxpedda daxpedda left a comment

Choose a reason for hiding this comment

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

I tried to use this to implement decaf448 for voprf, but wasn't able to get things to match the test vectors (see the specification).

I played around with it for quite a while and eventually got it working, see mikelodder7#2. But unfortunately this broke a bunch of tests, no surprise: I'm not a cryptographer and I don't know what I'm doing.

I also added a test with a single test vector from the OPRF specification just to show how and where it breaks. I'm happy to add a full OPRF test vector suite as in #503, #600 and #964 when it passes.

Comment on lines +744 to +747
/// Construct a `Scalar` from a little-endian byte representation.
pub fn from_bytes(bytes: &[u8; 56]) -> Scalar {
Self(U448::from_le_slice(bytes))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This can produce invalid Scalars. Just missing modulo ORDER.

Comment on lines +892 to +902
pub fn hash<X>(msg: &[u8], dst: &[u8]) -> Self
where
X: for<'a> ExpandMsg<'a>,
{
let mut random_bytes = Array::<u8, U84>::default();
let dst = [dst];
let mut expander =
X::expand_message(&[msg], &dst, random_bytes.len()).expect("invalid dst");
expander.fill_bytes(&mut random_bytes);
Self::from_okm(&random_bytes)
}
Copy link
Contributor

@daxpedda daxpedda Apr 11, 2025

Choose a reason for hiding this comment

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

The specification says to use 64 bytes here. An public method taking 64 bytes, like Scalar::from_bytes_mod_order_wide(), would probably be useful as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

My assessment was incorrect here, the specification part I linked here is not related to the hash_to_field implementation.

Comment on lines +4 to +9
pub const DECAF_BASEPOINT: DecafPoint = DecafPoint(curve::twedwards::extended::ExtendedPoint {
X: TWISTED_EDWARDS_BASE_POINT.X,
Y: TWISTED_EDWARDS_BASE_POINT.Y,
Z: TWISTED_EDWARDS_BASE_POINT.Z,
T: TWISTED_EDWARDS_BASE_POINT.T,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

While I have no idea why the twisted curve is used for the internal representation, at least the public DecafPoint::GENERATOR has to be correct.

Suggested change
pub const DECAF_BASEPOINT: DecafPoint = DecafPoint(curve::twedwards::extended::ExtendedPoint {
X: TWISTED_EDWARDS_BASE_POINT.X,
Y: TWISTED_EDWARDS_BASE_POINT.Y,
Z: TWISTED_EDWARDS_BASE_POINT.Z,
T: TWISTED_EDWARDS_BASE_POINT.T,
});
pub const DECAF_BASEPOINT: DecafPoint = DecafPoint(curve::twedwards::extended::ExtendedPoint {
X: FieldElement(ConstMontyType::new(&U448::from_be_hex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa955555555555555555555555555555555555555555555555555555555"))),
Y: FieldElement(ConstMontyType::new(&U448::from_be_hex("ae05e9634ad7048db359d6205086c2b0036ed7a035884dd7b7e36d728ad8c4b80d6565833a2a3098bbbcb2bed1cda06bdaeafbcdea9386ed"))),
Z: FieldElement(ConstMontyType::new(&U448::from_u64(1))),
T: FieldElement(ConstMontyType::new(&U448::from_be_hex("696d84643374bace9d70983a12aa9d461da74d2d5c35e8d97ba72c3aba4450a5d29274229bd22c1d5e3a6474ee4ffb0e7a9e200a28eee402"))),
});

I got the definition of the base point from the specification.

This is covered in mikelodder7#2 (comment) as well.

}

impl FromOkm for Scalar {
type Length = U84;
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be 64 bytes as well, see #1121 (comment).

Copy link
Contributor

Choose a reason for hiding this comment

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

As pointed out in #1121 (comment), this is wrong.

tarcieri pushed a commit to RustCrypto/traits that referenced this pull request May 19, 2025
Main motivation was that `decaf448`
(RustCrypto/elliptic-curves#1121) requires
`ExpandMsgXof` instead of `ExpandMsgXmd`.

I was briefly tempted to add a `type Hash` to the `ExpandMsg` trait to
be able to write `type ExpandMsg = ExpandMsg<Hash = Self::Hash>`.
Otherwise it is possible to add a different hash in the `ExpandMsg`
type:
```rust
impl VoprfParamters for Fun128 {
    type Hash = Sha512;
    type ExpandMsg = ExpandMsgXmd<Sha256>;
    ...
}
```
But I thought its a bit much. Let me know if you want me to add that.
@baloo baloo mentioned this pull request May 29, 2025
@baloo
Copy link
Member

baloo commented May 29, 2025

I took a swab at a rebase of this PR in #1208 just because I needed ed448. I ended up converting some of it to primeorder.

Hopefully that can help with getting this merged.

@tarcieri
Copy link
Member

I ended up converting some of it to primeorder

That's a little weird since ed(wards)448 isn't a prime order curve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants