Skip to content

Commit bbf4302

Browse files
author
Andreas Schildbach
committed
WIP Add SegwitAddress as a subclass of AbstractAddress (parent of Address too).
Note the class names a subject to change, e.g. AbstractAddress > Address Address > LegacyAddress Uses Bech32 code from sipa/bech32#40.
1 parent 805e54b commit bbf4302

17 files changed

+656
-27
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2018 Andreas Schildbach
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.bitcoinj.core;
18+
19+
import javax.annotation.Nullable;
20+
21+
import org.bitcoinj.script.Script.ScriptType;
22+
23+
public abstract class AbstractAddress extends VersionedChecksummedBytes {
24+
public AbstractAddress(NetworkParameters params, byte[] bytes) {
25+
super(params, bytes);
26+
}
27+
28+
/**
29+
* Construct an address from its textual representation.
30+
*
31+
* @param params
32+
* The expected NetworkParameters or null if you don't want validation.
33+
* @param str
34+
* The textual form of the address, such as "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL".
35+
* @throws AddressFormatException
36+
* if the given string doesn't parse or the checksum is invalid
37+
* @throws WrongNetworkException
38+
* if the given string is valid but for a different chain (eg testnet vs mainnet)
39+
*/
40+
public static AbstractAddress fromString(@Nullable NetworkParameters params, String str)
41+
throws AddressFormatException {
42+
try {
43+
return Address.fromBase58(params, str);
44+
} catch (WrongNetworkException x) {
45+
throw x;
46+
} catch (AddressFormatException x) {
47+
try {
48+
return SegwitAddress.fromBech32(params, str);
49+
} catch (WrongNetworkException x2) {
50+
throw x;
51+
} catch (AddressFormatException x2) {
52+
throw new AddressFormatException(str);
53+
}
54+
}
55+
}
56+
57+
public abstract ScriptType getScriptType();
58+
}

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.bitcoinj.params.Networks;
2828
import org.bitcoinj.script.Script;
29+
import org.bitcoinj.script.Script.ScriptType;
2930

3031
import com.google.common.base.Objects;
3132

@@ -40,7 +41,7 @@
4041
* should be interpreted. Whilst almost all addresses today are hashes of public keys, another (currently unsupported
4142
* type) can contain a hash of a script instead.</p>
4243
*/
43-
public class Address extends VersionedChecksummedBytes {
44+
public class Address extends AbstractAddress {
4445
/**
4546
* An address is a RIPEMD160 hash of a public key, therefore is always 160 bits or 20 bytes.
4647
*/
@@ -114,19 +115,27 @@ else if (version == params.getP2SHHeader())
114115
}
115116
}
116117

