Add methods for ZKInputs IntermStates generation

- Add L1Tx TxCompressedData method
- Add PoolL2Tx TxCompressedDataV2 method
- Update ProcessTxs logic
- Add ZKInputs Intermediate States & Fee parameters calculation
This commit is contained in:
arnaucube
2020-11-18 22:16:33 +01:00
parent bf88eb60b8
commit d3a38a3ee1
10 changed files with 388 additions and 76 deletions

View File

@@ -6,6 +6,16 @@ import (
"math/big"
)
// MaxFeePlan is the maximum value of the FeePlan
const MaxFeePlan = 256
// FeePlan represents the fee model, a position in the array indicates the
// percentage of tokens paid in concept of fee for a transaction
var FeePlan = [MaxFeePlan]float64{}
// FeeFactorLsh60 is the feeFactor << 60
var FeeFactorLsh60 [256]*big.Int
// RecommendedFee is the recommended fee to pay in USD per transaction set by
// the coordinator according to the tx type (if the tx requires to create an
// account and register, only register or he account already esists)
@@ -31,13 +41,6 @@ func (f FeeSelector) Percentage() float64 {
}
}
// MaxFeePlan is the maximum value of the FeePlan
const MaxFeePlan = 256
// FeePlan represents the fee model, a position in the array indicates the
// percentage of tokens paid in concept of fee for a transaction
var FeePlan = [MaxFeePlan]float64{}
// CalcFeeAmount calculates the fee amount in tokens from an amount and
// feeSelector (fee index).
func CalcFeeAmount(amount *big.Int, feeSel FeeSelector) (*big.Int, error) {
@@ -55,9 +58,6 @@ func init() {
setFeeFactorLsh60(&FeeFactorLsh60)
}
// FeeFactorLsh60 is the feeFactor << 60
var FeeFactorLsh60 [256]*big.Int
func setFeeFactorLsh60(feeFactorLsh60 *[256]*big.Int) {
feeFactorLsh60[0], _ = new(big.Int).SetString("0", 10)
feeFactorLsh60[1], _ = new(big.Int).SetString("3", 10)

View File

@@ -146,6 +146,43 @@ func (tx L1Tx) Tx() Tx {
return genericTx
}
// TxCompressedData spec:
// [ 1 bits ] empty (toBJJSign) // 1 byte
// [ 8 bits ] empty (userFee) // 1 byte
// [ 40 bits ] empty (nonce) // 5 bytes
// [ 32 bits ] tokenID // 4 bytes
// [ 16 bits ] amountFloat16 // 2 bytes
// [ 48 bits ] toIdx // 6 bytes
// [ 48 bits ] fromIdx // 6 bytes
// [ 16 bits ] chainId // 2 bytes
// [ 32 bits ] empty (signatureConstant) // 4 bytes
// Total bits compressed data: 241 bits // 31 bytes in *big.Int representation
func (tx L1Tx) TxCompressedData() (*big.Int, error) {
amountFloat16, err := NewFloat16(tx.Amount)
if err != nil {
return nil, err
}
var b [31]byte
// b[0:7] empty: no fee neither nonce
copy(b[7:11], tx.TokenID.Bytes())
copy(b[11:13], amountFloat16.Bytes())
toIdxBytes, err := tx.ToIdx.Bytes()
if err != nil {
return nil, err
}
copy(b[13:19], toIdxBytes[:])
fromIdxBytes, err := tx.FromIdx.Bytes()
if err != nil {
return nil, err
}
copy(b[19:25], fromIdxBytes[:])
copy(b[25:27], []byte{0, 1}) // TODO this will be generated by the ChainID config parameter
// b[27:] empty: no signature
bi := new(big.Int).SetBytes(b[:])
return bi, nil
}
// BytesGeneric returns the generic representation of a L1Tx. This method is
// used to compute the []byte representation of a L1UserTx, and also to compute
// the L1TxData for the ZKInputs (at the HashGlobalInputs), using this method

View File

@@ -49,6 +49,22 @@ func TestNewL1CoordinatorTx(t *testing.T) {
assert.Equal(t, "0x01000000000000cafe005800", l1Tx.TxID.String())
}
func TestL1TxCompressedData(t *testing.T) {
tx := L1Tx{
FromIdx: 2,
ToIdx: 3,
Amount: big.NewInt(4),
TokenID: 5,
}
txCompressedData, err := tx.TxCompressedData()
assert.Nil(t, err)
// test vector value generated from javascript implementation
expectedStr := "7307597389635308713748674793997299267460566876160"
assert.Equal(t, expectedStr, txCompressedData.String())
assert.Equal(t, "050004000000000003000000000002000100000000", hex.EncodeToString(txCompressedData.Bytes()))
}
func TestL1userTxByteParsers(t *testing.T) {
var pkComp babyjub.PublicKeyComp
pkCompL := []byte("0x56ca90f80d7c374ae7485e9bcc47d4ac399460948da6aeeb899311097925a72c")

View File

@@ -140,13 +140,64 @@ func (tx *PoolL2Tx) TxCompressedData() (*big.Int, error) {
return nil, err
}
copy(b[19:25], fromIdxBytes[:])
copy(b[25:27], []byte{0, 1, 0, 0}) // TODO check js implementation (unexpected behaviour from test vector generated from js)
copy(b[25:27], []byte{0, 1}) // TODO this will be generated by the ChainID config parameter
copy(b[27:31], sc.Bytes())
bi := new(big.Int).SetBytes(b[:])
return bi, nil
}
// TxCompressedDataV2 spec:
// [ 1 bits ] toBJJSign // 1 byte
// [ 8 bits ] userFee // 1 byte
// [ 40 bits ] nonce // 5 bytes
// [ 32 bits ] tokenID // 4 bytes
// [ 16 bits ] amountFloat16 // 2 bytes
// [ 48 bits ] toIdx // 6 bytes
// [ 48 bits ] fromIdx // 6 bytes
// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation
func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
if tx.Amount == nil {
tx.Amount = big.NewInt(0)
}
amountFloat16, err := NewFloat16(tx.Amount)
if err != nil {
return nil, err
}
var b [25]byte
toBJJSign := byte(0)
if tx.ToBJJ != nil && babyjub.PointCoordSign(tx.ToBJJ.X) {
toBJJSign = byte(1)
}
b[0] = toBJJSign
b[1] = byte(tx.Fee)
nonceBytes, err := tx.Nonce.Bytes()
if err != nil {
return nil, err
}
copy(b[2:7], nonceBytes[:])
copy(b[7:11], tx.TokenID.Bytes())
copy(b[11:13], amountFloat16.Bytes())
toIdxBytes, err := tx.ToIdx.Bytes()
if err != nil {
return nil, err
}
copy(b[13:19], toIdxBytes[:])
fromIdxBytes, err := tx.FromIdx.Bytes()
if err != nil {
return nil, err
}
copy(b[19:25], fromIdxBytes[:])
bi := new(big.Int).SetBytes(b[:])
return bi, nil
}
// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
// can be inside a struct, which contains the 'Rq' transaction grouped inside,
// so then computing the 'RqTxCompressedDataV2' would be just calling
// 'tx.Rq.TxCompressedDataV2()'.
// RqTxCompressedDataV2 spec:
// [ 1 bits ] rqToBJJSign // 1 byte
// [ 8 bits ] rqUserFee // 1 byte

View File

@@ -40,10 +40,7 @@ func TestTxCompressedData(t *testing.T) {
// test vector value generated from javascript implementation
expectedStr := "1766847064778421992193717128424891165872736891548909569553540449389241871"
assert.Equal(t, expectedStr, txCompressedData.String())
expected, ok := new(big.Int).SetString(expectedStr, 10)
assert.True(t, ok)
assert.Equal(t, expected.Bytes(), txCompressedData.Bytes())
assert.Equal(t, "10000000000060000000500040000000000030000000000020001c60be60f", hex.EncodeToString(txCompressedData.Bytes())[1:])
assert.Equal(t, "010000000000060000000500040000000000030000000000020001c60be60f", hex.EncodeToString(txCompressedData.Bytes()))
tx = PoolL2Tx{
RqFromIdx: 7,
RqToIdx: 8,
@@ -58,10 +55,56 @@ func TestTxCompressedData(t *testing.T) {
// test vector value generated from javascript implementation
expectedStr = "6571340879233176732837827812956721483162819083004853354503"
assert.Equal(t, expectedStr, txCompressedData.String())
expected, ok = new(big.Int).SetString(expectedStr, 10)
assert.Equal(t, "010c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes()))
}
func TestTxCompressedDataV2(t *testing.T) {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
tx := PoolL2Tx{
FromIdx: 7,
ToIdx: 8,
Amount: big.NewInt(9),
TokenID: 10,
Nonce: 11,
Fee: 12,
ToBJJ: sk.Public(),
}
txCompressedData, err := tx.TxCompressedDataV2()
assert.Nil(t, err)
// test vector value generated from javascript implementation
expectedStr := "6571340879233176732837827812956721483162819083004853354503"
assert.Equal(t, expectedStr, txCompressedData.String())
expected, ok := new(big.Int).SetString(expectedStr, 10)
assert.True(t, ok)
assert.Equal(t, expected.Bytes(), txCompressedData.Bytes())
assert.Equal(t, "010c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes()))
}
func TestRqTxCompressedDataV2(t *testing.T) {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
tx := PoolL2Tx{
RqFromIdx: 7,
RqToIdx: 8,
RqAmount: big.NewInt(9),
RqTokenID: 10,
RqNonce: 11,
RqFee: 12,
RqToBJJ: sk.Public(),
}
txCompressedData, err := tx.RqTxCompressedDataV2()
assert.Nil(t, err)
// test vector value generated from javascript implementation
expectedStr := "6571340879233176732837827812956721483162819083004853354503"
assert.Equal(t, expectedStr, txCompressedData.String())
expected, ok := new(big.Int).SetString(expectedStr, 10)
assert.True(t, ok)
assert.Equal(t, expected.Bytes(), txCompressedData.Bytes())
assert.Equal(t, "10c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes())[1:])
assert.Equal(t, "010c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes()))
}
func TestHashToSign(t *testing.T) {

View File

@@ -52,3 +52,8 @@ func TokenIDFromBytes(b []byte) (TokenID, error) {
tid := binary.BigEndian.Uint32(b[:4])
return TokenID(tid), nil
}
// TokenIDFromBigInt returns a TokenID with the value of the given *big.Int
func TokenIDFromBigInt(b *big.Int) TokenID {
return TokenID(b.Int64())
}

View File

@@ -43,7 +43,7 @@ type ZKMetadata struct {
// ZKInputs represents the inputs that will be used to generate the zkSNARK proof
type ZKInputs struct {
Metadata ZKMetadata
Metadata ZKMetadata `json:"-"`
//
// General
@@ -183,23 +183,28 @@ type ZKInputs struct {
// will not be used.
// decode-tx
// ISOnChain indicates if tx is L1 (true) or L2 (false)
// ISOnChain indicates if tx is L1 (true (1)) or L2 (false (0))
ISOnChain []*big.Int `json:"imOnChain"` // bool, len: [nTx - 1]
// ISOutIdx current index account for each Tx
// Contains the index of the created account in case that the tx is of
// account creation type.
ISOutIdx []*big.Int `json:"imOutIdx"` // uint64 (max nLevels bits), len: [nTx - 1]
// rollup-tx
// ISStateRoot root at the moment of the Tx, the state root value once the Tx is processed into the state tree
// ISStateRoot root at the moment of the Tx (once processed), the state root value once the Tx is processed into the state tree
ISStateRoot []*big.Int `json:"imStateRoot"` // Hash, len: [nTx - 1]
// ISExitTree root at the moment of the Tx the value once the Tx is processed into the exit tree
// ISExitTree root at the moment (once processed) of the Tx the value
// once the Tx is processed into the exit tree
ISExitRoot []*big.Int `json:"imExitRoot"` // Hash, len: [nTx - 1]
// ISAccFeeOut accumulated fees once the Tx is processed
// ISAccFeeOut accumulated fees once the Tx is processed. Contains the
// array of FeeAccount Balances at each moment of each Tx processed.
ISAccFeeOut [][]*big.Int `json:"imAccFeeOut"` // big.Int, len: [nTx - 1][maxFeeIdxs]
// fee-tx
// ISStateRootFee root at the moment of the Tx, the state root value once the Tx is processed into the state tree
// ISStateRootFee root at the moment of the Tx (once processed), the state root value once the Tx is processed into the state tree
ISStateRootFee []*big.Int `json:"imStateRootFee"` // Hash, len: [maxFeeIdxs - 1]
// ISInitStateRootFee state root once all L1-L2 tx are processed (before computing the fees-tx)
ISInitStateRootFee *big.Int `json:"imInitStateRootFee"` // Hash
// ISFinalAccFee final accumulated fees (before computing the fees-tx)
// ISFinalAccFee final accumulated fees (before computing the fees-tx).
// Contains the final values of the ISAccFeeOut parameter
ISFinalAccFee []*big.Int `json:"imFinalAccFee"` // big.Int, len: [maxFeeIdxs - 1]
}