From 15ee38aae08372d8524d2fd1b916ac3aca88d190 Mon Sep 17 00:00:00 2001 From: Andreas Schildbach Date: Wed, 5 Apr 2023 18:05:37 +0200 Subject: [PATCH] ScriptBuilder: add ability to build scripts with `creationTime` --- .../main/java/org/bitcoinj/script/Script.java | 4 +- .../org/bitcoinj/script/ScriptBuilder.java | 77 +++++++++++++++---- .../main/java/org/bitcoinj/wallet/Wallet.java | 4 +- 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/script/Script.java b/core/src/main/java/org/bitcoinj/script/Script.java index 8d608cb90..191ecf94f 100644 --- a/core/src/main/java/org/bitcoinj/script/Script.java +++ b/core/src/main/java/org/bitcoinj/script/Script.java @@ -235,9 +235,9 @@ public class Script { } // Used from ScriptBuilder. - Script(List chunks) { + Script(List chunks, Instant creationTime) { this.chunks = Collections.unmodifiableList(new ArrayList<>(chunks)); - creationTime = TimeUtils.currentTime(); + this.creationTime = creationTime; } /** diff --git a/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java b/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java index aaed16e7e..8c959f52b 100644 --- a/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java +++ b/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java @@ -19,6 +19,7 @@ package org.bitcoinj.script; import org.bitcoinj.base.Address; +import org.bitcoinj.base.internal.TimeUtils; import org.bitcoinj.crypto.ECKey; import org.bitcoinj.base.LegacyAddress; import org.bitcoinj.base.SegwitAddress; @@ -29,11 +30,13 @@ import org.bitcoinj.base.ScriptType; import org.bitcoinj.crypto.internal.CryptoUtils; import javax.annotation.Nullable; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Stack; import static org.bitcoinj.base.internal.Preconditions.checkArgument; @@ -58,6 +61,7 @@ import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN; */ public class ScriptBuilder { private final List chunks; + private Instant creationTime = TimeUtils.currentTime(); /** Creates a fresh ScriptBuilder with an empty program. */ public ScriptBuilder() { @@ -69,6 +73,17 @@ public class ScriptBuilder { chunks = new ArrayList<>(template.getChunks()); } + /** + * Sets the creation time to build the script with. If this is not set, the current time is used by the builder. + * + * @param creationTime creation time to build the script with + * @return this builder + */ + public ScriptBuilder creationTime(Instant creationTime) { + this.creationTime = Objects.requireNonNull(creationTime); + return this; + } + /** Adds the given chunk to the end of the program */ public ScriptBuilder addChunk(ScriptChunk chunk) { return addChunk(chunks.size(), chunk); @@ -262,7 +277,7 @@ public class ScriptBuilder { /** Creates a new immutable Script based on the state of the builder. */ public Script build() { - return new Script(chunks); + return new Script(chunks, creationTime); } /** Creates an empty script. */ @@ -270,26 +285,46 @@ public class ScriptBuilder { return new ScriptBuilder().build(); } - /** Creates a scriptPubKey that encodes payment to the given address. */ + /** + * Creates a scriptPubKey that encodes payment to the given address. + * + * @param to address to send payment to + * @param creationTime creation time of the scriptPubKey + * @return scriptPubKey + */ + public static Script createOutputScript(Address to, Instant creationTime) { + return new ScriptBuilder().outputScript(to).creationTime(creationTime).build(); + } + + /** + * Creates a scriptPubKey that encodes payment to the given address. The creation time will be the current time. + * + * @param to address to send payment to + * @return scriptPubKey + */ public static Script createOutputScript(Address to) { + return new ScriptBuilder().outputScript(to).build(); + } + + private ScriptBuilder outputScript(Address to) { + checkState(chunks.isEmpty()); if (to instanceof LegacyAddress) { ScriptType scriptType = to.getOutputScriptType(); if (scriptType == ScriptType.P2PKH) - return createP2PKHOutputScript(((LegacyAddress) to).getHash()); + p2pkhOutputScript(((LegacyAddress) to).getHash()); else if (scriptType == ScriptType.P2SH) - return createP2SHOutputScript(((LegacyAddress) to).getHash()); + p2shOutputScript(((LegacyAddress) to).getHash()); else throw new IllegalStateException("Cannot handle " + scriptType); } else if (to instanceof SegwitAddress) { - ScriptBuilder builder = new ScriptBuilder(); // OP_0 SegwitAddress toSegwit = (SegwitAddress) to; - builder.smallNum(toSegwit.getWitnessVersion()); - builder.data(toSegwit.getWitnessProgram()); - return builder.build(); + smallNum(toSegwit.getWitnessVersion()); + data(toSegwit.getWitnessProgram()); } else { throw new IllegalStateException("Cannot handle " + to); } + return this; } /** @@ -457,14 +492,17 @@ public class ScriptBuilder { * Creates a scriptPubKey that sends to the given public key hash. */ public static Script createP2PKHOutputScript(byte[] hash) { + return new ScriptBuilder().p2pkhOutputScript(hash).build(); + } + + private ScriptBuilder p2pkhOutputScript(byte[] hash) { checkArgument(hash.length == LegacyAddress.LENGTH); - ScriptBuilder builder = new ScriptBuilder(); - builder.op(OP_DUP); - builder.op(OP_HASH160); - builder.data(hash); - builder.op(OP_EQUALVERIFY); - builder.op(OP_CHECKSIG); - return builder.build(); + checkState(chunks.isEmpty()); + return op(OP_DUP) + .op(OP_HASH160) + .data(hash) + .op(OP_EQUALVERIFY) + .op(OP_CHECKSIG); } /** @@ -500,8 +538,15 @@ public class ScriptBuilder { * @return an output script that sends to the redeem script */ public static Script createP2SHOutputScript(byte[] hash) { + return new ScriptBuilder().p2shOutputScript(hash).build(); + } + + private ScriptBuilder p2shOutputScript(byte[] hash) { checkArgument(hash.length == 20); - return new ScriptBuilder().op(OP_HASH160).data(hash).op(OP_EQUAL).build(); + checkState(chunks.isEmpty()); + return op(OP_HASH160) + .data(hash) + .op(OP_EQUAL); } /** diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java index 1f952c0e0..749bf4109 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java +++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java @@ -1076,8 +1076,7 @@ public class Wallet extends BaseTaggableObject public int addWatchedAddresses(final List
addresses, Instant creationTime) { List