Browse Source

Merge pull request #219 from hermeznetwork/feature/api-account-creation

Impl account creation auth endpoints
feature/sql-semaphore1
Eduard S 4 years ago
committed by GitHub
parent
commit
0e5aad4767
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 271 additions and 26 deletions
  1. +0
    -1
      api/api.go
  2. +80
    -2
      api/api_test.go
  3. +46
    -0
      api/dbtoapistructs.go
  4. +34
    -1
      api/handlers.go
  5. +7
    -0
      api/parsers.go
  6. +25
    -9
      api/swagger.yml
  7. +34
    -0
      common/accountcreationauths.go
  8. +7
    -1
      db/l2db/l2db.go
  9. +23
    -12
      test/l2db.go
  10. +15
    -0
      test/l2db_test.go

+ 0
- 1
api/api.go

@ -11,7 +11,6 @@ import (
var h *historydb.HistoryDB var h *historydb.HistoryDB
var cg *configAPI var cg *configAPI
var s *statedb.StateDB var s *statedb.StateDB
var l2 *l2db.L2DB var l2 *l2db.L2DB

+ 80
- 2
api/api_test.go

@ -51,6 +51,7 @@ type testCommon struct {
usrExits []exitAPI usrExits []exitAPI
poolTxsToSend []receivedPoolTx poolTxsToSend []receivedPoolTx
poolTxsToReceive []sendPoolTx poolTxsToReceive []sendPoolTx
auths []accountCreationAuthAPI
router *swagger.Router router *swagger.Router
} }
@ -637,6 +638,15 @@ func TestMain(m *testing.M) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Account creation auth
const nAuths = 5
auths := test.GenAuths(nAuths)
// Transform auths to API format
apiAuths := []accountCreationAuthAPI{}
for _, auth := range auths {
apiAuth := accountCreationAuthToAPI(auth)
apiAuths = append(apiAuths, *apiAuth)
}
// Set testCommon // Set testCommon
tc = testCommon{ tc = testCommon{
blocks: blocks, blocks: blocks,
@ -652,6 +662,7 @@ func TestMain(m *testing.M) {
usrExits: usrExits, usrExits: usrExits,
poolTxsToSend: poolTxsToSend, poolTxsToSend: poolTxsToSend,
poolTxsToReceive: poolTxsToReceive, poolTxsToReceive: poolTxsToReceive,
auths: apiAuths,
router: router, router: router,
} }
// Fake server // Fake server
@ -1325,6 +1336,69 @@ func TestGetCoordinators(t *testing.T) {
err = doBadReq("GET", path, nil, 404) err = doBadReq("GET", path, nil, 404)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestAccountCreationAuth(t *testing.T) {
// POST
endpoint := apiURL + "account-creation-authorization"
for _, auth := range tc.auths {
jsonAuthBytes, err := json.Marshal(auth)
assert.NoError(t, err)
jsonAuthReader := bytes.NewReader(jsonAuthBytes)
fmt.Println(string(jsonAuthBytes))
assert.NoError(
t, doGoodReq(
"POST",
endpoint,
jsonAuthReader, nil,
),
)
}
// GET
endpoint += "/"
for _, auth := range tc.auths {
fetchedAuth := accountCreationAuthAPI{}
assert.NoError(
t, doGoodReq(
"GET",
endpoint+auth.EthAddr,
nil, &fetchedAuth,
),
)
assertAuth(t, auth, fetchedAuth)
}
// POST
// 400
// Wrong addr
badAuth := tc.auths[0]
badAuth.EthAddr = ethAddrToHez(ethCommon.BigToAddress(big.NewInt(1)))
jsonAuthBytes, err := json.Marshal(badAuth)
assert.NoError(t, err)
jsonAuthReader := bytes.NewReader(jsonAuthBytes)
err = doBadReq("POST", endpoint, jsonAuthReader, 400)
assert.NoError(t, err)
// Wrong signature
badAuth = tc.auths[0]
badAuth.Signature = badAuth.Signature[:len(badAuth.Signature)-1]
badAuth.Signature += "F"
jsonAuthBytes, err = json.Marshal(badAuth)
assert.NoError(t, err)
jsonAuthReader = bytes.NewReader(jsonAuthBytes)
err = doBadReq("POST", endpoint, jsonAuthReader, 400)
assert.NoError(t, err)
// GET
// 400
err = doBadReq("GET", endpoint+"hez:0xFooBar", nil, 400)
assert.NoError(t, err)
// 404
err = doBadReq("GET", endpoint+"hez:0x0000000000000000000000000000000000000001", nil, 404)
assert.NoError(t, err)
}
func assertAuth(t *testing.T, expected, actual accountCreationAuthAPI) {
// timestamp should be very close to now
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
expected.Timestamp = actual.Timestamp
assert.Equal(t, expected, actual)
}
func doGoodReqPaginated( func doGoodReqPaginated(
path, order string, path, order string,
@ -1393,7 +1467,7 @@ func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{})
if err != nil { if err != nil {
return err return err
} }
if resp.Body == nil {
if resp.Body == nil && returnStruct != nil {
return errors.New("Nil body") return errors.New("Nil body")
} }
//nolint //nolint
@ -1403,10 +1477,14 @@ func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{})
return err return err
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return fmt.Errorf("%d response: %s", resp.StatusCode, string(body))
return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
}
if returnStruct == nil {
return nil
} }
// Unmarshal body into return struct // Unmarshal body into return struct
if err := json.Unmarshal(body, returnStruct); err != nil { if err := json.Unmarshal(body, returnStruct); err != nil {
log.Error("invalid json: " + string(body))
return err return err
} }
// Validate response against swagger spec // Validate response against swagger spec

