ceremonyclient/node/crypto/channel/feldman.go
2024-06-21 01:34:50 -05:00

338 lines
8.7 KiB
Go

package channel
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"fmt"
"math/big"
"github.com/pkg/errors"
"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
)
type Feldman struct {
threshold int
total int
id int
fragsForCounterparties map[int][]byte
fragsFromCounterparties map[int]curves.Scalar
zkpok curves.Scalar
secret curves.Scalar
scalar curves.Scalar
generator curves.Point
publicKey curves.Point
point curves.Point
randomCommitmentPoint curves.Point
round FeldmanRound
zkcommitsFromCounterparties map[int][]byte
pointsFromCounterparties map[int]curves.Point
curve curves.Curve
}
type FeldmanReveal struct {
Point []byte
RandomCommitmentPoint []byte
ZKPoK []byte
}
var ErrWrongRound = errors.New("wrong round for feldman")
type FeldmanRound int
const (
FELDMAN_ROUND_UNINITIALIZED = FeldmanRound(0)
FELDMAN_ROUND_INITIALIZED = FeldmanRound(1)
FELDMAN_ROUND_COMMITTED = FeldmanRound(2)
FELDMAN_ROUND_REVEALED = FeldmanRound(3)
FELDMAN_ROUND_RECONSTRUCTED = FeldmanRound(4)
)
func NewFeldman(
threshold, total, id int,
secret curves.Scalar,
curve curves.Curve,
generator curves.Point,
) (*Feldman, error) {
return &Feldman{
threshold: threshold,
total: total,
id: id,
fragsForCounterparties: make(map[int][]byte),
fragsFromCounterparties: make(map[int]curves.Scalar),
zkpok: nil,
secret: secret,
scalar: nil,
generator: generator,
publicKey: secret.Point().Generator(),
point: secret.Point().Generator(),
round: FELDMAN_ROUND_UNINITIALIZED,
zkcommitsFromCounterparties: make(map[int][]byte),
pointsFromCounterparties: make(map[int]curves.Point),
curve: curve,
}, nil
}
func (f *Feldman) SamplePolynomial() error {
if f.round != FELDMAN_ROUND_UNINITIALIZED {
return errors.Wrap(ErrWrongRound, "sample polynomial")
}
coeffs := append([]curves.Scalar{}, f.secret)
for i := 1; i < f.threshold; i++ {
secret := f.curve.NewScalar()
secret = secret.Random(rand.Reader)
coeffs = append(coeffs, secret)
}
for i := 1; i <= f.total; i++ {
result := coeffs[0].Clone()
x := f.curve.Scalar.New(i)
for j := 1; j < f.threshold; j++ {
term := coeffs[j].Mul(x)
result = result.Add(term)
x = x.Mul(f.curve.Scalar.New(i))
}
if i == f.id {
f.scalar = result
} else {
fragBytes := result.Bytes()
f.fragsForCounterparties[i] = fragBytes
}
}
f.round = FELDMAN_ROUND_INITIALIZED
return nil
}
func (f *Feldman) Scalar() curves.Scalar {
return f.scalar
}
func (f *Feldman) GetPolyFrags() (map[int][]byte, error) {
if f.round != FELDMAN_ROUND_INITIALIZED {
return nil, errors.Wrap(ErrWrongRound, "get poly frags")
}
return f.fragsForCounterparties, nil
}
func (f *Feldman) SetPolyFragForParty(id int, frag []byte) ([]byte, error) {
if f.round != FELDMAN_ROUND_INITIALIZED {
return nil, errors.Wrap(ErrWrongRound, "set poly frag for party")
}
var err error
f.fragsFromCounterparties[id], err = f.curve.NewScalar().SetBytes(frag)
if err != nil {
return nil, errors.Wrap(err, "set poly frag for party")
}
if len(f.fragsFromCounterparties) == f.total-1 {
for _, v := range f.fragsFromCounterparties {
f.scalar = f.scalar.Add(v)
}
f.point = f.generator.Mul(f.scalar)
randCommitment := f.curve.NewScalar().Random(rand.Reader)
f.randomCommitmentPoint = f.generator.Mul(randCommitment)
randCommitmentPointBytes := f.randomCommitmentPoint.ToAffineCompressed()
publicPointBytes := f.point.ToAffineCompressed()
challenge := sha256.Sum256(
append(
append([]byte{}, publicPointBytes...),
randCommitmentPointBytes...,
),
)
challengeBig, err := f.curve.NewScalar().SetBigInt(
new(big.Int).SetBytes(challenge[:]),
)
if err != nil {
return nil, errors.Wrap(err, "set poly frag for party")
}
f.zkpok = f.scalar.Mul(challengeBig).Add(randCommitment)
zkpokBytes := f.zkpok.Bytes()
zkcommit := sha256.Sum256(
append(
append([]byte{}, randCommitmentPointBytes...),
zkpokBytes...,
),
)
f.round = FELDMAN_ROUND_COMMITTED
return zkcommit[:], nil
}
return []byte{}, nil
}
func (f *Feldman) ReceiveCommitments(
id int,
zkcommit []byte,
) (*FeldmanReveal, error) {
if f.round != FELDMAN_ROUND_COMMITTED {
return nil, errors.Wrap(ErrWrongRound, "receive commitments")
}
f.zkcommitsFromCounterparties[id] = zkcommit
if len(f.zkcommitsFromCounterparties) == f.total-1 {
publicPointBytes := f.point.ToAffineCompressed()
randCommitmentPointBytes := f.randomCommitmentPoint.ToAffineCompressed()
f.round = FELDMAN_ROUND_REVEALED
zkpokBytes := f.zkpok.Bytes()
return &FeldmanReveal{
Point: publicPointBytes,
RandomCommitmentPoint: randCommitmentPointBytes,
ZKPoK: zkpokBytes,
}, nil
}
return nil, nil
}
func (f *Feldman) Recombine(id int, reveal *FeldmanReveal) (bool, error) {
if f.round != FELDMAN_ROUND_REVEALED {
return false, errors.Wrap(ErrWrongRound, "recombine")
}
counterpartyPoint, err := f.curve.NewGeneratorPoint().FromAffineCompressed(
reveal.Point,
)
if err != nil {
return false, errors.Wrap(err, "recombine")
}
if counterpartyPoint.Equal(f.curve.NewGeneratorPoint()) ||
counterpartyPoint.Equal(f.generator) {
return false, errors.Wrap(errors.New("counterparty sent generator"), "recombine")
}
counterpartyRandomCommitmentPoint, err := f.curve.NewGeneratorPoint().
FromAffineCompressed(reveal.RandomCommitmentPoint)
if err != nil {
return false, errors.Wrap(err, "recombine")
}
if counterpartyRandomCommitmentPoint.Equal(f.curve.NewGeneratorPoint()) ||
counterpartyRandomCommitmentPoint.Equal(f.generator) {
return false, errors.Wrap(errors.New("counterparty sent generator"), "recombine")
}
counterpartyZKPoK, err := f.curve.NewScalar().SetBytes(reveal.ZKPoK)
if err != nil {
return false, errors.Wrap(err, "recombine")
}
counterpartyZKCommit := f.zkcommitsFromCounterparties[id]
challenge := sha256.Sum256(append(
append([]byte{}, reveal.Point...),
reveal.RandomCommitmentPoint...,
))
challengeBig, err := f.curve.NewScalar().SetBigInt(
new(big.Int).SetBytes(challenge[:]),
)
if err != nil {
return false, errors.Wrap(err, "recombine")
}
proof := f.generator.Mul(counterpartyZKPoK)
counterpartyRandomCommitmentPoint = counterpartyRandomCommitmentPoint.Add(
counterpartyPoint.Mul(challengeBig),
)
if !proof.Equal(counterpartyRandomCommitmentPoint) {
return false, errors.Wrap(
errors.New(fmt.Sprintf("invalid proof from %d", id)),
"recombine",
)
}
verifier := sha256.Sum256(append(
append([]byte{}, reveal.RandomCommitmentPoint...),
reveal.ZKPoK...,
))
if !bytes.Equal(counterpartyZKCommit, verifier[:]) {
return false, errors.Wrap(
errors.New(fmt.Sprintf("%d changed zkpok after commit", id)),
"recombine",
)
}
f.pointsFromCounterparties[id] = counterpartyPoint
if len(f.pointsFromCounterparties) == f.total-1 {
f.pointsFromCounterparties[f.id] = f.point
for i := 1; i <= f.total-f.threshold+1; i++ {
var reconstructedSum curves.Point = nil
for j := i; j < f.threshold+i; j++ {
num := f.curve.Scalar.One()
den := f.curve.Scalar.One()
for k := i; k < f.threshold+i; k++ {
if j != k {
j := f.curve.NewScalar().New(j)
k := f.curve.NewScalar().New(k)
num = num.Mul(k)
den = den.Mul(k.Sub(j))
}
}
den, _ = den.Invert()
reconstructedFragment := f.pointsFromCounterparties[j].Mul(num.Mul(den))
if reconstructedSum == nil {
reconstructedSum = reconstructedFragment
} else {
reconstructedSum = reconstructedSum.Add(reconstructedFragment)
}
}
if f.publicKey.Equal(f.curve.NewGeneratorPoint()) ||
f.publicKey.Equal(f.generator) {
f.publicKey = reconstructedSum
} else if !f.publicKey.Equal(reconstructedSum) {
return false, errors.Wrap(
errors.New("recombination mismatch"),
"recombine",
)
}
}
f.round = FELDMAN_ROUND_RECONSTRUCTED
}
return f.round == FELDMAN_ROUND_RECONSTRUCTED, nil
}
func (f *Feldman) PublicKey() curves.Point {
return f.publicKey
}
func (f *Feldman) PublicKeyBytes() []byte {
return f.publicKey.ToAffineCompressed()
}
func ReverseScalarBytes(inBytes []byte, length int) []byte {
outBytes := make([]byte, length)
for i, j := 0, len(inBytes)-1; j >= 0; i, j = i+1, j-1 {
outBytes[i] = inBytes[j]
}
return outBytes
}