ceremonyclient/node/crypto/wesolowski_frame_prover.go
Cassandra Heart 9e1d07d1a0
Squashed commit of the following:
commit d05a4d5f688dbd09900ceccdcc5f8109dd0671c2
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Wed Jun 12 00:50:16 2024 -0500

    merge

commit db57ff1f191f9dedc87ca77da1c71244dd2325bd
Merge: 7b43494 2e3279a
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Wed Jun 12 00:49:32 2024 -0500

    Merge branch 'v1.4.19' into not-release

commit 7b43494246e28152b46710c8c9821429d4231f7e
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Wed Jun 12 00:49:13 2024 -0500

    pull from release site

commit 2e3279ac930ac630d9ca2b26cf4f3232abe79823
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 06:31:02 2024 -0500

    remove binaries

commit 2768a8778b3860c5736352c8aa950e3496a46e56
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 06:24:44 2024 -0500

    signatory #8 added

commit 6a944628575ccadd17c9f9f4a11a49c032fa0c1d
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 06:08:26 2024 -0500

    signatory #6 added

commit b401fb65e5ddbe0340fe85aab1182d6120a4e161
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 05:39:03 2024 -0500

    signatory #3 added

commit e5700913c0f6246fb607bcd3e219c257cb4a80e9
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 05:31:24 2024 -0500

    signatory #15 added

commit 9b1da6c03e517135bfcd59226f900adab42f3687
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 05:23:02 2024 -0500

    signatories #4 and #16 added

commit 9c97d1bbc399a070ac21b35ed9b1af127fa4c7ea
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 04:59:27 2024 -0500

    signatories #1 and #2 added

commit 905e3f78a8121eade1c331ae910ed25dd534f27a
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 04:40:32 2024 -0500

    build, binaries, signatory #13

commit ebfb57bc29d9ed1fb25d0dd100e38709354b3d84
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Sat Jun 8 03:38:53 2024 -0500

    tests pass, let's go

commit 5d4612c6c624c3dc18f9a5657936034ac9d9d8dd
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Fri Jun 7 03:53:15 2024 -0500

    update version info + readme

commit 6b0dd69e930d01b98acb8d7b56bb5d572e1a4324
Merge: 090d630 859221b
Author: Cassie Heart <cassandra@quilibrium.com>
Date:   Fri Jun 7 08:25:16 2024 +0000

    Merge branch 'feat-data-worker-direct-config' into 'v1.4.19'

    feat: support detached configuration mode for data workers

    See merge request quilibrium/ceremonyclient!7

commit 859221b179ab2631fa474be2494259afaaa6bd51
Author: Cassandra Heart <cassandra@quilibrium.com>
Date:   Fri Jun 7 03:24:22 2024 -0500

    feat: support detached configuration mode for data workers

commit 090d6301d44a2aa88886120783cd5a6e537aa6d1
Merge: 62db30c d1cae94
Author: Cassie Heart <cassandra@quilibrium.com>
Date:   Fri Jun 7 06:25:43 2024 +0000

    Merge branch 'feat-go-1-22' into 'v1.4.19'

    feat: go 1.22 support

    See merge request quilibrium/ceremonyclient!6

commit d1cae942165f4871f8051e266722c0ca717780cb
Author: Cassie Heart <cassandra@quilibrium.com>
Date:   Fri Jun 7 06:25:43 2024 +0000

    feat: go 1.22 support

commit 62db30c54f9258c92113c6664ce817670a339083
Merge: 0cbc0d0 f36cea3
Author: Cassie Heart <cassandra@quilibrium.com>
Date:   Fri Jun 7 03:52:17 2024 +0000

    Merge branch 'rust-vdf' into 'v1.4.19'

    Switch to Rust VDF

    See merge request quilibrium/ceremonyclient!2

commit f36cea323bfe5e56f519f59f9a0cce35f0f8b6ab
Author: Agost Biro <agostbiro@gmail.com>
Date:   Fri Jun 7 03:52:16 2024 +0000

    Switch to Rust VDF

commit 0cbc0d0d319713e20ca7f48588c4153833e58429
Merge: 986e12c 0c48a83
Author: Cassie Heart <cassandra@quilibrium.com>
Date:   Fri Jun 7 00:50:15 2024 +0000

    Merge branch 'release_image' into 'v1.4.19'

    create docker image based on release binaries

    See merge request quilibrium/ceremonyclient!4

