mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-02 20:36:44 +00:00
qr code display and random sampler port
This commit is contained in:
parent
4068a6c541
commit
709c65ec20
8 changed files with 322 additions and 208 deletions
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -3,13 +3,11 @@ package com.sparrowwallet.sparrow.ur;
|
|||
import co.nstant.in.cbor.CborBuilder;
|
||||
import co.nstant.in.cbor.CborEncoder;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.sparrow.ur.fountain.FountainEncoder;
|
||||
import com.sparrowwallet.sparrow.ur.fountain.RandomXoshiro256StarStar;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
@ -37,17 +35,17 @@ public class URTest {
|
|||
"ur:bytes/7-9/ltatascfadaxcywenbpljkhdcavszownjkwtclrtvaynhpahrtoxmwvwatmedibkaegdosftvandiodagdhthtrlnnhy",
|
||||
"ur:bytes/8-9/ltayascfadaxcywenbpljkhdcadmsponkkbbhgsolnjntegepmttmoonftnbuoiyrehfrtsabzsttorodklubbuyaetk",
|
||||
"ur:bytes/9-9/ltasascfadaxcywenbpljkhdcajskecpmdckihdyhphfotjojtfmlpwmadspaxrkytbztpbauotbgtgtaeaevtgavtny",
|
||||
"ur:bytes/10-9/ltbkascfadaxcywenbpljkhdcazoqdayfeaavsnnrffhjnfytplguytsoyspgdrhluheihtyettewtytcfrtdeahhdad",
|
||||
"ur:bytes/11-9/ltbdascfadaxcywenbpljkhdcavdintbiyjltafyknfspefrvdondtvlgdckfslthkgtghsbsbbtiyechthdlakobtfd",
|
||||
"ur:bytes/12-9/ltbnascfadaxcywenbpljkhdcalndikttpecueksoecypdssvtplkiryjydioefywyrtjlsedppagwpseturfhbzmdmd",
|
||||
"ur:bytes/13-9/ltbtascfadaxcywenbpljkhdcazoqdayfeaavsnnrffhjnfytplguytsoyspgdrhluheihtyettewtytcfrtutwtoyon",
|
||||
"ur:bytes/14-9/ltbaascfadaxcywenbpljkhdcanbsfjpsotnltmhmoztgmlbykfgrsntlsserojoisbzmhbegspkjyhhwnqdfneobkfd",
|
||||
"ur:bytes/10-9/ltbkascfadaxcywenbpljkhdcahkadaemejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtwdkiplzs",
|
||||
"ur:bytes/11-9/ltbdascfadaxcywenbpljkhdcahelbknlkuejnbadmssfhfrdpsbiegecpasvssovlgeykssjykklronvsjkvetiiapk",
|
||||
"ur:bytes/12-9/ltbnascfadaxcywenbpljkhdcarllaluzodmgstospeyiefmwejlwtpedamktksrvlcygmzmmovovllarodtmtbnptrs",
|
||||
"ur:bytes/13-9/ltbtascfadaxcywenbpljkhdcamtkgtpknghchchyketwsvwgwfdhpgmgtylctotztpdrpayoschcmhplffziachrfgd",
|
||||
"ur:bytes/14-9/ltbaascfadaxcywenbpljkhdcapazmwnvonnvdnsbyleynwtnsjkjndeoldydkbkdslgjkbbkortbelomueekgvstegt",
|
||||
"ur:bytes/15-9/ltbsascfadaxcywenbpljkhdcaynmhpddpzoversbdqdfyrehnqzlugmjzmnmtwmrouohtstgsbsahpawkditkckynwt",
|
||||
"ur:bytes/16-9/ltbeascfadaxcywenbpljkhdcazoqdayfeaavsnnrffhjnfytplguytsoyspgdrhluheihtyettewtytcfrtghtduycm",
|
||||
"ur:bytes/17-9/ltbyascfadaxcywenbpljkhdcarpfeneknvyyadifltalghskpgrfgsngulagspfpthyrpgrsoatjnuyflvsdmpyinmw",
|
||||
"ur:bytes/18-9/ltbgascfadaxcywenbpljkhdcavtrfmwktecjnnsyafsemaaspglynhhrhmyjyoelgjtpyhkssamdsfehfnsfrcfrnyk",
|
||||
"ur:bytes/19-9/ltbwascfadaxcywenbpljkhdcaenbgkghtlbiybwfpjlbyecmoythnmesbkopahtiofywnutvacfhdjyiobwrtlbtbme",
|
||||
"ur:bytes/20-9/ltbbascfadaxcywenbpljkhdcarssrwyztwmaemotbytayfhvwltmocmndlpnsjejtdkhyntpflboevtrnwsdkjssbrs"
|
||||
"ur:bytes/16-9/ltbeascfadaxcywenbpljkhdcawygekobamwtlihsnpalpsghenskkiynthdzttsimtojetprsttmukirlrsbtamjtpd",
|
||||
"ur:bytes/17-9/ltbyascfadaxcywenbpljkhdcamklgftaxykpewyrtqzhydntpnytyisincxmhtbceaykolduortotiaiaiafhiaoyce",
|
||||
"ur:bytes/18-9/ltbgascfadaxcywenbpljkhdcahkadaemejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtntwkbkwy",
|
||||
"ur:bytes/19-9/ltbwascfadaxcywenbpljkhdcadekicpaajootjzpsdrbalteywllbdsnbinaerkurspbncxgslgftvtsrjtksplcpeo",
|
||||
"ur:bytes/20-9/ltbbascfadaxcywenbpljkhdcayapmrleeleaxpasfrtrdkncffwjyjzgyetdmlewtkpktgllepfrltatazcksmhkbot"
|
||||
};
|
||||
Assert.assertArrayEquals("", expectedParts, parts.toArray());
|
||||
}
|
||||
|
@ -70,37 +68,6 @@ public class URTest {
|
|||
Assert.assertEquals(ur, decodedUR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncoderCBOR() {
|
||||
byte[] message = makeMessage(256, "Wolf");
|
||||
FountainEncoder fountainEncoder = new FountainEncoder(message, 30, 10, 0);
|
||||
FountainEncoder.Part[] parts = IntStream.range(0, 20).mapToObj(i -> fountainEncoder.nextPart()).collect(Collectors.toList()).toArray(new FountainEncoder.Part[20]);
|
||||
List<String> partsHex = Arrays.stream(parts).map(part -> Utils.bytesToHex(part.toCborBytes())).collect(Collectors.toList());
|
||||
String[] expectedPartsHex = new String[] {
|
||||
"8501091901001a0167aa07581d916ec65cf77cadf55cd7f9cda1a1030026ddd42e905b77adc36e4f2d3c",
|
||||
"8502091901001a0167aa07581dcba44f7f04f2de44f42d84c374a0e149136f25b01852545961d55f7f7a",
|
||||
"8503091901001a0167aa07581d8cde6d0e2ec43f3b2dcb644a2209e8c9e34af5c4747984a5e873c9cf5f",
|
||||
"8504091901001a0167aa07581d965e25ee29039fdf8ca74f1c769fc07eb7ebaec46e0695aea6cbd60b3e",
|
||||
"8505091901001a0167aa07581dc4bbff1b9ffe8a9e7240129377b9d3711ed38d412fbb4442256f1e6f59",
|
||||
"8506091901001a0167aa07581d5e0fc57fed451fb0a0101fb76b1fb1e1b88cfdfdaa946294a47de8fff1",
|
||||
"8507091901001a0167aa07581d73f021c0e6f65b05c0a494e50791270a0050a73ae69b6725505a2ec8a5",
|
||||
"8508091901001a0167aa07581d791457c9876dd34aadd192a53aa0dc66b556c0c215c7ceb8248b717c22",
|
||||
"8509091901001a0167aa07581d951e65305b56a3706e3e86eb01c803bbf915d80edcd64d4d0000000000",
|
||||
"850a091901001a0167aa07581d4a1b58fa2733399e5ee04d87a2d1628186e3cd250f3ae0e25d7ae7a22b",
|
||||
"850b091901001a0167aa07581dd35acd70953cf29b542a94cbd75790c73cb4cb1056d56557bf0b70b936",
|
||||
"850c091901001a0167aa07581d8cde6d0e2ec43f3b2dcb644a2209e8c9e34af5c4747984a5e873c9cf5f",
|
||||
"850d091901001a0167aa07581d760be7ad1c6187902bbc04f539b9ee5eb8ea6833222edea36031306c01",
|
||||
"850e091901001a0167aa07581dcba44f7f04f2de44f42d84c374a0e149136f25b01852545961d55f7f7a",
|
||||
"850f091901001a0167aa07581d262518878e747c6eee337fbbd189f77b385efe55597b54cab65b7f8ac0",
|
||||
"8510091901001a0167aa07581d2d4a0b8fb95226315ab796cd72f9c5f8ea2a5a84221f7e31318f71b7df",
|
||||
"8511091901001a0167aa07581d81bf178523c1e7daaacdc944d67183c8958ce8951768b4bb3cafb8dd51",
|
||||
"8512091901001a0167aa07581d8e1548d10a2cd18416a3428bcb1fe2e3cac640274e91df20bdbd4e4df9",
|
||||
"8513091901001a0167aa07581d8a65ebbda606df01244a3dad6b76e258150e4c07021ce5c07ed30160c5",
|
||||
"8514091901001a0167aa07581d2b44620c8371a48f6935d2b525f19c7a4e98e3043a6a64d462870e98ce"};
|
||||
|
||||
Assert.assertEquals(Arrays.asList(expectedPartsHex), partsHex);
|
||||
}
|
||||
|
||||
public static byte[] makeMessage(int len, String seed) {
|
||||
RandomXoshiro256StarStar rng = new RandomXoshiro256StarStar(seed);
|
||||
byte[] message = new byte[len];
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
public class FountainCodesTest {
|
||||
@Test
|
||||
|
@ -25,9 +26,9 @@ public class FountainCodesTest {
|
|||
@Test
|
||||
public void testRandomSampler() {
|
||||
RandomXoshiro256StarStar rng = new RandomXoshiro256StarStar("Wolf");
|
||||
AliasMethod aliasMethod = new AliasMethod(List.of(1d, 2d, 4d, 8d), rng);
|
||||
int[] numbers = IntStream.range(0, 500).map(i -> aliasMethod.next()).toArray();
|
||||
int[] expectedNumbers = new int[] {2, 1, 2, 0, 2, 2, 0, 1, 0, 1, 1, 2, 1, 2, 3, 3, 1, 2, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 3, 0, 1, 0, 2, 0, 1, 3, 3, 0, 3, 1, 2, 1, 2, 2, 0, 3, 2, 3, 2, 3, 1, 3, 1, 1, 0, 0, 1, 0, 0, 3, 0, 0, 2, 1, 2, 3, 3, 3, 3, 2, 0, 2, 3, 0, 0, 3, 1, 0, 2, 1, 1, 3, 0, 0, 2, 1, 1, 3, 3, 1, 3, 0, 1, 1, 0, 1, 0, 0, 1, 0, 3, 2, 2, 2, 1, 1, 0, 1, 3, 1, 0, 3, 3, 1, 3, 2, 1, 2, 2, 1, 3, 3, 3, 3, 2, 0, 0, 2, 2, 0, 2, 2, 1, 3, 2, 1, 2, 2, 2, 3, 0, 2, 1, 3, 1, 3, 1, 3, 0, 2, 2, 3, 2, 3, 1, 1, 1, 2, 3, 0, 1, 2, 3, 1, 2, 2, 1, 3, 3, 3, 2, 1, 0, 1, 1, 3, 2, 2, 3, 0, 0, 2, 0, 1, 0, 2, 2, 2, 1, 0, 2, 1, 2, 1, 3, 0, 0, 1, 0, 0, 0, 0, 1, 2, 1, 0, 1, 3, 1, 1, 3, 2, 1, 0, 2, 2, 0, 1, 1, 3, 0, 3, 3, 0, 1, 3, 3, 1, 2, 1, 1, 1, 2, 3, 2, 2, 1, 3, 2, 1, 3, 0, 2, 2, 0, 1, 3, 3, 0, 1, 1, 2, 3, 0, 2, 3, 1, 2, 1, 0, 2, 2, 0, 1, 2, 1, 3, 3, 0, 0, 3, 1, 2, 2, 0, 0, 2, 1, 1, 3, 2, 0, 3, 0, 0, 3, 0, 0, 3, 2, 2, 3, 3, 3, 3, 3, 3, 2, 1, 2, 2, 0, 2, 3, 3, 1, 3, 2, 1, 3, 2, 0, 0, 0, 0, 1, 0, 3, 1, 1, 1, 0, 0, 3, 0, 1, 1, 3, 2, 3, 3, 3, 2, 2, 2, 2, 0, 2, 2, 0, 2, 3, 2, 0, 3, 2, 2, 3, 3, 0, 3, 0, 2, 1, 2, 1, 0, 2, 2, 0, 0, 0, 0, 3, 0, 3, 0, 2, 3, 0, 3, 3, 3, 3, 3, 2, 3, 1, 2, 1, 0, 3, 0, 0, 1, 3, 0, 0, 0, 1, 3, 0, 3, 1, 0, 3, 2, 3, 0, 0, 1, 1, 3, 1, 3, 3, 1, 1, 2, 3, 3, 0, 0, 0, 2, 2, 2, 2, 1, 0, 2, 2, 3, 2, 2, 0, 2, 3, 2, 0, 3, 0, 1, 2, 2, 0, 2, 3, 0, 0, 2, 0, 3, 0, 1, 1, 3, 2, 2, 2, 0, 1, 2, 3, 3, 2, 2, 1, 2, 3, 1, 1, 1, 0, 3, 3, 0, 1, 2, 1, 0, 3, 2, 0, 3, 1, 1, 2, 2, 0, 1, 3, 0, 1, 3, 0, 0, 2, 0, 3, 0, 1, 2, 2, 0, 3, 1, 2, 0, 2};
|
||||
RandomSampler randomSampler = new RandomSampler(List.of(1d, 2d, 4d, 8d));
|
||||
int[] numbers = IntStream.range(0, 500).map(i -> randomSampler.next(rng)).toArray();
|
||||
int[] expectedNumbers = new int[] {3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 3, 3, 1, 2, 2, 1, 3, 3, 2, 3, 3, 1, 1, 2, 1, 1, 3, 1, 3, 1, 2, 0, 2, 1, 0, 3, 3, 3, 1, 3, 3, 3, 3, 1, 3, 2, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 0, 3, 3, 3, 3, 1, 2, 3, 3, 2, 2, 2, 1, 2, 2, 1, 2, 3, 1, 3, 0, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 1, 3, 3, 2, 0, 2, 2, 3, 1, 1, 2, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 2, 3, 1, 2, 1, 1, 3, 1, 3, 2, 2, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 2, 3, 3, 1, 2, 3, 3, 1, 3, 2, 3, 3, 3, 2, 3, 1, 3, 0, 3, 2, 1, 1, 3, 1, 3, 2, 3, 3, 3, 3, 2, 0, 3, 3, 1, 3, 0, 2, 1, 3, 3, 1, 1, 3, 1, 2, 3, 3, 3, 0, 2, 3, 2, 0, 1, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 2, 0, 2, 3, 3, 3, 3, 2, 1, 1, 1, 2, 1, 3, 3, 3, 2, 2, 3, 3, 1, 2, 3, 0, 3, 2, 3, 3, 3, 3, 0, 2, 2, 3, 2, 2, 3, 3, 3, 3, 1, 3, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1, 3, 0, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 2, 2, 2, 3, 1, 1, 3, 2, 2, 0, 3, 2, 1, 2, 1, 0, 3, 3, 3, 2, 2, 3, 2, 1, 2, 0, 0, 3, 3, 2, 3, 3, 2, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 1, 1, 3, 2, 2, 3, 1, 1, 0, 1, 3, 2, 3, 3, 2, 3, 3, 2, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 1, 2, 3, 3, 2, 2, 2, 2, 3, 3, 2, 0, 2, 1, 3, 3, 3, 3, 0, 3, 3, 3, 3, 2, 2, 3, 1, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 2, 1, 3, 3, 3, 3, 2, 2, 0, 1, 2, 3, 2, 0, 3, 3, 3, 3, 3, 3, 1, 3, 3, 2, 3, 2, 2, 3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 2, 2, 1, 3, 3, 3, 3, 1, 2, 3, 2, 3, 3, 2, 3, 2, 3, 3, 3, 2, 3, 1, 2, 3, 2, 1, 1, 3, 3, 2, 3, 3, 2, 3, 3, 0, 0, 1, 3, 3, 2, 3, 3, 3, 3, 1, 3, 3, 0, 3, 2, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 2};
|
||||
|
||||
Assert.assertArrayEquals(expectedNumbers, numbers);
|
||||
}
|
||||
|
@ -42,6 +43,96 @@ public class FountainCodesTest {
|
|||
Assert.assertEquals(expectedNumbers, numbers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartitionAndJoin() {
|
||||
byte[] message = URTest.makeMessage(1024, "Wolf");
|
||||
int fragmentLen = FountainEncoder.findNominalFragmentLength(message.length, 10, 100);
|
||||
List<byte[]> fragments = FountainEncoder.partitionMessage(message, fragmentLen);
|
||||
List<String> fragmentsHex = fragments.stream().map(Utils::bytesToHex).collect(Collectors.toList());
|
||||
String[] expectedFragmentsHex = new String[] {
|
||||
"916ec65cf77cadf55cd7f9cda1a1030026ddd42e905b77adc36e4f2d3ccba44f7f04f2de44f42d84c374a0e149136f25b01852545961d55f7f7a8cde6d0e2ec43f3b2dcb644a2209e8c9e34af5c4747984a5e873c9cf5f965e25ee29039f",
|
||||
"df8ca74f1c769fc07eb7ebaec46e0695aea6cbd60b3ec4bbff1b9ffe8a9e7240129377b9d3711ed38d412fbb4442256f1e6f595e0fc57fed451fb0a0101fb76b1fb1e1b88cfdfdaa946294a47de8fff173f021c0e6f65b05c0a494e50791",
|
||||
"270a0050a73ae69b6725505a2ec8a5791457c9876dd34aadd192a53aa0dc66b556c0c215c7ceb8248b717c22951e65305b56a3706e3e86eb01c803bbf915d80edcd64d4d41977fa6f78dc07eecd072aae5bc8a852397e06034dba6a0b570",
|
||||
"797c3a89b16673c94838d884923b8186ee2db5c98407cab15e13678d072b43e406ad49477c2e45e85e52ca82a94f6df7bbbe7afbed3a3a830029f29090f25217e48d1f42993a640a67916aa7480177354cc7440215ae41e4d02eae9a1912",
|
||||
"33a6d4922a792c1b7244aa879fefdb4628dc8b0923568869a983b8c661ffab9b2ed2c149e38d41fba090b94155adbed32f8b18142ff0d7de4eeef2b04adf26f2456b46775c6c20b37602df7da179e2332feba8329bbb8d727a138b4ba7a5",
|
||||
"03215eda2ef1e953d89383a382c11d3f2cad37a4ee59a91236a3e56dcf89f6ac81dd4159989c317bd649d9cbc617f73fe10033bd288c60977481a09b343d3f676070e67da757b86de27bfca74392bac2996f7822a7d8f71a489ec6180390",
|
||||
"089ea80a8fcd6526413ec6c9a339115f111d78ef21d456660aa85f790910ffa2dc58d6a5b93705caef1091474938bd312427021ad1eeafbd19e0d916ddb111fabd8dcab5ad6a6ec3a9c6973809580cb2c164e26686b5b98cfb017a337968",
|
||||
"c7daaa14ae5152a067277b1b3902677d979f8e39cc2aafb3bc06fcf69160a853e6869dcc09a11b5009f91e6b89e5b927ab1527a735660faa6012b420dd926d940d742be6a64fb01cdc0cff9faa323f02ba41436871a0eab851e7f5782d10",
|
||||
"fbefde2a7e9ae9dc1e5c2c48f74f6c824ce9ef3c89f68800d44587bedc4ab417cfb3e7447d90e1e417e6e05d30e87239d3a5d1d45993d4461e60a0192831640aa32dedde185a371ded2ae15f8a93dba8809482ce49225daadfbb0fec629e",
|
||||
"23880789bdf9ed73be57fa84d555134630e8d0f7df48349f29869a477c13ccca9cd555ac42ad7f568416c3d61959d0ed568b2b81c7771e9088ad7fd55fd4386bafbf5a528c30f107139249357368ffa980de2c76ddd9ce4191376be0e6b5",
|
||||
"170010067e2e75ebe2d2904aeb1f89d5dc98cd4a6f2faaa8be6d03354c990fd895a97feb54668473e9d942bb99e196d897e8f1b01625cf48a7b78d249bb4985c065aa8cd1402ed2ba1b6f908f63dcd84b66425df00000000000000000000"
|
||||
};
|
||||
|
||||
Assert.assertEquals(Arrays.asList(expectedFragmentsHex), fragmentsHex);
|
||||
|
||||
byte[] rejoinedMessage = FountainDecoder.joinFragments(fragments, message.length);
|
||||
Assert.assertArrayEquals(message, rejoinedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChooseDegree() {
|
||||
byte[] message = URTest.makeMessage(1024, "Wolf");
|
||||
int fragmentLen = FountainEncoder.findNominalFragmentLength(message.length, 10, 100);
|
||||
List<byte[]> fragments = FountainEncoder.partitionMessage(message, fragmentLen);
|
||||
List<Integer> degrees = IntStream.rangeClosed(1, 200).mapToObj( nonce -> {
|
||||
RandomXoshiro256StarStar partRng = new RandomXoshiro256StarStar("Wolf-" + nonce);
|
||||
return FountainUtils.chooseDegree(fragments.size(), partRng);
|
||||
}).collect(Collectors.toList());
|
||||
Integer[] expectedDegrees = new Integer[] {
|
||||
11, 3, 6, 5, 2, 1, 2, 11, 1, 3, 9, 10, 10, 4, 2, 1, 1, 2, 1, 1, 5, 2, 4, 10, 3, 2, 1, 1, 3, 11, 2, 6, 2, 9, 9, 2, 6, 7, 2, 5, 2, 4, 3, 1, 6, 11, 2, 11, 3, 1, 6, 3, 1, 4, 5, 3, 6, 1, 1, 3, 1, 2, 2, 1, 4, 5, 1, 1, 9, 1, 1, 6, 4, 1, 5, 1, 2, 2, 3, 1, 1, 5, 2, 6, 1, 7, 11, 1, 8, 1, 5, 1, 1, 2, 2, 6, 4, 10, 1, 2, 5, 5, 5, 1, 1, 4, 1, 1, 1, 3, 5, 5, 5, 1, 4, 3, 3, 5, 1, 11, 3, 2, 8, 1, 2, 1, 1, 4, 5, 2, 1, 1, 1, 5, 6, 11, 10, 7, 4, 7, 1, 5, 3, 1, 1, 9, 1, 2, 5, 5, 2, 2, 3, 10, 1, 3, 2, 3, 3, 1, 1, 2, 1, 3, 2, 2, 1, 3, 8, 4, 1, 11, 6, 3, 1, 1, 1, 1, 1, 3, 1, 2, 1, 10, 1, 1, 8, 2, 7, 1, 2, 1, 9, 2, 10, 2, 1, 3, 4, 10
|
||||
};
|
||||
Assert.assertEquals(Arrays.asList(expectedDegrees), degrees);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChooseFragment() {
|
||||
byte[] message = URTest.makeMessage(1024, "Wolf");
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(message);
|
||||
long checksum = crc32.getValue();
|
||||
int fragmentLen = FountainEncoder.findNominalFragmentLength(message.length, 10, 100);
|
||||
List<byte[]> fragments = FountainEncoder.partitionMessage(message, fragmentLen);
|
||||
List<List<Integer>> partIndexes = IntStream.rangeClosed(1, 30).mapToObj(nonce -> {
|
||||
return FountainUtils.chooseFragments(nonce, fragments.size(), checksum).stream().sorted().collect(Collectors.toList());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
Integer[][] expectedFragmentIndexes = new Integer[][] {
|
||||
{0},
|
||||
{1},
|
||||
{2},
|
||||
{3},
|
||||
{4},
|
||||
{5},
|
||||
{6},
|
||||
{7},
|
||||
{8},
|
||||
{9},
|
||||
{10},
|
||||
{9},
|
||||
{2, 5, 6, 8, 9, 10},
|
||||
{8},
|
||||
{1, 5},
|
||||
{1},
|
||||
{0, 2, 4, 5, 8, 10},
|
||||
{5},
|
||||
{2},
|
||||
{2},
|
||||
{0, 1, 3, 4, 5, 7, 9, 10},
|
||||
{0, 1, 2, 3, 5, 6, 8, 9, 10},
|
||||
{0, 2, 4, 5, 7, 8, 9, 10},
|
||||
{3, 5},
|
||||
{4},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
||||
{0, 1, 3, 4, 5, 6, 7, 9, 10},
|
||||
{6},
|
||||
{5, 6},
|
||||
{7}
|
||||
};
|
||||
|
||||
List<List<Integer>> expectedPartIndexes = Arrays.stream(expectedFragmentIndexes).map(Arrays::asList).collect(Collectors.toList());
|
||||
Assert.assertEquals(expectedPartIndexes, partIndexes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXOR() {
|
||||
RandomXoshiro256StarStar rng = new RandomXoshiro256StarStar("Wolf");
|
||||
|
@ -73,17 +164,17 @@ public class FountainCodesTest {
|
|||
"8507091901001a0167aa07581d73f021c0e6f65b05c0a494e50791270a0050a73ae69b6725505a2ec8a5",
|
||||
"8508091901001a0167aa07581d791457c9876dd34aadd192a53aa0dc66b556c0c215c7ceb8248b717c22",
|
||||
"8509091901001a0167aa07581d951e65305b56a3706e3e86eb01c803bbf915d80edcd64d4d0000000000",
|
||||
"850a091901001a0167aa07581d4a1b58fa2733399e5ee04d87a2d1628186e3cd250f3ae0e25d7ae7a22b",
|
||||
"850b091901001a0167aa07581dd35acd70953cf29b542a94cbd75790c73cb4cb1056d56557bf0b70b936",
|
||||
"850c091901001a0167aa07581d8cde6d0e2ec43f3b2dcb644a2209e8c9e34af5c4747984a5e873c9cf5f",
|
||||
"850a091901001a0167aa07581d330f0f33a05eead4f331df229871bee733b50de71afd2e5a79f196de09",
|
||||
"850b091901001a0167aa07581d3b205ce5e52d8c24a52cffa34c564fa1af3fdffcd349dc4258ee4ee828",
|
||||
"850c091901001a0167aa07581ddd7bf725ea6c16d531b5f03254783803048ca08b87148daacd1cd7a006",
|
||||
"850d091901001a0167aa07581d760be7ad1c6187902bbc04f539b9ee5eb8ea6833222edea36031306c01",
|
||||
"850e091901001a0167aa07581dcba44f7f04f2de44f42d84c374a0e149136f25b01852545961d55f7f7a",
|
||||
"850f091901001a0167aa07581d262518878e747c6eee337fbbd189f77b385efe55597b54cab65b7f8ac0",
|
||||
"8510091901001a0167aa07581d2d4a0b8fb95226315ab796cd72f9c5f8ea2a5a84221f7e31318f71b7df",
|
||||
"8511091901001a0167aa07581d81bf178523c1e7daaacdc944d67183c8958ce8951768b4bb3cafb8dd51",
|
||||
"8512091901001a0167aa07581d8e1548d10a2cd18416a3428bcb1fe2e3cac640274e91df20bdbd4e4df9",
|
||||
"8513091901001a0167aa07581d8a65ebbda606df01244a3dad6b76e258150e4c07021ce5c07ed30160c5",
|
||||
"8514091901001a0167aa07581d2b44620c8371a48f6935d2b525f19c7a4e98e3043a6a64d462870e98ce"
|
||||
"850e091901001a0167aa07581d5bf4031217d2c3254b088fa7553778b5003632f46e21db129416f65b55",
|
||||
"850f091901001a0167aa07581d73f021c0e6f65b05c0a494e50791270a0050a73ae69b6725505a2ec8a5",
|
||||
"8510091901001a0167aa07581db8546ebfe2048541348910267331c643133f828afec9337c318f71b7df",
|
||||
"8511091901001a0167aa07581d23dedeea74e3a0fb052befabefa13e2f80e4315c9dceed4c8630612e64",
|
||||
"8512091901001a0167aa07581dd01a8daee769ce34b6b35d3ca0005302724abddae405bdb419c0a6b208",
|
||||
"8513091901001a0167aa07581d3171c5dc365766eff25ae47c6f10e7de48cfb8474e050e5fe997a6dc24",
|
||||
"8514091901001a0167aa07581de055c2433562184fa71b4be94f262e200f01c6f74c284b0dc6fae6673f"
|
||||
};
|
||||
|
||||
Assert.assertEquals(Arrays.asList(expectedPartsHex), partsHex);
|
||||
|
|
Loading…
Reference in a new issue