Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I2P "B33" / Encrypted Base32 Address Encoding In Rust #36

Open
bonedaddy opened this issue Jan 21, 2023 · 2 comments
Open

I2P "B33" / Encrypted Base32 Address Encoding In Rust #36

bonedaddy opened this issue Jan 21, 2023 · 2 comments

Comments

@bonedaddy
Copy link

bonedaddy commented Jan 21, 2023

Overview

Hello, I'm attempting to work with encrypted leasesets using rust, and am having difficulties calculating the encrypted/blinded destination address. Any attempt at using the derived b33 addresses results in a lookup error being returned:

Corrupt b32 address (or unsupported options)

Implementation

So far this is the function I have written modified from the java implementation

pub fn b33_address(
  public_key_data: &[u8], 
  public_key_type: u8,
  require_secret: bool, 
  client_auth: bool
) -> Option<String> {
    if public_key_type != 7 && public_key_type != 11 {
      return None;
    }
    let mut data_vec = Vec::with_capacity(public_key_data.len()+3);
    data_vec.extend_from_slice(&[0, 0, 0]);
    data_vec.extend_from_slice(public_key_data);
    let summer: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_CKSUM);
    let chk_sum = summer.checksum(&data_vec[3..]);
    if require_secret {
      data_vec[0] = 0x02;
    }
    if client_auth {
      data_vec[0] |= 0x04;
    }
    data_vec[1] = (public_key_type & 0xff) as u8;
    data_vec[2] = (11 & 0xff) as u8;
    data_vec[0] ^= chk_sum as u8;
    data_vec[1] ^= (chk_sum >> 8) as u8;
    data_vec[2] ^= (chk_sum >> 16) as u8;
    Some(format!("{}.b32.i2p", BASE32_I2P.encode(&data_vec)))
}

And here is how I'm testing:

    let pub_key = "3eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbQ=".to_string();
    let pub_key = BASE64_I2P.decode(pub_key.as_bytes()).unwrap();
    let addr =b33_address(&pub_key[..],7, true, true).unwrap();
    println!("{}", addr);

Note that to generate the base64 string stored in the variable pub_key, I'm using the i2p-rs library, and running the following from

	let (pubkey, seckey) = {
		let mut sam_conn = SamConnection::connect(DEFAULT_API).unwrap();
		sam_conn
			.generate_destination(SignatureType::EdDsaSha512Ed25519)
			.unwrap()
	};
	let decoded = BASE64_I2P.decode(pubkey.as_bytes()).unwrap();
        // the output of this is stored in the `pub_key` variable
	println!("public key {}", BASE64_I2P.encode(&decoded[0..32]));

The result of the test is the following "b33" address m7nz7xpbohymusevu4lic3jmukc62ykf5r322vvfxgtwnrzirazadfnu.b32.i2p however when attempting to navigate to that address using my web browser, I receive the following error message

Corrupt b32 address (or unsupported options)

Key Material

I've included the following keys generated via the SAM api, using sig type 7 (EdDSA_SHA512_Ed25519)

Public Key:

3eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN~I24i13ln4SUng99dgTQX33scumIuRGu38WuL80hc3BQAEAAcAAA==

Secret Key:

3eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN~I24i13ln4SUng99dgTQX33scumIuRGu38WuL80hc3BQAEAAcAAMTRtED335Fs3UCsTmT65U~tnkqMzcfGprDZ6UDKCFp~LPWL73lcud9JTpSk2JIRbskxFLKziPwTi-O2rC4nsjDDxLsHowR--R1bloIjn73S7T-Wcy2FCbZgsi~O~vhKEZohlnCI46GwgNJVwRHr0AvBfe4VrZFfF-ti73alo0Jv3m-C15vReuwS15JW1DN2mFI6FqB4bru0wIqi359JprVgTSrViIdNAmLpVtaoLuCAt-iT6Uhr2F6xncw~yz1UFmqpRQqszUTk09vluEKeWZomc9v7sPUvpCjQKoahD2pP7svlxuAXIDGGrTpNg3U~7vrp1eFXt1y6FzrRI6HzgnVfbU5EIqQTI~FqdQAr9uVV5590LzJfSShF32I83oMWDQ==
@bonedaddy bonedaddy changed the title I2P "B33" Address Encoding In Rust I2P "B33" / Encrypted Base32 Address Encoding In Rust Jan 21, 2023
@bonedaddy
Copy link
Author

Did a bit more reading and it looks like the b33_address function i wrote is wrong, as the address needs to be derived from the public key, which isn't returned directly by DEST GENERATE with the SAM API.

I've rewritten the function to what I believe is now the correct implementation, however I'm still unable to derive the address associated with the published encrypted leaseset 🤔

pub fn b33_address(tun_conf: TunnelConfig, require_secret: bool, client_auth: bool) -> Option<String> {
    use crc::Crc;
    let public_key_type = match tun_conf.sam_options.signature_type {
        SignatureType::EdDsaSha512Ed25519 => 7,
        SignatureType::RedDsaSha512Ed25519 => 11,
        _ => return None,
    };
    // the private key returned from `DEST GENERATE`
    let decoded_private_key = BASE64_I2P.decode(tun_conf.secret_key.as_bytes()).ok()?;
    // the public key returned from `DEST GENERATE`
    let decoded_destination = BASE64_I2P.decode(tun_conf.public_key.as_bytes()).ok()?;
    let stripped_private_key = &decoded_private_key[decoded_destination.len()..];

    let private_key = &stripped_private_key[0..32];

    let public_key = if public_key_type == 7 {
        let sk = ed25519_dalek::SecretKey::from_bytes(private_key).ok()?;
        let pk: ed25519_dalek::PublicKey = (&sk).into();
        pk.as_bytes().to_vec()
    } else if public_key_type == 11 {
        let mut pk: [u8; 32] = [0_u8; 32];
        pk.copy_from_slice(private_key);
        let sk = x25519_dalek::StaticSecret::from(pk);
        let pk = x25519_dalek::PublicKey::from(&sk);
        pk.as_bytes().to_vec()
    } else {
        unreachable!()
    };
    let mut data_vec = Vec::with_capacity(public_key.len() + 3);
    data_vec.extend_from_slice(&[0, 0, 0]);
    data_vec.extend_from_slice(&public_key[..]);

    let summer: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_CKSUM);
    let chk_sum = summer.checksum(&data_vec[3..]);

    if require_secret {
        data_vec[0] = 0x02;
    }
    if client_auth {
        data_vec[0] |= 0x04;
    }

    data_vec[1] = (public_key_type & 0xff) as u8;
    data_vec[2] = (11 & 0xff) as u8;

    data_vec[0] ^= chk_sum as u8;
    data_vec[1] ^= (chk_sum >> 8) as u8;
    data_vec[2] ^= (chk_sum >> 16) as u8;

    Some(format!("{}.b32.i2p", BASE32_I2P.encode(&data_vec)))
}

@zzzi2p
Copy link
Contributor

zzzi2p commented Feb 18, 2023

base64 decode the full 'secret' string and save binary in privkeys.dat
run: java -jar /path/to/i2p.jar privatekeyfile privkeys.dat
compare what it gives you with your output
if it's different, check format at top of PrivateKeyFile.java to see where you went wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants