ScriptBuilder: add ability to build scripts with creationTime

This commit is contained in:
Andreas Schildbach 2023-04-05 18:05:37 +02:00
parent 6d00650efa
commit 15ee38aae0
3 changed files with 64 additions and 21 deletions

View File

@ -235,9 +235,9 @@ public class Script {
}
// Used from ScriptBuilder.
Script(List<ScriptChunk> chunks) {
Script(List<ScriptChunk> chunks, Instant creationTime) {
this.chunks = Collections.unmodifiableList(new ArrayList<>(chunks));
creationTime = TimeUtils.currentTime();
this.creationTime = creationTime;
}
/**

View File

@ -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<ScriptChunk> 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 <pubKeyHash|scriptHash>
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);
}
/**

View File

@ -1076,8 +1076,7 @@ public class Wallet extends BaseTaggableObject
public int addWatchedAddresses(final List<Address> addresses, Instant creationTime) {
List<Script> scripts = new ArrayList<>();
for (Address address : addresses) {
Script script = ScriptBuilder.createOutputScript(address);
script.setCreationTime(creationTime);
Script script = ScriptBuilder.createOutputScript(address, creationTime);
scripts.add(script);
}
return addWatchedScripts(scripts);
@ -1093,7 +1092,6 @@ public class Wallet extends BaseTaggableObject
List<Script> scripts = new ArrayList<>();
for (Address address : addresses) {
Script script = ScriptBuilder.createOutputScript(address);
script.clearCreationTime();
scripts.add(script);
}
return addWatchedScripts(scripts);