2024-02-13 07:04:56 +00:00
|
|
|
|
package time_test
|
|
|
|
|
|
|
|
|
|
import (
|
2024-02-21 08:10:23 +00:00
|
|
|
|
"bytes"
|
2024-02-13 07:04:56 +00:00
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"testing"
|
2024-02-16 09:42:37 +00:00
|
|
|
|
gotime "time"
|
2024-02-13 07:04:56 +00:00
|
|
|
|
|
|
|
|
|
"github.com/cloudflare/circl/sign/ed448"
|
|
|
|
|
"github.com/iden3/go-iden3-crypto/poseidon"
|
|
|
|
|
"github.com/libp2p/go-libp2p/core/crypto"
|
|
|
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/node/consensus/time"
|
|
|
|
|
qcrypto "source.quilibrium.com/quilibrium/monorepo/node/crypto"
|
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/node/keys"
|
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/node/store"
|
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/node/tries"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func generateTestProvers() (
|
|
|
|
|
keys.KeyManager,
|
|
|
|
|
[]peer.ID,
|
|
|
|
|
[][]byte,
|
|
|
|
|
[][]byte,
|
|
|
|
|
map[string]string,
|
|
|
|
|
*tries.RollingFrecencyCritbitTrie,
|
|
|
|
|
) {
|
|
|
|
|
keyManager := keys.NewInMemoryKeyManager()
|
|
|
|
|
peers := []peer.ID{}
|
|
|
|
|
pubKeys := [][]byte{}
|
|
|
|
|
privKeys := [][]byte{}
|
|
|
|
|
addrMap := map[string]string{}
|
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
|
keyManager.CreateSigningKey(
|
|
|
|
|
fmt.Sprintf("test-key-%d", i),
|
|
|
|
|
keys.KeyTypeEd448,
|
|
|
|
|
)
|
|
|
|
|
k, err := keyManager.GetRawKey(fmt.Sprintf("test-key-%d", i))
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
privKey, err := crypto.UnmarshalEd448PrivateKey([]byte(k.PrivateKey))
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
privKeys = append(privKeys, []byte(k.PrivateKey))
|
|
|
|
|
|
|
|
|
|
pub := privKey.GetPublic()
|
|
|
|
|
id, err := peer.IDFromPublicKey(pub)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
peers = append(peers, id)
|
|
|
|
|
|
|
|
|
|
keyManager.CreateSigningKey(
|
|
|
|
|
fmt.Sprintf("proving-key-%d", i),
|
|
|
|
|
keys.KeyTypeEd448,
|
|
|
|
|
)
|
|
|
|
|
pk, err := keyManager.GetRawKey(fmt.Sprintf("proving-key-%d", i))
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pprivKey, err := crypto.UnmarshalEd448PrivateKey([]byte(pk.PrivateKey))
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ppub := pprivKey.GetPublic()
|
|
|
|
|
ppubKey, err := ppub.Raw()
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pubKeys = append(pubKeys, ppubKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proverTrie := &tries.RollingFrecencyCritbitTrie{}
|
|
|
|
|
|
|
|
|
|
for i, s := range pubKeys {
|
|
|
|
|
addr, err := poseidon.HashBytes(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addrBytes := addr.Bytes()
|
|
|
|
|
addrBytes = append(make([]byte, 32-len(addrBytes)), addrBytes...)
|
|
|
|
|
proverTrie.Add(addrBytes, 0)
|
|
|
|
|
addrMap[string(addrBytes)] = fmt.Sprintf("proving-key-%d", i)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return keyManager,
|
|
|
|
|
peers,
|
|
|
|
|
pubKeys,
|
|
|
|
|
privKeys,
|
|
|
|
|
addrMap,
|
|
|
|
|
proverTrie
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDataTimeReel(t *testing.T) {
|
2024-03-04 03:20:24 +00:00
|
|
|
|
logger, _ := zap.NewDevelopment()
|
2024-02-13 07:04:56 +00:00
|
|
|
|
db := store.NewInMemKVDB()
|
|
|
|
|
clockStore := store.NewPebbleClockStore(db, logger)
|
|
|
|
|
prover := qcrypto.NewWesolowskiFrameProver(logger)
|
|
|
|
|
filter := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
|
|
|
|
keyManager,
|
|
|
|
|
_,
|
|
|
|
|
pubKeys,
|
|
|
|
|
_,
|
|
|
|
|
addrMap,
|
|
|
|
|
proverTrie := generateTestProvers()
|
|
|
|
|
|
2024-02-16 09:42:37 +00:00
|
|
|
|
// We're going to set this up by churning 40 master frames so we don't
|
2024-02-13 07:04:56 +00:00
|
|
|
|
// have to zig zag on master and data frames to confirm data time reel
|
|
|
|
|
// behaviors
|
|
|
|
|
m := time.NewMasterTimeReel(
|
|
|
|
|
logger,
|
|
|
|
|
clockStore,
|
|
|
|
|
&config.EngineConfig{
|
|
|
|
|
Filter: filter,
|
|
|
|
|
GenesisSeed: strings.Repeat("00", 516),
|
|
|
|
|
Difficulty: 10,
|
|
|
|
|
},
|
|
|
|
|
prover,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
err := m.Start()
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
frame, err := m.Head()
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
frames := []*protobufs.ClockFrame{}
|
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
frameCh := m.NewFrameCh()
|
|
|
|
|
go func() {
|
2024-02-16 09:42:37 +00:00
|
|
|
|
for i := 0; i < 40; i++ {
|
2024-02-13 07:04:56 +00:00
|
|
|
|
frames = append(frames, <-frameCh)
|
|
|
|
|
}
|
|
|
|
|
wg.Done()
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// in order
|
2024-02-16 09:42:37 +00:00
|
|
|
|
for i := int64(0); i < 40; i++ {
|
2024-02-13 07:04:56 +00:00
|
|
|
|
frame, err = prover.ProveMasterClockFrame(frame, i+1, 10)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
2024-03-17 21:14:37 +00:00
|
|
|
|
err := m.Insert(frame, false)
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
|
2024-02-16 09:42:37 +00:00
|
|
|
|
for i := 0; i < 40; i++ {
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NotNil(t, frames[i])
|
|
|
|
|
assert.Equal(t, frames[i].FrameNumber, uint64(i+1))
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-16 09:42:37 +00:00
|
|
|
|
filterBytes := []byte{
|
|
|
|
|
0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
|
0xff, 0xff,
|
|
|
|
|
}
|
2024-02-13 07:04:56 +00:00
|
|
|
|
|
|
|
|
|
// Ok, now onto the data time reel. We're going to test the following
|
|
|
|
|
// scenarios:
|
|
|
|
|
// 1. Z-dist optimal, in order
|
|
|
|
|
// 2. Z-dist optimal, out of order
|
|
|
|
|
// 3. 90% optimal, out of order
|
|
|
|
|
// 4. Malicious majority, out of order
|
|
|
|
|
d := time.NewDataTimeReel(
|
|
|
|
|
filterBytes,
|
|
|
|
|
logger,
|
|
|
|
|
clockStore,
|
|
|
|
|
&config.EngineConfig{
|
|
|
|
|
Filter: filter,
|
|
|
|
|
GenesisSeed: strings.Repeat("00", 516),
|
|
|
|
|
Difficulty: 10,
|
|
|
|
|
},
|
|
|
|
|
prover,
|
|
|
|
|
frames[0].Output,
|
|
|
|
|
&qcrypto.InclusionAggregateProof{
|
|
|
|
|
InclusionCommitments: []*qcrypto.InclusionCommitment{},
|
|
|
|
|
AggregateCommitment: []byte{},
|
|
|
|
|
Proof: []byte{},
|
|
|
|
|
},
|
|
|
|
|
pubKeys,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
err = d.Start()
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
frame, err = d.Head()
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
dataFrames := []*protobufs.ClockFrame{}
|
|
|
|
|
datawg := sync.WaitGroup{}
|
|
|
|
|
datawg.Add(1)
|
|
|
|
|
dataFrameCh := d.NewFrameCh()
|
2024-02-21 08:10:23 +00:00
|
|
|
|
targetFrameParentSelector := []byte{}
|
2024-02-13 07:04:56 +00:00
|
|
|
|
go func() {
|
2024-02-21 08:10:23 +00:00
|
|
|
|
for {
|
|
|
|
|
frame := <-dataFrameCh
|
|
|
|
|
dataFrames = append(dataFrames, frame)
|
|
|
|
|
if frame.FrameNumber == 40 && bytes.Equal(
|
|
|
|
|
frame.ParentSelector,
|
|
|
|
|
targetFrameParentSelector,
|
|
|
|
|
) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-13 07:04:56 +00:00
|
|
|
|
}
|
|
|
|
|
datawg.Done()
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// 1. z-dist optimal – proof submission is strictly master-frame evoked leader
|
|
|
|
|
for i := int64(0); i < 10; i++ {
|
2024-02-16 09:42:37 +00:00
|
|
|
|
masterSelector, err := frames[i].GetSelector()
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
proverSelection := proverTrie.FindNearest(
|
|
|
|
|
masterSelector.FillBytes(make([]byte, 32)),
|
|
|
|
|
)
|
|
|
|
|
optimalSigner, _ := keyManager.GetSigningKey(
|
|
|
|
|
addrMap[string(proverSelection.External.Key)],
|
|
|
|
|
)
|
|
|
|
|
frame, err = prover.ProveDataClockFrame(
|
|
|
|
|
frame,
|
|
|
|
|
[][]byte{},
|
|
|
|
|
[]*protobufs.InclusionAggregateProof{},
|
|
|
|
|
optimalSigner,
|
|
|
|
|
i+1,
|
|
|
|
|
10,
|
|
|
|
|
)
|
2024-03-17 21:14:37 +00:00
|
|
|
|
d.Insert(frame, false)
|
2024-02-13 07:04:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. z-dist optimal, out of order – proof submission is strictly master-frame
|
|
|
|
|
// evoked leader, but arrived completely backwards
|
|
|
|
|
insertFrames := []*protobufs.ClockFrame{}
|
|
|
|
|
|
|
|
|
|
for i := int64(10); i < 20; i++ {
|
2024-02-16 09:42:37 +00:00
|
|
|
|
masterSelector, err := frames[i].GetSelector()
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
proverSelection := proverTrie.FindNearest(
|
|
|
|
|
masterSelector.FillBytes(make([]byte, 32)),
|
|
|
|
|
)
|
|
|
|
|
optimalSigner, _ := keyManager.GetSigningKey(
|
|
|
|
|
addrMap[string(proverSelection.External.Key)],
|
|
|
|
|
)
|
|
|
|
|
frame, err = prover.ProveDataClockFrame(
|
|
|
|
|
frame,
|
|
|
|
|
[][]byte{},
|
|
|
|
|
[]*protobufs.InclusionAggregateProof{},
|
|
|
|
|
optimalSigner,
|
|
|
|
|
i+1,
|
|
|
|
|
10,
|
|
|
|
|
)
|
|
|
|
|
insertFrames = append(insertFrames, frame)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 9; i >= 0; i-- {
|
2024-03-17 21:14:37 +00:00
|
|
|
|
err := d.Insert(insertFrames[i], false)
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 90% optimal, out of order
|
|
|
|
|
insertFrames = []*protobufs.ClockFrame{}
|
|
|
|
|
|
|
|
|
|
for i := int64(20); i < 25; i++ {
|
2024-02-16 09:42:37 +00:00
|
|
|
|
masterSelector, err := frames[i].GetSelector()
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
proverSelection := proverTrie.FindNearest(
|
|
|
|
|
masterSelector.FillBytes(make([]byte, 32)),
|
|
|
|
|
)
|
|
|
|
|
optimalSigner, _ := keyManager.GetSigningKey(
|
|
|
|
|
addrMap[string(proverSelection.External.Key)],
|
|
|
|
|
)
|
|
|
|
|
frame, err = prover.ProveDataClockFrame(
|
|
|
|
|
frame,
|
|
|
|
|
[][]byte{},
|
|
|
|
|
[]*protobufs.InclusionAggregateProof{},
|
|
|
|
|
optimalSigner,
|
|
|
|
|
i+1,
|
|
|
|
|
10,
|
|
|
|
|
)
|
2024-03-17 21:14:37 +00:00
|
|
|
|
d.Insert(frame, false)
|
2024-02-13 07:04:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-16 09:42:37 +00:00
|
|
|
|
masterSelector, err := frames[25].GetSelector()
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
proverSelections := proverTrie.FindNearestAndApproximateNeighbors(
|
|
|
|
|
masterSelector.FillBytes(make([]byte, 32)),
|
|
|
|
|
)
|
|
|
|
|
suboptimalSigner2, _ := keyManager.GetSigningKey(
|
|
|
|
|
addrMap[string(proverSelections[2].External.Key)],
|
|
|
|
|
)
|
|
|
|
|
// What we're trying to simulate: consensus heads progressed on a slightly
|
|
|
|
|
// less optimal prover.
|
|
|
|
|
frame, err = prover.ProveDataClockFrame(
|
|
|
|
|
frame,
|
|
|
|
|
[][]byte{},
|
|
|
|
|
[]*protobufs.InclusionAggregateProof{},
|
|
|
|
|
suboptimalSigner2,
|
|
|
|
|
26,
|
|
|
|
|
10,
|
|
|
|
|
)
|
|
|
|
|
insertFrames = append(insertFrames, frame)
|
|
|
|
|
|
|
|
|
|
for i := int64(26); i < 30; i++ {
|
2024-02-16 09:42:37 +00:00
|
|
|
|
masterSelector, err := frames[i].GetSelector()
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
proverSelection := proverTrie.FindNearest(
|
|
|
|
|
masterSelector.FillBytes(make([]byte, 32)),
|
|
|
|
|
)
|
|
|
|
|
optimalSigner, _ := keyManager.GetSigningKey(
|
|
|
|
|
addrMap[string(proverSelection.External.Key)],
|
|
|
|
|
)
|
|
|
|
|
frame, err = prover.ProveDataClockFrame(
|
|
|
|
|
frame,
|
|
|
|
|
[][]byte{},
|
|
|
|
|
[]*protobufs.InclusionAggregateProof{},
|
|
|
|
|
optimalSigner,
|
|
|
|
|
i+1,
|
|
|
|
|
10,
|
|
|
|
|
)
|
|
|
|
|
insertFrames = append(insertFrames, frame)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 4; i >= 0; i-- {
|
2024-03-17 21:14:37 +00:00
|
|
|
|
err := d.Insert(insertFrames[i], false)
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Malicious majority, out of order – handle a suppressive majority and
|
|
|
|
|
// force consensus on the lowest distance sub-tree:
|
|
|
|
|
insertFrames = []*protobufs.ClockFrame{}
|
|
|
|
|
conflictFrames := []*protobufs.ClockFrame{}
|
|
|
|
|
optimalKeySet := [][]byte{}
|
|
|
|
|
suppressedFrame := frame
|
|
|
|
|
for i := int64(30); i < 40; i++ {
|
2024-02-16 09:42:37 +00:00
|
|
|
|
masterSelector, err := frames[i].GetSelector()
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
proverSelections := proverTrie.FindNearestAndApproximateNeighbors(
|
|
|
|
|
masterSelector.FillBytes(make([]byte, 32)),
|
|
|
|
|
)
|
|
|
|
|
optimalSigner, _ := keyManager.GetSigningKey(
|
|
|
|
|
addrMap[string(proverSelections[0].External.Key)],
|
|
|
|
|
)
|
|
|
|
|
suboptimalSigner2, _ := keyManager.GetSigningKey(
|
|
|
|
|
addrMap[string(proverSelections[2].External.Key)],
|
|
|
|
|
)
|
|
|
|
|
optimalKeySet = append(optimalKeySet, []byte(
|
|
|
|
|
(optimalSigner.Public()).(ed448.PublicKey),
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
// What we're trying to simulate: the majority is intentionally ignoring
|
|
|
|
|
// the most optimal signer
|
2024-02-16 09:42:37 +00:00
|
|
|
|
suppressedFrame, err = prover.ProveDataClockFrame(
|
2024-02-13 07:04:56 +00:00
|
|
|
|
suppressedFrame,
|
|
|
|
|
[][]byte{},
|
|
|
|
|
[]*protobufs.InclusionAggregateProof{},
|
|
|
|
|
optimalSigner,
|
|
|
|
|
i+1,
|
|
|
|
|
10,
|
|
|
|
|
)
|
|
|
|
|
insertFrames = append(insertFrames, suppressedFrame)
|
2024-02-21 08:10:23 +00:00
|
|
|
|
if i == 39 {
|
|
|
|
|
targetFrameParentSelector = suppressedFrame.ParentSelector
|
|
|
|
|
}
|
2024-02-13 07:04:56 +00:00
|
|
|
|
frame, err = prover.ProveDataClockFrame(
|
|
|
|
|
frame,
|
|
|
|
|
[][]byte{},
|
|
|
|
|
[]*protobufs.InclusionAggregateProof{},
|
|
|
|
|
suboptimalSigner2,
|
|
|
|
|
i+1,
|
|
|
|
|
10,
|
|
|
|
|
)
|
|
|
|
|
conflictFrames = append(conflictFrames, frame)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 9; i >= 0; i-- {
|
2024-03-17 21:14:37 +00:00
|
|
|
|
err := d.Insert(conflictFrames[i], false)
|
2024-02-16 09:42:37 +00:00
|
|
|
|
// force linear ordering
|
|
|
|
|
gotime.Sleep(1 * gotime.Second)
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-16 09:42:37 +00:00
|
|
|
|
// Someone is honest, but running backwards:
|
2024-02-13 07:04:56 +00:00
|
|
|
|
for i := 9; i >= 0; i-- {
|
2024-03-17 21:14:37 +00:00
|
|
|
|
err := d.Insert(insertFrames[i], false)
|
2024-02-16 09:42:37 +00:00
|
|
|
|
gotime.Sleep(1 * gotime.Second)
|
2024-02-13 07:04:56 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
datawg.Wait()
|
|
|
|
|
|
2024-02-21 08:10:23 +00:00
|
|
|
|
assert.Equal(t, uint64(40), dataFrames[len(dataFrames)-1].FrameNumber)
|
|
|
|
|
assert.Equal(
|
|
|
|
|
t,
|
|
|
|
|
optimalKeySet[len(optimalKeySet)-1],
|
|
|
|
|
dataFrames[len(dataFrames)-1].GetPublicKeySignatureEd448().PublicKey.KeyValue,
|
|
|
|
|
)
|
2024-02-13 07:04:56 +00:00
|
|
|
|
}
|