Skip to content

Commit 48b71de

Browse files
committed
added SavableDigest support to TupleHash.
1 parent 160db88 commit 48b71de

File tree

3 files changed

+188
-60
lines changed

3 files changed

+188
-60
lines changed

core/src/main/java/org/bouncycastle/crypto/digests/TupleHash.java

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package org.bouncycastle.crypto.digests;
22

33
import org.bouncycastle.crypto.DataLengthException;
4-
import org.bouncycastle.crypto.Digest;
4+
import org.bouncycastle.crypto.SavableDigest;
55
import org.bouncycastle.crypto.Xof;
6+
import org.bouncycastle.util.Arrays;
7+
import org.bouncycastle.util.Memoable;
8+
import org.bouncycastle.util.Pack;
69
import org.bouncycastle.util.Strings;
710

811
/**
@@ -13,14 +16,14 @@
1316
* </p>
1417
*/
1518
public class TupleHash
16-
implements Xof, Digest
19+
implements Xof, SavableDigest
1720
{
1821
private static final byte[] N_TUPLE_HASH = Strings.toByteArray("TupleHash");
1922

2023
private final CSHAKEDigest cshake;
21-
private final int bitLength;
22-
private final int outputLength;
2324

25+
private int bitLength;
26+
private int outputLength;
2427
private boolean firstOutput;
2528

2629
/**
@@ -46,11 +49,27 @@ public TupleHash(int bitLength, byte[] S, int outputSize)
4649
public TupleHash(TupleHash original)
4750
{
4851
this.cshake = new CSHAKEDigest(original.cshake);
52+
this.bitLength = original.bitLength;
53+
this.outputLength = original.outputLength;
54+
this.firstOutput = original.firstOutput;
55+
}
56+
57+
public TupleHash(byte[] state)
58+
{
59+
this.cshake = new CSHAKEDigest(Arrays.copyOfRange(state, 0, state.length - 9));
60+
this.bitLength = Pack.bigEndianToInt(state, state.length - 9);
61+
this.outputLength = Pack.bigEndianToInt(state, state.length - 5);
62+
this.firstOutput = state[state.length - 1] != 0;
63+
}
64+
65+
private void copyIn(TupleHash original)
66+
{
67+
this.cshake.reset(original.cshake);
4968
this.bitLength = cshake.fixedOutputLength;
5069
this.outputLength = bitLength * 2 / 8;
5170
this.firstOutput = original.firstOutput;
5271
}
53-
72+
5473
public String getAlgorithmName()
5574
{
5675
return "TupleHash" + cshake.getAlgorithmName().substring(6);
@@ -133,4 +152,26 @@ public void reset()
133152
cshake.reset();
134153
firstOutput = true;
135154
}
155+
156+
public byte[] getEncodedState()
157+
{
158+
byte[] cshakeState = this.cshake.getEncodedState();
159+
byte[] extraState = new byte[4 + 4 + 1];
160+
161+
Pack.intToBigEndian(this.bitLength, extraState, 0);
162+
Pack.intToBigEndian(this.outputLength, extraState, 4);
163+
extraState[8] = this.firstOutput ? (byte)1 : (byte)0;
164+
165+
return Arrays.concatenate(cshakeState, extraState);
166+
}
167+
168+
public Memoable copy()
169+
{
170+
return new TupleHash(this);
171+
}
172+
173+
public void reset(Memoable other)
174+
{
175+
copyIn((TupleHash)other);
176+
}
136177
}

core/src/test/java/org/bouncycastle/crypto/test/DigestTest.java

Lines changed: 108 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.bouncycastle.crypto.OutputLengthException;
1414
import org.bouncycastle.crypto.Xof;
1515
import org.bouncycastle.crypto.digests.EncodableDigest;
16+
import org.bouncycastle.crypto.digests.TupleHash;
1617
import org.bouncycastle.test.TestResourceFinder;
1718
import org.bouncycastle.util.Arrays;
1819
import org.bouncycastle.util.Memoable;
@@ -71,71 +72,115 @@ public void performTest()
7172

7273
private void testEncodedState(byte[] resBuf, byte[] input, byte[] expected)
7374
{
74-
// test state encoding;
75-
digest.update(input, 0, input.length / 2);
75+
if (digest instanceof TupleHash)
76+
{
77+
digest.update(input, 0, input.length);
78+
Digest copy1 = cloneDigest(((EncodableDigest)digest).getEncodedState());
79+
Digest copy2 = cloneDigest(((EncodableDigest)copy1).getEncodedState());
7680

77-
// copy the Digest
78-
Digest copy1 = cloneDigest(((EncodableDigest)digest).getEncodedState());
79-
Digest copy2 = cloneDigest(((EncodableDigest)copy1).getEncodedState());
81+
digest.doFinal(resBuf, 0);
8082

81-
digest.update(input, input.length / 2, input.length - input.length / 2);
83+
if (!areEqual(expected, resBuf))
84+
{
85+
fail("failing TupleHash state vector test", expected, new String(Hex.encode(resBuf)));
86+
}
8287

83-
digest.doFinal(resBuf, 0);
88+
copy2.doFinal(resBuf, 0);
8489

85-
if (!areEqual(expected, resBuf))
86-
{
87-
fail("failing state vector test", expected, new String(Hex.encode(resBuf)));
90+
if (!areEqual(expected, resBuf))
91+
{
92+
fail("failing TupleHash state copy2 vector test", expected, new String(Hex.encode(resBuf)));
93+
}
8894
}
95+
else
96+
{
97+
// test state encoding;
98+
digest.update(input, 0, input.length / 2);
8999

90-
copy1.update(input, input.length / 2, input.length - input.length / 2);
91-
copy1.doFinal(resBuf, 0);
100+
// copy the Digest
101+
Digest copy1 = cloneDigest(((EncodableDigest)digest).getEncodedState());
102+
Digest copy2 = cloneDigest(((EncodableDigest)copy1).getEncodedState());
92103

93-
if (!areEqual(expected, resBuf))
94-
{
95-
fail("failing state copy1 vector test", expected, new String(Hex.encode(resBuf)));
96-
}
104+
digest.update(input, input.length / 2, input.length - input.length / 2);
97105

98-
copy2.update(input, input.length / 2, input.length - input.length / 2);
99-
copy2.doFinal(resBuf, 0);
106+
digest.doFinal(resBuf, 0);
100107

101-
if (!areEqual(expected, resBuf))
102-
{
103-
fail("failing state copy2 vector test", expected, new String(Hex.encode(resBuf)));
108+
if (!areEqual(expected, resBuf))
109+
{
110+
fail("failing state vector test", expected, new String(Hex.encode(resBuf)));
111+
}
112+
113+
copy1.update(input, input.length / 2, input.length - input.length / 2);
114+
copy1.doFinal(resBuf, 0);
115+
116+
if (!areEqual(expected, resBuf))
117+
{
118+
fail("failing state copy1 vector test", expected, new String(Hex.encode(resBuf)));
119+
}
120+
121+
copy2.update(input, input.length / 2, input.length - input.length / 2);
122+
copy2.doFinal(resBuf, 0);
123+
124+
if (!areEqual(expected, resBuf))
125+
{
126+
fail("failing state copy2 vector test", expected, new String(Hex.encode(resBuf)));
127+
}
104128
}
105129
}
106130

107131
private void testMemo(byte[] resBuf, byte[] input, byte[] expected)
108132
{
109-
Memoable m = (Memoable)digest;
133+
if (digest instanceof TupleHash)
134+
{
135+
Memoable m = (Memoable)digest;
110136

111-
digest.update(input, 0, input.length / 2);
137+
digest.update(input, 0, input.length);
112138

113-
// copy the Digest
114-
Memoable copy1 = m.copy();
115-
Memoable copy2 = copy1.copy();
139+
Memoable copy1 = m.copy();
140+
Memoable copy2 = copy1.copy();
116141

117-
digest.update(input, input.length / 2, input.length - input.length / 2);
118-
digest.doFinal(resBuf, 0);
142+
digest.doFinal(resBuf, 0);
143+
if (!areEqual(expected, resBuf))
144+
{
145+
fail("failing tuplehash memo vector test 1", results[results.length - 1], new String(Hex.encode(resBuf)));
146+
}
119147

120-
if (!areEqual(expected, resBuf))
121-
{
122-
fail("failing memo vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
148+
Digest d = (Digest)copy2;
149+
d.doFinal(resBuf, 0);
123150
}
151+
else
152+
{
153+
Memoable m = (Memoable)digest;
124154

125-
m.reset(copy1);
155+
digest.update(input, 0, input.length / 2);
126156

127-
digest.update(input, input.length / 2, input.length - input.length / 2);
128-
digest.doFinal(resBuf, 0);
157+
// copy the Digest
158+
Memoable copy1 = m.copy();
159+
Memoable copy2 = copy1.copy();
129160

130-
if (!areEqual(expected, resBuf))
131-
{
132-
fail("failing memo reset vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
133-
}
161+
digest.update(input, input.length / 2, input.length - input.length / 2);
162+
digest.doFinal(resBuf, 0);
134163

135-
Digest md = (Digest)copy2;
164+
if (!areEqual(expected, resBuf))
165+
{
166+
fail("failing memo vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
167+
}
168+
169+
m.reset(copy1);
170+
171+
digest.update(input, input.length / 2, input.length - input.length / 2);
172+
digest.doFinal(resBuf, 0);
173+
174+
if (!areEqual(expected, resBuf))
175+
{
176+
fail("failing memo reset vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
177+
}
136178

137-
md.update(input, input.length / 2, input.length - input.length / 2);
138-
md.doFinal(resBuf, 0);
179+
Digest md = (Digest)copy2;
180+
181+
md.update(input, input.length / 2, input.length - input.length / 2);
182+
md.doFinal(resBuf, 0);
183+
}
139184

140185
if (!areEqual(expected, resBuf))
141186
{
@@ -145,21 +190,32 @@ private void testMemo(byte[] resBuf, byte[] input, byte[] expected)
145190

146191
private void testClone(byte[] resBuf, byte[] input, byte[] expected)
147192
{
148-
digest.update(input, 0, input.length / 2);
193+
if (digest instanceof TupleHash)
194+
{
195+
// can't support multiple updates like the others - it's the whole point!
196+
Digest d = cloneDigest(digest);
149197

150-
// clone the Digest
151-
Digest d = cloneDigest(digest);
198+
d.update(input, 0, input.length);
199+
d.doFinal(resBuf, 0);
200+
}
201+
else
202+
{
203+
digest.update(input, 0, input.length / 2);
152204

153-
digest.update(input, input.length / 2, input.length - input.length / 2);
154-
digest.doFinal(resBuf, 0);
205+
// clone the Digest
206+
Digest d = cloneDigest(digest);
155207

156-
if (!areEqual(expected, resBuf))
157-
{
158-
fail("failing clone vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
159-
}
208+
digest.update(input, input.length / 2, input.length - input.length / 2);
209+
digest.doFinal(resBuf, 0);
210+
211+
if (!areEqual(expected, resBuf))
212+
{
213+
fail("failing clone vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
214+
}
160215

161-
d.update(input, input.length / 2, input.length - input.length / 2);
162-
d.doFinal(resBuf, 0);
216+
d.update(input, input.length / 2, input.length - input.length / 2);
217+
d.doFinal(resBuf, 0);
218+
}
163219

164220
if (!areEqual(expected, resBuf))
165221
{

core/src/test/java/org/bouncycastle/crypto/test/TupleHashTest.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,45 @@
55
import org.bouncycastle.util.Arrays;
66
import org.bouncycastle.util.Strings;
77
import org.bouncycastle.util.encoders.Hex;
8-
import org.bouncycastle.util.test.SimpleTest;
98

109
/**
1110
* TupleHash test vectors from:
1211
* <p>
1312
* https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf
1413
*/
1514
public class TupleHashTest
16-
extends SimpleTest
15+
extends DigestTest
1716
{
17+
private static String[] messages =
18+
{
19+
"",
20+
"a",
21+
"abc",
22+
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
23+
};
24+
25+
private static String[] digests =
26+
{
27+
"549330469327c593eb95b1d467c48e5781939e135e10632c804ef8a69c73281c",
28+
"98e1cb3104a046dcdc77a6acbee4177553ba15cb0235a3db99f506198dc9c8b5",
29+
"873195cadfea6bc6a71cdd903da87afb49fd232d71db817c3abcad48ad8a7898",
30+
"b9588fbf7302809815ebd989d00752f732a08dc9b1153b6f3a097f518cdc44ea"
31+
};
32+
33+
public TupleHashTest()
34+
{
35+
super(new TupleHash(128, new byte[0]), messages, digests);
36+
}
37+
1838
public String getName()
1939
{
2040
return "TupleHash";
2141
}
2242

2343
public void performTest()
24-
throws Exception
2544
{
45+
super.performTest();
46+
2647
TupleHash tHash = new TupleHash(128, new byte[0]);
2748

2849
tHash.update(Hex.decode("000102"), 0, 3);
@@ -105,6 +126,16 @@ public void performTest()
105126
testClone();
106127
}
107128

129+
protected Digest cloneDigest(Digest digest)
130+
{
131+
return new TupleHash((TupleHash)digest);
132+
}
133+
134+
protected Digest cloneDigest(byte[] state)
135+
{
136+
return new TupleHash(state);
137+
}
138+
108139
private void testClone()
109140
{
110141
Digest digest = new TupleHash(256, Strings.toByteArray("My Tuple App"));

0 commit comments

Comments
 (0)