Skip to content

Commit afcdc81

Browse files
author
Andreas Schildbach
committed
VersionedChecksummedBytes: Store network params rather than version, in preparation for native segwit addresses.
1 parent 1d15d95 commit afcdc81

File tree

6 files changed

+168
-190
lines changed

6 files changed

+168
-190
lines changed

core/src/main/java/org/bitcoinj/core/Address.java

Lines changed: 48 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@
1818

1919
package org.bitcoinj.core;
2020

21-
import java.io.IOException;
22-
import java.io.ObjectInputStream;
23-
import java.io.ObjectOutputStream;
21+
import static com.google.common.base.Preconditions.checkArgument;
22+
23+
import java.util.Arrays;
24+
25+
import javax.annotation.Nullable;
2426

2527
import org.bitcoinj.params.Networks;
2628
import org.bitcoinj.script.Script;
2729
import org.bitcoinj.script.ScriptPattern;
2830

29-
import javax.annotation.Nullable;
30-
31-
import static com.google.common.base.Preconditions.checkArgument;
32-
import static com.google.common.base.Preconditions.checkNotNull;
31+
import com.google.common.base.Objects;
3332

3433
/**
3534
* <p>A Bitcoin address looks like 1MsScoe2fTJoq4ZPdQgqyhgWeoNamYPevy and is derived from an elliptic curve public key
@@ -48,7 +47,8 @@ public class Address extends VersionedChecksummedBytes {
4847
*/
4948
public static final int LENGTH = 20;
5049

51-
private transient NetworkParameters params;
50+
/** True if P2SH, false if P2PKH. */
51+
public final boolean p2sh;
5252

