Skip to content

Commit

Permalink
core crypto primitives for compact certificates (#1359)
Browse files Browse the repository at this point in the history
Implement core crypto primitives for compact certificates
  • Loading branch information
zeldovich authored Sep 9, 2020
1 parent 399078c commit 6bc1c01
Show file tree
Hide file tree
Showing 12 changed files with 2,843 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
186 changes: 186 additions & 0 deletions crypto/compactcert/bigfloat.go
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

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)
}
162 changes: 162 additions & 0 deletions crypto/compactcert/bigfloat_test.go
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

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)
}
}
Loading

0 comments on commit 6bc1c01

Please sign in to comment.