Skip to content

Commit 151f902

Browse files
committed
fix(ciphers): require k as a verify parameter in HORS
1 parent ba7e98a commit 151f902

2 files changed

Lines changed: 51 additions & 25 deletions

File tree

‎src/main/java/com/thealgorithms/ciphers/HorsSignature.java‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ public byte[][] getPublicKey() {
8282
return deepCopy(publicKey);
8383
}
8484

85+
/**
86+
* Returns the number of secrets revealed per signature. Like {@code t}, it is part of the public
87+
* key and is needed for verification.
88+
*
89+
* @return the parameter {@code k}
90+
*/
91+
public int getK() {
92+
return k;
93+
}
94+
8595
/**
8696
* Signs a message. Every signature reveals {@code k} secrets, so a key pair should only sign a
8797
* few messages.
@@ -103,28 +113,33 @@ public byte[][] sign(byte[] message) {
103113
}
104114

105115
/**
106-
* Verifies a signature against a public key. {@code t} is taken from the public key length and
107-
* {@code k} from the signature length.
116+
* Verifies a signature against a public key. {@code t} is taken from the public key length.
117+
* {@code k} is part of the public key and must be supplied by the verifier: taking it from the
118+
* signature would let an attacker submit a shorter signature that reveals fewer secrets.
108119
*
109120
* @param message the signed message
110121
* @param signature the signature to check
111122
* @param publicKey the public key of the signer
123+
* @param k the number of secrets per signature used by the signer (see {@link #getK()})
112124
* @return true if the signature is valid for the message and public key, false otherwise
113125
* @throws IllegalArgumentException if an argument is null, the public key length is not a
114-
* supported {@code t}, a value is not 32 bytes long, or the signature is empty or uses more
115-
* than 256 digest bits
126+
* supported {@code t}, {@code k} is not supported for this {@code t}, a value is not 32
127+
* bytes long, or the signature does not contain exactly {@code k} values
116128
*/
117-
public static boolean verify(byte[] message, byte[][] signature, byte[][] publicKey) {
129+
public static boolean verify(byte[] message, byte[][] signature, byte[][] publicKey, int k) {
118130
if (message == null) {
119131
throw new IllegalArgumentException("message must not be null");
120132
}
121133
validateValues(publicKey, "publicKey");
122134
validateValues(signature, "signature");
123135
int tau = tauOf(publicKey.length);
124-
validateK(signature.length, tau);
136+
validateK(k, tau);
137+
if (signature.length != k) {
138+
throw new IllegalArgumentException("signature must contain exactly " + k + " values, got " + signature.length);
139+
}
125140

126-
int[] indices = messageIndices(hash(message), signature.length, tau);
127-
for (int j = 0; j < signature.length; j++) {
141+
int[] indices = messageIndices(hash(message), k, tau);
142+
for (int j = 0; j < k; j++) {
128143
if (!MessageDigest.isEqual(hash(signature[j]), publicKey[indices[j]])) {
129144
return false;
130145
}

‎src/test/java/com/thealgorithms/ciphers/HorsSignatureTest.java‎

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ void testValidSignatureVerifies() {
2323

2424
byte[][] signature = keyPair.sign(MESSAGE);
2525

26-
assertTrue(HorsSignature.verify(MESSAGE, signature, keyPair.getPublicKey()));
26+
assertTrue(HorsSignature.verify(MESSAGE, signature, keyPair.getPublicKey(), keyPair.getK()));
2727
}
2828

2929
@Test
@@ -35,7 +35,7 @@ void testSameKeySignsSeveralMessages() {
3535
byte[] message = ("message " + i).getBytes(StandardCharsets.UTF_8);
3636
byte[][] signature = keyPair.sign(message);
3737

38-
assertTrue(HorsSignature.verify(message, signature, publicKey));
38+
assertTrue(HorsSignature.verify(message, signature, publicKey, keyPair.getK()));
3939
}
4040
}
4141

@@ -54,7 +54,7 @@ void testTamperedMessageFailsVerification() {
5454

5555
tampered[0] ^= 0x01;
5656

57-
assertFalse(HorsSignature.verify(tampered, signature, keyPair.getPublicKey()));
57+
assertFalse(HorsSignature.verify(tampered, signature, keyPair.getPublicKey(), keyPair.getK()));
5858
}
5959

6060
@Test
@@ -64,7 +64,7 @@ void testTamperedSignatureFailsVerification() {
6464

6565
signature[3][0] ^= 0x01;
6666

67-
assertFalse(HorsSignature.verify(MESSAGE, signature, keyPair.getPublicKey()));
67+
assertFalse(HorsSignature.verify(MESSAGE, signature, keyPair.getPublicKey(), keyPair.getK()));
6868
}
6969

7070
@Test
@@ -74,7 +74,7 @@ void testDifferentPublicKeyDoesNotVerify() {
7474

7575
byte[][] signature = keyPair1.sign(MESSAGE);
7676

77-
assertFalse(HorsSignature.verify(MESSAGE, signature, keyPair2.getPublicKey()));
77+
assertFalse(HorsSignature.verify(MESSAGE, signature, keyPair2.getPublicKey(), keyPair1.getK()));
7878
}
7979

8080
@Test
@@ -135,8 +135,8 @@ void testSignAndVerifyForDifferentParameters(int t, int k) {
135135

136136
byte[][] signature = keyPair.sign(MESSAGE);
137137

138-
assertArrayEquals(new int[] {k, t}, new int[] {signature.length, keyPair.getPublicKey().length});
139-
assertTrue(HorsSignature.verify(MESSAGE, signature, keyPair.getPublicKey()));
138+
assertArrayEquals(new int[] {k, k, t}, new int[] {signature.length, keyPair.getK(), keyPair.getPublicKey().length});
139+
assertTrue(HorsSignature.verify(MESSAGE, signature, keyPair.getPublicKey(), k));
140140
}
141141

142142
@ParameterizedTest
@@ -157,18 +157,29 @@ void testNullAndMalformedInput() {
157157
byte[][] tooManyValues = new byte[65][32];
158158

159159
assertThrows(IllegalArgumentException.class, () -> keyPair.sign(null));
160-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(null, signature, publicKey));
161-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, null, publicKey));
162-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, nullValue, publicKey));
163-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, shortValue, publicKey));
164-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, new byte[0][], publicKey));
165-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, tooManyValues, publicKey));
166-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, signature, null));
167-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, signature, new byte[15][32]));
168-
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, signature, nullValue));
160+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(null, signature, publicKey, 4));
161+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, null, publicKey, 4));
162+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, nullValue, publicKey, 4));
163+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, shortValue, publicKey, 4));
164+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, new byte[0][], publicKey, 4));
165+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, tooManyValues, publicKey, 4));
166+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, signature, null, 4));
167+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, signature, new byte[15][32], 4));
168+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, signature, nullValue, 4));
169+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, signature, publicKey, 3));
170+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, signature, publicKey, 0));
171+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, tooManyValues, publicKey, 65));
169172
assertThrows(IllegalArgumentException.class, () -> HorsSignature.messageIndices(new byte[1], 2, 8));
170173
}
171174

175+
@Test
176+
void testTruncatedSignatureIsRejected() {
177+
HorsSignature keyPair = new HorsSignature();
178+
byte[][] truncated = Arrays.copyOf(keyPair.sign(MESSAGE), 1);
179+
180+
assertThrows(IllegalArgumentException.class, () -> HorsSignature.verify(MESSAGE, truncated, keyPair.getPublicKey(), keyPair.getK()));
181+
}
182+
172183
@Test
173184
void testModifyingReturnedArraysDoesNotChangeInternalState() {
174185
HorsSignature keyPair = new HorsSignature();
@@ -181,7 +192,7 @@ void testModifyingReturnedArraysDoesNotChangeInternalState() {
181192

182193
assertArrayEquals(expectedPublicKey, keyPair.getPublicKey());
183194
assertArrayEquals(expectedSignature, keyPair.sign(MESSAGE));
184-
assertTrue(HorsSignature.verify(MESSAGE, expectedSignature, keyPair.getPublicKey()));
195+
assertTrue(HorsSignature.verify(MESSAGE, expectedSignature, keyPair.getPublicKey(), keyPair.getK()));
185196
}
186197

187198
private static byte[] sha256(byte[] data) throws NoSuchAlgorithmException {

0 commit comments

Comments
 (0)