Skip to content

Commit 49545ce

Browse files
committed
Remove publicKey argument from Ed25519ExpandedPrivateKey.sign
1 parent 37d00ae commit 49545ce

File tree

5 files changed

+53
-26
lines changed

5 files changed

+53
-26
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Security
9+
- `Ed25519ExpandedPrivateKey.sign` no longer takes a `publicKey` argument. The
10+
previous API allowed the caller to control how the public key was cached in
11+
memory, but it created an opportunity for misuse: if two signatures were
12+
created using different public keys, the private scalar could be recovered
13+
from the signatures (see [here][pubkey-2014] and [here][pubkey-2022] for
14+
details). We now always cache the public key ourselves to provide a safer
15+
signing API.
16+
17+
[pubkey-2014]: https://github.com/jedisct1/libsodium/issues/170
18+
[pubkey-2022]: https://github.com/MystenLabs/ed25519-unsafe-libs
819

920
## [0.1.0] - 2020-04-13
1021
Initial release!

src/jmh/java/cafe/cryptography/ed25519/Ed25519Bench.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,17 @@ public void prepare() {
3333
this.vk = this.sk.derivePublic();
3434
this.message = new byte[64];
3535
r.nextBytes(this.message);
36-
this.signature = this.sk.expand().sign(this.message, this.vk);
36+
this.signature = this.sk.expand().sign(this.message);
3737
}
3838

3939
@Benchmark
4040
public Ed25519PublicKey keygen() {
4141
return Ed25519PrivateKey.generate(this.r).derivePublic();
4242
}
4343

44-
@Benchmark
45-
public Ed25519ExpandedPrivateKey expand() {
46-
return this.sk.expand();
47-
}
48-
4944
@Benchmark
5045
public Ed25519Signature sign() {
51-
return this.expsk.sign(this.message, this.vk);
46+
return this.expsk.sign(this.message);
5247
}
5348

5449
@Benchmark