5353
/**
5454
* Private constructor. Use {@link #fromBase58(NetworkParameters, String)},
@@ -62,13 +62,10 @@ public class Address extends VersionedChecksummedBytes {
6262
* @param hash160
6363
* 20-byte hash of pubkey or script
6464
*/
65-
private Address(NetworkParameters params, int version, byte[] hash160) throws WrongNetworkException {
66-
super(version, hash160);
67-
checkNotNull(params);
65+
private Address(NetworkParameters params, boolean p2sh, byte[] hash160) throws WrongNetworkException {
66+
super(params, hash160);
6867
checkArgument(hash160.length == 20, "Addresses are 160-bit hashes, so you must provide 20 bytes");
69-
if (!isAcceptableVersion(params, version))
70-
throw new WrongNetworkException(version);
71-
this.params = params;
68+
this.p2sh = p2sh;
7269
}
7370

7471
/**
@@ -82,7 +79,7 @@ private Address(NetworkParameters params, int version, byte[] hash160) throws Wr
8279
* @return constructed address
8380
*/
8481
public static Address fromPubKeyHash(NetworkParameters params, byte[] hash160) {
85-
return new Address(params, params.getAddressHeader(), hash160);
82+
return new Address(params, false, hash160);
8683
}
8784

8885
/**
@@ -110,7 +107,7 @@ public static Address fromKey(NetworkParameters params, ECKey key) {
110107
*/
111108
public static Address fromP2SHHash(NetworkParameters params, byte[] hash160) {
112109
try {
113-
return new Address(params, params.getP2SHHeader(), hash160);
110+
return new Address(params, true, hash160);
114111
} catch (WrongNetworkException e) {
115112
throw new RuntimeException(e); // Cannot happen.
116113
}
@@ -144,37 +141,35 @@ public static Address fromP2SHScript(NetworkParameters params, Script scriptPubK
144141
* if the given address is valid but for a different chain (eg testnet vs mainnet)
145142
*/
146143
public static Address fromBase58(@Nullable NetworkParameters params, String base58) throws AddressFormatException {
147-
return new Address(params, base58);
144+
byte[] versionAndDataBytes = Base58.decodeChecked(base58);
145+
int version = versionAndDataBytes[0] & 0xFF;
146+
byte[] bytes = Arrays.copyOfRange(versionAndDataBytes, 1, versionAndDataBytes.length);
147+
if (params == null) {
148+
for (NetworkParameters p : Networks.get()) {
149+
if (version == p.getAddressHeader())
150+
return new Address(p, false, bytes);
151+
else if (version == p.getP2SHHeader())
152+
return new Address(p, true, bytes);
153+
}
154+
throw new AddressFormatException("No network found for " + base58);
155+
} else {
156+
if (version == params.getAddressHeader())
157+
return new Address(params, false, bytes);
158+
else if (version == params.getP2SHHeader())
159+
return new Address(params, true, bytes);
160+
throw new WrongNetworkException(version);
161+
}
148162
}
149163

150164
/** @deprecated use {@link #fromPubKeyHash(NetworkParameters, byte[])} */
151165
@Deprecated
152166
public Address(NetworkParameters params, byte[] hash160) {
153-
this(params, params.getAddressHeader(), hash160);
167+
this(params, false, hash160);
154168
}
155169

156-
/** @deprecated Use {@link #fromBase58(NetworkParameters, String)} */
157-
@Deprecated
158-
public Address(@Nullable NetworkParameters params, String address) throws AddressFormatException {
159-
super(address);
160-
if (params != null) {
161-
if (!isAcceptableVersion(params, version)) {
162-
throw new WrongNetworkException(version);
163-
}
164-
this.params = params;
165-
} else {
166-
NetworkParameters paramsFound = null;
167-
for (NetworkParameters p : Networks.get()) {
168-
if (isAcceptableVersion(p, version)) {
169-
paramsFound = p;
170-
break;
171-
}
172-
}
173-
if (paramsFound == null)
174-
throw new AddressFormatException("No network found for " + address);
175-
176-
this.params = paramsFound;
177-
}
170+
@Override
171+
protected int getVersion() {
172+
return p2sh ? params.getP2SHHeader() : params.getAddressHeader();
178173
}
179174

180175
/** The (big endian) 20 byte hash that is the core of a Bitcoin address. */
@@ -187,20 +182,7 @@ public byte[] getHash160() {
187182
* See also https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki: Address Format for pay-to-script-hash
188183
*/
189184
public boolean isP2SHAddress() {
190-
final NetworkParameters parameters = getParameters();
191-
return parameters != null && this.version == parameters.p2shHeader;
192-
}
193-
194-
/**
195-
* Examines the version byte of the address and attempts to find a matching NetworkParameters. If you aren't sure
196-
* which network the address is intended for (eg, it was provided by a user), you can use this to decide if it is
197-
* compatible with the current wallet. You should be able to handle a null response from this method. Note that the
198-
* parameters returned is not necessarily the same as the one the Address was created with.
199-
*
200-
* @return network the address is valid for
201-
*/
202-
public NetworkParameters getParameters() {
203-
return params;
185+
return p2sh;
204186
}
205187

206188
/**
@@ -219,31 +201,23 @@ public static NetworkParameters getParametersFromAddress(String address) throws
219201
}
220202
}
221203

222-
/**
223-
* Check if a given address version is valid given the NetworkParameters.
224-
*/
225-
private static boolean isAcceptableVersion(NetworkParameters params, int version) {
226-
if (version == params.getAddressHeader())
227-
return true;
228-
if (version == params.getP2SHHeader())
204+
@Override
205+
public boolean equals(Object o) {
206+
if (this == o)
229207
return true;
230-
return false;
208+
if (o == null || getClass() != o.getClass())
209+
return false;
210+
Address other = (Address) o;
211+
return super.equals(other) && this.p2sh == other.p2sh;
231212
}
232213

233214
@Override
234-
public Address clone() throws CloneNotSupportedException {
235-
return (Address) super.clone();
215+
public int hashCode() {
216+
return Objects.hashCode(super.hashCode(), p2sh);
236217
}
237218

238-
// Java serialization
239-
240-
private void writeObject(ObjectOutputStream out) throws IOException {
241-
out.defaultWriteObject();
242-
out.writeUTF(params.id);
243-
}
244-
245-
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
246-
in.defaultReadObject();
247-
params = NetworkParameters.fromID(in.readUTF());
219+
@Override
220+
public Address clone() throws CloneNotSupportedException {
221+
return (Address) super.clone();
248222
}
249223
}

core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717

1818
package org.bitcoinj.core;
1919

20-
import com.google.common.base.Objects;
2120
import com.google.common.base.Preconditions;
2221

2322
import java.util.Arrays;
2423

2524
import javax.annotation.Nullable;
2625

26+
import org.bitcoinj.params.Networks;
27+
2728
/**
2829
* Parses and generates private keys in the form used by the Bitcoin "dumpprivkey" command. This is the private key
2930
* bytes with a header byte and 4 checksum bytes at the end. If there are 33 private key bytes instead of 32, then
@@ -42,13 +43,37 @@ public class DumpedPrivateKey extends VersionedChecksummedBytes {
4243
* @throws WrongNetworkException
4344
* if the given private key is valid but for a different chain (eg testnet vs mainnet)
4445
*/
45-
public static DumpedPrivateKey fromBase58(@Nullable NetworkParameters params,String base58) throws AddressFormatException {
46-
return new DumpedPrivateKey(params, base58);
46+
public static DumpedPrivateKey fromBase58(@Nullable NetworkParameters params, String base58)
47+
throws AddressFormatException {
48+
byte[] versionAndDataBytes = Base58.decodeChecked(base58);
49+
int version = versionAndDataBytes[0] & 0xFF;
50+
byte[] bytes = Arrays.copyOfRange(versionAndDataBytes, 1, versionAndDataBytes.length);
51+
if (params == null) {
52+
for (NetworkParameters p : Networks.get())
53+
if (version == p.getDumpedPrivateKeyHeader())
54+
return new DumpedPrivateKey(p, bytes);
55+
throw new AddressFormatException("No network found for " + base58);
56+
} else {
57+
if (version == params.getDumpedPrivateKeyHeader())
58+
return new DumpedPrivateKey(params, bytes);
59+
throw new WrongNetworkException(version);
60+
}
61+
}
62+
63+
private DumpedPrivateKey(NetworkParameters params, byte[] bytes) {
64+
super(params, bytes);
65+
if (bytes.length != 32 && bytes.length != 33)
66+
throw new AddressFormatException("Wrong number of bytes for a private key, not 32 or 33");
4767
}
4868

4969
// Used by ECKey.getPrivateKeyEncoded()
5070
DumpedPrivateKey(NetworkParameters params, byte[] keyBytes, boolean compressed) {
51-
super(params.getDumpedPrivateKeyHeader(), encode(keyBytes, compressed));
71+
this(params, encode(keyBytes, compressed));
72+
}
73+
74+
@Override
75+
protected int getVersion() {
76+
return params.getDumpedPrivateKeyHeader();
5277
}
5378

5479
private static byte[] encode(byte[] keyBytes, boolean compressed) {
@@ -64,17 +89,6 @@ private static byte[] encode(byte[] keyBytes, boolean compressed) {
6489
}
6590
}
6691

67-
/** @deprecated Use {@link #fromBase58(NetworkParameters, String)} */
68-
@Deprecated
69-
public DumpedPrivateKey(@Nullable NetworkParameters params, String encoded) throws AddressFormatException {
70-
super(encoded);
71-
if (params != null && version != params.getDumpedPrivateKeyHeader())
72-
throw new WrongNetworkException(version);
73-
if (bytes.length != 32 && bytes.length != 33) {
74-
throw new AddressFormatException("Wrong number of bytes for a private key, not 32 or 33");
75-
}
76-
}
77-
7892
/**
7993
* Returns an ECKey created from this encoded private key.
8094
*/
@@ -88,17 +102,4 @@ public ECKey getKey() {
88102
public boolean isPubKeyCompressed() {
89103
return bytes.length == 33 && bytes[32] == 1;
90104
}
91-
92-
@Override
93-
public boolean equals(Object o) {
94-
if (this == o) return true;
95-
if (o == null || getClass() != o.getClass()) return false;
96-
DumpedPrivateKey other = (DumpedPrivateKey) o;
97-
return version == other.version && Arrays.equals(bytes, other.bytes);
98-
}
99-
100-
@Override
101-
public int hashCode() {
102-
return Objects.hashCode(version, Arrays.hashCode(bytes));
103-
}
104105
}

0 commit comments

Comments
 (0)