mirror of
https://github.com/luxfi/sdk.git
synced 2026-11-02 15:28:56 +00:00
github.com/luxfi/codec is gone from this module — no import, no call, no require. Nothing here marshals or unmarshals a transaction any more. WHY IT BROKE. codec deleted its jsonrpc subpackage (1b67d6a, "delete dead subpackages") while nine files imported it. It looked dead because the PUBLISHED [email protected] still ships it, so only a workspace build — which resolves codec from the local checkout — ever saw the break. THE WIRE. A tx already carries its own ZAP encoding, so the codec was re-deriving what the tx could hand over: txs.Codec.Marshal(txs.CodecVersion, &tx.Unsigned) -> tx.Unsigned.Bytes() txs.UnsignedBytes(tx.Unsigned) txs.Codec.Marshal(txs.CodecVersion, tx) -> tx.Initialize() txs.Codec.Unmarshal(txBytes, &tx) -> txs.Parse(txBytes) codec.Unmarshal(utxoBytes, &utxo) -> utxo.ParseUTXO(...) These are the same calls the node makes in txs.Tx.Sign. AddAllUTXOs no longer takes a codec.Manager, and SortTransferableOutputs / state.Sort / SortOperations no longer get one — upstream dropped the parameter. PARALLEL IMPLEMENTATIONS, DELETED. wallet/chain/{p,x} carried a second copy of the builder and signer beside the subpackages that already held them — p/signer_visitor.go and p/signer/visitor.go were both 399 lines of the same thing, and each defect had to be fixed twice. The flat files are gone; the package keeps the facade it already had in wallet_exports.go, which re-exports the subpackage. -4787 lines. wallet/chain/c is deleted. Its whole reason to exist was a codec.Manager scaffold for C-chain atomic-tx wire that nothing runs: NewWallet was passed nil for it, and the builder was commented out. The two symbols anything used move to where they are read — Account, a balance and a nonce off an EVM client, and EVMKeychain, the geth-address half of a keychain. Wallet no longer advertises a C() that only ever returned nil. ALIGNED. XAssetID -> UTXOAssetID, matching runtime.Runtime and netrunner, which already expected it. Keychain GetEth -> GetByEVM, EthAddrs -> EVMAddrs. warp UnsignedMessage and Message collapsed into one flat Message, so a signature is an Envelope rather than a second message type. netrunner's HealthResponse is read as a field: the getter form is protobuf, behind //go:build grpc, and the default build is ZAP-native. VERIFIED on a machine with the native libraries: go build ./... exits 0 with no codec require in go.mod, and go test ./... is 23 ok. Three packages still fail to build their tests — profiles, wallet/keychain, wallet/primary — and all three fail identically on the tree before this change: Consensus, VerifyHybridSecp256k1MLDSA and isXChainNotEnabled are referenced by tests and defined nowhere. evm failed there too and now passes.
400 lines
11 KiB
Go
400 lines
11 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package signer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/crypto/hash"
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
"github.com/luxfi/database"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/keychain"
|
|
"github.com/luxfi/proto/p/stakeable"
|
|
"github.com/luxfi/proto/p/txs"
|
|
lux "github.com/luxfi/utxo"
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
"github.com/luxfi/vm/components/verify"
|
|
)
|
|
|
|
var (
|
|
_ txs.Visitor = (*visitor)(nil)
|
|
|
|
ErrUnsupportedTxType = errors.New("unsupported tx type")
|
|
ErrUnknownInputType = errors.New("unknown input type")
|
|
ErrUnknownOutputType = errors.New("unknown output type")
|
|
ErrInvalidUTXOSigIndex = errors.New("invalid UTXO signature index")
|
|
ErrUnknownAuthType = errors.New("unknown auth type")
|
|
ErrUnknownOwnerType = errors.New("unknown owner type")
|
|
ErrUnknownCredentialType = errors.New("unknown credential type")
|
|
|
|
emptySig [secp256k1.SignatureLen]byte
|
|
)
|
|
|
|
// visitor handles signing transactions for the signer
|
|
type visitor struct {
|
|
kc keychain.Keychain
|
|
backend Backend
|
|
ctx context.Context
|
|
tx *txs.Tx
|
|
}
|
|
|
|
func (*visitor) AdvanceTimeTx(*txs.AdvanceTimeTx) error {
|
|
return ErrUnsupportedTxType
|
|
}
|
|
|
|
func (*visitor) RewardValidatorTx(*txs.RewardValidatorTx) error {
|
|
return ErrUnsupportedTxType
|
|
}
|
|
|
|
func (s *visitor) AddValidatorTx(tx *txs.AddValidatorTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, false, txSigners)
|
|
}
|
|
|
|
func (s *visitor) AddChainValidatorTx(tx *txs.AddChainValidatorTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chainAuthSigners, err := s.getAuthSigners(tx.ChainValidator.Chain, tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txSigners = append(txSigners, chainAuthSigners)
|
|
return sign(s.tx, false, txSigners)
|
|
}
|
|
|
|
func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, false, txSigners)
|
|
}
|
|
|
|
func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chainAuthSigners, err := s.getAuthSigners(tx.ValidateNetworkID, tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txSigners = append(txSigners, chainAuthSigners)
|
|
return sign(s.tx, false, txSigners)
|
|
}
|
|
|
|
func (s *visitor) CreateNetworkTx(tx *txs.CreateNetworkTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, false, txSigners)
|
|
}
|
|
|
|
func (s *visitor) ImportTx(tx *txs.ImportTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txImportSigners, err := s.getSigners(tx.SourceChain, tx.ImportedInputs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txSigners = append(txSigners, txImportSigners...)
|
|
return sign(s.tx, false, txSigners)
|
|
}
|
|
|
|
func (s *visitor) ExportTx(tx *txs.ExportTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, false, txSigners)
|
|
}
|
|
|
|
// Removed in regenesis
|
|
func (s *visitor) RemoveChainValidatorTx(tx *txs.RemoveChainValidatorTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chainAuthSigners, err := s.getAuthSigners(tx.Chain, tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txSigners = append(txSigners, chainAuthSigners)
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) TransformChainTx(tx *txs.TransformChainTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chainAuthSigners, err := s.getAuthSigners(tx.Chain, tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txSigners = append(txSigners, chainAuthSigners)
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) TransferChainOwnershipTx(tx *txs.TransferChainOwnershipTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chainAuthSigners, err := s.getAuthSigners(tx.Chain, tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txSigners = append(txSigners, chainAuthSigners)
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) BaseTx(tx *txs.BaseTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, false, txSigners)
|
|
}
|
|
|
|
func (s *visitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chainAuthSigners, err := s.getAuthSigners(tx.Chain, tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txSigners = append(txSigners, chainAuthSigners)
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeightTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1ValidatorBalanceTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) error {
|
|
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
disableAuthSigners, err := s.getAuthSigners(tx.ValidationID, tx.DisableAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txSigners = append(txSigners, disableAuthSigners)
|
|
return sign(s.tx, true, txSigners)
|
|
}
|
|
|
|
func (s *visitor) getSigners(sourceChainID ids.ID, ins []*lux.TransferableInput) ([][]keychain.Signer, error) {
|
|
txSigners := make([][]keychain.Signer, len(ins))
|
|
for credIndex, transferInput := range ins {
|
|
inIntf := transferInput.In
|
|
if stakeableIn, ok := inIntf.(*stakeable.LockIn); ok {
|
|
inIntf = stakeableIn.TransferableIn
|
|
}
|
|
|
|
input, ok := inIntf.(*secp256k1fx.TransferInput)
|
|
if !ok {
|
|
return nil, ErrUnknownInputType
|
|
}
|
|
|
|
inputSigners := make([]keychain.Signer, len(input.SigIndices))
|
|
txSigners[credIndex] = inputSigners
|
|
|
|
utxoID := transferInput.InputID()
|
|
utxo, err := s.backend.GetUTXO(s.ctx, sourceChainID, utxoID)
|
|
if err == database.ErrNotFound {
|
|
// If we don't have access to the UTXO, then we can't sign this
|
|
// transaction. However, we can attempt to partially sign it.
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outIntf := utxo.Out
|
|
if stakeableOut, ok := outIntf.(*stakeable.LockOut); ok {
|
|
outIntf = stakeableOut.TransferableOut
|
|
}
|
|
|
|
out, ok := outIntf.(*secp256k1fx.TransferOutput)
|
|
if !ok {
|
|
return nil, ErrUnknownOutputType
|
|
}
|
|
|
|
for sigIndex, addrIndex := range input.SigIndices {
|
|
if addrIndex >= uint32(len(out.Addrs)) {
|
|
return nil, ErrInvalidUTXOSigIndex
|
|
}
|
|
|
|
addr := out.Addrs[addrIndex]
|
|
key, ok := s.kc.Get(addr)
|
|
if !ok {
|
|
// If we don't have access to the key, then we can't sign this
|
|
// transaction. However, we can attempt to partially sign it.
|
|
continue
|
|
}
|
|
inputSigners[sigIndex] = key
|
|
}
|
|
}
|
|
return txSigners, nil
|
|
}
|
|
|
|
func (s *visitor) getAuthSigners(ownerID ids.ID, auth verify.Verifiable) ([]keychain.Signer, error) {
|
|
input, ok := auth.(*secp256k1fx.Input)
|
|
if !ok {
|
|
return nil, ErrUnknownAuthType
|
|
}
|
|
|
|
ownerIntf, err := s.backend.GetOwner(s.ctx, ownerID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"failed to fetch owner for %q: %w",
|
|
ownerID,
|
|
err,
|
|
)
|
|
}
|
|
owner, ok := ownerIntf.(*secp256k1fx.OutputOwners)
|
|
if !ok {
|
|
return nil, ErrUnknownOwnerType
|
|
}
|
|
|
|
authSigners := make([]keychain.Signer, len(input.SigIndices))
|
|
for sigIndex, addrIndex := range input.SigIndices {
|
|
if addrIndex >= uint32(len(owner.Addrs)) {
|
|
return nil, ErrInvalidUTXOSigIndex
|
|
}
|
|
|
|
addr := owner.Addrs[addrIndex]
|
|
key, ok := s.kc.Get(addr)
|
|
if !ok {
|
|
// If we don't have access to the key, then we can't sign this
|
|
// transaction. However, we can attempt to partially sign it.
|
|
continue
|
|
}
|
|
authSigners[sigIndex] = key
|
|
}
|
|
return authSigners, nil
|
|
}
|
|
|
|
func sign(tx *txs.Tx, signHash bool, txSigners [][]keychain.Signer) error {
|
|
// The unsigned buffer IS the wire — the tx carries its own ZAP encoding,
|
|
// so there is no codec to marshal through and no version to pass. This is
|
|
// the same call the node makes in txs.Tx.Sign.
|
|
unsignedBytes := tx.Unsigned.Bytes()
|
|
unsignedHash := hash.ComputeHash256(unsignedBytes)
|
|
|
|
var err error
|
|
|
|
if expectedLen := len(txSigners); expectedLen != len(tx.Creds) {
|
|
tx.Creds = make([]verify.Verifiable, expectedLen)
|
|
}
|
|
|
|
sigCache := make(map[ids.ShortID][secp256k1.SignatureLen]byte)
|
|
for credIndex, inputSigners := range txSigners {
|
|
credIntf := tx.Creds[credIndex]
|
|
if credIntf == nil {
|
|
credIntf = &secp256k1fx.Credential{}
|
|
tx.Creds[credIndex] = credIntf
|
|
}
|
|
|
|
cred, ok := credIntf.(*secp256k1fx.Credential)
|
|
if !ok {
|
|
return ErrUnknownCredentialType
|
|
}
|
|
if expectedLen := len(inputSigners); expectedLen != len(cred.Sigs) {
|
|
cred.Sigs = make([][secp256k1.SignatureLen]byte, expectedLen)
|
|
}
|
|
|
|
for sigIndex, signer := range inputSigners {
|
|
if signer == nil {
|
|
// If we don't have access to the key, then we can't sign this
|
|
// transaction. However, we can attempt to partially sign it.
|
|
continue
|
|
}
|
|
addr := signer.Address()
|
|
if sig := cred.Sigs[sigIndex]; sig != emptySig {
|
|
// If this signature has already been populated, we can just
|
|
// copy the needed signature for the future.
|
|
sigCache[addr] = sig
|
|
continue
|
|
}
|
|
|
|
if sig, exists := sigCache[addr]; exists {
|
|
// If this key has already produced a signature, we can just
|
|
// copy the previous signature.
|
|
cred.Sigs[sigIndex] = sig
|
|
continue
|
|
}
|
|
|
|
var sig []byte
|
|
if signHash {
|
|
sig, err = signer.SignHash(unsignedHash)
|
|
} else {
|
|
sig, err = signer.Sign(unsignedBytes)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("problem signing tx: %w", err)
|
|
}
|
|
copy(cred.Sigs[sigIndex][:], sig)
|
|
sigCache[addr] = cred.Sigs[sigIndex]
|
|
}
|
|
}
|
|
|
|
// Signed bytes are unsigned ‖ credentials, and the tx ID is their hash.
|
|
// Initialize does both from the Creds just populated, so neither the
|
|
// concatenation nor the ID is restated here.
|
|
return tx.Initialize()
|
|
}
|