7cf87a764f
动钱代码,按"联网建/广播 + 离线签"气隙流程,离线签名独立验签防篡改: - wallet: DecodeTronAddress/TronAddressBodyHex(base58check 解码校验)、AddressFromMnemonic(离线校验用)。 - tron/sign: TxID(sha256 raw_data)、SignRawData(secp256k1 → R||S||recid 65B)、RecoverAddressBody。 - tron/abi: ABIEncodeTransferParams(transfer(address,uint256) 参数)、keccak 地址体。 - tron/tx(联网): BuildTransfer(triggersmartcontract 建未签名)、TRC20Balance、Broadcast。 - cmd/sweep: plan/build/sign/broadcast 四段;sign 对每笔独立验:①重算 txid 防篡改 ②收款人+额 ABI 内嵌 ③USDT 合约内嵌 ④派生地址==owner,任一不符拒签;助记词只经 PAY_SWEEP_MNEMONIC(离线机,不入 arg)。 - 测试:签名可恢复到正确地址、ABI 编码、地址解码 roundtrip 全绿。 - 联网上链部分标注需 Phase E 真链验证;README 补 sweep 用法 + 验签说明 + gas 提醒。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75 lines
2.4 KiB
Go
75 lines
2.4 KiB
Go
package tron
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
|
)
|
|
|
|
// TxID computes the TRON transaction id: sha256 of the raw_data bytes. The
|
|
// signature is made over this hash. Recomputing it offline from raw_data_hex
|
|
// (rather than trusting a txID handed over by the online builder) is what makes
|
|
// air-gapped signing safe — a tampered raw_data yields a different id.
|
|
func TxID(rawDataHex string) ([]byte, error) {
|
|
raw, err := hex.DecodeString(rawDataHex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tron: raw_data hex: %w", err)
|
|
}
|
|
if len(raw) == 0 {
|
|
return nil, fmt.Errorf("tron: empty raw_data")
|
|
}
|
|
h := sha256.Sum256(raw)
|
|
return h[:], nil
|
|
}
|
|
|
|
// SignRawData signs raw_data with a hex private key and returns the 65-byte TRON
|
|
// signature hex: R(32) || S(32) || recid(1, value 0/1). OFFLINE ONLY.
|
|
func SignRawData(rawDataHex, privHex string) (string, error) {
|
|
txid, err := TxID(rawDataHex)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
pb, err := hex.DecodeString(privHex)
|
|
if err != nil {
|
|
return "", fmt.Errorf("tron: privkey hex: %w", err)
|
|
}
|
|
priv, _ := btcec.PrivKeyFromBytes(pb)
|
|
|
|
// SignCompact returns 65 bytes: [header || R || S], header = 27+recid for an
|
|
// uncompressed key. TRON wants R || S || recid, so rearrange.
|
|
compact := ecdsa.SignCompact(priv, txid, false)
|
|
if len(compact) != 65 {
|
|
return "", fmt.Errorf("tron: unexpected compact signature length %d", len(compact))
|
|
}
|
|
recid := compact[0] - 27
|
|
sig := make([]byte, 0, 65)
|
|
sig = append(sig, compact[1:65]...) // R || S
|
|
sig = append(sig, recid) // recovery id 0/1
|
|
return hex.EncodeToString(sig), nil
|
|
}
|
|
|
|
// RecoverAddressBody recovers the signer's 20-byte address body from a raw_data
|
|
// hex + TRON signature hex — used by tests (and could verify a signature).
|
|
func RecoverAddressBody(rawDataHex, sigHex string) ([]byte, error) {
|
|
txid, err := TxID(rawDataHex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sig, err := hex.DecodeString(sigHex)
|
|
if err != nil || len(sig) != 65 {
|
|
return nil, fmt.Errorf("tron: signature must be 65 bytes hex")
|
|
}
|
|
// Rebuild btcec compact layout: [header=27+recid || R || S].
|
|
compact := make([]byte, 65)
|
|
compact[0] = 27 + sig[64]
|
|
copy(compact[1:], sig[:64])
|
|
pub, _, err := ecdsa.RecoverCompact(compact, txid)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tron: recover: %w", err)
|
|
}
|
|
return keccakAddressBody(pub.SerializeUncompressed()), nil
|
|
}
|