diff --git a/core/src/main/java/org/bitcoinj/core/VersionMessage.java b/core/src/main/java/org/bitcoinj/core/VersionMessage.java index 92eb2d39955..72fd0d6b377 100644 --- a/core/src/main/java/org/bitcoinj/core/VersionMessage.java +++ b/core/src/main/java/org/bitcoinj/core/VersionMessage.java @@ -16,7 +16,6 @@ package org.bitcoinj.core; -import com.google.common.net.InetAddresses; import org.bitcoinj.base.internal.Buffers; import org.bitcoinj.base.internal.TimeUtils; import org.bitcoinj.base.internal.ByteUtils; @@ -24,6 +23,7 @@ import org.jspecify.annotations.Nullable; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; @@ -149,14 +149,21 @@ public VersionMessage(NetworkParameters params, int bestHeight) { this.clientVersion = ProtocolVersion.CURRENT.intValue(); this.localServices = Services.none(); this.time = TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS); - InetAddress localhost = InetAddresses.forString("127.0.0.1"); this.receivingServices = Services.none(); - this.receivingAddr = new InetSocketAddress(localhost, params.getPort()); + this.receivingAddr = new InetSocketAddress(getLocalhostAddr(), params.getPort()); this.subVer = LIBRARY_SUBVER; this.bestHeight = bestHeight; this.relayTxesBeforeFilter = true; } + private InetAddress getLocalhostAddr() { + try { + return InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + private VersionMessage(int clientVersion, Services localServices, Instant time, Services receivingServices, InetSocketAddress receivingAddr, String subVer, long bestHeight, boolean relayTxesBeforeFilter) { diff --git a/core/src/main/java/org/bitcoinj/script/Script.java b/core/src/main/java/org/bitcoinj/script/Script.java index 32c316ecbc3..7d68ad99829 100644 --- a/core/src/main/java/org/bitcoinj/script/Script.java +++ b/core/src/main/java/org/bitcoinj/script/Script.java @@ -114,7 +114,7 @@ public static Script of(List chunks) { * @param creationTime creation time to associate the script with * @return script that wraps the chunks */ - public static Script of(List chunks, Instant creationTime) { + public static Script of(List chunks, @Nullable Instant creationTime) { return new Script(chunks, creationTime); } @@ -139,7 +139,7 @@ public static Script parse(byte[] program) throws ScriptException { * @return parsed program * @throws ScriptException if the program could not be parsed */ - public static Script parse(byte[] program, Instant creationTime) throws ScriptException { + public static Script parse(byte[] program, @Nullable Instant creationTime) throws ScriptException { return new Script(program, creationTime); } @@ -476,8 +476,10 @@ public List getPubKeys() { ArrayList result = new ArrayList<>(); int numKeys = Script.decodeFromOpN(chunks.get(chunks.size() - 2).opcode); - for (int i = 0 ; i < numKeys ; i++) - result.add(ECKey.fromPublicOnly(chunks.get(1 + i).data)); + for (int i = 0 ; i < numKeys ; i++) { + byte[] data = Objects.requireNonNull(chunks.get(1 + i).data); + result.add(ECKey.fromPublicOnly(data)); + } return result; } @@ -486,7 +488,8 @@ private int findSigInRedeem(byte[] signatureBytes, Sha256Hash hash) throws Signa int numKeys = Script.decodeFromOpN(chunks.get(chunks.size() - 2).opcode); TransactionSignature signature = TransactionSignature.decodeFromBitcoin(signatureBytes, true, false); for (int i = 0 ; i < numKeys ; i++) { - if (ECKey.fromPublicOnly(chunks.get(i + 1).data).verify(hash, signature)) { + byte[] data = Objects.requireNonNull(chunks.get(1 + i).data); + if (ECKey.fromPublicOnly(data).verify(hash, signature)) { return i; } } @@ -570,7 +573,7 @@ public static long getP2SHSigOpCount(byte[] scriptSig) throws ScriptException { Collections.reverse(chunks); for (ScriptChunk chunk : chunks) { if (!chunk.isOpCode()) { - Script subScript = parse(chunk.data); + Script subScript = parse(Objects.requireNonNull(chunk.data)); return getSigOpCount(subScript.chunks, true); } } diff --git a/core/src/main/java/org/bitcoinj/script/ScriptExecution.java b/core/src/main/java/org/bitcoinj/script/ScriptExecution.java index 5fbe0f6de29..52045897428 100644 --- a/core/src/main/java/org/bitcoinj/script/ScriptExecution.java +++ b/core/src/main/java/org/bitcoinj/script/ScriptExecution.java @@ -46,6 +46,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Set; import static org.bitcoinj.script.ScriptOpCodes.OP_0; @@ -718,6 +719,7 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in } break; } + Objects.requireNonNull(txContainingThis); executeCheckLockTimeVerify(txContainingThis, (int) index, stack, verifyFlags); break; case OP_CHECKSEQUENCEVERIFY: @@ -728,6 +730,7 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in } break; } + Objects.requireNonNull(txContainingThis); executeCheckSequenceVerify(txContainingThis, (int) index, stack, verifyFlags); break; case OP_NOP1: @@ -1018,6 +1021,7 @@ public static void correctlySpends(Script script, Transaction txContainingThis, Script scriptPubKey, Set verifyFlags) throws ScriptException { List chunks = script.chunks(); if (ScriptPattern.isP2WPKH(scriptPubKey)) { + Objects.requireNonNull(witness); // For segwit, full validation isn't implemented. So we simply check the signature. P2SH_P2WPKH is handled // by the P2SH code for now. if (witness.getPushCount() < 2) @@ -1040,11 +1044,12 @@ public static void correctlySpends(Script script, Transaction txContainingThis, throw new ScriptException(ScriptError.SCRIPT_ERR_SCRIPT_SIZE, "Invalid size: " + chunks.size()); TransactionSignature signature; try { - signature = TransactionSignature.decodeFromBitcoin(chunks.get(0).data, true, true); + byte[] data = Objects.requireNonNull(chunks.get(0).data); + signature = TransactionSignature.decodeFromBitcoin(data, true, true); } catch (SignatureDecodeException x) { throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_DER, "Cannot decode", x); } - ECKey pubkey = ECKey.fromPublicOnly(chunks.get(1).data); + ECKey pubkey = ECKey.fromPublicOnly(Objects.requireNonNull(chunks.get(1).data)); Sha256Hash sigHash = txContainingThis.hashForSignature(scriptSigIndex, scriptPubKey, signature.sigHashMode(), false); boolean validSig = pubkey.verify(sigHash, signature); @@ -1055,7 +1060,8 @@ public static void correctlySpends(Script script, Transaction txContainingThis, throw new ScriptException(ScriptError.SCRIPT_ERR_SCRIPT_SIZE, "Invalid size: " + chunks.size()); TransactionSignature signature; try { - signature = TransactionSignature.decodeFromBitcoin(chunks.get(0).data, false, false); + byte[] data = Objects.requireNonNull(chunks.get(0).data); + signature = TransactionSignature.decodeFromBitcoin(data, false, false); } catch (SignatureDecodeException x) { throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_DER, "Cannot decode", x); } @@ -1121,6 +1127,7 @@ public static void correctlySpends(Script script, Transaction txContainingThis, // TODO: Check if we can take out enforceP2SH if there's a checkpoint at the enforcement block. if (verifyFlags.contains(VerifyFlag.P2SH) && ScriptPattern.isP2SH(scriptPubKey)) { + Objects.requireNonNull(p2shStack); for (ScriptChunk chunk : script.chunks()) if (!chunk.isPushData()) throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_PUSHONLY, "Attempted to spend a P2SH scriptPubKey with a script that contained the script op " + chunk); diff --git a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java index 437b0e2ac06..568639ab920 100644 --- a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java +++ b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Objects; import static org.bitcoinj.script.Script.decodeFromOpN; import static org.bitcoinj.script.ScriptOpCodes.OP_0; @@ -72,7 +73,7 @@ public static boolean isP2PKH(Script script) { * will want to guard calls to this method with {@link #isP2PKH(Script)}. */ public static byte[] extractHashFromP2PKH(Script script) { - return script.chunks().get(2).data; + return Objects.requireNonNull(script.chunks().get(2).data); } /** @@ -114,7 +115,7 @@ public static boolean isP2SH(Script script) { * will want to guard calls to this method with {@link #isP2SH(Script)}. */ public static byte[] extractHashFromP2SH(Script script) { - return script.chunks().get(1).data; + return Objects.requireNonNull(script.chunks().get(1).data); } /** @@ -145,7 +146,7 @@ public static boolean isP2PK(Script script) { * want to guard calls to this method with {@link #isP2PK(Script)}. */ public static byte[] extractKeyFromP2PK(Script script) { - return script.chunks().get(0).data; + return Objects.requireNonNull(script.chunks().get(0).data); } /** @@ -201,7 +202,7 @@ public static boolean isP2WSH(Script script) { * {@link #isP2WH(Script)}. */ public static byte[] extractHashFromP2WH(Script script) { - return script.chunks().get(1).data; + return Objects.requireNonNull(script.chunks().get(1).data); } /** @@ -227,7 +228,7 @@ public static boolean isP2TR(Script script) { * form, so you will want to guard calls to this method with {@link #isP2TR(Script)}. */ public static byte[] extractOutputKeyFromP2TR(Script script) { - return script.chunks().get(1).data; + return Objects.requireNonNull(script.chunks().get(1).data); } /** diff --git a/core/src/main/java/org/bitcoinj/script/package-info.java b/core/src/main/java/org/bitcoinj/script/package-info.java index 9dba0b607d8..8bfd5a29bad 100644 --- a/core/src/main/java/org/bitcoinj/script/package-info.java +++ b/core/src/main/java/org/bitcoinj/script/package-info.java @@ -17,4 +17,7 @@ /** * Classes for working with and executing Bitcoin script programs, as embedded in inputs and outputs. */ -package org.bitcoinj.script; \ No newline at end of file +@NullMarked +package org.bitcoinj.script; + +import org.jspecify.annotations.NullMarked; diff --git a/core/src/test/java/org/bitcoinj/core/VersionMessageTest.java b/core/src/test/java/org/bitcoinj/core/VersionMessageTest.java index b5905fb36e3..adbb0b68bfa 100644 --- a/core/src/test/java/org/bitcoinj/core/VersionMessageTest.java +++ b/core/src/test/java/org/bitcoinj/core/VersionMessageTest.java @@ -20,8 +20,10 @@ import org.bitcoinj.params.TestNet3Params; import org.junit.Test; +import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.time.Instant; @@ -32,6 +34,23 @@ public class VersionMessageTest { private static final NetworkParameters TESTNET = TestNet3Params.get(); + private static final Inet4Address LOCALHOST_IPV4ADDR = getLocalhostAddr(); + + private static Inet4Address getLocalhostAddr() { + try { + return (Inet4Address) InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testConstructor() { + VersionMessage versionMessage = new VersionMessage(TESTNET, 0); + // Receiving address is currently hardcoded to 127.0.0.1 (ipv4 localhost) + assertEquals(LOCALHOST_IPV4ADDR, versionMessage.receivingAddr.getAddress()); + assertEquals(TESTNET.getPort(), versionMessage.receivingAddr.getPort()); + } @Test public void decode_noRelay_bestHeight_subVer() { diff --git a/core/src/test/java/org/bitcoinj/script/ScriptChunkSizeTest.java b/core/src/test/java/org/bitcoinj/script/ScriptChunkSizeTest.java index 4694c3d8415..192e8d73cd5 100644 --- a/core/src/test/java/org/bitcoinj/script/ScriptChunkSizeTest.java +++ b/core/src/test/java/org/bitcoinj/script/ScriptChunkSizeTest.java @@ -16,6 +16,7 @@ package org.bitcoinj.script; +import org.jspecify.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -24,6 +25,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Random; import static org.bitcoinj.script.ScriptOpCodes.OP_NOP; @@ -42,7 +44,7 @@ public class ScriptChunkSizeTest { private static final Random RANDOM = new Random(42); @Parameterized.Parameter - public ScriptChunk scriptChunk; + @Nullable public ScriptChunk scriptChunk; @Parameterized.Parameters public static Collection data() { @@ -83,6 +85,7 @@ private static byte[] randomBytes(int size) { @Test public void testSize() { + Objects.requireNonNull(scriptChunk); assertEquals(scriptChunk.toByteArray().length, scriptChunk.size()); } } diff --git a/core/src/test/java/org/bitcoinj/script/ScriptTest.java b/core/src/test/java/org/bitcoinj/script/ScriptTest.java index 12982dcec97..09ac0f94ef5 100644 --- a/core/src/test/java/org/bitcoinj/script/ScriptTest.java +++ b/core/src/test/java/org/bitcoinj/script/ScriptTest.java @@ -79,6 +79,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -393,9 +394,10 @@ public void dataDrivenValidTransactions() throws Exception { for (int i = 0; i < transaction.getInputs().size(); i++) { TransactionInput input = transaction.getInput(i); - assertTrue(scriptPubKeys.containsKey(input.getOutpoint())); + Script script = scriptPubKeys.get(input.getOutpoint()); + assertNotNull(script); ScriptExecution.correctlySpends(input.getScriptSig(), transaction, i, null, null, - scriptPubKeys.get(input.getOutpoint()), verifyFlags); + script, verifyFlags); } } catch (Exception e) { System.err.println(test); @@ -442,10 +444,11 @@ public void dataDrivenInvalidTransactions() throws Exception { for (int i = 0; i < transaction.getInputs().size() && valid; i++) { TransactionInput input = transaction.getInput(i); - assertTrue(scriptPubKeys.containsKey(input.getOutpoint())); + Script script = scriptPubKeys.get(input.getOutpoint()); + assertNotNull(script); try { ScriptExecution.correctlySpends(input.getScriptSig(), transaction, i, null, null, - scriptPubKeys.get(input.getOutpoint()), verifyFlags); + script, verifyFlags); } catch (VerificationException e) { valid = false; } diff --git a/examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java b/examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java index a8e9ebae207..dbd631729db 100644 --- a/examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java +++ b/examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java @@ -106,7 +106,7 @@ public RedeemData findRedeemDataFromScriptHash(byte[] scriptHash) { EnumSet.of(ScriptExecution.VerifyFlag.DERSIG, ScriptExecution.VerifyFlag.P2SH)); final Script scriptSig = input.getScriptSig(); - final TransactionSignature signature = TransactionSignature.decodeFromBitcoin(scriptSig.chunks().get(0).pushData(), true, false); + final TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Objects.requireNonNull(scriptSig.chunks().get(0).pushData()), true, false); // First output a conventional low-S transaction with the LOW_S flag, for the tx_valid.json set System.out.println("[\"A transaction with a low-S signature.\"],"); @@ -119,7 +119,7 @@ public RedeemData findRedeemDataFromScriptHash(byte[] scriptHash) { final BigInteger highS = HIGH_S_DIFFERENCE.subtract(signature.s); final TransactionSignature highSig = new TransactionSignature(signature.r, highS); - input = input.withScriptSig(new ScriptBuilder().data(highSig.encodeToBitcoin()).data(scriptSig.chunks().get(1).pushData()).build()); + input = input.withScriptSig(new ScriptBuilder().data(highSig.encodeToBitcoin()).data(Objects.requireNonNull(scriptSig.chunks().get(1).pushData())).build()); ScriptExecution.correctlySpends(input.getScriptSig(), outputTransaction, 0, null, null, output.getScriptPubKey(), EnumSet.of(ScriptExecution.VerifyFlag.P2SH));