Vocdoni blind-csp is a modular API backend for Certification Service Providers (CSP) using Blind signatures (among others).
Blind signatures were first suggested by David Chaum: a cryptographic scheme that allows for signatures over disguised (blinded) messages. The blinder (voter in our scenario) can then un-blind the signature and use it as a normal/standard one. This protocol was designed for RSA, but we use it over the EC secp256k1.
The API server supports x509 certificates for client authentication so it is a convinient way for authenticating standard/official certificates while preserving the privacy.
Its design makes very easy to write new authentication handlers such as the ones found in the handlers/
directory. A pretty useful use case is to authenticate via SMS (already supported) but there are other pretty cool handlers that can be implemented such as authentication via Discord, Twitter or E-residency cards.
The CSP server cannot see the payload of what is being signed (it is blinded) and since the server cannot see what is signing a valid signature proof provided by the CSP might be reused or requested for a different validation process. This is not something is desired to happen because the voter can then vote on processes where is not allowed to.
For making the CSP voter approval valid only for a specific process (identified by a 20 bytes word: electionId), a deterministic key derivation is used. So the CSP is only required to publish a single root public key. The specific per-process keys will be computed independently by all parties (CSP will derive its election private key and the process organizers will derive the election public key).
To this end we use the following simple approach (G is the EC generator):
PubKeyRootCSP = PrivKeyRootCSP * G
PrivKey2 = PrivkeyRootCSP + electionId
PubKey2 = PubKeyRootCSP + electionId
So if PubKey2 becomes the election CSP public key, there is no way the CSP can share signatures before the electionId is known and there is no way to reuse a CSP signature for a different election process.
The HTTP(s) API is very minimalistic and the handler implements the method for authentication which is one of the core parts of the CSP. Let's see some examples using the Simple Math handler that requires the user to solve a simple math challenge.
The handler requires a two steps authentication process:
- Requires a name and replies with the challenge (["123","200"])
- Requires the challenge solution (["323"])
The info
endpoint provides the description of the authentication handler.
The authType
parameter indicates the kind of authentication is required by the CSP.
Curently only auth
as authType is supported, but oauth
or others might be added in the future.
The signatureType
is a string array containing the list of supported signature types (used as the CSP proof).
Currently the supported signature types are blind
for ECDSA blind signature, ecdsa
for plain ECDSA signature over an arbitrary payload,
and sharedkey
for fetching a shared secret (signature of electionId).
The authSteps
object array describes the authentication steps and its parameters for a given authentication handler.
So in the following example there are two steps (size of the object array), the first one requires a
text field named name
. The second a 4 digits integer named solution
.
curl http://127.0.0.1:5000/v1/auth/elections/info
{
"title": "Simple math challenge",
"authType": "auth",
"signatureType": ["blind","ecdsa","sharedkey"],
"authSteps": [
{
"title": "name",
"type": "text"
},
{
"title": "solution",
"type": "int4"
}
]
}
The endpoint blind/auth/<step>
, where step is a 32 byte integer, handles the authentication steps for the handler.
The client needs to perform all steps (in our case 2) starting from 0, successfully and serially.
An authToken
is provided by the CSP in order to identify the client
in the following steps.
An array of strings named response
might be returned by the handler if the client
requires some data for performing the next step. In our case the challenge numbers that
must be sum by the client.
- Request
curl -s 127.0.0.1:5000/v1/auth/elections/A9893a41fc7046d66d39fdc073ed901af6bec66ecc070a97f9cb2dda02b11265/blind/auth/0 -X POST -d '{"authData":["John Smith"]}'
- Response OK
{
"authToken": "9ba29669-3a38-43ac-a8f6-d6ac99d2e3a2",
"response": [
"141",
"484"
]
}
- Response Error
{
"error": "Error invalid name"
}
In the final step, if the authentication challenge is resolved, the CSP returns token
, the data
that can be used by the client to prepare and ask for the signature. In our case the signature is
of type blind
so the token is the curve point R
required for blinding the payload.
- Request
# for the authData the string 574 is passed as it is the sum of 141 and 484 as integers
curl -s 127.0.0.1:5000/v1/auth/elections/A9893a41fc7046d66d39fdc073ed901af6bec66ecc070a97f9cb2dda02b11265/ecdsa/auth/1 -X POST -d '{"authToken":"8b16df36-9720-487f-b3eb-a46dfdebdb36", "authData":["574"]}'
- Response OK
{
"token": "0d2347cf59313bdb4038f0c6643e9289d694c1c67d4d1d66f56968e374d48669"
}
- Response Error
{
"error": "Message goes here"
}
Is the signature performed by the CSP. The payload to sign is usually an ephemeral ECDSA public key that the client creates for performing the vote for a specific voting process, but can also be any kind of privacy preserving digital ID.
- Request
curl -X POST https://server.foo/v1/auth/processes/12345.../blind/sign -d '{ "payload": "0xabcdef...", "token": "0x123bcde..." }'
- Response OK
{
"signature": "0x1234567890abcde..." // the blind signature
}
- Response Error
{
"error": "Invalid token"
}
The shared key is a common key for all users belonging to the same electionId. It might be used as shared key for encrypting process data so only the users that are able to authenticate can decrypt the data.
The shared key is the ECDSA salted signature of a keccak256 hash of a given electionId.
The sharedkey endpoint requires the same authentication steps described by the info
method.
However the handler might apply different restrictions such as allow the authentication succeed more
than one time.
- Request
curl -s 127.0.0.1:5000/v1/auth/elections/A9893a41fc7046d66d39fdc073ed901af6bec66ecc070a97f9cb2dda02b11265/sharedkey/0 -X POST -d '{"authData":["John Smith"]}'
- Response OK
{
"authToken":"12ab5ec4-bfc5-4dd1-896f-46ae06b15e81",
"response":["232","333"]
}
- Response Error
{
"error": "Message goes here"
}
- Request
curl -s 127.0.0.1:5000/v1/auth/elections/A9893a41fc7046d66d39fdc073ed901af6bec66ecc070a97f9cb2dda02b11265/sharedkey/1 -X POST -d '{"authToken":"12ab5ec4-bfc5-4dd1-896f-46ae06b15e81", "authData":["565"]}'
- Response OK
{
"sharedkey": "a6d7b59f5f6dfff418464c3fa2895ad872d402bda6e85f1ba62fe6f50f703ea87247ca8bf34a00a15cd768ba44cd6c99044a2ff4b6f837f77c243102872f03c101"
}
- Response Error
{
"error": "invalid authData"
}
Some handlers might enable an indexer in order to let the user know the list of elections where he is eligible for participating. The indexer endpoint takes as URL parameter a unique identifier by the user (hexadecimal string format) and returns the list of election identifiers (if any).
- Request
curl http://127.0.0.1:5000/v1/auth/elections/indexer/a216bc43310f46d66d39fdc073ed901af6bec66ecc070a97f9cb2dda01ba0241
- Response Ok
{
"elections": [
{
"electionId": "2222222222222222222222222222222222222222222222222222222222222222",
"remainingAttempts": 5,
"consumed": false,
"extra": [
"41"
]
},
{
"electionId": "1111111111111111111111111111111111111111111111111111111111111111",
"remainingAttempts": 5,
"consumed": false,
"extra": [
"41"
]
}
]
}
- Response Error
{
"error": "user not found"
}
See the test.sh
file for a full flow example.
$ go run . --loglevel=debug --handler=simpleMath
Using path /home/user/.blindcsp
2022-06-17T16:29:19+02:00 INFO blind-csp/main.go:124 logger construction succeeded at level debug with output stdout
2022-06-17T16:29:19+02:00 INFO blind-csp/main.go:142 using ECDSA signer with address 0x5D7Ad549556B40E05ef7576B26b368c824263B30
2022-06-17T16:29:19+02:00 INFO blind-csp/main.go:157 using handler simpleMath
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:150 starting go-chi http server
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:164 router ready at http://[::]:5000
2022-06-17T16:29:19+02:00 INFO blind-csp/main.go:186 CSP root public key: 02c5d98b525d844440f16d4e0492dc8e4c8188ab00ed3d4bb104365280db8a9252
2022-06-17T16:29:19+02:00 DEBUG csp/csp.go:52 initializing persistent storage on /home/p4u/.blindcsp/simpleMath
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:178 added namespace bearerStd
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:220 added public handler for namespace bearerStd with pattern /v1/auth/elections/ping
2022-06-17T16:29:19+02:00 INFO bearerstdapi/bearerstdapi.go:160 registered GET public method for path /v1/auth/elections/ping
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:220 added public handler for namespace bearerStd with pattern /v1/auth/elections/info
2022-06-17T16:29:19+02:00 INFO bearerstdapi/bearerstdapi.go:160 registered GET public method for path /v1/auth/elections/info
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:220 added public handler for namespace bearerStd with pattern /v1/auth/elections/{electionId}/{signType}/auth/{step}
2022-06-17T16:29:19+02:00 INFO bearerstdapi/bearerstdapi.go:160 registered POST public method for path /v1/auth/elections/{electionId}/{signType}/auth/{step}
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:220 added public handler for namespace bearerStd with pattern /v1/auth/elections/{electionId}/{signType}/auth
2022-06-17T16:29:19+02:00 INFO bearerstdapi/bearerstdapi.go:160 registered POST public method for path /v1/auth/elections/{electionId}/{signType}/auth
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:220 added public handler for namespace bearerStd with pattern /v1/auth/elections/{electionId}/{signType}/sign
2022-06-17T16:29:19+02:00 INFO bearerstdapi/bearerstdapi.go:160 registered POST public method for path /v1/auth/elections/{electionId}/{signType}/sign
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:220 added public handler for namespace bearerStd with pattern /v1/auth/elections/{electionId}/sharedkey/{step}
2022-06-17T16:29:19+02:00 INFO bearerstdapi/bearerstdapi.go:160 registered POST public method for path /v1/auth/elections/{electionId}/sharedkey/{step}
2022-06-17T16:29:19+02:00 INFO httprouter/httprouter.go:220 added public handler for namespace bearerStd with pattern /v1/auth/elections/{electionId}/sharedkey
2022-06-17T16:29:19+02:00 INFO bearerstdapi/bearerstdapi.go:160 registered POST public method for path /v1/auth/elections/{electionId}/sharedkey
$ go run . --help
--baseURL string base URL path for serving the API (default "/v1/auth")
--dataDir string datadir for storing files and config (default "/home/user/.blindcsp")
--domain string domain name for tls with letsencrypt (port 443 must be forwarded)
--handler string the authentication handler to use, available: {dummy uniqueIp idCat rsa} (default "dummy")
--handlerOpts strings options that will be passed to the handler
--key string private CSP key as hexadecimal string (leave empty for autogenerate)
--logLevel string log level {debug,info,warn,error} (default "info")
--port int port to listen (default 5000)
- H. Mala, N. Nezhadansari, "New Blind Signature Schemes Based on the (Elliptic Curve) Discrete Logarithm Problem" https://sci-hub.st/10.1109/iccke.2013.6682844 Implementation: https://github.com/arnaucube/go-blindsecp256k1