75 lines
2.7 KiB
Go
75 lines
2.7 KiB
Go
|
package privacypass
|
||
|
|
||
|
import (
|
||
|
"crypto/hmac"
|
||
|
"cwtch.im/tapir/primitives/core"
|
||
|
"encoding/hex"
|
||
|
"fmt"
|
||
|
"github.com/bwesterb/go-ristretto"
|
||
|
"golang.org/x/crypto/sha3"
|
||
|
)
|
||
|
|
||
|
// TokenServer implements a token server.
|
||
|
type TokenServer struct {
|
||
|
k *ristretto.Scalar
|
||
|
Y *ristretto.Point
|
||
|
seen map[string]bool
|
||
|
}
|
||
|
|
||
|
// NewTokenServer generates a new TokenServer (used mostly for testing with ephemeral instances)
|
||
|
func NewTokenServer() TokenServer {
|
||
|
k := new(ristretto.Scalar).Rand()
|
||
|
return TokenServer{k, new(ristretto.Point).ScalarMultBase(k), make(map[string]bool)}
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
if _, spent := ts.seen[hex.EncodeToString(token.t)]; spent {
|
||
|
return false
|
||
|
}
|
||
|
Ht := sha3.Sum256(token.t)
|
||
|
T := new(ristretto.Point).SetElligator(&Ht)
|
||
|
W := new(ristretto.Point).ScalarMult(T, ts.k)
|
||
|
key := sha3.Sum256(append(token.t, W.Bytes()...))
|
||
|
mac := hmac.New(sha3.New512, key[:])
|
||
|
K := mac.Sum(data)
|
||
|
result := hmac.Equal(token.MAC, K)
|
||
|
if result == true {
|
||
|
ts.seen[hex.EncodeToString(token.t)] = true
|
||
|
}
|
||
|
return result
|
||
|
}
|