diff --git a/common/float16.go b/common/float16.go deleted file mode 100644 index 4a33c49..0000000 --- a/common/float16.go +++ /dev/null @@ -1,132 +0,0 @@ -// Package common Float16 provides methods to work with Hermez custom half float -// precision, 16 bits, codification internally called Float16 has been adopted -// to encode large integers. This is done in order to save bits when L2 -// transactions are published. -//nolint:gomnd -package common - -import ( - "encoding/binary" - "errors" - "math/big" - - "github.com/hermeznetwork/tracerr" -) - -var ( - // ErrRoundingLoss is used when converted big.Int to Float16 causes rounding loss - ErrRoundingLoss = errors.New("input value causes rounding loss") -) - -// Float16 represents a float in a 16 bit format -type Float16 uint16 - -// Bytes return a byte array of length 2 with the Float16 value encoded in BigEndian -func (f16 Float16) Bytes() []byte { - var b [2]byte - binary.BigEndian.PutUint16(b[:], uint16(f16)) - return b[:] -} - -// Float16FromBytes returns a Float16 from a byte array of 2 bytes. -func Float16FromBytes(b []byte) *Float16 { - // WARNING b[:2] for a b where len(b)<2 can break - f16 := Float16(binary.BigEndian.Uint16(b[:2])) - return &f16 -} - -// BigInt converts the Float16 to a *big.Int integer -func (f16 *Float16) BigInt() *big.Int { - fl := int64(*f16) - - m := big.NewInt(fl & 0x3FF) - e := big.NewInt(fl >> 11) - e5 := (fl >> 10) & 0x01 - - exp := big.NewInt(0).Exp(big.NewInt(10), e, nil) - res := m.Mul(m, exp) - - if e5 != 0 && e.Cmp(big.NewInt(0)) != 0 { - res.Add(res, exp.Div(exp, big.NewInt(2))) - } - return res -} - -// floorFix2Float converts a fix to a float, always rounding down -func floorFix2Float(_f *big.Int) Float16 { - zero := big.NewInt(0) - ten := big.NewInt(10) - e := int64(0) - - m := big.NewInt(0) - m.Set(_f) - - if m.Cmp(zero) == 0 { - return 0 - } - - s := big.NewInt(0).Rsh(m, 10) - - for s.Cmp(zero) != 0 { - m.Div(m, ten) - s.Rsh(m, 10) - e++ - } - - return Float16(m.Int64() | e<<11) -} - -// NewFloat16 encodes a *big.Int integer as a Float16, returning error in -// case of loss during the encoding. -func NewFloat16(f *big.Int) (Float16, error) { - fl1 := floorFix2Float(f) - fi1 := fl1.BigInt() - fl2 := fl1 | 0x400 - fi2 := fl2.BigInt() - - m3 := (fl1 & 0x3FF) + 1 - e3 := fl1 >> 11 - - if m3&0x400 == 0 { - m3 = 0x66 - e3++ - } - - fl3 := m3 + e3<<11 - fi3 := fl3.BigInt() - - res := fl1 - - d := big.NewInt(0).Abs(fi1.Sub(fi1, f)) - d2 := big.NewInt(0).Abs(fi2.Sub(fi2, f)) - - if d.Cmp(d2) == 1 { - res = fl2 - d = d2 - } - - d3 := big.NewInt(0).Abs(fi3.Sub(fi3, f)) - - if d.Cmp(d3) == 1 { - res = fl3 - } - - // Do rounding check - if res.BigInt().Cmp(f) == 0 { - return res, nil - } - return res, tracerr.Wrap(ErrRoundingLoss) -} - -// NewFloat16Floor encodes a big.Int integer as a Float16, rounding down in -// case of loss during the encoding. -func NewFloat16Floor(f *big.Int) Float16 { - fl1 := floorFix2Float(f) - fl2 := fl1 | 0x400 - fi2 := fl2.BigInt() - - if fi2.Cmp(f) < 1 { - return fl2 - } - return fl1 -} diff --git a/common/float16_test.go b/common/float16_test.go deleted file mode 100644 index 1ba7052..0000000 --- a/common/float16_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package common - -import ( - "math/big" - "testing" - - "github.com/hermeznetwork/tracerr" - "github.com/stretchr/testify/assert" -) - -func TestConversionsFloat16(t *testing.T) { - testVector := map[Float16]string{ - 0x307B: "123000000", - 0x1DC6: "454500", - 0xFFFF: "10235000000000000000000000000000000", - 0x0000: "0", - 0x0400: "0", - 0x0001: "1", - 0x0401: "1", - 0x0800: "0", - 0x0c00: "5", - 0x0801: "10", - 0x0c01: "15", - } - - for test := range testVector { - fix := test.BigInt() - - assert.Equal(t, fix.String(), testVector[test]) - - bi := big.NewInt(0) - bi.SetString(testVector[test], 10) - - fl, err := NewFloat16(bi) - assert.NoError(t, err) - - fx2 := fl.BigInt() - assert.Equal(t, fx2.String(), testVector[test]) - } -} - -func TestFloorFix2FloatFloat16(t *testing.T) { - testVector := map[string]Float16{ - "87999990000000000": 0x776f, - "87950000000000001": 0x776f, - "87950000000000000": 0x776f, - "87949999999999999": 0x736f, - } - - for test := range testVector { - bi := big.NewInt(0) - bi.SetString(test, 10) - - testFloat := NewFloat16Floor(bi) - - assert.Equal(t, testFloat, testVector[test]) - } -} - -func TestConversionLossesFloat16(t *testing.T) { - a := big.NewInt(1000) - b, err := NewFloat16(a) - assert.NoError(t, err) - c := b.BigInt() - assert.Equal(t, c, a) - - a = big.NewInt(1024) - b, err = NewFloat16(a) - assert.Equal(t, ErrRoundingLoss, tracerr.Unwrap(err)) - c = b.BigInt() - assert.NotEqual(t, c, a) - - a = big.NewInt(32767) - b, err = NewFloat16(a) - assert.Equal(t, ErrRoundingLoss, tracerr.Unwrap(err)) - c = b.BigInt() - assert.NotEqual(t, c, a) - - a = big.NewInt(32768) - b, err = NewFloat16(a) - assert.Equal(t, ErrRoundingLoss, tracerr.Unwrap(err)) - c = b.BigInt() - assert.NotEqual(t, c, a) - - a = big.NewInt(65536000) - b, err = NewFloat16(a) - assert.Equal(t, ErrRoundingLoss, tracerr.Unwrap(err)) - c = b.BigInt() - assert.NotEqual(t, c, a) -} - -func BenchmarkFloat16(b *testing.B) { - newBigInt := func(s string) *big.Int { - bigInt, ok := new(big.Int).SetString(s, 10) - if !ok { - panic("Bad big int") - } - return bigInt - } - type pair struct { - Float16 Float16 - BigInt *big.Int - } - testVector := []pair{ - {0x307B, newBigInt("123000000")}, - {0x1DC6, newBigInt("454500")}, - {0xFFFF, newBigInt("10235000000000000000000000000000000")}, - {0x0000, newBigInt("0")}, - {0x0400, newBigInt("0")}, - {0x0001, newBigInt("1")}, - {0x0401, newBigInt("1")}, - {0x0800, newBigInt("0")}, - {0x0c00, newBigInt("5")}, - {0x0801, newBigInt("10")}, - {0x0c01, newBigInt("15")}, - } - b.Run("floorFix2Float()", func(b *testing.B) { - for i := 0; i < b.N; i++ { - NewFloat16Floor(testVector[i%len(testVector)].BigInt) - } - }) - b.Run("NewFloat16()", func(b *testing.B) { - for i := 0; i < b.N; i++ { - _, _ = NewFloat16(testVector[i%len(testVector)].BigInt) - } - }) - b.Run("Float16.BigInt()", func(b *testing.B) { - for i := 0; i < b.N; i++ { - testVector[i%len(testVector)].Float16.BigInt() - } - }) -} diff --git a/common/float40.go b/common/float40.go index 92d8c48..246cff7 100644 --- a/common/float40.go +++ b/common/float40.go @@ -6,7 +6,6 @@ package common import ( - "bytes" "encoding/binary" "errors" "math/big" @@ -24,8 +23,8 @@ const ( ) var ( - // ErrFloat40Overflow is used when a given nonce overflows the maximum - // capacity of the Float40 (2**40-1) + // ErrFloat40Overflow is used when a given Float40 overflows the + // maximum capacity of the Float40 (2**40-1) ErrFloat40Overflow = errors.New("Float40 overflow, max value: 2**40 -1") // ErrFloat40E31 is used when the e > 31 when trying to convert a // *big.Int to Float40 @@ -88,15 +87,14 @@ func NewFloat40(f *big.Int) (Float40, error) { zero := big.NewInt(0) ten := big.NewInt(10) thres := big.NewInt(0x08_00_00_00_00) - for bytes.Equal(zero.Bytes(), new(big.Int).Mod(m, ten).Bytes()) && - !bytes.Equal(zero.Bytes(), new(big.Int).Div(m, thres).Bytes()) { + for new(big.Int).Mod(m, ten).Cmp(zero) == 0 && m.Cmp(thres) >= 0 { m = new(big.Int).Div(m, ten) e = new(big.Int).Add(e, big.NewInt(1)) } if e.Int64() > 31 { return 0, ErrFloat40E31 } - if !bytes.Equal(zero.Bytes(), new(big.Int).Div(m, thres).Bytes()) { + if m.Cmp(thres) >= 0 { return 0, ErrFloat40NotEnoughPrecission } r := new(big.Int).Add(m, diff --git a/common/pooll2tx.go b/common/pooll2tx.go index 34150e1..c5f324a 100644 --- a/common/pooll2tx.go +++ b/common/pooll2tx.go @@ -36,7 +36,7 @@ type PoolL2Tx struct { ToEthAddr ethCommon.Address `meddler:"to_eth_addr,zeroisnull"` ToBJJ babyjub.PublicKeyComp `meddler:"to_bjj,zeroisnull"` TokenID TokenID `meddler:"token_id"` - Amount *big.Int `meddler:"amount,bigint"` // TODO: change to float40 + Amount *big.Int `meddler:"amount,bigint"` Fee FeeSelector `meddler:"fee"` Nonce Nonce `meddler:"nonce"` // effective 40 bits used State PoolL2TxState `meddler:"state"` @@ -53,7 +53,7 @@ type PoolL2Tx struct { RqToEthAddr ethCommon.Address `meddler:"rq_to_eth_addr,zeroisnull"` RqToBJJ babyjub.PublicKeyComp `meddler:"rq_to_bjj,zeroisnull"` RqTokenID TokenID `meddler:"rq_token_id,zeroisnull"` - RqAmount *big.Int `meddler:"rq_amount,bigintnull"` // TODO: change to float40 + RqAmount *big.Int `meddler:"rq_amount,bigintnull"` RqFee FeeSelector `meddler:"rq_fee,zeroisnull"` RqNonce Nonce `meddler:"rq_nonce,zeroisnull"` // effective 48 bits used AbsoluteFee float64 `meddler:"fee_usd,zeroisnull"` @@ -126,7 +126,7 @@ func (tx *PoolL2Tx) SetID() error { // [ 48 bits ] fromIdx // 6 bytes // [ 16 bits ] chainId // 2 bytes // [ 32 bits ] signatureConstant // 4 bytes -// Total bits compressed data: 241 bits // 31 bytes in *big.Int representation +// Total bits compressed data: 225 bits // 29 bytes in *big.Int representation func (tx *PoolL2Tx) TxCompressedData(chainID uint16) (*big.Int, error) { var b [29]byte @@ -179,7 +179,7 @@ func TxCompressedDataEmpty(chainID uint16) *big.Int { // [ 40 bits ] amountFloat40 // 5 bytes // [ 48 bits ] toIdx // 6 bytes // [ 48 bits ] fromIdx // 6 bytes -// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation +// Total bits compressed data: 217 bits // 28 bytes in *big.Int representation func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) { if tx.Amount == nil { tx.Amount = big.NewInt(0) @@ -238,7 +238,7 @@ func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) { // [ 40 bits ] rqAmountFloat40 // 5 bytes // [ 48 bits ] rqToIdx // 6 bytes // [ 48 bits ] rqFromIdx // 6 bytes -// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation +// Total bits compressed data: 217 bits // 28 bytes in *big.Int representation func (tx *PoolL2Tx) RqTxCompressedDataV2() (*big.Int, error) { if tx.RqAmount == nil { tx.RqAmount = big.NewInt(0) diff --git a/common/zk.go b/common/zk.go index ab30e21..6f16bdf 100644 --- a/common/zk.go +++ b/common/zk.go @@ -103,7 +103,7 @@ type ZKInputs struct { // ToEthAddr ToEthAddr []*big.Int `json:"toEthAddr"` // ethCommon.Address, len: [maxTx] // AmountF encoded as float40 - AmountF []*big.Int `json:"amountF"` + AmountF []*big.Int `json:"amountF"` // uint40 len: [maxTx] // OnChain determines if is L1 (1/true) or L2 (0/false) OnChain []*big.Int `json:"onChain"` // bool, len: [maxTx] @@ -479,7 +479,7 @@ func (z ZKInputs) ToHashGlobalData() ([]byte, error) { copy(newExitRoot, z.Metadata.NewExitRootRaw.Bytes()) b = append(b, newExitRoot...) - // [MAX_L1_TX * (2 * MAX_NLEVELS + 480) bits] L1TxsData + // [MAX_L1_TX * (2 * MAX_NLEVELS + 528) bits] L1TxsData l1TxDataLen := (2*z.Metadata.MaxLevels + 528) l1TxsDataLen := (z.Metadata.MaxL1Tx * l1TxDataLen) l1TxsData := make([]byte, l1TxsDataLen/8) //nolint:gomnd @@ -497,7 +497,7 @@ func (z ZKInputs) ToHashGlobalData() ([]byte, error) { } b = append(b, l1TxsDataAvailability...) - // [MAX_TX*(2*NLevels + 24) bits] L2TxsData + // [MAX_TX*(2*NLevels + 48) bits] L2TxsData var l2TxsData []byte l2TxDataLen := 2*z.Metadata.NLevels + 48 //nolint:gomnd l2TxsDataLen := (z.Metadata.MaxTx * l2TxDataLen)