mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-07-31 03:56:16 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53d1f196e4 | ||
|
|
64b8da14a6 | ||
|
|
529cc3d6e7 | ||
|
|
e13fe897e1 | ||
|
|
dac1a61601 | ||
|
|
227c7ae637 | ||
|
|
e11675b9a8 |
+1
-1
@@ -20,7 +20,7 @@ if(System.getProperty("os.arch") == "aarch64") {
|
||||
def headless = "true".equals(System.getProperty("java.awt.headless"))
|
||||
|
||||
group = 'com.sparrowwallet'
|
||||
version = '2.4.0'
|
||||
version = '2.4.1'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
+20
-47
@@ -12,63 +12,36 @@ Work on resolving both of these issues is ongoing.
|
||||
### Install Java
|
||||
|
||||
Because Sparrow bundles a Java runtime in the release binaries, it is essential to have the same version of Java installed when creating the release.
|
||||
For v1.6.6 to v1.9.1, this was Eclipse Temurin 18.0.1+10. For v2.0.0 and later, Eclipse Temurin 22.0.2+9 is used.
|
||||
For v1.6.6 to v1.9.1, this was Eclipse Temurin 18.0.1+10. For v2.0.0 to v2.3.1, this was Eclipse Temurin 22.0.2+9. For v2.4.0 and later, Eclipse Temurin 25.0.2+10 is used.
|
||||
|
||||
Note: Do not install Java using a system package manager (e.g. apt, dnf, rpm).
|
||||
Linux packages replace the JDK's bundled `cacerts` file with a symlink to the system CA certificates, which differ from those in the release tarballs and will produce a non-reproducible build.
|
||||
|
||||
#### Java from Adoptium github repo
|
||||
|
||||
It is available for all supported platforms from [Eclipse Temurin 22.0.2+9](https://github.com/adoptium/temurin22-binaries/releases/tag/jdk-22.0.2%2B9).
|
||||
It is available for all supported platforms from [Eclipse Temurin 25.0.2+10](https://github.com/adoptium/temurin25-binaries/releases/tag/jdk-25.0.2%2B10).
|
||||
|
||||
For reference, the downloads are as follows:
|
||||
- [Linux x64](https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22.0.2%2B9/OpenJDK22U-jdk_x64_linux_hotspot_22.0.2_9.tar.gz)
|
||||
- [Linux aarch64](https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22.0.2%2B9/OpenJDK22U-jdk_aarch64_linux_hotspot_22.0.2_9.tar.gz)
|
||||
- [MacOS x64](https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22.0.2%2B9/OpenJDK22U-jdk_x64_mac_hotspot_22.0.2_9.tar.gz)
|
||||
- [MacOS aarch64](https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22.0.2%2B9/OpenJDK22U-jdk_aarch64_mac_hotspot_22.0.2_9.tar.gz)
|
||||
- [Windows x64](https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22.0.2%2B9/OpenJDK22U-jdk_x64_windows_hotspot_22.0.2_9.zip)
|
||||
- [Linux x64](https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_x64_linux_hotspot_25.0.2_10.tar.gz)
|
||||
- [Linux aarch64](https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_aarch64_linux_hotspot_25.0.2_10.tar.gz)
|
||||
- [MacOS x64](https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_x64_mac_hotspot_25.0.2_10.tar.gz)
|
||||
- [MacOS aarch64](https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.2_10.tar.gz)
|
||||
- [Windows x64](https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_x64_windows_hotspot_25.0.2_10.zip)
|
||||
|
||||
#### Java from Adoptium deb repo
|
||||
|
||||
It is also possible to install via a package manager on *nix systems. For example, on Debian/Ubuntu systems:
|
||||
|
||||
- Install dependencies:
|
||||
```sh
|
||||
sudo apt-get install -y wget curl apt-transport-https gnupg
|
||||
On Linux, extract the tarball and set `JAVA_HOME` to use it for the build:
|
||||
```shell
|
||||
tar -xzf OpenJDK25U-jdk_x64_linux_hotspot_25.0.2_10.tar.gz
|
||||
export JAVA_HOME=$PWD/jdk-25.0.2+10
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
```
|
||||
|
||||
Download Adoptium public PGP key:
|
||||
```sh
|
||||
curl --tlsv1.2 --proto =https --location -o adoptium.asc https://packages.adoptium.net/artifactory/api/gpg/key/public
|
||||
```
|
||||
#### Java from SDKMAN
|
||||
|
||||
Check if key fingerprint matches: `3B04D753C9050D9A5D343F39843C48A565F8F04B`:
|
||||
```
|
||||
gpg --import --import-options show-only adoptium.asc
|
||||
```
|
||||
If key doesn't match, do not proceed.
|
||||
|
||||
Add Adoptium PGP key to a the keyring shared folder:
|
||||
```sh
|
||||
sudo cp adoptium.asc /usr/share/keyrings/
|
||||
```
|
||||
|
||||
Add Adoptium debian repository:
|
||||
```sh
|
||||
echo "deb [signed-by=/usr/share/keyrings/adoptium.asc] https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | sudo tee /etc/apt/sources.list.d/adoptium.list
|
||||
```
|
||||
|
||||
Update cache, install the desired temurin version and configure java to be linked to this same version:
|
||||
```
|
||||
sudo apt update -y
|
||||
sudo apt-get install -y temurin-22-jdk=22.0.2+9
|
||||
sudo update-alternatives --config java
|
||||
```
|
||||
|
||||
#### Java from SDK
|
||||
|
||||
A alternative option for all platforms is to use the [sdkman.io](https://sdkman.io/) package manager ([Git Bash for Windows](https://git-scm.com/download/win) is a good choice on that platform).
|
||||
An alternative option for all platforms is to use the [sdkman.io](https://sdkman.io/) package manager ([Git Bash for Windows](https://git-scm.com/download/win) is a good choice on that platform).
|
||||
See the installation [instructions here](https://sdkman.io/install).
|
||||
Once installed, run
|
||||
```shell
|
||||
sdk install java 22.0.2-tem
|
||||
sdk install java 25.0.2-tem
|
||||
```
|
||||
|
||||
### Other requirements
|
||||
@@ -83,7 +56,7 @@ sudo apt install -y rpm fakeroot binutils
|
||||
First, assign a temporary variable in your shell for the specific release you want to build. For the current one specify:
|
||||
|
||||
```shell
|
||||
GIT_TAG="2.3.1"
|
||||
GIT_TAG="2.4.0"
|
||||
```
|
||||
|
||||
The project can then be initially cloned as follows:
|
||||
@@ -101,7 +74,7 @@ git checkout "${GIT_TAG}"
|
||||
```
|
||||
|
||||
Note - there is an additional step if you updated rather than initially cloned your repo at `GIT_TAG`.
|
||||
This is due to the [drongo submodule](https://github.com/sparrowwallet/drongo/tree/master) which needs to be checked out to the commit state it had at the time of the release.
|
||||
This is due to the Git submodules which need to be checked out to the commit state they had at the time of the release.
|
||||
Only then your build will be comparable to the provided one in the release section of Github.
|
||||
To checkout the submodule to the correct commit for `GIT_TAG`, additionally run:
|
||||
|
||||
|
||||
+1
-1
Submodule lark updated: 36cf0c85dc...b074099a38
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.4.0</string>
|
||||
<string>2.4.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
||||
|
||||
@@ -1140,12 +1140,37 @@ public class AppController implements Initializable {
|
||||
AppServices.moveToActiveWindowScreen(window, 800, 450);
|
||||
List<File> files = fileChooser.showOpenMultipleDialog(window);
|
||||
if(files != null) {
|
||||
configureWalletsDir(files);
|
||||
for(File file : files) {
|
||||
openWalletFile(file, forceSameWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void configureWalletsDir(List<File> files) {
|
||||
List<File> parentDirs = files.stream().map(File::getParentFile).distinct().collect(Collectors.toList());
|
||||
if(parentDirs.size() == 1 && !Boolean.FALSE.equals(Config.get().getSuggestChangeWalletsDir())) {
|
||||
File selectedDir = parentDirs.getFirst();
|
||||
boolean sameDir;
|
||||
try {
|
||||
sameDir = Files.isSameFile(selectedDir.toPath(), Storage.getWalletsDir().toPath());
|
||||
} catch(IOException e) {
|
||||
sameDir = selectedDir.toPath().normalize().equals(Storage.getWalletsDir().toPath().normalize());
|
||||
}
|
||||
if(!sameDir) {
|
||||
ConfirmationAlert alert = new ConfirmationAlert("Change wallets directory?",
|
||||
"Do you want to configure Sparrow to use " + selectedDir + " as the default wallets directory?", ButtonType.NO, ButtonType.YES);
|
||||
Optional<ButtonType> optType = alert.showAndWait();
|
||||
if(optType.isPresent() && optType.get() == ButtonType.YES) {
|
||||
Config.get().setWalletsDir(selectedDir);
|
||||
Config.get().setSuggestChangeWalletsDir(null);
|
||||
} else if(alert.isDontAskAgain()) {
|
||||
Config.get().setSuggestChangeWalletsDir(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void openWalletFile(File file, boolean forceSameWindow) {
|
||||
try {
|
||||
Storage storage = new Storage(file);
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.*;
|
||||
public class SparrowWallet {
|
||||
public static final String APP_ID = "sparrow";
|
||||
public static final String APP_NAME = "Sparrow";
|
||||
public static final String APP_VERSION = "2.4.0";
|
||||
public static final String APP_VERSION = "2.4.1";
|
||||
public static final String APP_VERSION_SUFFIX = "";
|
||||
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
||||
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
||||
|
||||
@@ -28,6 +28,7 @@ public class AddressLabelSkin extends LabelSkin {
|
||||
super(control);
|
||||
|
||||
displayFlow = new TextFlow();
|
||||
displayFlow.setManaged(false);
|
||||
displayFlow.setMouseTransparent(true);
|
||||
|
||||
getChildren().addFirst(displayFlow);
|
||||
|
||||
@@ -37,6 +37,7 @@ public class AddressTextFieldSkin extends CustomTextFieldSkin {
|
||||
super(control);
|
||||
|
||||
displayFlow = new TextFlow();
|
||||
displayFlow.setManaged(false);
|
||||
displayFlow.setMouseTransparent(true);
|
||||
|
||||
clip = new Rectangle();
|
||||
|
||||
@@ -18,6 +18,7 @@ public class AddressTreeTableCellSkin<S, T> extends TreeTableCellSkin<S, T> {
|
||||
super(cell);
|
||||
|
||||
displayFlow = new TextFlow();
|
||||
displayFlow.setManaged(false);
|
||||
displayFlow.setMouseTransparent(true);
|
||||
displayFlow.setMinWidth(Region.USE_PREF_SIZE);
|
||||
getChildren().add(displayFlow);
|
||||
|
||||
@@ -62,7 +62,7 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
private static final List<String> ARCHIVE_EXTENSIONS = List.of("zip", "tar.gz", "tar.bz2", "tar.xz", "rar", "7z");
|
||||
|
||||
private static final String SPARROW_RELEASE_PREFIX = "sparrow-";
|
||||
private static final String SPARROW_RELEASE_ALT_PREFIX = "sparrow_";
|
||||
private static final String[] SPARROW_RELEASE_ALT_PREFIXES = { "sparrowwallet-", "sparrowwallet_", "sparrowserver-", "sparrowserver_" };
|
||||
private static final String SPARROW_MANIFEST_SUFFIX = "-manifest.txt";
|
||||
private static final String SPARROW_SIGNATURE_SUFFIX = SPARROW_MANIFEST_SUFFIX + ".asc";
|
||||
private static final Pattern SPARROW_RELEASE_VERSION = Pattern.compile("[0-9]+(\\.[0-9]+)*");
|
||||
@@ -465,7 +465,7 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
}
|
||||
|
||||
String providedName = providedFile.getName().toLowerCase(Locale.ROOT);
|
||||
if(providedName.startsWith(SPARROW_RELEASE_PREFIX) || providedName.startsWith(SPARROW_RELEASE_ALT_PREFIX)) {
|
||||
if(providedName.startsWith(SPARROW_RELEASE_PREFIX) || Arrays.stream(SPARROW_RELEASE_ALT_PREFIXES).anyMatch(providedName::startsWith)) {
|
||||
Matcher matcher = SPARROW_RELEASE_VERSION.matcher(providedFile.getName());
|
||||
if(matcher.find()) {
|
||||
String version = matcher.group();
|
||||
@@ -591,7 +591,8 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
}
|
||||
}
|
||||
|
||||
if((name.startsWith(SPARROW_RELEASE_PREFIX) || name.startsWith(SPARROW_RELEASE_ALT_PREFIX)) && file.length() >= MIN_VALID_SPARROW_RELEASE_SIZE) {
|
||||
if((name.startsWith(SPARROW_RELEASE_PREFIX) || Arrays.stream(SPARROW_RELEASE_ALT_PREFIXES).anyMatch(name::startsWith))
|
||||
&& file.length() >= MIN_VALID_SPARROW_RELEASE_SIZE) {
|
||||
Matcher matcher = SPARROW_RELEASE_VERSION.matcher(name);
|
||||
return matcher.find();
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class WalletNameDialog extends Dialog<WalletNameDialog.NameAndBirthDate>
|
||||
name = (CustomTextField)TextFields.createClearableTextField();
|
||||
name.setText(initialName);
|
||||
name.setTextFormatter(new TextFormatter<>((change) -> {
|
||||
change.setText(change.getText().replaceAll("[\\\\/:*?\"<>|]", "_"));
|
||||
change.setText(change.getText().replaceAll("[\\\\/:*?\"<>|;]", "_"));
|
||||
return change;
|
||||
}));
|
||||
content.getChildren().add(name);
|
||||
|
||||
@@ -59,6 +59,8 @@ public class Config {
|
||||
private Boolean connectToBroadcast;
|
||||
private Boolean connectToResolve;
|
||||
private Boolean suggestSendToMany;
|
||||
private Boolean suggestChangeWalletsDir;
|
||||
private File walletsDir;
|
||||
private List<File> recentWalletFiles;
|
||||
private Integer keyDerivationPeriod;
|
||||
private long dustAttackThreshold = DUST_ATTACK_THRESHOLD_SATS;
|
||||
@@ -406,6 +408,24 @@ public class Config {
|
||||
flush();
|
||||
}
|
||||
|
||||
public Boolean getSuggestChangeWalletsDir() {
|
||||
return suggestChangeWalletsDir;
|
||||
}
|
||||
|
||||
public void setSuggestChangeWalletsDir(Boolean suggestChangeWalletsDir) {
|
||||
this.suggestChangeWalletsDir = suggestChangeWalletsDir;
|
||||
flush();
|
||||
}
|
||||
|
||||
public File getWalletsDir() {
|
||||
return walletsDir;
|
||||
}
|
||||
|
||||
public void setWalletsDir(File walletsDir) {
|
||||
this.walletsDir = walletsDir;
|
||||
flush();
|
||||
}
|
||||
|
||||
public List<File> getRecentWalletFiles() {
|
||||
return recentWalletFiles;
|
||||
}
|
||||
|
||||
@@ -29,10 +29,7 @@ public class Samourai implements KeystoreFileImport {
|
||||
try {
|
||||
String input = CharStreams.toString(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||
|
||||
Gson gson = new Gson();
|
||||
Type stringStringMap = new TypeToken<Map<String, JsonElement>>() {
|
||||
}.getType();
|
||||
Map<String, JsonElement> map = gson.fromJson(input, stringStringMap);
|
||||
Map<String, JsonElement> map = this.parseJsonInput(input);
|
||||
|
||||
String payload = input;
|
||||
if(map.containsKey("payload")) {
|
||||
@@ -53,7 +50,7 @@ public class Samourai implements KeystoreFileImport {
|
||||
throw new ImportException("Unsupported backup version: " + version);
|
||||
}
|
||||
|
||||
SamouraiBackup backup = gson.fromJson(decrypted, SamouraiBackup.class);
|
||||
SamouraiBackup backup = new Gson().fromJson(decrypted, SamouraiBackup.class);
|
||||
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes(backup.wallet.seed), password, 0);
|
||||
Keystore keystore = Keystore.fromSeed(seed, scriptType.getDefaultDerivation());
|
||||
keystore.setLabel(getWalletModel().toDisplayString());
|
||||
@@ -67,6 +64,24 @@ public class Samourai implements KeystoreFileImport {
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, JsonElement> parseJsonInput(String input) {
|
||||
Gson gson = new Gson();
|
||||
Type stringStringMap = new TypeToken<Map<String, JsonElement>>() {
|
||||
}.getType();
|
||||
|
||||
try {
|
||||
return gson.fromJson(input, stringStringMap);
|
||||
} catch (JsonParseException e) {
|
||||
int closingBracket = input.indexOf('}');
|
||||
if (closingBracket < 0) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
String fixedInput = input.substring(0, closingBracket + 1);
|
||||
return gson.fromJson(fixedInput, stringStringMap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKeystoreImportScannable() {
|
||||
return false;
|
||||
|
||||
@@ -485,7 +485,16 @@ public class Storage {
|
||||
}
|
||||
|
||||
public static File getWalletsDir() {
|
||||
File walletsDir = new File(getSparrowDir(), WALLETS_DIR);
|
||||
File walletsDir = Config.get().getWalletsDir();
|
||||
if(walletsDir != null) {
|
||||
if(!walletsDir.exists() && (walletsDir.getParentFile() == null || !walletsDir.getParentFile().exists() || !walletsDir.getParentFile().canWrite())) {
|
||||
log.info("Configured wallets directory " + walletsDir.getAbsolutePath() + " is not reachable, reverting to default");
|
||||
walletsDir = null;
|
||||
}
|
||||
}
|
||||
if(walletsDir == null) {
|
||||
walletsDir = new File(getSparrowDir(), WALLETS_DIR);
|
||||
}
|
||||
if(!walletsDir.exists()) {
|
||||
createOwnerOnlyDirectory(walletsDir);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.nio.file.StandardCopyOption;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -54,6 +55,7 @@ public class DbPersistence implements Persistence {
|
||||
private static final String H2_USER = "sa";
|
||||
private static final String H2_PASSWORD = "";
|
||||
public static final String MIGRATION_RESOURCES_DIR = "com/sparrowwallet/sparrow/sql/";
|
||||
private static final Pattern JDBC_URL_INJECTION_PATTERN = Pattern.compile(";\\w+=");
|
||||
|
||||
private HikariDataSource dataSource;
|
||||
private AsymmetricKeyDeriver keyDeriver;
|
||||
@@ -695,7 +697,10 @@ public class DbPersistence implements Persistence {
|
||||
}
|
||||
}
|
||||
|
||||
private String getUrl(File walletFile, String password) {
|
||||
private String getUrl(File walletFile, String password) throws StorageException {
|
||||
if(JDBC_URL_INJECTION_PATTERN.matcher(walletFile.getName()).find()) {
|
||||
throw new StorageException("Wallet file name contains invalid characters");
|
||||
}
|
||||
return "jdbc:h2:" + walletFile.getAbsolutePath().replace("." + getType().getExtension(), "") + ";INIT=SET TRACE_LEVEL_FILE=4;TRACE_LEVEL_FILE=4;DEFRAG_ALWAYS=true;MAX_COMPACT_TIME=5000;DATABASE_TO_UPPER=false" + (password == null ? "" : ";CIPHER=AES");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user