mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
593 lines
15 KiB
Go
593 lines
15 KiB
Go
package vdf
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"slices"
|
|
|
|
"github.com/iden3/go-iden3-crypto/poseidon"
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/crypto/sha3"
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/protobufs"
|
|
qcrypto "source.quilibrium.com/quilibrium/monorepo/types/crypto"
|
|
)
|
|
|
|
type WesolowskiFrameProver struct {
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func NewCachedWesolowskiFrameProver(logger *zap.Logger) qcrypto.FrameProver {
|
|
return qcrypto.NewCachedFrameProver(NewWesolowskiFrameProver(logger))
|
|
}
|
|
|
|
func NewWesolowskiFrameProver(logger *zap.Logger) *WesolowskiFrameProver {
|
|
return &WesolowskiFrameProver{
|
|
logger,
|
|
}
|
|
}
|
|
|
|
// SetBitAtIndex sets the bit at the given index in a copy of the mask and
|
|
// returns it.
|
|
func SetBitAtIndex(mask []byte, index uint8) []byte {
|
|
byteIndex := index / 8
|
|
bitPos := index % 8
|
|
|
|
newMask := make([]byte, 32)
|
|
copy(newMask, mask)
|
|
mask = newMask
|
|
|
|
mask[byteIndex] |= 1 << bitPos
|
|
return mask
|
|
}
|
|
|
|
// GetSetBitIndices returns a slice of indices where bits are set in the mask.
|
|
func GetSetBitIndices(mask []byte) []uint8 {
|
|
var indices []uint8
|
|
for byteIdx, b := range mask {
|
|
for bitPos := 0; bitPos < 8; bitPos++ {
|
|
if b&(1<<bitPos) != 0 {
|
|
indices = append(indices, uint8(byteIdx*8+bitPos))
|
|
}
|
|
}
|
|
}
|
|
return indices
|
|
}
|
|
|
|
func (w *WesolowskiFrameProver) ProveFrameHeaderGenesis(
|
|
address []byte,
|
|
difficulty uint32,
|
|
input []byte,
|
|
feeMultiplierVote uint64,
|
|
) (*protobufs.FrameHeader, error) {
|
|
input = slices.Clone(input)
|
|
input = append(input, address...)
|
|
input = binary.BigEndian.AppendUint64(
|
|
input,
|
|
0,
|
|
)
|
|
input = binary.BigEndian.AppendUint64(input, uint64(0))
|
|
input = binary.BigEndian.AppendUint32(input, difficulty)
|
|
input = binary.BigEndian.AppendUint64(input, feeMultiplierVote)
|
|
input = append(input, make([]byte, 32)...)
|
|
|
|
b := sha3.Sum256(input)
|
|
o := WesolowskiSolve(b, difficulty)
|
|
|
|
stateRoots := make([][]byte, 4)
|
|
for i := range stateRoots {
|
|
stateRoots[i] = make([]byte, 74)
|
|
}
|
|
|
|
header := &protobufs.FrameHeader{
|
|
Address: address,
|
|
FrameNumber: 0,
|
|
Timestamp: 0,
|
|
Difficulty: difficulty,
|
|
Output: o[:],
|
|
ParentSelector: make([]byte, 32),
|
|
FeeMultiplierVote: feeMultiplierVote,
|
|
RequestsRoot: make([]byte, 74),
|
|
StateRoots: stateRoots,
|
|
}
|
|
|
|
return header, nil
|
|
}
|
|
|
|
func (w *WesolowskiFrameProver) ProveFrameHeader(
|
|
previousFrame *protobufs.FrameHeader,
|
|
address []byte,
|
|
requestsRoot []byte,
|
|
stateRoots [][]byte,
|
|
prover []byte,
|
|
provingKey qcrypto.Signer,
|
|
timestamp int64,
|
|
difficulty uint32,
|
|
feeMultiplierVote uint64,
|
|
proverIndex uint8,
|
|
) (*protobufs.FrameHeader, error) {
|
|
if previousFrame == nil {
|
|
return nil, errors.Wrap(
|
|
errors.New("missing header"),
|
|
"prove frame header",
|
|
)
|
|
}
|
|
|
|
pubkeyType := provingKey.GetType()
|
|
|
|
previousSelectorBytes := [516]byte{}
|
|
copy(previousSelectorBytes[:], previousFrame.Output[:516])
|
|
|
|
parent, err := poseidon.HashBytes(previousSelectorBytes[:])
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "prove frame header")
|
|
}
|
|
|
|
input := []byte{}
|
|
input = append(input, address...)
|
|
input = binary.BigEndian.AppendUint64(
|
|
input,
|
|
previousFrame.FrameNumber+1,
|
|
)
|
|
input = binary.BigEndian.AppendUint64(input, uint64(timestamp))
|
|
input = binary.BigEndian.AppendUint32(input, difficulty)
|
|
input = binary.BigEndian.AppendUint64(input, feeMultiplierVote)
|
|
input = append(input, parent.FillBytes(make([]byte, 32))...)
|
|
input = append(input, requestsRoot...)
|
|
|
|
for _, stateRoot := range stateRoots {
|
|
input = append(input, stateRoot...)
|
|
}
|
|
|
|
input = append(input, prover...)
|
|
|
|
b := sha3.Sum256(input)
|
|
o := WesolowskiSolve(b, difficulty)
|
|
|
|
domain := append([]byte("shard"), address...)
|
|
signature, err := provingKey.SignWithDomain(
|
|
append(append([]byte{}, b[:]...), o[:]...),
|
|
domain,
|
|
)
|
|
if err != nil {
|
|
return nil, errors.Wrap(
|
|
err,
|
|
"prove frame header",
|
|
)
|
|
}
|
|
|
|
header := &protobufs.FrameHeader{
|
|
Address: address,
|
|
FrameNumber: previousFrame.FrameNumber + 1,
|
|
Timestamp: timestamp,
|
|
Difficulty: difficulty,
|
|
Output: o[:],
|
|
ParentSelector: parent.FillBytes(make([]byte, 32)),
|
|
FeeMultiplierVote: feeMultiplierVote,
|
|
RequestsRoot: requestsRoot,
|
|
StateRoots: stateRoots,
|
|
Prover: prover,
|
|
}
|
|
|
|
switch pubkeyType {
|
|
case qcrypto.KeyTypeBLS48581G1:
|
|
fallthrough
|
|
case qcrypto.KeyTypeBLS48581G2:
|
|
header.PublicKeySignatureBls48581 = &protobufs.BLS48581AggregateSignature{
|
|
Bitmask: SetBitAtIndex(make([]byte, 32), proverIndex),
|
|
Signature: signature,
|
|
PublicKey: &protobufs.BLS48581G2PublicKey{
|
|
KeyValue: provingKey.Public().([]byte),
|
|
},
|
|
}
|
|
default:
|
|
return nil, errors.Wrap(
|
|
errors.New("unsupported proving key"),
|
|
"prove frame header",
|
|
)
|
|
}
|
|
|
|
return header, nil
|
|
}
|
|
|
|
// GetFrameSignaturePayload extracts the signature payload from a frame header
|
|
func (w *WesolowskiFrameProver) GetFrameSignaturePayload(
|
|
frame *protobufs.FrameHeader,
|
|
) ([]byte, error) {
|
|
if len(frame.ParentSelector) != 32 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid selector"),
|
|
"get frame signature payload",
|
|
)
|
|
}
|
|
|
|
if len(frame.Output) != 516 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid output"),
|
|
"get frame signature payload",
|
|
)
|
|
}
|
|
|
|
input := []byte{}
|
|
input = append(input, frame.Address...)
|
|
input = binary.BigEndian.AppendUint64(
|
|
input,
|
|
frame.FrameNumber,
|
|
)
|
|
input = binary.BigEndian.AppendUint64(input, uint64(frame.Timestamp))
|
|
input = binary.BigEndian.AppendUint32(input, frame.Difficulty)
|
|
input = binary.BigEndian.AppendUint64(input, frame.FeeMultiplierVote)
|
|
input = append(input, frame.ParentSelector...)
|
|
input = append(input, frame.RequestsRoot...)
|
|
|
|
for _, stateRoot := range frame.StateRoots {
|
|
input = append(input, stateRoot...)
|
|
}
|
|
|
|
input = append(input, frame.Prover...)
|
|
|
|
b := sha3.Sum256(input)
|
|
proof := [516]byte{}
|
|
copy(proof[:], frame.Output)
|
|
|
|
return append(append([]byte{}, b[:]...), proof[:]...), nil
|
|
}
|
|
|
|
func (w *WesolowskiFrameProver) VerifyFrameHeader(
|
|
frame *protobufs.FrameHeader,
|
|
bls qcrypto.BlsConstructor,
|
|
) ([]uint8, error) {
|
|
if len(frame.Address) == 0 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid address"),
|
|
"verify frame header",
|
|
)
|
|
}
|
|
|
|
if len(frame.RequestsRoot) != 74 && len(frame.RequestsRoot) != 64 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid requests root length"),
|
|
"verify frame header",
|
|
)
|
|
}
|
|
|
|
if len(frame.StateRoots) != 4 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid state roots count"),
|
|
"verify frame header",
|
|
)
|
|
}
|
|
|
|
for _, stateRoot := range frame.StateRoots {
|
|
if len(stateRoot) != 74 && len(stateRoot) != 64 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid state root length"),
|
|
"verify frame header",
|
|
)
|
|
}
|
|
}
|
|
|
|
if len(frame.Prover) == 0 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid prover"),
|
|
"verify frame header",
|
|
)
|
|
}
|
|
|
|
if frame.FrameNumber == 0 {
|
|
return bytes.Repeat([]uint8{0xff}, 32), nil
|
|
}
|
|
|
|
// Get the signature payload
|
|
signaturePayload, err := w.GetFrameSignaturePayload(frame)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "verify frame header")
|
|
}
|
|
|
|
// Extract the hash and proof from the signature payload
|
|
b := [32]byte{}
|
|
copy(b[:], signaturePayload[:32])
|
|
proof := [516]byte{}
|
|
copy(proof[:], signaturePayload[32:])
|
|
|
|
if !WesolowskiVerify(b, frame.Difficulty, proof) {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid proof"),
|
|
"verify frame header",
|
|
)
|
|
}
|
|
|
|
if frame.PublicKeySignatureBls48581 == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
valid, err := w.VerifyFrameHeaderSignature(frame, bls)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "verify frame header")
|
|
}
|
|
|
|
if !valid {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid signature"),
|
|
"verify frame header",
|
|
)
|
|
}
|
|
|
|
return GetSetBitIndices(frame.PublicKeySignatureBls48581.Bitmask), nil
|
|
}
|
|
|
|
func (w *WesolowskiFrameProver) VerifyFrameHeaderSignature(
|
|
frame *protobufs.FrameHeader,
|
|
bls qcrypto.BlsConstructor,
|
|
) (bool, error) {
|
|
// Get the signature payload
|
|
signaturePayload, err := w.GetFrameSignaturePayload(frame)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "verify frame header signature")
|
|
}
|
|
|
|
domain := append([]byte("shard"), frame.Address...)
|
|
if !bls.VerifySignatureRaw(
|
|
frame.PublicKeySignatureBls48581.PublicKey.KeyValue,
|
|
frame.PublicKeySignatureBls48581.Signature,
|
|
signaturePayload,
|
|
domain,
|
|
) {
|
|
return false, errors.Wrap(
|
|
errors.New("invalid signature"),
|
|
"verify frame header signature",
|
|
)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (w *WesolowskiFrameProver) ProveGlobalFrameHeader(
|
|
previousFrame *protobufs.GlobalFrameHeader,
|
|
commitments [][]byte,
|
|
proverRoot []byte,
|
|
stagedRoot []byte,
|
|
deploymentsRoot []byte,
|
|
provingKey qcrypto.Signer,
|
|
timestamp int64,
|
|
difficulty uint32,
|
|
proverIndex uint8,
|
|
) (*protobufs.GlobalFrameHeader, error) {
|
|
if previousFrame == nil {
|
|
return nil, errors.Wrap(
|
|
errors.New("missing header"),
|
|
"prove global frame header",
|
|
)
|
|
}
|
|
|
|
pubkeyType := provingKey.GetType()
|
|
|
|
previousSelectorBytes := [516]byte{}
|
|
copy(previousSelectorBytes[:], previousFrame.Output[:516])
|
|
|
|
parent, err := poseidon.HashBytes(previousSelectorBytes[:])
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "prove global frame header")
|
|
}
|
|
|
|
input := []byte{}
|
|
input = binary.BigEndian.AppendUint64(
|
|
input,
|
|
previousFrame.FrameNumber+1,
|
|
)
|
|
input = binary.BigEndian.AppendUint64(input, uint64(timestamp))
|
|
input = binary.BigEndian.AppendUint32(input, difficulty)
|
|
input = append(input, parent.FillBytes(make([]byte, 32))...)
|
|
|
|
for _, commitment := range commitments {
|
|
input = append(input, commitment...)
|
|
}
|
|
|
|
input = append(input, proverRoot...)
|
|
input = append(input, stagedRoot...)
|
|
input = append(input, deploymentsRoot...)
|
|
|
|
b := sha3.Sum256(input)
|
|
o := WesolowskiSolve(b, difficulty)
|
|
|
|
signature, err := provingKey.SignWithDomain(
|
|
append(append([]byte{}, b[:]...), o[:]...),
|
|
[]byte("global"),
|
|
)
|
|
if err != nil {
|
|
return nil, errors.Wrap(
|
|
err,
|
|
"prove global frame header",
|
|
)
|
|
}
|
|
|
|
header := &protobufs.GlobalFrameHeader{
|
|
FrameNumber: previousFrame.FrameNumber + 1,
|
|
Timestamp: timestamp,
|
|
Difficulty: difficulty,
|
|
Output: o[:],
|
|
ParentSelector: parent.FillBytes(make([]byte, 32)),
|
|
GlobalCommitments: commitments,
|
|
GlobalProverTreeCommitment: proverRoot,
|
|
GlobalProverTreeStagedCommitment: stagedRoot,
|
|
AppShardDeploymentsCommitment: deploymentsRoot,
|
|
}
|
|
|
|
switch pubkeyType {
|
|
case qcrypto.KeyTypeBLS48581G1:
|
|
fallthrough
|
|
case qcrypto.KeyTypeBLS48581G2:
|
|
header.PublicKeySignatureBls48581 = &protobufs.BLS48581AggregateSignature{
|
|
Bitmask: SetBitAtIndex(make([]byte, 32), proverIndex),
|
|
Signature: signature,
|
|
PublicKey: &protobufs.BLS48581G2PublicKey{
|
|
KeyValue: provingKey.Public().([]byte),
|
|
},
|
|
}
|
|
default:
|
|
return nil, errors.Wrap(
|
|
errors.New("unsupported proving key"),
|
|
"prove global frame header",
|
|
)
|
|
}
|
|
|
|
return header, nil
|
|
}
|
|
|
|
// GetGlobalFrameSignaturePayload extracts the signature payload from a global
|
|
// frame header
|
|
func (w *WesolowskiFrameProver) GetGlobalFrameSignaturePayload(
|
|
frame *protobufs.GlobalFrameHeader,
|
|
) ([]byte, error) {
|
|
if len(frame.ParentSelector) != 32 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid selector"),
|
|
"get global frame signature payload",
|
|
)
|
|
}
|
|
|
|
if len(frame.Output) != 516 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid output"),
|
|
"get global frame signature payload",
|
|
)
|
|
}
|
|
|
|
input := []byte{}
|
|
input = binary.BigEndian.AppendUint64(
|
|
input,
|
|
frame.FrameNumber,
|
|
)
|
|
input = binary.BigEndian.AppendUint64(input, uint64(frame.Timestamp))
|
|
input = binary.BigEndian.AppendUint32(input, frame.Difficulty)
|
|
input = append(input, frame.ParentSelector...)
|
|
|
|
for _, commitment := range frame.GlobalCommitments {
|
|
input = append(input, commitment...)
|
|
}
|
|
|
|
input = append(input, frame.GlobalProverTreeCommitment...)
|
|
input = append(input, frame.GlobalProverTreeStagedCommitment...)
|
|
input = append(input, frame.AppShardDeploymentsCommitment...)
|
|
|
|
b := sha3.Sum256(input)
|
|
proof := [516]byte{}
|
|
copy(proof[:], frame.Output)
|
|
|
|
return append(append([]byte{}, b[:]...), proof[:]...), nil
|
|
}
|
|
|
|
func (w *WesolowskiFrameProver) VerifyGlobalFrameHeader(
|
|
frame *protobufs.GlobalFrameHeader,
|
|
bls qcrypto.BlsConstructor,
|
|
) ([]uint8, error) {
|
|
if frame.PublicKeySignatureBls48581 == nil ||
|
|
frame.PublicKeySignatureBls48581.PublicKey == nil ||
|
|
len(frame.PublicKeySignatureBls48581.PublicKey.KeyValue) != 585 ||
|
|
len(frame.PublicKeySignatureBls48581.Signature) != 74 {
|
|
return nil, errors.Wrap(
|
|
errors.New("no valid signature provided"),
|
|
"verify global frame header",
|
|
)
|
|
}
|
|
|
|
if len(frame.GlobalCommitments) != 256 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid global commitment length"),
|
|
"verify global frame header",
|
|
)
|
|
} else {
|
|
for _, c := range frame.GlobalCommitments {
|
|
if len(c) != 74 && len(c) != 64 {
|
|
return nil, errors.Wrap(
|
|
errors.Errorf("invalid global commitment length: %d", len(c)),
|
|
"verify global frame header",
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate commitment lengths
|
|
for _, commitment := range frame.GlobalCommitments {
|
|
if len(commitment) != 74 && len(commitment) != 64 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid global commitment length"),
|
|
"verify global frame header",
|
|
)
|
|
}
|
|
}
|
|
|
|
if len(frame.GlobalProverTreeCommitment) != 74 &&
|
|
len(frame.GlobalProverTreeCommitment) != 64 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid global commitment length"),
|
|
"verify global frame header",
|
|
)
|
|
}
|
|
|
|
if len(frame.GlobalProverTreeStagedCommitment) != 74 &&
|
|
len(frame.GlobalProverTreeStagedCommitment) != 64 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid global commitment length"),
|
|
"verify global frame header",
|
|
)
|
|
}
|
|
|
|
if len(frame.AppShardDeploymentsCommitment) != 74 &&
|
|
len(frame.AppShardDeploymentsCommitment) != 64 {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid app shard commitment length"),
|
|
"verify global frame header",
|
|
)
|
|
}
|
|
|
|
// Get the signature payload
|
|
signaturePayload, err := w.GetGlobalFrameSignaturePayload(frame)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "verify global frame header")
|
|
}
|
|
|
|
// Extract the hash and proof from the signature payload
|
|
b := [32]byte{}
|
|
copy(b[:], signaturePayload[:32])
|
|
proof := [516]byte{}
|
|
copy(proof[:], signaturePayload[32:])
|
|
|
|
if !WesolowskiVerify(b, frame.Difficulty, proof) {
|
|
return nil, errors.Wrap(
|
|
errors.New("invalid proof"),
|
|
"verify global frame header",
|
|
)
|
|
}
|
|
|
|
return GetSetBitIndices(frame.PublicKeySignatureBls48581.Bitmask), nil
|
|
}
|
|
|
|
func (w *WesolowskiFrameProver) VerifyGlobalHeaderSignature(
|
|
frame *protobufs.GlobalFrameHeader,
|
|
bls qcrypto.BlsConstructor,
|
|
) (bool, error) {
|
|
// Get the signature payload
|
|
signaturePayload, err := w.GetGlobalFrameSignaturePayload(frame)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "verify global frame header")
|
|
}
|
|
|
|
if !bls.VerifySignatureRaw(
|
|
frame.PublicKeySignatureBls48581.PublicKey.KeyValue,
|
|
frame.PublicKeySignatureBls48581.Signature,
|
|
signaturePayload,
|
|
[]byte("global"),
|
|
) {
|
|
return false, errors.Wrap(
|
|
errors.New("invalid signature"),
|
|
"verify global frame header",
|
|
)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
var _ qcrypto.FrameProver = (*WesolowskiFrameProver)(nil)
|