+ 46
- 0
api/dbtoapistructs.go

@ -2,10 +2,12 @@ package api
import ( import (
"encoding/base64" "encoding/base64"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"strconv" "strconv"
"strings"
"time" "time"
ethCommon "github.com/ethereum/go-ethereum/common" ethCommon "github.com/ethereum/go-ethereum/common"
@ -665,3 +667,47 @@ func coordinatorsToAPI(dbCoordinators []historydb.HistoryCoordinator) []coordina
} }
return apiCoordinators return apiCoordinators
} }
// AccountCreationAuth
type accountCreationAuthAPI struct {
EthAddr string `json:"hezEthereumAddress" binding:"required"`
BJJ string `json:"bjj" binding:"required"`
Signature string `json:"signature" binding:"required"`
Timestamp time.Time `json:"timestamp"`
}
func accountCreationAuthToAPI(dbAuth *common.AccountCreationAuth) *accountCreationAuthAPI {
return &accountCreationAuthAPI{
EthAddr: ethAddrToHez(dbAuth.EthAddr),
BJJ: bjjToString(dbAuth.BJJ),
Signature: "0x" + hex.EncodeToString(dbAuth.Signature),
Timestamp: dbAuth.Timestamp,
}
}
func accountCreationAuthAPIToCommon(apiAuth *accountCreationAuthAPI) (*common.AccountCreationAuth, error) {
ethAddr, err := hezStringToEthAddr(apiAuth.EthAddr, "hezEthereumAddress")
if err != nil {
return nil, err
}
bjj, err := hezStringToBJJ(apiAuth.BJJ, "bjj")
if err != nil {
return nil, err
}
without0x := strings.TrimPrefix(apiAuth.Signature, "0x")
s, err := hex.DecodeString(without0x)
if err != nil {
return nil, err
}
auth := &common.AccountCreationAuth{
EthAddr: *ethAddr,
BJJ: bjj,
Signature: s,
Timestamp: apiAuth.Timestamp,
}
if !auth.VerifySignature() {
return nil, errors.New("invalid signature")
}
return auth, nil
}

+ 34
- 1
api/handlers.go