commit 0c48a83bb5751abf7c8c0ff188bfdc2130631e78
Author: Marius Scurtescu <marius.scurtescu@gmail.com>
Date:   Fri Jun 7 00:50:15 2024 +0000

    create docker image based on release binaries

commit 986e12c88bb2d2b412b59f7db1ae39f828304dbe
Merge: 58456c1 a3ef5c6
Author: Cassie Heart <cassandra@quilibrium.com>
Date:   Wed Jun 5 22:01:37 2024 +0000

    Merge branch 'signature_check' into 'v1.4.19'

    add default of signature check from QUILIBRIUM_SIGNATURE_CHECK env var

    See merge request quilibrium/ceremonyclient!1

commit a3ef5c6af2d5de107d01c45a62d7324165e2551b
Author: Marius Scurtescu <marius.scurtescu@gmail.com>
Date:   Wed Jun 5 14:37:50 2024 -0700

    add default of signature check from QUILIBRIUM_SIGNATURE_CHECK env var
2024-06-12 00:51:16 -05:00

619 lines
15 KiB
Go

package crypto
import (
"bytes"
"crypto"
"crypto/rand"
"encoding/binary"
"math/big"
"github.com/cloudflare/circl/sign/ed448"
"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/node/keys"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
"source.quilibrium.com/quilibrium/monorepo/node/tries"
"source.quilibrium.com/quilibrium/monorepo/vdf"
)
type WesolowskiFrameProver struct {
logger *zap.Logger
}
func NewWesolowskiFrameProver(logger *zap.Logger) *WesolowskiFrameProver {
return &WesolowskiFrameProver{
logger,
}
}
func (w *WesolowskiFrameProver) ProveMasterClockFrame(
previousFrame *protobufs.ClockFrame,
timestamp int64,
difficulty uint32,
) (*protobufs.ClockFrame, error) {
input := []byte{}
input = append(input, previousFrame.Filter...)
input = binary.BigEndian.AppendUint64(input, previousFrame.FrameNumber+1)
input = binary.BigEndian.AppendUint32(input, difficulty)
input = append(input, previousFrame.Output[:]...)
b := sha3.Sum256(input)
o := vdf.WesolowskiSolve(b, difficulty)
previousSelectorBytes := [516]byte{}
copy(previousSelectorBytes[:], previousFrame.Output[:516])
parent, err := poseidon.HashBytes(previousSelectorBytes[:])
if err != nil {
return nil, errors.Wrap(err, "prove clock frame")
}
frame := &protobufs.ClockFrame{
Filter: previousFrame.Filter,
FrameNumber: previousFrame.FrameNumber + 1,
Timestamp: timestamp,
Difficulty: difficulty,
ParentSelector: parent.FillBytes(make([]byte, 32)),
Input: previousFrame.Output,
AggregateProofs: []*protobufs.InclusionAggregateProof{},
Output: o[:],
}
return frame, nil
}
func (w *WesolowskiFrameProver) VerifyMasterClockFrame(
frame *protobufs.ClockFrame,
) error {
input := []byte{}
input = append(input, frame.Filter...)
input = binary.BigEndian.AppendUint64(input, frame.FrameNumber)
input = binary.BigEndian.AppendUint32(input, frame.Difficulty)
input = append(input, frame.Input...)
if len(frame.Input) < 516 {
return errors.Wrap(
errors.New("invalid input"),
"verify clock frame",
)
}
if len(frame.AggregateProofs) > 0 {
return errors.Wrap(
errors.New("invalid input"),
"verify clock frame",
)
}
if frame.PublicKeySignature != nil {
return errors.Wrap(
errors.New("invalid input"),
"verify clock frame",
)
}
if len(frame.Input) != 516 {
return errors.Wrap(
errors.New("invalid input"),
"verify clock frame",
)
}
if len(frame.Output) != 516 {
return errors.Wrap(
errors.New("invalid output"),
"verify clock frame",
)
}
b := sha3.Sum256(input)
proof := [516]byte{}
copy(proof[:], frame.Output)
if !vdf.WesolowskiVerify(b, frame.Difficulty, proof) {
w.logger.Error("invalid proof",
zap.Binary("filter", frame.Filter),
zap.Uint64("frame_number", frame.FrameNumber),
zap.Uint32("difficulty", frame.Difficulty),
zap.Binary("frame_input", frame.Input),
zap.Binary("frame_output", frame.Output),
)
return errors.Wrap(
errors.New("invalid proof"),
"verify clock frame",
)
}
previousSelectorBytes := [516]byte{}
copy(previousSelectorBytes[:], frame.Input[:516])
parent, err := poseidon.HashBytes(previousSelectorBytes[:])
if err != nil {
return errors.Wrap(err, "verify clock frame")
}
selector := new(big.Int).SetBytes(frame.ParentSelector)
if parent.Cmp(selector) != 0 {
return errors.Wrap(
errors.New("selector did not match input"),
"verify clock frame",
)
}
return nil
}
func (w *WesolowskiFrameProver) CreateMasterGenesisFrame(
filter []byte,
seed []byte,
difficulty uint32,
) (
*protobufs.ClockFrame,
error,
) {
b := sha3.Sum256(seed)
o := vdf.WesolowskiSolve(b, difficulty)
inputMessage := o[:]
w.logger.Debug("proving genesis frame")
input := []byte{}
input = append(input, filter...)
input = binary.BigEndian.AppendUint64(input, 0)
input = binary.BigEndian.AppendUint32(input, difficulty)
if bytes.Equal(seed, []byte{0x00}) {
value := [516]byte{}
input = append(input, value[:]...)
} else {
input = append(input, seed...)
}
b = sha3.Sum256(input)
o = vdf.WesolowskiSolve(b, difficulty)
frame := &protobufs.ClockFrame{
Filter: filter,
FrameNumber: 0,
Timestamp: 0,
Difficulty: difficulty,
Input: inputMessage,
Output: o[:],
ParentSelector: []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
AggregateProofs: []*protobufs.InclusionAggregateProof{},
PublicKeySignature: nil,
}
return frame, nil
}
func (w *WesolowskiFrameProver) ProveDataClockFrame(
previousFrame *protobufs.ClockFrame,
commitments [][]byte,
aggregateProofs []*protobufs.InclusionAggregateProof,
provingKey crypto.Signer,
timestamp int64,
difficulty uint32,
) (*protobufs.ClockFrame, error) {
var pubkey []byte
pubkeyType := keys.KeyTypeEd448
ed448PublicKey, ok := provingKey.Public().(ed448.PublicKey)
if ok {
pubkey = []byte(ed448PublicKey)
} else {
return nil, errors.Wrap(
errors.New("no valid signature provided"),
"prove clock frame",
)
}
h, err := poseidon.HashBytes(pubkey)
if err != nil {
return nil, errors.Wrap(
errors.New("could not hash proving key"),
"prove clock frame",
)
}
address := h.Bytes()
input := []byte{}
input = append(input, previousFrame.Filter...)
input = binary.BigEndian.AppendUint64(input, previousFrame.FrameNumber+1)
input = binary.BigEndian.AppendUint64(input, uint64(timestamp))
input = binary.BigEndian.AppendUint32(input, difficulty)
input = append(input, address...)
input = append(input, previousFrame.Output[:]...)
commitmentInput := []byte{}
for _, commitment := range commitments {
commitmentInput = append(commitmentInput, commitment...)
}
input = append(input, commitmentInput...)
b := sha3.Sum256(input)
o := vdf.WesolowskiSolve(b, difficulty)
// TODO: make this configurable for signing algorithms that allow
// user-supplied hash functions
signature, err := provingKey.Sign(
rand.Reader,
append(append([]byte{}, b[:]...), o[:]...),
crypto.Hash(0),
)
if err != nil {
return nil, errors.Wrap(
err,
"prove",
)
}
previousSelectorBytes := [516]byte{}
copy(previousSelectorBytes[:], previousFrame.Output[:516])
parent, err := poseidon.HashBytes(previousSelectorBytes[:])
if err != nil {
return nil, errors.Wrap(err, "prove clock frame")
}
frame := &protobufs.ClockFrame{
Filter: previousFrame.Filter,
FrameNumber: previousFrame.FrameNumber + 1,
Timestamp: timestamp,
Difficulty: difficulty,
ParentSelector: parent.FillBytes(make([]byte, 32)),
Input: append(
append([]byte{}, previousFrame.Output...),
commitmentInput...,
),
AggregateProofs: aggregateProofs,
Output: o[:],
}
switch pubkeyType {
case keys.KeyTypeEd448:
frame.PublicKeySignature = &protobufs.ClockFrame_PublicKeySignatureEd448{
PublicKeySignatureEd448: &protobufs.Ed448Signature{
Signature: signature,
PublicKey: &protobufs.Ed448PublicKey{
KeyValue: pubkey,
},
},
}
default:
return nil, errors.Wrap(
errors.New("unsupported proving key"),
"prove clock frame",
)
}
return frame, nil
}
func (w *WesolowskiFrameProver) CreateDataGenesisFrame(
filter []byte,
origin []byte,
difficulty uint32,
inclusionProof *InclusionAggregateProof,
proverKeys [][]byte,
preDusk bool,
) (*protobufs.ClockFrame, *tries.RollingFrecencyCritbitTrie, error) {
frameProverTrie := &tries.RollingFrecencyCritbitTrie{}
for _, s := range proverKeys {
addr, err := poseidon.HashBytes(s)
if err != nil {
panic(err)
}
addrBytes := addr.Bytes()
addrBytes = append(make([]byte, 32-len(addrBytes)), addrBytes...)
frameProverTrie.Add(addrBytes, 0)
}
w.logger.Info("proving genesis frame")
input := []byte{}
input = append(input, filter...)
input = binary.BigEndian.AppendUint64(input, 0)
input = binary.BigEndian.AppendUint64(input, 0)
input = binary.BigEndian.AppendUint32(input, difficulty)
input = append(input, origin...)
if !preDusk {
input = append(input, inclusionProof.AggregateCommitment...)
}
b := sha3.Sum256(input)
o := vdf.WesolowskiSolve(b, difficulty)
commitments := []*protobufs.InclusionCommitment{}
for i, commit := range inclusionProof.InclusionCommitments {
commitments = append(commitments, &protobufs.InclusionCommitment{
Filter: filter,
FrameNumber: 0,
Position: uint32(i),
TypeUrl: commit.TypeUrl,
Data: commit.Data,
Commitment: commit.Commitment,
})
}
frame := &protobufs.ClockFrame{
Filter: filter,
FrameNumber: 0,
Timestamp: 0,
Difficulty: difficulty,
Input: append(
append([]byte{}, origin...),
inclusionProof.AggregateCommitment...,
),
Output: o[:],
ParentSelector: []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
AggregateProofs: []*protobufs.InclusionAggregateProof{
{
Filter: filter,
FrameNumber: 0,
InclusionCommitments: commitments,
Proof: inclusionProof.Proof,
},
},
PublicKeySignature: nil,
}
return frame, frameProverTrie, nil
}
func (w *WesolowskiFrameProver) VerifyDataClockFrame(
frame *protobufs.ClockFrame,
) error {
var pubkey []byte
var signature []byte
pubkeyType := keys.KeyTypeEd448
ed448PublicKey := frame.GetPublicKeySignatureEd448()
if ed448PublicKey != nil {
pubkey = ed448PublicKey.PublicKey.KeyValue
signature = ed448PublicKey.Signature
} else {
return errors.Wrap(
errors.New("no valid signature provided"),
"verify clock frame",
)
}
h, err := poseidon.HashBytes(pubkey)
if err != nil {
return errors.Wrap(
errors.New("could not hash proving key"),
"verify clock frame",
)
}
address := h.Bytes()
input := []byte{}
input = append(input, frame.Filter...)
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, address...)
input = append(input, frame.Input...)
if len(frame.Input) < 516 {
return errors.Wrap(
errors.New("invalid input"),
"verify clock frame",
)
}
if len(frame.Output) != 516 {
return errors.Wrap(
errors.New("invalid output"),
"verify clock frame",
)
}
b := sha3.Sum256(input)
proof := [516]byte{}
copy(proof[:], frame.Output)
// TODO: make this configurable for signing algorithms that allow
// user-supplied hash functions
switch pubkeyType {
case keys.KeyTypeEd448:
if len(pubkey) != 57 || len(signature) != 114 || !ed448.VerifyAny(
pubkey,
append(append([]byte{}, b[:]...), frame.Output...),
signature,
crypto.Hash(0),
) {
return errors.Wrap(
errors.New("invalid signature for issuer"),
"verify clock frame",
)
}
}
if !vdf.WesolowskiVerify(b, frame.Difficulty, proof) {
return errors.Wrap(
errors.New("invalid proof"),
"verify clock frame",
)
}
previousSelectorBytes := [516]byte{}
copy(previousSelectorBytes[:], frame.Input[:516])
parent, err := poseidon.HashBytes(previousSelectorBytes[:])
if err != nil {
return errors.Wrap(err, "verify clock frame")
}
selector := new(big.Int).SetBytes(frame.ParentSelector)
if parent.Cmp(selector) != 0 {
return errors.Wrap(
errors.New("selector did not match input"),
"verify clock frame",
)
}
return nil
}
func (w *WesolowskiFrameProver) GenerateWeakRecursiveProofIndex(
frame *protobufs.ClockFrame,
) (uint64, error) {
hash, err := poseidon.HashBytes(frame.Output)
if err != nil {
return 0, errors.Wrap(err, "generate weak recursive proof")
}
return hash.Mod(
hash,
new(big.Int).SetUint64(frame.FrameNumber),
).Uint64(), nil
}
func (w *WesolowskiFrameProver) FetchRecursiveProof(
frame *protobufs.ClockFrame,
) []byte {
var pubkey []byte
ed448PublicKey := frame.GetPublicKeySignatureEd448()
if ed448PublicKey != nil {
pubkey = ed448PublicKey.PublicKey.KeyValue
} else {
return nil
}
h, err := poseidon.HashBytes(pubkey)
if err != nil {
return nil
}
address := h.Bytes()
input := []byte{}
input = append(input, frame.Filter...)
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, address...)
input = append(input, frame.Input...)
input = append(input, frame.Output...)
return input
}
func (w *WesolowskiFrameProver) VerifyWeakRecursiveProof(
frame *protobufs.ClockFrame,
proof []byte,
deepVerifier *protobufs.ClockFrame,
) bool {
hash, err := poseidon.HashBytes(frame.Output)
if err != nil {
w.logger.Debug("could not hash output")
return false
}
frameNumber := hash.Mod(
hash,
new(big.Int).SetUint64(frame.FrameNumber),
).Uint64()
if len(proof) < 1084 {
w.logger.Debug("invalid proof size")
return false
}
filter := proof[:len(frame.Filter)]
check := binary.BigEndian.Uint64(
proof[len(frame.Filter) : len(frame.Filter)+8],
)
timestamp := binary.BigEndian.Uint64(
proof[len(frame.Filter)+8 : len(frame.Filter)+16],
)
difficulty := binary.BigEndian.Uint32(
proof[len(frame.Filter)+16 : len(frame.Filter)+20],
)
input := proof[len(frame.Filter)+52:]
if check != frameNumber ||
!bytes.Equal(filter, frame.Filter) ||
int64(timestamp) >= frame.Timestamp ||
difficulty > frame.Difficulty ||
len(input) < 1032 {
w.logger.Debug(
"check failed",
zap.Bool("failed_frame_number", check != frameNumber),
zap.Bool("failed_filter", !bytes.Equal(filter, frame.Filter)),
zap.Bool("failed_timestamp", int64(timestamp) >= frame.Timestamp),
zap.Bool("failed_difficulty", difficulty > frame.Difficulty),
zap.Bool("failed_input_size", len(input) < 1032),
)
return false
}
if deepVerifier != nil && (check != deepVerifier.FrameNumber ||
!bytes.Equal(filter, deepVerifier.Filter) ||
int64(timestamp) != deepVerifier.Timestamp ||
difficulty != deepVerifier.Difficulty ||
!bytes.Equal(input[:len(input)-516], deepVerifier.Input)) {
return false
}
b := sha3.Sum256(input[:len(input)-516])
output := [516]byte{}
copy(output[:], input[len(input)-516:])
if vdf.WesolowskiVerify(b, difficulty, output) {
w.logger.Debug("verification succeeded")
return true
} else {
w.logger.Debug("verification failed")
return false
}
}
func (w *WesolowskiFrameProver) CalculateChallengeProof(
challenge []byte,
core uint32,
increment uint32,
) ([]byte, error) {
difficulty := 200000 - (increment / 4)
instanceInput := binary.BigEndian.AppendUint32([]byte{}, core)
instanceInput = append(instanceInput, challenge...)
b := sha3.Sum256(instanceInput)
o := vdf.WesolowskiSolve(b, uint32(difficulty))
output := make([]byte, 516)
copy(output[:], o[:])
return output, nil
}
func (w *WesolowskiFrameProver) VerifyChallengeProof(
challenge []byte,
increment uint32,
core uint32,
proof []byte,
) bool {
difficulty := 200000 - (increment / 4)
if len(proof) != 516 {
return false
}
instanceInput := binary.BigEndian.AppendUint32([]byte{}, core)
instanceInput = append(instanceInput, challenge...)
b := sha3.Sum256(instanceInput)
check := vdf.WesolowskiVerify(b, difficulty, [516]byte(proof))
return check
}