Schnorr Data Structures (#1564)

* Pulled down crypto things from dlc branch including: Schnorr data structures, Schnorr Bouncy Castle implementation, FieldElement, and tests for these things

* Call fromValidHex in CryptoUtil
This commit is contained in:
Nadav Kohen 2020-06-16 09:52:45 -05:00 committed by GitHub
parent 001d1c9d7a
commit baf49b7452
20 changed files with 1227 additions and 24 deletions

View File

@ -1,7 +1,7 @@
package org.bitcoins.core.util
import org.bitcoins.crypto.CryptoUtil
import org.bitcoins.testkit.core.gen.CryptoGenerators
import org.bitcoins.testkit.core.gen.{CryptoGenerators, NumberGenerator}
import org.bitcoins.testkit.util.BitcoinSUnitTest
import scodec.bits._
@ -93,4 +93,18 @@ class CryptoUtilTest extends BitcoinSUnitTest {
}
}
it must "compute tagged hashes correctly" in {
forAll(NumberGenerator.bytevector) { bytes =>
assert(
CryptoUtil.sha256SchnorrChallenge(bytes) == CryptoUtil
.taggedSha256(bytes, "BIP340/challenge"))
assert(
CryptoUtil.sha256SchnorrNonce(bytes) == CryptoUtil
.taggedSha256(bytes, "BIP340/nonce"))
assert(
CryptoUtil.sha256SchnorrAuxRand(bytes) == CryptoUtil
.taggedSha256(bytes, "BIP340/aux"))
}
}
}

View File

