-
Notifications
You must be signed in to change notification settings - Fork 7
feat (geo-resto): Add geo-resto package for location-based experiences #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
a9741cf
d6ea2b8
4099c86
96aa0ce
a286517
fc682d3
0ef3949
e3df41a
3297a28
3004e91
7c7c006
29a47eb
10e7201
50c4472
d436ed4
44fa137
7f49c46
17e1263
4b3368c
6350c6c
6cd9afc
865cb24
7981ebd
0175a2d
92d9303
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # 🌍 Geo-Resto | ||
|
|
||
| A Gno-based realm for creating a decentralized, on-chain record of real-world places and visits. | ||
|
|
||
| ## Core Features | ||
|
|
||
| - **Location Management**: Add and verify geographic locations, creating a permanent, user-owned registry. | ||
| - **Proof of Presence**: Check-in at locations using a secure challenge-response mechanism to prove you were there. | ||
| - **Event System**: Organize location-based events like meetups, airdrops, or community gatherings. | ||
| - **QR Code Verification**: A robust system for event organizers to securely verify attendee presence in real-time. | ||
| - **On-Chain History**: All data is immutable, timestamped, and verifiable on the Gno blockchain. | ||
|
|
||
| ## How It Works | ||
|
|
||
| The realm is organized into modules that handle specific tasks: | ||
|
|
||
| - `geo_resto.gno`: The main entry point and public API. | ||
| - `location.gno`: Manages location data. | ||
| - `visit.gno`: Handles check-ins and visit history. | ||
| - `event.gno`: Manages the event lifecycle. | ||
| - `auth.gno`: Secures the system with rate-limiting, access control, and verification logic. | ||
| - `renderer.gno`: Renders the web interface. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Add a Location | ||
| ```gno | ||
| AddLocation("Eiffel Tower", "Iconic tower in Paris", 48.8584, 2.2945, "landmark") | ||
| ``` | ||
|
|
||
| ### Check-In at a Location | ||
| 1. **Get the challenge:** | ||
| ```gno | ||
| GetLocationChallenge("loc_1") | ||
| ``` | ||
| 2. **Submit the proof (challenge response):** | ||
| ```gno | ||
| CheckIn("loc_1", "proof_from_challenge") | ||
| ``` | ||
|
|
||
| ### Create an Event | ||
| ```gno | ||
| CreateEvent("loc_1", "Tech Meetup", "Weekly discussion", "", 1, startTime, endTime) | ||
| ``` | ||
|
|
||
| ## Event QR Code Verification | ||
|
|
||
| A secure system for proving event attendance. | ||
|
|
||
| - **Organizers**: Create an event to get a QR code. At the event, generate temporary verification codes for attendees using `GenerateAttendeeCode("event_1")`. | ||
| - **Attendees**: Get verified by the organizer. | ||
| - **Stats**: Organizers can track attendance with `GetVerifiedAttendees("event_1")` and `GetEventVerificationStats("event_1")`. | ||
|
|
||
| ## Future Ideas | ||
|
|
||
| - **Zero-Knowledge Proofs**: For privacy-preserving check-ins. | ||
| - **Community Governance**: Location ratings, reviews, and decentralized moderation. | ||
| - **Enhanced Visuals**: Richer map interfaces and data visualizations. | ||
|
|
||
| ## Testing | ||
|
|
||
| Run the full test suite with: | ||
| ```bash | ||
| gno test | ||
| ``` | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,264 @@ | ||
| package georesto | ||
|
|
||
| import ( | ||
| "crypto/sha256" | ||
| "encoding/hex" | ||
| "math" | ||
| "strconv" | ||
| "time" | ||
| ) | ||
|
|
||
| // AuthManager handles access control and proof verification | ||
| type AuthManager struct { | ||
| trustedVerifiers map[string]bool // Trusted verifier addresses | ||
| adminAddresses map[string]bool // Admin addresses | ||
| rateLimitTracker map[string]int64 // Tracks last action time for a user and action | ||
| accessTokens map[string]int64 // token -> expiration time | ||
|
||
| } | ||
|
|
||
| // NewAuthManager creates a new authentication manager instance | ||
| func NewAuthManager() *AuthManager { | ||
| return &AuthManager{ | ||
| trustedVerifiers: make(map[string]bool), | ||
| adminAddresses: make(map[string]bool), | ||
| rateLimitTracker: make(map[string]int64), | ||
| accessTokens: make(map[string]int64), | ||
| } | ||
| } | ||
|
|
||
| // AddTrustedVerifier adds a trusted verifier address | ||
| func (am *AuthManager) AddTrustedVerifier(verifierAddress string, admin address) bool { | ||
| if !am.IsAdmin(admin.String()) { | ||
| return false | ||
| } | ||
|
|
||
| am.trustedVerifiers[verifierAddress] = true | ||
| return true | ||
| } | ||
|
|
||
| // RemoveTrustedVerifier removes a trusted verifier address | ||
| func (am *AuthManager) RemoveTrustedVerifier(verifierAddress string, admin address) bool { | ||
| if !am.IsAdmin(admin.String()) { | ||
| return false | ||
| } | ||
|
|
||
| delete(am.trustedVerifiers, verifierAddress) | ||
| return true | ||
| } | ||
|
|
||
| // IsTrustedVerifier checks if an address is a trusted verifier | ||
| func (am *AuthManager) IsTrustedVerifier(address string) bool { | ||
| return am.trustedVerifiers[address] | ||
| } | ||
|
|
||
| // AddAdmin adds an admin address | ||
| func (am *AuthManager) AddAdmin(adminAddress string, currentAdmin address) bool { | ||
| // Only existing admins can add new admins | ||
| if len(am.adminAddresses) > 0 && !am.IsAdmin(currentAdmin.String()) { | ||
| return false | ||
| } | ||
|
|
||
| am.adminAddresses[adminAddress] = true | ||
| return true | ||
| } | ||
|
|
||
| // IsAdmin checks if an address is an admin | ||
| func (am *AuthManager) IsAdmin(address string) bool { | ||
| return am.adminAddresses[address] | ||
| } | ||
|
|
||
| // VerifyLocationProof verifies a cryptographic proof for location check-in | ||
| func (am *AuthManager) VerifyLocationProof(userAddress, locationID, proof string, timestamp int64) bool { | ||
| // In a real implementation, this would involve more complex verification. | ||
| // For now, we'll check if the proof is a valid challenge response. | ||
| return am.VerifyLocationChallenge(locationID, proof) | ||
| } | ||
|
|
||
| // GenerateLocationProof generates a proof for location check-in | ||
| func (am *AuthManager) GenerateLocationProof(userAddress, locationID string, timestamp int64) string { | ||
| return am.generateLocationProof(userAddress, locationID, timestamp) | ||
| } | ||
|
|
||
| // VerifyEventAccess verifies access to a password-protected event | ||
| func (am *AuthManager) VerifyEventAccess(eventID, password string, user address) bool { | ||
| event := eventManager.GetEvent(eventID) | ||
| if event == nil { | ||
| return false | ||
| } | ||
|
|
||
| // If no password required, access is granted | ||
| if event.Password == "" { | ||
| return true | ||
| } | ||
|
|
||
| // Verify password | ||
| hashedPassword := am.hashString(password) | ||
| return hashedPassword == event.Password | ||
| } | ||
|
||
|
|
||
| // CanModifyLocation checks if a user can modify a location | ||
| func (am *AuthManager) CanModifyLocation(locationID string, user address) bool { | ||
| location := locationManager.GetLocation(locationID) | ||
| if location == nil { | ||
| return false | ||
| } | ||
|
|
||
| userAddr := user.String() | ||
|
|
||
| // Location creator can always modify | ||
| if location.Creator.String() == userAddr { | ||
| return true | ||
| } | ||
|
|
||
| // Admins can modify any location | ||
| if am.IsAdmin(userAddr) { | ||
| return true | ||
| } | ||
|
|
||
| // Trusted verifiers can modify for verification purposes | ||
| if am.IsTrustedVerifier(userAddr) { | ||
| return true | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| // CanModifyEvent checks if a user can modify an event | ||
| func (am *AuthManager) CanModifyEvent(eventID string, user address) bool { | ||
| event := eventManager.GetEvent(eventID) | ||
| if event == nil { | ||
| return false | ||
| } | ||
|
|
||
| userAddr := user.String() | ||
|
|
||
| // Event creator can always modify | ||
| if event.Creator.String() == userAddr { | ||
| return true | ||
| } | ||
|
|
||
| // Admins can modify any event | ||
| if am.IsAdmin(userAddr) { | ||
| return true | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| // ValidateCoordinates validates GPS coordinates | ||
| func (am *AuthManager) ValidateCoordinates(latitude, longitude float64) bool { | ||
| return latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180 | ||
| } | ||
|
|
||
| // ValidateProximity checks if user is within acceptable range of location | ||
| func (am *AuthManager) ValidateProximity(userLat, userLon, locationLat, locationLon, maxDistanceKm float64) bool { | ||
| distance := haversine(userLat, userLon, locationLat, locationLon) | ||
| return distance <= maxDistanceKm | ||
| } | ||
|
|
||
| // GenerateAccessToken generates a temporary access token for API access | ||
| func (am *AuthManager) GenerateAccessToken(userAddress string, expirationTime int64) string { | ||
| data := userAddress + ":" + strconv.FormatInt(expirationTime, 10) | ||
| hash := sha256.Sum256([]byte(data)) | ||
| token := hex.EncodeToString(hash[:]) | ||
| am.accessTokens[token] = expirationTime | ||
| return token | ||
| } | ||
|
|
||
| // VerifyAccessToken verifies an access token | ||
| func (am *AuthManager) VerifyAccessToken(token, userAddress string, currentTime int64) bool { | ||
| expirationTime, exists := am.accessTokens[token] | ||
| if !exists { | ||
| return false // Token does not exist | ||
| } | ||
|
|
||
| if currentTime > expirationTime { | ||
| delete(am.accessTokens, token) // Clean up expired token | ||
| return false // Token has expired | ||
| } | ||
|
|
||
| // Optional: Verify that the token was generated for the correct userAddress. | ||
| // This would require storing the userAddress with the token or regenerating | ||
| // the token to check for a match. For now, we'll keep it simple. | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| // CheckRateLimit implements basic rate limiting for API calls | ||
| func (am *AuthManager) CheckRateLimit(userAddress string, action string, cooldownSeconds int64) bool { | ||
| key := userAddress + ":" + action | ||
| lastActionTime, exists := am.rateLimitTracker[key] | ||
|
|
||
| currentTime := time.Now().Unix() | ||
|
|
||
| if exists && (currentTime-lastActionTime) < cooldownSeconds { | ||
| return false // Rate limit exceeded | ||
| } | ||
|
|
||
| am.rateLimitTracker[key] = currentTime | ||
| return true | ||
| } | ||
|
|
||
| // Helper methods | ||
|
|
||
| func (am *AuthManager) generateLocationProof(userAddress, locationID string, timestamp int64) string { | ||
| data := userAddress + ":" + locationID + ":" + strconv.FormatInt(timestamp, 10) | ||
| hash := sha256.Sum256([]byte(data)) | ||
| return hex.EncodeToString(hash[:]) | ||
| } | ||
|
|
||
| func (am *AuthManager) hashString(input string) string { | ||
| hash := sha256.Sum256([]byte(input)) | ||
| return hex.EncodeToString(hash[:]) | ||
| } | ||
|
|
||
| // VerifyZKProof verifies a zero-knowledge proof (placeholder for future implementation) | ||
| func (am *AuthManager) VerifyZKProof(proof string, publicInputs []string) bool { | ||
| // Placeholder for ZK proof verification | ||
| // In a real implementation, this would verify a zk-SNARK or zk-STARK proof | ||
| // allowing users to prove they were at a location without revealing exact coordinates | ||
| return len(proof) > 0 && len(publicInputs) > 0 | ||
| } | ||
|
|
||
| // GenerateLocationChallenge generates a challenge for location verification | ||
| func (am *AuthManager) GenerateLocationChallenge(locationID string) string { | ||
| // Generate a time-based challenge that can be solved by someone physically present | ||
| timestamp := time.Now().Unix() | ||
| data := locationID + ":" + strconv.FormatInt(timestamp/300, 10) // 5-minute windows | ||
| hash := sha256.Sum256([]byte(data)) | ||
| return hex.EncodeToString(hash[:8]) // Return first 8 bytes as challenge | ||
| } | ||
|
|
||
| // VerifyLocationChallenge verifies a location challenge response | ||
| func (am *AuthManager) VerifyLocationChallenge(locationID, response string) bool { | ||
| // Check if response matches any of the last few time windows (for clock drift tolerance) | ||
| currentTime := time.Now().Unix() | ||
| for i := 0; i < 3; i++ { // Check current and previous 2 windows | ||
| timeWindow := (currentTime / 300) - int64(i) | ||
| data := locationID + ":" + strconv.FormatInt(timeWindow, 10) | ||
| hash := sha256.Sum256([]byte(data)) | ||
| expectedResponse := hex.EncodeToString(hash[:8]) | ||
|
|
||
| if response == expectedResponse { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| // haversine calculates the distance between two points on Earth. | ||
| func haversine(lat1, lon1, lat2, lon2 float64) float64 { | ||
| const R = 6371 // Earth radius in kilometers | ||
| dLat := (lat2 - lat1) * (math.Pi / 180.0) | ||
| dLon := (lon2 - lon1) * (math.Pi / 180.0) | ||
| lat1Rad := lat1 * (math.Pi / 180.0) | ||
| lat2Rad := lat2 * (math.Pi / 180.0) | ||
|
|
||
| a := math.Sin(dLat/2)*math.Sin(dLat/2) + | ||
| math.Cos(lat1Rad)*math.Cos(lat2Rad)* | ||
| math.Sin(dLon/2)*math.Sin(dLon/2) | ||
| c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) | ||
|
|
||
| return R * c | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole file and functionality already exists in packages like
p/nt/ownable&gno.land/p/nt/ownable/exts/authorizable. Please remove this code and use existing libraries. This will shorten your code, make it more readable, and increase the reusability value and trust of your code.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still unresolved