ceremonyclient/node/execution/intrinsics/token/application/token_handle_mint.go
Cassandra Heart 7f8137df67
reset cutoffs
2025-01-30 03:20:52 -06:00

769 lines
21 KiB
Go

package application
import (
"bytes"
"encoding/binary"
"math/big"
"math/bits"
"github.com/iden3/go-iden3-crypto/poseidon"
pcrypto "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/mr-tron/base58"
"github.com/pkg/errors"
"go.uber.org/zap"
"golang.org/x/crypto/sha3"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
"source.quilibrium.com/quilibrium/monorepo/node/store"
"source.quilibrium.com/quilibrium/monorepo/node/tries"
)
// for tests, these need to be var
var PROOF_FRAME_CUTOFF = uint64(0)
var PROOF_FRAME_RING_RESET = uint64(0)
var PROOF_FRAME_RING_RESET_2 = uint64(0)
var PROOF_FRAME_COMBINE_CUTOFF = uint64(0)
const PROOF_FRAME_SENIORITY_REPAIR = 0
type processedMint struct {
isPre2 bool
penalty bool
deletedProof *protobufs.TokenOutput_DeletedProof
parallelism uint32
priorCommitment []byte
newCommitment []byte
newFrameNumber uint64
implicitAddr []byte
validForReward bool
treeVerified bool
wesoVerified bool
}
func (a *TokenApplication) preProcessMint(
currentFrameNumber uint64,
t *protobufs.MintCoinRequest,
frame *protobufs.ClockFrame,
) (
out *processedMint,
err error,
) {
if err := t.Validate(); err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "pre process mint")
}
payload := []byte("mint")
for _, p := range t.Proofs {
payload = append(payload, p...)
}
pk, err := pcrypto.UnmarshalEd448PublicKey(
t.Signature.PublicKey.KeyValue,
)
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "pre process mint")
}
peerId, err := peer.IDFromPublicKey(pk)
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "pre process mint")
}
addr, err := poseidon.HashBytes(
t.Signature.PublicKey.KeyValue,
)
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "pre process mint")
}
addrBytes := addr.FillBytes(make([]byte, 32))
altAddr, err := poseidon.HashBytes([]byte(peerId))
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "pre process mint")
}
altAddrBytes := altAddr.FillBytes(make([]byte, 32))
// todo: set termination frame for this:
if len(t.Proofs) == 1 && a.Tries[0].Contains(addrBytes) &&
bytes.Equal(t.Signature.PublicKey.KeyValue, a.Beacon) {
return &processedMint{
isPre2: true,
penalty: false,
}, nil
} else if len(t.Proofs) > 0 && currentFrameNumber > PROOF_FRAME_CUTOFF &&
currentFrameNumber < PROOF_FRAME_COMBINE_CUTOFF {
a.Logger.Debug(
"got mint from peer",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
_, prfs, err := a.CoinStore.GetPreCoinProofsForOwner(altAddrBytes)
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "pre process mint")
}
var delete *protobufs.PreCoinProof
var commitment []byte
var previousFrame *protobufs.ClockFrame
var previousParallelism uint32
for _, pr := range prfs {
if len(pr.Proof) >= 3 && (len(pr.Commitment) == 40 || len(pr.Commitment) == 72) {
delete = pr
commitment = pr.Commitment[:32]
previousFrameNumber := binary.BigEndian.Uint64(pr.Commitment[32:])
previousParallelism = binary.BigEndian.Uint32(pr.Proof[36:40])
previousFrame, _, err = a.ClockStore.GetDataClockFrame(
frame.Filter,
previousFrameNumber,
true,
)
if err != nil {
a.Logger.Debug(
"invalid frame",
zap.Error(err),
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
return &processedMint{
isPre2: false,
penalty: true,
}, nil
}
}
}
newCommitment, parallelism, newFrameNumber, verified, err :=
tries.UnpackAndVerifyOutput(commitment, t.Proofs)
if err != nil {
a.Logger.Debug(
"mint error",
zap.Error(err),
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
return &processedMint{
isPre2: false,
penalty: true,
}, nil
}
if previousParallelism != 0 && previousParallelism != parallelism {
verified = false
}
if !verified {
a.Logger.Debug(
"tree verification failed",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
}
// Current frame - 1 is because the current frame is the newly created frame,
// and the provers are submitting proofs on the frame preceding the one they
// last saw. This enforces liveness and creates a punishment for being
// late.
if (previousFrame != nil && newFrameNumber <= previousFrame.FrameNumber) ||
newFrameNumber < currentFrameNumber-1 {
previousFrameNumber := uint64(0)
if previousFrame != nil {
previousFrameNumber = previousFrame.FrameNumber
}
a.Logger.Debug(
"received out of order proofs, ignoring",
zap.Error(err),
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("previous_frame", previousFrameNumber),
zap.Uint64("new_frame", newFrameNumber),
zap.Uint64("frame_number", currentFrameNumber),
)
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
wesoVerified := true
if verified && delete != nil && len(t.Proofs) > 3 {
newFrame, _, err := a.ClockStore.GetDataClockFrame(
frame.Filter,
newFrameNumber,
true,
)
if err != nil {
a.Logger.Debug(
"invalid frame",
zap.Error(err),
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
return &processedMint{
isPre2: false,
penalty: true,
}, nil
}
hash := sha3.Sum256(newFrame.Output)
pick := tries.BytesToUnbiasedMod(hash, uint64(parallelism))
challenge := []byte{}
challenge = append(challenge, peerId...)
challenge = binary.BigEndian.AppendUint64(
challenge,
previousFrame.FrameNumber,
)
individualChallenge := append([]byte{}, challenge...)
individualChallenge = binary.BigEndian.AppendUint32(
individualChallenge,
uint32(pick),
)
leaf := t.Proofs[len(t.Proofs)-1]
individualChallenge = append(individualChallenge, previousFrame.Output...)
if len(leaf) != 516 {
a.Logger.Debug(
"invalid size",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
zap.Int("proof_size", len(leaf)),
)
return &processedMint{
isPre2: false,
penalty: true,
}, nil
}
if bytes.Equal(leaf, bytes.Repeat([]byte{0x00}, 516)) ||
!a.FrameProver.VerifyChallengeProof(
individualChallenge,
frame.Difficulty,
leaf,
) {
a.Logger.Debug(
"invalid proof",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
// we want this to still apply the next commit even if this proof failed
wesoVerified = false
}
}
var deletedProof *protobufs.TokenOutput_DeletedProof
if delete != nil {
deletedProof = &protobufs.TokenOutput_DeletedProof{
DeletedProof: delete,
}
}
validForReward := verified && delete != nil && len(t.Proofs) > 3 && wesoVerified
return &processedMint{
isPre2: false,
penalty: false,
deletedProof: deletedProof,
parallelism: parallelism,
newCommitment: newCommitment,
newFrameNumber: newFrameNumber,
implicitAddr: altAddrBytes,
validForReward: validForReward,
treeVerified: verified,
wesoVerified: wesoVerified,
}, nil
} else if len(t.Proofs) > 0 && currentFrameNumber >= PROOF_FRAME_COMBINE_CUTOFF {
a.Logger.Debug(
"got mint from peer",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
_, prfs, err := a.CoinStore.GetPreCoinProofsForOwner(altAddrBytes)
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "pre process mint")
}
var delete *protobufs.PreCoinProof
var commitment []byte
var previousFrame *protobufs.ClockFrame
var previousParallelism uint32
var priorCommitment []byte
for _, pr := range prfs {
if len(pr.Proof) >= 3 && (len(pr.Commitment) == 40 || len(pr.Commitment) == 72) {
delete = pr
commitment = pr.Commitment[:32]
previousFrameNumber := binary.BigEndian.Uint64(pr.Commitment[32:40])
previousParallelism = binary.BigEndian.Uint32(pr.Proof[36:40])
previousFrame, _, err = a.ClockStore.GetDataClockFrame(
frame.Filter,
previousFrameNumber,
true,
)
if len(pr.Commitment) > 40 {
priorCommitment = pr.Commitment[40:]
}
if err != nil {
a.Logger.Debug(
"invalid frame",
zap.Error(err),
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
return &processedMint{
isPre2: false,
penalty: true,
}, nil
}
}
}
newCommitment, parallelism, newFrameNumber, verified, err :=
tries.UnpackAndVerifyMultiOutput(commitment, t.Proofs)
if err != nil {
a.Logger.Debug(
"mint error",
zap.Error(err),
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
return &processedMint{
isPre2: false,
penalty: true,
}, nil
}
if previousParallelism != 0 && previousParallelism != parallelism {
verified = false
}
if !verified {
a.Logger.Debug(
"tree verification failed",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
}
// Current frame - 1 is because the current frame is the newly created frame,
// and the provers are submitting proofs on the frame preceding the one they
// last saw. This enforces liveness and creates a punishment for being
// late.
if (previousFrame != nil && newFrameNumber <= previousFrame.FrameNumber) ||
newFrameNumber < currentFrameNumber-1 {
previousFrameNumber := uint64(0)
if previousFrame != nil {
previousFrameNumber = previousFrame.FrameNumber
}
a.Logger.Debug(
"received out of order proofs, ignoring",
zap.Error(err),
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("previous_frame", previousFrameNumber),
zap.Uint64("new_frame", newFrameNumber),
zap.Uint64("frame_number", currentFrameNumber),
)
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
wesoVerified := true
if verified && delete != nil && len(t.Proofs) > 3 {
newFrame, _, err := a.ClockStore.GetDataClockFrame(
frame.Filter,
newFrameNumber,
true,
)
if err != nil {
a.Logger.Debug(
"invalid frame",
zap.Error(err),
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
return &processedMint{
isPre2: false,
penalty: true,
}, nil
}
hash := sha3.Sum256(append(append([]byte{}, newFrame.Output...), commitment...))
pick := tries.BytesToUnbiasedMod(hash, uint64(parallelism))
challenge := []byte{}
challenge = append(challenge, peerId...)
challenge = binary.BigEndian.AppendUint64(
challenge,
previousFrame.FrameNumber,
)
additional := bits.Len64(uint64(parallelism)-1) - 1
picks := []int{int(pick)}
outputs := [][]byte{t.Proofs[len(t.Proofs)-(additional+1)]}
for additional > 0 {
hash = sha3.Sum256(hash[:])
pick := tries.BytesToUnbiasedMod(hash, uint64(parallelism))
found := false
for _, p := range picks {
if p == int(pick) {
found = true
break
}
}
if !found {
picks = append(picks, int(pick))
outputs = append(outputs, t.Proofs[len(t.Proofs)-additional])
additional--
}
}
for i, pick := range picks {
individualChallenge := append([]byte{}, challenge...)
individualChallenge = binary.BigEndian.AppendUint32(
individualChallenge,
uint32(pick),
)
individualChallenge = append(individualChallenge, previousFrame.Output...)
individualChallenge = append(individualChallenge, priorCommitment...)
leaf := outputs[i]
if len(leaf) != 516 {
a.Logger.Debug(
"invalid size",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
zap.Int("proof_size", len(leaf)),
)
return &processedMint{
isPre2: false,
penalty: true,
}, nil
}
if bytes.Equal(leaf, bytes.Repeat([]byte{0x00}, 516)) ||
!a.FrameProver.VerifyChallengeProof(
individualChallenge,
frame.Difficulty,
leaf,
) {
a.Logger.Debug(
"invalid proof",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
// we want this to still apply the next commit even if this proof failed
wesoVerified = wesoVerified && false
}
}
}
var deletedProof *protobufs.TokenOutput_DeletedProof
if delete != nil {
deletedProof = &protobufs.TokenOutput_DeletedProof{
DeletedProof: delete,
}
}
validForReward := verified && delete != nil && len(t.Proofs) > 3 && wesoVerified
return &processedMint{
isPre2: false,
penalty: false,
deletedProof: deletedProof,
parallelism: parallelism,
priorCommitment: commitment,
newCommitment: newCommitment,
newFrameNumber: newFrameNumber,
implicitAddr: altAddrBytes,
validForReward: validForReward,
treeVerified: verified,
wesoVerified: wesoVerified,
}, nil
}
a.Logger.Debug(
"could not find case for proof",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
return nil, errors.Wrap(ErrInvalidStateTransition, "pre process mint")
}
func (a *TokenApplication) handleMint(
currentFrameNumber uint64,
t *protobufs.MintCoinRequest,
frame *protobufs.ClockFrame,
processed *processedMint,
parallelismMap map[int]uint64,
) ([]*protobufs.TokenOutput, error) {
payload := []byte("mint")
for _, p := range t.Proofs {
payload = append(payload, p...)
}
pk, err := pcrypto.UnmarshalEd448PublicKey(
t.Signature.PublicKey.KeyValue,
)
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
peerId, err := peer.IDFromPublicKey(pk)
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
altAddr, err := poseidon.HashBytes([]byte(peerId))
if err != nil {
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
altAddrBytes := altAddr.FillBytes(make([]byte, 32))
// todo: set termination frame for this:
if processed.isPre2 {
if len(t.Proofs[0]) != 64 {
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
_, pr, err := a.CoinStore.GetPreCoinProofsForOwner(t.Proofs[0][32:])
if err != nil && !errors.Is(err, store.ErrNotFound) {
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
for _, p := range pr {
if p.IndexProof == nil && bytes.Equal(p.Amount, t.Proofs[0][:32]) {
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
}
outputs := []*protobufs.TokenOutput{
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Proof{
Proof: &protobufs.PreCoinProof{
Amount: t.Proofs[0][:32],
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: t.Proofs[0][32:],
},
},
},
Proof: t.Signature.Signature,
},
},
},
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Coin{
Coin: &protobufs.Coin{
Amount: t.Proofs[0][:32],
Intersection: make([]byte, 1024),
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: t.Proofs[0][32:],
},
},
},
},
},
},
}
return outputs, nil
} else if len(t.Proofs) > 0 && currentFrameNumber > PROOF_FRAME_CUTOFF {
a.Logger.Debug(
"got mint from peer",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
ring := -1
for i, t := range a.Tries[1:] {
if t.Contains(altAddrBytes) {
ring = i
}
}
if processed.penalty {
return []*protobufs.TokenOutput{&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Penalty{
Penalty: &protobufs.ProverPenalty{
Quantity: 10,
Account: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: altAddrBytes,
},
},
},
},
},
}}, nil
}
outputs := []*protobufs.TokenOutput{}
if processed.deletedProof != nil {
outputs = append(
outputs,
&protobufs.TokenOutput{
Output: processed.deletedProof,
},
)
}
if processed.validForReward {
storage := PomwBasis(1, ring, currentFrameNumber)
m := parallelismMap[ring]
if m == 0 {
m = 1
}
storage.Quo(storage, big.NewInt(int64(m)))
storage.Mul(storage, big.NewInt(int64(processed.parallelism)))
storageBytes := storage.FillBytes(make([]byte, 32))
a.Logger.Debug(
"issued reward",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
zap.String("reward", storage.String()),
)
outputs = append(
outputs,
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Proof{
Proof: &protobufs.PreCoinProof{
Commitment: append(
binary.BigEndian.AppendUint64(
append([]byte{}, processed.newCommitment...),
processed.newFrameNumber,
),
processed.priorCommitment...,
),
Amount: storageBytes,
Proof: payload,
Difficulty: a.Difficulty,
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: altAddrBytes,
},
},
},
},
},
},
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Coin{
Coin: &protobufs.Coin{
Amount: storageBytes,
Intersection: make([]byte, 1024),
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: altAddrBytes,
},
},
},
},
},
},
)
} else {
outputs = append(
outputs,
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Proof{
Proof: &protobufs.PreCoinProof{
Commitment: append(
binary.BigEndian.AppendUint64(
append([]byte{}, processed.newCommitment...),
processed.newFrameNumber,
),
processed.priorCommitment...,
),
Proof: payload,
Difficulty: a.Difficulty,
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: altAddrBytes,
},
},
},
},
},
},
)
if !processed.wesoVerified ||
(currentFrameNumber < PROOF_FRAME_RING_RESET && !processed.treeVerified) {
outputs = append(outputs, &protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Penalty{
Penalty: &protobufs.ProverPenalty{
Quantity: 10,
Account: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: altAddrBytes,
},
},
},
},
},
})
}
}
return outputs, nil
}
a.Logger.Debug(
"could not find case for proof",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
func PomwBasis(generation uint64, ring int, currentFrameNumber uint64) *big.Int {
prec := uint(53)
one := new(big.Float).SetPrec(prec).SetInt64(1)
divisor := new(big.Float).SetPrec(prec).SetInt64(1048576)
normalized := new(big.Float).SetPrec(prec)
// A simple hack for estimating state growth in terms of frames, based on
// linear relationship of state growth:
normalized.SetInt64(int64((737280 + currentFrameNumber) / 184320))
normalized.Quo(normalized, divisor)
// 1/2^n
exp := new(big.Float).SetPrec(prec).SetInt64(1)
if generation > 0 {
powerOfTwo := new(big.Float).SetPrec(prec).SetInt64(2)
powerOfTwo.SetInt64(1)
for i := uint64(0); i < generation; i++ {
powerOfTwo.Mul(powerOfTwo, big.NewFloat(2))
}
exp.Quo(one, powerOfTwo)
}
// (d/1048576)^(1/2^n)
result := new(big.Float).Copy(normalized)
if generation > 0 {
for i := uint64(0); i < generation; i++ {
result.Sqrt(result)
}
}
// Calculate 1/result
result.Quo(one, result)
// Divide by 2^s
if ring > 0 {
divisor := new(big.Float).SetPrec(prec).SetInt64(1)
for i := 0; i < ring; i++ {
divisor.Mul(divisor, big.NewFloat(2))
}
result.Quo(result, divisor)
}
result.Mul(result, new(big.Float).SetPrec(prec).SetInt64(8000000000))
out, _ := result.Int(new(big.Int))
return out
}