ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Sway 智能合约中的 SHA-256 哈希:std::hash 模块的使用与底层实现解析

2026/9/12 20:53:52 拓冰建站 浏览量
Sway 智能合约中的 SHA-256 哈希:std::hash 模块的使用与底层实现解析 Sway 智能合约中的 SHA-256 哈希std::hash 模块的使用与底层实现解析【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway本篇技术指南围绕 Sway 标准库std::hash模块中的sha256函数展开讲解如何在 Sway 智能合约中导入并使用 SHA-256 对单值及复合类型tuple、struct、enum、array 等进行哈希并结合当前仓库中 hash.sw 的源码深入剖析Hasher、Hashtrait 以及「平凡哈希trivial hashing」优化路径的底层原理。读完本文你将掌握 Sway 中 sha256 的完整调用方式、多值哈希的正确姿势以及如何为自己的自定义类型实现Hashtrait 以参与哈希运算。从哪来std::hash 模块的定位Sway 标准库将哈希能力收敛在std::hash模块中。根据 Hashing 文档索引该模块对外提供两个核心函数sha256计算 SHA-2-256 哈希返回b256keccak256计算 KECCAK-256 哈希返回b256它们都是泛型函数接收一个泛型参数T返回类型固定为b25632 字节的哈希摘要。本文以sha256为主线keccak256的用法与之一致仅在最终算法上有差异对应 VM 指令s256与k256。导入 sha256sha256与Hasher、Hashtrait 等类型共同定义在 sway-lib-std/src/hash.sw 中。要使用它首先需要将模块引入当前作用域。文档示例与配套代码 docs/reference/src/code/operations/hashing/src/lib.sw 给出的导入方式是use std::hash::*;使用通配符导入可以一次性引入sha256、keccak256、Hasher以及Hashtrait便于后续直接调用。若只想引入某一个函数也可以使用精确导入例如use std::hash::sha256;。仓库中的可运行示例 examples/hashing/src/main.sw 正是采用use std::hash::*;的写法。使用 Hasher 累积数据并计算 sha256sha256的典型用法是借助Hasher类型先创建一个Hasher实例将需要参与哈希的数据逐一写入其中最后调用sha256()得到摘要。文档中给出的核心示例fn sha256_hashing(age: u64, name: str, status: bool) - b256 { let mut hasher Hasher::new(); age.hash(hasher); hasher.write_str(name); status.hash(hasher); hasher.sha256() }这个例子展示了三种写入方式的混用数值类型u64通过Hashtrait 的hash方法写入字符串str通过Hasher::write_str写入布尔类型同样走Hashtrait 的hash方法。Hasher 的底层形态从源码看Hasher本质是一个字节累积器pub struct Hasher { bytes: Bytes, }其内部封装了一个Bytes动态缓冲区所有写入操作最终都是把数据追加到这个缓冲区中。Hasher提供的方法包括方法作用new()创建空Hasherwith_capacity(capacity)以指定容量创建Hasher减少扩容开销write(ref mut self, bytes: Bytes)追加一段Bytes的内容不含长度前缀write_raw_slice(ref mut self, slice: raw_slice)追加一个raw_slice中的原始字节write_u8(ref mut self, value: u8)追加单个u8write_str(ref mut self, s: str)追加字符串的内容字节不含长度前缀write_str_arrayS(ref mut self, s: S)追加字符串数组的内容字节sha256(self) - b256对累积内容计算 SHA-256返回b256keccak256(self) - b256对累积内容计算 KECCAK-256返回b256值得注意的细节write_str和write_raw_slice只追加内容字节不会把数据的长度一并写入。也就是说Hasher中的字节序列就是哈希的输入序列本身长度信息不参与哈希计算除非通过Hashtrait 显式写入或开启experimental_new_hashing特性。sha256 的 VM 指令实现Hasher::sha256的实现直接内联了 Fuel VM 的哈希指令s256pub fn sha256(self) - b256 { let mut result_buffer b256::min(); asm( hash: result_buffer, ptr: self.bytes.ptr(), bytes: self.bytes.len(), ) { s256 hash ptr bytes; hash: b256 } }它把累积缓冲区的指针和长度交给 VM 指令一次性完成 SHA-2-256 计算并写回b256结果。同理keccak256只是把指令换成k256。这也意味着 Sway 的 sha256 是 VM 原语级操作具备确定的 gas 成本与可预测的执行性能。对多个值进行哈希包装成复合类型sha256是泛型函数一次只能接收一个类型为T的值。文档明确指出当需要对多个值进行哈希时必须把这些值包装进一个类型中例如tuple元组数组array结构体struct枚举enum这与 Sway 的「单参数哈希」设计一致任何复合类型只要实现了Hash都可以作为T整体参与哈希。使用元组包装多值哈希最直接的写法是使用元组let hash_of_three sha256((42_u64, sway, true));元组的哈希规则由std标准库中Hash for (A, B, ...)的一系列实现定义按元组元素顺序依次调用各元素的hash方法。标准库为 1 元到 8 元的元组都提供了Hash实现因此任意元数在 8 以内的元组都可以直接哈希。需要说明的是由于str、bool等类型的哈希字节表示与内存表示存在差异包含这类字段的元组会走非平凡路径逐个元素写入Hasher具体机制见下文「平凡哈希」一节。复合类型同样适用除元组外struct、enum、array等复合类型同样可以作为T。仓库中的可运行脚本 examples/hashing/src/main.sw 给出了覆盖面极广的示例包括// 各种整数 let sha_hashed_u8 sha256(u8::max()); let sha_hashed_u16 sha256(u16::max()); let sha_hashed_u32 sha256(u32::max()); let sha_hashed_u64 sha256(u64::max()); // b256、布尔 let sha_hashed_b256 sha256(VALUE_A); let sha_hashed_bool sha256(true); // 字符串 let sha_hashed_str sha256(Fastest Modular Execution Layer!); // 元组与数组 let sha_hashed_tuple sha256((true, 7)); let sha_hashed_array sha256([4, 5, 6]); // 枚举 let sha_hashed_enum sha256(Location::Earth); // 包含多种字段的复杂结构体 let sha_hashed_struct sha256(Person { /* ... */ });这证明 Sway 的 sha256 对基础类型、动态字符串、元组、数组、枚举乃至多层嵌套的复杂结构体都有开箱即用的支持。自定义类型如何参与哈希实现 Hash trait若要让自定义的struct或enum参与sha256需要为其实现Hashtrait。Hashtrait 定义在 hash.sw 中包含两个必须实现的方法pub trait Hash { fn is_hash_trivial() - bool; fn hash(self, ref mut state: Hasher); }hash定义该类型的哈希字节表示如何写入Hasher即确定「参与哈希的字节序列」is_hash_trivial声明该类型的内存表示是否与哈希字节表示逐字节一致见下文。实现示例examples/hashing/src/main.sw中的Stats结构体是一个典型实现impl Hash for Stats { fn is_hash_trivial() - bool { // Stats 由两个 u64 组成内存表示与哈希字节表示一致 true } fn hash(self, ref mut state: Hasher) { self.strength.hash(state); self.agility.hash(state); } }而包含bool、str、数组等字段的Person结构体则声明为非平凡哈希impl Hash for Person { fn is_hash_trivial() - bool { // 含 bool、str、数组内存表示与哈希字节表示不一致 false } fn hash(self, ref mut state: Hasher) { self.name.hash(state); self.age.hash(state); self.alive.hash(state); // ... 按字段顺序写入 } }注意内存布局细节源码中对is_hash_trivial的注释列出了一些极易踩坑的细节直接引用自 hash.swu16、u32在内存中以 8 字节槽位存储但哈希字节表示仅为 2 字节与 4 字节因此它们永远不是平凡哈希类型任何包含它们的聚合类型也不是结构体/元组中的bool、u8、u16、u32字段在内存中会填充到 8 字节因此包含这类字段的聚合类型不是平凡哈希的枚举的 tag 在内存中按u64存储但标准库的Hash实现按u8哈希因此枚举不是平凡哈希的集合类型Bytes、Vec、raw_slice、str、str[N]、数组及包含它们的聚合类型是否平凡哈希取决于experimental_new_hashing特性开启后集合会在内容前附加长度前缀导致哈希字节表示与内存表示不一致。核心准则is_hash_trivial返回true是一项强保证一旦误报将产生错误的哈希结果拿不准时请返回false——返回false永远安全只是放弃了优化。平凡哈希免缓冲区的 gas 优化路径sha256泛型函数同样适用于keccak256在 hash.sw 中的实现会根据T是否平凡哈希选择两条路径pub fn sha256T(val: T) - b256 where T: Hash, { const IS_TRIVIAL: bool is_hash_trivial::T(); if IS_TRIVIAL { // 直接对 val 的内存地址与内存大小执行 s256 // 无需构造中间 Hasher 缓冲区 let mut result_buffer b256::zero(); asm( hash: result_buffer, ptr: __addr_of(val), bytes: __size_of::T(), ) { s256 hash ptr bytes; hash: b256 } } else { // 非平凡路径构造 Hasher逐字段写入后计算 let capacity get_initial_capacity::T(); let mut hasher Hasher::with_capacity(capacity); val.hash(hasher); hasher.sha256() } }平凡路径当T的内存表示与哈希字节表示一致时直接对val的地址取__size_of::T()字节执行s256指令省去了构造中间缓冲区与逐字段写入的开销显著节省 gas。非平凡路径先按get_initial_capacity::T()对非动态类型精确取__size_of::T()对str数组另有处理创建带初始容量的Hasher再通过T::hash写入最后调用Hasher::sha256。验证已知摘要hash.sw 文档注释中给出了可直接验证的向量use std::hash::*; fn foo() { let result sha256(Fuel); assert_eq(result, 0xa80f942f4112036dfc2da86daf6d2ef6ede3164dd56d1000eb82fa87c992450f); }sha256(Fuel)的摘要应为0xa80f94...2450f这一向量可用于在本地forc环境验证实现正确性。运行示例仓库提供了可编译运行的完整脚本示例 examples/hashing/src/main.sw其中定义了Location、Stats、Person三个类型并分别实现了Hash随后在main()中对整数、b256、布尔、字符串、元组、数组、枚举和复杂结构体逐一计算 sha256 与 keccak256并通过log输出结果。在仓库根目录下使用forc即可构建运行该脚本cd examples/hashing forc build forc run该示例同时覆盖sha256与keccak256是与本文配套的最佳实战参考。注意其哈希输入中包含动态str与带填充字段的结构体因此会走非平凡路径正好可以对照上文的两条实现路径理解执行过程。总结sha256来自std::hash通过use std::hash::*;导入返回b256多值哈希必须把多个值包装进元组、结构体、枚举或数组等复合类型底层Hasher负责字节累积最终由 VM 指令s256完成 SHA-2-256 计算自定义类型实现Hashtraithashis_hash_trivial即可参与哈希其中is_hash_trivial决定是否走免缓冲区的 gas 优化路径误报true会产出错误摘要保守返回false更安全若需 KECCAK-256用法完全一致仅调用keccak256即可详见姊妹篇文档。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考