Skip to content

Implement Ed25519 #2297

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

Merged
merged 6 commits into from
Jul 18, 2025
Merged

Implement Ed25519 #2297

merged 6 commits into from
Jul 18, 2025

Conversation

Airtz
Copy link
Contributor

@Airtz Airtz commented Jul 12, 2025

Implementation of the Ed25519 Digital Signature Algorithm.

1 main module : std::crypto::ed25519
3 internal submodules : std::crypto::ed25519::c25519, std::crypto::ed25519::f25519 and std::crypto::ed25519::fbase

API :

  • alias PrivateKey = char[32]
  • alias PublicKey = char[PrivateKey.len]
  • alias Signature = char[2 * PublicKey.len]
  • fn void public_keygen(char[] private_key)
  • fn Signature sign(char[] message, char[] private_key, char[] public_key)
  • fn bool verify(char[] message, char[] signature, char[] public_key)

Exemple usage :

PrivateKey private_key = x"97b0d0435be3a583df0b77aff277e5fc8f857cae1a827723241bb9bbf7e96018";

PublicKey public_key = ed25519::public_keygen(&private_key);
assert(public_key == x"fd462c5c7044b224f4a6cc0f0e5e16511be881be639b3fbc600f7b5d6da54e2f");

String message = "abcdefghijklmnopqrstuvwxyz0123456789";

Signature signature = ed25519::sign(message, &private_key, &public_key);
assert(signature == x"c03cf7b181583f6353a9434be910a484e1720247a0dc384d601e495294d0202728dda5f31bc62b00158b1588bc2a65f48ccfaa1b1431aff3d8da9271b7de9403");
assert(ed25519::verify(message, &signature, &public_key));

@lerno
Copy link
Collaborator

lerno commented Jul 14, 2025

I note that this includes a 256 bit int implementation. Is it possible to perhaps use the BigInt implementation already in std::math?

0x56, 0xb1, 0x83, 0x82, 0x9a, 0x14, 0xe0, 0x00,
0x30, 0xd1, 0xf3, 0xee, 0xf2, 0x80, 0x8e, 0x19,
0xe7, 0xfc, 0xdf, 0x56, 0xdc, 0xd9, 0x06, 0x24
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Something like this can use the hex literals:

const Int DD = x"59f1b226949bd6eb"
                "56b183829a14e000"
                "30d1f3eef2808e19"
                "e7fcdf56dcd90624";

@require public_key.len == PublicKey.len
*>
fn bool verify(char[] message, char[] signature, char[] public_key) {
char ok = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

As a general guide, stick to the coding style in the rest of the standard library (Allman braces, tabs for indentation)

fbase::Int z = fbase::from_bytes(&&sha.final());
fbase::Int e = fbase::from_bytes(exp[:fbase::Int.len]);

r[f25519::Int.len..] = z.mul(&e).add(&k)[..];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Following the general code style, type names are made unique on their own. They are not made unique through module namespacing, so F25519Int would be fine, f25519::Int would not be. In this case, rather than making it long, make c25519 module part of the std::crypto::ed25519, just with private visibility.

@Airtz
Copy link
Contributor Author

Airtz commented Jul 15, 2025

I note that this includes a 256 bit int implementation. Is it possible to perhaps use the BigInt implementation already in std::math?

No, since :

  • Not leveraging the fact that you're operating on a prime field (that is, mod 2^255-19) would be much slower.
  • BigInt is not constant-time.
  • BigInt is limited to 32 bytes and this needs to handle up to 64.

Thank you for the feedback. I'll see to address the style issues.

@lerno
Copy link
Collaborator

lerno commented Jul 15, 2025

See if you can try to make it self contained in a single file, unless there are more that will share the same implementation.

@param [&in] a
@param [&in] b
*>
fn char eq(F25519Int* a, F25519Int* b)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is std::crypto::safe_compare but constant-time. Unsure why safe_compare use == to return a boolean since this operator is most likely not constant-time.

@param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1"
*>
fn F25519Int f25519_select(F25519Int* zero, F25519Int* one, char condition)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe this should be abstracted in std::crypto alongside safe_compare.

@Airtz
Copy link
Contributor Author

Airtz commented Jul 18, 2025

So, operator overloading not working on pointers was indeed a bug 😬
I assumed it was on purpose to enforce purity and defaulted to method chains. Looks much better now !

@lerno lerno merged commit b1a22b5 into c3lang:master Jul 18, 2025
42 checks passed
@lerno
Copy link
Collaborator

lerno commented Jul 18, 2025

Thank you!

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.

2 participants