ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Sway 智能合约中的哈希与密码学:sha256/keccak256 与 ECDSA 签名恢复验证实战指南

Sway 智能合约中的哈希与密码学:sha256/keccak256 与 ECDSA 签名恢复验证实战指南 Sway 智能合约中的哈希与密码学sha256/keccak256 与 ECDSA 签名恢复验证实战指南【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway本篇技术指南聚焦 Sway 语言的标准库std为区块链智能合约提供的密码学能力以sha256与 EVM 兼容keccak256为核心的哈希函数族以及基于secp256k1、secp256r1、Ed25519三种非对称签名方案的签名恢复与验证 API。通过完整阅读本文你将掌握在 Fuel 链上合约中对任意自定义类型结构体、枚举、元组、数组等进行哈希的正确姿势理解Hashtrait 与is_hash_trivial优化机制并能直接复用以Signature/Message/PublicKey为核心的签名恢复与验证代码实现公钥恢复、Fuel 地址恢复、EVM 地址恢复及三类签名验证。文中所有代码均取自当前仓库的可运行示例并辅以标准库源码级解析。概览标准库提供的密码学原语Sway 标准库将常用的密码学操作封装为易于调用的 API整体分为两大块哈希Hashing提供 SHA-2 家族的sha256与 Keccak-256EVM 兼容的keccak256二者均为泛型函数可作用于任意实现了Hashtrait 的类型签名恢复与验证Signature Recovery VerificationFuel 支持 3 种非对称签名方案——Secp256k1、Secp256r1和Ed25519标准库在其上提供了公钥恢复、地址恢复与签名验证的一整套 API。对应的标准库实现位于 sway-lib-std/src/hash.sw 与 sway-lib-std/src/crypto/ 目录含signature.sw、message.sw、public_key.sw、secp256k1.sw、secp256r1.sw、ed25519.sw等模块。哈希sha256 与 keccak256泛型哈希函数对任意类型开箱即用哈希部分的官方示例位于 examples/hashing/src/main.sw它演示了sha256与keccak256两种函数对从标量到复杂聚合类型的覆盖能力script; use std::hash::*; fn main() { let zero b256::min(); // 对整数进行哈希 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 { name: John, age: 9000, alive: true, location: Location::Mars, stats: Stats { strength: 10, agility: 9 }, some_tuple: (true, 8), some_array: [17, 76], some_b256: zero, }); log(sha_hashed_u8); // ... 其余 log 输出 // keccak256 的使用方式完全一致 let keccak_hashed_u8 keccak256(u8::max()); let keccak_hashed_str keccak256(Fastest Modular Execution Layer!); let keccak_hashed_struct keccak256(Person { /* ... */ }); }示例中的Person结构体包含str、u64、bool、枚举、嵌套结构体、元组、数组与b256等八种字段类型Location是仅含 tag 的枚举Stats是含两个u64的结构体——它们都通过实现Hashtrait 获得哈希能力。这也说明了sha256/keccak256的本质它们是受Hashtrait 约束的泛型函数任何自定义类型只要实现Hash就能直接参与哈希。Hasher 与 Hash trait自定义类型哈希的基石sway-lib-std/src/hash.sw 中定义了核心类型Hasher其内部封装了一个Bytes动态缓冲提供一组写入方法write(bytes)/write_raw_slice(slice)追加字节内容注意不追加长度write_u8(value)追加单个u8write_str(s)/write_str_arrayS(s)追加字符串或字符串数组的原始字节sha256()/keccak256()对已累积的缓冲执行最终哈希。Hasher的底层是内联汇编调用 Fuel VM 的哈希指令s256 hash ptr bytesSHA-2-256与k256 hash ptr bytesKeccak-256见 hash.sw。Hashtrait 定义了两个方法hash.swpub trait Hash { fn is_hash_trivial() - bool; fn hash(self, ref mut state: Hasher); }hash决定值如何把自身写入Hasher这是唯一影响最终哈希结果的方法is_hash_trivial是优化提示声明值的内存表示与其写入Hasher的哈希字节表示是否逐字节一致。实现 Hash trait以示例为模板仍以 examples/hashing/src/main.sw 中的三种类型为例impl Hash for Location { fn is_hash_trivial() - bool { // Location 仅包含枚举 tag按 u64 哈希属于平凡哈希 true } fn hash(self, ref mut state: Hasher) { match self { Location::Earth { 0_u64.hash(state); } Location::Mars { 1_u64.hash(state); } } } } impl Hash for Stats { fn is_hash_trivial() - bool { // 两个 u64 字段、无填充平凡哈希 true } fn hash(self, ref mut state: Hasher) { self.strength.hash(state); self.agility.hash(state); } } 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); self.location.hash(state); self.stats.hash(state); self.some_tuple.hash(state); self.some_array.hash(state); self.some_b256.hash(state); } }要点归纳结构体/元组等聚合类型逐字段依次调用field.hash(state)枚举先哈希判别器tag再哈希载荷。当 tag 按u64哈希且枚举仅有 tag 时可声明为平凡哈希如Location字段的类型本身必须实现Hash标准库已为u8/u16/u32/u64/u256/b256/bool/()/str/Bytes/Vec/数组/元组/Option/Result等预实现。关于Hashtrait 更完整的规则含is_hash_trivial的安全边界与各类示例可参阅官方进阶文档 Implementing the Hash Trait。is_hash_trivial显著更省 gas 的优化路径在 hash.sw 中sha256T的实现展示了平凡哈希的优化逻辑pub fn sha256T(val: T) - b256 where T: Hash, { const IS_TRIVIAL: bool is_hash_trivial::T(); if IS_TRIVIAL { // 内存表示与哈希字节表示逐字节一致 // 直接对原始内存做哈希避免在 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 { let capacity get_initial_capacity::T(); let mut hasher Hasher::with_capacity(capacity); val.hash(hasher); hasher.sha256() } }当is_hash_trivial()返回true时编译器直接以__size_of::T()字节的原始内存调用s256/k256指令省去了构造Hasher中间缓冲的全部开销否则才走写入 Hasher → 对缓冲哈希的通用路径。文档与源码均强调返回true是一种强保证错误返回true会产生错误哈希而返回false永远安全只是放弃优化。标准库中对平凡哈希的判断非常保守从 hash.sw 可看到u8/u64/b256/u256/bool/()为平凡哈希u16/u32在内存中占用 8 字节槽位按u64存储但哈希字节表示仅 2/4 字节因此永不为平凡哈希其hash实现通过__addr_of(self).add::u8(...)精确截取低位字节含bool/u8/u16/u32字段的结构体因内存填充padding 到 8 字节而非平凡枚举 tag 在内存中按u64存储但库内Hash实现按u8哈希如 signature.sw 中Signature的0_u8/1_u8/2_u8tag故枚举默认非平凡集合类型Bytes、Vec、raw_slice、str、str[N]、数组的平凡性取决于experimental_new_hashing特性开关开启时集合内容会前置长度前缀导致哈希字节表示与内存表示不再一致。keccak256Thash.sw采用完全相同的双路径策略仅最终指令换成k256。签名恢复与验证三类非对称方案Fuel 支持 3 种非对称签名方案Secp256k1比特币/Ethereum 系主流曲线、Secp256r1NIST P-256与Ed25519高性能 EdDSA。标准库以统一枚举抽象它们signature.swpub enum Signature { Secp256k1: Secp256k1, Secp256r1: Secp256r1, Ed25519: Ed25519, }配套的核心类型Messagemessage.sw被签名消息的规范化表示内部以Bytes存储可从b256或Bytes构造通常为 32 字节的已哈希消息PublicKeypublic_key.sw非压缩形式公钥Secp256k1/Secp256r1为 64 字节Ed25519为 32 字节支持From(b256, b256)、Fromb256等构造方式。以下六种操作的完整可运行示例位于 examples/signatures/src/main.sw文档中的代码片段由ANCHOR标注原样包含自该文件。公钥恢复Public Key Recovery给定Signature与签名消息Message可恢复出签名私钥对应的PublicKey// Secp256k1 公钥恢复 let secp256k1_signature: Signature Signature::Secp256k1(Secp256k1::from(( 0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8, 0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678, ))); let signed_message Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15); // 恢复出的公钥对 let secp256k1_public_key secp256k1_signature.recover(signed_message); assert(secp256k1_public_key.is_ok()); assert( secp256k1_public_key.unwrap() PublicKey::from(( 0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c, 0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965, )), );示例文件同时给出了Secp256r1的等价恢复用例。注意recover对Ed25519不适用——从源码看Signature::recoversignature.sw对Ed25519变体会返回Err(SignatureError::InvalidOperation)。地址恢复Fuel 地址与 EVM 地址从签名与消息不仅能恢复公钥还能直接恢复 FuelAddress// Secp256k1 Fuel 地址恢复 let secp256k1_address secp256k1_signature.address(signed_message); assert(secp256k1_address.is_ok()); assert( secp256k1_address.unwrap() Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881), );EVM 地址恢复同样被支持// Secp256k1 EVM 地址恢复 let secp256k1_evm_address secp256k1_signature.evm_address(signed_message); assert(secp256k1_evm_address.is_ok()); assert( secp256k1_evm_address.unwrap() EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb), );从 secp256k1.sw 的实现可以看出两类地址的派生差异这是理解恢复语义的关键Fuel 地址Address::from(sha256(pub_key))——对恢复出的 64 字节公钥做 SHA-256EVM 地址EvmAddress::from(keccak256(pub_key))——对公钥做 Keccak-256源码注释明确说明 EVM 地址派生自 keccak256 而非 sha256。这也解释了sha256/keccak256两类哈希函数在签名体系中的具体应用场景。Secp256r1的address/evm_address用法一致且evm_address恢复对Ed25519同样返回InvalidOperation。签名验证公钥、地址、EVM 地址公钥签名验证给定Signature、PublicKey与Message验证消息确由该公钥对应的私钥签署let secp256k1_verified secp256k1_signature.verify(secp256k1_public_key, signed_message); assert(secp256k1_verified.is_ok()); // Ed25519 仅支持验证不支持恢复且消息需为已哈希消息 let ed25519_public_key PublicKey::from(0x314fa58689bbe1da2430517de2d772b384a1c1d2e9cb87e73c6afcf246045b10); let ed25519_signature Signature::Ed25519(Ed25519::from(( 0xf38cef9361894be6c6e0eddec28a663d099d7ddff17c8077a1447d7ecb4e6545, 0xf5084560039486d3462dd65a40c80a74709b2f06d450ffc5dc00345c6b2cdd00, ))); let hashed_message Message::from(sha256(b256::zero())); let ed25519_verified ed25519_signature.verify(ed25519_public_key, hashed_message); assert(ed25519_verified.is_ok());Secp256k1::verify的底层逻辑secp256k1.sw是先对公钥长度做校验len() ! 64报InvalidPublicKey再执行recover并与传入公钥比对不匹配返回InvalidSignature。地址签名验证给定Signature、Address与Message验证消息由该地址签署let secp256k1_address Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881); let secp256k1_signature Secp256k1::from(( 0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8, 0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678, )); let signed_message Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15); let secp256k1_verified secp256k1_signature.verify_address(secp256k1_address, signed_message); assert(secp256k1_verified.is_ok());注意此处示例直接使用了Secp256k1而非Signature枚举类型verify_address对Secp256r1同样可用Ed25519不适用。EVM 地址签名验证EVM 地址验证同样被支持let secp256k1_evm_address EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb); let secp256k1_signature Signature::Secp256k1(Secp256k1::from(( 0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c, 0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d, ))); let signed_message Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b81df1e4a9c4323); let secp256k1_verified secp256k1_signature.verify_evm_address(secp256k1_evm_address, signed_message); assert(secp256k1_verified.is_ok());API 能力速查表基于 signature.sw 的实现六类操作的方案支持情况总结如下操作APISecp256k1Secp256r1Ed25519公钥恢复recover(message)✅✅❌返回InvalidOperationFuel 地址恢复address(message)✅✅❌EVM 地址恢复evm_address(message)✅✅❌公钥验证verify(public_key, message)✅✅✅地址验证verify_address(address, message)✅✅❌EVM 地址验证verify_evm_address(evm_address, message)✅✅❌此外Signature还提供了as_secp256k1()/as_secp256r1()/as_ed25519()类型访问器、is_secp256k1()等类型判断以及bits() - [u8; 64]原始字节访问便于与其他模块互操作。底层原理恢复与验证如何工作从源码可以梳理出Secp256k1恢复的完整调用链。以 secp256k1.sw 的recover为例pub fn recover(self, message: Message) - ResultPublicKey, SignatureError { let public_key_buffer (b256::zero(), b256::zero()); let was_error asm( buffer: __addr_of(public_key_buffer), sig: __addr_of(self), hash: message.bytes().ptr(), ) { eck1 buffer sig hash; err }; if was_error 1 { Err(SignatureError::UnrecoverablePublicKey) } else { Ok(PublicKey::from(public_key_buffer)) } }恢复操作直接映射到 Fuel VM 的eck1指令ECC secp256k1 公钥恢复并检查$err寄存器判断是否失败失败时返回SignatureError::UnrecoverablePublicKey。Secp256r1对应ecr1指令见 secp256r1.swEd25519验证则走各自的专用指令路径。错误类型集中在 signature_error.sw包含UnrecoverablePublicKey、InvalidPublicKey、InvalidSignature、InvalidOperation等变体供调用方选择错误处理策略。地址派生链路也值得强调recover出公钥后address走sha256(pub_key)evm_address走keccak256(pub_key)而verify/verify_address/verify_evm_address本质上都是先恢复、再比对的组合操作区别仅在于比对对象公钥 / Fuel 地址 / EVM 地址。这意味着合约中只需一次性完成恢复即可同时服务于多种校验需求。实战建议与注意事项自定类型的哈希先实现Hashsha256/keccak256是T: Hash约束的泛型函数。若类型未实现HashSway 目前不支持 derive哈希将无法编译。实现模板与陷阱清单参见 Implementing the Hash Trait。is_hash_trivial宁缺毋滥拿不准时一律返回false只有确认内存表示 哈希字节表示如纯u64/b256/u256且无填充的聚合、tag 按u64哈希的纯 tag 枚举才返回true以获得直接哈希原始内存的 gas 优化。注意new_hashing特性对哈希字节表示的影响开启experimental_new_hashing后集合类型会前置长度前缀哈希结果与关闭时不同跨版本保持一致的哈希字节表示需要显式管理该开关详见源码中的cfg(experimental_new_hashing ...)分支。Message应传规范化哈希消息示例中Message均构造为 32 字节值Message::from(b256)Ed25519 验证更是直接对sha256(b256::zero())结果验证——请确保签名侧与被验证侧对同一消息的规范化方式一致。Ed25519 只验不恢复需要公钥/地址恢复的场景只能选用Secp256k1或Secp256r1。优先使用示例作为测试基线本仓库的 examples/hashing/src/main.sw 与 examples/signatures/src/main.sw 内置了大量assert断言其中包含完整的 32 字节十六进制测试向量可直接复制到自己的项目中作为正确性基线验证标准库行为与预期的哈希值、恢复结果一致。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表