mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
358 lines
11 KiB
Go
358 lines
11 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package multisig
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm"
|
|
|
|
"github.com/luxfi/cli/v2/sdk/network"
|
|
"github.com/luxfi/node/v2/v2/utils/crypto/secp256k1"
|
|
"github.com/luxfi/node/v2/v2/vms/components/verify"
|
|
"github.com/luxfi/node/v2/v2/vms/secp256k1fx"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm/txs"
|
|
)
|
|
|
|
type TxKind int64
|
|
|
|
var ErrUndefinedTx = fmt.Errorf("tx is undefined")
|
|
|
|
const (
|
|
Undefined TxKind = iota
|
|
PChainRemoveSubnetValidatorTx
|
|
PChainAddSubnetValidatorTx
|
|
PChainCreateChainTx
|
|
PChainTransformSubnetTx
|
|
PChainAddPermissionlessValidatorTx
|
|
PChainTransferSubnetOwnershipTx
|
|
)
|
|
|
|
type Multisig struct {
|
|
PChainTx *txs.Tx
|
|
controlKeys []ids.ShortID
|
|
threshold uint32
|
|
}
|
|
|
|
func New(pChainTx *txs.Tx) *Multisig {
|
|
ms := Multisig{
|
|
PChainTx: pChainTx,
|
|
}
|
|
return &ms
|
|
}
|
|
|
|
func (ms *Multisig) String() string {
|
|
if ms.PChainTx != nil {
|
|
return ms.PChainTx.ID().String()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (ms *Multisig) Undefined() bool {
|
|
return ms.PChainTx == nil
|
|
}
|
|
|
|
func (ms *Multisig) ToBytes() ([]byte, error) {
|
|
if ms.Undefined() {
|
|
return nil, ErrUndefinedTx
|
|
}
|
|
txBytes, err := txs.Codec.Marshal(txs.CodecVersion, ms.PChainTx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("couldn't marshal signed tx: %w", err)
|
|
}
|
|
return txBytes, nil
|
|
}
|
|
|
|
func (ms *Multisig) FromBytes(txBytes []byte) error {
|
|
var tx txs.Tx
|
|
if _, err := txs.Codec.Unmarshal(txBytes, &tx); err != nil {
|
|
return fmt.Errorf("error unmarshaling signed tx: %w", err)
|
|
}
|
|
if err := tx.Initialize(txs.Codec); err != nil {
|
|
return fmt.Errorf("error initializing signed tx: %w", err)
|
|
}
|
|
ms.PChainTx = &tx
|
|
return nil
|
|
}
|
|
|
|
func (ms *Multisig) IsReadyToCommit() (bool, error) {
|
|
if ms.Undefined() {
|
|
return false, ErrUndefinedTx
|
|
}
|
|
unsignedTx := ms.PChainTx.Unsigned
|
|
switch unsignedTx.(type) {
|
|
case *txs.CreateSubnetTx:
|
|
return true, nil
|
|
default:
|
|
}
|
|
_, remainingSigners, err := ms.GetRemainingAuthSigners()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return len(remainingSigners) == 0, nil
|
|
}
|
|
|
|
// GetRemainingAuthSigners gets subnet auth addresses that have not signed a given tx
|
|
// - get the string slice of auth signers for the tx (GetAuthSigners)
|
|
// - verifies that all creds in tx.Creds, except the last one, are fully signed
|
|
// (a cred is fully signed if all the signatures in cred.Sigs are non-empty)
|
|
// - computes remaining signers by iterating the last cred in tx.Creds, associated to subnet auth signing
|
|
// - for each sig in cred.Sig: if sig is empty, then add the associated auth signer address (obtained from
|
|
// authSigners by using the index) to the remaining signers list
|
|
//
|
|
// if the tx is fully signed, returns empty slice
|
|
func (ms *Multisig) GetRemainingAuthSigners() ([]ids.ShortID, []ids.ShortID, error) {
|
|
if ms.Undefined() {
|
|
return nil, nil, ErrUndefinedTx
|
|
}
|
|
authSigners, err := ms.GetAuthSigners()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
emptySig := [secp256k1.SignatureLen]byte{}
|
|
numCreds := len(ms.PChainTx.Creds)
|
|
// we should have at least 1 cred for output owners and 1 cred for subnet auth
|
|
if numCreds < 2 {
|
|
return nil, nil, fmt.Errorf("expected tx.Creds of len 2, got %d. doesn't seem to be a multisig tx with subnet auth requirements", numCreds)
|
|
}
|
|
// signatures for output owners should be filled (all creds except last one)
|
|
for credIndex := range ms.PChainTx.Creds[:numCreds-1] {
|
|
cred, ok := ms.PChainTx.Creds[credIndex].(*secp256k1fx.Credential)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("expected cred to be of type *secp256k1fx.Credential, got %T", ms.PChainTx.Creds[credIndex])
|
|
}
|
|
for i, sig := range cred.Sigs {
|
|
if sig == emptySig {
|
|
return nil, nil, fmt.Errorf("expected funding sig %d of cred %d to be filled", i, credIndex)
|
|
}
|
|
}
|
|
}
|
|
// signatures for subnet auth (last cred)
|
|
cred, ok := ms.PChainTx.Creds[numCreds-1].(*secp256k1fx.Credential)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("expected cred to be of type *secp256k1fx.Credential, got %T", ms.PChainTx.Creds[1])
|
|
}
|
|
if len(cred.Sigs) != len(authSigners) {
|
|
return nil, nil, fmt.Errorf("expected number of cred's signatures %d to equal number of auth signers %d",
|
|
len(cred.Sigs),
|
|
len(authSigners),
|
|
)
|
|
}
|
|
remainingSigners := []ids.ShortID{}
|
|
for i, sig := range cred.Sigs {
|
|
if sig == emptySig {
|
|
remainingSigners = append(remainingSigners, authSigners[i])
|
|
}
|
|
}
|
|
return authSigners, remainingSigners, nil
|
|
}
|
|
|
|
// GetAuthSigners gets all subnet auth addresses that are required to sign a given tx
|
|
// - get subnet control keys as string slice using P-Chain API (GetOwners)
|
|
// - get subnet auth indices from the tx, field tx.UnsignedTx.SubnetAuth
|
|
// - creates the string slice of required subnet auth addresses by applying
|
|
// the indices to the control keys slice
|
|
func (ms *Multisig) GetAuthSigners() ([]ids.ShortID, error) {
|
|
if ms.Undefined() {
|
|
return nil, ErrUndefinedTx
|
|
}
|
|
controlKeys, _, err := ms.GetSubnetOwners()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
unsignedTx := ms.PChainTx.Unsigned
|
|
var subnetAuth verify.Verifiable
|
|
switch unsignedTx := unsignedTx.(type) {
|
|
case *txs.RemoveSubnetValidatorTx:
|
|
subnetAuth = unsignedTx.SubnetAuth
|
|
case *txs.AddSubnetValidatorTx:
|
|
subnetAuth = unsignedTx.SubnetAuth
|
|
case *txs.CreateChainTx:
|
|
subnetAuth = unsignedTx.SubnetAuth
|
|
case *txs.TransformSubnetTx:
|
|
subnetAuth = unsignedTx.SubnetAuth
|
|
case *txs.TransferSubnetOwnershipTx:
|
|
subnetAuth = unsignedTx.SubnetAuth
|
|
default:
|
|
return nil, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
|
|
}
|
|
subnetInput, ok := subnetAuth.(*secp256k1fx.Input)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected subnetAuth of type *secp256k1fx.Input, got %T", subnetAuth)
|
|
}
|
|
authSigners := []ids.ShortID{}
|
|
for _, sigIndex := range subnetInput.SigIndices {
|
|
if sigIndex >= uint32(len(controlKeys)) {
|
|
return nil, fmt.Errorf("signer index %d exceeds number of control keys", sigIndex)
|
|
}
|
|
authSigners = append(authSigners, controlKeys[sigIndex])
|
|
}
|
|
return authSigners, nil
|
|
}
|
|
|
|
func (*Multisig) GetSpendSigners() ([]ids.ShortID, error) {
|
|
return nil, fmt.Errorf("not implemented yet")
|
|
}
|
|
|
|
func (ms *Multisig) GetTxKind() (TxKind, error) {
|
|
if ms.Undefined() {
|
|
return Undefined, ErrUndefinedTx
|
|
}
|
|
unsignedTx := ms.PChainTx.Unsigned
|
|
switch unsignedTx := unsignedTx.(type) {
|
|
case *txs.RemoveSubnetValidatorTx:
|
|
return PChainRemoveSubnetValidatorTx, nil
|
|
case *txs.AddSubnetValidatorTx:
|
|
return PChainAddSubnetValidatorTx, nil
|
|
case *txs.CreateChainTx:
|
|
return PChainCreateChainTx, nil
|
|
case *txs.TransformSubnetTx:
|
|
return PChainTransformSubnetTx, nil
|
|
case *txs.AddPermissionlessValidatorTx:
|
|
return PChainAddPermissionlessValidatorTx, nil
|
|
case *txs.TransferSubnetOwnershipTx:
|
|
return PChainTransferSubnetOwnershipTx, nil
|
|
default:
|
|
return Undefined, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
|
|
}
|
|
}
|
|
|
|
// get network id associated to tx
|
|
func (ms *Multisig) GetNetworkID() (uint32, error) {
|
|
if ms.Undefined() {
|
|
return 0, ErrUndefinedTx
|
|
}
|
|
unsignedTx := ms.PChainTx.Unsigned
|
|
var networkID uint32
|
|
switch unsignedTx := unsignedTx.(type) {
|
|
case *txs.RemoveSubnetValidatorTx:
|
|
networkID = unsignedTx.NetworkID
|
|
case *txs.AddSubnetValidatorTx:
|
|
networkID = unsignedTx.NetworkID
|
|
case *txs.CreateChainTx:
|
|
networkID = unsignedTx.NetworkID
|
|
case *txs.TransformSubnetTx:
|
|
networkID = unsignedTx.NetworkID
|
|
case *txs.AddPermissionlessValidatorTx:
|
|
networkID = unsignedTx.NetworkID
|
|
case *txs.TransferSubnetOwnershipTx:
|
|
networkID = unsignedTx.NetworkID
|
|
default:
|
|
return 0, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
|
|
}
|
|
return networkID, nil
|
|
}
|
|
|
|
// get network model associated to tx
|
|
func (ms *Multisig) GetNetwork() (network.Network, error) {
|
|
if ms.Undefined() {
|
|
return network.UndefinedNetwork, ErrUndefinedTx
|
|
}
|
|
networkID, err := ms.GetNetworkID()
|
|
if err != nil {
|
|
return network.UndefinedNetwork, err
|
|
}
|
|
newNetwork := network.NetworkFromNetworkID(networkID)
|
|
if newNetwork.Kind == network.Undefined {
|
|
return network.UndefinedNetwork, fmt.Errorf("undefined network model for tx")
|
|
}
|
|
return newNetwork, nil
|
|
}
|
|
|
|
func (ms *Multisig) GetBlockchainID() (ids.ID, error) {
|
|
if ms.Undefined() {
|
|
return ids.Empty, ErrUndefinedTx
|
|
}
|
|
unsignedTx := ms.PChainTx.Unsigned
|
|
var blockchainID ids.ID
|
|
switch unsignedTx := unsignedTx.(type) {
|
|
case *txs.RemoveSubnetValidatorTx:
|
|
copy(blockchainID[:], unsignedTx.BlockchainID[:])
|
|
case *txs.AddSubnetValidatorTx:
|
|
copy(blockchainID[:], unsignedTx.BlockchainID[:])
|
|
case *txs.CreateChainTx:
|
|
copy(blockchainID[:], unsignedTx.BlockchainID[:])
|
|
case *txs.TransformSubnetTx:
|
|
copy(blockchainID[:], unsignedTx.BlockchainID[:])
|
|
case *txs.AddPermissionlessValidatorTx:
|
|
copy(blockchainID[:], unsignedTx.BlockchainID[:])
|
|
case *txs.TransferSubnetOwnershipTx:
|
|
copy(blockchainID[:], unsignedTx.BlockchainID[:])
|
|
default:
|
|
return ids.Empty, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
|
|
}
|
|
return blockchainID, nil
|
|
}
|
|
|
|
// GetSubnetID gets subnet id associated to tx
|
|
func (ms *Multisig) GetSubnetID() (ids.ID, error) {
|
|
if ms.Undefined() {
|
|
return ids.Empty, ErrUndefinedTx
|
|
}
|
|
unsignedTx := ms.PChainTx.Unsigned
|
|
var subnetID ids.ID
|
|
switch unsignedTx := unsignedTx.(type) {
|
|
case *txs.RemoveSubnetValidatorTx:
|
|
copy(subnetID[:], unsignedTx.Subnet[:])
|
|
case *txs.AddSubnetValidatorTx:
|
|
subnetID = unsignedTx.SubnetValidator.Subnet
|
|
case *txs.CreateChainTx:
|
|
subnetID = unsignedTx.SubnetID
|
|
case *txs.TransformSubnetTx:
|
|
subnetID = unsignedTx.Subnet
|
|
case *txs.AddPermissionlessValidatorTx:
|
|
subnetID = unsignedTx.Subnet
|
|
case *txs.TransferSubnetOwnershipTx:
|
|
subnetID = unsignedTx.Subnet
|
|
default:
|
|
return ids.Empty, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
|
|
}
|
|
return subnetID, nil
|
|
}
|
|
|
|
func (ms *Multisig) GetSubnetOwners() ([]ids.ShortID, uint32, error) {
|
|
if ms.Undefined() {
|
|
return nil, 0, ErrUndefinedTx
|
|
}
|
|
if ms.controlKeys == nil {
|
|
subnetID, err := ms.GetSubnetID()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
network, err := ms.GetNetwork()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
controlKeys, threshold, err := GetOwners(network, subnetID)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
ms.controlKeys = controlKeys
|
|
ms.threshold = threshold
|
|
}
|
|
return ms.controlKeys, ms.threshold, nil
|
|
}
|
|
|
|
func GetOwners(network network.Network, subnetID ids.ID) ([]ids.ShortID, uint32, error) {
|
|
pClient := platformvm.NewClient(network.Endpoint)
|
|
ctx := context.Background()
|
|
subnetResponse, err := pClient.GetSubnet(ctx, subnetID)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("subnet tx %s query error: %w", subnetID, err)
|
|
}
|
|
controlKeys := subnetResponse.ControlKeys
|
|
threshold := subnetResponse.Threshold
|
|
return controlKeys, threshold, nil
|
|
}
|
|
|
|
func (ms *Multisig) GetWrappedPChainTx() (*txs.Tx, error) {
|
|
if ms.Undefined() {
|
|
return nil, ErrUndefinedTx
|
|
}
|
|
return ms.PChainTx, nil
|
|
}
|