@ -6,13 +6,13 @@ import org.bitcoins.core.number.{UInt32, UInt8}
import org.bitcoins.core.util._
import org.bitcoins.crypto.{
BaseECKey,
BouncyCastleUtil,
CryptoContext,
CryptoUtil,
ECDigitalSignature,
ECPrivateKey,
ECPublicKey,
Factory,
FieldElement,
MaskedToString,
NetworkElement
}
@ -213,12 +213,14 @@ sealed abstract class ExtPrivateKey
//parse256(IL) + kpar (mod n)
val tweak = CryptoContext.default match {
case CryptoContext.LibSecp256k1 =>
NativeSecp256k1.privKeyTweakAdd(il.toArray, key.bytes.toArray)
val tweakByteArr =
NativeSecp256k1.privKeyTweakAdd(il.toArray, key.bytes.toArray)
ByteVector(tweakByteArr)
case CryptoContext.BouncyCastle =>
val sum = BouncyCastleUtil.addNumbers(key.bytes, il)
sum.toByteArray
val sum = key.fieldElement.add(FieldElement(il))
sum.bytes
}
val childKey = ECPrivateKey(ByteVector(tweak))
val childKey = ECPrivateKey(tweak)
val fp = CryptoUtil.sha256Hash160(key.publicKey.bytes).bytes.take(4)
ExtPrivateKey(version, depth + UInt8.one, fp, idx, ChainCode(ir), childKey)
}
@ -271,6 +273,20 @@ object ExtPrivateKey extends Factory[ExtPrivateKey] {
"Fingerprint must be 4 bytes in size, got: " + fingerprint)
}
def freshRootKey(version: ExtKeyPrivVersion): ExtPrivateKey = {
val privKey = ECPrivateKey.freshPrivateKey
val chainCode = ChainCode.fromBytes(ECPrivateKey.freshPrivateKey.bytes)
ExtPrivateKey(
version,
UInt8.zero,
UInt32.zero.bytes,
UInt32.zero,
chainCode,
privKey
)
}
/** Takes in a base58 string and tries to convert it to an extended private key */
def fromString(base58: String): Try[ExtPrivateKey] =
ExtKey.fromString(base58) match {

View File

@ -5,6 +5,7 @@ import java.math.BigInteger
import org.bitcoins.core.number._
import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.core.protocol.blockchain.BlockHeader.TargetDifficultyHelper
import org.bitcoins.crypto.FieldElement
import scodec.bits.{BitVector, ByteVector}
import scala.math.BigInt
@ -28,6 +29,10 @@ sealed abstract class NumberUtil extends BitcoinSLogger {
toUnsignedInt(bytes.toArray)
}
def uintToFieldElement(bytes: ByteVector): FieldElement = {
FieldElement(toUnsignedInt(bytes))
}
/** Converts a sequence of bytes to a **big endian** unsigned integer */
def toUnsignedInt(bytes: Array[Byte]): BigInt = {
BigInt(new BigInteger(1, bytes))

View File

@ -0,0 +1,119 @@
package org.bitcoins.crypto
import org.bitcoins.testkit.util.BitcoinSUnitTest
import org.scalatest.Assertion
import scodec.bits.ByteVector
import scala.util.{Failure, Success, Try}
/** Tests from https://github.com/sipa/bips/blob/bip-taproot/bip-0340/test-vectors.csv */
class BIP340Test extends BitcoinSUnitTest {
behavior of "Schnorr Signing"
def testSign(
index: Int,
secKey: ECPrivateKey,
auxRand: ByteVector,
msg: ByteVector,
expectedSig: SchnorrDigitalSignature): Assertion = {
val secpSig = secKey.schnorrSign(msg, auxRand)
val bouncyCastleSig = BouncyCastleUtil.schnorrSign(msg, secKey, auxRand)
assert(secpSig == expectedSig,
s"Test $index failed signing for libsecp256k1")
assert(bouncyCastleSig == expectedSig,
s"Test $index failed signing for Bouncy Castle")
assert(bouncyCastleSig == secpSig)
}
def testVerify(
index: Int,
pubKey: SchnorrPublicKey,
msg: ByteVector,
sig: SchnorrDigitalSignature,
expectedResult: Boolean,
comment: String): Assertion = {
val secpResult = pubKey.verify(msg, sig)
val bouncyCastleResult = BouncyCastleUtil.schnorrVerify(msg, pubKey, sig)
assert(secpResult == expectedResult,
s"Test $index failed verification for libsecp256k1: $comment")
assert(bouncyCastleResult == expectedResult,
s"Test $index failed verification for Bouncy Castle: $comment")
assert(bouncyCastleResult == secpResult)
}
def test(
index: Int,
secKeyOpt: Option[String],
pubKey: String,
auxRandOpt: Option[String],
msg: String,
sig: String,
result: Boolean,
comment: String): Assertion = {
val pkT = Try(SchnorrPublicKey(pubKey))
val msgBytes = ByteVector.fromHex(msg).get
val schnorrSigT = Try(SchnorrDigitalSignature(sig))
(pkT, schnorrSigT) match {
case (Success(pk), Success(schnorrSig)) =>
(secKeyOpt, auxRandOpt) match {
case (Some(secKeyStr), Some(auxRandStr)) =>
val secKey = ECPrivateKey(secKeyStr)
assert(secKey.schnorrPublicKey == pk)
val auxRand = ByteVector.fromHex(auxRandStr).get
testSign(index, secKey, auxRand, msgBytes, schnorrSig)
case _ => ()
}
testVerify(index, pk, msgBytes, schnorrSig, result, comment)
case (Failure(_), _) |
(_, Failure(_)) => // Must be verify only test resulting in false
assert(secKeyOpt.isEmpty)
assert(auxRandOpt.isEmpty)
assert(!result, s"Test $index failed to parse signature: $comment")
}
}
private def toOpt(str: String): Option[String] = {
if (str.isEmpty) {
None
} else {
Some(str)
}
}
it must "pass the BIP 340 test-vectors with both secp256k1 bindings and bouncy castle" in {
val bufferedSource =
io.Source.fromURL(getClass.getResource("/bip340-test-vectors.csv"))
try {
val lines = bufferedSource.getLines
val _ = lines.next()
for (line <- bufferedSource.getLines) {
val testVec = line.split(",").map(_.trim)
val index = testVec.head.toInt
val secKeyOpt = toOpt(testVec(1))
val pubKey = testVec(2)
val auxRandOpt = toOpt(testVec(3))
val msg = testVec(4)
val sig = testVec(5)
val result = testVec(6).toBoolean
val comment = if (testVec.length > 7) {
testVec(7)
} else {
""
}
test(index = index,
secKeyOpt = secKeyOpt,
pubKey = pubKey,
auxRandOpt = auxRandOpt,
msg = msg,
sig = sig,
result = result,
comment = comment)
}
} finally {
bufferedSource.close()
}
}
}

View File

@ -10,6 +10,9 @@ import scodec.bits.ByteVector
class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
behavior of "CryptoLibraries"
override def withFixture(test: NoArgTest): Outcome = {
@ -24,16 +27,7 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
it must "add private keys the same" in {
forAll(CryptoGenerators.privateKey, CryptoGenerators.privateKey) {
case (priv1, priv2) =>
val sumWithBouncyCastle =
BouncyCastleUtil.addNumbers(priv1.bytes, priv2.bytes)
val sumWithSecp = NativeSecp256k1.privKeyTweakAdd(priv1.bytes.toArray,
priv2.bytes.toArray)
val sumKeyWithBouncyCastle =
ECPrivateKey(ByteVector(sumWithBouncyCastle.toByteArray))
val sumKeyWithSecp = ECPrivateKey(ByteVector(sumWithSecp))
assert(sumKeyWithBouncyCastle == sumKeyWithSecp)
assert(priv1.addWithBouncyCastle(priv2) == priv1.addWithSecp(priv2))
}
}
@ -51,6 +45,15 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
}
}
it must "multiply keys the same" in {
forAll(CryptoGenerators.publicKey, CryptoGenerators.fieldElement) {
case (pubKey, tweak) =>
assert(
pubKey.tweakMultiplyWithSecp(tweak) == pubKey
.tweakMultiplyWithBouncyCastle(tweak))
}
}
it must "validate keys the same" in {
val keyOrGarbageGen = Gen.oneOf(CryptoGenerators.publicKey.map(_.bytes),
NumberGenerator.bytevector(33))
@ -102,4 +105,66 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
.verify(bytes, badSig, context = LibSecp256k1))
}
}
/*
it must "compute schnorr signatures the same" in {
forAll(CryptoGenerators.privateKey,
NumberGenerator.bytevector(32),
NumberGenerator.bytevector(32)) {
case (privKey, bytes, auxRand) =>
assert(
privKey.schnorrSign(bytes, auxRand, context = BouncyCastle) == privKey
.schnorrSign(bytes, auxRand, context = LibSecp256k1))
}
}
it must "compute schnorr signature for fixed nonce the same" in {
forAll(CryptoGenerators.privateKey,
CryptoGenerators.privateKey,
NumberGenerator.bytevector(32)) {
case (privKey, nonceKey, bytes) =>
val sigBC = privKey
.schnorrSignWithNonce(bytes, nonceKey, context = BouncyCastle)
val sigSecP = privKey
.schnorrSignWithNonce(bytes, nonceKey, context = LibSecp256k1)
assert(sigBC.bytes == sigSecP.bytes)
}
}
it must "validate schnorr signatures the same" in {
forAll(CryptoGenerators.privateKey,
NumberGenerator.bytevector(32),
CryptoGenerators.schnorrDigitalSignature) {
case (privKey, bytes, badSig) =>
val sig = privKey.schnorrSign(bytes)
val pubKey = privKey.schnorrPublicKey
assert(
pubKey.verify(bytes, sig, context = BouncyCastle) == pubKey
.verify(bytes, sig, context = LibSecp256k1))
assert(
pubKey.verify(bytes, badSig, context = BouncyCastle) == pubKey
.verify(bytes, badSig, context = LibSecp256k1))
}
}
it must "compute schnorr signature points the same" in {
forAll(CryptoGenerators.schnorrPublicKey,
CryptoGenerators.schnorrNonce,
NumberGenerator.bytevector(32)) {
case (pubKey, nonce, bytes) =>
val bouncyCastleSigPoint =
pubKey.computeSigPoint(bytes,
nonce,
compressed = true,
context = BouncyCastle)
val secpSigPoint = pubKey.computeSigPoint(bytes,
nonce,
compressed = true,
context = LibSecp256k1)
assert(bouncyCastleSigPoint == secpSigPoint)
}
}
*/
}

View File

