From aa5904768d55b2c8bc1ab8d8e936c018b33a9810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 20 Jan 2023 11:19:17 +0100 Subject: [PATCH 01/22] tbls: first iteration of BLS abstraction This commit also adds an example implementation of the BLS abstraction for Herumi's BLS library. This code is not finished (missing testing, and many Godoc comments), and it is pushed as a request for comment from the team. --- go.mod | 1 + go.sum | 2 + tbls/tbls.go | 162 ++++++++++++++++++++++++++++++ tbls/tbls_herumi.go | 239 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 404 insertions(+) create mode 100644 tbls/tbls.go create mode 100644 tbls/tbls_herumi.go diff --git a/go.mod b/go.mod index 3eb9319dd..3e65bf1a5 100644 --- a/go.mod +++ b/go.mod @@ -95,6 +95,7 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/herumi/bls-eth-go-binary v1.28.1 // indirect github.com/huin/goupnp v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/go-cid v0.2.0 // indirect diff --git a/go.sum b/go.sum index 0cbcf1f21..2b8b1c3e0 100644 --- a/go.sum +++ b/go.sum @@ -316,6 +316,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e h1:wCMygKUQhmcQAjlk2Gquzq6dLmyMv2kF+llRspoRgrk= github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= +github.com/herumi/bls-eth-go-binary v1.28.1 h1:fcIZ48y5EE9973k05XjE8+P3YiQgjZz4JI/YabAm8KA= +github.com/herumi/bls-eth-go-binary v1.28.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= diff --git a/tbls/tbls.go b/tbls/tbls.go new file mode 100644 index 000000000..a4a961fac --- /dev/null +++ b/tbls/tbls.go @@ -0,0 +1,162 @@ +// Copyright © 2023 Obol Labs Inc. + +package tbls + +import ( + "encoding" + "fmt" +) + +/* +`tbls` package operations: +- key generation +- threshold cryptography + - key splitting + - key aggregation (public, private) + - signature verification and aggregation + - simple signature aggregation + - simple signature verification + - signature on a byte slice + - signature with a partial key +*/ + +type ThresholdManager struct { + total uint + threshold uint + generator ThresholdGenerator +} + +func NewThresholdManager(generator ThresholdGenerator, total, threshold uint) (ThresholdManager, error) { + if generator == nil { + return ThresholdManager{}, fmt.Errorf("generator can't be nil") + } + + if threshold > total { + return ThresholdManager{}, fmt.Errorf("threshold can't be greater than total") + } + + if threshold == 0 { + return ThresholdManager{}, fmt.Errorf("threshold can't be zero") + } + + if total == 0 { + return ThresholdManager{}, fmt.Errorf("total can't be zero") + } + + return ThresholdManager{ + threshold: threshold, + total: total, + generator: generator, + }, nil +} + +func (tm ThresholdManager) Generate() ([]PartialPrivateKey, error) { + pk, err := tm.generator.Generate() + if err != nil { + return nil, err + } + + return tm.generator.Split(pk, tm.total, tm.threshold) +} + +// VerifyAggregate verifies all partial signatures against a message and aggregates them. +// It returns the aggregated signature and slice of valid partial signature identifiers. +func (tm ThresholdManager) VerifyAggregate(pubkeys []PartialPublicKey, partialSigs []PartialSignature, msg []byte) (Signature, []uint, error) { + if len(partialSigs) < int(tm.threshold) { + return nil, nil, fmt.Errorf("insufficient signatures") + } + + var ( + signers []uint + validSigs []PartialSignature + ) + + for idx := 0; idx < len(partialSigs); idx++ { + signature := partialSigs[idx] + pubkey := pubkeys[idx] + + if err := signature.Verify(pubkey, msg); err != nil { + continue + } + + validSigs = append(validSigs, signature) + signers = append(signers, pubkey.ID()) + } + + if len(validSigs) < int(tm.threshold) { + return nil, nil, fmt.Errorf("insufficient valid signatures") + } + + aggSig, err := tm.generator.CombineSignatures(partialSigs, pubkeys) + if err != nil { + return nil, nil, fmt.Errorf("cannot aggregate signatures after verification, %w", err) + } + + return aggSig, signers, nil +} + +// Aggregate aggregates partialSigs into a single Signature, with ID's taken from pubkeys. +func (tm ThresholdManager) Aggregate(pubkeys []PartialPublicKey, partialSigs []PartialSignature) (Signature, error) { + aggSig, err := tm.generator.CombineSignatures(partialSigs, pubkeys) + if err != nil { + return nil, fmt.Errorf("cannot aggregate signatures after verification, %w", err) + } + + return aggSig, nil +} + +// ThresholdGenerator generates threshold public and private keys according to +// the specified parameters. +type ThresholdGenerator interface { + // Split splits original into total amount of PartialPrivateKey's, with threshold + // amount of them needed to recover secret. + Split(original PrivateKey, total, threshold uint) ([]PartialPrivateKey, error) + + // RecoverPrivateKey recombines the PartialPrivateKey's back to the original PrivateKey. + RecoverPrivateKey([]PartialPrivateKey) (PrivateKey, error) + + // CombineSignatures combines all the input PartialSignature's in a complete + // Signature. + CombineSignatures([]PartialSignature, []PartialPublicKey) (Signature, error) + + // Generate generates a new PrivateKey from the OS source of entropy + Generate() (PrivateKey, error) +} + +// PublicKey is a BLS12-381 public key. +// It represents a full public key, not a share of it. +type PublicKey interface { + encoding.TextMarshaler +} + +// PrivateKey is a BLS12-381 private key. +// It represents a full private key, not a share of it. +type PrivateKey interface { + encoding.TextMarshaler + encoding.TextUnmarshaler + + PublicKey() PublicKey + Sign(data []byte) (Signature, error) +} + +// PartialPublicKey is a share of a full BLS12-381 key. +type PartialPublicKey interface { + PublicKey + ID() uint +} + +// PartialPrivateKey is a share of a full BLS12-381 key. +type PartialPrivateKey interface { + PrivateKey + ID() uint +} + +// Signature represents a BLS12-381 signature made with a PrivateKey. +type Signature interface { + Verify(pk PublicKey, message []byte) error +} + +// PartialSignature represents a BLS12-381 signature made with a PartialPrivateKey. +type PartialSignature interface { + Verify(pk PartialPublicKey, message []byte) error +} diff --git a/tbls/tbls_herumi.go b/tbls/tbls_herumi.go new file mode 100644 index 000000000..5dcf5ebd6 --- /dev/null +++ b/tbls/tbls_herumi.go @@ -0,0 +1,239 @@ +package tbls + +import ( + "fmt" + "github.com/herumi/bls-eth-go-binary/bls" + "strconv" + "sync" +) + +var herumiInit = sync.Once{} + +// PSA: as much as init() is (almost) an antipattern in Go, Herumi BLS implementation needs an initialization routine +// before it can be used. +// Hence, we embed it in an init() method along with a sync.Once, so that this effect is only run once. +func init() { + herumiInit.Do(func() { + if err := bls.Init(bls.BLS12_381); err != nil { + panic(fmt.Errorf("cannot initialize Herumi BLS, %w", err)) + } + + if err := bls.SetETHmode(bls.EthModeLatest); err != nil { + panic(fmt.Errorf("cannot initialize Herumi BLS, %w", err)) + } + }) +} + +type HerumiPublicKey struct { + bls.PublicKey +} + +func (h HerumiPublicKey) MarshalText() ([]byte, error) { + return []byte(h.SerializeToHexStr()), nil +} + +type HerumiPrivateKey struct { + bls.SecretKey +} + +func (h *HerumiPrivateKey) MarshalText() ([]byte, error) { + return []byte(h.SerializeToHexStr()), nil +} + +func (h *HerumiPrivateKey) UnmarshalText(text []byte) error { + return h.SetHexString(string(text)) +} + +func (h *HerumiPrivateKey) PublicKey() PublicKey { + p, err := h.GetSafePublicKey() + if err != nil { + panic(fmt.Errorf("cannot retrieve public key from private key, %w", err)) + } + + return HerumiPublicKey{ + PublicKey: *p, + } +} + +func (h *HerumiPrivateKey) Sign(data []byte) (Signature, error) { + return HerumiSignature{ + Sign: *h.SignByte(data), + }, nil +} + +type HerumiPartialPublicKey struct { + HerumiPublicKey + id uint +} + +func (h HerumiPartialPublicKey) ID() uint { + return h.id +} + +type HerumiPartialPrivateKey struct { + HerumiPrivateKey + id uint +} + +func (h HerumiPartialPrivateKey) ID() uint { + return h.id +} + +type HerumiSignature struct { + bls.Sign +} + +func (h HerumiSignature) Verify(pk PublicKey, message []byte) error { + // assert pk to its raw type + hpubk, ok := pk.(HerumiPublicKey) + if !ok { + return fmt.Errorf("public key is not in Herumi format") + } + + if !h.VerifyByte(&hpubk.PublicKey, message) { + return fmt.Errorf("signature not verified") + } + + return nil +} + +type HerumiPartialSignature struct { + HerumiSignature +} + +func (h HerumiPartialSignature) Verify(pk PartialPublicKey, message []byte) error { + // assert pk to its raw type + hpubk, ok := pk.(HerumiPartialPublicKey) + if !ok { + return fmt.Errorf("public key is not in Herumi format") + } + + if !h.VerifyByte(&hpubk.PublicKey, message) { + return fmt.Errorf("signature not verified") + } + + return nil +} + +type HerumiThresholdGenerator struct{} + +func (h HerumiThresholdGenerator) Split(original PrivateKey, total, threshold uint) ([]PartialPrivateKey, error) { + // cast original to its herumi representation + horiginal, ok := original.(*HerumiPrivateKey) + if !ok { + return nil, fmt.Errorf("original is not in Herumi format") + } + + // master key Polynomial + poly := make([]bls.SecretKey, threshold) + + poly[0] = horiginal.SecretKey + + // initialize threshold amount of points + for i := 1; i < int(threshold); i++ { + sk := bls.SecretKey{} + sk.SetByCSPRNG() + poly[i] = sk + } + + ret := make([]PartialPrivateKey, total) + for i := 1; i <= int(total); i++ { + blsID := bls.ID{} + + err := blsID.SetDecString(fmt.Sprintf("%d", i)) + if err != nil { + return nil, fmt.Errorf("cannot set ID %d for key number %d, %w", i, i, err) + } + + sk := bls.SecretKey{} + + err = sk.Set(poly, &blsID) + if err != nil { + return nil, err + } + + ret[i] = &HerumiPartialPrivateKey{ + HerumiPrivateKey: HerumiPrivateKey{ + SecretKey: sk, + }, + id: uint(i), + } + } + + return ret, nil +} + +func (h HerumiThresholdGenerator) RecoverPrivateKey(keys []PartialPrivateKey) (PrivateKey, error) { + pk := bls.SecretKey{} + + rawKeys := []bls.SecretKey{} + rawIDs := []bls.ID{} + + for idx, key := range keys { + // assert key to herumi type + kpk, ok := key.(*HerumiPartialPrivateKey) + if !ok { + return nil, fmt.Errorf("private key %d is not in Herumi format", idx) + } + + rawKeys = append(rawKeys, kpk.SecretKey) + + id := bls.ID{} + if err := id.SetDecString(strconv.Itoa(int(kpk.id))); err != nil { + return nil, fmt.Errorf("private key %d id isn't a number", kpk.id) + } + + rawIDs = append(rawIDs, id) + } + + if err := pk.Recover(rawKeys, rawIDs); err != nil { + return nil, fmt.Errorf("cannot recover full private key from partial keys, %w", err) + } + + return &HerumiPrivateKey{ + SecretKey: pk, + }, nil +} + +func (h HerumiThresholdGenerator) CombineSignatures(psigns []PartialSignature, pkeys []PartialPublicKey) (Signature, error) { + var rawSigns []bls.Sign + var rawIDs []bls.ID + + for idx, sign := range psigns { + hsign, ok := sign.(HerumiPartialSignature) + if !ok { + return nil, fmt.Errorf("partial signature %d is not in Herumi format", idx) + } + + rawSigns = append(rawSigns, hsign.Sign) + } + + for idx, pk := range pkeys { + id := bls.ID{} + + if err := id.SetDecString(strconv.Itoa(int(pk.ID()))); err != nil { + return nil, fmt.Errorf("cannot set partial public key %d's id, %w", idx, err) + } + + rawIDs = append(rawIDs, id) + } + + complete := bls.Sign{} + + if err := complete.Recover(rawSigns, rawIDs); err != nil { + return nil, fmt.Errorf("cannot combine signatures, %w", err) + } + + return HerumiSignature{ + Sign: complete, + }, nil +} + +func (h HerumiThresholdGenerator) Generate() (PrivateKey, error) { + p := bls.SecretKey{} + p.SetByCSPRNG() + + return &HerumiPrivateKey{ + SecretKey: p, + }, nil +} From 06b762620cdfffbaf041dd902f4ee1dbd380a53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 20 Jan 2023 15:53:27 +0100 Subject: [PATCH 02/22] tbls: implement BLS abstraction as a thin layer on top of []byte objects. The `taketwo` package is a placeholder, once we settle on an interface it will be all merged under `tbls`. --- tbls/taketwo/herumi/herumi.go | 178 +++++++++++++++++++++++++++++ tbls/taketwo/herumi/herumi_test.go | 123 ++++++++++++++++++++ tbls/taketwo/take_two.go | 64 +++++++++++ tbls/taketwo/unimplemented.go | 36 ++++++ 4 files changed, 401 insertions(+) create mode 100644 tbls/taketwo/herumi/herumi.go create mode 100644 tbls/taketwo/herumi/herumi_test.go create mode 100644 tbls/taketwo/take_two.go create mode 100644 tbls/taketwo/unimplemented.go diff --git a/tbls/taketwo/herumi/herumi.go b/tbls/taketwo/herumi/herumi.go new file mode 100644 index 000000000..1b2f3573f --- /dev/null +++ b/tbls/taketwo/herumi/herumi.go @@ -0,0 +1,178 @@ +package herumi + +import ( + "encoding/hex" + "fmt" + "github.com/herumi/bls-eth-go-binary/bls" + "strconv" + "sync" +) + +var initializationOnce = sync.Once{} + +// PSA: as much as init() is (almost) an antipattern in Go, Herumi BLS implementation needs an initialization routine +// before it can be used. +// Hence, we embed it in an init() method along with a sync.Once, so that this effect is only run once. +func init() { + initializationOnce.Do(func() { + if err := bls.Init(bls.BLS12_381); err != nil { + panic(fmt.Errorf("cannot initialize Herumi BLS, %w", err)) + } + + if err := bls.SetETHmode(bls.EthModeLatest); err != nil { + panic(fmt.Errorf("cannot initialize Herumi BLS, %w", err)) + } + }) +} + +// Herumi is an Implementation with Herumi-specific inner logic. +type Herumi struct{} + +func (h Herumi) GenerateSecretKey() ([]byte, error) { + p := bls.SecretKey{} + p.SetByCSPRNG() + + return p.Serialize(), nil +} + +func (h Herumi) SecretToPublicKey(secret []byte) ([]byte, error) { + p := bls.SecretKey{} + + if err := p.Deserialize(secret); err != nil { + return nil, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) + } + + pubk, err := p.GetSafePublicKey() + if err != nil { + return nil, fmt.Errorf("cannot obtain public key from secret secret, %w", err) + } + + return pubk.Serialize(), nil +} + +func (h Herumi) ThresholdSplit(secret []byte, total uint, threshold uint) (map[int][]byte, error) { + p := bls.SecretKey{} + + if err := p.Deserialize(secret); err != nil { + return nil, fmt.Errorf("cannot unmarshal bytes into Herumi secret key, %w", err) + } + + // master key Polynomial + poly := make([]bls.SecretKey, threshold) + + poly[0] = p + + // initialize threshold amount of points + for i := 1; i < int(threshold); i++ { + sk := bls.SecretKey{} + sk.SetByCSPRNG() + poly[i] = sk + } + + ret := make(map[int][]byte) + for i := 1; i <= int(total); i++ { + blsID := bls.ID{} + + err := blsID.SetDecString(fmt.Sprintf("%d", i)) + if err != nil { + return nil, fmt.Errorf("cannot set ID %d for key number %d, %w", i, i, err) + } + + sk := bls.SecretKey{} + + err = sk.Set(poly, &blsID) + if err != nil { + return nil, err + } + + ret[i] = sk.Serialize() + } + + return ret, nil +} + +func (h Herumi) RecoverSecret(shares map[int][]byte) ([]byte, error) { + var pk bls.SecretKey + + var rawKeys []bls.SecretKey + var rawIDs []bls.ID + + for idx, key := range shares { + var kpk bls.SecretKey + if err := kpk.Deserialize(key); err != nil { + return nil, fmt.Errorf("cannot unmarshal key with index %d into Herumi secret key, %w", idx, err) + } + + rawKeys = append(rawKeys, kpk) + + id := bls.ID{} + if err := id.SetDecString(strconv.Itoa(idx)); err != nil { + return nil, fmt.Errorf("private key id %d id isn't a number", idx) + } + + rawIDs = append(rawIDs, id) + } + + if err := pk.Recover(rawKeys, rawIDs); err != nil { + return nil, fmt.Errorf("cannot recover full private key from partial keys, %w", err) + } + + return pk.Serialize(), nil +} + +func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]byte, error) { + var rawSigns []bls.Sign + var rawIDs []bls.ID + + for idx, rawSignature := range partialSignaturesByIndex { + var signature bls.Sign + if err := signature.Deserialize(rawSignature); err != nil { + return nil, fmt.Errorf("cannot unmarshal signature with index %d into Herumi signature, %w", idx, err) + } + + rawSigns = append(rawSigns, signature) + + id := bls.ID{} + if err := id.SetDecString(strconv.Itoa(idx)); err != nil { + return nil, fmt.Errorf("signature id %d id isn't a number", idx) + } + + rawIDs = append(rawIDs, id) + } + + complete := bls.Sign{} + + if err := complete.Recover(rawSigns, rawIDs); err != nil { + return nil, fmt.Errorf("cannot combine signatures, %w", err) + } + + return complete.Serialize(), nil +} + +func (h Herumi) Verify(compressedPublicKey []byte, data []byte, rawSignature []byte) error { + var pubKey bls.PublicKey + if err := pubKey.Deserialize(compressedPublicKey); err != nil { + return fmt.Errorf("cannot set compressed public key in Herumi format, %w", err) + } + + var signature bls.Sign + if err := signature.Deserialize(rawSignature); err != nil { + return fmt.Errorf("cannot unmarshal signature into Herumi signature, %w", err) + } + + if !signature.VerifyByte(&pubKey, data) { + return fmt.Errorf("signature not verified") + } + + return nil +} + +func (h Herumi) Sign(privateKey []byte, data []byte) ([]byte, error) { + p := bls.SecretKey{} + + if err := p.SetHexString(hex.EncodeToString(privateKey)); err != nil { + return nil, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) + } + + return p.SignByte(data).Serialize(), nil +} diff --git a/tbls/taketwo/herumi/herumi_test.go b/tbls/taketwo/herumi/herumi_test.go new file mode 100644 index 000000000..60f5574ca --- /dev/null +++ b/tbls/taketwo/herumi/herumi_test.go @@ -0,0 +1,123 @@ +package herumi + +import ( + "encoding/hex" + "github.com/herumi/bls-eth-go-binary/bls" + "github.com/obolnetwork/charon/tbls/taketwo" + "github.com/stretchr/testify/require" + "testing" +) + +func TestHerumi_GenerateSecretKey(t *testing.T) { + var impl taketwo.Implementation = Herumi{} + + secret, err := impl.GenerateSecretKey() + require.NoError(t, err) + require.NotEmpty(t, secret) +} + +func TestHerumi_SecretToPublicKey(t *testing.T) { + var impl taketwo.Implementation = Herumi{} + + secret, err := impl.GenerateSecretKey() + require.NoError(t, err) + require.NotEmpty(t, secret) + + pubk, err := impl.SecretToPublicKey(secret) + require.NoError(t, err) + require.NotEmpty(t, pubk) +} + +func TestHerumi_ThresholdSplit(t *testing.T) { + var impl taketwo.Implementation = Herumi{} + + secret, err := impl.GenerateSecretKey() + require.NoError(t, err) + require.NotEmpty(t, secret) + + shares, err := impl.ThresholdSplit(secret, 5, 3) + require.NoError(t, err) + require.NotEmpty(t, shares) +} + +func TestHerumi_RecoverSecret(t *testing.T) { + var impl taketwo.Implementation = Herumi{} + + secret, err := impl.GenerateSecretKey() + require.NoError(t, err) + require.NotEmpty(t, secret) + + shares, err := impl.ThresholdSplit(secret, 5, 3) + require.NoError(t, err) + + recovered, err := impl.RecoverSecret(shares) + require.NoError(t, err) + + require.Equal(t, secret, recovered) +} + +func TestHerumi_ThresholdAggregate(t *testing.T) { + var impl taketwo.Implementation = Herumi{} + + data := []byte("hello obol!") + + secret, err := impl.GenerateSecretKey() + require.NoError(t, err) + require.NotEmpty(t, secret) + + totalOGSig, err := impl.Sign(secret, data) + require.NoError(t, err) + + shares, err := impl.ThresholdSplit(secret, 5, 3) + require.NoError(t, err) + + signatures := map[int][]byte{} + + for idx, key := range shares { + p := bls.SecretKey{} + + require.NoError(t, p.SetHexString(hex.EncodeToString(key))) + + signature := p.SignByte(data) + signatures[idx] = signature.Serialize() + } + + totalSig, err := impl.ThresholdAggregate(signatures) + require.NoError(t, err) + + require.Equal(t, totalOGSig, totalSig) +} + +func TestHerumi_Verify(t *testing.T) { + var impl taketwo.Implementation = Herumi{} + + data := []byte("hello obol!") + + secret, err := impl.GenerateSecretKey() + require.NoError(t, err) + require.NotEmpty(t, secret) + + signature, err := impl.Sign(secret, data) + require.NoError(t, err) + require.NotEmpty(t, signature) + + pubkey, err := impl.SecretToPublicKey(secret) + require.NoError(t, err) + require.NotEmpty(t, pubkey) + + require.NoError(t, impl.Verify(pubkey, data, signature)) +} + +func TestHerumi_Sign(t *testing.T) { + var impl taketwo.Implementation = Herumi{} + + data := []byte("hello obol!") + + secret, err := impl.GenerateSecretKey() + require.NoError(t, err) + require.NotEmpty(t, secret) + + signature, err := impl.Sign(secret, data) + require.NoError(t, err) + require.NotEmpty(t, signature) +} diff --git a/tbls/taketwo/take_two.go b/tbls/taketwo/take_two.go new file mode 100644 index 000000000..4c69e65b5 --- /dev/null +++ b/tbls/taketwo/take_two.go @@ -0,0 +1,64 @@ +package taketwo + +var impl Implementation = Unimplemented{} + +// Implementation defines the backing implementation for all the public functions of this package. +type Implementation interface { + // GenerateSecretKey generates a secret key and returns its compressed serialized representation. + GenerateSecretKey() ([]byte, error) + + // SecretToPublicKey extracts the public key associated with the secret passed in input, and returns its + // compressed serialized representation. + SecretToPublicKey([]byte) ([]byte, error) + + // ThresholdSplit splits secret into total units of secret keys, with the given threshold. + // It returns a map that associates each private, compressed private key to its ID. + ThresholdSplit(secret []byte, total uint, threshold uint) (map[int][]byte, error) + + // RecoverSecret recovers the original secret off the input shares. + RecoverSecret(shares map[int][]byte) ([]byte, error) + + // ThresholdAggregate aggregates the partial signatures passed in input in the final original signature. + ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]byte, error) + + // Verify verifies that signature has been produced with the private key associated with compressedPublicKey, on + // the provided data. + Verify(compressedPublicKey []byte, data []byte, signature []byte) error + + // Sign signs data with the provided private key, and returns the resulting signature. + // This function works on both shares of private keys, and complete private keys. + Sign(privateKey []byte, data []byte) ([]byte, error) +} + +// SetImplementation sets newImpl as the package backing implementation. +func SetImplementation(newImpl Implementation) { + impl = newImpl +} + +func GenerateSecretKey() ([]byte, error) { + return impl.GenerateSecretKey() +} + +func SecretToPublicKey(secret []byte) ([]byte, error) { + return impl.SecretToPublicKey(secret) +} + +func ThrehsoldSplit(secret []byte, total uint, threshold uint) (map[int][]byte, error) { + return impl.ThresholdSplit(secret, total, threshold) +} + +func RecoverSecret(shares map[int][]byte) ([]byte, error) { + return impl.RecoverSecret(shares) +} + +func ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]byte, error) { + return impl.ThresholdAggregate(partialSignaturesByIndex) +} + +func Verify(compressedPublicKey []byte, data []byte, signature []byte) error { + return impl.Verify(compressedPublicKey, data, signature) +} + +func Sign(privateKey []byte, data []byte) ([]byte, error) { + return impl.Sign(privateKey, data) +} diff --git a/tbls/taketwo/unimplemented.go b/tbls/taketwo/unimplemented.go new file mode 100644 index 000000000..3e3bf19e9 --- /dev/null +++ b/tbls/taketwo/unimplemented.go @@ -0,0 +1,36 @@ +package taketwo + +import "fmt" + +var ErrNotImplemented = fmt.Errorf("not implemented") + +// Unimplemented is an Implementation that always returns ErrNotImplemented. +type Unimplemented struct{} + +func (u Unimplemented) GenerateSecretKey() ([]byte, error) { + return nil, ErrNotImplemented +} + +func (u Unimplemented) SecretToPublicKey(_ []byte) ([]byte, error) { + return nil, ErrNotImplemented +} + +func (u Unimplemented) ThresholdSplit(_ []byte, _ uint, _ uint) (map[int][]byte, error) { + return nil, ErrNotImplemented +} + +func (u Unimplemented) RecoverSecret(_ map[int][]byte) ([]byte, error) { + return nil, ErrNotImplemented +} + +func (u Unimplemented) ThresholdAggregate(_ map[int][]byte) ([]byte, error) { + return nil, ErrNotImplemented +} + +func (u Unimplemented) Verify(_ []byte, _ []byte, _ []byte) error { + return ErrNotImplemented +} + +func (u Unimplemented) Sign(_ []byte, _ []byte) ([]byte, error) { + return nil, ErrNotImplemented +} From 4be276bdd354cf2681ffbb9abcc4f3f4a929884e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 20 Jan 2023 16:00:48 +0100 Subject: [PATCH 03/22] tbls: refactor variable initialization use `var x type` form --- tbls/taketwo/herumi/herumi.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tbls/taketwo/herumi/herumi.go b/tbls/taketwo/herumi/herumi.go index 1b2f3573f..e38e8564d 100644 --- a/tbls/taketwo/herumi/herumi.go +++ b/tbls/taketwo/herumi/herumi.go @@ -1,7 +1,6 @@ package herumi import ( - "encoding/hex" "fmt" "github.com/herumi/bls-eth-go-binary/bls" "strconv" @@ -29,14 +28,14 @@ func init() { type Herumi struct{} func (h Herumi) GenerateSecretKey() ([]byte, error) { - p := bls.SecretKey{} + var p bls.SecretKey p.SetByCSPRNG() return p.Serialize(), nil } func (h Herumi) SecretToPublicKey(secret []byte) ([]byte, error) { - p := bls.SecretKey{} + var p bls.SecretKey if err := p.Deserialize(secret); err != nil { return nil, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) @@ -51,7 +50,7 @@ func (h Herumi) SecretToPublicKey(secret []byte) ([]byte, error) { } func (h Herumi) ThresholdSplit(secret []byte, total uint, threshold uint) (map[int][]byte, error) { - p := bls.SecretKey{} + var p bls.SecretKey if err := p.Deserialize(secret); err != nil { return nil, fmt.Errorf("cannot unmarshal bytes into Herumi secret key, %w", err) @@ -64,21 +63,21 @@ func (h Herumi) ThresholdSplit(secret []byte, total uint, threshold uint) (map[i // initialize threshold amount of points for i := 1; i < int(threshold); i++ { - sk := bls.SecretKey{} + var sk bls.SecretKey sk.SetByCSPRNG() poly[i] = sk } ret := make(map[int][]byte) for i := 1; i <= int(total); i++ { - blsID := bls.ID{} + var blsID bls.ID err := blsID.SetDecString(fmt.Sprintf("%d", i)) if err != nil { return nil, fmt.Errorf("cannot set ID %d for key number %d, %w", i, i, err) } - sk := bls.SecretKey{} + var sk bls.SecretKey err = sk.Set(poly, &blsID) if err != nil { @@ -105,7 +104,7 @@ func (h Herumi) RecoverSecret(shares map[int][]byte) ([]byte, error) { rawKeys = append(rawKeys, kpk) - id := bls.ID{} + var id bls.ID if err := id.SetDecString(strconv.Itoa(idx)); err != nil { return nil, fmt.Errorf("private key id %d id isn't a number", idx) } @@ -132,7 +131,7 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]b rawSigns = append(rawSigns, signature) - id := bls.ID{} + var id bls.ID if err := id.SetDecString(strconv.Itoa(idx)); err != nil { return nil, fmt.Errorf("signature id %d id isn't a number", idx) } @@ -140,7 +139,7 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]b rawIDs = append(rawIDs, id) } - complete := bls.Sign{} + var complete bls.Sign if err := complete.Recover(rawSigns, rawIDs); err != nil { return nil, fmt.Errorf("cannot combine signatures, %w", err) @@ -168,9 +167,9 @@ func (h Herumi) Verify(compressedPublicKey []byte, data []byte, rawSignature []b } func (h Herumi) Sign(privateKey []byte, data []byte) ([]byte, error) { - p := bls.SecretKey{} + var p bls.SecretKey - if err := p.SetHexString(hex.EncodeToString(privateKey)); err != nil { + if err := p.Deserialize(privateKey); err != nil { return nil, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) } From 9a4ac3824fbbc6650ab71e696e4392bb284a8d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 20 Jan 2023 16:06:16 +0100 Subject: [PATCH 04/22] tbls: place Implementation behind a lock --- tbls/taketwo/take_two.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tbls/taketwo/take_two.go b/tbls/taketwo/take_two.go index 4c69e65b5..53829ed61 100644 --- a/tbls/taketwo/take_two.go +++ b/tbls/taketwo/take_two.go @@ -1,6 +1,9 @@ package taketwo +import "sync" + var impl Implementation = Unimplemented{} +var implLock sync.Mutex // Implementation defines the backing implementation for all the public functions of this package. type Implementation interface { @@ -32,6 +35,8 @@ type Implementation interface { // SetImplementation sets newImpl as the package backing implementation. func SetImplementation(newImpl Implementation) { + implLock.Lock() + defer implLock.Unlock() impl = newImpl } From 7bd40dc356667f1d2f16b425564f8e396fa0b484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 20 Jan 2023 16:40:03 +0100 Subject: [PATCH 05/22] tbls: replace raw []byte with specialized types Just a thin wrapper over []byte, to favorite type safety. --- tbls/taketwo/herumi/herumi.go | 17 +++++++------ tbls/taketwo/herumi/herumi_test.go | 2 +- tbls/taketwo/take_two.go | 41 +++++++++++++++++++----------- tbls/taketwo/unimplemented.go | 14 +++++----- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/tbls/taketwo/herumi/herumi.go b/tbls/taketwo/herumi/herumi.go index e38e8564d..8609b4c48 100644 --- a/tbls/taketwo/herumi/herumi.go +++ b/tbls/taketwo/herumi/herumi.go @@ -3,6 +3,7 @@ package herumi import ( "fmt" "github.com/herumi/bls-eth-go-binary/bls" + "github.com/obolnetwork/charon/tbls/taketwo" "strconv" "sync" ) @@ -27,14 +28,14 @@ func init() { // Herumi is an Implementation with Herumi-specific inner logic. type Herumi struct{} -func (h Herumi) GenerateSecretKey() ([]byte, error) { +func (h Herumi) GenerateSecretKey() (taketwo.PrivateKey, error) { var p bls.SecretKey p.SetByCSPRNG() return p.Serialize(), nil } -func (h Herumi) SecretToPublicKey(secret []byte) ([]byte, error) { +func (h Herumi) SecretToPublicKey(secret taketwo.PrivateKey) (taketwo.PublicKey, error) { var p bls.SecretKey if err := p.Deserialize(secret); err != nil { @@ -49,7 +50,7 @@ func (h Herumi) SecretToPublicKey(secret []byte) ([]byte, error) { return pubk.Serialize(), nil } -func (h Herumi) ThresholdSplit(secret []byte, total uint, threshold uint) (map[int][]byte, error) { +func (h Herumi) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold uint) (map[int]taketwo.PrivateKey, error) { var p bls.SecretKey if err := p.Deserialize(secret); err != nil { @@ -68,7 +69,7 @@ func (h Herumi) ThresholdSplit(secret []byte, total uint, threshold uint) (map[i poly[i] = sk } - ret := make(map[int][]byte) + ret := make(map[int]taketwo.PrivateKey) for i := 1; i <= int(total); i++ { var blsID bls.ID @@ -90,7 +91,7 @@ func (h Herumi) ThresholdSplit(secret []byte, total uint, threshold uint) (map[i return ret, nil } -func (h Herumi) RecoverSecret(shares map[int][]byte) ([]byte, error) { +func (h Herumi) RecoverSecret(shares map[int]taketwo.PrivateKey) (taketwo.PrivateKey, error) { var pk bls.SecretKey var rawKeys []bls.SecretKey @@ -119,7 +120,7 @@ func (h Herumi) RecoverSecret(shares map[int][]byte) ([]byte, error) { return pk.Serialize(), nil } -func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]byte, error) { +func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo.Signature) (taketwo.Signature, error) { var rawSigns []bls.Sign var rawIDs []bls.ID @@ -148,7 +149,7 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]b return complete.Serialize(), nil } -func (h Herumi) Verify(compressedPublicKey []byte, data []byte, rawSignature []byte) error { +func (h Herumi) Verify(compressedPublicKey taketwo.PublicKey, data []byte, rawSignature taketwo.Signature) error { var pubKey bls.PublicKey if err := pubKey.Deserialize(compressedPublicKey); err != nil { return fmt.Errorf("cannot set compressed public key in Herumi format, %w", err) @@ -166,7 +167,7 @@ func (h Herumi) Verify(compressedPublicKey []byte, data []byte, rawSignature []b return nil } -func (h Herumi) Sign(privateKey []byte, data []byte) ([]byte, error) { +func (h Herumi) Sign(privateKey taketwo.PrivateKey, data []byte) (taketwo.Signature, error) { var p bls.SecretKey if err := p.Deserialize(privateKey); err != nil { diff --git a/tbls/taketwo/herumi/herumi_test.go b/tbls/taketwo/herumi/herumi_test.go index 60f5574ca..113fc31f2 100644 --- a/tbls/taketwo/herumi/herumi_test.go +++ b/tbls/taketwo/herumi/herumi_test.go @@ -71,7 +71,7 @@ func TestHerumi_ThresholdAggregate(t *testing.T) { shares, err := impl.ThresholdSplit(secret, 5, 3) require.NoError(t, err) - signatures := map[int][]byte{} + signatures := map[int]taketwo.Signature{} for idx, key := range shares { p := bls.SecretKey{} diff --git a/tbls/taketwo/take_two.go b/tbls/taketwo/take_two.go index 53829ed61..e424bac44 100644 --- a/tbls/taketwo/take_two.go +++ b/tbls/taketwo/take_two.go @@ -5,32 +5,43 @@ import "sync" var impl Implementation = Unimplemented{} var implLock sync.Mutex +type ( + // PublicKey is a byte slice containing a compressed BLS12-381 public key. + PublicKey []byte + + // PrivateKey is a byte slice containing a compressed BLS12-381 private key. + PrivateKey []byte + + // Signature is a byte slice containing a BLS12-381 signature. + Signature []byte +) + // Implementation defines the backing implementation for all the public functions of this package. type Implementation interface { // GenerateSecretKey generates a secret key and returns its compressed serialized representation. - GenerateSecretKey() ([]byte, error) + GenerateSecretKey() (PrivateKey, error) // SecretToPublicKey extracts the public key associated with the secret passed in input, and returns its // compressed serialized representation. - SecretToPublicKey([]byte) ([]byte, error) + SecretToPublicKey(PrivateKey) (PublicKey, error) - // ThresholdSplit splits secret into total units of secret keys, with the given threshold. + // ThresholdSplit splits a compressed secret into total units of secret keys, with the given threshold. // It returns a map that associates each private, compressed private key to its ID. - ThresholdSplit(secret []byte, total uint, threshold uint) (map[int][]byte, error) + ThresholdSplit(secret PrivateKey, total uint, threshold uint) (map[int]PrivateKey, error) // RecoverSecret recovers the original secret off the input shares. - RecoverSecret(shares map[int][]byte) ([]byte, error) + RecoverSecret(shares map[int]PrivateKey) (PrivateKey, error) // ThresholdAggregate aggregates the partial signatures passed in input in the final original signature. - ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]byte, error) + ThresholdAggregate(partialSignaturesByIndex map[int]Signature) (Signature, error) // Verify verifies that signature has been produced with the private key associated with compressedPublicKey, on // the provided data. - Verify(compressedPublicKey []byte, data []byte, signature []byte) error + Verify(compressedPublicKey PublicKey, data []byte, signature Signature) error // Sign signs data with the provided private key, and returns the resulting signature. // This function works on both shares of private keys, and complete private keys. - Sign(privateKey []byte, data []byte) ([]byte, error) + Sign(privateKey PrivateKey, data []byte) (Signature, error) } // SetImplementation sets newImpl as the package backing implementation. @@ -40,30 +51,30 @@ func SetImplementation(newImpl Implementation) { impl = newImpl } -func GenerateSecretKey() ([]byte, error) { +func GenerateSecretKey() (PrivateKey, error) { return impl.GenerateSecretKey() } -func SecretToPublicKey(secret []byte) ([]byte, error) { +func SecretToPublicKey(secret PrivateKey) (PublicKey, error) { return impl.SecretToPublicKey(secret) } -func ThrehsoldSplit(secret []byte, total uint, threshold uint) (map[int][]byte, error) { +func ThresholdSplit(secret PrivateKey, total uint, threshold uint) (map[int]PrivateKey, error) { return impl.ThresholdSplit(secret, total, threshold) } -func RecoverSecret(shares map[int][]byte) ([]byte, error) { +func RecoverSecret(shares map[int]PrivateKey) (PrivateKey, error) { return impl.RecoverSecret(shares) } -func ThresholdAggregate(partialSignaturesByIndex map[int][]byte) ([]byte, error) { +func ThresholdAggregate(partialSignaturesByIndex map[int]Signature) (Signature, error) { return impl.ThresholdAggregate(partialSignaturesByIndex) } -func Verify(compressedPublicKey []byte, data []byte, signature []byte) error { +func Verify(compressedPublicKey PublicKey, data []byte, signature Signature) error { return impl.Verify(compressedPublicKey, data, signature) } -func Sign(privateKey []byte, data []byte) ([]byte, error) { +func SSign(privateKey PrivateKey, data []byte) (Signature, error) { return impl.Sign(privateKey, data) } diff --git a/tbls/taketwo/unimplemented.go b/tbls/taketwo/unimplemented.go index 3e3bf19e9..79dc43def 100644 --- a/tbls/taketwo/unimplemented.go +++ b/tbls/taketwo/unimplemented.go @@ -7,30 +7,30 @@ var ErrNotImplemented = fmt.Errorf("not implemented") // Unimplemented is an Implementation that always returns ErrNotImplemented. type Unimplemented struct{} -func (u Unimplemented) GenerateSecretKey() ([]byte, error) { +func (u Unimplemented) GenerateSecretKey() (PrivateKey, error) { return nil, ErrNotImplemented } -func (u Unimplemented) SecretToPublicKey(_ []byte) ([]byte, error) { +func (u Unimplemented) SecretToPublicKey(_ PrivateKey) (PublicKey, error) { return nil, ErrNotImplemented } -func (u Unimplemented) ThresholdSplit(_ []byte, _ uint, _ uint) (map[int][]byte, error) { +func (u Unimplemented) ThresholdSplit(_ PrivateKey, _ uint, _ uint) (map[int]PrivateKey, error) { return nil, ErrNotImplemented } -func (u Unimplemented) RecoverSecret(_ map[int][]byte) ([]byte, error) { +func (u Unimplemented) RecoverSecret(_ map[int]PrivateKey) (PrivateKey, error) { return nil, ErrNotImplemented } -func (u Unimplemented) ThresholdAggregate(_ map[int][]byte) ([]byte, error) { +func (u Unimplemented) ThresholdAggregate(_ map[int]Signature) (Signature, error) { return nil, ErrNotImplemented } -func (u Unimplemented) Verify(_ []byte, _ []byte, _ []byte) error { +func (u Unimplemented) Verify(_ PublicKey, _ []byte, _ Signature) error { return ErrNotImplemented } -func (u Unimplemented) Sign(_ []byte, _ []byte) ([]byte, error) { +func (u Unimplemented) Sign(_ PrivateKey, _ []byte) (Signature, error) { return nil, ErrNotImplemented } From e90f9d02ca396a95ae0957166bece6b7736d8fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 20 Jan 2023 17:28:09 +0100 Subject: [PATCH 06/22] tbls: pass total and threshold parameters to RecoverSecret Needed because Kryptology API requires it. --- tbls/taketwo/herumi/herumi.go | 2 +- tbls/taketwo/herumi/herumi_test.go | 2 +- tbls/taketwo/take_two.go | 6 +++--- tbls/taketwo/unimplemented.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tbls/taketwo/herumi/herumi.go b/tbls/taketwo/herumi/herumi.go index 8609b4c48..b1dac4c07 100644 --- a/tbls/taketwo/herumi/herumi.go +++ b/tbls/taketwo/herumi/herumi.go @@ -91,7 +91,7 @@ func (h Herumi) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold return ret, nil } -func (h Herumi) RecoverSecret(shares map[int]taketwo.PrivateKey) (taketwo.PrivateKey, error) { +func (h Herumi) RecoverSecret(shares map[int]taketwo.PrivateKey, _, _ uint) (taketwo.PrivateKey, error) { var pk bls.SecretKey var rawKeys []bls.SecretKey diff --git a/tbls/taketwo/herumi/herumi_test.go b/tbls/taketwo/herumi/herumi_test.go index 113fc31f2..f1da33f19 100644 --- a/tbls/taketwo/herumi/herumi_test.go +++ b/tbls/taketwo/herumi/herumi_test.go @@ -50,7 +50,7 @@ func TestHerumi_RecoverSecret(t *testing.T) { shares, err := impl.ThresholdSplit(secret, 5, 3) require.NoError(t, err) - recovered, err := impl.RecoverSecret(shares) + recovered, err := impl.RecoverSecret(shares, 5, 3) require.NoError(t, err) require.Equal(t, secret, recovered) diff --git a/tbls/taketwo/take_two.go b/tbls/taketwo/take_two.go index e424bac44..4399b80c3 100644 --- a/tbls/taketwo/take_two.go +++ b/tbls/taketwo/take_two.go @@ -30,7 +30,7 @@ type Implementation interface { ThresholdSplit(secret PrivateKey, total uint, threshold uint) (map[int]PrivateKey, error) // RecoverSecret recovers the original secret off the input shares. - RecoverSecret(shares map[int]PrivateKey) (PrivateKey, error) + RecoverSecret(shares map[int]PrivateKey, total uint, threshold uint) (PrivateKey, error) // ThresholdAggregate aggregates the partial signatures passed in input in the final original signature. ThresholdAggregate(partialSignaturesByIndex map[int]Signature) (Signature, error) @@ -63,8 +63,8 @@ func ThresholdSplit(secret PrivateKey, total uint, threshold uint) (map[int]Priv return impl.ThresholdSplit(secret, total, threshold) } -func RecoverSecret(shares map[int]PrivateKey) (PrivateKey, error) { - return impl.RecoverSecret(shares) +func RecoverSecret(shares map[int]PrivateKey, total uint, threshold uint) (PrivateKey, error) { + return impl.RecoverSecret(shares, total, threshold) } func ThresholdAggregate(partialSignaturesByIndex map[int]Signature) (Signature, error) { diff --git a/tbls/taketwo/unimplemented.go b/tbls/taketwo/unimplemented.go index 79dc43def..207ebffc0 100644 --- a/tbls/taketwo/unimplemented.go +++ b/tbls/taketwo/unimplemented.go @@ -19,7 +19,7 @@ func (u Unimplemented) ThresholdSplit(_ PrivateKey, _ uint, _ uint) (map[int]Pri return nil, ErrNotImplemented } -func (u Unimplemented) RecoverSecret(_ map[int]PrivateKey) (PrivateKey, error) { +func (u Unimplemented) RecoverSecret(_ map[int]PrivateKey, _ uint, _ uint) (PrivateKey, error) { return nil, ErrNotImplemented } From 5626ff230746f2f88419f0399d69e00333c5b1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 20 Jan 2023 17:30:54 +0100 Subject: [PATCH 07/22] tbls: partial Kryptology implementation of tbls.Interface Missing ThresholdAggregate, Verify and Sign, plus tests. --- tbls/taketwo/kryptology/kryptology.go | 144 ++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 tbls/taketwo/kryptology/kryptology.go diff --git a/tbls/taketwo/kryptology/kryptology.go b/tbls/taketwo/kryptology/kryptology.go new file mode 100644 index 000000000..7982c748d --- /dev/null +++ b/tbls/taketwo/kryptology/kryptology.go @@ -0,0 +1,144 @@ +package kryptology + +import ( + "crypto/rand" + "github.com/coinbase/kryptology/pkg/core/curves" + share "github.com/coinbase/kryptology/pkg/sharing" + "github.com/obolnetwork/charon/app/errors" + "github.com/obolnetwork/charon/tbls/taketwo" + "io" + + "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" +) + +// blsScheme is the BLS12-381 ETH2 signature scheme with standard domain separation tag used for signatures. +// blsScheme uses proofs of possession to mitigate rogue-key attacks. +// see: https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03#section-4.2.3 +var blsScheme = bls_sig.NewSigEth2() + +// Kryptology is an Implementation with Kryptology-specific inner logic. +type Kryptology struct{} + +func (k Kryptology) GenerateSecretKey() (taketwo.PrivateKey, error) { + _, secret, err := blsScheme.Keygen() + if err != nil { + return nil, errors.Wrap(err, "generate key") + } + + return secret.MarshalBinary() +} + +func (k Kryptology) SecretToPublicKey(key taketwo.PrivateKey) (taketwo.PublicKey, error) { + rawKey := new(bls_sig.SecretKey) + if err := rawKey.UnmarshalBinary(key); err != nil { + return nil, errors.Wrap(err, "unmarshal raw key into kryptology object") + } + + pubKey, err := rawKey.GetPublicKeyVt() + if err != nil { + return nil, errors.Wrap(err, "get public key") + } + + return pubKey.MarshalBinary() +} + +func (k Kryptology) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold uint) (map[int]taketwo.PrivateKey, error) { + scheme, err := share.NewFeldman(uint32(total), uint32(threshold), curves.BLS12381G1()) + if err != nil { + return nil, errors.Wrap(err, "new Feldman VSS") + } + + secretScaler, err := curves.BLS12381G1().NewScalar().SetBytes(secret) + if err != nil { + return nil, errors.Wrap(err, "convert to scaler") + } + + _, shares, err := scheme.Split(secretScaler, rand.Reader) + if err != nil { + return nil, errors.Wrap(err, "split Secret Key") + } + + sks := make(map[int]taketwo.PrivateKey) + + for _, s := range shares { + sks[int(s.Id)] = s.Value + } + + return sks, nil +} + +func (k Kryptology) RecoverSecret(shares map[int]taketwo.PrivateKey, total uint, threshold uint) (taketwo.PrivateKey, error) { + var shamirShares []*share.ShamirShare + for idx, value := range shares { + shamirShare := share.ShamirShare{ + Id: uint32(idx), + Value: value, + } + + shamirShares = append(shamirShares, &shamirShare) + } + + scheme, err := share.NewFeldman(uint32(total), uint32(threshold), curves.BLS12381G1()) + if err != nil { + return nil, errors.Wrap(err, "new Feldman VSS") + } + + secretScaler, err := scheme.Combine(shamirShares...) + if err != nil { + return nil, errors.Wrap(err, "combine shares") + } + + resp := new(bls_sig.SecretKey) + if err := resp.UnmarshalBinary(secretScaler.Bytes()); err != nil { + return nil, errors.Wrap(err, "unmarshal secret") + } + + return resp.MarshalBinary() +} + +func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo.Signature) (taketwo.Signature, error) { + //TODO implement me + panic("implement me") +} + +func (k Kryptology) Verify(compressedPublicKey taketwo.PublicKey, data []byte, signature taketwo.Signature) error { + //TODO implement me + panic("implement me") +} + +func (k Kryptology) Sign(privateKey taketwo.PrivateKey, data []byte) (taketwo.Signature, error) { + //TODO implement me + panic("implement me") +} + +// SplitSecret splits the secret and returns n secret shares and t verifiers. +func splitSecret(secret taketwo.PrivateKey, t, n int, reader io.Reader) ([]*bls_sig.SecretKeyShare, error) { + scheme, err := share.NewFeldman(uint32(t), uint32(n), curves.BLS12381G1()) + if err != nil { + return nil, errors.Wrap(err, "new Feldman VSS") + } + + secretScaler, err := curves.BLS12381G1().NewScalar().SetBytes(secret) + if err != nil { + return nil, errors.Wrap(err, "convert to scaler") + } + + _, shares, err := scheme.Split(secretScaler, reader) + if err != nil { + return nil, errors.Wrap(err, "split Secret Key") + } + + sks := make([]*bls_sig.SecretKeyShare, len(shares)) + + for i, s := range shares { + // ref: https://github.com/coinbase/kryptology/blob/71ffd4cbf01951cd0ee056fc7b45b13ffb178330/pkg/signatures/bls/bls_sig/lib.go#L26 + skbin := s.Value + skbin = append(skbin, byte(s.Id)) + sks[i] = &bls_sig.SecretKeyShare{} + if err := sks[i].UnmarshalBinary(skbin); err != nil { + return nil, errors.Wrap(err, "unmarshalling shamir share") + } + } + + return sks, nil +} From 74178d210add9a679a04cfeada179fd0596343ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Mon, 23 Jan 2023 15:28:47 +0100 Subject: [PATCH 08/22] tbls: generalize testing framework Since we're trying to abstract library details, it makes sense to have a common set of tests, and something in place that allows us to run it by switching the underlying implementation. --- tbls/taketwo/herumi/herumi_test.go | 123 --------------------------- tbls/taketwo/take_two_test.go | 130 +++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 123 deletions(-) delete mode 100644 tbls/taketwo/herumi/herumi_test.go create mode 100644 tbls/taketwo/take_two_test.go diff --git a/tbls/taketwo/herumi/herumi_test.go b/tbls/taketwo/herumi/herumi_test.go deleted file mode 100644 index f1da33f19..000000000 --- a/tbls/taketwo/herumi/herumi_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package herumi - -import ( - "encoding/hex" - "github.com/herumi/bls-eth-go-binary/bls" - "github.com/obolnetwork/charon/tbls/taketwo" - "github.com/stretchr/testify/require" - "testing" -) - -func TestHerumi_GenerateSecretKey(t *testing.T) { - var impl taketwo.Implementation = Herumi{} - - secret, err := impl.GenerateSecretKey() - require.NoError(t, err) - require.NotEmpty(t, secret) -} - -func TestHerumi_SecretToPublicKey(t *testing.T) { - var impl taketwo.Implementation = Herumi{} - - secret, err := impl.GenerateSecretKey() - require.NoError(t, err) - require.NotEmpty(t, secret) - - pubk, err := impl.SecretToPublicKey(secret) - require.NoError(t, err) - require.NotEmpty(t, pubk) -} - -func TestHerumi_ThresholdSplit(t *testing.T) { - var impl taketwo.Implementation = Herumi{} - - secret, err := impl.GenerateSecretKey() - require.NoError(t, err) - require.NotEmpty(t, secret) - - shares, err := impl.ThresholdSplit(secret, 5, 3) - require.NoError(t, err) - require.NotEmpty(t, shares) -} - -func TestHerumi_RecoverSecret(t *testing.T) { - var impl taketwo.Implementation = Herumi{} - - secret, err := impl.GenerateSecretKey() - require.NoError(t, err) - require.NotEmpty(t, secret) - - shares, err := impl.ThresholdSplit(secret, 5, 3) - require.NoError(t, err) - - recovered, err := impl.RecoverSecret(shares, 5, 3) - require.NoError(t, err) - - require.Equal(t, secret, recovered) -} - -func TestHerumi_ThresholdAggregate(t *testing.T) { - var impl taketwo.Implementation = Herumi{} - - data := []byte("hello obol!") - - secret, err := impl.GenerateSecretKey() - require.NoError(t, err) - require.NotEmpty(t, secret) - - totalOGSig, err := impl.Sign(secret, data) - require.NoError(t, err) - - shares, err := impl.ThresholdSplit(secret, 5, 3) - require.NoError(t, err) - - signatures := map[int]taketwo.Signature{} - - for idx, key := range shares { - p := bls.SecretKey{} - - require.NoError(t, p.SetHexString(hex.EncodeToString(key))) - - signature := p.SignByte(data) - signatures[idx] = signature.Serialize() - } - - totalSig, err := impl.ThresholdAggregate(signatures) - require.NoError(t, err) - - require.Equal(t, totalOGSig, totalSig) -} - -func TestHerumi_Verify(t *testing.T) { - var impl taketwo.Implementation = Herumi{} - - data := []byte("hello obol!") - - secret, err := impl.GenerateSecretKey() - require.NoError(t, err) - require.NotEmpty(t, secret) - - signature, err := impl.Sign(secret, data) - require.NoError(t, err) - require.NotEmpty(t, signature) - - pubkey, err := impl.SecretToPublicKey(secret) - require.NoError(t, err) - require.NotEmpty(t, pubkey) - - require.NoError(t, impl.Verify(pubkey, data, signature)) -} - -func TestHerumi_Sign(t *testing.T) { - var impl taketwo.Implementation = Herumi{} - - data := []byte("hello obol!") - - secret, err := impl.GenerateSecretKey() - require.NoError(t, err) - require.NotEmpty(t, secret) - - signature, err := impl.Sign(secret, data) - require.NoError(t, err) - require.NotEmpty(t, signature) -} diff --git a/tbls/taketwo/take_two_test.go b/tbls/taketwo/take_two_test.go new file mode 100644 index 000000000..3f533580e --- /dev/null +++ b/tbls/taketwo/take_two_test.go @@ -0,0 +1,130 @@ +package taketwo_test + +import ( + "github.com/obolnetwork/charon/tbls/taketwo" + herumiImpl "github.com/obolnetwork/charon/tbls/taketwo/herumi" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "testing" +) + +type TestSuite struct { + suite.Suite + + impl taketwo.Implementation +} + +func NewTestSuite(implementation taketwo.Implementation) TestSuite { + return TestSuite{ + impl: implementation, + } +} + +func (ts *TestSuite) SetupTest() { + taketwo.SetImplementation(ts.impl) +} + +func (ts *TestSuite) Test_GenerateSecretKey() { + secret, err := ts.impl.GenerateSecretKey() + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), secret) +} + +func (ts *TestSuite) Test_SecretToPublicKey() { + secret, err := ts.impl.GenerateSecretKey() + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), secret) + + pubk, err := ts.impl.SecretToPublicKey(secret) + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), pubk) +} + +func (ts *TestSuite) Test_ThresholdSplit() { + secret, err := ts.impl.GenerateSecretKey() + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), secret) + + shares, err := ts.impl.ThresholdSplit(secret, 5, 3) + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), shares) +} + +func (ts *TestSuite) Test_RecoverSecret() { + secret, err := ts.impl.GenerateSecretKey() + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), secret) + + shares, err := ts.impl.ThresholdSplit(secret, 5, 3) + require.NoError(ts.T(), err) + + recovered, err := ts.impl.RecoverSecret(shares, 5, 3) + require.NoError(ts.T(), err) + + require.Equal(ts.T(), secret, recovered) +} + +func (ts *TestSuite) Test_ThresholdAggregate() { + data := []byte("hello obol!") + + secret, err := ts.impl.GenerateSecretKey() + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), secret) + + totalOGSig, err := ts.impl.Sign(secret, data) + require.NoError(ts.T(), err) + + shares, err := ts.impl.ThresholdSplit(secret, 5, 3) + require.NoError(ts.T(), err) + + signatures := map[int]taketwo.Signature{} + + for idx, key := range shares { + signature, err := ts.impl.Sign(key, data) + require.NoError(ts.T(), err) + signatures[idx] = signature + } + + totalSig, err := ts.impl.ThresholdAggregate(signatures) + require.NoError(ts.T(), err) + + require.Equal(ts.T(), totalOGSig, totalSig) +} + +func (ts *TestSuite) Test_Verify() { + data := []byte("hello obol!") + + secret, err := ts.impl.GenerateSecretKey() + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), secret) + + signature, err := ts.impl.Sign(secret, data) + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), signature) + + pubkey, err := ts.impl.SecretToPublicKey(secret) + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), pubkey) + + require.NoError(ts.T(), ts.impl.Verify(pubkey, data, signature)) +} + +func (ts *TestSuite) Test_Sign() { + data := []byte("hello obol!") + + secret, err := ts.impl.GenerateSecretKey() + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), secret) + + signature, err := ts.impl.Sign(secret, data) + require.NoError(ts.T(), err) + require.NotEmpty(ts.T(), signature) +} + +func TestHerumiImplementation(t *testing.T) { + herumi := herumiImpl.Herumi{} + + ts := NewTestSuite(herumi) + + suite.Run(t, &ts) +} From 67ab196da39b9f6c478c718072b6d1d04778a2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Mon, 23 Jan 2023 16:08:55 +0100 Subject: [PATCH 09/22] tbls: complete Kryptology implementation of taketwo.Implementation Shared test suite looks alright. --- tbls/taketwo/kryptology/kryptology.go | 83 +++++++++++++++------------ tbls/taketwo/take_two_test.go | 15 +++-- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/tbls/taketwo/kryptology/kryptology.go b/tbls/taketwo/kryptology/kryptology.go index 7982c748d..fc2b9b2a9 100644 --- a/tbls/taketwo/kryptology/kryptology.go +++ b/tbls/taketwo/kryptology/kryptology.go @@ -4,11 +4,9 @@ import ( "crypto/rand" "github.com/coinbase/kryptology/pkg/core/curves" share "github.com/coinbase/kryptology/pkg/sharing" + "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" "github.com/obolnetwork/charon/app/errors" "github.com/obolnetwork/charon/tbls/taketwo" - "io" - - "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" ) // blsScheme is the BLS12-381 ETH2 signature scheme with standard domain separation tag used for signatures. @@ -34,7 +32,7 @@ func (k Kryptology) SecretToPublicKey(key taketwo.PrivateKey) (taketwo.PublicKey return nil, errors.Wrap(err, "unmarshal raw key into kryptology object") } - pubKey, err := rawKey.GetPublicKeyVt() + pubKey, err := rawKey.GetPublicKey() if err != nil { return nil, errors.Wrap(err, "get public key") } @@ -43,7 +41,7 @@ func (k Kryptology) SecretToPublicKey(key taketwo.PrivateKey) (taketwo.PublicKey } func (k Kryptology) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold uint) (map[int]taketwo.PrivateKey, error) { - scheme, err := share.NewFeldman(uint32(total), uint32(threshold), curves.BLS12381G1()) + scheme, err := share.NewFeldman(uint32(threshold), uint32(total), curves.BLS12381G1()) if err != nil { return nil, errors.Wrap(err, "new Feldman VSS") } @@ -78,7 +76,7 @@ func (k Kryptology) RecoverSecret(shares map[int]taketwo.PrivateKey, total uint, shamirShares = append(shamirShares, &shamirShare) } - scheme, err := share.NewFeldman(uint32(total), uint32(threshold), curves.BLS12381G1()) + scheme, err := share.NewFeldman(uint32(threshold), uint32(total), curves.BLS12381G1()) if err != nil { return nil, errors.Wrap(err, "new Feldman VSS") } @@ -97,48 +95,61 @@ func (k Kryptology) RecoverSecret(shares map[int]taketwo.PrivateKey, total uint, } func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo.Signature) (taketwo.Signature, error) { - //TODO implement me - panic("implement me") -} + var kryptologyPartialSigs []*bls_sig.PartialSignature -func (k Kryptology) Verify(compressedPublicKey taketwo.PublicKey, data []byte, signature taketwo.Signature) error { - //TODO implement me - panic("implement me") -} + for idx, sig := range partialSignaturesByIndex { + rawSign := new(bls_sig.Signature) + if err := rawSign.UnmarshalBinary(sig); err != nil { + return nil, errors.Wrap(err, "unmarshal raw signature into kryptology object") + } -func (k Kryptology) Sign(privateKey taketwo.PrivateKey, data []byte) (taketwo.Signature, error) { - //TODO implement me - panic("implement me") -} + kryptologyPartialSigs = append(kryptologyPartialSigs, &bls_sig.PartialSignature{ + Identifier: byte(idx), + Signature: rawSign.Value, + }) + } -// SplitSecret splits the secret and returns n secret shares and t verifiers. -func splitSecret(secret taketwo.PrivateKey, t, n int, reader io.Reader) ([]*bls_sig.SecretKeyShare, error) { - scheme, err := share.NewFeldman(uint32(t), uint32(n), curves.BLS12381G1()) + aggSig, err := blsScheme.CombineSignatures(kryptologyPartialSigs...) if err != nil { - return nil, errors.Wrap(err, "new Feldman VSS") + return nil, errors.Wrap(err, "aggregate signatures") } - secretScaler, err := curves.BLS12381G1().NewScalar().SetBytes(secret) - if err != nil { - return nil, errors.Wrap(err, "convert to scaler") + return aggSig.MarshalBinary() +} + +func (k Kryptology) Verify(compressedPublicKey taketwo.PublicKey, data []byte, signature taketwo.Signature) error { + rawKey := new(bls_sig.PublicKey) + if err := rawKey.UnmarshalBinary(compressedPublicKey); err != nil { + return errors.Wrap(err, "unmarshal raw public key into kryptology object") + } + + rawSign := new(bls_sig.Signature) + if err := rawSign.UnmarshalBinary(signature); err != nil { + return errors.Wrap(err, "unmarshal raw signature into kryptology object") } - _, shares, err := scheme.Split(secretScaler, reader) + valid, err := blsScheme.Verify(rawKey, data, rawSign) if err != nil { - return nil, errors.Wrap(err, "split Secret Key") + return errors.Wrap(err, "verification error") } - sks := make([]*bls_sig.SecretKeyShare, len(shares)) + if !valid { + return errors.New("signature verification failed") + } - for i, s := range shares { - // ref: https://github.com/coinbase/kryptology/blob/71ffd4cbf01951cd0ee056fc7b45b13ffb178330/pkg/signatures/bls/bls_sig/lib.go#L26 - skbin := s.Value - skbin = append(skbin, byte(s.Id)) - sks[i] = &bls_sig.SecretKeyShare{} - if err := sks[i].UnmarshalBinary(skbin); err != nil { - return nil, errors.Wrap(err, "unmarshalling shamir share") - } + return nil +} + +func (k Kryptology) Sign(privateKey taketwo.PrivateKey, data []byte) (taketwo.Signature, error) { + rawKey := new(bls_sig.SecretKey) + if err := rawKey.UnmarshalBinary(privateKey); err != nil { + return nil, errors.Wrap(err, "unmarshal raw private key into kryptology object") } - return sks, nil + rawSign, err := blsScheme.Sign(rawKey, data) + if err != nil { + return nil, err + } + + return rawSign.MarshalBinary() } diff --git a/tbls/taketwo/take_two_test.go b/tbls/taketwo/take_two_test.go index 3f533580e..7a27a5869 100644 --- a/tbls/taketwo/take_two_test.go +++ b/tbls/taketwo/take_two_test.go @@ -3,6 +3,7 @@ package taketwo_test import ( "github.com/obolnetwork/charon/tbls/taketwo" herumiImpl "github.com/obolnetwork/charon/tbls/taketwo/herumi" + "github.com/obolnetwork/charon/tbls/taketwo/kryptology" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "testing" @@ -121,10 +122,16 @@ func (ts *TestSuite) Test_Sign() { require.NotEmpty(ts.T(), signature) } -func TestHerumiImplementation(t *testing.T) { - herumi := herumiImpl.Herumi{} - - ts := NewTestSuite(herumi) +func runSuite(t *testing.T, i taketwo.Implementation) { + ts := NewTestSuite(i) suite.Run(t, &ts) } + +func TestHerumiImplementation(t *testing.T) { + runSuite(t, herumiImpl.Herumi{}) +} + +func TestKryptologyImplementation(t *testing.T) { + runSuite(t, kryptology.Kryptology{}) +} From 660eadfaf05b31ed63943b3c58ec99230d0bb355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Mon, 23 Jan 2023 16:38:55 +0100 Subject: [PATCH 10/22] tbls: add randomized taketwo.Implementation This taketwo.Implementation takes a set of taketwo.Implementation's, and whenever one of the interface's method is called it takes one randomly. This is useful for testing, since it can tell us whether a set of taketwo.Implementation's are compatible among themselves. --- tbls/taketwo/take_two.go | 2 +- tbls/taketwo/take_two_test.go | 141 +++++++++++++++++++++++++++++----- 2 files changed, 121 insertions(+), 22 deletions(-) diff --git a/tbls/taketwo/take_two.go b/tbls/taketwo/take_two.go index 4399b80c3..66ace0656 100644 --- a/tbls/taketwo/take_two.go +++ b/tbls/taketwo/take_two.go @@ -75,6 +75,6 @@ func Verify(compressedPublicKey PublicKey, data []byte, signature Signature) err return impl.Verify(compressedPublicKey, data, signature) } -func SSign(privateKey PrivateKey, data []byte) (Signature, error) { +func Sign(privateKey PrivateKey, data []byte) (Signature, error) { return impl.Sign(privateKey, data) } diff --git a/tbls/taketwo/take_two_test.go b/tbls/taketwo/take_two_test.go index 7a27a5869..84f853dd4 100644 --- a/tbls/taketwo/take_two_test.go +++ b/tbls/taketwo/take_two_test.go @@ -1,11 +1,13 @@ package taketwo_test import ( + "crypto/rand" "github.com/obolnetwork/charon/tbls/taketwo" herumiImpl "github.com/obolnetwork/charon/tbls/taketwo/herumi" "github.com/obolnetwork/charon/tbls/taketwo/kryptology" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "math/big" "testing" ) @@ -15,9 +17,9 @@ type TestSuite struct { impl taketwo.Implementation } -func NewTestSuite(implementation taketwo.Implementation) TestSuite { +func NewTestSuite(implementations taketwo.Implementation) TestSuite { return TestSuite{ - impl: implementation, + impl: implementations, } } @@ -25,41 +27,45 @@ func (ts *TestSuite) SetupTest() { taketwo.SetImplementation(ts.impl) } +func (ts *TestSuite) implToUse() { + +} + func (ts *TestSuite) Test_GenerateSecretKey() { - secret, err := ts.impl.GenerateSecretKey() + secret, err := taketwo.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) } func (ts *TestSuite) Test_SecretToPublicKey() { - secret, err := ts.impl.GenerateSecretKey() + secret, err := taketwo.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - pubk, err := ts.impl.SecretToPublicKey(secret) + pubk, err := taketwo.SecretToPublicKey(secret) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), pubk) } func (ts *TestSuite) Test_ThresholdSplit() { - secret, err := ts.impl.GenerateSecretKey() + secret, err := taketwo.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - shares, err := ts.impl.ThresholdSplit(secret, 5, 3) + shares, err := taketwo.ThresholdSplit(secret, 5, 3) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), shares) } func (ts *TestSuite) Test_RecoverSecret() { - secret, err := ts.impl.GenerateSecretKey() + secret, err := taketwo.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - shares, err := ts.impl.ThresholdSplit(secret, 5, 3) + shares, err := taketwo.ThresholdSplit(secret, 5, 3) require.NoError(ts.T(), err) - recovered, err := ts.impl.RecoverSecret(shares, 5, 3) + recovered, err := taketwo.RecoverSecret(shares, 5, 3) require.NoError(ts.T(), err) require.Equal(ts.T(), secret, recovered) @@ -68,25 +74,25 @@ func (ts *TestSuite) Test_RecoverSecret() { func (ts *TestSuite) Test_ThresholdAggregate() { data := []byte("hello obol!") - secret, err := ts.impl.GenerateSecretKey() + secret, err := taketwo.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - totalOGSig, err := ts.impl.Sign(secret, data) + totalOGSig, err := taketwo.Sign(secret, data) require.NoError(ts.T(), err) - shares, err := ts.impl.ThresholdSplit(secret, 5, 3) + shares, err := taketwo.ThresholdSplit(secret, 5, 3) require.NoError(ts.T(), err) signatures := map[int]taketwo.Signature{} for idx, key := range shares { - signature, err := ts.impl.Sign(key, data) + signature, err := taketwo.Sign(key, data) require.NoError(ts.T(), err) signatures[idx] = signature } - totalSig, err := ts.impl.ThresholdAggregate(signatures) + totalSig, err := taketwo.ThresholdAggregate(signatures) require.NoError(ts.T(), err) require.Equal(ts.T(), totalOGSig, totalSig) @@ -95,29 +101,29 @@ func (ts *TestSuite) Test_ThresholdAggregate() { func (ts *TestSuite) Test_Verify() { data := []byte("hello obol!") - secret, err := ts.impl.GenerateSecretKey() + secret, err := taketwo.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - signature, err := ts.impl.Sign(secret, data) + signature, err := taketwo.Sign(secret, data) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), signature) - pubkey, err := ts.impl.SecretToPublicKey(secret) + pubkey, err := taketwo.SecretToPublicKey(secret) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), pubkey) - require.NoError(ts.T(), ts.impl.Verify(pubkey, data, signature)) + require.NoError(ts.T(), taketwo.Verify(pubkey, data, signature)) } func (ts *TestSuite) Test_Sign() { data := []byte("hello obol!") - secret, err := ts.impl.GenerateSecretKey() + secret, err := taketwo.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - signature, err := ts.impl.Sign(secret, data) + signature, err := taketwo.Sign(secret, data) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), signature) } @@ -135,3 +141,96 @@ func TestHerumiImplementation(t *testing.T) { func TestKryptologyImplementation(t *testing.T) { runSuite(t, kryptology.Kryptology{}) } + +func TestRandomized(t *testing.T) { + runSuite(t, randomizedImpl{ + implementations: []taketwo.Implementation{ + herumiImpl.Herumi{}, + kryptology.Kryptology{}, + }, + }) +} + +// randomizedImpl randomizes execution of each call by choosing a random element from +// the implementations slice. +// Useful to test whether two implementations are compatible. +type randomizedImpl struct { + implementations []taketwo.Implementation +} + +func (r randomizedImpl) selectImpl() (taketwo.Implementation, error) { + blen := big.NewInt(int64(len(r.implementations))) + + // random number: [0, len(ts.impl)) + rawN, err := rand.Int(rand.Reader, blen) + if err != nil { + return nil, err + } + + nativeN := int(rawN.Int64()) + + return r.implementations[nativeN], nil +} + +func (r randomizedImpl) GenerateSecretKey() (taketwo.PrivateKey, error) { + impl, err := r.selectImpl() + if err != nil { + return nil, err + } + + return impl.GenerateSecretKey() +} + +func (r randomizedImpl) SecretToPublicKey(key taketwo.PrivateKey) (taketwo.PublicKey, error) { + impl, err := r.selectImpl() + if err != nil { + return nil, err + } + + return impl.SecretToPublicKey(key) +} + +func (r randomizedImpl) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold uint) (map[int]taketwo.PrivateKey, error) { + impl, err := r.selectImpl() + if err != nil { + return nil, err + } + + return impl.ThresholdSplit(secret, total, threshold) +} + +func (r randomizedImpl) RecoverSecret(shares map[int]taketwo.PrivateKey, total uint, threshold uint) (taketwo.PrivateKey, error) { + impl, err := r.selectImpl() + if err != nil { + return nil, err + } + + return impl.RecoverSecret(shares, total, threshold) +} + +func (r randomizedImpl) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo.Signature) (taketwo.Signature, error) { + impl, err := r.selectImpl() + if err != nil { + return nil, err + } + + return impl.ThresholdAggregate(partialSignaturesByIndex) +} + +func (r randomizedImpl) Verify(compressedPublicKey taketwo.PublicKey, data []byte, signature taketwo.Signature) error { + impl, err := r.selectImpl() + if err != nil { + return err + } + + return impl.Verify(compressedPublicKey, data, signature) +} + +func (r randomizedImpl) Sign(privateKey taketwo.PrivateKey, data []byte) (taketwo.Signature, error) { + impl, err := r.selectImpl() + if err != nil { + return nil, err + } + + return impl.Sign(privateKey, data) +} From 1f96967f0e0a20820816502bf97bcf98697070fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Mon, 23 Jan 2023 16:39:30 +0100 Subject: [PATCH 11/22] tbls: remove unused method in taketwo tests --- tbls/taketwo/take_two_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tbls/taketwo/take_two_test.go b/tbls/taketwo/take_two_test.go index 84f853dd4..8d4281b0a 100644 --- a/tbls/taketwo/take_two_test.go +++ b/tbls/taketwo/take_two_test.go @@ -27,10 +27,6 @@ func (ts *TestSuite) SetupTest() { taketwo.SetImplementation(ts.impl) } -func (ts *TestSuite) implToUse() { - -} - func (ts *TestSuite) Test_GenerateSecretKey() { secret, err := taketwo.GenerateSecretKey() require.NoError(ts.T(), err) From 5d8838159302a1f569bd1ec5dd6a1a707967e5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Mon, 23 Jan 2023 17:30:05 +0100 Subject: [PATCH 12/22] tbls: add benchmarks for all available taketwo.Implementation's --- tbls/taketwo/take_two_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tbls/taketwo/take_two_test.go b/tbls/taketwo/take_two_test.go index 8d4281b0a..5ac35838f 100644 --- a/tbls/taketwo/take_two_test.go +++ b/tbls/taketwo/take_two_test.go @@ -138,6 +138,37 @@ func TestKryptologyImplementation(t *testing.T) { runSuite(t, kryptology.Kryptology{}) } +func runBenchmark(b *testing.B, impl taketwo.Implementation) { + s := NewTestSuite(impl) + t := &testing.T{} + s.SetT(t) + s.SetupTest() + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + // NOTE: we can't run suite.Run() here because testify doesn't allow us to pass testing.B in place of + // testing.T. + // So we're manually listing all the interface's methods here. + // I'm sorry. + s.Test_GenerateSecretKey() + s.Test_SecretToPublicKey() + s.Test_ThresholdSplit() + s.Test_RecoverSecret() + s.Test_ThresholdAggregate() + s.Test_Verify() + s.Test_Sign() + } +} + +func BenchmarkHerumiImplementation(b *testing.B) { + runBenchmark(b, herumiImpl.Herumi{}) +} + +func BenchmarkKryptologyImplementation(b *testing.B) { + runBenchmark(b, kryptology.Kryptology{}) +} + func TestRandomized(t *testing.T) { runSuite(t, randomizedImpl{ implementations: []taketwo.Implementation{ From 2202389b7987f9319d282da93ad7298f6cda275e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Mon, 23 Jan 2023 17:41:19 +0100 Subject: [PATCH 13/22] tbls: add FuzzRandomImplementations fuzzing target Helps with randomization/compatibility testing. --- tbls/taketwo/take_two_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tbls/taketwo/take_two_test.go b/tbls/taketwo/take_two_test.go index 5ac35838f..01959a81b 100644 --- a/tbls/taketwo/take_two_test.go +++ b/tbls/taketwo/take_two_test.go @@ -261,3 +261,9 @@ func (r randomizedImpl) Sign(privateKey taketwo.PrivateKey, data []byte) (taketw return impl.Sign(privateKey, data) } + +func FuzzRandomImplementations(f *testing.F) { + f.Fuzz(func(t *testing.T, _ byte) { + TestRandomized(t) + }) +} From 4c8f2d770bf310bcdadb776ad84e2b8cf35adcbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Wed, 25 Jan 2023 11:54:23 +0100 Subject: [PATCH 14/22] tbls: remove first iteration of BLS abstraction --- tbls/tbls.go | 162 ------------------------------ tbls/tbls_herumi.go | 239 -------------------------------------------- 2 files changed, 401 deletions(-) delete mode 100644 tbls/tbls.go delete mode 100644 tbls/tbls_herumi.go diff --git a/tbls/tbls.go b/tbls/tbls.go deleted file mode 100644 index a4a961fac..000000000 --- a/tbls/tbls.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright © 2023 Obol Labs Inc. - -package tbls - -import ( - "encoding" - "fmt" -) - -/* -`tbls` package operations: -- key generation -- threshold cryptography - - key splitting - - key aggregation (public, private) - - signature verification and aggregation - - simple signature aggregation - - simple signature verification - - signature on a byte slice - - signature with a partial key -*/ - -type ThresholdManager struct { - total uint - threshold uint - generator ThresholdGenerator -} - -func NewThresholdManager(generator ThresholdGenerator, total, threshold uint) (ThresholdManager, error) { - if generator == nil { - return ThresholdManager{}, fmt.Errorf("generator can't be nil") - } - - if threshold > total { - return ThresholdManager{}, fmt.Errorf("threshold can't be greater than total") - } - - if threshold == 0 { - return ThresholdManager{}, fmt.Errorf("threshold can't be zero") - } - - if total == 0 { - return ThresholdManager{}, fmt.Errorf("total can't be zero") - } - - return ThresholdManager{ - threshold: threshold, - total: total, - generator: generator, - }, nil -} - -func (tm ThresholdManager) Generate() ([]PartialPrivateKey, error) { - pk, err := tm.generator.Generate() - if err != nil { - return nil, err - } - - return tm.generator.Split(pk, tm.total, tm.threshold) -} - -// VerifyAggregate verifies all partial signatures against a message and aggregates them. -// It returns the aggregated signature and slice of valid partial signature identifiers. -func (tm ThresholdManager) VerifyAggregate(pubkeys []PartialPublicKey, partialSigs []PartialSignature, msg []byte) (Signature, []uint, error) { - if len(partialSigs) < int(tm.threshold) { - return nil, nil, fmt.Errorf("insufficient signatures") - } - - var ( - signers []uint - validSigs []PartialSignature - ) - - for idx := 0; idx < len(partialSigs); idx++ { - signature := partialSigs[idx] - pubkey := pubkeys[idx] - - if err := signature.Verify(pubkey, msg); err != nil { - continue - } - - validSigs = append(validSigs, signature) - signers = append(signers, pubkey.ID()) - } - - if len(validSigs) < int(tm.threshold) { - return nil, nil, fmt.Errorf("insufficient valid signatures") - } - - aggSig, err := tm.generator.CombineSignatures(partialSigs, pubkeys) - if err != nil { - return nil, nil, fmt.Errorf("cannot aggregate signatures after verification, %w", err) - } - - return aggSig, signers, nil -} - -// Aggregate aggregates partialSigs into a single Signature, with ID's taken from pubkeys. -func (tm ThresholdManager) Aggregate(pubkeys []PartialPublicKey, partialSigs []PartialSignature) (Signature, error) { - aggSig, err := tm.generator.CombineSignatures(partialSigs, pubkeys) - if err != nil { - return nil, fmt.Errorf("cannot aggregate signatures after verification, %w", err) - } - - return aggSig, nil -} - -// ThresholdGenerator generates threshold public and private keys according to -// the specified parameters. -type ThresholdGenerator interface { - // Split splits original into total amount of PartialPrivateKey's, with threshold - // amount of them needed to recover secret. - Split(original PrivateKey, total, threshold uint) ([]PartialPrivateKey, error) - - // RecoverPrivateKey recombines the PartialPrivateKey's back to the original PrivateKey. - RecoverPrivateKey([]PartialPrivateKey) (PrivateKey, error) - - // CombineSignatures combines all the input PartialSignature's in a complete - // Signature. - CombineSignatures([]PartialSignature, []PartialPublicKey) (Signature, error) - - // Generate generates a new PrivateKey from the OS source of entropy - Generate() (PrivateKey, error) -} - -// PublicKey is a BLS12-381 public key. -// It represents a full public key, not a share of it. -type PublicKey interface { - encoding.TextMarshaler -} - -// PrivateKey is a BLS12-381 private key. -// It represents a full private key, not a share of it. -type PrivateKey interface { - encoding.TextMarshaler - encoding.TextUnmarshaler - - PublicKey() PublicKey - Sign(data []byte) (Signature, error) -} - -// PartialPublicKey is a share of a full BLS12-381 key. -type PartialPublicKey interface { - PublicKey - ID() uint -} - -// PartialPrivateKey is a share of a full BLS12-381 key. -type PartialPrivateKey interface { - PrivateKey - ID() uint -} - -// Signature represents a BLS12-381 signature made with a PrivateKey. -type Signature interface { - Verify(pk PublicKey, message []byte) error -} - -// PartialSignature represents a BLS12-381 signature made with a PartialPrivateKey. -type PartialSignature interface { - Verify(pk PartialPublicKey, message []byte) error -} diff --git a/tbls/tbls_herumi.go b/tbls/tbls_herumi.go deleted file mode 100644 index 5dcf5ebd6..000000000 --- a/tbls/tbls_herumi.go +++ /dev/null @@ -1,239 +0,0 @@ -package tbls - -import ( - "fmt" - "github.com/herumi/bls-eth-go-binary/bls" - "strconv" - "sync" -) - -var herumiInit = sync.Once{} - -// PSA: as much as init() is (almost) an antipattern in Go, Herumi BLS implementation needs an initialization routine -// before it can be used. -// Hence, we embed it in an init() method along with a sync.Once, so that this effect is only run once. -func init() { - herumiInit.Do(func() { - if err := bls.Init(bls.BLS12_381); err != nil { - panic(fmt.Errorf("cannot initialize Herumi BLS, %w", err)) - } - - if err := bls.SetETHmode(bls.EthModeLatest); err != nil { - panic(fmt.Errorf("cannot initialize Herumi BLS, %w", err)) - } - }) -} - -type HerumiPublicKey struct { - bls.PublicKey -} - -func (h HerumiPublicKey) MarshalText() ([]byte, error) { - return []byte(h.SerializeToHexStr()), nil -} - -type HerumiPrivateKey struct { - bls.SecretKey -} - -func (h *HerumiPrivateKey) MarshalText() ([]byte, error) { - return []byte(h.SerializeToHexStr()), nil -} - -func (h *HerumiPrivateKey) UnmarshalText(text []byte) error { - return h.SetHexString(string(text)) -} - -func (h *HerumiPrivateKey) PublicKey() PublicKey { - p, err := h.GetSafePublicKey() - if err != nil { - panic(fmt.Errorf("cannot retrieve public key from private key, %w", err)) - } - - return HerumiPublicKey{ - PublicKey: *p, - } -} - -func (h *HerumiPrivateKey) Sign(data []byte) (Signature, error) { - return HerumiSignature{ - Sign: *h.SignByte(data), - }, nil -} - -type HerumiPartialPublicKey struct { - HerumiPublicKey - id uint -} - -func (h HerumiPartialPublicKey) ID() uint { - return h.id -} - -type HerumiPartialPrivateKey struct { - HerumiPrivateKey - id uint -} - -func (h HerumiPartialPrivateKey) ID() uint { - return h.id -} - -type HerumiSignature struct { - bls.Sign -} - -func (h HerumiSignature) Verify(pk PublicKey, message []byte) error { - // assert pk to its raw type - hpubk, ok := pk.(HerumiPublicKey) - if !ok { - return fmt.Errorf("public key is not in Herumi format") - } - - if !h.VerifyByte(&hpubk.PublicKey, message) { - return fmt.Errorf("signature not verified") - } - - return nil -} - -type HerumiPartialSignature struct { - HerumiSignature -} - -func (h HerumiPartialSignature) Verify(pk PartialPublicKey, message []byte) error { - // assert pk to its raw type - hpubk, ok := pk.(HerumiPartialPublicKey) - if !ok { - return fmt.Errorf("public key is not in Herumi format") - } - - if !h.VerifyByte(&hpubk.PublicKey, message) { - return fmt.Errorf("signature not verified") - } - - return nil -} - -type HerumiThresholdGenerator struct{} - -func (h HerumiThresholdGenerator) Split(original PrivateKey, total, threshold uint) ([]PartialPrivateKey, error) { - // cast original to its herumi representation - horiginal, ok := original.(*HerumiPrivateKey) - if !ok { - return nil, fmt.Errorf("original is not in Herumi format") - } - - // master key Polynomial - poly := make([]bls.SecretKey, threshold) - - poly[0] = horiginal.SecretKey - - // initialize threshold amount of points - for i := 1; i < int(threshold); i++ { - sk := bls.SecretKey{} - sk.SetByCSPRNG() - poly[i] = sk - } - - ret := make([]PartialPrivateKey, total) - for i := 1; i <= int(total); i++ { - blsID := bls.ID{} - - err := blsID.SetDecString(fmt.Sprintf("%d", i)) - if err != nil { - return nil, fmt.Errorf("cannot set ID %d for key number %d, %w", i, i, err) - } - - sk := bls.SecretKey{} - - err = sk.Set(poly, &blsID) - if err != nil { - return nil, err - } - - ret[i] = &HerumiPartialPrivateKey{ - HerumiPrivateKey: HerumiPrivateKey{ - SecretKey: sk, - }, - id: uint(i), - } - } - - return ret, nil -} - -func (h HerumiThresholdGenerator) RecoverPrivateKey(keys []PartialPrivateKey) (PrivateKey, error) { - pk := bls.SecretKey{} - - rawKeys := []bls.SecretKey{} - rawIDs := []bls.ID{} - - for idx, key := range keys { - // assert key to herumi type - kpk, ok := key.(*HerumiPartialPrivateKey) - if !ok { - return nil, fmt.Errorf("private key %d is not in Herumi format", idx) - } - - rawKeys = append(rawKeys, kpk.SecretKey) - - id := bls.ID{} - if err := id.SetDecString(strconv.Itoa(int(kpk.id))); err != nil { - return nil, fmt.Errorf("private key %d id isn't a number", kpk.id) - } - - rawIDs = append(rawIDs, id) - } - - if err := pk.Recover(rawKeys, rawIDs); err != nil { - return nil, fmt.Errorf("cannot recover full private key from partial keys, %w", err) - } - - return &HerumiPrivateKey{ - SecretKey: pk, - }, nil -} - -func (h HerumiThresholdGenerator) CombineSignatures(psigns []PartialSignature, pkeys []PartialPublicKey) (Signature, error) { - var rawSigns []bls.Sign - var rawIDs []bls.ID - - for idx, sign := range psigns { - hsign, ok := sign.(HerumiPartialSignature) - if !ok { - return nil, fmt.Errorf("partial signature %d is not in Herumi format", idx) - } - - rawSigns = append(rawSigns, hsign.Sign) - } - - for idx, pk := range pkeys { - id := bls.ID{} - - if err := id.SetDecString(strconv.Itoa(int(pk.ID()))); err != nil { - return nil, fmt.Errorf("cannot set partial public key %d's id, %w", idx, err) - } - - rawIDs = append(rawIDs, id) - } - - complete := bls.Sign{} - - if err := complete.Recover(rawSigns, rawIDs); err != nil { - return nil, fmt.Errorf("cannot combine signatures, %w", err) - } - - return HerumiSignature{ - Sign: complete, - }, nil -} - -func (h HerumiThresholdGenerator) Generate() (PrivateKey, error) { - p := bls.SecretKey{} - p.SetByCSPRNG() - - return &HerumiPrivateKey{ - SecretKey: p, - }, nil -} From ec3818a38a86fd921a061aceb409f1a647c8fb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Wed, 25 Jan 2023 12:08:49 +0100 Subject: [PATCH 15/22] tbls: rename taketwo to v2 Once we migrate away from the original bls abstraction implementation, v2 package content will move to the root tbls package. --- tbls/{taketwo => v2}/herumi/herumi.go | 18 ++--- tbls/{taketwo => v2}/kryptology/kryptology.go | 18 ++--- tbls/{taketwo/take_two.go => v2/tbls.go} | 2 +- .../take_two_test.go => v2/tbls_test.go} | 78 +++++++++---------- tbls/{taketwo => v2}/unimplemented.go | 2 +- 5 files changed, 59 insertions(+), 59 deletions(-) rename tbls/{taketwo => v2}/herumi/herumi.go (84%) rename tbls/{taketwo => v2}/kryptology/kryptology.go (83%) rename tbls/{taketwo/take_two.go => v2/tbls.go} (99%) rename tbls/{taketwo/take_two_test.go => v2/tbls_test.go} (65%) rename tbls/{taketwo => v2}/unimplemented.go (98%) diff --git a/tbls/taketwo/herumi/herumi.go b/tbls/v2/herumi/herumi.go similarity index 84% rename from tbls/taketwo/herumi/herumi.go rename to tbls/v2/herumi/herumi.go index b1dac4c07..ad70ab313 100644 --- a/tbls/taketwo/herumi/herumi.go +++ b/tbls/v2/herumi/herumi.go @@ -3,7 +3,7 @@ package herumi import ( "fmt" "github.com/herumi/bls-eth-go-binary/bls" - "github.com/obolnetwork/charon/tbls/taketwo" + "github.com/obolnetwork/charon/tbls/v2" "strconv" "sync" ) @@ -28,14 +28,14 @@ func init() { // Herumi is an Implementation with Herumi-specific inner logic. type Herumi struct{} -func (h Herumi) GenerateSecretKey() (taketwo.PrivateKey, error) { +func (h Herumi) GenerateSecretKey() (v2.PrivateKey, error) { var p bls.SecretKey p.SetByCSPRNG() return p.Serialize(), nil } -func (h Herumi) SecretToPublicKey(secret taketwo.PrivateKey) (taketwo.PublicKey, error) { +func (h Herumi) SecretToPublicKey(secret v2.PrivateKey) (v2.PublicKey, error) { var p bls.SecretKey if err := p.Deserialize(secret); err != nil { @@ -50,7 +50,7 @@ func (h Herumi) SecretToPublicKey(secret taketwo.PrivateKey) (taketwo.PublicKey, return pubk.Serialize(), nil } -func (h Herumi) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold uint) (map[int]taketwo.PrivateKey, error) { +func (h Herumi) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { var p bls.SecretKey if err := p.Deserialize(secret); err != nil { @@ -69,7 +69,7 @@ func (h Herumi) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold poly[i] = sk } - ret := make(map[int]taketwo.PrivateKey) + ret := make(map[int]v2.PrivateKey) for i := 1; i <= int(total); i++ { var blsID bls.ID @@ -91,7 +91,7 @@ func (h Herumi) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold return ret, nil } -func (h Herumi) RecoverSecret(shares map[int]taketwo.PrivateKey, _, _ uint) (taketwo.PrivateKey, error) { +func (h Herumi) RecoverSecret(shares map[int]v2.PrivateKey, _, _ uint) (v2.PrivateKey, error) { var pk bls.SecretKey var rawKeys []bls.SecretKey @@ -120,7 +120,7 @@ func (h Herumi) RecoverSecret(shares map[int]taketwo.PrivateKey, _, _ uint) (tak return pk.Serialize(), nil } -func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo.Signature) (taketwo.Signature, error) { +func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { var rawSigns []bls.Sign var rawIDs []bls.ID @@ -149,7 +149,7 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo.Sign return complete.Serialize(), nil } -func (h Herumi) Verify(compressedPublicKey taketwo.PublicKey, data []byte, rawSignature taketwo.Signature) error { +func (h Herumi) Verify(compressedPublicKey v2.PublicKey, data []byte, rawSignature v2.Signature) error { var pubKey bls.PublicKey if err := pubKey.Deserialize(compressedPublicKey); err != nil { return fmt.Errorf("cannot set compressed public key in Herumi format, %w", err) @@ -167,7 +167,7 @@ func (h Herumi) Verify(compressedPublicKey taketwo.PublicKey, data []byte, rawSi return nil } -func (h Herumi) Sign(privateKey taketwo.PrivateKey, data []byte) (taketwo.Signature, error) { +func (h Herumi) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { var p bls.SecretKey if err := p.Deserialize(privateKey); err != nil { diff --git a/tbls/taketwo/kryptology/kryptology.go b/tbls/v2/kryptology/kryptology.go similarity index 83% rename from tbls/taketwo/kryptology/kryptology.go rename to tbls/v2/kryptology/kryptology.go index fc2b9b2a9..c51e4b4ed 100644 --- a/tbls/taketwo/kryptology/kryptology.go +++ b/tbls/v2/kryptology/kryptology.go @@ -6,7 +6,7 @@ import ( share "github.com/coinbase/kryptology/pkg/sharing" "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" "github.com/obolnetwork/charon/app/errors" - "github.com/obolnetwork/charon/tbls/taketwo" + "github.com/obolnetwork/charon/tbls/v2" ) // blsScheme is the BLS12-381 ETH2 signature scheme with standard domain separation tag used for signatures. @@ -17,7 +17,7 @@ var blsScheme = bls_sig.NewSigEth2() // Kryptology is an Implementation with Kryptology-specific inner logic. type Kryptology struct{} -func (k Kryptology) GenerateSecretKey() (taketwo.PrivateKey, error) { +func (k Kryptology) GenerateSecretKey() (v2.PrivateKey, error) { _, secret, err := blsScheme.Keygen() if err != nil { return nil, errors.Wrap(err, "generate key") @@ -26,7 +26,7 @@ func (k Kryptology) GenerateSecretKey() (taketwo.PrivateKey, error) { return secret.MarshalBinary() } -func (k Kryptology) SecretToPublicKey(key taketwo.PrivateKey) (taketwo.PublicKey, error) { +func (k Kryptology) SecretToPublicKey(key v2.PrivateKey) (v2.PublicKey, error) { rawKey := new(bls_sig.SecretKey) if err := rawKey.UnmarshalBinary(key); err != nil { return nil, errors.Wrap(err, "unmarshal raw key into kryptology object") @@ -40,7 +40,7 @@ func (k Kryptology) SecretToPublicKey(key taketwo.PrivateKey) (taketwo.PublicKey return pubKey.MarshalBinary() } -func (k Kryptology) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold uint) (map[int]taketwo.PrivateKey, error) { +func (k Kryptology) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { scheme, err := share.NewFeldman(uint32(threshold), uint32(total), curves.BLS12381G1()) if err != nil { return nil, errors.Wrap(err, "new Feldman VSS") @@ -56,7 +56,7 @@ func (k Kryptology) ThresholdSplit(secret taketwo.PrivateKey, total uint, thresh return nil, errors.Wrap(err, "split Secret Key") } - sks := make(map[int]taketwo.PrivateKey) + sks := make(map[int]v2.PrivateKey) for _, s := range shares { sks[int(s.Id)] = s.Value @@ -65,7 +65,7 @@ func (k Kryptology) ThresholdSplit(secret taketwo.PrivateKey, total uint, thresh return sks, nil } -func (k Kryptology) RecoverSecret(shares map[int]taketwo.PrivateKey, total uint, threshold uint) (taketwo.PrivateKey, error) { +func (k Kryptology) RecoverSecret(shares map[int]v2.PrivateKey, total uint, threshold uint) (v2.PrivateKey, error) { var shamirShares []*share.ShamirShare for idx, value := range shares { shamirShare := share.ShamirShare{ @@ -94,7 +94,7 @@ func (k Kryptology) RecoverSecret(shares map[int]taketwo.PrivateKey, total uint, return resp.MarshalBinary() } -func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo.Signature) (taketwo.Signature, error) { +func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { var kryptologyPartialSigs []*bls_sig.PartialSignature for idx, sig := range partialSignaturesByIndex { @@ -117,7 +117,7 @@ func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo. return aggSig.MarshalBinary() } -func (k Kryptology) Verify(compressedPublicKey taketwo.PublicKey, data []byte, signature taketwo.Signature) error { +func (k Kryptology) Verify(compressedPublicKey v2.PublicKey, data []byte, signature v2.Signature) error { rawKey := new(bls_sig.PublicKey) if err := rawKey.UnmarshalBinary(compressedPublicKey); err != nil { return errors.Wrap(err, "unmarshal raw public key into kryptology object") @@ -140,7 +140,7 @@ func (k Kryptology) Verify(compressedPublicKey taketwo.PublicKey, data []byte, s return nil } -func (k Kryptology) Sign(privateKey taketwo.PrivateKey, data []byte) (taketwo.Signature, error) { +func (k Kryptology) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { rawKey := new(bls_sig.SecretKey) if err := rawKey.UnmarshalBinary(privateKey); err != nil { return nil, errors.Wrap(err, "unmarshal raw private key into kryptology object") diff --git a/tbls/taketwo/take_two.go b/tbls/v2/tbls.go similarity index 99% rename from tbls/taketwo/take_two.go rename to tbls/v2/tbls.go index 66ace0656..6a9ee927c 100644 --- a/tbls/taketwo/take_two.go +++ b/tbls/v2/tbls.go @@ -1,4 +1,4 @@ -package taketwo +package v2 import "sync" diff --git a/tbls/taketwo/take_two_test.go b/tbls/v2/tbls_test.go similarity index 65% rename from tbls/taketwo/take_two_test.go rename to tbls/v2/tbls_test.go index 01959a81b..5e9eadccc 100644 --- a/tbls/taketwo/take_two_test.go +++ b/tbls/v2/tbls_test.go @@ -1,10 +1,10 @@ -package taketwo_test +package v2_test import ( "crypto/rand" - "github.com/obolnetwork/charon/tbls/taketwo" - herumiImpl "github.com/obolnetwork/charon/tbls/taketwo/herumi" - "github.com/obolnetwork/charon/tbls/taketwo/kryptology" + "github.com/obolnetwork/charon/tbls/v2" + herumiImpl "github.com/obolnetwork/charon/tbls/v2/herumi" + "github.com/obolnetwork/charon/tbls/v2/kryptology" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "math/big" @@ -14,54 +14,54 @@ import ( type TestSuite struct { suite.Suite - impl taketwo.Implementation + impl v2.Implementation } -func NewTestSuite(implementations taketwo.Implementation) TestSuite { +func NewTestSuite(implementations v2.Implementation) TestSuite { return TestSuite{ impl: implementations, } } func (ts *TestSuite) SetupTest() { - taketwo.SetImplementation(ts.impl) + v2.SetImplementation(ts.impl) } func (ts *TestSuite) Test_GenerateSecretKey() { - secret, err := taketwo.GenerateSecretKey() + secret, err := v2.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) } func (ts *TestSuite) Test_SecretToPublicKey() { - secret, err := taketwo.GenerateSecretKey() + secret, err := v2.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - pubk, err := taketwo.SecretToPublicKey(secret) + pubk, err := v2.SecretToPublicKey(secret) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), pubk) } func (ts *TestSuite) Test_ThresholdSplit() { - secret, err := taketwo.GenerateSecretKey() + secret, err := v2.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - shares, err := taketwo.ThresholdSplit(secret, 5, 3) + shares, err := v2.ThresholdSplit(secret, 5, 3) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), shares) } func (ts *TestSuite) Test_RecoverSecret() { - secret, err := taketwo.GenerateSecretKey() + secret, err := v2.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - shares, err := taketwo.ThresholdSplit(secret, 5, 3) + shares, err := v2.ThresholdSplit(secret, 5, 3) require.NoError(ts.T(), err) - recovered, err := taketwo.RecoverSecret(shares, 5, 3) + recovered, err := v2.RecoverSecret(shares, 5, 3) require.NoError(ts.T(), err) require.Equal(ts.T(), secret, recovered) @@ -70,25 +70,25 @@ func (ts *TestSuite) Test_RecoverSecret() { func (ts *TestSuite) Test_ThresholdAggregate() { data := []byte("hello obol!") - secret, err := taketwo.GenerateSecretKey() + secret, err := v2.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - totalOGSig, err := taketwo.Sign(secret, data) + totalOGSig, err := v2.Sign(secret, data) require.NoError(ts.T(), err) - shares, err := taketwo.ThresholdSplit(secret, 5, 3) + shares, err := v2.ThresholdSplit(secret, 5, 3) require.NoError(ts.T(), err) - signatures := map[int]taketwo.Signature{} + signatures := map[int]v2.Signature{} for idx, key := range shares { - signature, err := taketwo.Sign(key, data) + signature, err := v2.Sign(key, data) require.NoError(ts.T(), err) signatures[idx] = signature } - totalSig, err := taketwo.ThresholdAggregate(signatures) + totalSig, err := v2.ThresholdAggregate(signatures) require.NoError(ts.T(), err) require.Equal(ts.T(), totalOGSig, totalSig) @@ -97,34 +97,34 @@ func (ts *TestSuite) Test_ThresholdAggregate() { func (ts *TestSuite) Test_Verify() { data := []byte("hello obol!") - secret, err := taketwo.GenerateSecretKey() + secret, err := v2.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - signature, err := taketwo.Sign(secret, data) + signature, err := v2.Sign(secret, data) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), signature) - pubkey, err := taketwo.SecretToPublicKey(secret) + pubkey, err := v2.SecretToPublicKey(secret) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), pubkey) - require.NoError(ts.T(), taketwo.Verify(pubkey, data, signature)) + require.NoError(ts.T(), v2.Verify(pubkey, data, signature)) } func (ts *TestSuite) Test_Sign() { data := []byte("hello obol!") - secret, err := taketwo.GenerateSecretKey() + secret, err := v2.GenerateSecretKey() require.NoError(ts.T(), err) require.NotEmpty(ts.T(), secret) - signature, err := taketwo.Sign(secret, data) + signature, err := v2.Sign(secret, data) require.NoError(ts.T(), err) require.NotEmpty(ts.T(), signature) } -func runSuite(t *testing.T, i taketwo.Implementation) { +func runSuite(t *testing.T, i v2.Implementation) { ts := NewTestSuite(i) suite.Run(t, &ts) @@ -138,7 +138,7 @@ func TestKryptologyImplementation(t *testing.T) { runSuite(t, kryptology.Kryptology{}) } -func runBenchmark(b *testing.B, impl taketwo.Implementation) { +func runBenchmark(b *testing.B, impl v2.Implementation) { s := NewTestSuite(impl) t := &testing.T{} s.SetT(t) @@ -171,7 +171,7 @@ func BenchmarkKryptologyImplementation(b *testing.B) { func TestRandomized(t *testing.T) { runSuite(t, randomizedImpl{ - implementations: []taketwo.Implementation{ + implementations: []v2.Implementation{ herumiImpl.Herumi{}, kryptology.Kryptology{}, }, @@ -182,10 +182,10 @@ func TestRandomized(t *testing.T) { // the implementations slice. // Useful to test whether two implementations are compatible. type randomizedImpl struct { - implementations []taketwo.Implementation + implementations []v2.Implementation } -func (r randomizedImpl) selectImpl() (taketwo.Implementation, error) { +func (r randomizedImpl) selectImpl() (v2.Implementation, error) { blen := big.NewInt(int64(len(r.implementations))) // random number: [0, len(ts.impl)) @@ -199,7 +199,7 @@ func (r randomizedImpl) selectImpl() (taketwo.Implementation, error) { return r.implementations[nativeN], nil } -func (r randomizedImpl) GenerateSecretKey() (taketwo.PrivateKey, error) { +func (r randomizedImpl) GenerateSecretKey() (v2.PrivateKey, error) { impl, err := r.selectImpl() if err != nil { return nil, err @@ -208,7 +208,7 @@ func (r randomizedImpl) GenerateSecretKey() (taketwo.PrivateKey, error) { return impl.GenerateSecretKey() } -func (r randomizedImpl) SecretToPublicKey(key taketwo.PrivateKey) (taketwo.PublicKey, error) { +func (r randomizedImpl) SecretToPublicKey(key v2.PrivateKey) (v2.PublicKey, error) { impl, err := r.selectImpl() if err != nil { return nil, err @@ -217,7 +217,7 @@ func (r randomizedImpl) SecretToPublicKey(key taketwo.PrivateKey) (taketwo.Publi return impl.SecretToPublicKey(key) } -func (r randomizedImpl) ThresholdSplit(secret taketwo.PrivateKey, total uint, threshold uint) (map[int]taketwo.PrivateKey, error) { +func (r randomizedImpl) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { impl, err := r.selectImpl() if err != nil { return nil, err @@ -226,7 +226,7 @@ func (r randomizedImpl) ThresholdSplit(secret taketwo.PrivateKey, total uint, th return impl.ThresholdSplit(secret, total, threshold) } -func (r randomizedImpl) RecoverSecret(shares map[int]taketwo.PrivateKey, total uint, threshold uint) (taketwo.PrivateKey, error) { +func (r randomizedImpl) RecoverSecret(shares map[int]v2.PrivateKey, total uint, threshold uint) (v2.PrivateKey, error) { impl, err := r.selectImpl() if err != nil { return nil, err @@ -235,7 +235,7 @@ func (r randomizedImpl) RecoverSecret(shares map[int]taketwo.PrivateKey, total u return impl.RecoverSecret(shares, total, threshold) } -func (r randomizedImpl) ThresholdAggregate(partialSignaturesByIndex map[int]taketwo.Signature) (taketwo.Signature, error) { +func (r randomizedImpl) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { impl, err := r.selectImpl() if err != nil { return nil, err @@ -244,7 +244,7 @@ func (r randomizedImpl) ThresholdAggregate(partialSignaturesByIndex map[int]take return impl.ThresholdAggregate(partialSignaturesByIndex) } -func (r randomizedImpl) Verify(compressedPublicKey taketwo.PublicKey, data []byte, signature taketwo.Signature) error { +func (r randomizedImpl) Verify(compressedPublicKey v2.PublicKey, data []byte, signature v2.Signature) error { impl, err := r.selectImpl() if err != nil { return err @@ -253,7 +253,7 @@ func (r randomizedImpl) Verify(compressedPublicKey taketwo.PublicKey, data []byt return impl.Verify(compressedPublicKey, data, signature) } -func (r randomizedImpl) Sign(privateKey taketwo.PrivateKey, data []byte) (taketwo.Signature, error) { +func (r randomizedImpl) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { impl, err := r.selectImpl() if err != nil { return nil, err diff --git a/tbls/taketwo/unimplemented.go b/tbls/v2/unimplemented.go similarity index 98% rename from tbls/taketwo/unimplemented.go rename to tbls/v2/unimplemented.go index 207ebffc0..d9f9ea191 100644 --- a/tbls/taketwo/unimplemented.go +++ b/tbls/v2/unimplemented.go @@ -1,4 +1,4 @@ -package taketwo +package v2 import "fmt" From a196c499120110c4cbdd2314fe35a74eef59ecd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Wed, 25 Jan 2023 13:05:16 +0100 Subject: [PATCH 16/22] tbls: PublicKey, PrivateKey and Signature are now arrays Helps with type safety. --- tbls/v2/herumi/herumi.go | 54 +++++++++++++++----------- tbls/v2/kryptology/kryptology.go | 65 +++++++++++++++++++++----------- tbls/v2/tbls.go | 6 +-- tbls/v2/tbls_test.go | 12 +++--- tbls/v2/unimplemented.go | 10 ++--- 5 files changed, 88 insertions(+), 59 deletions(-) diff --git a/tbls/v2/herumi/herumi.go b/tbls/v2/herumi/herumi.go index ad70ab313..bc9da289b 100644 --- a/tbls/v2/herumi/herumi.go +++ b/tbls/v2/herumi/herumi.go @@ -32,28 +32,33 @@ func (h Herumi) GenerateSecretKey() (v2.PrivateKey, error) { var p bls.SecretKey p.SetByCSPRNG() - return p.Serialize(), nil + // Commenting here once, this syntax will appear often: + // here I'm converting ret to a pointer to instance of v2.PrivateKey, which is + // an array with constant size. + // I'm dereferencing it to return a copy as well. + // Ref: https://go.dev/ref/spec#Conversions_from_slice_to_array_pointer + return *(*v2.PrivateKey)(p.Serialize()), nil } func (h Herumi) SecretToPublicKey(secret v2.PrivateKey) (v2.PublicKey, error) { var p bls.SecretKey - if err := p.Deserialize(secret); err != nil { - return nil, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) + if err := p.Deserialize(secret[:]); err != nil { + return v2.PublicKey{}, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) } pubk, err := p.GetSafePublicKey() if err != nil { - return nil, fmt.Errorf("cannot obtain public key from secret secret, %w", err) + return v2.PublicKey{}, fmt.Errorf("cannot obtain public key from secret secret, %w", err) } - return pubk.Serialize(), nil + return *(*v2.PublicKey)(pubk.Serialize()), nil } func (h Herumi) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { var p bls.SecretKey - if err := p.Deserialize(secret); err != nil { + if err := p.Deserialize(secret[:]); err != nil { return nil, fmt.Errorf("cannot unmarshal bytes into Herumi secret key, %w", err) } @@ -85,7 +90,7 @@ func (h Herumi) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) return nil, err } - ret[i] = sk.Serialize() + ret[i] = *(*v2.PrivateKey)(sk.Serialize()) } return ret, nil @@ -98,26 +103,28 @@ func (h Herumi) RecoverSecret(shares map[int]v2.PrivateKey, _, _ uint) (v2.Priva var rawIDs []bls.ID for idx, key := range shares { + // do a local copy, we're dealing with references here + key := key var kpk bls.SecretKey - if err := kpk.Deserialize(key); err != nil { - return nil, fmt.Errorf("cannot unmarshal key with index %d into Herumi secret key, %w", idx, err) + if err := kpk.Deserialize(key[:]); err != nil { + return v2.PrivateKey{}, fmt.Errorf("cannot unmarshal key with index %d into Herumi secret key, %w", idx, err) } rawKeys = append(rawKeys, kpk) var id bls.ID if err := id.SetDecString(strconv.Itoa(idx)); err != nil { - return nil, fmt.Errorf("private key id %d id isn't a number", idx) + return v2.PrivateKey{}, fmt.Errorf("private key id %d id isn't a number", idx) } rawIDs = append(rawIDs, id) } if err := pk.Recover(rawKeys, rawIDs); err != nil { - return nil, fmt.Errorf("cannot recover full private key from partial keys, %w", err) + return v2.PrivateKey{}, fmt.Errorf("cannot recover full private key from partial keys, %w", err) } - return pk.Serialize(), nil + return *(*v2.PrivateKey)(pk.Serialize()), nil } func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { @@ -125,16 +132,18 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature var rawIDs []bls.ID for idx, rawSignature := range partialSignaturesByIndex { + // do a local copy, we're dealing with references here + rawSignature := rawSignature var signature bls.Sign - if err := signature.Deserialize(rawSignature); err != nil { - return nil, fmt.Errorf("cannot unmarshal signature with index %d into Herumi signature, %w", idx, err) + if err := signature.Deserialize(rawSignature[:]); err != nil { + return v2.Signature{}, fmt.Errorf("cannot unmarshal signature with index %d into Herumi signature, %w", idx, err) } rawSigns = append(rawSigns, signature) var id bls.ID if err := id.SetDecString(strconv.Itoa(idx)); err != nil { - return nil, fmt.Errorf("signature id %d id isn't a number", idx) + return v2.Signature{}, fmt.Errorf("signature id %d id isn't a number", idx) } rawIDs = append(rawIDs, id) @@ -143,20 +152,20 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature var complete bls.Sign if err := complete.Recover(rawSigns, rawIDs); err != nil { - return nil, fmt.Errorf("cannot combine signatures, %w", err) + return v2.Signature{}, fmt.Errorf("cannot combine signatures, %w", err) } - return complete.Serialize(), nil + return *(*v2.Signature)(complete.Serialize()), nil } func (h Herumi) Verify(compressedPublicKey v2.PublicKey, data []byte, rawSignature v2.Signature) error { var pubKey bls.PublicKey - if err := pubKey.Deserialize(compressedPublicKey); err != nil { + if err := pubKey.Deserialize(compressedPublicKey[:]); err != nil { return fmt.Errorf("cannot set compressed public key in Herumi format, %w", err) } var signature bls.Sign - if err := signature.Deserialize(rawSignature); err != nil { + if err := signature.Deserialize(rawSignature[:]); err != nil { return fmt.Errorf("cannot unmarshal signature into Herumi signature, %w", err) } @@ -170,9 +179,10 @@ func (h Herumi) Verify(compressedPublicKey v2.PublicKey, data []byte, rawSignatu func (h Herumi) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { var p bls.SecretKey - if err := p.Deserialize(privateKey); err != nil { - return nil, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) + if err := p.Deserialize(privateKey[:]); err != nil { + return v2.Signature{}, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) } - return p.SignByte(data).Serialize(), nil + sigBytes := p.SignByte(data).Serialize() + return *(*v2.Signature)(sigBytes), nil } diff --git a/tbls/v2/kryptology/kryptology.go b/tbls/v2/kryptology/kryptology.go index c51e4b4ed..b6c738042 100644 --- a/tbls/v2/kryptology/kryptology.go +++ b/tbls/v2/kryptology/kryptology.go @@ -20,24 +20,33 @@ type Kryptology struct{} func (k Kryptology) GenerateSecretKey() (v2.PrivateKey, error) { _, secret, err := blsScheme.Keygen() if err != nil { - return nil, errors.Wrap(err, "generate key") + return v2.PrivateKey{}, errors.Wrap(err, "generate key") } - return secret.MarshalBinary() + ret, err := secret.MarshalBinary() + + // Commenting here once, this syntax will appear often: + // here I'm converting ret to a pointer to instance of v2.PrivateKey, which is + // an array with constant size. + // I'm dereferencing it to return a copy as well. + // Ref: https://go.dev/ref/spec#Conversions_from_slice_to_array_pointer + return *(*v2.PrivateKey)(ret), err } func (k Kryptology) SecretToPublicKey(key v2.PrivateKey) (v2.PublicKey, error) { rawKey := new(bls_sig.SecretKey) - if err := rawKey.UnmarshalBinary(key); err != nil { - return nil, errors.Wrap(err, "unmarshal raw key into kryptology object") + if err := rawKey.UnmarshalBinary(key[:]); err != nil { + return v2.PublicKey{}, errors.Wrap(err, "unmarshal raw key into kryptology object") } pubKey, err := rawKey.GetPublicKey() if err != nil { - return nil, errors.Wrap(err, "get public key") + return v2.PublicKey{}, errors.Wrap(err, "get public key") } - return pubKey.MarshalBinary() + ret, err := pubKey.MarshalBinary() + + return *(*v2.PublicKey)(ret), err } func (k Kryptology) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { @@ -46,7 +55,7 @@ func (k Kryptology) ThresholdSplit(secret v2.PrivateKey, total uint, threshold u return nil, errors.Wrap(err, "new Feldman VSS") } - secretScaler, err := curves.BLS12381G1().NewScalar().SetBytes(secret) + secretScaler, err := curves.BLS12381G1().NewScalar().SetBytes(secret[:]) if err != nil { return nil, errors.Wrap(err, "convert to scaler") } @@ -59,7 +68,7 @@ func (k Kryptology) ThresholdSplit(secret v2.PrivateKey, total uint, threshold u sks := make(map[int]v2.PrivateKey) for _, s := range shares { - sks[int(s.Id)] = s.Value + sks[int(s.Id)] = *(*v2.PrivateKey)(s.Value) } return sks, nil @@ -68,9 +77,11 @@ func (k Kryptology) ThresholdSplit(secret v2.PrivateKey, total uint, threshold u func (k Kryptology) RecoverSecret(shares map[int]v2.PrivateKey, total uint, threshold uint) (v2.PrivateKey, error) { var shamirShares []*share.ShamirShare for idx, value := range shares { + // do a local copy, we're dealing with references here + value := value shamirShare := share.ShamirShare{ Id: uint32(idx), - Value: value, + Value: value[:], } shamirShares = append(shamirShares, &shamirShare) @@ -78,29 +89,33 @@ func (k Kryptology) RecoverSecret(shares map[int]v2.PrivateKey, total uint, thre scheme, err := share.NewFeldman(uint32(threshold), uint32(total), curves.BLS12381G1()) if err != nil { - return nil, errors.Wrap(err, "new Feldman VSS") + return v2.PrivateKey{}, errors.Wrap(err, "new Feldman VSS") } secretScaler, err := scheme.Combine(shamirShares...) if err != nil { - return nil, errors.Wrap(err, "combine shares") + return v2.PrivateKey{}, errors.Wrap(err, "combine shares") } resp := new(bls_sig.SecretKey) if err := resp.UnmarshalBinary(secretScaler.Bytes()); err != nil { - return nil, errors.Wrap(err, "unmarshal secret") + return v2.PrivateKey{}, errors.Wrap(err, "unmarshal secret") } - return resp.MarshalBinary() + ret, err := resp.MarshalBinary() + + return *(*v2.PrivateKey)(ret), err } func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { var kryptologyPartialSigs []*bls_sig.PartialSignature for idx, sig := range partialSignaturesByIndex { + // do a local copy, we're dealing with references here + sig := sig rawSign := new(bls_sig.Signature) - if err := rawSign.UnmarshalBinary(sig); err != nil { - return nil, errors.Wrap(err, "unmarshal raw signature into kryptology object") + if err := rawSign.UnmarshalBinary(sig[:]); err != nil { + return v2.Signature{}, errors.Wrap(err, "unmarshal raw signature into kryptology object") } kryptologyPartialSigs = append(kryptologyPartialSigs, &bls_sig.PartialSignature{ @@ -111,20 +126,22 @@ func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signa aggSig, err := blsScheme.CombineSignatures(kryptologyPartialSigs...) if err != nil { - return nil, errors.Wrap(err, "aggregate signatures") + return v2.Signature{}, errors.Wrap(err, "aggregate signatures") } - return aggSig.MarshalBinary() + ret, err := aggSig.MarshalBinary() + + return *(*v2.Signature)(ret), err } func (k Kryptology) Verify(compressedPublicKey v2.PublicKey, data []byte, signature v2.Signature) error { rawKey := new(bls_sig.PublicKey) - if err := rawKey.UnmarshalBinary(compressedPublicKey); err != nil { + if err := rawKey.UnmarshalBinary(compressedPublicKey[:]); err != nil { return errors.Wrap(err, "unmarshal raw public key into kryptology object") } rawSign := new(bls_sig.Signature) - if err := rawSign.UnmarshalBinary(signature); err != nil { + if err := rawSign.UnmarshalBinary(signature[:]); err != nil { return errors.Wrap(err, "unmarshal raw signature into kryptology object") } @@ -142,14 +159,16 @@ func (k Kryptology) Verify(compressedPublicKey v2.PublicKey, data []byte, signat func (k Kryptology) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { rawKey := new(bls_sig.SecretKey) - if err := rawKey.UnmarshalBinary(privateKey); err != nil { - return nil, errors.Wrap(err, "unmarshal raw private key into kryptology object") + if err := rawKey.UnmarshalBinary(privateKey[:]); err != nil { + return v2.Signature{}, errors.Wrap(err, "unmarshal raw private key into kryptology object") } rawSign, err := blsScheme.Sign(rawKey, data) if err != nil { - return nil, err + return v2.Signature{}, err } - return rawSign.MarshalBinary() + ret, err := rawSign.MarshalBinary() + + return *(*v2.Signature)(ret), err } diff --git a/tbls/v2/tbls.go b/tbls/v2/tbls.go index 6a9ee927c..909670b15 100644 --- a/tbls/v2/tbls.go +++ b/tbls/v2/tbls.go @@ -7,13 +7,13 @@ var implLock sync.Mutex type ( // PublicKey is a byte slice containing a compressed BLS12-381 public key. - PublicKey []byte + PublicKey [48]byte // PrivateKey is a byte slice containing a compressed BLS12-381 private key. - PrivateKey []byte + PrivateKey [32]byte // Signature is a byte slice containing a BLS12-381 signature. - Signature []byte + Signature [96]byte ) // Implementation defines the backing implementation for all the public functions of this package. diff --git a/tbls/v2/tbls_test.go b/tbls/v2/tbls_test.go index 5e9eadccc..b4e7c8ce8 100644 --- a/tbls/v2/tbls_test.go +++ b/tbls/v2/tbls_test.go @@ -64,7 +64,7 @@ func (ts *TestSuite) Test_RecoverSecret() { recovered, err := v2.RecoverSecret(shares, 5, 3) require.NoError(ts.T(), err) - require.Equal(ts.T(), secret, recovered) + require.ElementsMatch(ts.T(), secret, recovered) } func (ts *TestSuite) Test_ThresholdAggregate() { @@ -202,7 +202,7 @@ func (r randomizedImpl) selectImpl() (v2.Implementation, error) { func (r randomizedImpl) GenerateSecretKey() (v2.PrivateKey, error) { impl, err := r.selectImpl() if err != nil { - return nil, err + return v2.PrivateKey{}, err } return impl.GenerateSecretKey() @@ -211,7 +211,7 @@ func (r randomizedImpl) GenerateSecretKey() (v2.PrivateKey, error) { func (r randomizedImpl) SecretToPublicKey(key v2.PrivateKey) (v2.PublicKey, error) { impl, err := r.selectImpl() if err != nil { - return nil, err + return v2.PublicKey{}, err } return impl.SecretToPublicKey(key) @@ -229,7 +229,7 @@ func (r randomizedImpl) ThresholdSplit(secret v2.PrivateKey, total uint, thresho func (r randomizedImpl) RecoverSecret(shares map[int]v2.PrivateKey, total uint, threshold uint) (v2.PrivateKey, error) { impl, err := r.selectImpl() if err != nil { - return nil, err + return v2.PrivateKey{}, err } return impl.RecoverSecret(shares, total, threshold) @@ -238,7 +238,7 @@ func (r randomizedImpl) RecoverSecret(shares map[int]v2.PrivateKey, total uint, func (r randomizedImpl) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { impl, err := r.selectImpl() if err != nil { - return nil, err + return v2.Signature{}, err } return impl.ThresholdAggregate(partialSignaturesByIndex) @@ -256,7 +256,7 @@ func (r randomizedImpl) Verify(compressedPublicKey v2.PublicKey, data []byte, si func (r randomizedImpl) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { impl, err := r.selectImpl() if err != nil { - return nil, err + return v2.Signature{}, err } return impl.Sign(privateKey, data) diff --git a/tbls/v2/unimplemented.go b/tbls/v2/unimplemented.go index d9f9ea191..5bfe7003c 100644 --- a/tbls/v2/unimplemented.go +++ b/tbls/v2/unimplemented.go @@ -8,11 +8,11 @@ var ErrNotImplemented = fmt.Errorf("not implemented") type Unimplemented struct{} func (u Unimplemented) GenerateSecretKey() (PrivateKey, error) { - return nil, ErrNotImplemented + return PrivateKey{}, ErrNotImplemented } func (u Unimplemented) SecretToPublicKey(_ PrivateKey) (PublicKey, error) { - return nil, ErrNotImplemented + return PublicKey{}, ErrNotImplemented } func (u Unimplemented) ThresholdSplit(_ PrivateKey, _ uint, _ uint) (map[int]PrivateKey, error) { @@ -20,11 +20,11 @@ func (u Unimplemented) ThresholdSplit(_ PrivateKey, _ uint, _ uint) (map[int]Pri } func (u Unimplemented) RecoverSecret(_ map[int]PrivateKey, _ uint, _ uint) (PrivateKey, error) { - return nil, ErrNotImplemented + return PrivateKey{}, ErrNotImplemented } func (u Unimplemented) ThresholdAggregate(_ map[int]Signature) (Signature, error) { - return nil, ErrNotImplemented + return Signature{}, ErrNotImplemented } func (u Unimplemented) Verify(_ PublicKey, _ []byte, _ Signature) error { @@ -32,5 +32,5 @@ func (u Unimplemented) Verify(_ PublicKey, _ []byte, _ Signature) error { } func (u Unimplemented) Sign(_ PrivateKey, _ []byte) (Signature, error) { - return nil, ErrNotImplemented + return Signature{}, ErrNotImplemented } From 012e8132704cf47022e452dbdf67c4e981694b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Wed, 25 Jan 2023 13:15:56 +0100 Subject: [PATCH 17/22] tbls: convert fmt.Errorf calls to errors package in herumi implementation --- tbls/v2/herumi/herumi.go | 57 ++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/tbls/v2/herumi/herumi.go b/tbls/v2/herumi/herumi.go index bc9da289b..81a217dcb 100644 --- a/tbls/v2/herumi/herumi.go +++ b/tbls/v2/herumi/herumi.go @@ -3,6 +3,8 @@ package herumi import ( "fmt" "github.com/herumi/bls-eth-go-binary/bls" + "github.com/obolnetwork/charon/app/errors" + "github.com/obolnetwork/charon/app/z" "github.com/obolnetwork/charon/tbls/v2" "strconv" "sync" @@ -16,11 +18,11 @@ var initializationOnce = sync.Once{} func init() { initializationOnce.Do(func() { if err := bls.Init(bls.BLS12_381); err != nil { - panic(fmt.Errorf("cannot initialize Herumi BLS, %w", err)) + panic(errors.Wrap(err, "cannot initialize Herumi BLS")) } if err := bls.SetETHmode(bls.EthModeLatest); err != nil { - panic(fmt.Errorf("cannot initialize Herumi BLS, %w", err)) + panic(errors.Wrap(err, "cannot initialize Herumi BLS")) } }) } @@ -44,12 +46,12 @@ func (h Herumi) SecretToPublicKey(secret v2.PrivateKey) (v2.PublicKey, error) { var p bls.SecretKey if err := p.Deserialize(secret[:]); err != nil { - return v2.PublicKey{}, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) + return v2.PublicKey{}, errors.Wrap(err, "cannot unmarshal secret into Herumi secret key") } pubk, err := p.GetSafePublicKey() if err != nil { - return v2.PublicKey{}, fmt.Errorf("cannot obtain public key from secret secret, %w", err) + return v2.PublicKey{}, errors.Wrap(err, "cannot obtain public key from secret secret") } return *(*v2.PublicKey)(pubk.Serialize()), nil @@ -59,7 +61,7 @@ func (h Herumi) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) var p bls.SecretKey if err := p.Deserialize(secret[:]); err != nil { - return nil, fmt.Errorf("cannot unmarshal bytes into Herumi secret key, %w", err) + return nil, errors.Wrap(err, "cannot unmarshal bytes into Herumi secret key") } // master key Polynomial @@ -80,14 +82,19 @@ func (h Herumi) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) err := blsID.SetDecString(fmt.Sprintf("%d", i)) if err != nil { - return nil, fmt.Errorf("cannot set ID %d for key number %d, %w", i, i, err) + return nil, errors.Wrap( + err, + "cannot set ID", + z.Int("id_number", i), + z.Int("key_number", i), + ) } var sk bls.SecretKey err = sk.Set(poly, &blsID) if err != nil { - return nil, err + return nil, errors.Wrap(err, "cannot set ID on polynomial", z.Int("id_number", i)) } ret[i] = *(*v2.PrivateKey)(sk.Serialize()) @@ -107,21 +114,29 @@ func (h Herumi) RecoverSecret(shares map[int]v2.PrivateKey, _, _ uint) (v2.Priva key := key var kpk bls.SecretKey if err := kpk.Deserialize(key[:]); err != nil { - return v2.PrivateKey{}, fmt.Errorf("cannot unmarshal key with index %d into Herumi secret key, %w", idx, err) + return v2.PrivateKey{}, errors.Wrap( + err, + "cannot unmarshal key with into Herumi secret key", + z.Int("key_number", idx), + ) } rawKeys = append(rawKeys, kpk) var id bls.ID if err := id.SetDecString(strconv.Itoa(idx)); err != nil { - return v2.PrivateKey{}, fmt.Errorf("private key id %d id isn't a number", idx) + return v2.PrivateKey{}, errors.Wrap( + err, + "private key isn't a number", + z.Int("key_number", idx), + ) } rawIDs = append(rawIDs, id) } if err := pk.Recover(rawKeys, rawIDs); err != nil { - return v2.PrivateKey{}, fmt.Errorf("cannot recover full private key from partial keys, %w", err) + return v2.PrivateKey{}, errors.Wrap(err, "cannot recover full private key from partial keys") } return *(*v2.PrivateKey)(pk.Serialize()), nil @@ -136,14 +151,22 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature rawSignature := rawSignature var signature bls.Sign if err := signature.Deserialize(rawSignature[:]); err != nil { - return v2.Signature{}, fmt.Errorf("cannot unmarshal signature with index %d into Herumi signature, %w", idx, err) + return v2.Signature{}, errors.Wrap( + err, + "cannot unmarshal signature into Herumi signature", + z.Int("signature_number", idx), + ) } rawSigns = append(rawSigns, signature) var id bls.ID if err := id.SetDecString(strconv.Itoa(idx)); err != nil { - return v2.Signature{}, fmt.Errorf("signature id %d id isn't a number", idx) + return v2.Signature{}, errors.Wrap( + err, + "signature id isn't a number", + z.Int("signature_number", idx), + ) } rawIDs = append(rawIDs, id) @@ -152,7 +175,7 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature var complete bls.Sign if err := complete.Recover(rawSigns, rawIDs); err != nil { - return v2.Signature{}, fmt.Errorf("cannot combine signatures, %w", err) + return v2.Signature{}, errors.Wrap(err, "cannot combine signatures") } return *(*v2.Signature)(complete.Serialize()), nil @@ -161,16 +184,16 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature func (h Herumi) Verify(compressedPublicKey v2.PublicKey, data []byte, rawSignature v2.Signature) error { var pubKey bls.PublicKey if err := pubKey.Deserialize(compressedPublicKey[:]); err != nil { - return fmt.Errorf("cannot set compressed public key in Herumi format, %w", err) + return errors.Wrap(err, "cannot set compressed public key in Herumi format") } var signature bls.Sign if err := signature.Deserialize(rawSignature[:]); err != nil { - return fmt.Errorf("cannot unmarshal signature into Herumi signature, %w", err) + return errors.Wrap(err, "cannot unmarshal signature into Herumi signature") } if !signature.VerifyByte(&pubKey, data) { - return fmt.Errorf("signature not verified") + return errors.New("signature not verified") } return nil @@ -180,7 +203,7 @@ func (h Herumi) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error var p bls.SecretKey if err := p.Deserialize(privateKey[:]); err != nil { - return v2.Signature{}, fmt.Errorf("cannot unmarshal secret into Herumi secret key, %w", err) + return v2.Signature{}, errors.Wrap(err, "cannot unmarshal secret into Herumi secret key") } sigBytes := p.SignByte(data).Serialize() From 7ed71c1ac9fa9a7806f8bed2b9aab00f5b3440ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Thu, 26 Jan 2023 11:16:49 +0100 Subject: [PATCH 18/22] tbls: fix golangci-lint errors --- tbls/v2/herumi/herumi.go | 27 ++++++++++++-------- tbls/v2/kryptology/kryptology.go | 44 ++++++++++++++++++++++---------- tbls/v2/tbls.go | 6 +++-- tbls/v2/tbls_test.go | 10 +++++--- tbls/v2/unimplemented.go | 20 ++++++++------- 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/tbls/v2/herumi/herumi.go b/tbls/v2/herumi/herumi.go index 81a217dcb..a3b57bbf4 100644 --- a/tbls/v2/herumi/herumi.go +++ b/tbls/v2/herumi/herumi.go @@ -2,12 +2,13 @@ package herumi import ( "fmt" + "strconv" + "sync" + "github.com/herumi/bls-eth-go-binary/bls" "github.com/obolnetwork/charon/app/errors" "github.com/obolnetwork/charon/app/z" - "github.com/obolnetwork/charon/tbls/v2" - "strconv" - "sync" + v2 "github.com/obolnetwork/charon/tbls/v2" ) var initializationOnce = sync.Once{} @@ -15,8 +16,11 @@ var initializationOnce = sync.Once{} // PSA: as much as init() is (almost) an antipattern in Go, Herumi BLS implementation needs an initialization routine // before it can be used. // Hence, we embed it in an init() method along with a sync.Once, so that this effect is only run once. +// +//nolint:gochecknoinits func init() { initializationOnce.Do(func() { + //nolint:nosnakecase if err := bls.Init(bls.BLS12_381); err != nil { panic(errors.Wrap(err, "cannot initialize Herumi BLS")) } @@ -30,7 +34,7 @@ func init() { // Herumi is an Implementation with Herumi-specific inner logic. type Herumi struct{} -func (h Herumi) GenerateSecretKey() (v2.PrivateKey, error) { +func (Herumi) GenerateSecretKey() (v2.PrivateKey, error) { var p bls.SecretKey p.SetByCSPRNG() @@ -42,7 +46,7 @@ func (h Herumi) GenerateSecretKey() (v2.PrivateKey, error) { return *(*v2.PrivateKey)(p.Serialize()), nil } -func (h Herumi) SecretToPublicKey(secret v2.PrivateKey) (v2.PublicKey, error) { +func (Herumi) SecretToPublicKey(secret v2.PrivateKey) (v2.PublicKey, error) { var p bls.SecretKey if err := p.Deserialize(secret[:]); err != nil { @@ -51,13 +55,13 @@ func (h Herumi) SecretToPublicKey(secret v2.PrivateKey) (v2.PublicKey, error) { pubk, err := p.GetSafePublicKey() if err != nil { - return v2.PublicKey{}, errors.Wrap(err, "cannot obtain public key from secret secret") + return v2.PublicKey{}, errors.Wrap(err, "cannot obtain public key from secret") } return *(*v2.PublicKey)(pubk.Serialize()), nil } -func (h Herumi) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { +func (Herumi) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { var p bls.SecretKey if err := p.Deserialize(secret[:]); err != nil { @@ -103,7 +107,7 @@ func (h Herumi) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) return ret, nil } -func (h Herumi) RecoverSecret(shares map[int]v2.PrivateKey, _, _ uint) (v2.PrivateKey, error) { +func (Herumi) RecoverSecret(shares map[int]v2.PrivateKey, _, _ uint) (v2.PrivateKey, error) { var pk bls.SecretKey var rawKeys []bls.SecretKey @@ -142,7 +146,7 @@ func (h Herumi) RecoverSecret(shares map[int]v2.PrivateKey, _, _ uint) (v2.Priva return *(*v2.PrivateKey)(pk.Serialize()), nil } -func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { +func (Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { var rawSigns []bls.Sign var rawIDs []bls.ID @@ -181,7 +185,7 @@ func (h Herumi) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature return *(*v2.Signature)(complete.Serialize()), nil } -func (h Herumi) Verify(compressedPublicKey v2.PublicKey, data []byte, rawSignature v2.Signature) error { +func (Herumi) Verify(compressedPublicKey v2.PublicKey, data []byte, rawSignature v2.Signature) error { var pubKey bls.PublicKey if err := pubKey.Deserialize(compressedPublicKey[:]); err != nil { return errors.Wrap(err, "cannot set compressed public key in Herumi format") @@ -199,7 +203,7 @@ func (h Herumi) Verify(compressedPublicKey v2.PublicKey, data []byte, rawSignatu return nil } -func (h Herumi) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { +func (Herumi) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { var p bls.SecretKey if err := p.Deserialize(privateKey[:]); err != nil { @@ -207,5 +211,6 @@ func (h Herumi) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error } sigBytes := p.SignByte(data).Serialize() + return *(*v2.Signature)(sigBytes), nil } diff --git a/tbls/v2/kryptology/kryptology.go b/tbls/v2/kryptology/kryptology.go index b6c738042..6bd31e49d 100644 --- a/tbls/v2/kryptology/kryptology.go +++ b/tbls/v2/kryptology/kryptology.go @@ -2,11 +2,12 @@ package kryptology import ( "crypto/rand" + "github.com/coinbase/kryptology/pkg/core/curves" share "github.com/coinbase/kryptology/pkg/sharing" "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" "github.com/obolnetwork/charon/app/errors" - "github.com/obolnetwork/charon/tbls/v2" + v2 "github.com/obolnetwork/charon/tbls/v2" ) // blsScheme is the BLS12-381 ETH2 signature scheme with standard domain separation tag used for signatures. @@ -17,23 +18,26 @@ var blsScheme = bls_sig.NewSigEth2() // Kryptology is an Implementation with Kryptology-specific inner logic. type Kryptology struct{} -func (k Kryptology) GenerateSecretKey() (v2.PrivateKey, error) { +func (Kryptology) GenerateSecretKey() (v2.PrivateKey, error) { _, secret, err := blsScheme.Keygen() if err != nil { return v2.PrivateKey{}, errors.Wrap(err, "generate key") } ret, err := secret.MarshalBinary() + if err != nil { + return v2.PrivateKey{}, errors.Wrap(err, "cannot unmarshal generated secret into kryptology object") + } // Commenting here once, this syntax will appear often: // here I'm converting ret to a pointer to instance of v2.PrivateKey, which is // an array with constant size. // I'm dereferencing it to return a copy as well. // Ref: https://go.dev/ref/spec#Conversions_from_slice_to_array_pointer - return *(*v2.PrivateKey)(ret), err + return *(*v2.PrivateKey)(ret), nil } -func (k Kryptology) SecretToPublicKey(key v2.PrivateKey) (v2.PublicKey, error) { +func (Kryptology) SecretToPublicKey(key v2.PrivateKey) (v2.PublicKey, error) { rawKey := new(bls_sig.SecretKey) if err := rawKey.UnmarshalBinary(key[:]); err != nil { return v2.PublicKey{}, errors.Wrap(err, "unmarshal raw key into kryptology object") @@ -45,11 +49,14 @@ func (k Kryptology) SecretToPublicKey(key v2.PrivateKey) (v2.PublicKey, error) { } ret, err := pubKey.MarshalBinary() + if err != nil { + return v2.PublicKey{}, errors.Wrap(err, "cannot marshal public key from kryptology object") + } - return *(*v2.PublicKey)(ret), err + return *(*v2.PublicKey)(ret), nil } -func (k Kryptology) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { +func (Kryptology) ThresholdSplit(secret v2.PrivateKey, total uint, threshold uint) (map[int]v2.PrivateKey, error) { scheme, err := share.NewFeldman(uint32(threshold), uint32(total), curves.BLS12381G1()) if err != nil { return nil, errors.Wrap(err, "new Feldman VSS") @@ -74,7 +81,7 @@ func (k Kryptology) ThresholdSplit(secret v2.PrivateKey, total uint, threshold u return sks, nil } -func (k Kryptology) RecoverSecret(shares map[int]v2.PrivateKey, total uint, threshold uint) (v2.PrivateKey, error) { +func (Kryptology) RecoverSecret(shares map[int]v2.PrivateKey, total uint, threshold uint) (v2.PrivateKey, error) { var shamirShares []*share.ShamirShare for idx, value := range shares { // do a local copy, we're dealing with references here @@ -103,11 +110,14 @@ func (k Kryptology) RecoverSecret(shares map[int]v2.PrivateKey, total uint, thre } ret, err := resp.MarshalBinary() + if err != nil { + return v2.PrivateKey{}, errors.Wrap(err, "cannot marshal private key from kryptology object") + } - return *(*v2.PrivateKey)(ret), err + return *(*v2.PrivateKey)(ret), nil } -func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { +func (Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signature) (v2.Signature, error) { var kryptologyPartialSigs []*bls_sig.PartialSignature for idx, sig := range partialSignaturesByIndex { @@ -130,11 +140,14 @@ func (k Kryptology) ThresholdAggregate(partialSignaturesByIndex map[int]v2.Signa } ret, err := aggSig.MarshalBinary() + if err != nil { + return v2.Signature{}, errors.Wrap(err, "cannot marshal signature from kryptology object") + } - return *(*v2.Signature)(ret), err + return *(*v2.Signature)(ret), nil } -func (k Kryptology) Verify(compressedPublicKey v2.PublicKey, data []byte, signature v2.Signature) error { +func (Kryptology) Verify(compressedPublicKey v2.PublicKey, data []byte, signature v2.Signature) error { rawKey := new(bls_sig.PublicKey) if err := rawKey.UnmarshalBinary(compressedPublicKey[:]); err != nil { return errors.Wrap(err, "unmarshal raw public key into kryptology object") @@ -157,7 +170,7 @@ func (k Kryptology) Verify(compressedPublicKey v2.PublicKey, data []byte, signat return nil } -func (k Kryptology) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { +func (Kryptology) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, error) { rawKey := new(bls_sig.SecretKey) if err := rawKey.UnmarshalBinary(privateKey[:]); err != nil { return v2.Signature{}, errors.Wrap(err, "unmarshal raw private key into kryptology object") @@ -165,10 +178,13 @@ func (k Kryptology) Sign(privateKey v2.PrivateKey, data []byte) (v2.Signature, e rawSign, err := blsScheme.Sign(rawKey, data) if err != nil { - return v2.Signature{}, err + return v2.Signature{}, errors.Wrap(err, "cannot execute kryptology signature") } ret, err := rawSign.MarshalBinary() + if err != nil { + return v2.Signature{}, errors.Wrap(err, "cannot marshal signature from kryptology object") + } - return *(*v2.Signature)(ret), err + return *(*v2.Signature)(ret), nil } diff --git a/tbls/v2/tbls.go b/tbls/v2/tbls.go index 909670b15..99cc00eff 100644 --- a/tbls/v2/tbls.go +++ b/tbls/v2/tbls.go @@ -2,8 +2,10 @@ package v2 import "sync" -var impl Implementation = Unimplemented{} -var implLock sync.Mutex +var ( + impl Implementation = Unimplemented{} + implLock sync.Mutex +) type ( // PublicKey is a byte slice containing a compressed BLS12-381 public key. diff --git a/tbls/v2/tbls_test.go b/tbls/v2/tbls_test.go index b4e7c8ce8..d45272fcd 100644 --- a/tbls/v2/tbls_test.go +++ b/tbls/v2/tbls_test.go @@ -2,13 +2,14 @@ package v2_test import ( "crypto/rand" - "github.com/obolnetwork/charon/tbls/v2" + "math/big" + "testing" + + v2 "github.com/obolnetwork/charon/tbls/v2" herumiImpl "github.com/obolnetwork/charon/tbls/v2/herumi" "github.com/obolnetwork/charon/tbls/v2/kryptology" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "math/big" - "testing" ) type TestSuite struct { @@ -125,6 +126,7 @@ func (ts *TestSuite) Test_Sign() { } func runSuite(t *testing.T, i v2.Implementation) { + t.Helper() ts := NewTestSuite(i) suite.Run(t, &ts) @@ -139,6 +141,7 @@ func TestKryptologyImplementation(t *testing.T) { } func runBenchmark(b *testing.B, impl v2.Implementation) { + b.Helper() s := NewTestSuite(impl) t := &testing.T{} s.SetT(t) @@ -191,6 +194,7 @@ func (r randomizedImpl) selectImpl() (v2.Implementation, error) { // random number: [0, len(ts.impl)) rawN, err := rand.Int(rand.Reader, blen) if err != nil { + //nolint:wrapcheck return nil, err } diff --git a/tbls/v2/unimplemented.go b/tbls/v2/unimplemented.go index 5bfe7003c..1a14cc9f1 100644 --- a/tbls/v2/unimplemented.go +++ b/tbls/v2/unimplemented.go @@ -1,36 +1,38 @@ package v2 -import "fmt" +import ( + "github.com/obolnetwork/charon/app/errors" +) -var ErrNotImplemented = fmt.Errorf("not implemented") +var ErrNotImplemented = errors.New("not implemented") // Unimplemented is an Implementation that always returns ErrNotImplemented. type Unimplemented struct{} -func (u Unimplemented) GenerateSecretKey() (PrivateKey, error) { +func (Unimplemented) GenerateSecretKey() (PrivateKey, error) { return PrivateKey{}, ErrNotImplemented } -func (u Unimplemented) SecretToPublicKey(_ PrivateKey) (PublicKey, error) { +func (Unimplemented) SecretToPublicKey(_ PrivateKey) (PublicKey, error) { return PublicKey{}, ErrNotImplemented } -func (u Unimplemented) ThresholdSplit(_ PrivateKey, _ uint, _ uint) (map[int]PrivateKey, error) { +func (Unimplemented) ThresholdSplit(_ PrivateKey, _ uint, _ uint) (map[int]PrivateKey, error) { return nil, ErrNotImplemented } -func (u Unimplemented) RecoverSecret(_ map[int]PrivateKey, _ uint, _ uint) (PrivateKey, error) { +func (Unimplemented) RecoverSecret(_ map[int]PrivateKey, _ uint, _ uint) (PrivateKey, error) { return PrivateKey{}, ErrNotImplemented } -func (u Unimplemented) ThresholdAggregate(_ map[int]Signature) (Signature, error) { +func (Unimplemented) ThresholdAggregate(_ map[int]Signature) (Signature, error) { return Signature{}, ErrNotImplemented } -func (u Unimplemented) Verify(_ PublicKey, _ []byte, _ Signature) error { +func (Unimplemented) Verify(_ PublicKey, _ []byte, _ Signature) error { return ErrNotImplemented } -func (u Unimplemented) Sign(_ PrivateKey, _ []byte) (Signature, error) { +func (Unimplemented) Sign(_ PrivateKey, _ []byte) (Signature, error) { return Signature{}, ErrNotImplemented } From f18d06300e8d35887f9557eb014f88e6711f2e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Thu, 26 Jan 2023 11:21:42 +0100 Subject: [PATCH 19/22] tbls: fix pre-commit issues --- go.mod | 2 +- go.sum | 1 - tbls/v2/herumi/herumi.go | 16 ++++++++++++++++ tbls/v2/kryptology/kryptology.go | 16 ++++++++++++++++ tbls/v2/tbls.go | 15 +++++++++++++++ tbls/v2/tbls_test.go | 20 ++++++++++++++++++-- tbls/v2/unimplemented.go | 15 +++++++++++++++ 7 files changed, 81 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 3e65bf1a5..310e3471e 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/ferranbt/fastssz v0.1.2 github.com/golang/snappy v0.0.4 github.com/gorilla/mux v1.8.0 + github.com/herumi/bls-eth-go-binary v1.28.1 github.com/ipfs/go-log/v2 v2.5.1 github.com/jonboulle/clockwork v0.3.0 github.com/jsternberg/zap-logfmt v1.3.0 @@ -95,7 +96,6 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/herumi/bls-eth-go-binary v1.28.1 // indirect github.com/huin/goupnp v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/go-cid v0.2.0 // indirect diff --git a/go.sum b/go.sum index 2b8b1c3e0..e50faf53f 100644 --- a/go.sum +++ b/go.sum @@ -314,7 +314,6 @@ github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuW github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e h1:wCMygKUQhmcQAjlk2Gquzq6dLmyMv2kF+llRspoRgrk= github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= github.com/herumi/bls-eth-go-binary v1.28.1 h1:fcIZ48y5EE9973k05XjE8+P3YiQgjZz4JI/YabAm8KA= github.com/herumi/bls-eth-go-binary v1.28.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= diff --git a/tbls/v2/herumi/herumi.go b/tbls/v2/herumi/herumi.go index a3b57bbf4..912b35685 100644 --- a/tbls/v2/herumi/herumi.go +++ b/tbls/v2/herumi/herumi.go @@ -1,3 +1,18 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + package herumi import ( @@ -6,6 +21,7 @@ import ( "sync" "github.com/herumi/bls-eth-go-binary/bls" + "github.com/obolnetwork/charon/app/errors" "github.com/obolnetwork/charon/app/z" v2 "github.com/obolnetwork/charon/tbls/v2" diff --git a/tbls/v2/kryptology/kryptology.go b/tbls/v2/kryptology/kryptology.go index 6bd31e49d..7c40eb9fc 100644 --- a/tbls/v2/kryptology/kryptology.go +++ b/tbls/v2/kryptology/kryptology.go @@ -1,3 +1,18 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + package kryptology import ( @@ -6,6 +21,7 @@ import ( "github.com/coinbase/kryptology/pkg/core/curves" share "github.com/coinbase/kryptology/pkg/sharing" "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" + "github.com/obolnetwork/charon/app/errors" v2 "github.com/obolnetwork/charon/tbls/v2" ) diff --git a/tbls/v2/tbls.go b/tbls/v2/tbls.go index 99cc00eff..37f0b0908 100644 --- a/tbls/v2/tbls.go +++ b/tbls/v2/tbls.go @@ -1,3 +1,18 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + package v2 import "sync" diff --git a/tbls/v2/tbls_test.go b/tbls/v2/tbls_test.go index d45272fcd..1f7387847 100644 --- a/tbls/v2/tbls_test.go +++ b/tbls/v2/tbls_test.go @@ -1,3 +1,18 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + package v2_test import ( @@ -5,11 +20,12 @@ import ( "math/big" "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + v2 "github.com/obolnetwork/charon/tbls/v2" herumiImpl "github.com/obolnetwork/charon/tbls/v2/herumi" "github.com/obolnetwork/charon/tbls/v2/kryptology" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" ) type TestSuite struct { diff --git a/tbls/v2/unimplemented.go b/tbls/v2/unimplemented.go index 1a14cc9f1..934fe9d2c 100644 --- a/tbls/v2/unimplemented.go +++ b/tbls/v2/unimplemented.go @@ -1,3 +1,18 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + package v2 import ( From bd4daeeb045756d53d618a76e263a363ba6b7af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Thu, 26 Jan 2023 17:02:01 +0100 Subject: [PATCH 20/22] tbls: shorten herumi sync.Once variable name --- tbls/v2/herumi/herumi.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tbls/v2/herumi/herumi.go b/tbls/v2/herumi/herumi.go index 912b35685..be18e7c8d 100644 --- a/tbls/v2/herumi/herumi.go +++ b/tbls/v2/herumi/herumi.go @@ -27,7 +27,7 @@ import ( v2 "github.com/obolnetwork/charon/tbls/v2" ) -var initializationOnce = sync.Once{} +var initOnce = sync.Once{} // PSA: as much as init() is (almost) an antipattern in Go, Herumi BLS implementation needs an initialization routine // before it can be used. @@ -35,7 +35,7 @@ var initializationOnce = sync.Once{} // //nolint:gochecknoinits func init() { - initializationOnce.Do(func() { + initOnce.Do(func() { //nolint:nosnakecase if err := bls.Init(bls.BLS12_381); err != nil { panic(errors.Wrap(err, "cannot initialize Herumi BLS")) From ad3cac8f3f4781c2e98794d7a450ba535182fae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Thu, 26 Jan 2023 17:02:51 +0100 Subject: [PATCH 21/22] tbls: fix GenerateSecret comment in herumi implementation --- tbls/v2/herumi/herumi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tbls/v2/herumi/herumi.go b/tbls/v2/herumi/herumi.go index be18e7c8d..f154ef766 100644 --- a/tbls/v2/herumi/herumi.go +++ b/tbls/v2/herumi/herumi.go @@ -55,7 +55,7 @@ func (Herumi) GenerateSecretKey() (v2.PrivateKey, error) { p.SetByCSPRNG() // Commenting here once, this syntax will appear often: - // here I'm converting ret to a pointer to instance of v2.PrivateKey, which is + // here I'm converting the output of p.Serialize() to a pointer to instance of v2.PrivateKey, which is // an array with constant size. // I'm dereferencing it to return a copy as well. // Ref: https://go.dev/ref/spec#Conversions_from_slice_to_array_pointer From c5c7160e7bd762209b706358ab73b7e22e0a498d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Thu, 26 Jan 2023 17:07:39 +0100 Subject: [PATCH 22/22] testutil: remove CGO_ENABLE=0 herumi uses cgo. --- testutil/compose/define.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testutil/compose/define.go b/testutil/compose/define.go index 19db5989f..b53a93735 100644 --- a/testutil/compose/define.go +++ b/testutil/compose/define.go @@ -257,7 +257,7 @@ func buildLocal(ctx context.Context, dir string) error { log.Info(ctx, "Building local charon binary", z.Str("repo", repo), z.Str("target", target)) cmd := exec.CommandContext(ctx, "go", "build", "-o", target) - cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0") + cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = repo