Ed25519 signatures¶
This module provides the protocol layer for Ed25519 as specified by RFC 8032.
The underlying curve group is the native twisted Edwards group provided by
sage.schemes.elliptic_curves.ell_edwards. Keeping the two layers
separate is important: a generic twisted Edwards curve has an equation and a
group law, while Ed25519 additionally fixes the field, parameters, base point,
cofactor, hash function, scalar pruning, and byte encoding.
The public functions implement deterministic Ed25519 signing and verification using the 32-byte private-key seed format from RFC 8032:
sage: seed = bytes.fromhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
sage: public_key = ed25519_public_key(seed)
sage: public_key.hex()
'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
sage: signature = ed25519_sign(seed, b"")
sage: signature.hex()
'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'
sage: ed25519_verify(public_key, b"", signature)
True
>>> from sage.all import *
>>> seed = bytes.fromhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
>>> public_key = ed25519_public_key(seed)
>>> public_key.hex()
'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
>>> signature = ed25519_sign(seed, b"")
>>> signature.hex()
'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'
>>> ed25519_verify(public_key, b"", signature)
True
seed = bytes.fromhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
public_key = ed25519_public_key(seed)
public_key.hex()
signature = ed25519_sign(seed, b"")
signature.hex()
ed25519_verify(public_key, b"", signature)
The curve constructor and base point are cached, so ordinary Sage additive group operations can be used directly without a second hand-written scalar multiplication implementation:
sage: C = Ed25519()
sage: B = Ed25519BasePoint()
sage: C(B) == B
True
sage: ed25519_decode(ed25519_encode(B)) == B
True
>>> from sage.all import *
>>> C = Ed25519()
>>> B = Ed25519BasePoint()
>>> C(B) == B
True
>>> ed25519_decode(ed25519_encode(B)) == B
True
C = Ed25519() B = Ed25519BasePoint() C(B) == B ed25519_decode(ed25519_encode(B)) == B
This module intentionally does not provide X25519 or an interchangeable private-key object. X25519 is a different protocol using the Montgomery form and Ed25519’s signing key is a seed, not the already-pruned scalar.
REFERENCES:
S. Josefsson and I. Liusvaara, Edwards-Curve Digital Signature Algorithm (EdDSA), RFC 8032, January 2017.
AUTHORS:
SageMath developers (2026): initial Ed25519 protocol support
- sage.crypto.ed25519.Ed25519()[source]¶
Return the standard Ed25519 twisted Edwards curve.
The returned parent is the curve
\[-x^2 + y^2 = 1 + d x^2 y^2, \qquad d = -121665/121666\]over
GF(2^255 - 19). The protocol-specific encoding and signing functions areed25519_encode(),ed25519_decode(),ed25519_sign(), anded25519_verify().EXAMPLES:
sage: C = Ed25519() sage: C.base_ring().order() == ED25519_FIELD_SIZE True sage: C.a() == -1 True sage: C.d() == C.base_ring()(-121665) / C.base_ring()(121666) True
>>> from sage.all import * >>> C = Ed25519() >>> C.base_ring().order() == ED25519_FIELD_SIZE True >>> C.a() == -Integer(1) True >>> C.d() == C.base_ring()(-Integer(121665)) / C.base_ring()(Integer(121666)) True
C = Ed25519() C.base_ring().order() == ED25519_FIELD_SIZE C.a() == -1 C.d() == C.base_ring()(-121665) / C.base_ring()(121666)
- sage.crypto.ed25519.Ed25519BasePoint()[source]¶
Return the standard Ed25519 base point
B.EXAMPLES:
sage: B = Ed25519BasePoint() sage: B.parent() is Ed25519() True sage: B.coordinates() == (ED25519_BASE_X, ED25519_BASE_Y) True
>>> from sage.all import * >>> B = Ed25519BasePoint() >>> B.parent() is Ed25519() True >>> B.coordinates() == (ED25519_BASE_X, ED25519_BASE_Y) True
B = Ed25519BasePoint() B.parent() is Ed25519() B.coordinates() == (ED25519_BASE_X, ED25519_BASE_Y)
- sage.crypto.ed25519.ed25519_decode(data)[source]¶
Decode a canonical 32-byte Ed25519 point encoding.
The square root is recovered in
GF(2^255 - 19)using thep = 5 mod 8square-root method. The returned point is a point on the full curve; callers that need a prime-order point must apply the protocol’s cofactor handling.EXAMPLES:
sage: ed25519_decode(bytes.fromhex('5866666666666666666666666666666666666666666666666666666666666666')) == Ed25519BasePoint() True sage: ed25519_decode(b'\xff' * 32) Traceback (most recent call last): ... ValueError: the encoded y-coordinate is not canonical
>>> from sage.all import * >>> ed25519_decode(bytes.fromhex('5866666666666666666666666666666666666666666666666666666666666666')) == Ed25519BasePoint() True >>> ed25519_decode(b'\xff' * Integer(32)) Traceback (most recent call last): ... ValueError: the encoded y-coordinate is not canonical
ed25519_decode(bytes.fromhex('5866666666666666666666666666666666666666666666666666666666666666')) == Ed25519BasePoint() ed25519_decode(b'\xff' * 32)
- sage.crypto.ed25519.ed25519_encode(point)[source]¶
Encode an Ed25519 point as 32 little-endian bytes.
The low 255 bits encode the affine
ycoordinate and the high bit encodes the least significant bit ofx. Coordinates are required to be canonical field elements andpointmust belong toEd25519().EXAMPLES:
sage: ed25519_encode(Ed25519BasePoint()).hex() '5866666666666666666666666666666666666666666666666666666666666666' sage: ed25519_decode(ed25519_encode(Ed25519BasePoint())) == Ed25519BasePoint() True
>>> from sage.all import * >>> ed25519_encode(Ed25519BasePoint()).hex() '5866666666666666666666666666666666666666666666666666666666666666' >>> ed25519_decode(ed25519_encode(Ed25519BasePoint())) == Ed25519BasePoint() True
ed25519_encode(Ed25519BasePoint()).hex() ed25519_decode(ed25519_encode(Ed25519BasePoint())) == Ed25519BasePoint()
- sage.crypto.ed25519.ed25519_public_key(seed)[source]¶
Derive the 32-byte Ed25519 public key from a 32-byte seed.
EXAMPLES:
sage: seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') sage: ed25519_public_key(seed).hex() 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
>>> from sage.all import * >>> seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') >>> ed25519_public_key(seed).hex() 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') ed25519_public_key(seed).hex()
- sage.crypto.ed25519.ed25519_sign(seed, message)[source]¶
Create a deterministic Ed25519 signature.
seedis the 32-byte private-key seed andmessageis a bytes-like message. The result is the 64-byte concatenationENC(R) || ENC(S).EXAMPLES:
sage: seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') sage: ed25519_sign(seed, b'').hex() 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'
>>> from sage.all import * >>> seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') >>> ed25519_sign(seed, b'').hex() 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'
seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') ed25519_sign(seed, b'').hex()
- sage.crypto.ed25519.ed25519_verify(public_key, message, signature)[source]¶
Verify an Ed25519 signature.
The RFC 8032 verification equation is checked with the cofactor:
\[[8][S]B = [8]R + [8][k]A,\]where
kis the reduced SHA-512 challenge. Malformed encodings and non-canonicalSvalues returnFalse.EXAMPLES:
sage: seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') sage: public_key = ed25519_public_key(seed) sage: signature = ed25519_sign(seed, b'') sage: ed25519_verify(public_key, b'', signature) True sage: ed25519_verify(public_key, b'bad', signature) False sage: import operator sage: ed25519_verify(public_key, b'', signature[:-1] + bytes([operator.xor(signature[-1], 1)])) False
>>> from sage.all import * >>> seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') >>> public_key = ed25519_public_key(seed) >>> signature = ed25519_sign(seed, b'') >>> ed25519_verify(public_key, b'', signature) True >>> ed25519_verify(public_key, b'bad', signature) False >>> import operator >>> ed25519_verify(public_key, b'', signature[:-Integer(1)] + bytes([operator.xor(signature[-Integer(1)], Integer(1))])) False
seed = bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') public_key = ed25519_public_key(seed) signature = ed25519_sign(seed, b'') ed25519_verify(public_key, b'', signature) ed25519_verify(public_key, b'bad', signature) import operator ed25519_verify(public_key, b'', signature[:-1] + bytes([operator.xor(signature[-1], 1)]))