Add Schnorr and Adaptor Secp Bindings and Update Adaptor (#2885)

* Replaced secp256k1 with secp256k1-zkp as submodule pointing to my java-bindings branch

* Built new binaries for schnorr signing and adaptor signing and integrated into LibSecp256k1CryptoRuntime

* Added public key compression function with tests, removed old adaptor signature point serializers

* Implemented ECDSA adaptor signatures in scala according to the most recent spec

* Added static test vectors for adaptor signing from spec

* Moved bouncy castle adaptor signing tests to .jvm

* Added scaladocs and responded to nits

* Added scaladocs with legends to spec naming

* Responded to Ben's review

* Fixed scala 2.12 compile issue

* Fixed BouncyCastle secKeyVerify

* Updated add-to-jni build instructions

* Updated secp256k1-zkp to target bitcoin-s-master

* Add windows binary (#14)

* Added Mac OS binaries

Co-authored-by: benthecarman <benthecarman@live.com>
This commit is contained in:
Nadav Kohen 2021-04-21 09:42:43 -05:00 committed by GitHub
parent 13fc3c2b4e
commit 7fd9aca304
33 changed files with 1382 additions and 272 deletions

7
.gitmodules vendored
View File

@ -1,4 +1,3 @@
[submodule "secp256k1"]
path = secp256k1
url = https://github.com/bitcoin-s/secp256k1.git
branch = bitcoin-s-master
[submodule "secp256k1-zkp"]
path = secp256k1-zkp
url = git@github.com:bitcoin-s/secp256k1-zkp.git

View File

@ -32,9 +32,10 @@ class BouncyCastleBIP340Test extends BitcoinSCryptoTest {
sig: SchnorrDigitalSignature,
expectedResult: Boolean,
comment: String): Assertion = {
val secpResult = pubKey.verify(msg, sig)
val secpResult = Try(pubKey.verify(msg, sig)).getOrElse(false)
val bouncyCastleResult =
BouncycastleCryptoRuntime.schnorrVerify(msg, pubKey, sig)
Try(BouncycastleCryptoRuntime.schnorrVerify(msg, pubKey, sig))
.getOrElse(false)
assert(secpResult == expectedResult,
s"Test $index failed verification for libsecp256k1: $comment")
assert(bouncyCastleResult == expectedResult,

View File

@ -0,0 +1,242 @@
package org.bitcoins.crypto
import org.scalatest.Assertion
import scodec.bits.ByteVector
/** Executes tests at https://github.com/discreetlogcontracts/dlcspecs/blob/9b11bcc99341ad40d8ae146d5d3ead116d5bb131/test/ecdsa_adaptor.json
* along with some static signing tests Jesse generated.
*/
class BouncyCastleECAdaptorSignatureTest extends BitcoinSCryptoTest {
behavior of "Bouncy Castle ECDSA Adaptor Signing"
def testSign(
secKey: String,
msg: String,
decKey: String,
pubKey: String,
encKey: String,
auxRand: String,
adaptorSig: String): Assertion = {
testSign(
ECPrivateKey(secKey),
ByteVector.fromValidHex(msg),
ECPrivateKey(decKey),
ECPublicKey(pubKey),
ECPublicKey(encKey),
ByteVector.fromValidHex(auxRand),
ECAdaptorSignature(adaptorSig)
)
}
def testSign(
secKey: ECPrivateKey,
msg: ByteVector,
decKey: ECPrivateKey,
pubKey: ECPublicKey,
encKey: ECPublicKey,
auxRand: ByteVector,
adaptorSig: ECAdaptorSignature): Assertion = {
assert(pubKey == secKey.publicKey)
assert(
BouncycastleCryptoRuntime
.adaptorSign(secKey, encKey, msg, auxRand) == adaptorSig)
assert(
BouncycastleCryptoRuntime.adaptorVerify(adaptorSig, pubKey, msg, encKey))
val sigBC = BouncycastleCryptoRuntime.adaptorComplete(decKey, adaptorSig)
assert(
BouncycastleCryptoRuntime
.extractAdaptorSecret(sigBC, adaptorSig, encKey) == decKey)
}
it must "pass Jesse's static test vectors" in {
testSign(
secKey =
"00033065FBFD7BABE601089E75C463D16E8FB8C731ED520DE425585099E27F70",
msg = "9BB3A7C4CF1C10C225FF6C78867352559C6B0AD43DA0744BF0744A1C520FB03A",
decKey =
"918B8E4F61F44EA1EF3F3C236973F2F12796E0A028FCEC3F30D71BF1C035A95A",
pubKey =
"036ED79915CEAAFBFDACF7D3ABF105991B28922A90B8E7BEAE69E5413A599AE6BF",
encKey =
"038B3E8F09A59F17A3CC7B8BDF04038CD102CDBE6A9FFCFFF1A7A8EA749826F48D",
auxRand =
"580E40DCE3C772ED359FE6D9C1459702016845F1981ECC04E371B00E7B851ACA",
adaptorSig =
"0389CFE2E8861E763EA33C64C5034C292011A34CB8EAEC5376200B9D7D35F9304C03D951170788AEC19A94100ED8381040BD22C5A60F7264B53E51567D36A3DA9F5F30DC49EBC212AEED4535EA39C8B8F9418AFC8B4E8899C8C978B4440C4EC4474FD90760B4037045C2DA538AB237B1DCE99209A0093949A472F24F44C6A7F25084B47ECB9F342D5E5249BFFB3C9B6E3BCD5E3356558615CD0B256C53E13F74D753"
)
testSign(
secKey =
"B700B67DF0ADC70A715C6883CA2AB21976E33E974094F430E8932C3C64F88D49",
msg = "3F690F3844B9ACF40D2CF91383BD6912CEB306FF909FC4DB7022372F8943A0F7",
decKey =
"BF984ABB08D8DAE02F0A3EC7626AD5EF95458732B7D1CA49D027F7A87CD754AC",
pubKey =
"026CA09B06BC3D11A5EF8EC4AF8BD7BFF8AEFE5257FA9C058698747D2E7C0443CB",
encKey =
"0258ECE4ABAFF624689160C197057E6BAF6D1E539114CF84B63E544EEFA970E006",
auxRand =
"2938CC748CEC98E96C2A1B45FC1F8B9036A0547ACFC6D81C0EFA439058ED6423",
adaptorSig =
"02A4A2900432C36D8ADA1E6124E22D9E902744814EEF3A0E82399B0F440EEBDE790262309993372A9602344771B0B23D6BC6F6CC168F72DEBA0B964134DE418A4C3A863D8EFCCE991BBD3B2AB990863D61F6C5D6826AE04F2A6D140A46E6CBA340CE527B7889B7FAE3253CD2E0C49D9A0DA23A08FB18B2A22C5238F81535674E20FD00FDAC5C53EA54D51DCF3290D231DD5FC164ED6756F85883668EBAAF4FA86CB1"
)
testSign(
secKey =
"29999EEF451EB2E62772A965329967F8B75CEB1E15B0FFB596B6E0C9CA7D2127",
msg = "D9B5C45FFB11BC6425E7786014A2ADABCE6A75580CC5B43EFF30FE2261F98F6F",
decKey =
"16B30482C5BE8E19ACFB869749EAB4383DF44416E7B329720E00809E014E5088",
pubKey =
"02A3A0913F145690141DC2CBD33CCBBD204A8793B4F037718199D7625DA6A84B7B",
encKey =
"02A7E8E1E648D7977AE8A9AD1E7E77D875D098E34717E5B66183BF0969873B8B70",
auxRand =
"4A259D87BFFAE9E6A18B5CCD68E5FA24AEBCB424D22F015ACD27FE66FE007C90",
adaptorSig =
"03485D1947850BEA0DEEB384F978D0C423E1D9E19AF5B0BB23DB7524C4556B4838025815E5A01897A0C6731F91718B4933F342822BE8995EEEDD7C755746825E4D8B885C5F737D408D105D50B41AE1A107EBD82C9ADA567D4B71AEC808291CA8287A3087E5438B64C83806B36ABAFE69B4AF373209C02DF5516DFF83114188BD7457893D7691842AB373C049A17663505F11F23223119E5FBE2BCA9892E33C8726E6"
)
testSign(
secKey =
"BA837CC074A2CABAB78266230F01B8241937FC76358230736745E9CCB387D4FC",
msg = "2E5BBCDBF24618F586910CB2DB7F4475EAD4F2C37DC4115C0DDC0ABB537917E5",
decKey =
"AF81094DB8EEA02499C1F98A385BCE55EB00683FC5A4141A50B344671DC76EE3",
pubKey =
"0384F22B3539B17727395C42BB0CCF81A6952FC7F3F561942EEA173B46B84AA890",
encKey =
"02D9D918CE57AEF470F4E5B543D7599330502CBEAB1285EDA545E38A37F2A32325",
auxRand =
"B32E2416E5259212F037C5E83688AF83EBF0AE73B757FEB26ACBD19199D63BCD",
adaptorSig =
"039E956529933038083DA428AA2E333AAFD35947A8D11997670DA0287987410ED302604BF918A20793981BB9E19B5B677EE764BA23E4FA09A7726D1FA37AE179F0160CFFFCB792F9A46D0240A424265454BB971CC9175763E8A9772635A61C253A39875826BBEB83779ED05144CE35E8B6B2A3469EB3A949543119AC28CC27AFD1D299520B2A7ED5CA1D814BA96EB80750A9F56F878F7D2D26E70B2245A39DE9AB59"
)
testSign(
secKey =
"AEEF60CF249A0CFE57B971A0624B41D6E7007F82B8FE86A568F2E9DC5CE01C6D",
msg = "45131FDB8F3AED1AE4040BC43EB1D24AD144D9085B35C3F7036C490EFD492D9A",
decKey =
"AFD71563537C0E8438EE7FA047C91890471F5C699A63F2C0520C5CF99050FCB9",
pubKey =
"03789E4707B057FE03771827A344B1BF13B21307DA5E88726BEBDFFB601EA863AE",
encKey =
"0224F1566286D1D49189250F878710AF5A2F3658FC7633E4B33F825561B2E3BBAE",
auxRand =
"034FB76F56207BA7D8BD7C944C82A2198B0B61CE31160F6ED5B8CC642A1C55AA",
adaptorSig =
"02B90DDC325AF9A443B8845A10D3D3FB6A90D9937E4AF56E2E814106AEE976A9D2024FA51F36F15F4C4390FD881B01EB5C9488928F626C3104442BA230EE890B08FC656096218AF24A8B92ED518F9FE1F13213DB771187F95640ECAF02658A2ABA40A9CD5E89DB8215879A294CCE4E72786FE5E2267BC25F934CE34CFBF2FEF361A0BDFF1B2132552C0BD0C699A3195C5EA6D82C50B784175DE54E4924BCD15C06DB"
)
}
it must "pass happy path verification test" in {
val adaptorSig = ECAdaptorSignature(
"03424d14a5471c048ab87b3b83f6085d125d5864249ae4297a57c84e74710bb6730223f325042fce535d040fee52ec13231bf709ccd84233c6944b90317e62528b2527dff9d659a96db4c99f9750168308633c1867b70f3a18fb0f4539a1aecedcd1fc0148fc22f36b6303083ece3f872b18e35d368b3958efe5fb081f7716736ccb598d269aa3084d57e1855e1ea9a45efc10463bbf32ae378029f5763ceb40173f")
val msg = ByteVector.fromValidHex(
"8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d")
val pubKey = ECPublicKey(
"035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c")
val encKey = ECPublicKey(
"02c2662c97488b07b6e819124b8989849206334a4c2fbdf691f7b34d2b16e9c293")
val decKey = ECPrivateKey(
"0b2aba63b885a0f0e96fa0f303920c7fb7431ddfa94376ad94d969fbf4109dc8")
val signature = ECDigitalSignature.fromRS(
"424d14a5471c048ab87b3b83f6085d125d5864249ae4297a57c84e74710bb67329e80e0ee60e57af3e625bbae1672b1ecaa58effe613426b024fa1621d903394")
assert(
BouncycastleCryptoRuntime.adaptorVerify(adaptorSig, pubKey, msg, encKey))
assert(
BouncycastleCryptoRuntime.adaptorComplete(decKey,
adaptorSig) == signature)
assert(
BouncycastleCryptoRuntime.extractAdaptorSecret(signature,
adaptorSig,
encKey) == decKey)
}
it must "pass ECDSA malleable verification test" in {
val adaptorSig = ECAdaptorSignature(
"036035c89860ec62ad153f69b5b3077bcd08fbb0d28dc7f7f6df4a05cca35455be037043b63c56f6317d9928e8f91007335748c49824220db14ad10d80a5d00a9654af0996c1824c64c90b951bb2734aaecf78d4b36131a47238c3fa2ba25e2ced54255b06df696de1483c3767242a3728826e05f79e3981e12553355bba8a0131cd370e63e3da73106f638576a5aab0ea6d45c042574c0c8d0b14b8c7c01cfe9072")
val msg = ByteVector.fromValidHex(
"8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d")
val pubKey = ECPublicKey(
"035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c")
val encKey = ECPublicKey(
"024eee18be9a5a5224000f916c80b393447989e7194bc0b0f1ad7a03369702bb51")
val decKey = ECPrivateKey(
"db2debddb002473a001dd70b06f6c97bdcd1c46ba1001237fe0ee1aeffb2b6c4")
val signature = ECDigitalSignature.fromRS(
"6035c89860ec62ad153f69b5b3077bcd08fbb0d28dc7f7f6df4a05cca35455be4ceacf921546c03dd1be596723ad1e7691bdac73d88cc36c421c5e7f08384305")
assert(
BouncycastleCryptoRuntime.adaptorVerify(adaptorSig, pubKey, msg, encKey))
assert(
BouncycastleCryptoRuntime.adaptorComplete(decKey,
adaptorSig) == signature)
assert(
BouncycastleCryptoRuntime.extractAdaptorSecret(signature,
adaptorSig,
encKey) == decKey)
}
it must "fail to validate a false proof" in {
val adaptorSig = ECAdaptorSignature(
"03f94dca206d7582c015fb9bffe4e43b14591b30ef7d2b464d103ec5e116595dba03127f8ac3533d249280332474339000922eb6a58e3b9bf4fc7e01e4b4df2b7a4100a1e089f16e5d70bb89f961516f1de0684cc79db978495df2f399b0d01ed7240fa6e3252aedb58bdc6b5877b0c602628a235dd1ccaebdddcbe96198c0c21bead7b05f423b673d14d206fa1507b2dbe2722af792b8c266fc25a2d901d7e2c335")
val msg = ByteVector.fromValidHex(
"8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d")
val pubKey = ECPublicKey(
"035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c")
val encKey = ECPublicKey(
"0214ccb756249ad6e733c80285ea7ac2ee12ffebbcee4e556e6810793a60c45ad4")
assert(
!BouncycastleCryptoRuntime.adaptorVerify(adaptorSig, pubKey, msg, encKey))
}
it must "pass happy path recovery test" in {
val adaptorSig = ECAdaptorSignature(
"03f2db6e9ed33092cc0b898fd6b282e99bdaeccb3de85c2d2512d8d507f9abab290210c01b5bed7094a12664aeaab3402d8709a8f362b140328d1b36dd7cb420d02fb66b1230d61c16d0cd0a2a02246d5ac7848dcd6f04fe627053cd3c7015a7d4aa6ac2b04347348bd67da43be8722515d99a7985fbfa66f0365c701de76ff0400dffdc9fa84dddf413a729823b16af60aa6361bc32e7cfd6701e32957c72ace67b")
val encKey = ECPublicKey(
"027ee4f899bc9c5f2b626fa1a9b37ce291c0388b5227e90b0fd8f4fa576164ede7")
val decKey = ECPrivateKey(
"9cf3ea9be594366b78c457162908af3c2ea177058177e9c6bf99047927773a06")
val signature = ECDigitalSignature.fromRS(
"f2db6e9ed33092cc0b898fd6b282e99bdaeccb3de85c2d2512d8d507f9abab2921811fe7b53becf3b7affa9442abaa93c0ab8a8e45cd7ee2ea8d258bfc25d464")
assert(
BouncycastleCryptoRuntime.extractAdaptorSecret(signature,
adaptorSig,
encKey) == decKey)
}
it must "fail to extract a secret on unrelated signatures" in {
val adaptorSig = ECAdaptorSignature(
"03aa86d78059a91059c29ec1a757c4dc029ff636a1e6c1142fefe1e9d7339617c003a8153e50c0c8574a38d389e61bbb0b5815169e060924e4b5f2e78ff13aa7ad858e0c27c4b9eed9d60521b3f54ff83ca4774be5fb3a680f820a35e8840f4aaf2de88e7c5cff38a37b78725904ef97bb82341328d55987019bd38ae1745e3efe0f8ea8bdfede0d378fc1f96e944a7505249f41e93781509ee0bade77290d39cd12")
val encKey = ECPublicKey(
"035176d24129741b0fcaa5fd6750727ce30860447e0a92c9ebebdeb7c3f93995ed")
val signature = ECDigitalSignature.fromRS(
"f7f7fe6bd056fc4abd70d335f72d0aa1e8406bba68f3e579e4789475323564a452c46176c7fb40aa37d5651341f55697dab27d84a213b30c93011a7790bace8c")
assertThrows[Exception](
BouncycastleCryptoRuntime
.extractAdaptorSecret(signature, adaptorSig, encKey))
}
it must "pass recovery test for high s value" in {
val adaptorSig = ECAdaptorSignature(
"032c637cd797dd8c2ce261907ed43e82d6d1a48cbabbbece801133dd8d70a01b1403eb615a3e59b1cbbf4f87acaf645be1eda32a066611f35dd5557802802b14b19c81c04c3fefac5783b2077bd43fa0a39ab8a64d4d78332a5d621ea23eca46bc011011ab82dda6deb85699f508744d70d4134bea03f784d285b5c6c15a56e4e1fab4bc356abbdebb3b8fe1e55e6dd6d2a9ea457e91b2e6642fae69f9dbb5258854")
val encKey = ECPublicKey(
"02042537e913ad74c4bbd8da9607ad3b9cb297d08e014afc51133083f1bd687a62")
val decKey = ECPrivateKey(
"324719b51ff2474c9438eb76494b0dc0bcceeb529f0a5428fd198ad8f886e99c")
val signature = ECDigitalSignature.fromRS(
"2c637cd797dd8c2ce261907ed43e82d6d1a48cbabbbece801133dd8d70a01b14b5f24321f550b7b9dd06ee4fcfd82bdad8b142ff93a790cc4d9f7962b38c6a3b")
assert(
BouncycastleCryptoRuntime.extractAdaptorSecret(signature,
adaptorSig,
encKey) == decKey)
}
}

View File

@ -1,7 +1,7 @@
package org.bitcoins.crypto
import org.scalacheck.Gen
import org.scalatest.{Outcome, Succeeded}
import org.scalatest.{Assertion, Outcome, Succeeded}
class BouncyCastleSecp256k1Test extends BitcoinSCryptoTest {
@ -18,33 +18,26 @@ class BouncyCastleSecp256k1Test extends BitcoinSCryptoTest {
}
}
def testCompatibility[T](func: CryptoRuntime => T): Assertion = {
assert(func(BouncycastleCryptoRuntime) == func(LibSecp256k1CryptoRuntime))
}
it must "add private keys the same" in {
forAll(CryptoGenerators.privateKey, CryptoGenerators.privateKey) {
case (priv1, priv2) =>
assert(
BouncycastleCryptoRuntime
.add(priv1, priv2) == LibSecp256k1CryptoRuntime.add(priv1, priv2))
case (priv1, priv2) => testCompatibility(_.add(priv1, priv2))
}
}
it must "add public keys the same" in {
forAll(CryptoGenerators.publicKey, CryptoGenerators.privateKey) {
case (pubKey, privKey) =>
val sumKeyExpected =
LibSecp256k1CryptoRuntime.pubKeyTweakAdd(pubKey, privKey)
val sumKey =
BouncycastleCryptoRuntime.pubKeyTweakAdd(pubKey, privKey)
assert(sumKey == sumKeyExpected)
testCompatibility(_.pubKeyTweakAdd(pubKey, privKey))
}
}
it must "multiply keys the same" in {
forAll(CryptoGenerators.publicKey, CryptoGenerators.fieldElement) {
case (pubKey, tweak) =>
assert(
LibSecp256k1CryptoRuntime.tweakMultiply(pubKey, tweak) ==
BouncycastleCryptoRuntime.tweakMultiply(pubKey, tweak))
case (pubKey, tweak) => testCompatibility(_.tweakMultiply(pubKey, tweak))
}
}
@ -53,39 +46,25 @@ class BouncyCastleSecp256k1Test extends BitcoinSCryptoTest {
Gen.oneOf(CryptoGenerators.publicKey.map(_.bytes),
NumberGenerator.bytevector(33))
forAll(keyOrGarbageGen) { bytes =>
assert(
LibSecp256k1CryptoRuntime.isValidPubKey(bytes) ==
BouncycastleCryptoRuntime.isValidPubKey(bytes)
)
testCompatibility(_.isValidPubKey(bytes))
}
}
it must "decompress keys the same" in {
forAll(CryptoGenerators.publicKey) { pubKey =>
assert(
LibSecp256k1CryptoRuntime.decompressed(pubKey) ==
BouncycastleCryptoRuntime.decompressed(pubKey)
)
testCompatibility(_.decompressed(pubKey))
}
}
it must "compute public keys the same" in {
forAll(CryptoGenerators.privateKey) { privKey =>
assert(
LibSecp256k1CryptoRuntime.publicKey(privKey) ==
BouncycastleCryptoRuntime.publicKey(privKey)
)
testCompatibility(_.publicKey(privKey))
}
}
it must "compute signatures the same" in {
forAll(CryptoGenerators.privateKey, NumberGenerator.bytevector(32)) {
case (privKey, bytes) =>
assert(
LibSecp256k1CryptoRuntime.sign(
privKey,
bytes) == BouncycastleCryptoRuntime.sign(privKey, bytes)
)
case (privKey, bytes) => testCompatibility(_.sign(privKey, bytes))
}
}
@ -93,14 +72,7 @@ class BouncyCastleSecp256k1Test extends BitcoinSCryptoTest {
forAll(CryptoGenerators.privateKey,
NumberGenerator.bytevector(32),
NumberGenerator.bytevector(32)) { case (privKey, bytes, entropy) =>
assert(
LibSecp256k1CryptoRuntime.signWithEntropy(
privKey,
bytes,
entropy) == BouncycastleCryptoRuntime.signWithEntropy(privKey,
bytes,
entropy)
)
testCompatibility(_.signWithEntropy(privKey, bytes, entropy))
}
}
@ -110,43 +82,25 @@ class BouncyCastleSecp256k1Test extends BitcoinSCryptoTest {
CryptoGenerators.digitalSignature) { case (privKey, bytes, badSig) =>
val sig = privKey.sign(bytes)
val pubKey = privKey.publicKey
assert(
LibSecp256k1CryptoRuntime.verify(
pubKey,
bytes,
sig) == BouncycastleCryptoRuntime.verify(pubKey, bytes, sig)
)
assert(
LibSecp256k1CryptoRuntime.verify(
pubKey,
bytes,
badSig) == BouncycastleCryptoRuntime.verify(pubKey, bytes, badSig)
)
testCompatibility(_.verify(pubKey, bytes, sig))
testCompatibility(_.verify(pubKey, bytes, badSig))
}
}
/*
it must "compute schnorr signatures the same" in {
forAll(CryptoGenerators.privateKey,
NumberGenerator.bytevector(32),
NumberGenerator.bytevector(32)) {
case (privKey, bytes, auxRand) =>
assert(
privKey.schnorrSign(bytes, auxRand, context = BouncyCastle) == privKey
.schnorrSign(bytes, auxRand, context = LibSecp256k1))
NumberGenerator.bytevector(32)) { case (privKey, bytes, auxRand) =>
testCompatibility(_.schnorrSign(bytes, privKey, auxRand))
}
}
it must "compute schnorr signature for fixed nonce the same" in {
forAll(CryptoGenerators.privateKey,
CryptoGenerators.privateKey,
NumberGenerator.bytevector(32)) {
case (privKey, nonceKey, bytes) =>
val sigBC = privKey
.schnorrSignWithNonce(bytes, nonceKey, context = BouncyCastle)
val sigSecP = privKey
.schnorrSignWithNonce(bytes, nonceKey, context = LibSecp256k1)
assert(sigBC.bytes == sigSecP.bytes)
NumberGenerator.bytevector(32)) { case (privKey, nonceKey, bytes) =>
testCompatibility(_.schnorrSignWithNonce(bytes, privKey, nonceKey))
}
}
@ -157,33 +111,56 @@ class BouncyCastleSecp256k1Test extends BitcoinSCryptoTest {
case (privKey, bytes, badSig) =>
val sig = privKey.schnorrSign(bytes)
val pubKey = privKey.schnorrPublicKey
assert(
pubKey.verify(bytes, sig, context = BouncyCastle) == pubKey
.verify(bytes, sig, context = LibSecp256k1))
assert(
pubKey.verify(bytes, badSig, context = BouncyCastle) == pubKey
.verify(bytes, badSig, context = LibSecp256k1))
testCompatibility(_.schnorrVerify(bytes, pubKey, sig))
testCompatibility(_.schnorrVerify(bytes, pubKey, badSig))
}
}
it must "compute schnorr signature points the same" in {
forAll(CryptoGenerators.schnorrPublicKey,
CryptoGenerators.schnorrNonce,
NumberGenerator.bytevector(32)) { case (pubKey, nonce, bytes) =>
testCompatibility(
_.schnorrComputeSigPoint(bytes, nonce, pubKey, compressed = true))
}
}
it must "compute adaptor signatures the same" in {
forAll(CryptoGenerators.privateKey,
CryptoGenerators.publicKey,
NumberGenerator.bytevector(32),
NumberGenerator.bytevector(32)) {
case (pubKey, nonce, bytes) =>
val bouncyCastleSigPoint =
pubKey.computeSigPoint(bytes,
nonce,
compressed = true,
context = BouncyCastle)
case (privKey, adaptor, msg, auxRand) =>
testCompatibility(_.adaptorSign(privKey, adaptor, msg, auxRand))
}
}
val secpSigPoint = pubKey.computeSigPoint(bytes,
nonce,
compressed = true,
context = LibSecp256k1)
it must "verify adaptor signatures the same" in {
forAll(CryptoGenerators.privateKey,
CryptoGenerators.publicKey,
NumberGenerator.bytevector(32),
CryptoGenerators.adaptorSignature) {
case (privKey, adaptor, msg, badSig) =>
val sig = privKey.adaptorSign(adaptor, msg)
val pubKey = privKey.publicKey
assert(bouncyCastleSigPoint == secpSigPoint)
testCompatibility(_.adaptorVerify(sig, pubKey, msg, adaptor))
testCompatibility(_.adaptorVerify(badSig, pubKey, msg, adaptor))
}
}
it must "complete adaptor signatures the same" in {
forAll(CryptoGenerators.privateKey, CryptoGenerators.adaptorSignature) {
case (adaptorSecret, adaptorSig) =>
testCompatibility(_.adaptorComplete(adaptorSecret, adaptorSig))
}
}
it must "extract adaptor secrets the same" in {
forAll(CryptoGenerators.adaptorSignatureWithDecryptedSignatureAndAdaptor) {
case (adaptorSig, sig, adaptor) =>
testCompatibility(_.extractAdaptorSecret(sig, adaptorSig, adaptor))
}
}
*/
}

View File

@ -105,10 +105,26 @@ sealed abstract class CryptoGenerators {
tweakedNonce <- publicKey
untweakedNonce <- publicKey
adaptedS <- fieldElement
proofS <- fieldElement
proofE <- fieldElement
proofS <- fieldElement
} yield {
ECAdaptorSignature(tweakedNonce, adaptedS, untweakedNonce, proofS, proofE)
ECAdaptorSignature(tweakedNonce, untweakedNonce, adaptedS, proofE, proofS)
}
}
def adaptorSignatureWithDecryptedSignatureAndAdaptor: Gen[
(ECAdaptorSignature, ECDigitalSignature, ECPublicKey)] = {
for {
privKey <- privateKey
decKey <- privateKey
data <- NumberGenerator.bytevector(32)
auxRand <- NumberGenerator.bytevector(32)
} yield {
val adaptorPoint = decKey.publicKey
val adaptorSig = privKey.adaptorSign(adaptorPoint, data, auxRand)
val sig = decKey.completeAdaptorSignature(adaptorSig)
(adaptorSig, sig, adaptorPoint)
}
}

View File

@ -0,0 +1,233 @@
package org.bitcoins.crypto
import org.scalatest.Assertion
import scodec.bits.ByteVector
/** Executes tests at https://github.com/discreetlogcontracts/dlcspecs/blob/9b11bcc99341ad40d8ae146d5d3ead116d5bb131/test/ecdsa_adaptor.json
* along with some static signing tests Jesse generated.
*/
class ECAdaptorSignatureTest extends BitcoinSCryptoTest {
behavior of "ECDSA Adaptor Signing"
def testSign(
secKey: String,
msg: String,
decKey: String,
pubKey: String,
encKey: String,
auxRand: String,
adaptorSig: String): Assertion = {
testSign(
ECPrivateKey(secKey),
ByteVector.fromValidHex(msg),
ECPrivateKey(decKey),
ECPublicKey(pubKey),
ECPublicKey(encKey),
ByteVector.fromValidHex(auxRand),
ECAdaptorSignature(adaptorSig)
)
}
def testSign(
secKey: ECPrivateKey,
msg: ByteVector,
decKey: ECPrivateKey,
pubKey: ECPublicKey,
encKey: ECPublicKey,
auxRand: ByteVector,
adaptorSig: ECAdaptorSignature): Assertion = {
assert(pubKey == secKey.publicKey)
assert(secKey.adaptorSign(encKey, msg, auxRand) == adaptorSig)
assert(pubKey.adaptorVerify(msg, encKey, adaptorSig))
val sig = decKey.completeAdaptorSignature(adaptorSig)
assert(encKey.extractAdaptorSecret(adaptorSig, sig) == decKey)
}
it must "pass Jesse's static test vectors" in {
testSign(
secKey =
"00033065FBFD7BABE601089E75C463D16E8FB8C731ED520DE425585099E27F70",
msg = "9BB3A7C4CF1C10C225FF6C78867352559C6B0AD43DA0744BF0744A1C520FB03A",
decKey =
"918B8E4F61F44EA1EF3F3C236973F2F12796E0A028FCEC3F30D71BF1C035A95A",
pubKey =
"036ED79915CEAAFBFDACF7D3ABF105991B28922A90B8E7BEAE69E5413A599AE6BF",
encKey =
"038B3E8F09A59F17A3CC7B8BDF04038CD102CDBE6A9FFCFFF1A7A8EA749826F48D",
auxRand =
"580E40DCE3C772ED359FE6D9C1459702016845F1981ECC04E371B00E7B851ACA",
adaptorSig =
"0389CFE2E8861E763EA33C64C5034C292011A34CB8EAEC5376200B9D7D35F9304C03D951170788AEC19A94100ED8381040BD22C5A60F7264B53E51567D36A3DA9F5F30DC49EBC212AEED4535EA39C8B8F9418AFC8B4E8899C8C978B4440C4EC4474FD90760B4037045C2DA538AB237B1DCE99209A0093949A472F24F44C6A7F25084B47ECB9F342D5E5249BFFB3C9B6E3BCD5E3356558615CD0B256C53E13F74D753"
)
testSign(
secKey =
"B700B67DF0ADC70A715C6883CA2AB21976E33E974094F430E8932C3C64F88D49",
msg = "3F690F3844B9ACF40D2CF91383BD6912CEB306FF909FC4DB7022372F8943A0F7",
decKey =
"BF984ABB08D8DAE02F0A3EC7626AD5EF95458732B7D1CA49D027F7A87CD754AC",
pubKey =
"026CA09B06BC3D11A5EF8EC4AF8BD7BFF8AEFE5257FA9C058698747D2E7C0443CB",
encKey =
"0258ECE4ABAFF624689160C197057E6BAF6D1E539114CF84B63E544EEFA970E006",
auxRand =
"2938CC748CEC98E96C2A1B45FC1F8B9036A0547ACFC6D81C0EFA439058ED6423",
adaptorSig =
"02A4A2900432C36D8ADA1E6124E22D9E902744814EEF3A0E82399B0F440EEBDE790262309993372A9602344771B0B23D6BC6F6CC168F72DEBA0B964134DE418A4C3A863D8EFCCE991BBD3B2AB990863D61F6C5D6826AE04F2A6D140A46E6CBA340CE527B7889B7FAE3253CD2E0C49D9A0DA23A08FB18B2A22C5238F81535674E20FD00FDAC5C53EA54D51DCF3290D231DD5FC164ED6756F85883668EBAAF4FA86CB1"
)
testSign(
secKey =
"29999EEF451EB2E62772A965329967F8B75CEB1E15B0FFB596B6E0C9CA7D2127",
msg = "D9B5C45FFB11BC6425E7786014A2ADABCE6A75580CC5B43EFF30FE2261F98F6F",
decKey =
"16B30482C5BE8E19ACFB869749EAB4383DF44416E7B329720E00809E014E5088",
pubKey =
"02A3A0913F145690141DC2CBD33CCBBD204A8793B4F037718199D7625DA6A84B7B",
encKey =
"02A7E8E1E648D7977AE8A9AD1E7E77D875D098E34717E5B66183BF0969873B8B70",
auxRand =
"4A259D87BFFAE9E6A18B5CCD68E5FA24AEBCB424D22F015ACD27FE66FE007C90",
adaptorSig =
"03485D1947850BEA0DEEB384F978D0C423E1D9E19AF5B0BB23DB7524C4556B4838025815E5A01897A0C6731F91718B4933F342822BE8995EEEDD7C755746825E4D8B885C5F737D408D105D50B41AE1A107EBD82C9ADA567D4B71AEC808291CA8287A3087E5438B64C83806B36ABAFE69B4AF373209C02DF5516DFF83114188BD7457893D7691842AB373C049A17663505F11F23223119E5FBE2BCA9892E33C8726E6"
)
testSign(
secKey =
"BA837CC074A2CABAB78266230F01B8241937FC76358230736745E9CCB387D4FC",
msg = "2E5BBCDBF24618F586910CB2DB7F4475EAD4F2C37DC4115C0DDC0ABB537917E5",
decKey =
"AF81094DB8EEA02499C1F98A385BCE55EB00683FC5A4141A50B344671DC76EE3",
pubKey =
"0384F22B3539B17727395C42BB0CCF81A6952FC7F3F561942EEA173B46B84AA890",
encKey =
"02D9D918CE57AEF470F4E5B543D7599330502CBEAB1285EDA545E38A37F2A32325",
auxRand =
"B32E2416E5259212F037C5E83688AF83EBF0AE73B757FEB26ACBD19199D63BCD",
adaptorSig =
"039E956529933038083DA428AA2E333AAFD35947A8D11997670DA0287987410ED302604BF918A20793981BB9E19B5B677EE764BA23E4FA09A7726D1FA37AE179F0160CFFFCB792F9A46D0240A424265454BB971CC9175763E8A9772635A61C253A39875826BBEB83779ED05144CE35E8B6B2A3469EB3A949543119AC28CC27AFD1D299520B2A7ED5CA1D814BA96EB80750A9F56F878F7D2D26E70B2245A39DE9AB59"
)
testSign(
secKey =
"AEEF60CF249A0CFE57B971A0624B41D6E7007F82B8FE86A568F2E9DC5CE01C6D",
msg = "45131FDB8F3AED1AE4040BC43EB1D24AD144D9085B35C3F7036C490EFD492D9A",
decKey =
"AFD71563537C0E8438EE7FA047C91890471F5C699A63F2C0520C5CF99050FCB9",
pubKey =
"03789E4707B057FE03771827A344B1BF13B21307DA5E88726BEBDFFB601EA863AE",
encKey =
"0224F1566286D1D49189250F878710AF5A2F3658FC7633E4B33F825561B2E3BBAE",
auxRand =
"034FB76F56207BA7D8BD7C944C82A2198B0B61CE31160F6ED5B8CC642A1C55AA",
adaptorSig =
"02B90DDC325AF9A443B8845A10D3D3FB6A90D9937E4AF56E2E814106AEE976A9D2024FA51F36F15F4C4390FD881B01EB5C9488928F626C3104442BA230EE890B08FC656096218AF24A8B92ED518F9FE1F13213DB771187F95640ECAF02658A2ABA40A9CD5E89DB8215879A294CCE4E72786FE5E2267BC25F934CE34CFBF2FEF361A0BDFF1B2132552C0BD0C699A3195C5EA6D82C50B784175DE54E4924BCD15C06DB"
)
}
it must "pass happy path verification test" in {
val adaptorSig = ECAdaptorSignature(
"03424d14a5471c048ab87b3b83f6085d125d5864249ae4297a57c84e74710bb6730223f325042fce535d040fee52ec13231bf709ccd84233c6944b90317e62528b2527dff9d659a96db4c99f9750168308633c1867b70f3a18fb0f4539a1aecedcd1fc0148fc22f36b6303083ece3f872b18e35d368b3958efe5fb081f7716736ccb598d269aa3084d57e1855e1ea9a45efc10463bbf32ae378029f5763ceb40173f")
val msg = ByteVector.fromValidHex(
"8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d")
val pubKey = ECPublicKey(
"035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c")
val encKey = ECPublicKey(
"02c2662c97488b07b6e819124b8989849206334a4c2fbdf691f7b34d2b16e9c293")
val decKey = ECPrivateKey(
"0b2aba63b885a0f0e96fa0f303920c7fb7431ddfa94376ad94d969fbf4109dc8")
val signature = ECDigitalSignature.fromRS(
"424d14a5471c048ab87b3b83f6085d125d5864249ae4297a57c84e74710bb67329e80e0ee60e57af3e625bbae1672b1ecaa58effe613426b024fa1621d903394")
assert(pubKey.adaptorVerify(msg, encKey, adaptorSig))
assert(decKey.completeAdaptorSignature(adaptorSig) == signature)
assert(encKey.extractAdaptorSecret(adaptorSig, signature) == decKey)
}
it must "pass ECDSA malleable verification test" in {
val adaptorSig = ECAdaptorSignature(
"036035c89860ec62ad153f69b5b3077bcd08fbb0d28dc7f7f6df4a05cca35455be037043b63c56f6317d9928e8f91007335748c49824220db14ad10d80a5d00a9654af0996c1824c64c90b951bb2734aaecf78d4b36131a47238c3fa2ba25e2ced54255b06df696de1483c3767242a3728826e05f79e3981e12553355bba8a0131cd370e63e3da73106f638576a5aab0ea6d45c042574c0c8d0b14b8c7c01cfe9072")
val msg = ByteVector.fromValidHex(
"8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d")
val pubKey = ECPublicKey(
"035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c")
val encKey = ECPublicKey(
"024eee18be9a5a5224000f916c80b393447989e7194bc0b0f1ad7a03369702bb51")
val decKey = ECPrivateKey(
"db2debddb002473a001dd70b06f6c97bdcd1c46ba1001237fe0ee1aeffb2b6c4")
val signature = ECDigitalSignature.fromRS(
"6035c89860ec62ad153f69b5b3077bcd08fbb0d28dc7f7f6df4a05cca35455be4ceacf921546c03dd1be596723ad1e7691bdac73d88cc36c421c5e7f08384305")
assert(pubKey.adaptorVerify(msg, encKey, adaptorSig))
assert(decKey.completeAdaptorSignature(adaptorSig) == signature)
assert(encKey.extractAdaptorSecret(adaptorSig, signature) == decKey)
}
it must "fail to validate a false proof" in {
val adaptorSig = ECAdaptorSignature(
"03f94dca206d7582c015fb9bffe4e43b14591b30ef7d2b464d103ec5e116595dba03127f8ac3533d249280332474339000922eb6a58e3b9bf4fc7e01e4b4df2b7a4100a1e089f16e5d70bb89f961516f1de0684cc79db978495df2f399b0d01ed7240fa6e3252aedb58bdc6b5877b0c602628a235dd1ccaebdddcbe96198c0c21bead7b05f423b673d14d206fa1507b2dbe2722af792b8c266fc25a2d901d7e2c335")
val msg = ByteVector.fromValidHex(
"8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d")
val pubKey = ECPublicKey(
"035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c")
val encKey = ECPublicKey(
"0214ccb756249ad6e733c80285ea7ac2ee12ffebbcee4e556e6810793a60c45ad4")
assert(!pubKey.adaptorVerify(msg, encKey, adaptorSig))
}
it must "pass happy path recovery test" in {
val adaptorSig = ECAdaptorSignature(
"03f2db6e9ed33092cc0b898fd6b282e99bdaeccb3de85c2d2512d8d507f9abab290210c01b5bed7094a12664aeaab3402d8709a8f362b140328d1b36dd7cb420d02fb66b1230d61c16d0cd0a2a02246d5ac7848dcd6f04fe627053cd3c7015a7d4aa6ac2b04347348bd67da43be8722515d99a7985fbfa66f0365c701de76ff0400dffdc9fa84dddf413a729823b16af60aa6361bc32e7cfd6701e32957c72ace67b")
val encKey = ECPublicKey(
"027ee4f899bc9c5f2b626fa1a9b37ce291c0388b5227e90b0fd8f4fa576164ede7")
val decKey = ECPrivateKey(
"9cf3ea9be594366b78c457162908af3c2ea177058177e9c6bf99047927773a06")
val signature = ECDigitalSignature.fromRS(
"f2db6e9ed33092cc0b898fd6b282e99bdaeccb3de85c2d2512d8d507f9abab2921811fe7b53becf3b7affa9442abaa93c0ab8a8e45cd7ee2ea8d258bfc25d464")
assert(encKey.extractAdaptorSecret(adaptorSig, signature) == decKey)
}
it must "fail to extract a secret on unrelated signatures" in {
val adaptorSig = ECAdaptorSignature(
"03aa86d78059a91059c29ec1a757c4dc029ff636a1e6c1142fefe1e9d7339617c003a8153e50c0c8574a38d389e61bbb0b5815169e060924e4b5f2e78ff13aa7ad858e0c27c4b9eed9d60521b3f54ff83ca4774be5fb3a680f820a35e8840f4aaf2de88e7c5cff38a37b78725904ef97bb82341328d55987019bd38ae1745e3efe0f8ea8bdfede0d378fc1f96e944a7505249f41e93781509ee0bade77290d39cd12")
val encKey = ECPublicKey(
"035176d24129741b0fcaa5fd6750727ce30860447e0a92c9ebebdeb7c3f93995ed")
val signature = ECDigitalSignature.fromRS(
"f7f7fe6bd056fc4abd70d335f72d0aa1e8406bba68f3e579e4789475323564a452c46176c7fb40aa37d5651341f55697dab27d84a213b30c93011a7790bace8c")
assertThrows[Exception](encKey.extractAdaptorSecret(adaptorSig, signature))
}
it must "pass recovery test for high s value" in {
val adaptorSig = ECAdaptorSignature(
"032c637cd797dd8c2ce261907ed43e82d6d1a48cbabbbece801133dd8d70a01b1403eb615a3e59b1cbbf4f87acaf645be1eda32a066611f35dd5557802802b14b19c81c04c3fefac5783b2077bd43fa0a39ab8a64d4d78332a5d621ea23eca46bc011011ab82dda6deb85699f508744d70d4134bea03f784d285b5c6c15a56e4e1fab4bc356abbdebb3b8fe1e55e6dd6d2a9ea457e91b2e6642fae69f9dbb5258854")
val encKey = ECPublicKey(
"02042537e913ad74c4bbd8da9607ad3b9cb297d08e014afc51133083f1bd687a62")
val decKey = ECPrivateKey(
"324719b51ff2474c9438eb76494b0dc0bcceeb529f0a5428fd198ad8f886e99c")
val signature = ECDigitalSignature.fromRS(
"2c637cd797dd8c2ce261907ed43e82d6d1a48cbabbbece801133dd8d70a01b14b5f24321f550b7b9dd06ee4fcfd82bdad8b142ff93a790cc4d9f7962b38c6a3b")
assert(encKey.extractAdaptorSecret(adaptorSig, signature) == decKey)
}
it must "pass serialization tests" in {
val goodSigsT = scala.util.Try {
ECAdaptorSignature(
"03e6d51da7bc2bf24cf9dfd9acc6c4f0a3e74d8a6273ee5a573ed6818e3095b60903f33bc98f9d2ea3511f2e24f3358557c815abd7713c9318af9f4dfab4441898ecd619acb1cb75c1a5946fbaf716d227199a6479a678d10a6d95512d674fb7703d85b58980b8e6c54bd20616bdb9461dccd8eebb7d7e7c83a91452cc20edf53be5b0fe0db44dddaaafbe737678c684b6e89b9b4b679b1855aa6ed644498b89c918")
ECAdaptorSignature(
"03fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2c03f33bc98f9d2ea3511f2e24f3358557c815abd7713c9318af9f4dfab4441898ecd619acb1cb75c1a5946fbaf716d227199a6479a678d10a6d95512d674fb7703d85b58980b8e6c54bd20616bdb9461dccd8eebb7d7e7c83a91452cc20edf53be5b0fe0db44dddaaafbe737678c684b6e89b9b4b679b1855aa6ed644498b89c918")
ECAdaptorSignature(
"03e6d51da7bc2bf24cf9dfd9acc6c4f0a3e74d8a6273ee5a573ed6818e3095b60903fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2cd619acb1cb75c1a5946fbaf716d227199a6479a678d10a6d95512d674fb7703d85b58980b8e6c54bd20616bdb9461dccd8eebb7d7e7c83a91452cc20edf53be5b0fe0db44dddaaafbe737678c684b6e89b9b4b679b1855aa6ed644498b89c918")
}
assert(goodSigsT.isSuccess)
assertThrows[IllegalArgumentException](ECAdaptorSignature(
"03e6d51da7bc2bf24cf9dfd9acc6c4f0a3e74d8a6273ee5a573ed6818e3095b60903f33bc98f9d2ea3511f2e24f3358557c815abd7713c9318af9f4dfab4441898ec000000000000000000000000000000000000000000000000000000000000000085b58980b8e6c54bd20616bdb9461dccd8eebb7d7e7c83a91452cc20edf53be5b0fe0db44dddaaafbe737678c684b6e89b9b4b679b1855aa6ed644498b89c918"))
assertThrows[IllegalArgumentException](ECAdaptorSignature(
"03e6d51da7bc2bf24cf9dfd9acc6c4f0a3e74d8a6273ee5a573ed6818e3095b60903f33bc98f9d2ea3511f2e24f3358557c815abd7713c9318af9f4dfab4441898ecfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036414185b58980b8e6c54bd20616bdb9461dccd8eebb7d7e7c83a91452cc20edf53be5b0fe0db44dddaaafbe737678c684b6e89b9b4b679b1855aa6ed644498b89c918"))
}
}

View File

@ -83,7 +83,7 @@ class ECPublicKeyTest extends BitcoinSCryptoTest {
assert(pubkey.bytes == decompressed.bytes)
}
it must "be able to add infinity points" in {
it must "not be able to add opposite public keys" in {
val privkey = ECPrivateKey.freshPrivateKey
val pubkey1 = privkey.publicKey
val firstByte: Byte =
@ -93,9 +93,10 @@ class ECPublicKeyTest extends BitcoinSCryptoTest {
val pubkey2 =
ECPublicKey.fromBytes(ByteVector(firstByte) ++ pubkey1.bytes.tail)
val res1 = CryptoUtil.add(pubkey1, pubkey2)
assert(res1 == ECPublicKey.infinity)
assertThrows[Exception] {
val sumKey = CryptoUtil.add(pubkey1, pubkey2)
if (sumKey == ECPublicKey.infinity) fail()
}
val decompressedPubkey1 =
CryptoUtil.publicKeyConvert(pubkey1, compressed = false)
@ -103,10 +104,29 @@ class ECPublicKeyTest extends BitcoinSCryptoTest {
val decompressedPubkey2 =
CryptoUtil.publicKeyConvert(pubkey2, compressed = false)
val res2 =
CryptoUtil.add(decompressedPubkey1, decompressedPubkey2)
assertThrows[Exception] {
val sumKey = CryptoUtil.add(decompressedPubkey1, decompressedPubkey2)
if (sumKey == ECPublicKey.infinity) fail()
}
}
assert(res2 == ECPublicKey.infinity)
it must "correctly compress keys" in {
forAll(CryptoGenerators.privateKey) { privKey =>
val pubKey = privKey.publicKey
val pubKeyCompressed = pubKey.compressed
val pubKeyDecompressed = pubKey.decompressed
if (privKey.isCompressed) {
assert(pubKey == pubKeyCompressed)
} else {
assert(pubKey == pubKeyDecompressed)
}
assert(pubKeyCompressed.decompressed == pubKeyDecompressed)
assert(pubKeyCompressed.compressed == pubKeyCompressed)
assert(pubKeyDecompressed.compressed == pubKeyCompressed)
assert(pubKeyDecompressed.decompressed == pubKeyDecompressed)
}
}
}

View File

@ -22,6 +22,20 @@ class FieldElementTest extends BitcoinSCryptoTest {
}
}
it must "compute parity correctly" in {
val zeroBI = BigInt(0).bigInteger
val oneBI = BigInt(1).bigInteger
val twoBI = BigInt(2).bigInteger
forAll(CryptoGenerators.fieldElement) { fe =>
val isEven = fe.toBigInteger.mod(twoBI).equals(zeroBI)
val isOdd = fe.toBigInteger.mod(twoBI).equals(oneBI)
assert(fe.isEven == isEven)
assert(fe.isOdd == isOdd)
}
}
it must "add small numbers correctly" in {
forAll(CryptoGenerators.smallFieldElement,
CryptoGenerators.smallFieldElement) { case (fe1, fe2) =>

View File

@ -140,9 +140,12 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime {
entropy: ByteVector): ECDigitalSignature =
BouncyCastleUtil.signWithEntropy(bytes, privateKey, entropy)
override def secKeyVerify(privateKeyBytes: ByteVector): Boolean =
BouncyCastleCryptoParams.curve.getCurve
.isValidFieldElement(new BigInteger(1, privateKeyBytes.toArray))
override def secKeyVerify(privateKeyBytes: ByteVector): Boolean = {
val num = new BigInteger(1, privateKeyBytes.toArray)
BouncyCastleCryptoParams.curve.getCurve.isValidFieldElement(num) && num
.compareTo(BouncyCastleCryptoParams.curve.getN) < 0
}
override def verify(
publicKey: ECPublicKey,

View File

@ -141,8 +141,12 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime {
ByteVector(sum)
}
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey =
BouncycastleCryptoRuntime.add(pk1, pk2)
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = {
val summands = Array(pk1.bytes.toArray, pk2.bytes.toArray)
val sumKey = NativeSecp256k1.pubKeyCombine(summands, pk1.isCompressed)
ECPublicKey(ByteVector(sumKey))
}
override def pubKeyTweakAdd(
pubkey: ECPublicKey,
@ -162,40 +166,35 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime {
}
}
// TODO: add a native implementation
override def schnorrSign(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
auxRand: ByteVector): SchnorrDigitalSignature = {
// val sigBytes =
// NativeSecp256k1.schnorrSign(dataToSign.toArray,
// privateKey.bytes.toArray,
// auxRand.toArray)
// SchnorrDigitalSignature(ByteVector(sigBytes))
BouncycastleCryptoRuntime.schnorrSign(dataToSign, privateKey, auxRand)
val sigBytes =
NativeSecp256k1.schnorrSign(dataToSign.toArray,
privateKey.bytes.toArray,
auxRand.toArray)
SchnorrDigitalSignature(ByteVector(sigBytes))
}
// TODO: add a native implementation
override def schnorrSignWithNonce(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
nonceKey: ECPrivateKey): SchnorrDigitalSignature = {
// val sigBytes =
// NativeSecp256k1.schnorrSignWithNonce(dataToSign.toArray,
// privateKey.bytes.toArray,
// nonceKey.bytes.toArray)
// SchnorrDigitalSignature(ByteVector(sigBytes))
BouncycastleCryptoRuntime.schnorrSignWithNonce(dataToSign,
privateKey,
nonceKey)
val sigBytes =
NativeSecp256k1.schnorrSignWithNonce(dataToSign.toArray,
privateKey.bytes.toArray,
nonceKey.bytes.toArray)
SchnorrDigitalSignature(ByteVector(sigBytes))
}
// TODO: add a native implementation
override def schnorrVerify(
data: ByteVector,
schnorrPubKey: SchnorrPublicKey,
signature: SchnorrDigitalSignature): Boolean = {
BouncycastleCryptoRuntime.schnorrVerify(data, schnorrPubKey, signature)
NativeSecp256k1.schnorrVerify(signature.bytes.toArray,
data.toArray,
schnorrPubKey.bytes.toArray)
}
// TODO: add a native implementation
@ -210,60 +209,48 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime {
compressed)
}
// TODO: add a native implementation
override def adaptorSign(
key: ECPrivateKey,
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
// val sigWithProof = NativeSecp256k1.adaptorSign(key.bytes.toArray,
// adaptorPoint.bytes.toArray,
// msg.toArray)
// ECAdaptorSignature(ByteVector(sigWithProof))
BouncycastleCryptoRuntime.adaptorSign(key, adaptorPoint, msg)
msg: ByteVector,
auxRand: ByteVector): ECAdaptorSignature = {
val sig = NativeSecp256k1.adaptorSign(key.bytes.toArray,
adaptorPoint.bytes.toArray,
msg.toArray,
auxRand.toArray)
ECAdaptorSignature(ByteVector(sig))
}
// TODO: add a native implementation
override def adaptorComplete(
key: ECPrivateKey,
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
// val sigBytes =
// NativeSecp256k1.adaptorAdapt(key.bytes.toArray,
// adaptorSignature.adaptedSig.toArray)
// ECDigitalSignature.fromBytes(ByteVector(sigBytes))
BouncycastleCryptoRuntime.adaptorComplete(key, adaptorSignature)
val sigBytes =
NativeSecp256k1.adaptorAdapt(key.bytes.toArray,
adaptorSignature.bytes.toArray)
ECDigitalSignature.fromBytes(ByteVector(sigBytes))
}
// TODO: add a native implementation
override def extractAdaptorSecret(
signature: ECDigitalSignature,
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey): ECPrivateKey = {
// val secretBytes = NativeSecp256k1.adaptorExtractSecret(
// signature.bytes.toArray,
// adaptorSignature.adaptedSig.toArray,
// key.bytes.toArray)
//
// ECPrivateKey(ByteVector(secretBytes))
BouncycastleCryptoRuntime.extractAdaptorSecret(signature,
adaptorSignature,
key)
val secretBytes = NativeSecp256k1.adaptorExtractSecret(
signature.bytes.toArray,
adaptorSignature.bytes.toArray,
key.bytes.toArray)
ECPrivateKey(ByteVector(secretBytes))
}
// TODO: add a native implementation
override def adaptorVerify(
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey,
msg: ByteVector,
adaptorPoint: ECPublicKey): Boolean = {
// NativeSecp256k1.adaptorVerify(adaptorSignature.adaptedSig.toArray,
// key.bytes.toArray,
// msg.toArray,
// adaptorPoint.bytes.toArray,
// adaptorSignature.dleqProof.toArray)
BouncycastleCryptoRuntime.adaptorVerify(adaptorSignature,
key,
msg,
adaptorPoint)
NativeSecp256k1.adaptorVerify(adaptorSignature.bytes.toArray,
key.bytes.toArray,
msg.toArray,
adaptorPoint.bytes.toArray)
}
override def isValidSignatureEncoding(
@ -276,8 +263,18 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime {
override def sipHash(item: ByteVector, key: SipHashKey): Long =
BouncycastleCryptoRuntime.sipHash(item, key)
override def decodePoint(bytes: ByteVector): ECPoint =
BouncycastleCryptoRuntime.decodePoint(bytes)
override def decodePoint(bytes: ByteVector): ECPoint = {
val infinityPt: ByteVector = ByteVector.fromByte(0x00)
if (bytes == infinityPt) {
ECPointInfinity
} else {
val pointBytes = NativeSecp256k1.decompress(bytes.toArray)
val xBytes = pointBytes.tail.take(32)
val yBytes = pointBytes.takeRight(32)
ECPoint(xBytes, yBytes)
}
}
override def randomBytes(n: Int): ByteVector = {
BouncycastleCryptoRuntime.randomBytes(n)

View File

@ -2,10 +2,53 @@ package org.bitcoins.crypto
import scodec.bits.ByteVector
/** Implements the ECDSA Adaptor Signing Specification:
* https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md
*
* Note that the naming is not entirely consistent between the specification
* and this file in hopes of making this code more readable.
*
* The naming in this file more closely matches the naming in the secp256k1-zkp implementation:
* https://github.com/ElementsProject/secp256k1-zkp/tree/master/src/modules/ecdsa_adaptor
*
* Legend:
* x <> privKey
* X <> pubKey
* y <> adaptorSecret
* Y <> adaptorPoint/adaptor
* messageHash <> dataToSign/data/message
* R_a <> untweakedNonce
* R <> tweakedNonce
* proof <> (e, s)
*/
object AdaptorUtil {
import ECAdaptorSignature.{deserializePoint, serializePoint}
// Compute s' = k^-1 * (dataToSign + rx*privateKey)
/** Generates a secure random nonce as is done in BIP340:
* https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#default-signing
*/
def adaptorNonce(
message: ByteVector,
privKey: ECPrivateKey,
adaptorPoint: ECPublicKey,
algoName: String,
auxRand: ByteVector): FieldElement = {
val randHash = CryptoUtil.sha256ECDSAAdaptorAux(auxRand).bytes
val maskedKey = randHash.xor(privKey.bytes)
val bytesToHash = maskedKey ++ adaptorPoint.compressed.bytes ++ message
val nonceHash = algoName match {
case "DLEQ" => CryptoUtil.sha256DLEQ(bytesToHash)
case "ECDSAadaptor/non" => CryptoUtil.sha256ECDSAAdaptorNonce(bytesToHash)
case _: String => CryptoUtil.taggedSha256(bytesToHash, algoName)
}
FieldElement(nonceHash.bytes)
}
/** Computes s_a = inverse(k) * (dataToSign + rx*privateKey)
* which is the third from last step in
* https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#encrypted-signing
*/
private def adaptorSignHelper(
dataToSign: ByteVector,
k: FieldElement,
@ -14,7 +57,7 @@ object AdaptorUtil {
CryptoUtil.decodePoint(r) match {
case ECPointInfinity =>
throw new IllegalArgumentException(
s"Invalid point, got=${ECPointInfinity}")
s"Invalid point, got=$ECPointInfinity")
case point: ECPointImpl =>
val rx = FieldElement(point.x.toBigInteger)
val x = privateKey.fieldElement
@ -25,16 +68,17 @@ object AdaptorUtil {
}
}
/** Implements https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#encrypted-signing */
def adaptorSign(
privateKey: ECPrivateKey,
adaptorPoint: ECPublicKey,
dataToSign: ByteVector): ECAdaptorSignature = {
// Include dataToSign and adaptor in nonce derivation
val hash =
CryptoUtil.sha256(dataToSign ++ serializePoint(adaptorPoint))
val k = DLEQUtil.dleqNonceFunc(hash.bytes,
privateKey.fieldElement,
"ECDSAAdaptorNon")
dataToSign: ByteVector,
auxRand: ByteVector): ECAdaptorSignature = {
val k = adaptorNonce(dataToSign,
privateKey,
adaptorPoint,
"ECDSAadaptor/non",
auxRand)
if (k.isZero) {
throw new RuntimeException("Nonce cannot be zero.")
@ -44,16 +88,15 @@ object AdaptorUtil {
val tweakedNonce = adaptorPoint.tweakMultiply(k) // k*Y
// DLEQ_prove((G,R'),(Y, R))
val (proofS, proofE) =
DLEQUtil.dleqProve(k, adaptorPoint, "ECDSAAdaptorSig")
val (proofE, proofS) = DLEQUtil.dleqProve(k, adaptorPoint, auxRand)
// s' = k^-1*(m + rx*x)
val adaptedSig = adaptorSignHelper(dataToSign, k, tweakedNonce, privateKey)
ECAdaptorSignature(tweakedNonce, adaptedSig, untweakedNonce, proofS, proofE)
ECAdaptorSignature(tweakedNonce, untweakedNonce, adaptedSig, proofE, proofS)
}
// Compute R'x = s^-1 * (msg*G + rx*pubKey) = s^-1 * (msg + rx*privKey) * G
/** Computes R = inverse(s) * (msg*G + rx*pubKey) = inverse(s) * (msg + rx*privKey) * G */
private def adaptorVerifyHelper(
rx: FieldElement,
s: FieldElement,
@ -63,40 +106,33 @@ object AdaptorUtil {
val untweakedPoint =
m.getPublicKey.add(pubKey.tweakMultiply(rx)).tweakMultiply(s.inverse)
FieldElement(untweakedPoint.bytes.tail)
FieldElement(untweakedPoint.compressed.bytes.tail)
}
/** https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#encryption-verification */
def adaptorVerify(
adaptorSig: ECAdaptorSignature,
pubKey: ECPublicKey,
data: ByteVector,
adaptor: ECPublicKey): Boolean = {
val untweakedNonce = deserializePoint(adaptorSig.dleqProof.take(33))
val proofS = FieldElement(adaptorSig.dleqProof.drop(33).take(32))
val proofR = FieldElement(adaptorSig.dleqProof.drop(65))
val tweakedNonce = deserializePoint(adaptorSig.adaptedSig.take(33))
val adaptedSig = FieldElement(adaptorSig.adaptedSig.drop(33))
val validProof = DLEQUtil.dleqVerify(
"ECDSAAdaptorSig",
proofS,
proofR,
untweakedNonce,
adaptorSig.dleqProofS,
adaptorSig.dleqProofE,
adaptorSig.untweakedNonce,
adaptor,
tweakedNonce
adaptorSig.tweakedNonce
)
if (validProof) {
val tweakedNoncex = FieldElement(tweakedNonce.bytes.tail)
val untweakedNoncex = FieldElement(untweakedNonce.bytes.tail)
val tweakedNoncex = FieldElement(adaptorSig.tweakedNonce.bytes.tail)
val untweakedNoncex = FieldElement(adaptorSig.untweakedNonce.bytes.tail)
if (tweakedNoncex.isZero || untweakedNoncex.isZero) {
false
} else {
val untweakedRx =
adaptorVerifyHelper(tweakedNoncex, adaptedSig, pubKey, data)
adaptorVerifyHelper(tweakedNoncex, adaptorSig.adaptedS, pubKey, data)
untweakedRx == untweakedNoncex
}
@ -105,25 +141,32 @@ object AdaptorUtil {
}
}
/** Implements https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#decryption */
def adaptorComplete(
adaptorSecret: ECPrivateKey,
adaptedSig: ByteVector): ECDigitalSignature = {
val tweakedNonce: ECPublicKey =
ECAdaptorSignature.deserializePoint(adaptedSig.take(33))
val rx = FieldElement(tweakedNonce.bytes.tail)
val adaptedS: FieldElement = FieldElement(adaptedSig.drop(33))
val correctedS = adaptedS.multInv(adaptorSecret.fieldElement)
adaptorSig: ECAdaptorSignature): ECDigitalSignature = {
val rx = FieldElement(adaptorSig.tweakedNonce.bytes.tail)
val correctedS = adaptorSig.adaptedS.multInv(adaptorSecret.fieldElement)
val sig = ECDigitalSignature.fromRS(BigInt(rx.toBigInteger),
BigInt(correctedS.toBigInteger))
DERSignatureUtil.lowS(sig)
}
/** Implements https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#key-recovery */
def extractAdaptorSecret(
sig: ECDigitalSignature,
adaptorSig: ECAdaptorSignature,
adaptor: ECPublicKey): ECPrivateKey = {
require(adaptorSig.tweakedNonce.bytes.tail == sig.rBytes,
"Adaptor signature must be related to signature")
val secretOrNeg = adaptorSig.adaptedS.multInv(FieldElement(sig.s))
require(
secretOrNeg.getPublicKey.compressed.bytes.tail == adaptor.compressed.bytes.tail,
s"Invalid inputs: $sig, $adaptorSig, and $adaptor")
if (secretOrNeg.getPublicKey == adaptor) {
secretOrNeg.toPrivateKey
} else {

View File

@ -64,6 +64,46 @@ trait CryptoRuntime {
)
}
def sha256SchnorrChallenge(bytes: ByteVector): Sha256Digest = {
sha256(schnorrChallengeTagBytes ++ bytes)
}
// The tag "DLEQ"
private lazy val dleqTagBytes = {
ByteVector
.fromValidHex(
"4ba8f2d94f2cbc10e555e8befa96b62f14cd7396aff9cdaf0f638c673166d5274ba8f2d94f2cbc10e555e8befa96b62f14cd7396aff9cdaf0f638c673166d527"
)
}
def sha256DLEQ(bytes: ByteVector): Sha256Digest = {
sha256(dleqTagBytes ++ bytes)
}
// The tag "ECDSAadaptor/aux"
private lazy val ecdsaAdaptorAuxTagBytes = {
ByteVector
.fromValidHex(
"bffb017fd37ef53b393ddd2e2a168c340b3ee5dd4e53c89de63a74b1d9061b0dbffb017fd37ef53b393ddd2e2a168c340b3ee5dd4e53c89de63a74b1d9061b0d"
)
}
def sha256ECDSAAdaptorAux(bytes: ByteVector): Sha256Digest = {
sha256(ecdsaAdaptorAuxTagBytes ++ bytes)
}
// The tag "ECDSAadaptor/non"
private lazy val ecdsaAdaptorNonceTagBytes = {
ByteVector
.fromValidHex(
"848f232fbe7f662cf520de8e335986eaf63a6617dd2a8b28f3a180a681d6f161848f232fbe7f662cf520de8e335986eaf63a6617dd2a8b28f3a180a681d6f161"
)
}
def sha256ECDSAAdaptorNonce(bytes: ByteVector): Sha256Digest = {
sha256(ecdsaAdaptorNonceTagBytes ++ bytes)
}
// The tag "DLC/oracle/attestation/v0"
private val dlcAttestationTagBytes = {
ByteVector
@ -72,10 +112,6 @@ trait CryptoRuntime {
)
}
def sha256SchnorrChallenge(bytes: ByteVector): Sha256Digest = {
sha256(schnorrChallengeTagBytes ++ bytes)
}
def sha256DLCAttestation(bytes: ByteVector): Sha256Digest = {
sha256(dlcAttestationTagBytes ++ bytes)
}
@ -265,14 +301,15 @@ trait CryptoRuntime {
def adaptorSign(
key: ECPrivateKey,
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
AdaptorUtil.adaptorSign(key, adaptorPoint, msg)
msg: ByteVector,
auxRand: ByteVector): ECAdaptorSignature = {
AdaptorUtil.adaptorSign(key, adaptorPoint, msg, auxRand)
}
def adaptorComplete(
key: ECPrivateKey,
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
AdaptorUtil.adaptorComplete(key, adaptorSignature.adaptedSig)
AdaptorUtil.adaptorComplete(key, adaptorSignature)
}
def extractAdaptorSecret(

View File

@ -161,8 +161,9 @@ trait CryptoUtil extends CryptoRuntime {
override def adaptorSign(
key: ECPrivateKey,
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature =
cryptoRuntime.adaptorSign(key, adaptorPoint, msg)
msg: ByteVector,
auxRand: ByteVector): ECAdaptorSignature =
cryptoRuntime.adaptorSign(key, adaptorPoint, msg, auxRand)
override def adaptorComplete(
key: ECPrivateKey,

View File

@ -2,8 +2,29 @@ package org.bitcoins.crypto
import scodec.bits.ByteVector
/** Implements the DLEQ ZKP Specification:
* https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md
*
* Note that the naming is not entirely consistent between the specification
* and this file in hopes of making this code more readable.
*
* The naming in this file more closely matches the naming in the secp256k1-zkp implementation:
* https://github.com/ElementsProject/secp256k1-zkp/tree/master/src/modules/ecdsa_adaptor
*
* Legend:
* x <> fe
* X <> p1/point
* y <> adaptorSecret
* Y <> adaptorPoint/adaptor
* Z <> p2/tweakedPoint
* a <> k
* A_G <> r1
* A_Y <> r2
* b <> e
* c <> s
* proof <> (e, s)
*/
object DLEQUtil {
import ECAdaptorSignature.serializePoint
def dleqPair(
fe: FieldElement,
@ -14,55 +35,62 @@ object DLEQUtil {
(point, tweakedPoint)
}
def dleqNonceFunc(
hash: ByteVector,
/** Computes the nonce for dleqProve as specified in
* https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#proving
*/
def dleqNonce(
fe: FieldElement,
algoName: String): FieldElement = {
val kBytes =
CryptoUtil.taggedSha256(fe.bytes ++ hash, algoName).bytes
FieldElement(kBytes)
adaptorPoint: ECPublicKey,
point: ECPublicKey,
tweakedPoint: ECPublicKey,
auxRand: ByteVector): FieldElement = {
val hash = CryptoUtil
.sha256(point.compressed.bytes ++ tweakedPoint.compressed.bytes)
.bytes
AdaptorUtil.adaptorNonce(hash,
fe.toPrivateKey,
adaptorPoint,
"DLEQ",
auxRand)
}
/** Computes the challenge hash value for dleqProve as specified in
* https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#proving
*/
def dleqChallengeHash(
algoName: String,
adaptorPoint: ECPublicKey,
r1: ECPublicKey,
r2: ECPublicKey,
p1: ECPublicKey,
p2: ECPublicKey): ByteVector = {
CryptoUtil
.taggedSha256(
serializePoint(adaptorPoint) ++ serializePoint(r1) ++ serializePoint(
r2) ++ serializePoint(p1) ++ serializePoint(p2),
algoName)
.sha256DLEQ(
p1.compressed.bytes ++ adaptorPoint.compressed.bytes ++
p2.compressed.bytes ++ r1.compressed.bytes ++ r2.compressed.bytes)
.bytes
}
/** Proves that the DLOG_G(R') = DLOG_Y(R) (= fe)
* For a full description, see https://cs.nyu.edu/courses/spring07/G22.3220-001/lec3.pdf
* @see https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#proving
*/
def dleqProve(
fe: FieldElement,
adaptorPoint: ECPublicKey,
algoName: String): (FieldElement, FieldElement) = {
auxRand: ByteVector): (FieldElement, FieldElement) = {
require(!fe.isZero, "Input field element cannot be zero.")
// (fe*G, fe*Y)
val (p1, p2) = dleqPair(fe, adaptorPoint)
// hash(Y || fe*G || fe*Y)
val hash =
CryptoUtil
.sha256(
serializePoint(adaptorPoint) ++ serializePoint(p1) ++ serializePoint(
p2))
.bytes
val k = dleqNonceFunc(hash, fe, algoName)
val k = dleqNonce(fe, adaptorPoint, p1, p2, auxRand)
if (k.isZero) {
throw new RuntimeException("Nonce cannot be zero.")
}
val r1 = k.getPublicKey
val r2 = adaptorPoint.tweakMultiply(k)
val (r1, r2) = dleqPair(k, adaptorPoint)
// Hash all components to get a challenge (this is the trick that turns
// interactive ZKPs into non-interactive ZKPs, using hash assumptions)
@ -72,7 +100,7 @@ object DLEQUtil {
// this hash as the challenge to the prover as loosely speaking this
// should only be game-able if the prover can reverse hash functions.
val challengeHash =
dleqChallengeHash(algoName, adaptorPoint, r1, r2, p1, p2)
dleqChallengeHash(adaptorPoint, r1, r2, p1, p2)
val e = FieldElement(challengeHash)
// s = k + fe*challenge. This proof works because then k = fe*challenge - s
@ -81,12 +109,13 @@ object DLEQUtil {
// if R = y*R' which is what we are trying to prove.
val s = fe.multiply(e).add(k)
(s, e)
(e, s)
}
/** Verifies a proof that the DLOG_G of P1 equals the DLOG_adaptor of P2 */
/** Verifies a proof that the DLOG_G of P1 equals the DLOG_adaptor of P2
* @see https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#verifying
*/
def dleqVerify(
algoName: String,
s: FieldElement,
e: FieldElement,
p1: ECPublicKey,
@ -94,7 +123,7 @@ object DLEQUtil {
p2: ECPublicKey): Boolean = {
val r1 = p1.tweakMultiply(e.negate).add(s.getPublicKey)
val r2 = p2.tweakMultiply(e.negate).add(adaptor.tweakMultiply(s))
val challengeHash = dleqChallengeHash(algoName, adaptor, r1, r2, p1, p2)
val challengeHash = dleqChallengeHash(adaptor, r1, r2, p1, p2)
challengeHash == e.bytes
}

View File

@ -3,22 +3,19 @@ package org.bitcoins.crypto
import scodec.bits.ByteVector
case class ECAdaptorSignature(bytes: ByteVector) extends NetworkElement {
require(
bytes.length == 162,
s"Adaptor signature must have 65 byte sig and 97 byte dleq proof, got $bytes")
require(bytes.length == 162,
s"Adaptor signature must be 162 bytes, got $bytes")
val (adaptedSig: ByteVector, dleqProof: ByteVector) = bytes.splitAt(65)
val (adaptedSig: ByteVector, dleqProof: ByteVector) = bytes.splitAt(98)
val tweakedNonce: ECPublicKey =
ECAdaptorSignature.deserializePoint(adaptedSig.take(33))
val adaptedS: FieldElement = FieldElement(adaptedSig.drop(33))
val tweakedNonce: ECPublicKey = ECPublicKey(adaptedSig.take(33))
val untweakedNonce: ECPublicKey = ECPublicKey(adaptedSig.drop(33).take(33))
val adaptedS: FieldElement = FieldElement(adaptedSig.drop(66))
require(!adaptedS.isZero, "Adapted signature cannot be zero.")
val untweakedNonce: ECPublicKey =
ECAdaptorSignature.deserializePoint(dleqProof.take(33))
val dleqProofS: FieldElement = FieldElement(dleqProof.drop(33).take(32))
val dleqProofE: FieldElement = FieldElement(dleqProof.drop(65))
val dleqProofE: FieldElement = FieldElement(dleqProof.take(32))
val dleqProofS: FieldElement = FieldElement(dleqProof.drop(32))
require(
tweakedNonce.bytes.nonEmpty && CryptoUtil.isValidPubKey(tweakedNonce.bytes),
@ -36,33 +33,23 @@ object ECAdaptorSignature extends Factory[ECAdaptorSignature] {
def apply(
tweakedNonce: ECPublicKey,
adaptedS: FieldElement,
untweakedNonce: ECPublicKey,
dleqProofS: FieldElement,
dleqProofE: FieldElement): ECAdaptorSignature = {
adaptedS: FieldElement,
dleqProofE: FieldElement,
dleqProofS: FieldElement): ECAdaptorSignature = {
fromBytes(
serializePoint(tweakedNonce) ++ adaptedS.bytes ++ serializePoint(
untweakedNonce) ++ dleqProofS.bytes ++ dleqProofE.bytes
tweakedNonce.compressed.bytes ++ untweakedNonce.compressed.bytes ++
adaptedS.bytes ++ dleqProofE.bytes ++ dleqProofS.bytes
)
}
lazy val dummy: ECAdaptorSignature = {
ECAdaptorSignature(
ECPublicKey.freshPublicKey,
ECPrivateKey.freshPrivateKey.fieldElement,
ECPublicKey.freshPublicKey,
ECPrivateKey.freshPrivateKey.fieldElement,
ECPrivateKey.freshPrivateKey.fieldElement,
ECPrivateKey.freshPrivateKey.fieldElement
)
}
def serializePoint(point: ECPublicKey): ByteVector = {
val (sign, xCoor) = point.bytes.splitAt(1)
sign.map(b => (b & 0x01).toByte) ++ xCoor
}
def deserializePoint(point: ByteVector): ECPublicKey = {
val (sign, xCoor) = point.splitAt(1)
ECPublicKey(sign.map(b => (b | 0x02).toByte) ++ xCoor)
}
}

View File

@ -53,7 +53,15 @@ sealed abstract class ECPrivateKey
def adaptorSign(
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
CryptoUtil.adaptorSign(this, adaptorPoint, msg)
val auxRand = ECPrivateKey.freshPrivateKey.bytes
adaptorSign(adaptorPoint, msg, auxRand)
}
def adaptorSign(
adaptorPoint: ECPublicKey,
msg: ByteVector,
auxRand: ByteVector): ECAdaptorSignature = {
CryptoUtil.adaptorSign(this, adaptorPoint, msg, auxRand)
}
def completeAdaptorSignature(
@ -233,6 +241,17 @@ sealed abstract class ECPublicKey extends BaseECKey {
def decompressed: ECPublicKey =
CryptoUtil.decompressed(this)
def compressed: ECPublicKey = {
if (isCompressed || bytes == ByteVector.fromByte(0x00)) {
this
} else {
val key = if (bytes.length == 65) this else decompressed
val (x, y) = key.bytes.tail.splitAt(32)
val leadByte = if (FieldElement(y).isEven) 2.toByte else 3.toByte
ECPublicKey(x.+:(leadByte))
}
}
/** Adds this ECPublicKey to another as points and returns the resulting ECPublicKey.
*
* Note: if this ever becomes a bottleneck, secp256k1_ec_pubkey_combine should

View File

@ -21,6 +21,12 @@ case class FieldElement(bytes: ByteVector) extends NetworkElement {
def isZero: Boolean = bytes.toArray.forall(_ == 0.toByte)
def isEven: Boolean = {
(bytes.last & 0x01) == 0
}
def isOdd: Boolean = !isEven
def toPrivateKey: ECPrivateKey =
if (!isZero) {
privKeyT.get

View File

@ -3,7 +3,7 @@ id: jni-modify
title: Adding to Secp256k1 JNI
---
Bitcoin-S uses a Java Native Interface (JNI) to execute functions in [secp256k1](https://github.com/bitcoin-core/secp256k1) from java/scala. The native java bindings used to be a part of the secp256k1 library that was maintained by bitcoin-core, but it was [removed in October 2019](https://github.com/bitcoin-core/secp256k1/pull/682). We maintain a [fork of secp256k1](https://github.com/bitcoin-s/secp256k1) which forks off of bitcoin-core's `master` but re-introduces the jni. This is also the easiest way to add functionality from new projects such as [Schnorr signatures](https://github.com/bitcoin-core/secp256k1/pull/558) and [ECDSA adaptor signatures](https://github.com/jonasnick/secp256k1/pull/14) by rebasing the bitcoin-s branch with the JNI on top of these experimental branches. That said, it is quite tricky to hook up new functionality in secp256k1 into bitcoin-s and specifically `NativeSecp256k1.java`. The following is a description of this process.
Bitcoin-S uses a Java Native Interface (JNI) to execute functions in [secp256k1-zkp](https://github.com/ElementsProject/secp256k1-zkp) from java/scala. The native java bindings used to be a part of the secp256k1 library that was maintained by bitcoin-core, but it was [removed in October 2019](https://github.com/bitcoin-core/secp256k1/pull/682). We maintain a [fork of secp256k1](https://github.com/bitcoin-s/secp256k1) which forks off of bitcoin-core's `master` but re-introduces the jni. This is also the easiest way to add functionality from new projects such as [Schnorr signatures](https://github.com/bitcoin-core/secp256k1/pull/558) and [ECDSA adaptor signatures](https://github.com/ElementsProject/secp256k1-zkp/pull/117) by rebasing the bitcoin-s branch with the JNI on top of these experimental branches. That said, it is quite tricky to hook up new functionality in secp256k1 into bitcoin-s and specifically `NativeSecp256k1.java`. The following is a description of this process.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@ -354,7 +354,7 @@ I normally first build the C binaries and add to Bitcoin-S before coming back to
1. Translate `NativeSecp256k1` and `NativeSecp256k1Test` to jni project
By translate I mean to say that you must copy the functions from those files to the corresponding files in the `bitcoin-s/secp256k1jni` project. For tests this will require changing calls to `DatatypeConverter` to either `toByteArray` or `toHex` as well as changing the method to make it non-`static` as well as adding the `@Test` annotation above the method (rather than adding to a `main` method).
By translate I mean to say that you must copy the functions from those files to the corresponding files in the `bitcoin-s/secp256k1jni` project. For tests this will require changing the methods to be non-`static` as well as adding the `@Test` annotation above each method (rather than adding to a `main` method).
2. Configure and build `secp256k1`
@ -379,8 +379,8 @@ I normally first build the C binaries and add to Bitcoin-S before coming back to
```bashrc
./autogen.sh
./configure --enable-jni --enable-experimental --enable-module-ecdh
make
./configure --enable-jni --enable-experimental --enable-module-ecdh --enable-module-schnorrsig --enable-module-ecdsa-adaptor
make CFLAGS="-std=c99"
make check
make check-java
```
@ -398,7 +398,7 @@ I normally first build the C binaries and add to Bitcoin-S before coming back to
```bashrc
echo "LDFLAGS = -no-undefined" >> Makefile.am
./configure --host=x86_64-w64-mingw32 --enable-experimental --enable-module_ecdh --enable-jni && make clean && make CFLAGS="-std=c99"
./configure --host=x86_64-w64-mingw32 --enable-jni --enable-experimental --enable-module-ecdh --enable-module-schnorrsig --enable-module-ecdsa-adaptor && make clean && make CFLAGS="-std=c99"
```
There may be some errors that can be ignored:

@ -1 +0,0 @@
Subproject commit e2e7cf2fba66c8c3bff33e2bb671e69e2184c597

1
secp256k1-zkp Submodule

@ -0,0 +1 @@
Subproject commit 6dd724b72bb2d47de514eb92e96c6ae6d26ae160

View File

@ -28,7 +28,7 @@ age=0
revision=0
# Is this an already installed library?
installed=yes
installed=no
# Should we warn about portability when linking against -modules?
shouldnotlink=no

View File

@ -1 +0,0 @@
libsecp256k1.so.0.0.0

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,41 @@
# libsecp256k1_jni.la - a libtool library file
# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-2
#
# Please DO NOT delete this file!
# It is necessary for linking the library.
# The name that we can dlopen(3).
dlname=''
# Names of this library.
library_names=''
# The name of the static archive.
old_library='libsecp256k1_jni.a'
# Linker flags that cannot go in dependency_libs.
inherited_linker_flags=''
# Libraries that this one depends upon.
dependency_libs=''
# Names of additional weak libraries provided by this library
weak_library_names=''
# Version information for libsecp256k1_jni.
current=
age=
revision=
# Is this an already installed library?
installed=no
# Should we warn about portability when linking against -modules?
shouldnotlink=no
# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''
# Directory that this library needs to be installed in:
libdir=''

2
secp256k1jni/natives/osx_64/libsecp256k1.la Normal file → Executable file
View File

@ -28,7 +28,7 @@ age=0
revision=0
# Is this an already installed library?
installed=no
installed=yes
# Should we warn about portability when linking against -modules?
shouldnotlink=no

View File

@ -407,6 +407,55 @@ public class NativeSecp256k1 {
return pubArr;
}
/**
* libsecp256k1 PubKey Combine - Add pubkeys together
*
* @param pubkeys array of ECDSA Public key, 33 or 65 bytes each
* @param compressed should the output public key be compressed
*/
public static byte[] pubKeyCombine(byte[][] pubkeys, boolean compressed) throws AssertFailException{
int numKeys = pubkeys.length;
checkArgument(numKeys > 0);
int pubkeyLength = pubkeys[0].length;
checkArgument(pubkeyLength == 33 || pubkeyLength == 65);
for (byte[] pubkey : pubkeys) {
checkArgument(pubkey.length == pubkeyLength);
}
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < numKeys * pubkeyLength) {
byteBuff = ByteBuffer.allocateDirect(numKeys * pubkeyLength);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
safeRewind(byteBuff);
for (byte[] pubkey : pubkeys) {
byteBuff.put(pubkey);
}
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_ec_pubkey_combine(byteBuff,Secp256k1Context.getContext(), pubkeyLength, numKeys, compressed);
} finally {
r.unlock();
}
byte[] pubArr = retByteArray[0];
int pubLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF;
int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue();
assertEquals(pubArr.length, pubLen, "Got bad pubkey length.");
assertEquals(retVal, 1, "Failed return value check.");
return pubArr;
}
/**
* libsecp256k1 Decompress - Parse and decompress a variable-length pub key
*
@ -515,6 +564,266 @@ public class NativeSecp256k1 {
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();
}
}
public static byte[] adaptorSign(byte[] seckey, byte[] adaptorPoint, byte[] data, byte[] auxRand) throws AssertFailException{
checkArgument(seckey.length == 32 &&
data.length == 32 &&
(adaptorPoint.length == 33 || adaptorPoint.length == 65) &&
auxRand.length == 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + 32 + adaptorPoint.length + 32) {
byteBuff = ByteBuffer.allocateDirect(32 + 32 + adaptorPoint.length + 32);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(seckey);
byteBuff.put(adaptorPoint);
byteBuff.put(data);
byteBuff.put(auxRand);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_ecdsa_adaptor_sign(byteBuff, Secp256k1Context.getContext(), adaptorPoint.length);
} finally {
r.unlock();
}
byte[] sigArr = retByteArray[0];
int retVal = new BigInteger(new byte[] { retByteArray[1][0] }).intValue();
if (retVal == 0) {
return new byte[]{};
} else {
return sigArr;
}
}
public static boolean adaptorVerify(byte[] adaptorSig, byte[] pubKey, byte[] data, byte[] adaptorPoint) throws AssertFailException{
checkArgument(data.length == 32 &&
adaptorSig.length == 162 &&
(pubKey.length == 33 || pubKey.length == 65) &&
adaptorPoint.length == pubKey.length);
ByteBuffer byteBuff = nativeECDSABuffer.get();
int buffLen = 32 + 162 + pubKey.length + adaptorPoint.length;
if (byteBuff == null || byteBuff.capacity() < buffLen) {
byteBuff = ByteBuffer.allocateDirect(buffLen);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(adaptorSig);
byteBuff.put(pubKey);
byteBuff.put(data);
byteBuff.put(adaptorPoint);
r.lock();
try {
return secp256k1_ecdsa_adaptor_sig_verify(byteBuff, Secp256k1Context.getContext(), pubKey.length) == 1;
} finally {
r.unlock();
}
}
public static byte[] adaptorAdapt(byte[] adaptorSec, byte[] adaptorSig) throws AssertFailException{
checkArgument(adaptorSec.length == 32 && adaptorSig.length == 162);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + 162) {
byteBuff = ByteBuffer.allocateDirect(32 + 162);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(adaptorSec);
byteBuff.put(adaptorSig);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_ecdsa_adaptor_adapt(byteBuff, Secp256k1Context.getContext());
} finally {
r.unlock();
}
byte[] sigArr = retByteArray[0];
int sigLen = new BigInteger(new byte[] { retByteArray[1][0] }).intValue();
int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue();
assertEquals(sigArr.length, sigLen, "Got bad signature length.");
return retVal == 0 ? new byte[0] : sigArr;
}
public static byte[] adaptorExtractSecret(byte[] sig, byte[] adaptorSig, byte[] adaptor) throws AssertFailException{
checkArgument(sig.length <= 520 && (adaptor.length == 33 || adaptor.length == 65) && adaptorSig.length == 162);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < sig.length + adaptor.length + 162) {
byteBuff = ByteBuffer.allocateDirect(sig.length + adaptor.length + 162);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(sig);
byteBuff.put(adaptorSig);
byteBuff.put(adaptor);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_ecdsa_adaptor_extract_secret(byteBuff, Secp256k1Context.getContext(), sig.length, adaptor.length);
} finally {
r.unlock();
}
byte[] sigArr = retByteArray[0];
int retVal = new BigInteger(new byte[] { retByteArray[1][0] }).intValue();
return retVal == 0 ? new byte[0] : sigArr;
}
/**
* libsecp256k1 randomize - updates the context randomization
*
@ -540,7 +849,6 @@ public class NativeSecp256k1 {
}
}
/**
* This helper method is needed to resolve issue 1524 on bitcoin-s
* This is because the API changed for ByteBuffer between jdks < 9 and jdk >= 9
@ -578,8 +886,25 @@ public class NativeSecp256k1 {
private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context, boolean compressed);
private static native byte[][] secp256k1_ec_pubkey_combine(ByteBuffer byteBuff, long context, int pubLen, int numKeys, boolean compressed);
private static native byte[][] secp256k1_ec_pubkey_decompress(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);
private static native byte[][] secp256k1_ecdsa_adaptor_sign(ByteBuffer byteBuff, long context, int adaptorLen);
private static native int secp256k1_ecdsa_adaptor_sig_verify(ByteBuffer byteBuff, long context, int pubKeyLen);
private static native byte[][] secp256k1_ecdsa_adaptor_adapt(ByteBuffer byteBuff, long context);
private static native byte[][] secp256k1_ecdsa_adaptor_extract_secret(ByteBuffer byteBuff, long context, int sigLen, int adaptorLen);
}

View File

@ -209,6 +209,24 @@ public class NativeSecp256k1Test {
assertEquals( result, true, "testRandomize");
}
/**
* This tests public key addition
*/
@Test
public void testPubKeyCombine() throws AssertFailException {
byte[] pub1 = toByteArray("023F75B56D0695CA76261F0BCD0B31B11B86D150AADED8D31AAA86561E52622A99");
byte[] pub2 = toByteArray("0341981FEC932450DD70F8BE107BA43F902B9F17923E8D2C487F69026E62703FBA");
byte[] pub3 = toByteArray("02BF0298EAFAE04E45789BC5C3419BF718AFDBC7951EDEB1BFE4073CEE06B40C20");
byte[][] pubs = { pub1, pub2, pub3 };
byte[] resultArr = NativeSecp256k1.pubKeyCombine( pubs , false);
String pubkeyString = toHex(resultArr);
byte[] resultArrCompressed = NativeSecp256k1.pubKeyCombine( pubs , true);
String pubkeyStringCompressed = toHex(resultArrCompressed);
assertEquals(pubkeyString , "0436456D9DB1ACC4B3DB73CA77AA760D6BF7163A7432A3B1F189FB5C4EC2344A622DBB33694B9F067C48F09ED860AD68D12E5355B08157C985E11AA016944B3E7E" , "testPubKeyCombine");
assertEquals(pubkeyStringCompressed , "0236456D9DB1ACC4B3DB73CA77AA760D6BF7163A7432A3B1F189FB5C4EC2344A62" , "testPubKeyCombine (compressed)");
}
/**
* Tests that we can decompress valid public keys
* @throws AssertFailException
@ -258,6 +276,108 @@ public class NativeSecp256k1Test {
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 = "6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE5077C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8";
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 = "5DA618C1936EC728E5CCFF29207F1680DCF4146370BDCFAB0039951B91E3637A958E91D68537D1F6F19687CEC1FD5DB1D83DA56EF3ADE1F3C611BABD7D08AF42";
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 = "03735ACF82EEF9DA1540EFB07A68251D5476DABB11AC77054924ECCBB4121885E8";
assertEquals(pointStr, expectedPoint, "testSchnorrComputeSigPoint");
}
*/
@Test
public void testSchnorrVerify() throws AssertFailException{
byte[] sig = toByteArray("6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE5077C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8");
byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] pubx = toByteArray("B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390");
boolean result = NativeSecp256k1.schnorrVerify(sig, data, pubx);
assertEquals(result, true, "testSchnorrVerify");
}
@Test
public void testAdaptorSign() throws AssertFailException {
byte[] msg = toByteArray("9BB3A7C4CF1C10C225FF6C78867352559C6B0AD43DA0744BF0744A1C520FB03A");
byte[] adaptor = toByteArray("038B3E8F09A59F17A3CC7B8BDF04038CD102CDBE6A9FFCFFF1A7A8EA749826F48D");
byte[] seckey = toByteArray("00033065FBFD7BABE601089E75C463D16E8FB8C731ED520DE425585099E27F70");
byte[] auxRand = toByteArray("580E40DCE3C772ED359FE6D9C1459702016845F1981ECC04E371B00E7B851ACA");
String expectedAdaptorSig = "0389CFE2E8861E763EA33C64C5034C292011A34CB8EAEC5376200B9D7D35F9304C03D951170788AEC19A94100ED8381040BD22C5A60F7264B53E51567D36A3DA9F5F30DC49EBC212AEED4535EA39C8B8F9418AFC8B4E8899C8C978B4440C4EC4474FD90760B4037045C2DA538AB237B1DCE99209A0093949A472F24F44C6A7F25084B47ECB9F342D5E5249BFFB3C9B6E3BCD5E3356558615CD0B256C53E13F74D753";
byte[] resultArr = NativeSecp256k1.adaptorSign(seckey, adaptor, msg, auxRand);
assertEquals(resultArr.length, 162, "testAdaptorSign");
String adaptorSig = toHex(resultArr);
assertEquals(adaptorSig, expectedAdaptorSig, "testAdaptorSign");
}
@Test
public void testAdaptorVeirfy() throws AssertFailException {
byte[] msg = toByteArray("8131E6F4B45754F2C90BD06688CEEABC0C45055460729928B4EECF11026A9E2D");
byte[] adaptorSig = toByteArray("03424D14A5471C048AB87B3B83F6085D125D5864249AE4297A57C84E74710BB6730223F325042FCE535D040FEE52EC13231BF709CCD84233C6944B90317E62528B2527DFF9D659A96DB4C99F9750168308633C1867B70F3A18FB0F4539A1AECEDCD1FC0148FC22F36B6303083ECE3F872B18E35D368B3958EFE5FB081F7716736CCB598D269AA3084D57E1855E1EA9A45EFC10463BBF32AE378029F5763CEB40173F");
byte[] adaptor = toByteArray("02C2662C97488B07B6E819124B8989849206334A4C2FBDF691F7B34D2B16E9C293");
byte[] pubkey = toByteArray("035BE5E9478209674A96E60F1F037F6176540FD001FA1D64694770C56A7709C42C");
boolean result = NativeSecp256k1.adaptorVerify(adaptorSig, pubkey, msg, adaptor);
assertEquals( result, true , "testAdaptorVeirfy");
}
@Test
public void testAdaptorAdapt() throws AssertFailException {
byte[] secret = toByteArray("0B2ABA63B885A0F0E96FA0F303920C7FB7431DDFA94376AD94D969FBF4109DC8");
byte[] adaptorSig = toByteArray("03424D14A5471C048AB87B3B83F6085D125D5864249AE4297A57C84E74710BB6730223F325042FCE535D040FEE52EC13231BF709CCD84233C6944B90317E62528B2527DFF9D659A96DB4C99F9750168308633C1867B70F3A18FB0F4539A1AECEDCD1FC0148FC22F36B6303083ECE3F872B18E35D368B3958EFE5FB081F7716736CCB598D269AA3084D57E1855E1EA9A45EFC10463BBF32AE378029F5763CEB40173F");
byte[] resultArr = NativeSecp256k1.adaptorAdapt(secret, adaptorSig);
String expectedSig = "30440220424D14A5471C048AB87B3B83F6085D125D5864249AE4297A57C84E74710BB673022029E80E0EE60E57AF3E625BBAE1672B1ECAA58EFFE613426B024FA1621D903394";
String sigString = toHex(resultArr);
assertEquals(sigString , expectedSig , "testAdaptorAdapt");
}
@Test
public void testAdaptorExtractSecret() throws AssertFailException {
byte[] sig = toByteArray("30440220424D14A5471C048AB87B3B83F6085D125D5864249AE4297A57C84E74710BB673022029E80E0EE60E57AF3E625BBAE1672B1ECAA58EFFE613426B024FA1621D903394");
byte[] adaptorSig = toByteArray("03424D14A5471C048AB87B3B83F6085D125D5864249AE4297A57C84E74710BB6730223F325042FCE535D040FEE52EC13231BF709CCD84233C6944B90317E62528B2527DFF9D659A96DB4C99F9750168308633C1867B70F3A18FB0F4539A1AECEDCD1FC0148FC22F36B6303083ECE3F872B18E35D368B3958EFE5FB081F7716736CCB598D269AA3084D57E1855E1EA9A45EFC10463BBF32AE378029F5763CEB40173F");
byte[] adaptor = toByteArray("02C2662C97488B07B6E819124B8989849206334A4C2FBDF691F7B34D2B16E9C293");
byte[] resultArr = NativeSecp256k1.adaptorExtractSecret(sig, adaptorSig, adaptor);
String expectedSecret = "0B2ABA63B885A0F0E96FA0F303920C7FB7431DDFA94376AD94D969FBF4109DC8";
String sigString = toHex(resultArr);
assertEquals(sigString , expectedSecret , "testAdaptorExtractSecret");
}
//https://stackoverflow.com/a/19119453/967713
private static byte[] toByteArray(final String hex) {

View File

@ -232,10 +232,10 @@ sealed abstract class CryptoGenerators {
tweakedNonce <- publicKey
untweakedNonce <- publicKey
adaptedS <- fieldElement
proofS <- fieldElement
proofE <- fieldElement
proofS <- fieldElement
} yield {
ECAdaptorSignature(tweakedNonce, adaptedS, untweakedNonce, proofS, proofE)
ECAdaptorSignature(tweakedNonce, untweakedNonce, adaptedS, proofE, proofS)
}
}