forked from bitcoinj/bitcoinj
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement native segwit addresses in new SegwitAddress class.
This commit establishes a base class for legacy and segwit addresses. Uses Bech32 code from sipa/bech32#40.
- Loading branch information
Andreas Schildbach
committed
Feb 27, 2018
1 parent
afcdc81
commit 68f6548
Showing
18 changed files
with
864 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright 2018 Andreas Schildbach | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.bitcoinj.core; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
import org.bitcoinj.script.Script.ScriptType; | ||
|
||
/** | ||
* <p> | ||
* Base class for addresses, e.g. native segwit addresses ({@link SegwitAddress}) or legacy addresses ({@link Address}). | ||
* </p> | ||
* | ||
* <p> | ||
* Use {@link #fromString(NetworkParameters, String)} to conveniently construct any kind of address from its textual | ||
* form. | ||
* </p> | ||
*/ | ||
public abstract class AbstractAddress extends VersionedChecksummedBytes { | ||
public AbstractAddress(NetworkParameters params, byte[] bytes) { | ||
super(params, bytes); | ||
} | ||
|
||
/** | ||
* Construct an address from its textual form. | ||
* | ||
* @param params | ||
* the expected network this address is valid for, or null if the network should be derived from the | ||
* textual form | ||
* @param str | ||
* the textual form of the address, such as "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL" or | ||
* "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" | ||
* @return constructed address | ||
* @throws AddressFormatException | ||
* if the given string doesn't parse or the checksum is invalid | ||
* @throws WrongNetworkException | ||
* if the given string is valid but not for the expected network (eg testnet vs mainnet) | ||
*/ | ||
public static AbstractAddress fromString(@Nullable NetworkParameters params, String str) | ||
throws AddressFormatException { | ||
try { | ||
return Address.fromBase58(params, str); | ||
} catch (WrongNetworkException x) { | ||
throw x; | ||
} catch (AddressFormatException x) { | ||
try { | ||
return SegwitAddress.fromBech32(params, str); | ||
} catch (WrongNetworkException x2) { | ||
throw x; | ||
} catch (AddressFormatException x2) { | ||
throw new AddressFormatException(str); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Get either the public key hash or script hash that is encoded in the address. | ||
* | ||
* @return hash that is encoded in the address | ||
*/ | ||
public abstract byte[] getHash(); | ||
|
||
/** | ||
* Get the type of output script that will be used for sending to the address. | ||
* | ||
* @return type of output script | ||
*/ | ||
public abstract ScriptType getOutputScriptType(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
/* Copyright (c) 2018 Coinomi Ltd | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
package org.bitcoinj.core; | ||
|
||
import java.util.Arrays; | ||
import java.util.Locale; | ||
|
||
public class Bech32 { | ||
/** The Bech32 character set for encoding. */ | ||
private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; | ||
|
||
/** The Bech32 character set for decoding. */ | ||
private static final byte[] CHARSET_REV = { | ||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | ||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | ||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | ||
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, | ||
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, | ||
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, | ||
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, | ||
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 | ||
}; | ||
|
||
public static class Bech32Data { | ||
final String hrp; | ||
final byte[] data; | ||
|
||
private Bech32Data(final String hrp, final byte[] data) { | ||
this.hrp = hrp; | ||
this.data = data; | ||
} | ||
} | ||
|
||
/** Find the polynomial with value coefficients mod the generator as 30-bit. */ | ||
private static int polymod(final byte[] values) { | ||
int c = 1; | ||
for (byte v_i: values) { | ||
int c0 = (c >>> 25) & 0xff; | ||
c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff); | ||
if ((c0 & 1) != 0) c ^= 0x3b6a57b2; | ||
if ((c0 & 2) != 0) c ^= 0x26508e6d; | ||
if ((c0 & 4) != 0) c ^= 0x1ea119fa; | ||
if ((c0 & 8) != 0) c ^= 0x3d4233dd; | ||
if ((c0 & 16) != 0) c ^= 0x2a1462b3; | ||
} | ||
return c; | ||
} | ||
|
||
/** 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]; | ||
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); | ||
ret[i + hrpLength + 1] = (byte) (c & 0x1f); | ||
} | ||
ret[hrpLength] = 0; | ||
return ret; | ||
} | ||
|
||
/** Verify a checksum. */ | ||
private static boolean 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; | ||
} | ||
|
||
/** Create a checksum. */ | ||
private static byte[] createChecksum(final String hrp, 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; | ||
byte[] ret = new byte[6]; | ||
for (int i = 0; i < 6; ++i) { | ||
ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31); | ||
} | ||
return ret; | ||
} | ||
|
||
/** Encode a Bech32 string. */ | ||
public static String encode(final Bech32Data bech32) throws AddressFormatException { | ||
return encode(bech32.hrp, bech32.data); | ||
} | ||
|
||
/** Encode a Bech32 string. */ | ||
public static String encode(String hrp, final byte[] values) throws AddressFormatException { | ||
if (hrp.length() < 1) throw new AddressFormatException("Human-readable part is too short"); | ||
if (hrp.length() > 83) throw new AddressFormatException("Human-readable part is too long"); | ||
hrp = hrp.toLowerCase(Locale.ROOT); | ||
byte[] checksum = createChecksum(hrp, 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); | ||
StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length); | ||
sb.append(hrp); | ||
sb.append('1'); | ||
for (byte b : combined) { | ||
sb.append(CHARSET.charAt(b)); | ||
} | ||
return sb.toString(); | ||
} | ||
|
||
/** Decode a Bech32 string. */ | ||
public static Bech32Data decode(final String str) throws AddressFormatException { | ||
boolean lower = false, upper = false; | ||
if (str.length() < 8) throw new AddressFormatException("Input too short"); | ||
if (str.length() > 90) throw new AddressFormatException("Input too long"); | ||
for (int i = 0; i < str.length(); ++i) { | ||
char c = str.charAt(i); | ||
if (c < 33 || c > 126) throw new AddressFormatException("Characters out of range"); | ||
if (c >= 'a' && c <= 'z') lower = true; | ||
if (c >= 'A' && c <= 'Z') upper = true; | ||
} | ||
if (lower && upper) throw new AddressFormatException("Cannot mix upper and lower cases"); | ||
int pos = str.lastIndexOf('1'); | ||
if (pos < 1) throw new AddressFormatException("Missing human-readable part"); | ||
if (pos + 7 > str.length()) throw new AddressFormatException("Data part too short"); | ||
byte[] values = new byte[str.length() - 1 - pos]; | ||
for (int i = 0; i < str.length() - 1 - pos; ++i) { | ||
char c = str.charAt(i + pos + 1); | ||
if (CHARSET_REV[c] == -1) throw new AddressFormatException("Characters out of range"); | ||
values[i] = CHARSET_REV[c]; | ||
} | ||
String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT); | ||
if (!verifyChecksum(hrp, values)) throw new AddressFormatException("Invalid checksum"); | ||
return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.