Merge pull request #60 from Christewart/libsecp256k1

Libsecp256k1 integration
This commit is contained in:
Chris Stewart 2017-02-23 19:27:52 -06:00 committed by GitHub
commit f6bc61c7cd
13 changed files with 839 additions and 44 deletions

53
.gitignore vendored
View file

@ -16,4 +16,55 @@ project/plugins/project/
.scala_dependencies
.worksheet
.idea/*
*.iml
*.iml
# secp256k1 ignore files
bench_inv
bench_ecdh
bench_sign
bench_verify
bench_schnorr_verify
bench_recover
bench_internal
tests
exhaustive_tests
gen_context
*.exe
*.so
*.a
!.gitignore
Makefile
configure
.libs/
Makefile.in
aclocal.m4
autom4te.cache/
config.log
config.status
*.tar.gz
*.la
libtool
.deps/
.dirstamp
*.lo
*.o
*~
src/libsecp256k1-config.h
src/libsecp256k1-config.h.in
src/ecmult_static_context.h
build-aux/config.guess
build-aux/config.sub
build-aux/depcomp
build-aux/install-sh
build-aux/ltmain.sh
build-aux/m4/libtool.m4
build-aux/m4/lt~obsolete.m4
build-aux/m4/ltoptions.m4
build-aux/m4/ltsugar.m4
build-aux/m4/ltversion.m4
build-aux/missing
build-aux/compile
build-aux/test-driver
src/stamp-h1
libsecp256k1.pc

View file

@ -0,0 +1,457 @@
/*
* Copyright 2013 Google Inc.
* Copyright 2014-2016 the libsecp256k1 contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoin;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.math.BigInteger;
import com.google.common.base.Preconditions;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.bitcoin.NativeSecp256k1Util.*;
/**
* <p>This class holds native methods to handle ECDSA verification.</p>
*
* <p>You can find an example library that can be used for this at https://github.com/bitcoin/secp256k1</p>
*
* <p>To build secp256k1 for use with bitcoinj, run
* `./configure --enable-jni --enable-experimental --enable-module-ecdh`
* and `make` then copy `.libs/libsecp256k1.so` to your system library path
* or point the JVM to the folder containing it with -Djava.library.path
* </p>
*/
public class NativeSecp256k1 {
private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private static final Lock r = rwl.readLock();
private static final Lock w = rwl.writeLock();
private static ThreadLocal<ByteBuffer> nativeECDSABuffer = new ThreadLocal<ByteBuffer>();
/**
* Verifies the given secp256k1 signature in native code.
* Calling when enabled == false is undefined (probably library not loaded)
*
* @param data The data which was signed, must be exactly 32 bytes
* @param signature The signature
* @param pub The public key which did the signing
*/
public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws AssertFailException{
checkInvariant(data.length == 32 && signature.length <= 520 && pub.length <= 520);
checkInvariant(Secp256k1Context.isEnabled());
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 520) {
byteBuff = ByteBuffer.allocateDirect(520);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(data);
byteBuff.put(signature);
byteBuff.put(pub);
byte[][] retByteArray;
r.lock();
try {
return secp256k1_ecdsa_verify(byteBuff, Secp256k1Context.getContext(),
signature.length, pub.length) == 1;
} finally {
r.unlock();
}
}
/**
* libsecp256k1 Create an ECDSA signature.
*
* @param data Message hash, 32 bytes
* @param key Secret key, 32 bytes
*
* Return values
* @param sig byte array of signature
*/
public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{
checkInvariant(data != null);
checkInvariant(data.length == 32);
checkInvariant(sec.length <= 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + 32) {
byteBuff = ByteBuffer.allocateDirect(32 + 32);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(data);
byteBuff.put(sec);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_ecdsa_sign(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;
}
/**
* libsecp256k1 Seckey Verify - returns 1 if valid, 0 if invalid
*
* @param seckey ECDSA Secret key, 32 bytes
*/
public static boolean secKeyVerify(byte[] seckey) {
checkInvariant(seckey.length == 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < seckey.length) {
byteBuff = ByteBuffer.allocateDirect(seckey.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(seckey);
r.lock();
try {
return secp256k1_ec_seckey_verify(byteBuff,Secp256k1Context.getContext()) == 1;
} finally {
r.unlock();
}
}
/**
* libsecp256k1 Compute Pubkey - computes public key from secret key
*
* @param seckey ECDSA Secret key, 32 bytes
*
* Return values
* @param pubkey ECDSA Public key, 33 or 65 bytes
*/
//TODO add a 'compressed' arg
public static byte[] computePubkey(byte[] seckey, boolean fCompressed) throws AssertFailException{
checkInvariant(seckey.length == 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < seckey.length) {
byteBuff = ByteBuffer.allocateDirect(seckey.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(seckey);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext(), fCompressed);
} finally {
r.unlock();
}
byte[] pubArr = retByteArray[0];
int pubLen = new BigInteger(new byte[] { retByteArray[1][0] }).intValue();
int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue();
assertEquals(pubArr.length, pubLen, "Got bad pubkey length.");
return retVal == 0 ? new byte[0]: pubArr;
}
/**
* libsecp256k1 Cleanup - This destroys the secp256k1 context object
* This should be called at the end of the program for proper cleanup of the context.
*/
public static synchronized void cleanup() {
w.lock();
try {
secp256k1_destroy_context(Secp256k1Context.getContext());
} finally {
w.unlock();
}
}
public static long cloneContext() {
r.lock();
try {
return secp256k1_ctx_clone(Secp256k1Context.getContext());
} finally { r.unlock(); }
}
/**
* libsecp256k1 PrivKey Tweak-Mul - Tweak privkey by multiplying to it
*
* @param tweak some bytes to tweak with
* @param seckey 32-byte seckey
*/
public static byte[] privKeyTweakMul(byte[] privkey, byte[] tweak) throws AssertFailException{
checkInvariant(privkey.length == 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) {
byteBuff = ByteBuffer.allocateDirect(privkey.length + tweak.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(privkey);
byteBuff.put(tweak);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_privkey_tweak_mul(byteBuff,Secp256k1Context.getContext());
} finally {
r.unlock();
}
byte[] privArr = retByteArray[0];
int privLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF;
int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue();
assertEquals(privArr.length, privLen, "Got bad pubkey length.");
assertEquals(retVal, 1, "Failed return value check.");
return privArr;
}
/**
* libsecp256k1 PrivKey Tweak-Add - Tweak privkey by adding to it
*
* @param tweak some bytes to tweak with
* @param seckey 32-byte seckey
*/
public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws AssertFailException{
checkInvariant(privkey.length == 32);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) {
byteBuff = ByteBuffer.allocateDirect(privkey.length + tweak.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(privkey);
byteBuff.put(tweak);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_privkey_tweak_add(byteBuff,Secp256k1Context.getContext());
} finally {
r.unlock();
}
byte[] privArr = retByteArray[0];
int privLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF;
int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue();
assertEquals(privArr.length, privLen, "Got bad pubkey length.");
assertEquals(retVal, 1, "Failed return value check.");
return privArr;
}
/**
* libsecp256k1 PubKey Tweak-Add - Tweak pubkey by adding to it
*
* @param tweak some bytes to tweak with
* @param pubkey 32-byte seckey
*/
public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak, boolean fCompressed) throws AssertFailException{
checkInvariant(pubkey.length == 33 || pubkey.length == 65);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) {
byteBuff = ByteBuffer.allocateDirect(pubkey.length + tweak.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(pubkey);
byteBuff.put(tweak);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_pubkey_tweak_add(byteBuff,Secp256k1Context.getContext(), pubkey.length, fCompressed);
} 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 PubKey Tweak-Mul - Tweak pubkey by multiplying to it
*
* @param tweak some bytes to tweak with
* @param pubkey 32-byte seckey
*/
public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak, boolean fCompressed) throws AssertFailException{
checkInvariant(pubkey.length == 33 || pubkey.length == 65);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) {
byteBuff = ByteBuffer.allocateDirect(pubkey.length + tweak.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(pubkey);
byteBuff.put(tweak);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length, fCompressed);
} 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 create ECDH secret - constant time ECDH calculation
*
* @param seckey byte array of secret key used in exponentiaion
* @param pubkey byte array of public key used in exponentiaion
*/
public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws AssertFailException{
checkInvariant(seckey.length <= 32 && pubkey.length <= 65);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + pubkey.length) {
byteBuff = ByteBuffer.allocateDirect(32 + pubkey.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(seckey);
byteBuff.put(pubkey);
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_ecdh(byteBuff, Secp256k1Context.getContext(), pubkey.length);
} finally {
r.unlock();
}
byte[] resArr = retByteArray[0];
int retVal = new BigInteger(new byte[] { retByteArray[1][0] }).intValue();
assertEquals(resArr.length, 32, "Got bad result length.");
assertEquals(retVal, 1, "Failed return value check.");
return resArr;
}
/**
* libsecp256k1 randomize - updates the context randomization
*
* @param seed 32-byte random seed
*/
public static synchronized boolean randomize(byte[] seed) throws AssertFailException{
checkInvariant(seed.length == 32 || seed == null);
ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < seed.length) {
byteBuff = ByteBuffer.allocateDirect(seed.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(seed);
w.lock();
try {
return secp256k1_context_randomize(byteBuff, Secp256k1Context.getContext()) == 1;
} finally {
w.unlock();
}
}
private static native long secp256k1_ctx_clone(long context);
private static native int secp256k1_context_randomize(ByteBuffer byteBuff, long context);
private static native byte[][] secp256k1_privkey_tweak_add(ByteBuffer byteBuff, long context);
private static native byte[][] secp256k1_privkey_tweak_mul(ByteBuffer byteBuff, long context);
private static native byte[][] secp256k1_pubkey_tweak_add(ByteBuffer byteBuff, long context, int pubLen, boolean fCompressed);
private static native byte[][] secp256k1_pubkey_tweak_mul(ByteBuffer byteBuff, long context, int pubLen, boolean fCompressed);
private static native void secp256k1_destroy_context(long context);
private static native int secp256k1_ecdsa_verify(ByteBuffer byteBuff, long context, int sigLen, int pubLen);
private static native byte[][] secp256k1_ecdsa_sign(ByteBuffer byteBuff, long context);
private static native int secp256k1_ec_seckey_verify(ByteBuffer byteBuff, long context);
private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context, boolean fCompressed);
private static native byte[][] secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen);
private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen);
private static void checkInvariant(boolean result) throws IllegalArgumentException {
if (!result) throw new IllegalArgumentException();
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2014-2016 the libsecp256k1 contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoin;
public class NativeSecp256k1Util {
public static void assertEquals( int val, int val2, String message ) throws AssertFailException{
if( val != val2 )
throw new AssertFailException("FAIL: " + message);
}
public static void assertEquals( boolean val, boolean val2, String message ) throws AssertFailException{
if( val != val2 )
throw new AssertFailException("FAIL: " + message);
else
System.out.println("PASS: " + message);
}
public static void assertEquals( String val, String val2, String message ) throws AssertFailException{
if( !val.equals(val2) )
throw new AssertFailException("FAIL: " + message);
else
System.out.println("PASS: " + message);
}
public static class AssertFailException extends Exception {
public AssertFailException(String message) {
super( message );
}
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright 2014-2016 the libsecp256k1 contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoin;
/**
* This class holds the context reference used in native methods
* to handle ECDSA operations.
*/
public class Secp256k1Context {
private static final boolean enabled; //true if the library is loaded
private static final long context; //ref to pointer to context obj
static { //static initializer
boolean isEnabled = true;
long contextRef = -1;
try {
System.loadLibrary("secp256k1");
contextRef = secp256k1_init_context();
} catch (UnsatisfiedLinkError e) {
System.out.println("UnsatisfiedLinkError: " + e.toString());
isEnabled = false;
}
enabled = isEnabled;
context = contextRef;
}
public static boolean isEnabled() {
return enabled;
}
public static long getContext() {
if(!enabled) return -1; //sanity check
return context;
}
private static native long secp256k1_init_context();
}

View file

@ -17,8 +17,7 @@ sealed trait ECDigitalSignature extends BitcoinSLogger {
/**
* Checks if this signature is encoded to DER correctly
* https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1
*
* @return boolean representing if the signature is a valid
* @return boolean representing if the signature is a valid
*/
def isDEREncoded : Boolean = DERSignatureUtil.isDEREncoded(this)
@ -27,32 +26,26 @@ sealed trait ECDigitalSignature extends BitcoinSLogger {
* [[https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki]]
* */
def isStrictEncoded: Boolean = DERSignatureUtil.isValidSignatureEncoding(this)
/**
* Decodes the digital signature into it's r and s points
* throws an exception if the given sequence of bytes is not a DER encoded signature
*
* @return the (r,s) values for the elliptic curve digital signature
* @return the (r,s) values for the elliptic curve digital signature
*/
def decodeSignature : (BigInt,BigInt) = DERSignatureUtil.decodeSignature(this)
/**
* Represents the r value found in a elliptic curve digital signature
*/
/** Represents the r value found in a elliptic curve digital signature */
def r = decodeSignature._1
/**
* Represents the s value found in a elliptic curve digital signature
*
* @return
*/
/** Represents the s value found in a elliptic curve digital signature */
def s = decodeSignature._2
}
case object EmptyDigitalSignature extends ECDigitalSignature {
def bytes = Seq()
def bytes = Nil
override def r = java.math.BigInteger.valueOf(0)
override def s = r
}
@ -91,12 +84,4 @@ object ECDigitalSignature extends Factory[ECDigitalSignature] {
r.toByteArray.toSeq ++ Seq(0x2.toByte, s.toByteArray.size.toByte) ++ s.toByteArray.toSeq
fromBytes(bytes)
}
/** Checks if the given digital signature uses a low s value,
* if it does not it converts it to a low s value and returns it */
private def lowS(signature: ECDigitalSignature): ECDigitalSignature = {
if (signature.s.bigInteger.compareTo(CryptoParams.halfCurveOrder) <= 0) signature
else ECDigitalSignature(signature.r,CryptoParams.curve.getN().subtract(signature.s.bigInteger))
}
}

View file

@ -9,3 +9,4 @@ trait ECKey {
}
case class ECKeyImpl(privateKey : Option[ECPrivateKey], publicKey : ECPublicKey) extends ECKey

View file

@ -6,16 +6,10 @@ import org.bitcoins.core.util.{Factory, BitcoinSUtil}
* Created by chris on 5/24/16.
*/
sealed trait HashDigest {
/**
* The message digest represented in bytes
* @return
*/
/** The message digest represented in bytes */
def bytes : Seq[Byte]
/**
* The message digest represented in hexadecimal
* @return
*/
/** The message digest represented in hexadecimal */
def hex : String = BitcoinSUtil.encodeHex(bytes)
}
@ -39,6 +33,7 @@ sealed trait Sha256Digest extends HashDigest
object Sha256Digest extends Factory[Sha256Digest] {
private case class Sha256DigestImpl(bytes: Seq[Byte]) extends Sha256Digest {
require(bytes.length == 32, "Sha256Digest must be 32 bytes in size, got: " + bytes.length)
override def toString = "Sha256DigestImpl(" + hex + ")"
}
override def fromBytes(bytes:Seq[Byte]) : Sha256Digest = Sha256DigestImpl(bytes)
@ -51,6 +46,7 @@ sealed trait DoubleSha256Digest extends HashDigest
object DoubleSha256Digest extends Factory[DoubleSha256Digest] {
private case class DoubleSha256DigestImpl(bytes: Seq[Byte]) extends DoubleSha256Digest {
require(bytes.length == 32, "DoubleSha256Digest must always be 32 bytes, got: " + bytes.length)
override def toString = "DoubleSha256DigestImpl(" + hex + ")"
}
override def fromBytes(bytes : Seq[Byte]) : DoubleSha256Digest = DoubleSha256DigestImpl(bytes)
@ -64,19 +60,21 @@ sealed trait RipeMd160Digest extends HashDigest
object RipeMd160Digest extends Factory[RipeMd160Digest] {
private case class RipeMd160DigestImpl(bytes : Seq[Byte]) extends RipeMd160Digest {
require(bytes.length == 20, "RIPEMD160Digest must always be 20 bytes, got: " + bytes.length)
override def toString = "RipeMd160DigestImpl(" + hex + ")"
}
override def fromBytes(bytes : Seq[Byte]) : RipeMd160Digest = RipeMd160DigestImpl(bytes)
}
/**
* Represents the result of RIPEMD160(SHA256())
*/
sealed trait Sha256Hash160Digest extends HashDigest
object Sha256Hash160Digest extends Factory[Sha256Hash160Digest] {
case class Sha256Hash160DigestImpl(bytes : Seq[Byte]) extends Sha256Hash160Digest {
private case class Sha256Hash160DigestImpl(bytes : Seq[Byte]) extends Sha256Hash160Digest {
require(bytes.length == 20, "Sha256Hash160Digest must always be 20 bytes, got: " + bytes.length)
override def toString = "Sha256Hash160DigestImpl(" + hex + ")"
}
override def fromBytes(bytes : Seq[Byte]) = Sha256Hash160DigestImpl(bytes)
override def fromBytes(bytes : Seq[Byte]): Sha256Hash160Digest = Sha256Hash160DigestImpl(bytes)
}

View file

@ -62,7 +62,8 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
}
logger.debug("Hash for signature: " + BitcoinSUtil.encodeHex(hashForSignature.bytes))
val isValid = pubKey.verify(hashForSignature,signature)
val sigWithoutHashType = stripHashType(signature)
val isValid = pubKey.verify(hashForSignature,sigWithoutHashType)
if (isValid) SignatureValidationSuccess
else nullFailCheck(Seq(signature),SignatureValidationErrorIncorrectSignatures, flags)
}
@ -123,6 +124,7 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
}
/** If the NULLFAIL flag is set as defined in BIP146, it checks to make sure all failed signatures were an empty byte vector
* [[https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL]]
* */
@ -134,6 +136,11 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
SignatureValidationErrorNullFail
} else result
}
/** Removes the hash type from the [[org.bitcoins.core.crypto.ECDigitalSignature]] */
private def stripHashType(sig: ECDigitalSignature): ECDigitalSignature = {
ECDigitalSignature(sig.bytes.slice(0,sig.bytes.length-1))
}
}
object TransactionSignatureChecker extends TransactionSignatureChecker

View file

@ -23,6 +23,7 @@ trait TransactionSignatureCreator extends BitcoinSLogger {
val sig = ECDigitalSignature(signature.bytes ++ Seq(hashType.byte))
logger.debug("TxSigCreator sig: " + sig)
require(sig.isStrictEncoded, "We did not create a signature that is strictly encoded, got: " + sig)
require(DERSignatureUtil.isLowS(sig), "Sig does not have a low s value")
sig
}
}

View file

@ -0,0 +1,193 @@
package org.bitcoin;
import com.google.common.io.BaseEncoding;
import org.junit.Test;
import static org.bitcoin.NativeSecp256k1Util.AssertFailException;
import static org.bitcoin.NativeSecp256k1Util.assertEquals;
/**
* This class holds test cases defined for testing this library.
*/
public class NativeSecp256k1Test {
//TODO improve comments/add more tests
/**
* This tests verify() for a valid signature
*/
@Test
public void testVerifyPos() throws AssertFailException {
boolean result = false;
byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing"
byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase());
byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase());
result = NativeSecp256k1.verify( data, sig, pub);
assertEquals( result, true , "testVerifyPos");
}
/**
* This tests verify() for a non-valid signature
*/
@Test
public void testVerifyNeg() throws AssertFailException{
boolean result = false;
byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91".toLowerCase()); //sha256hash of "testing"
byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase());
byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase());
result = NativeSecp256k1.verify( data, sig, pub);
//System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16));
assertEquals( result, false , "testVerifyNeg");
}
/**
* This tests secret key verify() for a valid secretkey
*/
@Test
public void testSecKeyVerifyPos() throws AssertFailException{
boolean result = false;
byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase());
result = NativeSecp256k1.secKeyVerify( sec );
//System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16));
assertEquals( result, true , "testSecKeyVerifyPos");
}
/**
* This tests secret key verify() for a invalid secretkey
*/
@Test
public void testSecKeyVerifyNeg() throws AssertFailException{
boolean result = false;
byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase());
result = NativeSecp256k1.secKeyVerify( sec );
//System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16));
assertEquals( result, false , "testSecKeyVerifyNeg");
}
/**
* This tests public key create() for a valid secretkey
*/
@Test
public void testPubKeyCreatePos() throws AssertFailException{
byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase());
byte[] resultArr = NativeSecp256k1.computePubkey( sec, true);
String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( pubkeyString , "02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D" , "testPubKeyCreatePos");
}
/**
* This tests public key create() for a invalid secretkey
*/
@Test
public void testPubKeyCreateNeg() throws AssertFailException{
byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase());
byte[] resultArr = NativeSecp256k1.computePubkey( sec, true);
String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( pubkeyString, "" , "testPubKeyCreateNeg");
}
/**
* This tests sign() for a valid secretkey
*/
@Test
public void testSignPos() throws AssertFailException{
byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing"
byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase());
byte[] resultArr = NativeSecp256k1.sign(data, sec);
String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( sigString, "30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9" , "testSignPos");
}
/**
* This tests sign() for a invalid secretkey
*/
@Test
public void testSignNeg() throws AssertFailException{
byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing"
byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase());
byte[] resultArr = NativeSecp256k1.sign(data, sec);
String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( sigString, "" , "testSignNeg");
}
/**
* This tests private key tweak-add
*/
@Test
public void testPrivKeyTweakAdd_1() throws AssertFailException {
byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase());
byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak"
byte[] resultArr = NativeSecp256k1.privKeyTweakAdd( sec , data );
String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( sigString , "A168571E189E6F9A7E2D657A4B53AE99B909F7E712D1C23CED28093CD57C88F3" , "testPrivKeyAdd_1");
}
/**
* This tests private key tweak-mul
*/
@Test
public void testPrivKeyTweakMul_1() throws AssertFailException {
byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase());
byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak"
byte[] resultArr = NativeSecp256k1.privKeyTweakMul( sec , data );
String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( sigString , "97F8184235F101550F3C71C927507651BD3F1CDB4A5A33B8986ACF0DEE20FFFC" , "testPrivKeyMul_1");
}
/**
* This tests private key tweak-add uncompressed
*/
@Test
public void testPrivKeyTweakAdd_2() throws AssertFailException {
byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase());
byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak"
byte[] resultArr = NativeSecp256k1.pubKeyTweakAdd( pub , data, false );
String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( sigString , "0411C6790F4B663CCE607BAAE08C43557EDC1A4D11D88DFCB3D841D0C6A941AF525A268E2A863C148555C48FB5FBA368E88718A46E205FABC3DBA2CCFFAB0796EF" , "testPrivKeyAdd_2");
}
/**
* This tests private key tweak-mul uncompressed
*/
@Test
public void testPrivKeyTweakMul_2() throws AssertFailException {
byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase());
byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak"
byte[] resultArr = NativeSecp256k1.pubKeyTweakMul( pub , data, false );
String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( sigString , "04E0FE6FE55EBCA626B98A807F6CAF654139E14E5E3698F01A9A658E21DC1D2791EC060D4F412A794D5370F672BC94B722640B5F76914151CFCA6E712CA48CC589", "testPrivKeyMul_2");
}
/**
* This tests seed randomization
*/
@Test
public void testRandomize() throws AssertFailException {
byte[] seed = BaseEncoding.base16().lowerCase().decode("A441B15FE9A3CF56661190A0B93B9DEC7D04127288CC87250967CF3B52894D11".toLowerCase()); //sha256hash of "random"
boolean result = NativeSecp256k1.randomize(seed);
assertEquals( result, true, "testRandomize");
}
@Test
public void testCreateECDHSecret() throws AssertFailException{
byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase());
byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase());
byte[] resultArr = NativeSecp256k1.createECDHSecret(sec, pub);
String ecdhString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr);
assertEquals( ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043" , "testCreateECDHSecret");
}
}

