mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-19 21:45:36 +01:00
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:
parent
28aea46e33
commit
03729103a1
37 changed files with 1461 additions and 26 deletions
|
@ -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
|
||||||
|
|
16
core-test/src/test/resources/bip340-test-vectors.csv
Normal file
16
core-test/src/test/resources/bip340-test-vectors.csv
Normal 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
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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] {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
134
core/src/main/scala/org/bitcoins/core/crypto/FieldElement.scala
Normal file
134
core/src/main/scala/org/bitcoins/core/crypto/FieldElement.scala
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue