Added secp256k1_schnorrsig_sign to JNI

Added secp256k1_schnorrsig_verify to the JNI

Added new schnorrSignWithNonce

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

Fixed binding, doesn't work yet

Added tests, they fail

Added BIP 340 test vectors

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

Added tests

Added Bouncy Castle implementation and further integration

Implemented bouncy castle fallback for all secp schnorr functions

Implemented FieldElement to abstract modular BigInt computations in the Secp256k1 field

Implemented sig in SchnorrDigitalSignature as a FieldElement

Vamped up testing

Added windows binaries

Added osx binaries

added windows binaries

Responded to review

Cleaned up secp commits

Responded to review

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

Cleaned up a couple things for coverage purposes

Set bitcoin-s-schnorr to secp branch
This commit is contained in:
nkohen 2020-04-08 12:43:11 -05:00
parent 5b2ad821ad
commit 85dbf2fd80
21 changed files with 255 additions and 24 deletions

View file

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

View file

@ -119,7 +119,6 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
} }
} }
/*
it must "compute schnorr signatures the same" in { it must "compute schnorr signatures the same" in {
forAll(CryptoGenerators.privateKey, forAll(CryptoGenerators.privateKey,
NumberGenerator.bytevector(32), NumberGenerator.bytevector(32),
@ -179,5 +178,4 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
assert(bouncyCastleSigPoint == secpSigPoint) assert(bouncyCastleSigPoint == secpSigPoint)
} }
} }
*/
} }

View file

