Added secp256k1_schnorrsig_sign to JNI

Added secp256k1_schnorrsig_verify to the JNI

Added new schnorrSignWithNonce

Fixed schnorr signing and added a test making sure schnorrSign and schnorrSignWithNonce agree

Fixed binding, doesn't work yet

Added tests, they fail

Added BIP 340 test vectors

Implemented sigpoint computation using group operators. I believe the nonce is being incorrectly parsed half the time as it should not be treated as an xonly_pubkey

Added tests

Added Bouncy Castle implementation and further integration

Implemented bouncy castle fallback for all secp schnorr functions

Implemented FieldElement to abstract modular BigInt computations in the Secp256k1 field

Implemented sig in SchnorrDigitalSignature as a FieldElement

Vamped up testing

Added windows binaries

Added osx binaries

added windows binaries

Responded to review

Cleaned up secp commits

Responded to review

Replaced custom modInverse implementation in FieldElement with java.math.BigInteger.modInverse

Cleaned up a couple things for coverage purposes

Set bitcoin-s-schnorr to secp branch
This commit is contained in:
nkohen 2020-04-08 12:43:11 -05:00
parent 28aea46e33
commit 03729103a1
37 changed files with 1461 additions and 26 deletions

View file

@ -96,11 +96,11 @@ stages:
- name: test - name: test
if: if:
commit_message !~ /^Docs:/ AND NOT commit_message !~ /^Docs:/ AND NOT
((branch = master AND type = push) OR (tag IS present)) ((branch = schnorr-dlc AND type = push) OR (tag IS present))
# don't run tests on merge builds, just publish library # don't run tests on merge builds, just publish library
# and website # and website
- name: release - name: release
if: ((branch = master AND type = push) OR (tag IS present)) AND NOT fork if: ((branch = schnorr-dlc AND type = push) OR (tag IS present)) AND NOT fork
script: script:
# Modify PATH to include binaries we are about to download # Modify PATH to include binaries we are about to download

View file