117-
@Override
118-
protected int getVersion() {
119-
return p2sh ? params.getP2SHHeader() : params.getAddressHeader();
118+
/**
119+
* Returns the base58-encoded textual representation, including version and checksum bytes.
120+
*/
121+
public String toBase58() {
122+
return toBase58(p2sh ? params.getP2SHHeader() : params.getAddressHeader(), bytes);
120123
}
121124

122125
/** The (big endian) 20 byte hash that is the core of a Bitcoin address. */
123126
public byte[] getHash160() {
124127
return bytes;
125128
}
126129

130+
@Override
131+
public ScriptType getScriptType() {
132+
return p2sh ? ScriptType.P2SH : ScriptType.P2PKH;
133+
}
134+
127135
/**
128136
* Returns true if this address is a Pay-To-Script-Hash (P2SH) address.
129137
* See also https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki: Address Format for pay-to-script-hash
138+
* @deprecated Use {@link #getScriptType()}
130139
*/
131140
public boolean isP2SHAddress() {
132141
return p2sh;
@@ -162,6 +171,11 @@ public int hashCode() {
162171
return Objects.hashCode(super.hashCode(), p2sh);
163172
}
164173

174+
@Override
175+
public String toString() {
176+
return toBase58();
177+
}
178+
165179
/**
166180
* This implementation narrows the return type to <code>Address</code>.
167181
*/
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/* Copyright (c) 2018 Coinomi Ltd
2+
*
3+
* Permission is hereby granted, free of charge, to any person obtaining a copy
4+
* of this software and associated documentation files (the "Software"), to deal
5+
* in the Software without restriction, including without limitation the rights
6+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
* copies of the Software, and to permit persons to whom the Software is
8+
* furnished to do so, subject to the following conditions:
9+
*
10+
* The above copyright notice and this permission notice shall be included in
11+
* all copies or substantial portions of the Software.
12+
*
13+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
* THE SOFTWARE.
20+
*/
21+
22+
package org.bitcoinj.core;
23+
24+
import java.util.Arrays;
25+
import java.util.Locale;
26+
27+
public class Bech32 {
28+
/** The Bech32 character set for encoding. */
29+
private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
30+
31+
/** The Bech32 character set for decoding. */
32+
private static final byte[] CHARSET_REV = {
33+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
34+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
35+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
36+
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
37+
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
38+
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
39+
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
40+
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
41+
};
42+
43+
public static class Bech32Data {
44+
final String hrp;
45+
final byte[] values;
46+
47+
private Bech32Data(final String hrp, final byte[] values) {
48+
this.hrp = hrp;
49+
this.values = values;
50+
}
51+
}
52+
53+
/** Find the polynomial with value coefficients mod the generator as 30-bit. */
54+
private static int polymod(final byte[] values) {
55+
int c = 1;
56+
for (byte v_i: values) {
57+
int c0 = (c >>> 25) & 0xff;
58+
c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff);
59+
if ((c0 & 1) != 0) c ^= 0x3b6a57b2;
60+
if ((c0 & 2) != 0) c ^= 0x26508e6d;
61+
if ((c0 & 4) != 0) c ^= 0x1ea119fa;
62+
if ((c0 & 8) != 0) c ^= 0x3d4233dd;
63+
if ((c0 & 16) != 0) c ^= 0x2a1462b3;
64+
}
65+
return c;
66+
}
67+
68+
/** Expand a HRP for use in checksum computation. */
69+
private static byte[] expandHrp(final String hrp) {
70+
int hrpLength = hrp.length();
71+
byte ret[] = new byte[hrpLength * 2 + 1];
72+
for (int i = 0; i < hrpLength; ++i) {
73+
int c = hrp.charAt(i) & 0x7f; // Limit to standard 7-bit ASCII
74+
ret[i] = (byte) ((c >>> 5) & 0x07);
75+
ret[i + hrpLength + 1] = (byte) (c & 0x1f);
76+
}
77+
ret[hrpLength] = 0;
78+
return ret;
79+
}
80+
81+
/** Verify a checksum. */
82+
private static boolean verifyChecksum(final String hrp, final byte[] values) {
83+
byte[] hrpExpanded = expandHrp(hrp);
84+
byte[] combined = new byte[hrpExpanded.length + values.length];
85+
System.arraycopy(hrpExpanded, 0, combined, 0, hrpExpanded.length);
86+
System.arraycopy(values, 0, combined, hrpExpanded.length, values.length);
87+
return polymod(combined) == 1;
88+
}
89+
90+
/** Create a checksum. */
91+
private static byte[] createChecksum(final String hrp, final byte[] values) {
92+
byte[] hrpExpanded = expandHrp(hrp);
93+
byte[] enc = new byte[hrpExpanded.length + values.length + 6];
94+
System.arraycopy(hrpExpanded, 0, enc, 0, hrpExpanded.length);
95+
System.arraycopy(values, 0, enc, hrpExpanded.length, values.length);
96+
int mod = polymod(enc) ^ 1;
97+
byte[] ret = new byte[6];
98+
for (int i = 0; i < 6; ++i) {
99+
ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31);
100+
}
101+
return ret;
102+
}
103+
104+
/** Encode a Bech32 string. */
105+
public static String encode(final Bech32Data bech32) throws AddressFormatException {
106+
return encode(bech32.hrp, bech32.values);
107+
}
108+
109+
/** Encode a Bech32 string. */
110+
public static String encode(String hrp, final byte[] values) throws AddressFormatException {
111+
if (hrp.length() < 1) throw new AddressFormatException("Human-readable part is too short");
112+
if (hrp.length() > 83) throw new AddressFormatException("Human-readable part is too long");
113+
hrp = hrp.toLowerCase(Locale.ROOT);
114+
byte[] checksum = createChecksum(hrp, values);
115+
byte[] combined = new byte[values.length + checksum.length];
116+
System.arraycopy(values, 0, combined, 0, values.length);
117+
System.arraycopy(checksum, 0, combined, values.length, checksum.length);
118+
StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length);
119+
sb.append(hrp);
120+
sb.append('1');
121+
for (byte b : combined) {
122+
sb.append(CHARSET.charAt(b));
123+
}
124+
return sb.toString();
125+
}
126+
127+
/** Decode a Bech32 string. */
128+
public static Bech32Data decode(final String str) throws AddressFormatException {
129+
boolean lower = false, upper = false;
130+
if (str.length() < 8) throw new AddressFormatException("Input too short");
131+
if (str.length() > 90) throw new AddressFormatException("Input too long");
132+
for (int i = 0; i < str.length(); ++i) {
133+
char c = str.charAt(i);
134+
if (c < 33 || c > 126) throw new AddressFormatException("Characters out of range");
135+
if (c >= 'a' && c <= 'z') lower = true;
136+
if (c >= 'A' && c <= 'Z') upper = true;
137+
}
138+
if (lower && upper) throw new AddressFormatException("Cannot mix upper and lower cases");
139+
int pos = str.lastIndexOf('1');
140+
if (pos < 1) throw new AddressFormatException("Missing human-readable part");
141+
if (pos + 7 > str.length()) throw new AddressFormatException("Data part too short");
142+
byte[] values = new byte[str.length() - 1 - pos];
143+
for (int i = 0; i < str.length() - 1 - pos; ++i) {
144+
char c = str.charAt(i + pos + 1);
145+
if (CHARSET_REV[c] == -1) throw new AddressFormatException("Characters out of range");
146+
values[i] = CHARSET_REV[c];
147+
}
148+
String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT);
149+
if (!verifyChecksum(hrp, values)) throw new AddressFormatException("Invalid checksum");
150+
return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6));
151+
}
152+
}

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ private DumpedPrivateKey(NetworkParameters params, byte[] bytes) {
7171
this(params, encode(keyBytes, compressed));
7272
}
7373

