From 4724fefc92b3cf0c11453f6191c6ff74b9869c60 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sun, 22 Sep 2024 16:04:57 +0200 Subject: [PATCH 1/3] Replace drand's kyber fork with upstream --- blssig/aggregation.go | 6 +++--- blssig/cache_test.go | 4 ++-- blssig/signer.go | 6 +++--- blssig/verifier.go | 9 +++------ blssig/verifier_test.go | 4 ++-- go.mod | 3 +-- go.sum | 6 ++---- sim/signing/bls.go | 6 +++--- test/signing_suite_test.go | 4 ++-- 9 files changed, 21 insertions(+), 27 deletions(-) diff --git a/blssig/aggregation.go b/blssig/aggregation.go index ea468305..22e5bc9a 100644 --- a/blssig/aggregation.go +++ b/blssig/aggregation.go @@ -10,8 +10,8 @@ import ( "github.com/filecoin-project/go-f3/internal/measurements" "go.opentelemetry.io/otel/metric" - "github.com/drand/kyber" - "github.com/drand/kyber/sign" + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/sign" ) // Max size of the point cache. @@ -103,7 +103,7 @@ func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*sign.Mask, error) { kPubkeys = append(kPubkeys, point.Clone()) } - mask, err := sign.NewMask(v.suite, kPubkeys, nil) + mask, err := sign.NewMask(kPubkeys, nil) if err != nil { return nil, fmt.Errorf("creating key mask: %w", err) } diff --git a/blssig/cache_test.go b/blssig/cache_test.go index 91d564f7..33eb651a 100644 --- a/blssig/cache_test.go +++ b/blssig/cache_test.go @@ -4,9 +4,9 @@ import ( "runtime" "testing" - bls12381 "github.com/drand/kyber-bls12381" - "github.com/drand/kyber/sign/bdn" "github.com/stretchr/testify/require" + bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" + "go.dedis.ch/kyber/v4/sign/bdn" "github.com/filecoin-project/go-f3/gpbft" ) diff --git a/blssig/signer.go b/blssig/signer.go index 9c3cf6f8..f7618778 100644 --- a/blssig/signer.go +++ b/blssig/signer.go @@ -5,10 +5,10 @@ import ( "context" "errors" - "github.com/drand/kyber" - bls12381 "github.com/drand/kyber-bls12381" - "github.com/drand/kyber/sign/bdn" "github.com/filecoin-project/go-f3/gpbft" + "go.dedis.ch/kyber/v4" + bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" + "go.dedis.ch/kyber/v4/sign/bdn" ) var _ gpbft.Signer = (*Signer)(nil) diff --git a/blssig/verifier.go b/blssig/verifier.go index ad6c746d..1dece32f 100644 --- a/blssig/verifier.go +++ b/blssig/verifier.go @@ -7,10 +7,9 @@ import ( "runtime/debug" "sync" - "github.com/drand/kyber" - bls12381 "github.com/drand/kyber-bls12381" - "github.com/drand/kyber/pairing" - "github.com/drand/kyber/sign/bdn" + "go.dedis.ch/kyber/v4" + bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" + "go.dedis.ch/kyber/v4/sign/bdn" "go.opentelemetry.io/otel/metric" "github.com/filecoin-project/go-f3/gpbft" @@ -18,7 +17,6 @@ import ( ) type Verifier struct { - suite pairing.Suite scheme *bdn.Scheme keyGroup kyber.Group @@ -29,7 +27,6 @@ type Verifier struct { func VerifierWithKeyOnG1() *Verifier { suite := bls12381.NewBLS12381Suite() return &Verifier{ - suite: suite, scheme: bdn.NewSchemeOnG2(suite), keyGroup: suite.G1(), } diff --git a/blssig/verifier_test.go b/blssig/verifier_test.go index bced0983..b1e4551a 100644 --- a/blssig/verifier_test.go +++ b/blssig/verifier_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" - bls12381 "github.com/drand/kyber-bls12381" - "github.com/drand/kyber/sign/bdn" "github.com/stretchr/testify/require" + bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" + "go.dedis.ch/kyber/v4/sign/bdn" ) func BenchmarkBLSSigning(b *testing.B) { diff --git a/go.mod b/go.mod index 2dfe4150..4455239c 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,6 @@ module github.com/filecoin-project/go-f3 go 1.22 require ( - github.com/drand/kyber v1.3.1 - github.com/drand/kyber-bls12381 v0.3.1 github.com/filecoin-project/go-bitfield v0.2.4 github.com/filecoin-project/go-clock v0.1.0 github.com/filecoin-project/go-state-types v0.14.0 @@ -22,6 +20,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.25.5 github.com/whyrusleeping/cbor-gen v0.1.1 + go.dedis.ch/kyber/v4 v4.0.0-pre2.0.20240916105431-b283c0cdd30a go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/metric v1.28.0 go.uber.org/multierr v1.11.0 diff --git a/go.sum b/go.sum index 00790557..e9926b05 100644 --- a/go.sum +++ b/go.sum @@ -47,10 +47,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/drand/kyber v1.3.1 h1:E0p6M3II+loMVwTlAp5zu4+GGZFNiRfq02qZxzw2T+Y= -github.com/drand/kyber v1.3.1/go.mod h1:f+mNHjiGT++CuueBrpeMhFNdKZAsy0tu03bKq9D5LPA= -github.com/drand/kyber-bls12381 v0.3.1 h1:KWb8l/zYTP5yrvKTgvhOrk2eNPscbMiUOIeWBnmUxGo= -github.com/drand/kyber-bls12381 v0.3.1/go.mod h1:H4y9bLPu7KZA/1efDg+jtJ7emKx+ro3PU7/jWUVt140= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= @@ -436,6 +432,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= +go.dedis.ch/kyber/v4 v4.0.0-pre2.0.20240916105431-b283c0cdd30a h1:FF9ZER1DSNuJWcpC/P9AJ9LpswxPxyyQ65YTOqdzDd0= +go.dedis.ch/kyber/v4 v4.0.0-pre2.0.20240916105431-b283c0cdd30a/go.mod h1:tg6jwKTYEjm94VxkFwiQy+ec9hoQvccIU989wNjXWVI= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= diff --git a/sim/signing/bls.go b/sim/signing/bls.go index e9a3878d..cf551897 100644 --- a/sim/signing/bls.go +++ b/sim/signing/bls.go @@ -5,11 +5,11 @@ import ( "errors" "sync" - bls12381 "github.com/drand/kyber-bls12381" - "github.com/drand/kyber/pairing" - "github.com/drand/kyber/sign/bdn" "github.com/filecoin-project/go-f3/blssig" "github.com/filecoin-project/go-f3/gpbft" + "go.dedis.ch/kyber/v4/pairing" + bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" + "go.dedis.ch/kyber/v4/sign/bdn" ) var _ Backend = (*BLSBackend)(nil) diff --git a/test/signing_suite_test.go b/test/signing_suite_test.go index 95dd9cb9..b1a4e097 100644 --- a/test/signing_suite_test.go +++ b/test/signing_suite_test.go @@ -5,13 +5,13 @@ import ( "slices" "testing" - bls12381 "github.com/drand/kyber-bls12381" - "github.com/drand/kyber/sign/bdn" "github.com/filecoin-project/go-f3/blssig" "github.com/filecoin-project/go-f3/gpbft" "github.com/filecoin-project/go-f3/sim/signing" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" + "go.dedis.ch/kyber/v4/sign/bdn" ) type ( From abc816966d121ccc73738541e8437d921d004013 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sun, 22 Sep 2024 16:14:27 +0200 Subject: [PATCH 2/3] Import the BDN/Gnark module from upstream dedis It's probably going to take a while for upstream to merge the changes so we're importing just the changed package (BDN) and the new package (Gnark) into this repo. That way we avoid forking the entire repo but can still import our changes. Any changes to these pacakges should be submitted as PRs to upstream _first_, then backported to this repo. Includes: - https://github.com/dedis/kyber/pull/546 - https://github.com/dedis/kyber/pull/551 - https://github.com/dedis/kyber/pull/553 --- blssig/aggregation.go | 14 +- blssig/cache_test.go | 6 +- blssig/signer.go | 6 +- blssig/verifier.go | 6 +- blssig/verifier_test.go | 6 +- go.mod | 7 +- go.sum | 21 +- internal/bls/LICENSE | 375 +++++++++++++++++++++++++++++ internal/bls/README.md | 1 + internal/bls/bdn/bdn.go | 221 +++++++++++++++++ internal/bls/bdn/bdn_test.go | 254 +++++++++++++++++++ internal/bls/bdn/mask.go | 221 +++++++++++++++++ internal/bls/bdn/mask_test.go | 153 ++++++++++++ internal/bls/gnark/adapter.go | 48 ++++ internal/bls/gnark/adapter_test.go | 28 +++ internal/bls/gnark/g1.go | 142 +++++++++++ internal/bls/gnark/g2.go | 142 +++++++++++ internal/bls/gnark/group.go | 23 ++ internal/bls/gnark/gt.go | 108 +++++++++ internal/bls/gnark/scalar.go | 109 +++++++++ internal/bls/gnark/suite.go | 83 +++++++ internal/bls/gnark/suite_test.go | 49 ++++ sim/signing/bls.go | 7 +- test/signing_suite_test.go | 11 +- 24 files changed, 2010 insertions(+), 31 deletions(-) create mode 100644 internal/bls/LICENSE create mode 100644 internal/bls/README.md create mode 100644 internal/bls/bdn/bdn.go create mode 100644 internal/bls/bdn/bdn_test.go create mode 100644 internal/bls/bdn/mask.go create mode 100644 internal/bls/bdn/mask_test.go create mode 100644 internal/bls/gnark/adapter.go create mode 100644 internal/bls/gnark/adapter_test.go create mode 100644 internal/bls/gnark/g1.go create mode 100644 internal/bls/gnark/g2.go create mode 100644 internal/bls/gnark/group.go create mode 100644 internal/bls/gnark/gt.go create mode 100644 internal/bls/gnark/scalar.go create mode 100644 internal/bls/gnark/suite.go create mode 100644 internal/bls/gnark/suite_test.go diff --git a/blssig/aggregation.go b/blssig/aggregation.go index 22e5bc9a..2fc56562 100644 --- a/blssig/aggregation.go +++ b/blssig/aggregation.go @@ -6,12 +6,12 @@ import ( "fmt" "runtime/debug" - "github.com/filecoin-project/go-f3/gpbft" - "github.com/filecoin-project/go-f3/internal/measurements" + "go.dedis.ch/kyber/v4" "go.opentelemetry.io/otel/metric" - "go.dedis.ch/kyber/v4" - "go.dedis.ch/kyber/v4/sign" + "github.com/filecoin-project/go-f3/gpbft" + "github.com/filecoin-project/go-f3/internal/bls/bdn" + "github.com/filecoin-project/go-f3/internal/measurements" ) // Max size of the point cache. @@ -93,7 +93,7 @@ func (v *Verifier) VerifyAggregate(msg []byte, signature []byte, pubkeys []gpbft return v.scheme.Verify(aggPubKey, msg, signature) } -func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*sign.Mask, error) { +func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*bdn.Mask, error) { kPubkeys := make([]kyber.Point, 0, len(pubkeys)) for i, p := range pubkeys { point, err := v.pubkeyToPoint(p) @@ -103,9 +103,9 @@ func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*sign.Mask, error) { kPubkeys = append(kPubkeys, point.Clone()) } - mask, err := sign.NewMask(kPubkeys, nil) + mask, err := bdn.NewMask(v.keyGroup, kPubkeys, nil) if err != nil { - return nil, fmt.Errorf("creating key mask: %w", err) + return nil, fmt.Errorf("creating bdn mask: %w", err) } for i := range kPubkeys { err := mask.SetBit(i, true) diff --git a/blssig/cache_test.go b/blssig/cache_test.go index 33eb651a..d9bfd7da 100644 --- a/blssig/cache_test.go +++ b/blssig/cache_test.go @@ -5,16 +5,16 @@ import ( "testing" "github.com/stretchr/testify/require" - bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" - "go.dedis.ch/kyber/v4/sign/bdn" "github.com/filecoin-project/go-f3/gpbft" + "github.com/filecoin-project/go-f3/internal/bls/bdn" + bls12381 "github.com/filecoin-project/go-f3/internal/bls/gnark" ) const maxCacheMemory uint64 = 10 << 20 // 10MiB func TestCacheMemory(t *testing.T) { - suite := bls12381.NewBLS12381Suite() + suite := bls12381.NewSuiteBLS12381() scheme := bdn.NewSchemeOnG2(suite) rand := suite.RandomStream() diff --git a/blssig/signer.go b/blssig/signer.go index f7618778..f90027ef 100644 --- a/blssig/signer.go +++ b/blssig/signer.go @@ -6,9 +6,9 @@ import ( "errors" "github.com/filecoin-project/go-f3/gpbft" + "github.com/filecoin-project/go-f3/internal/bls/bdn" + bls12381 "github.com/filecoin-project/go-f3/internal/bls/gnark" "go.dedis.ch/kyber/v4" - bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" - "go.dedis.ch/kyber/v4/sign/bdn" ) var _ gpbft.Signer = (*Signer)(nil) @@ -21,7 +21,7 @@ type Signer struct { func SignerWithKeyOnG1(pub gpbft.PubKey, privKey kyber.Scalar) *Signer { return &Signer{ - scheme: bdn.NewSchemeOnG2(bls12381.NewBLS12381Suite()), + scheme: bdn.NewSchemeOnG2(bls12381.NewSuiteBLS12381()), pubKey: pub, privKey: privKey, } diff --git a/blssig/verifier.go b/blssig/verifier.go index 1dece32f..282d700d 100644 --- a/blssig/verifier.go +++ b/blssig/verifier.go @@ -8,11 +8,11 @@ import ( "sync" "go.dedis.ch/kyber/v4" - bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" - "go.dedis.ch/kyber/v4/sign/bdn" "go.opentelemetry.io/otel/metric" "github.com/filecoin-project/go-f3/gpbft" + "github.com/filecoin-project/go-f3/internal/bls/bdn" + bls12381 "github.com/filecoin-project/go-f3/internal/bls/gnark" "github.com/filecoin-project/go-f3/internal/measurements" ) @@ -25,7 +25,7 @@ type Verifier struct { } func VerifierWithKeyOnG1() *Verifier { - suite := bls12381.NewBLS12381Suite() + suite := bls12381.NewSuiteBLS12381() return &Verifier{ scheme: bdn.NewSchemeOnG2(suite), keyGroup: suite.G1(), diff --git a/blssig/verifier_test.go b/blssig/verifier_test.go index b1e4551a..6ad70b3b 100644 --- a/blssig/verifier_test.go +++ b/blssig/verifier_test.go @@ -4,14 +4,14 @@ import ( "context" "testing" + "github.com/filecoin-project/go-f3/internal/bls/bdn" + bls12381 "github.com/filecoin-project/go-f3/internal/bls/gnark" "github.com/stretchr/testify/require" - bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" - "go.dedis.ch/kyber/v4/sign/bdn" ) func BenchmarkBLSSigning(b *testing.B) { var ( - blsSuit = bls12381.NewBLS12381Suite() + blsSuit = bls12381.NewSuiteBLS12381() blsSchema = bdn.NewSchemeOnG2(blsSuit) ) privKey, pubKey := blsSchema.NewKeyPair(blsSuit.RandomStream()) diff --git a/go.mod b/go.mod index 4455239c..207261c8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/filecoin-project/go-f3 go 1.22 require ( + github.com/consensys/gnark-crypto v0.12.1 github.com/filecoin-project/go-bitfield v0.2.4 github.com/filecoin-project/go-clock v0.1.0 github.com/filecoin-project/go-state-types v0.14.0 @@ -35,7 +36,9 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect @@ -62,7 +65,6 @@ require ( github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect - github.com/kilic/bls12-381 v0.1.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/koron/go-ssdp v0.0.4 // indirect @@ -82,6 +84,7 @@ require ( github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect @@ -128,6 +131,7 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect + go.dedis.ch/fixbuf v1.0.3 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/fx v1.21.1 // indirect @@ -140,4 +144,5 @@ require ( google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index e9926b05..11a0ee6f 100644 --- a/go.sum +++ b/go.sum @@ -18,12 +18,18 @@ github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= +github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -108,6 +114,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -152,8 +159,6 @@ github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= -github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -173,6 +178,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= @@ -224,6 +231,9 @@ github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+ github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= @@ -434,6 +444,8 @@ go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= go.dedis.ch/kyber/v4 v4.0.0-pre2.0.20240916105431-b283c0cdd30a h1:FF9ZER1DSNuJWcpC/P9AJ9LpswxPxyyQ65YTOqdzDd0= go.dedis.ch/kyber/v4 v4.0.0-pre2.0.20240916105431-b283c0cdd30a/go.mod h1:tg6jwKTYEjm94VxkFwiQy+ec9hoQvccIU989wNjXWVI= +go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= +go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= @@ -555,7 +567,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -661,6 +672,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -672,5 +685,7 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/internal/bls/LICENSE b/internal/bls/LICENSE new file mode 100644 index 00000000..411d65a2 --- /dev/null +++ b/internal/bls/LICENSE @@ -0,0 +1,375 @@ +This code is (c) by DEDIS/EPFL 2017 under the MPL v2 or later version. + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/internal/bls/README.md b/internal/bls/README.md new file mode 100644 index 00000000..b5eddb6c --- /dev/null +++ b/internal/bls/README.md @@ -0,0 +1 @@ +# A temporary fork of part of dedis/kyber diff --git a/internal/bls/bdn/bdn.go b/internal/bls/bdn/bdn.go new file mode 100644 index 00000000..8f1fce38 --- /dev/null +++ b/internal/bls/bdn/bdn.go @@ -0,0 +1,221 @@ +// Package bdn implements the Boneh-Drijvers-Neven signature scheme which is +// an implementation of the bls package which is robust against rogue public-key attacks. Those +// attacks could allow an attacker to forge a public-key and then make a verifiable +// signature for an aggregation of signatures. It fixes the situation by +// adding coefficients to the aggregate. +// +// See the papers: +// https://eprint.iacr.org/2018/483.pdf +// https://crypto.stanford.edu/~dabo/pubs/papers/BLSmultisig.html +package bdn + +import ( + "crypto/cipher" + "errors" + "fmt" + "slices" + + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/pairing" + "go.dedis.ch/kyber/v4/sign" + "go.dedis.ch/kyber/v4/sign/bls" //lint:ignore SA1019 internal + "golang.org/x/crypto/blake2s" +) + +// For the choice of H, we're mostly worried about the second preimage attack. In +// other words, find m' where H(m) == H(m') +// We also use the entire roster so that the coefficient will vary for the same +// public key used in different roster +func hashPointToR(group kyber.Group, pubs []kyber.Point) ([]kyber.Scalar, error) { + h, err := blake2s.NewXOF(blake2s.OutputLengthUnknown, nil) + if err != nil { + return nil, err + } + for _, pub := range pubs { + peer, err := pub.MarshalBinary() + if err != nil { + return nil, err + } + _, err = h.Write(peer) + if err != nil { + return nil, err + } + } + + out := make([]byte, 16*len(pubs)) + _, err = h.Read(out) + if err != nil { + return nil, err + } + + coefs := make([]kyber.Scalar, len(pubs)) + for i := range coefs { + scalar := group.Scalar() + bytes := out[i*16 : (i+1)*16] + if scalar.ByteOrder() != kyber.LittleEndian { + slices.Reverse(bytes) + } + scalar.SetBytes(bytes) + coefs[i] = scalar + } + + return coefs, nil +} + +type Scheme struct { + blsScheme sign.AggregatableScheme + sigGroup kyber.Group + keyGroup kyber.Group + pairing func(signature, public, hashedPoint kyber.Point) bool +} + +// NewSchemeOnG1 returns a sign.Scheme that uses G1 for its signature space and G2 +// for its public keys +func NewSchemeOnG1(suite pairing.Suite) *Scheme { + sigGroup := suite.G1() + keyGroup := suite.G2() + pairing := func(public, hashedMsg, sigPoint kyber.Point) bool { + return suite.ValidatePairing(hashedMsg, public, sigPoint, keyGroup.Point().Base()) + } + return &Scheme{ + blsScheme: bls.NewSchemeOnG1(suite), + sigGroup: sigGroup, + keyGroup: keyGroup, + pairing: pairing, + } +} + +// NewSchemeOnG2 returns a sign.Scheme that uses G2 for its signature space and +// G1 for its public key +func NewSchemeOnG2(suite pairing.Suite) *Scheme { + sigGroup := suite.G2() + keyGroup := suite.G1() + pairing := func(public, hashedMsg, sigPoint kyber.Point) bool { + return suite.ValidatePairing(public, hashedMsg, keyGroup.Point().Base(), sigPoint) + } + return &Scheme{ + blsScheme: bls.NewSchemeOnG2(suite), + sigGroup: sigGroup, + keyGroup: keyGroup, + pairing: pairing, + } +} + +// NewKeyPair creates a new BLS signing key pair. The private key x is a scalar +// and the public key X is a point on the scheme's key group. +func (scheme *Scheme) NewKeyPair(random cipher.Stream) (kyber.Scalar, kyber.Point) { + return scheme.blsScheme.NewKeyPair(random) +} + +// Sign creates a BLS signature S = x * H(m) on a message m using the private +// key x. The signature S is a point on the scheme's signature group. +func (scheme *Scheme) Sign(x kyber.Scalar, msg []byte) ([]byte, error) { + return scheme.blsScheme.Sign(x, msg) +} + +// Verify checks the given BLS signature S on the message m using the public +// key X by verifying that the equality e(H(m), X) == e(H(m), x*B2) == +// e(x*H(m), B2) == e(S, B2) holds where e is the pairing operation and B2 is +// the base point from the scheme's key group. +func (scheme *Scheme) Verify(x kyber.Point, msg, sig []byte) error { + return scheme.blsScheme.Verify(x, msg, sig) +} + +// AggregateSignatures aggregates the signatures using a coefficient for each +// one of them where c = H(pk) and H: keyGroup -> R with R = {1, ..., 2^128} +func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask *Mask) (kyber.Point, error) { + agg := scheme.sigGroup.Point() + for i := range mask.publics { + if enabled, err := mask.GetBit(i); err != nil { + // this should never happen because of the loop boundary + // an error here is probably a bug in the mask implementation + return nil, fmt.Errorf("couldn't find the index %d: %w", i, err) + } else if !enabled { + continue + } + + if len(sigs) == 0 { + return nil, errors.New("length of signatures and public keys must match") + } + + buf := sigs[0] + sigs = sigs[1:] + + sig := scheme.sigGroup.Point() + err := sig.UnmarshalBinary(buf) + if err != nil { + return nil, err + } + + sigC := sig.Clone().Mul(mask.publicCoefs[i], sig) + // c+1 because R is in the range [1, 2^128] and not [0, 2^128-1] + sigC = sigC.Add(sigC, sig) + agg = agg.Add(agg, sigC) + } + + if len(sigs) > 0 { + return nil, errors.New("length of signatures and public keys must match") + } + + return agg, nil +} + +// AggregatePublicKeys aggregates a set of public keys (similarly to +// AggregateSignatures for signatures) using the hash function +// H: keyGroup -> R with R = {1, ..., 2^128}. +func (scheme *Scheme) AggregatePublicKeys(mask *Mask) (kyber.Point, error) { + agg := scheme.keyGroup.Point() + for i := range mask.publics { + if enabled, err := mask.GetBit(i); err != nil { + // this should never happen because of the loop boundary + // an error here is probably a bug in the mask implementation + return nil, fmt.Errorf("couldn't find the index %d: %w", i, err) + } else if !enabled { + continue + } + + agg = agg.Add(agg, mask.publicTerms[i]) + } + + return agg, nil +} + +// v1 API Deprecated ---------------------------------- + +// NewKeyPair creates a new BLS signing key pair. The private key x is a scalar +// and the public key X is a point on curve G2. +// Deprecated: use the new scheme methods instead. +func NewKeyPair(suite pairing.Suite, random cipher.Stream) (kyber.Scalar, kyber.Point) { + return NewSchemeOnG1(suite).NewKeyPair(random) +} + +// Sign creates a BLS signature S = x * H(m) on a message m using the private +// key x. The signature S is a point on curve G1. +// Deprecated: use the new scheme methods instead. +func Sign(suite pairing.Suite, x kyber.Scalar, msg []byte) ([]byte, error) { + return NewSchemeOnG1(suite).Sign(x, msg) +} + +// Verify checks the given BLS signature S on the message m using the public +// key X by verifying that the equality e(H(m), X) == e(H(m), x*B2) == +// e(x*H(m), B2) == e(S, B2) holds where e is the pairing operation and B2 is +// the base point from curve G2. +// Deprecated: use the new scheme methods instead. +func Verify(suite pairing.Suite, x kyber.Point, msg, sig []byte) error { + return NewSchemeOnG1(suite).Verify(x, msg, sig) +} + +// AggregateSignatures aggregates the signatures using a coefficient for each +// one of them where c = H(pk) and H: G2 -> R with R = {1, ..., 2^128} +// Deprecated: use the new scheme methods instead. +func AggregateSignatures(suite pairing.Suite, sigs [][]byte, mask *Mask) (kyber.Point, error) { + return NewSchemeOnG1(suite).AggregateSignatures(sigs, mask) +} + +// AggregatePublicKeys aggregates a set of public keys (similarly to +// AggregateSignatures for signatures) using the hash function +// H: G2 -> R with R = {1, ..., 2^128}. +// Deprecated: use the new scheme methods instead. +func AggregatePublicKeys(suite pairing.Suite, mask *Mask) (kyber.Point, error) { + return NewSchemeOnG1(suite).AggregatePublicKeys(mask) +} diff --git a/internal/bls/bdn/bdn_test.go b/internal/bls/bdn/bdn_test.go new file mode 100644 index 00000000..db49d8f7 --- /dev/null +++ b/internal/bls/bdn/bdn_test.go @@ -0,0 +1,254 @@ +package bdn + +import ( + "encoding" + "encoding/hex" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/pairing/bn256" + "go.dedis.ch/kyber/v4/sign/bls" //lint:ignore SA1019 internal + "go.dedis.ch/kyber/v4/util/random" + + "github.com/filecoin-project/go-f3/internal/bls/gnark" +) + +var suite = bn256.NewSuiteBn256() +var two = suite.Scalar().Add(suite.Scalar().One(), suite.Scalar().One()) +var three = suite.Scalar().Add(two, suite.Scalar().One()) + +// Reference test for other languages +func TestBDN_HashPointToR_BN256(t *testing.T) { + p1 := suite.Point().Base() + p2 := suite.Point().Mul(two, suite.Point().Base()) + p3 := suite.Point().Mul(three, suite.Point().Base()) + + coefs, err := hashPointToR(suite, []kyber.Point{p1, p2, p3}) + + require.NoError(t, err) + require.Equal(t, "35b5b395f58aba3b192fb7e1e5f2abd3", coefs[0].String()) + require.Equal(t, "14dcc79d46b09b93075266e47cd4b19e", coefs[1].String()) + require.Equal(t, "933f6013eb3f654f9489d6d45ad04eaf", coefs[2].String()) + //require.Equal(t, 16, coefs[0].MarshalSize()) + + mask, _ := NewMask(suite, []kyber.Point{p1, p2, p3}, nil) + mask.SetBit(0, true) + mask.SetBit(1, true) + mask.SetBit(2, true) + + agg, err := AggregatePublicKeys(suite, mask) + require.NoError(t, err) + + buf, err := agg.MarshalBinary() + require.NoError(t, err) + ref := "1432ef60379c6549f7e0dbaf289cb45487c9d7da91fc20648f319a9fbebb23164abea76cdf7b1a3d20d539d9fe096b1d6fb3ee31bf1d426cd4a0d09d603b09f55f473fde972aa27aa991c249e890c1e4a678d470592dd09782d0fb3774834f0b2e20074a49870f039848a6b1aff95e1a1f8170163c77098e1f3530744d1826ce" + require.Equal(t, ref, fmt.Sprintf("%x", buf)) +} + +func TestBDN_AggregateSignatures(t *testing.T) { + msg := []byte("Hello Boneh-Lynn-Shacham") + private1, public1 := NewKeyPair(suite, random.New()) + private2, public2 := NewKeyPair(suite, random.New()) + sig1, err := Sign(suite, private1, msg) + require.NoError(t, err) + sig2, err := Sign(suite, private2, msg) + require.NoError(t, err) + + mask, _ := NewMask(suite, []kyber.Point{public1, public2}, nil) + mask.SetBit(0, true) + mask.SetBit(1, true) + + _, err = AggregateSignatures(suite, [][]byte{sig1}, mask) + require.Error(t, err) + + aggregatedSig, err := AggregateSignatures(suite, [][]byte{sig1, sig2}, mask) + require.NoError(t, err) + + aggregatedKey, err := AggregatePublicKeys(suite, mask) + require.NoError(t, err) + + sig, err := aggregatedSig.MarshalBinary() + require.NoError(t, err) + + err = Verify(suite, aggregatedKey, msg, sig) + require.NoError(t, err) + + mask.SetBit(1, false) + aggregatedKey, err = AggregatePublicKeys(suite, mask) + require.NoError(t, err) + + err = Verify(suite, aggregatedKey, msg, sig) + require.Error(t, err) +} + +func TestBDN_SubsetSignature(t *testing.T) { + msg := []byte("Hello Boneh-Lynn-Shacham") + private1, public1 := NewKeyPair(suite, random.New()) + private2, public2 := NewKeyPair(suite, random.New()) + _, public3 := NewKeyPair(suite, random.New()) + sig1, err := Sign(suite, private1, msg) + require.NoError(t, err) + sig2, err := Sign(suite, private2, msg) + require.NoError(t, err) + + mask, _ := NewMask(suite, []kyber.Point{public1, public3, public2}, nil) + mask.SetBit(0, true) + mask.SetBit(2, true) + + aggregatedSig, err := AggregateSignatures(suite, [][]byte{sig1, sig2}, mask) + require.NoError(t, err) + + aggregatedKey, err := AggregatePublicKeys(suite, mask) + require.NoError(t, err) + + sig, err := aggregatedSig.MarshalBinary() + require.NoError(t, err) + + err = Verify(suite, aggregatedKey, msg, sig) + require.NoError(t, err) +} + +func TestBDN_RogueAttack(t *testing.T) { + msg := []byte("Hello Boneh-Lynn-Shacham") + scheme := bls.NewSchemeOnG1(suite) + // honest + _, public1 := scheme.NewKeyPair(random.New()) + // attacker + private2, public2 := scheme.NewKeyPair(random.New()) + + // create a forged public-key for public1 + rogue := public1.Clone().Sub(public2, public1) + + pubs := []kyber.Point{public1, rogue} + + sig, err := Sign(suite, private2, msg) + require.NoError(t, err) + + // Old scheme not resistant to the attack + agg := scheme.AggregatePublicKeys(pubs...) + require.NoError(t, scheme.Verify(agg, msg, sig)) + + // New scheme that should detect + mask, _ := NewMask(suite, pubs, nil) + mask.SetBit(0, true) + mask.SetBit(1, true) + agg, err = AggregatePublicKeys(suite, mask) + require.NoError(t, err) + require.Error(t, Verify(suite, agg, msg, sig)) +} + +func Benchmark_BDN_AggregateSigs(b *testing.B) { + private1, public1 := NewKeyPair(suite, random.New()) + private2, public2 := NewKeyPair(suite, random.New()) + msg := []byte("Hello many times Boneh-Lynn-Shacham") + sig1, err := Sign(suite, private1, msg) + require.Nil(b, err) + sig2, err := Sign(suite, private2, msg) + require.Nil(b, err) + + mask, _ := NewMask(suite, []kyber.Point{public1, public2}, nil) + mask.SetBit(0, true) + mask.SetBit(1, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + AggregateSignatures(suite, [][]byte{sig1, sig2}, mask) + } +} + +func Benchmark_BDN_BLS12381_AggregateVerify(b *testing.B) { + suite := gnark.NewSuiteBLS12381() + schemeOnG2 := NewSchemeOnG2(suite) + + rng := random.New() + pubKeys := make([]kyber.Point, 3000) + privKeys := make([]kyber.Scalar, 3000) + for i := range pubKeys { + privKeys[i], pubKeys[i] = schemeOnG2.NewKeyPair(rng) + } + + mask, err := NewMask(suite.G1(), pubKeys, nil) + require.NoError(b, err) + for i := range pubKeys { + require.NoError(b, mask.SetBit(i, true)) + } + + msg := []byte("Hello many times Boneh-Lynn-Shacham") + sigs := make([][]byte, len(privKeys)) + for i, k := range privKeys { + s, err := schemeOnG2.Sign(k, msg) + require.NoError(b, err) + sigs[i] = s + } + + sig, err := schemeOnG2.AggregateSignatures(sigs, mask) + require.NoError(b, err) + sigb, err := sig.MarshalBinary() + require.NoError(b, err) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + pk, err := schemeOnG2.AggregatePublicKeys(mask) + require.NoError(b, err) + require.NoError(b, schemeOnG2.Verify(pk, msg, sigb)) + } +} + +func unmarshalHex[T encoding.BinaryUnmarshaler](t *testing.T, into T, s string) T { + t.Helper() + b, err := hex.DecodeString(s) + require.NoError(t, err) + require.NoError(t, into.UnmarshalBinary(b)) + return into +} + +// This tests exists to make sure we don't accidentally make breaking changes to signature +// aggregation by using checking against known aggregated signatures and keys. +func TestBDNFixtures(t *testing.T) { + schemeOnG1 := NewSchemeOnG1(suite) + + public1 := unmarshalHex(t, suite.G2().Point(), "1a30714035c7a161e286e54c191b8c68345bd8239c74925a26290e8e1ae97ed6657958a17dca12c943fadceb11b824402389ff427179e0f10194da3c1b771c6083797d2b5915ea78123cbdb99ea6389d6d6b67dcb512a2b552c373094ee5693524e3ebb4a176f7efa7285c25c80081d8cb598745978f1a63b886c09a316b1493") + private1 := unmarshalHex(t, suite.G2().Scalar(), "49cfe5e9f4532670137184d43c0299f8b635bcacf6b0af7cab262494602d9f38") + public2 := unmarshalHex(t, suite.G2().Point(), "603bc61466ec8762ec6de2ba9a80b9d302d08f580d1685ac45a8e404a6ed549719dc0faf94d896a9983ff23423772720e3de5d800bc200de6f7d7e146162d3183b8880c5c0d8b71ca4b3b40f30c12d8cc0679c81a47c239c6aa7e9cc2edab4a927fe865cd413c1c17e3df8f74108e784cd77dd3e161bdaf30019a55826a32a1f") + private2 := unmarshalHex(t, suite.G2().Scalar(), "493abea4bb35b74c78ad9245f9d37883aeb6ee91f7fb0d8a8e11abf7aa2be581") + public3 := unmarshalHex(t, suite.G2().Point(), "56118769a1f0b6286abacaa32109c1497ab0819c5d21f27317e184b6681c283007aa981cb4760de044946febdd6503ab77a4586bc29c04159e53a6fa5dcb9c0261ccd1cb2e28db5204ca829ac9f6be95f957a626544adc34ba3bc542533b6e2f5cbd0567e343641a61a42b63f26c3625f74b66f6f46d17b3bf1688fae4d455ec") + private3 := unmarshalHex(t, suite.G2().Scalar(), "7fb0ebc317e161502208c3c16a4af890dedc3c7b275e8a04e99c0528aa6a19aa") + + sig1Exp, err := hex.DecodeString("0913b76987be19f943be23b636cab9a2484507717326bd8bbdcdbbb6b8d5eb9253cfb3597c3fa550ee4972a398813650825a871f8e0b242ae5ddbce1b7c0e2a8") + require.NoError(t, err) + sig2Exp, err := hex.DecodeString("21195d29b1863bca1559e24375211d1411d8a28a8f4c772870b07f4ccda2fd5e337c1315c210475c683e3aa8b87d3aed3f7255b3087daa30d1e1432dd61d7484") + require.NoError(t, err) + sig3Exp, err := hex.DecodeString("3c1ac80345c1733630dbdc8106925c867544b521c259f9fa9678d477e6e5d3d212b09bc0d95137c3dbc0af2241415156c56e757d5577a609293584d045593195") + require.NoError(t, err) + + aggSigExp := unmarshalHex(t, suite.G1().Point(), "43c1d2ad5a7d71a08f3cd7495db6b3c81a4547af1b76438b2f215e85ec178fea048f93f6ffed65a69ea757b47761e7178103bb347fd79689652e55b6e0054af2") + aggKeyExp := unmarshalHex(t, suite.G2().Point(), "43b5161ede207b9a69fc93114b0c5022b76cc22e813ba739c7e622d826b132333cd637505399963b94e393ec7f5d4875f82391620b34be1fde1f232204fa4f723935d4dbfb725f059456bcf2557f846c03190969f7b800e904d25b0b5bcbdd421c9877d443f0313c3425dfc1e7e646b665d27b9e649faadef1129f95670d70e1") + + msg := []byte("Hello many times Boneh-Lynn-Shacham") + sig1, err := schemeOnG1.Sign(private1, msg) + require.Nil(t, err) + require.Equal(t, sig1Exp, sig1) + + sig2, err := schemeOnG1.Sign(private2, msg) + require.Nil(t, err) + require.Equal(t, sig2Exp, sig2) + + sig3, err := schemeOnG1.Sign(private3, msg) + require.Nil(t, err) + require.Equal(t, sig3Exp, sig3) + + mask, _ := NewMask(suite, []kyber.Point{public1, public2, public3}, nil) + mask.SetBit(0, true) + mask.SetBit(1, false) + mask.SetBit(2, true) + + aggSig, err := schemeOnG1.AggregateSignatures([][]byte{sig1, sig3}, mask) + require.NoError(t, err) + require.True(t, aggSigExp.Equal(aggSig)) + + aggKey, err := schemeOnG1.AggregatePublicKeys(mask) + require.NoError(t, err) + require.True(t, aggKeyExp.Equal(aggKey)) +} diff --git a/internal/bls/bdn/mask.go b/internal/bls/bdn/mask.go new file mode 100644 index 00000000..8e695b87 --- /dev/null +++ b/internal/bls/bdn/mask.go @@ -0,0 +1,221 @@ +package bdn + +import ( + "errors" + "fmt" + "slices" + + "go.dedis.ch/kyber/v4" +) + +// Mask is a bitmask of the participation to a collective signature. +type Mask struct { + // The bitmask indicating which public keys are enabled/disabled for aggregation. This is + // the only mutable field. + mask []byte + + // The following fields are immutable and should not be changed after the mask is created. + // They may be shared between multiple masks. + + // Public keys for aggregation & signature verification. + publics []kyber.Point + // Coefficients used when aggregating signatures. + publicCoefs []kyber.Scalar + // Terms used to aggregate public keys + publicTerms []kyber.Point +} + +// NewMask creates a new mask from a list of public keys. If a key is provided, it +// will set the bit of the key to 1 or return an error if it is not found. +// +// The returned Mask will contain pre-computed terms and coefficients for all provided public +// keys, so it should be re-used for optimal performance (e.g., by creating a "base" mask and +// cloning it whenever aggregating signatures and/or public keys). +func NewMask(group kyber.Group, publics []kyber.Point, myKey kyber.Point) (*Mask, error) { + m := &Mask{ + publics: publics, + } + m.mask = make([]byte, m.Len()) + + if myKey != nil { + for i, key := range publics { + if key.Equal(myKey) { + err := m.SetBit(i, true) + return m, err + } + } + + return nil, errors.New("key not found") + } + + var err error + m.publicCoefs, err = hashPointToR(group, publics) + if err != nil { + return nil, fmt.Errorf("failed to hash public keys: %w", err) + } + + m.publicTerms = make([]kyber.Point, len(publics)) + for i, pub := range publics { + pubC := pub.Clone().Mul(m.publicCoefs[i], pub) + m.publicTerms[i] = pubC.Add(pubC, pub) + } + + return m, nil +} + +// Mask returns the bitmask as a byte array. +func (m *Mask) Mask() []byte { + clone := make([]byte, len(m.mask)) + copy(clone, m.mask) + return clone +} + +// Len returns the length of the byte array necessary to store the bitmask. +func (m *Mask) Len() int { + return (len(m.publics) + 7) / 8 +} + +// SetMask replaces the current mask by the new one if the length matches. +func (m *Mask) SetMask(mask []byte) error { + if m.Len() != len(mask) { + return fmt.Errorf("mismatching mask lengths") + } + + m.mask = mask + return nil +} + +// GetBit returns true if the given bit is set. +func (m *Mask) GetBit(i int) (bool, error) { + if i >= len(m.publics) || i < 0 { + return false, errors.New("index out of range") + } + + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + return m.mask[byteIndex]&mask != 0, nil +} + +// SetBit turns on or off the bit at the given index. +func (m *Mask) SetBit(i int, enable bool) error { + if i >= len(m.publics) || i < 0 { + return errors.New("index out of range") + } + + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + if enable { + m.mask[byteIndex] |= mask + } else { + m.mask[byteIndex] &^= mask + } + return nil +} + +// forEachBitEnabled is a helper to iterate over the bits set to 1 in the mask +// and to return the result of the callback only if it is positive. +func (m *Mask) forEachBitEnabled(f func(i, j, n int) int) int { + n := 0 + for i, b := range m.mask { + for j := uint(0); j < 8; j++ { + mm := byte(1) << (j & 7) + + if b&mm != 0 { + if res := f(i, int(j), n); res >= 0 { + return res + } + + n++ + } + } + } + + return -1 +} + +// IndexOfNthEnabled returns the index of the nth enabled bit or -1 if out of bounds. +func (m *Mask) IndexOfNthEnabled(nth int) int { + return m.forEachBitEnabled(func(i, j, n int) int { + if n == nth { + return i*8 + int(j) + } + + return -1 + }) +} + +// NthEnabledAtIndex returns the sum of bits set to 1 until the given index. In other +// words, it returns how many bits are enabled before the given index. +func (m *Mask) NthEnabledAtIndex(idx int) int { + return m.forEachBitEnabled(func(i, j, n int) int { + if i*8+int(j) == idx { + return n + } + + return -1 + }) +} + +// Publics returns a copy of the list of public keys. +func (m *Mask) Publics() []kyber.Point { + pubs := make([]kyber.Point, len(m.publics)) + copy(pubs, m.publics) + return pubs +} + +// Participants returns the list of public keys participating. +func (m *Mask) Participants() []kyber.Point { + pp := []kyber.Point{} + for i, p := range m.publics { + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + if (m.mask[byteIndex] & mask) != 0 { + pp = append(pp, p) + } + } + + return pp +} + +// CountEnabled returns the number of bit set to 1 +func (m *Mask) CountEnabled() int { + count := 0 + for i := range m.publics { + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + if (m.mask[byteIndex] & mask) != 0 { + count++ + } + } + return count +} + +// CountTotal returns the number of potential participants +func (m *Mask) CountTotal() int { + return len(m.publics) +} + +// Merge merges the given mask to the current one only if +// the length matches +func (m *Mask) Merge(mask []byte) error { + if len(m.mask) != len(mask) { + return errors.New("mismatching mask length") + } + + for i := range m.mask { + m.mask[i] |= mask[i] + } + + return nil +} + +// Clone copies the mask while keeping the precomputed coefficients, etc. This method is thread safe +// and does not modify the original mask. Modifications to the new Mask will not affect the original. +func (m *Mask) Clone() *Mask { + return &Mask{ + mask: slices.Clone(m.mask), + publics: m.publics, + publicCoefs: m.publicCoefs, + publicTerms: m.publicTerms, + } +} diff --git a/internal/bls/bdn/mask_test.go b/internal/bls/bdn/mask_test.go new file mode 100644 index 00000000..9cccecab --- /dev/null +++ b/internal/bls/bdn/mask_test.go @@ -0,0 +1,153 @@ +package bdn + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/util/key" +) + +const n = 17 + +var publics []kyber.Point + +func init() { + publics = make([]kyber.Point, n) + + for i := 0; i < n; i++ { + kp := key.NewKeyPair(suite) + publics[i] = kp.Public + } +} + +func TestMask_CreateMask(t *testing.T) { + mask, err := NewMask(suite, publics, nil) + require.NoError(t, err) + + require.Equal(t, len(publics), len(mask.Publics())) + require.Equal(t, 0, mask.CountEnabled()) + require.Equal(t, n, mask.CountTotal()) + require.Equal(t, n/8+1, mask.Len()) + require.Equal(t, uint8(0), mask.Mask()[0]) + + mask, err = NewMask(suite, publics, publics[2]) + require.NoError(t, err) + + require.Equal(t, len(publics), len(mask.Publics())) + require.Equal(t, 1, mask.CountEnabled()) + require.Equal(t, uint8(0x4), mask.Mask()[0]) + + _, err = NewMask(suite, publics, suite.G1().Point()) + require.Error(t, err) +} + +func TestMask_SetBit(t *testing.T) { + mask, err := NewMask(suite, publics, publics[2]) + require.NoError(t, err) + + // Make sure the mask is initially as we'd expect. + + bit, err := mask.GetBit(1) + require.NoError(t, err) + require.False(t, bit) + + bit, err = mask.GetBit(2) + require.NoError(t, err) + require.True(t, bit) + + // Set bit 1 + + err = mask.SetBit(1, true) + require.NoError(t, err) + require.Equal(t, uint8(0x6), mask.Mask()[0]) + require.Equal(t, 2, len(mask.Participants())) + + bit, err = mask.GetBit(1) + require.NoError(t, err) + require.True(t, bit) + + // Set bit 1 again, nothing should change + + err = mask.SetBit(1, true) + require.NoError(t, err) + require.Equal(t, uint8(0x6), mask.Mask()[0]) + require.Equal(t, 2, len(mask.Participants())) + + bit, err = mask.GetBit(1) + require.NoError(t, err) + require.True(t, bit) + + // Unset bit 2 + + err = mask.SetBit(2, false) + require.NoError(t, err) + require.Equal(t, uint8(0x2), mask.Mask()[0]) + require.Equal(t, 1, len(mask.Participants())) + + bit, err = mask.GetBit(2) + require.NoError(t, err) + require.False(t, bit) + + // Set bit 10 (using byte 2 now) + + err = mask.SetBit(10, true) + require.NoError(t, err) + require.Equal(t, uint8(0x2), mask.Mask()[0]) + require.Equal(t, uint8(0x4), mask.Mask()[1]) + require.Equal(t, 2, len(mask.Participants())) + + bit, err = mask.GetBit(10) + require.NoError(t, err) + require.True(t, bit) + + // And make sure the range limit works. + + err = mask.SetBit(-1, true) + require.Error(t, err) + err = mask.SetBit(len(publics), true) + require.Error(t, err) +} + +func TestMask_SetAndMerge(t *testing.T) { + mask, err := NewMask(suite, publics, publics[2]) + require.NoError(t, err) + + err = mask.SetMask([]byte{}) + require.Error(t, err) + + err = mask.SetMask([]byte{0, 0, 0}) + require.NoError(t, err) + + err = mask.Merge([]byte{}) + require.Error(t, err) + + err = mask.Merge([]byte{0x6, 0, 0}) + require.NoError(t, err) + require.Equal(t, uint8(0x6), mask.Mask()[0]) +} + +func TestMask_PositionalQueries(t *testing.T) { + mask, err := NewMask(suite, publics, publics[2]) + require.NoError(t, err) + + for i := 0; i < 10000; i++ { + bb := make([]byte, 3) + _, err := rand.Read(bb) + require.NoError(t, err) + bb[2] &= byte(1) << 7 + + err = mask.SetMask(bb) + require.NoError(t, err) + + for j := 0; j < mask.CountEnabled(); j++ { + idx := mask.IndexOfNthEnabled(j) + n := mask.NthEnabledAtIndex(idx) + require.Equal(t, j, n) + } + + require.Equal(t, -1, mask.IndexOfNthEnabled(mask.CountEnabled()+1)) + require.Equal(t, -1, mask.NthEnabledAtIndex(-1)) + } +} diff --git a/internal/bls/gnark/adapter.go b/internal/bls/gnark/adapter.go new file mode 100644 index 00000000..cca42e0b --- /dev/null +++ b/internal/bls/gnark/adapter.go @@ -0,0 +1,48 @@ +package gnark + +import ( + "go.dedis.ch/kyber/v4" +) + +// SuiteBLS12381 is an adapter that implements the suites.Suite interface so that +// bls12381 can be used as a common suite to generate key pairs for instance but +// still preserves the properties of the pairing (e.g. the Pair function). +// +// It's important to note that the Point function will generate a point +// compatible with public keys only (group G2) where the signature must be +// used as a point from the group G1. +type SuiteBLS12381 struct { + Suite + kyber.Group +} + +// NewSuiteBLS12381 makes a new BN256 suite +func NewSuiteBLS12381() *SuiteBLS12381 { + return &SuiteBLS12381{} +} + +// Point generates a point from the G2 group that can only be used +// for public keys +func (s *SuiteBLS12381) Point() kyber.Point { + return s.G2().Point() +} + +// PointLen returns the length of a G2 point +func (s *SuiteBLS12381) PointLen() int { + return s.G2().PointLen() +} + +// Scalar generates a scalar +func (s *SuiteBLS12381) Scalar() kyber.Scalar { + return s.G1().Scalar() +} + +// ScalarLen returns the length of a scalar +func (s *SuiteBLS12381) ScalarLen() int { + return s.G1().ScalarLen() +} + +// String returns the name of the suite +func (s *SuiteBLS12381) String() string { + return "gnark.adapter" +} diff --git a/internal/bls/gnark/adapter_test.go b/internal/bls/gnark/adapter_test.go new file mode 100644 index 00000000..1e457aeb --- /dev/null +++ b/internal/bls/gnark/adapter_test.go @@ -0,0 +1,28 @@ +package gnark + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v4/util/key" +) + +func TestAdapter_SuiteBLS12381(t *testing.T) { + suite := NewSuiteBLS12381() + + pair := key.NewKeyPair(suite) + pubkey, err := pair.Public.MarshalBinary() + require.Nil(t, err) + privkey, err := pair.Private.MarshalBinary() + require.Nil(t, err) + + pubhex := suite.Point() + err = pubhex.UnmarshalBinary(pubkey) + require.Nil(t, err) + + privhex := suite.Scalar() + err = privhex.UnmarshalBinary(privkey) + require.Nil(t, err) + + require.Equal(t, "gnark.adapter", suite.String()) +} diff --git a/internal/bls/gnark/g1.go b/internal/bls/gnark/g1.go new file mode 100644 index 00000000..a4041462 --- /dev/null +++ b/internal/bls/gnark/g1.go @@ -0,0 +1,142 @@ +package gnark + +import ( + "crypto/cipher" + "fmt" + "io" + "math/big" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "go.dedis.ch/kyber/v4" +) + +var _ kyber.SubGroupElement = &G1Elt{} + +// G1Elt is a wrapper around a G1 point on the BLS12-381 Gnark curve. +type G1Elt struct{ inner bls12381.G1Jac } + +// MarshalBinary returns a compressed point, without any domain separation tag information +func (p *G1Elt) MarshalBinary() (data []byte, err error) { + var g1aff bls12381.G1Affine + g1aff.FromJacobian(&p.inner) + res := g1aff.Bytes() + return res[:], nil +} + +// UnmarshalBinary populates the point from a compressed point representation. +func (p *G1Elt) UnmarshalBinary(data []byte) error { + var g1aff bls12381.G1Affine + _, err := g1aff.SetBytes(data) + if err != nil { + return fmt.Errorf("setting affine representation: %w", err) + } + + p.inner.FromAffine(&g1aff) + return nil +} + +func (p *G1Elt) String() string { return p.inner.String() } + +func (p *G1Elt) MarshalSize() int { return bls12381.SizeOfG1AffineCompressed } + +// MarshalTo writes a compressed point to the Writer, without any domain separation tag information +func (p *G1Elt) MarshalTo(w io.Writer) (int, error) { + buf, err := p.MarshalBinary() + if err != nil { + return 0, err + } + return w.Write(buf) +} + +// UnmarshalFrom populates the point from a compressed point representation read from the Reader. +func (p *G1Elt) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, p.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + return n, p.UnmarshalBinary(buf) +} + +func (p *G1Elt) Equal(p2 kyber.Point) bool { x := p2.(*G1Elt); return p.inner.Equal(&x.inner) } + +func (p *G1Elt) Null() kyber.Point { + p.inner.X.SetZero() + p.inner.Y.SetOne() + p.inner.Z.SetZero() + return p +} + +func (p *G1Elt) Base() kyber.Point { + p.inner, _, _, _ = bls12381.Generators() + return p +} + +func (p *G1Elt) Pick(rand cipher.Stream) kyber.Point { + var buf [32]byte + rand.XORKeyStream(buf[:], buf[:]) + return p.Hash(buf[:]) +} + +func (p *G1Elt) Set(p2 kyber.Point) kyber.Point { p.inner = p2.(*G1Elt).inner; return p } + +func (p *G1Elt) Clone() kyber.Point { return new(G1Elt).Set(p) } + +func (p *G1Elt) EmbedLen() int { + panic("bls12-381: unsupported operation") +} + +func (p *G1Elt) Embed(_ []byte, _ cipher.Stream) kyber.Point { + panic("bls12-381: unsupported operation") +} + +func (p *G1Elt) Data() ([]byte, error) { + panic("bls12-381: unsupported operation") +} + +func (p *G1Elt) Add(a, b kyber.Point) kyber.Point { + aa, bb := a.(*G1Elt), b.(*G1Elt) + p.inner.Set(&aa.inner) + p.inner.AddAssign(&bb.inner) + return p +} + +func (p *G1Elt) Sub(a, b kyber.Point) kyber.Point { + aa, bb := a.(*G1Elt), b.(*G1Elt) + p.inner.Set(&aa.inner) + p.inner.SubAssign(&bb.inner) + return p +} + +func (p *G1Elt) Neg(a kyber.Point) kyber.Point { + p.inner.Neg(&a.(*G1Elt).inner) + return p +} + +func (p *G1Elt) Mul(s kyber.Scalar, q kyber.Point) kyber.Point { + if q == nil { + q = new(G1Elt).Base() + } + ss, qq := s.(*Scalar), q.(*G1Elt) + var scalar big.Int + ss.inner.BigInt(&scalar) + p.inner.ScalarMultiplication(&qq.inner, &scalar) + return p +} + +func (p *G1Elt) IsInCorrectGroup() bool { + return !(p.inner.X.IsZero() && p.inner.Y.IsZero() && p.inner.X.IsZero()) && + p.inner.IsOnCurve() && p.inner.IsInSubGroup() +} + +var domainG1 = []byte("BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_") + +func (p *G1Elt) Hash(msg []byte) kyber.Point { return p.Hash2(msg, domainG1) } +func (p *G1Elt) Hash2(msg, dst []byte) kyber.Point { + g1aff, err := bls12381.HashToG1(msg, dst) + if err != nil { + panic(fmt.Errorf("error while hashing: %w", err)) + } + p.inner.FromAffine(&g1aff) + return p +} diff --git a/internal/bls/gnark/g2.go b/internal/bls/gnark/g2.go new file mode 100644 index 00000000..48f6a291 --- /dev/null +++ b/internal/bls/gnark/g2.go @@ -0,0 +1,142 @@ +package gnark + +import ( + "crypto/cipher" + "fmt" + "io" + "math/big" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "go.dedis.ch/kyber/v4" +) + +var _ kyber.SubGroupElement = &G2Elt{} + +// G2Elt is a wrapper around the Gnark G2 point type. +type G2Elt struct{ inner bls12381.G2Jac } + +// MarshalBinary returns a compressed point, without any domain separation tag information +func (p *G2Elt) MarshalBinary() (data []byte, err error) { + var g2aff bls12381.G2Affine + g2aff.FromJacobian(&p.inner) + res := g2aff.Bytes() + return res[:], nil +} + +// UnmarshalBinary populates the point from a compressed point representation. +func (p *G2Elt) UnmarshalBinary(data []byte) error { + var g2aff bls12381.G2Affine + _, err := g2aff.SetBytes(data) + if err != nil { + return fmt.Errorf("setting affine representation: %w", err) + } + + p.inner.FromAffine(&g2aff) + return nil +} + +func (p *G2Elt) String() string { return p.inner.String() } + +func (p *G2Elt) MarshalSize() int { return bls12381.SizeOfG2AffineCompressed } + +// MarshalTo writes a compressed point to the Writer, without any domain separation tag information +func (p *G2Elt) MarshalTo(w io.Writer) (int, error) { + buf, err := p.MarshalBinary() + if err != nil { + return 0, err + } + return w.Write(buf) +} + +// UnmarshalFrom populates the point from a compressed point representation read from the Reader. +func (p *G2Elt) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, p.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + return n, p.UnmarshalBinary(buf) +} + +func (p *G2Elt) Equal(p2 kyber.Point) bool { x := p2.(*G2Elt); return p.inner.Equal(&x.inner) } + +func (p *G2Elt) Null() kyber.Point { + p.inner.X.SetZero() + p.inner.Y.SetOne() + p.inner.Z.SetZero() + return p +} + +func (p *G2Elt) Base() kyber.Point { + _, p.inner, _, _ = bls12381.Generators() + return p +} + +func (p *G2Elt) Pick(rand cipher.Stream) kyber.Point { + var buf [32]byte + rand.XORKeyStream(buf[:], buf[:]) + return p.Hash(buf[:]) +} + +func (p *G2Elt) Set(p2 kyber.Point) kyber.Point { p.inner = p2.(*G2Elt).inner; return p } + +func (p *G2Elt) Clone() kyber.Point { return new(G2Elt).Set(p) } + +func (p *G2Elt) EmbedLen() int { + panic("bls12-381: unsupported operation") +} + +func (p *G2Elt) Embed(_ []byte, _ cipher.Stream) kyber.Point { + panic("bls12-381: unsupported operation") +} + +func (p *G2Elt) Data() ([]byte, error) { + panic("bls12-381: unsupported operation") +} + +func (p *G2Elt) Add(a, b kyber.Point) kyber.Point { + aa, bb := a.(*G2Elt), b.(*G2Elt) + p.inner.Set(&aa.inner) + p.inner.AddAssign(&bb.inner) + return p +} + +func (p *G2Elt) Sub(a, b kyber.Point) kyber.Point { + aa, bb := a.(*G2Elt), b.(*G2Elt) + p.inner.Set(&aa.inner) + p.inner.SubAssign(&bb.inner) + return p +} + +func (p *G2Elt) Neg(a kyber.Point) kyber.Point { + p.inner.Neg(&a.(*G2Elt).inner) + return p +} + +func (p *G2Elt) Mul(s kyber.Scalar, q kyber.Point) kyber.Point { + if q == nil { + q = new(G2Elt).Base() + } + ss, qq := s.(*Scalar), q.(*G2Elt) + var scalar big.Int + ss.inner.BigInt(&scalar) + p.inner.ScalarMultiplication(&qq.inner, &scalar) + return p +} + +func (p *G2Elt) IsInCorrectGroup() bool { + return !(p.inner.X.IsZero() && p.inner.Y.IsZero() && p.inner.X.IsZero()) && + p.inner.IsOnCurve() && p.inner.IsInSubGroup() +} + +var domainG2 = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_") + +func (p *G2Elt) Hash(msg []byte) kyber.Point { return p.Hash2(msg, domainG2) } +func (p *G2Elt) Hash2(msg, dst []byte) kyber.Point { + g1aff, err := bls12381.HashToG2(msg, dst) + if err != nil { + panic(fmt.Errorf("error while hashing: %w", err)) + } + p.inner.FromAffine(&g1aff) + return p +} diff --git a/internal/bls/gnark/group.go b/internal/bls/gnark/group.go new file mode 100644 index 00000000..e08a4cf9 --- /dev/null +++ b/internal/bls/gnark/group.go @@ -0,0 +1,23 @@ +package gnark + +import ( + fr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "go.dedis.ch/kyber/v4" +) + +var ( + G1 kyber.Group = &groupBls{name: "bls12-381.G1", newPoint: func() kyber.Point { return new(G1Elt).Null() }} + G2 kyber.Group = &groupBls{name: "bls12-381.G2", newPoint: func() kyber.Point { return new(G2Elt).Null() }} + GT kyber.Group = &groupBls{name: "bls12-381.GT", newPoint: func() kyber.Point { return new(GTElt).Null() }} +) + +type groupBls struct { + name string + newPoint func() kyber.Point +} + +func (g groupBls) String() string { return g.name } +func (g groupBls) ScalarLen() int { return fr.Bytes } +func (g groupBls) Scalar() kyber.Scalar { return new(Scalar).SetInt64(0) } +func (g groupBls) PointLen() int { return g.newPoint().MarshalSize() } +func (g groupBls) Point() kyber.Point { return g.newPoint() } diff --git a/internal/bls/gnark/gt.go b/internal/bls/gnark/gt.go new file mode 100644 index 00000000..13f684bc --- /dev/null +++ b/internal/bls/gnark/gt.go @@ -0,0 +1,108 @@ +package gnark + +import ( + "crypto/cipher" + "io" + "math/big" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "go.dedis.ch/kyber/v4" +) + +var gtBase *bls12381.GT + +func init() { + _, _, g1, g2 := bls12381.Generators() + gt, err := bls12381.Pair([]bls12381.G1Affine{g1}, []bls12381.G2Affine{g2}) + if err != nil { + panic(err) + } + gtBase = > +} + +var _ kyber.Point = >Elt{} + +// GTElt is a wrapper around the Circl Gt point type. +type GTElt struct{ inner bls12381.GT } + +// MarshalBinary returns a compressed point, without any domain separation tag information +func (p *GTElt) MarshalBinary() (data []byte, err error) { + res := p.inner.Bytes() + return res[:], nil +} + +// UnmarshalBinary populates the point from a compressed point representation. +func (p *GTElt) UnmarshalBinary(data []byte) error { return p.inner.Unmarshal(data) } + +func (p *GTElt) String() string { return p.inner.String() } + +func (p *GTElt) MarshalSize() int { return bls12381.SizeOfGT } + +// MarshalTo writes a compressed point to the Writer, without any domain separation tag information +func (p *GTElt) MarshalTo(w io.Writer) (int, error) { + buf, err := p.MarshalBinary() + if err != nil { + return 0, err + } + return w.Write(buf) +} + +// UnmarshalFrom populates the point from a compressed point representation read from the Reader. +func (p *GTElt) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, p.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + return n, p.UnmarshalBinary(buf) +} + +func (p *GTElt) Equal(p2 kyber.Point) bool { x := p2.(*GTElt); return p.inner.Equal(&x.inner) } + +func (p *GTElt) Null() kyber.Point { p.inner.SetOne(); return p } + +func (p *GTElt) Base() kyber.Point { p.inner = *gtBase; return p } + +func (p *GTElt) Pick(_ cipher.Stream) kyber.Point { + panic("bls12-381: unsupported operation") +} + +func (p *GTElt) Set(p2 kyber.Point) kyber.Point { p.inner = p2.(*GTElt).inner; return p } + +func (p *GTElt) Clone() kyber.Point { return new(GTElt).Set(p) } + +func (p *GTElt) EmbedLen() int { + panic("bls12-381: unsupported operation") +} + +func (p *GTElt) Embed(_ []byte, _ cipher.Stream) kyber.Point { + panic("bls12-381: unsupported operation") +} + +func (p *GTElt) Data() ([]byte, error) { + panic("bls12-381: unsupported operation") +} + +func (p *GTElt) Add(a, b kyber.Point) kyber.Point { + aa, bb := a.(*GTElt), b.(*GTElt) + p.inner.Mul(&aa.inner, &bb.inner) + return p +} + +func (p *GTElt) Sub(a, b kyber.Point) kyber.Point { + return p.Add(a, new(GTElt).Neg(b)) +} + +func (p *GTElt) Neg(a kyber.Point) kyber.Point { + aa := a.(*GTElt) + p.inner.Inverse(&aa.inner) + return p +} + +func (p *GTElt) Mul(s kyber.Scalar, q kyber.Point) kyber.Point { + qq, ss := q.(*GTElt), s.(*Scalar) + var scalar big.Int + ss.inner.BigInt(&scalar) + p.inner.Exp(qq.inner, &scalar) + return p +} diff --git a/internal/bls/gnark/scalar.go b/internal/bls/gnark/scalar.go new file mode 100644 index 00000000..38047b52 --- /dev/null +++ b/internal/bls/gnark/scalar.go @@ -0,0 +1,109 @@ +package gnark + +import ( + "crypto/cipher" + "io" + "math/big" + + fr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/util/random" +) + +var _ kyber.Scalar = &Scalar{} + +type Scalar struct{ inner fr.Element } + +func (s *Scalar) MarshalBinary() (data []byte, err error) { res := s.inner.Bytes(); return res[:], nil } + +func (s *Scalar) UnmarshalBinary(data []byte) error { s.inner.SetBytes(data); return nil } + +func (s *Scalar) String() string { return s.inner.String() } + +func (s *Scalar) MarshalSize() int { return fr.Bytes } + +func (s *Scalar) MarshalTo(w io.Writer) (int, error) { + buf := s.inner.Bytes() + return w.Write(buf[:]) +} + +func (s *Scalar) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, s.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + s.inner.SetBytes(buf) + return n, nil +} + +func (s *Scalar) Equal(s2 kyber.Scalar) bool { + x := s2.(*Scalar) + return s.inner.Cmp(&x.inner) == 0 +} + +func (s *Scalar) Set(a kyber.Scalar) kyber.Scalar { + aa := a.(*Scalar) + s.inner.Set(&aa.inner) + return s +} + +func (s *Scalar) Clone() kyber.Scalar { return new(Scalar).Set(s) } + +func (s *Scalar) SetInt64(v int64) kyber.Scalar { + s.inner.SetInt64(v) + + return s +} + +func (s *Scalar) Zero() kyber.Scalar { s.inner.SetUint64(0); return s } + +func (s *Scalar) Add(a, b kyber.Scalar) kyber.Scalar { + aa, bb := a.(*Scalar), b.(*Scalar) + s.inner.Add(&aa.inner, &bb.inner) + return s +} + +func (s *Scalar) Sub(a, b kyber.Scalar) kyber.Scalar { + aa, bb := a.(*Scalar), b.(*Scalar) + s.inner.Sub(&aa.inner, &bb.inner) + return s +} + +func (s *Scalar) Neg(a kyber.Scalar) kyber.Scalar { + s.Set(a) + s.inner.Neg(&s.inner) + return s +} + +func (s *Scalar) One() kyber.Scalar { s.inner.SetUint64(1); return s } + +func (s *Scalar) Mul(a, b kyber.Scalar) kyber.Scalar { + aa, bb := a.(*Scalar), b.(*Scalar) + s.inner.Mul(&aa.inner, &bb.inner) + return s +} + +func (s *Scalar) Div(a, b kyber.Scalar) kyber.Scalar { return s.Mul(new(Scalar).Inv(b), a) } + +func (s *Scalar) Inv(a kyber.Scalar) kyber.Scalar { + aa := a.(*Scalar) + s.inner.Inverse(&aa.inner) + return s +} + +func (s *Scalar) Pick(stream cipher.Stream) kyber.Scalar { + n := random.Int(fr.Modulus(), stream) + s.inner.SetBigInt(n) + return s +} + +func (s *Scalar) SetBytes(data []byte) kyber.Scalar { s.inner.SetBytes(data); return s } + +func (s *Scalar) ByteOrder() kyber.ByteOrder { + return kyber.BigEndian +} + +func (s *Scalar) GroupOrder() *big.Int { + return fr.Modulus() +} diff --git a/internal/bls/gnark/suite.go b/internal/bls/gnark/suite.go new file mode 100644 index 00000000..f500f33b --- /dev/null +++ b/internal/bls/gnark/suite.go @@ -0,0 +1,83 @@ +package gnark + +import ( + "crypto/cipher" + "crypto/sha256" + "fmt" + "hash" + "io" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/pairing" + "go.dedis.ch/kyber/v4/util/random" + "go.dedis.ch/kyber/v4/xof/blake2xb" +) + +var _ pairing.Suite = Suite{} + +type Suite struct{} + +func NewSuite() (s Suite) { return } + +func (s Suite) String() string { return "bls12381" } +func (s Suite) G1() kyber.Group { return G1 } +func (s Suite) G2() kyber.Group { return G2 } +func (s Suite) GT() kyber.Group { return GT } + +func (s Suite) Pair(p1, p2 kyber.Point) kyber.Point { + aa, bb := p1.(*G1Elt), p2.(*G2Elt) + var g1aff bls12381.G1Affine + g1aff.FromJacobian(&aa.inner) + var g2aff bls12381.G2Affine + g2aff.FromJacobian(&bb.inner) + gt, err := bls12381.Pair([]bls12381.G1Affine{g1aff}, []bls12381.G2Affine{g2aff}) + if err != nil { + panic(fmt.Errorf("error in gnark pairing: %w", err)) + } + + return >Elt{gt} +} + +func (s Suite) ValidatePairing(p1, p2, p3, p4 kyber.Point) bool { + a, b := p1.(*G1Elt), p2.(*G2Elt) + c, d := p3.(*G1Elt), p4.(*G2Elt) + + var aAff, cAff bls12381.G1Affine + var bAff, dAff bls12381.G2Affine + aAff.FromJacobian(&a.inner) + bAff.FromJacobian(&b.inner) + cAff.FromJacobian(&c.inner) + dAff.FromJacobian(&d.inner) + + cAff.Neg(&cAff) + + out, err := bls12381.PairingCheck( + []bls12381.G1Affine{aAff, cAff}, + []bls12381.G2Affine{bAff, dAff}, + ) + if err != nil { + panic(fmt.Errorf("error in gnark pairing: %w", err)) + } + return out +} + +func (s Suite) Read(_ io.Reader, _ ...interface{}) error { + panic("Suite.Read(): deprecated in drand") +} + +func (s Suite) Write(_ io.Writer, _ ...interface{}) error { + panic("Suite.Write(): deprecated in drand") +} + +func (s Suite) Hash() hash.Hash { + return sha256.New() +} + +func (s Suite) XOF(seed []byte) kyber.XOF { + return blake2xb.New(seed) +} + +func (s Suite) RandomStream() cipher.Stream { + return random.New() +} diff --git a/internal/bls/gnark/suite_test.go b/internal/bls/gnark/suite_test.go new file mode 100644 index 00000000..532441b2 --- /dev/null +++ b/internal/bls/gnark/suite_test.go @@ -0,0 +1,49 @@ +package gnark + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "testing" + + "go.dedis.ch/kyber/v4/pairing" + + "go.dedis.ch/kyber/v4" +) + +func TestVerifySigOnG2(t *testing.T) { + pk := "868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31" + sig := "8d61d9100567de44682506aea1a7a6fa6e5491cd27a0a0ed349ef6910ac5ac20ff7bc3e09d7c046566c9f7f3c6f3b10104990e7cb424998203d8f7de586fb7fa5f60045417a432684f85093b06ca91c769f0e7ca19268375e659c2a2352b4655" + prevSig := "176f93498eac9ca337150b46d21dd58673ea4e3581185f869672e59fa4cb390a" + round := uint64(1) + + suite := NewSuite() + pkb, _ := hex.DecodeString(pk) + pubkeyP := suite.G1().Point() + pubkeyP.UnmarshalBinary(pkb) + sigb, _ := hex.DecodeString(sig) + sigP := suite.G2().Point() + sigP.UnmarshalBinary(sigb) + prev, _ := hex.DecodeString(prevSig) + h := sha256.New() + h.Write(prev) + _ = binary.Write(h, binary.BigEndian, round) + msg := h.Sum(nil) + + base := suite.G1().Point().Base().Clone() + MsgP := suite.G2().Point().(kyber.HashablePoint).Hash(msg) + if !suite.ValidatePairing(base, sigP, pubkeyP, MsgP) { + t.Fatalf("Error validating pairing") + } +} + +func TestImplementInterfaces(_ *testing.T) { + var _ kyber.Point = &G1Elt{} + var _ kyber.Point = &G2Elt{} + var _ kyber.Point = >Elt{} + var _ kyber.HashablePoint = &G1Elt{} + var _ kyber.HashablePoint = &G2Elt{} + // var _ kyber.hashablePoint = &KyberGT{} // GT is not hashable for now + var _ kyber.Group = &groupBls{} + var _ pairing.Suite = &Suite{} +} diff --git a/sim/signing/bls.go b/sim/signing/bls.go index cf551897..10ac51e2 100644 --- a/sim/signing/bls.go +++ b/sim/signing/bls.go @@ -8,8 +8,9 @@ import ( "github.com/filecoin-project/go-f3/blssig" "github.com/filecoin-project/go-f3/gpbft" "go.dedis.ch/kyber/v4/pairing" - bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" - "go.dedis.ch/kyber/v4/sign/bdn" + + "github.com/filecoin-project/go-f3/internal/bls/bdn" + bls12381 "github.com/filecoin-project/go-f3/internal/bls/gnark" ) var _ Backend = (*BLSBackend)(nil) @@ -36,7 +37,7 @@ func (b *BLSBackend) Sign(ctx context.Context, sender gpbft.PubKey, msg []byte) } func NewBLSBackend() *BLSBackend { - suite := bls12381.NewBLS12381Suite() + suite := bls12381.NewSuiteBLS12381() return &BLSBackend{ Verifier: blssig.VerifierWithKeyOnG1(), signersByPubKey: make(map[string]*blssig.Signer), diff --git a/test/signing_suite_test.go b/test/signing_suite_test.go index b1a4e097..4d5680a3 100644 --- a/test/signing_suite_test.go +++ b/test/signing_suite_test.go @@ -5,13 +5,14 @@ import ( "slices" "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/filecoin-project/go-f3/blssig" "github.com/filecoin-project/go-f3/gpbft" + "github.com/filecoin-project/go-f3/internal/bls/bdn" + bls12381 "github.com/filecoin-project/go-f3/internal/bls/gnark" "github.com/filecoin-project/go-f3/sim/signing" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - bls12381 "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" - "go.dedis.ch/kyber/v4/sign/bdn" ) type ( @@ -25,7 +26,7 @@ type ( func TestBLSSigning(t *testing.T) { var ( - blsSuit = bls12381.NewBLS12381Suite() + blsSuit = bls12381.NewSuiteBLS12381() blsSchema = bdn.NewSchemeOnG2(blsSuit) ) t.Parallel() From f5af3ff338eb10a42d6e727a3759df6801d00dfd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sun, 22 Sep 2024 16:16:34 +0200 Subject: [PATCH 3/3] Use a mask for BLS aggregation and improve caching fixes #592 --- blssig/aggregation.go | 64 +++++++++++++++++------------ certs/certs.go | 12 ++++-- emulator/host.go | 5 ++- emulator/instance.go | 35 +++++++++------- emulator/signing.go | 53 ++++++++++++++++++------ gpbft/api.go | 23 +++++++++-- gpbft/gpbft.go | 34 ++++++++-------- gpbft/mock_host_test.go | 83 ++++++++------------------------------ gpbft/participant.go | 8 ++-- gpbft/powertable.go | 8 ++++ host.go | 28 ++++++++----- sim/adversary/decide.go | 28 +++++++------ sim/adversary/withhold.go | 10 +++-- sim/ec.go | 23 +++++++---- sim/host.go | 4 +- sim/justification.go | 9 ++++- sim/signing/fake.go | 74 +++++++++++++++++++++------------ test/signing_suite_test.go | 44 +++++++++++++------- 18 files changed, 323 insertions(+), 222 deletions(-) diff --git a/blssig/aggregation.go b/blssig/aggregation.go index 2fc56562..7d160d4e 100644 --- a/blssig/aggregation.go +++ b/blssig/aggregation.go @@ -17,7 +17,12 @@ import ( // Max size of the point cache. const maxPointCacheSize = 10_000 -func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg []byte, _err error) { +type aggregation struct { + mask *bdn.Mask + scheme *bdn.Scheme +} + +func (a *aggregation) Aggregate(mask []int, signatures [][]byte) (_agg []byte, _err error) { defer func() { status := measurements.AttrStatusSuccess if _err != nil { @@ -25,29 +30,31 @@ func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg } if perr := recover(); perr != nil { - _err = fmt.Errorf("panicked aggregating public keys: %v\n%s", + _err = fmt.Errorf("panicked aggregating signatures: %v\n%s", perr, string(debug.Stack())) log.Error(_err) status = measurements.AttrStatusPanic } metrics.aggregate.Record( - context.TODO(), int64(len(pubkeys)), + context.TODO(), int64(len(mask)), metric.WithAttributes(status), ) }() - if len(pubkeys) != len(signatures) { + if len(mask) != len(signatures) { return nil, fmt.Errorf("lengths of pubkeys and sigs does not match %d != %d", - len(pubkeys), len(signatures)) + len(mask), len(signatures)) } - mask, err := v.pubkeysToMask(pubkeys) - if err != nil { - return nil, fmt.Errorf("converting public keys to mask: %w", err) + bdnMask := a.mask.Clone() + for _, bit := range mask { + if err := bdnMask.SetBit(bit, true); err != nil { + return nil, err + } } - aggSigPoint, err := v.scheme.AggregateSignatures(signatures, mask) + aggSigPoint, err := a.scheme.AggregateSignatures(signatures, bdnMask) if err != nil { return nil, fmt.Errorf("computing aggregate signature: %w", err) } @@ -59,7 +66,7 @@ func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg return aggSig, nil } -func (v *Verifier) VerifyAggregate(msg []byte, signature []byte, pubkeys []gpbft.PubKey) (_err error) { +func (a *aggregation) VerifyAggregate(mask []int, msg []byte, signature []byte) (_err error) { defer func() { status := measurements.AttrStatusSuccess if _err != nil { @@ -75,25 +82,35 @@ func (v *Verifier) VerifyAggregate(msg []byte, signature []byte, pubkeys []gpbft } metrics.verifyAggregate.Record( - context.TODO(), int64(len(pubkeys)), + context.TODO(), int64(len(mask)), metric.WithAttributes(status), ) }() - mask, err := v.pubkeysToMask(pubkeys) - if err != nil { - return fmt.Errorf("converting public keys to mask: %w", err) + bdnMask := a.mask.Clone() + for _, bit := range mask { + if err := bdnMask.SetBit(bit, true); err != nil { + return err + } } - aggPubKey, err := v.scheme.AggregatePublicKeys(mask) + aggPubKey, err := a.scheme.AggregatePublicKeys(bdnMask) if err != nil { return fmt.Errorf("aggregating public keys: %w", err) } - return v.scheme.Verify(aggPubKey, msg, signature) + return a.scheme.Verify(aggPubKey, msg, signature) } -func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*bdn.Mask, error) { +func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey) (_agg gpbft.Aggregate, _err error) { + defer func() { + if perr := recover(); perr != nil { + _err = fmt.Errorf("panicked aggregating public keys: %v\n%s", + perr, string(debug.Stack())) + log.Error(_err) + } + }() + kPubkeys := make([]kyber.Point, 0, len(pubkeys)) for i, p := range pubkeys { point, err := v.pubkeyToPoint(p) @@ -105,13 +122,10 @@ func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*bdn.Mask, error) { mask, err := bdn.NewMask(v.keyGroup, kPubkeys, nil) if err != nil { - return nil, fmt.Errorf("creating bdn mask: %w", err) - } - for i := range kPubkeys { - err := mask.SetBit(i, true) - if err != nil { - return nil, fmt.Errorf("setting mask bit %d: %w", i, err) - } + return nil, fmt.Errorf("creating key mask: %w", err) } - return mask, nil + return &aggregation{ + mask: mask, + scheme: v.scheme, + }, nil } diff --git a/certs/certs.go b/certs/certs.go index b22ca346..40c3a7c2 100644 --- a/certs/certs.go +++ b/certs/certs.go @@ -150,7 +150,8 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf return fmt.Errorf("failed to scale power table: %w", err) } - signers := make([]gpbft.PubKey, 0, len(powerTable)) + keys := powerTable.PublicKeys() + mask := make([]int, 0, len(powerTable)) var signerPowers int64 if err := cert.Signers.ForEach(func(i uint64) error { if i >= uint64(len(powerTable)) { @@ -165,7 +166,7 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf cert.GPBFTInstance, powerTable[i].ID) } signerPowers += power - signers = append(signers, powerTable[i].PubKey) + mask = append(mask, int(i)) return nil }); err != nil { return err @@ -192,7 +193,12 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf signedBytes = payload.MarshalForSigning(nn) } - if err := verifier.VerifyAggregate(signedBytes, cert.Signature, signers); err != nil { + aggregate, err := verifier.Aggregate(keys) + if err != nil { + return err + } + + if err := aggregate.VerifyAggregate(mask, signedBytes, cert.Signature); err != nil { return fmt.Errorf("invalid signature on finality certificate for instance %d: %w", cert.GPBFTInstance, err) } return nil diff --git a/emulator/host.go b/emulator/host.go index 2c3e9579..0ce89d36 100644 --- a/emulator/host.go +++ b/emulator/host.go @@ -82,8 +82,9 @@ func (h *driverHost) GetCommittee(id uint64) (*gpbft.Committee, error) { return nil, fmt.Errorf("instance ID %d not found", id) } return &gpbft.Committee{ - PowerTable: instance.powerTable, - Beacon: instance.beacon, + PowerTable: instance.powerTable, + Beacon: instance.beacon, + AggregateVerifier: instance.aggregateVerifier, }, nil } diff --git a/emulator/instance.go b/emulator/instance.go index d5882259..4d711159 100644 --- a/emulator/instance.go +++ b/emulator/instance.go @@ -14,14 +14,15 @@ import ( // Instance represents a GPBFT instance capturing all the information necessary // for GPBFT to function, along with the final decision reached if any. type Instance struct { - t *testing.T - id uint64 - supplementalData gpbft.SupplementalData - proposal gpbft.ECChain - powerTable *gpbft.PowerTable - beacon []byte - decision *gpbft.Justification - signing Signing + t *testing.T + id uint64 + supplementalData gpbft.SupplementalData + proposal gpbft.ECChain + powerTable *gpbft.PowerTable + beacon []byte + decision *gpbft.Justification + signing Signing + aggregateVerifier gpbft.Aggregate } // NewInstance instantiates a new Instance for emulation. If absent, the @@ -58,7 +59,8 @@ func NewInstance(t *testing.T, id uint64, powerEntries gpbft.PowerEntries, propo } proposalChain, err := gpbft.NewChain(proposal[0], proposal[1:]...) require.NoError(t, err) - return &Instance{ + + i := &Instance{ t: t, id: id, powerTable: pt, @@ -68,11 +70,18 @@ func NewInstance(t *testing.T, id uint64, powerEntries gpbft.PowerEntries, propo Commitments: [32]byte{}, PowerTable: ptCid, }, - signing: AdhocSigning(), } + + i.SetSigning(AdhocSigning()) + return i } -func (i *Instance) SetSigning(signing Signing) { i.signing = signing } +func (i *Instance) SetSigning(signing Signing) { + var err error + i.signing = signing + i.aggregateVerifier, err = signing.Aggregate(i.powerTable.Entries.PublicKeys()) + require.NoError(i.t, err) +} func (i *Instance) Proposal() gpbft.ECChain { return i.proposal } func (i *Instance) GetDecision() *gpbft.Justification { return i.decision } func (i *Instance) ID() uint64 { return i.id } @@ -140,7 +149,6 @@ func (i *Instance) NewJustificationWithPayload(payload gpbft.Payload, from ...gp msg := i.signing.MarshalPayloadForSigning(networkName, &payload) qr := gpbft.QuorumResult{ Signers: make([]int, len(from)), - PubKeys: make([]gpbft.PubKey, len(from)), Signatures: make([][]byte, len(from)), } for j, actor := range from { @@ -150,10 +158,9 @@ func (i *Instance) NewJustificationWithPayload(payload gpbft.Payload, from ...gp signature, err := i.signing.Sign(context.Background(), entry.PubKey, msg) require.NoError(i.t, err) qr.Signatures[j] = signature - qr.PubKeys[j] = entry.PubKey qr.Signers[j] = index } - aggregate, err := i.signing.Aggregate(qr.PubKeys, qr.Signatures) + aggregate, err := i.aggregateVerifier.Aggregate(qr.Signers, qr.Signatures) require.NoError(i.t, err) return &gpbft.Justification{ Vote: payload, diff --git a/emulator/signing.go b/emulator/signing.go index 6979f618..c715506e 100644 --- a/emulator/signing.go +++ b/emulator/signing.go @@ -3,6 +3,7 @@ package emulator import ( "bytes" "context" + "encoding/binary" "errors" "hash/crc32" @@ -58,13 +59,22 @@ func (s adhocSigning) Verify(sender gpbft.PubKey, msg, got []byte) error { } } -func (s adhocSigning) Aggregate(signers []gpbft.PubKey, sigs [][]byte) ([]byte, error) { - if len(signers) != len(sigs) { +type aggregate struct { + keys []gpbft.PubKey + signing adhocSigning +} + +// Aggregate implements gpbft.Aggregate. +func (a *aggregate) Aggregate(signerMask []int, sigs [][]byte) ([]byte, error) { + if len(signerMask) != len(sigs) { return nil, errors.New("public keys and signatures length mismatch") } hasher := crc32.NewIEEE() - for i, signer := range signers { - if _, err := hasher.Write(signer); err != nil { + for i, bit := range signerMask { + if err := binary.Write(hasher, binary.BigEndian, uint64(bit)); err != nil { + return nil, err + } + if _, err := hasher.Write(a.keys[bit]); err != nil { return nil, err } if _, err := hasher.Write(sigs[i]); err != nil { @@ -74,16 +84,17 @@ func (s adhocSigning) Aggregate(signers []gpbft.PubKey, sigs [][]byte) ([]byte, return hasher.Sum(nil), nil } -func (s adhocSigning) VerifyAggregate(payload, got []byte, signers []gpbft.PubKey) error { - signatures := make([][]byte, len(signers)) +// VerifyAggregate implements gpbft.Aggregate. +func (a *aggregate) VerifyAggregate(signerMask []int, payload []byte, got []byte) error { + signatures := make([][]byte, len(signerMask)) var err error - for i, signer := range signers { - signatures[i], err = s.Sign(context.Background(), signer, payload) + for i, bit := range signerMask { + signatures[i], err = a.signing.Sign(context.Background(), a.keys[bit], payload) if err != nil { return err } } - want, err := s.Aggregate(signers, signatures) + want, err := a.Aggregate(signerMask, signatures) if err != nil { return err } @@ -93,23 +104,34 @@ func (s adhocSigning) VerifyAggregate(payload, got []byte, signers []gpbft.PubKe return nil } +func (s adhocSigning) Aggregate(keys []gpbft.PubKey) (gpbft.Aggregate, error) { + return &aggregate{keys: keys, + signing: s, + }, nil +} + func (s adhocSigning) MarshalPayloadForSigning(name gpbft.NetworkName, payload *gpbft.Payload) []byte { return payload.MarshalForSigning(name) } type erroneousSigning struct{} +type erroneousAggregate struct{} func (p erroneousSigning) Verify(gpbft.PubKey, []byte, []byte) error { return errors.New("err Verify") } -func (p erroneousSigning) VerifyAggregate([]byte, []byte, []gpbft.PubKey) error { +func (p erroneousAggregate) VerifyAggregate([]int, []byte, []byte) error { return errors.New("err VerifyAggregate") } -func (p erroneousSigning) Aggregate([]gpbft.PubKey, [][]byte) ([]byte, error) { +func (p erroneousAggregate) Aggregate([]int, [][]byte) ([]byte, error) { return nil, errors.New("err Aggregate") } + +func (p erroneousSigning) Aggregate([]gpbft.PubKey) (gpbft.Aggregate, error) { + return erroneousAggregate{}, nil +} func (p erroneousSigning) Sign(context.Context, gpbft.PubKey, []byte) ([]byte, error) { return nil, errors.New("err Sign") } @@ -119,9 +141,16 @@ func (p erroneousSigning) MarshalPayloadForSigning(gpbft.NetworkName, *gpbft.Pay } type panicSigning struct{} +type panicAggregate struct{} func (p panicSigning) Verify(gpbft.PubKey, []byte, []byte) error { panic("π") } func (p panicSigning) VerifyAggregate([]byte, []byte, []gpbft.PubKey) error { panic("π") } -func (p panicSigning) Aggregate([]gpbft.PubKey, [][]byte) ([]byte, error) { panic("π") } func (p panicSigning) Sign(context.Context, gpbft.PubKey, []byte) ([]byte, error) { panic("π") } func (p panicSigning) MarshalPayloadForSigning(gpbft.NetworkName, *gpbft.Payload) []byte { panic("π") } + +func (p panicSigning) Aggregate([]gpbft.PubKey) (gpbft.Aggregate, error) { + return panicAggregate{}, nil +} + +func (p panicAggregate) VerifyAggregate([]int, []byte, []byte) error { panic("π") } +func (p panicAggregate) Aggregate([]int, [][]byte) ([]byte, error) { panic("π") } diff --git a/gpbft/api.go b/gpbft/api.go index 5a3d0da3..c3458fe0 100644 --- a/gpbft/api.go +++ b/gpbft/api.go @@ -83,6 +83,9 @@ type Committee struct { PowerTable *PowerTable // Beacon is the unique beacon value associated with the committee. Beacon []byte + // AggregateVerifier is used to aggregate and verify aggregate signatures made by the + // committee. + AggregateVerifier Aggregate } // Endpoint to which participants can send messages. @@ -116,15 +119,27 @@ type SigningMarshaler interface { MarshalPayloadForSigning(NetworkName, *Payload) []byte } +type Aggregate interface { + // Aggregates signatures from a participants. + // + // Implementations must be safe for concurrent use. + Aggregate(signerMask []int, sigs [][]byte) ([]byte, error) + // VerifyAggregate verifies an aggregate signature. + // + // Implementations must be safe for concurrent use. + VerifyAggregate(signerMask []int, payload, aggSig []byte) error +} + type Verifier interface { // Verifies a signature for the given public key. + // // Implementations must be safe for concurrent use. Verify(pubKey PubKey, msg, sig []byte) error - // Aggregates signatures from a participants. - Aggregate(pubKeys []PubKey, sigs [][]byte) ([]byte, error) - // VerifyAggregate verifies an aggregate signature. + // Return an Aggregate that can aggregate and verify aggregate signatures made by the given + // public keys. + // // Implementations must be safe for concurrent use. - VerifyAggregate(payload, aggSig []byte, signers []PubKey) error + Aggregate(pubKeys []PubKey) (Aggregate, error) } type Signatures interface { diff --git a/gpbft/gpbft.go b/gpbft/gpbft.go index 9a317e6a..c9a091ce 100644 --- a/gpbft/gpbft.go +++ b/gpbft/gpbft.go @@ -156,6 +156,8 @@ type instance struct { input ECChain // The power table for the base chain, used for power in this instance. powerTable *PowerTable + // The aggregate signature verifier/aggregator. + aggregateVerifier Aggregate // The beacon value from the base chain, used for tickets in this instance. beacon []byte // Current round number. @@ -218,6 +220,7 @@ func newInstance( input ECChain, data *SupplementalData, powerTable *PowerTable, + aggregateVerifier Aggregate, beacon []byte) (*instance, error) { if input.IsZero() { return nil, fmt.Errorf("input is empty") @@ -228,16 +231,17 @@ func newInstance( metrics.currentRound.Record(context.TODO(), 0) return &instance{ - participant: participant, - instanceID: instanceID, - input: input, - powerTable: powerTable, - beacon: beacon, - round: 0, - phase: INITIAL_PHASE, - supplementalData: data, - proposal: input, - value: ECChain{}, + participant: participant, + instanceID: instanceID, + input: input, + powerTable: powerTable, + aggregateVerifier: aggregateVerifier, + beacon: beacon, + round: 0, + phase: INITIAL_PHASE, + supplementalData: data, + proposal: input, + value: ECChain{}, candidates: map[ChainKey]struct{}{ input.BaseChain().Key(): {}, }, @@ -986,7 +990,7 @@ func (i *instance) alarmAfterSynchrony() time.Time { // Builds a justification for a value from a quorum result. func (i *instance) buildJustification(quorum QuorumResult, round uint64, phase Phase, value ECChain) *Justification { - aggSignature, err := quorum.Aggregate(i.participant.host) + aggSignature, err := quorum.Aggregate(i.aggregateVerifier) if err != nil { panic(fmt.Errorf("aggregating for phase %v: %v", phase, err)) } @@ -1174,12 +1178,11 @@ func (q *quorumState) CouldReachStrongQuorumFor(key ChainKey, withAdversary bool type QuorumResult struct { // Signers is an array of indexes into the powertable, sorted in increasing order Signers []int - PubKeys []PubKey Signatures [][]byte } -func (q QuorumResult) Aggregate(v Verifier) ([]byte, error) { - return v.Aggregate(q.PubKeys, q.Signatures) +func (q QuorumResult) Aggregate(v Aggregate) ([]byte, error) { + return v.Aggregate(q.Signers, q.Signatures) } func (q QuorumResult) SignersBitfield() bitfield.BitField { @@ -1216,7 +1219,6 @@ func (q *quorumState) FindStrongQuorumFor(key ChainKey) (QuorumResult, bool) { // Accumulate signers and signatures until they reach a strong quorum. signatures := make([][]byte, 0, len(chainSupport.signatures)) - pubkeys := make([]PubKey, 0, len(signatures)) var justificationPower int64 for i, idx := range signers { if idx >= len(q.powerTable.Entries) { @@ -1226,11 +1228,9 @@ func (q *quorumState) FindStrongQuorumFor(key ChainKey) (QuorumResult, bool) { entry := q.powerTable.Entries[idx] justificationPower += power signatures = append(signatures, chainSupport.signatures[entry.ID]) - pubkeys = append(pubkeys, entry.PubKey) if IsStrongQuorum(justificationPower, q.powerTable.ScaledTotal) { return QuorumResult{ Signers: signers[:i+1], - PubKeys: pubkeys, Signatures: signatures, }, true } diff --git a/gpbft/mock_host_test.go b/gpbft/mock_host_test.go index 715c23da..3ad16a84 100644 --- a/gpbft/mock_host_test.go +++ b/gpbft/mock_host_test.go @@ -21,29 +21,29 @@ func (_m *MockHost) EXPECT() *MockHost_Expecter { return &MockHost_Expecter{mock: &_m.Mock} } -// Aggregate provides a mock function with given fields: pubKeys, sigs -func (_m *MockHost) Aggregate(pubKeys []PubKey, sigs [][]byte) ([]byte, error) { - ret := _m.Called(pubKeys, sigs) +// Aggregate provides a mock function with given fields: pubKeys +func (_m *MockHost) Aggregate(pubKeys []PubKey) (Aggregate, error) { + ret := _m.Called(pubKeys) if len(ret) == 0 { panic("no return value specified for Aggregate") } - var r0 []byte + var r0 Aggregate var r1 error - if rf, ok := ret.Get(0).(func([]PubKey, [][]byte) ([]byte, error)); ok { - return rf(pubKeys, sigs) + if rf, ok := ret.Get(0).(func([]PubKey) (Aggregate, error)); ok { + return rf(pubKeys) } - if rf, ok := ret.Get(0).(func([]PubKey, [][]byte) []byte); ok { - r0 = rf(pubKeys, sigs) + if rf, ok := ret.Get(0).(func([]PubKey) Aggregate); ok { + r0 = rf(pubKeys) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) + r0 = ret.Get(0).(Aggregate) } } - if rf, ok := ret.Get(1).(func([]PubKey, [][]byte) error); ok { - r1 = rf(pubKeys, sigs) + if rf, ok := ret.Get(1).(func([]PubKey) error); ok { + r1 = rf(pubKeys) } else { r1 = ret.Error(1) } @@ -58,24 +58,23 @@ type MockHost_Aggregate_Call struct { // Aggregate is a helper method to define mock.On call // - pubKeys []PubKey -// - sigs [][]byte -func (_e *MockHost_Expecter) Aggregate(pubKeys interface{}, sigs interface{}) *MockHost_Aggregate_Call { - return &MockHost_Aggregate_Call{Call: _e.mock.On("Aggregate", pubKeys, sigs)} +func (_e *MockHost_Expecter) Aggregate(pubKeys interface{}) *MockHost_Aggregate_Call { + return &MockHost_Aggregate_Call{Call: _e.mock.On("Aggregate", pubKeys)} } -func (_c *MockHost_Aggregate_Call) Run(run func(pubKeys []PubKey, sigs [][]byte)) *MockHost_Aggregate_Call { +func (_c *MockHost_Aggregate_Call) Run(run func(pubKeys []PubKey)) *MockHost_Aggregate_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].([]PubKey), args[1].([][]byte)) + run(args[0].([]PubKey)) }) return _c } -func (_c *MockHost_Aggregate_Call) Return(_a0 []byte, _a1 error) *MockHost_Aggregate_Call { +func (_c *MockHost_Aggregate_Call) Return(_a0 Aggregate, _a1 error) *MockHost_Aggregate_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockHost_Aggregate_Call) RunAndReturn(run func([]PubKey, [][]byte) ([]byte, error)) *MockHost_Aggregate_Call { +func (_c *MockHost_Aggregate_Call) RunAndReturn(run func([]PubKey) (Aggregate, error)) *MockHost_Aggregate_Call { _c.Call.Return(run) return _c } @@ -527,54 +526,6 @@ func (_c *MockHost_Verify_Call) RunAndReturn(run func(PubKey, []byte, []byte) er return _c } -// VerifyAggregate provides a mock function with given fields: payload, aggSig, signers -func (_m *MockHost) VerifyAggregate(payload []byte, aggSig []byte, signers []PubKey) error { - ret := _m.Called(payload, aggSig, signers) - - if len(ret) == 0 { - panic("no return value specified for VerifyAggregate") - } - - var r0 error - if rf, ok := ret.Get(0).(func([]byte, []byte, []PubKey) error); ok { - r0 = rf(payload, aggSig, signers) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockHost_VerifyAggregate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'VerifyAggregate' -type MockHost_VerifyAggregate_Call struct { - *mock.Call -} - -// VerifyAggregate is a helper method to define mock.On call -// - payload []byte -// - aggSig []byte -// - signers []PubKey -func (_e *MockHost_Expecter) VerifyAggregate(payload interface{}, aggSig interface{}, signers interface{}) *MockHost_VerifyAggregate_Call { - return &MockHost_VerifyAggregate_Call{Call: _e.mock.On("VerifyAggregate", payload, aggSig, signers)} -} - -func (_c *MockHost_VerifyAggregate_Call) Run(run func(payload []byte, aggSig []byte, signers []PubKey)) *MockHost_VerifyAggregate_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].([]byte), args[1].([]byte), args[2].([]PubKey)) - }) - return _c -} - -func (_c *MockHost_VerifyAggregate_Call) Return(_a0 error) *MockHost_VerifyAggregate_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockHost_VerifyAggregate_Call) RunAndReturn(run func([]byte, []byte, []PubKey) error) *MockHost_VerifyAggregate_Call { - _c.Call.Return(run) - return _c -} - // NewMockHost creates a new instance of MockHost. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockHost(t interface { diff --git a/gpbft/participant.go b/gpbft/participant.go index 4cd48b2e..147e4b5b 100644 --- a/gpbft/participant.go +++ b/gpbft/participant.go @@ -333,7 +333,7 @@ func (p *Participant) validateJustification(msg *GMessage, comt *Committee) erro // Check justification power and signature. var justificationPower int64 - signers := make([]PubKey, 0) + signers := make([]int, 0) if err := msg.Justification.Signers.ForEach(func(bit uint64) error { if int(bit) >= len(comt.PowerTable.Entries) { return fmt.Errorf("invalid signer index: %d", bit) @@ -343,7 +343,7 @@ func (p *Participant) validateJustification(msg *GMessage, comt *Committee) erro return fmt.Errorf("signer with ID %d has no power", comt.PowerTable.Entries[bit].ID) } justificationPower += power - signers = append(signers, comt.PowerTable.Entries[bit].PubKey) + signers = append(signers, int(bit)) return nil }); err != nil { return fmt.Errorf("failed to iterate over signers: %w", err) @@ -354,7 +354,7 @@ func (p *Participant) validateJustification(msg *GMessage, comt *Committee) erro } payload := p.host.MarshalPayloadForSigning(p.host.NetworkName(), &msg.Justification.Vote) - if err := p.host.VerifyAggregate(payload, msg.Justification.Signature, signers); err != nil { + if err := comt.AggregateVerifier.VerifyAggregate(signers, payload, msg.Justification.Signature); err != nil { return fmt.Errorf("verification of the aggregate failed: %+v: %w", msg.Justification, err) } @@ -445,7 +445,7 @@ func (p *Participant) beginInstance() error { if err != nil { return err } - if p.gpbft, err = newInstance(p, p.currentInstance, chain, data, comt.PowerTable, comt.Beacon); err != nil { + if p.gpbft, err = newInstance(p, p.currentInstance, chain, data, comt.PowerTable, comt.AggregateVerifier, comt.Beacon); err != nil { return fmt.Errorf("failed creating new gpbft instance: %w", err) } if err := p.gpbft.Start(); err != nil { diff --git a/gpbft/powertable.go b/gpbft/powertable.go index c8a34f71..c052dfac 100644 --- a/gpbft/powertable.go +++ b/gpbft/powertable.go @@ -33,6 +33,14 @@ type PowerTable struct { ScaledTotal int64 } +func (e PowerEntries) PublicKeys() []PubKey { + keys := make([]PubKey, len(e)) + for i, e := range e { + keys[i] = e.PubKey + } + return keys +} + func (p *PowerEntry) Equal(o *PowerEntry) bool { return p.ID == o.ID && p.Power == o.Power && bytes.Equal(p.PubKey, o.PubKey) } diff --git a/host.go b/host.go index 747b56eb..e24c29d3 100644 --- a/host.go +++ b/host.go @@ -620,9 +620,22 @@ func (h *gpbftHost) GetCommittee(instance uint64) (_ *gpbft.Committee, _err erro if err := table.Validate(); err != nil { return nil, fmt.Errorf("invalid power table for instance %d: %w", instance, err) } + + // NOTE: we're intentionally keeping participants here even if they have no + // effective power (after rounding power) to simplify things. The runtime cost is + // minimal and it means that the keys can be aggregated before any rounding is done. + // TODO: this is slow and under a lock, but we only want to do it once per + // instance... ideally we'd have a per-instance lock/once, but that probably isn't + // worth it. + agg, err := h.Aggregate(table.Entries.PublicKeys()) + if err != nil { + return nil, fmt.Errorf("failed to pre-compute aggregate mask for instance %d: %w", instance, err) + } + return &gpbft.Committee{ - PowerTable: table, - Beacon: ts.Beacon(), + PowerTable: table, + Beacon: ts.Beacon(), + AggregateVerifier: agg, }, nil } @@ -732,13 +745,6 @@ func (h *gpbftHost) Verify(pubKey gpbft.PubKey, msg []byte, sig []byte) error { return h.verifier.Verify(pubKey, msg, sig) } -// Aggregates signatures from a participants. -func (h *gpbftHost) Aggregate(pubKeys []gpbft.PubKey, sigs [][]byte) ([]byte, error) { - return h.verifier.Aggregate(pubKeys, sigs) -} - -// VerifyAggregate verifies an aggregate signature. -// Implementations must be safe for concurrent use. -func (h *gpbftHost) VerifyAggregate(payload []byte, aggSig []byte, signers []gpbft.PubKey) error { - return h.verifier.VerifyAggregate(payload, aggSig, signers) +func (h *gpbftHost) Aggregate(pubKeys []gpbft.PubKey) (gpbft.Aggregate, error) { + return h.verifier.Aggregate(pubKeys) } diff --git a/sim/adversary/decide.go b/sim/adversary/decide.go index c9e4a5d0..815aba05 100644 --- a/sim/adversary/decide.go +++ b/sim/adversary/decide.go @@ -97,30 +97,32 @@ func (i *ImmediateDecide) StartInstanceAt(instance uint64, _when time.Time) erro } var ( - pubkeys []gpbft.PubKey - sigs [][]byte + mask []int + sigs [][]byte ) if err := signers.ForEach(func(j uint64) error { - pubkey := gpbft.PubKey("fake pubkeyaaaaa") - sig := []byte("fake sig") - if j < uint64(len(committee.PowerTable.Entries)) { - pubkey = committee.PowerTable.Entries[j].PubKey - var err error - sig, err = i.host.Sign(context.Background(), pubkey, sigPayload) - if err != nil { - return err - } + if j >= uint64(len(committee.PowerTable.Entries)) { + return nil + } + pubkey := committee.PowerTable.Entries[j].PubKey + sig, err := i.host.Sign(context.Background(), pubkey, sigPayload) + if err != nil { + return err } - pubkeys = append(pubkeys, pubkey) + mask = append(mask, int(j)) sigs = append(sigs, sig) return nil }); err != nil { panic(err) } - aggregatedSig, err := i.host.Aggregate(pubkeys, sigs) + agg, err := i.host.Aggregate(committee.PowerTable.Entries.PublicKeys()) + if err != nil { + panic(err) + } + aggregatedSig, err := agg.Aggregate(mask, sigs) if err != nil { panic(err) } diff --git a/sim/adversary/withhold.go b/sim/adversary/withhold.go index 0610b763..211a667f 100644 --- a/sim/adversary/withhold.go +++ b/sim/adversary/withhold.go @@ -106,15 +106,19 @@ func (w *WithholdCommit) StartInstanceAt(instance uint64, _when time.Time) error sort.Ints(signers) signatures := make([][]byte, 0) - pubKeys := make([]gpbft.PubKey, 0) + mask := make([]int, 0) prepareMarshalled := w.host.MarshalPayloadForSigning(w.host.NetworkName(), &preparePayload) for _, signerIndex := range signers { entry := committee.PowerTable.Entries[signerIndex] signatures = append(signatures, w.sign(entry.PubKey, prepareMarshalled)) - pubKeys = append(pubKeys, entry.PubKey) + mask = append(mask, signerIndex) justification.Signers.Set(uint64(signerIndex)) } - justification.Signature, err = w.host.Aggregate(pubKeys, signatures) + agg, err := w.host.Aggregate(committee.PowerTable.Entries.PublicKeys()) + if err != nil { + panic(err) + } + justification.Signature, err = agg.Aggregate(mask, signatures) if err != nil { panic(err) } diff --git a/sim/ec.go b/sim/ec.go index e6b9c81b..3d39453f 100644 --- a/sim/ec.go +++ b/sim/ec.go @@ -36,8 +36,9 @@ type ECInstance struct { // SupplementalData is the additional data for this instance. SupplementalData *gpbft.SupplementalData - ec *simEC - decisions map[gpbft.ActorID]*gpbft.Justification + ec *simEC + decisions map[gpbft.ActorID]*gpbft.Justification + aggregateVerifier gpbft.Aggregate } type errGroup []error @@ -64,6 +65,12 @@ func (ec *simEC) BeginInstance(baseChain gpbft.ECChain, pt *gpbft.PowerTable) *E // Note a real beacon value will come from a finalised chain with some lookback. beacon := baseChain.Head().Key nextInstanceID := uint64(ec.Len()) + + agg, err := ec.verifier.Aggregate(pt.Entries.PublicKeys()) + if err != nil { + panic(err) + } + instance := &ECInstance{ Instance: nextInstanceID, BaseChain: baseChain, @@ -72,8 +79,9 @@ func (ec *simEC) BeginInstance(baseChain gpbft.ECChain, pt *gpbft.PowerTable) *E SupplementalData: &gpbft.SupplementalData{ PowerTable: gpbft.MakeCid([]byte(fmt.Sprintf("supp-data-pt@%d", nextInstanceID))), }, - ec: ec, - decisions: make(map[gpbft.ActorID]*gpbft.Justification), + ec: ec, + aggregateVerifier: agg, + decisions: make(map[gpbft.ActorID]*gpbft.Justification), } ec.instances = append(ec.instances, instance) return instance @@ -123,14 +131,14 @@ func (eci *ECInstance) validateDecision(decision *gpbft.Justification) error { // Extract signers. justificationPower := gpbft.NewStoragePower(0) - signers := make([]gpbft.PubKey, 0) + signers := make([]int, 0) powerTable := eci.PowerTable if err := decision.Signers.ForEach(func(bit uint64) error { if int(bit) >= len(powerTable.Entries) { return fmt.Errorf("invalid signer index: %d", bit) } justificationPower = big.Add(justificationPower, powerTable.Entries[bit].Power) - signers = append(signers, powerTable.Entries[bit].PubKey) + signers = append(signers, int(bit)) return nil }); err != nil { return fmt.Errorf("failed to iterate over signers: %w", err) @@ -144,7 +152,8 @@ func (eci *ECInstance) validateDecision(decision *gpbft.Justification) error { } // Verify aggregate signature payload := eci.ec.verifier.MarshalPayloadForSigning(eci.ec.networkName, &decision.Vote) - if err := eci.ec.verifier.VerifyAggregate(payload, decision.Signature, signers); err != nil { + + if err := eci.aggregateVerifier.VerifyAggregate(signers, payload, decision.Signature); err != nil { return fmt.Errorf("invalid aggregate signature: %v: %w", decision, err) } diff --git a/sim/host.go b/sim/host.go index 75330f19..0cdb983b 100644 --- a/sim/host.go +++ b/sim/host.go @@ -90,7 +90,9 @@ func (v *simHost) GetCommittee(instance uint64) (*gpbft.Committee, error) { return nil, ErrInstanceUnavailable } return &gpbft.Committee{ - PowerTable: i.PowerTable, Beacon: i.Beacon, + PowerTable: i.PowerTable, + Beacon: i.Beacon, + AggregateVerifier: i.aggregateVerifier, }, nil } diff --git a/sim/justification.go b/sim/justification.go index 336c2f02..d75e2f53 100644 --- a/sim/justification.go +++ b/sim/justification.go @@ -67,6 +67,8 @@ func MakeJustification(backend signing.Backend, nn gpbft.NetworkName, chain gpbf slices.SortFunc(votes, func(a, b vote) int { return cmp.Compare(a.index, b.index) }) + signers = signers[:len(votes)] + slices.Sort(signers) pks := make([]gpbft.PubKey, len(votes)) sigs := make([][]byte, len(votes)) for i, vote := range votes { @@ -74,7 +76,12 @@ func MakeJustification(backend signing.Backend, nn gpbft.NetworkName, chain gpbf sigs[i] = vote.sig } - sig, err := backend.Aggregate(pks, sigs) + agg, err := backend.Aggregate(powerTable.PublicKeys()) + if err != nil { + return nil, err + } + + sig, err := agg.Aggregate(signers, sigs) if err != nil { return nil, err } diff --git a/sim/signing/fake.go b/sim/signing/fake.go index c962101e..2d107e89 100644 --- a/sim/signing/fake.go +++ b/sim/signing/fake.go @@ -76,35 +76,18 @@ func (s *FakeBackend) Verify(signer gpbft.PubKey, msg, sig []byte) error { } } -func (*FakeBackend) Aggregate(signers []gpbft.PubKey, sigs [][]byte) ([]byte, error) { - if len(signers) != len(sigs) { - return nil, errors.New("public keys and signatures length mismatch") - } - hasher := sha256.New() - for i, signer := range signers { +func (s *FakeBackend) Aggregate(keys []gpbft.PubKey) (gpbft.Aggregate, error) { + for i, signer := range keys { if len(signer) != 16 { - return nil, fmt.Errorf("wrong signer pubkey length: %d != 16", len(signer)) + return nil, fmt.Errorf("wrong signer %d pubkey length: %d != 16", i, len(signer)) } - hasher.Write(signer) - hasher.Write(sigs[i]) } - return hasher.Sum(nil), nil -} -func (s *FakeBackend) VerifyAggregate(payload, aggSig []byte, signers []gpbft.PubKey) error { - hasher := sha256.New() - for _, signer := range signers { - sig, err := s.generateSignature(signer, payload) - if err != nil { - return err - } - hasher.Write(signer) - hasher.Write(sig) - } - if !bytes.Equal(aggSig, hasher.Sum(nil)) { - return errors.New("signature is not valid") - } - return nil + return &fakeAggregate{ + keys: keys, + backend: s, + }, nil + } func (v *FakeBackend) MarshalPayloadForSigning(nn gpbft.NetworkName, p *gpbft.Payload) []byte { @@ -142,3 +125,44 @@ func (v *FakeBackend) MarshalPayloadForSigning(nn gpbft.NetworkName, p *gpbft.Pa } return buf.Bytes() } + +type fakeAggregate struct { + keys []gpbft.PubKey + backend *FakeBackend +} + +// Aggregate implements gpbft.Aggregate. +func (f *fakeAggregate) Aggregate(signerMask []int, sigs [][]byte) ([]byte, error) { + if len(signerMask) != len(sigs) { + return nil, errors.New("public keys and signatures length mismatch") + } + hasher := sha256.New() + for i, bit := range signerMask { + if bit >= len(f.keys) { + return nil, fmt.Errorf("signer %d out of range", bit) + } + binary.Write(hasher, binary.BigEndian, int64(bit)) + hasher.Write(f.keys[bit]) + hasher.Write(sigs[i]) + } + return hasher.Sum(nil), nil +} + +// VerifyAggregate implements gpbft.Aggregate. +func (f *fakeAggregate) VerifyAggregate(signerMask []int, payload []byte, aggSig []byte) error { + hasher := sha256.New() + for _, bit := range signerMask { + signer := f.keys[bit] + sig, err := f.backend.generateSignature(signer, payload) + if err != nil { + return err + } + binary.Write(hasher, binary.BigEndian, int64(bit)) + hasher.Write(signer) + hasher.Write(sig) + } + if !bytes.Equal(aggSig, hasher.Sum(nil)) { + return errors.New("signature is not valid") + } + return nil +} diff --git a/test/signing_suite_test.go b/test/signing_suite_test.go index 4d5680a3..9d26a3f1 100644 --- a/test/signing_suite_test.go +++ b/test/signing_suite_test.go @@ -90,44 +90,60 @@ func (s *SigningTestSuite) TestAggregateAndVerify() { pubKey2, signer2 := s.signerTestSubject(s.T()) pubKeys := []gpbft.PubKey{pubKey1, pubKey2} + aggregator, err := s.verifier.Aggregate(pubKeys) + require.NoError(s.T(), err) + + mask := []int{0, 1} sigs := make([][]byte, len(pubKeys)) - var err error sigs[0], err = signer1.Sign(ctx, pubKey1, msg) require.NoError(s.T(), err) sigs[1], err = signer2.Sign(ctx, pubKey2, msg) require.NoError(s.T(), err) - aggSig, err := s.verifier.Aggregate(pubKeys, sigs) + aggSig, err := aggregator.Aggregate(mask, sigs) require.NoError(t, err) - err = s.verifier.VerifyAggregate(msg, aggSig, pubKeys) + err = aggregator.VerifyAggregate(mask, msg, aggSig) require.NoError(t, err) - aggSig, err = s.verifier.Aggregate(pubKeys[0:1], sigs[0:1]) + aggSig, err = aggregator.Aggregate(mask[0:1], sigs[0:1]) require.NoError(t, err) - err = s.verifier.VerifyAggregate(msg, aggSig, pubKeys) + err = aggregator.VerifyAggregate(mask, msg, aggSig) require.Error(t, err) - aggSig, err = s.verifier.Aggregate(pubKeys, [][]byte{sigs[0], sigs[0]}) + aggSig, err = aggregator.Aggregate(mask, [][]byte{sigs[0], sigs[0]}) require.NoError(t, err) - err = s.verifier.VerifyAggregate(msg, aggSig, pubKeys) + err = aggregator.VerifyAggregate(mask, msg, aggSig) require.Error(t, err) - err = s.verifier.VerifyAggregate(msg, []byte("bad sig"), pubKeys) + err = aggregator.VerifyAggregate(mask, msg, []byte("bad sig")) require.Error(t, err) - _, err = s.verifier.Aggregate(pubKeys, [][]byte{sigs[0]}) + _, err = aggregator.Aggregate(mask, [][]byte{sigs[0]}) require.Error(t, err, "Missmatched pubkeys and sigs lengths should fail") { pubKeys2 := slices.Clone(pubKeys) - pubKeys2[0] = slices.Clone(pubKeys2[0]) - pubKeys2[0] = pubKeys2[0][1:len(pubKeys2)] - _, err = s.verifier.Aggregate(pubKeys2, sigs) - require.Error(t, err, "damaged pubkey should error") + pubKey3, _ := s.signerTestSubject(s.T()) + pubKeys2[0] = pubKey3 + wrongKeyAggregator, err := s.verifier.Aggregate(pubKeys2) + require.NoError(t, err) - require.Error(t, s.verifier.VerifyAggregate(msg, aggSig, pubKeys2), "damaged pubkey should error") + require.Error(t, wrongKeyAggregator.VerifyAggregate(mask, msg, aggSig), "wrong pubkey should error") } + + t.Run("mask out of range", func(t *testing.T) { + _, err = aggregator.Aggregate([]int{0, 3}, [][]byte{sigs[0]}) + require.Error(t, err, "mask out of range") + }) + + t.Run("empty signature is always valid", func(t *testing.T) { + sig, err := aggregator.Aggregate([]int{}, [][]byte{}) + require.NoError(t, err) + + err = aggregator.VerifyAggregate([]int{}, []byte("anything"), sig) + require.NoError(t, err) + }) }