From 5669709b0ed83bfba36c8ebddce31523aa30e026 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 17 Jun 2022 17:36:32 +0100 Subject: [PATCH 01/10] Add HOTP algorithm --- .../src/main/scala/bobcats/HotpPlatform.scala | 21 +++++++ .../src/main/scala/bobcats/HotpPlatform.scala | 21 +++++++ core/shared/src/main/scala/bobcats/Hotp.scala | 58 +++++++++++++++++++ .../src/test/scala/bobcats/HotpSuite.scala | 54 +++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 core/js/src/main/scala/bobcats/HotpPlatform.scala create mode 100644 core/jvm/src/main/scala/bobcats/HotpPlatform.scala create mode 100644 core/shared/src/main/scala/bobcats/Hotp.scala create mode 100644 core/shared/src/test/scala/bobcats/HotpSuite.scala diff --git a/core/js/src/main/scala/bobcats/HotpPlatform.scala b/core/js/src/main/scala/bobcats/HotpPlatform.scala new file mode 100644 index 0000000..3764420 --- /dev/null +++ b/core/js/src/main/scala/bobcats/HotpPlatform.scala @@ -0,0 +1,21 @@ +/* + * Copyright 2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bobcats + +private[bobcats] trait HotpPlatform[F[_]] {} + +private[bobcats] trait HotpCompanionPlatform {} diff --git a/core/jvm/src/main/scala/bobcats/HotpPlatform.scala b/core/jvm/src/main/scala/bobcats/HotpPlatform.scala new file mode 100644 index 0000000..3764420 --- /dev/null +++ b/core/jvm/src/main/scala/bobcats/HotpPlatform.scala @@ -0,0 +1,21 @@ +/* + * Copyright 2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bobcats + +private[bobcats] trait HotpPlatform[F[_]] {} + +private[bobcats] trait HotpCompanionPlatform {} diff --git a/core/shared/src/main/scala/bobcats/Hotp.scala b/core/shared/src/main/scala/bobcats/Hotp.scala new file mode 100644 index 0000000..c1f3576 --- /dev/null +++ b/core/shared/src/main/scala/bobcats/Hotp.scala @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bobcats + +import cats.effect.kernel.Sync +import cats.syntax.functor._ +import scodec.bits.ByteVector + +sealed trait Hotp[F[_]] extends HotpPlatform[F] { + def generate( + key: SecretKey[HmacAlgorithm.SHA1.type], + movingFactor: Long, + digits: Int = 6 + ): F[Int] +} + +private[bobcats] trait UnsealedHotp[F[_]] extends Hotp[F] + +object Hotp extends HotpCompanionPlatform { + private val powersOfTen = + Array(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000) + + def apply[F[_]](implicit hotp: Hotp[F]): hotp.type = hotp + + implicit def forSync[F[_]](implicit F: Sync[F], H: Hmac[F]): Hotp[F] = + new UnsealedHotp[F] { + override def generate( + key: SecretKey[HmacAlgorithm.SHA1.type], + movingFactor: Long, + digits: Int + ): F[Int] = { + H.digest(key, ByteVector.fromLong(movingFactor)).map { hmac => + val offset = hmac.last & 0xf + + val binaryCode = ((hmac.get(offset.longValue) & 0x7f) << 24) | + ((hmac.get((offset + 1).longValue) & 0xff) << 16) | + ((hmac.get((offset + 2).longValue) & 0xff) << 8) | + (hmac.get((offset + 3).longValue) & 0xff) + + binaryCode % powersOfTen(digits) + } + } + } +} diff --git a/core/shared/src/test/scala/bobcats/HotpSuite.scala b/core/shared/src/test/scala/bobcats/HotpSuite.scala new file mode 100644 index 0000000..3fd9ac9 --- /dev/null +++ b/core/shared/src/test/scala/bobcats/HotpSuite.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bobcats + +package bobcats + +import cats.Functor +import cats.effect.IO +import cats.effect.SyncIO +import munit.CatsEffectSuite +import cats.syntax.functor._ + +import scala.reflect.ClassTag +import scodec.bits.ByteVector + +class HotpSuite extends CatsEffectSuite { + + val key = ByteVector.fromHex("3132333435363738393031323334353637383930").get + + val expectedValues = List( + 755224, 287082, 359152, 969429, 338314, 254676, 287922, 162583, 399871, 520489 + ) + + def tests[F[_]: Hotp: Functor](implicit ct: ClassTag[F[Nothing]]) = { + expectedValues.zipWithIndex.foreach { + case (expected, counter) => + test(s"RFC4226 test case ${counter} for ${ct.runtimeClass.getSimpleName()}") { + Hotp[F] + .generate(SecretKeySpec(key, HmacAlgorithm.SHA1), counter.toLong, digits = 6) + .map { obtained => assertEquals(obtained, expected) } + } + } + } + + if (Set("JVM", "NodeJS").contains(BuildInfo.runtime)) + tests[SyncIO] + + if (BuildInfo.runtime != "JVM") + tests[IO] +} From 3d6ebfe06169c9953e2570adbeee76d5e6075d63 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 17 Jun 2022 17:52:28 +0100 Subject: [PATCH 02/10] Add HOTP#generate overload instead of using default arguments, use preconditions for HOTP digits --- core/shared/src/main/scala/bobcats/Hotp.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/bobcats/Hotp.scala b/core/shared/src/main/scala/bobcats/Hotp.scala index c1f3576..d2b4b63 100644 --- a/core/shared/src/main/scala/bobcats/Hotp.scala +++ b/core/shared/src/main/scala/bobcats/Hotp.scala @@ -24,7 +24,12 @@ sealed trait Hotp[F[_]] extends HotpPlatform[F] { def generate( key: SecretKey[HmacAlgorithm.SHA1.type], movingFactor: Long, - digits: Int = 6 + digits: Int + ): F[Int] + + def generate( + key: SecretKey[HmacAlgorithm.SHA1.type], + movingFactor: Long ): F[Int] } @@ -38,11 +43,19 @@ object Hotp extends HotpCompanionPlatform { implicit def forSync[F[_]](implicit F: Sync[F], H: Hmac[F]): Hotp[F] = new UnsealedHotp[F] { + override def generate( + key: SecretKey[HmacAlgorithm.SHA1.type], + movingFactor: Long): F[Int] = + generate(key, movingFactor, digits = 6) + override def generate( key: SecretKey[HmacAlgorithm.SHA1.type], movingFactor: Long, digits: Int ): F[Int] = { + require(digits >= 6, s"digits must be at least 6, was $digits") + require(digits < 10, s"digits must be less than 10, was $digits") + H.digest(key, ByteVector.fromLong(movingFactor)).map { hmac => val offset = hmac.last & 0xf From e3adb374cb14b11a7b96b3c85aa7d26bc418dc59 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 17 Jun 2022 17:54:53 +0100 Subject: [PATCH 03/10] Remove HOTP platform traits for now --- .../src/main/scala/bobcats/HotpPlatform.scala | 21 ------------------- .../src/main/scala/bobcats/HotpPlatform.scala | 21 ------------------- core/shared/src/main/scala/bobcats/Hotp.scala | 4 ++-- 3 files changed, 2 insertions(+), 44 deletions(-) delete mode 100644 core/js/src/main/scala/bobcats/HotpPlatform.scala delete mode 100644 core/jvm/src/main/scala/bobcats/HotpPlatform.scala diff --git a/core/js/src/main/scala/bobcats/HotpPlatform.scala b/core/js/src/main/scala/bobcats/HotpPlatform.scala deleted file mode 100644 index 3764420..0000000 --- a/core/js/src/main/scala/bobcats/HotpPlatform.scala +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2021 Typelevel - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package bobcats - -private[bobcats] trait HotpPlatform[F[_]] {} - -private[bobcats] trait HotpCompanionPlatform {} diff --git a/core/jvm/src/main/scala/bobcats/HotpPlatform.scala b/core/jvm/src/main/scala/bobcats/HotpPlatform.scala deleted file mode 100644 index 3764420..0000000 --- a/core/jvm/src/main/scala/bobcats/HotpPlatform.scala +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2021 Typelevel - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package bobcats - -private[bobcats] trait HotpPlatform[F[_]] {} - -private[bobcats] trait HotpCompanionPlatform {} diff --git a/core/shared/src/main/scala/bobcats/Hotp.scala b/core/shared/src/main/scala/bobcats/Hotp.scala index d2b4b63..4a7a4f6 100644 --- a/core/shared/src/main/scala/bobcats/Hotp.scala +++ b/core/shared/src/main/scala/bobcats/Hotp.scala @@ -20,7 +20,7 @@ import cats.effect.kernel.Sync import cats.syntax.functor._ import scodec.bits.ByteVector -sealed trait Hotp[F[_]] extends HotpPlatform[F] { +sealed trait Hotp[F[_]] { def generate( key: SecretKey[HmacAlgorithm.SHA1.type], movingFactor: Long, @@ -35,7 +35,7 @@ sealed trait Hotp[F[_]] extends HotpPlatform[F] { private[bobcats] trait UnsealedHotp[F[_]] extends Hotp[F] -object Hotp extends HotpCompanionPlatform { +object Hotp { private val powersOfTen = Array(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000) From 7ae48faca46a0bb7b83ef2d3237f4c29c7e451c2 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 17 Jun 2022 18:04:32 +0100 Subject: [PATCH 04/10] Make HOTP#generate overload a default implementation --- core/shared/src/main/scala/bobcats/Hotp.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/shared/src/main/scala/bobcats/Hotp.scala b/core/shared/src/main/scala/bobcats/Hotp.scala index 4a7a4f6..3a2cf02 100644 --- a/core/shared/src/main/scala/bobcats/Hotp.scala +++ b/core/shared/src/main/scala/bobcats/Hotp.scala @@ -30,7 +30,8 @@ sealed trait Hotp[F[_]] { def generate( key: SecretKey[HmacAlgorithm.SHA1.type], movingFactor: Long - ): F[Int] + ): F[Int] = + generate(key, movingFactor, digits = 6) } private[bobcats] trait UnsealedHotp[F[_]] extends Hotp[F] @@ -43,11 +44,6 @@ object Hotp { implicit def forSync[F[_]](implicit F: Sync[F], H: Hmac[F]): Hotp[F] = new UnsealedHotp[F] { - override def generate( - key: SecretKey[HmacAlgorithm.SHA1.type], - movingFactor: Long): F[Int] = - generate(key, movingFactor, digits = 6) - override def generate( key: SecretKey[HmacAlgorithm.SHA1.type], movingFactor: Long, From 04ffc71f9ae3f2bc2eba95de29092deda0fc8a37 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Mon, 20 Jun 2022 12:23:12 +0100 Subject: [PATCH 05/10] Remove Hotp capability trait and use companion object methods instead --- core/shared/src/main/scala/bobcats/Hotp.scala | 55 ++++++++----------- .../src/test/scala/bobcats/HotpSuite.scala | 16 +++--- 2 files changed, 29 insertions(+), 42 deletions(-) diff --git a/core/shared/src/main/scala/bobcats/Hotp.scala b/core/shared/src/main/scala/bobcats/Hotp.scala index 3a2cf02..bb0ab8b 100644 --- a/core/shared/src/main/scala/bobcats/Hotp.scala +++ b/core/shared/src/main/scala/bobcats/Hotp.scala @@ -16,23 +16,11 @@ package bobcats -import cats.effect.kernel.Sync +import cats.Functor import cats.syntax.functor._ import scodec.bits.ByteVector -sealed trait Hotp[F[_]] { - def generate( - key: SecretKey[HmacAlgorithm.SHA1.type], - movingFactor: Long, - digits: Int - ): F[Int] - - def generate( - key: SecretKey[HmacAlgorithm.SHA1.type], - movingFactor: Long - ): F[Int] = - generate(key, movingFactor, digits = 6) -} +sealed trait Hotp[F[_]] {} private[bobcats] trait UnsealedHotp[F[_]] extends Hotp[F] @@ -40,28 +28,29 @@ object Hotp { private val powersOfTen = Array(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000) - def apply[F[_]](implicit hotp: Hotp[F]): hotp.type = hotp + def generate[F[_]]( + key: SecretKey[HmacAlgorithm.SHA1.type], + movingFactor: Long + )(implicit F: Functor[F], H: Hmac[F]): F[Int] = + generate(key, movingFactor, digits = 6) - implicit def forSync[F[_]](implicit F: Sync[F], H: Hmac[F]): Hotp[F] = - new UnsealedHotp[F] { - override def generate( - key: SecretKey[HmacAlgorithm.SHA1.type], - movingFactor: Long, - digits: Int - ): F[Int] = { - require(digits >= 6, s"digits must be at least 6, was $digits") - require(digits < 10, s"digits must be less than 10, was $digits") + def generate[F[_]]( + key: SecretKey[HmacAlgorithm.SHA1.type], + movingFactor: Long, + digits: Int + )(implicit F: Functor[F], H: Hmac[F]): F[Int] = { + require(digits >= 6, s"digits must be at least 6, was $digits") + require(digits < 10, s"digits must be less than 10, was $digits") - H.digest(key, ByteVector.fromLong(movingFactor)).map { hmac => - val offset = hmac.last & 0xf + H.digest(key, ByteVector.fromLong(movingFactor)).map { hmac => + val offset = hmac.last & 0xf - val binaryCode = ((hmac.get(offset.longValue) & 0x7f) << 24) | - ((hmac.get((offset + 1).longValue) & 0xff) << 16) | - ((hmac.get((offset + 2).longValue) & 0xff) << 8) | - (hmac.get((offset + 3).longValue) & 0xff) + val binaryCode = ((hmac.get(offset.longValue) & 0x7f) << 24) | + ((hmac.get((offset + 1).longValue) & 0xff) << 16) | + ((hmac.get((offset + 2).longValue) & 0xff) << 8) | + (hmac.get((offset + 3).longValue) & 0xff) - binaryCode % powersOfTen(digits) - } - } + binaryCode % powersOfTen(digits) } + } } diff --git a/core/shared/src/test/scala/bobcats/HotpSuite.scala b/core/shared/src/test/scala/bobcats/HotpSuite.scala index 3fd9ac9..e45c047 100644 --- a/core/shared/src/test/scala/bobcats/HotpSuite.scala +++ b/core/shared/src/test/scala/bobcats/HotpSuite.scala @@ -21,11 +21,11 @@ package bobcats import cats.Functor import cats.effect.IO import cats.effect.SyncIO -import munit.CatsEffectSuite import cats.syntax.functor._ +import munit.CatsEffectSuite +import scodec.bits.ByteVector import scala.reflect.ClassTag -import scodec.bits.ByteVector class HotpSuite extends CatsEffectSuite { @@ -35,20 +35,18 @@ class HotpSuite extends CatsEffectSuite { 755224, 287082, 359152, 969429, 338314, 254676, 287922, 162583, 399871, 520489 ) - def tests[F[_]: Hotp: Functor](implicit ct: ClassTag[F[Nothing]]) = { + def tests[F[_]: Hmac: Functor](implicit ct: ClassTag[F[Nothing]]) = { expectedValues.zipWithIndex.foreach { case (expected, counter) => test(s"RFC4226 test case ${counter} for ${ct.runtimeClass.getSimpleName()}") { - Hotp[F] - .generate(SecretKeySpec(key, HmacAlgorithm.SHA1), counter.toLong, digits = 6) + Hotp + .generate[F](SecretKeySpec(key, HmacAlgorithm.SHA1), counter.toLong, digits = 6) .map { obtained => assertEquals(obtained, expected) } } } } - if (Set("JVM", "NodeJS").contains(BuildInfo.runtime)) - tests[SyncIO] + tests[SyncIO] - if (BuildInfo.runtime != "JVM") - tests[IO] + tests[IO] } From 35f45648673edb8b4980ef0224bbd12eb1a377e6 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Mon, 20 Jun 2022 12:32:43 +0100 Subject: [PATCH 06/10] Remove the capability trait --- core/shared/src/main/scala/bobcats/Hotp.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/shared/src/main/scala/bobcats/Hotp.scala b/core/shared/src/main/scala/bobcats/Hotp.scala index bb0ab8b..a26293c 100644 --- a/core/shared/src/main/scala/bobcats/Hotp.scala +++ b/core/shared/src/main/scala/bobcats/Hotp.scala @@ -20,10 +20,6 @@ import cats.Functor import cats.syntax.functor._ import scodec.bits.ByteVector -sealed trait Hotp[F[_]] {} - -private[bobcats] trait UnsealedHotp[F[_]] extends Hotp[F] - object Hotp { private val powersOfTen = Array(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000) From 557cc6fe7d07118ff705fc40fb9c3d26eca7070a Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 24 Jun 2022 16:25:33 +0100 Subject: [PATCH 07/10] Remove HOTP SyncIO testing --- core/shared/src/test/scala/bobcats/HotpSuite.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/shared/src/test/scala/bobcats/HotpSuite.scala b/core/shared/src/test/scala/bobcats/HotpSuite.scala index e45c047..a320fe3 100644 --- a/core/shared/src/test/scala/bobcats/HotpSuite.scala +++ b/core/shared/src/test/scala/bobcats/HotpSuite.scala @@ -20,7 +20,6 @@ package bobcats import cats.Functor import cats.effect.IO -import cats.effect.SyncIO import cats.syntax.functor._ import munit.CatsEffectSuite import scodec.bits.ByteVector @@ -46,7 +45,5 @@ class HotpSuite extends CatsEffectSuite { } } - tests[SyncIO] - tests[IO] } From c3ff8398cda40717982cb8796c3fb31465a6c523 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 24 Jun 2022 16:41:44 +0100 Subject: [PATCH 08/10] Remove redundant package declaration --- core/shared/src/test/scala/bobcats/HotpSuite.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/shared/src/test/scala/bobcats/HotpSuite.scala b/core/shared/src/test/scala/bobcats/HotpSuite.scala index a320fe3..c619d7f 100644 --- a/core/shared/src/test/scala/bobcats/HotpSuite.scala +++ b/core/shared/src/test/scala/bobcats/HotpSuite.scala @@ -16,8 +16,6 @@ package bobcats -package bobcats - import cats.Functor import cats.effect.IO import cats.syntax.functor._ From e2193f6f85dce9b5bc9c98c7c1419c727fb9014d Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 15 Jul 2022 13:27:57 +0100 Subject: [PATCH 09/10] Replace another usage of ByteVector.fromHex Co-authored-by: Arman Bilge --- core/shared/src/test/scala/bobcats/HotpSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/test/scala/bobcats/HotpSuite.scala b/core/shared/src/test/scala/bobcats/HotpSuite.scala index c619d7f..792b67d 100644 --- a/core/shared/src/test/scala/bobcats/HotpSuite.scala +++ b/core/shared/src/test/scala/bobcats/HotpSuite.scala @@ -26,7 +26,7 @@ import scala.reflect.ClassTag class HotpSuite extends CatsEffectSuite { - val key = ByteVector.fromHex("3132333435363738393031323334353637383930").get + val key = hex"3132333435363738393031323334353637383930" val expectedValues = List( 755224, 287082, 359152, 969429, 338314, 254676, 287922, 162583, 399871, 520489 From aee956a01a02be4f780e0d5d8c262261f60bef23 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 15 Jul 2022 13:32:35 +0100 Subject: [PATCH 10/10] Add missing scodec hex syntax import --- core/shared/src/test/scala/bobcats/HotpSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/test/scala/bobcats/HotpSuite.scala b/core/shared/src/test/scala/bobcats/HotpSuite.scala index 792b67d..c424c2f 100644 --- a/core/shared/src/test/scala/bobcats/HotpSuite.scala +++ b/core/shared/src/test/scala/bobcats/HotpSuite.scala @@ -20,7 +20,7 @@ import cats.Functor import cats.effect.IO import cats.syntax.functor._ import munit.CatsEffectSuite -import scodec.bits.ByteVector +import scodec.bits._ import scala.reflect.ClassTag