package connections import ( "cwtch.im/cwtch/protocol/groups" "encoding/json" "errors" "git.openprivacy.ca/cwtch.im/tapir" "git.openprivacy.ca/cwtch.im/tapir/applications" "git.openprivacy.ca/cwtch.im/tapir/networks/tor" "git.openprivacy.ca/cwtch.im/tapir/primitives" "git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/log" "github.com/gtank/ristretto255" "reflect" "sync" "time" ) // NewTokenBoardClient generates a new Client for Token Board func NewTokenBoardClient(acn connectivity.ACN, Y *ristretto255.Element, tokenServiceOnion string, lastKnownSignature []byte, groupMessageHandler func(server string, gm *groups.EncryptedGroupMessage), serverAuthedHandler func(server string), serverSyncedHandler func(server string), serverClosedHandler func(server string)) tapir.Application { tba := new(TokenBoardClient) tba.acn = acn tba.tokenService = privacypass.NewTokenServer() tba.tokenService.Y = Y tba.tokenServiceOnion = tokenServiceOnion tba.receiveGroupMessageHandler = groupMessageHandler tba.serverAuthedHandler = serverAuthedHandler tba.serverSyncedHandler = serverSyncedHandler tba.serverClosedHandler = serverClosedHandler tba.lastKnownSignature = lastKnownSignature return tba } // TokenBoardClient defines a client for the TokenBoard server type TokenBoardClient struct { applications.AuthApp connection tapir.Connection receiveGroupMessageHandler func(server string, gm *groups.EncryptedGroupMessage) serverAuthedHandler func(server string) serverSyncedHandler func(server string) serverClosedHandler func(server string) // Token service handling acn connectivity.ACN tokens []*privacypass.Token tokenLock sync.Mutex tokenService *privacypass.TokenServer tokenServiceOnion string lastKnownSignature []byte } // NewInstance Client a new TokenBoardApp func (ta *TokenBoardClient) NewInstance() tapir.Application { tba := new(TokenBoardClient) tba.serverAuthedHandler = ta.serverAuthedHandler tba.serverSyncedHandler = ta.serverSyncedHandler tba.serverClosedHandler = ta.serverClosedHandler tba.receiveGroupMessageHandler = ta.receiveGroupMessageHandler tba.acn = ta.acn tba.tokenService = ta.tokenService tba.tokenServiceOnion = ta.tokenServiceOnion tba.lastKnownSignature = ta.lastKnownSignature return tba } // Init initializes the cryptographic TokenBoardApp func (ta *TokenBoardClient) Init(connection tapir.Connection) { ta.AuthApp.Init(connection) if connection.HasCapability(applications.AuthCapability) { ta.connection = connection ta.serverAuthedHandler(ta.connection.Hostname()) log.Debugf("Successfully Initialized Connection to %v", connection.Hostname()) go ta.Listen() // Optimistically acquire many tokens for this server... go ta.MakePayment() go ta.MakePayment() ta.Replay() } else { connection.Close() } } // Listen processes the messages for this application func (ta *TokenBoardClient) Listen() { for { log.Debugf("Client waiting...") data := ta.connection.Expect() if len(data) == 0 { log.Debugf("Server closed the connection...") ta.serverClosedHandler(ta.connection.Hostname()) return // connection is closed } // We always expect the server to follow protocol, and the second it doesn't we close the connection var message groups.Message if err := json.Unmarshal(data, &message); err != nil { log.Debugf("Server sent an unexpected message, closing the connection: %v", err) ta.serverClosedHandler(ta.connection.Hostname()) ta.connection.Close() return } switch message.MessageType { case groups.NewMessageMessage: if message.NewMessage != nil { ta.receiveGroupMessageHandler(ta.connection.Hostname(), &message.NewMessage.EGM) } else { log.Debugf("Server sent an unexpected NewMessage, closing the connection: %s", data) ta.serverClosedHandler(ta.connection.Hostname()) ta.connection.Close() return } case groups.PostResultMessage: // TODO handle failure case groups.ReplayResultMessage: if message.ReplayResult != nil { log.Debugf("Replaying %v Messages...", message.ReplayResult.NumMessages) for i := 0; i < message.ReplayResult.NumMessages; i++ { data := ta.connection.Expect() if len(data) == 0 { log.Debugf("Server sent an unexpected EncryptedGroupMessage, closing the connection") ta.serverClosedHandler(ta.connection.Hostname()) ta.connection.Close() return } egm := &groups.EncryptedGroupMessage{} if err := json.Unmarshal(data, egm); err == nil { ta.receiveGroupMessageHandler(ta.connection.Hostname(), egm) ta.lastKnownSignature = egm.Signature } else { log.Debugf("Server sent an unexpected EncryptedGroupMessage, closing the connection: %v", err) ta.serverClosedHandler(ta.connection.Hostname()) ta.connection.Close() return } } ta.serverSyncedHandler(ta.connection.Hostname()) ta.connection.SetCapability(groups.CwtchServerSyncedCapability) } } } } // Replay posts a Replay Message to the server. func (ta *TokenBoardClient) Replay() { data, _ := json.Marshal(groups.Message{MessageType: groups.ReplayRequestMessage, ReplayRequest: &groups.ReplayRequest{LastCommit: ta.lastKnownSignature}}) ta.connection.Send(data) } // PurchaseTokens purchases the given number of tokens from the server (using the provided payment handler) func (ta *TokenBoardClient) PurchaseTokens() { ta.MakePayment() } // Post sends a Post Request to the server func (ta *TokenBoardClient) Post(ct []byte, sig []byte) (bool, int) { egm := groups.EncryptedGroupMessage{Ciphertext: ct, Signature: sig} token, numTokens, err := ta.NextToken(egm.ToBytes(), ta.connection.Hostname()) if err == nil { data, _ := json.Marshal(groups.Message{MessageType: groups.PostRequestMessage, PostRequest: &groups.PostRequest{EGM: egm, Token: token}}) log.Debugf("Message Length: %s %v", data, len(data)) err := ta.connection.Send(data) if err != nil { return false, numTokens } return true, numTokens } log.Debugf("No Valid Tokens: %v", err) return false, numTokens } // MakePayment uses the PoW based token protocol to obtain more tokens func (ta *TokenBoardClient) MakePayment() error { log.Debugf("Making a Payment") id, sk := primitives.InitializeEphemeralIdentity() client := new(tor.BaseOnionService) client.Init(ta.acn, sk, &id) defer client.Shutdown() tokenApplication := new(applications.TokenApplication) tokenApplication.TokenService = ta.tokenService powTokenApp := new(applications.ApplicationChain). ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). ChainApplication(tokenApplication, applications.HasTokensCapability) log.Debugf("Waiting for successful PoW Auth...") connected, err := client.Connect(ta.tokenServiceOnion, powTokenApp) if connected && err == nil { log.Debugf("Waiting for successful Token Acquisition...") conn, err := client.WaitForCapabilityOrClose(ta.tokenServiceOnion, applications.HasTokensCapability) if err == nil { powtapp, ok := conn.App().(*applications.TokenApplication) if ok { // Update tokens...we need a lock here to prevent SpendToken from modifying the tokens // during this process.. log.Debugf("Updating Tokens") ta.tokenLock.Lock() ta.tokens = append(ta.tokens, powtapp.Tokens...) ta.tokenLock.Unlock() log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit()) conn.Close() return nil } log.Errorf("invalid cast of powapp. this should never happen %v %v", powtapp, reflect.TypeOf(conn.App())) return errors.New("invalid cast of powapp. this should never happen") } log.Debugf("could not connect to payment server..trying again: %v", err) return ta.MakePayment() } else if connected && err != nil { log.Debugf("inexplicable error: %v", err) } log.Debugf("failed to make a connection. trying again...") // it doesn't actually take that long to make a payment, so waiting a small amount of time should suffice time.Sleep(time.Second) return ta.MakePayment() } // NextToken retrieves the next token func (ta *TokenBoardClient) NextToken(data []byte, hostname string) (privacypass.SpentToken, int, error) { // Taken the first new token, we need a lock here because tokens can be appended by MakePayment // which could result in weird behaviour... ta.tokenLock.Lock() defer ta.tokenLock.Unlock() if len(ta.tokens) == 0 { return privacypass.SpentToken{}, len(ta.tokens), errors.New("no more tokens") } token := ta.tokens[0] ta.tokens = ta.tokens[1:] return token.SpendToken(append(data, hostname...)), len(ta.tokens), nil }