mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-11-01 07:51:15 +00:00
Compare commits
30 Commits
5c641c4af0
...
ed4e22a0ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed4e22a0ca | ||
|
|
09b481f1ef | ||
|
|
db97c60af1 | ||
|
|
2318b51630 | ||
|
|
03d1da5423 | ||
|
|
646044cd81 | ||
|
|
35ed4417f0 | ||
|
|
9167013ac4 | ||
|
|
b028270cc4 | ||
|
|
22f87a493a | ||
|
|
5156f7de89 | ||
|
|
93e9594bad | ||
|
|
16a22316b2 | ||
|
|
c549bdb03f | ||
|
|
c9938a36ea | ||
|
|
6c5d7d1ef0 | ||
|
|
1a0d8713e4 | ||
|
|
c9a4e68157 | ||
|
|
7e8ea63230 | ||
|
|
47e725a4d8 | ||
|
|
920eed52d7 | ||
|
|
4007bae1e2 | ||
|
|
ca992d169c | ||
|
|
f43c741877 | ||
|
|
459fa27ef9 | ||
|
|
97395b5fff | ||
|
|
ff6a5fc59a | ||
|
|
84de59f31f | ||
|
|
83eaebc357 | ||
|
|
4b2a9c3b0d |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -102,7 +102,7 @@ jobs:
|
||||
"$RUNNER_TEMP/attestation-subjects/BITCHAT_SHA256SUMS.unsigned"
|
||||
cp "$RUNNER_TEMP"/release-a/bitchat-android-*-unsigned.apk \
|
||||
"$RUNNER_TEMP/attestation-subjects/"
|
||||
cp "$RUNNER_TEMP/release-a/bitchat-android-release-unsigned.aab" \
|
||||
cp "$RUNNER_TEMP"/release-a/bitchat-android-*-unsigned.aab \
|
||||
"$RUNNER_TEMP/attestation-subjects/"
|
||||
|
||||
- name: Attest verified unsigned release
|
||||
|
||||
156
PRIVACY_POLICY.md
Normal file
156
PRIVACY_POLICY.md
Normal file
@ -0,0 +1,156 @@
|
||||
# bitchat Privacy Policy
|
||||
|
||||
*Last updated: January 2025*
|
||||
|
||||
## Our Commitment
|
||||
|
||||
bitchat is designed with privacy as its foundation. We believe private communication is a fundamental human right. This policy explains how bitchat protects your privacy.
|
||||
|
||||
## Summary
|
||||
|
||||
**WE DO NOT COLLECT ANY INFORMATION.**
|
||||
|
||||
- **No personal data collection** - We don't collect names, emails, or phone numbers
|
||||
- **No location data collection** - Location is accessed only for local processing (BLE/Geohash) and is never collected or sent to us
|
||||
- **Hybrid Functionality** - bitchat offers two modes of communication:
|
||||
- **Bluetooth Mesh Chat**: This mode is completely offline, using peer-to-peer Bluetooth connections. It does not use any servers or internet connection.
|
||||
- **Geohash Chat**: This mode uses an internet connection to communicate with others in a specific geographic area. It relies on Nostr relays for message transport.
|
||||
- **No tracking** - We have no analytics, telemetry, or user tracking
|
||||
- **Open source** - You can verify these claims by reading our code
|
||||
|
||||
## What Information bitchat Stores
|
||||
|
||||
### On Your Device Only
|
||||
|
||||
1. **Identity Key**
|
||||
- A cryptographic key generated on first launch
|
||||
- Stored locally in your device's secure storage
|
||||
- Allows you to maintain "favorite" relationships across app restarts
|
||||
- Never leaves your device
|
||||
|
||||
2. **Nickname**
|
||||
- The display name you choose (or auto-generated)
|
||||
- Stored only on your device
|
||||
- Shared with peers you communicate with
|
||||
|
||||
3. **Message History** (if enabled)
|
||||
- When room owners enable retention, messages are saved locally
|
||||
- Stored encrypted on your device
|
||||
- You can delete this at any time
|
||||
|
||||
4. **Favorite Peers**
|
||||
- Public keys of peers you mark as favorites
|
||||
- Stored only on your device
|
||||
- Allows you to recognize these peers in future sessions
|
||||
|
||||
### Temporary Session Data
|
||||
|
||||
During each session, bitchat temporarily maintains:
|
||||
- Active peer connections (forgotten when app closes)
|
||||
- Routing information for message delivery
|
||||
- Cached messages for offline peers (12 hours max)
|
||||
|
||||
## What Information is Shared
|
||||
|
||||
### With Other bitchat Users
|
||||
|
||||
When you use bitchat, nearby peers can see:
|
||||
- Your chosen nickname
|
||||
- Your ephemeral public key (changes each session)
|
||||
- Messages you send to public rooms or directly to them
|
||||
- Your approximate Bluetooth signal strength (for connection quality)
|
||||
|
||||
### With Room Members
|
||||
|
||||
When you join a password-protected room:
|
||||
- Your messages are visible to others with the password
|
||||
- Your nickname appears in the member list
|
||||
- Room owners can see you've joined
|
||||
|
||||
## What We DON'T Do
|
||||
|
||||
bitchat **never**:
|
||||
- Collects personal information
|
||||
- Collects location history
|
||||
- Transmits any data to us (the developers)
|
||||
- Stores data on servers
|
||||
- Shares data with third parties
|
||||
- Uses analytics or telemetry
|
||||
- Creates user profiles
|
||||
- Requires registration
|
||||
|
||||
## Encryption
|
||||
|
||||
All private messages use end-to-end encryption:
|
||||
- **X25519** for key exchange
|
||||
- **AES-256-GCM** for message encryption
|
||||
- **Ed25519** for digital signatures
|
||||
- **Argon2id** for password-protected rooms
|
||||
|
||||
## Your Rights
|
||||
|
||||
You have complete control:
|
||||
- **Delete Everything**: Triple-tap the logo to instantly wipe all data
|
||||
- **Leave Anytime**: Close the app and your presence disappears
|
||||
- **No Account**: Nothing to delete from servers because there are none
|
||||
- **Portability**: Your data never leaves your device unless you export it
|
||||
|
||||
## Location Data & Permissions
|
||||
|
||||
To provide the core functionality of bitchat, we access your device's location data. This access is necessary for the following specific purposes:
|
||||
|
||||
### 1. Bluetooth Low Energy (BLE) Scanning
|
||||
- **Why we need it:** The Android operating system requires Location permission to scan for nearby Bluetooth LE devices (especially on Android 11 and lower). This is a system-level requirement because Bluetooth scans can theoretically be used to derive location.
|
||||
- **How we use it:** We use this permission strictly to discover other bitchat peers nearby for the "Bluetooth Mesh Chat" mode.
|
||||
- **Privacy protection:** We do not record or store your location during this process. The data is processed instantaneously by the Android system to facilitate the connection.
|
||||
|
||||
### 2. Geohash Chat Functionality
|
||||
- **Why we need it:** The "Geohash Chat" mode allows you to communicate with others in your approximate geographic area.
|
||||
- **How we use it:** If you enable this mode, we access your location to calculate a "geohash" (a short alphanumeric string representing a geographic region). This geohash is used to find and subscribe to relevant channels on decentralized Nostr relays.
|
||||
- **Privacy protection:**
|
||||
- Your precise GPS coordinates are **never** sent to any server or peer.
|
||||
- Only the coarse geohash (representing an area, not a pinpoint) is shared with the Nostr network.
|
||||
- You can use the "Bluetooth Mesh Chat" mode without this feature if you prefer.
|
||||
|
||||
**We do not collect, store, or share your location history.** Location data is processed locally on your device to enable these specific features.
|
||||
|
||||
## Children's Privacy
|
||||
|
||||
bitchat does not knowingly collect information from children. The app has no age verification because it collects no personal information from anyone.
|
||||
|
||||
## Data Retention
|
||||
|
||||
- **Messages**: Deleted from memory when app closes (unless room retention is enabled)
|
||||
- **Identity Key**: Persists until you delete the app
|
||||
- **Favorites**: Persist until you remove them or delete the app
|
||||
- **Everything Else**: Exists only during active sessions
|
||||
|
||||
## Security Measures
|
||||
|
||||
- All communication is encrypted
|
||||
- No data transmitted to servers (there are none)
|
||||
- Open source code for public audit
|
||||
- Regular security updates
|
||||
- Cryptographic signatures prevent tampering
|
||||
|
||||
## Changes to This Policy
|
||||
|
||||
If we update this policy:
|
||||
- The "Last updated" date will change
|
||||
- The updated policy will be included in the app
|
||||
- No retroactive changes can affect data (since we don't collect any)
|
||||
|
||||
## Contact
|
||||
|
||||
bitchat is an open source project. For privacy questions:
|
||||
- Review our code: https://github.com/yourusername/bitchat
|
||||
- Open an issue on GitHub
|
||||
- Join the discussion in public rooms
|
||||
|
||||
## Philosophy
|
||||
|
||||
Privacy isn't just a feature—it's the entire point. bitchat proves that modern communication doesn't require surrendering your privacy. No accounts, no servers, no surveillance. Just people talking freely.
|
||||
|
||||
---
|
||||
|
||||
*This policy is released into the public domain under The Unlicense, just like bitchat itself.*
|
||||
@ -30,8 +30,8 @@ android {
|
||||
applicationId = "com.bitchat.droid"
|
||||
minSdk = libs.versions.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.targetSdk.get().toInt()
|
||||
versionCode = 36
|
||||
versionName = "1.7.5"
|
||||
versionCode = 38
|
||||
versionName = "2.0.1"
|
||||
buildConfigField(
|
||||
"String",
|
||||
"GITHUB_RELEASE_CERT_SHA256",
|
||||
@ -111,6 +111,13 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
composeCompiler {
|
||||
// Kotlin 2.4.10's optional Compose group-key mapping depends on unspecified
|
||||
// class-file iteration order. Keep the normal R8 mapping, but omit that
|
||||
// augmentation until its producer is deterministic across clean builds.
|
||||
includeComposeMappingFile.set(false)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
compilerOptions {
|
||||
|
||||
@ -1,414 +1,416 @@
|
||||
Relay URL,Latitude,Longitude
|
||||
nostr.n7ekb.net,47.4941,-122.294
|
||||
relay.kilombino.com,43.6532,-79.3832
|
||||
relay02.lnfi.network,35.6764,139.65
|
||||
relay.liberbitworld.org,43.6532,-79.3832
|
||||
articles.layer3.news,37.3387,-121.885
|
||||
relay.nostr.net,43.6532,-79.3832
|
||||
nostr.easycryptosend.it,43.6532,-79.3832
|
||||
strfry.bonsai.com,39.0438,-77.4874
|
||||
relay.olas.app:443,60.1699,24.9384
|
||||
relay.sharegap.net,43.6532,-79.3832
|
||||
relay.ditto.pub,43.6532,-79.3832
|
||||
nostrride.io,37.3986,-121.964
|
||||
relay.favillakey.io,-27.4705,153.026
|
||||
nostr-relay.xbytez.io,50.6924,3.20113
|
||||
relay.nostrmap.net:443,60.1699,24.9384
|
||||
mostro-p2p.tech,50.1109,8.68213
|
||||
relay.aidatanorge.no,43.6532,-79.3832
|
||||
nostr.openhoofd.nl,51.5717,3.70417
|
||||
relay.chatbett.de,40.7128,-74.006
|
||||
relay.ohstr.com:443,43.6532,-79.3832
|
||||
nostr.overmind.lol:443,43.6532,-79.3832
|
||||
nostr.red5d.dev,43.6532,-79.3832
|
||||
relay.snort.social,53.3498,-6.26031
|
||||
relay.directsponsor.net,42.8864,-78.8784
|
||||
relay.bullishbounty.com:443,43.6532,-79.3832
|
||||
relay.scuba323.com,40.8218,-74.45
|
||||
nostr.spicyz.io,43.6532,-79.3832
|
||||
nostr.chaima.info,50.1109,8.68213
|
||||
nostr.purpura.cloud,43.6532,-79.3832
|
||||
relay.nostrops.com,32.71,-96.6745
|
||||
relay.veganostr.com:443,60.1699,24.9384
|
||||
relay.ohstr.com,43.6532,-79.3832
|
||||
soloco.nl,43.6532,-79.3832
|
||||
nostr.plantroon.com:443,50.1013,8.62643
|
||||
relay.chorus.community:443,48.5333,10.7
|
||||
relay.fiatdenier.com,43.7787,-79.5393
|
||||
nostr.whitenode45.ddns.net,40.55,-74.4758
|
||||
relay.cosmicbolt.net:443,37.3986,-121.964
|
||||
relay.lanacoin-eternity.com:443,40.8302,-74.1299
|
||||
relay.cosmicbolt.net,37.3986,-121.964
|
||||
fanfares.nostr1.com:443,40.7057,-74.0136
|
||||
nostr-kyomu-haskell.onrender.com,37.7775,-122.397
|
||||
relay.keykeeper.world,40.7824,-74.0711
|
||||
relay-testnet.k8s.layer3.news,37.3387,-121.885
|
||||
dm-test-strfry-generic.samt.st,43.6532,-79.3832
|
||||
relay.laantungir.net,-19.4692,-42.5315
|
||||
nostr.sathoarder.com,48.5734,7.75211
|
||||
relay.mccormick.cx:443,52.3563,4.95714
|
||||
relay.solife.me,43.6532,-79.3832
|
||||
relay.fountain.fm:443,43.6532,-79.3832
|
||||
nostrcity-club.fly.dev,38.7946,-106.535
|
||||
nostr.mas-family.eu,60.3478,15.7505
|
||||
no.str.cr,8.96171,-83.5246
|
||||
relay.wavefunc.live,41.8781,-87.6298
|
||||
relay.cyberguy.fyi,52.6907,4.8181
|
||||
adre.su,59.9311,30.3609
|
||||
relay.erybody.com,41.4513,-81.7021
|
||||
relay.mrmave.work,43.6532,-79.3832
|
||||
relay.decentralia.fr,48.122,11.589
|
||||
relay.mccormick.cx,52.3563,4.95714
|
||||
nostr.dlcdevkit.com:443,40.0992,-83.1141
|
||||
nostr-02.yakihonne.com,1.32123,103.695
|
||||
nostr.21crypto.ch,47.5356,8.73209
|
||||
slick.mjex.me,39.0418,-77.4744
|
||||
nostr.spicyz.io:443,43.6532,-79.3832
|
||||
relay-dev.gulugulu.moe,43.6532,-79.3832
|
||||
relay.nostx.io,43.6532,-79.3832
|
||||
relay.wisp.talk,49.4543,11.0746
|
||||
relay.plebeian.market:443,50.1109,8.68213
|
||||
relay.mostr.pub:443,43.6532,-79.3832
|
||||
relay.aarpia.com,37.3986,-121.964
|
||||
chat-relay.zap-work.com,43.6532,-79.3832
|
||||
chat-relay.zap-work.com:443,43.6532,-79.3832
|
||||
relay.mitchelltribe.com:443,39.0438,-77.4874
|
||||
relay.paulstephenborile.com,49.4543,11.0746
|
||||
relay.trotters.cc,43.6532,-79.3832
|
||||
nexus.libernet.app:443,43.6532,-79.3832
|
||||
syb.lol,34.0549,-118.243
|
||||
relay.cypherflow.ai,48.8575,2.35138
|
||||
relay.despera.space,52.1782,20.9926
|
||||
relay.loveisbitcoin.com,43.6532,-79.3832
|
||||
relay.rushmi0.win,43.6532,-79.3832
|
||||
relay.degmods.com,50.4754,12.3683
|
||||
relay.satlantis.io,39.0438,-77.4874
|
||||
relay.underorion.se,50.1109,8.68213
|
||||
nostr.mom:443,50.4754,12.3683
|
||||
nostr-verified.wellorder.net,45.5201,-122.99
|
||||
relay.nostreon.com,60.1699,24.9384
|
||||
nostr.christiansass.de,54.3213,10.1349
|
||||
rilo.nostria.app:443,43.6532,-79.3832
|
||||
relay.trotters.cc:443,43.6532,-79.3832
|
||||
kotukonostr.onrender.com,37.7775,-122.397
|
||||
relay.nmail.li,50.9871,2.12554
|
||||
relay.artio.inf.unibe.ch,46.9501,7.43678
|
||||
nos.lol,50.4754,12.3683
|
||||
nostr.snowbla.de:443,50.4754,12.3683
|
||||
relay.openresist.com,43.6532,-79.3832
|
||||
ribo.us.nostria.app,43.6532,-79.3832
|
||||
bitchat.nostr1.com,40.7057,-74.0136
|
||||
vm-1734.lnvps.cloud,53.3498,-6.26031
|
||||
nostr-relay.xbytez.io:443,50.6924,3.20113
|
||||
nostr.sathoarder.com:443,48.5734,7.75211
|
||||
bcast.girino.org,43.6532,-79.3832
|
||||
ribo.us.nostria.app:443,43.6532,-79.3832
|
||||
strfry.shock.network,39.0438,-77.4874
|
||||
relay.staging.plebeian.market:443,51.5072,-0.127586
|
||||
offchain.pub:443,39.1585,-94.5728
|
||||
relayone.soundhsa.com,39.0997,-94.5786
|
||||
relay.libernet.app,43.6532,-79.3832
|
||||
relay.earthly.city,34.1749,-118.54
|
||||
relayrs.notoshi.win:443,43.6532,-79.3832
|
||||
nostr.vulpem.com,49.4543,11.0746
|
||||
relay.getsafebox.app:443,43.6532,-79.3832
|
||||
relay.paulstephenborile.com:443,49.4543,11.0746
|
||||
nostr.unkn0wn.world,46.8499,9.53287
|
||||
relay.binaryrobot.com,43.6532,-79.3832
|
||||
relay.wellorder.net,45.5201,-122.99
|
||||
relayone.geektank.ai:443,39.0997,-94.5786
|
||||
relay.wasabiwallet.io,43.6532,-79.3832
|
||||
top.testrelay.top,43.6532,-79.3832
|
||||
nostr.2b9t.xyz,34.0549,-118.243
|
||||
relay.nostr.com,50.1109,8.68213
|
||||
nostr.azzamo.net:443,52.2633,21.0283
|
||||
nostr.dlcdevkit.com,40.0992,-83.1141
|
||||
nostr.robosats.org,64.1476,-21.9392
|
||||
nostr.iskarion.ddns.net,43.3076,-2.95421
|
||||
relay2.angor.io,48.1046,11.6002
|
||||
relay.mmwaves.de,48.8575,2.35138
|
||||
relay.agorist.space:443,52.3734,4.89406
|
||||
relay.dreamith.to,43.6532,-79.3832
|
||||
nostr-02.uid.ovh,50.9871,2.12554
|
||||
nostr.thebiglake.org,32.71,-96.6745
|
||||
nostr-relay.corb.net,39.6478,-104.988
|
||||
relay.dyne.org,49.0291,8.35705
|
||||
nosflare-leefcore.leefcore.workers.dev,43.6532,-79.3832
|
||||
relay.angor.io,48.1046,11.6002
|
||||
nostrelay.circum.space,52.2245,8.826
|
||||
nostr-01.uid.ovh,50.9871,2.12554
|
||||
maxq.descendant.io,43.6532,-79.3832
|
||||
offchain.bostr.online,43.6532,-79.3832
|
||||
relay.binaryrobot.com:443,43.6532,-79.3832
|
||||
nostr.planix.org,43.6532,-79.3832
|
||||
tribune-panel-growing-noon.trycloudflare.com,43.6532,-79.3832
|
||||
prl.plus,55.7628,37.5983
|
||||
relay.mwaters.net,50.9871,2.12554
|
||||
dev.relay.stream,43.6532,-79.3832
|
||||
relay.bornheimer.app,50.1109,8.68213
|
||||
inbox.scuba323.com,40.8218,-74.45
|
||||
relay-us.zombi.cloudrodion.com,40.7862,-74.0743
|
||||
relay.nuts.cash,52.3676,4.90414
|
||||
bruh.samt.st,43.6532,-79.3832
|
||||
nostr.carroarmato0.be:443,50.914,3.21378
|
||||
relay.plebeian.market,50.1109,8.68213
|
||||
schnorr.me:443,43.6532,-79.3832
|
||||
nostr.thalheim.io:443,60.1699,24.9384
|
||||
nostr.88mph.life,52.1941,-2.21905
|
||||
cache.trustr.ing,43.6548,-79.3885
|
||||
ec2.f7z.io,60.1699,24.9384
|
||||
relay.btcforplebs.com,43.6532,-79.3832
|
||||
nostr.liberty.fans,36.8767,-89.5879
|
||||
syb.lol:443,34.0549,-118.243
|
||||
nostr.islandarea.net:443,35.4669,-97.6473
|
||||
nostr.mom,50.4754,12.3683
|
||||
nostr.data.haus:443,50.4754,12.3683
|
||||
strfry.bonsai.com:443,39.0438,-77.4874
|
||||
nostr.highway15.net,28.0445,-82.6699
|
||||
relay.wavlake.com:443,41.2619,-95.8608
|
||||
relay.nostriot.com:443,41.5695,-83.9786
|
||||
relay.gulugulu.moe:443,43.6532,-79.3832
|
||||
rele.speyhard.fi,50.1109,8.68213
|
||||
nostr.hoppe-relay.it.com,42.8864,-78.8784
|
||||
nostr2.girino.org:443,43.6532,-79.3832
|
||||
nostr.oxtr.dev,50.4754,12.3683
|
||||
nostr.bitcoiner.social:443,40.7608,-111.891
|
||||
nostrrelay.taylorperron.com,45.5029,-73.5723
|
||||
nostr-01.yakihonne.com:443,1.32123,103.695
|
||||
nostr.tagomago.me,42.3601,-71.0589
|
||||
relay.hivetalk.org,40.8302,-74.1299
|
||||
relay.sigit.io:443,50.4754,12.3683
|
||||
relay.openresist.com:443,43.6532,-79.3832
|
||||
cs-relay.nostrdev.com:443,50.4754,12.3683
|
||||
no.str.cr:443,8.96171,-83.5246
|
||||
relay.nearhood.co.uk,51.5134,-0.0890675
|
||||
relay.lanavault.space,60.1699,24.9384
|
||||
relay.guggero.org,46.5971,9.59652
|
||||
nexus.libernet.app,43.6532,-79.3832
|
||||
relay.nostriot.com,41.5695,-83.9786
|
||||
dm-test-strfry-discovery.samt.st:443,43.6532,-79.3832
|
||||
x.kojira.io:443,43.6532,-79.3832
|
||||
nostr.4rs.nl,49.0291,8.35696
|
||||
relayone.geektank.ai,39.0997,-94.5786
|
||||
nostr.debate.report,50.1109,8.68213
|
||||
relay.gulugulu.moe,43.6532,-79.3832
|
||||
public.crostr.com,43.6532,-79.3832
|
||||
offchain.pub,39.1585,-94.5728
|
||||
relay.tdw.lol,41.8781,-87.6298
|
||||
relay5.bitransfer.org,43.6532,-79.3832
|
||||
relay.manneken.brussels,49.4543,11.0746
|
||||
chorus.pjv.me,45.5201,-122.99
|
||||
relay.fckstate.net,59.3293,18.0686
|
||||
treuzkas.branruz.com,48.8575,2.35138
|
||||
nostr-relay.cbrx.io,43.6532,-79.3832
|
||||
nostr-relay.nextblockvending.com,47.2343,-119.853
|
||||
nostr.fullstackcash.net,45.5201,-122.99
|
||||
relay.primal.net,43.6532,-79.3832
|
||||
relay.mappingbitcoin.com,43.6532,-79.3832
|
||||
nostr.mikoshi.de,50.1109,8.68213
|
||||
nostr.stakey.net,52.3676,4.90414
|
||||
nostr-rs-relay-qj1h.onrender.com,37.7775,-122.397
|
||||
relay.wavlake.com,41.2619,-95.8608
|
||||
relay.ordoplay.com,50.1109,8.68213
|
||||
relay.klabo.world,47.674,-122.122
|
||||
buzz.cashu.space,50.1109,8.68213
|
||||
relay.nostrhub.fr,48.1045,11.6004
|
||||
portal-relay.pareto.space,49.0291,8.35696
|
||||
dev-relay.nostreon.com,60.1699,24.9384
|
||||
relay.islandbitcoin.com,12.8498,77.6545
|
||||
testr.nymble.world,40.8054,-74.0241
|
||||
relayrs.notoshi.win,43.6532,-79.3832
|
||||
nostr-relay.corb.net:443,39.6478,-104.988
|
||||
cdn.satellite.earth,40.8302,-74.1299
|
||||
rilo.nostria.app,43.6532,-79.3832
|
||||
nostr.pbfs.io,50.4754,12.3683
|
||||
nostr2.girino.org,43.6532,-79.3832
|
||||
0x-nostr-relay.fly.dev,38.7946,-106.535
|
||||
nostr.girino.org,43.6532,-79.3832
|
||||
relay.layer.systems,49.0291,8.35695
|
||||
relay.olas.app,60.1699,24.9384
|
||||
ribo.eu.nostria.app,43.6532,-79.3832
|
||||
buzz.ac2n-share.kozow.com,45.764,4.83566
|
||||
auth.nostr1.com,40.7057,-74.0136
|
||||
relay.bitmacro.cloud,43.6532,-79.3832
|
||||
relay.qstr.app,50.1109,8.68213
|
||||
relay.ru.ac.th,13.7607,100.627
|
||||
ribo.nostria.app:443,43.6532,-79.3832
|
||||
relay.44billion.net,43.6532,-79.3832
|
||||
cs-relay.nostrdev.com,50.4754,12.3683
|
||||
relay.nostrian-conquest.com:443,41.223,-111.974
|
||||
relay.bullishbounty.com,43.6532,-79.3832
|
||||
relay.edufeed.org,49.4521,11.0767
|
||||
porchlight.social,43.6532,-79.3832
|
||||
nostr.bitcoiner.social,40.7608,-111.891
|
||||
testnet-relay.samt.st,40.8302,-74.1299
|
||||
relay.vrtmrz.net,43.6532,-79.3832
|
||||
nostr.wecsats.io:443,43.6532,-79.3832
|
||||
relay.satmaxt.xyz:443,43.6532,-79.3832
|
||||
nostr.snowbla.de,50.4754,12.3683
|
||||
nostr.rtvslawenia.com:443,49.4543,11.0746
|
||||
relay.flashapp.me,43.6548,-79.3885
|
||||
purplerelay.com:443,43.6532,-79.3832
|
||||
relay.staging.plebeian.market,51.5072,-0.127586
|
||||
relay.libernet.app:443,43.6532,-79.3832
|
||||
relay.0xchat.com,43.6532,-79.3832
|
||||
myvoiceourstory.org,37.3598,-121.981
|
||||
nrs-01.darkcloudarcade.com:443,39.0997,-94.5786
|
||||
relay.vrtmrz.net:443,43.6532,-79.3832
|
||||
relay.zone667.com,60.1699,24.9384
|
||||
nostr.nodesmap.com,59.3327,18.0656
|
||||
relay.mostr.pub,43.6532,-79.3832
|
||||
relay.veganostr.com:443,60.1699,24.9384
|
||||
relay.dreamith.to,43.6532,-79.3832
|
||||
buzz.cashu.space,50.1109,8.68213
|
||||
relay.sincensura.org,43.6532,-79.3832
|
||||
relayrs.notoshi.win:443,43.6532,-79.3832
|
||||
nostr.bond,50.1109,8.68213
|
||||
relay.edufeed.org,49.4521,11.0767
|
||||
relay.bullishbounty.com:443,43.6532,-79.3832
|
||||
relay.nostr.blockhenge.com,39.0438,-77.4874
|
||||
relay2.fiatdenier.com,50.1013,8.62643
|
||||
relay.mwaters.net,50.9871,2.12554
|
||||
relay2.angor.io,48.1046,11.6002
|
||||
memlay.v0l.io,53.3498,-6.26031
|
||||
nostr.wecsats.io,43.6532,-79.3832
|
||||
vm-1734.lnvps.cloud,53.3498,-6.26031
|
||||
dm-test-strfry-discovery.samt.st:443,43.6532,-79.3832
|
||||
nostr2.girino.org:443,43.6532,-79.3832
|
||||
nostr.robosats.org,64.1476,-21.9392
|
||||
relay.atsocy.com,43.6532,-79.3832
|
||||
relayone.geektank.ai:443,39.0997,-94.5786
|
||||
relay.olas.app:443,60.1699,24.9384
|
||||
relay.nostu.be,40.4167,-3.70329
|
||||
relay.otrta.me,50.1109,8.68213
|
||||
nostr.spaceshell.xyz,43.6532,-79.3832
|
||||
rilo.nostria.app,43.6532,-79.3832
|
||||
relay.agorist.space,52.3734,4.89406
|
||||
relayone.soundhsa.com:443,39.0997,-94.5786
|
||||
relay.satmaxt.xyz,43.6532,-79.3832
|
||||
relay.zapanimations.com,40.8054,-74.0241
|
||||
relay.sovereignresonance.org,48.8575,2.35138
|
||||
nostr.relay.hedwig.sh,60.1699,24.9384
|
||||
relay-dev.gulugulu.moe:443,43.6532,-79.3832
|
||||
relay.arx-ccn.com,50.4754,12.3683
|
||||
nip85.nosfabrica.com,39.0997,-94.5786
|
||||
relay.nostrmap.net,60.1699,24.9384
|
||||
relay.bullishbounty.com,43.6532,-79.3832
|
||||
nrs-01.darkcloudarcade.com,39.0997,-94.5786
|
||||
spamspamspamspam.rest,43.6532,-79.3832
|
||||
nostr.davenov.com,50.1109,8.68213
|
||||
relay.littlebitstudios.com,43.6532,-79.3832
|
||||
purplerelay.com,43.6532,-79.3832
|
||||
nos.lol:443,50.4754,12.3683
|
||||
relay2.veganostr.com,60.1699,24.9384
|
||||
relay-rpi.edufeed.org,49.4521,11.0767
|
||||
relay.lacrypta.ar,43.6532,-79.3832
|
||||
relay.tdw.gg,34.0549,-118.243
|
||||
relay.getvia.xyz,60.1699,24.9384
|
||||
relay.lanacoin-eternity.com,40.8302,-74.1299
|
||||
conduitl2.fly.dev,38.7946,-106.535
|
||||
relay.angor.io:443,48.1046,11.6002
|
||||
nostr.rtvslawenia.com,49.4543,11.0746
|
||||
relay.wavefunc.live:443,41.8781,-87.6298
|
||||
relay.wasabiwallet.io,43.6532,-79.3832
|
||||
cs-relay.nostrdev.com:443,50.4754,12.3683
|
||||
nostr.4rs.nl,49.0291,8.35696
|
||||
nexus.libernet.app,43.6532,-79.3832
|
||||
relay-dev.gulugulu.moe,43.6532,-79.3832
|
||||
nostr.88mph.life,52.1941,-2.21905
|
||||
relayone.soundhsa.com:443,39.0997,-94.5786
|
||||
social.amanah.eblessing.co,48.1046,11.6002
|
||||
relay.getsafebox.app,43.6532,-79.3832
|
||||
relay.44billion.net,43.6532,-79.3832
|
||||
relay.conduit.market,38.7946,-106.535
|
||||
nostr.infero.net,35.6764,139.65
|
||||
relay.staging.plebeian.market,51.5072,-0.127586
|
||||
relay.snort.social,53.3498,-6.26031
|
||||
purplerelay.com:443,43.6532,-79.3832
|
||||
nostr.mas-family.eu,60.3478,15.7505
|
||||
relay.ru.ac.th,13.7607,100.627
|
||||
relay.openresist.com,43.6532,-79.3832
|
||||
antiprimal.net,43.6532,-79.3832
|
||||
relay.illuminodes.com,43.6532,-79.3832
|
||||
relay.mmwaves.de:443,48.8575,2.35138
|
||||
nostr.spicyz.io,43.6532,-79.3832
|
||||
relay.nostrfeed.com,60.1699,24.9384
|
||||
strfry.apps3.slidestr.net,40.4167,-3.70329
|
||||
relay.directsponsor.net,42.8864,-78.8784
|
||||
relay.nostrhub.fr,48.1045,11.6004
|
||||
nostr.plantroon.com,50.1013,8.62643
|
||||
nostr-verified.wellorder.net,45.5201,-122.99
|
||||
relay.bnos.space:443,43.6532,-79.3832
|
||||
relay.pyramid.li,47.4093,8.46503
|
||||
public.crostr.com,43.6532,-79.3832
|
||||
relay.kilombino.com,43.6532,-79.3832
|
||||
relay.libernet.app:443,43.6532,-79.3832
|
||||
relay.internationalright-wing.org,-22.4692,-48.9875
|
||||
nostr.overmind.lol:443,43.6532,-79.3832
|
||||
relay.ordoplay.com,50.1109,8.68213
|
||||
mostro-p2p.tech,50.1109,8.68213
|
||||
offchain.pub:443,39.1585,-94.5728
|
||||
cs-relay.nostrdev.com,50.4754,12.3683
|
||||
relay-can.zombi.cloudrodion.com,43.6532,-79.3832
|
||||
nostr.unkn0wn.world,46.8499,9.53287
|
||||
relay.shadowbip.com,50.1109,8.68213
|
||||
relay.lanacoin-eternity.com:443,40.8302,-74.1299
|
||||
relay.staging.commonshub.brussels,49.4543,11.0746
|
||||
relay2.angor.io:443,48.1046,11.6002
|
||||
relay.arx-ccn.com,50.4754,12.3683
|
||||
relay.underorion.se,50.1109,8.68213
|
||||
bread.nostrsms.com,40.8218,-74.45
|
||||
relay.cypherflow.ai,48.8575,2.35138
|
||||
nostr.islandarea.net:443,35.4669,-97.6473
|
||||
relay.homeinhk.xyz,35.694,139.754
|
||||
relay.ditto.pub:443,43.6532,-79.3832
|
||||
nostr.fullstackcash.net,45.5201,-122.99
|
||||
bitcoinostr.duckdns.org,38.9504,-0.14007
|
||||
relay.trotters.cc,43.6532,-79.3832
|
||||
portal-relay.pareto.space,49.0291,8.35696
|
||||
relay.openresist.com:443,43.6532,-79.3832
|
||||
relay.endfiat.money,59.3327,18.0656
|
||||
nostr.hoppe-relay.it.com,42.8864,-78.8784
|
||||
nostr.sovereignservices.xyz,43.6532,-79.3832
|
||||
relay.fundstr.me,42.3601,-71.0589
|
||||
relay5.bitransfer.org,43.6532,-79.3832
|
||||
nrs-01.darkcloudarcade.com:443,39.0997,-94.5786
|
||||
testr.nymble.world,40.8054,-74.0241
|
||||
strfry.shock.network:443,39.0438,-77.4874
|
||||
chorus.mikedilger.com:444,-36.8906,174.794
|
||||
relay.minibolt.info,43.6532,-79.3832
|
||||
nos.lol:443,50.4754,12.3683
|
||||
relay.liberbitworld.org,43.6532,-79.3832
|
||||
maxq.descendant.io,43.6532,-79.3832
|
||||
nostrcity-club.fly.dev,38.7946,-106.535
|
||||
relay.nostriot.com,41.5695,-83.9786
|
||||
nostr.n7ekb.net,47.4941,-122.294
|
||||
relay.erybody.com,41.4513,-81.7021
|
||||
nostr-rs-relay-qj1h.onrender.com,37.7775,-122.397
|
||||
nostrrelay.taylorperron.com,45.5029,-73.5723
|
||||
syb.lol,34.0549,-118.243
|
||||
relay.nostr.com,50.1109,8.68213
|
||||
relay.hivetalk.org,40.8302,-74.1299
|
||||
relay.degmods.com,50.4754,12.3683
|
||||
relay.laantungir.net,-19.4692,-42.5315
|
||||
relay.nearhood.co.uk,51.5134,-0.0890675
|
||||
relay.tdw.lol,41.8781,-87.6298
|
||||
ribo.us.nostria.app,43.6532,-79.3832
|
||||
public.obelisk.ar,43.6532,-79.3832
|
||||
prl.plus,55.7628,37.5983
|
||||
21milionidinostr.duckdns.org,45.5192,9.08625
|
||||
nostr.carroarmato0.be,50.914,3.21378
|
||||
nostr.pbfs.io:443,50.4754,12.3683
|
||||
ribo.nostria.app,43.6532,-79.3832
|
||||
nostr.sovereignservices.xyz,43.6532,-79.3832
|
||||
relay.mostr.pub,43.6532,-79.3832
|
||||
schnorr.me,43.6532,-79.3832
|
||||
x.kojira.io,43.6532,-79.3832
|
||||
public.obelisk.ar,43.6532,-79.3832
|
||||
relay.angor.io:443,48.1046,11.6002
|
||||
nostr.oxtr.dev:443,50.4754,12.3683
|
||||
nostr.ltd,19.076,72.8777
|
||||
nostr.tac.lol:443,47.4748,-122.273
|
||||
nostr.twinkle.lol,51.902,7.6657
|
||||
relay.s-w.art,43.6532,-79.3832
|
||||
relay.openfarmtools.org,60.1699,24.9384
|
||||
nostr.hekster.org,37.3986,-121.964
|
||||
strfry.apps3.slidestr.net,40.4167,-3.70329
|
||||
relay.mypathtofire.de,42.8864,-78.8784
|
||||
nostr.wecsats.io,43.6532,-79.3832
|
||||
nostr.computingcache.com,34.0356,-118.442
|
||||
nostr.janx.com,43.6532,-79.3832
|
||||
bread.nostrsms.com,40.8218,-74.45
|
||||
relay.chorus.community,48.5333,10.7
|
||||
testnet-relay.samt.st:443,40.8302,-74.1299
|
||||
relay.kaleidoswap.com,50.8476,4.35717
|
||||
yabu.me,35.6092,139.73
|
||||
nostr-dev.wellorder.net,45.5201,-122.99
|
||||
relay.layer.systems:443,49.0291,8.35695
|
||||
relay-can.zombi.cloudrodion.com,43.6532,-79.3832
|
||||
nostr-relay.zimage.com,34.282,-118.439
|
||||
relay.fundstr.me,42.3601,-71.0589
|
||||
nostr.stakey.net:443,52.3676,4.90414
|
||||
relay.satsmarkt.club,52.6907,4.8181
|
||||
relay.satsapp.me,50.4754,12.3683
|
||||
bendernostur.duckdns.org:8443,50.1109,8.68213
|
||||
relay.illuminodes.com,43.6532,-79.3832
|
||||
nostr.chaima.info:443,50.1109,8.68213
|
||||
nostr.hekster.org:443,37.3986,-121.964
|
||||
nostr.yutakobayashi.com,43.6532,-79.3832
|
||||
freelay.sovbit.host,60.1699,24.9384
|
||||
insta-relay.apps3.slidestr.net,40.4167,-3.70329
|
||||
infinity-signal-relay.digitalforlifeagency.workers.dev,43.6532,-79.3832
|
||||
relay.internationalright-wing.org,-22.4692,-48.9875
|
||||
relay.mmwaves.de:443,48.8575,2.35138
|
||||
public.crostr.com:443,43.6532,-79.3832
|
||||
relay.nostrian-conquest.com,41.223,-111.974
|
||||
relay-rpi.edufeed.org:443,49.4521,11.0767
|
||||
relay2.angor.io:443,48.1046,11.6002
|
||||
relay-fra.zombi.cloudrodion.com,48.8566,2.35222
|
||||
bunk-test.feeds.relay.tools,38.6327,-90.1961
|
||||
nostr-pr02.redscrypt.org,52.3676,4.90414
|
||||
nostr.tac.lol,47.4748,-122.273
|
||||
nostr.overpay.com,29.7449,-95.5343
|
||||
relay.novospes.com,43.6532,-79.3832
|
||||
nostrcity-club.fly.dev:443,38.7946,-106.535
|
||||
relay01.lnfi.network,35.6764,139.65
|
||||
relay.lightning.pub:443,39.0438,-77.4874
|
||||
relay.pyramid.li,47.4093,8.46503
|
||||
relay.nostu.be,40.4167,-3.70329
|
||||
nostr-rs-relay.dev.fedibtc.com:443,39.0438,-77.4874
|
||||
relay.endfiat.money:443,59.3327,18.0656
|
||||
nostr.spaceshell.xyz,43.6532,-79.3832
|
||||
21milionidinostr.duckdns.org,45.5192,9.08625
|
||||
articles.layer3.news:443,37.3387,-121.885
|
||||
wheat.happytavern.co,43.6532,-79.3832
|
||||
nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874
|
||||
nostr.na.social,43.6532,-79.3832
|
||||
social.amanah.eblessing.co,48.1046,11.6002
|
||||
reraw.pbla2fish.cc,43.6532,-79.3832
|
||||
nostr.girino.org:443,43.6532,-79.3832
|
||||
staging.yabu.me,35.6092,139.73
|
||||
nostr.islandarea.net,35.4669,-97.6473
|
||||
relay.endfiat.money,59.3327,18.0656
|
||||
relay.mitchelltribe.com,39.0438,-77.4874
|
||||
nostr.myshosholoza.co.za,52.3676,4.90414
|
||||
relay.staging.commonshub.brussels,49.4543,11.0746
|
||||
espelho.girino.org,43.6532,-79.3832
|
||||
nostr.computingcache.com,45.5341,-122.956
|
||||
relay.chorus.community:443,48.5333,10.7
|
||||
nostr-relay.nextblockvending.com,47.2343,-119.853
|
||||
nostr.easycryptosend.it,43.6532,-79.3832
|
||||
no.str.cr:443,8.96171,-83.5246
|
||||
nostr.snowbla.de:443,50.4754,12.3683
|
||||
bridge.tagomago.me,42.3601,-71.0589
|
||||
relay.agentry.com,42.8864,-78.8784
|
||||
testnet.samt.st,43.6532,-79.3832
|
||||
nostr.wild-vibes.ts.net,48.8566,2.35222
|
||||
relay.getsafebox.app,43.6532,-79.3832
|
||||
relay.notoshi.win,13.3396,100.93
|
||||
bitcoinostr.duckdns.org,41.1976,1.11167
|
||||
relay.sigit.io,50.4754,12.3683
|
||||
bucket.coracle.social,37.7775,-122.397
|
||||
dm-test-strfry-discovery.samt.st,43.6532,-79.3832
|
||||
vault.iris.to:443,43.6532,-79.3832
|
||||
relay.wisp.talk:443,49.4543,11.0746
|
||||
relay.mostro.network,40.8302,-74.1299
|
||||
nostr.l484.com,30.2672,-97.7431
|
||||
relay.froth.zone,60.1699,24.9384
|
||||
relay.beamhop.com,43.6532,-79.3832
|
||||
relay.sincensura.org,43.6532,-79.3832
|
||||
relay.ditto.pub:443,43.6532,-79.3832
|
||||
nostr.azzamo.net,52.2633,21.0283
|
||||
relay.edufeed.org:443,49.4521,11.0767
|
||||
ribo.eu.nostria.app:443,43.6532,-79.3832
|
||||
vault.iris.to,43.6532,-79.3832
|
||||
nostr.plantroon.com,50.1013,8.62643
|
||||
fanfares.nostr1.com,40.7057,-74.0136
|
||||
budabit.nostr1.com,40.7057,-74.0136
|
||||
relay.damus.io,43.6532,-79.3832
|
||||
strfry.shock.network:443,39.0438,-77.4874
|
||||
bitcoiner.social,40.7608,-111.891
|
||||
basspistol.org,49.0291,8.35696
|
||||
nostr.thalheim.io,60.1699,24.9384
|
||||
relay.conduit.market,38.7946,-106.535
|
||||
relay.yoinekodo.jp,43.6532,-79.3832
|
||||
nostrbtc.com,43.6532,-79.3832
|
||||
relay.lightning.pub,39.0438,-77.4874
|
||||
nostr-01.yakihonne.com,1.32123,103.695
|
||||
nostr-pub.wellorder.net,45.5201,-122.99
|
||||
relay.endfiat.money:443,59.3327,18.0656
|
||||
relay.klabo.world,47.674,-122.122
|
||||
articles.layer3.news:443,37.3387,-121.885
|
||||
nostr.21crypto.ch,47.5356,8.73209
|
||||
relay.kaleidoswap.com,50.8476,4.35717
|
||||
nostr.relay.hedwig.sh,60.1699,24.9384
|
||||
nostr.yutakobayashi.com,43.6532,-79.3832
|
||||
strfry.ymir.cloud,43.6532,-79.3832
|
||||
relay.minibolt.info,43.6532,-79.3832
|
||||
nostr.data.haus,50.4754,12.3683
|
||||
relay.otrta.me,50.1109,8.68213
|
||||
relay.samt.st,40.8302,-74.1299
|
||||
relay.nostr.blockhenge.com,39.0438,-77.4874
|
||||
relay.minibolt.info:443,43.6532,-79.3832
|
||||
nostr.2b9t.xyz:443,34.0549,-118.243
|
||||
relay.littlebitstudios.com,43.6532,-79.3832
|
||||
testnet-relay.samt.st:443,40.8302,-74.1299
|
||||
relay.islandbitcoin.com,12.8498,77.6545
|
||||
relay.zone667.com,60.1699,24.9384
|
||||
relay.lightning.pub:443,39.0438,-77.4874
|
||||
relay.layer.systems:443,49.0291,8.35695
|
||||
offchain.pub,39.1585,-94.5728
|
||||
nos.lol,50.4754,12.3683
|
||||
cdn.satellite.earth,40.8302,-74.1299
|
||||
relay-us.zombi.cloudrodion.com,40.7862,-74.0743
|
||||
relay.wisp.talk:443,49.4543,11.0746
|
||||
nostr.hekster.org:443,37.3986,-121.964
|
||||
nostr.mom:443,50.4754,12.3683
|
||||
relay.mccormick.cx:443,52.3563,4.95714
|
||||
dev.relay.stream,43.6532,-79.3832
|
||||
dm-test-nostr-rs-42-disabled.samt.st,43.6532,-79.3832
|
||||
relay.veganostr.com,60.1699,24.9384
|
||||
relay.fountain.fm,43.6532,-79.3832
|
||||
relay.vrtmrz.net:443,43.6532,-79.3832
|
||||
relayone.soundhsa.com,39.0997,-94.5786
|
||||
relay.agorist.space:443,52.3734,4.89406
|
||||
relay-fra.zombi.cloudrodion.com,48.8566,2.35222
|
||||
nostr.dlcdevkit.com:443,40.0992,-83.1141
|
||||
node.kommonzenze.de,49.4521,11.0767
|
||||
relay-arg.zombi.cloudrodion.com,1.35208,103.82
|
||||
nostr.myshosholoza.co.za:443,52.3676,4.90414
|
||||
temp.iris.to,43.6532,-79.3832
|
||||
relay.nostrmap.net,60.1699,24.9384
|
||||
relay.beamhop.com,43.6532,-79.3832
|
||||
nostr.2b9t.xyz:443,34.0549,-118.243
|
||||
relay.chatbett.de,40.7128,-74.006
|
||||
relay.tdw.gg,34.0549,-118.243
|
||||
budabit.nostr1.com,40.7057,-74.0136
|
||||
nostr.novacisko.cz,52.2026,20.9397
|
||||
relay.samt.st,40.8302,-74.1299
|
||||
bruh.samt.st,43.6532,-79.3832
|
||||
nostr.wild-vibes.ts.net,48.8566,2.35222
|
||||
relay.opossumwire.org,34.0549,-118.243
|
||||
relay.angor.io,48.1046,11.6002
|
||||
relay.nostrmap.net:443,60.1699,24.9384
|
||||
nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874
|
||||
chat-relay.zap-work.com,43.6532,-79.3832
|
||||
relay.earthly.city,34.1749,-118.54
|
||||
relay.mappingbitcoin.com,43.6532,-79.3832
|
||||
relay.satmaxt.xyz,43.6532,-79.3832
|
||||
relay.primal.net,43.6532,-79.3832
|
||||
relay.bornheimer.app,50.1109,8.68213
|
||||
nostr.tagomago.me,42.3601,-71.0589
|
||||
relay.orly.dev,32.7767,-96.797
|
||||
no.str.cr,8.96171,-83.5246
|
||||
auth.nostr1.com,40.7057,-74.0136
|
||||
relay.opmaat.org,60.1699,24.9384
|
||||
temp.iris.to,43.6532,-79.3832
|
||||
conduitl2.fly.dev,38.7946,-106.535
|
||||
yabu.me,35.6092,139.73
|
||||
nostrride.io,37.3986,-121.964
|
||||
nostr.plantroon.com:443,50.1013,8.62643
|
||||
relay.wavlake.com,41.2619,-95.8608
|
||||
nostrbtc.com,43.6532,-79.3832
|
||||
relay.mitchelltribe.com:443,39.0438,-77.4874
|
||||
relay.manneken.brussels,49.4543,11.0746
|
||||
relay.lanacoin-eternity.com,40.8302,-74.1299
|
||||
bendernostur.duckdns.org:8443,50.1109,8.68213
|
||||
nostr-kyomu-haskell.onrender.com,37.7775,-122.397
|
||||
nostr.nodesmap.com,59.3327,18.0656
|
||||
nostr-01.yakihonne.com,1.32123,103.695
|
||||
bunk-test.feeds.relay.tools,38.6327,-90.1961
|
||||
relay.btcforplebs.com,43.6532,-79.3832
|
||||
relay.beginningend.com,35.2227,-97.4786
|
||||
nostr.iskarion.ddns.net,43.3076,-2.95421
|
||||
nostr-relay.cbrx.io,43.6532,-79.3832
|
||||
relay.nostrian-conquest.com,41.223,-111.974
|
||||
relay.cosmicbolt.net:443,37.3986,-121.964
|
||||
nostr.oxtr.dev:443,50.4754,12.3683
|
||||
nostr-relay.acloud.kdns.fr,43.6532,-79.3832
|
||||
relay.mccormick.cx,52.3563,4.95714
|
||||
relay.fiatdenier.com,43.7787,-79.5393
|
||||
nostr.bitcoiner.social:443,40.7608,-111.891
|
||||
relay.gulugulu.moe:443,43.6532,-79.3832
|
||||
relay.piazza.today,48.122,11.589
|
||||
nostr.ac,38.958,-77.3592
|
||||
strfry.bonsai.com,39.0438,-77.4874
|
||||
dev-relay.nostreon.com,60.1699,24.9384
|
||||
relay.olas.app,60.1699,24.9384
|
||||
relay.loveisbitcoin.com,43.6532,-79.3832
|
||||
strfry.bonsai.com:443,39.0438,-77.4874
|
||||
nostr.tac.lol,47.4748,-122.273
|
||||
rilo.nostria.app:443,43.6532,-79.3832
|
||||
nostr.azzamo.net,52.2633,21.0283
|
||||
nostr.wecsats.io:443,43.6532,-79.3832
|
||||
relay.wellorder.net,45.5201,-122.99
|
||||
relay.libernet.app,43.6532,-79.3832
|
||||
nostr.thalheim.io,60.1699,24.9384
|
||||
relayrs.notoshi.win,43.6532,-79.3832
|
||||
ribo.nostria.app:443,43.6532,-79.3832
|
||||
relay.gulugulu.moe,43.6532,-79.3832
|
||||
relay.qstr.app,50.1109,8.68213
|
||||
myvoiceourstory.org,37.3598,-121.981
|
||||
nostr-relay.zimage.com,34.282,-118.439
|
||||
treuzkas.branruz.com,48.8575,2.35138
|
||||
top.testrelay.top,43.6532,-79.3832
|
||||
relay.cyberguy.fyi,52.6907,4.8181
|
||||
articles.layer3.news,37.3387,-121.885
|
||||
nostr.sathoarder.com,48.5734,7.75211
|
||||
nostr.carroarmato0.be:443,50.914,3.21378
|
||||
nostr-02.uid.ovh,50.9871,2.12554
|
||||
ribo.us.nostria.app:443,43.6532,-79.3832
|
||||
relay.ditto.pub,43.6532,-79.3832
|
||||
nostr.chaima.info:443,50.1109,8.68213
|
||||
relay.bnos.space,43.6532,-79.3832
|
||||
relay.solife.me,43.6532,-79.3832
|
||||
relay.wavlake.com:443,41.2619,-95.8608
|
||||
bucket.coracle.social,37.7775,-122.397
|
||||
nostr.christiansass.de,52.5244,13.4105
|
||||
relay.dyne.org,49.0291,8.35705
|
||||
testnet-relay.samt.st,40.8302,-74.1299
|
||||
nostream.seitendan.com,34.7062,135.493
|
||||
basspistol.org,49.0291,8.35696
|
||||
purplerelay.com,43.6532,-79.3832
|
||||
relay.wisp.talk,49.4543,11.0746
|
||||
relay.getsafebox.app:443,43.6532,-79.3832
|
||||
freelay.sovbit.host,60.1699,24.9384
|
||||
relay.sigit.io:443,50.4754,12.3683
|
||||
buzz.ac2n-share.kozow.com,45.764,4.83566
|
||||
relay.mostro.network,40.8302,-74.1299
|
||||
nostrcheck.me,43.6532,-79.3832
|
||||
nostr.rtvslawenia.com:443,49.4543,11.0746
|
||||
insta-relay.apps3.slidestr.net,40.4167,-3.70329
|
||||
nostr.vulpem.com,49.4543,11.0746
|
||||
relay-na1.metanomalist.com,43.6532,-79.3832
|
||||
chorus.bonsai.com,39.0438,-77.4874
|
||||
ec2.f7z.io,60.1699,24.9384
|
||||
relay-dev.gulugulu.moe:443,43.6532,-79.3832
|
||||
nostr.twinkle.lol,51.902,7.6657
|
||||
relay.lanavault.space,60.1699,24.9384
|
||||
nostr.pbfs.io,50.4754,12.3683
|
||||
bitchat.nostr1.com,40.7057,-74.0136
|
||||
nostr.highway15.net,28.0445,-82.6699
|
||||
nostr.tabordalab.com,60.1699,24.9384
|
||||
relay.ohstr.com,43.6532,-79.3832
|
||||
schnorr.me,43.6532,-79.3832
|
||||
relay.s-w.art,43.6532,-79.3832
|
||||
relay.nmail.li,50.9871,2.12554
|
||||
bcast.girino.org,43.6532,-79.3832
|
||||
relay.nostrops.com,32.71,-96.6745
|
||||
dm-test-strfry-generic.samt.st,43.6532,-79.3832
|
||||
relay.keykeeper.world,40.7824,-74.0711
|
||||
relay.satsmarkt.club,52.6907,4.8181
|
||||
adre.su,59.9311,30.3609
|
||||
relay.edufeed.org:443,49.4521,11.0767
|
||||
infinity-signal-relay.digitalforlifeagency.workers.dev,43.6532,-79.3832
|
||||
relay.lightning.pub,39.0438,-77.4874
|
||||
porchlight.social,43.6532,-79.3832
|
||||
nostr.snowbla.de,50.4754,12.3683
|
||||
relay.trotters.cc:443,43.6532,-79.3832
|
||||
nostr-relay.xbytez.io,50.6924,3.20113
|
||||
relay.nostr.net,43.6532,-79.3832
|
||||
nostrcity-club.fly.dev:443,38.7946,-106.535
|
||||
nostr.mikoshi.de,50.1109,8.68213
|
||||
nostr.mom,50.4754,12.3683
|
||||
schnorr.me:443,43.6532,-79.3832
|
||||
chat-relay.zap-work.com:443,43.6532,-79.3832
|
||||
relay.paulstephenborile.com:443,49.4543,11.0746
|
||||
relay.fckstate.net,59.3293,18.0686
|
||||
offchain.bostr.online,43.6532,-79.3832
|
||||
nostr.oxtr.dev,50.4754,12.3683
|
||||
testnet.samt.st,43.6532,-79.3832
|
||||
nostr.planix.org,43.6532,-79.3832
|
||||
relay.getvia.xyz,60.1699,24.9384
|
||||
relay.lacrypta.ar,43.6532,-79.3832
|
||||
nostr.janx.com,43.6532,-79.3832
|
||||
relay.yoinekodo.jp,43.6532,-79.3832
|
||||
nostrelay.circum.space,52.2245,8.826
|
||||
staging.yabu.me,35.6092,139.73
|
||||
relay.staging.plebeian.market:443,51.5072,-0.127586
|
||||
reraw.pbla2fish.cc,43.6532,-79.3832
|
||||
relay.satsapp.me,50.4754,12.3683
|
||||
x.kojira.io,43.6532,-79.3832
|
||||
relay.layer.systems,49.0291,8.35695
|
||||
relay01.lnfi.network,35.6764,139.65
|
||||
nostr.whitenode45.ddns.net,40.55,-74.4758
|
||||
relay-rpi.edufeed.org,49.4521,11.0767
|
||||
nostr-dev.wellorder.net,45.5201,-122.99
|
||||
nostr.overmind.lol,43.6532,-79.3832
|
||||
relay.aarpia.com,37.3986,-121.964
|
||||
relayone.geektank.ai,39.0997,-94.5786
|
||||
nostr.purpura.cloud,43.6532,-79.3832
|
||||
nostr.data.haus:443,50.4754,12.3683
|
||||
nexus.libernet.app:443,43.6532,-79.3832
|
||||
nostr.thebiglake.org,32.71,-96.6745
|
||||
relay.bowlafterbowl.com,32.9483,-96.7299
|
||||
relay.mitchelltribe.com,39.0438,-77.4874
|
||||
nostr-relay.xbytez.io:443,50.6924,3.20113
|
||||
bitcoiner.social,40.7608,-111.891
|
||||
relay.guggero.org,46.5971,9.59652
|
||||
relay.mrmave.work,43.6532,-79.3832
|
||||
relay.flashapp.me,43.6548,-79.3885
|
||||
relay.mmwaves.de,48.8575,2.35138
|
||||
nostr.red5d.dev,43.6532,-79.3832
|
||||
relay.plebeian.market,50.1109,8.68213
|
||||
nostr.girino.org,43.6532,-79.3832
|
||||
relay02.lnfi.network,35.6764,139.65
|
||||
relay.vrtmrz.net,43.6532,-79.3832
|
||||
ynostr.yael.at,60.1699,24.9384
|
||||
relay.cosmicbolt.net,37.3986,-121.964
|
||||
nostr-rs-relay.dev.fedibtc.com:443,39.0438,-77.4874
|
||||
nostrelay.circum.space:443,52.2245,8.826
|
||||
nostr.notribe.net,40.8302,-74.1299
|
||||
nostr.tac.lol:443,47.4748,-122.273
|
||||
relay-arg.zombi.cloudrodion.com,1.35208,103.82
|
||||
relay.agora.social,50.7383,15.0648
|
||||
vault.iris.to:443,43.6532,-79.3832
|
||||
relay.nexterz.com,43.6532,-79.3832
|
||||
relay2.veganostr.com,60.1699,24.9384
|
||||
strfry.shock.network,39.0438,-77.4874
|
||||
relay.notoshi.win,13.3396,100.93
|
||||
vault.iris.to,43.6532,-79.3832
|
||||
relay.novospes.com,43.6532,-79.3832
|
||||
nip85.nosfabrica.com,39.0997,-94.5786
|
||||
relay-testnet.k8s.layer3.news,37.3387,-121.885
|
||||
nostr.stakey.net,52.3676,4.90414
|
||||
syb.lol:443,34.0549,-118.243
|
||||
nostr-01.uid.ovh,50.9871,2.12554
|
||||
nostr-02.yakihonne.com,1.32123,103.695
|
||||
relay.favillakey.io,-27.4705,153.026
|
||||
relay.plebeian.market:443,50.1109,8.68213
|
||||
r.iqbqioza.com,43.6532,-79.3832
|
||||
relay.ohstr.com:443,43.6532,-79.3832
|
||||
relay.paulstephenborile.com,49.4543,11.0746
|
||||
nostr.2b9t.xyz,34.0549,-118.243
|
||||
slick.mjex.me,39.0418,-77.4744
|
||||
nostr.hekster.org,37.3986,-121.964
|
||||
nostr.stakey.net:443,52.3676,4.90414
|
||||
relay.sharegap.net,43.6532,-79.3832
|
||||
relay.veganostr.com,60.1699,24.9384
|
||||
nosflare-leefcore.leefcore.workers.dev,43.6532,-79.3832
|
||||
nostr.data.haus,50.4754,12.3683
|
||||
x.kojira.io:443,43.6532,-79.3832
|
||||
relay.nostx.io,43.6532,-79.3832
|
||||
wheat.happytavern.co,43.6532,-79.3832
|
||||
nostr.myshosholoza.co.za:443,52.3676,4.90414
|
||||
inbox.scuba323.com,40.8218,-74.45
|
||||
nostr.chaima.info,50.1109,8.68213
|
||||
relay.nostriches.club,43.6532,-79.3832
|
||||
fanfares.nostr1.com,40.7057,-74.0136
|
||||
relay.bitmacro.cloud,43.6532,-79.3832
|
||||
relay.mostr.pub:443,43.6532,-79.3832
|
||||
nostr.spicyz.io:443,43.6532,-79.3832
|
||||
relay.agentry.com,42.8864,-78.8784
|
||||
nostr.thalheim.io:443,60.1699,24.9384
|
||||
tribune-panel-growing-noon.trycloudflare.com,43.6532,-79.3832
|
||||
relay.mypathtofire.de,42.8864,-78.8784
|
||||
nostr-relay.corb.net:443,39.6478,-104.988
|
||||
dm-test-strfry-discovery.samt.st,43.6532,-79.3832
|
||||
nostr.debate.report,50.1109,8.68213
|
||||
relay.nostrcheck.me,43.6532,-79.3832
|
||||
relay.scuba323.com,40.8218,-74.45
|
||||
relay.decentralia.fr,48.122,11.589
|
||||
fanfares.nostr1.com:443,40.7057,-74.0136
|
||||
relay.sigit.io,50.4754,12.3683
|
||||
ribo.nostria.app,43.6532,-79.3832
|
||||
nostr.davenov.com,50.1109,8.68213
|
||||
nostr.azzamo.net:443,52.2633,21.0283
|
||||
ribo.eu.nostria.app,43.6532,-79.3832
|
||||
nostr-01.yakihonne.com:443,1.32123,103.695
|
||||
desktop-r082364.tail4b3c94.ts.net,48.8566,2.35222
|
||||
0x-nostr-relay.fly.dev,38.7946,-106.535
|
||||
soloco.nl,43.6532,-79.3832
|
||||
relay.chorus.community,48.5333,10.7
|
||||
nostr.sathoarder.com:443,48.5734,7.75211
|
||||
nostr.myshosholoza.co.za,52.3676,4.90414
|
||||
cache.trustr.ing,43.6548,-79.3885
|
||||
nostr.islandarea.net,35.4669,-97.6473
|
||||
nostr-relay.corb.net,39.6478,-104.988
|
||||
relay-rpi.edufeed.org:443,49.4521,11.0767
|
||||
rele.speyhard.fi,50.1109,8.68213
|
||||
relay.pocketnostr.com,40.8054,-74.0241
|
||||
relay.nuts.cash,52.3676,4.90414
|
||||
spamspamspamspam.rest,43.6532,-79.3832
|
||||
nostr.dlcdevkit.com,40.0992,-83.1141
|
||||
relay.satlantis.io,39.0438,-77.4874
|
||||
nostr.na.social,43.6532,-79.3832
|
||||
relay.nostreon.com,60.1699,24.9384
|
||||
|
||||
|
@ -29,9 +29,23 @@ data class PeerCapabilities(val rawValue: Long) : Parcelable {
|
||||
companion object {
|
||||
val NONE = PeerCapabilities(0)
|
||||
|
||||
val PREKEYS = PeerCapabilities(1L shl 0)
|
||||
val WIFI_BULK = PeerCapabilities(1L shl 1)
|
||||
val GATEWAY = PeerCapabilities(1L shl 2)
|
||||
val GROUPS = PeerCapabilities(1L shl 3)
|
||||
val BOARD = PeerCapabilities(1L shl 4)
|
||||
val VOUCH = PeerCapabilities(1L shl 5)
|
||||
val MESH_DIAGNOSTICS = PeerCapabilities(1L shl 6)
|
||||
val BRIDGE = PeerCapabilities(1L shl 7)
|
||||
|
||||
/** Noise-encrypted private BitchatFilePacket using payload type 0x20. */
|
||||
val PRIVATE_MEDIA = PeerCapabilities(1L shl 8)
|
||||
|
||||
val PRIVATE_MEDIA_RECEIPTS = PeerCapabilities(1L shl 9)
|
||||
|
||||
/** Reserved by iOS; decode it but do not advertise or act on it. */
|
||||
val NON_DESTRUCTIVE_NOISE_REPLACEMENT = PeerCapabilities(1L shl 10)
|
||||
|
||||
/** Capabilities implemented by this Android build. */
|
||||
val LOCAL_SUPPORTED = PRIVATE_MEDIA
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.bitchat.android.protocol
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
@ -34,6 +35,27 @@ object SpecialRecipients {
|
||||
val BROADCAST = ByteArray(8) { 0xFF.toByte() } // All 0xFF = broadcast
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload as it arrived on the wire, set by [BinaryProtocol.decode].
|
||||
*
|
||||
* Signatures cover a re-encoding of the packet, and verification re-encodes too.
|
||||
* DEFLATE output is not canonical and clients use different encoders
|
||||
* (java.util.zip.Deflater here, Apple's compression_encode_buffer on iOS), so
|
||||
* re-compressing can change the preimage and reject a valid packet. Reusing these
|
||||
* bytes also stops a relay, which re-encodes on TTL decrement, from substituting
|
||||
* its own encoding.
|
||||
*
|
||||
* [forPayload] ties the bytes to the payload they decode to: replace the payload
|
||||
* and the encoder compresses instead.
|
||||
*
|
||||
* Not a data class: generated equals/hashCode over ByteArray compares references.
|
||||
*/
|
||||
class WirePayload(
|
||||
val bytes: ByteArray,
|
||||
val compressed: Boolean,
|
||||
val forPayload: ByteArray
|
||||
)
|
||||
|
||||
/**
|
||||
* Binary packet format - 100% backward compatible with iOS version
|
||||
*
|
||||
@ -61,7 +83,10 @@ data class BitchatPacket(
|
||||
val payload: ByteArray,
|
||||
var signature: ByteArray? = null, // Changed from val to var for packet signing
|
||||
var ttl: UByte,
|
||||
var route: List<ByteArray>? = null // Optional source route: ordered list of peerIDs (8 bytes each), not including sender and final recipient
|
||||
var route: List<ByteArray>? = null, // Optional source route: ordered list of peerIDs (8 bytes each), not including sender and final recipient
|
||||
// Set by BinaryProtocol.decode. Not part of packet identity, so it stays out of
|
||||
// the parcel, equals and hashCode. Losing it only costs a re-compression.
|
||||
@IgnoredOnParcel val wirePayload: WirePayload? = null
|
||||
) : Parcelable {
|
||||
|
||||
constructor(
|
||||
@ -100,7 +125,8 @@ data class BitchatPacket(
|
||||
payload = payload,
|
||||
signature = null, // Remove signature for signing
|
||||
route = route,
|
||||
ttl = com.bitchat.android.util.AppConstants.SYNC_TTL_HOPS // Use fixed TTL=0 for signing to ensure relay compatibility
|
||||
ttl = com.bitchat.android.util.AppConstants.SYNC_TTL_HOPS, // Use fixed TTL=0 for signing to ensure relay compatibility
|
||||
wirePayload = wirePayload // preimage must use the originator's bytes
|
||||
)
|
||||
return BinaryProtocol.encode(unsignedPacket)
|
||||
}
|
||||
@ -214,7 +240,17 @@ object BinaryProtocol {
|
||||
var originalPayloadSize: Int? = null
|
||||
var isCompressed = false
|
||||
|
||||
if (CompressionUtil.shouldCompress(payload)) {
|
||||
// Re-encode of a decoded packet: reuse the originator's bytes (see WirePayload).
|
||||
val wire = packet.wirePayload
|
||||
if (wire != null && wire.forPayload.contentEquals(packet.payload)) {
|
||||
if (wire.compressed) {
|
||||
payload = wire.bytes
|
||||
originalPayloadSize = packet.payload.size
|
||||
isCompressed = true
|
||||
}
|
||||
// Uncompressed on the wire: keep it that way. shouldCompress agrees
|
||||
// across clients, but the "only if smaller" check need not.
|
||||
} else if (CompressionUtil.shouldCompress(payload)) {
|
||||
CompressionUtil.compress(payload)?.let { compressedPayload ->
|
||||
originalPayloadSize = payload.size
|
||||
payload = compressedPayload
|
||||
@ -450,6 +486,8 @@ object BinaryProtocol {
|
||||
} else null
|
||||
|
||||
// Payload
|
||||
// Kept so the packet can be re-encoded byte-identically (see WirePayload).
|
||||
var receivedCompressed: ByteArray? = null
|
||||
val payload = if (isCompressed) {
|
||||
val lengthFieldBytes = if (version >= 2u.toUByte()) 4 else 2
|
||||
if (payloadLength.toInt() < lengthFieldBytes) return null
|
||||
@ -488,6 +526,8 @@ object BinaryProtocol {
|
||||
val expandedPayload = CompressionUtil.withDecompressionResources(resourceBytes) {
|
||||
val compressedPayload = ByteArray(compressedSize)
|
||||
buffer.get(compressedPayload)
|
||||
// Captured here so the allocation stays inside the reservation.
|
||||
receivedCompressed = compressedPayload
|
||||
decompress(compressedPayload, originalSize)
|
||||
} ?: return null
|
||||
if (expandedPayload.size != originalSize) {
|
||||
@ -520,7 +560,12 @@ object BinaryProtocol {
|
||||
payload = payload,
|
||||
signature = signature,
|
||||
ttl = ttl,
|
||||
route = route
|
||||
route = route,
|
||||
wirePayload = WirePayload(
|
||||
bytes = receivedCompressed ?: payload,
|
||||
compressed = receivedCompressed != null,
|
||||
forPayload = payload
|
||||
)
|
||||
)
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
@ -39,6 +39,7 @@ import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.bitchat.android.ui.theme.BitchatFontFamily
|
||||
@ -46,6 +47,8 @@ import com.bitchat.android.R
|
||||
import com.bitchat.android.core.ui.icon.BitChatIcon
|
||||
import com.bitchat.android.ui.theme.BitchatMotion
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
import com.bitchat.android.util.sdp
|
||||
import com.bitchat.android.util.ssp
|
||||
|
||||
/**
|
||||
* Building blocks for the redesigned About sheet.
|
||||
@ -497,14 +500,14 @@ internal fun BitchatBadge(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(colorScheme.primary.copy(alpha = 0.15f), RoundedCornerShape(4.dp))
|
||||
.padding(horizontal = 5.dp, vertical = 2.dp)
|
||||
.padding(horizontal = 5.sdp(), vertical = 2.sdp())
|
||||
) {
|
||||
Text(
|
||||
text = text.uppercase(),
|
||||
fontFamily = BitchatFontFamily,
|
||||
fontSize = 10.sp,
|
||||
fontSize = 9.ssp(),
|
||||
fontWeight = FontWeight.Bold,
|
||||
letterSpacing = 0.5.sp,
|
||||
letterSpacing = 0.5.ssp(),
|
||||
color = colorScheme.primary
|
||||
)
|
||||
}
|
||||
@ -513,7 +516,8 @@ internal fun BitchatBadge(
|
||||
// MARK: - Shared bottom-sheet primitives
|
||||
|
||||
/** Horizontal inset for sheet content. Cards inset from here; labels align to the same edge. */
|
||||
internal val SheetHorizontalPadding = 16.dp
|
||||
internal val SheetHorizontalPadding: Dp
|
||||
@Composable get() = 16.sdp()
|
||||
|
||||
/**
|
||||
* Section divider label used across the sheets, e.g. `BOOKMARKED`, `NEARBY`, `ON LOCATION`.
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import com.bitchat.android.geohash.ChannelID
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.security.MessageDigest
|
||||
@ -16,7 +17,8 @@ class ChannelManager(
|
||||
private val state: ChatState,
|
||||
private val messageManager: MessageManager,
|
||||
private val dataManager: DataManager,
|
||||
private val coroutineScope: CoroutineScope
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val onSwitchToMeshLocation: () -> Unit = {}
|
||||
) {
|
||||
|
||||
// Channel encryption and security
|
||||
@ -109,6 +111,8 @@ class ChannelManager(
|
||||
fun switchToChannel(channel: String?) {
|
||||
state.setCurrentChannel(channel)
|
||||
state.setSelectedPrivateChatPeer(null)
|
||||
state.setSelectedLocationChannel(ChannelID.Mesh)
|
||||
onSwitchToMeshLocation()
|
||||
|
||||
// Clear unread count
|
||||
channel?.let { ch ->
|
||||
|
||||
@ -140,7 +140,17 @@ class ChatViewModel(
|
||||
private val conversationListPreferences =
|
||||
com.bitchat.android.services.ConversationListPreferences.getInstance(getApplication())
|
||||
private val messageManager = MessageManager(state)
|
||||
private val channelManager = ChannelManager(state, messageManager, dataManager, viewModelScope)
|
||||
private val channelManager = ChannelManager(
|
||||
state,
|
||||
messageManager,
|
||||
dataManager,
|
||||
viewModelScope,
|
||||
onSwitchToMeshLocation = {
|
||||
com.bitchat.android.geohash.LocationChannelManager
|
||||
.getInstance(getApplication())
|
||||
.select(com.bitchat.android.geohash.ChannelID.Mesh)
|
||||
}
|
||||
)
|
||||
|
||||
// Create Noise session delegate for clean dependency injection
|
||||
private val noiseSessionDelegate = object : NoiseSessionDelegate {
|
||||
|
||||
@ -74,6 +74,8 @@ import com.bitchat.android.net.TorMode
|
||||
import com.bitchat.android.net.TorPreferenceManager
|
||||
import com.bitchat.android.ui.theme.BitchatMotion
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
import com.bitchat.android.util.sdp
|
||||
import com.bitchat.android.util.ssp
|
||||
import com.bitchat.android.wifiaware.WifiAwareController
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
@ -237,13 +239,13 @@ fun LocationChannelsSheet(
|
||||
iconRes = R.drawable.ic_spec_range,
|
||||
title = stringResource(R.string.mesh_title),
|
||||
subtitle = stringResource(R.string.mesh_section_subtitle),
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
modifier = Modifier.padding(top = 8.sdp())
|
||||
)
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = AboutHorizontalPadding)
|
||||
.padding(top = 10.dp),
|
||||
.padding(top = 10.sdp()),
|
||||
color = colorScheme.surface,
|
||||
shape = AboutCardShape
|
||||
) {
|
||||
@ -1049,7 +1051,7 @@ private fun ChannelSettingsToggleRow(
|
||||
Text(
|
||||
text = title,
|
||||
fontFamily = BitchatFontFamily,
|
||||
fontSize = 14.sp,
|
||||
fontSize = 14.ssp(),
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = if (enabled) colorScheme.onSurface else palette.textTertiary
|
||||
)
|
||||
@ -1058,12 +1060,12 @@ private fun ChannelSettingsToggleRow(
|
||||
Text(
|
||||
text = subtitle,
|
||||
fontFamily = BitchatFontFamily,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 17.sp,
|
||||
fontSize = 12.ssp(),
|
||||
lineHeight = 17.ssp(),
|
||||
color = if (enabled) colorScheme.onSurfaceVariant else palette.textTertiary
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Spacer(modifier = Modifier.width(16.sdp()))
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = { if (enabled) onCheckedChange(it) },
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
package com.bitchat.android.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlin.math.min
|
||||
|
||||
/** Reference design width (dp) the UI was designed against — matches a standard phone viewport. */
|
||||
private const val BASE_WIDTH = 360f
|
||||
|
||||
/** Clamp scaling so tiny/huge screens (foldables, tablets) don't over- or under-scale content. */
|
||||
private const val MIN_SCALE = 0.85f
|
||||
private const val MAX_SCALE = 1.30f
|
||||
|
||||
/**
|
||||
* Returns the current width-based scale factor, clamped to [MIN_SCALE, MAX_SCALE].
|
||||
* Uses the smaller of width/height dp so rotation to landscape doesn't blow up the scale.
|
||||
*/
|
||||
@SuppressLint("ConfigurationScreenWidthHeight")
|
||||
@Composable
|
||||
private fun rememberUiScale(): Float {
|
||||
val configuration = LocalConfiguration.current
|
||||
val shortestDp = min(configuration.screenWidthDp, configuration.screenHeightDp)
|
||||
return remember(shortestDp) {
|
||||
(shortestDp / BASE_WIDTH).coerceIn(MIN_SCALE, MAX_SCALE)
|
||||
}
|
||||
}
|
||||
|
||||
/** Scales a dp value relative to [BASE_WIDTH], clamped to avoid extreme scaling on outlier screens. */
|
||||
@Composable
|
||||
fun Float.sdp(): Dp = (this * rememberUiScale()).dp
|
||||
|
||||
/** Scales a sp value relative to [BASE_WIDTH], clamped to avoid extreme scaling on outlier screens. */
|
||||
@Composable
|
||||
fun Float.ssp(): TextUnit = (this * rememberUiScale()).sp
|
||||
|
||||
/** Int convenience overload — lets you write `16.sdp()` directly. */
|
||||
@Composable
|
||||
fun Int.sdp(): Dp = this.toFloat().sdp()
|
||||
|
||||
@Composable
|
||||
fun Int.ssp(): TextUnit = this.toFloat().ssp()
|
||||
|
||||
/** Double convenience overload — covers decimal literals like `8.5.sdp()`, which Kotlin infers as Double by default. */
|
||||
@Composable
|
||||
fun Double.sdp(): Dp = this.toFloat().sdp()
|
||||
|
||||
@Composable
|
||||
fun Double.ssp(): TextUnit = this.toFloat().ssp()
|
||||
@ -942,6 +942,61 @@ class BinaryProtocolTest {
|
||||
assertTrue("Payload must match", payload.contentEquals(decoded.payload))
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-encoding preserves a payload compressed by a different DEFLATE encoder
|
||||
*
|
||||
* Verification re-encodes the packet, so a re-encode must reproduce the
|
||||
* originator's bytes. iOS compresses with Apple's compression_encode_buffer,
|
||||
* which need not match java.util.zip.Deflater byte for byte.
|
||||
*
|
||||
* The payload is wrapped in a raw-DEFLATE "stored" block (BTYPE=00): valid, it
|
||||
* inflates correctly, and no compressor would emit it, so it stands in for a
|
||||
* foreign encoder without a second compression library. Compared unpadded so
|
||||
* the result does not depend on how padding is generated.
|
||||
*/
|
||||
@Test
|
||||
fun `re-encoding preserves a foreign encoder's compressed payload`() {
|
||||
val payload = "the mesh is up near the north gate. relay running all evening. "
|
||||
.repeat(4)
|
||||
.toByteArray()
|
||||
|
||||
// Raw DEFLATE stored block: BFINAL=1 BTYPE=00, then LEN and NLEN little-endian.
|
||||
val stored = ByteArray(5 + payload.size)
|
||||
stored[0] = 0x01
|
||||
stored[1] = (payload.size and 0xFF).toByte()
|
||||
stored[2] = ((payload.size shr 8) and 0xFF).toByte()
|
||||
stored[3] = (payload.size.inv() and 0xFF).toByte()
|
||||
stored[4] = ((payload.size.inv() shr 8) and 0xFF).toByte()
|
||||
payload.copyInto(stored, 5)
|
||||
|
||||
val buffer = ByteBuffer.allocate(1024).apply { order(ByteOrder.BIG_ENDIAN) }
|
||||
buffer.put(2.toByte()) // version = 2
|
||||
buffer.put(MessageType.MESSAGE.value.toByte()) // type
|
||||
buffer.put(5.toByte()) // ttl
|
||||
buffer.putLong(fixedTimestamp.toLong()) // timestamp (8 bytes)
|
||||
buffer.put(BinaryProtocol.Flags.IS_COMPRESSED.toByte()) // flags
|
||||
buffer.putInt(stored.size + 4) // payloadLength incl. originalSize
|
||||
buffer.put(hexToBytes(senderHex)) // senderID (8 bytes)
|
||||
buffer.putInt(payload.size) // originalSize
|
||||
buffer.put(stored) // compressed payload
|
||||
|
||||
val raw = ByteArray(buffer.position())
|
||||
buffer.rewind()
|
||||
buffer.get(raw)
|
||||
|
||||
val decoded = BinaryProtocol.decode(raw)
|
||||
assertNotNull("Foreign-encoded frame must decode", decoded)
|
||||
assertArrayEquals("Payload must inflate to the original", payload, decoded!!.payload)
|
||||
|
||||
val reEncoded = BinaryProtocol.encode(decoded, padding = false)
|
||||
assertNotNull("Re-encode must succeed", reEncoded)
|
||||
assertArrayEquals(
|
||||
"Re-encode must reproduce the originator's bytes, or a valid signature stops verifying",
|
||||
raw,
|
||||
reEncoded
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* v2 compression bomb is rejected
|
||||
*
|
||||
|
||||
@ -24,11 +24,12 @@ import org.robolectric.RobolectricTestRunner
|
||||
import java.util.Date
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class CommandProcessorTest() {
|
||||
class CommandProcessorTest {
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val testScope = TestScope(testDispatcher)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val testScope = TestScope(testDispatcher)
|
||||
private val chatState = ChatState(scope = testScope)
|
||||
private lateinit var commandProcessor: CommandProcessor
|
||||
|
||||
@ -63,11 +64,11 @@ class CommandProcessorTest() {
|
||||
val channel = "channel-1"
|
||||
|
||||
val result = commandProcessor.processCommand(
|
||||
command = "/j $channel",
|
||||
meshService = meshService,
|
||||
myPeerID = "peer-id",
|
||||
onSendMessage = { a, b, c -> { } },
|
||||
viewModel = null
|
||||
command = "/j $channel",
|
||||
meshService = meshService,
|
||||
myPeerID = "peer-id",
|
||||
onSendMessage = { a, b, c -> { } },
|
||||
viewModel = null
|
||||
)
|
||||
|
||||
assertEquals(result, true)
|
||||
@ -163,6 +164,24 @@ class CommandProcessorTest() {
|
||||
assertEquals(0, chatState.getMessagesValue().size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `join command leaves active geohash selection for mesh channel routing`() {
|
||||
chatState.setSelectedLocationChannel(
|
||||
ChannelID.Location(GeohashChannel(GeohashChannelLevel.REGION, "9q"))
|
||||
)
|
||||
|
||||
commandProcessor.processCommand(
|
||||
command = "/join backchannel",
|
||||
meshService = meshService,
|
||||
myPeerID = "peer-id",
|
||||
onSendMessage = { _, _, _ -> },
|
||||
viewModel = null
|
||||
)
|
||||
|
||||
assertEquals("#backchannel", chatState.getCurrentChannelValue())
|
||||
assertEquals(ChannelID.Mesh, chatState.selectedLocationChannel.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clearSuggestions hides the command suggestion popup`() {
|
||||
// Typing "/" opens the command popup.
|
||||
@ -187,5 +206,6 @@ class CommandProcessorTest() {
|
||||
commandProcessor.clearSuggestions()
|
||||
assertFalse(chatState.getShowMentionSuggestionsValue())
|
||||
assertTrue(chatState.getMentionSuggestionsValue().isEmpty())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,38 @@ class IdentityAnnouncementTest {
|
||||
private val signingKey = ByteArray(32) { 0x22 }
|
||||
|
||||
@Test
|
||||
fun `private media capability uses iOS little-endian bytes`() {
|
||||
fun `capability assignments and encoding match iOS`() {
|
||||
assertEquals(1L shl 0, PeerCapabilities.PREKEYS.rawValue)
|
||||
assertEquals(1L shl 1, PeerCapabilities.WIFI_BULK.rawValue)
|
||||
assertEquals(1L shl 2, PeerCapabilities.GATEWAY.rawValue)
|
||||
assertEquals(1L shl 3, PeerCapabilities.GROUPS.rawValue)
|
||||
assertEquals(1L shl 4, PeerCapabilities.BOARD.rawValue)
|
||||
assertEquals(1L shl 5, PeerCapabilities.VOUCH.rawValue)
|
||||
assertEquals(1L shl 6, PeerCapabilities.MESH_DIAGNOSTICS.rawValue)
|
||||
assertEquals(1L shl 7, PeerCapabilities.BRIDGE.rawValue)
|
||||
assertEquals(1L shl 8, PeerCapabilities.PRIVATE_MEDIA.rawValue)
|
||||
assertEquals(1L shl 9, PeerCapabilities.PRIVATE_MEDIA_RECEIPTS.rawValue)
|
||||
assertEquals(1L shl 10, PeerCapabilities.NON_DESTRUCTIVE_NOISE_REPLACEMENT.rawValue)
|
||||
|
||||
val assigned = PeerCapabilities((1L shl 11) - 1)
|
||||
assertArrayEquals(byteArrayOf(0xFF.toByte(), 0x07), assigned.encoded())
|
||||
assertEquals(assigned, PeerCapabilities.decode(assigned.encoded()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `capability encoding is minimal and preserves low 64 unknown bits`() {
|
||||
assertArrayEquals(byteArrayOf(0x00), PeerCapabilities.NONE.encoded())
|
||||
assertArrayEquals(byteArrayOf(0x01), PeerCapabilities.PREKEYS.encoded())
|
||||
assertArrayEquals(byteArrayOf(0x00, 0x01), PeerCapabilities.PRIVATE_MEDIA.encoded())
|
||||
assertTrue(PeerCapabilities.decode(byteArrayOf(0x00, 0x01)).contains(PeerCapabilities.PRIVATE_MEDIA))
|
||||
|
||||
val future = PeerCapabilities.decode(
|
||||
byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80.toByte(), 0x55)
|
||||
)
|
||||
assertEquals(Long.MIN_VALUE or 1L, future.rawValue)
|
||||
assertArrayEquals(
|
||||
byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80.toByte()),
|
||||
future.encoded()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -71,5 +100,6 @@ class IdentityAnnouncementTest {
|
||||
.capabilities!!
|
||||
.contains(PeerCapabilities.PRIVATE_MEDIA)
|
||||
)
|
||||
assertEquals(PeerCapabilities.PRIVATE_MEDIA, PeerCapabilities.LOCAL_SUPPORTED)
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,9 +29,16 @@ Existing TLVs (unchanged):
|
||||
|
||||
Other optional extension:
|
||||
|
||||
- `0x05` CAPABILITIES: Minimal little-endian feature bitfield. Bit 8 advertises
|
||||
authenticated Noise private media (`PRIVATE_MEDIA_V1`); its exact value is
|
||||
`00 01`. An empty value is valid and means no advertised capabilities.
|
||||
- `0x05` CAPABILITIES: Minimal little-endian feature bitfield. Assigned bits
|
||||
match iOS: 0 prekeys, 1 Wi-Fi bulk, 2 gateway, 3 private groups, 4 board,
|
||||
5 vouch, 6 mesh diagnostics, 7 bridge, 8 authenticated Noise private media,
|
||||
9 private-media receipts, and 10 reserved non-destructive Noise replacement.
|
||||
Bit 8's exact value is `00 01`. An empty value is valid and means no
|
||||
advertised capabilities. For this announcement TLV, unknown bits and bytes
|
||||
beyond the low 64 bits must be tolerated for forward compatibility. This
|
||||
tolerance does not apply to the Noise-authenticated peer-state payload
|
||||
`0x21`, whose capability field must be canonical and 1...8 bytes; see
|
||||
`PRIVATE_MEDIA_V1.md`.
|
||||
|
||||
New TLV (optional):
|
||||
|
||||
@ -69,6 +76,12 @@ This matches the on‑wire 8‑byte `senderID`/`recipientID` encoding used in th
|
||||
security-sensitive bit is trusted only after the signed announcement key is
|
||||
bound to the matching remote static key from a live Noise handshake; see
|
||||
`PRIVATE_MEDIA_V1.md`.
|
||||
- Capability bits are independent discovery hints. Implementations advertise
|
||||
only wire features they implement and must not infer support from platform,
|
||||
app version, or unrelated transports with similar names.
|
||||
- Private-media receipts and retry (bit 9) require the current Noise
|
||||
generation's authenticated `0x21` peer state to prove both bits 8 and 9. An
|
||||
announcement alone never authorizes that behavior.
|
||||
- If a `0x04` TLV is present:
|
||||
- Interpret the value as `N = length / 8` peer IDs (ignore trailing non‑aligned bytes).
|
||||
- Each 8‑byte chunk is decoded back to a 16‑hex‑char peer ID string (lowercase).
|
||||
|
||||
@ -11,11 +11,10 @@ The central rule is:
|
||||
No keystore or password is stored in GitHub, GitHub Actions, the repository,
|
||||
release notes, or workflow artifacts.
|
||||
|
||||
This runbook currently releases only the phone app, `com.bitchat.droid`.
|
||||
The `:wear` module is tested and dependency-locked in CI but is not included in
|
||||
the canonical release artifact set. Do not publish its development-signed
|
||||
outputs. Wear distribution requires a separate signing identity, reproducible
|
||||
artifact contract, Play listing, and maintainer runbook.
|
||||
This runbook releases both the phone and Wear OS apps under the shared Play
|
||||
application ID `com.bitchat.droid`. Wear releases use the independent version
|
||||
code range beginning at `1000000001`; every phone and Wear artifact uploaded to
|
||||
one Play listing must have a unique version code.
|
||||
|
||||
For the technical trust model and third-party verification instructions, see
|
||||
[Reproducible builds](reproducible-builds.md).
|
||||
@ -28,7 +27,7 @@ For the technical trust model and third-party verification instructions, see
|
||||
| Create tag | Maintainer machine | Signed `vX.Y.Z` tag |
|
||||
| Build twice | GitHub Actions | Two identical unsigned APK/AAB builds |
|
||||
| Promote build | GitHub Actions | Attested `verified-unsigned-release` artifact |
|
||||
| Sign | Maintainer machine | Three installable APKs and one Play upload AAB |
|
||||
| Sign | Maintainer machine | Four installable APKs and two Play upload AABs |
|
||||
| Test Play build | Play Console internal track | Play-generated APKs tested before public rollout |
|
||||
| Prepare release | Maintainer machine and GitHub draft | Checksummed public release assets |
|
||||
| Publish | GitHub Releases and Play Console | Public GitHub release and promoted Play rollout |
|
||||
@ -42,8 +41,8 @@ These are four separate signing identities:
|
||||
2. **GitHub APK signature**: lets Android install and update the APKs published
|
||||
on GitHub. Use the existing GitHub release key. Its certificate SHA-256 must
|
||||
match `BITCHAT_GITHUB_RELEASE_CERT_SHA256` in `gradle.properties`.
|
||||
3. **Play upload signature**: the maintainer signs the AAB with the Play upload
|
||||
key so Play will accept it.
|
||||
3. **Play upload signature**: the maintainer signs both AABs with the Play
|
||||
upload key so Play will accept them.
|
||||
4. **Play app signature**: Google generates device APKs and signs them with the
|
||||
separate Play app-signing key.
|
||||
|
||||
@ -119,9 +118,10 @@ a command line, in a shell profile, or in a release-notes file.
|
||||
|
||||
## 1. Prepare and approve the release commit
|
||||
|
||||
1. Update `versionCode` and `versionName` in `app/build.gradle.kts`.
|
||||
`versionCode` must be greater than every build previously uploaded to Play.
|
||||
`versionName` must match the release tag without the leading `v`.
|
||||
1. Update `versionCode` and `versionName` in `app/build.gradle.kts`. For a Wear
|
||||
release, also update them in `wear/build.gradle.kts` using the reserved Wear
|
||||
version-code range. Every code must be unique across both form factors.
|
||||
The phone `versionName` must match the release tag without the leading `v`.
|
||||
2. Merge all intended release changes into `main`.
|
||||
3. Confirm required CI checks are green.
|
||||
4. Complete the
|
||||
@ -147,6 +147,7 @@ Set shell variables for the rest of the release:
|
||||
export REPOSITORY=permissionlesstech/bitchat-android
|
||||
export TAG=vX.Y.Z
|
||||
export VERSION_CODE=NN
|
||||
export WEAR_VERSION_CODE=1000000001
|
||||
export RELEASE_DIR="release-${TAG#v}"
|
||||
```
|
||||
|
||||
@ -156,6 +157,7 @@ Confirm the source version:
|
||||
grep -nE 'versionCode|versionName' app/build.gradle.kts
|
||||
test "$(sed -n 's/.*versionName = "\([^"]*\)".*/\1/p' app/build.gradle.kts)" = "${TAG#v}"
|
||||
test "$(sed -n 's/.*versionCode = \([0-9][0-9]*\).*/\1/p' app/build.gradle.kts)" = "$VERSION_CODE"
|
||||
test "$(sed -n 's/.*versionCode = \([0-9_][0-9_]*\).*/\1/p' wear/build.gradle.kts | tr -d _)" = "$WEAR_VERSION_CODE"
|
||||
```
|
||||
|
||||
Stop if either `test` command fails.
|
||||
@ -251,6 +253,8 @@ The directory must initially contain exactly these canonical files:
|
||||
- `bitchat-android-x86-unsigned.apk`
|
||||
- `bitchat-android-x86_64-unsigned.apk`
|
||||
- `bitchat-android-release-unsigned.aab`
|
||||
- `bitchat-android-wear-unsigned.apk`
|
||||
- `bitchat-android-wear-release-unsigned.aab`
|
||||
|
||||
Verify the checksum manifest:
|
||||
|
||||
@ -328,6 +332,7 @@ It creates:
|
||||
|
||||
- `bitchat-android-arm64.apk`
|
||||
- `bitchat-android-universal.apk`
|
||||
- `bitchat-android-wear.apk`
|
||||
- `bitchat-android-x86_64.apk`
|
||||
|
||||
The unsigned armv7 and x86 APKs remain available for reproducibility, but are
|
||||
@ -337,7 +342,7 @@ If the helper stops after creating any signed file, do not continue or overwrite
|
||||
files manually. Start again in a new directory downloaded from the same
|
||||
successful workflow run.
|
||||
|
||||
## 6. Sign the Google Play AAB locally
|
||||
## 6. Sign the Google Play AABs locally
|
||||
|
||||
Set the pinned JDK and Play upload-key locations, then load the passwords:
|
||||
|
||||
@ -361,18 +366,20 @@ Run:
|
||||
tools/reproducible-builds/sign-play-bundle.sh "$RELEASE_DIR"
|
||||
```
|
||||
|
||||
The helper creates `bitchat-android-play-upload.aab`, verifies its JAR
|
||||
signature, proves that every non-signature payload entry matches the canonical
|
||||
The helper creates `bitchat-android-play-upload.aab` and
|
||||
`bitchat-android-wear-play-upload.aab`, verifies their JAR signatures, proves
|
||||
that every non-signature payload entry matches the corresponding canonical
|
||||
unsigned AAB, and updates `SHA256SUMS`.
|
||||
|
||||
This is the only file to upload to Play Console:
|
||||
These are the only files to upload to Play Console:
|
||||
|
||||
```text
|
||||
release-X.Y.Z/bitchat-android-play-upload.aab
|
||||
release-X.Y.Z/bitchat-android-wear-play-upload.aab
|
||||
```
|
||||
|
||||
Do not upload an APK or `bitchat-android-release-unsigned.aab` to Play. Do not
|
||||
open Android Studio and rebuild the bundle.
|
||||
Do not upload an APK or either `*-release-unsigned.aab` to Play. Do not open
|
||||
Android Studio and rebuild the bundles.
|
||||
|
||||
Remove passwords from the environment after both signing steps:
|
||||
|
||||
@ -394,16 +401,20 @@ tools/reproducible-builds/prepare-github-release.sh "$RELEASE_DIR"
|
||||
The helper verifies all checksums and renames the public build information and
|
||||
checksum manifests. It refuses missing or pre-existing release files.
|
||||
|
||||
The final GitHub Release must contain all 13 files below:
|
||||
The final GitHub Release must contain all 17 files below:
|
||||
|
||||
| Asset | Signed? | Why it is published |
|
||||
|---|---:|---|
|
||||
| `bitchat-android-arm64.apk` | APK release key | Primary direct-install APK |
|
||||
| `bitchat-android-universal.apk` | APK release key | Fallback direct-install APK |
|
||||
| `bitchat-android-x86_64.apk` | APK release key | x86_64 install APK |
|
||||
| Five `bitchat-android-*-unsigned.apk` files | No | Reproducibility inputs for every ABI target |
|
||||
| `bitchat-android-wear.apk` | APK release key | Wear OS direct-install APK |
|
||||
| Five phone `bitchat-android-*-unsigned.apk` files | No | Reproducibility inputs for every phone ABI target |
|
||||
| `bitchat-android-release-unsigned.aab` | No | Canonical reproducible Play input |
|
||||
| `bitchat-android-play-upload.aab` | Play upload key | Exact bundle uploaded to Play |
|
||||
| `bitchat-android-wear-unsigned.apk` | No | Canonical reproducible Wear install input |
|
||||
| `bitchat-android-wear-release-unsigned.aab` | No | Canonical reproducible Wear Play input |
|
||||
| `bitchat-android-wear-play-upload.aab` | Play upload key | Exact bundle uploaded to the Wear OS track |
|
||||
| `BITCHAT_BUILDINFO.json` | GitHub attestation | Source commit and pinned toolchain |
|
||||
| `BITCHAT_SHA256SUMS.unsigned` | GitHub attestation | Original canonical CI manifest |
|
||||
| `BITCHAT_SHA256SUMS` | No detached signature | SHA-256 for every published asset |
|
||||
@ -432,12 +443,13 @@ Create a local release-notes file. At minimum it must contain:
|
||||
## Bitchat Android vX.Y.Z
|
||||
|
||||
- Version code: NN
|
||||
- Wear version: 0.1.0 (code 1000000001)
|
||||
- Source tag: vX.Y.Z
|
||||
- GitHub APK signing certificate SHA-256: FINGERPRINT
|
||||
- Play upload certificate SHA-256: FINGERPRINT
|
||||
- Play app-signing certificate SHA-256: FINGERPRINT
|
||||
|
||||
Checksums, canonical unsigned APK/AAB inputs, the exact Play upload AAB, and
|
||||
Checksums, canonical unsigned APK/AAB inputs, the exact Play upload AABs, and
|
||||
build information are attached. See `docs/reproducible-builds.md` for public
|
||||
verification instructions.
|
||||
|
||||
@ -474,7 +486,7 @@ gh release view "$TAG" --repo "$REPOSITORY" --web
|
||||
|
||||
Keep it as a draft until the Play internal-track checks below pass.
|
||||
|
||||
## 9. Upload and test the AAB in Google Play
|
||||
## 9. Upload and test the AABs in Google Play
|
||||
|
||||
1. Open Play Console and select `com.bitchat.droid`.
|
||||
2. Open **Test and release > Testing > Internal testing**.
|
||||
@ -495,16 +507,23 @@ Keep it as a draft until the Play internal-track checks below pass.
|
||||
10. In **Setup > App integrity**, confirm the Play app-signing certificate
|
||||
SHA-256 is the value recorded in the GitHub release notes.
|
||||
|
||||
Promote this same tested Play release from internal testing to production.
|
||||
Do not rebuild or upload a second AAB for production. Use a staged production
|
||||
rollout when appropriate.
|
||||
For the initial Wear release, open **Test and release > Advanced settings >
|
||||
Form factors**, add Wear OS, upload the required Wear screenshot, and use the
|
||||
dedicated **Wear OS only** test track. Upload exactly
|
||||
`bitchat-android-wear-play-upload.aab`, confirm version code `1000000001`, and
|
||||
complete the Wear OS opt-in and review flow. Promote the already-tested Wear
|
||||
artifact on its dedicated track; do not add it to the mobile track.
|
||||
|
||||
Promote these same tested Play releases from their test tracks to their
|
||||
respective production tracks. Do not rebuild or upload replacement AABs for
|
||||
production. Use staged production rollouts when appropriate.
|
||||
|
||||
Google signs the device APKs with the Play app-signing key, so Play-delivered
|
||||
APKs will not be byte-identical to the GitHub APKs. That is expected.
|
||||
|
||||
## 10. Publish GitHub and promote Play
|
||||
|
||||
After the internal Play build passes and the GitHub draft has all 13 assets:
|
||||
After both Play test builds pass and the GitHub draft has all 17 assets:
|
||||
|
||||
```bash
|
||||
gh release edit "$TAG" \
|
||||
@ -539,7 +558,7 @@ tools/reproducible-builds/verify-github-release.sh "$TAG"
|
||||
Also verify:
|
||||
|
||||
- the GitHub release is marked **Latest**;
|
||||
- the three signed APKs install and show the expected version;
|
||||
- the four signed APKs install and show the expected version;
|
||||
- the Play listing shows the intended production version and rollout state;
|
||||
- a Play-installed build is signed by the app-signing certificate recorded in
|
||||
the release notes; and
|
||||
@ -572,10 +591,10 @@ Also verify:
|
||||
- [ ] `verified-unsigned-release` downloaded by run ID
|
||||
- [ ] Checksums, source commit, and attestations verified
|
||||
- [ ] GitHub APKs signed locally with the pinned certificate
|
||||
- [ ] Play AAB signed locally with the registered upload key
|
||||
- [ ] `BITCHAT_SHA256SUMS` verifies all 13 release assets
|
||||
- [ ] Phone and Wear Play AABs signed locally with the registered upload key
|
||||
- [ ] `BITCHAT_SHA256SUMS` verifies all 17 release assets
|
||||
- [ ] GitHub draft created with certificate fingerprints and all assets
|
||||
- [ ] Exact signed AAB uploaded to and tested on Play internal track
|
||||
- [ ] Exact signed phone and Wear AABs uploaded to and tested on their tracks
|
||||
- [ ] GitHub Release published
|
||||
- [ ] Same tested Play release promoted to production
|
||||
- [ ] Public GitHub and Play verification completed
|
||||
@ -586,3 +605,5 @@ Also verify:
|
||||
- [GitHub CLI: create a release](https://cli.github.com/manual/gh_release_create)
|
||||
- [Google Play App Signing](https://support.google.com/googleplay/android-developer/answer/9842756)
|
||||
- [Play Console App bundle explorer](https://support.google.com/googleplay/android-developer/answer/9859152)
|
||||
- [Package and distribute Wear OS apps](https://developer.android.com/training/wearables/packaging)
|
||||
- [Manage form-factor releases on dedicated tracks](https://support.google.com/googleplay/android-developer/answer/13295490)
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
# Reproducible builds
|
||||
|
||||
Bitchat's canonical phone-app release build produces byte-for-byte reproducible
|
||||
unsigned APKs and an unsigned Android App Bundle (AAB). CI builds the release
|
||||
Bitchat's canonical release build produces byte-for-byte reproducible unsigned
|
||||
phone and Wear OS APKs and Android App Bundles (AABs). CI builds the release
|
||||
twice in independent jobs and exposes a verified release artifact only when
|
||||
every canonical byte matches. The `:wear` module is tested and
|
||||
dependency-locked but is outside this phone artifact contract until a separate
|
||||
Wear release and signing process is defined.
|
||||
every canonical byte matches.
|
||||
|
||||
Signing remains local: no keystore or signing password is stored in or exposed
|
||||
to GitHub Actions. Anyone can reproduce the unsigned artifacts; maintainers
|
||||
download the verified CI output, sign the selected GitHub APKs and Play upload
|
||||
AAB locally, then manually publish those exact files.
|
||||
AABs locally, then manually publish those exact files.
|
||||
|
||||
Maintainers should use the complete
|
||||
[Android maintainer release guide](maintainer-release-guide.md) for the
|
||||
@ -33,10 +31,15 @@ GitHub/Google Play publication checklist.
|
||||
|
||||
The build uses a clean source tree, an isolated Gradle user home, UTC, a stable
|
||||
locale, `SOURCE_DATE_EPOCH` from the Git commit, no Gradle build or configuration
|
||||
cache, fresh tasks, and a non-incremental in-process Kotlin compiler. Native
|
||||
builds remap source paths and release validation rejects host paths in packaged
|
||||
libraries. The container overlays a canonical `local.properties`, so an ignored
|
||||
Android Studio file cannot redirect Gradle to a host-specific SDK.
|
||||
cache, fresh tasks, a non-incremental in-process Kotlin compiler, and R8's
|
||||
deterministic-debugging mode. The R8 mode uses one compiler thread and disables
|
||||
randomized input shuffling. The regular R8 ProGuard map remains embedded in each
|
||||
AAB. Kotlin 2.4.10's optional Compose group-key mapping augmentation is disabled
|
||||
because its duplicate-key selection depends on unspecified class-file iteration
|
||||
order across clean builds. Native builds remap source paths and release
|
||||
validation rejects host paths in packaged libraries. The container overlays a
|
||||
canonical `local.properties`, so an ignored Android Studio file cannot redirect
|
||||
Gradle to a host-specific SDK.
|
||||
|
||||
AGP's embedded VCS record is disabled because its Git discovery depends on the
|
||||
host checkout layout. The canonical `BUILDINFO.json` and GitHub provenance
|
||||
@ -56,8 +59,10 @@ The authoritative pins are:
|
||||
|
||||
## Reproduce a release locally
|
||||
|
||||
Requirements are Git, Docker with Linux/amd64 support, and enough free space for
|
||||
the Android and Gradle images and dependencies.
|
||||
Requirements are Git, Docker with Linux/amd64 support, more than 8 GiB of memory
|
||||
available to the Docker VM (16 GiB recommended), and enough free space for the
|
||||
Android and Gradle images and dependencies. R8's single-threaded deterministic
|
||||
mode can exceed an 8 GiB Docker memory limit while optimizing the phone app.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/permissionlesstech/bitchat-android.git
|
||||
@ -71,6 +76,8 @@ The output contains:
|
||||
|
||||
- unsigned APKs for arm64, armv7, x86, x86_64, and universal installs
|
||||
- `bitchat-android-release-unsigned.aab`
|
||||
- `bitchat-android-wear-unsigned.apk`
|
||||
- `bitchat-android-wear-release-unsigned.aab`
|
||||
- `BUILDINFO.json`
|
||||
- `SHA256SUMS.unsigned`
|
||||
|
||||
@ -104,13 +111,13 @@ tools/reproducible-builds/verify-github-release.sh vX.Y.Z
|
||||
|
||||
That command:
|
||||
|
||||
1. downloads all release APKs, the AAB, build information, and checksum files;
|
||||
1. downloads all release APKs, AABs, build information, and checksum files;
|
||||
2. verifies the canonical unsigned build's GitHub artifact-attestation subjects
|
||||
against this repository;
|
||||
3. verifies `BITCHAT_SHA256SUMS`;
|
||||
4. checks that the local source commit is the release commit;
|
||||
5. rebuilds in the pinned container; and
|
||||
6. byte-compares every unsigned APK, the unsigned AAB, build information, and
|
||||
6. byte-compares every unsigned APK, both unsigned AABs, build information, and
|
||||
the unsigned checksum manifest.
|
||||
|
||||
To verify the published checksums and attestations without rebuilding:
|
||||
@ -154,8 +161,8 @@ gh attestation verify bitchat-android-universal-unsigned.apk \
|
||||
|
||||
Google Play App Signing changes the verification boundary:
|
||||
|
||||
- maintainers upload a signed AAB using the upload key;
|
||||
- Google Play generates optimized, device-specific APK splits from that AAB;
|
||||
- maintainers upload signed phone and Wear AABs using the upload key;
|
||||
- Google Play generates optimized, device-specific APK splits from those AABs;
|
||||
- Google signs the delivered APKs with the app-signing key.
|
||||
|
||||
Consequently, a Play-delivered APK is not expected to be byte-identical to the
|
||||
@ -163,8 +170,8 @@ GitHub universal APK or to a locally built APK. Use this procedure instead:
|
||||
|
||||
1. In Play Console, open **Test and release > App bundle explorer**, select the
|
||||
release/version code, and download the original app bundle if that option is
|
||||
available to your account. Compare its unsigned payload with the reproduced
|
||||
`bitchat-android-release-unsigned.aab`:
|
||||
available to your account. Compare its unsigned payload with the matching
|
||||
reproduced phone or Wear AAB:
|
||||
|
||||
```bash
|
||||
tools/reproducible-builds/compare-archive-payloads.sh \
|
||||
@ -204,9 +211,10 @@ maintainers to retain the uploaded AAB, publish its digest and provenance, and
|
||||
record the Play version code and app-signing certificate fingerprint alongside
|
||||
the release.
|
||||
|
||||
The GitHub workflow builds and attests the canonical unsigned AAB. A maintainer
|
||||
locally creates `bitchat-android-play-upload.aab` from that exact file and
|
||||
uploads it manually to Google Play.
|
||||
The GitHub workflow builds and attests both canonical unsigned AABs. A
|
||||
maintainer locally creates `bitchat-android-play-upload.aab` and
|
||||
`bitchat-android-wear-play-upload.aab` from those exact files and uploads them
|
||||
manually to Google Play.
|
||||
|
||||
## Maintainer release process
|
||||
|
||||
|
||||
@ -3,11 +3,6 @@
|
||||
<configuration>
|
||||
<verify-metadata>true</verify-metadata>
|
||||
<verify-signatures>false</verify-signatures>
|
||||
<trusted-artifacts>
|
||||
<trust file=".*-javadoc[.]jar" regex="true" reason="IDE documentation attachments are not build inputs"/>
|
||||
<trust file=".*-sources[.]jar" regex="true" reason="IDE source attachments are not build inputs"/>
|
||||
<trust file=".*-src[.]zip" regex="true" reason="Gradle IDE source attachments are not build inputs"/>
|
||||
</trusted-artifacts>
|
||||
</configuration>
|
||||
<components>
|
||||
<component group="androidx.activity" name="activity" version="1.13.0">
|
||||
@ -2147,6 +2142,9 @@
|
||||
<artifact name="aapt2-9.3.1-15703166-osx.jar">
|
||||
<sha256 value="1e35bc2ce18c3aae840be2a29659ce50d6043e907a44d98ee1cf375d044fa29c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="aapt2-9.3.1-15703166-windows.jar">
|
||||
<sha256 value="b1006ecec7e5936257e95e97f3eba7ef439d3e44178967cc048f86c9119fb231" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="aapt2-9.3.1-15703166.pom">
|
||||
<sha256 value="edb82dcbe738ca78b31612cb307bf99e5f02f25c8125048f933e792fd7778beb" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
@ -4911,6 +4909,9 @@
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.junit" name="junit-bom" version="5.9.2">
|
||||
<artifact name="junit-bom-5.9.2.module">
|
||||
<sha256 value="ab137ba5a8e32c9b066bf9126a1c76dd5614b724ba5c0b02549772b5e9f4cf1f" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="junit-bom-5.9.2.pom">
|
||||
<sha256 value="2ed07d65845131f5336a86476c9a4056b59d0b58b9815ab3679bb0f36f35f705" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
|
||||
@ -42,6 +42,9 @@ if ! [[ "$SOURCE_DATE_EPOCH" =~ ^[0-9]+$ ]]; then
|
||||
fi
|
||||
|
||||
gradle_args=(
|
||||
# R8 otherwise randomizes input traversal and uses multiple compiler threads.
|
||||
# The optimized DEX can still match while its embedded ProGuard map differs.
|
||||
-Dcom.android.tools.r8.deterministicdebugging=1
|
||||
-Pkotlin.compiler.execution.strategy=in-process
|
||||
-Pkotlin.incremental=false
|
||||
--no-build-cache
|
||||
@ -56,11 +59,20 @@ cd "$PROJECT_ROOT"
|
||||
|
||||
aab_source="$PROJECT_ROOT/app/build/outputs/bundle/release/app-release.aab"
|
||||
if [ ! -f "$aab_source" ]; then
|
||||
echo "error: expected release AAB not found" >&2
|
||||
echo "error: expected phone release AAB not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$aab_source" "$OUTPUT_DIR/bitchat-android-release-unsigned.aab"
|
||||
|
||||
./gradlew "${gradle_args[@]}" :wear:clean :wear:bundleRelease
|
||||
|
||||
wear_aab_source="$PROJECT_ROOT/wear/build/outputs/bundle/release/wear-release.aab"
|
||||
if [ ! -f "$wear_aab_source" ]; then
|
||||
echo "error: expected Wear release AAB not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$wear_aab_source" "$OUTPUT_DIR/bitchat-android-wear-release-unsigned.aab"
|
||||
|
||||
# AGP cannot build split APKs and an app bundle from the same intermediates.
|
||||
./gradlew "${gradle_args[@]}" :app:clean :app:assembleRelease
|
||||
|
||||
@ -81,6 +93,15 @@ for source_name in "${!apk_names[@]}"; do
|
||||
cp "$source_path" "$OUTPUT_DIR/${apk_names[$source_name]}"
|
||||
done
|
||||
|
||||
./gradlew "${gradle_args[@]}" :wear:clean :wear:assembleRelease
|
||||
|
||||
wear_apk_source="$PROJECT_ROOT/wear/build/outputs/apk/release/wear-release-unsigned.apk"
|
||||
if [ ! -f "$wear_apk_source" ]; then
|
||||
echo "error: expected Wear release APK not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$wear_apk_source" "$OUTPUT_DIR/bitchat-android-wear-unsigned.apk"
|
||||
|
||||
source_commit="${BITCHAT_SOURCE_COMMIT:-$(git -C "$PROJECT_ROOT" rev-parse HEAD)}"
|
||||
if ! [[ "$source_commit" =~ ^([0-9a-f]{40}|[0-9a-f]{64})$ ]]; then
|
||||
echo "error: source commit must be a full Git object ID" >&2
|
||||
@ -109,6 +130,7 @@ EOF
|
||||
sha256sum BUILDINFO.json
|
||||
sha256sum bitchat-android-*-unsigned.apk
|
||||
sha256sum bitchat-android-release-unsigned.aab
|
||||
sha256sum bitchat-android-wear-release-unsigned.aab
|
||||
} | sort -k2 > SHA256SUMS.unsigned
|
||||
)
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@ artifacts=(
|
||||
bitchat-android-armv7-unsigned.apk
|
||||
bitchat-android-release-unsigned.aab
|
||||
bitchat-android-universal-unsigned.apk
|
||||
bitchat-android-wear-release-unsigned.aab
|
||||
bitchat-android-wear-unsigned.apk
|
||||
bitchat-android-x86-unsigned.apk
|
||||
bitchat-android-x86_64-unsigned.apk
|
||||
)
|
||||
|
||||
@ -30,6 +30,10 @@ required=(
|
||||
bitchat-android-release-unsigned.aab
|
||||
bitchat-android-universal-unsigned.apk
|
||||
bitchat-android-universal.apk
|
||||
bitchat-android-wear-play-upload.aab
|
||||
bitchat-android-wear-release-unsigned.aab
|
||||
bitchat-android-wear-unsigned.apk
|
||||
bitchat-android-wear.apk
|
||||
bitchat-android-x86-unsigned.apk
|
||||
bitchat-android-x86_64-unsigned.apk
|
||||
bitchat-android-x86_64.apk
|
||||
@ -53,6 +57,8 @@ for artifact_path in "$RELEASE_DIR"/*; do
|
||||
bitchat-android-armv7-unsigned.apk|\
|
||||
bitchat-android-play-upload.aab|bitchat-android-release-unsigned.aab|\
|
||||
bitchat-android-universal-unsigned.apk|bitchat-android-universal.apk|\
|
||||
bitchat-android-wear-play-upload.aab|bitchat-android-wear-release-unsigned.aab|\
|
||||
bitchat-android-wear-unsigned.apk|bitchat-android-wear.apk|\
|
||||
bitchat-android-x86-unsigned.apk|\
|
||||
bitchat-android-x86_64-unsigned.apk|bitchat-android-x86_64.apk)
|
||||
;;
|
||||
|
||||
@ -48,13 +48,6 @@ fi
|
||||
"${SHA256[@]}" -c SHA256SUMS.unsigned
|
||||
)
|
||||
|
||||
UNSIGNED_AAB="$RELEASE_DIR/bitchat-android-release-unsigned.aab"
|
||||
SIGNED_AAB="$RELEASE_DIR/bitchat-android-play-upload.aab"
|
||||
if [ -e "$SIGNED_AAB" ]; then
|
||||
echo "error: signed Play upload AAB already exists" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TEMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||
STOREPASS_FILE="$TEMP_DIR/storepass"
|
||||
@ -63,17 +56,40 @@ printf '%s\n' "$BITCHAT_PLAY_KEYSTORE_PASSWORD" > "$STOREPASS_FILE"
|
||||
printf '%s\n' "$BITCHAT_PLAY_KEY_PASSWORD" > "$KEYPASS_FILE"
|
||||
chmod 600 "$STOREPASS_FILE" "$KEYPASS_FILE"
|
||||
|
||||
"$JARSIGNER" \
|
||||
-keystore "$KEYSTORE" \
|
||||
-storepass:file "$STOREPASS_FILE" \
|
||||
-keypass:file "$KEYPASS_FILE" \
|
||||
-digestalg SHA-256 \
|
||||
-signedjar "$SIGNED_AAB" \
|
||||
"$UNSIGNED_AAB" \
|
||||
"$BITCHAT_PLAY_UPLOAD_KEY_ALIAS"
|
||||
unsigned_names=(
|
||||
"bitchat-android-release-unsigned.aab"
|
||||
"bitchat-android-wear-release-unsigned.aab"
|
||||
)
|
||||
signed_names=(
|
||||
"bitchat-android-play-upload.aab"
|
||||
"bitchat-android-wear-play-upload.aab"
|
||||
)
|
||||
|
||||
"$JARSIGNER" -verify "$SIGNED_AAB" >/dev/null
|
||||
"$SCRIPT_DIR/compare-archive-payloads.sh" "$UNSIGNED_AAB" "$SIGNED_AAB"
|
||||
for ((index = 0; index < ${#unsigned_names[@]}; index++)); do
|
||||
unsigned_aab="$RELEASE_DIR/${unsigned_names[$index]}"
|
||||
signed_aab="$RELEASE_DIR/${signed_names[$index]}"
|
||||
|
||||
if [ ! -f "$unsigned_aab" ]; then
|
||||
echo "error: unsigned Play AAB not found: ${unsigned_names[$index]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -e "$signed_aab" ]; then
|
||||
echo "error: signed Play upload AAB already exists: ${signed_names[$index]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$JARSIGNER" \
|
||||
-keystore "$KEYSTORE" \
|
||||
-storepass:file "$STOREPASS_FILE" \
|
||||
-keypass:file "$KEYPASS_FILE" \
|
||||
-digestalg SHA-256 \
|
||||
-signedjar "$signed_aab" \
|
||||
"$unsigned_aab" \
|
||||
"$BITCHAT_PLAY_UPLOAD_KEY_ALIAS"
|
||||
|
||||
"$JARSIGNER" -verify "$signed_aab" >/dev/null
|
||||
"$SCRIPT_DIR/compare-archive-payloads.sh" "$unsigned_aab" "$signed_aab"
|
||||
done
|
||||
|
||||
(
|
||||
cd "$RELEASE_DIR"
|
||||
@ -86,4 +102,4 @@ chmod 600 "$STOREPASS_FILE" "$KEYPASS_FILE"
|
||||
} | sort -k2 > SHA256SUMS
|
||||
)
|
||||
|
||||
echo "Verified unsigned AAB was signed locally with the Play upload key and checksummed."
|
||||
echo "Verified unsigned phone and Wear AABs were signed locally with the Play upload key and checksummed."
|
||||
|
||||
@ -68,11 +68,13 @@ export BITCHAT_GITHUB_KEY_PASSWORD
|
||||
unsigned_names=(
|
||||
"bitchat-android-arm64-unsigned.apk"
|
||||
"bitchat-android-universal-unsigned.apk"
|
||||
"bitchat-android-wear-unsigned.apk"
|
||||
"bitchat-android-x86_64-unsigned.apk"
|
||||
)
|
||||
signed_names=(
|
||||
"bitchat-android-arm64.apk"
|
||||
"bitchat-android-universal.apk"
|
||||
"bitchat-android-wear.apk"
|
||||
"bitchat-android-x86_64.apk"
|
||||
)
|
||||
|
||||
|
||||
@ -50,6 +50,8 @@ attested_artifacts=(
|
||||
bitchat-android-armv7-unsigned.apk
|
||||
bitchat-android-release-unsigned.aab
|
||||
bitchat-android-universal-unsigned.apk
|
||||
bitchat-android-wear-release-unsigned.aab
|
||||
bitchat-android-wear-unsigned.apk
|
||||
bitchat-android-x86-unsigned.apk
|
||||
bitchat-android-x86_64-unsigned.apk
|
||||
)
|
||||
|
||||
@ -9,21 +9,29 @@ plugins {
|
||||
android {
|
||||
namespace = "com.bitchat.watch"
|
||||
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||
buildToolsVersion = libs.versions.buildTools.get()
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.bitchat.watch"
|
||||
applicationId = "com.bitchat.droid"
|
||||
minSdk = 33 // Wear OS 4 (Pixel Watch 1+): the S+ Bluetooth permissions the app
|
||||
// declares only exist from API 31, and API 30 would additionally require location
|
||||
// for BLE scan results, which the app deliberately refuses.
|
||||
targetSdk = libs.versions.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
// Wear releases use a separate high range because Play requires every artifact in
|
||||
// one application ID to have a unique version code across all form factors.
|
||||
versionCode = 1_000_000_002
|
||||
versionName = "0.1.1"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
@ -32,9 +40,9 @@ android {
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
// Sign with the debug key so release builds can be installed over the
|
||||
// debug app during development (same signature = seamless upgrade).
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
vcsInfo {
|
||||
include = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +65,13 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
composeCompiler {
|
||||
// Kotlin 2.4.10's optional Compose group-key mapping depends on unspecified
|
||||
// class-file iteration order. Keep the normal R8 mapping, but omit that
|
||||
// augmentation until its producer is deterministic across clean builds.
|
||||
includeComposeMappingFile.set(false)
|
||||
}
|
||||
|
||||
// Shared bitchat protocol stack: compiled from :app sources in place (never moved/copied by hand).
|
||||
// AGP's source directory sets no longer support include/exclude filters, so a Sync task
|
||||
// materializes a filtered mirror into build/sharedSrc and that directory is added as a source
|
||||
@ -158,6 +173,7 @@ tasks.matching { it.name.contains("UnitTest", ignoreCase = true) }.configureEach
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user