From f82638cd89296af146dd53cacc3e3670e95ef041 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sun, 18 Aug 2024 13:14:56 -0400 Subject: [PATCH] Add ByteVector#equalsConstantTime --- .../src/main/scala/scodec/bits/ByteVector.scala | 15 +++++++++++++++ .../test/scala/scodec/bits/ByteVectorTest.scala | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/core/shared/src/main/scala/scodec/bits/ByteVector.scala b/core/shared/src/main/scala/scodec/bits/ByteVector.scala index df8d139e..59741225 100644 --- a/core/shared/src/main/scala/scodec/bits/ByteVector.scala +++ b/core/shared/src/main/scala/scodec/bits/ByteVector.scala @@ -1293,6 +1293,21 @@ sealed abstract class ByteVector case _ => false } + /** Like [[equals]] but compares all bytes instead of returning after first non-matching byte. + * @group collection + */ + def equalsConstantTime(other: ByteVector): Boolean = + if (this.size != other.size) false + else { + var result, idx = 0 + val s = this.size + while (idx < s) { + result = result | (this(idx) ^ other(idx)) + idx += 1 + } + result == 0 + } + /** Display the size and bytes of this `ByteVector`. For bit vectors beyond a certain size, only a * hash of the contents are shown. * @group collection diff --git a/core/shared/src/test/scala/scodec/bits/ByteVectorTest.scala b/core/shared/src/test/scala/scodec/bits/ByteVectorTest.scala index 798458e9..2d44b8db 100644 --- a/core/shared/src/test/scala/scodec/bits/ByteVectorTest.scala +++ b/core/shared/src/test/scala/scodec/bits/ByteVectorTest.scala @@ -59,6 +59,13 @@ class ByteVectorTest extends BitsSuite { forAll((b: ByteVector, b2: ByteVector) => assert((b == b2) == (b === b2))) } + property("equalsConstantTime") { + forAll(bytesWithIndex) { case (b, m) => + assert((b.take(m) ++ b.drop(m)).equalsConstantTime(b)) + } && + forAll((b: ByteVector, b2: ByteVector) => assert((b === b2) == (b.equalsConstantTime(b2)))) + } + test("compact is a no-op for already compact byte vectors") { val b = ByteVector(0x80) assert((b.compact eq b.compact) == true)