package connections import ( "cwtch.im/cwtch/protocol/groups" "cwtch.im/tapir" "cwtch.im/tapir/applications" "cwtch.im/tapir/networks/tor" "cwtch.im/tapir/primitives" "cwtch.im/tapir/primitives/privacypass" "encoding/json" "errors" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/log" "github.com/gtank/ristretto255" ) // NewTokenBoardClient generates a new Client for Token Board func NewTokenBoardClient(acn connectivity.ACN, Y *ristretto255.Element, tokenServiceOnion string, groupMessageHandler func(server string, gm *groups.EncryptedGroupMessage), serverSyncedHandler 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.serverSyncedHandler = serverSyncedHandler 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) serverSyncedHandler func(server string) // Token service handling acn connectivity.ACN tokens []*privacypass.Token tokenService *privacypass.TokenServer tokenServiceOnion string } // NewInstance Client a new TokenBoardApp func (ta *TokenBoardClient) NewInstance() tapir.Application { tba := new(TokenBoardClient) tba.serverSyncedHandler = ta.serverSyncedHandler tba.receiveGroupMessageHandler = ta.receiveGroupMessageHandler tba.acn = ta.acn tba.tokenService = ta.tokenService tba.tokenServiceOnion = ta.tokenServiceOnion 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.connection.SetCapability(groups.CwtchServerSyncedCapability) log.Debugf("Successfully Initialized Connection") go ta.Listen() 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...") return // connection is closed } // We always expect the server to follow protocol, and the second it doesn't we close the connection // TODO issue an error so the client is aware 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.connection.Close() return } switch message.MessageType { case groups.NewMessageMessage: if message.NewMessage != nil { ta.receiveGroupMessageHandler(ta.connection.Hostname(), &message.NewMessage.EGM) } else { // TODO: Send this error to the UI log.Debugf("Server sent an unexpected NewMessage, closing the connection: %s", data) 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() egm := &groups.EncryptedGroupMessage{} if err := json.Unmarshal(data, egm); err == nil { ta.receiveGroupMessageHandler(ta.connection.Hostname(), egm) } else { // TODO: Send this error to the UI log.Debugf("Server sent an unexpected EncryptedGroupMessage, closing the connection: %v", err) ta.connection.Close() return } } ta.serverSyncedHandler(ta.connection.Hostname()) } } } } // Replay posts a Replay Message to the server. func (ta *TokenBoardClient) Replay() { // TODO - Allow configurable ranges data, _ := json.Marshal(groups.Message{MessageType: groups.ReplayRequestMessage, ReplayRequest: &groups.ReplayRequest{LastCommit: []byte{}}}) 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 { egm := groups.EncryptedGroupMessage{Ciphertext: ct, Signature: sig} token, 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)) ta.connection.Send(data) return true } log.Debugf("No Valid Tokens: %v", err) return false } // MakePayment uses the PoW based token protocol to obtain more tokens func (ta *TokenBoardClient) MakePayment() { log.Debugf("Making a Payment %v", ta) id, sk := primitives.InitializeEphemeralIdentity() var client tapir.Service client = new(tor.BaseOnionService) client.Init(ta.acn, sk, &id) tokenApplication := new(applications.TokenApplication) tokenApplication.TokenService = ta.tokenService powTokenApp := new(applications.ApplicationChain). ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). ChainApplication(tokenApplication, applications.HasTokensCapability) client.Connect(ta.tokenServiceOnion, powTokenApp) conn, err := client.WaitForCapabilityOrClose(ta.tokenServiceOnion, applications.HasTokensCapability) if err == nil { powtapp, _ := conn.App().(*applications.TokenApplication) ta.tokens = append(ta.tokens, powtapp.Tokens...) log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit()) conn.Close() return } log.Debugf("Error making payment: to %v %v", ta.tokenServiceOnion, err) } // NextToken retrieves the next token func (ta *TokenBoardClient) NextToken(data []byte, hostname string) (privacypass.SpentToken, error) { if len(ta.tokens) == 0 { return privacypass.SpentToken{}, errors.New("No more tokens") } token := ta.tokens[0] ta.tokens = ta.tokens[1:] return token.SpendToken(append(data, hostname...)), nil }