news 2026/9/23 1:14:12

Diem 网络层 Noise IK 加密握手协议全解析:从规格说明到源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Diem 网络层 Noise IK 加密握手协议全解析:从规格说明到源码实现

Diem 网络层 Noise IK 加密握手协议全解析:从规格说明到源码实现

【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem

DiemNet 节点之间的所有通信都通过 Noise 协议框架 为骨架,系统讲解 Diem 如何在三种网络(公共全节点网络 PFN、验证者全节点网络 VFN、验证者网络 VN)中落地 Noise IK 握手、后握手会话加解密以及防重放攻击设计,并结合 network/src/noise/handshake.rs、network/src/noise/stream.rs 与 crates/diem-crypto/src/noise.rs 的源码实现做纵深印证。读完本文,你将掌握 DiemNet 安全传输层的完整协议细节、常量计算方式、三种网络的认证模式差异,以及如何在源码与测试中验证这套实现。

背景:DiemNet 安全传输层概览

DiemNet(见 specifications/network/README.md)是 Diem 生态中任意两个节点之间的主要网络协议,它只描述线路上的消息结构与顺序,底层消息投递依赖 TCP 传输。所有节点间的通信都必须使用 Noise 协议进行加密和认证——这正是 specifications/network/noise.md 这份规格文档的核心内容。

Noise 层在 DiemNet 的连接生命周期中扮演"安全传输升级(Secure Transport Upgrade)"的角色。一条完整连接的建立顺序为:

server: TCP::bind [address] client: discover server_address = "/ip4/[address]/ln-noise-ik/[public_key]/ln-handshake/[version]" // TCP 握手 client: TCP::connect [address] server: TCP::accept // DiemNet Noise IK 握手 client: server_peer_id = Noise::upgrade_outbound [public_key] server: client_peer_id = Noise::upgrade_inbound // DiemNet 版本握手 client: (server_version, server_protocols) = Handshake::upgrade [version] server: (client_version, client_protocols) = Handshake::upgrade [version]

其中Noise::upgrade_outboundNoise::upgrade_inbound即本文的主角,定义于 specifications/network/noise.md#handshake,其 Rust 实现位于 network/src/noise/handshake.rs 的NoiseUpgrader结构体。

三种网络与两类认证模式

规格文档将 Diem 的网络划分为三类,分别适用不同的认证强度:

  1. 公共全节点网络(Public Full Node Network, PFN):公共全节点可以连接由验证者运营的全节点。
  2. 验证者全节点网络(Validator Full Node Network, VFN):验证者运营的全节点可以连接它们自己的验证者。
  3. 验证者网络(Validator Network, VN):验证者之间相互连接。

在认证语义上(参见 specifications/network/README.md 的 "Authentication Modes" 一节):

  • Mutual(双向认证):连接双方都在安全传输握手中认证对端,用于 VN。因为 Noise IK 握手的首条消息已加密,只有持有目标密钥对的服务器才能解密并响应,所以客户端总是能认证服务器(类似 TLS 证书固定到具体公钥);而服务器是否认证客户端取决于网络配置。
  • Server-only(仅服务器认证):只有拨号方(客户端)认证监听方(服务器),用于 VFN。规格文档特别注明:"注意,VFN 中不进行客户端认证,因为它目前是一个私有网络"。

在源码中,这两种模式被建模为HandshakeAuthMode枚举(network/src/noise/handshake.rs):