@ -0,0 +1,16 @@
index,secret key,public key,aux_rand,message,signature,verification result,comment
0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,067E337AD551B2276EC705E43F0920926A9CE08AC68159F9D258C9BBA412781C9F059FCDF4824F13B3D7C1305316F956704BB3FEA2C26142E18ACD90A90C947E,TRUE,
1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0E12B8C520948A776753A96F21ABD7FDC2D7D0C0DDC90851BE17B04E75EF86A47EF0DA46C4DC4D0D1BCB8668C2CE16C54C7C23A6716EDE303AF86774917CF928,TRUE,
2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,FC012F9FB8FE00A358F51EF93DCE0DC0C895F6E9A87C6C4905BC820B0C3677616B8737D14E703AF8E16E22E5B8F26227D41E5128F82D86F747244CC289C74D1D,TRUE,
3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FC132D4E426DFF535AEC0FA7083AC5118BC1D5FFFD848ABD8290C23F271CA0DD11AEDCEA3F55DA9BD677FE29C9DDA0CF878BCE43FDE0E313D69D1AF7A5AE8369,TRUE,test fails if msg is reduced modulo p or n
4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C630EC50E5363E227ACAC6F542CE1C0B186657E0E0D1A6FFE283A33438DE4738419,TRUE,
5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,7036D6BFE1837AE919631039A2CF652A295DFAC9A8BBB0806014B2F48DD7C807941607B563ABBA414287F374A332BA3636DE009EE1EF551A17796B72B68B8A24,FALSE,public key not on the curve
6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F995A579DA959FA739FCE39E8BD16FECB5CDCF97060B2C73CDE60E87ABCA1AA5D9,FALSE,has_square_y(R) is false
7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,F8704654F4687B7365ED32E796DE92761390A3BCC495179BFE073817B7ED32824E76B987F7C1F9A751EF5C343F7645D3CFFC7D570B9A7192EBF1898E1344E3BF,FALSE,negated message
8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,7036D6BFE1837AE919631039A2CF652A295DFAC9A8BBB0806014B2F48DD7C8076BE9F84A9C5445BEBD780C8B5CCD45C883D0DC47CD594B21A858F31A19AAB71D,FALSE,negated s value
9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000009915EE59F07F9DBBAEDC31BFCC9B34AD49DE669CD24773BCED77DDA36D073EC8,FALSE,sG - eP is infinite. Test fails in single verification if has_square_y(inf) is defined as true and x(inf) as 0
10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000001C7EC918B2B9CF34071BB54BED7EB4BB6BAB148E9A7E36E6B228F95DFA08B43EC,FALSE,sG - eP is infinite. Test fails in single verification if has_square_y(inf) is defined as true and x(inf) as 1
11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D941607B563ABBA414287F374A332BA3636DE009EE1EF551A17796B72B68B8A24,FALSE,sig[0:32] is not an X coordinate on the curve
12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F941607B563ABBA414287F374A332BA3636DE009EE1EF551A17796B72B68B8A24,FALSE,sig[0:32] is equal to field size
13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,7036D6BFE1837AE919631039A2CF652A295DFAC9A8BBB0806014B2F48DD7C807FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order
14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,7036D6BFE1837AE919631039A2CF652A295DFAC9A8BBB0806014B2F48DD7C807941607B563ABBA414287F374A332BA3636DE009EE1EF551A17796B72B68B8A24,FALSE,public key is not a valid X coordinate because it exceeds the field size
1 index secret key public key aux_rand message signature verification result comment
2 0 0000000000000000000000000000000000000000000000000000000000000003 F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 067E337AD551B2276EC705E43F0920926A9CE08AC68159F9D258C9BBA412781C9F059FCDF4824F13B3D7C1305316F956704BB3FEA2C26142E18ACD90A90C947E TRUE
3 1 B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 0000000000000000000000000000000000000000000000000000000000000001 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 0E12B8C520948A776753A96F21ABD7FDC2D7D0C0DDC90851BE17B04E75EF86A47EF0DA46C4DC4D0D1BCB8668C2CE16C54C7C23A6716EDE303AF86774917CF928 TRUE
4 2 C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9 DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8 C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906 7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C FC012F9FB8FE00A358F51EF93DCE0DC0C895F6E9A87C6C4905BC820B0C3677616B8737D14E703AF8E16E22E5B8F26227D41E5128F82D86F747244CC289C74D1D TRUE
5 3 0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710 25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FC132D4E426DFF535AEC0FA7083AC5118BC1D5FFFD848ABD8290C23F271CA0DD11AEDCEA3F55DA9BD677FE29C9DDA0CF878BCE43FDE0E313D69D1AF7A5AE8369 TRUE test fails if msg is reduced modulo p or n
6 4 D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9 4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703 00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C630EC50E5363E227ACAC6F542CE1C0B186657E0E0D1A6FFE283A33438DE4738419 TRUE
7 5 EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 7036D6BFE1837AE919631039A2CF652A295DFAC9A8BBB0806014B2F48DD7C807941607B563ABBA414287F374A332BA3636DE009EE1EF551A17796B72B68B8A24 FALSE public key not on the curve
8 6 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F995A579DA959FA739FCE39E8BD16FECB5CDCF97060B2C73CDE60E87ABCA1AA5D9 FALSE has_square_y(R) is false
9 7 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 F8704654F4687B7365ED32E796DE92761390A3BCC495179BFE073817B7ED32824E76B987F7C1F9A751EF5C343F7645D3CFFC7D570B9A7192EBF1898E1344E3BF FALSE negated message
10 8 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 7036D6BFE1837AE919631039A2CF652A295DFAC9A8BBB0806014B2F48DD7C8076BE9F84A9C5445BEBD780C8B5CCD45C883D0DC47CD594B21A858F31A19AAB71D FALSE negated s value
11 9 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 00000000000000000000000000000000000000000000000000000000000000009915EE59F07F9DBBAEDC31BFCC9B34AD49DE669CD24773BCED77DDA36D073EC8 FALSE sG - eP is infinite. Test fails in single verification if has_square_y(inf) is defined as true and x(inf) as 0
12 10 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 0000000000000000000000000000000000000000000000000000000000000001C7EC918B2B9CF34071BB54BED7EB4BB6BAB148E9A7E36E6B228F95DFA08B43EC FALSE sG - eP is infinite. Test fails in single verification if has_square_y(inf) is defined as true and x(inf) as 1
13 11 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D941607B563ABBA414287F374A332BA3636DE009EE1EF551A17796B72B68B8A24 FALSE sig[0:32] is not an X coordinate on the curve
14 12 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F941607B563ABBA414287F374A332BA3636DE009EE1EF551A17796B72B68B8A24 FALSE sig[0:32] is equal to field size
15 13 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 7036D6BFE1837AE919631039A2CF652A295DFAC9A8BBB0806014B2F48DD7C807FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 FALSE sig[32:64] is equal to curve order
16 14 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 7036D6BFE1837AE919631039A2CF652A295DFAC9A8BBB0806014B2F48DD7C807941607B563ABBA414287F374A332BA3636DE009EE1EF551A17796B72B68B8A24 FALSE public key is not a valid X coordinate because it exceeds the field size

