Skip to content

Commit

Permalink
Add: eventID check, endpoint to get airdrop, new fields for tx handling
Browse files Browse the repository at this point in the history
  • Loading branch information
violog committed Apr 19, 2024
1 parent b0dad69 commit 56be93c
Show file tree
Hide file tree
Showing 14 changed files with 263 additions and 41 deletions.
5 changes: 0 additions & 5 deletions docs/spec/components/responses/invalidAuth.yaml

This file was deleted.

32 changes: 32 additions & 0 deletions docs/spec/components/schemas/Airdrop.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
allOf:
- $ref: '#/components/schemas/AirdropKey'
- type: object
required:
- attributes
properties:
attributes:
type: object
required:
- address
- status
- created_at
- updated_at
properties:
address:
type: string
description: Destination address for the airdrop
example: "rarimo1qlyq3ej7j7rrkw6sluz658pzne88ymf66vjcap"
status:
type: string
description: Status of the airdrop transaction
enum: [ pending, completed ]
created_at:
type: string
format: time.Time
description: RFC3339 UTC timestamp of the airdrop creation
example: "2021-09-01T00:00:00Z"
updated_at:
type: string
format: time.Time
description: RFC3339 UTC timestamp of the airdrop successful tx
example: "2021-09-01T00:00:00Z"
12 changes: 12 additions & 0 deletions docs/spec/components/schemas/AirdropKey.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type: object
required:
- id
- type
properties:
id:
type: string
description: User nullifier
example: "0x04a32216f2425dc7343031352de3d62a7b0d3b4bf7a66d6c8c2aa8c9f4f2632b"
type:
type: string
enum: [ airdrop ]
19 changes: 13 additions & 6 deletions docs/spec/paths/integrations@[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@ post:
data:
$ref: '#/components/schemas/CreateAirdrop'
responses:
204:
description: No content
401:
$ref: '#/components/responses/invalidAuth'
404:
$ref: '#/components/responses/notFound'
201:
description: Airdrop was created, transaction was queued
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/Airdrop'
400:
$ref: '#/components/responses/invalidParameter'
409:
description: Airdrop was already done
content:
Expand Down
37 changes: 37 additions & 0 deletions docs/spec/paths/integrations@airdrop-svc@airdrops@{id}.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
get:
tags:
- Airdrop
summary: Create airdrop
description: Create an airdrop for unique user. The proof will be verified.
operationId: createAirdrop
parameters:
- in: path
name: id
description: User nullifier
required: true
schema:
type: string
example: "0x04a32216f2425dc7343031352de3d62a7b0d3b4bf7a66d6c8c2aa8c9f4f2632b"
responses:
200:
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/Airdrop'
400:
$ref: '#/components/responses/invalidParameter'
404:
$ref: '#/components/responses/notFound'
409:
description: Airdrop was already done
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
500:
$ref: '#/components/responses/internalError'
7 changes: 6 additions & 1 deletion internal/assets/migrations/001_initial.sql
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
-- +migrate Up
CREATE TYPE tx_status AS ENUM ('pending', 'completed');

CREATE TABLE participants
(
nullifier text PRIMARY KEY,
address text NOT NULL,
created_at timestamp without time zone NOT NULL default NOW()
status tx_status NOT NULL,
created_at timestamp without time zone NOT NULL default NOW(),
updated_at timestamp without time zone NOT NULL default NOW()
);

-- +migrate Down
DROP TABLE participants;
DROP TYPE tx_status;
23 changes: 16 additions & 7 deletions internal/data/participants.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ import (
"gitlab.com/distributed_lab/kit/pgdb"
)

const (
TxStatusPending = "pending"
TxStatusCompleted = "completed"
)

const participantsTable = "participants"

type Participant struct {
Nullifier string `db:"nullifier"`
Address string `db:"address"`
Status string `db:"status"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"created_at"`
}

type ParticipantsQ struct {
Expand All @@ -34,17 +41,19 @@ func (q *ParticipantsQ) New() *ParticipantsQ {
return NewParticipantsQ(q.db)
}

func (q *ParticipantsQ) Insert(nullifier, address string) error {
func (q *ParticipantsQ) Insert(p Participant) (*Participant, error) {
var res Participant
stmt := squirrel.Insert(participantsTable).SetMap(map[string]interface{}{
"nullifier": nullifier,
"address": address,
})
"nullifier": p.Nullifier,
"address": p.Address,
"status": p.Status,
}).Suffix("RETURNING *")

if err := q.db.Exec(stmt); err != nil {
return fmt.Errorf("insert participant %s: %w", nullifier, err)
if err := q.db.Get(&res, stmt); err != nil {
return nil, fmt.Errorf("insert participant %+v: %w", p, err)
}

return nil
return &res, nil
}

func (q *ParticipantsQ) Transaction(fn func() error) error {
Expand Down
34 changes: 18 additions & 16 deletions internal/service/handlers/create_airdrop.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/iden3/go-rapidsnark/verifier"
"github.com/rarimo/airdrop-svc/internal/config"
data "github.com/rarimo/airdrop-svc/internal/data"
"github.com/rarimo/airdrop-svc/internal/service/requests"
"github.com/rarimo/airdrop-svc/resources"
"gitlab.com/distributed_lab/ape"
Expand All @@ -20,12 +21,9 @@ import (
// https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set1_sigalgs_list.html

const (
SHA1 = "sha1"
SHA256 = "sha256"

SHA256withRSA = "SHA256withRSA"
SHA1withECDSA = "SHA1withECDSA"
SHA256withECDSA = "SHA256withECDSA"
sha256rsa = "SHA256withRSA"
sha1ecdsa = "SHA1withECDSA"
sha256ecdsa = "SHA256withECDSA"
)

func CreateAirdrop(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -57,7 +55,11 @@ func CreateAirdrop(w http.ResponseWriter, r *http.Request) {
}

err = ParticipantsQ(r).Transaction(func() error {
err = ParticipantsQ(r).Insert(nullifier, req.Data.Attributes.Address)
participant, err = ParticipantsQ(r).Insert(data.Participant{
Nullifier: nullifier,
Address: req.Data.Attributes.Address,
Status: data.TxStatusPending,
})
if err != nil {
return fmt.Errorf("insert participant: %w", err)
}
Expand All @@ -70,17 +72,17 @@ func CreateAirdrop(w http.ResponseWriter, r *http.Request) {
return
}

w.WriteHeader(http.StatusNoContent)
ape.Render(w, toAirdropResponse(*participant))
}

func verifyProof(req resources.CreateAirdropRequest, cfg *config.VerifierConfig) error {
var key []byte
algorithm := signatureAlgorithm(req.Data.Attributes.Algorithm)
switch algorithm {
case SHA1withECDSA:
key = cfg.VerificationKeys[SHA1]
case SHA256withRSA, SHA256withECDSA:
key = cfg.VerificationKeys[SHA256]
case sha1ecdsa:
key = cfg.VerificationKeys["sha1"]
case sha256rsa, sha256ecdsa:
key = cfg.VerificationKeys["sha256"]
default:
return fmt.Errorf("unsupported algorithm: %s", req.Data.Attributes.Algorithm)
}
Expand All @@ -95,17 +97,17 @@ func verifyProof(req resources.CreateAirdropRequest, cfg *config.VerifierConfig)

var algorithmsMap = map[string]map[string]string{
"SHA1": {
"ECDSA": SHA1withECDSA,
"ECDSA": sha1ecdsa,
},
"SHA256": {
"RSA": SHA256withRSA,
"ECDSA": SHA256withECDSA,
"RSA": sha256rsa,
"ECDSA": sha256ecdsa,
},
}

func signatureAlgorithm(passedAlgorithm string) string {
if passedAlgorithm == "rsaEncryption" {
return SHA256withRSA
return sha256rsa
}

if strings.Contains(strings.ToUpper(passedAlgorithm), "PSS") {
Expand Down
53 changes: 53 additions & 0 deletions internal/service/handlers/get_airdrop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package handlers

import (
"net/http"

"github.com/go-chi/chi"
validation "github.com/go-ozzo/ozzo-validation/v4"
data "github.com/rarimo/airdrop-svc/internal/data"
"github.com/rarimo/airdrop-svc/resources"
"gitlab.com/distributed_lab/ape"
"gitlab.com/distributed_lab/ape/problems"
)

func GetAirdrop(w http.ResponseWriter, r *http.Request) {
var (
id = chi.URLParam(r, "id")
err error = validation.Errors{"{id}": validation.Validate(id, validation.Required)}
)
if err != nil {
ape.RenderErr(w, problems.BadRequest(err)...)
return
}

participant, err := ParticipantsQ(r).Get(id)
if err != nil {
Log(r).WithError(err).Error("Failed to get participant by ID")
ape.RenderErr(w, problems.InternalError())
return
}
if participant == nil {
ape.RenderErr(w, problems.NotFound())
return
}

ape.Render(w, toAirdropResponse(*participant))
}

func toAirdropResponse(p data.Participant) resources.AirdropResponse {
return resources.AirdropResponse{
Data: resources.Airdrop{
Key: resources.Key{
ID: p.Nullifier,
Type: resources.AIRDROP,
},
Attributes: resources.AirdropAttributes{
Address: p.Address,
Status: p.Status,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
},
},
}
}
15 changes: 11 additions & 4 deletions internal/service/requests/create_airdrop.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package requests
import (
"encoding/json"
"fmt"
"math/big"
"net/http"
"time"

Expand All @@ -17,10 +18,12 @@ const (
pubSignalBirthDate
pubSignalExpirationDate
pubSignalCitizenship = 6
pubSignalEventID = 9
pubSignalEventData = 10
pubSignalSelector = 12

proofSelectorValue = "39"
proofEventIDValue = "ac42d1a986804618c7a793fbe814d9b31e47be51e082806363dca6958f3062"
)

func NewCreateAirdrop(r *http.Request, cfg *config.VerifierConfig) (req resources.CreateAirdropRequest, err error) {
Expand All @@ -45,17 +48,21 @@ func NewCreateAirdrop(r *http.Request, cfg *config.VerifierConfig) (req resource
return req, err
}

addrBytes, _ := types.AccAddressFromBech32(attr.Address)
addrDec := encodeInt(addrBytes)
citizenship := decodeInt(signals[pubSignalCitizenship])
var (
addrBytes, _ = types.AccAddressFromBech32(attr.Address)
addrDec = encodeInt(addrBytes)
citizenship = decodeInt(signals[pubSignalCitizenship])
eventID, _ = new(big.Int).SetString(signals[pubSignalEventID], 10)
)

return req, val.Errors{
"pub_signals/nullifier": val.Validate(signals[PubSignalNullifier], val.Required),
"pub_signals/selector": val.Validate(signals[pubSignalSelector], val.Required, val.In(proofSelectorValue)),
"pub_signals/expiration_date": val.Validate(signals[pubSignalExpirationDate], val.Required, afterDate(time.Now().UTC())),
"pub_signals/birth_date": val.Validate(signals[pubSignalBirthDate], val.Required, beforeDate(olderThanDate)),
"pub_signals/citizenship": val.Validate(citizenship, val.Required, val.In(cfg.AllowedCitizenships...)),
"pub_signals/event_data": val.Validate(signals[pubSignalEventData], val.Required, val.In(addrDec, "0x"+addrDec)),
"pub_signals/event_id": val.Validate(eventID.Text(16), val.Required, val.In(proofEventIDValue)),
"pub_signals/event_data": val.Validate(signals[pubSignalEventData], val.Required, val.In(addrDec)),
}.Filter()
}

Expand Down
5 changes: 3 additions & 2 deletions internal/service/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ func Run(ctx context.Context, cfg *config.Config) {
),
handlers.DBCloneMiddleware(cfg.DB()),
)
r.Route("/integrations/airdrop-svc", func(r chi.Router) {
r.Post("/airdrops", handlers.CreateAirdrop)
r.Route("/integrations/airdrop-svc/airdrops", func(r chi.Router) {
r.Post("/", handlers.CreateAirdrop)
r.Get("/{id}", handlers.GetAirdrop)
})

cfg.Log().Info("Service started")
Expand Down
Loading

0 comments on commit 56be93c

Please sign in to comment.