在区块链世界里,钱包既是资产的钥匙,也是我们与DApp打交道的入口。
最近我在做一个基于Sui的靓号钱包生成器(比如生成0x666…888这种钱包地址),过程中踩了不少坑。也正因为这些坑,我开始深入研究Sui钱包的生成机制,意外发现了不少有趣的底层细节。
平时我主要在以太坊生态中开发,因此这篇文章也尝试从以太坊钱包的角度出发,结合实际代码和开发经验,梳理一下Sui钱包的技术差异,也分享给你。
从表面看,Sui和以太坊的钱包地址都以0x开头,看起来像是同一套体系。但细心的你就会发现:
以太坊地址: 0x742d35Cc6285590D93a62a6A6C4aD39BE1ad5AB7 (40个字符)
Sui地址: 0x773ac487d4372b6dd40feae15d526d1aba7899f6d197a20476c59701cc42ada5 (64个字符)
一个是20个字节,一个是32个字节,这个差异不仅是长度问题,更是底层密码学架构的不同。
以太坊的选择:
secp256k1(与比特币同款)Keccak256BIP32/BIP44标准Sui的创新:
Ed25519(更安全)Blake2b256(性能与安全并重)SLIP-0010(专为Ed25519设计的密钥派生标准,区别于以太坊的BIP32)从技术演进角度看,Ed25519确实优于secp256k1。这种签名算法不仅速度快、数据结构紧凑,还具有更强的抗攻击能力。Polkadot、Solana等新链也都选择了它,可见其优势已被广泛认可。
如果中本聪在2011年之后(Ed25519于2011年发布)设计比特币,或许会考虑使用这个更先进的算法。
了解了Sui钱包的技术底座后,接下来我们一步步拆解Sui钱包是如何从助记词生成钱包地址的。每一节我都配有Rust示例代码,方便你验证和参考。
示例如下:
use bip39::Mnemonic;
use rand::{rngs::OsRng, RngCore};
// 生成12词助记词(也支持15、18、21、24词)
let mut entropy = [0u8; 16]; // 128位熵用于12词助记词
OsRng.fill_bytes(&mut entropy);
let mnemonic = Mnemonic::from_entropy(&entropy)?;
println!("助记词: {}", mnemonic);
// 输出示例:
// obvious expose tuition hunt battle element maze now column entry solve pair
这一步和以太坊一样,都是使用 BIP39 标准生成助记词(支持 12/15/18/21/24 个词)。它是你钱包的种子,丢了就等于丢了全部资产——那句经典的话依旧适用:Not your keys, not your coins.
这一步是Sui与以太坊分道扬镳的地方。
刚开始我想当然地以为Sui也是用HDWallet那套标准,用BIP32/44来派生私钥,结果把生成的助记词往Slush钱包里一导入,地址完全对不上!折腾了半天才发现,原来Sui用的是SLIP-0010标准,密钥派生路径为m/44'/784'/0'/0'/0',并不兼容传统的HDWallet。
我在Sui仓库中找到了相关定义,具体可参见 sui/crates/sui-keys/src/key_derive.rs,其中derive_ed25519_private_key函数就是使用SLIP-0010标准实现的。
示例如下:
use slip10_ed25519::derive_ed25519_private_key;
// 从助记词生成种子
let seed = mnemonic.to_seed("");
// Sui使用SLIP-0010进行密钥派生
// 派生路径: m/44'/784'/0'/0'/0'
let derivation_path = [
0x8000002C, // 44' (hardened)
0x80000310, // 784' (hardened, Sui币种代码)
0x80000000, // 0' (hardened, 账户索引)
0x80000000, // 0' (hardened, 变更索引)
0x80000000, // 0' (hardened, 地址索引)
];
let private_key_bytes = derive_ed25519_private_key(&seed, &derivation_path);
// 对比:以太坊的做法(错误示例,不要在Sui中使用)
// let private_key = &seed[0..32]; // 简单切片,不兼容官方钱包
SLIP-0010与BIP32最大的不同之一是:它只支持硬化派生路径,这是为了适配Ed25519的特性。
示例如下:
use ed25519_dalek::SigningKey;
// 从私钥生成Ed25519签名密钥
let signing_key = SigningKey::from_bytes(&private_key_bytes);
let public_key = signing_key.verifying_key();
println!("公钥: {}", hex::encode(public_key.as_bytes()));
Sui的地址生成思路跟比特币、以太坊类似,都是对公钥进行哈希。但具体实现上有两个不同:
示例如下:
use blake2::{Blake2b256, Digest};
// Sui地址生成算法
let scheme_byte = 0x00u8; // Ed25519算法标识字节
let input = [&[scheme_byte], public_key.as_bytes()].concat();
let hash = Blake2b256::digest(&input);
let address = &hash[0..32]; // 取前32字节
let address_hex = format!("0x{}", hex::encode(address));
println!("Sui钱包地址: {}", address_hex);
这个“标识字节”的设计很巧妙,它不仅用于区分签名算法(比如Ed25519、Secp256r1或BLS),也为未来引入多种签名机制预留了空间。
更重要的是,它还为支持多签等更复杂的签名结构打下了基础。比如在Sui中,多签被封装成一种特定的签名方案(例如 0x03表示多签)。当验证器读取到这个标识字节时,就能自动识别签名类型,并调用对应的校验逻辑,从而无需依赖智能合约,就能原生支持多签交易。
// ❌ 错误做法:直接切片种子
let wrong_private_key = &seed[0..32];
// ✅ 正确做法:使用SLIP-0010
let correct_private_key = derive_ed25519_private_key(&seed, &derivation_path);
我当时就是直接切片种子,结果生成的助记词导入Suiet钱包后,地址完全不一样!自然就用不了。
# ❌ 错误:使用BIP32(以太坊标准)
bip32 = "0.4"
# ✅ 正确:使用SLIP-0010(Sui标准)
slip10_ed25519 = "0.1"
虽然助记词格式看起来一样,但Sui背后的密钥派生逻辑与以太坊完全不同,用错库很容易导致地址不一致、交易失败。
相比以太坊使用的Keccak256,Sui选择了Blake2b256,兼顾了安全性与性能,更适合高频交易与现代加密场景。
// 以太坊方式(如果用在Sui上)
let eth_hash = keccak256(&public_key_bytes);
let eth_address = ð_hash[12..]; // 取后20字节
// Sui方式
let sui_input = [&[0x00], &public_key_bytes].concat();
let sui_hash = blake2b256(&sui_input);
let sui_address = &sui_hash[0..32]; // 取前32字节
相比之下:
20字节32字节,并加入签名算法标识这样设计虽然让Sui钱包地址更长,但安全性更高,结构也更灵活。
基于这些技术原理,我们来看完整的Rust实现:
use anyhow::Result;
use bip39::Mnemonic;
use blake2::{Blake2b, digest::{consts::U32, Digest}};
use ed25519_dalek::SigningKey;
use slip10_ed25519::derive_ed25519_private_key;
use rand::{rngs::OsRng, RngCore};
pub struct SuiWallet {
pub address: String,
pub public_key: String,
pub private_key: String,
pub mnemonic: String,
}
impl SuiWallet {
pub fn generate() -> Result<Self> {
// 生成助记词
let mut entropy = [0u8; 16]; // 128位熵用于12词助记词
OsRng.fill_bytes(&mut entropy);
let mnemonic = Mnemonic::from_entropy(&entropy)?;
let seed = mnemonic.to_seed("");
// 密钥派生(Sui官方标准)
// 派生路径: m/44'/784'/0'/0'/0'
let derivation_path = [
0x8000002C, // 44' (hardened)
0x80000310, // 784' (hardened, Sui币种代码)
0x80000000, // 0' (hardened, 账户索引)
0x80000000, // 0' (hardened, 变更索引)
0x80000000, // 0' (hardened, 地址索引)
];
let private_key_bytes = derive_ed25519_private_key(&seed, &derivation_path);
// 生成公钥
let signing_key = SigningKey::from_bytes(&private_key_bytes);
let public_key = signing_key.verifying_key();
// 计算地址 - 关键修复:使用正确的Blake2b实现
let scheme_byte = 0x00u8; // Ed25519标识
let mut hasher = Blake2b::<U32>::default();
Digest::update(&mut hasher, &[scheme_byte]);
Digest::update(&mut hasher, public_key.as_bytes());
let result = hasher.finalize();
let address = format!("0x{}", hex::encode(result));
Ok(SuiWallet {
address,
public_key: hex::encode(public_key.as_bytes()),
private_key: hex::encode(&private_key_bytes),
mnemonic: mnemonic.to_string(),
})
}
}
// 使用示例
fn main() -> Result<()> {
let wallet = SuiWallet::generate()?;
println!("新的Sui钱包已生成!");
println!("地址: {}", wallet.address);
println!("公钥: {}", wallet.public_key);
println!("私钥: {}", wallet.private_key);
println!("助记词: {}", wallet.mnemonic);
println!("请安全保管助记词!");
Ok(())
}
完整示例代码,我打包放在我的知识星球中,有需要可自取。
[dependencies]
# 助记词生成
bip39 = "2.0"
# Sui官方密钥派生标准
slip10_ed25519 = "0.1"
# Ed25519椭圆曲线密码学
ed25519-dalek = "2.0"
# Blake2b哈希算法
blake2 = "0.10"
# 十六进制编码工具
hex = "0.4"
# 错误处理
anyhow = "1.0"
# 安全内存清零
zeroize = "1.7"
# 安全随机数生成
rand = "0.8"
[dev-dependencies]
# 测试框架
tokio-test = "0.4"
# 基准测试
criterion = "0.5"
# 属性测试
proptest = "1.0"
[profile.release]
# 启用链接时优化
lto = true
# 代码生成单元优化
codegen-units = 1
# 性能优先
opt-level = 3
# 去除panic信息(生产环境)
panic = "abort"
配置好开发环境后,我们再看看钱包开发的安全实践。
私钥用完不清理,可能就一直留在内存里,有被窃听的风险。最好养成随用随清的习惯。
use zeroize::Zeroize;
// 及时清理敏感数据
let mut private_key_bytes = derive_ed25519_private_key(&seed, &path)?;
// ... 使用私钥 ...
private_key_bytes.zeroize(); // 清零内存
私钥的随机数最好交给操作系统来生成,别用那些不安全的随机源,不然真有可能被暴力破解。
use rand::rngs::OsRng;
// 使用操作系统提供的安全随机数生成器
let mnemonic = Mnemonic::generate_in_with(&mut OsRng, 12)?;
通过设置600权限,确保生成的钱包文件仅当前用户可读写,防止私钥被其他用户访问。
use std::fs::OpenOptions;
use std::os::unix::fs::OpenOptionsExt;
// 创建仅所有者可访问的文件
let file = OpenOptions::new()
.create(true)
.write(true)
.mode(0o600) // 仅所有者可读写
.open("wallet.json")?;
密钥生成对比:
50,000-80,000CPU cycles200,000-300,000CPU cycles3-4倍哈希算法对比:
3.2 GB/s吞吐量1.6 GB/s吞吐量2倍实际影响因素:
以上数据来自公开的密码学基准测试,实际性能因硬件和实现而异。
但是,Sui地址生成整体上可能稍慢一些,主要原因:
实际影响如何?
对于日常使用(创建钱包、发送交易),这点性能差异完全可以忽略。但如果你像我一样在做靓号钱包生成器,需要大量创建地址,那Ed25519的优势就会逐渐显现出来。
当然,考虑到安全性和未来扩展性的提升,这点性能代价是完全值得的。从性能看未来,Sui钱包有哪些趋势?
从技术角度看,Sui这套钱包架构确实代表了一个新方向:
我觉得,Ed25519 + SLIP-0010这个组合,很可能会成为新公链钱包的标配。
从secp256k1到Ed25519,从Keccak到Blake2b,每一个技术选择的背后,都是对安全性、性能与可扩展性的极致追求。
对我们开发者来说,知道怎么用,是第一步;理解为何如此设计,才是真正走进系统底层的开始。
如果你正在基于Sui链开发钱包、做签名方案,或者也对密码学结构充满好奇,欢迎一起交流、学习。
基于全文检索与主题相似度
说在前面 如果你正在Sui上开发DApp,并考虑在某些场景下为用户免除gas费,比如: - 交易机器人的用户跟单服务 - GameFi中的游戏状态同步和数据上报 - NFT市场的批量上架和管理操作 - DAO治理中的提案投票 - 社交应用中的点赞、评论、转发…… 那么你需要一个Gas Pool服务,
说在前面 作为一名在以太坊生态摸爬滚打多年的开发者,我最近刚完成了第一个Solana项目——一个类似pump.fun的meme发B平台的合约开发。 说实话,刚开始接触Solana时,我内心是拒绝的——又要学新语言,又要适应新的开发范式,感觉像是要重新学编程一样。 但当我看到同样的代币发行功能,在So
关注我,带你转型Web3开发,分享我从2018年入行区块链以来的开发经验(DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch) 说在前面 在DApp开发中,随机数生成是个有趣的挑战。无论是为NFT赋予独特属性,还是在去中心化游戏中实现公平的抽奖,都离