View file

@ -0,0 +1,119 @@
package org.bitcoins.core.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

@ -9,6 +9,9 @@ import scodec.bits.ByteVector
class BouncyCastleSecp256k1Test extends BitcoinSUnitTest { class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
behavior of "CryptoLibraries" behavior of "CryptoLibraries"
override def withFixture(test: NoArgTest): Outcome = { override def withFixture(test: NoArgTest): Outcome = {
@ -23,16 +26,7 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
it must "add private keys the same" in { it must "add private keys the same" in {
forAll(CryptoGenerators.privateKey, CryptoGenerators.privateKey) { forAll(CryptoGenerators.privateKey, CryptoGenerators.privateKey) {
case (priv1, priv2) => case (priv1, priv2) =>
val sumWithBouncyCastle = assert(priv1.addWithBouncyCastle(priv2) == priv1.addWithSecp(priv2))
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)
} }
} }
@ -50,6 +44,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 { it must "validate keys the same" in {
val keyOrGarbageGen = Gen.oneOf(CryptoGenerators.publicKey.map(_.bytes), val keyOrGarbageGen = Gen.oneOf(CryptoGenerators.publicKey.map(_.bytes),
NumberGenerator.bytevector(33)) NumberGenerator.bytevector(33))
@ -100,4 +103,63 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
.verify(bytes, badSig, useSecp = true)) .verify(bytes, badSig, useSecp = true))
} }
} }
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, useSecp = false) == privKey
.schnorrSign(bytes, auxRand, useSecp = true))
}
}
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, useSecp = false)
val sigSecP = privKey
.schnorrSignWithNonce(bytes, nonceKey, useSecp = true)
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, useSecp = false) == pubKey
.verify(bytes, sig, useSecp = true))
assert(
pubKey.verify(bytes, badSig, useSecp = false) == pubKey
.verify(bytes, badSig, useSecp = true))
}
}
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,
useSecp = false)
val secpSigPoint = pubKey.computeSigPoint(bytes,
nonce,
compressed = true,
useSecp = true)
assert(bouncyCastleSigPoint == secpSigPoint)
}
}
} }

View file

