mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-11-02 18:26:43 +00:00
add support for sending to taproot addresses
This commit is contained in:
parent
107a165fc1
commit
967a2c2026
10 changed files with 279 additions and 24 deletions
|
@ -109,6 +109,10 @@ public abstract class Address {
|
|||
if(data.hrp.equals(network.getBech32AddressHRP())) {
|
||||
int witnessVersion = data.data[0];
|
||||
if(witnessVersion == 0) {
|
||||
if(data.encoding != Bech32.Encoding.BECH32) {
|
||||
throw new InvalidAddressException("Invalid address - witness version is 0 but encoding is " + data.encoding);
|
||||
}
|
||||
|
||||
byte[] convertedProgram = Arrays.copyOfRange(data.data, 1, data.data.length);
|
||||
byte[] witnessProgram = Bech32.convertBits(convertedProgram, 0, convertedProgram.length, 5, 8, false);
|
||||
if(witnessProgram.length == 20) {
|
||||
|
@ -117,6 +121,16 @@ public abstract class Address {
|
|||
if(witnessProgram.length == 32) {
|
||||
return new P2WSHAddress(witnessProgram);
|
||||
}
|
||||
} else if(witnessVersion == 1) {
|
||||
if(data.encoding != Bech32.Encoding.BECH32M) {
|
||||
throw new InvalidAddressException("Invalid address - witness version is 1 but encoding is " + data.encoding);
|
||||
}
|
||||
|
||||
byte[] convertedProgram = Arrays.copyOfRange(data.data, 1, data.data.length);
|
||||
byte[] witnessProgram = Bech32.convertBits(convertedProgram, 0, convertedProgram.length, 5, 8, false);
|
||||
if(witnessProgram.length == 32) {
|
||||
return new P2TRAddress(witnessProgram);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import com.sparrowwallet.drongo.protocol.Script;
|
|||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
public class P2PKAddress extends Address {
|
||||
private byte[] pubKey;
|
||||
private final byte[] pubKey;
|
||||
|
||||
public P2PKAddress(byte[] pubKey) {
|
||||
super(Utils.sha256hash160(pubKey));
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package com.sparrowwallet.drongo.address;
|
||||
|
||||
import com.sparrowwallet.drongo.Network;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.Bech32;
|
||||
import com.sparrowwallet.drongo.protocol.Script;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
public class P2TRAddress extends Address {
|
||||
private final byte[] pubKey;
|
||||
|
||||
public P2TRAddress(byte[] pubKey) {
|
||||
super(Utils.sha256hash160(pubKey));
|
||||
this.pubKey = pubKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion(Network network) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAddress(Network network) {
|
||||
return Bech32.encode(network.getBech32AddressHRP(), getVersion(), pubKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptType getScriptType() {
|
||||
return ScriptType.P2TR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Script getOutputScript() {
|
||||
return getScriptType().getOutputScript(pubKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getOutputScriptData() {
|
||||
return pubKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutputScriptDataType() {
|
||||
return "Taproot";
|
||||
}
|
||||
}
|
|
@ -340,6 +340,13 @@ public class ECKey implements EncryptableItem {
|
|||
return pub.getEncoded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the x coordinate of the raw public key value. This appears in transaction scriptPubKeys for Taproot outputs.
|
||||
*/
|
||||
public byte[] getPubKeyXCoord() {
|
||||
return pub.getEncodedXCoord();
|
||||
}
|
||||
|
||||
/** Gets the public key in the form of an elliptic curve point object from Bouncy Castle. */
|
||||
public ECPoint getPubKeyPoint() {
|
||||
return pub.get();
|
||||
|
@ -625,8 +632,10 @@ public class ECKey implements EncryptableItem {
|
|||
* Returns true if the given pubkey is canonical, i.e. the correct length taking into account compression.
|
||||
*/
|
||||
public static boolean isPubKeyCanonical(byte[] pubkey) {
|
||||
if (pubkey.length < 33)
|
||||
if (pubkey.length < 32)
|
||||
return false;
|
||||
if (pubkey.length == 32)
|
||||
return true;
|
||||
if (pubkey[0] == 0x04) {
|
||||
// Uncompressed pubkey
|
||||
if (pubkey.length != 65)
|
||||
|
@ -644,7 +653,7 @@ public class ECKey implements EncryptableItem {
|
|||
* Returns true if the given pubkey is in its compressed form.
|
||||
*/
|
||||
public static boolean isPubKeyCompressed(byte[] encoded) {
|
||||
if (encoded.length == 33 && (encoded[0] == 0x02 || encoded[0] == 0x03))
|
||||
if (encoded.length == 32 || (encoded.length == 33 && (encoded[0] == 0x02 || encoded[0] == 0x03)))
|
||||
return true;
|
||||
else if (encoded.length == 65 && encoded[0] == 0x04)
|
||||
return false;
|
||||
|
|
|
@ -20,7 +20,7 @@ public class LazyECPoint {
|
|||
|
||||
public LazyECPoint(ECCurve curve, byte[] bits) {
|
||||
this.curve = curve;
|
||||
this.bits = bits;
|
||||
this.bits = (bits != null && bits.length == 32 ? addYCoord(bits) : bits);
|
||||
this.compressed = ECKey.isPubKeyCompressed(bits);
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,13 @@ public class LazyECPoint {
|
|||
return get().getEncoded(compressed);
|
||||
}
|
||||
|
||||
public byte[] getEncodedXCoord() {
|
||||
byte[] compressed = getEncoded(true);
|
||||
byte[] xcoord = new byte[32];
|
||||
System.arraycopy(compressed, 1, xcoord, 0, 32);
|
||||
return xcoord;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Hex.toHexString(getEncoded());
|
||||
}
|
||||
|
@ -80,4 +87,11 @@ public class LazyECPoint {
|
|||
private byte[] getCanonicalEncoding() {
|
||||
return getEncoded(true);
|
||||
}
|
||||
|
||||
private static byte[] addYCoord(byte[] xcoord) {
|
||||
byte[] compressed = new byte[33];
|
||||
compressed[0] = 0x02;
|
||||
System.arraycopy(xcoord, 0, compressed, 1, 32);
|
||||
return compressed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,18 @@ public class Bech32 {
|
|||
public static class Bech32Data {
|
||||
public final String hrp;
|
||||
public final byte[] data;
|
||||
public final Encoding encoding;
|
||||
|
||||
private Bech32Data(final String hrp, final byte[] data) {
|
||||
this.hrp = hrp;
|
||||
this.data = data;
|
||||
this.encoding = (data[0] == 0x00 ? Encoding.BECH32 : Encoding.BECH32M);
|
||||
}
|
||||
|
||||
public Bech32Data(String hrp, byte[] data, Encoding encoding) {
|
||||
this.hrp = hrp;
|
||||
this.data = data;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +72,7 @@ public class Bech32 {
|
|||
/** Expand a HRP for use in checksum computation. */
|
||||
private static byte[] expandHrp(final String hrp) {
|
||||
int hrpLength = hrp.length();
|
||||
byte ret[] = new byte[hrpLength * 2 + 1];
|
||||
byte[] ret = new byte[hrpLength * 2 + 1];
|
||||
for (int i = 0; i < hrpLength; ++i) {
|
||||
int c = hrp.charAt(i) & 0x7f; // Limit to standard 7-bit ASCII
|
||||
ret[i] = (byte) ((c >>> 5) & 0x07);
|
||||
|
@ -75,21 +83,29 @@ public class Bech32 {
|
|||
}
|
||||
|
||||
/** Verify a checksum. */
|
||||
private static boolean verifyChecksum(final String hrp, final byte[] values) {
|
||||
private static Encoding verifyChecksum(final String hrp, final byte[] values) {
|
||||
byte[] hrpExpanded = expandHrp(hrp);
|
||||
byte[] combined = new byte[hrpExpanded.length + values.length];
|
||||
System.arraycopy(hrpExpanded, 0, combined, 0, hrpExpanded.length);
|
||||
System.arraycopy(values, 0, combined, hrpExpanded.length, values.length);
|
||||
return polymod(combined) == 1;
|
||||
|
||||
int check = polymod(combined);
|
||||
for(Encoding encoding : Encoding.values()) {
|
||||
if(check == encoding.checksumConstant) {
|
||||
return encoding;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Create a checksum. */
|
||||
private static byte[] createChecksum(final String hrp, final byte[] values) {
|
||||
private static byte[] createChecksum(final String hrp, Encoding encoding, final byte[] values) {
|
||||
byte[] hrpExpanded = expandHrp(hrp);
|
||||
byte[] enc = new byte[hrpExpanded.length + values.length + 6];
|
||||
System.arraycopy(hrpExpanded, 0, enc, 0, hrpExpanded.length);
|
||||
System.arraycopy(values, 0, enc, hrpExpanded.length, values.length);
|
||||
int mod = polymod(enc) ^ 1;
|
||||
int mod = polymod(enc) ^ encoding.checksumConstant;
|
||||
byte[] ret = new byte[6];
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31);
|
||||
|
@ -99,16 +115,17 @@ public class Bech32 {
|
|||
|
||||
/** Encode a Bech32 string. */
|
||||
public static String encode(final Bech32Data bech32) {
|
||||
return encode(bech32.hrp, bech32.data);
|
||||
return encode(bech32.hrp, bech32.encoding, bech32.data);
|
||||
}
|
||||
|
||||
/** Encode a Bech32 string. */
|
||||
public static String encode(String hrp, int version, final byte[] values) {
|
||||
return encode(hrp, encode(0, values));
|
||||
Encoding encoding = (version == 0 ? Encoding.BECH32 : Encoding.BECH32M);
|
||||
return encode(hrp, encoding, encode(version, values));
|
||||
}
|
||||
|
||||
/** Encode a Bech32 string. */
|
||||
public static String encode(String hrp, final byte[] values) {
|
||||
public static String encode(String hrp, Encoding encoding, final byte[] values) {
|
||||
if(hrp.length() < 1) {
|
||||
throw new ProtocolException("Human-readable part is too short");
|
||||
}
|
||||
|
@ -118,7 +135,7 @@ public class Bech32 {
|
|||
}
|
||||
|
||||
hrp = hrp.toLowerCase(Locale.ROOT);
|
||||
byte[] checksum = createChecksum(hrp, values);
|
||||
byte[] checksum = createChecksum(hrp, encoding, values);
|
||||
byte[] combined = new byte[values.length + checksum.length];
|
||||
System.arraycopy(values, 0, combined, 0, values.length);
|
||||
System.arraycopy(checksum, 0, combined, values.length, checksum.length);
|
||||
|
@ -163,14 +180,18 @@ public class Bech32 {
|
|||
values[i] = CHARSET_REV[c];
|
||||
}
|
||||
String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT);
|
||||
if (!verifyChecksum(hrp, values)) throw new ProtocolException("Invalid checksum");
|
||||
return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6));
|
||||
Encoding encoding = verifyChecksum(hrp, values);
|
||||
if(encoding == null) {
|
||||
throw new ProtocolException("Invalid checksum");
|
||||
}
|
||||
|
||||
return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6), encoding);
|
||||
}
|
||||
|
||||
private static byte[] encode(int witnessVersion, byte[] witnessProgram) {
|
||||
byte[] convertedProgram = convertBits(witnessProgram, 0, witnessProgram.length, 8, 5, true);
|
||||
byte[] bytes = new byte[1 + convertedProgram.length];
|
||||
bytes[0] = (byte) (Script.encodeToOpN(witnessVersion) & 0xff);
|
||||
bytes[0] = (byte)(witnessVersion & 0xff);
|
||||
System.arraycopy(convertedProgram, 0, bytes, 1, convertedProgram.length);
|
||||
return bytes;
|
||||
}
|
||||
|
@ -206,4 +227,14 @@ public class Bech32 {
|
|||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
public enum Encoding {
|
||||
BECH32(1), BECH32M(0x2bc830a3);
|
||||
|
||||
private final int checksumConstant;
|
||||
|
||||
Encoding(int checksumConstant) {
|
||||
this.checksumConstant = checksumConstant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,6 +135,21 @@ public class Script {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>If the program somehow pays to a pubkey, returns the pubkey.</p>
|
||||
*
|
||||
* <p>Otherwise this method throws a ScriptException.</p>
|
||||
*/
|
||||
public ECKey getPubKey() throws ProtocolException {
|
||||
for(ScriptType scriptType : SINGLE_KEY_TYPES) {
|
||||
if(scriptType.isScriptType(this)) {
|
||||
return scriptType.getPublicKeyFromScript(this);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ProtocolException("Script not a standard form that contains a single key");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>If the program somehow pays to a hash, returns the hash.</p>
|
||||
*
|
||||
|
@ -160,8 +175,10 @@ public class Script {
|
|||
}
|
||||
}
|
||||
|
||||
if(P2PK.isScriptType(this)) {
|
||||
return new Address[] { P2PK.getAddress(P2PK.getPublicKeyFromScript(this).getPubKey()) };
|
||||
for(ScriptType scriptType : SINGLE_KEY_TYPES) {
|
||||
if(scriptType.isScriptType(this)) {
|
||||
return new Address[] { scriptType.getAddress(scriptType.getPublicKeyFromScript(this)) };
|
||||
}
|
||||
}
|
||||
|
||||
if(MULTISIG.isScriptType(this)) {
|
||||
|
|
|
@ -974,6 +974,106 @@ public enum ScriptType {
|
|||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI, CUSTOM);
|
||||
}
|
||||
},
|
||||
P2TR("P2TR", "Taproot (P2TR)", "m/6789'/0'/0'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] pubKey) {
|
||||
return new P2TRAddress(pubKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress(ECKey key) {
|
||||
return getAddress(key.getPubKeyXCoord());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress(Script script) {
|
||||
throw new ProtocolException("Cannot create a taproot address without a keypath");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Script getOutputScript(byte[] pubKey) {
|
||||
List<ScriptChunk> chunks = new ArrayList<>();
|
||||
chunks.add(new ScriptChunk(OP_1, null));
|
||||
chunks.add(new ScriptChunk(pubKey.length, pubKey));
|
||||
|
||||
return new Script(chunks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Script getOutputScript(ECKey key) {
|
||||
return getOutputScript(key.getPubKeyXCoord());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Script getOutputScript(Script script) {
|
||||
throw new ProtocolException("Cannot create a taproot output script without a keypath");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutputDescriptor(ECKey key) {
|
||||
return getDescriptor() + Utils.bytesToHex(key.getPubKeyXCoord()) + getCloseDescriptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutputDescriptor(Script script) {
|
||||
throw new ProtocolException("Cannot create a taproot output descriptor without a keypath");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptor() {
|
||||
return "tr(";
|
||||
}
|
||||
|
||||
@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 (chunk1data.length != 32)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getHashFromScript(Script script) {
|
||||
throw new ProtocolException("P2TR script does not contain a hash, use getPublicKeyFromScript(script) to retrieve public key");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKey getPublicKeyFromScript(Script script) {
|
||||
return ECKey.fromPublicOnly(script.chunks.get(1).data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
|
||||
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
|
||||
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
|
||||
}
|
||||
|
||||
@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 List<PolicyType> getAllowedPolicyTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
|
||||
private final String name;
|
||||
|
@ -1087,18 +1187,22 @@ public enum ScriptType {
|
|||
|
||||
public abstract TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, Map<ECKey, TransactionSignature> pubKeySignatures);
|
||||
|
||||
public static final ScriptType[] SINGLE_KEY_TYPES = {P2PK, P2TR};
|
||||
|
||||
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[] NON_WITNESS_TYPES = {P2PK, P2PKH, P2SH};
|
||||
|
||||
public static final ScriptType[] WITNESS_TYPES = {P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};
|
||||
public static final ScriptType[] WITNESS_TYPES = {P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH, P2TR};
|
||||
|
||||
public static List<ScriptType> getScriptTypesForPolicyType(PolicyType policyType) {
|
||||
return Arrays.stream(values()).filter(scriptType -> scriptType.isAllowed(policyType)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<ScriptType> getAddressableScriptTypes(PolicyType policyType) {
|
||||
return Arrays.stream(values()).filter(scriptType -> scriptType.isAllowed(policyType) && Arrays.asList(SINGLE_HASH_TYPES).contains(scriptType)).collect(Collectors.toList());
|
||||
return Arrays.stream(ADDRESSABLE_TYPES).filter(scriptType -> scriptType.isAllowed(policyType)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static ScriptType getType(Script script) {
|
||||
|
@ -1166,6 +1270,9 @@ public enum ScriptType {
|
|||
return (32 + 4 + 1 + 13 + (107 / WITNESS_SCALE_FACTOR) + 4);
|
||||
} else if(P2SH_P2WSH.equals(this)) {
|
||||
return (32 + 4 + 1 + 35 + (107 / WITNESS_SCALE_FACTOR) + 4);
|
||||
} else if(P2TR.equals(this)) {
|
||||
//Assume a default keypath spend
|
||||
return (32 + 4 + 1 + (66 / WITNESS_SCALE_FACTOR) + 4);
|
||||
} else if(Arrays.asList(WITNESS_TYPES).contains(this)) {
|
||||
//Return length of spending input with 75% discount to script size
|
||||
return (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4);
|
||||
|
|
|
@ -443,7 +443,7 @@ public class Wallet extends Persistable {
|
|||
public int getNoInputsWeightUnits(List<Payment> payments) {
|
||||
Transaction transaction = new Transaction();
|
||||
if(Arrays.asList(ScriptType.WITNESS_TYPES).contains(getScriptType())) {
|
||||
transaction.setSegwitVersion(0);
|
||||
transaction.setSegwitVersion(1);
|
||||
}
|
||||
for(Payment payment : payments) {
|
||||
transaction.addOutput(payment.getAmount(), payment.getAddress());
|
||||
|
|
|
@ -49,6 +49,14 @@ public class AddressTest {
|
|||
Address address10 = Address.fromString(Network.SIGNET, "2NCZUtUt6gzXyBiPEQi5yQyrgR6f6F6Ki6A");
|
||||
Assert.assertTrue(address10 instanceof P2SHAddress);
|
||||
Assert.assertEquals("2NCZUtUt6gzXyBiPEQi5yQyrgR6f6F6Ki6A", address10.toString(Network.SIGNET));
|
||||
|
||||
Address address11 = Address.fromString("bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0");
|
||||
Assert.assertTrue(address11 instanceof P2TRAddress);
|
||||
Assert.assertEquals("bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", address11.toString());
|
||||
|
||||
Address address12 = Address.fromString(Network.TESTNET, "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c");
|
||||
Assert.assertTrue(address12 instanceof P2TRAddress);
|
||||
Assert.assertEquals("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", address12.toString(Network.TESTNET));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -74,6 +82,10 @@ public class AddressTest {
|
|||
Address address9 = Address.fromString("2NCZUtUt6gzXyBiPEQi5yQyrgR6f6F6Ki6A");
|
||||
Assert.assertTrue(address9 instanceof P2SHAddress);
|
||||
Assert.assertEquals("2NCZUtUt6gzXyBiPEQi5yQyrgR6f6F6Ki6A", address9.toString());
|
||||
|
||||
Address address12 = Address.fromString("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c");
|
||||
Assert.assertTrue(address12 instanceof P2TRAddress);
|
||||
Assert.assertEquals("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", address12.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -119,6 +131,11 @@ public class AddressTest {
|
|||
Address address1 = Address.fromString("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmb3");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidAddressException.class)
|
||||
public void invalidEncodingAddressTest() throws InvalidAddressException {
|
||||
Address address1 = Address.fromString("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Network.set(null);
|
||||
|
|
Loading…
Reference in a new issue