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>
77 lines
2.3 KiB
Go
77 lines
2.3 KiB
Go
package tron
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/wangjia/pangolin/pay/internal/wallet"
|
|
)
|
|
|
|
const testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
|
|
// addr index 0 of the test mnemonic (see wallet golden vector).
|
|
const testAddr0 = "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH"
|
|
|
|
// TestSignRecoverRoundtrip proves the signing path is correct: signing raw_data
|
|
// with address 0's private key yields a signature that recovers to address 0.
|
|
// This is the crux of the money-moving path — if it holds, TRON will accept the
|
|
// signature as coming from the owner.
|
|
func TestSignRecoverRoundtrip(t *testing.T) {
|
|
priv, err := wallet.PrivKeyHexFromMnemonic(testMnemonic, "", 0, 0, 0)
|
|
if err != nil {
|
|
t.Fatalf("privkey: %v", err)
|
|
}
|
|
rawHex := "0a0212340a0212341234567890abcdef" // arbitrary non-empty raw_data
|
|
|
|
sig, err := SignRawData(rawHex, priv)
|
|
if err != nil {
|
|
t.Fatalf("sign: %v", err)
|
|
}
|
|
if len(sig) != 130 { // 65 bytes == 130 hex chars
|
|
t.Fatalf("signature hex len %d, want 130", len(sig))
|
|
}
|
|
|
|
body, err := RecoverAddressBody(rawHex, sig)
|
|
if err != nil {
|
|
t.Fatalf("recover: %v", err)
|
|
}
|
|
wantBody, _ := wallet.TronAddressBodyHex(testAddr0)
|
|
if hex.EncodeToString(body) != wantBody {
|
|
t.Fatalf("recovered body %x != address 0 body %s", body, wantBody)
|
|
}
|
|
}
|
|
|
|
func TestABIEncodeTransferParams(t *testing.T) {
|
|
p, err := ABIEncodeTransferParams(testAddr0, 5_000000)
|
|
if err != nil {
|
|
t.Fatalf("abi: %v", err)
|
|
}
|
|
if len(p) != 128 { // 64 bytes == 128 hex chars
|
|
t.Fatalf("param hex len %d, want 128", len(p))
|
|
}
|
|
// bytes[12:32] must equal the 20-byte address body.
|
|
wantBody, _ := wallet.TronAddressBodyHex(testAddr0)
|
|
if p[24:64] != wantBody {
|
|
t.Fatalf("recipient word %s != body %s", p[24:64], wantBody)
|
|
}
|
|
// bytes[0:12] must be zero padding.
|
|
if p[0:24] != "000000000000000000000000" {
|
|
t.Fatalf("recipient not left-padded: %s", p[0:24])
|
|
}
|
|
// amount word must decode to 5000000.
|
|
amt, ok := new(big.Int).SetString(p[64:128], 16)
|
|
if !ok || amt.Int64() != 5_000000 {
|
|
t.Fatalf("amount word decodes to %v, want 5000000", amt)
|
|
}
|
|
}
|
|
|
|
func TestABIEncodeRejectsBad(t *testing.T) {
|
|
if _, err := ABIEncodeTransferParams(testAddr0, 0); err == nil {
|
|
t.Fatal("expected error on zero amount")
|
|
}
|
|
if _, err := ABIEncodeTransferParams("garbage", 1); err == nil {
|
|
t.Fatal("expected error on bad address")
|
|
}
|
|
}
|