ceremonyclient/node/execution/intrinsics/global/global_prover_join.go
2025-12-15 16:45:31 -06:00

916 lines
24 KiB
Go

package global
import (
"encoding/binary"
"fmt"
"math/big"
"slices"
"github.com/iden3/go-iden3-crypto/poseidon"
pcrypto "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
hgcrdt "source.quilibrium.com/quilibrium/monorepo/hypergraph"
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/global/compat"
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token"
hgstate "source.quilibrium.com/quilibrium/monorepo/node/execution/state/hypergraph"
"source.quilibrium.com/quilibrium/monorepo/types/crypto"
"source.quilibrium.com/quilibrium/monorepo/types/execution/intrinsics"
"source.quilibrium.com/quilibrium/monorepo/types/execution/state"
"source.quilibrium.com/quilibrium/monorepo/types/hypergraph"
"source.quilibrium.com/quilibrium/monorepo/types/keys"
"source.quilibrium.com/quilibrium/monorepo/types/schema"
"source.quilibrium.com/quilibrium/monorepo/types/store"
"source.quilibrium.com/quilibrium/monorepo/types/tries"
qcrypto "source.quilibrium.com/quilibrium/monorepo/types/tries"
)
type BLS48581SignatureWithProofOfPossession struct {
// The BLS48-581 public key of the signer
PublicKey []byte
// The BLS48-581 signature
Signature []byte
// The Proof of Possession of public key signature
PopSignature []byte
}
type SeniorityMerge struct {
// The key type, used to distinguish old Ed448 keys vs BLS48-581 keys
KeyType crypto.KeyType
// The public key of the merge source
PublicKey []byte
// The signature of the public key
Signature []byte
// Private fields
signer crypto.Signer
}
func NewSeniorityMerge(
keyType crypto.KeyType,
signer crypto.Signer,
) *SeniorityMerge {
return &SeniorityMerge{
KeyType: keyType,
PublicKey: signer.Public().([]byte),
signer: signer,
}
}
type ProverJoin struct {
// The filters representing the join request (can be multiple)
Filters [][]byte
// The frame number when this request is made
FrameNumber uint64
// The public key signature with proof of possession for BLS48581
PublicKeySignatureBLS48581 BLS48581SignatureWithProofOfPossession
// Any optional merge targets for seniority
MergeTargets []*SeniorityMerge
// The optional delegated address for rewards to accrue, when omitted, uses
// the prover address
DelegateAddress []byte
// The proof element assuring availability and commitment of the workers
Proof []byte
// Private fields
keyManager keys.KeyManager
hypergraph hypergraph.Hypergraph
rdfMultiprover *schema.RDFMultiprover
frameProver crypto.FrameProver
frameStore store.ClockStore
}
func NewProverJoin(
filters [][]byte,
frameNumber uint64,
mergeTargets []*SeniorityMerge,
delegateAddress []byte,
keyManager keys.KeyManager,
hypergraph hypergraph.Hypergraph,
rdfMultiprover *schema.RDFMultiprover,
frameProver crypto.FrameProver,
frameStore store.ClockStore,
) (*ProverJoin, error) {
return &ProverJoin{
Filters: filters, // buildutils:allow-slice-alias slice is static
FrameNumber: frameNumber,
MergeTargets: mergeTargets, // buildutils:allow-slice-alias slice is static
DelegateAddress: delegateAddress, // buildutils:allow-slice-alias slice is static
keyManager: keyManager,
hypergraph: hypergraph,
rdfMultiprover: rdfMultiprover,
frameProver: frameProver,
frameStore: frameStore,
}, nil
}
// GetCost implements intrinsics.IntrinsicOperation.
func (p *ProverJoin) GetCost() (*big.Int, error) {
return big.NewInt(0), nil
}
// Materialize implements intrinsics.IntrinsicOperation.
func (p *ProverJoin) Materialize(
frameNumber uint64,
state state.State,
) (state.State, error) {
hg := state.(*hgstate.HypergraphState)
publicKey := p.PublicKeySignatureBLS48581.PublicKey
proverAddressBI, err := poseidon.HashBytes(publicKey)
if err != nil || proverAddressBI == nil {
return nil, errors.Wrap(errors.New("invalid address"), "materialize")
}
proverAddress := proverAddressBI.FillBytes(make([]byte, 32))
// Full address for the prover entry
proverFullAddress := [64]byte{}
copy(proverFullAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(proverFullAddress[32:], proverAddress)
// Check if prover already exists
vertex, err := hg.Get(
proverFullAddress[:32],
proverFullAddress[32:],
hgstate.VertexAddsDiscriminator,
)
proverExists := err == nil
var proverTree *tries.VectorCommitmentTree
if proverExists {
var ok bool
proverTree, ok = vertex.(*tries.VectorCommitmentTree)
if !ok || proverTree == nil {
return nil, errors.Wrap(
errors.New("invalid object returned for vertex"),
"materialize",
)
}
}
if !proverExists {
// Create new prover entry
proverTree = &qcrypto.VectorCommitmentTree{}
// Store the public key
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"prover:Prover",
"PublicKey",
publicKey,
proverTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Store status (0 = joining since we have allocations joining)
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"prover:Prover",
"Status",
[]byte{0},
proverTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Store available storage (initially 0)
availableStorageBytes := make([]byte, 8)
binary.BigEndian.PutUint64(availableStorageBytes, 0)
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"prover:Prover",
"AvailableStorage",
availableStorageBytes,
proverTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Calculate seniority from MergeTargets
var seniority uint64 = 0
if len(p.MergeTargets) > 0 {
// Convert Ed448 public keys to peer IDs
var peerIds []string
for _, target := range p.MergeTargets {
if target.KeyType == crypto.KeyTypeEd448 {
pk, err := pcrypto.UnmarshalEd448PublicKey(target.PublicKey)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
peerId, err := peer.IDFromPublicKey(pk)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
peerIds = append(peerIds, peerId.String())
}
}
// Get aggregated seniority
if len(peerIds) > 0 {
seniorityBig := compat.GetAggregatedSeniority(peerIds)
if seniorityBig.IsUint64() {
seniority = seniorityBig.Uint64()
}
}
}
// Store seniority
seniorityBytes := make([]byte, 8)
binary.BigEndian.PutUint64(seniorityBytes, seniority)
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"prover:Prover",
"Seniority",
seniorityBytes,
proverTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Create prover vertex
proverVertex := hg.NewVertexAddMaterializedState(
intrinsics.GLOBAL_INTRINSIC_ADDRESS,
[32]byte(proverAddress),
frameNumber,
nil,
proverTree,
)
err = hg.Set(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
proverAddress,
hgstate.VertexAddsDiscriminator,
frameNumber,
proverVertex,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Create ProverReward entry in QUIL token address with zero balance
rewardTree := &qcrypto.VectorCommitmentTree{}
delegateAddress := proverAddress
if len(p.DelegateAddress) == 32 {
delegateAddress = p.DelegateAddress
}
derivedRewardAddress, err := poseidon.HashBytes(
slices.Concat(token.QUIL_TOKEN_ADDRESS[:], proverAddress),
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"reward:ProverReward",
"DelegateAddress",
delegateAddress,
rewardTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Set zero balance
zeroBalance := make([]byte, 32)
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"reward:ProverReward",
"Balance",
zeroBalance,
rewardTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Create reward vertex in QUIL token address
rewardVertex := hg.NewVertexAddMaterializedState(
[32]byte(intrinsics.GLOBAL_INTRINSIC_ADDRESS[:]),
[32]byte(derivedRewardAddress.FillBytes(make([]byte, 32))),
frameNumber,
nil,
rewardTree,
)
err = hg.Set(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
derivedRewardAddress.FillBytes(make([]byte, 32)),
hgstate.VertexAddsDiscriminator,
frameNumber,
rewardVertex,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
}
// Create hyperedge for this prover
hyperedgeAddress := [32]byte(proverAddress)
hyperedge := hgcrdt.NewHyperedge(
intrinsics.GLOBAL_INTRINSIC_ADDRESS,
hyperedgeAddress,
)
// Get existing hyperedge if it exists
existingHyperedge, err := hg.Get(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
hyperedgeAddress[:],
hgstate.HyperedgeAddsDiscriminator,
)
if err == nil && existingHyperedge != nil {
// Use existing hyperedge
var ok bool
hyperedge, ok = existingHyperedge.(hypergraph.Hyperedge)
if !ok {
return nil, errors.Wrap(
errors.New("invalid object returned for hyperedge"),
"materialize",
)
}
}
// Create ProverAllocation entries for each filter
for _, filter := range p.Filters {
// Calculate allocation address: poseidon.Hash(publicKey || filter)
allocationAddressBI, err := poseidon.HashBytes(
slices.Concat([]byte("PROVER_ALLOCATION"), publicKey, filter),
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
allocationAddress := allocationAddressBI.FillBytes(make([]byte, 32))
// Create allocation tree
allocationTree := &qcrypto.VectorCommitmentTree{}
// Store prover reference
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"allocation:ProverAllocation",
"Prover",
proverAddress,
allocationTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Store allocation status (0 = joining)
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"allocation:ProverAllocation",
"Status",
[]byte{0},
allocationTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Store confirmation filter
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"allocation:ProverAllocation",
"ConfirmationFilter",
filter,
allocationTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Store join frame number
frameNumberBytes := make([]byte, 8)
binary.BigEndian.PutUint64(frameNumberBytes, p.FrameNumber)
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"allocation:ProverAllocation",
"JoinFrameNumber",
frameNumberBytes,
allocationTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Get a copy of the original allocation tree for change tracking
var prior *tries.VectorCommitmentTree
originalAllocationVertex, err := hg.Get(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
allocationAddress,
hgstate.VertexAddsDiscriminator,
)
if err == nil && originalAllocationVertex != nil {
prior = originalAllocationVertex.(*tries.VectorCommitmentTree)
}
// Create allocation vertex
allocationVertex := hg.NewVertexAddMaterializedState(
intrinsics.GLOBAL_INTRINSIC_ADDRESS,
[32]byte(allocationAddress),
frameNumber,
prior,
allocationTree,
)
err = hg.Set(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
allocationAddress,
hgstate.VertexAddsDiscriminator,
frameNumber,
allocationVertex,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Add allocation vertex to hyperedge
hyperedge.AddExtrinsic(allocationVertex.GetVertex())
}
for _, mt := range p.MergeTargets {
spentMergeBI, err := poseidon.HashBytes(slices.Concat(
[]byte("PROVER_JOIN_MERGE"),
mt.PublicKey,
))
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// confirm this has not already been used
spentAddress := [64]byte{}
copy(spentAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(spentAddress[32:], spentMergeBI.FillBytes(make([]byte, 32)))
spentMergeVertex := hg.NewVertexAddMaterializedState(
intrinsics.GLOBAL_INTRINSIC_ADDRESS,
[32]byte(spentMergeBI.FillBytes(make([]byte, 32))),
frameNumber,
nil,
&tries.VectorCommitmentTree{},
)
err = hg.Set(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
spentMergeBI.FillBytes(make([]byte, 32)),
hgstate.VertexAddsDiscriminator,
frameNumber,
spentMergeVertex,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
}
var priorHyperedge *tries.VectorCommitmentTree
previousHyperedge, err := hg.Get(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
hyperedgeAddress[:],
hgstate.HyperedgeAddsDiscriminator,
)
if err == nil && previousHyperedge != nil {
// Use existing hyperedge
var ok bool
prior, ok := previousHyperedge.(hypergraph.Hyperedge)
if !ok {
return nil, errors.Wrap(
errors.New("invalid object returned for hyperedge"),
"materialize",
)
}
priorHyperedge = prior.GetExtrinsicTree()
}
// Update hyperedge
hyperedgeState := hg.NewHyperedgeAddMaterializedState(
frameNumber,
priorHyperedge,
hyperedge,
)
err = hg.Set(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
hyperedgeAddress[:],
hgstate.HyperedgeAddsDiscriminator,
frameNumber,
hyperedgeState,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
return state, nil
}
// Prove implements intrinsics.IntrinsicOperation.
func (p *ProverJoin) Prove(frameNumber uint64) error {
// Get the q-prover-key
prover, err := p.keyManager.GetSigningKey("q-prover-key")
if err != nil {
return errors.Wrap(err, "prove")
}
for _, mt := range p.MergeTargets {
if mt.signer != nil {
mt.Signature, err = mt.signer.SignWithDomain(
p.PublicKeySignatureBLS48581.PublicKey,
[]byte("PROVER_JOIN_MERGE"),
)
if err != nil {
return errors.Wrap(err, "prove")
}
}
}
joinClone := p.ToProtobuf()
joinClone.PublicKeySignatureBls48581 = nil
joinMessage, err := joinClone.ToCanonicalBytes()
if err != nil {
return errors.Wrap(err, "prove")
}
// Create the domain for the first signature
// Poseidon hash of GLOBAL_INTRINSIC_ADDRESS concatenated with "PROVER_JOIN"
joinDomainPreimage := slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
[]byte("PROVER_JOIN"),
)
joinDomain, err := poseidon.HashBytes(joinDomainPreimage)
if err != nil {
return errors.Wrap(err, "prove")
}
// Create first signature over the join message with the join domain
signature, err := prover.SignWithDomain(
joinMessage,
joinDomain.FillBytes(make([]byte, 32)),
)
if err != nil {
return errors.Wrap(err, "prove")
}
// Create the domain for the proof of possession
popDomain := []byte("BLS48_POP_SK")
// Create the proof of possession signature over the public key with the POP
// domain
popSignature, err := prover.SignWithDomain(
prover.Public().([]byte),
popDomain,
)
if err != nil {
return errors.Wrap(err, "prove")
}
// Create the BLS48581SignatureWithProofOfPossession
p.PublicKeySignatureBLS48581 = BLS48581SignatureWithProofOfPossession{
Signature: signature,
PublicKey: prover.Public().([]byte),
PopSignature: popSignature,
}
return nil
}
func (p *ProverJoin) GetReadAddresses(frameNumber uint64) ([][]byte, error) {
return nil, nil
}
func (p *ProverJoin) GetWriteAddresses(frameNumber uint64) ([][]byte, error) {
publicKey := p.PublicKeySignatureBLS48581.PublicKey
proverAddressBI, err := poseidon.HashBytes(publicKey)
if err != nil || proverAddressBI == nil {
return nil, errors.Wrap(
errors.New("invalid address"),
"get write addresses",
)
}
proverAddress := proverAddressBI.FillBytes(make([]byte, 32))
proverFullAddress := [64]byte{}
copy(proverFullAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(proverFullAddress[32:], proverAddress)
addresses := map[string]struct{}{}
addresses[string(proverFullAddress[:])] = struct{}{}
derivedRewardAddress, err := poseidon.HashBytes(
slices.Concat(token.QUIL_TOKEN_ADDRESS[:], proverAddress),
)
if err != nil {
return nil, errors.Wrap(err, "get write addresses")
}
addresses[string(slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
derivedRewardAddress.FillBytes(make([]byte, 32)),
))] = struct{}{}
for _, filter := range p.Filters {
allocationAddressBI, err := poseidon.HashBytes(
slices.Concat([]byte("PROVER_ALLOCATION"), publicKey, filter),
)
if err != nil {
return nil, errors.Wrap(err, "get write addresses")
}
allocationAddress := allocationAddressBI.FillBytes(make([]byte, 32))
addresses[string(slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
allocationAddress,
))] = struct{}{}
}
for _, mt := range p.MergeTargets {
spentMergeBI, err := poseidon.HashBytes(slices.Concat(
[]byte("PROVER_JOIN_MERGE"),
mt.PublicKey,
))
if err != nil {
return nil, errors.Wrap(err, "get write addresses")
}
addresses[string(slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
spentMergeBI.FillBytes(make([]byte, 32)),
))] = struct{}{}
}
result := [][]byte{}
for key := range addresses {
result = append(result, []byte(key))
}
return result, nil
}
// Verify implements intrinsics.IntrinsicOperation.
func (p *ProverJoin) Verify(frameNumber uint64) (valid bool, err error) {
defer func() {
if r := recover(); r != nil {
valid = false
err = fmt.Errorf("panic from: %v", r)
}
}()
// First check if prover can join (not in tree or in left state)
addressBI, err := poseidon.HashBytes(p.PublicKeySignatureBLS48581.PublicKey)
if err != nil {
return false, errors.Wrap(err, "verify")
}
address := addressBI.FillBytes(make([]byte, 32))
for _, filter := range p.Filters {
if len(filter) < 32 {
return false, errors.Wrap(errors.New("invalid filter size"), "verify")
}
}
if len(p.Proof)%516 != 0 || len(p.Proof)/516 != len(p.Filters) {
return false, errors.Wrap(errors.New("proof size mismatch"), "verify")
}
// Disallow too old of a request
if p.FrameNumber+10 < frameNumber {
return false, errors.Wrap(errors.New("outdated request"), "verify")
}
frame, err := p.frameStore.GetGlobalClockFrame(p.FrameNumber)
if err != nil {
frames, err := p.frameStore.RangeGlobalClockFrameCandidates(
p.FrameNumber,
p.FrameNumber,
)
if err != nil {
return false, errors.Wrap(errors.Wrap(
err,
fmt.Sprintf("frame number: %d", p.FrameNumber),
), "verify")
}
if !frames.First() || !frames.Valid() {
return false, errors.Wrap(errors.Wrap(
errors.New("not found"),
fmt.Sprintf("frame number: %d", p.FrameNumber),
), "verify")
}
frame, err = frames.Value()
frames.Close()
if err != nil {
return false, errors.Wrap(errors.Wrap(
err,
fmt.Sprintf("frame number: %d", p.FrameNumber),
), "verify")
}
}
// Prepare challenge for verification
challenge := sha3.Sum256(frame.Header.Output)
ids := [][]byte{}
for idx, filter := range p.Filters {
ids = append(ids, slices.Concat(
address,
filter,
binary.BigEndian.AppendUint32(nil, uint32(idx)),
))
}
solutions := [][516]byte{}
for i := range p.Filters {
solutions = append(solutions, [516]byte(p.Proof[i*516:(i+1)*516]))
}
valid, err = p.frameProver.VerifyMultiProof(
challenge,
frame.Header.Difficulty,
ids,
solutions,
)
if err != nil || !valid {
return false, errors.Wrap(errors.New("invalid multi proof"), "verify")
}
for _, mt := range p.MergeTargets {
valid, err := p.keyManager.ValidateSignature(
mt.KeyType,
mt.PublicKey,
p.PublicKeySignatureBLS48581.PublicKey,
mt.Signature,
[]byte("PROVER_JOIN_MERGE"),
)
if err != nil || !valid {
return false, errors.Wrap(err, "verify")
}
spentMergeBI, err := poseidon.HashBytes(slices.Concat(
[]byte("PROVER_JOIN_MERGE"),
mt.PublicKey,
))
if err != nil {
return false, errors.Wrap(err, "verify")
}
// confirm this has not already been used
spentAddress := [64]byte{}
copy(spentAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(spentAddress[32:], spentMergeBI.FillBytes(make([]byte, 32)))
v, err := p.hypergraph.GetVertex(spentAddress)
if err == nil && v != nil {
return false, errors.Wrap(
errors.New("merge target already used"),
"verify",
)
}
}
// Get the existing prover vertex data
proverAddress := [64]byte{}
copy(proverAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(proverAddress[32:], address)
proverVertexData, err := p.hypergraph.GetVertexData(proverAddress)
if err == nil && proverVertexData != nil {
tree := proverVertexData
kickedFrame, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"allocation:ProverAllocation",
"KickFrameNumber",
tree,
)
if err == nil && len(kickedFrame) == 8 {
kickedFrame := binary.BigEndian.Uint64(kickedFrame)
if kickedFrame != 0 {
// Prover has been kicked for malicious behavior
return false, errors.Wrap(
errors.New("prover has been previously kicked"),
"verify",
)
}
}
}
for _, f := range p.Filters {
allocationAddressBI, err := poseidon.HashBytes(
slices.Concat(
[]byte("PROVER_ALLOCATION"),
p.PublicKeySignatureBLS48581.PublicKey,
f,
),
)
if err != nil {
return false, errors.Wrap(err, "verify")
}
allocationAddress := allocationAddressBI.FillBytes(make([]byte, 32))
// Create composite address: GLOBAL_INTRINSIC_ADDRESS + prover address
fullAddress := [64]byte{}
copy(fullAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(fullAddress[32:], allocationAddress)
// Get the existing prover allocation vertex data
vertexData, err := p.hypergraph.GetVertexData(fullAddress)
if err == nil && vertexData != nil {
// Prover exists, check if they're in left state (4)
tree := vertexData
// Check if prover is in left state (4)
statusData, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"allocation:ProverAllocation",
"Status",
tree,
)
if err == nil && len(statusData) > 0 {
status := statusData[0]
if status != 4 {
// Prover is in some other state - cannot join
return false, errors.Wrap(
errors.New("prover already exists in non-left state"),
"verify",
)
}
}
}
}
// If we get here, either prover doesn't exist or is in left state - both are
// valid
joinClone := p.ToProtobuf()
joinClone.PublicKeySignatureBls48581 = nil
joinMessage, err := joinClone.ToCanonicalBytes()
if err != nil {
return false, errors.Wrap(err, "verify")
}
// Create the domain for the first signature
// Poseidon hash of GLOBAL_INTRINSIC_ADDRESS concatenated with "PROVER_JOIN"
joinDomainPreimage := slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
[]byte("PROVER_JOIN"),
)
joinDomain, err := poseidon.HashBytes(joinDomainPreimage)
if err != nil {
return false, errors.Wrap(err, "verify")
}
// Create the domain for the proof of possession
popDomain := []byte("BLS48_POP_SK")
// Verify the signature
valid, err = p.keyManager.ValidateSignature(
crypto.KeyTypeBLS48581G1,
p.PublicKeySignatureBLS48581.PublicKey,
joinMessage,
p.PublicKeySignatureBLS48581.Signature,
joinDomain.FillBytes(make([]byte, 32)),
)
if err != nil || !valid {
return false, errors.Wrap(errors.New("invalid signature"), "verify")
}
// Verify the proof of possession
valid, err = p.keyManager.ValidateSignature(
crypto.KeyTypeBLS48581G1,
p.PublicKeySignatureBLS48581.PublicKey,
p.PublicKeySignatureBLS48581.PublicKey,
p.PublicKeySignatureBLS48581.PopSignature,
popDomain,
)
if err != nil || !valid {
return false, errors.Wrap(errors.New("invalid pop signature"), "verify")
}
// Verify any merge signatures
for _, mt := range p.MergeTargets {
valid, err := p.keyManager.ValidateSignature(
mt.KeyType,
mt.PublicKey,
p.PublicKeySignatureBLS48581.PublicKey,
mt.Signature,
[]byte("PROVER_JOIN_MERGE"),
)
if err != nil || !valid {
return false, errors.Wrap(errors.New("invalid merge signature"), "verify")
}
}
return true, nil
}
var _ intrinsics.IntrinsicOperation = (*ProverJoin)(nil)