Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<optional>true</optional>
<scope>test</scope>
</dependency>

<dependency>
Expand Down
15 changes: 9 additions & 6 deletions api/src/main/java/io/bosonnetwork/CryptoContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@
/**
* <p>
* CryptoContext provides a cryptographic context for encrypting and decrypting messages
* using public-key authenticated encryption. It manages nonce generation and validation
* to ensure message uniqueness and replay protection.
* using public-key authenticated encryption. It manages nonce generation for outgoing messages
* and provides a basic safeguard against immediate reuse of the previous incoming nonce
* (see {@link #decrypt(byte[])} for the precise, limited guarantee).
* </p>
* <p>
* <b>Thread Safety:</b> The nonce generation for outgoing messages is synchronized to ensure
Expand Down Expand Up @@ -138,14 +139,16 @@ public byte[] encrypt(byte[] data) {
/**
* Decrypts the given data, verifying and extracting the prepended nonce.
* <p>
* This method checks for nonce reuse to prevent replay attacks. If the nonce
* is duplicated (i.e., the same as the last received nonce), a {@link CryptoException}
* is thrown.
* As a basic safeguard this rejects an exact repeat of the <em>immediately previous</em>
* peer nonce (throwing {@link CryptoException}). This is <strong>not</strong> full replay
* protection: it does not detect reuse of any earlier nonce, and the check is not thread-safe
* (concurrent {@code decrypt} calls may race). Callers that need strong replay protection must
* track seen nonces themselves.
* </p>
*
* @param data The encrypted data, with the nonce prepended (nonce || ciphertext).
* @return The decrypted plaintext data.
* @throws CryptoException If the input is invalid, the nonce is duplicated, or decryption fails.
* @throws CryptoException If the input is invalid, the nonce repeats the previous one, or decryption fails.
* @throws NullPointerException if {@code data} is {@code null}.
*/
public byte[] decrypt(byte[] data) throws CryptoException {
Expand Down
5 changes: 4 additions & 1 deletion api/src/main/java/io/bosonnetwork/Id.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public class Id implements Comparable<Id> {
/**
* 3-way comparator. For sorting {@code Id} instances based on their
* distance to a target identifier using the XOR metric.
*
* @deprecated use {@link Id#threeWayCompare(Id, Id)} directly via a comparator instead,
* e.g. {@code (a, b) -> target.threeWayCompare(a, b)}.
*/
@Deprecated
public static class ThreeWayComparator implements java.util.Comparator<Id> {
Expand Down Expand Up @@ -372,7 +375,7 @@ public byte[] getBytes() {
*
* @return the internal byte array (must not be modified).
*/
public final byte[] bytes() {
public final byte[] bytesUnsafe() {
// Performance critical method: returns internal array directly
return bytes;
}
Expand Down
19 changes: 10 additions & 9 deletions api/src/main/java/io/bosonnetwork/Identity.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,30 @@ public interface Identity {
boolean verify(byte[] data, byte[] signature);

/**
* Encrypts the provided data for the specified receiver using a one-shot encryption
* Encrypts the provided data for the specified recipient using a one-shot encryption
* operation. Random nonce is generated and prefixed to the encrypted data to ensure
* uniqueness and prevent replay attacks.
*
* @param receiver the {@link Id} of the intended receiver for whom the data is encrypted
* @param recipient the {@link Id} of the intended recipient for whom the data is encrypted
* @param data the plaintext data to encrypt
* @return the encrypted data as a byte array, prefixed with a random nonce
* @throws CryptoException if the encryption process fails due to cryptographic errors
*/
byte[] encrypt(Id receiver, byte[] data) throws CryptoException;
byte[] encrypt(Id recipient, byte[] data) throws CryptoException;

/**
* Encrypts the provided data for the specified receiver using a one-shot encryption
* operation. The encryption process may also incorporate the supplied nonce to ensure
* data uniqueness and prevent replay attacks.
* Encrypts the provided data for the specified recipient using a one-shot encryption
* operation with the caller-supplied nonce. Unlike {@link #encrypt(Id, byte[])}, the nonce is
* provided by the caller and is <strong>not</strong> prepended to the returned ciphertext;
* the caller is responsible for ensuring the nonce is unique per key and message.
*
* @param receiver the {@link Id} of the intended receiver for whom the data is encrypted
* @param nonce the byte array used as nonce for the encryption process, ensuring uniqueness
* @param recipient the {@link Id} of the intended recipient for whom the data is encrypted
* @param nonce the byte array used as nonce for the encryption process; must be unique per key/message
* @param data the plaintext data to encrypt
* @return the encrypted data as a byte array
* @throws CryptoException if the encryption fails due to cryptographic errors or invalid parameters
*/
byte[] encrypt(Id receiver, byte[] nonce, byte[] data) throws CryptoException;
byte[] encrypt(Id recipient, byte[] nonce, byte[] data) throws CryptoException;

/**
* Decrypts the provided encrypted data sent by the specified sender using a one-shot decryption operation.
Expand Down
4 changes: 2 additions & 2 deletions api/src/main/java/io/bosonnetwork/Network.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public enum Network {
* Checks if the specified socket address can apply for this network.
*
* @param addr the socket address to check.
* @return true is the address can apply for this network, otherwise false.
* @return true if the address can apply for this network, otherwise false.
*/
public boolean canUseSocketAddress(InetSocketAddress addr) {
return canUseAddress(addr.getAddress());
Expand All @@ -79,7 +79,7 @@ public boolean canUseSocketAddress(InetSocketAddress addr) {
* Checks if the specified IP address can apply for this network.
*
* @param addr the IP address to check.
* @return true is the address can apply for this network, otherwise false.
* @return true if the address can apply for this network, otherwise false.
*/
public boolean canUseAddress(InetAddress addr) {
return preferredAddressType.isInstance(addr);
Expand Down
31 changes: 18 additions & 13 deletions api/src/main/java/io/bosonnetwork/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.util.Collection;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.CompletableFuture;

import io.bosonnetwork.crypto.CryptoException;
Expand All @@ -40,6 +41,12 @@
* <li>Storing values and announcing peers (optionally persistent)</li>
* <li>Cryptographic operations: sign, verify, encrypt, decrypt</li>
* </ul>
* <p>
* <b>Lookup result conventions:</b> {@link #findNode} returns a {@link Result} that may carry the
* node's IPv4 and/or IPv6 address (either side may be {@code null}). Single-result lookups
* ({@link #findValue}, the single-result {@link #findPeer(Id)}) complete with {@code null} when
* nothing is found, while collection lookups ({@link #getPeers}, {@link #findPeer(Id, int, int, LookupOption)})
* complete with an empty list.
*/
public interface Node extends Identity {
/** The maximum age for a peer (2 hours). */
Expand Down Expand Up @@ -242,10 +249,10 @@ default CompletableFuture<Void> storeValue(Value value, boolean persistent) {
CompletableFuture<Void> storeValue(Value value, int expectedSequenceNumber, boolean persistent);

/**
* Finds peers in the network by ID using the default lookup option.
* Finds a peer in the network by ID using the default lookup option.
*
* @param id the {@link Id} to find peers for
* @return a {@link CompletableFuture} containing the list of {@link PeerInfo}
* @return a {@link CompletableFuture} containing the {@link PeerInfo}, or {@code null} if not found
*/
default CompletableFuture<PeerInfo> findPeer(Id id) {
return findPeer(id, -1, 1, null)
Expand Down Expand Up @@ -450,21 +457,19 @@ default CompletableFuture<Void> announcePeer(PeerInfo peer, boolean persistent)
CryptoContext createCryptoContext(Id id) throws CryptoException;

/**
* Creates and initializes a new KadNode instance using the provided configuration.
* Creates and initializes a new {@link Node} instance using the provided configuration.
* <p>
* The concrete implementation is discovered via the {@link ServiceLoader} mechanism,
* looking up a registered {@link NodeFactory} provider (the Kademlia DHT node is
* provided by the {@code boson-dht} module).
*
* @param config the node configuration
* @return an initialized {@link Node} instance
* @throws BosonException if the KadNode cannot be initialized
* @throws BosonException if no node implementation is available or it cannot be initialized
*/
static Node kadNode(NodeConfiguration config) throws BosonException {
try {
return (Node) Class.forName("io.bosonnetwork.kademlia.KadNode")
.getConstructor(NodeConfiguration.class)
.newInstance(config);
} catch (ClassNotFoundException e) {
throw new BosonException("KadNode not found in classpath", e);
} catch (Exception e) {
throw new BosonException("Internal error: can not instantiate KadNode", e);
}
NodeFactory factory = ServiceLoader.load(NodeFactory.class).findFirst()
.orElseThrow(() -> new BosonException("No NodeFactory implementation found in classpath"));
return factory.create(config);
}
}
2 changes: 1 addition & 1 deletion api/src/main/java/io/bosonnetwork/NodeConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ default Path dataDir() {
/**
* Provides the URL for database storage used by the DHT node.
*
* @return the external database URL as a string, or {@code null} if not configured.
* @return the database URL as a string; defaults to {@code "jdbc:sqlite:node.db"}.
*/
default String databaseUri() {
return "jdbc:sqlite:node.db";
Expand Down
46 changes: 46 additions & 0 deletions api/src/main/java/io/bosonnetwork/NodeFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 - bosonnetwork.io
*
* 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 io.bosonnetwork;

/**
* Service provider interface for creating {@link Node} instances.
* <p>
* Implementations are discovered at runtime via the {@link java.util.ServiceLoader}
* mechanism, which decouples the {@code boson-api} contract from the concrete node
* implementation (e.g. the Kademlia DHT node in {@code boson-dht}). Providers register
* themselves through a {@code META-INF/services/io.bosonnetwork.NodeFactory} entry, or a
* {@code provides io.bosonnetwork.NodeFactory with ...} declaration when running on the
* Java module path.
*
* @see Node#kadNode(NodeConfiguration)
*/
public interface NodeFactory {
/**
* Creates and initializes a new {@link Node} instance using the provided configuration.
*
* @param config the node configuration
* @return an initialized {@link Node} instance
* @throws BosonException if the node cannot be initialized
*/
Node create(NodeConfiguration config) throws BosonException;
}
43 changes: 15 additions & 28 deletions api/src/main/java/io/bosonnetwork/NodeInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import java.util.Objects;

/**
* THis class represent the node information in the Boson network, it contains
* This class represents the node information in the Boson network; it contains
* basic node network information.
*/
public class NodeInfo {
Expand All @@ -44,12 +44,8 @@ public class NodeInfo {
* @param addr the node socket address.
*/
public NodeInfo(Id id, InetSocketAddress addr) {
if (id == null)
throw new IllegalArgumentException("Invalid node id: null");

if (addr == null)
throw new IllegalArgumentException("Invalid socket address: null");

Objects.requireNonNull(id, "id");
Objects.requireNonNull(addr, "addr");
if (addr.getPort() <= 0 || addr.getPort() > 65535)
throw new IllegalArgumentException("Invalid port: " + addr.getPort());

Expand All @@ -65,12 +61,8 @@ public NodeInfo(Id id, InetSocketAddress addr) {
* @param port the node port number.
*/
public NodeInfo(Id id, InetAddress addr, int port) {
if (id == null)
throw new IllegalArgumentException("Invalid node id: null");

if (addr == null)
throw new IllegalArgumentException("Invalid socket address: null");

Objects.requireNonNull(id, "id");
Objects.requireNonNull(addr, "addr");
if (port <= 0 || port > 65535)
throw new IllegalArgumentException("Invalid port: " + port);

Expand All @@ -86,12 +78,8 @@ public NodeInfo(Id id, InetAddress addr, int port) {
* @param port the node port number.
*/
public NodeInfo(Id id, String host, int port) {
if (id == null)
throw new IllegalArgumentException("Invalid node id: null");

if (host == null)
throw new IllegalArgumentException("Invalid socket address: null");

Objects.requireNonNull(id, "id");
Objects.requireNonNull(host, "host");
if (port <= 0 || port > 65535)
throw new IllegalArgumentException("Invalid port: " + port);

Expand All @@ -107,10 +95,8 @@ public NodeInfo(Id id, String host, int port) {
* @param port the node port number.
*/
public NodeInfo(Id id, byte[] addr, int port) {
if (id == null)
throw new IllegalArgumentException("Invalid node id: null");
if (addr == null)
throw new IllegalArgumentException("Invalid socket address: null");
Objects.requireNonNull(id, "id");
Objects.requireNonNull(addr, "addr");
if (port <= 0 || port > 65535)
throw new IllegalArgumentException("Invalid port: " + port);

Expand All @@ -129,8 +115,7 @@ public NodeInfo(Id id, byte[] addr, int port) {
* @param ni another node info object.
*/
protected NodeInfo(NodeInfo ni) {
if (ni == null)
throw new IllegalArgumentException("Invalid node info: null");
Objects.requireNonNull(ni, "ni");

this.id = ni.id;
this.addr = ni.addr;
Expand Down Expand Up @@ -202,10 +187,12 @@ public int getVersion() {
}

/**
* Checks if the node information is identical with the other one.
* Checks whether this node info conflicts with another, i.e. they share the same id
* <em>or</em> the same socket address. This is a partial match used to detect identity/address
* collisions, not full equality (see {@link #equals(Object)}).
*
* @param other another node info object to check
* @return true if the other node info object is identical with this, false otherwise.
* @return true if this and {@code other} share the same id or the same address, false otherwise.
*/
public boolean matches(NodeInfo other) {
if (other != null)
Expand All @@ -216,7 +203,7 @@ public boolean matches(NodeInfo other) {

@Override
public int hashCode() {
return 0x6030A + Objects.hash(id, addr, version);
return 0x6030A + Objects.hash(id, addr);
}

@Override
Expand Down
Loading
Loading