Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f758f47ba | |||
| e0274bb394 | |||
| 607d4dc743 | |||
| 25f7b7a083 |
Generated
+1
@@ -3924,6 +3924,7 @@ dependencies = [
|
|||||||
name = "nymsphinx"
|
name = "nymsphinx"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"criterion 0.3.6",
|
||||||
"crypto",
|
"crypto",
|
||||||
"mixnet-contract-common",
|
"mixnet-contract-common",
|
||||||
"nymsphinx-acknowledgements",
|
"nymsphinx-acknowledgements",
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ topology = { path = "../topology" }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||||
|
criterion = "0.3"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "benchmarks"
|
||||||
|
harness = false
|
||||||
|
|
||||||
# do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require
|
# do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require
|
||||||
# net2 via tokio-util -> tokio -> mio -> net2
|
# net2 via tokio-util -> tokio -> mio -> net2
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use criterion::{black_box, Criterion, criterion_group, criterion_main};
|
||||||
|
|
||||||
|
use crypto::asymmetric::{encryption, identity};
|
||||||
|
use crypto::asymmetric::encryption::{KeyPair, PrivateKey};
|
||||||
|
use crypto::asymmetric::identity::PublicKey;
|
||||||
|
use mixnet_contract_common::Layer;
|
||||||
|
use nymsphinx::{delays, Node, NODE_ADDRESS_LENGTH, NodeAddressBytes, NymsphinxPayloadBuilder, PAYLOAD_OVERHEAD_SIZE, SphinxPacket};
|
||||||
|
use nymsphinx::acknowledgements::AckKey;
|
||||||
|
use nymsphinx::acknowledgements::surb_ack::SurbAck;
|
||||||
|
use nymsphinx::addressing::clients::Recipient;
|
||||||
|
use nymsphinx::builder::SphinxPacketBuilder;
|
||||||
|
use nymsphinx::chunking::fragment::{Fragment, FragmentHeader, FragmentIdentifier};
|
||||||
|
use nymsphinx::cover::generate_loop_cover_packet;
|
||||||
|
use nymsphinx::crypto::keygen;
|
||||||
|
use nymsphinx::params::packet_sizes::PacketSize::{ExtendedPacket16, ExtendedPacket32, ExtendedPacket8, RegularPacket};
|
||||||
|
use nymsphinx::params::PacketSize;
|
||||||
|
use topology::{gateway, mix, MixLayer, NymTopology};
|
||||||
|
|
||||||
|
const REGULAR_PACKET_SIZE: usize = PAYLOAD_OVERHEAD_SIZE + 2 * 1024;
|
||||||
|
const EXTENDED_PACKET_SIZE_8: usize = PAYLOAD_OVERHEAD_SIZE + 8 * 1024;
|
||||||
|
const EXTENDED_PACKET_SIZE_16: usize = PAYLOAD_OVERHEAD_SIZE + 16 * 1024;
|
||||||
|
const EXTENDED_PACKET_SIZE_32: usize = PAYLOAD_OVERHEAD_SIZE + 32 * 1024;
|
||||||
|
|
||||||
|
struct BenchCase {
|
||||||
|
packet_size: PacketSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn feature_topology(sender_gateway_id: PublicKey, recipient_gateway_id: PublicKey) -> (NymTopology, KeyPair) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let gateway1 = gateway::Node {
|
||||||
|
owner: "N/A".to_string(),
|
||||||
|
stake: 1000,
|
||||||
|
location: "N/A".to_string(),
|
||||||
|
host: "1.1.1.1".parse().unwrap(),
|
||||||
|
mix_host: "1.1.1.1:1789".parse().unwrap(),
|
||||||
|
clients_port: 8888,
|
||||||
|
identity_key: sender_gateway_id,
|
||||||
|
sphinx_key: encryption::PublicKey::from_base58_string(
|
||||||
|
"C7cown6dYCLZpLiMFC1PaBmhvLvmJmLDJGeRTbPD45bX",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
version: "0.x.0".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let gateway2 = gateway::Node {
|
||||||
|
identity_key: recipient_gateway_id,
|
||||||
|
..gateway1.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let node1_enc_keys = KeyPair::new(&mut rng);
|
||||||
|
let node1 = mix::Node {
|
||||||
|
mix_id: 42,
|
||||||
|
owner: "N/A".to_string(),
|
||||||
|
host: "3.3.3.3".parse().unwrap(),
|
||||||
|
mix_host: "3.3.3.3:1789".parse().unwrap(),
|
||||||
|
identity_key: identity::PublicKey::from_base58_string(
|
||||||
|
"3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
sphinx_key: *node1_enc_keys.public_key(),
|
||||||
|
layer: Layer::One,
|
||||||
|
version: "0.x.0".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let node2 = mix::Node {
|
||||||
|
owner: "Alice".to_string(),
|
||||||
|
..node1.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let node3 = mix::Node {
|
||||||
|
owner: "Bob".to_string(),
|
||||||
|
..node1.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut mixes: HashMap<MixLayer, Vec<mix::Node>> = HashMap::new();
|
||||||
|
mixes.insert(1, vec![node1]);
|
||||||
|
mixes.insert(2, vec![node2]);
|
||||||
|
mixes.insert(3, vec![node3]);
|
||||||
|
|
||||||
|
let topology = NymTopology::new(mixes, vec![gateway1, gateway2]);
|
||||||
|
(topology, node1_enc_keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_packet_copy(packet: &SphinxPacket) -> SphinxPacket {
|
||||||
|
SphinxPacket::from_bytes(&packet.to_bytes()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_loop_packet_create(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("benchmark-sphinx");
|
||||||
|
// group.sample_size(200);
|
||||||
|
group.measurement_time(Duration::from_secs(500));
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let case = BenchCase {
|
||||||
|
packet_size: RegularPacket,
|
||||||
|
};
|
||||||
|
|
||||||
|
// create sender
|
||||||
|
let sender_client_id_pair = identity::KeyPair::new(&mut rng);
|
||||||
|
let sender_client_enc_pair = encryption::KeyPair::new(&mut rng);
|
||||||
|
let sender_gateway_id_pair = identity::KeyPair::new(&mut rng);
|
||||||
|
let packet_sender = Recipient::new(
|
||||||
|
*sender_client_id_pair.public_key(),
|
||||||
|
*sender_client_enc_pair.public_key(),
|
||||||
|
*sender_gateway_id_pair.public_key(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// build topology
|
||||||
|
let (topology, node_keypair) = feature_topology(*sender_gateway_id_pair.public_key(), *sender_gateway_id_pair.public_key());
|
||||||
|
// generate the encryption key for the ack
|
||||||
|
let ack_key = AckKey::new(&mut rng);
|
||||||
|
|
||||||
|
group.bench_function(
|
||||||
|
&format!(
|
||||||
|
"[Sphinx] create_loop_cover_packet_with_payload_size_{}",
|
||||||
|
case.packet_size.payload_size(),
|
||||||
|
),
|
||||||
|
|b| {
|
||||||
|
b.iter(|| {
|
||||||
|
generate_loop_cover_packet(
|
||||||
|
&mut rng,
|
||||||
|
&topology,
|
||||||
|
&ack_key,
|
||||||
|
&packet_sender,
|
||||||
|
Duration::from_millis(50),
|
||||||
|
Duration::from_millis(50),
|
||||||
|
case.packet_size)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// let's create the packet to later benchmark the processing
|
||||||
|
let packet = generate_loop_cover_packet(
|
||||||
|
&mut rng,
|
||||||
|
&topology,
|
||||||
|
&ack_key,
|
||||||
|
&packet_sender,
|
||||||
|
Duration::from_millis(50),
|
||||||
|
Duration::from_millis(50),
|
||||||
|
case.packet_size).unwrap();
|
||||||
|
|
||||||
|
group.bench_function(
|
||||||
|
&format!(
|
||||||
|
"[Sphinx] process_loop_cover_packet_with_payload_size_{}",
|
||||||
|
case.packet_size.payload_size(),
|
||||||
|
),
|
||||||
|
|b| {
|
||||||
|
b.iter(|| {
|
||||||
|
make_packet_copy(&packet.sphinx_packet).process(&node_keypair.private_key().into())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// let new_packet = packet.sphinx_packet.process(&node_keypair.private_key().into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_new_no_surb(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("benchmark-sphinx");
|
||||||
|
// group.sample_size(200);
|
||||||
|
group.measurement_time(Duration::from_secs(500));
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let case = BenchCase {
|
||||||
|
packet_size: ExtendedPacket8,
|
||||||
|
};
|
||||||
|
|
||||||
|
// create sender
|
||||||
|
let sender_client_id_pair = identity::KeyPair::new(&mut rng);
|
||||||
|
let sender_client_enc_pair = encryption::KeyPair::new(&mut rng);
|
||||||
|
let sender_gateway_id_pair = identity::KeyPair::new(&mut rng);
|
||||||
|
let packet_sender = Recipient::new(
|
||||||
|
*sender_client_id_pair.public_key(),
|
||||||
|
*sender_client_enc_pair.public_key(),
|
||||||
|
*sender_gateway_id_pair.public_key(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// create recipient
|
||||||
|
let recipient_client_id_pair = identity::KeyPair::new(&mut rng);
|
||||||
|
let recipient_client_enc_pair = encryption::KeyPair::new(&mut rng);
|
||||||
|
let recipient_gateway_id_pair = identity::KeyPair::new(&mut rng);
|
||||||
|
let packet_recipient = Recipient::new(
|
||||||
|
*recipient_client_id_pair.public_key(),
|
||||||
|
*recipient_client_enc_pair.public_key(),
|
||||||
|
*recipient_gateway_id_pair.public_key(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// build topology
|
||||||
|
let (topology, node_keypair) = feature_topology(*sender_gateway_id_pair.public_key(), *recipient_gateway_id_pair.public_key());
|
||||||
|
|
||||||
|
// generate pseudorandom route for the packet
|
||||||
|
let route = topology.random_route_to_gateway(
|
||||||
|
&mut rng,
|
||||||
|
3,
|
||||||
|
packet_recipient.gateway(),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// generate some payload
|
||||||
|
let mlen = 40;
|
||||||
|
let mut msg = vec![0u8; mlen];
|
||||||
|
let fragment = Fragment {
|
||||||
|
header: FragmentHeader::try_new(
|
||||||
|
12345,
|
||||||
|
u8::max_value(),
|
||||||
|
u8::max_value(),
|
||||||
|
None,
|
||||||
|
Some(1234),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
payload: msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let ack_key = AckKey::new(&mut rng);
|
||||||
|
let surb_ack = SurbAck::construct(
|
||||||
|
&mut rng,
|
||||||
|
&packet_sender,
|
||||||
|
&ack_key,
|
||||||
|
fragment.fragment_identifier().to_bytes(),
|
||||||
|
Duration::from_millis(50),
|
||||||
|
&topology,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
let packet_payload = NymsphinxPayloadBuilder::new(fragment, surb_ack)
|
||||||
|
.build_regular(&mut rng, packet_recipient.encryption_key());
|
||||||
|
|
||||||
|
let delays = delays::generate_from_average_duration(route.len(), Duration::from_millis(50));
|
||||||
|
let destination = packet_recipient.as_sphinx_destination();
|
||||||
|
|
||||||
|
group.bench_function(
|
||||||
|
&format!(
|
||||||
|
"[Sphinx] create_packet_no_reply_surbs_with_payload_size_{}",
|
||||||
|
case.packet_size.payload_size(),
|
||||||
|
),
|
||||||
|
|b| {
|
||||||
|
b.iter(|| {
|
||||||
|
SphinxPacketBuilder::new()
|
||||||
|
.with_payload_size(case.packet_size.payload_size())
|
||||||
|
.build_packet(packet_payload.clone(), &route, &destination, &delays)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// let's create the packet to later benchmark the processing
|
||||||
|
let sphinx_packet = SphinxPacketBuilder::new()
|
||||||
|
.with_payload_size(case.packet_size.payload_size())
|
||||||
|
.build_packet(packet_payload.clone(), &route, &destination, &delays)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
group.bench_function(
|
||||||
|
&format!(
|
||||||
|
"[Sphinx] process_packet_with_payload_size_{}",
|
||||||
|
case.packet_size.payload_size(),
|
||||||
|
),
|
||||||
|
|b| {
|
||||||
|
b.iter(|| {
|
||||||
|
make_packet_copy(&sphinx_packet).process(&node_keypair.private_key().into())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(sphinx, bench_loop_packet_create, bench_new_no_surb);
|
||||||
|
criterion_main!(sphinx);
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::ChunkingError;
|
|
||||||
use nymsphinx_params::{SerializedFragmentIdentifier, FRAG_ID_LEN};
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
|
use nymsphinx_params::{FRAG_ID_LEN, SerializedFragmentIdentifier};
|
||||||
|
|
||||||
|
use crate::ChunkingError;
|
||||||
|
|
||||||
// Personal reflection: In hindsight I've spent too much time on relatively too little
|
// Personal reflection: In hindsight I've spent too much time on relatively too little
|
||||||
// gain here, as even though I might have saved couple of bytes per packet, the gain
|
// gain here, as even though I might have saved couple of bytes per packet, the gain
|
||||||
// is negligible in the context of having to include SURB-ACKs and reply-SURBs in the packets.
|
// is negligible in the context of having to include SURB-ACKs and reply-SURBs in the packets.
|
||||||
@@ -110,8 +112,8 @@ impl FragmentIdentifier {
|
|||||||
/// header used to reconstruct the message after being received.
|
/// header used to reconstruct the message after being received.
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone)]
|
||||||
pub struct Fragment {
|
pub struct Fragment {
|
||||||
header: FragmentHeader,
|
pub header: FragmentHeader,
|
||||||
payload: Vec<u8>,
|
pub payload: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// manual implementation to hide detailed payload that we don't care about
|
// manual implementation to hide detailed payload that we don't care about
|
||||||
@@ -290,7 +292,7 @@ impl Fragment {
|
|||||||
/// and for the longest messages, without upper bound, there is usually also only 7 bytes
|
/// and for the longest messages, without upper bound, there is usually also only 7 bytes
|
||||||
/// of overhead apart from first and last fragments in each set that instead have 10 bytes of overhead.
|
/// of overhead apart from first and last fragments in each set that instead have 10 bytes of overhead.
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub(crate) struct FragmentHeader {
|
pub struct FragmentHeader {
|
||||||
/// ID associated with `FragmentSet` to which this particular `Fragment` belongs.
|
/// ID associated with `FragmentSet` to which this particular `Fragment` belongs.
|
||||||
/// Its value is restricted to (0, i32::max_value()].
|
/// Its value is restricted to (0, i32::max_value()].
|
||||||
/// Note that it *excludes* 0, but *includes* i32::max_value().
|
/// Note that it *excludes* 0, but *includes* i32::max_value().
|
||||||
@@ -319,7 +321,7 @@ impl FragmentHeader {
|
|||||||
/// Tries to create a new `FragmentHeader` using provided metadata. Bunch of logical
|
/// Tries to create a new `FragmentHeader` using provided metadata. Bunch of logical
|
||||||
/// checks are performed to see if the data is not self-contradictory,
|
/// checks are performed to see if the data is not self-contradictory,
|
||||||
/// for example if current_fragment > total_fragments.
|
/// for example if current_fragment > total_fragments.
|
||||||
fn try_new(
|
pub fn try_new(
|
||||||
id: i32,
|
id: i32,
|
||||||
total_fragments: u8,
|
total_fragments: u8,
|
||||||
current_fragment: u8,
|
current_fragment: u8,
|
||||||
@@ -460,9 +462,11 @@ impl FragmentHeader {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod fragment_tests {
|
mod fragment_tests {
|
||||||
use super::*;
|
use rand::{RngCore, thread_rng};
|
||||||
|
|
||||||
use nymsphinx_params::packet_sizes::PacketSize;
|
use nymsphinx_params::packet_sizes::PacketSize;
|
||||||
use rand::{thread_rng, RngCore};
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
fn max_plaintext_size() -> usize {
|
fn max_plaintext_size() -> usize {
|
||||||
PacketSize::default().plaintext_size() - PacketSize::AckPacket.size()
|
PacketSize::default().plaintext_size() - PacketSize::AckPacket.size()
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
|
|
||||||
use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
|
use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
|
||||||
use nymsphinx_params::{PacketMode, PacketSize};
|
use nymsphinx_params::{PacketMode, PacketSize};
|
||||||
use nymsphinx_types::SphinxPacket;
|
use nymsphinx_types::SphinxPacket;
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MixPacketFormattingError {
|
pub enum MixPacketFormattingError {
|
||||||
@@ -46,7 +47,7 @@ impl From<NymNodeRoutingAddressError> for MixPacketFormattingError {
|
|||||||
|
|
||||||
pub struct MixPacket {
|
pub struct MixPacket {
|
||||||
next_hop: NymNodeRoutingAddress,
|
next_hop: NymNodeRoutingAddress,
|
||||||
sphinx_packet: SphinxPacket,
|
pub sphinx_packet: SphinxPacket,
|
||||||
packet_mode: PacketMode,
|
packet_mode: PacketMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use rand::{CryptoRng, RngCore};
|
||||||
|
|
||||||
use crypto::aes::cipher::{KeyIvInit, StreamCipher};
|
use crypto::aes::cipher::{KeyIvInit, StreamCipher};
|
||||||
use crypto::asymmetric::encryption;
|
use crypto::asymmetric::encryption;
|
||||||
use crypto::shared_key::new_ephemeral_shared_key;
|
use crypto::shared_key::new_ephemeral_shared_key;
|
||||||
@@ -12,7 +14,6 @@ use nymsphinx_chunking::fragment::Fragment;
|
|||||||
use nymsphinx_params::{
|
use nymsphinx_params::{
|
||||||
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm,
|
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm,
|
||||||
};
|
};
|
||||||
use rand::{CryptoRng, RngCore};
|
|
||||||
|
|
||||||
pub struct NymsphinxPayloadBuilder {
|
pub struct NymsphinxPayloadBuilder {
|
||||||
fragment: Fragment,
|
fragment: Fragment,
|
||||||
@@ -88,6 +89,7 @@ impl NymsphinxPayloadBuilder {
|
|||||||
// the actual byte data that will be put into the sphinx packet paylaod.
|
// the actual byte data that will be put into the sphinx packet paylaod.
|
||||||
// no more transformations are going to happen to it
|
// no more transformations are going to happen to it
|
||||||
// TODO: use that fact for some better compile time assertions
|
// TODO: use that fact for some better compile time assertions
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct NymsphinxPayload(Vec<u8>);
|
pub struct NymsphinxPayload(Vec<u8>);
|
||||||
|
|
||||||
impl AsRef<[u8]> for NymsphinxPayload {
|
impl AsRef<[u8]> for NymsphinxPayload {
|
||||||
|
|||||||
Reference in New Issue
Block a user