mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
Signing authority was resolved from a name the requester wrote about itself. CrossChainRequest already receives the sender's chain id from the transport, which authenticated it, and then passed req.RequestingChain to the permission lookup instead -- so the proven identity sat unused in the signature while a field the sender controlled decided what it was allowed to do. The stock table grants B-Chain sign, keygen and reshare at 100k/day, so the claim was worth having. Caller carries an authenticated chain and nothing else can be substituted for it: the only constructor takes the transport's chain id, the fields are unexported, and the zero value grants nothing, so a path that forgets to authenticate fails closed instead of quietly inheriting rights. Making it a type rather than a convention is what turns "remember not to read the body" into something the compiler checks -- it listed every call site that had been passing a claim. Authorization binds to the chain ID, never to the label. A label is an operator's convenience and anyone can spell it; the id is the identity consensus itself uses. An entry with no chainId is bound to no chain and grants nothing, and Initialize names those entries at boot, because silence there would read as "custody is configured" right up until the first release failed. HTTP keygen and sign are gone rather than guarded. That transport authenticates no chain, so there was no check to write -- a body can say it is the bridge as easily as it can say anything else. Ceremonies are requested by chains over the cross-chain path; the HTTP surface reads replicated state, which needs no identity. Co-authored-by: Hanzo Dev <[email protected]>
60 lines
2.3 KiB
Go
60 lines
2.3 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package mpcvm
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
// Caller is a chain whose identity the TRANSPORT authenticated.
|
|
//
|
|
// It cannot be built from a request body. The only constructor takes the chain
|
|
// id the transport supplies, so "who is asking" is never a field the asker
|
|
// writes about itself. A name in a payload is a claim, and a claim its own
|
|
// subject authored proves nothing — the same reason a node's ownership
|
|
// attestation is read out of M-Chain consensus state and not out of the node's
|
|
// own config, where its operator could write whatever it liked.
|
|
//
|
|
// The zero Caller holds no permissions and can do nothing, so a path that
|
|
// forgets to authenticate fails closed rather than silently acquiring rights.
|
|
type Caller struct {
|
|
name string
|
|
perms *ChainPermissions
|
|
}
|
|
|
|
// Name is the operator's label for the authenticated chain, used for
|
|
// attribution and quota accounting. It is an OUTPUT of authentication, never an
|
|
// input to it.
|
|
func (c Caller) Name() string { return c.name }
|
|
|
|
// caller resolves the permissions of the chain the transport authenticated as
|
|
// sender.
|
|
//
|
|
// Authorization binds to the chain ID, never to a name. A name is an operator's
|
|
// label — convenient in a config file, and worth exactly nothing as evidence,
|
|
// because anyone can spell it. A chain id is the identity consensus itself
|
|
// uses.
|
|
//
|
|
// A permission entry carrying no ChainID is bound to no chain and therefore
|
|
// authorizes nobody. That is deliberate: custody is granted to a specific
|
|
// chain by an operator who names it, and an entry that matched on its label
|
|
// alone would hand custody to whoever typed the label. An unbound entry refuses
|
|
// and says so, because "this chain may not sign" and "this deployment never
|
|
// bound the chain" are different operational problems.
|
|
func (vm *VM) caller(sender ids.ID) (Caller, error) {
|
|
vm.mu.RLock()
|
|
defer vm.mu.RUnlock()
|
|
for name, p := range vm.config.AuthorizedChains {
|
|
if p == nil || p.ChainID == "" {
|
|
continue
|
|
}
|
|
if p.ChainID == sender.String() {
|
|
return Caller{name: name, perms: p}, nil
|
|
}
|
|
}
|
|
return Caller{}, fmt.Errorf("%w: no permission entry is bound to chain %s", ErrUnauthorizedChain, sender)
|
|
}
|