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

541 lines
14 KiB
Go

package global
import (
"bytes"
"encoding/binary"
"math/big"
"slices"
"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/pkg/errors"
"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/tries"
)
type ProverReject struct {
// The filters representing the reject request
Filters [][]byte
// The frame number when this request is made
FrameNumber uint64
// The BLS48581 addressed signature
PublicKeySignatureBLS48581 BLS48581AddressedSignature
// Private fields
keyManager keys.KeyManager
hypergraph hypergraph.Hypergraph
rdfMultiprover *schema.RDFMultiprover
}
func NewProverReject(
filters [][]byte,
frameNumber uint64,
keyManager keys.KeyManager,
hypergraph hypergraph.Hypergraph,
rdfMultiprover *schema.RDFMultiprover,
) (*ProverReject, error) {
return &ProverReject{
Filters: filters, // buildutils:allow-slice-alias slice is static
FrameNumber: frameNumber,
keyManager: keyManager,
hypergraph: hypergraph,
rdfMultiprover: rdfMultiprover,
}, nil
}
// GetCost implements intrinsics.IntrinsicOperation.
func (p *ProverReject) GetCost() (*big.Int, error) {
return big.NewInt(0), nil
}
// Materialize implements intrinsics.IntrinsicOperation.
func (p *ProverReject) Materialize(
frameNumber uint64,
state state.State,
) (state.State, error) {
hg := state.(*hgstate.HypergraphState)
proverAddress := p.PublicKeySignatureBLS48581.Address
proverFullAddress := [64]byte{}
copy(proverFullAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(proverFullAddress[32:], proverAddress)
// Get the existing prover vertex
vertex, err := hg.Get(
proverFullAddress[:32],
proverFullAddress[32:],
hgstate.VertexAddsDiscriminator,
)
if err != nil || vertex == nil {
return nil, errors.Wrap(
errors.New("prover not found"),
"materialize",
)
}
var proverTree *tries.VectorCommitmentTree
var ok bool
proverTree, ok = vertex.(*tries.VectorCommitmentTree)
if !ok || proverTree == nil {
return nil, errors.Wrap(
errors.New("invalid object returned for vertex"),
"materialize",
)
}
// Get prover public key for allocation lookup
publicKey, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"prover:Prover",
"PublicKey",
proverTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
for _, filter := range p.Filters {
// Calculate allocation address:
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))
allocationFullAddress := [64]byte{}
copy(allocationFullAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(allocationFullAddress[32:], allocationAddress)
// Get allocation vertex
allocationVertex, err := hg.Get(
allocationFullAddress[:32],
allocationFullAddress[32:],
hgstate.VertexAddsDiscriminator,
)
if err != nil || allocationVertex == nil {
return nil, errors.Wrap(
errors.New("allocation not found"),
"materialize",
)
}
var allocationTree *tries.VectorCommitmentTree
allocationTree, ok = allocationVertex.(*tries.VectorCommitmentTree)
if !ok || allocationTree == nil {
return nil, errors.Wrap(
errors.New("invalid object returned for vertex"),
"materialize",
)
}
// Check current allocation status
statusBytes, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"allocation:ProverAllocation",
"Status",
allocationTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
status := uint8(0)
if len(statusBytes) > 0 {
status = statusBytes[0]
}
// Determine what we're rejecting based on current status
if status == 0 {
// Rejecting join - update allocation status to left (4)
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"allocation:ProverAllocation",
"Status",
[]byte{4},
allocationTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Store join rejection 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",
"JoinRejectFrameNumber",
frameNumberBytes,
allocationTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
} else if status == 3 {
// Rejecting leave - update allocation status back to active (1)
err = p.rdfMultiprover.Set(
GLOBAL_RDF_SCHEMA,
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
"allocation:ProverAllocation",
"Status",
[]byte{1},
allocationTree,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
// Store leave rejection 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",
"LeaveRejectFrameNumber",
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(
allocationFullAddress[:32],
allocationFullAddress[32:],
hgstate.VertexAddsDiscriminator,
)
if err == nil && originalAllocationVertex != nil {
prior = originalAllocationVertex.(*tries.VectorCommitmentTree)
}
// Update allocation vertex
updatedAllocation := hg.NewVertexAddMaterializedState(
intrinsics.GLOBAL_INTRINSIC_ADDRESS,
[32]byte(allocationFullAddress[32:]),
frameNumber,
prior,
allocationTree,
)
err = hg.Set(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
allocationAddress,
hgstate.VertexAddsDiscriminator,
frameNumber,
updatedAllocation,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
}
// Update the prover status to reflect the aggregate allocation status
err = UpdateAggregateProverStatus(
hg,
proverAddress,
frameNumber,
proverTree,
p.rdfMultiprover,
)
if err != nil {
return nil, errors.Wrap(err, "materialize")
}
return state, nil
}
// Prove implements intrinsics.IntrinsicOperation.
func (p *ProverReject) 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")
}
// Get the public key
pubKey := prover.Public().([]byte)
// Compute the address from the public key using Poseidon hash
addressBI, err := poseidon.HashBytes(pubKey)
if err != nil {
return errors.Wrap(err, "prove")
}
address := addressBI.FillBytes(make([]byte, 32))
// Create reject message contents
rejectMessage := bytes.Buffer{}
// Add filter
rejectMessage.Write(slices.Concat(p.Filters...))
// Add frame number
frameNumberBytes := make([]byte, 8)
binary.BigEndian.PutUint64(frameNumberBytes, p.FrameNumber)
rejectMessage.Write(frameNumberBytes)
// Create the domain for the signature
// Poseidon hash of GLOBAL_INTRINSIC_ADDRESS concatenated with "PROVER_REJECT"
rejectDomainPreimage := slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
[]byte("PROVER_REJECT"),
)
rejectDomain, err := poseidon.HashBytes(rejectDomainPreimage)
if err != nil {
return errors.Wrap(err, "prove")
}
// Create signature over the reject message with the reject domain
signature, err := prover.SignWithDomain(
rejectMessage.Bytes(),
rejectDomain.Bytes(),
)
if err != nil {
return errors.Wrap(err, "prove")
}
// Create the BLS48581AddressedSignature
p.PublicKeySignatureBLS48581 = BLS48581AddressedSignature{
Signature: signature,
Address: address,
}
return nil
}
func (p *ProverReject) GetReadAddresses(frameNumber uint64) ([][]byte, error) {
return nil, nil
}
func (p *ProverReject) GetWriteAddresses(frameNumber uint64) ([][]byte, error) {
proverAddress := p.PublicKeySignatureBLS48581.Address
proverFullAddress := [64]byte{}
copy(proverFullAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(proverFullAddress[32:], proverAddress)
// Get the existing prover vertex
proverTree, err := p.hypergraph.GetVertexData(proverFullAddress)
if err != nil || proverTree == nil {
return nil, errors.Wrap(
errors.New("prover not found"),
"get write addresses",
)
}
// Get prover public key for allocation lookup
publicKey, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"prover:Prover",
"PublicKey",
proverTree,
)
if err != nil {
return nil, errors.Wrap(err, "get write addresses")
}
result := [][]byte{}
addresses := map[string]struct{}{}
addresses[string(proverFullAddress[:])] = struct{}{}
for _, filter := range p.Filters {
// Calculate allocation address:
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))
allocationFullAddress := [64]byte{}
copy(allocationFullAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(allocationFullAddress[32:], allocationAddress)
addresses[string(allocationFullAddress[:])] = struct{}{}
for key := range addresses {
result = append(result, []byte(key))
}
}
return result, nil
}
// Verify implements intrinsics.IntrinsicOperation.
func (p *ProverReject) Verify(frameNumber uint64) (bool, error) {
// Create reject message contents
rejectMessage := bytes.Buffer{}
// Add filter
rejectMessage.Write(slices.Concat(p.Filters...))
// Add frame number
frameNumberBytes := make([]byte, 8)
binary.BigEndian.PutUint64(frameNumberBytes, p.FrameNumber)
rejectMessage.Write(frameNumberBytes)
// Create the domain for the signature
rejectDomainPreimage := slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
[]byte("PROVER_REJECT"),
)
rejectDomain, err := poseidon.HashBytes(rejectDomainPreimage)
if err != nil {
return false, errors.Wrap(err, "verify")
}
_, err = p.hypergraph.GetVertex([64]byte(slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
p.PublicKeySignatureBLS48581.Address,
)))
if err != nil {
return false, errors.Wrap(err, "verify")
}
tree, err := p.hypergraph.GetVertexData([64]byte(slices.Concat(
intrinsics.GLOBAL_INTRINSIC_ADDRESS[:],
p.PublicKeySignatureBLS48581.Address,
)))
if err != nil {
return false, errors.Wrap(err, "verify")
}
pubkey, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"prover:Prover",
"PublicKey",
tree,
)
if err != nil {
return false, errors.Wrap(err, "verify")
}
for _, filter := range p.Filters {
// Calculate allocation address to verify it exists
allocationAddressBI, err := poseidon.HashBytes(
slices.Concat([]byte("PROVER_ALLOCATION"), pubkey, filter),
)
if err != nil {
return false, errors.Wrap(err, "verify")
}
allocationAddress := allocationAddressBI.FillBytes(make([]byte, 32))
allocationFullAddress := [64]byte{}
copy(allocationFullAddress[:32], intrinsics.GLOBAL_INTRINSIC_ADDRESS[:])
copy(allocationFullAddress[32:], allocationAddress)
// Get allocation vertex
allocationTree, err := p.hypergraph.GetVertexData(allocationFullAddress)
if err != nil || allocationTree == nil {
return false, errors.Wrap(
errors.New("allocation not found"),
"verify",
)
}
// Check current allocation status
statusBytes, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"allocation:ProverAllocation",
"Status",
allocationTree,
)
if err != nil {
return false, errors.Wrap(err, "verify")
}
status := uint8(0)
if len(statusBytes) > 0 {
status = statusBytes[0]
}
// Can only reject if allocation is in joining (0) or leaving (3) state
if status != 0 && status != 3 {
return false, errors.Wrap(
errors.New("invalid allocation state for rejection"),
"verify",
)
}
if status == 0 {
// Rejecting join
// Get join frame number
joinFrameBytes, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"allocation:ProverAllocation",
"JoinFrameNumber",
allocationTree,
)
if err != nil || len(joinFrameBytes) != 8 {
return false, errors.Wrap(errors.New("missing join frame"), "verify")
}
joinFrame := binary.BigEndian.Uint64(joinFrameBytes)
// Special case: if join was before frame 255840, can reject any time
if joinFrame >= token.FRAME_2_1_EXTENDED_ENROLL_END {
// Otherwise same timing constraints as confirm
framesSinceJoin := frameNumber - joinFrame
if framesSinceJoin > 720 {
return false, errors.Wrap(
errors.New("join already implicitly rejected after 720 frames"),
"verify",
)
}
}
} else if status == 3 {
// Rejecting leave
// Get leave frame number
leaveFrameBytes, err := p.rdfMultiprover.Get(
GLOBAL_RDF_SCHEMA,
"allocation:ProverAllocation",
"LeaveFrameNumber",
allocationTree,
)
if err != nil || len(leaveFrameBytes) != 8 {
return false, errors.Wrap(errors.New("missing leave frame"), "verify")
}
leaveFrame := binary.BigEndian.Uint64(leaveFrameBytes)
framesSinceLeave := frameNumber - leaveFrame
if framesSinceLeave < 360 {
return false, errors.Wrap(
errors.New("must wait 360 frames after leave to reject"),
"verify",
)
}
if framesSinceLeave > 720 {
return false, errors.Wrap(
errors.New("leave already implicitly confirmed after 720 frames"),
"verify",
)
}
}
}
// Verify the signature
valid, err := p.keyManager.ValidateSignature(
crypto.KeyTypeBLS48581G1,
pubkey,
rejectMessage.Bytes(),
p.PublicKeySignatureBLS48581.Signature,
rejectDomain.Bytes(),
)
if err != nil || !valid {
return false, errors.Wrap(errors.New("invalid signature"), "verify")
}
return true, nil
}
var _ intrinsics.IntrinsicOperation = (*ProverReject)(nil)