pub enum HandshakeAuthMode { /// 双向认证模式:双方使用 trusted_peers 集合互相认证,并启用防重放机制。 /// 例如 Diem 验证者网络中,验证者只允许当前验证者集合内的对端连接。 Mutual { anti_replay_timestamps: RwLock<AntiReplayTimestamps>, trusted_peers: Arc<RwLock<PeerSet>>, }, /// 半双向认证模式:拨号方认证服务器;服务器允许所有入站连接, /// 但若入站连接属于其可信集合则将其标记为 Trusted。 MaybeMutual(Arc<RwLock<PeerSet>>), }

其中MaybeMutual的"服务器允许所有入站连接"语义,正好对应 VFN 私有网络中"不做客户端认证"的设计——服务器不再强制要求客户端在可信集合中,而是退化为校验客户端 peer_id 与其公钥的派生关系。构造入口HandshakeAuthMode::mutual(trusted_peers)maybe_mutual(...)server_only()提供了三种便捷创建方式。

NetworkAddress 中的 Noise 协议预协商

Noise 协议通过节点通告或配置的NetworkAddress实现"预协商"。规范的 DiemNet 地址在基础传输协议之后包含如下Protocol

human-readable format: "/ln-noise-ik/<x25519-public-key>"

其中<x25519-public-key>是通告方在 Noise 术语中的远端静态公钥(remote static public key),以小写十六进制编码。例如 specifications/network/README.md 给出的完整示例地址:

"/ip4/10.0.0.61/tcp/6080/ln-noise-ik/080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120/ln-handshake/0"

其协议栈依次为:基础传输(/ip4/<ipaddr>/tcp/<port>等)→ 安全传输升级(/ln-noise-ik/<x25519-public-key>)→ DiemNet 握手升级(/ln-handshake/<version>)。

在 specifications/network/network-address.md 的数据结构定义中,Protocol枚举包含NoiseIK(x25519::PublicKey)变体,其人类可读格式示例为:

NoiseIK(b"080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120") => "/ln-noise-ik/080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120",

这意味着拨号方在发起连接前就已从地址中得知服务器公钥——这正是 IK 模式"预先知晓对方静态公钥"的前提。

依赖的 Noise 原语与密码套件

规格文档明确,实现依赖 Noise 规范中定义的以下函数:

  • Initialize(handshake_pattern, initiator, prologue, s, e, rs, re)
  • WriteMessage(payload, message_buffer)
  • ReadMessage(message, payload_buffer)
  • EncryptWithAd(ad, plaintext)
  • DecryptWithAd(ad, ciphertext)

密码套件组合为:

  • X25519密钥交换(RFC 7748)
  • AES-GCM认证加密算法(NIST SP 800-38D)
  • SHA-256哈希函数

这些组合直接编码进协议名常量中。在 crates/diem-crypto/src/noise.rs 中可以看到(注意:为满足 Noise 规范对协议名 32 字节定长的要求,名字后补了\0\0\0\0):

/// The only Noise handshake protocol that we implement in this file. const PROTOCOL_NAME: &[u8] = b"Noise_IK_25519_AESGCM_SHA256\0\0\0\0"; /// A noise message cannot be larger than 65535 bytes as per the specification. pub const MAX_SIZE_NOISE_MSG: usize = 65535; /// The authentication tag length of AES-GCM. pub const AES_GCM_TAGLEN: usize = 16;

同文件还提供了两个长度计算常量函数,它们是下文消息长度推导的基础:

pub const fn encrypted_len(plaintext_len: usize) -> usize { plaintext_len + AES_GCM_TAGLEN } pub const fn handshake_init_msg_len(payload_len: usize) -> usize { // e + 加密的 s + 加密的 payload let e_len = x25519::PUBLIC_KEY_SIZE; let enc_s_len = encrypted_len(x25519::PUBLIC_KEY_SIZE); let enc_payload_len = encrypted_len(payload_len); e_len + enc_s_len + enc_payload_len } pub const fn handshake_resp_msg_len(payload_len: usize) -> usize { // e + 加密的 payload let e_len = x25519::PUBLIC_KEY_SIZE; let enc_payload_len = encrypted_len(payload_len); e_len + enc_payload_len }

选择的握手模式:IK

Diem 使用 Noise 的IK 握手模式

IK: <- s ... -> e, es, s, ss <- e, ee, se

这是一个**单轮往返(one-round trip)**协议,其语义是:

  • 客户端预先知道服务器的静态公钥;
  • 客户端在握手中发送自己的静态公钥(从而实现服务器侧对客户端的认证基础)。

该模式已在 noiseexplorer 上被形式化验证。

关键常量

规格文档给出了三个对实现至关重要的常量:

常量组成说明
PROTOCOL_NAME"Noise_IK_25519_AESGCM_SHA256"与 Noise 一起使用的协议名(实现时补齐 32 字节定长)
HANDSHAKE_MSG_132 + (32 + 16) + (8 + 16)第一条握手消息大小,含公钥、加密公钥和加密的 8 字节负载
HANDSHAKE_MSG_232 + 16第二条握手消息大小,含公钥和加密的 0 字节负载

对照源码可以精确还原这些数值:X25519 公钥长度为 32 字节(x25519::PUBLIC_KEY_SIZE),AES-GCM 认证标签长度为 16 字节(AES_GCM_TAGLEN),因此:

  • HANDSHAKE_MSG_1 = e(32) + encrypted_s(32+16) + encrypted_payload(8+16) = 104字节;
  • HANDSHAKE_MSG_2 = e(32) + encrypted_payload(0+16) = 48字节。

在 network/src/noise/handshake.rs 的NoiseUpgrader中,这两个尺寸被定义为常量并与 prologue 一起参与整体消息尺寸计算:

/// The prologue is the client's peer_id and the remote's expected public key. const PROLOGUE_SIZE: usize = PeerId::LENGTH + x25519::PUBLIC_KEY_SIZE; /// The client message consist of the prologue + a noise message with a timestamp as payload. const CLIENT_MESSAGE_SIZE: usize = Self::PROLOGUE_SIZE + noise::handshake_init_msg_len(AntiReplayTimestamps::TIMESTAMP_SIZE); /// The server's message contains no payload. const SERVER_MESSAGE_SIZE: usize = noise::handshake_resp_msg_len(0);

注意:规格文档中的HANDSHAKE_MSG_1/HANDSHAKE_MSG_2仅指纯 Noise 消息部分,而实际线路上客户端还要先发送PROLOGUE_SIZE(16 字节 peer_id + 32 字节公钥 = 48 字节)的 prologue,因此线路上第一条客户端消息总长为48 + 104 = 152字节。这也是 specifications/network/README.md 中连接建立示意中Noise::upgrade_outbound [public_key]会携带公钥参数的原因。

对端状态(Peer State)

规格文档定义了对端需要维护的变量:

  • peer_id:对端的 id,16 字节值。取值规则为:
    • 在 VN 中,是对端的链上账户地址(account address);
    • 在其他网络中,当对端没有账户地址时,取对端公钥的最后 16 字节。
  • private_key:对端的 X25519 私钥,32 字节。
  • public_key:对端的 X25519 公钥,32 字节。

验证者(validator)在 VN 中还额外维护:

  • trusted_peers:peer_id 到公钥的映射,代表当前验证者集合(validator set)。
  • timestamps:peer_id 到"最近一次见到的 8 字节时间戳"的映射。该值可视为无状态且严格递增的计数器,用于防止重放攻击(详见下文"安全考虑"一节)。

源码中,trusted_peers的类型为Arc<RwLock<PeerSet>>PeerSetHashMap<PeerId, Peer>,见 network/src/noise/handshake.rs),而timestamps的实现是AntiReplayTimestamps结构体,内部为HashMap<x25519::PublicKey, u64>

/// 在双向认证网络中,客户端消息附带时间戳, /// 用于防止重放攻击——攻击者即使不知道客户端静态密钥, /// 也能重放握手消息迫使对端执行若干次 Diffie-Hellman 运算。 /// 因此,响应方总是检查时间戳是否严格递增, /// 将其视为有状态计数器。若时间戳曾出现过或未严格递增, /// 可提前中止握手并避免昂贵的 Diffie-Hellman 计算。 #[derive(Default)] pub struct AntiReplayTimestamps(HashMap<x25519::PublicKey, u64>);

握手流程详解

规格文档将握手分为客户端(upgrade_outbound)与服务端(upgrade_inbound)两条路径,并给出纯逻辑描述;源码则给出了完整的异步实现。下面逐一对照。

客户端:upgrade_outbound(remote_public_key)

规格定义的步骤:

  1. prologue以明文形式发送给服务器,内容为peer_id后跟remote_public_key
  2. 调用 Noise 的Initialize(PROTOCOL_NAME, true, prologue, private_key, null, remote_public_key, null)
  3. 构造一个 8 字节payload,内容为当前纪元 Unix 时间(毫秒精度)。
  4. 调用 Noise 的WriteMessage(payload, message_buffer)
  5. message_buffer发送给服务器。
  6. 接收大小为HANDSHAKE_MSG_2字节的server_response
  7. 调用 Noise 的ReadMessage(server_response, null),返回两个CipherState

源码实现(network/src/noise/handshake.rs 的upgrade_outbound):

pub async fn upgrade_outbound<TSocket, F>( &self, mut socket: TSocket, remote_public_key: x25519::PublicKey, time_provider: F, ) -> Result<NoiseStream<TSocket>, NoiseHandshakeError> where TSocket: AsyncRead + AsyncWrite + Debug + Unpin, F: Fn() -> [u8; AntiReplayTimestamps::TIMESTAMP_SIZE], { // buffer to hold prologue + first noise handshake message let mut client_message = [0; Self::CLIENT_MESSAGE_SIZE]; // craft prologue = self_peer_id | expected_public_key client_message[..PeerId::LENGTH].copy_from_slice(self.network_context.peer_id().as_ref()); client_message[PeerId::LENGTH..Self::PROLOGUE_SIZE] .copy_from_slice(remote_public_key.as_slice()); let (prologue_msg, client_noise_msg) = client_message.split_at_mut(Self::PROLOGUE_SIZE); // craft 8-byte payload as current timestamp (in milliseconds) let payload = time_provider(); // craft first handshake message (-> e, es, s, ss) let mut rng = rand::rngs::OsRng; let initiator_state = self .noise_config .initiate_connection(&mut rng, prologue_msg, remote_public_key, Some(&payload), client_noise_msg) .map_err(NoiseHandshakeError::BuildClientHandshakeMessageFailed)?; socket.write_all(&client_message).await?; socket.flush().await?; // receive the server's response (<- e, ee, se) let mut server_response = [0u8; Self::SERVER_MESSAGE_SIZE]; socket.read_exact(&mut server_response).await?; let (_, session) = self .noise_config .finalize_connection(initiator_state, &server_response) .map_err(NoiseHandshakeError::ClientFinalizeFailed)?; Ok(NoiseStream::new(socket, session)) }

实现细节与规格一一对应:

  • time_provider默认注入AntiReplayTimestamps::now,其实现为取duration_since_epoch().as_millis() as u64后转为 8 字节小端序——这正是规格中"当前纪元 Unix 毫秒时间戳"的来源;
  • initiate_connection对应 Noise 的Initialize+WriteMessageSome(&payload)将时间戳作为握手消息的加密负载;
  • 服务器响应读取固定SERVER_MESSAGE_SIZE字节后,finalize_connection完成ReadMessage,得到NoiseSession,最终包装为NoiseStream

服务端:upgrade_inbound()

规格定义的步骤:

  1. 接收客户端prologue,应包含 32 字节initiator_peer_id后跟 32 字节responder_expected_public_key
  2. 校验initiator_peer_id
    • VN 或 VFN:在可信对端集合中;
    • PFN:由公钥正确派生。
  3. 校验responder_expected_public_key是本机公钥。
  4. 接收大小为HANDSHAKE_MSG_1字节的client_message
  5. 调用Initialize(PROTOCOL_NAME, true, prologue, private_key, null, remote_public_key, null)
  6. 调用ReadMessage(client_message, payload_buffer)
  7. VN 中:强制initiator_public_keytrusted_peers中且对应该 peer_id;强制 payload 大于该 peer_id 已见的时间戳;存储新时间戳。
  8. PFN 与 VFN 中:强制initiator_peer_idinitiator_public_key正确派生。
  9. 调用WriteMessage(null, message_buffer),存储两个CipherState,发送响应。

源码实现要点(upgrade_inbound):

// receive the prologue + first noise handshake message socket.read_exact(&mut client_message).await?; // extract prologue (remote_peer_id | self_public_key) let (remote_peer_id, self_expected_public_key) = client_message[..Self::PROLOGUE_SIZE].split_at(PeerId::LENGTH); let remote_peer_id = PeerId::try_from(remote_peer_id)?; // reject accidental self-dials if remote_peer_id == self.network_context.peer_id() { return Err(NoiseHandshakeError::SelfDialDetected); } // verify that this is indeed our public key if self_expected_public_key != self.noise_config.public_key().as_slice() { return Err(NoiseHandshakeError::ClientExpectingDifferentPubkey(...)); } let (prologue, client_init_message) = client_message.split_at(Self::PROLOGUE_SIZE); let (remote_public_key, handshake_state, payload) = self .noise_config .parse_client_init_message(prologue, client_init_message)?;

这里有一个规格文档之外、源码补充的细节:自拨号检测(SelfDialDetected)——若收到的客户端 peer_id 恰为服务器自身 peer_id,直接拒绝,用于防范本机发现配置错误或恶意发现节点通告环回地址与本机公钥的情况。

随后按认证模式分流:

let peer_role = match &self.auth_mode { HandshakeAuthMode::Mutual { trusted_peers, .. } => { match trusted_peers.read().get(&remote_peer_id) { Some(peer) => Self::authenticate_inbound(remote_peer_short, peer, &remote_public_key), None => Err(NoiseHandshakeError::UnauthenticatedClient(remote_peer_short, remote_peer_id)), } } HandshakeAuthMode::MaybeMutual(trusted_peers) => { match trusted_peers.read().get(&remote_peer_id) { Some(peer) => Self::authenticate_inbound(remote_peer_short, peer, &remote_public_key), None => { // if not, verify that their peerid is constructed correctly from their public key let derived_remote_peer_id = diem_types::account_address::from_identity_public_key(remote_public_key); if derived_remote_peer_id != remote_peer_id { Err(NoiseHandshakeError::ClientPeerIdMismatch(...)) } else { Ok(PeerRole::Unknown) } } } } }?;

authenticate_inbound进一步校验该 peer 的公钥集合是否包含握手消息中携带的远端公钥:

fn authenticate_inbound( remote_peer_short: ShortHexStr, peer: &Peer, remote_public_key: &x25519::PublicKey, ) -> Result<PeerRole, NoiseHandshakeError> { if !peer.keys.contains(remote_public_key) { return Err(NoiseHandshakeError::UnauthenticatedClientPubkey(...)); } Ok(peer.role) }

在双向认证模式(VN)下,还会校验握手负载中的时间戳(防重放,详见下节),然后构造并发送服务器响应:

if let Some(anti_replay_timestamps) = self.auth_mode.anti_replay_timestamps() { // 校验 payload 长度必须为 8 字节 if payload.len() != AntiReplayTimestamps::TIMESTAMP_SIZE { ... } let client_timestamp = u64::from_le_bytes(client_timestamp); let mut anti_replay_timestamps = anti_replay_timestamps.write(); if anti_replay_timestamps.is_replay(remote_public_key, client_timestamp) { return Err(NoiseHandshakeError::ServerReplayDetected(...)); } anti_replay_timestamps.store_timestamp(remote_public_key, client_timestamp); } // construct the response (<- e, ee, se),服务器负载为空 let mut server_response = [0u8; Self::SERVER_MESSAGE_SIZE]; let session = self .noise_config .respond_to_client(&mut rng, handshake_state, None, &mut server_response)?; socket.write_all(&server_response).await?; Ok((NoiseStream::new(socket, session), remote_peer_id, peer_role))

规格中"PFN 与 VFN 中校验 peer_id 由公钥派生"的实现即diem_types::account_address::from_identity_public_key(remote_public_key),将 X25519 身份公钥映射为账户地址,再与客户端声明的 peer_id 比对;MaybeMutual模式下若客户端不在可信集合中,则以PeerRole::Unknown标记其连接而非拒绝——这正是 VFN 私有网络"不做客户端认证"的落地点。

后握手会话:加密与解密(Post-handshake)

握手成功后,双方各持有两个 NoiseCipherState(分别用于发送与接收方向)。规格文档定义了封装函数:

  • encrypt(message)
    1. 用第一个CipherState调用EncryptWithAd(null, message)构造ciphertext
    2. ciphertext长度作为 2 字节值发送给对端;
    3. 发送ciphertext
  • decrypt(message)
    1. 从对端接收 2 字节并解释为length
    2. 接收length字节ciphertext
    3. 调用DecryptWithAd(null, ciphertext)并返回结果。

核心设计动机是:Noise 本身是"长度不敏感"(length-unaware)的,因此必须在每个 Noise 消息前附加长度前缀。源码 network/src/noise/stream.rs 中NoiseStream的文档注释说明了这一点,并在ReadState/WriteState状态机中实现了该协议:

  • 写路径:BufferData(缓冲明文)→write_message_in_place(原地加密并追加 16 字节认证标签)→WriteFrameLen(写 u16 大端长度)→WriteEncryptedFrame(写密文帧)→Flush
  • 读路径:ReadFrameLen(读 2 字节帧长)→ReadFrame(读密文帧)→session.read_message_in_place(原地解密)→CopyDecryptedFrame(拷贝明文到用户缓冲)。

帧长的计算与 Noise 的 65535 字节上限约束相关:

// encrypted messages include a tag along with the payload. const MAX_WRITE_BUFFER_LENGTH: usize = noise::decrypted_len(noise::MAX_SIZE_NOISE_MSG);

即单帧明文最大为65535 - 16 = 65519字节。帧长为 0 被视为非预期情况(ReadState::Eof(Err(()))),读取方向遇到 EOF 则优雅结束(Ok(None)表示远端正常关闭,读到 1 字节后 EOF 则视为异常断开)。NoiseStream实现了futures::io::AsyncReadAsyncWritetrait,因此在 specifications/network/README.md 的消息协议部分,DiemNet 消息会先被序列化、切分为不超过 65519 字节的块,再逐块交给 Noise 层加密成独立帧发送;DiemNet 侧的最大帧长为 8 MiB(MAX_DIEMNET_FRAME_LEN)。测试用例u16_max_writesinterleaved_writes(network/src/noise/stream.rs)分别验证了满帧写入与交错读写场景。

安全考虑

重放攻击(Replay Attacks)

在双向认证的 VN 中,中间人观察者理论上可以重放第一条握手消息,达到两个目的:

  1. 驱逐(evict)一条合法的进行中连接;
  2. 迫使服务器执行无用的密码学运算(CPU 消耗型 DoS)。

规格文档的缓解方案:

  • 由于 Noise IK 握手模式不提供密钥确认(key confirmation),为阻止第一种攻击,必须在确认连接前等待另一条客户端消息(即继续观察客户端行为后再确认连接)。
  • 为阻止第二种攻击,在客户端第一条 Noise 消息的负载中附加计数器:重放会被"计数器未严格递增"检测出来。为避免客户端维护计数器状态,使用8 字节 Unix 时间戳;为避免连接问题阻止客户端连接,将精度设为毫秒。借助该对策,服务器在检测到重放时可以提前中止握手,把必须执行的 Diffie-Hellman 密钥交换次数从 4 次减半到 2 次。

源码中的AntiReplayTimestamps精确实现了这一设计(network/src/noise/handshake.rs):

pub const TIMESTAMP_SIZE: usize = 8; pub fn now() -> [u8; Self::TIMESTAMP_SIZE] { let now: u64 = duration_since_epoch().as_millis() as u64; now.to_le_bytes() } pub fn is_replay(&self, pubkey: x25519::PublicKey, timestamp: u64) -> bool { if let Some(last_timestamp) = self.0.get(&pubkey) { &timestamp <= last_timestamp } else { false } } pub fn store_timestamp(&mut self, pubkey: x25519::PublicKey, timestamp: u64) { self.0 .entry(pubkey) .and_modify(|last_timestamp| *last_timestamp = timestamp) .or_insert(timestamp); }

is_replay判定"时间戳 <= 上次记录值"即为重放;时间戳以小端序to_le_bytes)编码为 8 字节负载。防重放仅在Mutual模式下启用(HandshakeAuthMode::Mutual持有anti_replay_timestamps字段),源码注释解释了原因:该机制理论上可处处适用,但在非双向认证场景下需要花时间做旧时间戳的垃圾回收以避免无界内存,而在双向认证场景下可信对端集合有界且极少变化,因此不存在这些问题。

规格文档同时指出该方案的边界:

  • 若验证者崩溃,将丢失已见客户端时间戳的记录,攻击者可以按顺序重放某个客户端的所有握手;但这不能阻止攻击者阻止合法连接尝试(因为合法的更新时间戳会被接受)。
  • 在 FN(全节点网络)中不对此做防护,因为攻击者本来就可以随意构造任意数量的合法握手。

测试test_timestamp_replay(network/src/noise/handshake.rs)完整验证了四段行为:有效时间戳成功 → 过去时间戳失败 → 相同时间戳失败 → 未来时间戳成功,与规格描述完全一致。

Rekey

规格文档明确:当前实现不进行会话 Rekey,因此会话是长生命周期的,不具备前向保密与后向保密(forward/backward secrecy)。文档认为这目前不构成问题,理由有二:验证者之间不交换关键机密数据;重要消息在应用层还有额外的签名保护。

负载安全属性(Payload Security Property)

根据 Noise 规范的负载安全属性一节,IK 模式下发送方认证易受 KCI(密钥泄露伪装)攻击:如果服务器密钥被泄露,攻击者可以向该服务器冒充任何人。规格文档明确表示接受该风险(We accept the risk)。

身份隐藏(Identity Hiding)

Noise 规范的身份隐藏一节指出 IK 模式在身份隐藏方面的风险,文档同样接受,因为 Diem 网络中对端的身份并非私密信息——验证者公钥、账户地址等身份信息本身就是公开可发现的(通过链上发现协议)。

从源码测试验证协议行为

仓库中的单元测试为规格提供了直接的可验证证据,读者可以按如下方式在本地复现:

  • 运行握手相关测试(network/src/noise/handshake.rs 的#[cfg(test)] mod test):
cd /data/web/disk1/git_repo/gh_mirrors/di/diem && cargo test -p network --lib noise::handshake

其中覆盖了:双向/仅服务器认证下的成功握手(test_handshake_success_*)、自拨号拒绝(test_handshake_self_fails_*)、双向认证下未认证密钥对/未认证 peer_id 拒绝(test_handshake_unauthed_*)、仅服务器认证下 peer_id 与公钥不匹配拒绝(test_handshake_client_peerid_mismatch_fails_server_only_auth)、分片读取下的握手成功(test_handshake_fragmented_reads)、时间戳重放检测(test_timestamp_replay)。

  • 运行流加解密测试(network/src/noise/stream.rs):
cd /data/web/disk1/git_repo/gh_mirrors/di/diem && cargo test -p network --lib noise::stream

其中simple_testinterleaved_writesu16_max_writesfragmented_stream分别验证了基本读写、交错双向读写、满帧(65535 字节密文帧)写入与 TCP 分片下的读写正确性;dont_read_forever验证了对全零字节流不会无限读取(应报错返回)。

  • 密码学原语测试(crates/diem-crypto/src/noise.rs 对应的单元测试位于 crates/diem-crypto/src/unit_tests/noise_test.rs):
cd /data/web/disk1/git_repo/gh_mirrors/di/diem && cargo test -p diem-crypto --lib noise

此外,network/src/noise/mod.rs 顶部还提供了一段完整的文档示例,演示了如何用NoiseUpgraderHandshakeAuthMode::mutual与内存套接字MemorySocket端到端完成握手并双向收发消息,是理解本协议最直观的"最小可运行"范例。

总结

Diem 网络层的安全传输完全建立在 Noise IK 单轮往返握手之上,规格文档 specifications/network/noise.md 精确规定了协议名、消息尺寸、对端状态、双向握手步骤、后握手加解密框架及安全边界;源码 network/src/noise/handshake.rs 与 network/src/noise/stream.rs 则给出了完整的异步实现,并在 crates/diem-crypto/src/noise.rs 中提供了精简版Noise_IK_25519_AESGCM_SHA256原语。三者相互印证,构成了从规格到实现再到测试的完整闭环。理解这套协议,也就理解了 DiemNet 全链路安全的基础:基于 IK 的服务器认证、基于 trusted peers 集合的可选双向认证、基于毫秒时间戳的状态化防重放,以及明确声明并接受的前向保密缺失、KCI 与身份暴露风险。

【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 1:08:37

紧耦合差分对为何增大串扰?奇模偶模阻抗与高速PCB设计

简介&#xff1a;面向高速网络设计与PCB工程师的差分对信号完整性专题资料&#xff0c;系统讲解差分对这一关键拓扑。内容从基本定义入手&#xff0c;阐明差分信号与共模信号的本质区别&#xff0c;并给出奇模、偶模驱动下的阻抗特性&#xff1a;差分阻抗为奇模阻抗的两倍&…

作者头像 李华
网站建设 2026/9/23 1:07:15

银河麒麟下源码编译安装SVN服务端及svnserve配置全攻略

简介&#xff1a;面向银河麒麟操作系统的运维与开发人员&#xff0c;这份文档详实记录了在国产Linux环境下从零搭建SVN版本控制服务的完整流程。全文以实操为主线&#xff0c;涵盖Subversion及其依赖组件apr、apr-util、SQLite的源码下载、编译安装&#xff0c;环境变量配置、版…

作者头像 李华
网站建设 2026/9/22 22:24:36

华为杯研究生数学建模历年真题及优秀论文(2010-2025年)

“华为杯” 第二十三届中国研究生数学建模竞赛&#xff08;2026&#xff09;将于 9 月 23 日&#xff08;周三&#xff09;8:00 — 9 月 27 日&#xff08;周日&#xff09;12:00华为杯历年真题及优秀论文 下载⬇️链接: https://pan.baidu.com/s/1_ezm5JXyuNk3x1HkOpeNoA?pwd…

作者头像 李华
网站建设 2026/9/22 22:13:54

破解会议记录耗时困境,5 款 AI 纪要工具实测夺回时间成本

你有没有过这种经历&#xff1a;一场2小时的跨部门会议&#xff0c;大家讨论得热火朝天&#xff0c;你埋头记笔记&#xff0c;手都快写断了&#xff0c;结果会后整理时&#xff0c;发现漏掉了几个关键决策点&#xff0c;或者某个责任人的待办事项记混了。更崩溃的是&#xff0c…

作者头像 李华