Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions secp-api/src/main/java/org/bitcoinj/secp/Secp256k1.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ default SecpPubKey ecPubKeyFromXOnly(SecpXOnlyPubKey xOnlyPubKey){
*/
SecpResult<SecpXOnlyPubKey> xOnlyPubKeyParse(byte[] inputData);

default SecpResult<EcdsaSignature> ecdsaSign(SecpHash256 messageHash, SecpPrivKey privKey) {
return ecdsaSign(messageHash.toByteArray(), privKey);
}

/**
* Sign a message hash using the ECDSA algorithm
* @param msg_hash_data 32-byte hash of message to sign
Expand All @@ -256,6 +260,10 @@ default SecpPubKey ecPubKeyFromXOnly(SecpXOnlyPubKey xOnlyPubKey){
*/
SecpResult<EcdsaSignature> ecdsaSign(byte[] msg_hash_data, SecpPrivKey privKey);

default SecpResult<EcdsaSignature> ecdsaSignLowR(SecpHash256 messageHash, SecpPrivKey privKey) {
return ecdsaSignLowR(messageHash.toByteArray(), privKey);
}

/**
* Sign a message hash using the ECDSA algorithm and Low-R signature griding
* <p>
Expand Down Expand Up @@ -284,6 +292,10 @@ default SecpPubKey ecPubKeyFromXOnly(SecpXOnlyPubKey xOnlyPubKey){
*/
SecpResult<EcdsaSignature> ecdsaSignatureParseCompact(byte[] serialized_signature);

default SecpResult<Boolean> ecdsaVerify(EcdsaSignature sig, SecpHash256 messageHash, SecpPubKey pubKey) {
return ecdsaVerify(sig, messageHash.toByteArray(), pubKey);
}

/**
* Verify an ECDSA signature is valid and low-s.
* @param sig The signature to verify.
Expand All @@ -293,13 +305,17 @@ default SecpPubKey ecPubKeyFromXOnly(SecpXOnlyPubKey xOnlyPubKey){
*/
SecpResult<Boolean> ecdsaVerify(EcdsaSignature sig, byte[] msg_hash_data, SecpPubKey pubKey);

SecpHash256 sha256(byte[] message);

SecpHash256 sha256Import(byte[] messageHash);

/**
* Generate a tagged SHA-256 hash.
* @param tag a tag specifying the context of usage
* @param message the message itself
* @return the SHA-256 HASH
*/
default byte[] taggedSha256(String tag, String message) {
default SecpHash256 taggedSha256(String tag, String message) {
return taggedSha256(tag.getBytes(StandardCharsets.UTF_8), message.getBytes(StandardCharsets.UTF_8));
}

Expand All @@ -309,7 +325,11 @@ default byte[] taggedSha256(String tag, String message) {
* @param message the message itself
* @return the SHA-256 HASH
*/
byte[] taggedSha256(byte[] tag, byte[] message);
SecpHash256 taggedSha256(byte[] tag, byte[] message);

default SchnorrSignature schnorrSigSign32(SecpHash256 message, SecpPrivKey privKey) {
return schnorrSigSign32(message.toByteArray(), privKey);
}

/**
* Create a Schnorr signature for a message.
Expand All @@ -326,7 +346,11 @@ default byte[] taggedSha256(String tag, String message) {
* @param auxiliaryRandom auxiliary randomness (typically from a test vector)
* @return the signature
*/
SchnorrSignature schnorrSigSign32(byte[] messageHash, SecpPrivKey privKey, byte[] auxiliaryRandom);
SchnorrSignature schnorrSigSign32(SecpHash256 messageHash, SecpPrivKey privKey, byte[] auxiliaryRandom);

default SecpResult<Boolean> schnorrSigVerify(SchnorrSignature signature, SecpHash256 messageHash, SecpXOnlyPubKey pubKey) {
return schnorrSigVerify(signature, messageHash.toByteArray(), pubKey);
}

/**
* Verify a Schnorr signature.
Expand Down
24 changes: 24 additions & 0 deletions secp-api/src/main/java/org/bitcoinj/secp/SecpHash256.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2023-2026 secp256k1-jdk Developers.
*
* 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.bitcoinj.secp;

/**
* A {@link SecpUInt256} that is a SHA-256 hash.
* A hash can be any uint256 value, so this is really just a marker
* interface that is used by methods that return a SHA-256 Hash.
*/
public interface SecpHash256 extends SecpUInt256 {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.bitcoinj.secp.internal;

import org.bitcoinj.secp.SecpScalar;
import org.bitcoinj.secp.SecpUInt256;

import java.math.BigInteger;
import java.security.MessageDigest;
Expand All @@ -27,6 +28,10 @@ public class SecpScalarImpl implements SecpScalar {
/** scalar value as a 32-byte big-endian byte array */
private final byte[] value;

public SecpScalarImpl(SecpUInt256 u256) {
value = checkInRange(u256.toByteArray());
}

public SecpScalarImpl(byte[] bytes) {
value = checkInRange(bytes);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2023-2026 secp256k1-jdk Developers.
*
* 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.bitcoinj.secp.bitcoinj;

import org.bitcoinj.base.Network;
import org.bitcoinj.base.SegwitAddress;
import org.bitcoinj.secp.Secp256k1;
import org.bitcoinj.secp.SecpXOnlyPubKey;

/**
*
*/
public interface TapRootAddress {
static SegwitAddress fromXOnlyPubKey(Secp256k1 secp, Network network, SecpXOnlyPubKey xOnlyPubKey) {
WitnessMaker maker = new WitnessMaker(secp);
SecpXOnlyPubKey tweakedPubKey = maker.tweakedPubKey(xOnlyPubKey);
return SegwitAddress.fromProgram(network, 1, tweakedPubKey.toByteArray());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
*/
package org.bitcoinj.secp.bitcoinj;

import org.bitcoinj.secp.SecpFieldElement;
import org.bitcoinj.secp.SecpHash256;
import org.bitcoinj.secp.SecpPoint;
import org.bitcoinj.secp.SecpScalar;
import org.bitcoinj.secp.SecpXOnlyPubKey;
import org.bitcoinj.secp.Secp256k1;
import org.bitcoinj.secp.internal.SecpScalarImpl;
import org.bitcoinj.secp.internal.SecpXOnlyPubKeyImpl;

import java.nio.charset.StandardCharsets;

Expand All @@ -42,23 +43,23 @@ public WitnessMaker(Secp256k1 secp) {
/// returns Q.x()
/// @param xOnlyPubKey The x-only pubKey
/// @return tweaked, pubKey as an x-only field element
public SecpFieldElement tweakedPubKey(SecpXOnlyPubKey xOnlyPubKey) {
public SecpXOnlyPubKey tweakedPubKey(SecpXOnlyPubKey xOnlyPubKey) {
SecpScalar tweak = hashTapTweak(xOnlyPubKey);
return tweakedPubKey(xOnlyPubKey, tweak);
}

/// Return Q.x(), where Q = P + tweak * G
SecpFieldElement tweakedPubKey(SecpXOnlyPubKey xOnlyPubKey, SecpScalar tweak) {
SecpXOnlyPubKey tweakedPubKey(SecpXOnlyPubKey xOnlyPubKey, SecpScalar tweak) {
SecpPoint.Uncompressed P = secp.ecPubKeyFromXOnly(xOnlyPubKey);
SecpPoint.Uncompressed tempPoint = secp.ecPubKeyTweakMul(Secp256k1.G, tweak.toBigInteger()).point();
SecpPoint.Uncompressed tempPoint = secp.ecPubKeyTweakMul(Secp256k1.G, tweak.toBigInteger());
// tweakedPubKey (aka Q)
SecpPoint.Uncompressed Q = secp.ecPubKeyCombine(P, tempPoint);
return Q.x();
return new SecpXOnlyPubKeyImpl(Q.x());
}

/// int(hashTapTweak(bytes(P)))
SecpScalar hashTapTweak(SecpXOnlyPubKey xOnlyPubKey) {
byte[] tweak = secp.taggedSha256(TAG_TAP_TWEAK, xOnlyPubKey.serialize());
SecpHash256 tweak = secp.taggedSha256(TAG_TAP_TWEAK, xOnlyPubKey.serialize());
return new SecpScalarImpl(tweak);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ void createAddressTest(BigInteger key, String address) throws Exception {
Address tapRootAddress;
SecpKeyPair keyPair = secp.ecKeyPairCreate(secp.ecPrivKeyImport(key));
WitnessMaker maker = new WitnessMaker(secp);
SecpFieldElement tweakedPubKey = maker.tweakedPubKey(keyPair.publicKey().xOnly());
SecpXOnlyPubKey tweakedPubKey = maker.tweakedPubKey(keyPair.publicKey().xOnly());
tapRootAddress = SegwitAddress.fromProgram(network, 1, tweakedPubKey.toByteArray());
Assertions.assertEquals(address, tapRootAddress.toString());
}
Expand All @@ -76,10 +76,8 @@ void bipVector0() {
byte[] serializedInternalPubKey = parseHex("d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d");
String expectedBip350Address = "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5";

WitnessMaker maker = new WitnessMaker(secp);
SecpXOnlyPubKey internalPubkey = secp.xOnlyPubKeyParse(serializedInternalPubKey).get();
SecpFieldElement tweakedPubKey = maker.tweakedPubKey(internalPubkey);
Address tapRootAddress = SegwitAddress.fromProgram(network, 1, tweakedPubKey.toByteArray());
Address tapRootAddress = TapRootAddress.fromXOnlyPubKey(secp, network, internalPubkey);

Assertions.assertEquals(expectedBip350Address, tapRootAddress.toString());
}
Expand All @@ -106,7 +104,7 @@ void bipVector0WithIntermediateChecks() {
Assertions.assertArrayEquals(expectedTweak, tweak.toByteArray());

// tweakedPubKey (aka Q.x(), where Q = P + int(hashTapTweak(bytes(P)))G)
SecpFieldElement tweakedPubKey = maker.tweakedPubKey(internalPubkey, tweak);
SecpXOnlyPubKey tweakedPubKey = maker.tweakedPubKey(internalPubkey, tweak);

Assertions.assertArrayEquals(expectedTweakedPubkey, tweakedPubKey.toByteArray());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.bitcoinj.secp.bouncy;

import org.bitcoinj.secp.EcdhSharedSecret;
import org.bitcoinj.secp.SecpHash256;
import org.bitcoinj.secp.SecpKeyPair;
import org.bitcoinj.secp.SecpPoint;
import org.bitcoinj.secp.SecpPubKey;
Expand Down Expand Up @@ -301,7 +302,21 @@ public SecpResult<Boolean> ecdsaVerify(EcdsaSignature signature, byte[] msg_hash
}

@Override
public byte[] taggedSha256(byte[] tag, byte[] message) {
public SecpHash256Bc sha256(byte[] message) {
try {
return new SecpHash256Bc(MessageDigest.getInstance("SHA-256").digest(message));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e); // Can't happen.
}
}

@Override
public SecpHash256Bc sha256Import(byte[] messageHash) {
return new SecpHash256Bc(messageHash);
}

@Override
public SecpHash256Bc taggedSha256(byte[] tag, byte[] message) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] tagHash;
Expand All @@ -314,7 +329,7 @@ public byte[] taggedSha256(byte[] tag, byte[] message) {
digest.update(tagHash, 0, 32);
digest.update(message, 0, message.length);

return digest.digest();
return new SecpHash256Bc(digest.digest());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
Expand All @@ -326,20 +341,21 @@ public SchnorrSignature schnorrSigSign32(byte[] msg_hash, SecpPrivKey privKey) {
byte[] auxiliaryRandom = new byte[32];
try {
fillRandom(auxiliaryRandom);
return schnorrSigSign32(msg_hash, privKey, auxiliaryRandom);
SecpHash256 hash = new SecpHash256Bc(msg_hash);
return schnorrSigSign32(hash, privKey, auxiliaryRandom);
} finally {
Arrays.fill(auxiliaryRandom, (byte) 0);
}
}

@Override
public SchnorrSignature schnorrSigSign32(byte[] msg_hash, SecpPrivKey privKey, byte[] auxiliaryRandom) {
public SchnorrSignature schnorrSigSign32(SecpHash256 messageHash, SecpPrivKey privKey, byte[] auxiliaryRandom) {
ECPrivateKeyParameters priv = new ECPrivateKeyParameters(privKey.getS(), BC_ECDOMAIN_PARAMS);

BIP340Signer signer = new BIP340Signer();

signer.init(true, new ParametersWithRandom(priv, new FixedBytesRandom(auxiliaryRandom)));
signer.update(msg_hash, 0, msg_hash.length);
signer.update(messageHash.toByteArray(), 0, 32);

return SchnorrSignatureImpl.of(signer.generateSignature());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2023-2026 secp256k1-jdk Developers.
*
* 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.bitcoinj.secp.bouncy;

import org.bitcoinj.secp.SecpHash256;
import org.bitcoinj.secp.internal.UInt256;

import java.math.BigInteger;

/**
*
*/
public class SecpHash256Bc implements SecpHash256 {
private final byte[] hash;

public SecpHash256Bc(byte[] hash) {
this.hash = hash;
}

@Override
public BigInteger toBigInteger() {
return UInt256.toBigInteger(hash);
}

@Override
public byte[] toByteArray() {
return hash.clone();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

import module org.bitcoinj.secp;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import static java.lang.IO.println;
Expand All @@ -29,7 +27,7 @@ public class Ecdsa {
* Here the message is "Hello, world!" and the hash function is SHA-256.
* See https://bitcoin.stackexchange.com/questions/81115/if-someone-wanted-to-pretend-to-be-satoshi-by-posting-a-fake-signature-to-defrau/81116#81116
*/
final byte[] messageHash = hash("Hello, world!");
final byte[] message = "Hello, world!".getBytes();

void main() {
println("Running secp256k1-jdk Ecdsa example...");
Expand All @@ -50,6 +48,7 @@ void main() {

/* Generate an ECDSA signature using Bitcoin-standard low-r nonce grinding.
* Signing with a valid context and verified secret key should never fail. */
SecpHash256 messageHash = secp.sha256(message);
EcdsaSignature sig = secp.ecdsaSignLowR(messageHash, privKey).get();

/* Serialize the signature in a compact form. Should always succeed according to
Expand Down Expand Up @@ -83,12 +82,4 @@ void main() {
privKey.destroy();
}
}

private static byte[] hash(String messageString) {
try {
return MessageDigest.getInstance("SHA-256").digest(messageString.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e); // Can't happen.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void main() {

/* === Signing === */

byte[] messageHash = secp.taggedSha256(tag, msg);
SecpHash256 messageHash = secp.taggedSha256(tag, msg);

SchnorrSignature signature = secp.schnorrSigSign32(messageHash, keyPair);

Expand All @@ -51,7 +51,7 @@ void main() {
SecpXOnlyPubKey xOnly2 = secp.xOnlyPubKeyParse(serializedXOnly).get();

/* Compute the tagged hash on the received message using the same tag as the signer. */
byte[] messageHash2 = secp.taggedSha256(tag, msg);
SecpHash256 messageHash2 = secp.taggedSha256(tag, msg);

boolean isValidSignature = secp.schnorrSigVerify(signature, messageHash2, xOnly2).get();

Expand Down
Loading
Loading