@ -84,6 +84,7 @@ class ECPrivateKeyTest extends BitcoinSUnitTest {
it must "have serialization symmetry" in { it must "have serialization symmetry" in {
forAll(CryptoGenerators.privateKey) { privKey => forAll(CryptoGenerators.privateKey) { privKey =>
assert(ECPrivateKey(privKey.hex) == privKey) assert(ECPrivateKey(privKey.hex) == privKey)
assert(ECPrivateKey.fromFieldElement(privKey.fieldElement) == privKey)
} }
} }
@ -108,4 +109,16 @@ class ECPrivateKeyTest extends BitcoinSUnitTest {
ECPrivateKey().toString must be("Masked(ECPrivateKeyImpl)") 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.core.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.core.crypto
import org.bitcoins.core.util.{CryptoUtil, 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.core.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.core.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

@ -1,6 +1,6 @@
package org.bitcoins.core.util package org.bitcoins.core.util
import org.bitcoins.testkit.core.gen.CryptoGenerators import org.bitcoins.testkit.core.gen.{CryptoGenerators, NumberGenerator}
import org.bitcoins.testkit.util.BitcoinSUnitTest import org.bitcoins.testkit.util.BitcoinSUnitTest
import scodec.bits._ import scodec.bits._
@ -92,4 +92,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

@ -2,7 +2,7 @@ package org.bitcoins.core.crypto
import java.math.BigInteger import java.math.BigInteger
import org.bitcoins.core.util.BitcoinSUtil import org.bitcoins.core.util.{BitcoinSUtil, CryptoUtil}
import org.bouncycastle.crypto.digests.SHA256Digest import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.params.{ import org.bouncycastle.crypto.params.{
ECPrivateKeyParameters, ECPrivateKeyParameters,
@ -12,23 +12,20 @@ import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
import org.bouncycastle.math.ec.{ECCurve, ECPoint} import org.bouncycastle.math.ec.{ECCurve, ECPoint}
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.util.Try import scala.util.{Failure, Success, Try}
object BouncyCastleUtil { object BouncyCastleUtil {
private val curve: ECCurve = CryptoParams.curve.getCurve private val curve: ECCurve = CryptoParams.curve.getCurve
private val G: ECPoint = CryptoParams.curve.getG private val G: ECPoint = CryptoParams.curve.getG
private val N: BigInteger = CryptoParams.curve.getN
private def getBigInteger(bytes: ByteVector): BigInteger = { private def getBigInteger(bytes: ByteVector): BigInteger = {
new BigInteger(1, bytes.toArray) new BigInteger(1, bytes.toArray)
} }
def addNumbers(num1: ByteVector, num2: ByteVector): BigInteger = { def pubKeyTweakMul(publicKey: ECPublicKey, tweak: ByteVector): ECPublicKey = {
val bigInteger1 = getBigInteger(num1) val point = publicKey.toPoint.multiply(getBigInteger(tweak))
val bigInteger2 = getBigInteger(num2) ECPublicKey.fromPoint(point, publicKey.isCompressed)
bigInteger1.add(bigInteger2).mod(N)
} }
def decodePoint(bytes: ByteVector): ECPoint = { def decodePoint(bytes: ByteVector): ECPoint = {
@ -111,4 +108,79 @@ object BouncyCastleUtil {
} }
resultTry.getOrElse(false) 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

@ -74,6 +74,127 @@ sealed abstract class ECPrivateKey
implicit ec: ExecutionContext): Future[ECDigitalSignature] = implicit ec: ExecutionContext): Future[ECDigitalSignature] =
Future(sign(hash)) Future(sign(hash))
def schnorrSign(dataToSign: ByteVector): SchnorrDigitalSignature = {
schnorrSign(dataToSign, Secp256k1Context.isEnabled)
}
def schnorrSign(
dataToSign: ByteVector,
useSecp: Boolean): SchnorrDigitalSignature = {
val auxRand = ECPrivateKey.freshPrivateKey.bytes
schnorrSign(dataToSign, auxRand, useSecp)
}
def schnorrSign(
dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = {
schnorrSign(dataToSign, auxRand, Secp256k1Context.isEnabled)
}
def schnorrSign(
dataToSign: ByteVector,
auxRand: ByteVector,
useSecp: Boolean): SchnorrDigitalSignature = {
if (useSecp) {
schnorrSignWithSecp(dataToSign, auxRand)
} else {
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)
}
def schnorrSignWithNonce(
dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = {
schnorrSignWithNonce(dataToSign, nonce, Secp256k1Context.isEnabled)
}
def schnorrSignWithNonce(
dataToSign: ByteVector,
nonce: ECPrivateKey,
useSecp: Boolean): SchnorrDigitalSignature = {
if (useSecp) {
schnorrSignWithNonceWithSecp(dataToSign, nonce)
} else {
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, Secp256k1Context.isEnabled)
}
def add(other: ECPrivateKey, useSecp: Boolean): ECPrivateKey = {
if (useSecp) {
addWithSecp(other)
} else {
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 */ /** Signifies if the this private key corresponds to a compressed public key */
def isCompressed: Boolean def isCompressed: Boolean
@ -103,6 +224,14 @@ sealed abstract class ECPrivateKey
BouncyCastleUtil.computePublicKey(this) BouncyCastleUtil.computePublicKey(this)
} }
def schnorrPublicKey: SchnorrPublicKey = {
SchnorrPublicKey(publicKey.bytes)
}
def schnorrNonce: SchnorrNonce = {
SchnorrNonce(publicKey.bytes)
}
/** /**
* Converts a [[org.bitcoins.core.crypto.ECPrivateKey ECPrivateKey]] to * Converts a [[org.bitcoins.core.crypto.ECPrivateKey ECPrivateKey]] to
* [[https://en.bitcoin.it/wiki/Wallet_import_format WIF]] * [[https://en.bitcoin.it/wiki/Wallet_import_format WIF]]
@ -119,6 +248,8 @@ sealed abstract class ECPrivateKey
Base58.encode(encodedPrivKey) Base58.encode(encodedPrivKey)
} }
def fieldElement: FieldElement = FieldElement(bytes)
override def toStringSensitive: String = s"ECPrivateKey($hex,$isCompressed)" override def toStringSensitive: String = s"ECPrivateKey($hex,$isCompressed)"
} }
@ -167,6 +298,10 @@ object ECPrivateKey extends Factory[ECPrivateKey] {
def fromHex(hex: String, isCompressed: Boolean): ECPrivateKey = def fromHex(hex: String, isCompressed: Boolean): ECPrivateKey =
fromBytes(BitcoinSUtil.decodeHex(hex), isCompressed) fromBytes(BitcoinSUtil.decodeHex(hex), isCompressed)
def fromFieldElement(fieldElement: FieldElement): ECPrivateKey = {
fieldElement.toPrivateKey
}
/** Generates a fresh [[org.bitcoins.core.crypto.ECPrivateKey ECPrivateKey]] that has not been used before. */ /** Generates a fresh [[org.bitcoins.core.crypto.ECPrivateKey ECPrivateKey]] that has not been used before. */
def apply(): ECPrivateKey = ECPrivateKey(true) def apply(): ECPrivateKey = ECPrivateKey(true)
@ -327,6 +462,23 @@ sealed abstract class ECPublicKey extends BaseECKey {
def verify(hex: String, signature: ECDigitalSignature): Boolean = def verify(hex: String, signature: ECDigitalSignature): Boolean =
verify(BitcoinSUtil.decodeHex(hex), signature) verify(BitcoinSUtil.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 = "ECPublicKey(" + hex + ")" override def toString = "ECPublicKey(" + hex + ")"
/** Checks if the [[org.bitcoins.core.crypto.ECPublicKey ECPublicKey]] is compressed */ /** Checks if the [[org.bitcoins.core.crypto.ECPublicKey ECPublicKey]] is compressed */
@ -380,6 +532,29 @@ sealed abstract class ECPublicKey extends BaseECKey {
ECPublicKey.fromPoint(sumPoint) ECPublicKey.fromPoint(sumPoint)
} }
def tweakMultiply(tweak: FieldElement): ECPublicKey = {
tweakMultiply(tweak, Secp256k1Context.isEnabled)
}
def tweakMultiply(tweak: FieldElement, useSecp: Boolean): ECPublicKey = {
if (useSecp) {
tweakMultiplyWithSecp(tweak)
} else {
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] { object ECPublicKey extends Factory[ECPublicKey] {

View file

@ -201,12 +201,14 @@ sealed abstract class ExtPrivateKey
//should be ECGroup addition //should be ECGroup addition
//parse256(IL) + kpar (mod n) //parse256(IL) + kpar (mod n)
val tweak = if (Secp256k1Context.isEnabled) { val tweak = if (Secp256k1Context.isEnabled) {
val tweakByteArr =
NativeSecp256k1.privKeyTweakAdd(il.toArray, key.bytes.toArray) NativeSecp256k1.privKeyTweakAdd(il.toArray, key.bytes.toArray)
ByteVector(tweakByteArr)
} else { } else {
val sum = BouncyCastleUtil.addNumbers(key.bytes, il) val sum = key.fieldElement.add(FieldElement(il))
sum.toByteArray sum.bytes
} }
val childKey = ECPrivateKey(ByteVector(tweak)) val childKey = ECPrivateKey(tweak)
val fp = CryptoUtil.sha256Hash160(key.publicKey.bytes).bytes.take(4) val fp = CryptoUtil.sha256Hash160(key.publicKey.bytes).bytes.take(4)
ExtPrivateKey(version, depth + UInt8.one, fp, idx, ChainCode(ir), childKey) ExtPrivateKey(version, depth + UInt8.one, fp, idx, ChainCode(ir), childKey)
} }

View file

@ -0,0 +1,134 @@
package org.bitcoins.core.crypto
import java.math.BigInteger
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.Factory
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,19 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.Factory
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,91 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.{BitcoinSUtil, CryptoUtil, Factory}
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: " +
BitcoinSUtil.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,132 @@
package org.bitcoins.core.crypto
import org.bitcoin.{NativeSecp256k1, Secp256k1Context}
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.{BitcoinSUtil, Factory}
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")
def verify(data: ByteVector, signature: SchnorrDigitalSignature): Boolean = {
verify(data, signature, Secp256k1Context.isEnabled)
}
def verify(
data: ByteVector,
signature: SchnorrDigitalSignature,
useSecp: Boolean): Boolean = {
if (useSecp) {
verifyWithSecp(data, signature)
} else {
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, Secp256k1Context.isEnabled)
}
def computeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean): ECPublicKey = {
computeSigPoint(data, nonce, compressed, Secp256k1Context.isEnabled)
}
def computeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean,
useSecp: Boolean): ECPublicKey = {
if (useSecp) {
computeSigPointWithSecp(data, nonce, compressed)
} else {
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 = if (Secp256k1Context.isEnabled) {
NativeSecp256k1.isValidPubKey(pubKeyBytes.toArray)
} else {
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: " +
BitcoinSUtil.encodeHex(bytes) + " which is of size: " + bytes.size)
}
}
def apply(xCoor: FieldElement): SchnorrPublicKey = {
SchnorrPublicKey(xCoor.bytes)
}
}

View file

@ -38,6 +38,51 @@ trait CryptoUtil extends BitcoinSLogger {
sha256(bits.toByteVector) 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
.fromHex(
"07e00dcd3055c1b36ee93effe4d7f266024cdef4116982ff5dfdc1a97e77062907e00dcd3055c1b36ee93effe4d7f266024cdef4116982ff5dfdc1a97e770629"
)
.get
}
def sha256SchnorrChallenge(bytes: ByteVector): Sha256Digest = {
sha256(schnorrChallengeTagBytes ++ bytes)
}
// The tag "BIP340/nonce"
private val schnorrNonceTagBytes = {
ByteVector
.fromHex(
"a2ba14a6b39c1c505260bf3aceb07febde3ab34c35c9259d25bd6972f15e6564a2ba14a6b39c1c505260bf3aceb07febde3ab34c35c9259d25bd6972f15e6564"
)
.get
}
def sha256SchnorrNonce(bytes: ByteVector): Sha256Digest = {
sha256(schnorrNonceTagBytes ++ bytes)
}
// The tag "BIP340/aux"
private val schnorrAuxTagBytes = {
ByteVector
.fromHex(
"4b07426ad8630dcdbadf8dee1e94f09ac2df4e7ee2629e5e6b27c8666c8cf31e4b07426ad8630dcdbadf8dee1e94f09ac2df4e7ee2629e5e6b27c8666c8cf31e"
)
.get
}
def sha256SchnorrAuxRand(bytes: ByteVector): Sha256Digest = {
sha256(schnorrAuxTagBytes ++ bytes)
}
/** Performs SHA1(bytes). */ /** Performs SHA1(bytes). */
def sha1(bytes: ByteVector): Sha1Digest = { def sha1(bytes: ByteVector): Sha1Digest = {
val hash = MessageDigest.getInstance("SHA-1").digest(bytes.toArray).toList val hash = MessageDigest.getInstance("SHA-1").digest(bytes.toArray).toList

View file

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

View file

@ -1,3 +1,8 @@
version in ThisBuild ~= { version =>
val withoutSuffix = version.dropRight(8)
withoutSuffix + "SCHNORR-DLC-SNAPSHOT"
}
val scala2_11 = "2.11.12" val scala2_11 = "2.11.12"
val scala2_12 = "2.12.11" val scala2_12 = "2.12.11"
val scala2_13 = "2.13.1" val scala2_13 = "2.13.1"

@ -1 +1 @@
Subproject commit e0239255f1f386cf76279c008253b70cd17fe466 Subproject commit c56f1d3246607edd0aa4bb3e1e8cf58b1e192c9b

View file

@ -457,6 +457,140 @@ public class NativeSecp256k1 {
return resArr; return resArr;
} }
/**
* libsecp256k1 schnorr sign - generates a BIP 340 Schnorr signature
*
* @param data message to sign
* @param secKey key to sign with
*/
public static byte[] schnorrSign(byte[] data, byte[] secKey, byte[] auxRand) throws AssertFailException {
checkArgument(data.length == 32 && secKey.length == 32 && auxRand.length == 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + 32 + 32) {
byteBuff = ByteBuffer.allocateDirect(32 + 32 + 32);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(data);
byteBuff.put(secKey);
byteBuff.put(auxRand);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_schnorrsig_sign(byteBuff, Secp256k1Context.getContext());
} finally {
r.unlock();
}
byte[] sigArray = retByteArray[0];
int retVal = new BigInteger(new byte[] { retByteArray[1][0] }).intValue();
assertEquals(retVal, 1, "Failed return value check.");
return sigArray;
}
/**
* libsecp256k1 schnorr sign - generates a BIP 340 Schnorr signature
*
* @param data message to sign
* @param secKey key to sign with
* @param nonce the nonce (k value) used in signing
*/
public static byte[] schnorrSignWithNonce(byte[] data, byte[] secKey, byte[] nonce) throws AssertFailException {
checkArgument(data.length == 32 && secKey.length == 32 && nonce.length == 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + 32 + 32) {
byteBuff = ByteBuffer.allocateDirect(32 + 32 + 32);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(data);
byteBuff.put(secKey);
byteBuff.put(nonce);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_schnorrsig_sign_with_nonce(byteBuff, Secp256k1Context.getContext());
} finally {
r.unlock();
}
byte[] sigArray = retByteArray[0];
int retVal = new BigInteger(new byte[]{retByteArray[1][0]}).intValue();
assertEquals(retVal, 1, "Failed return value check.");
return sigArray;
}
public static byte[] schnorrComputeSigPoint(byte[] data, byte[] nonce, byte[] pubkey, boolean compressed) throws AssertFailException {
checkArgument(data.length == 32 && nonce.length == 32 && pubkey.length == 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + 32 + 32) {
byteBuff = ByteBuffer.allocateDirect(32 + 32 + 32);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(data);
byteBuff.put(nonce);
byteBuff.put(pubkey);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_schnorrsig_compute_sigpoint(byteBuff, Secp256k1Context.getContext(), compressed);
} finally {
r.unlock();
}
byte[] pointArray = retByteArray[0];
int outputLen = new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF;
int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue();
assertEquals(pointArray.length, outputLen, "Got bad point length.");
assertEquals(retVal, 1, "Failed return value check.");
return pointArray;
}
/**
* libsecp256k1 schnorr verify - verifies BIP 340 Schnorr signatures
*
* @param sig signature to verify
* @param data message the signature has signed
* @param pubx the key that did the signing
*/
public static boolean schnorrVerify(byte[] sig, byte[] data, byte[] pubx) throws AssertFailException {
checkArgument(sig.length == 64 && data.length == 32 && pubx.length == 32);
ByteBuffer byteBuffer = nativeECDSABuffer.get();
if (byteBuffer == null || byteBuffer.capacity() < 64 + 32 + 32) {
byteBuffer = ByteBuffer.allocateDirect(64 + 32 + 32);
byteBuffer.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuffer);
}
byteBuffer.rewind();
byteBuffer.put(sig);
byteBuffer.put(data);
byteBuffer.put(pubx);
r.lock();
try {
return secp256k1_schnorrsig_verify(byteBuffer, Secp256k1Context.getContext()) == 1;
} finally {
r.unlock();
}
}
/** /**
* libsecp256k1 randomize - updates the context randomization * libsecp256k1 randomize - updates the context randomization
* *
@ -508,4 +642,11 @@ public class NativeSecp256k1 {
private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen); private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen);
private static native byte[][] secp256k1_schnorrsig_sign(ByteBuffer byteBuff, long context);
private static native byte[][] secp256k1_schnorrsig_sign_with_nonce(ByteBuffer byteBuff, long context);
private static native byte[][] secp256k1_schnorrsig_compute_sigpoint(ByteBuffer byteBuff, long context, boolean compressed);
private static native int secp256k1_schnorrsig_verify(ByteBuffer byteBuffer, long context);
} }

View file

@ -229,6 +229,52 @@ public class NativeSecp256k1Test {
assertEquals( ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043" , "testCreateECDHSecret"); assertEquals( ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043" , "testCreateECDHSecret");
} }
@Test
public void testSchnorrSign() throws AssertFailException{
byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] secKey = toByteArray("688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF");
byte[] auxRand = toByteArray("02CCE08E913F22A36C5648D6405A2C7C50106E7AA2F1649E381C7F09D16B80AB");
byte[] sigArr = NativeSecp256k1.schnorrSign(data, secKey, auxRand);
String sigStr = toHex(sigArr);
String expectedSig = "F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B5477C988C51634A8DC955950A58FF5DC8C506DDB796121E6675946312680C26CF33";
assertEquals(sigStr, expectedSig, "testSchnorrSign");
}
@Test
public void testSchnorrSignWithNonce() throws AssertFailException{
byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] secKey = toByteArray("688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF");
byte[] nonce = toByteArray("8C8CA771D3C25EB38DE7401818EEDA281AC5446F5C1396148F8D9D67592440FE");
byte[] sigArr = NativeSecp256k1.schnorrSignWithNonce(data, secKey, nonce);
String sigStr = toHex(sigArr);
String expectedSig = "5DA618C1936EC728E5CCFF29207F1680DCF4146370BDCFAB0039951B91E3637A50A2A860B130D009405511C3EAFE943E157A0DF2C2020E3E50DF05ADB175332F";
assertEquals(sigStr, expectedSig, "testSchnorrSignWithNonce");
}
@Test
public void testSchnorrComputeSigPoint() throws AssertFailException{
byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] nonce = toByteArray("F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B547");
byte[] pubKey = toByteArray("B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390");
byte[] pointArr = NativeSecp256k1.schnorrComputeSigPoint(data, nonce, pubKey, true);
String pointStr = toHex(pointArr);
String expectedPoint = "020D17280B8D2C2BD3B597B4446419C151DC237353D0FB9EC03D4EB7E8DE7EE0A8";
assertEquals(pointStr, expectedPoint, "testSchnorrComputeSigPoint");
}
@Test
public void testSchnorrVerify() throws AssertFailException{
byte[] sig = toByteArray("F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B5477C988C51634A8DC955950A58FF5DC8C506DDB796121E6675946312680C26CF33");
byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] pubx = toByteArray("B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390");
boolean result = NativeSecp256k1.schnorrVerify(sig, data, pubx);
assertEquals(result, true, "testSchnorrVerify");
}
//https://stackoverflow.com/a/19119453/967713 //https://stackoverflow.com/a/19119453/967713
private static byte[] toByteArray(final String hex) { private static byte[] toByteArray(final String hex) {

View file

@ -118,6 +118,36 @@ sealed abstract class CryptoGenerators {
def privateKey: Gen[ECPrivateKey] = Gen.delay(ECPrivateKey()) 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 * Generate a sequence of private keys
* @param num maximum number of keys to generate * @param num maximum number of keys to generate
@ -174,6 +204,13 @@ sealed abstract class CryptoGenerators {
hash <- CryptoGenerators.doubleSha256Digest hash <- CryptoGenerators.doubleSha256Digest
} yield privKey.sign(hash) } yield privKey.sign(hash)
def schnorrDigitalSignature: Gen[SchnorrDigitalSignature] = {
for {
privKey <- privateKey
hash <- CryptoGenerators.doubleSha256Digest
} yield privKey.schnorrSign(hash.bytes)
}
def sha256Digest: Gen[Sha256Digest] = def sha256Digest: Gen[Sha256Digest] =
for { for {
bytes <- NumberGenerator.bytevector bytes <- NumberGenerator.bytevector