add pay to anchor script and address type

This commit is contained in:
Craig Raw 2025-04-14 15:49:02 +02:00
parent 3b36947419
commit abb598d3b0
5 changed files with 167 additions and 5 deletions

View file

@ -134,6 +134,8 @@ public abstract class Address {
byte[] witnessProgram = Bech32.convertBits(convertedProgram, 0, convertedProgram.length, 5, 8, false);
if(witnessProgram.length == 32) {
return new P2TRAddress(witnessProgram);
} else if(Arrays.equals(witnessProgram, ScriptType.ANCHOR_WITNESS_PROGRAM)) {
return new P2AAddress(witnessProgram);
}
}
}

View file

@ -0,0 +1,31 @@
package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.drongo.protocol.Bech32;
import com.sparrowwallet.drongo.protocol.ScriptType;
public class P2AAddress extends Address {
public P2AAddress(byte[] data) {
super(data);
}
@Override
public int getVersion(Network network) {
return 1;
}
@Override
public String getAddress(Network network) {
return Bech32.encode(network.getBech32AddressHRP(), getVersion(), data);
}
@Override
public ScriptType getScriptType() {
return ScriptType.P2A;
}
@Override
public String getOutputScriptDataType() {
return "Anchor";
}
}

View file

@ -212,6 +212,10 @@ public class Script {
return addresses.toArray(new Address[addresses.size()]);
}
if(P2A.isScriptType(this)) {
return new Address[] { P2A.getAddress(P2A.getDataFromScript(this)) };
}
throw new NonStandardScriptException("Cannot find addresses in non standard script: " + toString());
}

View file

@ -1134,6 +1134,122 @@ public enum ScriptType {
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
}
},
P2A("P2A", "Anchor (P2A)", "m/86'/0'/0'") {
@Override
public Address getAddress(byte[] data) {
return new P2AAddress(data);
}
@Override
public Address getAddress(ECKey derivedKey) {
throw new ProtocolException("Cannot create a anchor address with a key");
}
@Override
public Address getAddress(Script script) {
throw new ProtocolException("Cannot create a anchor address with a script");
}
@Override
public Script getOutputScript(byte[] data) {
List<ScriptChunk> chunks = new ArrayList<>();
chunks.add(new ScriptChunk(OP_1, null));
chunks.add(new ScriptChunk(data.length, data));
return new Script(chunks);
}
@Override
public Script getOutputScript(ECKey derivedKey) {
throw new ProtocolException("Cannot create an anchor output script with a key");
}
@Override
public Script getOutputScript(Script script) {
throw new ProtocolException("Cannot create an anchor output script with a script");
}
@Override
public String getOutputDescriptor(ECKey derivedKey) {
throw new ProtocolException("Cannot create an anchor output descriptor with a key");
}
@Override
public String getOutputDescriptor(Script script) {
throw new ProtocolException("Cannot create an anchor output descriptor with a script");
}
@Override
public String getDescriptor() {
return "addr(";
}
@Override
public boolean isScriptType(Script script) {
List<ScriptChunk> chunks = script.chunks;
if (chunks.size() != 2)
return false;
if (!chunks.get(0).equalsOpCode(OP_1))
return false;
byte[] chunk1data = chunks.get(1).data;
if (chunk1data == null)
return false;
if (!Arrays.equals(chunk1data, ANCHOR_WITNESS_PROGRAM)) {
return false;
}
return true;
}
@Override
public byte[] getDataFromScript(Script script) {
return script.chunks.get(1).data;
}
@Override
public byte[] getHashFromScript(Script script) {
throw new ProtocolException("P2A does not contain a hash");
}
@Override
public ECKey getPublicKeyFromScript(Script script) {
throw new ProtocolException("P2A does not contain a key");
}
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
return new Script(new byte[0]);
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
Script scriptSig = getScriptSig(prevOutput.getScript(), pubKey, signature);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, Map<ECKey, TransactionSignature> pubKeySignatures) {
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, Map<ECKey, TransactionSignature> pubKeySignatures) {
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.SCHNORR;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return Collections.emptyList();
}
};
private final String name;
@ -1250,6 +1366,10 @@ public enum ScriptType {
public abstract boolean isScriptType(Script script);
public byte[] getDataFromScript(Script script) {
throw new ProtocolException("Script type " + this + " does not contain data");
}
public abstract byte[] getHashFromScript(Script script);
public Address[] getAddresses(Script script) {
@ -1282,11 +1402,13 @@ public enum ScriptType {
public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};
public static final ScriptType[] ADDRESSABLE_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH, P2TR};
public static final ScriptType[] ADDRESSABLE_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH, P2TR, P2A};
public static final ScriptType[] NON_WITNESS_TYPES = {P2PK, P2PKH, P2SH};
public static final ScriptType[] WITNESS_TYPES = {P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH, P2TR};
public static final ScriptType[] WITNESS_TYPES = {P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH, P2TR, P2A};
public static final byte[] ANCHOR_WITNESS_PROGRAM = new byte[] {78, 115};
public static List<ScriptType> getScriptTypesForPolicyType(PolicyType policyType) {
return Arrays.stream(values()).filter(scriptType -> scriptType.isAllowed(policyType)).collect(Collectors.toList());
@ -1364,6 +1486,8 @@ public enum ScriptType {
} else if(P2TR.equals(this)) {
//Assume a default keypath spend
return (32 + 4 + 1 + ((double)66 / WITNESS_SCALE_FACTOR) + 4);
} else if(P2A.equals(this)) {
return 32 + 4 + 1 + 4;
} else if(Arrays.asList(WITNESS_TYPES).contains(this)) {
//Return length of spending input with 75% discount to script size
return (32 + 4 + 1 + ((double)107 / WITNESS_SCALE_FACTOR) + 4);

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.P2AAddress;
public class Payment {
private Address address;
@ -15,10 +16,10 @@ public class Payment {
public Payment(Address address, String label, long amount, boolean sendMax, Type type) {
this.address = address;
this.label = label;
this.label = label == null && address instanceof P2AAddress ? address.getOutputScriptDataType() : label;
this.amount = amount;
this.sendMax = sendMax;
this.type = type;
this.type = type == Type.DEFAULT && address instanceof P2AAddress ? Type.ANCHOR : type;
}
public Address getAddress() {
@ -62,6 +63,6 @@ public class Payment {
}
public enum Type {
DEFAULT, WHIRLPOOL_FEE, FAKE_MIX, MIX;
DEFAULT, WHIRLPOOL_FEE, FAKE_MIX, MIX, ANCHOR;
}
}