Files
zeekayandHanzo Dev 7cc836869f port the Go staker-set and priority tables, and check the BLS proof against Go
TestStakerLess in both directions of each clause, the TestBaseStakers*
set as behaviour of State, every priority predicate against every
priority, and the credential-range table with the three shapes a remote
sender controls.

And a proof of possession Go made now verifies here. Consensus
aggregates signatures under these keys, so the domain the proof is made
under has to be the same on both sides or each accepts proofs the other
refuses. It was only ever a comment saying so; it is now a Go-made
proof in the vector file.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 02:35:18 -07:00

384 lines
11 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause-Eco
// Emits the wire vectors `tests/wire_vectors.rs` checks against.
//
// This is the other side of the claim "same wire": it builds each transaction
// and block with the Go P-Chain's own constructors and prints the bytes, so
// the Rust test compares against what Go actually writes rather than against
// what this port believes Go writes.
//
// It is not built by cargo and not part of this crate's compilation. To run
// it, drop it into a package inside a checkout of the Go node — the reference
// this port was made from — and run it:
//
// mkdir -p <go-node>/cmd/pvmvec && cp gen.go <go-node>/cmd/pvmvec/main.go
// cd <go-node> && go run ./cmd/pvmvec
//
// Each line is a name and the hex of one value's bytes. Paste a line into
// tests/wire_vectors.rs when a vector is added or a layout deliberately
// changes; a layout that changes by accident shows up as a failing test.
package main
import (
"encoding/hex"
"fmt"
"time"
"github.com/luxfi/crypto/bls/signer/localsigner"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/platformvm/block"
"github.com/luxfi/node/vms/platformvm/genesis"
"github.com/luxfi/node/vms/platformvm/stakeable"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/platformvm/security"
"github.com/luxfi/node/vms/platformvm/signer"
"github.com/luxfi/node/vms/platformvm/warp/message"
"github.com/luxfi/node/vms/platformvm/txs"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
)
func id(b byte) ids.ID {
var i ids.ID
for k := range i {
i[k] = b
}
return i
}
func short(b byte) ids.ShortID {
var s ids.ShortID
for k := range s {
s[k] = b
}
return s
}
func nodeID(b byte) ids.NodeID {
var n ids.NodeID
for k := range n {
n[k] = b
}
return n
}
func out(asset byte, amt uint64, owner byte) *lux.TransferableOutput {
return &lux.TransferableOutput{
Asset: lux.Asset{ID: id(asset)},
Out: &secp256k1fx.TransferOutput{
Amt: amt,
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Threshold: 1,
Addrs: []ids.ShortID{short(owner)},
},
},
}
}
func in(tx byte, idx uint32, amt uint64) *lux.TransferableInput {
return &lux.TransferableInput{
UTXOID: lux.UTXOID{TxID: id(tx), OutputIndex: idx},
Asset: lux.Asset{ID: id(9)},
In: &secp256k1fx.TransferInput{Amt: amt, Input: secp256k1fx.Input{SigIndices: []uint32{0}}},
}
}
func base() *lux.BaseTx {
return &lux.BaseTx{
NetworkID: 1,
BlockchainID: id(3),
Outs: []*lux.TransferableOutput{out(9, 100, 1)},
Ins: []*lux.TransferableInput{in(1, 0, 200)},
}
}
func owners(b byte) fx.Owner {
return &secp256k1fx.OutputOwners{Locktime: 0, Threshold: 1, Addrs: []ids.ShortID{short(b)}}
}
func emit(name string, b []byte, err error) {
if err != nil {
panic(fmt.Sprintf("%s: %v", name, err))
}
fmt.Printf("%s %s\n", name, hex.EncodeToString(b))
}
func main() {
bt, err := txs.NewBaseTx(base())
emit("base", bt.Bytes(), err)
rv := txs.NewRewardValidatorTx(id(7))
emit("reward", rv.Bytes(), nil)
v := txs.Validator{NodeID: nodeID(5), Start: 1000, End: 2000, Wght: 50}
stake := []*lux.TransferableOutput{out(9, 50, 2)}
av, err := txs.NewAddValidatorTx(base(), v, stake, owners(3), 20000)
emit("add_validator", av.Bytes(), err)
ad, err := txs.NewAddDelegatorTx(base(), v, stake, owners(3))
emit("add_delegator", ad.Bytes(), err)
acv, err := txs.NewAddChainValidatorTx(base(), v, id(6), &secp256k1fx.Input{SigIndices: []uint32{1}})
emit("add_chain_validator", acv.Bytes(), err)
cc, err := txs.NewCreateChainTx(base(), id(6), "a chain", id(8), []ids.ID{id(1), id(2)}, []byte("genesis bytes"), &secp256k1fx.Input{SigIndices: []uint32{0, 2}})
emit("create_chain", cc.Bytes(), err)
it, err := txs.NewImportTx(base(), id(4), []*lux.TransferableInput{in(2, 1, 300)})
emit("import", it.Bytes(), err)
et, err := txs.NewExportTx(base(), id(4), []*lux.TransferableOutput{out(9, 40, 1)})
emit("export", et.Bytes(), err)
// blocks
sb, err := block.NewStandardBlock(time.Unix(1000, 0), id(1), 5, []*txs.Tx{})
emit("block_standard_empty", sb.Bytes(), err)
cb, err := block.NewCommitBlock(time.Unix(1000, 0), id(1), 5)
emit("block_commit", cb.Bytes(), err)
ab, err := block.NewAbortBlock(time.Unix(1000, 0), id(1), 5)
emit("block_abort", ab.Bytes(), err)
rcv, err := txs.NewRemoveChainValidatorTx(base(), nodeID(5), id(6), &secp256k1fx.Input{SigIndices: []uint32{0}})
emit("remove_chain_validator", rcv.Bytes(), err)
tco, err := txs.NewTransferChainOwnershipTx(base(), id(6), &secp256k1fx.Input{SigIndices: []uint32{0}}, owners(7))
emit("transfer_chain_ownership", tco.Bytes(), err)
inc, err := txs.NewIncreaseL1ValidatorBalanceTx(base(), id(13), 999)
emit("increase_l1_balance", inc.Bytes(), err)
dis, err := txs.NewDisableL1ValidatorTx(base(), id(13), &secp256k1fx.Input{SigIndices: []uint32{0, 1}})
emit("disable_l1_validator", dis.Bytes(), err)
apd, err := txs.NewAddPermissionlessDelegatorTx(base(), v, id(6), stake, owners(3))
emit("add_permissionless_delegator", apd.Bytes(), err)
pop := &signer.ProofOfPossession{}
for k := range pop.PublicKey {
pop.PublicKey[k] = 11
}
for k := range pop.ProofOfPossession {
pop.ProofOfPossession[k] = 12
}
apv, err := txs.NewAddPermissionlessValidatorTx(base(), v, ids.Empty, pop, stake, owners(3), owners(4), 20000)
emit("add_permissionless_validator", apv.Bytes(), err)
apve, err := txs.NewAddPermissionlessValidatorTx(base(), v, id(6), &signer.Empty{}, stake, owners(3), owners(4), 20000)
emit("add_permissionless_validator_empty_signer", apve.Bytes(), err)
signedBase, err := txs.NewBaseTx(base())
if err != nil {
panic(err)
}
stx := &txs.Tx{Unsigned: signedBase, Creds: []verify.Verifiable{
&secp256k1fx.Credential{Sigs: [][65]byte{sig(1), sig(2)}},
&secp256k1fx.Credential{Sigs: [][65]byte{}},
&secp256k1fx.Credential{Sigs: [][65]byte{sig(3)}},
}}
if err := stx.Initialize(); err != nil {
panic(err)
}
emit("signed_base_three_creds", stx.Bytes(), nil)
pb, err := block.NewProposalBlock(time.Unix(1000, 0), id(1), 5, rv2(), nil)
emit("block_proposal", pb.Bytes(), err)
sbtx, err := block.NewStandardBlock(time.Unix(1000, 0), id(1), 5, []*txs.Tx{stx})
emit("block_standard_one_tx", sbtx.Bytes(), err)
tc, err := txs.NewTransformChainTx(
base(),
id(6), // chain
id(14), // asset
1_000_000,
2_000_000,
100_000,
120_000,
1_000,
500_000,
3600,
86400,
20_000,
500,
5,
800_000,
&secp256k1fx.Input{SigIndices: []uint32{0, 3}},
)
emit("transform_chain", tc.Bytes(), err)
var popBlob [96]byte
for k := range popBlob {
popBlob[k] = 15
}
rl, err := txs.NewRegisterL1ValidatorTx(base(), 777, popBlob, []byte("a warp message"))
emit("register_l1_validator", rl.Bytes(), err)
sw, err := txs.NewSetL1ValidatorWeightTx(base(), []byte("another warp message"))
emit("set_l1_validator_weight", sw.Bytes(), err)
// Two genesis validators, in node-id order, so the sorted-and-unique rule
// and both address runs are exercised.
nv := []*txs.NetworkValidator{
networkValidator(5, 100, 900, 11, 12, []ids.ShortID{short(1)}, []ids.ShortID{short(2), short(3)}),
networkValidator(6, 200, 800, 13, 14, []ids.ShortID{short(4)}, nil),
}
cn, err := txs.NewCreateNetworkTx(
base(),
ids.Empty, // parent: the primary network, so this makes an L1
owners(7),
security.Mode{RestakeParent: false, Admission: security.Open, Threshold: 1000, Manager: security.Contract},
nv,
id(8),
[]byte("manager address"),
)
emit("create_network", cn.Bytes(), err)
// The simplest network there is: it restakes its parent and runs no set of
// its own, so it carries no validators.
cn2, err := txs.NewCreateNetworkTx(
base(),
id(6),
owners(7),
security.Mode{RestakeParent: true, Admission: security.NoOwnSet},
nil,
ids.Empty,
nil,
)
emit("create_network_restaking", cn2.Bytes(), err)
cv, err := txs.NewConvertNetworkTx(
base(),
id(6), // the network being promoted
ids.Empty,
id(8),
security.Mode{RestakeParent: false, Admission: security.Gated, Manager: security.Contract},
[]byte("manager address"),
nv,
&secp256k1fx.Input{SigIndices: []uint32{1, 2}},
)
emit("convert_network", cv.Bytes(), err)
plainUTXO, err := genesisUTXO(0, 100, 0).UTXO.WireBytes()
emit("utxo_plain", plainUTXO, err)
lockedUTXO, err := genesisUTXO(1, 250, 5000).UTXO.WireBytes()
emit("utxo_locked", lockedUTXO, err)
gvdr := &txs.Tx{Unsigned: apv}
if err := gvdr.Initialize(); err != nil {
panic(err)
}
gchain := &txs.Tx{Unsigned: cc}
if err := gchain.Initialize(); err != nil {
panic(err)
}
g := &genesis.Genesis{
UTXOs: []*genesis.UTXO{genesisUTXO(0, 100, 0), genesisUTXO(1, 250, 5000)},
Validators: []*txs.Tx{gvdr},
Chains: []*txs.Tx{gchain},
Timestamp: 1000,
InitialSupply: 360_000_000_000_000,
Message: "let there be light",
}
gb, err := g.Bytes()
emit("genesis", gb, err)
// The address a key spends under, and a signature over a known hash, so
// the Rust side can prove it recovers the same address Go does.
sk, err := secp256k1.ToPrivateKey(bytes32(0x11))
if err != nil {
panic(err)
}
addr := sk.PublicKey().Address()
emit("spend_address", addr[:], nil)
sighash := bytes32(0x07)
sigBytes, err := sk.SignHash(sighash)
emit("spend_signature", sigBytes, err)
// A validator's BLS key and the proof that it holds it, from a fixed
// secret, so the Rust side can check that it verifies under the same
// domain tag Go signs under.
blsKey, err := localsigner.FromSeed(bytes32(0x21))
if err != nil {
panic(err)
}
blsPoP, err := signer.NewProofOfPossession(blsKey)
if err != nil {
panic(err)
}
emit("bls_public_key", blsPoP.PublicKey[:], nil)
emit("bls_proof_of_possession", blsPoP.ProofOfPossession[:], nil)
}
func bytes32(b byte) []byte {
out := make([]byte, 32)
for i := range out {
out[i] = b
}
return out
}
// genesisUTXO builds one allocation exactly as the P-Chain genesis holds it:
// an unspent output owned by one address, optionally locked until a time.
func genesisUTXO(index uint32, amount, lock uint64) *genesis.UTXO {
var out lux.TransferableOut = &secp256k1fx.TransferOutput{
Amt: amount,
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Threshold: 1,
Addrs: []ids.ShortID{short(1)},
},
}
if lock != 0 {
out = &stakeable.LockOut{Locktime: lock, TransferableOut: out}
}
return &genesis.UTXO{
UTXO: lux.UTXO{
UTXOID: lux.UTXOID{TxID: ids.Empty, OutputIndex: index},
Asset: lux.Asset{ID: id(9)},
Out: out,
},
Message: []byte("hello"),
}
}
func networkValidator(node byte, weight, balance uint64, pk, pop byte, remaining, deactivation []ids.ShortID) *txs.NetworkValidator {
v := &txs.NetworkValidator{
NodeID: nodeID(node).Bytes(),
Weight: weight,
Balance: balance,
RemainingBalanceOwner: message.PChainOwner{Threshold: 1, Addresses: remaining},
DeactivationOwner: message.PChainOwner{Threshold: uint32(len(deactivation)), Addresses: deactivation},
}
for k := range v.Signer.PublicKey {
v.Signer.PublicKey[k] = pk
}
for k := range v.Signer.ProofOfPossession {
v.Signer.ProofOfPossession[k] = pop
}
return v
}
func sig(b byte) [65]byte {
var s [65]byte
for k := range s {
s[k] = b
}
return s
}
func rv2() *txs.Tx {
t := &txs.Tx{Unsigned: txs.NewRewardValidatorTx(id(7))}
if err := t.Initialize(); err != nil {
panic(err)
}
return t
}