@ -80,6 +80,7 @@ class ECPrivateKeyTest extends BitcoinSUnitTest {
it must "have serialization symmetry" in {
forAll(CryptoGenerators.privateKey) { privKey =>
assert(ECPrivateKey(privKey.hex) == privKey)
assert(ECPrivateKey.fromFieldElement(privKey.fieldElement) == privKey)
}
}
@ -104,4 +105,16 @@ class ECPrivateKeyTest extends BitcoinSUnitTest {
ECPrivateKey().toString must be("Masked(ECPrivateKeyImpl)")
}
it must "successfully negate itself" in {
forAll(CryptoGenerators.nonZeroPrivKey) { privKey =>
val negPrivKey = privKey.negate
val pubKey = privKey.publicKey
val negPubKey = negPrivKey.publicKey
assert(pubKey.bytes.tail == negPubKey.bytes.tail)
assert(pubKey.bytes.head != negPubKey.bytes.head)
assert(
privKey.fieldElement.add(negPrivKey.fieldElement) == FieldElement.zero)
}
}
}

View File

@ -0,0 +1,97 @@
package org.bitcoins.crypto
import org.bitcoins.testkit.core.gen.CryptoGenerators
import org.bitcoins.testkit.util.BitcoinSUnitTest
class FieldElementTest extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
behavior of "FieldElement"
private val N = CryptoParams.curve.getN
it must "have serialization symmetry" in {
forAll(CryptoGenerators.fieldElement) { fe =>
assert(FieldElement(fe.bytes) == fe)
}
}
it must "add zero correctly" in {
forAll(CryptoGenerators.fieldElement) { fe =>
assert(fe.add(FieldElement.zero) == fe)
assert(FieldElement.zero.add(fe) == fe)
}
}
it must "add small numbers correctly" in {
forAll(CryptoGenerators.smallFieldElement,
CryptoGenerators.smallFieldElement) {
case (fe1, fe2) =>
val feSum = fe1.add(fe2).toBigInteger
val bigIntSum = fe1.toBigInteger.add(fe2.toBigInteger)
assert(feSum == bigIntSum)
}
}
it must "add large numbers correctly" in {
forAll(CryptoGenerators.largeFieldElement,
CryptoGenerators.largeFieldElement) {
case (fe1, fe2) =>
val feSum = fe1.add(fe2).toBigInteger
val bigIntSum = fe1.toBigInteger.add(fe2.toBigInteger).subtract(N)
assert(feSum == bigIntSum)
}
}
it must "subtract numbers correctly" in {
forAll(CryptoGenerators.fieldElement, CryptoGenerators.fieldElement) {
case (fe1, fe2) =>
if (fe1.toBigInteger.compareTo(fe2.toBigInteger) > 0) {
val feDiff = fe1.subtract(fe2).toBigInteger
val bigIntDiff = fe1.toBigInteger.subtract(fe2.toBigInteger)
assert(feDiff == bigIntDiff)
} else {
val feDiff = fe2.subtract(fe1).toBigInteger
val bigIntDiff = fe2.toBigInteger.subtract(fe1.toBigInteger)
assert(feDiff == bigIntDiff)
}
}
}
it must "wrap around correctly" in {
assert(FieldElement.nMinusOne.add(FieldElement.one) == FieldElement.zero)
assert(
FieldElement.zero.subtract(FieldElement.one) == FieldElement.nMinusOne)
}
it must "multiply small numbers correctly" in {
forAll(CryptoGenerators.reallySmallFieldElement,
CryptoGenerators.reallySmallFieldElement) {
case (fe1, fe2) =>
val feProduct = fe1.multiply(fe2).toBigInteger
val bigIntProduct = fe1.toBigInteger.multiply(fe2.toBigInteger)
assert(feProduct == bigIntProduct)
}
}
it must "negate correctly" in {
forAll(CryptoGenerators.fieldElement) { fe =>
val negFe = fe.negate
assert(fe.add(negFe) == FieldElement.zero)
}
}
it must "invert correctly" in {
forAll(CryptoGenerators.fieldElement) { fe =>
val feInv = fe.inverse
assert(fe.multiply(feInv) == FieldElement.one)
}
}
}

View File

