Add ByteString.equals(other, constantTime) overload#1812
Conversation
|
Your test doesn't make any attempt to validate the constant-time-ness of the implementation. |
|
Fair point. A wall-clock timing assertion would be flaky in CI, so the test only covers functional correctness; the constant-time property comes from the implementation never short-circuiting. Happy to drop the constant-time framing and document it purely as the structural guarantee, or take whatever direction you prefer here. |
|
We should be able to use simple statistics to determine that given two very large byte strings:
|
Signed-off-by: say <say.apm35@gmail.com>
|
Added a JVM statistical timing test in |
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
|
Fixed: switched to org.junit.{Test,Assert.assertTrue} (matching the rest of jvmTest) and reformatted the assertTrue calls to multi-line style so spotlessKotlinCheck passes on ktlint 0.48.2. |
…gTest Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
|
Fixed: added missing |
|
Trying to kick CI after this morning's incident. |
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
|
The CT-vs-normal(match) ratio was flaky on loom/Windows CI (>5x overhead from XOR accumulation on slower runners). Dropped that assertion -- the two that matter are CT(match)~CT(mismatch) (neither short-circuits) and normal(mismatch)<<CT(mismatch) (normal does). Push a59f221. |
ByteString.equals(other, constantTime) overload
|
|
||
| actual override fun equals(other: Any?) = commonEquals(other) | ||
|
|
||
| actual fun equals(other: ByteString, constantTime: Boolean) = commonEqualsConstantTime(other) |
There was a problem hiding this comment.
This does not handle constantTime=false correctly.
There was a problem hiding this comment.
Fixed in a4e8c40. The overload now branches on the flag and falls back to the regular equals for constantTime=false, with the false path covered in ByteStringTest.
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
JakeWharton
left a comment
There was a problem hiding this comment.
Looks good! Just one question.
| var result = 0 | ||
| for (i in 0 until size) { | ||
| result = result or (this[i].toInt() xor other[i].toInt()) |
There was a problem hiding this comment.
Is there a reason you chose to use 0 as the true-y value and or the comparisons as opposed to using 1 and and? The result is the same, but this version is unintuitive to me because it's not how I think about the comparisons being done.
There was a problem hiding this comment.
It's the standard constant-time idiom: XOR each byte pair (0 when equal) and OR the results, so result stays 0 only if every pair matched and becomes non-zero the moment any byte differs, with a single comparison at the very end. Going with 1 and AND would need a per-byte equality test (this[i] == other[i]), which reintroduces a branch/comparison inside the loop that the OR-of-XOR form avoids. It mirrors crypto/subtle.ConstantTimeCompare and CRYPTO_memcmp. Happy to add a short comment to that effect if it helps readability.
Adds a constant-time
ByteString.equals(other, constantTime)overload for timing-safe comparison of secrets like hashes and MACs, as requested in #1797. After a length check it XORs every byte and never short-circuits, so the running time does not depend on where the byte strings differ. Implemented incommonMainso it works on all targets, and tested across the basic and segmented byte string variants.Closes #1797. Closes #1799