74-
@Override
75-
protected int getVersion() {
76-
return params.getDumpedPrivateKeyHeader();
74+
/**
75+
* Returns the base58-encoded textual representation, including version and checksum bytes.
76+
*/
77+
public String toBase58() {
78+
return toBase58(params.getDumpedPrivateKeyHeader(), bytes);
7779
}
7880

7981
private static byte[] encode(byte[] keyBytes, boolean compressed) {
@@ -102,4 +104,9 @@ public ECKey getKey() {
102104
public boolean isPubKeyCompressed() {
103105
return bytes.length == 33 && bytes[32] == 1;
104106
}
107+
108+
@Override
109+
public String toString() {
110+
return toBase58();
111+
}
105112
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public abstract class NetworkParameters {
7777
protected int addressHeader;
7878
protected int p2shHeader;
7979
protected int dumpedPrivateKeyHeader;
80+
protected String segwitAddressHrp;
8081
protected int interval;
8182
protected int targetTimespan;
8283
protected byte[] alertSigningKey;
@@ -336,6 +337,11 @@ public int getDumpedPrivateKeyHeader() {
336337
return dumpedPrivateKeyHeader;
337338
}
338339

340+
/** Human readable part of bech32 encoded segwit address. */
341+
public String getSegwitAddressHrp() {
342+
return segwitAddressHrp;
343+
}
344+
339345
/**
340346
* How much time in seconds is supposed to pass between "interval" blocks. If the actual elapsed time is
341347
* significantly different from this value, the network difficulty formula will produce a different value. Both

0 commit comments

Comments
 (0)