@ -0,0 +1,132 @@
package org.bitcoins.crypto
import org.bitcoins.core.util.NumberUtil
import org.bitcoins.testkit.core.gen.{CryptoGenerators, NumberGenerator}
import org.bitcoins.testkit.util.BitcoinSUnitTest
class SchnorrDigitalSignatureTest extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
behavior of "SchnorrDigitalSignature"
it must "have serialization symmetry" in {
forAll(CryptoGenerators.schnorrDigitalSignature) { sig =>
assert(SchnorrDigitalSignature(sig.bytes) == sig)
}
}
it must "must create and verify a digital signature" in {
forAll(NumberGenerator.bytevector(32), CryptoGenerators.privateKey) {
case (bytes, privKey) =>
val sig = privKey.schnorrSign(bytes)
assert(privKey.publicKey.schnorrVerify(bytes, sig))
}
}
it must "must not reuse R values" in {
forAll(CryptoGenerators.privateKey,
NumberGenerator.bytevector(32),
NumberGenerator.bytevector(32)) {
case (privKey, bytes1, bytes2) =>
val sig1 = privKey.schnorrSign(bytes1)
val sig2 = privKey.schnorrSign(bytes2)
assert(sig1.bytes != sig2.bytes)
assert(sig1.rx != sig2.rx)
}
}
it must "generate R values correctly" in {
forAll(CryptoGenerators.privateKey,
NumberGenerator.bytevector(32),
NumberGenerator.bytevector(32)) {
case (privKey, auxRand, bytes) =>
val nonce = SchnorrNonce.kFromBipSchnorr(privKey, bytes, auxRand)
val sig1 = privKey.schnorrSign(bytes, auxRand)
val sig2 = privKey.schnorrSignWithNonce(bytes, nonce)
assert(sig1 == sig2)
}
}
it must "correctly compute signature points" in {
forAll(CryptoGenerators.privateKey, NumberGenerator.bytevector(32)) {
case (privKey, data) =>
val pubKey = privKey.publicKey
val sig = privKey.schnorrSign(data)
val sigPoint = pubKey.schnorrComputePoint(data, sig.rx)
assert(sigPoint == sig.sig.toPrivateKey.publicKey)
}
}
it must "correctly compute signature points for sigs with fixed nonces" in {
forAll(CryptoGenerators.privateKey,
NumberGenerator.bytevector(32),
CryptoGenerators.privateKey) {
case (privKey, data, nonce) =>
val pubKey = privKey.publicKey
val sig = privKey.schnorrSignWithNonce(data, nonce)
assert(sig.rx == nonce.schnorrNonce)
val sigPoint = pubKey.schnorrComputePoint(data, sig.rx)
assert(sigPoint == sig.sig.toPrivateKey.publicKey)
}
}
/** Schnorr signatures have the property that if two messages are signed with the same key
* and nonce, then they are leaked:
*
* sig1 = nonce + message1*privKey
* sig2 = nonce + message2*privKey
*
* => sig1 - sig2 = (message1 - message2)*privKey
* => privKey = (sig1 - sig2) * inverse(message1 - message2)
*/
it must "leak keys if two messages are signed" in {
forAll(CryptoGenerators.nonZeroPrivKey,
CryptoGenerators.nonZeroPrivKey,
NumberGenerator.bytevector(32),
NumberGenerator.bytevector(32)) {
case (privKey, nonce, message1, message2) =>
// This will only work if we sign two different messages
assert(message1 != message2)
// Sign both messages using the same privKey and nonce
val sig1 = privKey.schnorrSignWithNonce(message1, nonce)
val sig2 = privKey.schnorrSignWithNonce(message2, nonce)
// s1 = nonce + e1*privKey
val s1 = sig1.sig
// s2 = nonce + e2*privKey
val s2 = sig2.sig
// When signing a message you actually sign SHA256_challenge(Rx || pubKey || message)
val bytesToHash1 = sig1.rx.bytes ++ privKey.schnorrPublicKey.bytes ++ message1
val e1Bytes = CryptoUtil.sha256SchnorrChallenge(bytesToHash1).bytes
val bytesToHash2 = sig2.rx.bytes ++ privKey.schnorrPublicKey.bytes ++ message2
val e2Bytes = CryptoUtil.sha256SchnorrChallenge(bytesToHash2).bytes
val e1 = NumberUtil.uintToFieldElement(e1Bytes)
val e2 = NumberUtil.uintToFieldElement(e2Bytes)
val k = nonce.nonceKey.fieldElement
val x = privKey.schnorrKey.fieldElement
// Test that we have correctly computed the components
assert(k.add(e1.multiply(x)) == s1)
assert(k.add(e2.multiply(x)) == s2)
// Note that all of s1, s2, e1, and e2 are public information:
// s1 - s2 = nonce + e1*privKey - (nonce + e2*privKey) = privKey*(e1-e2)
// => privKey = (s1 - s2) * modInverse(e1 - e2)
val privNum = s1.subtract(s2).multInv(e1.subtract(e2))
// Assert that we've correctly recovered the private key form public info
assert(privNum == x)
}
}
}

View File

