EdDSA
Edwards Digital Signing Algorithm
Symbols based on the book "Elliptic Curves in Cryptography" by I.F. Blake, G. Seroussi and N.P. Smart See page 4 for an overview of the DSA algorithm. This book along with the first few sections of "Cryptography: An Introduction" by N.P. Smart are recommended reads in order to understand better the concepts of "scalars" and "CurvePoint" and their arithmatic over finite fields.
Notation:
privateKey: 64 bytes, first 32 bytes form the scalar integer x
, the latter bytes are used for private nonce generation
publicKey: 32 bytes
x: bigint scalar representation of privateKey
g: generator BASE point
h: CurvePoint representation of publicKey
m: (hashed) message, kept as bytes
k: a practically random number, created by applying a one-way function to the message and part of the private key
a: first part of signature
b: second part of signature
*
: group multiplication of a CurvePoint by a scalar integer, or multiplication of 2 scalars (depending on context)
+
: CurvePoint addition or scalar addition depending on context
.
: byte concatenation
[n:N]
: slice bytes
f(a,h,m)
: a one-way function for publicy known information
mod()
: take modulo of a scalar wrt. the order of the Curve
hash()
: Sha512 hash function
encodeScalar
: turn a scalar integer into bytes
decodeScalar
: turn bytes into a scalar integer
encodePoint
: turn a CurvePoint into bytes
decodePoint
: turn bytes into a CurvePoint
The algorithm below is approached from an additive perspective.
- Generate 64 random private key bytes privateKey = random(64)
- Generate the associated scalar
x
: x = decodeScalar(privateKey[0:32]) - Generate public key CurvePoint: h = g*x
- Encode public key: publicKey = encodePoint(h)
- Create first part of a signature: k = decodeScalar(hash(privateKey[32:64] . m)) a = g*k signature[0:32] = encodePoint(a)
- Create second part of a signature: f(a,h,m) = decodeScalar(hash(signature[0:32] . publicKey . m)) b = mod(k + f(a,h,m)*x) signature[32:64] = encodeScalar(b)
- Verify a signature: a = decodePoint(signature[0:32]) b = decodeScalar(signature[32:64]) h = decodePoint(publicKey) f(a,h,m) = decodeScalar(hash(signature[0:32] . publicKey . m)) gb === a + hf(a,h,m)
We can show that this works by substituting the private calculations done upon signing (the arithmatic takes care of the mod() operator): g*(k + f(a,h,m)x) === gk + hf(a,h,m) gk + gxf(a,h,m) === gk + hf(a,h,m)
We know that g*x == h
, QED.
The arithmatic details are handled by the CurvePoint class
export interface EdDSA {
derivePublicKey(
privateKeyBytes: number[],
hashPrivateKey: boolean
): number[]
sign(
message: number[],
privateKeyBytes: number[],
hashPrivateKey: boolean
): number[]
verify(
signature: number[],
message: number[],
publicKey: number[]
): boolean
}
Properties
derivePublicKey
edDSA.derivePublicKey satisfies (
privateKeyBytes: number[],
hashPrivateKey: boolean
) => number[]
sign
edDSA.sign satisfies (
message: number[],
privateKeyBytes: number[],
hashPrivateKey: boolean
) => number[]
verify
edDSA.verify satisfies (
signature: number[],
message: number[],
publicKey: number[]
) => boolean