Auditable Store
This commit is contained in:
parent
ac6e44b09a
commit
5b64c2d708
|
@ -1,7 +1,12 @@
|
||||||
package tapir
|
package tapir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/tapir/primitives/core"
|
||||||
|
)
|
||||||
|
|
||||||
// Application defines the interface for all Tapir Applications
|
// Application defines the interface for all Tapir Applications
|
||||||
type Application interface {
|
type Application interface {
|
||||||
NewInstance() Application
|
NewInstance() Application
|
||||||
Init(connection Connection)
|
Init(connection Connection)
|
||||||
|
Transcript() *core.Transcript
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ const AuthCapability = "AUTH"
|
||||||
|
|
||||||
// AuthApp is the concrete Application type that handles Authentication
|
// AuthApp is the concrete Application type that handles Authentication
|
||||||
type AuthApp struct {
|
type AuthApp struct {
|
||||||
|
TranscriptApp
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInstance creates a new instance of the AuthApp
|
// NewInstance creates a new instance of the AuthApp
|
||||||
|
@ -30,7 +31,8 @@ func (ea AuthApp) NewInstance() tapir.Application {
|
||||||
|
|
||||||
// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability
|
// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability
|
||||||
// or the connection is closed.
|
// or the connection is closed.
|
||||||
func (ea AuthApp) Init(connection tapir.Connection) {
|
func (ea *AuthApp) Init(connection tapir.Connection) {
|
||||||
|
ea.TranscriptApp.Init(connection)
|
||||||
longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
|
longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
|
||||||
ephemeralIdentity, _ := primitives.InitializeEphemeralIdentity()
|
ephemeralIdentity, _ := primitives.InitializeEphemeralIdentity()
|
||||||
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
|
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
|
||||||
|
@ -80,8 +82,8 @@ func (ea AuthApp) Init(connection tapir.Connection) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive a challenge from the transcript of the public parameters of this authentication protocol
|
// Derive a challenge from the transcript of the public parameters of this authentication protocol
|
||||||
var transcript *primitives.Transcript
|
transcript := ea.Transcript()
|
||||||
transcript = primitives.NewTranscript("tapir-auth-" + outboundHostname + "-" + inboundHostname)
|
transcript.AddToTranscript("auth-protocol", []byte(outboundHostname+"-"+inboundHostname))
|
||||||
transcript.AddToTranscript("outbound-challenge", outboundAuthMessage)
|
transcript.AddToTranscript("outbound-challenge", outboundAuthMessage)
|
||||||
transcript.AddToTranscript("inbound-challenge", inboundAuthMessage)
|
transcript.AddToTranscript("inbound-challenge", inboundAuthMessage)
|
||||||
challengeBytes := transcript.CommitToTranscript("3dh-auth-challenge")
|
challengeBytes := transcript.CommitToTranscript("3dh-auth-challenge")
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package applications
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/tapir"
|
||||||
|
"cwtch.im/tapir/primitives/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TranscriptApp defines a Tapir Meta=App which provides a global cryptographic transcript
|
||||||
|
type TranscriptApp struct {
|
||||||
|
transcript *core.Transcript
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInstance creates a new TranscriptApp
|
||||||
|
func (TranscriptApp) NewInstance() tapir.Application {
|
||||||
|
ta := new(TranscriptApp)
|
||||||
|
return ta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes the cryptographic transcript
|
||||||
|
func (ta *TranscriptApp) Init(connection tapir.Connection) {
|
||||||
|
ta.transcript = core.NewTranscript("tapir-transcript")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transcript returns a pointer to the cryptographic transcript
|
||||||
|
func (ta *TranscriptApp) Transcript() *core.Transcript {
|
||||||
|
return ta.transcript
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package primitives
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"cwtch.im/tapir/primitives/core"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignedProof encapsulates a signed proof
|
||||||
|
type SignedProof []byte
|
||||||
|
|
||||||
|
// Message encapsulates a message for more readable code.
|
||||||
|
type Message []byte
|
||||||
|
|
||||||
|
// State defines an array of messages.
|
||||||
|
type State struct {
|
||||||
|
message []Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditableStore defines a cryptographically secure & auditable transcript of messages sent from multiple
|
||||||
|
// unrelated clients to a server.
|
||||||
|
type AuditableStore struct {
|
||||||
|
state State
|
||||||
|
identity Identity
|
||||||
|
transcript *core.Transcript
|
||||||
|
latestCommit []byte
|
||||||
|
commits map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes an auditable store
|
||||||
|
func (as *AuditableStore) Init(identity Identity) {
|
||||||
|
as.identity = identity
|
||||||
|
as.transcript = core.NewTranscript("auditable-data-store")
|
||||||
|
as.commits = make(map[string]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a message to the auditable store
|
||||||
|
func (as *AuditableStore) Add(message Message, latestCommit []byte) ([]byte, SignedProof, error) {
|
||||||
|
if subtle.ConstantTimeCompare(latestCommit, as.latestCommit) == 1 {
|
||||||
|
as.state.message = append(as.state.message, message)
|
||||||
|
as.transcript.AddToTranscript("new-message", message)
|
||||||
|
as.latestCommit = as.identity.Sign(as.transcript.CommitToTranscript("commit"))
|
||||||
|
return as.latestCommit, as.identity.Sign(as.latestCommit), nil
|
||||||
|
}
|
||||||
|
// this prevents multiple clients updating at the same time and will likely cause retry storms.
|
||||||
|
return nil, nil, errors.New("attempt to append out of date transcript")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current auditable state
|
||||||
|
func (as *AuditableStore) GetState() (State, []byte, SignedProof) {
|
||||||
|
return as.state, as.latestCommit, as.identity.Sign(as.latestCommit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeState merges a given state onto our state, first verifying that the two transcripts align
|
||||||
|
func (as *AuditableStore) MergeState(state State, signedStateProof SignedProof, key ed25519.PublicKey) error {
|
||||||
|
next := len(as.state.message)
|
||||||
|
for _, m := range state.message[next:] {
|
||||||
|
as.state.message = append(as.state.message, m)
|
||||||
|
|
||||||
|
// We reconstruct the transcript
|
||||||
|
as.transcript.AddToTranscript("new-message", m)
|
||||||
|
as.latestCommit = as.identity.Sign(as.transcript.CommitToTranscript("commit"))
|
||||||
|
as.commits[base64.StdEncoding.EncodeToString(as.latestCommit)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that our state matches the servers signed state
|
||||||
|
// this is *not* a security check, as a rogue server can simply sign any state
|
||||||
|
// however committing to a state allows us to build fraud proofs for malicious servers later on.
|
||||||
|
if ed25519.Verify(key, as.latestCommit, signedStateProof) == false {
|
||||||
|
return errors.New("state is not consistent, the server is malicious")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyFraudProof - the main idea behind this is as follows:
|
||||||
|
//
|
||||||
|
// Every update requires the server to sign, and thus commit to, a transcript
|
||||||
|
// Clients reconstruct the transcript via MergeState, as such clients can keep track of every commit.
|
||||||
|
// if a client can present a signed transcript commit from the server that other clients do not have, it is proof
|
||||||
|
// that either 1) they are out of sync with the server or 2) the server is presenting different transcripts to different people
|
||||||
|
//
|
||||||
|
// If, after syncing, the FraudProof still validates, then the server must be malicious.
|
||||||
|
// the information revealed by publicizing a fraud proof is minimal it only reveals the inconsistent transcript commit
|
||||||
|
// and not the cause (which could be reordered messages, dropped messages, additional messages or any combination)
|
||||||
|
func (as *AuditableStore) VerifyFraudProof(commit []byte, signedFraudProof SignedProof, key ed25519.PublicKey) (bool, error) {
|
||||||
|
|
||||||
|
if ed25519.Verify(key, commit, signedFraudProof) == false {
|
||||||
|
// This could happen due to misuse of this function (trying to verify a proof with the wrong public key)
|
||||||
|
// This could happen if the server lies to us and submits a fake state proof, however we cannot use this to
|
||||||
|
// prove that the server is acting maliciously
|
||||||
|
return false, errors.New("signed proof has not been signed by the given public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := as.commits[base64.StdEncoding.EncodeToString(commit)]
|
||||||
|
if !exists {
|
||||||
|
// We have a message signed by the server which verifies that a message was inserted into the state at a given index
|
||||||
|
// However this directly contradicts our version of the state.
|
||||||
|
// There is still a possibility that we are out of sync with the server and that new messages have since been added
|
||||||
|
// We assume that the caller has first Merged the most recent state.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package primitives
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuditableStore(t *testing.T) {
|
||||||
|
as := new(AuditableStore)
|
||||||
|
vs := new(AuditableStore)
|
||||||
|
|
||||||
|
serverID, _ := InitializeEphemeralIdentity()
|
||||||
|
as.Init(serverID)
|
||||||
|
vs.Init(serverID) // This doesn't do anything
|
||||||
|
|
||||||
|
as.Add([]byte("Hello World"), as.latestCommit)
|
||||||
|
state, _, proof := as.GetState()
|
||||||
|
|
||||||
|
if vs.MergeState(state, proof, serverID.PublicKey()) != nil {
|
||||||
|
t.Fatalf("Fraud Proof Failed on Honest Proof")
|
||||||
|
}
|
||||||
|
|
||||||
|
commit, fraudProof, _ := as.Add([]byte("Hello World 2"), as.latestCommit)
|
||||||
|
|
||||||
|
// If you comment these out it simulates a lying server.
|
||||||
|
state, _, proof = as.GetState()
|
||||||
|
if vs.MergeState(state, proof, serverID.PublicKey()) != nil {
|
||||||
|
t.Fatalf("Fraud Proof Failed on Honest Proof")
|
||||||
|
}
|
||||||
|
|
||||||
|
fraud, err := vs.VerifyFraudProof(commit, fraudProof, serverID.PublicKey())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error validated fraud proof: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fraud {
|
||||||
|
t.Fatalf("Technically a fraud, but the client hasn't updated yet")
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ package primitives
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,32 +14,30 @@ type BloomFilter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init constructs a bloom filter of size m
|
// Init constructs a bloom filter of size m
|
||||||
func (bf *BloomFilter) Init(m int16) {
|
func (bf *BloomFilter) Init(m int64) {
|
||||||
bf.B = make([]bool, m)
|
bf.B = make([]bool, m)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash transforms a message to a set of bit flips
|
// Hash transforms a message to a set of bit flips
|
||||||
// Supports up to m == 65535
|
|
||||||
func (bf *BloomFilter) Hash(msg []byte) []int {
|
func (bf *BloomFilter) Hash(msg []byte) []int {
|
||||||
hash := sha256.Sum256(msg)
|
|
||||||
|
|
||||||
pos1a := (int(hash[0]) + int(hash[1]) + int(hash[2]) + int(hash[3])) % 0xFF
|
// Not the fastest hash function ever, but cryptographic security is more important than speed.
|
||||||
pos1b := (int(hash[4]) + int(hash[5]) + int(hash[6]) + int(hash[7])) % 0xFF
|
hash1 := sha256.Sum256(append([]byte("h1"), msg...))
|
||||||
pos1 := ((pos1a << 8) + pos1b) & (0xFFFF % len(bf.B))
|
hash2 := sha256.Sum256(append([]byte("h2"), msg...))
|
||||||
|
hash3 := sha256.Sum256(append([]byte("h3"), msg...))
|
||||||
|
hash4 := sha256.Sum256(append([]byte("h4"), msg...))
|
||||||
|
|
||||||
pos2a := (int(hash[8]) + int(hash[9]) + int(hash[10]) + int(hash[11])) % 0xFF
|
m := int64(len(bf.B))
|
||||||
pos2b := (int(hash[12]) + int(hash[13]) + int(hash[14]) + int(hash[15])) % 0xFF
|
// Number of bytes needed to pick a position from [0,m)
|
||||||
pos2 := ((pos2a << 8) + pos2b) & (0xFFFF % len(bf.B))
|
B := int(math.Ceil(math.Log2(float64(m)) / 8.0))
|
||||||
|
|
||||||
pos3a := (int(hash[16]) + int(hash[17]) + int(hash[18]) + int(hash[19])) % 0xFF
|
p1 := big.NewInt(0).SetBytes(hash1[:B]).Int64()
|
||||||
pos3b := (int(hash[20]) + int(hash[21]) + int(hash[22]) + int(hash[23])) % 0xFF
|
p2 := big.NewInt(0).SetBytes(hash2[:B]).Int64()
|
||||||
pos3 := ((pos3a << 8) + pos3b) & (0xFFFF % len(bf.B))
|
p3 := big.NewInt(0).SetBytes(hash3[:B]).Int64()
|
||||||
|
p4 := big.NewInt(0).SetBytes(hash4[:B]).Int64()
|
||||||
|
|
||||||
pos4a := (int(hash[24]) + int(hash[25]) + int(hash[26]) + int(hash[27])) % 0xFF
|
return []int{int(p1), int(p2), int(p3), int(p4)}
|
||||||
pos4b := (int(hash[28]) + int(hash[29]) + int(hash[30]) + int(hash[31])) % 0xFF
|
|
||||||
pos4 := ((pos4a << 8) + pos4b) & (0xFFFF % len(bf.B))
|
|
||||||
|
|
||||||
return []int{pos1, pos2, pos3, pos4}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert updates the BloomFilter (suitable for concurrent use)
|
// Insert updates the BloomFilter (suitable for concurrent use)
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package primitives
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBloomFilter_Insert(t *testing.T) {
|
||||||
|
bf := new(BloomFilter)
|
||||||
|
bf.Init(256)
|
||||||
|
|
||||||
|
fp := 0
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
input := []byte("test" + strconv.Itoa(256+i))
|
||||||
|
if bf.Check(input) {
|
||||||
|
t.Log("False Positive!")
|
||||||
|
fp++
|
||||||
|
}
|
||||||
|
bf.Insert(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Num false positives %v %v%%", fp, (float64(fp)/256.0)*100)
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package primitives
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/bwesterb/go-ristretto"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
"hash"
|
"hash"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transcript implements a transcript of a public coin argument.
|
// Transcript implements a transcript of a public coin argument.
|
||||||
|
@ -49,3 +51,33 @@ func (t *Transcript) CommitToTranscript(label string) []byte {
|
||||||
t.AddToTranscript(label, b)
|
t.AddToTranscript(label, b)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PRNG defines a psuedorandom number generator
|
||||||
|
type PRNG struct {
|
||||||
|
prng io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next "random" scalar from the PRNG
|
||||||
|
func (prng *PRNG) Next() *ristretto.Scalar {
|
||||||
|
buf := [32]byte{}
|
||||||
|
io.ReadFull(prng.prng, buf[:])
|
||||||
|
return new(ristretto.Scalar).SetBytes(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitToPRNG commits the label to the transcript and derives a PRNG from the transcript.
|
||||||
|
func (t *Transcript) CommitToPRNG(label string) PRNG {
|
||||||
|
t.AddToTranscript("commit-prng", []byte(label))
|
||||||
|
b := t.hash.Sum([]byte{})
|
||||||
|
t.AddToTranscript(label, b)
|
||||||
|
prng := sha3.NewShake256()
|
||||||
|
prng.Write(b)
|
||||||
|
return PRNG{prng: prng}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitToTranscriptScalar is a convenience method for CommitToTranscript which returns a ristretto Scalar
|
||||||
|
func (t *Transcript) CommitToTranscriptScalar(label string) *ristretto.Scalar {
|
||||||
|
c := t.CommitToTranscript(label)
|
||||||
|
cs := [32]byte{}
|
||||||
|
copy(cs[:], c[:])
|
||||||
|
return new(ristretto.Scalar).SetBytes(&cs)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewTranscript(t *testing.T) {
|
||||||
|
|
||||||
|
// Some very basic integrity checking
|
||||||
|
transcript := NewTranscript("label")
|
||||||
|
|
||||||
|
transcript.AddToTranscript("action", []byte("test data"))
|
||||||
|
|
||||||
|
if transcript.OutputTranscriptToAudit() != transcript.OutputTranscriptToAudit() {
|
||||||
|
t.Fatalf("Multiple Audit Calls should not impact underlying Transcript")
|
||||||
|
}
|
||||||
|
t.Logf("%v", transcript.OutputTranscriptToAudit())
|
||||||
|
t.Logf("%v", transcript.CommitToTranscript("first commit"))
|
||||||
|
t.Logf("%v", transcript.OutputTranscriptToAudit())
|
||||||
|
t.Logf("%v", transcript.CommitToTranscript("second commit"))
|
||||||
|
t.Logf("%v", transcript.OutputTranscriptToAudit())
|
||||||
|
|
||||||
|
transcript.AddToTranscript("action", []byte("test data"))
|
||||||
|
|
||||||
|
t.Logf("%v", transcript.CommitToTranscript("third commit"))
|
||||||
|
t.Logf("%v", transcript.OutputTranscriptToAudit())
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package primitives
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/tapir/primitives/core"
|
||||||
|
"github.com/bwesterb/go-ristretto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DLProof Encapsulates a Discrete Log / Schnorr Proof
|
||||||
|
// Note that these parameters are read-only.
|
||||||
|
type DLProof struct {
|
||||||
|
V, A ristretto.Point
|
||||||
|
R ristretto.Scalar
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscreteLogProof - Proof of Knowledge of Exponent
|
||||||
|
// Given V = xG
|
||||||
|
// Peggy: z := choose randomly from Zq
|
||||||
|
// A := zG
|
||||||
|
// c := H(transcript(G,V,A)) mod q
|
||||||
|
// r := (z + cx) mod q
|
||||||
|
//
|
||||||
|
// Sends A,r,V to Vicky
|
||||||
|
func DiscreteLogProof(x ristretto.Scalar, v ristretto.Point, transcript *core.Transcript) (proof DLProof) {
|
||||||
|
|
||||||
|
transcript.AddToTranscript("G", new(ristretto.Point).SetBase().Bytes())
|
||||||
|
|
||||||
|
// We bind the proof to our public V
|
||||||
|
proof.V = v
|
||||||
|
transcript.AddToTranscript("V", proof.V.Bytes())
|
||||||
|
|
||||||
|
// Generate a random z
|
||||||
|
// A := zG
|
||||||
|
z := new(ristretto.Scalar).Rand()
|
||||||
|
proof.A = *new(ristretto.Point).ScalarMultBase(z)
|
||||||
|
transcript.AddToTranscript("A", proof.A.Bytes())
|
||||||
|
|
||||||
|
// Derive Challenge
|
||||||
|
c := transcript.CommitToTranscriptScalar("c")
|
||||||
|
|
||||||
|
// r := (z + cx) mod p
|
||||||
|
cx := new(ristretto.Scalar).Mul(c, &x)
|
||||||
|
proof.R = *new(ristretto.Scalar).Add(z, cx)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyDiscreteLogProof validates a given Schnorr Proof
|
||||||
|
// Vicky gets A,r,V from Peggy
|
||||||
|
// Vicky computes c := H(transcript(G,V,A)) mod q
|
||||||
|
// Vicky checks rG := A + cV
|
||||||
|
// rG ?= zG + cV
|
||||||
|
// (z+cx)G ?= zG + cV
|
||||||
|
// ?= zG + cxG
|
||||||
|
// Thus demonstrating that Peggy knows the discrete log to V
|
||||||
|
func VerifyDiscreteLogProof(proof DLProof, transcript *core.Transcript) bool {
|
||||||
|
|
||||||
|
transcript.AddToTranscript("G", new(ristretto.Point).SetBase().Bytes())
|
||||||
|
transcript.AddToTranscript("V", proof.V.Bytes())
|
||||||
|
transcript.AddToTranscript("A", proof.A.Bytes())
|
||||||
|
c := transcript.CommitToTranscriptScalar("c")
|
||||||
|
|
||||||
|
// Compute left hand side
|
||||||
|
lhs := new(ristretto.Point).ScalarMultBase(&proof.R)
|
||||||
|
|
||||||
|
// Compute right hand side
|
||||||
|
cV := new(ristretto.Point).ScalarMult(&proof.V, c)
|
||||||
|
rhs := new(ristretto.Point).Add(&proof.A, cV)
|
||||||
|
|
||||||
|
// Result of verification: lhs ?= rhs
|
||||||
|
return lhs.Equals(rhs)
|
||||||
|
}
|
|
@ -41,7 +41,7 @@ func (i *Identity) PublicKey() ed25519.PublicKey {
|
||||||
return *i.edpubk
|
return *i.edpubk
|
||||||
}
|
}
|
||||||
|
|
||||||
// EDH performs a diffie-helman operation on this identities private key with the given public key.
|
// EDH performs a diffie-hellman operation on this identities private key with the given public key.
|
||||||
func (i *Identity) EDH(key ed25519.PublicKey) []byte {
|
func (i *Identity) EDH(key ed25519.PublicKey) []byte {
|
||||||
secret := utils.EDH(*i.edpk, key)
|
secret := utils.EDH(*i.edpk, key)
|
||||||
return secret[:]
|
return secret[:]
|
||||||
|
@ -51,3 +51,8 @@ func (i *Identity) EDH(key ed25519.PublicKey) []byte {
|
||||||
func (i *Identity) Hostname() string {
|
func (i *Identity) Hostname() string {
|
||||||
return utils.GetTorV3Hostname(*i.edpubk)
|
return utils.GetTorV3Hostname(*i.edpubk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign produces a signature for a given message attributable to the given identity
|
||||||
|
func (i *Identity) Sign(input []byte) []byte {
|
||||||
|
return ed25519.Sign(*i.edpk, input)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package primitives
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIdentity_EDH(t *testing.T) {
|
||||||
|
|
||||||
|
id1, _ := InitializeEphemeralIdentity()
|
||||||
|
id2, _ := InitializeEphemeralIdentity()
|
||||||
|
|
||||||
|
k1 := id1.EDH(id2.PublicKey())
|
||||||
|
k2 := id2.EDH(id1.PublicKey())
|
||||||
|
|
||||||
|
t.Logf("k1: %x\nk2: %x\n", k1, k2)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package privacypass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/tapir/primitives/core"
|
||||||
|
"github.com/bwesterb/go-ristretto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DLEQProof encapsulates a Chaum-Pedersen DLEQ Proof
|
||||||
|
// David Chaum and Torben P. Pedersen. Wallet databaseswith observers. In Ernest F. Brickell, editor,CRYPTO’92,volume 740 ofLNCS, pages 89–105. Springer, Heidelberg,August 1993
|
||||||
|
type DLEQProof struct {
|
||||||
|
C *ristretto.Scalar
|
||||||
|
S *ristretto.Scalar
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscreteLogEquivalenceProof constructs a valid DLEQProof for the given parameters and transcript
|
||||||
|
// Given P = kX, Q = kP, Y=kX
|
||||||
|
// Peggy: t := choose randomly from Zq
|
||||||
|
// A := tX
|
||||||
|
// B := tP
|
||||||
|
// c := H(transcript(X,Y,P,Q,A,B))
|
||||||
|
// s := (t + ck) mod q
|
||||||
|
//
|
||||||
|
// Sends c,s to Vicky
|
||||||
|
func DiscreteLogEquivalenceProof(k *ristretto.Scalar, X *ristretto.Point, Y *ristretto.Point, P *ristretto.Point, Q *ristretto.Point, transcript *core.Transcript) DLEQProof {
|
||||||
|
t := new(ristretto.Scalar).Rand()
|
||||||
|
A := new(ristretto.Point).ScalarMult(X, t)
|
||||||
|
B := new(ristretto.Point).ScalarMult(P, t)
|
||||||
|
|
||||||
|
transcript.AddToTranscript("X", X.Bytes())
|
||||||
|
transcript.AddToTranscript("Y", Y.Bytes())
|
||||||
|
transcript.AddToTranscript("P", P.Bytes())
|
||||||
|
transcript.AddToTranscript("Q", Q.Bytes())
|
||||||
|
transcript.AddToTranscript("A", A.Bytes())
|
||||||
|
transcript.AddToTranscript("B", B.Bytes())
|
||||||
|
|
||||||
|
c := transcript.CommitToTranscriptScalar("c")
|
||||||
|
s := new(ristretto.Scalar).Sub(t, new(ristretto.Scalar).Mul(c, k))
|
||||||
|
return DLEQProof{c, s}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyDiscreteLogEquivalenceProof verifies the DLEQ for the given parameters and transcript
|
||||||
|
// Given P = kX, Q = kP, Y=kX, and Proof = (c,s)
|
||||||
|
// Vicky: X' := sX
|
||||||
|
// Y' := cY
|
||||||
|
// P' := sP
|
||||||
|
// Q' := cQ
|
||||||
|
// A' = X'+Y' == sX + cY ?= sX + ckX == (s+ck)X == tX == A
|
||||||
|
// B' = P'+Q' == sP + cQ ?= sP + ckP == (s+ck)P == tP == B
|
||||||
|
// c' := H(transcript(X,Y,P,Q,A',B'))
|
||||||
|
// Tests c ?= c
|
||||||
|
func VerifyDiscreteLogEquivalenceProof(dleq DLEQProof, X *ristretto.Point, Y *ristretto.Point, P *ristretto.Point, Q *ristretto.Point, transcript *core.Transcript) bool {
|
||||||
|
|
||||||
|
Xs := new(ristretto.Point).ScalarMult(X, dleq.S)
|
||||||
|
Yc := new(ristretto.Point).ScalarMult(Y, dleq.C)
|
||||||
|
Ps := new(ristretto.Point).ScalarMult(P, dleq.S)
|
||||||
|
Qc := new(ristretto.Point).ScalarMult(Q, dleq.C)
|
||||||
|
|
||||||
|
A := new(ristretto.Point).Add(Xs, Yc)
|
||||||
|
B := new(ristretto.Point).Add(Ps, Qc)
|
||||||
|
|
||||||
|
transcript.AddToTranscript("X", X.Bytes())
|
||||||
|
transcript.AddToTranscript("Y", Y.Bytes())
|
||||||
|
transcript.AddToTranscript("P", P.Bytes())
|
||||||
|
transcript.AddToTranscript("Q", Q.Bytes())
|
||||||
|
transcript.AddToTranscript("A", A.Bytes())
|
||||||
|
transcript.AddToTranscript("B", B.Bytes())
|
||||||
|
|
||||||
|
return transcript.CommitToTranscriptScalar("c").Equals(dleq.C)
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package privacypass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"cwtch.im/tapir/primitives/core"
|
||||||
|
"fmt"
|
||||||
|
"github.com/bwesterb/go-ristretto"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token is an implementation of PrivacyPass
|
||||||
|
// Davidson A, Goldberg I, Sullivan N, Tankersley G, Valsorda F. Privacy pass: Bypassing internet challenges anonymously. Proceedings on Privacy Enhancing Technologies. 2018 Jun 1;2018(3):164-80.
|
||||||
|
type Token struct {
|
||||||
|
t []byte
|
||||||
|
r *ristretto.Scalar
|
||||||
|
W *ristretto.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlindedToken encapsulates a Blinded Token
|
||||||
|
type BlindedToken struct {
|
||||||
|
P *ristretto.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedToken encapsulates a Signed (Blinded) Token
|
||||||
|
type SignedToken struct {
|
||||||
|
Q *ristretto.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpentToken encapsulates the parameters needed to spend a Token
|
||||||
|
type SpentToken struct {
|
||||||
|
t []byte
|
||||||
|
MAC []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenBlindedToken initializes the Token
|
||||||
|
// GenToken() & Blind()
|
||||||
|
func (t *Token) GenBlindedToken() BlindedToken {
|
||||||
|
t.t = make([]byte, 32)
|
||||||
|
rand.Read(t.t)
|
||||||
|
t.r = new(ristretto.Scalar).Rand()
|
||||||
|
|
||||||
|
Ht := sha3.Sum256(t.t)
|
||||||
|
T := new(ristretto.Point).SetElligator(&Ht)
|
||||||
|
P := new(ristretto.Point).ScalarMult(T, t.r)
|
||||||
|
return BlindedToken{P}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unblindSignedToken unblinds a token that has been signed by a server
|
||||||
|
func (t *Token) unblindSignedToken(token SignedToken) {
|
||||||
|
t.W = new(ristretto.Point).ScalarMult(token.Q, new(ristretto.Scalar).Inverse(t.r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpendToken binds the token with data and then redeems the token
|
||||||
|
func (t *Token) SpendToken(data []byte) SpentToken {
|
||||||
|
key := sha3.Sum256(append(t.t, t.W.Bytes()...))
|
||||||
|
mac := hmac.New(sha3.New512, key[:])
|
||||||
|
return SpentToken{t.t, mac.Sum(data)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateBlindedTokenBatch generates a batch of blinded tokens (and their unblinded equivalents)
|
||||||
|
func GenerateBlindedTokenBatch(num int) (tokens []*Token, blindedTokens []BlindedToken) {
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
tokens = append(tokens, new(Token))
|
||||||
|
blindedTokens = append(blindedTokens, tokens[i].GenBlindedToken())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyBatchProof verifies a given batch proof (see also UnblindSignedTokenBatch)
|
||||||
|
func verifyBatchProof(dleq DLEQProof, Y *ristretto.Point, blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) bool {
|
||||||
|
transcript.AddToTranscript("X", new(ristretto.Point).SetBase().Bytes())
|
||||||
|
transcript.AddToTranscript("Y", 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 VerifyDiscreteLogEquivalenceProof(dleq, new(ristretto.Point).SetBase(), Y, M, Z, transcript)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnblindSignedTokenBatch taking in a set of tokens, their blinded & signed counterparts, a server public key (Y), a DLEQ proof and a transcript
|
||||||
|
// verifies that the signing procedure has taken place correctly and unblinds the tokens.
|
||||||
|
func UnblindSignedTokenBatch(tokens []*Token, blindedTokens []BlindedToken, signedTokens []SignedToken, Y *ristretto.Point, proof DLEQProof, transcript *core.Transcript) bool {
|
||||||
|
verified := verifyBatchProof(proof, Y, blindedTokens, signedTokens, transcript)
|
||||||
|
if !verified {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, t := range tokens {
|
||||||
|
t.unblindSignedToken(signedTokens[i])
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package privacypass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/tapir/primitives/core"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToken_SpendToken(t *testing.T) {
|
||||||
|
server := NewTokenServer()
|
||||||
|
|
||||||
|
token := new(Token)
|
||||||
|
blindedToken := token.GenBlindedToken()
|
||||||
|
|
||||||
|
signedToken := server.SignBlindedToken(blindedToken)
|
||||||
|
token.unblindSignedToken(signedToken)
|
||||||
|
|
||||||
|
spentToken := token.SpendToken([]byte("Hello"))
|
||||||
|
|
||||||
|
if server.IsValid(spentToken, []byte("Hello World")) == true {
|
||||||
|
t.Errorf("Token Should be InValid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.IsValid(spentToken, []byte("Hello")) == false {
|
||||||
|
t.Errorf("Token Should be Valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.IsValid(spentToken, []byte("Hello")) == true {
|
||||||
|
t.Errorf("Token Should be Spent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateBlindedTokenBatch(t *testing.T) {
|
||||||
|
server := NewTokenServer()
|
||||||
|
|
||||||
|
clientTranscript := core.NewTranscript("privacyPass")
|
||||||
|
serverTranscript := core.NewTranscript("privacyPass")
|
||||||
|
|
||||||
|
tokens, blindedTokens := GenerateBlindedTokenBatch(10)
|
||||||
|
signedTokens, proof := server.SignBlindedTokenBatch(blindedTokens, serverTranscript)
|
||||||
|
|
||||||
|
verified := UnblindSignedTokenBatch(tokens, blindedTokens, signedTokens, server.Y, proof, clientTranscript)
|
||||||
|
|
||||||
|
if !verified {
|
||||||
|
t.Errorf("Something went wrong, the proof did not pass")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to Spend All the tokens
|
||||||
|
for _, token := range tokens {
|
||||||
|
spentToken := token.SpendToken([]byte("Hello"))
|
||||||
|
if server.IsValid(spentToken, []byte("Hello")) == false {
|
||||||
|
t.Errorf("Token Should be Valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Client Transcript,: %s", clientTranscript.OutputTranscriptToAudit())
|
||||||
|
t.Logf("Server Transcript,: %s", serverTranscript.OutputTranscriptToAudit())
|
||||||
|
|
||||||
|
wrongTranscript := core.NewTranscript("wrongTranscript")
|
||||||
|
verified = UnblindSignedTokenBatch(tokens, blindedTokens, signedTokens, server.Y, proof, wrongTranscript)
|
||||||
|
if verified {
|
||||||
|
t.Errorf("Something went wrong, the proof passed with wrong transcript: %s", wrongTranscript.OutputTranscriptToAudit())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
package primitives
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TimeProvider is an interface used by services to timestamp events. Why not just have them use time.Now()? We want
|
|
||||||
// to be able to write tests that simulate behavior over several hours, and thus having an interface to abstract away
|
|
||||||
// time details for the services is very useful.
|
|
||||||
type TimeProvider interface {
|
|
||||||
GetCurrentTime() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// OSTimeProvider provides a wrapper around time provider which simply provides the time as given by the operating system.
|
|
||||||
type OSTimeProvider struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCurrentTime returns the time provided by the OS
|
|
||||||
func (ostp OSTimeProvider) GetCurrentTime() time.Time {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
|
@ -21,12 +21,12 @@ type SimpleApp struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInstance should always return a new instantiation of the application.
|
// NewInstance should always return a new instantiation of the application.
|
||||||
func (ea SimpleApp) NewInstance() tapir.Application {
|
func (ea *SimpleApp) NewInstance() tapir.Application {
|
||||||
return new(SimpleApp)
|
return new(SimpleApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init is run when the connection is first started.
|
// Init is run when the connection is first started.
|
||||||
func (ea SimpleApp) Init(connection tapir.Connection) {
|
func (ea *SimpleApp) Init(connection tapir.Connection) {
|
||||||
// First run the Authentication App
|
// First run the Authentication App
|
||||||
ea.AuthApp.Init(connection)
|
ea.AuthApp.Init(connection)
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ func TestTapir(t *testing.T) {
|
||||||
sg := new(sync.WaitGroup)
|
sg := new(sync.WaitGroup)
|
||||||
sg.Add(1)
|
sg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
service.Listen(SimpleApp{})
|
service.Listen(new(SimpleApp))
|
||||||
sg.Done()
|
sg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ func genclient(acn connectivity.ACN) (tapir.Service, string) {
|
||||||
|
|
||||||
// Client will Connect and launch it's own Echo App goroutine.
|
// Client will Connect and launch it's own Echo App goroutine.
|
||||||
func connectclient(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup) {
|
func connectclient(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup) {
|
||||||
client.Connect(utils.GetTorV3Hostname(key), SimpleApp{})
|
client.Connect(utils.GetTorV3Hostname(key), new(SimpleApp))
|
||||||
|
|
||||||
// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
|
// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
|
||||||
// we will wait a little while then exit.
|
// we will wait a little while then exit.
|
||||||
|
|
|
@ -40,7 +40,7 @@ func TestTapirMaliciousRemote(t *testing.T) {
|
||||||
sg := new(sync.WaitGroup)
|
sg := new(sync.WaitGroup)
|
||||||
sg.Add(1)
|
sg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
service.Listen(applications.AuthApp{})
|
service.Listen(new(applications.AuthApp))
|
||||||
sg.Done()
|
sg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func TestTapirMaliciousRemote(t *testing.T) {
|
||||||
|
|
||||||
// Client will Connect and launch it's own Echo App goroutine.
|
// Client will Connect and launch it's own Echo App goroutine.
|
||||||
func connectclientandfail(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup, t *testing.T) {
|
func connectclientandfail(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup, t *testing.T) {
|
||||||
client.Connect(utils.GetTorV3Hostname(key), applications.AuthApp{})
|
client.Connect(utils.GetTorV3Hostname(key), new(applications.AuthApp))
|
||||||
|
|
||||||
// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
|
// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
|
||||||
// we will wait a little while then exit.
|
// we will wait a little while then exit.
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
set -e
|
set -e
|
||||||
pwd
|
pwd
|
||||||
go test ${1} -coverprofile=applications.cover.out -v ./applications
|
go test ${1} -coverprofile=applications.cover.out -v ./applications
|
||||||
|
go test ${1} -coverprofile=primitives.cover.out -v ./primitives
|
||||||
|
go test ${1} -coverprofile=primitives.core.cover.out -v ./primitives/core
|
||||||
|
go test ${1} -coverprofile=primitives.privacypass.cover.out -v ./primitives/privacypass
|
||||||
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
|
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
|
||||||
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
|
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
|
||||||
rm -rf *.cover.out
|
rm -rf *.cover.out
|
||||||
|
|
Loading…
Reference in New Issue