@ -0,0 +1,39 @@
package org.bitcoins.crypto
import org.bitcoins.testkit.core.gen.CryptoGenerators
import org.bitcoins.testkit.util.BitcoinSUnitTest
class SchnorrNonceTest extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
behavior of "SchnorrNonce"
it must "fail for incorrect lengths" in {
assertThrows[IllegalArgumentException](
SchnorrNonce(
"676f8c22de526e0c0904719847e63bda47b4eceb6986bdbaf8695db362811a"))
assertThrows[IllegalArgumentException](
SchnorrNonce(
"676f8c22de526e0c0904719847e63bda47b4eceb6986bdbaf8695db362811a010203"))
}
it must "fail for invalid x coordinate" in {
assertThrows[IllegalArgumentException](
SchnorrNonce(
"EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34"))
assertThrows[IllegalArgumentException](
SchnorrNonce(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"))
}
it must "have serialization symmetry" in {
forAll(CryptoGenerators.schnorrNonce) { pubKey =>
assert(SchnorrNonce(pubKey.bytes) == pubKey)
assert(SchnorrNonce(pubKey.xCoord) == pubKey)
}
}
}

View File

@ -0,0 +1,39 @@
package org.bitcoins.crypto
import org.bitcoins.testkit.core.gen.CryptoGenerators
import org.bitcoins.testkit.util.BitcoinSUnitTest
class SchnorrPublicKeyTest extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
behavior of "SchnorrPublicKey"
it must "fail for incorrect lengths" in {
assertThrows[IllegalArgumentException](
SchnorrPublicKey(
"676f8c22de526e0c0904719847e63bda47b4eceb6986bdbaf8695db362811a"))
assertThrows[IllegalArgumentException](
SchnorrPublicKey(
"676f8c22de526e0c0904719847e63bda47b4eceb6986bdbaf8695db362811a010203"))
}
it must "fail for invalid x coordinate" in {
assertThrows[IllegalArgumentException](
SchnorrPublicKey(
"EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34"))
assertThrows[IllegalArgumentException](
SchnorrPublicKey(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"))
}
it must "have serialization symmetry" in {
forAll(CryptoGenerators.schnorrPublicKey) { pubKey =>
assert(SchnorrPublicKey(pubKey.bytes) == pubKey)
assert(SchnorrPublicKey(pubKey.xCoord) == pubKey)
}
}
}

View File

@ -11,23 +11,20 @@ import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
import org.bouncycastle.math.ec.{ECCurve, ECPoint}
import scodec.bits.ByteVector
import scala.util.Try
import scala.util.{Failure, Success, Try}
object BouncyCastleUtil {
private val curve: ECCurve = CryptoParams.curve.getCurve
private val G: ECPoint = CryptoParams.curve.getG
private val N: BigInteger = CryptoParams.curve.getN
private def getBigInteger(bytes: ByteVector): BigInteger = {
new BigInteger(1, bytes.toArray)
}
def addNumbers(num1: ByteVector, num2: ByteVector): BigInteger = {
val bigInteger1 = getBigInteger(num1)
val bigInteger2 = getBigInteger(num2)
bigInteger1.add(bigInteger2).mod(N)
def pubKeyTweakMul(publicKey: ECPublicKey, tweak: ByteVector): ECPublicKey = {
val point = publicKey.toPoint.multiply(getBigInteger(tweak))
ECPublicKey.fromPoint(point, publicKey.isCompressed)
}
def decodePoint(bytes: ByteVector): ECPoint = {
@ -109,4 +106,79 @@ object BouncyCastleUtil {
}
resultTry.getOrElse(false)
}
def schnorrSign(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
auxRand: ByteVector): SchnorrDigitalSignature = {
val nonceKey =
SchnorrNonce.kFromBipSchnorr(privateKey, dataToSign, auxRand)
schnorrSignWithNonce(dataToSign, privateKey, nonceKey)
}
def schnorrSignWithNonce(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
nonceKey: ECPrivateKey): SchnorrDigitalSignature = {
val rx = nonceKey.schnorrNonce
val k = nonceKey.nonceKey.fieldElement
val x = privateKey.schnorrKey.fieldElement
val e = CryptoUtil
.sha256SchnorrChallenge(
rx.bytes ++ privateKey.schnorrPublicKey.bytes ++ dataToSign)
.bytes
val challenge = x.multiply(FieldElement(e))
val sig = k.add(challenge)
SchnorrDigitalSignature(rx, sig)
}
def schnorrVerify(
data: ByteVector,
schnorrPubKey: SchnorrPublicKey,
signature: SchnorrDigitalSignature): Boolean = {
val rx = signature.rx
val sT = Try(signature.sig.toPrivateKey)
sT match {
case Success(s) =>
val eBytes = CryptoUtil
.sha256SchnorrChallenge(rx.bytes ++ schnorrPubKey.bytes ++ data)
.bytes
val e = FieldElement(eBytes)
val negE = e.negate
val sigPoint = s.publicKey
val challengePoint = schnorrPubKey.publicKey.tweakMultiply(negE)
val computedR = challengePoint.add(sigPoint)
val yCoord = computedR.toPoint.getRawYCoord
yCoord != null && yCoord.sqrt() != null && computedR.schnorrNonce == rx
case Failure(_) => false
}
}
def schnorrComputeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
pubKey: SchnorrPublicKey,
compressed: Boolean): ECPublicKey = {
val eBytes = CryptoUtil
.sha256SchnorrChallenge(nonce.bytes ++ pubKey.bytes ++ data)
.bytes
val e = FieldElement(eBytes)
val compressedSigPoint =
nonce.publicKey.add(pubKey.publicKey.tweakMultiply(e))
if (compressed) {
compressedSigPoint
} else {
compressedSigPoint.decompressed
}
}
}

View File

@ -67,6 +67,7 @@ trait CryptoBytesUtil {
def flipEndianness(bytes: ByteVector): String = encodeHex(bytes.reverse)
private val Z: Char = '0'
/**
* Adds the amount padding bytes needed to fix the size of the hex string
* for instance, ints are required to be 4 bytes. If the number is just 1
@ -78,7 +79,7 @@ trait CryptoBytesUtil {
var counter = 0
while (counter < paddingNeeded) {
builder.append(Z)
counter+=1
counter += 1
}
builder.appendAll(hex)
builder.result()

View File

@ -37,6 +37,48 @@ trait CryptoUtil {
sha256(bits.toByteVector)
}
def taggedSha256(bytes: ByteVector, tag: String): Sha256Digest = {
val tagHash = sha256(ByteVector(tag.getBytes()))
val tagBytes = tagHash.bytes ++ tagHash.bytes
sha256(tagBytes ++ bytes)
}
// The tag "BIP340/challenge"
private val schnorrChallengeTagBytes = {
ByteVector
.fromValidHex(
"07e00dcd3055c1b36ee93effe4d7f266024cdef4116982ff5dfdc1a97e77062907e00dcd3055c1b36ee93effe4d7f266024cdef4116982ff5dfdc1a97e770629"
)
}
def sha256SchnorrChallenge(bytes: ByteVector): Sha256Digest = {
sha256(schnorrChallengeTagBytes ++ bytes)
}
// The tag "BIP340/nonce"
private val schnorrNonceTagBytes = {
ByteVector
.fromValidHex(
"a2ba14a6b39c1c505260bf3aceb07febde3ab34c35c9259d25bd6972f15e6564a2ba14a6b39c1c505260bf3aceb07febde3ab34c35c9259d25bd6972f15e6564"
)
}
def sha256SchnorrNonce(bytes: ByteVector): Sha256Digest = {
sha256(schnorrNonceTagBytes ++ bytes)
}
// The tag "BIP340/aux"
private val schnorrAuxTagBytes = {
ByteVector
.fromValidHex(
"4b07426ad8630dcdbadf8dee1e94f09ac2df4e7ee2629e5e6b27c8666c8cf31e4b07426ad8630dcdbadf8dee1e94f09ac2df4e7ee2629e5e6b27c8666c8cf31e"
)
}
def sha256SchnorrAuxRand(bytes: ByteVector): Sha256Digest = {
sha256(schnorrAuxTagBytes ++ bytes)
}
/** Performs SHA1(bytes). */
def sha1(bytes: ByteVector): Sha1Digest = {
val hash = MessageDigest.getInstance("SHA-1").digest(bytes.toArray).toList

View File

@ -10,6 +10,9 @@ sealed abstract class ECDigitalSignature {
require(s.signum == 1 || s.signum == 0, s"s must not be negative, got $s")
def hex: String = CryptoBytesUtil.encodeHex(bytes)
def ==(p: ECDigitalSignature): Boolean = this.bytes == p.bytes
def !=(p: ECDigitalSignature): Boolean = !(this == p)
def bytes: ByteVector
def isEmpty: Boolean = bytes.isEmpty

View File

@ -71,6 +71,104 @@ sealed abstract class ECPrivateKey
implicit ec: ExecutionContext): Future[ECDigitalSignature] =
Future(sign(hash))
def schnorrSign(dataToSign: ByteVector): SchnorrDigitalSignature = {
val auxRand = ECPrivateKey.freshPrivateKey.bytes
schnorrSign(dataToSign, auxRand)
}
// TODO: match on CryptoContext once secp version is added
def schnorrSign(
dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = {
schnorrSignWithBouncyCastle(dataToSign, auxRand)
}
/*
def schnorrSignWithSecp(
dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = {
val sigBytes =
NativeSecp256k1.schnorrSign(dataToSign.toArray,
bytes.toArray,
auxRand.toArray)
SchnorrDigitalSignature(ByteVector(sigBytes))
}
*/
def schnorrSignWithBouncyCastle(
dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = {
BouncyCastleUtil.schnorrSign(dataToSign, this, auxRand)
}
// TODO: match on CryptoContext once secp version is added
def schnorrSignWithNonce(
dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = {
schnorrSignWithNonceWithBouncyCastle(dataToSign, nonce)
}
/*
def schnorrSignWithNonceWithSecp(
dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = {
val sigBytes =
NativeSecp256k1.schnorrSignWithNonce(dataToSign.toArray,
bytes.toArray,
nonce.bytes.toArray)
SchnorrDigitalSignature(ByteVector(sigBytes))
}
*/
def schnorrSignWithNonceWithBouncyCastle(
dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = {
BouncyCastleUtil.schnorrSignWithNonce(dataToSign, this, nonce)
}
def nonceKey: ECPrivateKey = {
if (schnorrNonce.publicKey == publicKey) {
this
} else {
this.negate
}
}
def schnorrKey: ECPrivateKey = {
if (schnorrPublicKey.publicKey == publicKey) {
this
} else {
this.negate
}
}
def negate: ECPrivateKey = {
val negPrivKeyNum = CryptoParams.curve.getN
.subtract(new BigInteger(1, bytes.toArray))
ECPrivateKey(ByteVector(negPrivKeyNum.toByteArray))
}
def add(other: ECPrivateKey): ECPrivateKey = {
add(other, CryptoContext.default)
}
def add(other: ECPrivateKey, context: CryptoContext): ECPrivateKey = {
context match {
case CryptoContext.LibSecp256k1 => addWithSecp(other)
case CryptoContext.BouncyCastle => addWithBouncyCastle(other)
}
}
def addWithSecp(other: ECPrivateKey): ECPrivateKey = {
val sumBytes =
NativeSecp256k1.privKeyTweakAdd(bytes.toArray, other.bytes.toArray)
ECPrivateKey(ByteVector(sumBytes))
}
def addWithBouncyCastle(other: ECPrivateKey): ECPrivateKey = {
fieldElement.add(other.fieldElement).toPrivateKey
}
/** Signifies if the this private key corresponds to a compressed public key */
def isCompressed: Boolean
@ -99,6 +197,16 @@ sealed abstract class ECPrivateKey
BouncyCastleUtil.computePublicKey(this)
}
def schnorrPublicKey: SchnorrPublicKey = {
SchnorrPublicKey(publicKey.bytes)
}
def schnorrNonce: SchnorrNonce = {
SchnorrNonce(publicKey.bytes)
}
def fieldElement: FieldElement = FieldElement(bytes)
override def toStringSensitive: String = s"ECPrivateKey($hex,$isCompressed)"
}
@ -148,6 +256,10 @@ object ECPrivateKey extends Factory[ECPrivateKey] {
def fromHex(hex: String, isCompressed: Boolean): ECPrivateKey =
fromBytes(CryptoBytesUtil.decodeHex(hex), isCompressed)
def fromFieldElement(fieldElement: FieldElement): ECPrivateKey = {
fieldElement.toPrivateKey
}
/** Generates a fresh [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]] that has not been used before. */
def apply(): ECPrivateKey = ECPrivateKey(true)
@ -223,6 +335,23 @@ sealed abstract class ECPublicKey extends BaseECKey {
def verify(hex: String, signature: ECDigitalSignature): Boolean =
verify(CryptoBytesUtil.decodeHex(hex), signature)
def schnorrVerify(
data: ByteVector,
signature: SchnorrDigitalSignature): Boolean = {
schnorrPublicKey.verify(data, signature)
}
def schnorrComputePoint(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean = isCompressed): ECPublicKey = {
schnorrPublicKey.computeSigPoint(data, nonce, compressed)
}
def schnorrPublicKey: SchnorrPublicKey = SchnorrPublicKey(bytes)
def schnorrNonce: SchnorrNonce = SchnorrNonce(bytes)
override def toString: String = "ECPublicKey(" + hex + ")"
/** Checks if the [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] is compressed */
@ -275,6 +404,30 @@ sealed abstract class ECPublicKey extends BaseECKey {
ECPublicKey.fromPoint(sumPoint)
}
def tweakMultiply(tweak: FieldElement): ECPublicKey = {
tweakMultiply(tweak, CryptoContext.default)
}
def tweakMultiply(
tweak: FieldElement,
context: CryptoContext): ECPublicKey = {
context match {
case CryptoContext.LibSecp256k1 => tweakMultiplyWithSecp(tweak)
case CryptoContext.BouncyCastle => tweakMultiplyWithBouncyCastle(tweak)
}
}
def tweakMultiplyWithSecp(tweak: FieldElement): ECPublicKey = {
val mulBytes = NativeSecp256k1.pubKeyTweakMul(bytes.toArray,
tweak.bytes.toArray,
isCompressed)
ECPublicKey(ByteVector(mulBytes))
}
def tweakMultiplyWithBouncyCastle(tweak: FieldElement): ECPublicKey = {
BouncyCastleUtil.pubKeyTweakMul(this, tweak.bytes)
}
}
object ECPublicKey extends Factory[ECPublicKey] {

View File

@ -0,0 +1,132 @@
package org.bitcoins.crypto
import java.math.BigInteger
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.ByteVector
import scala.util.Try
/**
* Represents integers modulo the secp256k1 field size: pow(2,256) - 0x1000003D1.
*
* Supports arithmetic for these elements including +, -, *, and inverses.
* Supports 32 byte serialization as is needed for ECPrivateKeys.
*/
case class FieldElement(bytes: ByteVector) extends NetworkElement {
require(bytes.length == 32, s"Field elements must have 32 bytes, got $bytes")
private val privKeyT: Try[ECPrivateKey] = Try(ECPrivateKey(bytes))
require(
privKeyT.isSuccess || isZero,
s"$bytes is not a valid field element: ${privKeyT.failed.get.getMessage}")
def isZero: Boolean = bytes.toArray.forall(_ == 0.toByte)
def toPrivateKey: ECPrivateKey =
if (!isZero) {
privKeyT.get
} else {
throw new RuntimeException("Cannot turn zero into a private key")
}
def toBigInteger: BigInteger = FieldElement.getBigInteger(bytes)
def add(other: FieldElement): FieldElement = {
FieldElement.add(this, other)
}
def subtract(other: FieldElement): FieldElement = {
add(other.negate)
}
def multiply(other: FieldElement): FieldElement = {
FieldElement.multiply(this, other)
}
def multInv(other: FieldElement): FieldElement = {
multiply(other.inverse)
}
def negate: FieldElement = {
FieldElement.negate(this)
}
def getPoint: ECPoint = FieldElement.computePoint(this)
def getPublicKey: ECPublicKey = toPrivateKey.publicKey
def inverse: FieldElement = FieldElement.computeInverse(this)
}
object FieldElement extends Factory[FieldElement] {
override def fromBytes(bytes: ByteVector): FieldElement = {
if (bytes.length < 32) {
new FieldElement(bytes.padLeft(32))
} else if (bytes.length == 32) {
new FieldElement(bytes)
} else if (bytes.length == 33 && bytes.head == 0.toByte) {
new FieldElement(bytes.tail)
} else {
throw new IllegalArgumentException(
s"Field element cannot have more than 32 bytes, got $bytes")
}
}
def apply(num: BigInt): FieldElement = {
FieldElement(num.underlying())
}
def apply(num: BigInteger): FieldElement = {
FieldElement.fromByteArray(num.mod(N).toByteArray)
}
def fromByteArray(bytes: Array[Byte]): FieldElement = {
FieldElement(ByteVector(bytes))
}
val zero: FieldElement = FieldElement(ByteVector.empty)
val one: FieldElement = FieldElement(ByteVector.fromByte(1))
val nMinusOne: FieldElement = FieldElement(
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
private val G: ECPoint = CryptoParams.curve.getG
private val N: BigInteger = CryptoParams.curve.getN
private def getBigInteger(bytes: ByteVector): BigInteger = {
new BigInteger(1, bytes.toArray)
}
def add(fe1: FieldElement, fe2: FieldElement): FieldElement = {
val num1 = fe1.toBigInteger
val num2 = fe2.toBigInteger
val sum = num1.add(num2).mod(N)
FieldElement(sum)
}
def multiply(fe1: FieldElement, fe2: FieldElement): FieldElement = {
val num1 = fe1.toBigInteger
val num2 = fe2.toBigInteger
val sum = num1.multiply(num2).mod(N)
FieldElement(sum)
}
def negate(fe: FieldElement): FieldElement = {
val neg = N.subtract(fe.toBigInteger)
FieldElement(neg)
}
def computePoint(fe: FieldElement): ECPoint = G.multiply(fe.toBigInteger)
/** Computes the inverse (mod M) of the input using the Euclidean Algorithm (log time)
* Cribbed from [[https://www.geeksforgeeks.org/multiplicative-inverse-under-modulo-m/]]
*/
def computeInverse(fe: FieldElement): FieldElement = {
val inv = fe.toBigInteger.modInverse(N)
FieldElement(inv)
}
}

View File

@ -0,0 +1,17 @@
package org.bitcoins.crypto
import scodec.bits.ByteVector
case class SchnorrDigitalSignature(rx: SchnorrNonce, sig: FieldElement)
extends NetworkElement {
override def bytes: ByteVector = rx.bytes ++ sig.bytes
}
object SchnorrDigitalSignature extends Factory[SchnorrDigitalSignature] {
override def fromBytes(bytes: ByteVector): SchnorrDigitalSignature = {
require(bytes.length == 64,
s"SchnorrDigitalSignature must be exactly 64 bytes, got $bytes")
SchnorrDigitalSignature(SchnorrNonce(bytes.take(32)),
FieldElement(bytes.drop(32)))
}
}

View File

@ -0,0 +1,89 @@
package org.bitcoins.crypto
import scodec.bits.ByteVector
import scala.annotation.tailrec
import scala.util.Try
case class SchnorrNonce(bytes: ByteVector) extends NetworkElement {
require(bytes.length == 32, s"Schnorr nonce must be 32 bytes, get $bytes")
private val evenKey: ECPublicKey = ECPublicKey(s"02$hex")
private val oddKey: ECPublicKey = ECPublicKey(s"03$hex")
private val yCoordEven: Boolean = {
evenKey.toPoint.getRawYCoord.sqrt() != null
}
/** Computes the public key associated with a SchnorrNonce as specified in bip-schnorr.
* They y-coordinate is chosen to be a quadratic residue.
*/
val publicKey: ECPublicKey = {
if (yCoordEven) {
evenKey
} else {
oddKey
}
}
require(Try(publicKey).isSuccess,
s"Schnorr nonce must be a valid x coordinate, got $bytes")
require(
publicKey.toPoint.getRawYCoord.sqrt != null,
"Schnorr nonce must be an x coordinate for which a quadratic residue y coordinate exists")
def xCoord: FieldElement = {
FieldElement(bytes)
}
}
object SchnorrNonce extends Factory[SchnorrNonce] {
@tailrec
def fromBytes(bytes: ByteVector): SchnorrNonce = {
if (bytes.length == 32) {
new SchnorrNonce(bytes)
} else if (bytes.length < 32) {
// means we need to pad the private key with 0 bytes so we have 32 bytes
SchnorrNonce.fromBytes(bytes.padLeft(32))
} else if (bytes.length == 33) {
// this is for the case when java serialies a BigInteger to 33 bytes to hold the signed num representation
SchnorrNonce.fromBytes(bytes.tail)
} else {
throw new IllegalArgumentException(
"Schnorr nonce cannot be greater than 33 bytes in size, got: " +
CryptoBytesUtil.encodeHex(bytes) + " which is of size: " + bytes.size)
}
}
def kFromBipSchnorr(
privKey: ECPrivateKey,
message: ByteVector,
auxRand: ByteVector): ECPrivateKey = {
val privKeyForUse = privKey.schnorrKey
val randHash = CryptoUtil.sha256SchnorrAuxRand(auxRand).bytes
val maskedKey = randHash.xor(privKeyForUse.bytes)
val nonceHash = CryptoUtil.sha256SchnorrNonce(
maskedKey ++ privKey.schnorrPublicKey.bytes ++ message)
ECPrivateKey(nonceHash.bytes).nonceKey
}
/** Computes the bip-schnorr nonce for a given message and private key.
* This is intended to ensure that no two messages are signed with the
* same nonce.
*/
def fromBipSchnorr(
privKey: ECPrivateKey,
message: ByteVector,
auxRand: ByteVector): SchnorrNonce = {
val k = kFromBipSchnorr(privKey, message, auxRand)
k.publicKey.schnorrNonce
}
def apply(xCoor: FieldElement): SchnorrNonce = {
SchnorrNonce(xCoor.bytes)
}
}

View File

@ -0,0 +1,114 @@
package org.bitcoins.crypto
import org.bitcoin.NativeSecp256k1
import scodec.bits.ByteVector
import scala.annotation.tailrec
import scala.util.Try
case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement {
require(bytes.length == 32,
s"Schnorr public keys must be 32 bytes, got $bytes")
require(Try(publicKey).isSuccess,
s"Schnorr public key must be a valid x coordinate, got $bytes")
// TODO: match on CryptoContext once secp version is added
def verify(data: ByteVector, signature: SchnorrDigitalSignature): Boolean = {
verifyWithBouncyCastle(data, signature)
}
/*
def verifyWithSecp(
data: ByteVector,
signature: SchnorrDigitalSignature): Boolean = {
NativeSecp256k1.schnorrVerify(signature.bytes.toArray,
data.toArray,
bytes.toArray)
}
*/
def verifyWithBouncyCastle(
data: ByteVector,
signature: SchnorrDigitalSignature): Boolean = {
BouncyCastleUtil.schnorrVerify(data, this, signature)
}
def computeSigPoint(data: ByteVector, nonce: SchnorrNonce): ECPublicKey = {
computeSigPoint(data, nonce, compressed = true)
}
// TODO: match on CryptoContext once secp version is added
def computeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean): ECPublicKey = {
computeSigPointWithBouncyCastle(data, nonce, compressed)
}
/*
def computeSigPointWithSecp(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean = true): ECPublicKey = {
val sigPointBytes = NativeSecp256k1.schnorrComputeSigPoint(
data.toArray,
nonce.bytes.toArray,
bytes.toArray,
compressed)
ECPublicKey(ByteVector(sigPointBytes))
}
*/
def computeSigPointWithBouncyCastle(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean = true): ECPublicKey = {
BouncyCastleUtil.schnorrComputeSigPoint(data, nonce, this, compressed)
}
def publicKey: ECPublicKey = {
val pubKeyBytes = ByteVector.fromByte(2) ++ bytes
val validPubKey = CryptoContext.default match {
case CryptoContext.LibSecp256k1 =>
NativeSecp256k1.isValidPubKey(pubKeyBytes.toArray)
case CryptoContext.BouncyCastle =>
BouncyCastleUtil.validatePublicKey(pubKeyBytes)
}
require(
validPubKey,
s"Cannot construct schnorr public key from invalid x coordinate: $bytes")
ECPublicKey(pubKeyBytes)
}
def xCoord: FieldElement = FieldElement(bytes)
}
object SchnorrPublicKey extends Factory[SchnorrPublicKey] {
@tailrec
def fromBytes(bytes: ByteVector): SchnorrPublicKey = {
require(bytes.length <= 33,
s"XOnlyPublicKey must be less than 33 bytes, got $bytes")
if (bytes.length == 32)
new SchnorrPublicKey(bytes)
else if (bytes.length < 32) {
// means we need to pad the private key with 0 bytes so we have 32 bytes
SchnorrPublicKey.fromBytes(bytes.padLeft(32))
} else if (bytes.length == 33) {
// this is for the case when java serialies a BigInteger to 33 bytes to hold the signed num representation
SchnorrPublicKey.fromBytes(bytes.tail)
} else {
throw new IllegalArgumentException(
"XOnlyPublicKey cannot be greater than 33 bytes in size, got: " +
CryptoBytesUtil.encodeHex(bytes) + " which is of size: " + bytes.size)
}
}
def apply(xCoor: FieldElement): SchnorrPublicKey = {
SchnorrPublicKey(xCoor.bytes)
}
}

View File

@ -15,6 +15,10 @@ import org.bitcoins.crypto.{
ECDigitalSignature,
ECPrivateKey,
ECPublicKey,
FieldElement,
SchnorrDigitalSignature,
SchnorrNonce,
SchnorrPublicKey,
Sha256Digest,
Sha256Hash160Digest
}
@ -131,6 +135,36 @@ sealed abstract class CryptoGenerators {
def privateKey: Gen[ECPrivateKey] = Gen.delay(ECPrivateKey())
def fieldElement: Gen[FieldElement] = privateKey.map(_.fieldElement)
def smallFieldElement: Gen[FieldElement] =
NumberGenerator
.bytevector(30)
.map(bytes => FieldElement(ByteVector.fill(2)(0) ++ bytes))
def reallySmallFieldElement: Gen[FieldElement] =
NumberGenerator
.bytevector(15)
.map(bytes => FieldElement(ByteVector.fill(17)(0) ++ bytes))
def largeFieldElement: Gen[FieldElement] =
NumberGenerator
.bytevector(30)
.map(bytes => FieldElement(ByteVector.fill(2)(Byte.MinValue) ++ bytes))
def nonZeroFieldElement: Gen[FieldElement] =
nonZeroPrivKey.map(_.fieldElement)
/** Generates a random non-zero private key */
def nonZeroPrivKey: Gen[ECPrivateKey] =
privateKey.filter(_.bytes.toArray.exists(_ != 0.toByte))
def schnorrNonce: Gen[SchnorrNonce] =
nonZeroPrivKey.map(_.publicKey.bytes.tail).map(SchnorrNonce.fromBytes)
def schnorrPublicKey: Gen[SchnorrPublicKey] =
publicKey.map(_.schnorrPublicKey)
/**
* Generate a sequence of private keys
* @param num maximum number of keys to generate
@ -187,6 +221,13 @@ sealed abstract class CryptoGenerators {
hash <- CryptoGenerators.doubleSha256Digest
} yield privKey.sign(hash)
def schnorrDigitalSignature: Gen[SchnorrDigitalSignature] = {
for {
privKey <- privateKey
hash <- CryptoGenerators.doubleSha256Digest
} yield privKey.schnorrSign(hash.bytes)
}
def sha256Digest: Gen[Sha256Digest] =
for {
bytes <- NumberGenerator.bytevector