mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2024-11-20 18:22:12 +01:00
Support sending to P2SH addresses. Thanks to Mike Belshe.
Resolves issue 461.
This commit is contained in:
parent
0044c8d269
commit
98081f0568
@ -38,15 +38,26 @@ public class Address extends VersionedChecksummedBytes {
|
||||
*/
|
||||
public static final int LENGTH = 20;
|
||||
|
||||
/**
|
||||
* Construct an address from parameters, the address version, and the hash160 form. Example:<p>
|
||||
*
|
||||
* <pre>new Address(NetworkParameters.prodNet(), NetworkParameters.getAddressHeader(), Hex.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));</pre>
|
||||
*/
|
||||
public Address(NetworkParameters params, int version, byte[] hash160) {
|
||||
super(version, hash160);
|
||||
if (!isAcceptableVersion(params, version))
|
||||
throw new RuntimeException("Unrecognized Address version");
|
||||
if (hash160.length != 20) // 160 = 8 * 20
|
||||
throw new RuntimeException("Addresses are 160-bit hashes, so you must provide 20 bytes");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an address from parameters and the hash160 form. Example:<p>
|
||||
*
|
||||
* <pre>new Address(NetworkParameters.prodNet(), Hex.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));</pre>
|
||||
*/
|
||||
public Address(NetworkParameters params, byte[] hash160) {
|
||||
super(params.getAddressHeader(), hash160);
|
||||
if (hash160.length != 20) // 160 = 8 * 20
|
||||
throw new RuntimeException("Addresses are 160-bit hashes, so you must provide 20 bytes");
|
||||
this(params, params.getAddressHeader(), hash160);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,14 +73,7 @@ public class Address extends VersionedChecksummedBytes {
|
||||
public Address(@Nullable NetworkParameters params, String address) throws AddressFormatException, WrongNetworkException {
|
||||
super(address);
|
||||
if (params != null) {
|
||||
boolean found = false;
|
||||
for (int v : params.getAcceptableAddressCodes()) {
|
||||
if (version == v) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (!isAcceptableVersion(params, version)) {
|
||||
throw new WrongNetworkException(version, params.getAcceptableAddressCodes());
|
||||
}
|
||||
}
|
||||
@ -80,6 +84,14 @@ public class Address extends VersionedChecksummedBytes {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if this address is a Pay-To-Script-Hash (P2SH) address.
|
||||
* See also https://en.bitcoin.it/wiki/BIP_0013: Address Format for pay-to-script-hash
|
||||
*/
|
||||
public boolean isP2SHAddress() {
|
||||
return this.version == getParameters().p2shHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examines the version byte of the address and attempts to find a matching NetworkParameters. If you aren't sure
|
||||
* which network the address is intended for (eg, it was provided by a user), you can use this to decide if it is
|
||||
@ -92,10 +104,8 @@ public class Address extends VersionedChecksummedBytes {
|
||||
// TODO: There should be a more generic way to get all supported networks.
|
||||
NetworkParameters[] networks = { TestNet3Params.get(), MainNetParams.get() };
|
||||
for (NetworkParameters params : networks) {
|
||||
for (int code : params.getAcceptableAddressCodes()) {
|
||||
if (code == version) {
|
||||
return params;
|
||||
}
|
||||
if (isAcceptableVersion(params, version)) {
|
||||
return params;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -114,4 +124,16 @@ public class Address extends VersionedChecksummedBytes {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given address version is valid given the NetworkParameters.
|
||||
*/
|
||||
private boolean isAcceptableVersion(NetworkParameters params, int version) {
|
||||
for (int v : params.getAcceptableAddressCodes()) {
|
||||
if (version == v) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ public abstract class NetworkParameters implements Serializable {
|
||||
protected int port;
|
||||
protected long packetMagic;
|
||||
protected int addressHeader;
|
||||
protected int p2shHeader;
|
||||
protected int dumpedPrivateKeyHeader;
|
||||
protected int interval;
|
||||
protected int targetTimespan;
|
||||
@ -256,6 +257,13 @@ public abstract class NetworkParameters implements Serializable {
|
||||
return addressHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* First byte of a base58 encoded P2SH address. P2SH addresses are defined as part of BIP0013.
|
||||
*/
|
||||
public int getP2SHHeader() {
|
||||
return p2shHeader;
|
||||
}
|
||||
|
||||
/** First byte of a base58 encoded dumped private key. See {@link com.google.bitcoin.core.DumpedPrivateKey}. */
|
||||
public int getDumpedPrivateKeyHeader() {
|
||||
return dumpedPrivateKeyHeader;
|
||||
|
@ -31,9 +31,10 @@ public class MainNetParams extends NetworkParameters {
|
||||
interval = INTERVAL;
|
||||
targetTimespan = TARGET_TIMESPAN;
|
||||
proofOfWorkLimit = Utils.decodeCompactBits(0x1d00ffffL);
|
||||
acceptableAddressCodes = new int[] { 0 };
|
||||
dumpedPrivateKeyHeader = 128;
|
||||
addressHeader = 0;
|
||||
p2shHeader = 5;
|
||||
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
|
||||
port = 8333;
|
||||
packetMagic = 0xf9beb4d9L;
|
||||
genesisBlock.setDifficultyTarget(0x1d00ffffL);
|
||||
|
@ -32,10 +32,11 @@ public class TestNet2Params extends NetworkParameters {
|
||||
packetMagic = 0xfabfb5daL;
|
||||
port = 18333;
|
||||
addressHeader = 111;
|
||||
p2shHeader = 196;
|
||||
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
|
||||
interval = INTERVAL;
|
||||
targetTimespan = TARGET_TIMESPAN;
|
||||
proofOfWorkLimit = Utils.decodeCompactBits(0x1d0fffffL);
|
||||
acceptableAddressCodes = new int[] { 111 };
|
||||
dumpedPrivateKeyHeader = 239;
|
||||
genesisBlock.setTime(1296688602L);
|
||||
genesisBlock.setDifficultyTarget(0x1d07fff8L);
|
||||
|
@ -37,7 +37,8 @@ public class TestNet3Params extends NetworkParameters {
|
||||
proofOfWorkLimit = Utils.decodeCompactBits(0x1d00ffffL);
|
||||
port = 18333;
|
||||
addressHeader = 111;
|
||||
acceptableAddressCodes = new int[] { 111 };
|
||||
p2shHeader = 196;
|
||||
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
|
||||
dumpedPrivateKeyHeader = 239;
|
||||
genesisBlock.setTime(1296688602L);
|
||||
genesisBlock.setDifficultyTarget(0x1d00ffffL);
|
||||
|
@ -31,6 +31,8 @@ public class UnitTestParams extends NetworkParameters {
|
||||
id = ID_UNITTESTNET;
|
||||
packetMagic = 0x0b110907;
|
||||
addressHeader = 111;
|
||||
p2shHeader = 196;
|
||||
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
|
||||
proofOfWorkLimit = new BigInteger("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16);
|
||||
genesisBlock.setTime(System.currentTimeMillis() / 1000);
|
||||
genesisBlock.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET);
|
||||
@ -40,7 +42,6 @@ public class UnitTestParams extends NetworkParameters {
|
||||
dumpedPrivateKeyHeader = 239;
|
||||
targetTimespan = 200000000; // 6 years. Just a very big number.
|
||||
spendableCoinbaseDepth = 5;
|
||||
acceptableAddressCodes = new int[] { 111 };
|
||||
subsidyDecreaseBlockCount = 100;
|
||||
dnsSeeds = null;
|
||||
}
|
||||
|
@ -194,6 +194,18 @@ public class Script {
|
||||
chunks.get(4).equalsOpCode(OP_CHECKSIG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this script is of the form OP_HASH160 <scriptHash> OP_EQUAL, ie, payment to an
|
||||
* address like 35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU. This form was codified as part of BIP13 and BIP16,
|
||||
* for pay to script hash type addresses.
|
||||
*/
|
||||
public boolean isSentToP2SH() {
|
||||
return chunks.size() == 3 &&
|
||||
chunks.get(0).equalsOpCode(OP_HASH160) &&
|
||||
chunks.get(1).data.length == Address.LENGTH &&
|
||||
chunks.get(2).equalsOpCode(OP_EQUAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a program matches the standard template DUP HASH160 <pubkey hash> EQUALVERIFY CHECKSIG
|
||||
* then this function retrieves the third element, otherwise it throws a ScriptException.<p>
|
||||
@ -201,10 +213,12 @@ public class Script {
|
||||
* This is useful for fetching the destination address of a transaction.
|
||||
*/
|
||||
public byte[] getPubKeyHash() throws ScriptException {
|
||||
if (!isSentToAddress())
|
||||
if (isSentToAddress())
|
||||
return chunks.get(2).data;
|
||||
else if (isSentToP2SH())
|
||||
return chunks.get(1).data;
|
||||
else
|
||||
throw new ScriptException("Script not in the standard scriptPubKey form");
|
||||
// Otherwise, the third element is the hash of the public key, ie the bitcoin address.
|
||||
return chunks.get(2).data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,14 +64,23 @@ public class ScriptBuilder {
|
||||
|
||||
/** Creates a scriptPubKey that encodes payment to the given address. */
|
||||
public static Script createOutputScript(Address to) {
|
||||
// OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
|
||||
return new ScriptBuilder()
|
||||
if (to.isP2SHAddress()) {
|
||||
// OP_HASH160 <scriptHash> OP_EQUAL
|
||||
return new ScriptBuilder()
|
||||
.op(OP_HASH160)
|
||||
.data(to.getHash160())
|
||||
.op(OP_EQUAL)
|
||||
.build();
|
||||
} else {
|
||||
// OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
|
||||
return new ScriptBuilder()
|
||||
.op(OP_DUP)
|
||||
.op(OP_HASH160)
|
||||
.data(to.getHash160())
|
||||
.op(OP_EQUALVERIFY)
|
||||
.op(OP_CHECKSIG)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a scriptPubKey that encodes payment to the given raw public key. */
|
||||
|
@ -34,9 +34,11 @@ public class AddressTest {
|
||||
// Test a testnet address.
|
||||
Address a = new Address(testParams, Hex.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"));
|
||||
assertEquals("n4eA2nbYqErp7H6jebchxAN59DmNpksexv", a.toString());
|
||||
assertFalse(a.isP2SHAddress());
|
||||
|
||||
Address b = new Address(mainParams, Hex.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));
|
||||
assertEquals("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL", b.toString());
|
||||
assertFalse(b.isP2SHAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -90,4 +92,27 @@ public class AddressTest {
|
||||
params = Address.getParametersFromAddress("n4eA2nbYqErp7H6jebchxAN59DmNpksexv");
|
||||
assertEquals(TestNet3Params.get().getId(), params.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void p2shAddress() throws Exception {
|
||||
// Test that we can construct P2SH addresses
|
||||
Address mainNetP2SHAddress = new Address(MainNetParams.get(), "35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU");
|
||||
assertEquals(mainNetP2SHAddress.version, MainNetParams.get().p2shHeader);
|
||||
assertTrue(mainNetP2SHAddress.isP2SHAddress());
|
||||
Address testNetP2SHAddress = new Address(TestNet3Params.get(), "2MuVSxtfivPKJe93EC1Tb9UhJtGhsoWEHCe");
|
||||
assertEquals(testNetP2SHAddress.version, TestNet3Params.get().p2shHeader);
|
||||
assertTrue(testNetP2SHAddress.isP2SHAddress());
|
||||
|
||||
// Test that we can determine what network a P2SH address belongs to
|
||||
NetworkParameters mainNetParams = Address.getParametersFromAddress("35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU");
|
||||
assertEquals(MainNetParams.get().getId(), mainNetParams.getId());
|
||||
NetworkParameters testNetParams = Address.getParametersFromAddress("2MuVSxtfivPKJe93EC1Tb9UhJtGhsoWEHCe");
|
||||
assertEquals(TestNet3Params.get().getId(), testNetParams.getId());
|
||||
|
||||
// Test that we can convert them from hashes
|
||||
Address a = new Address(mainParams, MainNetParams.get().p2shHeader, Hex.decode("2ac4b0b501117cc8119c5797b519538d4942e90e"));
|
||||
assertEquals("35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU", a.toString());
|
||||
Address b = new Address(testParams, TestNet3Params.get().p2shHeader, Hex.decode("18a0e827269b5211eb51a4af1b2fa69333efa722"));
|
||||
assertEquals("2MuVSxtfivPKJe93EC1Tb9UhJtGhsoWEHCe", b.toString());
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import com.google.bitcoin.wallet.WalletFiles;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
import org.bitcoinj.wallet.Protos.ScryptParameters;
|
||||
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
||||
@ -42,6 +43,7 @@ import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigInteger;
|
||||
@ -106,20 +108,26 @@ public class WalletTest extends TestWithWallet {
|
||||
|
||||
@Test
|
||||
public void basicSpending() throws Exception {
|
||||
basicSpendingCommon(wallet, myAddress, false);
|
||||
basicSpendingCommon(wallet, myAddress, new ECKey().toAddress(params), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicSpendingToP2SH() throws Exception {
|
||||
Address destination = new Address(params, params.getP2SHHeader(), Hex.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));
|
||||
basicSpendingCommon(wallet, myAddress, destination, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicSpendingWithEncryptedWallet() throws Exception {
|
||||
basicSpendingCommon(encryptedWallet, myEncryptedAddress, true);
|
||||
basicSpendingCommon(encryptedWallet, myEncryptedAddress, new ECKey().toAddress(params), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicSpendingWithEncryptedMixedWallet() throws Exception {
|
||||
basicSpendingCommon(encryptedMixedWallet, myEncryptedAddress2, true);
|
||||
basicSpendingCommon(encryptedMixedWallet, myEncryptedAddress2, new ECKey().toAddress(params), true);
|
||||
}
|
||||
|
||||
private void basicSpendingCommon(Wallet wallet, Address toAddress, boolean testEncryption) throws Exception {
|
||||
private void basicSpendingCommon(Wallet wallet, Address toAddress, Address destination, boolean testEncryption) throws Exception {
|
||||
// We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change. We
|
||||
// will attach a small fee. Because the Bitcoin protocol makes it difficult to determine the fee of an
|
||||
// arbitrary transaction in isolation, we'll check that the fee was set by examining the size of the change.
|
||||
@ -128,7 +136,6 @@ public class WalletTest extends TestWithWallet {
|
||||
receiveATransaction(wallet, toAddress);
|
||||
|
||||
// Try to send too much and fail.
|
||||
Address destination = new ECKey().toAddress(params);
|
||||
BigInteger vHuge = toNanoCoins(10, 0);
|
||||
Wallet.SendRequest req = Wallet.SendRequest.to(destination, vHuge);
|
||||
try {
|
||||
|
@ -17,8 +17,10 @@
|
||||
package com.google.bitcoin.script;
|
||||
|
||||
import com.google.bitcoin.core.*;
|
||||
import com.google.bitcoin.params.MainNetParams;
|
||||
import com.google.bitcoin.params.TestNet3Params;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
@ -85,6 +87,12 @@ public class ScriptTest {
|
||||
// Actual execution is tested by the data driven tests.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testP2SHOutputScript() throws Exception {
|
||||
Address p2shAddress = new Address(MainNetParams.get(), "35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU");
|
||||
assertTrue(ScriptBuilder.createOutputScript(p2shAddress).isSentToP2SH());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIp() throws Exception {
|
||||
byte[] bytes = Hex.decode("41043e96222332ea7848323c08116dddafbfa917b8e37f0bdf63841628267148588a09a43540942d58d49717ad3fabfe14978cf4f0a8b84d2435dad16e9aa4d7f935ac");
|
||||
|
Loading…
Reference in New Issue
Block a user