View file

@ -16,7 +16,8 @@ class ECPublicKeyTest extends FlatSpec with MustMatchers {
val privateKeyHex = "180cb41c7c600be951b5d3d0a7334acc7506173875834f7a6c4c786a28fcbb19"
val key: ECPrivateKey = ECPrivateKey(privateKeyHex)
val signature: ECDigitalSignature = key.sign(Sha256Hash.ZERO_HASH.getBytes.toSeq)
val hash = DoubleSha256Digest(Sha256Hash.ZERO_HASH.getBytes.toSeq)
val signature: ECDigitalSignature = key.sign(hash)
val isValid : Boolean = key.publicKey.verify(Sha256Hash.ZERO_HASH.getBytes.toSeq,signature)
isValid must be (true)
@ -27,10 +28,11 @@ class ECPublicKeyTest extends FlatSpec with MustMatchers {
it must "fail to verify a piece of data if the wrong public key is given" in {
val privateKeyHex = "180cb41c7c600be951b5d3d0a7334acc7506173875834f7a6c4c786a28fcbb19"
val key: ECPrivateKey = ECPrivateKey(privateKeyHex)
val signature: ECDigitalSignature = key.sign(Sha256Hash.ZERO_HASH.getBytes.toSeq)
val hash = DoubleSha256Digest(Sha256Hash.ZERO_HASH.getBytes.toSeq)
val signature: ECDigitalSignature = key.sign(hash)
val wrongPublicKey = ECPublicKey.freshPublicKey
val isValid : Boolean = wrongPublicKey.verify(Sha256Hash.ZERO_HASH.getBytes.toSeq,signature)
val isValid : Boolean = wrongPublicKey.verify(hash,signature)
isValid must be (false)
}
@ -46,9 +48,11 @@ class ECPublicKeyTest extends FlatSpec with MustMatchers {
it must "verify a piece of data was signed with a bitcoins private key inside of bitcoinj" in {
val bitcoinsPrivKey = ECPrivateKey.freshPrivateKey
val bitcoinsSignature = bitcoinsPrivKey.sign(Sha256Hash.ZERO_HASH.getBytes)
val hash = DoubleSha256Digest(Sha256Hash.ZERO_HASH.getBytes)
val bitcoinsSignature = bitcoinsPrivKey.sign(hash)
val bitcoinjPublicKey = org.bitcoinj.core.ECKey.fromPublicOnly(bitcoinsPrivKey.publicKey.bytes.toArray)
bitcoinjPublicKey.verify(Sha256Hash.ZERO_HASH.getBytes, bitcoinsSignature.bytes.toArray) must be (true)
bitcoinjPublicKey.verify(Sha256Hash.ZERO_HASH.getBytes,
bitcoinsSignature.bytes.toArray) must be (true)
}
}

View file

@ -31,7 +31,6 @@ class TransactionSignatureCreatorSpec extends Properties("TransactionSignatureCr
val result = ScriptInterpreter.run(program)
result == ScriptOk
}
property("generate valid signatures for a multisignature transaction") =
Prop.forAllNoShrink(TransactionGenerators.signedMultiSigTransaction) {
case (txSignatureComponent: TransactionSignatureComponent, _) =>

View file

@ -55,17 +55,20 @@ class TransactionSignatureCreatorTest extends FlatSpec with MustMatchers with Bi
val scriptPubKey = P2PKScriptPubKey(publicKey)
val (creditingTx,outputIndex) = TransactionTestUtil.buildCreditingTransaction(scriptPubKey)
val scriptSig = P2PKScriptSignature(EmptyDigitalSignature)
val (spendingTx,inputIndex) = TransactionTestUtil.buildSpendingTransaction(creditingTx,scriptSig,outputIndex)
val (spendingTx,inputIndex) = TransactionTestUtil.buildSpendingTransaction(creditingTx,
scriptSig,outputIndex)
val txSignatureComponent = TransactionSignatureComponent(spendingTx,inputIndex,scriptPubKey,
Policy.standardScriptVerifyFlags)
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey,HashType.sigHashAll)
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,
privateKey, HashType.sigHashAll)
//add the signature to the scriptSig instead of having an empty scriptSig
val signedScriptSig = P2PKScriptSignature(txSignature)
val (signedTx,_) = TransactionTestUtil.buildSpendingTransaction(creditingTx,signedScriptSig,outputIndex)
val (signedTx,_) = TransactionTestUtil.buildSpendingTransaction(creditingTx,signedScriptSig,
outputIndex)
//run it through the interpreter
val program = ScriptProgram(signedTx,scriptPubKey,inputIndex, Policy.standardScriptVerifyFlags)
val program = ScriptProgram(signedTx, scriptPubKey, inputIndex, Policy.standardScriptVerifyFlags)
val result = ScriptInterpreter.run(program)