tapir/primitives/privacypass/tokenserver.go

83 lines
3.0 KiB
Go
Raw Normal View History

2019-09-14 23:44:19 +00:00
package privacypass
import (
"crypto/hmac"
"cwtch.im/tapir/primitives/core"
"encoding/hex"
"fmt"
2019-09-15 05:50:06 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/log"
2019-09-14 23:44:19 +00:00
"github.com/bwesterb/go-ristretto"
"golang.org/x/crypto/sha3"
2019-09-15 05:50:06 +00:00
"sync"
2019-09-14 23:44:19 +00:00
)
// TokenServer implements a token server.
type TokenServer struct {
2019-09-15 05:50:06 +00:00
k *ristretto.Scalar
Y *ristretto.Point
seen map[string]bool
mutex sync.Mutex
2019-09-14 23:44:19 +00:00
}
// NewTokenServer generates a new TokenServer (used mostly for testing with ephemeral instances)
func NewTokenServer() TokenServer {
k := new(ristretto.Scalar).Rand()
2019-09-15 05:50:06 +00:00
return TokenServer{k, new(ristretto.Point).ScalarMultBase(k), make(map[string]bool), sync.Mutex{}}
2019-09-14 23:44:19 +00:00
}
// SignBlindedToken calculates kP for the given BlindedToken P
func (ts *TokenServer) SignBlindedToken(bt BlindedToken) SignedToken {
Q := new(ristretto.Point).ScalarMult(bt.P, ts.k)
return SignedToken{Q}
}
// SignBlindedTokenBatch signs a batch of blinded tokens under a given transcript
func (ts *TokenServer) SignBlindedTokenBatch(blindedTokens []BlindedToken, transcript *core.Transcript) (signedTokens []SignedToken, proof DLEQProof) {
for _, bt := range blindedTokens {
signedTokens = append(signedTokens, ts.SignBlindedToken(bt))
}
return signedTokens, ts.constructBatchProof(blindedTokens, signedTokens, transcript)
}
// constructBatchProof construct a batch proof that all the signed tokens have been signed correctly
func (ts *TokenServer) constructBatchProof(blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) DLEQProof {
transcript.AddToTranscript("X", new(ristretto.Point).SetBase().Bytes())
transcript.AddToTranscript("Y", ts.Y.Bytes())
transcript.AddToTranscript("P[]", []byte(fmt.Sprintf("%v", blindedTokens)))
transcript.AddToTranscript("Q[]", []byte(fmt.Sprintf("%v", signedTokens)))
prng := transcript.CommitToPRNG("w")
M := new(ristretto.Point).SetZero()
Z := new(ristretto.Point).SetZero()
for i := range blindedTokens {
c := prng.Next()
M = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(blindedTokens[i].P, c), M)
Z = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(signedTokens[i].Q, c), Z)
}
return DiscreteLogEquivalenceProof(ts.k, new(ristretto.Point).SetBase(), ts.Y, M, Z, transcript)
}
// IsValid returns true a SpentToken is valid and has never been spent before, false otherwise.
func (ts *TokenServer) IsValid(token SpentToken, data []byte) bool {
2019-09-15 05:50:06 +00:00
log.Debugf("data: [%s]", data)
ts.mutex.Lock()
defer ts.mutex.Unlock() // We only want 1 client at a time redeeming tokens to prevent double-spends
if _, spent := ts.seen[hex.EncodeToString(token.T)]; spent {
2019-09-14 23:44:19 +00:00
return false
}
2019-09-15 05:50:06 +00:00
Ht := sha3.Sum256(token.T)
log.Debugf("token: %x", Ht)
2019-09-14 23:44:19 +00:00
T := new(ristretto.Point).SetElligator(&Ht)
W := new(ristretto.Point).ScalarMult(T, ts.k)
2019-09-15 05:50:06 +00:00
key := sha3.Sum256(append(token.T, W.Bytes()...))
2019-09-14 23:44:19 +00:00
mac := hmac.New(sha3.New512, key[:])
K := mac.Sum(data)
2019-09-15 05:50:06 +00:00
log.Debugf("mac: \n%x\nK:%x\n", token.MAC, K)
2019-09-14 23:44:19 +00:00
result := hmac.Equal(token.MAC, K)
if result == true {
2019-09-15 05:50:06 +00:00
ts.seen[hex.EncodeToString(token.T)] = true
2019-09-14 23:44:19 +00:00
}
return result
}