diff --git a/Makefile b/Makefile index 5293d7c97c..a5975b5d39 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ GOLDFLAGS := $(GOLDFLAGS_BASE) \ UNIT_TEST_SOURCES := $(sort $(shell GO111MODULE=off go list ./... | grep -v /go-algorand/test/ )) ALGOD_API_PACKAGES := $(sort $(shell GO111MODULE=off cd daemon/algod/api; go list ./... )) -MSGP_GENERATE := ./protocol ./crypto ./data/basics ./data/transactions ./data/committee ./data/bookkeeping ./data/hashable ./auction ./agreement ./rpcs ./node ./ledger +MSGP_GENERATE := ./protocol ./crypto ./crypto/compactcert ./data/basics ./data/transactions ./data/committee ./data/bookkeeping ./data/hashable ./auction ./agreement ./rpcs ./node ./ledger default: build diff --git a/crypto/compactcert/bigfloat.go b/crypto/compactcert/bigfloat.go new file mode 100644 index 0000000000..a11aabf9de --- /dev/null +++ b/crypto/compactcert/bigfloat.go @@ -0,0 +1,186 @@ +// Copyright (C) 2019-2020 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package compactcert + +import ( + "fmt" + "math/bits" +) + +// A bigFloat represents the number mantissa*2^exp, which must be non-zero. +// +// A canonical representation is one where the highest bit of mantissa is +// set. Every operation enforces canonicality of results. +// +// We use 32-bit values here to avoid requiring a 64bit-by-64bit-to-128bit +// multiply operation for anyone that needs to implement this (even though +// Go has this operation, as bits.Mul64). +type bigFloat struct { + mantissa uint32 + exp int32 +} + +// Each bigFloat is associated with a rounding mode (up, away from zero, or +// down, towards zero). This is reflected by these two types of bigFloat. +type bigFloatUp struct { + bigFloat +} + +type bigFloatDn struct { + bigFloat +} + +// canonicalize() ensures that the bigFloat is canonical. +func (a *bigFloat) canonicalize() { + if a.mantissa == 0 { + // Just to avoid infinite loops in some error case. + return + } + + for (a.mantissa & (1 << 31)) == 0 { + a.mantissa = a.mantissa << 1 + a.exp = a.exp - 1 + } +} + +// doRoundUp adds one to the mantissa of a canonical bigFloat +// to implement the rounding-up when there are leftover low bits. +func (a *bigFloatUp) doRoundUp() { + if a.mantissa == (1<<32)-1 { + a.mantissa = 1 << 31 + a.exp++ + } else { + a.mantissa++ + } +} + +// geRaw returns whether a>=b. The Raw suffix indicates that +// this comparison does not take rounding into account, and might +// not be true if done with arbitrary-precision numbers. +func (a *bigFloat) geRaw(b *bigFloat) bool { + if a.exp > b.exp { + return true + } + + if a.exp < b.exp { + return false + } + + return a.mantissa >= b.mantissa +} + +// ge returns whether a>=b. It requires that a was computed with +// rounding-down and b was computed with rounding-up, so that if +// ge returns true, the arbitrary-precision computation would have +// also been >=. +func (a *bigFloatDn) ge(b *bigFloatUp) bool { + return a.geRaw(&b.bigFloat) +} + +// setu64Dn sets the value to the supplied uint64 (which might get +// rounded down in the process). x must not be zero. truncated +// returns whether any non-zero bits were truncated (rounded down). +func (a *bigFloat) setu64Dn(x uint64) (truncated bool, err error) { + if x == 0 { + return false, fmt.Errorf("bigFloat cannot be zero") + } + + e := int32(0) + + for x >= (1 << 32) { + if (x & 1) != 0 { + truncated = true + } + + x = x >> 1 + e = e + 1 + } + + a.mantissa = uint32(x) + a.exp = e + a.canonicalize() + return +} + +// setu64 calls setu64Dn and implements rounding based on the type. +func (a *bigFloatUp) setu64(x uint64) error { + truncated, err := a.setu64Dn(x) + if truncated { + a.doRoundUp() + } + return err +} + +func (a *bigFloatDn) setu64(x uint64) error { + _, err := a.setu64Dn(x) + return err +} + +// setu32 sets the value to the supplied uint32. +func (a *bigFloat) setu32(x uint32) error { + if x == 0 { + return fmt.Errorf("bigFloat cannot be zero") + } + + a.mantissa = x + a.exp = 0 + a.canonicalize() + return nil +} + +// setpow2 sets the value to 2^x. +func (a *bigFloat) setpow2(x int32) { + a.mantissa = 1 + a.exp = x + a.canonicalize() +} + +// mulDn sets a to the product a*b, keeping the most significant 32 bits +// of the product's mantissa. The return value indicates if any non-zero +// bits were discarded (rounded down). +func (a *bigFloat) mulDn(b *bigFloat) bool { + hi, lo := bits.Mul32(a.mantissa, b.mantissa) + + a.mantissa = hi + a.exp = a.exp + b.exp + 32 + + if (a.mantissa & (1 << 31)) == 0 { + a.mantissa = (a.mantissa << 1) | (lo >> 31) + a.exp = a.exp - 1 + lo = lo << 1 + } + + return lo != 0 +} + +// mul calls mulDn and implements appropriate rounding. +// Types prevent multiplying two values with different rounding types. +func (a *bigFloatUp) mul(b *bigFloatUp) { + truncated := a.mulDn(&b.bigFloat) + if truncated { + a.doRoundUp() + } +} + +func (a *bigFloatDn) mul(b *bigFloatDn) { + a.mulDn(&b.bigFloat) +} + +// String returns a string representation of a. +func (a *bigFloat) String() string { + return fmt.Sprintf("%d*2^%d", a.mantissa, a.exp) +} diff --git a/crypto/compactcert/bigfloat_test.go b/crypto/compactcert/bigfloat_test.go new file mode 100644 index 0000000000..c7c78e5749 --- /dev/null +++ b/crypto/compactcert/bigfloat_test.go @@ -0,0 +1,162 @@ +// Copyright (C) 2019-2020 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package compactcert + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/crypto" +) + +func rand32() uint32 { + return uint32(crypto.RandUint64() & 0xffffffff) +} + +func TestBigFloatRounding(t *testing.T) { + a := &bigFloatDn{} + b := &bigFloatUp{} + + a.setu64(1 << 63) + b.setu64(1 << 63) + + require.True(t, a.geRaw(&b.bigFloat)) + require.True(t, b.geRaw(&a.bigFloat)) + + a.mul(a) + b.mul(b) + + require.True(t, a.geRaw(&b.bigFloat)) + require.True(t, b.geRaw(&a.bigFloat)) + + a.setu64((1 << 64) - 1) + b.setu64((1 << 64) - 1) + + require.False(t, a.geRaw(&b.bigFloat)) + require.True(t, b.geRaw(&a.bigFloat)) + + a.setu32((1 << 32) - 1) + b.setu32((1 << 32) - 1) + + a.mul(a) + b.mul(b) + + require.False(t, a.geRaw(&b.bigFloat)) + require.True(t, b.geRaw(&a.bigFloat)) +} + +func TestBigFloat(t *testing.T) { + a := &bigFloatDn{} + b := &bigFloatDn{} + + a.setu64(1) + require.Equal(t, a.mantissa, uint32(1<<31)) + require.Equal(t, a.exp, int32(-31)) + + a.setu32(1) + require.Equal(t, a.mantissa, uint32(1<<31)) + require.Equal(t, a.exp, int32(-31)) + + for i := int32(-256); i < 256; i++ { + a.setpow2(i) + require.Equal(t, a.mantissa, uint32(1<<31)) + require.Equal(t, a.exp, i-31) + } + + for i := 0; i < 8192; i++ { + x := rand32() + a.setu32(x) + require.True(t, a.exp <= 0) + require.Equal(t, x, a.mantissa>>(-a.exp)) + } + + for i := 0; i < 8192; i++ { + x := uint64(rand32()) + a.setu64(x) + if a.exp <= 0 { + require.Equal(t, x, uint64(a.mantissa>>(-a.exp))) + } + if a.exp >= 0 { + require.Equal(t, x>>a.exp, uint64(a.mantissa)) + } + } + + for i := 0; i < 8192; i++ { + x := crypto.RandUint64() + a.setu64(x) + if a.exp <= 0 { + require.Equal(t, x, uint64(a.mantissa>>(-a.exp))) + } + if a.exp >= 0 { + require.Equal(t, x>>a.exp, uint64(a.mantissa)) + } + } + + for i := 0; i < 8192; i++ { + x := rand32() + y := rand32() + a.setu64(uint64(x)) + b.setu64(uint64(y)) + + require.Equal(t, x >= y, a.geRaw(&b.bigFloat)) + require.Equal(t, x < y, b.geRaw(&a.bigFloat)) + require.True(t, a.geRaw(&a.bigFloat)) + require.True(t, b.geRaw(&b.bigFloat)) + } + + xx := &big.Int{} + yy := &big.Int{} + + for i := 0; i < 8192; i++ { + x := rand32() + y := rand32() + a.setu64(uint64(x)) + b.setu64(uint64(y)) + a.mul(b) + + xx.SetUint64(uint64(x)) + yy.SetUint64(uint64(y)) + xx.Mul(xx, yy) + if a.exp > 0 { + xx.Rsh(xx, uint(a.exp)) + } + if a.exp < 0 { + xx.Lsh(xx, uint(-a.exp)) + } + require.Equal(t, a.mantissa, uint32(xx.Uint64())) + } +} + +func BenchmarkBigFloatMulUp(b *testing.B) { + a := &bigFloatUp{} + a.setu32((1 << 32) - 1) + + for i := 0; i < b.N; i++ { + a.mul(a) + } +} + +func BenchmarkBigFloatMulDn(b *testing.B) { + a := &bigFloatDn{} + a.setu32((1 << 32) - 1) + + for i := 0; i < b.N; i++ { + a.mul(a) + } +} diff --git a/crypto/compactcert/builder.go b/crypto/compactcert/builder.go new file mode 100644 index 0000000000..a735627f4f --- /dev/null +++ b/crypto/compactcert/builder.go @@ -0,0 +1,246 @@ +// Copyright (C) 2019-2020 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package compactcert + +import ( + "fmt" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/data/basics" +) + +//msgp:ignore sigslot +type sigslot struct { + // Weight is the weight of the participant signing this message. + // This information is tracked here for convenience, but it does + // not appear in the commitment to the sigs array; it comes from + // the Weight field of the corresponding participant. + Weight uint64 + + // Include the parts of the sigslot that form the commitment to + // the sigs array. + sigslotCommit +} + +// Builder keeps track of signatures on a message and eventually produces +// a compact certificate for that message. +type Builder struct { + Params + + sigs []sigslot // Indexed by pos in participants + sigsHasValidL bool // The L values in sigs are consistent with weights + signedWeight uint64 // Total weight of signatures so far + participants []Participant + parttree *merklearray.Tree + + // Cached cert, if Build() was called and no subsequent + // Add() calls were made. + cert *Cert +} + +// MkBuilder constructs an empty builder (with no signatures). The message +// to be signed, as well as other security parameters, are specified in +// param. The participants that will sign the message are in part and +// parttree. +func MkBuilder(param Params, part []Participant, parttree *merklearray.Tree) (*Builder, error) { + npart := len(part) + + b := &Builder{ + Params: param, + sigs: make([]sigslot, npart), + sigsHasValidL: false, + signedWeight: 0, + participants: part, + parttree: parttree, + } + + return b, nil +} + +// Present checks if the builder already contains a signature at a particular +// offset. +func (b *Builder) Present(pos uint64) bool { + return b.sigs[pos].Weight != 0 +} + +// Add a signature to the set of signatures available for building a certificate. +// verifySig should be set to true in production; setting it to false is useful +// for benchmarking to avoid the cost of signature checks. +func (b *Builder) Add(pos uint64, sig crypto.OneTimeSignature, verifySig bool) error { + if b.Present(pos) { + return fmt.Errorf("position %d already added", pos) + } + + // Check participants array + if pos >= uint64(len(b.participants)) { + return fmt.Errorf("pos %d >= len(participants) %d", pos, len(b.participants)) + } + + p := b.participants[pos] + + if p.Weight == 0 { + return fmt.Errorf("position %d has zero weight", pos) + } + + // Check signature + ephID := basics.OneTimeIDForRound(b.SigRound, p.KeyDilution) + if verifySig && !p.PK.Verify(ephID, b.Msg, sig) { + return fmt.Errorf("signature does not verify under ID %v", ephID) + } + + // Remember the signature + b.sigs[pos].Weight = p.Weight + b.sigs[pos].Sig.OneTimeSignature = sig + b.signedWeight += p.Weight + b.cert = nil + b.sigsHasValidL = false + return nil +} + +// Ready returns whether the certificate is ready to be built. +func (b *Builder) Ready() bool { + return b.signedWeight > b.Params.ProvenWeight +} + +// SignedWeight returns the total weight of signatures added so far. +func (b *Builder) SignedWeight() uint64 { + return b.signedWeight +} + +//msgp:ignore sigsToCommit +type sigsToCommit []sigslot + +func (sc sigsToCommit) Length() uint64 { + return uint64(len(sc)) +} + +func (sc sigsToCommit) Get(pos uint64) (crypto.Hashable, error) { + if pos >= uint64(len(sc)) { + return nil, fmt.Errorf("pos %d past end %d", pos, len(sc)) + } + + return &sc[pos].sigslotCommit, nil +} + +// coinIndex returns the position pos in the sigs array such that the sum +// of all signature weights before pos is less than or equal to coinWeight, +// but the sum of all signature weights up to and including pos exceeds +// coinWeight. +// +// coinIndex works by doing a binary search on the sigs array. +func (b *Builder) coinIndex(coinWeight uint64) (uint64, error) { + if !b.sigsHasValidL { + return 0, fmt.Errorf("coinIndex: need valid L values") + } + + lo := uint64(0) + hi := uint64(len(b.sigs)) + +again: + if lo >= hi { + return 0, fmt.Errorf("coinIndex: lo %d >= hi %d", lo, hi) + } + + mid := (lo + hi) / 2 + if coinWeight < b.sigs[mid].L { + hi = mid + goto again + } + + if coinWeight < b.sigs[mid].L+b.sigs[mid].Weight { + return mid, nil + } + + lo = mid + 1 + goto again +} + +// Build returns a compact certificate, if the builder has accumulated +// enough signatures to construct it. +func (b *Builder) Build() (*Cert, error) { + if b.cert != nil { + return b.cert, nil + } + + if b.signedWeight <= b.Params.ProvenWeight { + return nil, fmt.Errorf("not enough signed weight: %d <= %d", b.signedWeight, b.Params.ProvenWeight) + } + + // Commit to the sigs array + for i := 1; i < len(b.sigs); i++ { + b.sigs[i].L = b.sigs[i-1].L + b.sigs[i-1].Weight + } + b.sigsHasValidL = true + + sigtree, err := merklearray.Build(sigsToCommit(b.sigs)) + if err != nil { + return nil, err + } + + // Reveal sufficient number of signatures + c := &Cert{ + SigCommit: sigtree.Root(), + SignedWeight: b.signedWeight, + Reveals: make(map[uint64]Reveal), + } + + nr, err := b.numReveals(b.signedWeight) + if err != nil { + return nil, err + } + + var proofPositions []uint64 + + for j := uint64(0); j < nr; j++ { + coin := hashCoin(j, c.SigCommit, c.SignedWeight) + pos, err := b.coinIndex(coin) + if err != nil { + return nil, err + } + + if pos >= uint64(len(b.participants)) { + return nil, fmt.Errorf("pos %d >= len(participants) %d", pos, len(b.participants)) + } + + // If we already revealed pos, no need to do it again + _, alreadyRevealed := c.Reveals[pos] + if alreadyRevealed { + continue + } + + // Generate the reveal for pos + c.Reveals[pos] = Reveal{ + SigSlot: b.sigs[pos].sigslotCommit, + Part: b.participants[pos], + } + + proofPositions = append(proofPositions, pos) + } + + c.SigProofs, err = sigtree.Prove(proofPositions) + if err != nil { + return nil, err + } + + c.PartProofs, err = b.parttree.Prove(proofPositions) + if err != nil { + return nil, err + } + + return c, nil +} diff --git a/crypto/compactcert/builder_test.go b/crypto/compactcert/builder_test.go new file mode 100644 index 0000000000..82b01de827 --- /dev/null +++ b/crypto/compactcert/builder_test.go @@ -0,0 +1,239 @@ +// Copyright (C) 2019-2020 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package compactcert + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/protocol" + + "github.com/stretchr/testify/require" +) + +type TestMessage string + +func (m TestMessage) ToBeHashed() (protocol.HashID, []byte) { + return protocol.Message, []byte(m) +} + +type PartCommit struct { + participants []Participant +} + +func (pc PartCommit) Length() uint64 { + return uint64(len(pc.participants)) +} + +func (pc PartCommit) Get(pos uint64) (crypto.Hashable, error) { + if pos >= uint64(len(pc.participants)) { + return nil, fmt.Errorf("pos %d >= len %d", pos, len(pc.participants)) + } + + return pc.participants[pos], nil +} + +func TestBuildVerify(t *testing.T) { + // Doing a full test of 1M accounts takes too much CPU time in CI. + doLargeTest := false + + totalWeight := 10000000 + npartHi := 10 + npartLo := 9990 + + if doLargeTest { + npartHi *= 100 + npartLo *= 100 + } + + npart := npartHi + npartLo + + param := Params{ + Msg: TestMessage("hello world"), + ProvenWeight: uint64(totalWeight / 2), + SigRound: 0, + SecKQ: 128, + } + + // Share the key; we allow the same vote key to appear in multiple accounts.. + key := crypto.GenerateOneTimeSignatureSecrets(0, 1) + + var parts []Participant + var sigs []crypto.OneTimeSignature + for i := 0; i < npartHi; i++ { + part := Participant{ + PK: key.OneTimeSignatureVerifier, + Weight: uint64(totalWeight / 2 / npartHi), + KeyDilution: 10000, + } + + parts = append(parts, part) + } + + for i := 0; i < npartLo; i++ { + part := Participant{ + PK: key.OneTimeSignatureVerifier, + Weight: uint64(totalWeight / 2 / npartLo), + KeyDilution: 10000, + } + + parts = append(parts, part) + } + + ephID := basics.OneTimeIDForRound(0, parts[0].KeyDilution) + sig := key.Sign(ephID, param.Msg) + + for i := 0; i < npart; i++ { + sigs = append(sigs, sig) + } + + partcom, err := merklearray.Build(PartCommit{parts}) + if err != nil { + t.Error(err) + } + + b, err := MkBuilder(param, parts, partcom) + if err != nil { + t.Error(err) + } + + for i := 0; i < npart; i++ { + err = b.Add(uint64(i), sigs[i], !doLargeTest) + if err != nil { + t.Error(err) + } + } + + cert, err := b.Build() + if err != nil { + t.Error(err) + } + + var someReveal Reveal + for _, rev := range cert.Reveals { + someReveal = rev + break + } + + certenc := protocol.Encode(cert) + fmt.Printf("Cert size:\n") + fmt.Printf(" %6d elems sigproofs\n", len(cert.SigProofs)) + fmt.Printf(" %6d bytes sigproofs\n", len(protocol.EncodeReflect(cert.SigProofs))) + fmt.Printf(" %6d bytes partproofs\n", len(protocol.EncodeReflect(cert.PartProofs))) + fmt.Printf(" %6d bytes sigproof per reveal\n", len(protocol.EncodeReflect(cert.SigProofs))/len(cert.Reveals)) + fmt.Printf(" %6d reveals:\n", len(cert.Reveals)) + fmt.Printf(" %6d bytes reveals[*] participant\n", len(protocol.Encode(&someReveal.Part))) + fmt.Printf(" %6d bytes reveals[*] sigslot\n", len(protocol.Encode(&someReveal.SigSlot))) + fmt.Printf(" %6d bytes reveals[*] total\n", len(protocol.Encode(&someReveal))) + fmt.Printf(" %6d bytes total\n", len(certenc)) + + verif := MkVerifier(param, partcom.Root()) + err = verif.Verify(cert) + if err != nil { + t.Error(err) + } +} + +func BenchmarkBuildVerify(b *testing.B) { + totalWeight := 1000000 + npart := 10000 + + param := Params{ + Msg: TestMessage("hello world"), + ProvenWeight: uint64(totalWeight / 2), + SigRound: 0, + SecKQ: 128, + } + + var parts []Participant + var partkeys []*crypto.OneTimeSignatureSecrets + var sigs []crypto.OneTimeSignature + for i := 0; i < npart; i++ { + key := crypto.GenerateOneTimeSignatureSecrets(0, 1) + part := Participant{ + PK: key.OneTimeSignatureVerifier, + Weight: uint64(totalWeight / npart), + KeyDilution: 10000, + } + + ephID := basics.OneTimeIDForRound(0, part.KeyDilution) + sig := key.Sign(ephID, param.Msg) + + partkeys = append(partkeys, key) + sigs = append(sigs, sig) + parts = append(parts, part) + } + + var cert *Cert + partcom, err := merklearray.Build(PartCommit{parts}) + if err != nil { + b.Error(err) + } + + b.Run("AddBuild", func(b *testing.B) { + for i := 0; i < b.N; i++ { + builder, err := MkBuilder(param, parts, partcom) + if err != nil { + b.Error(err) + } + + for i := 0; i < npart; i++ { + err = builder.Add(uint64(i), sigs[i], true) + if err != nil { + b.Error(err) + } + } + + cert, err = builder.Build() + if err != nil { + b.Error(err) + } + } + }) + + b.Run("Verify", func(b *testing.B) { + for i := 0; i < b.N; i++ { + verif := MkVerifier(param, partcom.Root()) + err = verif.Verify(cert) + if err != nil { + b.Error(err) + } + } + }) +} + +func TestCoinIndex(t *testing.T) { + n := 1000 + b := &Builder{ + sigs: make([]sigslot, n), + sigsHasValidL: true, + } + + for i := 0; i < n; i++ { + b.sigs[i].L = uint64(i) + b.sigs[i].Weight = 1 + } + + for i := 0; i < n; i++ { + pos, err := b.coinIndex(uint64(i)) + require.NoError(t, err) + require.Equal(t, pos, uint64(i)) + } +} diff --git a/crypto/compactcert/common.go b/crypto/compactcert/common.go new file mode 100644 index 0000000000..604dbcf475 --- /dev/null +++ b/crypto/compactcert/common.go @@ -0,0 +1,103 @@ +// Copyright (C) 2019-2020 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package compactcert + +import ( + "encoding/binary" + "fmt" + "math/big" + + "github.com/algorand/go-algorand/crypto" +) + +// hashCoin returns a number in [0, signedWeight) with a nearly uniform +// distribution, "randomized" by all of the supplied arguments. +func hashCoin(j uint64, sigcom crypto.Digest, signedWeight uint64) uint64 { + hashinput := make([]byte, 16+crypto.DigestSize) + binary.LittleEndian.PutUint64(hashinput[0:], j) + binary.LittleEndian.PutUint64(hashinput[8:], signedWeight) + copy(hashinput[16:], sigcom[:]) + h := crypto.Hash(hashinput) + + i := &big.Int{} + i.SetBytes(h[:]) + + w := &big.Int{} + w.SetUint64(signedWeight) + + res := &big.Int{} + res.Mod(i, w) + return res.Uint64() +} + +// numReveals computes the number of reveals necessary to achieve the desired +// security parameters. See section 8 of the ``Compact Certificates'' +// document for the analysis. +// +// numReveals is the smallest number that satisfies +// +// 2^-k >= 2^q * (provenWeight / signedWeight) ^ numReveals +// +// which is equivalent to the following: +// +// signedWeight ^ numReveals >= 2^(k+q) * provenWeight ^ numReveals +// +// To ensure that rounding errors do not reduce the security parameter, +// we compute the left-hand side with rounding-down, and compute the +// right-hand side with rounding-up. +func numReveals(signedWeight uint64, provenWeight uint64, secKQ uint64, bound uint64) (uint64, error) { + n := uint64(0) + + sw := &bigFloatDn{} + err := sw.setu64(signedWeight) + if err != nil { + return 0, err + } + + pw := &bigFloatUp{} + err = pw.setu64(provenWeight) + if err != nil { + return 0, err + } + + lhs := &bigFloatDn{} + err = lhs.setu64(1) + if err != nil { + return 0, err + } + + rhs := &bigFloatUp{} + rhs.setpow2(int32(secKQ)) + + for { + if lhs.ge(rhs) { + return n, nil + } + + if n >= bound { + return 0, fmt.Errorf("numReveals(%d, %d, %d) > %d", signedWeight, provenWeight, secKQ, bound) + } + + lhs.mul(sw) + rhs.mul(pw) + n++ + } +} + +func (p Params) numReveals(signedWeight uint64) (uint64, error) { + return numReveals(signedWeight, p.ProvenWeight, p.SecKQ, maxReveals) +} diff --git a/crypto/compactcert/common_test.go b/crypto/compactcert/common_test.go new file mode 100644 index 0000000000..aaefd10512 --- /dev/null +++ b/crypto/compactcert/common_test.go @@ -0,0 +1,99 @@ +// Copyright (C) 2019-2020 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package compactcert + +import ( + "testing" + + "github.com/algorand/go-algorand/crypto" +) + +func TestHashCoin(t *testing.T) { + var slots [32]uint64 + var sigcom [32]byte + + crypto.RandBytes(sigcom[:]) + + for j := uint64(0); j < 1000; j++ { + coin := hashCoin(j, sigcom, uint64(len(slots))) + if coin >= uint64(len(slots)) { + t.Errorf("hashCoin out of bounds") + } + + slots[coin]++ + } + + for i, count := range slots { + if count < 3 { + t.Errorf("slot %d too low: %d", i, count) + } + if count > 100 { + t.Errorf("slot %d too high: %d", i, count) + } + } +} + +func BenchmarkHashCoin(b *testing.B) { + var sigcom [32]byte + crypto.RandBytes(sigcom[:]) + + for i := 0; i < b.N; i++ { + hashCoin(uint64(i), sigcom, 1024) + } +} + +func TestNumReveals(t *testing.T) { + billion := uint64(1000 * 1000 * 1000) + microalgo := uint64(1000 * 1000) + provenWeight := 2 * billion * microalgo + secKQ := uint64(128) + bound := uint64(1000) + + for i := uint64(3); i < 10; i++ { + signedWeight := i * billion * microalgo + n, err := numReveals(signedWeight, provenWeight, secKQ, bound) + if err != nil { + t.Error(err) + } + + if n < 50 || n > 300 { + t.Errorf("numReveals(%d, %d, %d) = %d looks suspect", + signedWeight, provenWeight, secKQ, n) + } + } +} + +func BenchmarkNumReveals(b *testing.B) { + billion := uint64(1000 * 1000 * 1000) + microalgo := uint64(1000 * 1000) + provenWeight := 100 * billion * microalgo + signedWeight := 110 * billion * microalgo + secKQ := uint64(128) + bound := uint64(1000) + + nr, err := numReveals(signedWeight, provenWeight, secKQ, bound) + if nr < 900 { + b.Errorf("numReveals(%d, %d, %d) = %d < 900", signedWeight, provenWeight, secKQ, nr) + } + + for i := 0; i < b.N; i++ { + _, err = numReveals(signedWeight, provenWeight, secKQ, bound) + if err != nil { + b.Error(err) + } + } +} diff --git a/crypto/compactcert/msgp_gen.go b/crypto/compactcert/msgp_gen.go new file mode 100644 index 0000000000..4c12f3d9a9 --- /dev/null +++ b/crypto/compactcert/msgp_gen.go @@ -0,0 +1,1271 @@ +package compactcert + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +import ( + "sort" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/msgp/msgp" +) + +// The following msgp objects are implemented in this file: +// Cert +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// +// CompactOneTimeSignature +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// +// Participant +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// +// Reveal +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// +// sigslotCommit +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// + +// MarshalMsg implements msgp.Marshaler +func (z *Cert) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0005Len := uint32(5) + var zb0005Mask uint8 /* 6 bits */ + if len((*z).PartProofs) == 0 { + zb0005Len-- + zb0005Mask |= 0x1 + } + if len((*z).SigProofs) == 0 { + zb0005Len-- + zb0005Mask |= 0x2 + } + if (*z).SigCommit.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x8 + } + if len((*z).Reveals) == 0 { + zb0005Len-- + zb0005Mask |= 0x10 + } + if (*z).SignedWeight == 0 { + zb0005Len-- + zb0005Mask |= 0x20 + } + // variable map header, size zb0005Len + o = append(o, 0x80|uint8(zb0005Len)) + if zb0005Len != 0 { + if (zb0005Mask & 0x1) == 0 { // if not empty + // string "P" + o = append(o, 0xa1, 0x50) + if (*z).PartProofs == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).PartProofs))) + } + for zb0002 := range (*z).PartProofs { + o, err = (*z).PartProofs[zb0002].MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "PartProofs", zb0002) + return + } + } + } + if (zb0005Mask & 0x2) == 0 { // if not empty + // string "S" + o = append(o, 0xa1, 0x53) + if (*z).SigProofs == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).SigProofs))) + } + for zb0001 := range (*z).SigProofs { + o, err = (*z).SigProofs[zb0001].MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "SigProofs", zb0001) + return + } + } + } + if (zb0005Mask & 0x8) == 0 { // if not empty + // string "c" + o = append(o, 0xa1, 0x63) + o, err = (*z).SigCommit.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "SigCommit") + return + } + } + if (zb0005Mask & 0x10) == 0 { // if not empty + // string "r" + o = append(o, 0xa1, 0x72) + if (*z).Reveals == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).Reveals))) + } + zb0003_keys := make([]uint64, 0, len((*z).Reveals)) + for zb0003 := range (*z).Reveals { + zb0003_keys = append(zb0003_keys, zb0003) + } + sort.Sort(SortUint64(zb0003_keys)) + for _, zb0003 := range zb0003_keys { + zb0004 := (*z).Reveals[zb0003] + _ = zb0004 + o = msgp.AppendUint64(o, zb0003) + o, err = zb0004.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "Reveals", zb0003) + return + } + } + } + if (zb0005Mask & 0x20) == 0 { // if not empty + // string "w" + o = append(o, 0xa1, 0x77) + o = msgp.AppendUint64(o, (*z).SignedWeight) + } + } + return +} + +func (_ *Cert) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*Cert) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Cert) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).SigCommit.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigCommit") + return + } + } + if zb0005 > 0 { + zb0005-- + (*z).SignedWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SignedWeight") + return + } + } + if zb0005 > 0 { + zb0005-- + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigProofs") + return + } + if zb0007 > maxProofDigests { + err = msgp.ErrOverflow(uint64(zb0007), uint64(maxProofDigests)) + err = msgp.WrapError(err, "struct-from-array", "SigProofs") + return + } + if zb0008 { + (*z).SigProofs = nil + } else if (*z).SigProofs != nil && cap((*z).SigProofs) >= zb0007 { + (*z).SigProofs = ((*z).SigProofs)[:zb0007] + } else { + (*z).SigProofs = make([]crypto.Digest, zb0007) + } + for zb0001 := range (*z).SigProofs { + bts, err = (*z).SigProofs[zb0001].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigProofs", zb0001) + return + } + } + } + if zb0005 > 0 { + zb0005-- + var zb0009 int + var zb0010 bool + zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PartProofs") + return + } + if zb0009 > maxProofDigests { + err = msgp.ErrOverflow(uint64(zb0009), uint64(maxProofDigests)) + err = msgp.WrapError(err, "struct-from-array", "PartProofs") + return + } + if zb0010 { + (*z).PartProofs = nil + } else if (*z).PartProofs != nil && cap((*z).PartProofs) >= zb0009 { + (*z).PartProofs = ((*z).PartProofs)[:zb0009] + } else { + (*z).PartProofs = make([]crypto.Digest, zb0009) + } + for zb0002 := range (*z).PartProofs { + bts, err = (*z).PartProofs[zb0002].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PartProofs", zb0002) + return + } + } + } + if zb0005 > 0 { + zb0005-- + var zb0011 int + var zb0012 bool + zb0011, zb0012, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Reveals") + return + } + if zb0011 > maxReveals { + err = msgp.ErrOverflow(uint64(zb0011), uint64(maxReveals)) + err = msgp.WrapError(err, "struct-from-array", "Reveals") + return + } + if zb0012 { + (*z).Reveals = nil + } else if (*z).Reveals == nil { + (*z).Reveals = make(map[uint64]Reveal, zb0011) + } + for zb0011 > 0 { + var zb0003 uint64 + var zb0004 Reveal + zb0011-- + zb0003, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Reveals") + return + } + bts, err = zb0004.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Reveals", zb0003) + return + } + (*z).Reveals[zb0003] = zb0004 + } + } + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0006 { + (*z) = Cert{} + } + for zb0005 > 0 { + zb0005-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "c": + bts, err = (*z).SigCommit.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "SigCommit") + return + } + case "w": + (*z).SignedWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "SignedWeight") + return + } + case "S": + var zb0013 int + var zb0014 bool + zb0013, zb0014, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "SigProofs") + return + } + if zb0013 > maxProofDigests { + err = msgp.ErrOverflow(uint64(zb0013), uint64(maxProofDigests)) + err = msgp.WrapError(err, "SigProofs") + return + } + if zb0014 { + (*z).SigProofs = nil + } else if (*z).SigProofs != nil && cap((*z).SigProofs) >= zb0013 { + (*z).SigProofs = ((*z).SigProofs)[:zb0013] + } else { + (*z).SigProofs = make([]crypto.Digest, zb0013) + } + for zb0001 := range (*z).SigProofs { + bts, err = (*z).SigProofs[zb0001].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "SigProofs", zb0001) + return + } + } + case "P": + var zb0015 int + var zb0016 bool + zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "PartProofs") + return + } + if zb0015 > maxProofDigests { + err = msgp.ErrOverflow(uint64(zb0015), uint64(maxProofDigests)) + err = msgp.WrapError(err, "PartProofs") + return + } + if zb0016 { + (*z).PartProofs = nil + } else if (*z).PartProofs != nil && cap((*z).PartProofs) >= zb0015 { + (*z).PartProofs = ((*z).PartProofs)[:zb0015] + } else { + (*z).PartProofs = make([]crypto.Digest, zb0015) + } + for zb0002 := range (*z).PartProofs { + bts, err = (*z).PartProofs[zb0002].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "PartProofs", zb0002) + return + } + } + case "r": + var zb0017 int + var zb0018 bool + zb0017, zb0018, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Reveals") + return + } + if zb0017 > maxReveals { + err = msgp.ErrOverflow(uint64(zb0017), uint64(maxReveals)) + err = msgp.WrapError(err, "Reveals") + return + } + if zb0018 { + (*z).Reveals = nil + } else if (*z).Reveals == nil { + (*z).Reveals = make(map[uint64]Reveal, zb0017) + } + for zb0017 > 0 { + var zb0003 uint64 + var zb0004 Reveal + zb0017-- + zb0003, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Reveals") + return + } + bts, err = zb0004.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Reveals", zb0003) + return + } + (*z).Reveals[zb0003] = zb0004 + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *Cert) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Cert) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Cert) Msgsize() (s int) { + s = 1 + 2 + (*z).SigCommit.Msgsize() + 2 + msgp.Uint64Size + 2 + msgp.ArrayHeaderSize + for zb0001 := range (*z).SigProofs { + s += (*z).SigProofs[zb0001].Msgsize() + } + s += 2 + msgp.ArrayHeaderSize + for zb0002 := range (*z).PartProofs { + s += (*z).PartProofs[zb0002].Msgsize() + } + s += 2 + msgp.MapHeaderSize + if (*z).Reveals != nil { + for zb0003, zb0004 := range (*z).Reveals { + _ = zb0003 + _ = zb0004 + s += 0 + msgp.Uint64Size + zb0004.Msgsize() + } + } + return +} + +// MsgIsZero returns whether this is a zero value +func (z *Cert) MsgIsZero() bool { + return ((*z).SigCommit.MsgIsZero()) && ((*z).SignedWeight == 0) && (len((*z).SigProofs) == 0) && (len((*z).PartProofs) == 0) && (len((*z).Reveals) == 0) +} + +// MarshalMsg implements msgp.Marshaler +func (z *CompactOneTimeSignature) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(6) + var zb0001Mask uint8 /* 8 bits */ + if (*z).OneTimeSignature.PK.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).OneTimeSignature.PK1Sig.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x8 + } + if (*z).OneTimeSignature.PK2.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x10 + } + if (*z).OneTimeSignature.PK2Sig.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x20 + } + if (*z).OneTimeSignature.PKSigOld.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x40 + } + if (*z).OneTimeSignature.Sig.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x80 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "p" + o = append(o, 0xa1, 0x70) + o, err = (*z).OneTimeSignature.PK.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "PK") + return + } + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "p1s" + o = append(o, 0xa3, 0x70, 0x31, 0x73) + o, err = (*z).OneTimeSignature.PK1Sig.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "PK1Sig") + return + } + } + if (zb0001Mask & 0x10) == 0 { // if not empty + // string "p2" + o = append(o, 0xa2, 0x70, 0x32) + o, err = (*z).OneTimeSignature.PK2.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "PK2") + return + } + } + if (zb0001Mask & 0x20) == 0 { // if not empty + // string "p2s" + o = append(o, 0xa3, 0x70, 0x32, 0x73) + o, err = (*z).OneTimeSignature.PK2Sig.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "PK2Sig") + return + } + } + if (zb0001Mask & 0x40) == 0 { // if not empty + // string "ps" + o = append(o, 0xa2, 0x70, 0x73) + o, err = (*z).OneTimeSignature.PKSigOld.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "PKSigOld") + return + } + } + if (zb0001Mask & 0x80) == 0 { // if not empty + // string "s" + o = append(o, 0xa1, 0x73) + o, err = (*z).OneTimeSignature.Sig.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "Sig") + return + } + } + } + return +} + +func (_ *CompactOneTimeSignature) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*CompactOneTimeSignature) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *CompactOneTimeSignature) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).OneTimeSignature.Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Sig") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).OneTimeSignature.PK.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PK") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).OneTimeSignature.PKSigOld.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PKSigOld") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).OneTimeSignature.PK2.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PK2") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).OneTimeSignature.PK1Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PK1Sig") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).OneTimeSignature.PK2Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PK2Sig") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = CompactOneTimeSignature{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "s": + bts, err = (*z).OneTimeSignature.Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Sig") + return + } + case "p": + bts, err = (*z).OneTimeSignature.PK.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "PK") + return + } + case "ps": + bts, err = (*z).OneTimeSignature.PKSigOld.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "PKSigOld") + return + } + case "p2": + bts, err = (*z).OneTimeSignature.PK2.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "PK2") + return + } + case "p1s": + bts, err = (*z).OneTimeSignature.PK1Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "PK1Sig") + return + } + case "p2s": + bts, err = (*z).OneTimeSignature.PK2Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "PK2Sig") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *CompactOneTimeSignature) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*CompactOneTimeSignature) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *CompactOneTimeSignature) Msgsize() (s int) { + s = 1 + 2 + (*z).OneTimeSignature.Sig.Msgsize() + 2 + (*z).OneTimeSignature.PK.Msgsize() + 3 + (*z).OneTimeSignature.PKSigOld.Msgsize() + 3 + (*z).OneTimeSignature.PK2.Msgsize() + 4 + (*z).OneTimeSignature.PK1Sig.Msgsize() + 4 + (*z).OneTimeSignature.PK2Sig.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *CompactOneTimeSignature) MsgIsZero() bool { + return ((*z).OneTimeSignature.Sig.MsgIsZero()) && ((*z).OneTimeSignature.PK.MsgIsZero()) && ((*z).OneTimeSignature.PKSigOld.MsgIsZero()) && ((*z).OneTimeSignature.PK2.MsgIsZero()) && ((*z).OneTimeSignature.PK1Sig.MsgIsZero()) && ((*z).OneTimeSignature.PK2Sig.MsgIsZero()) +} + +// MarshalMsg implements msgp.Marshaler +func (z *Participant) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(3) + var zb0001Mask uint8 /* 4 bits */ + if (*z).KeyDilution == 0 { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).PK.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).Weight == 0 { + zb0001Len-- + zb0001Mask |= 0x8 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "d" + o = append(o, 0xa1, 0x64) + o = msgp.AppendUint64(o, (*z).KeyDilution) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "p" + o = append(o, 0xa1, 0x70) + o, err = (*z).PK.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "PK") + return + } + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "w" + o = append(o, 0xa1, 0x77) + o = msgp.AppendUint64(o, (*z).Weight) + } + } + return +} + +func (_ *Participant) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*Participant) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Participant) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).PK.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PK") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).Weight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Weight") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).KeyDilution, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "KeyDilution") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = Participant{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "p": + bts, err = (*z).PK.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "PK") + return + } + case "w": + (*z).Weight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Weight") + return + } + case "d": + (*z).KeyDilution, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "KeyDilution") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *Participant) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Participant) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Participant) Msgsize() (s int) { + s = 1 + 2 + (*z).PK.Msgsize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z *Participant) MsgIsZero() bool { + return ((*z).PK.MsgIsZero()) && ((*z).Weight == 0) && ((*z).KeyDilution == 0) +} + +// MarshalMsg implements msgp.Marshaler +func (z *Reveal) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).Part.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } + if ((*z).SigSlot.Sig.MsgIsZero()) && ((*z).SigSlot.L == 0) { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "p" + o = append(o, 0xa1, 0x70) + o, err = (*z).Part.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "Part") + return + } + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "s" + o = append(o, 0xa1, 0x73) + // omitempty: check for empty values + zb0002Len := uint32(2) + var zb0002Mask uint8 /* 3 bits */ + if (*z).SigSlot.L == 0 { + zb0002Len-- + zb0002Mask |= 0x2 + } + if (*z).SigSlot.Sig.MsgIsZero() { + zb0002Len-- + zb0002Mask |= 0x4 + } + // variable map header, size zb0002Len + o = append(o, 0x80|uint8(zb0002Len)) + if (zb0002Mask & 0x2) == 0 { // if not empty + // string "l" + o = append(o, 0xa1, 0x6c) + o = msgp.AppendUint64(o, (*z).SigSlot.L) + } + if (zb0002Mask & 0x4) == 0 { // if not empty + // string "s" + o = append(o, 0xa1, 0x73) + o, err = (*z).SigSlot.Sig.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "SigSlot", "Sig") + return + } + } + } + } + return +} + +func (_ *Reveal) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*Reveal) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Reveal) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + var zb0003 int + var zb0004 bool + zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot") + return + } + if zb0003 > 0 { + zb0003-- + bts, err = (*z).SigSlot.Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot", "struct-from-array", "Sig") + return + } + } + if zb0003 > 0 { + zb0003-- + (*z).SigSlot.L, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot", "struct-from-array", "L") + return + } + } + if zb0003 > 0 { + err = msgp.ErrTooManyArrayFields(zb0003) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot") + return + } + if zb0004 { + (*z).SigSlot = sigslotCommit{} + } + for zb0003 > 0 { + zb0003-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot") + return + } + switch string(field) { + case "s": + bts, err = (*z).SigSlot.Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot", "Sig") + return + } + case "l": + (*z).SigSlot.L, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot", "L") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SigSlot") + return + } + } + } + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Part.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Part") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = Reveal{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "s": + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "SigSlot") + return + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).SigSlot.Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "SigSlot", "struct-from-array", "Sig") + return + } + } + if zb0005 > 0 { + zb0005-- + (*z).SigSlot.L, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "SigSlot", "struct-from-array", "L") + return + } + } + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) + if err != nil { + err = msgp.WrapError(err, "SigSlot", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "SigSlot") + return + } + if zb0006 { + (*z).SigSlot = sigslotCommit{} + } + for zb0005 > 0 { + zb0005-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "SigSlot") + return + } + switch string(field) { + case "s": + bts, err = (*z).SigSlot.Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "SigSlot", "Sig") + return + } + case "l": + (*z).SigSlot.L, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "SigSlot", "L") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "SigSlot") + return + } + } + } + } + case "p": + bts, err = (*z).Part.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Part") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *Reveal) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Reveal) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Reveal) Msgsize() (s int) { + s = 1 + 2 + 1 + 2 + (*z).SigSlot.Sig.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).Part.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *Reveal) MsgIsZero() bool { + return (((*z).SigSlot.Sig.MsgIsZero()) && ((*z).SigSlot.L == 0)) && ((*z).Part.MsgIsZero()) +} + +// MarshalMsg implements msgp.Marshaler +func (z *sigslotCommit) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).L == 0 { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Sig.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "l" + o = append(o, 0xa1, 0x6c) + o = msgp.AppendUint64(o, (*z).L) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "s" + o = append(o, 0xa1, 0x73) + o, err = (*z).Sig.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "Sig") + return + } + } + } + return +} + +func (_ *sigslotCommit) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*sigslotCommit) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *sigslotCommit) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Sig") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).L, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "L") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = sigslotCommit{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "s": + bts, err = (*z).Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Sig") + return + } + case "l": + (*z).L, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "L") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *sigslotCommit) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*sigslotCommit) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *sigslotCommit) Msgsize() (s int) { + s = 1 + 2 + (*z).Sig.Msgsize() + 2 + msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z *sigslotCommit) MsgIsZero() bool { + return ((*z).Sig.MsgIsZero()) && ((*z).L == 0) +} diff --git a/crypto/compactcert/msgp_gen_test.go b/crypto/compactcert/msgp_gen_test.go new file mode 100644 index 0000000000..28f0e9d163 --- /dev/null +++ b/crypto/compactcert/msgp_gen_test.go @@ -0,0 +1,322 @@ +// +build !skip_msgp_testing + +package compactcert + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +import ( + "testing" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/msgp/msgp" +) + +func TestMarshalUnmarshalCert(t *testing.T) { + v := Cert{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingCert(t *testing.T) { + protocol.RunEncodingTest(t, &Cert{}) +} + +func BenchmarkMarshalMsgCert(b *testing.B) { + v := Cert{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgCert(b *testing.B) { + v := Cert{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalCert(b *testing.B) { + v := Cert{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalCompactOneTimeSignature(t *testing.T) { + v := CompactOneTimeSignature{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingCompactOneTimeSignature(t *testing.T) { + protocol.RunEncodingTest(t, &CompactOneTimeSignature{}) +} + +func BenchmarkMarshalMsgCompactOneTimeSignature(b *testing.B) { + v := CompactOneTimeSignature{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgCompactOneTimeSignature(b *testing.B) { + v := CompactOneTimeSignature{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalCompactOneTimeSignature(b *testing.B) { + v := CompactOneTimeSignature{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalParticipant(t *testing.T) { + v := Participant{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingParticipant(t *testing.T) { + protocol.RunEncodingTest(t, &Participant{}) +} + +func BenchmarkMarshalMsgParticipant(b *testing.B) { + v := Participant{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgParticipant(b *testing.B) { + v := Participant{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalParticipant(b *testing.B) { + v := Participant{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalReveal(t *testing.T) { + v := Reveal{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingReveal(t *testing.T) { + protocol.RunEncodingTest(t, &Reveal{}) +} + +func BenchmarkMarshalMsgReveal(b *testing.B) { + v := Reveal{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgReveal(b *testing.B) { + v := Reveal{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalReveal(b *testing.B) { + v := Reveal{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalsigslotCommit(t *testing.T) { + v := sigslotCommit{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingsigslotCommit(t *testing.T) { + protocol.RunEncodingTest(t, &sigslotCommit{}) +} + +func BenchmarkMarshalMsgsigslotCommit(b *testing.B) { + v := sigslotCommit{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgsigslotCommit(b *testing.B) { + v := sigslotCommit{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalsigslotCommit(b *testing.B) { + v := sigslotCommit{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/crypto/compactcert/structs.go b/crypto/compactcert/structs.go new file mode 100644 index 0000000000..37f756073b --- /dev/null +++ b/crypto/compactcert/structs.go @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2020 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package compactcert + +import ( + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/protocol" +) + +// Params defines common parameters for the verifier and builder. +type Params struct { + Msg crypto.Hashable // Message to be cerified + ProvenWeight uint64 // Weight threshold proven by the certificate + SigRound basics.Round // Ephemeral signature round to expect + SecKQ uint64 // Security parameter (k+q) from analysis document +} + +// A Participant corresponds to an account whose AccountData.Status +// is Online, and for which the expected sigRound satisfies +// AccountData.VoteFirstValid <= sigRound <= AccountData.VoteLastValid. +// +// In the Algorand ledger, it is possible for multiple accounts to have +// the same PK. Thus, the PK is not necessarily unique among Participants. +// However, each account will produce a unique Participant struct, to avoid +// potential DoS attacks where one account claims to have the same VoteID PK +// as another account. +type Participant struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // PK is AccountData.VoteID. + PK crypto.OneTimeSignatureVerifier `codec:"p"` + + // Weight is AccountData.MicroAlgos. + Weight uint64 `codec:"w"` + + // KeyDilution is AccountData.KeyDilution() with the protocol for sigRound + // as expected by the Builder. + KeyDilution uint64 `codec:"d"` +} + +// ToBeHashed implements the crypto.Hashable interface. +func (p Participant) ToBeHashed() (protocol.HashID, []byte) { + return protocol.CompactCertPart, protocol.Encode(&p) +} + +// CompactOneTimeSignature is crypto.OneTimeSignature with omitempty +type CompactOneTimeSignature struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + crypto.OneTimeSignature +} + +// A sigslotCommit is a single slot in the sigs array that forms the certificate. +type sigslotCommit struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // Sig is a signature by the participant on the expected message. + Sig CompactOneTimeSignature `codec:"s"` + + // L is the total weight of signatures in lower-numbered slots. + // This is initialized once the builder has collected a sufficient + // number of signatures. + L uint64 `codec:"l"` +} + +func (ssc sigslotCommit) ToBeHashed() (protocol.HashID, []byte) { + return protocol.CompactCertSig, protocol.Encode(&ssc) +} + +// Reveal is a single array position revealed as part of a compact +// certificate. It reveals an element of the signature array and +// the corresponding element of the participants array. +type Reveal struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + SigSlot sigslotCommit `codec:"s"` + Part Participant `codec:"p"` +} + +// maxReveals is a bound on allocation and on numReveals to limit log computation +const maxReveals = 1024 +const maxProofDigests = 20 * maxReveals + +// Cert represents a compact certificate. +type Cert struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + SigCommit crypto.Digest `codec:"c"` + SignedWeight uint64 `codec:"w"` + SigProofs []crypto.Digest `codec:"S,allocbound=maxProofDigests"` + PartProofs []crypto.Digest `codec:"P,allocbound=maxProofDigests"` + + // Reveals is a sparse map from the position being revealed + // to the corresponding elements from the sigs and participants + // arrays. + Reveals map[uint64]Reveal `codec:"r,allocbound=maxReveals"` +} + +// SortUint64 implements sorting by uint64 keys for +// canonical encoding of maps in msgpack format. +type SortUint64 = basics.SortUint64 diff --git a/crypto/compactcert/verifier.go b/crypto/compactcert/verifier.go new file mode 100644 index 0000000000..cfb7a7f792 --- /dev/null +++ b/crypto/compactcert/verifier.go @@ -0,0 +1,96 @@ +// Copyright (C) 2019-2020 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package compactcert + +import ( + "fmt" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/data/basics" +) + +// Verifier is used to verify a compact certificate. +type Verifier struct { + Params + + partcom crypto.Digest +} + +// MkVerifier constructs a verifier to check the compact certificate +// on the message specified in p, with partcom specifying the Merkle +// root of the participants that must sign the message. +func MkVerifier(p Params, partcom crypto.Digest) *Verifier { + return &Verifier{ + Params: p, + partcom: partcom, + } +} + +// Verify checks if c is a valid compact certificate for the message +// and participants that were used to construct the Verifier. +func (v *Verifier) Verify(c *Cert) error { + if c.SignedWeight <= v.ProvenWeight { + return fmt.Errorf("cert signed weight %d <= proven weight %d", c.SignedWeight, v.ProvenWeight) + } + + // Verify all of the reveals + sigs := make(map[uint64]crypto.Hashable) + parts := make(map[uint64]crypto.Hashable) + for pos, r := range c.Reveals { + sigs[pos] = r.SigSlot + parts[pos] = r.Part + + ephID := basics.OneTimeIDForRound(v.SigRound, r.Part.KeyDilution) + if !r.Part.PK.Verify(ephID, v.Msg, r.SigSlot.Sig.OneTimeSignature) { + return fmt.Errorf("signature in reveal pos %d does not verify", pos) + } + } + + err := merklearray.Verify(c.SigCommit, sigs, c.SigProofs) + if err != nil { + return err + } + + err = merklearray.Verify(v.partcom, parts, c.PartProofs) + if err != nil { + return err + } + + // Verify that the reveals contain the right coins + nr, err := v.numReveals(c.SignedWeight) + if err != nil { + return err + } + + for j := uint64(0); j < nr; j++ { + coin := hashCoin(j, c.SigCommit, c.SignedWeight) + matchingReveal := false + for _, r := range c.Reveals { + if r.SigSlot.L <= coin && coin < r.SigSlot.L+r.Part.Weight { + matchingReveal = true + break + } + } + + if !matchingReveal { + return fmt.Errorf("no reveal for coin %d at %d", j, coin) + } + } + + return nil +} diff --git a/protocol/hash.go b/protocol/hash.go index d3f821690e..4ab2786e69 100644 --- a/protocol/hash.go +++ b/protocol/hash.go @@ -28,6 +28,9 @@ const ( AuctionParams HashID = "aP" AuctionSettlement HashID = "aS" + CompactCertPart HashID = "ccp" + CompactCertSig HashID = "ccs" + AgreementSelector HashID = "AS" BlockHeader HashID = "BH" BalanceRecord HashID = "BR"