mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-09 16:24:53 +00:00
qr code display and random sampler port
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.client.j2se.MatrixToImageConfig;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.io.ImportException;
|
||||
import com.sparrowwallet.sparrow.ur.UR;
|
||||
import com.sparrowwallet.sparrow.ur.UREncoder;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.DialogPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import org.controlsfx.tools.Borders;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
public class QRDisplayDialog extends Dialog<UR> {
|
||||
private static final int MIN_FRAGMENT_LENGTH = 10;
|
||||
private static final int MAX_FRAGMENT_LENGTH = 100;
|
||||
|
||||
private final UR ur;
|
||||
private final UREncoder encoder;
|
||||
|
||||
private final ImageView qrImageView;
|
||||
|
||||
private String currentPart;
|
||||
|
||||
public QRDisplayDialog(byte[] data) {
|
||||
this(UR.fromBytes(data));
|
||||
}
|
||||
|
||||
public QRDisplayDialog(UR ur) {
|
||||
this.ur = ur;
|
||||
this.encoder = new UREncoder(ur, MAX_FRAGMENT_LENGTH, MIN_FRAGMENT_LENGTH, 0);
|
||||
|
||||
EventManager.get().register(this);
|
||||
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
qrImageView = new ImageView();
|
||||
stackPane.getChildren().add(qrImageView);
|
||||
|
||||
dialogPane.setContent(Borders.wrap(stackPane).lineBorder().outerPadding(0).innerPadding(0).buildAll());
|
||||
|
||||
nextPart();
|
||||
if(encoder.isSinglePart()) {
|
||||
qrImageView.setImage(getQrCode(currentPart));
|
||||
} else {
|
||||
AnimateQRService animateQRService = new AnimateQRService();
|
||||
animateQRService.setPeriod(Duration.millis(100));
|
||||
animateQRService.start();
|
||||
setOnCloseRequest(event -> {
|
||||
animateQRService.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
dialogPane.getButtonTypes().addAll(cancelButtonType);
|
||||
dialogPane.setPrefWidth(500);
|
||||
dialogPane.setPrefHeight(550);
|
||||
|
||||
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? ur : null);
|
||||
}
|
||||
|
||||
private void nextPart() {
|
||||
String fragment = encoder.nextPart();
|
||||
currentPart = fragment.toUpperCase();
|
||||
}
|
||||
|
||||
private Image getQrCode(String fragment) {
|
||||
try {
|
||||
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||
BitMatrix qrMatrix = qrCodeWriter.encode(fragment, BarcodeFormat.QR_CODE, 480, 480);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
MatrixToImageWriter.writeToStream(qrMatrix, "PNG", baos, new MatrixToImageConfig());
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
||||
return new Image(bais);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private class AnimateQRService extends ScheduledService<Boolean> {
|
||||
@Override
|
||||
protected Task<Boolean> createTask() {
|
||||
return new Task<>() {
|
||||
protected Boolean call() throws ImportException {
|
||||
Image qrImage = getQrCode(currentPart);
|
||||
qrImageView.setImage(qrImage);
|
||||
nextPart();
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,7 +530,8 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
ToggleButton toggleButton = (ToggleButton)event.getSource();
|
||||
toggleButton.setSelected(false);
|
||||
|
||||
headersForm.getSignedKeystores().add(headersForm.getSigningWallet().getKeystores().get(0));
|
||||
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(headersForm.getPsbt().serialize());
|
||||
qrDisplayDialog.show();
|
||||
}
|
||||
|
||||
public void scanPSBT(ActionEvent event) {
|
||||
|
||||
@@ -43,6 +43,14 @@ public class UR {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static UR fromBytes(byte[] data) {
|
||||
try {
|
||||
return new UR("bytes", data);
|
||||
} catch(UR.InvalidTypeException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(this == o) {
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
package com.sparrowwallet.sparrow.ur.fountain;
|
||||
|
||||
/******************************************************************************
|
||||
* File: AliasMethod.java
|
||||
* Author: Keith Schwarz (htiek@cs.stanford.edu)
|
||||
*
|
||||
* An implementation of the alias method implemented using Vose's algorithm.
|
||||
* The alias method allows for efficient sampling of random values from a
|
||||
* discrete probability distribution (i.e. rolling a loaded die) in O(1) time
|
||||
* each after O(n) preprocessing time.
|
||||
*
|
||||
* For a complete writeup on the alias method, including the intuition and
|
||||
* important proofs, please see the article "Darts, Dice, and Coins: Smpling
|
||||
* from a Discrete Distribution" at
|
||||
*
|
||||
* http://www.keithschwarz.com/darts-dice-coins/
|
||||
*/
|
||||
import java.util.*;
|
||||
|
||||
public final class AliasMethod {
|
||||
/* The random number generator used to sample from the distribution. */
|
||||
private final Random random;
|
||||
|
||||
/* The probability and alias tables. */
|
||||
private final int[] alias;
|
||||
private final double[] probability;
|
||||
|
||||
/**
|
||||
* Constructs a new AliasMethod to sample from a discrete distribution and
|
||||
* hand back outcomes based on the probability distribution.
|
||||
* <p>
|
||||
* Given as input a list of probabilities corresponding to outcomes 0, 1,
|
||||
* ..., n - 1, this constructor creates the probability and alias tables
|
||||
* needed to efficiently sample from this distribution.
|
||||
*
|
||||
* @param probabilities The list of probabilities.
|
||||
*/
|
||||
public AliasMethod(List<Double> probabilities) {
|
||||
this(probabilities, new Random());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new AliasMethod to sample from a discrete distribution and
|
||||
* hand back outcomes based on the probability distribution.
|
||||
* <p>
|
||||
* Given as input a list of probabilities corresponding to outcomes 0, 1,
|
||||
* ..., n - 1, along with the random number generator that should be used
|
||||
* as the underlying generator, this constructor creates the probability
|
||||
* and alias tables needed to efficiently sample from this distribution.
|
||||
*
|
||||
* @param probabilities The list of probabilities.
|
||||
* @param random The random number generator
|
||||
*/
|
||||
public AliasMethod(List<Double> probabilities, Random random) {
|
||||
/* Begin by doing basic structural checks on the inputs. */
|
||||
if (probabilities == null || random == null)
|
||||
throw new NullPointerException();
|
||||
if (probabilities.size() == 0)
|
||||
throw new IllegalArgumentException("Probability vector must be nonempty.");
|
||||
|
||||
/* Allocate space for the probability and alias tables. */
|
||||
probability = new double[probabilities.size()];
|
||||
alias = new int[probabilities.size()];
|
||||
|
||||
/* Store the underlying generator. */
|
||||
this.random = random;
|
||||
|
||||
/* Compute the average probability and cache it for later use. */
|
||||
final double average = 1.0 / probabilities.size();
|
||||
|
||||
/* Make a copy of the probabilities list, since we will be making
|
||||
* changes to it.
|
||||
*/
|
||||
probabilities = new ArrayList<Double>(probabilities);
|
||||
|
||||
/* Create two stacks to act as worklists as we populate the tables. */
|
||||
Deque<Integer> small = new ArrayDeque<Integer>();
|
||||
Deque<Integer> large = new ArrayDeque<Integer>();
|
||||
|
||||
/* Populate the stacks with the input probabilities. */
|
||||
for (int i = 0; i < probabilities.size(); ++i) {
|
||||
/* If the probability is below the average probability, then we add
|
||||
* it to the small list; otherwise we add it to the large list.
|
||||
*/
|
||||
if (probabilities.get(i) >= average)
|
||||
large.add(i);
|
||||
else
|
||||
small.add(i);
|
||||
}
|
||||
|
||||
/* As a note: in the mathematical specification of the algorithm, we
|
||||
* will always exhaust the small list before the big list. However,
|
||||
* due to floating point inaccuracies, this is not necessarily true.
|
||||
* Consequently, this inner loop (which tries to pair small and large
|
||||
* elements) will have to check that both lists aren't empty.
|
||||
*/
|
||||
while (!small.isEmpty() && !large.isEmpty()) {
|
||||
/* Get the index of the small and the large probabilities. */
|
||||
int less = small.removeLast();
|
||||
int more = large.removeLast();
|
||||
|
||||
/* These probabilities have not yet been scaled up to be such that
|
||||
* 1/n is given weight 1.0. We do this here instead.
|
||||
*/
|
||||
probability[less] = probabilities.get(less) * probabilities.size();
|
||||
alias[less] = more;
|
||||
|
||||
/* Decrease the probability of the larger one by the appropriate
|
||||
* amount.
|
||||
*/
|
||||
probabilities.set(more,
|
||||
(probabilities.get(more) + probabilities.get(less)) - average);
|
||||
|
||||
/* If the new probability is less than the average, add it into the
|
||||
* small list; otherwise add it to the large list.
|
||||
*/
|
||||
if (probabilities.get(more) >= 1.0 / probabilities.size())
|
||||
large.add(more);
|
||||
else
|
||||
small.add(more);
|
||||
}
|
||||
|
||||
/* At this point, everything is in one list, which means that the
|
||||
* remaining probabilities should all be 1/n. Based on this, set them
|
||||
* appropriately. Due to numerical issues, we can't be sure which
|
||||
* stack will hold the entries, so we empty both.
|
||||
*/
|
||||
while (!small.isEmpty())
|
||||
probability[small.removeLast()] = 1.0;
|
||||
while (!large.isEmpty())
|
||||
probability[large.removeLast()] = 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Samples a value from the underlying distribution.
|
||||
*
|
||||
* @return A random value sampled from the underlying distribution.
|
||||
*/
|
||||
public int next() {
|
||||
/* Generate a fair die roll to determine which column to inspect. */
|
||||
int column = random.nextInt(probability.length);
|
||||
|
||||
/* Generate a biased coin toss to determine which option to pick. */
|
||||
boolean coinToss = random.nextDouble() < probability[column];
|
||||
|
||||
/* Based on the outcome, return either the column or its alias. */
|
||||
return coinToss? column : alias[column];
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,8 @@ public class FountainUtils {
|
||||
|
||||
static int chooseDegree(int seqLen, RandomXoshiro256StarStar rng) {
|
||||
List<Double> degreeProbabilties = IntStream.range(1, seqLen + 1).mapToObj(i -> 1 / (double)i).collect(Collectors.toList());
|
||||
AliasMethod degreeChooser = new AliasMethod(degreeProbabilties, rng);
|
||||
return degreeChooser.next() + 1;
|
||||
RandomSampler randomSampler = new RandomSampler(degreeProbabilties);
|
||||
return randomSampler.next(rng) + 1;
|
||||
}
|
||||
|
||||
static List<Integer> shuffled(List<Integer> indexes, RandomXoshiro256StarStar rng) {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.sparrowwallet.sparrow.ur.fountain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Random-number sampling using the Walker-Vose alias method,
|
||||
* as described by Keith Schwarz (2011)
|
||||
* http://www.keithschwarz.com/darts-dice-coins
|
||||
*
|
||||
* Based on C implementation:
|
||||
* https://jugit.fz-juelich.de/mlz/ransampl
|
||||
*
|
||||
* Ported from https://github.com/BlockchainCommons/URKit
|
||||
*/
|
||||
public class RandomSampler {
|
||||
/* The probability and alias tables. */
|
||||
private final double[] probs;
|
||||
private final int[] aliases;
|
||||
|
||||
public RandomSampler(List<Double> probabilities) {
|
||||
if(probabilities.stream().anyMatch(prob -> prob < 0)) {
|
||||
throw new IllegalArgumentException("Probabilties must be > 0");
|
||||
}
|
||||
|
||||
// Normalize given probabilities
|
||||
double sum = probabilities.stream().reduce(0d, Double::sum);
|
||||
|
||||
int n = probabilities.size();
|
||||
List<Double> P = probabilities.stream().map(prob -> prob * (double)n / sum).collect(Collectors.toList());
|
||||
|
||||
List<Integer> S = new ArrayList<>();
|
||||
List<Integer> L = new ArrayList<>();
|
||||
|
||||
// Set separate index lists for small and large probabilities:
|
||||
for(int i = n - 1; i >= 0; i--) {
|
||||
// at variance from Schwarz, we reverse the index order
|
||||
if(P.get(i) < 1d) {
|
||||
S.add(i);
|
||||
} else {
|
||||
L.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Work through index lists
|
||||
double[] probs = new double[n];
|
||||
int[] aliases = new int[n];
|
||||
|
||||
while(!S.isEmpty() && !L.isEmpty()) {
|
||||
int a = S.remove(S.size() - 1);
|
||||
int g = L.remove(L.size() - 1);
|
||||
probs[a] = P.get(a);
|
||||
aliases[a] = g;
|
||||
P.set(g, P.get(g) + P.get(a) - 1);
|
||||
if(P.get(g) < 1) {
|
||||
S.add(g);
|
||||
} else {
|
||||
L.add(g);
|
||||
}
|
||||
}
|
||||
|
||||
while(!L.isEmpty()) {
|
||||
probs[L.remove(L.size() - 1)] = 1;
|
||||
}
|
||||
|
||||
while(!S.isEmpty()) {
|
||||
// can only happen through numeric instability
|
||||
probs[S.remove(S.size() - 1)] = 1;
|
||||
}
|
||||
|
||||
this.probs = probs;
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
public int next(Random random) {
|
||||
double r1 = random.nextDouble();
|
||||
double r2 = random.nextDouble();
|
||||
int n = probs.length;
|
||||
int i = (int)((double)n * r1);
|
||||
return r2 < probs[i] ? i : aliases[i];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user