v2.0.3-b2

This commit is contained in:
Cassandra Heart 2024-11-06 03:51:30 -06:00
parent e469fad46a
commit f3e502a2d1
No known key found for this signature in database
GPG Key ID: 6352152859385958
12 changed files with 658 additions and 146 deletions

View File

@ -137,7 +137,7 @@ var unlock *SignedGenesisUnlock
func DownloadAndVerifyGenesis(network uint) (*SignedGenesisUnlock, error) {
if network != 0 {
unlock = &SignedGenesisUnlock{
GenesisSeedHex: "726573697374206d7563682c206f626579206c6974746c657c000000000000000000000007",
GenesisSeedHex: "726573697374206d7563682c206f626579206c6974746c657c000000000000000000000008",
Beacon: []byte{
0x58, 0xef, 0xd9, 0x7e, 0xdd, 0x0e, 0xb6, 0x2f,
0x51, 0xc7, 0x5d, 0x00, 0x29, 0x12, 0x45, 0x49,

View File

@ -40,5 +40,5 @@ func GetPatchNumber() byte {
}
func GetRCNumber() byte {
return 0x01
return 0x02
}

View File

@ -90,7 +90,7 @@ func (e *DataClockConsensusEngine) prove(
var validTransactions *protobufs.TokenRequests
var invalidTransactions *protobufs.TokenRequests
app, validTransactions, invalidTransactions, err = app.ApplyTransitions(
previousFrame.FrameNumber,
previousFrame.FrameNumber+1,
e.stagedTransactions,
true,
)

View File

@ -18,6 +18,7 @@ import (
"github.com/multiformats/go-multiaddr"
mn "github.com/multiformats/go-multiaddr/net"
"github.com/pkg/errors"
mt "github.com/txaty/go-merkletree"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@ -98,6 +99,7 @@ type DataClockConsensusEngine struct {
statsClient protobufs.NodeStatsClient
currentReceivingSyncPeersMx sync.Mutex
currentReceivingSyncPeers int
announcedJoin int
beaconPeerId []byte
frameChan chan *protobufs.ClockFrame
@ -544,6 +546,8 @@ func (e *DataClockConsensusEngine) Start() <-chan error {
}
}
var previousTree *mt.MerkleTree
for e.state < consensus.EngineStateStopping {
nextFrame, err := e.dataTimeReel.Head()
if err != nil {
@ -551,12 +555,17 @@ func (e *DataClockConsensusEngine) Start() <-chan error {
}
if frame.FrameNumber == nextFrame.FrameNumber {
time.Sleep(5 * time.Second)
time.Sleep(1 * time.Second)
continue
}
if nextFrame.Timestamp < time.Now().UnixMilli()-30000 {
time.Sleep(1 * time.Second)
continue
}
frame = nextFrame
_, tries, err := e.clockStore.GetDataClockFrame(
_, triesAtFrame, err := e.clockStore.GetDataClockFrame(
e.filter,
frame.FrameNumber,
false,
@ -565,10 +574,40 @@ func (e *DataClockConsensusEngine) Start() <-chan error {
panic(err)
}
for i, trie := range tries[1:] {
modulo := len(clients)
for i, trie := range triesAtFrame[1:] {
if trie.Contains(peerProvingKeyAddress) {
e.logger.Info("creating data shard ring proof", zap.Int("ring", i))
e.PerformTimeProof(frame, frame.Difficulty, clients)
outputs := e.PerformTimeProof(frame, frame.Difficulty, clients)
proofTree, payload, output := tries.PackOutputIntoPayloadAndProof(
outputs,
modulo,
frame,
previousTree,
)
previousTree = proofTree
sig, err := e.pubSub.SignMessage(
payload,
)
if err != nil {
panic(err)
}
e.publishMessage(e.txFilter, &protobufs.TokenRequest{
Request: &protobufs.TokenRequest_Mint{
Mint: &protobufs.MintCoinRequest{
Proofs: output,
Signature: &protobufs.Ed448Signature{
PublicKey: &protobufs.Ed448PublicKey{
KeyValue: e.pubSub.GetPublicKey(),
},
Signature: sig,
},
},
},
})
}
}
}
@ -581,10 +620,10 @@ func (e *DataClockConsensusEngine) PerformTimeProof(
frame *protobufs.ClockFrame,
difficulty uint32,
clients []protobufs.DataIPCServiceClient,
) []byte {
) []mt.DataBlock {
wg := sync.WaitGroup{}
wg.Add(len(clients))
output := make([][]byte, len(clients))
output := make([]mt.DataBlock, len(clients))
for i, client := range clients {
i := i
client := client
@ -660,37 +699,18 @@ func (e *DataClockConsensusEngine) PerformTimeProof(
continue
}
output[i] = resp.Output
output[i] = tries.NewProofLeaf(resp.Output)
break
}
if output[i] == nil {
output[i] = tries.NewProofLeaf([]byte{})
}
wg.Done()
}()
}
wg.Wait()
payload := []byte("mint")
for _, out := range output {
payload = append(payload, out...)
}
sig, err := e.pubSub.SignMessage(
payload,
)
if err != nil {
panic(err)
}
e.publishMessage(e.txFilter, &protobufs.TokenRequest{
Request: &protobufs.TokenRequest_Mint{
Mint: &protobufs.MintCoinRequest{
Proofs: output,
Signature: &protobufs.Ed448Signature{
PublicKey: &protobufs.Ed448PublicKey{
KeyValue: e.pubSub.GetPublicKey(),
},
Signature: sig,
},
},
},
})
return []byte{}
return output
}
func (e *DataClockConsensusEngine) Stop(force bool) <-chan error {

View File

@ -118,20 +118,9 @@ func (e *DataClockConsensusEngine) processFrame(
return nextFrame
} else {
_, tries, err := e.clockStore.GetDataClockFrame(
e.filter,
latestFrame.FrameNumber,
false,
)
if err != nil {
e.logger.Error("error while fetching frame", zap.Error(err))
return latestFrame
}
found := false
for _, trie := range tries[1:] {
found = found || trie.Contains(e.pubSub.GetPeerID())
}
if !found && dataFrame.Timestamp > time.Now().UnixMilli()-30000 {
e.announcedJoin++
if e.announcedJoin < 5 && !e.IsInProverTrie(e.pubSub.GetPeerID()) &&
dataFrame.Timestamp > time.Now().UnixMilli()-30000 {
e.logger.Info("announcing prover join")
e.announceProverJoin()
}

View File

@ -3,6 +3,7 @@ package application
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"github.com/iden3/go-iden3-crypto/poseidon"
@ -15,6 +16,7 @@ import (
"source.quilibrium.com/quilibrium/monorepo/node/crypto"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
"source.quilibrium.com/quilibrium/monorepo/node/store"
"source.quilibrium.com/quilibrium/monorepo/node/tries"
)
func (a *TokenApplication) handleMint(
@ -147,52 +149,94 @@ func (a *TokenApplication) handleMint(
)
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
challenge := []byte{}
challenge = append(challenge, peerId...)
challenge = binary.BigEndian.AppendUint64(
challenge,
currentFrameNumber-1,
)
digest := make([]byte, 128)
s := sha3.NewShake256()
pubkey, _ := pk.Raw()
s.Write(pubkey)
_, err = s.Read(digest)
_, prfs, err := a.CoinStore.GetPreCoinProofsForOwner(
altAddr.FillBytes(make([]byte, 32)),
)
if err != nil {
panic(err)
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
outputs := []*protobufs.TokenOutput{}
proofs := []byte{}
hits := 0
var delete *protobufs.PreCoinProof
var commitment []byte
var previousFrame *protobufs.ClockFrame
for _, pr := range prfs {
if len(pr.Proof) >= 3 && len(pr.Commitment) == 40 {
delete = pr
commitment = pr.Commitment[:32]
previousFrameNumber := binary.BigEndian.Uint64(pr.Commitment[32:])
previousFrame, _, err = a.ClockStore.GetDataClockFrame(
frame.Filter,
previousFrameNumber,
false,
)
for i, p := range t.Proofs {
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 nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
}
}
newCommitment, parallelism, newFrame, 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 nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
if !verified {
a.Logger.Debug(
"tree verification failed",
zap.String("peer_id", base58.Encode([]byte(peerId))),
zap.Uint64("frame_number", currentFrameNumber),
)
}
if verified && delete != nil && len(t.Proofs) > 3 {
hash := sha3.Sum256(previousFrame.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(i),
uint32(pick),
)
individualChallenge = append(individualChallenge, frame.Output...)
if len(p) != 516 {
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(p)),
zap.Int("proof_size", len(leaf)),
)
continue
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
hits++
wesoProver := crypto.NewWesolowskiFrameProver(a.Logger)
if !wesoProver.VerifyChallengeProof(
individualChallenge,
frame.Difficulty,
p,
) {
fmt.Printf("%x\n", individualChallenge)
if bytes.Equal(leaf, bytes.Repeat([]byte{0x00}, 516)) ||
!wesoProver.VerifyChallengeProof(
individualChallenge,
frame.Difficulty,
leaf,
) {
a.Logger.Debug(
"invalid proof",
zap.String("peer_id", base58.Encode([]byte(peerId))),
@ -200,71 +244,104 @@ func (a *TokenApplication) handleMint(
)
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
proofs = append(proofs, p...)
}
if hits == 0 {
outputs := []*protobufs.TokenOutput{}
if delete != nil {
outputs = append(
outputs,
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_DeletedProof{
DeletedProof: delete,
},
},
)
}
if verified && delete != nil && len(t.Proofs) > 3 {
ringFactor := big.NewInt(2)
ringFactor.Exp(ringFactor, big.NewInt(int64(ring)), nil)
// const for testnet
storage := big.NewInt(int64(256 * parallelism))
unitFactor := big.NewInt(8000000000)
storage.Mul(storage, unitFactor)
storage.Quo(storage, big.NewInt(proverSet))
storage.Quo(storage, ringFactor)
a.Logger.Debug(
"no proofs",
"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: binary.BigEndian.AppendUint64(
append([]byte{}, newCommitment...),
newFrame,
),
Amount: storage.FillBytes(make([]byte, 32)),
Proof: payload,
Difficulty: a.Difficulty,
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: altAddr.FillBytes(make([]byte, 32)),
},
},
},
},
},
},
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Coin{
Coin: &protobufs.Coin{
Amount: storage.FillBytes(make([]byte, 32)),
Intersection: make([]byte, 1024),
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: altAddr.FillBytes(make([]byte, 32)),
},
},
},
},
},
},
)
} else {
outputs = append(
outputs,
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Proof{
Proof: &protobufs.PreCoinProof{
Commitment: binary.BigEndian.AppendUint64(
append([]byte{}, newCommitment...),
newFrame,
),
Proof: payload,
Difficulty: a.Difficulty,
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: altAddr.FillBytes(make([]byte, 32)),
},
},
},
},
},
},
)
return nil, errors.Wrap(ErrInvalidStateTransition, "handle mint")
}
ringFactor := big.NewInt(2)
ringFactor.Exp(ringFactor, big.NewInt(int64(ring)), nil)
storage := big.NewInt(int64(512 * hits))
unitFactor := big.NewInt(8000000000)
storage.Mul(storage, unitFactor)
storage.Quo(storage, big.NewInt(proverSet))
storage.Quo(storage, ringFactor)
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{
Amount: storage.FillBytes(make([]byte, 32)),
Proof: proofs,
Difficulty: a.Difficulty,
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: addr.FillBytes(make([]byte, 32)),
},
},
},
},
},
},
&protobufs.TokenOutput{
Output: &protobufs.TokenOutput_Coin{
Coin: &protobufs.Coin{
Amount: storage.FillBytes(make([]byte, 32)),
Intersection: make([]byte, 1024),
Owner: &protobufs.AccountRef{
Account: &protobufs.AccountRef_ImplicitAccount{
ImplicitAccount: &protobufs.ImplicitAccount{
ImplicitType: 0,
Address: addr.FillBytes(make([]byte, 32)),
},
},
},
},
},
},
)
lockMap[string(t.Signature.PublicKey.KeyValue)] = struct{}{}
return outputs, nil
}

