mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
A plugin is its own binary; the daemon that loads it has no business being linked into it. Twelve VMs reached into github.com/luxfi/node for four things, and each one now programs against a home that is actually its own: - fee admission (Policy, FlatPolicy, NoUserTxPolicy, Validate) joins the settlement half already in chains/fee. One fee package, and the nodefee alias that marked the split is gone. - the cross-chain artifact types move to chains/artifacts. - the factory the daemon registers is luxfi/vm/manager.Factory, and the version types are luxfi/version. Both are ordinary modules the daemon reads too, so a VM imports them directly rather than through anything of ours. The GPU switch is deleted, not moved. It was a global set during daemon startup that a separate process could never observe, so every plugin read its default; FHEOptions.Backend is the switch that was really there. go list -deps names no luxfi/node for aivm, bridgevm, dexvm, evm, fhevm, graphvm, identityvm, keyvm, mpcvm, quantumvm, schain or zkvm, and the built plugin binaries carry no such string. oraclevm and relayvm still do: they are re-export shims over luxfi/oracle and luxfi/relay, which build their fee policies upstream. Their tests keep naming the daemon's fee package because that is the type their value has.
184 lines
5.1 KiB
Go
184 lines
5.1 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package identityvm
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/luxfi/chains/artifacts"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
// Submit queues one state change for a block. Nothing here writes state: a
|
|
// change becomes state when a block carrying it is accepted, on every node,
|
|
// which is what "the chain agrees" means. CreateIdentity, RegisterIssuer and
|
|
// RevokeCredential used to write straight to the base database on whichever
|
|
// node received the call, so no two nodes held the same identity set — and a
|
|
// block naming an issuer verified on the node that registered it and nowhere
|
|
// else.
|
|
func (vm *VM) Submit(c *Change) error {
|
|
vm.chain.Lock()
|
|
defer vm.chain.Unlock()
|
|
|
|
// Refuse at the door what no block could carry. The pool is bounded, so a
|
|
// change that can never be built is a slot nothing else can use.
|
|
block := &Block{vm: vm, BlockTimestamp: time.Now().Unix()}
|
|
block.add(c)
|
|
if err := vm.check(block); err != nil {
|
|
return err
|
|
}
|
|
return vm.pending.Add(c)
|
|
}
|
|
|
|
// Identity returns an identity by id.
|
|
func (vm *VM) Identity(id ids.ID) (*Identity, error) {
|
|
vm.chain.RLock()
|
|
defer vm.chain.RUnlock()
|
|
|
|
identity, ok := vm.identities[id]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %s", errUnknownIdentity, id)
|
|
}
|
|
return identity, nil
|
|
}
|
|
|
|
// Resolve returns the identity a DID names. The DID is derived from the id, so
|
|
// this is a lookup rather than the scan of every identity it used to be —
|
|
// which returned whichever of two identical DIDs Go's map iteration reached
|
|
// first.
|
|
func (vm *VM) Resolve(did string) (*Identity, error) {
|
|
id, err := identityFrom(did)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return vm.Identity(id)
|
|
}
|
|
|
|
// Issuer returns an issuer by id.
|
|
func (vm *VM) Issuer(id ids.ID) (*Issuer, error) {
|
|
vm.chain.RLock()
|
|
defer vm.chain.RUnlock()
|
|
|
|
issuer, ok := vm.issuers[id]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %s", errUnknownIssuer, id)
|
|
}
|
|
return issuer, nil
|
|
}
|
|
|
|
// Issuers returns every issuer the chain holds, in id order. Map order is not
|
|
// an order, and an RPC that answers differently each call is one a client
|
|
// cannot page through.
|
|
func (vm *VM) Issuers() []*Issuer {
|
|
vm.chain.RLock()
|
|
defer vm.chain.RUnlock()
|
|
|
|
out := make([]*Issuer, 0, len(vm.issuers))
|
|
for _, issuer := range vm.issuers {
|
|
out = append(out, issuer)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].ID.Compare(out[j].ID) < 0 })
|
|
return out
|
|
}
|
|
|
|
// Credential returns a credential and its status as of now.
|
|
func (vm *VM) Credential(id ids.ID) (*Credential, string, error) {
|
|
vm.chain.RLock()
|
|
defer vm.chain.RUnlock()
|
|
|
|
cred, ok := vm.credentials[id]
|
|
if !ok {
|
|
return nil, "", fmt.Errorf("%w: %s", errUnknownCredential, id)
|
|
}
|
|
return cred, vm.status(cred), nil
|
|
}
|
|
|
|
// Verify reports whether a credential is usable now: recorded, not revoked,
|
|
// not expired.
|
|
//
|
|
// It used to also accept a "ZK proof" whose only test was that it was not
|
|
// empty, which is a length check standing in for a verdict. What makes a
|
|
// credential this chain's is the issuer's signature over it, and check()
|
|
// refuses a block carrying one without it.
|
|
func (vm *VM) Verify(id ids.ID) error {
|
|
vm.chain.RLock()
|
|
defer vm.chain.RUnlock()
|
|
|
|
cred, ok := vm.credentials[id]
|
|
if !ok {
|
|
return fmt.Errorf("%w: %s", errUnknownCredential, id)
|
|
}
|
|
switch vm.status(cred) {
|
|
case CredentialRevoked:
|
|
return fmt.Errorf("%w: %s", errCredentialRevoked, id)
|
|
case CredentialExpired:
|
|
return fmt.Errorf("%w: %s", errCredentialExpired, id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Credential states. A credential's state is DERIVED from the revocation set
|
|
// and its own expiry; it is not a field that has to be kept in step with both.
|
|
const (
|
|
CredentialActive = "active"
|
|
CredentialRevoked = "revoked"
|
|
CredentialExpired = "expired"
|
|
)
|
|
|
|
// status is what the chain says about a credential now. Caller holds the lock.
|
|
func (vm *VM) status(c *Credential) string {
|
|
if _, revoked := vm.revocations[c.ID]; revoked {
|
|
return CredentialRevoked
|
|
}
|
|
if !c.ExpirationDate.After(time.Now()) {
|
|
return CredentialExpired
|
|
}
|
|
return CredentialActive
|
|
}
|
|
|
|
// Proof builds a selective-disclosure artifact for a credential. It presents
|
|
// what the chain holds; the disclosure proof itself is the caller's.
|
|
func (vm *VM) Proof(id ids.ID, disclosure []byte) (*artifacts.CredentialProof, error) {
|
|
vm.chain.RLock()
|
|
defer vm.chain.RUnlock()
|
|
|
|
cred, ok := vm.credentials[id]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %s", errUnknownCredential, id)
|
|
}
|
|
|
|
claims, err := json.Marshal(cred.Claims)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
credType := ""
|
|
if len(cred.Type) > 0 {
|
|
credType = cred.Type[0]
|
|
}
|
|
|
|
var revoked uint64
|
|
if _, ok := vm.revocations[id]; ok {
|
|
revoked = 1
|
|
}
|
|
|
|
return &artifacts.CredentialProof{
|
|
Version_: 1,
|
|
SigSuite_: artifacts.SuitePQOnly,
|
|
CredentialID: id,
|
|
IssuerDID: didPrefix + cred.Issuer.String(),
|
|
SubjectDID: didPrefix + cred.Subject.String(),
|
|
CredType: credType,
|
|
ClaimsCommitment: sha256.Sum256(claims),
|
|
SelectiveProof: disclosure,
|
|
IssuedAt: cred.IssuanceDate,
|
|
ExpiresAt: cred.ExpirationDate,
|
|
RevocationEpoch: revoked,
|
|
}, nil
|
|
}
|