src/main/java/cafe/cryptography/ed25519/Ed25519ExpandedPrivateKey.java

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,52 @@ public class Ed25519ExpandedPrivateKey {
2424
/**
2525
* The prefix component of the expanded Ed25519 private key.
2626
*
27-
* Note that because the `final` keyword only makes the reference a constant, the
28-
* contents of this byte[] could in theory be mutated (via reflection, as this field
29-
* is private). This misunderstanding was a contributor to the "final" security bug in
30-
* Google's Java implementation of Ed25519 [0]. However, the primary cause of that bug
31-
* was their reuse of the prefix buffer to hold the result of calculating S; we are
32-
* protected from that failure mode by the type-safe curve25519-elisabeth API.
27+
* Note that because the `final` keyword only makes the reference a constant,
28+
* the contents of this byte[] could in theory be mutated (via reflection, as
29+
* this field is private). This misunderstanding was a contributor to the
30+
* "final" security bug in Google's Java implementation of Ed25519 [0]. However,
31+
* the primary cause of that bug was their reuse of the prefix buffer to hold
32+
* the result of calculating S; we are protected from that failure mode by the
33+
* type-safe curve25519-elisabeth API.
3334
*
3435
* [0] https://github.com/cryptosubtlety/final-security-bug
3536
*/
3637
private final byte[] prefix;
3738

39+
/**
40+
* The public key corresponding to this private key.
41+
*
42+
* We store the public key inside the expanded private key so that we always use
43+
* the correct public key when creating signatures, while caching its
44+
* computation along with the other expanded components.
45+
*
46+
* Version 0.1.0 of ed25519-elisabeth required the caller to provide the public
47+
* key. This allowed the caller to control how the public key was cached in
48+
* memory, but it created an opportunity for misuse: if two signatures were
49+
* created using different public keys, the private scalar could be recovered
50+
* from the signatures [0] [1]. We now always cache the public key ourselves to
51+
* provide a safer signing API.
52+
*
53+
* [0] https://github.com/jedisct1/libsodium/issues/170
54+
* [1] https://github.com/MystenLabs/ed25519-unsafe-libs
55+
*/
56+
private final Ed25519PublicKey publicKey;
57+
3858
Ed25519ExpandedPrivateKey(Scalar s, byte[] prefix) {
3959
this.s = s;
4060
this.prefix = prefix;
61+
EdwardsPoint A = Constants.ED25519_BASEPOINT_TABLE.multiply(this.s);
62+
this.publicKey = new Ed25519PublicKey(A);
4163
}
4264

4365
/**
44-
* Derive the Ed25519 public key corresponding to this expanded private key.
66+
* Returns the Ed25519 public key corresponding to this expanded private key.
4567
*
4668
* @return the public key.
4769
*/
4870
@NotNull
4971
public Ed25519PublicKey derivePublic() {
50-
EdwardsPoint A = Constants.ED25519_BASEPOINT_TABLE.multiply(this.s);
51-
return new Ed25519PublicKey(A);
72+
return this.publicKey;
5273
}
5374

5475
/**
@@ -57,8 +78,8 @@ public Ed25519PublicKey derivePublic() {
5778
* @return the signature.
5879
*/
5980
@NotNull
60-
public Ed25519Signature sign(@NotNull byte[] message, @NotNull Ed25519PublicKey publicKey) {
61-
return this.sign(message, 0, message.length, publicKey);
81+
public Ed25519Signature sign(@NotNull byte[] message) {
82+
return this.sign(message, 0, message.length);
6283
}
6384

6485
/**
@@ -67,7 +88,7 @@ public Ed25519Signature sign(@NotNull byte[] message, @NotNull Ed25519PublicKey
6788
* @return the signature.
6889
*/
6990
@NotNull
70-
public Ed25519Signature sign(@NotNull byte[] message, int offset, int length, @NotNull Ed25519PublicKey publicKey) {
91+
public Ed25519Signature sign(@NotNull byte[] message, int offset, int length) {
7192
// @formatter:off
7293
// RFC 8032, section 5.1:
7394
// PH(x) | x (i.e., the identity function)
@@ -101,7 +122,7 @@ public Ed25519Signature sign(@NotNull byte[] message, int offset, int length, @N
101122
// @formatter:on
102123
h.reset();
103124
h.update(R.toByteArray());
104-
h.update(publicKey.toByteArray());
125+
h.update(this.publicKey.toByteArray());
105126
h.update(message, offset, length);
106127
Scalar k = Scalar.fromBytesModOrderWide(h.digest());
107128

src/test/java/cafe/cryptography/ed25519/Ed25519Rfc8032TestVectors.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,11 @@ public void derivePublic() {
161161

162162
@Test
163163
public void testSign() {
164-
assertThat(TEST_1_SK.expand().sign(TEST_1_MSG, TEST_1_VK), is(TEST_1_SIG));
165-
assertThat(TEST_2_SK.expand().sign(TEST_2_MSG, TEST_2_VK), is(TEST_2_SIG));
166-
assertThat(TEST_3_SK.expand().sign(TEST_3_MSG, TEST_3_VK), is(TEST_3_SIG));
167-
assertThat(TEST_1024_SK.expand().sign(TEST_1024_MSG, TEST_1024_VK), is(TEST_1024_SIG));
168-
assertThat(TEST_SHA_SK.expand().sign(TEST_SHA_MSG, TEST_SHA_VK), is(TEST_SHA_SIG));
164+
assertThat(TEST_1_SK.expand().sign(TEST_1_MSG), is(TEST_1_SIG));
165+
assertThat(TEST_2_SK.expand().sign(TEST_2_MSG), is(TEST_2_SIG));
166+
assertThat(TEST_3_SK.expand().sign(TEST_3_MSG), is(TEST_3_SIG));
167+
assertThat(TEST_1024_SK.expand().sign(TEST_1024_MSG), is(TEST_1024_SIG));
168+
assertThat(TEST_SHA_SK.expand().sign(TEST_SHA_MSG), is(TEST_SHA_SIG));
169169
}
170170

171171
@Test

src/test/java/cafe/cryptography/ed25519/Ed25519TestVectors.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void testSign() {
8888
for (TestTuple testCase : testCases) {
8989
Ed25519PrivateKey sk = Ed25519PrivateKey.fromByteArray(testCase.sk);
9090
assertThat("Test case " + testCase.caseNum + " failed",
91-
sk.expand().sign(testCase.message, sk.derivePublic()).toByteArray(), is(testCase.signature));
91+
sk.expand().sign(testCase.message).toByteArray(), is(testCase.signature));
9292
}
9393
}
9494

0 commit comments

Comments
 (0)