@ -30,10 +30,43 @@ var (
) )
func postAccountCreationAuth(c *gin.Context) { func postAccountCreationAuth(c *gin.Context) {
// Parse body
var apiAuth accountCreationAuthAPI
if err := c.ShouldBindJSON(&apiAuth); err != nil {
retBadReq(err, c)
return
}
// API to common + verify signature
dbAuth, err := accountCreationAuthAPIToCommon(&apiAuth)
if err != nil {
retBadReq(err, c)
return
}
// Insert to DB
if err := l2.AddAccountCreationAuth(dbAuth); err != nil {
retSQLErr(err, c)
return
}
// Return OK
c.Status(http.StatusOK)
} }
func getAccountCreationAuth(c *gin.Context) { func getAccountCreationAuth(c *gin.Context) {
// Get hezEthereumAddress
addr, err := parseParamHezEthAddr(c)
if err != nil {
retBadReq(err, c)
return
}
// Fetch auth from l2DB
dbAuth, err := l2.GetAccountCreationAuth(*addr)
if err != nil {
retSQLErr(err, c)
return
}
apiAuth := accountCreationAuthToAPI(dbAuth)
// Build succesfull response
c.JSON(http.StatusOK, apiAuth)
} }
func postPoolTx(c *gin.Context) { func postPoolTx(c *gin.Context) {

+ 7
- 0
api/parsers.go

@ -310,6 +310,7 @@ func hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
} }
return bjj, nil return bjj, nil
} }
func parseEthAddr(c paramer, name string) (*ethCommon.Address, error) { func parseEthAddr(c paramer, name string) (*ethCommon.Address, error) {
addrStr := c.Param(name) addrStr := c.Param(name)
if addrStr == "" { if addrStr == "" {
@ -319,3 +320,9 @@ func parseEthAddr(c paramer, name string) (*ethCommon.Address, error) {
err := addr.UnmarshalText([]byte(addrStr)) err := addr.UnmarshalText([]byte(addrStr))
return &addr, err return &addr, err
} }
func parseParamHezEthAddr(c paramer) (*ethCommon.Address, error) {
const name = "hermezEthereumAddress"
addrStr := c.Param(name)
return hezStringToEthAddr(addrStr, name)
}

+ 25
- 9
api/swagger.yml

@ -76,7 +76,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/AccountCreationAuthorization'
$ref: '#/components/schemas/AccountCreationAuthorizationPost'
responses: responses:
'200': '200':
description: Successful operation. description: Successful operation.
@ -1251,7 +1251,7 @@ components:
$ref: '#/components/schemas/Nonce' $ref: '#/components/schemas/Nonce'
signature: signature:
allOf: allOf:
- $ref: '#/components/schemas/Signature'
- $ref: '#/components/schemas/BJJSignature'
- description: Signature of the transaction. More info [here](https://idocs.hermez.io/#/spec/zkrollup/README?id=l2a-idl2). - description: Signature of the transaction. More info [here](https://idocs.hermez.io/#/spec/zkrollup/README?id=l2a-idl2).
- example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03" - example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03"
requestFromAccountIndex: requestFromAccountIndex:
@ -1346,7 +1346,7 @@ components:
$ref: '#/components/schemas/PoolL2TransactionState' $ref: '#/components/schemas/PoolL2TransactionState'
signature: signature:
allOf: allOf:
- $ref: '#/components/schemas/Signature'
- $ref: '#/components/schemas/BJJSignature'
- description: Signature of the transaction. More info [here](https://idocs.hermez.io/#/spec/zkrollup/README?id=l2a-idl2). - description: Signature of the transaction. More info [here](https://idocs.hermez.io/#/spec/zkrollup/README?id=l2a-idl2).
- example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03" - example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03"
timestamp: timestamp:
@ -1517,7 +1517,12 @@ components:
- fing - fing
- fged - fged
- invl - invl
Signature:
ETHSignature:
type: string
description: Ethereum signature.
pattern: "^0x[a-fA-F0-9]{130}$"
example: "0xf9161cd688394772d93aa3e7b3f8f9553ca4f94f65b7cece93ed4a239d5c0b4677dca6d1d459e3a5c271a34de735d4664a43e5a8960a9a6e027d12c562dd448e1c"
BJJSignature:
type: string type: string
description: BabyJubJub compressed signature. description: BabyJubJub compressed signature.
pattern: "^[a-fA-F0-9]{128}$" pattern: "^[a-fA-F0-9]{128}$"
@ -1528,6 +1533,19 @@ components:
minimum: 0 minimum: 0
maximum: 4294967295 maximum: 4294967295
example: 5432 example: 5432
AccountCreationAuthorizationPost:
type: object
properties:
hezEthereumAddress:
$ref: '#/components/schemas/HezEthereumAddress'
bjj:
$ref: '#/components/schemas/BJJ'
signature:
$ref: '#/components/schemas/ETHSignature'
required:
- hezEthereumAddress
- bjj
- signature
AccountCreationAuthorization: AccountCreationAuthorization:
type: object type: object
properties: properties:
@ -1539,12 +1557,10 @@ components:
bjj: bjj:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
signature: signature:
allOf:
- $ref: '#/components/schemas/Signature'
- description: Signature of the auth message. More info [here](https://idocs.hermez.io/#/spec/zkrollup/README?id=regular-rollup-account).
- example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03"
$ref: '#/components/schemas/ETHSignature'
required: required:
- ethereumAddress
- timestamp
- hezEthereumAddress
- bjj - bjj
- signature - signature
HistoryTransaction: HistoryTransaction:

+ 34
- 0
common/accountcreationauths.go

@ -4,6 +4,7 @@ import (
"time" "time"
ethCommon "github.com/ethereum/go-ethereum/common" ethCommon "github.com/ethereum/go-ethereum/common"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/babyjub"
) )
@ -14,3 +15,36 @@ type AccountCreationAuth struct {
Signature []byte `meddler:"signature"` Signature []byte `meddler:"signature"`
Timestamp time.Time `meddler:"timestamp,utctime"` Timestamp time.Time `meddler:"timestamp,utctime"`
} }
// HashToSign builds the hash to be signed using BJJ pub key and the constant message
func (a *AccountCreationAuth) HashToSign() ([]byte, error) {
// Calculate message to be signed
const msg = "I authorize this babyjubjub key for hermez rollup account creation"
comp, err := a.BJJ.Compress().MarshalText()
if err != nil {
return nil, err
}
// Hash message (msg || compressed-bjj)
return ethCrypto.Keccak256Hash([]byte(msg), comp).Bytes(), nil
}
// VerifySignature ensures that the Signature is done with the specified EthAddr
func (a *AccountCreationAuth) VerifySignature() bool {
// Calculate hash to be signed
msg, err := a.HashToSign()
if err != nil {
return false
}
// Get public key from Signature
pubKBytes, err := ethCrypto.Ecrecover(msg, a.Signature)
if err != nil {
return false
}
pubK, err := ethCrypto.UnmarshalPubkey(pubKBytes)
if err != nil {
return false
}
// Get addr from pubK
addr := ethCrypto.PubkeyToAddress(*pubK)
return addr == a.EthAddr
}

+ 7
- 1
db/l2db/l2db.go

@ -46,7 +46,13 @@ func (l2db *L2DB) DB() *sqlx.DB {
// AddAccountCreationAuth inserts an account creation authorization into the DB // AddAccountCreationAuth inserts an account creation authorization into the DB
func (l2db *L2DB) AddAccountCreationAuth(auth *common.AccountCreationAuth) error { func (l2db *L2DB) AddAccountCreationAuth(auth *common.AccountCreationAuth) error {
return meddler.Insert(l2db.db, "account_creation_auth", auth)
// return meddler.Insert(l2db.db, "account_creation_auth", auth)
_, err := l2db.db.Exec(
`INSERT INTO account_creation_auth (eth_addr, bjj, signature)
VALUES ($1, $2, $3);`,
auth.EthAddr, auth.BJJ, auth.Signature,
)
return err
} }
// GetAccountCreationAuth returns an account creation authorization into the DB // GetAccountCreationAuth returns an account creation authorization into the DB

+ 23
- 12
test/l2db.go

@ -1,11 +1,7 @@
package test package test
import ( import (
"math/big"
"strconv"
"time"
ethCommon "github.com/ethereum/go-ethereum/common"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/common"
"github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/babyjub"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@ -84,13 +80,28 @@ func GenPoolTxs(n int, tokens []common.Token) []*common.PoolL2Tx {
func GenAuths(nAuths int) []*common.AccountCreationAuth { func GenAuths(nAuths int) []*common.AccountCreationAuth {
auths := []*common.AccountCreationAuth{} auths := []*common.AccountCreationAuth{}
for i := 0; i < nAuths; i++ { for i := 0; i < nAuths; i++ {
privK := babyjub.NewRandPrivKey()
auths = append(auths, &common.AccountCreationAuth{
EthAddr: ethCommon.BigToAddress(big.NewInt(int64(i))),
BJJ: privK.Public(),
Signature: []byte(strconv.Itoa(i)),
Timestamp: time.Now(),
})
// Generate keys
ethPrivK, err := ethCrypto.GenerateKey()
if err != nil {
panic(err)
}
bjjPrivK := babyjub.NewRandPrivKey()
// Generate auth
auth := &common.AccountCreationAuth{
EthAddr: ethCrypto.PubkeyToAddress(ethPrivK.PublicKey),
BJJ: bjjPrivK.Public(),
}
// Sign
h, err := auth.HashToSign()
if err != nil {
panic(err)
}
signature, err := ethCrypto.Sign(h, ethPrivK)
if err != nil {
panic(err)
}
auth.Signature = signature
auths = append(auths, auth)
} }
return auths return auths
} }

+ 15
- 0
test/l2db_test.go

@ -0,0 +1,15 @@
package test
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGenAuths(t *testing.T) {
const nAuths = 5
auths := GenAuths(nAuths)
for _, auth := range auths {
assert.True(t, auth.VerifySignature())
}
}

Loading…
Cancel
Save