@ -113,18 +113,34 @@ sealed abstract class ECPrivateKey
} }
def schnorrSign(dataToSign: ByteVector): SchnorrDigitalSignature = { def schnorrSign(dataToSign: ByteVector): SchnorrDigitalSignature = {
val auxRand = ECPrivateKey.freshPrivateKey.bytes schnorrSign(dataToSign, CryptoContext.default)
schnorrSign(dataToSign, auxRand) }
def schnorrSign(
dataToSign: ByteVector,
context: CryptoContext): SchnorrDigitalSignature = {
val auxRand = ECPrivateKey.freshPrivateKey.bytes
schnorrSign(dataToSign, auxRand, context)
} }
// TODO: match on CryptoContext once secp version is added
def schnorrSign( def schnorrSign(
dataToSign: ByteVector, dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = { auxRand: ByteVector): SchnorrDigitalSignature = {
schnorrSignWithBouncyCastle(dataToSign, auxRand) schnorrSign(dataToSign, auxRand, CryptoContext.default)
}
def schnorrSign(
dataToSign: ByteVector,
auxRand: ByteVector,
context: CryptoContext): SchnorrDigitalSignature = {
context match {
case CryptoContext.LibSecp256k1 =>
schnorrSignWithSecp(dataToSign, auxRand)
case CryptoContext.BouncyCastle =>
schnorrSignWithBouncyCastle(dataToSign, auxRand)
}
} }
/*
def schnorrSignWithSecp( def schnorrSignWithSecp(
dataToSign: ByteVector, dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = { auxRand: ByteVector): SchnorrDigitalSignature = {
@ -134,7 +150,6 @@ sealed abstract class ECPrivateKey
auxRand.toArray) auxRand.toArray)
SchnorrDigitalSignature(ByteVector(sigBytes)) SchnorrDigitalSignature(ByteVector(sigBytes))
} }
*/
def schnorrSignWithBouncyCastle( def schnorrSignWithBouncyCastle(
dataToSign: ByteVector, dataToSign: ByteVector,
@ -142,14 +157,24 @@ sealed abstract class ECPrivateKey
BouncyCastleUtil.schnorrSign(dataToSign, this, auxRand) BouncyCastleUtil.schnorrSign(dataToSign, this, auxRand)
} }
// TODO: match on CryptoContext once secp version is added
def schnorrSignWithNonce( def schnorrSignWithNonce(
dataToSign: ByteVector, dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = { nonce: ECPrivateKey): SchnorrDigitalSignature = {
schnorrSignWithNonceWithBouncyCastle(dataToSign, nonce) schnorrSignWithNonce(dataToSign, nonce, CryptoContext.default)
}
def schnorrSignWithNonce(
dataToSign: ByteVector,
nonce: ECPrivateKey,
context: CryptoContext): SchnorrDigitalSignature = {
context match {
case CryptoContext.LibSecp256k1 =>
schnorrSignWithNonceWithSecp(dataToSign, nonce)
case CryptoContext.BouncyCastle =>
schnorrSignWithNonceWithBouncyCastle(dataToSign, nonce)
}
} }
/*
def schnorrSignWithNonceWithSecp( def schnorrSignWithNonceWithSecp(
dataToSign: ByteVector, dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = { nonce: ECPrivateKey): SchnorrDigitalSignature = {
@ -159,7 +184,6 @@ sealed abstract class ECPrivateKey
nonce.bytes.toArray) nonce.bytes.toArray)
SchnorrDigitalSignature(ByteVector(sigBytes)) SchnorrDigitalSignature(ByteVector(sigBytes))
} }
*/
def schnorrSignWithNonceWithBouncyCastle( def schnorrSignWithNonceWithBouncyCastle(
dataToSign: ByteVector, dataToSign: ByteVector,

View file

@ -12,12 +12,20 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement {
require(Try(publicKey).isSuccess, require(Try(publicKey).isSuccess,
s"Schnorr public key must be a valid x coordinate, got $bytes") s"Schnorr public key must be a valid x coordinate, got $bytes")
// TODO: match on CryptoContext once secp version is added
def verify(data: ByteVector, signature: SchnorrDigitalSignature): Boolean = { def verify(data: ByteVector, signature: SchnorrDigitalSignature): Boolean = {
verifyWithBouncyCastle(data, signature) verify(data, signature, CryptoContext.default)
}
def verify(
data: ByteVector,
signature: SchnorrDigitalSignature,
context: CryptoContext): Boolean = {
context match {
case CryptoContext.LibSecp256k1 => verifyWithSecp(data, signature)
case CryptoContext.BouncyCastle => verifyWithBouncyCastle(data, signature)
}
} }
/*
def verifyWithSecp( def verifyWithSecp(
data: ByteVector, data: ByteVector,
signature: SchnorrDigitalSignature): Boolean = { signature: SchnorrDigitalSignature): Boolean = {
@ -25,7 +33,6 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement {
data.toArray, data.toArray,
bytes.toArray) bytes.toArray)
} }
*/
def verifyWithBouncyCastle( def verifyWithBouncyCastle(
data: ByteVector, data: ByteVector,
@ -34,18 +41,29 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement {
} }
def computeSigPoint(data: ByteVector, nonce: SchnorrNonce): ECPublicKey = { def computeSigPoint(data: ByteVector, nonce: SchnorrNonce): ECPublicKey = {
computeSigPoint(data, nonce, compressed = true) computeSigPoint(data, nonce, compressed = true, CryptoContext.default)
} }
// TODO: match on CryptoContext once secp version is added
def computeSigPoint( def computeSigPoint(
data: ByteVector, data: ByteVector,
nonce: SchnorrNonce, nonce: SchnorrNonce,
compressed: Boolean): ECPublicKey = { compressed: Boolean): ECPublicKey = {
computeSigPointWithBouncyCastle(data, nonce, compressed) computeSigPoint(data, nonce, compressed, CryptoContext.default)
}
def computeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean,
context: CryptoContext): ECPublicKey = {
context match {
case CryptoContext.LibSecp256k1 =>
computeSigPointWithSecp(data, nonce, compressed)
case CryptoContext.BouncyCastle =>
computeSigPointWithBouncyCastle(data, nonce, compressed)
}
} }
/*
def computeSigPointWithSecp( def computeSigPointWithSecp(
data: ByteVector, data: ByteVector,
nonce: SchnorrNonce, nonce: SchnorrNonce,
@ -57,7 +75,6 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement {
compressed) compressed)
ECPublicKey(ByteVector(sigPointBytes)) ECPublicKey(ByteVector(sigPointBytes))
} }
*/
def computeSigPointWithBouncyCastle( def computeSigPointWithBouncyCastle(
data: ByteVector, data: ByteVector,

View file

@ -1,5 +1,10 @@
import scala.util.Properties import scala.util.Properties
version in ThisBuild ~= { version =>
val withoutSuffix = version.dropRight(8)
withoutSuffix + "SCHNORR-DLC-SNAPSHOT"
}
val scala2_12 = "2.12.12" val scala2_12 = "2.12.12"
val scala2_13 = "2.13.2" val scala2_13 = "2.13.2"

@ -1 +1 @@
Subproject commit c9bab11ef05ef0cdc0cb0f0e02d3c8e052799767 Subproject commit da8fcb8c00611bf9a450763a8d1c41a28b7f76f4

View file

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

View file

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