View File

@ -5,6 +5,7 @@ import (
"crypto/rand"
"encoding/binary"
"encoding/hex"
"fmt"
"testing"
"time"
@ -13,8 +14,10 @@ import (
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/assert"
"github.com/txaty/go-merkletree"
"go.uber.org/zap"
qcrypto "source.quilibrium.com/quilibrium/monorepo/node/crypto"
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token"
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token/application"
"source.quilibrium.com/quilibrium/monorepo/node/p2p"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
@ -144,39 +147,45 @@ func TestHandleProverJoin(t *testing.T) {
assert.Error(t, err)
txn, _ = app.ClockStore.NewTransaction()
frame2, _ := wprover.ProveDataClockFrame(frame1, [][]byte{}, []*protobufs.InclusionAggregateProof{}, bprivKey, time.Now().UnixMilli(), 10000)
selbi, _ = frame1.GetSelector()
app.ClockStore.StageDataClockFrame(selbi.FillBytes(make([]byte, 32)), frame1, txn)
app.ClockStore.CommitDataClockFrame(frame2.Filter, 1, selbi.FillBytes(make([]byte, 32)), app.Tries, txn, false)
selbi, _ = frame2.GetSelector()
app.ClockStore.StageDataClockFrame(selbi.FillBytes(make([]byte, 32)), frame2, txn)
app.ClockStore.CommitDataClockFrame(frame2.Filter, 2, selbi.FillBytes(make([]byte, 32)), app.Tries, txn, false)
txn.Commit()
challenge := []byte{}
challenge = append(challenge, []byte(peerId)...)
challenge = binary.BigEndian.AppendUint64(
challenge,
1,
2,
)
individualChallenge := append([]byte{}, challenge...)
individualChallenge = binary.BigEndian.AppendUint32(
individualChallenge,
uint32(0),
)
individualChallenge = append(individualChallenge, frame1.Output...)
individualChallenge = append(individualChallenge, frame2.Output...)
fmt.Printf("%x\n", individualChallenge)
out, _ := wprover.CalculateChallengeProof(individualChallenge, 10000)
payload = []byte("mint")
payload = append(payload, out...)
proofTree, payload, output := tries.PackOutputIntoPayloadAndProof(
[]merkletree.DataBlock{tries.NewProofLeaf(out), tries.NewProofLeaf(make([]byte, 516))},
2,
frame2,
nil,
)
sig, _ = privKey.Sign(payload)
_, _, _, err = app.ApplyTransitions(2, &protobufs.TokenRequests{
app, success, _, err = app.ApplyTransitions(2, &protobufs.TokenRequests{
Requests: []*protobufs.TokenRequest{
&protobufs.TokenRequest{
Request: &protobufs.TokenRequest_Mint{
Mint: &protobufs.MintCoinRequest{
Proofs: [][]byte{out},
Proofs: output,
Signature: &protobufs.Ed448Signature{
Signature: sig,
PublicKey: &protobufs.Ed448PublicKey{
KeyValue: pubkey,
},
Signature: sig,
},
},
},
@ -185,4 +194,69 @@ func TestHandleProverJoin(t *testing.T) {
}, false)
assert.NoError(t, err)
assert.Len(t, success.Requests, 1)
assert.Len(t, app.TokenOutputs.Outputs, 1)
txn, _ = app.CoinStore.NewTransaction()
for i, o := range app.TokenOutputs.Outputs {
switch e := o.Output.(type) {
case *protobufs.TokenOutput_Coin:
a, err := token.GetAddressOfCoin(e.Coin, 1, uint64(i))
assert.NoError(t, err)
err = app.CoinStore.PutCoin(txn, 1, a, e.Coin)
assert.NoError(t, err)
case *protobufs.TokenOutput_DeletedCoin:
c, err := app.CoinStore.GetCoinByAddress(txn, e.DeletedCoin.Address)
assert.NoError(t, err)
err = app.CoinStore.DeleteCoin(txn, e.DeletedCoin.Address, c)
assert.NoError(t, err)
case *protobufs.TokenOutput_Proof:
a, err := token.GetAddressOfPreCoinProof(e.Proof)
assert.NoError(t, err)
err = app.CoinStore.PutPreCoinProof(txn, 1, a, e.Proof)
assert.NoError(t, err)
case *protobufs.TokenOutput_DeletedProof:
a, err := token.GetAddressOfPreCoinProof(e.DeletedProof)
assert.NoError(t, err)
c, err := app.CoinStore.GetPreCoinProofByAddress(a)
assert.NoError(t, err)
err = app.CoinStore.DeletePreCoinProof(txn, a, c)
assert.NoError(t, err)
}
}
err = txn.Commit()
txn, _ = app.ClockStore.NewTransaction()
frame3, _ := wprover.ProveDataClockFrame(frame2, [][]byte{}, []*protobufs.InclusionAggregateProof{}, bprivKey, time.Now().UnixMilli(), 10000)
selbi, _ = frame3.GetSelector()
app.ClockStore.StageDataClockFrame(selbi.FillBytes(make([]byte, 32)), frame3, txn)
app.ClockStore.CommitDataClockFrame(frame3.Filter, 1, selbi.FillBytes(make([]byte, 32)), app.Tries, txn, false)
txn.Commit()
proofTree, payload, output = tries.PackOutputIntoPayloadAndProof(
[]merkletree.DataBlock{tries.NewProofLeaf(out), tries.NewProofLeaf(make([]byte, 516))},
2,
frame3,
proofTree,
)
sig, _ = privKey.Sign(payload)
app, success, _, err = app.ApplyTransitions(3, &protobufs.TokenRequests{
Requests: []*protobufs.TokenRequest{
&protobufs.TokenRequest{
Request: &protobufs.TokenRequest_Mint{
Mint: &protobufs.MintCoinRequest{
Proofs: output,
Signature: &protobufs.Ed448Signature{
PublicKey: &protobufs.Ed448PublicKey{
KeyValue: pubkey,
},
Signature: sig,
},
},
},
},
},
}, false)
assert.NoError(t, err)
assert.Len(t, success.Requests, 1)
assert.Len(t, app.TokenOutputs.Outputs, 3)
}

View File

@ -758,7 +758,7 @@ func (e *TokenExecutionEngine) VerifyExecution(
}
a, _, _, err = a.ApplyTransitions(
parent.FrameNumber,
frame.FrameNumber,
transition,
false,
)

View File

@ -62,6 +62,7 @@ require (
github.com/pion/turn/v2 v2.1.6 // indirect
github.com/pion/webrtc/v3 v3.2.40 // indirect
github.com/rychipman/easylex v0.0.0-20160129204217-49ee7767142f // indirect
github.com/txaty/go-merkletree v0.2.2 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect

View File

@ -520,6 +520,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/txaty/go-merkletree v0.2.2 h1:K5bHDFK+Q3KK+gEJeyTOECKuIwl/LVo4CI+cm0/p34g=
github.com/txaty/go-merkletree v0.2.2/go.mod h1:w5HPEu7ubNw5LzS+91m+1/GtuZcWHKiPU3vEGi+ThJM=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=

176
node/tries/proof_leaf.go Normal file
View File

@ -0,0 +1,176 @@
package tries
import (
"encoding/binary"
"fmt"
"math"
"math/bits"
"github.com/pkg/errors"
mt "github.com/txaty/go-merkletree"
"golang.org/x/crypto/sha3"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
type ProofLeaf struct {
output []byte
}
var _ mt.DataBlock = (*ProofLeaf)(nil)
func NewProofLeaf(output []byte) *ProofLeaf {
return &ProofLeaf{output}
}
func (p *ProofLeaf) Serialize() ([]byte, error) {
return p.output, nil
}
func PackOutputIntoPayloadAndProof(
outputs []mt.DataBlock,
modulo int,
frame *protobufs.ClockFrame,
previousTree *mt.MerkleTree,
) (*mt.MerkleTree, []byte, [][]byte) {
tree, err := mt.New(
&mt.Config{
HashFunc: func(data []byte) ([]byte, error) {
hash := sha3.Sum256(data)
return hash[:], nil
},
Mode: mt.ModeProofGen,
DisableLeafHashing: true,
},
outputs,
)
if err != nil {
panic(err)
}
payload := []byte("mint")
payload = append(payload, tree.Root...)
payload = binary.BigEndian.AppendUint32(payload, uint32(modulo))
payload = binary.BigEndian.AppendUint64(payload, frame.FrameNumber)
output := [][]byte{
tree.Root,
binary.BigEndian.AppendUint32([]byte{}, uint32(modulo)),
binary.BigEndian.AppendUint64([]byte{}, frame.FrameNumber),
}
if previousTree != nil {
hash := sha3.Sum256(frame.Output)
pick := BytesToUnbiasedMod(hash, uint64(modulo))
for _, sib := range previousTree.Proofs[int(pick)].Siblings {
payload = append(payload, sib...)
output = append(output, sib)
}
payload = binary.BigEndian.AppendUint32(
payload,
previousTree.Proofs[int(pick)].Path,
)
output = append(
output,
binary.BigEndian.AppendUint32(
[]byte{},
previousTree.Proofs[int(pick)].Path,
),
)
payload = append(payload, previousTree.Leaves[int(pick)]...)
output = append(output, previousTree.Leaves[int(pick)])
}
return tree, payload, output
}
func UnpackAndVerifyOutput(
previousRoot []byte,
output [][]byte,
) (treeRoot []byte, modulo uint32, frameNumber uint64, verified bool, err error) {
if len(output) < 3 {
return nil, 0, 0, false, errors.Wrap(
fmt.Errorf("output too short, expected at least 3 elements"),
"unpack and verify output",
)
}
treeRoot = output[0]
modulo = binary.BigEndian.Uint32(output[1])
frameNumber = binary.BigEndian.Uint64(output[2])
payload := []byte("mint")
payload = append(payload, treeRoot...)
payload = binary.BigEndian.AppendUint32(payload, modulo)
payload = binary.BigEndian.AppendUint64(payload, frameNumber)
if len(output) > 3 {
numSiblings := bits.Len64(uint64(modulo) - 1)
if len(output) != 5+numSiblings {
return nil, 0, 0, false, errors.Wrap(
fmt.Errorf("invalid number of proof elements"),
"unpack and verify output",
)
}
siblings := output[3 : 3+numSiblings]
for _, sib := range siblings {
payload = append(payload, sib...)
}
pathBytes := output[3+numSiblings]
path := binary.BigEndian.Uint32(pathBytes)
payload = binary.BigEndian.AppendUint32(payload, path)
leaf := output[len(output)-1]
payload = append(payload, leaf...)
verified, err = mt.Verify(
NewProofLeaf(leaf),
&mt.Proof{
Siblings: siblings,
Path: path,
},
previousRoot,
&mt.Config{
HashFunc: func(data []byte) ([]byte, error) {
hash := sha3.Sum256(data)
return hash[:], nil
},
Mode: mt.ModeProofGen,
DisableLeafHashing: true,
},
)
if err != nil {
return nil, 0, 0, false, errors.Wrap(err, "unpack and verify output")
}
} else {
verified = true
}
return treeRoot, modulo, frameNumber, verified, nil
}
func BytesToUnbiasedMod(input [32]byte, modulus uint64) uint64 {
if modulus <= 1 {
return 0
}
hashValue := binary.BigEndian.Uint64(input[:8])
maxValid := math.MaxUint64 - (math.MaxUint64 % modulus)
result := hashValue
for result > maxValid {
offset := uint64(8)
for result > maxValid && offset <= 24 {
nextBytes := binary.BigEndian.Uint64(input[offset : offset+8])
result = (result * 31) ^ nextBytes
offset += 8
}
if result > maxValid {
result = (result * 31) ^ (result >> 32)
}
}
return result % modulus
}

View File

@ -0,0 +1,173 @@
package tries_test
import (
"crypto/rand"
"encoding/binary"
"testing"
"github.com/stretchr/testify/require"
mt "github.com/txaty/go-merkletree"
"golang.org/x/crypto/sha3"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
"source.quilibrium.com/quilibrium/monorepo/node/tries"
)
func TestPackAndVerifyOutput(t *testing.T) {
testCases := []struct {
name string
numLeaves int
modulo int
frameNum uint64
withPrev bool
}{
{
name: "Basic case without previous tree",
numLeaves: 4,
modulo: 4,
frameNum: 1,
withPrev: false,
},
{
name: "With previous tree",
numLeaves: 8,
modulo: 8,
frameNum: 2,
withPrev: true,
},
{
name: "Large tree with previous",
numLeaves: 16,
modulo: 16,
frameNum: 3,
withPrev: true,
},
{
name: "Non-power-of-2 modulo",
numLeaves: 10,
modulo: 7,
frameNum: 4,
withPrev: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
outputs := make([]mt.DataBlock, tc.numLeaves)
for i := range outputs {
data := make([]byte, 32)
binary.BigEndian.PutUint32(data, uint32(i))
outputs[i] = tries.NewProofLeaf(data)
}
frame := &protobufs.ClockFrame{
FrameNumber: tc.frameNum,
Output: make([]byte, 516),
}
rand.Read(frame.Output)
var previousTree *mt.MerkleTree
if tc.withPrev {
prevOutputs := make([]mt.DataBlock, tc.modulo)
for i := range prevOutputs {
data := make([]byte, 32)
binary.BigEndian.PutUint32(data, uint32(i))
prevOutputs[i] = tries.NewProofLeaf(data)
}
var err error
previousTree, err = mt.New(
&mt.Config{
HashFunc: func(data []byte) ([]byte, error) {
hash := sha3.Sum256(data)
return hash[:], nil
},
Mode: mt.ModeProofGen,
DisableLeafHashing: true,
},
prevOutputs,
)
require.NoError(t, err)
}
tree, payload, output := tries.PackOutputIntoPayloadAndProof(
outputs,
tc.modulo,
frame,
previousTree,
)
require.NotNil(t, tree)
require.NotEmpty(t, payload)
require.NotEmpty(t, output)
var previousRoot []byte
if previousTree != nil {
previousRoot = previousTree.Root
}
treeRoot, modulo, frameNumber, verified, err := tries.UnpackAndVerifyOutput(
previousRoot,
output,
)
require.NoError(t, err)
require.True(t, verified, "Output verification failed")
require.Equal(t, tree.Root, treeRoot, "Tree root mismatch")
require.Equal(t, uint32(tc.modulo), modulo, "Modulo mismatch")
require.Equal(t, tc.frameNum, frameNumber, "Frame number mismatch")
reconstructedPayload := []byte("mint")
reconstructedPayload = append(reconstructedPayload, treeRoot...)
reconstructedPayload = binary.BigEndian.AppendUint32(reconstructedPayload, modulo)
reconstructedPayload = binary.BigEndian.AppendUint64(reconstructedPayload, frameNumber)
if tc.withPrev {
for i := 3; i < len(output)-2; i++ {
reconstructedPayload = append(reconstructedPayload, output[i]...)
}
pathBytes := output[len(output)-2]
reconstructedPayload = append(reconstructedPayload, pathBytes...)
leafBytes := output[len(output)-1]
reconstructedPayload = append(reconstructedPayload, leafBytes...)
}
require.Equal(t, payload, reconstructedPayload, "Payload reconstruction mismatch")
if tc.withPrev {
t.Run("corrupted_proof", func(t *testing.T) {
corruptedOutput := make([][]byte, len(output))
copy(corruptedOutput, output)
if len(corruptedOutput) > 3 {
corruptedSibling := make([]byte, len(corruptedOutput[3]))
copy(corruptedSibling, corruptedOutput[3])
corruptedSibling[0] ^= 0xFF
corruptedOutput[3] = corruptedSibling
}
_, _, _, verified, err := tries.UnpackAndVerifyOutput(
previousRoot,
corruptedOutput,
)
require.False(t, verified, "Verification should fail with corrupted sibling")
require.NoError(t, err, "Unexpected error with corrupted sibling")
corruptedOutput = make([][]byte, len(output))
copy(corruptedOutput, output)
if len(corruptedOutput) > 0 {
lastIdx := len(corruptedOutput) - 1
corruptedLeaf := make([]byte, len(corruptedOutput[lastIdx]))
copy(corruptedLeaf, corruptedOutput[lastIdx])
corruptedLeaf[0] ^= 0xFF
corruptedOutput[lastIdx] = corruptedLeaf
}
_, _, _, verified, err = tries.UnpackAndVerifyOutput(
previousRoot,
corruptedOutput,
)
require.False(t, verified, "Verification should fail with corrupted leaf")
require.NoError(t, err, "Unexpected error with corrupted leaf")
})
}
})
}
}