mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-19 05:33:44 +01:00
PeerAddress: Support Tor hidden service addresses.
This commit is contained in:
parent
d511effbce
commit
fd85807422
@ -17,6 +17,8 @@
|
||||
|
||||
package org.bitcoinj.core;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import org.bouncycastle.jcajce.provider.digest.SHA3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@ -26,6 +28,9 @@ import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
@ -46,6 +51,9 @@ public class PeerAddress extends ChildMessage {
|
||||
private BigInteger services;
|
||||
private long time;
|
||||
|
||||
private static final BaseEncoding BASE32 = BaseEncoding.base32().lowerCase();
|
||||
private static final byte[] ONIONCAT_PREFIX = Utils.HEX.decode("fd87d87eeb43");
|
||||
|
||||
/**
|
||||
* Construct a peer address from a serialized payload.
|
||||
* @param params NetworkParameters object.
|
||||
@ -138,6 +146,28 @@ public class PeerAddress extends ChildMessage {
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else if (addr == null && hostname != null && hostname.toLowerCase(Locale.ROOT).endsWith(".onion")) {
|
||||
byte[] onionAddress = BASE32.decode(hostname.substring(0, hostname.length() - 6));
|
||||
if (onionAddress.length == 10) {
|
||||
// TORv2
|
||||
stream.write(0x03);
|
||||
stream.write(new VarInt(10).encode());
|
||||
stream.write(onionAddress);
|
||||
} else if (onionAddress.length == 32 + 2 + 1) {
|
||||
// TORv3
|
||||
stream.write(0x04);
|
||||
stream.write(new VarInt(32).encode());
|
||||
byte[] pubkey = Arrays.copyOfRange(onionAddress, 0, 32);
|
||||
byte[] checksum = Arrays.copyOfRange(onionAddress, 32, 34);
|
||||
byte torVersion = onionAddress[34];
|
||||
if (torVersion != 0x03)
|
||||
throw new IllegalStateException("version");
|
||||
if (!Arrays.equals(checksum, onionChecksum(pubkey, torVersion)))
|
||||
throw new IllegalStateException("checksum");
|
||||
stream.write(pubkey);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
@ -155,6 +185,15 @@ public class PeerAddress extends ChildMessage {
|
||||
ipBytes = v6addr;
|
||||
}
|
||||
stream.write(ipBytes);
|
||||
} else if (hostname != null && hostname.toLowerCase(Locale.ROOT).endsWith(".onion")) {
|
||||
byte[] onionAddress = BASE32.decode(hostname.substring(0, hostname.length() - 6));
|
||||
if (onionAddress.length == 10) {
|
||||
// TORv2
|
||||
stream.write(ONIONCAT_PREFIX);
|
||||
stream.write(onionAddress);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
@ -197,6 +236,23 @@ public class PeerAddress extends ChildMessage {
|
||||
throw new ProtocolException("invalid length of IPv6 address: " + addrLen);
|
||||
addr = getByAddress(addrBytes);
|
||||
hostname = null;
|
||||
} else if (networkId == 0x03) {
|
||||
// TORv2
|
||||
if (addrLen != 10)
|
||||
throw new ProtocolException("invalid length of TORv2 address: " + addrLen);
|
||||
hostname = BASE32.encode(addrBytes) + ".onion";
|
||||
addr = null;
|
||||
} else if (networkId == 0x04) {
|
||||
// TORv3
|
||||
if (addrLen != 32)
|
||||
throw new ProtocolException("invalid length of TORv3 address: " + addrLen);
|
||||
byte torVersion = 0x03;
|
||||
byte[] onionAddress = new byte[35];
|
||||
System.arraycopy(addrBytes, 0, onionAddress, 0, 32);
|
||||
System.arraycopy(onionChecksum(addrBytes, torVersion), 0, onionAddress, 32, 2);
|
||||
onionAddress[34] = torVersion;
|
||||
hostname = BASE32.encode(onionAddress) + ".onion";
|
||||
addr = null;
|
||||
} else {
|
||||
// ignore unknown network IDs
|
||||
addr = null;
|
||||
@ -207,8 +263,13 @@ public class PeerAddress extends ChildMessage {
|
||||
length += 8;
|
||||
byte[] addrBytes = readBytes(16);
|
||||
length += 16;
|
||||
addr = getByAddress(addrBytes);
|
||||
hostname = null;
|
||||
if (Arrays.equals(ONIONCAT_PREFIX, Arrays.copyOf(addrBytes, 6))) {
|
||||
byte[] onionAddress = Arrays.copyOfRange(addrBytes, 6, 16);
|
||||
hostname = BASE32.encode(onionAddress) + ".onion";
|
||||
} else {
|
||||
addr = getByAddress(addrBytes);
|
||||
hostname = null;
|
||||
}
|
||||
}
|
||||
port = Utils.readUint16BE(payload, cursor);
|
||||
cursor += 2;
|
||||
@ -223,6 +284,16 @@ public class PeerAddress extends ChildMessage {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] onionChecksum(byte[] pubkey, byte version) {
|
||||
if (pubkey.length != 32)
|
||||
throw new IllegalArgumentException();
|
||||
SHA3.Digest256 digest256 = new SHA3.Digest256();
|
||||
digest256.update(".onion checksum".getBytes(StandardCharsets.US_ASCII));
|
||||
digest256.update(pubkey);
|
||||
digest256.update(version);
|
||||
return Arrays.copyOf(digest256.digest(), 2);
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ import static org.junit.Assert.assertTrue;
|
||||
public class AddressV1MessageTest {
|
||||
|
||||
private static final NetworkParameters UNITTEST = UnitTestParams.get();
|
||||
// mostly copied from src/test/netbase_tests.cpp#stream_addrv1_hex
|
||||
// mostly copied from src/test/netbase_tests.cpp#stream_addrv1_hex and src/test/net_tests.cpp
|
||||
private static final String MESSAGE_HEX =
|
||||
"03" // number of entries
|
||||
"04" // number of entries
|
||||
|
||||
+ "61bc6649" // time, Fri Jan 9 02:54:25 UTC 2009
|
||||
+ "0000000000000000" // service flags, NODE_NONE
|
||||
@ -48,14 +48,19 @@ public class AddressV1MessageTest {
|
||||
+ "ffffffff" // time, Sun Feb 7 06:28:15 UTC 2106
|
||||
+ "4804000000000000" // service flags, NODE_WITNESS | NODE_COMPACT_FILTERS | NODE_NETWORK_LIMITED
|
||||
+ "00000000000000000000000000000001" // address, fixed 16 bytes (IPv6)
|
||||
+ "f1f2"; // port
|
||||
+ "f1f2" // port
|
||||
|
||||
+ "00000000" // time
|
||||
+ "0000000000000000" // service flags, NODE_NONE
|
||||
+ "fd87d87eeb43f1f2f3f4f5f6f7f8f9fa" // address, fixed 16 bytes (TORv2)
|
||||
+ "0000"; // port
|
||||
|
||||
@Test
|
||||
public void roundtrip() {
|
||||
AddressMessage message = new AddressV1Message(UNITTEST, HEX.decode(MESSAGE_HEX));
|
||||
|
||||
List<PeerAddress> addresses = message.getAddresses();
|
||||
assertEquals(3, addresses.size());
|
||||
assertEquals(4, addresses.size());
|
||||
PeerAddress a0 = addresses.get(0);
|
||||
assertEquals("2009-01-09T02:54:25Z", Utils.dateTimeFormat(a0.getTime() * 1000));
|
||||
assertEquals(0, a0.getServices().intValue());
|
||||
@ -78,6 +83,12 @@ public class AddressV1MessageTest {
|
||||
assertEquals("0:0:0:0:0:0:0:1", a2.getAddr().getHostAddress());
|
||||
assertNull(a2.getHostname());
|
||||
assertEquals(0xf1f2, a2.getPort());
|
||||
PeerAddress a3 = addresses.get(3);
|
||||
assertEquals("1970-01-01T00:00:00Z", Utils.dateTimeFormat(a3.getTime() * 1000));
|
||||
assertEquals(0, a3.getServices().intValue());
|
||||
assertNull(a3.getAddr());
|
||||
assertEquals("6hzph5hv6337r6p2.onion", a3.getHostname());
|
||||
assertEquals(0, a3.getPort());
|
||||
|
||||
assertEquals(MESSAGE_HEX, HEX.encode(message.bitcoinSerialize()));
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ import static org.junit.Assert.assertTrue;
|
||||
public class AddressV2MessageTest {
|
||||
|
||||
private static final NetworkParameters UNITTEST = UnitTestParams.get();
|
||||
// mostly copied from src/test/netbase_tests.cpp#stream_addrv2_hex
|
||||
// mostly copied from src/test/netbase_tests.cpp#stream_addrv2_hex and src/test/net_tests.cpp
|
||||
private static final String MESSAGE_HEX =
|
||||
"03" // number of entries
|
||||
"05" // number of entries
|
||||
|
||||
+ "61bc6649" // time, Fri Jan 9 02:54:25 UTC 2009
|
||||
+ "00" // service flags, COMPACTSIZE(NODE_NONE)
|
||||
@ -54,14 +54,28 @@ public class AddressV2MessageTest {
|
||||
+ "02" // network id, IPv6
|
||||
+ "10" // address length, COMPACTSIZE(16)
|
||||
+ "00000000000000000000000000000001" // address
|
||||
+ "f1f2"; // port
|
||||
+ "f1f2" // port
|
||||
|
||||
+ "00000000" // time
|
||||
+ "00" // service flags, COMPACTSIZE(NODE_NONE)
|
||||
+ "03" // network id, TORv2
|
||||
+ "0a" // address length, COMPACTSIZE(10)
|
||||
+ "f1f2f3f4f5f6f7f8f9fa" // address
|
||||
+ "0000" // port
|
||||
|
||||
+ "00000000" // time
|
||||
+ "00" // service flags, COMPACTSIZE(NODE_NONE)
|
||||
+ "04" // network id, TORv3
|
||||
+ "20"// address length, COMPACTSIZE(32)
|
||||
+ "53cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88" // address
|
||||
+ "0000"; // port
|
||||
|
||||
@Test
|
||||
public void roundtrip() {
|
||||
AddressMessage message = new AddressV2Message(UNITTEST, HEX.decode(MESSAGE_HEX));
|
||||
|
||||
List<PeerAddress> addresses = message.getAddresses();
|
||||
assertEquals(3, addresses.size());
|
||||
assertEquals(5, addresses.size());
|
||||
PeerAddress a0 = addresses.get(0);
|
||||
assertEquals("2009-01-09T02:54:25Z", Utils.dateTimeFormat(a0.getTime() * 1000));
|
||||
assertEquals(0, a0.getServices().intValue());
|
||||
@ -84,6 +98,18 @@ public class AddressV2MessageTest {
|
||||
assertEquals("0:0:0:0:0:0:0:1", a2.getAddr().getHostAddress());
|
||||
assertNull(a2.getHostname());
|
||||
assertEquals(0xf1f2, a2.getPort());
|
||||
PeerAddress a3 = addresses.get(3);
|
||||
assertEquals("1970-01-01T00:00:00Z", Utils.dateTimeFormat(a3.getTime() * 1000));
|
||||
assertEquals(0, a3.getServices().intValue());
|
||||
assertNull(a3.getAddr());
|
||||
assertEquals("6hzph5hv6337r6p2.onion", a3.getHostname());
|
||||
assertEquals(0, a3.getPort());
|
||||
PeerAddress a4 = addresses.get(4);
|
||||
assertEquals("1970-01-01T00:00:00Z", Utils.dateTimeFormat(a4.getTime() * 1000));
|
||||
assertEquals(0, a4.getServices().intValue());
|
||||
assertNull(a4.getAddr());
|
||||
assertEquals("kpgvmscirrdqpekbqjsvw5teanhatztpp2gl6eee4zkowvwfxwenqaid.onion", a4.getHostname());
|
||||
assertEquals(0, a4.getPort());
|
||||
|
||||
assertEquals(MESSAGE_HEX, HEX.encode(message.bitcoinSerialize()));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user