Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
initial commit
Archived did:key:z6Mkh1Ah...vwes opened 2 months ago
15 files changed +100 -23 c06b00e3 2cfcd420
modified Cargo.lock
@@ -673,8 +673,7 @@ dependencies = [
[[package]]
name = "cypheraddr"
version = "0.4.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "ba5c54d2ad4ab9941383519471b75d12abc1a7b4779265e233168f2703a730d9"
+
source = "git+https://github.com/lorenzleutgeb/cyphernet.rs.git?branch=push-ooltwtzkpvlk#ca0fedcac9f1075d516ce1f0129f4aa9b73ee81e"
dependencies = [
 "amplify",
 "base32",
@@ -685,8 +684,7 @@ dependencies = [
[[package]]
name = "cyphergraphy"
version = "0.3.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "b67c16c8ef5ddcdab57aab83fd8e770540ea3682ccdae09642c63575b0da2184"
+
source = "git+https://github.com/lorenzleutgeb/cyphernet.rs.git?branch=push-ooltwtzkpvlk#ca0fedcac9f1075d516ce1f0129f4aa9b73ee81e"
dependencies = [
 "amplify",
 "ec25519",
@@ -696,8 +694,7 @@ dependencies = [
[[package]]
name = "cyphernet"
version = "0.5.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "ac949369884a7a1d802cc669821269c707be8cec4d65043382e253733d2e62e1"
+
source = "git+https://github.com/lorenzleutgeb/cyphernet.rs.git?branch=push-ooltwtzkpvlk#ca0fedcac9f1075d516ce1f0129f4aa9b73ee81e"
dependencies = [
 "cypheraddr",
 "cyphergraphy",
@@ -2292,8 +2289,7 @@ dependencies = [
[[package]]
name = "noise-framework"
version = "0.4.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "b57e96e713d599dc58755d0e5bb2238908a63e13f624f70c8345fdb7d8b51bae"
+
source = "git+https://github.com/lorenzleutgeb/cyphernet.rs.git?branch=push-ooltwtzkpvlk#ca0fedcac9f1075d516ce1f0129f4aa9b73ee81e"
dependencies = [
 "amplify",
 "chacha20poly1305",
@@ -2330,7 +2326,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
-
 "windows-sys 0.59.0",
+
 "windows-sys 0.60.2",
]

[[package]]
@@ -3798,8 +3794,7 @@ dependencies = [
[[package]]
name = "socks5-client"
version = "0.4.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "ffc7dcf6fab1d65d82d633006a4cc658d76ce436e01cf1a7c71873c0eeba324c"
+
source = "git+https://github.com/lorenzleutgeb/cyphernet.rs.git?branch=push-ooltwtzkpvlk#ca0fedcac9f1075d516ce1f0129f4aa9b73ee81e"
dependencies = [
 "amplify",
 "cypheraddr",
modified Cargo.toml
@@ -96,3 +96,7 @@ clippy.must_use_candidate = "deny"
inherits = "release"
debug = true
incremental = false
+

+
#development
+
[patch.crates-io]
+
cyphernet = { git = "https://github.com/lorenzleutgeb/cyphernet.rs.git", branch = "push-ooltwtzkpvlk" }

\ No newline at end of file
modified crates/radicle-cli/src/terminal/format.rs
@@ -46,6 +46,7 @@ pub fn addr_compact(address: &Address) -> Paint<String> {
                .collect::<String>();
            format!("{start}…{end}")
        }
+
        HostName::I2p(i2p) => todo!(),
        _ => unreachable!(),
    };

modified crates/radicle-node/Cargo.toml
@@ -21,7 +21,7 @@ bytes = { workspace = true }
chrono = { workspace = true, features = ["clock"] }
colored = { workspace = true }
crossbeam-channel = { workspace = true }
-
cyphernet = { workspace = true, features = ["tor", "dns", "ed25519", "p2p-ed25519", "noise-framework", "noise_sha2"] }
+
cyphernet = { workspace = true, features = ["i2p", "tor", "dns", "ed25519", "p2p-ed25519", "noise-framework", "noise_sha2"] }
fastrand = { workspace = true }
lexopt = { workspace = true }
log = { workspace = true, features = ["kv", "std"] }
modified crates/radicle-node/src/wire.rs
@@ -1113,6 +1113,28 @@ pub fn dial<G: Ecdh<Pk = NodeId>>(
                ));
            }
        },
+
        // For I2P addresses, handle with care.
+
        (HostName::I2p(i2p), proxy) => match config.i2p {
+
            // In I2P proxy mode, simply use the configured proxy address.
+
            // This takes precedence over any global proxy.
+
            Some(AddressConfig::Proxy { address }) => address.into(),
+
            // In "forward" mode, if a global proxy is set, we use that, otherwise
+
            // we treat `.i2p` addresses as regular DNS names.
+
            Some(AddressConfig::Forward) => {
+
                if let Some(proxy) = proxy {
+
                    proxy.into()
+
                } else {
+
                    NetAddr::new(InetHost::Dns(i2p.to_string()), remote_addr.port)
+
                }
+
            }
+
            // If I2P address support isn't configured, refuse to connect.
+
            None => {
+
                return Err(io::Error::new(
+
                    io::ErrorKind::Unsupported,
+
                    "no configuration found for .i2p addresses",
+
                ));
+
            }
+
        },
        _ => {
            return Err(io::Error::new(
                io::ErrorKind::Unsupported,
modified crates/radicle-protocol/Cargo.toml
@@ -15,7 +15,7 @@ test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "qche
bloomy = "1.2"
bytes = { workspace = true }
crossbeam-channel = { workspace = true }
-
cyphernet = { workspace = true, features = ["tor"] }
+
cyphernet = { workspace = true, features = ["i2p", "tor"] }
fastrand = { workspace = true }
log = { workspace = true, features = ["std"] }
nonempty = { workspace = true, features = ["serialize"] }
@@ -35,4 +35,4 @@ paste = "1.0.15"
qcheck = { workspace = true }
qcheck-macros = { workspace = true }
radicle = { workspace = true, features = ["test"] }
-
radicle-crypto = { workspace = true, features = ["test", "cyphernet"] }
+
radicle-crypto = { workspace = true, features = ["test", "cyphernet"] }

\ No newline at end of file
modified crates/radicle-protocol/src/service.rs
@@ -2629,6 +2629,7 @@ where
        match AddressType::from(address) {
            // Only consider onion addresses if configured.
            AddressType::Onion => self.config.onion.is_some(),
+
            AddressType::I2p => self.config.i2p.is_some(),
            AddressType::Dns | AddressType::Ipv4 | AddressType::Ipv6 => true,
        }
    }
modified crates/radicle-protocol/src/wire.rs
@@ -15,7 +15,7 @@ use std::string::FromUtf8Error;

use bytes::{Buf, BufMut};

-
use cyphernet::addr::tor;
+
use cyphernet::addr::{i2p, tor};

use radicle::crypto::{PublicKey, Signature, Unverified};
use radicle::git;
@@ -60,6 +60,8 @@ pub enum Invalid {
    InvalidUserAgent { err: String },
    #[error("invalid onion address: {0}")]
    OnionAddr(#[from] tor::OnionAddrDecodeError),
+
    #[error("invalid i2p address: {0}")]
+
    I2pAddr(#[from] i2p::I2pAddrDecodeError),
    #[error("invalid timestamp: {actual_millis} millis")]
    Timestamp { actual_millis: u64 },

@@ -263,6 +265,12 @@ impl Encode for cyphernet::addr::tor::OnionAddrV3 {
    }
}

+
impl Encode for cyphernet::addr::i2p::I2pAddr {
+
    fn encode(&self, buf: &mut impl BufMut) {
+
        todo!()
+
    }
+
}
+

impl Encode for UserAgent {
    fn encode(&self, buf: &mut impl BufMut) {
        self.as_ref().encode(buf)
@@ -545,6 +553,12 @@ impl Decode for tor::OnionAddrV3 {
    }
}

+
impl Decode for i2p::I2pAddr {
+
    fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
+
        todo!()
+
    }
+
}
+

impl Encode for Timestamp {
    fn encode(&self, buf: &mut impl BufMut) {
        self.deref().encode(buf)
modified crates/radicle-protocol/src/wire/message.rs
@@ -2,7 +2,7 @@ use std::{mem, net};

use bytes::Buf;
use bytes::BufMut;
-
use cyphernet::addr::{tor, HostName, NetAddr};
+
use cyphernet::addr::{i2p, tor, HostName, NetAddr};
use radicle::crypto::Signature;
use radicle::git::Oid;
use radicle::identity::RepoId;
@@ -80,6 +80,7 @@ pub enum AddressType {
    Ipv6 = 2,
    Dns = 3,
    Onion = 4,
+
    I2p = 5,
}

impl From<AddressType> for u8 {
@@ -95,6 +96,7 @@ impl From<&Address> for AddressType {
            HostName::Ip(net::IpAddr::V6(_)) => AddressType::Ipv6,
            HostName::Dns(_) => AddressType::Dns,
            HostName::Tor(_) => AddressType::Onion,
+
            HostName::I2p(_) => AddressType::I2p,
            _ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
        }
    }
@@ -109,6 +111,7 @@ impl TryFrom<u8> for AddressType {
            2 => Ok(AddressType::Ipv6),
            3 => Ok(AddressType::Dns),
            4 => Ok(AddressType::Onion),
+
            5 => Ok(AddressType::I2p),
            _ => Err(other),
        }
    }
@@ -360,6 +363,10 @@ impl wire::Encode for Address {
                u8::from(AddressType::Onion).encode(buf);
                addr.encode(buf);
            }
+
            HostName::I2p(addr) => {
+
                u8::from(AddressType::I2p).encode(buf);
+
                addr.encode(buf);
+
            }
            _ => {
                unimplemented!(
                    "Encoding not defined for addresses of the same type as the following: {:?}",
@@ -398,6 +405,11 @@ impl wire::Decode for Address {

                HostName::Tor(onion)
            }
+
            Ok(AddressType::I2p) => {
+
                let i2p: i2p::I2pAddr = wire::Decode::decode(buf)?;
+

+
                HostName::I2p(i2p)
+
            }
            Err(other) => return Err(wire::Invalid::AddressType { actual: other }.into()),
        };
        let port = u16::decode(buf)?;
modified crates/radicle/Cargo.toml
@@ -32,7 +32,7 @@ bytesize = { version = "2", features = ["serde"] }
chrono = { workspace = true, features = ["clock"], optional = true }
colored = { workspace = true, optional = true }
crossbeam-channel = { workspace = true }
-
cyphernet = { workspace = true, features = ["tor", "dns", "p2p-ed25519"] }
+
cyphernet = { workspace = true, features = ["i2p", "tor", "dns", "p2p-ed25519"] }
dunce = { workspace = true }
fast-glob = { version = "0.3.2" }
fastrand = { workspace = true, features = ["std"] }
@@ -74,4 +74,4 @@ qcheck-macros = { workspace = true }
radicle-cob = { workspace = true, features = ["stable-commit-ids", "test"] }
radicle-crypto = { workspace = true, features = ["test"] }
radicle-git-metadata = { workspace = true }
-
tempfile = { workspace = true }
+
tempfile = { workspace = true }

\ No newline at end of file
modified crates/radicle/src/node.rs
@@ -428,7 +428,7 @@ impl TryFrom<&sqlite::Value> for Alias {
    feature = "schemars",
    derive(schemars::JsonSchema),
    schemars(description = "\
-
    An IP address, or a DNS name, or a Tor onion name, followed by the symbol ':', \
+
    An IP address, or a DNS name, or a Tor onion name, or I2P hash, followed by the symbol ':', \
    followed by a TCP port number.\
")
)]
@@ -439,6 +439,7 @@ pub struct Address(
        regex(pattern = r"^.+:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$"),
        extend("examples" = [
            "xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
+
            "f2atcc7udeub5kh4nkljtjwyk7ikjviorufzgwnfwhkphljl3vhq.b32.i2p:8776",
            "seed.example.com:8776",
            "192.0.2.0:31337",
        ]),
@@ -484,6 +485,14 @@ impl Address {
        }
    }

+
    /// Returns `true` if the [`HostName`] is a Tor I2P address.
+
    pub fn is_i2p(&self) -> bool {
+
        match self.0.host {
+
            HostName::I2p(_) => true,
+
            _ => false,
+
        }
+
    }
+

    /// Return the port number of the [`Address`].
    pub fn port(&self) -> u16 {
        self.0.port
modified crates/radicle/src/node/address.rs
@@ -202,6 +202,7 @@ pub enum AddressType {
    Ipv6 = 2,
    Dns = 3,
    Onion = 4,
+
    I2p = 5,
}

impl From<AddressType> for u8 {
@@ -217,6 +218,7 @@ impl From<&Address> for AddressType {
            HostName::Ip(net::IpAddr::V6(_)) => AddressType::Ipv6,
            HostName::Dns(_) => AddressType::Dns,
            HostName::Tor(_) => AddressType::Onion,
+
            HostName::I2p(_) => AddressType::I2p,
            _ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
        }
    }
@@ -231,6 +233,7 @@ impl TryFrom<u8> for AddressType {
            2 => Ok(AddressType::Ipv6),
            3 => Ok(AddressType::Dns),
            4 => Ok(AddressType::Onion),
+
            5 => Ok(AddressType::I2p),
            _ => Err(other),
        }
    }
modified crates/radicle/src/node/address/store.rs
@@ -536,6 +536,7 @@ impl TryFrom<&sql::Value> for AddressType {
                "ipv6" => Ok(AddressType::Ipv6),
                "dns" => Ok(AddressType::Dns),
                "onion" => Ok(AddressType::Onion),
+
                "i2p" => Ok(AddressType::I2p),
                _ => Err(err),
            },
            _ => Err(err),
@@ -550,6 +551,7 @@ impl sql::BindableWithIndex for AddressType {
            Self::Ipv6 => "ipv6".bind(stmt, i),
            Self::Dns => "dns".bind(stmt, i),
            Self::Onion => "onion".bind(stmt, i),
+
            Self::I2p => "i2p".bind(stmt, i),
        }
    }
}
modified crates/radicle/src/node/config.rs
@@ -19,7 +19,7 @@ pub type ProtocolVersion = u8;
pub mod seeds {
    use std::{str::FromStr, sync::LazyLock};

-
    use cyphernet::addr::{tor::OnionAddrV3, HostName, NetAddr};
+
    use cyphernet::addr::{i2p::I2pAddr, tor::OnionAddrV3, HostName, NetAddr};

    use super::{ConnectAddress, NodeId, PeerAddr};

@@ -44,6 +44,10 @@ pub mod seeds {
                )
                .unwrap()
                .into(),
+
                #[allow(clippy::unwrap_used)] // Value is manually verified.
+
                I2pAddr::from_str("f2atcc7udeub5kh4nkljtjwyk7ikjviorufzgwnfwhkphljl3vhq.b32.i2p")
+
                    .unwrap()
+
                    .into(),
            ],
        )
    });
@@ -61,6 +65,10 @@ pub mod seeds {
                )
                .unwrap()
                .into(),
+
                #[allow(clippy::unwrap_used)] // Value is manually verified.
+
                I2pAddr::from_str("f2atcc7udeub5kh4nkljtjwyk7ikjviorufzgwnfwhkphljl3vhq.b32.i2p")
+
                    .unwrap()
+
                    .into(),
            ],
        )
    });
@@ -270,7 +278,7 @@ pub struct RateLimits {
    derive(schemars::JsonSchema),
    schemars(description = "\
    A node address to connect to. Format: An Ed25519 public key in multibase encoding, \
-
    followed by the symbol '@', followed by an IP address, or a DNS name, or a Tor onion \
+
    followed by the symbol '@', followed by an IP address, or a DNS name, or a Tor onion, or I2P hash \
    name, followed by the symbol ':', followed by a TCP port number.\
")
)]
@@ -282,6 +290,7 @@ pub struct ConnectAddress(
        extend("examples" = [
            "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@rosa.radicle.xyz:8776",
            "z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
+
            "z6Mkvky2mnSYCTUMKRdAUoZXBXLLKtnWEkWeYQcGjjnmobAU@f2atcc7udeub5kh4nkljtjwyk7ikjviorufzgwnfwhkphljl3vhq.b32.i2p:8776",
            "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi@seed.example.com:8776",
            "z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5@192.0.2.0:31337",
        ]),
@@ -430,6 +439,9 @@ pub struct Config {
    /// Onion address config.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub onion: Option<AddressConfig>,
+
    /// I2P address config.
+
    #[serde(default, skip_serializing_if = "Option::is_none")]
+
    pub i2p: Option<AddressConfig>,
    /// Peer-to-peer network.
    #[serde(default)]
    pub network: Network,
@@ -479,6 +491,7 @@ impl Config {
            network: Network::default(),
            proxy: None,
            onion: None,
+
            i2p: None,
            relay: Relay::default(),
            limits: Limits::default(),
            workers: Workers::default(),
modified crates/radicle/src/test/arbitrary.rs
@@ -6,7 +6,7 @@ use std::{iter, net};

use crypto::test::signer::MockSigner;
use crypto::{PublicKey, Unverified};
-
use cyphernet::addr::tor::OnionAddrV3;
+
use cyphernet::addr::{i2p::I2pAddr, tor::OnionAddrV3};
use cyphernet::EcPk;
use qcheck::Arbitrary;

@@ -296,10 +296,11 @@ impl Arbitrary for Address {
            AddressType::Onion => {
                let pk = PublicKey::arbitrary(g);
                let addr = OnionAddrV3::from(
-
                    cyphernet::ed25519::PublicKey::from_pk_compressed(**pk).unwrap(),
+
                    cyphernet::ed25519::PublicKey::from_pk_compressed((**pk).into()).unwrap(),
                );
                cyphernet::addr::HostName::Tor(addr)
            }
+
            AddressType::I2p => todo!(),
        };

        Address::from(cyphernet::addr::NetAddr {