mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
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:
parent
13fc3c2b4e
commit
7fd9aca304
7
.gitmodules
vendored
7
.gitmodules
vendored
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) =>
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
1
secp256k1-zkp
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 6dd724b72bb2d47de514eb92e96c6ae6d26ae160
|
Binary file not shown.
@ -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
|
@ -1 +0,0 @@
|
||||
libsecp256k1.so.0.0.0
|
BIN
secp256k1jni/natives/linux_64/libsecp256k1.so
Executable file
BIN
secp256k1jni/natives/linux_64/libsecp256k1.so
Executable file
Binary file not shown.
BIN
secp256k1jni/natives/linux_64/libsecp256k1_jni.a
Normal file
BIN
secp256k1jni/natives/linux_64/libsecp256k1_jni.a
Normal file
Binary file not shown.
41
secp256k1jni/natives/linux_64/libsecp256k1_jni.la
Normal file
41
secp256k1jni/natives/linux_64/libsecp256k1_jni.la
Normal 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=''
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
secp256k1jni/natives/osx_64/libsecp256k1.la
Normal file → Executable file
2
secp256k1jni/natives/osx_64/libsecp256k1.la
Normal file → Executable 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
|
||||
|
Binary file not shown.
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user