224 lines
7.3 KiB
Java
224 lines
7.3 KiB
Java
/*
|
|
* Copyright 2015 Christian Basler
|
|
*
|
|
* 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 ch.dissem.bitmessage.entity;
|
|
|
|
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
import ch.dissem.bitmessage.utils.AccessCounter;
|
|
import ch.dissem.bitmessage.utils.Base58;
|
|
import ch.dissem.bitmessage.utils.Bytes;
|
|
import ch.dissem.bitmessage.utils.Encode;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.Serializable;
|
|
import java.util.Arrays;
|
|
import java.util.Objects;
|
|
|
|
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
|
import static ch.dissem.bitmessage.utils.Decode.varInt;
|
|
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
|
|
/**
|
|
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
|
|
* holding private keys.
|
|
*/
|
|
public class BitmessageAddress implements Serializable {
|
|
private final long version;
|
|
private final long stream;
|
|
private final byte[] ripe;
|
|
private final byte[] tag;
|
|
/**
|
|
* Used for V4 address encryption. It's easier to just create it regardless of address version.
|
|
*/
|
|
private final byte[] publicDecryptionKey;
|
|
|
|
private String address;
|
|
|
|
private PrivateKey privateKey;
|
|
private Pubkey pubkey;
|
|
|
|
private String alias;
|
|
private boolean subscribed;
|
|
|
|
BitmessageAddress(long version, long stream, byte[] ripe) {
|
|
try {
|
|
this.version = version;
|
|
this.stream = stream;
|
|
this.ripe = ripe;
|
|
|
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
Encode.varInt(version, os);
|
|
Encode.varInt(stream, os);
|
|
if (version < 4) {
|
|
byte[] checksum = security().sha512(os.toByteArray(), ripe);
|
|
this.tag = null;
|
|
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
} else {
|
|
// for tag and decryption key, the checksum has to be created with 0x00 padding
|
|
byte[] checksum = security().doubleSha512(os.toByteArray(), ripe);
|
|
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
|
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
}
|
|
// but for the address and its checksum they need to be stripped
|
|
int offset = Bytes.numberOfLeadingZeros(ripe);
|
|
os.write(ripe, offset, ripe.length - offset);
|
|
byte[] checksum = security().doubleSha512(os.toByteArray());
|
|
os.write(checksum, 0, 4);
|
|
this.address = "BM-" + Base58.encode(os.toByteArray());
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
public BitmessageAddress(Pubkey publicKey) {
|
|
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
|
|
this.pubkey = publicKey;
|
|
}
|
|
|
|
public BitmessageAddress(PrivateKey privateKey) {
|
|
this(privateKey.getPubkey());
|
|
this.privateKey = privateKey;
|
|
}
|
|
|
|
public BitmessageAddress(String address) {
|
|
try {
|
|
this.address = address;
|
|
byte[] bytes = Base58.decode(address.substring(3));
|
|
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
|
|
AccessCounter counter = new AccessCounter();
|
|
this.version = varInt(in, counter);
|
|
this.stream = varInt(in, counter);
|
|
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
|
|
|
|
// test checksum
|
|
byte[] checksum = security().doubleSha512(bytes, bytes.length - 4);
|
|
byte[] expectedChecksum = bytes(in, 4);
|
|
for (int i = 0; i < 4; i++) {
|
|
if (expectedChecksum[i] != checksum[i])
|
|
throw new IllegalArgumentException("Checksum of address failed");
|
|
}
|
|
if (version < 4) {
|
|
checksum = security().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
|
this.tag = null;
|
|
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
} else {
|
|
checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
|
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
|
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
}
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
public static byte[] calculateTag(long version, long stream, byte[] ripe) {
|
|
try {
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
Encode.varInt(version, out);
|
|
Encode.varInt(stream, out);
|
|
out.write(ripe);
|
|
return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
public long getStream() {
|
|
return stream;
|
|
}
|
|
|
|
public long getVersion() {
|
|
return version;
|
|
}
|
|
|
|
public Pubkey getPubkey() {
|
|
return pubkey;
|
|
}
|
|
|
|
public void setPubkey(Pubkey pubkey) {
|
|
if (pubkey instanceof V4Pubkey) {
|
|
if (!Arrays.equals(tag, ((V4Pubkey) pubkey).getTag()))
|
|
throw new IllegalArgumentException("Pubkey has incompatible tag");
|
|
}
|
|
if (!Arrays.equals(ripe, pubkey.getRipe()))
|
|
throw new IllegalArgumentException("Pubkey has incompatible ripe");
|
|
this.pubkey = pubkey;
|
|
}
|
|
|
|
/**
|
|
* @return the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts.
|
|
*/
|
|
public byte[] getPublicDecryptionKey() {
|
|
return publicDecryptionKey;
|
|
}
|
|
|
|
public PrivateKey getPrivateKey() {
|
|
return privateKey;
|
|
}
|
|
|
|
public String getAddress() {
|
|
return address;
|
|
}
|
|
|
|
public String getAlias() {
|
|
return alias;
|
|
}
|
|
|
|
public void setAlias(String alias) {
|
|
this.alias = alias;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return alias != null ? alias : address;
|
|
}
|
|
|
|
public byte[] getRipe() {
|
|
return ripe;
|
|
}
|
|
|
|
public byte[] getTag() {
|
|
return tag;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
BitmessageAddress address = (BitmessageAddress) o;
|
|
return Objects.equals(version, address.version) &&
|
|
Objects.equals(stream, address.stream) &&
|
|
Arrays.equals(ripe, address.ripe);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Arrays.hashCode(ripe);
|
|
}
|
|
|
|
public boolean isSubscribed() {
|
|
return subscribed;
|
|
}
|
|
|
|
public void setSubscribed(boolean subscribed) {
|
|
this.subscribed = subscribed;
|
|
}
|
|
}
|