Ton生成多签地址与注意
用 Python 生成 TON 普通钱包地址与多签钱包地址
下面用可运行的 Python 示例演示如何“离线”生成:
普通钱包(Wallet v4R2)的地址
多签钱包(以 SafeMultisig 风格为例)的地址
并解释底层原理:在 TON 中,地址来自合约的 StateInit(code + data)的哈希,而非“公钥哈希”。
示例基于 Python 库:
tonsdk
(轻量,便于构造 Cell/BOC/地址)如你更偏好
pytoniq
或tonpy
,我可以给出等价版本
提示:真实生产需使用“真实 code.boc”。本文也提供一个“演示 code”写法帮助你理解 data 变化对地址的影响,再给出如何替换为真实代码的方式。
TON 地址的底层原理
账户地址来源:
Address = (workchain, hash(StateInit))
其中
StateInit = { code: Cell, data: Cell, library?: Cell }
。序列化为 BOC 后取根 Cell 哈希,再与工作链 id(主网一般为 0)一起编码为可读字符串(
EQ...
、UQ...
)。普通钱包 vs 多签:
普通钱包:
code = 钱包标准代码(Wallet v4)
,data = {public_key, wallet_id, seqno, ...}
。多签钱包:
code = 多签合约代码(如 SafeMultisig)
,data = {阈值k, 公钥集合keys, seqno, pending等}
。地址与 data 强绑定,哪怕只改一个 bit,地址都会变化。
环境准备
pip install tonsdk
示例一:演示 StateInit → 地址(使用“演示 code”理解 data 影响)
这个例子用一个固定的“演示 code cell”代替真实合约代码,重点展示:
如何构造
data
如何组装
StateInit
改变
data
(例如阈值)会如何改变地址
from tonsdk.boc import Cell, Builder
from tonsdk.utils import Address
def build_demo_code_cell():
# 仅用于演示:随便写入一些常数,代表“合约字节码”
b = Builder()
b.store_uint(0xDEADBEEF, 32)
return b.end_cell()
def build_multisig_like_data_cell(pubkeys, threshold, initial_seqno=0):
# 演示型多签 data:顺序写入阈值、数量、seqno、N个公钥(每个32字节)
b = Builder()
b.store_uint(threshold, 16) # 阈值 M
b.store_uint(len(pubkeys), 16) # N
b.store_uint(initial_seqno, 32) # seqno
for pk in pubkeys:
assert len(pk) == 32, "pubkey must be 32 bytes"
b.store_bytes(pk)
return b.end_cell()
def build_state_init(code_cell, data_cell):
# 最简“StateInit 容器”:将 code 和 data 作为两个引用
# 注意:正式 StateInit 有特定 TL-B 标志位;tonsdk/高层库通常已有封装。
root = Builder().end_cell()
root.refs.append(code_cell)
root.refs.append(data_cell)
return root
def to_address(stateinit_cell, workchain=0):
# 地址 = (wc, hash(StateInit根cell))
return Address((workchain, stateinit_cell.hash))
# 构造“演示 code”
code = build_demo_code_cell()
# 三个演示公钥
K1 = bytes.fromhex("11"*32)
K2 = bytes.fromhex("22"*32)
K3 = bytes.fromhex("33"*32)
# 2-of-3
data_A = build_multisig_like_data_cell([K1, K2, K3], threshold=2, initial_seqno=0)
addr_A = to_address(build_state_init(code, data_A), 0)
# 3-of-3(仅阈值不同)
data_B = build_multisig_like_data_cell([K1, K2, K3], threshold=3, initial_seqno=0)
addr_B = to_address(build_state_init(code, data_B), 0)
print("Address A (2-of-3):", addr_A.to_string(is_user_friendly=True, bounce=True))
print("Address B (3-of-3):", addr_B.to_string(is_user_friendly=True, bounce=True))
assert addr_A.to_string() != addr_B.to_string()
要点:
即使 code 一样,只要 data 有差别(阈值、公钥顺序等),地址就不同。
真实合约中 data 的布局由其 TL-B 定义决定。
示例二:生成“普通钱包(Wallet v4R2)”地址
这里给出两种做法:
A. 快速路线:使用库的现成钱包封装(推荐新手)
B. 原理路线:手动准备真实
wallet v4
code 和 data
A. 使用 tonsdk 的现成封装
from tonsdk.wallet import Wallets, WalletVersion
# 1) 准备私钥/公钥(示例:随机生成;生产请从助记词或硬件导入)
import os
secret_key = os.urandom(32) # 32 bytes
# tonsdk 钱包封装会从 secret_key 推导 ed25519 公钥
# 2) 实例化 Wallet v4R2(主流事实标准)
wallet = Wallets.from_private_key(
secret_key=secret_key,
version=WalletVersion.V4R2, # 指定 v4R2
workchain=0, # 主网 workchain = 0
)
# 3) 读取钱包地址(库内部已构造 StateInit 并计算哈希)
addr_str_bounce = wallet.address.to_string(is_user_friendly=True, bounce=True)
addr_str_nonbounce = wallet.address.to_string(is_user_friendly=True, bounce=False)
print("Wallet v4R2 Address (bounce):", addr_str_bounce)
print("Wallet v4R2 Address (non-bounce):", addr_str_nonbounce)
解释:
Wallets.from_private_key(..., V4R2)
内部会装配 v4R2 的标准code
,并按其 TL-B 布局写入data
(包含public_key
、wallet_id
、seqno
等)。然后封装计算
StateInit
哈希生成地址。这就是大多数钱包 App/SDK 采用的事实标准。
B. 原理路线:手动 code + data
如果你手上有 wallet_v4r2.code.boc
,可以手动构造 data:
常见字段(示意):
wallet_id:uint32
、seqno:uint32
、public_key:uint256
字段实际顺序与宽度需与所用代码版本严格匹配,否则地址会不一致
from tonsdk.boc import Cell, Builder
from tonsdk.utils import Address
def load_code_boc(path):
with open(path, "rb") as f:
return Cell.one_from_boc(f.read())
def build_walletv4r2_data(pubkey_bytes32, wallet_id=698983191, seqno=0):
# 注意:此布局需与你的 v4R2 代码一致,示例仅展示常见字段
b = Builder()
b.store_uint(wallet_id, 32)
b.store_uint(seqno, 32)
b.store_bytes(pubkey_bytes32) # 32 bytes
return b.end_cell()
wallet_code = load_code_boc("wallet_v4r2.code.boc") # 真实文件
pubkey = bytes.fromhex("aa"*32) # 示例公钥
data = build_walletv4r2_data(pubkey)
# 组装 StateInit 并生成地址
root = Builder().end_cell()
root.refs.append(wallet_code)
root.refs.append(data)
addr = Address((0, root.hash))
print("Wallet v4R2 Address:", addr.to_string(is_user_friendly=True, bounce=True))
提示:
如果你希望完全“按字节”验证,可以将
StateInit
序列化为 BOC,并与浏览器(tonviewer/tonscan)上显示的 state init 哈希校对。
示例三:生成“多签钱包(SafeMultisig 风格)”地址
多签没有强制唯一标准,不同仓库的 data 布局可能不同。下面提供一种常见布局思路,并告诉你如何替换为真实 code.boc
与真实 TL-B 布局。
A. 概念/演示版(顺序数组存 keys)
from tonsdk.boc import Cell, Builder
from tonsdk.utils import Address
def load_code_boc(path):
with open(path, "rb") as f:
return Cell.one_from_boc(f.read())
def build_multisig_data(keys, threshold, seqno=0):
# 演示布局:k、n、seqno、线性keys(每个32字节)
b = Builder()
b.store_uint(threshold, 16) # k
b.store_uint(len(keys), 16) # n
b.store_uint(seqno, 32) # seqno
for pk in keys:
assert len(pk) == 32
b.store_bytes(pk)
return b.end_cell()
# 用真实 SafeMultisig 的 code.boc 替换这个文件
# 你需要从所用仓库拿到编译产物,如 safe_multisig.code.boc
multisig_code = load_code_boc("safe_multisig.code.boc")
K1 = bytes.fromhex("11"*32)
K2 = bytes.fromhex("22"*32)
K3 = bytes.fromhex("33"*32)
data = build_multisig_data([K1, K2, K3], threshold=2, seqno=0)
stateinit = Builder().end_cell()
stateinit.refs.append(multisig_code)
stateinit.refs.append(data)
addr = Address((0, stateinit.hash))
print("Multisig Address (2-of-3):", addr.to_string(is_user_friendly=True, bounce=True))
说明:
真实 SafeMultisig 常用 dictionary 存 keys 或带有 bitset/索引,对字段顺序、位宽、是否 pack 到 dict 有严格定义。必须对照源码或 README。
一旦字段与 code 版本匹配,离线地址就与部署一致。
B. 使用 dictionary 存储 keys(更贴近常见实现)
多数实现会用 dict 存储 keys,以索引为 key、以 pubkey(256)
为 value。tonsdk 有 Dictionary 支持,具体 API 依版本略有不同。示意如下:
from tonsdk.boc import Builder, Cell, Dict
from tonsdk.utils import Address
def build_keys_dict(keys):
# 字典 key 宽度 16(custodian index),value 宽度 256(pubkey)
d = Dict(16, 256)
for i, pk in enumerate(keys):
d.set(i, int.from_bytes(pk, "big"))
return d
def build_multisig_data_with_dict(keys, threshold, seqno=0):
b = Builder()
b.store_uint(threshold, 16) # k
b.store_uint(len(keys), 16) # n
b.store_uint(seqno, 32) # seqno
d = build_keys_dict(keys)
b.store_dict(d) # 将 dict 写入 data
return b.end_cell()
具体
Dict
的用法与 bit 宽、是否用store_dict
还是store_ref
取决于实现。你需要以目标仓库的 TL-B 为准。
关键步骤总结
准备 code
普通钱包:选 Wallet v4R2 的标准 code(库通常内置)
多签:选择目标仓库提供的
code.boc
(如 SafeMultisig)
构造 data
严格按 TL-B 定义写字段顺序和位宽
普通钱包一般包含:
public_key
、wallet_id
、seqno
多签一般包含:
k
、keys(dict/array)
、seqno
、以及可选的 pending/配置
组装 StateInit
根 cell 引用
code
和data
(正式 StateInit 还有字段存在位;多数库有现成封装)
计算地址
addr_hash = hash(StateInit_root_cell)
Address = (workchain, addr_hash)
→to_string(is_user_friendly=True, bounce=...)
验证与实践建议
一致性校验:部署前,多方离线计算地址,核对
code
与data
的哈希,避免被替换。排序规则:多签 keys 建议按字节序排序固定化,避免不同顺序导致地址不一致。
版本匹配:Wallet v4R2、SafeMultisig 不同版本之间的 data 布局可能不兼容,务必对应源码。
工具链:生产中建议用已有封装(如 ton-core/tonsdk/tonweb)来实例化标准钱包,减少手写 data 出错风险。
一些问题
为什么普通钱包/多签地址可以“部署前确定”?
因为地址只取决于
StateInit
(code+data),你可以离线构造它并计算哈希,这是 TON 的“可预测地址”特性。更换阈值或添加密钥会改变地址吗?
会。如果这是初始 data 的改变。部署后如果合约支持“升级/变更状态”则不会改变已部署地址,但那是运行时状态变更,不是初始
StateInit
的改变。