Feldman's VSS
parent
7e542a804c
commit
6100497e8e
|
@ -0,0 +1,220 @@
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "us.kirchmeier.capsule" version "1.0.1"
|
||||||
|
id 'com.google.protobuf' version '0.7.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'eclipse'
|
||||||
|
apply plugin: 'idea'
|
||||||
|
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
|
// Uncomment the lines below to define an application
|
||||||
|
// (this will also allow you to build a "fatCapsule" which includes
|
||||||
|
// the entire application, including all dependencies in a single jar)
|
||||||
|
//apply plugin: 'application'
|
||||||
|
//mainClassName='your.main.ApplicationClass'
|
||||||
|
|
||||||
|
// Is this a snapshot version?
|
||||||
|
ext { isSnapshot = false }
|
||||||
|
|
||||||
|
ext {
|
||||||
|
groupId = 'org.factcenter.meerkat'
|
||||||
|
nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/"
|
||||||
|
|
||||||
|
// Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing)
|
||||||
|
// Should be set in ${HOME}/.gradle/gradle.properties
|
||||||
|
nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : ""
|
||||||
|
nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "TODO: Add a description"
|
||||||
|
|
||||||
|
// Your project version
|
||||||
|
version = "0.0"
|
||||||
|
|
||||||
|
version += "${isSnapshot ? '-SNAPSHOT' : ''}"
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Meerkat common
|
||||||
|
compile project(':meerkat-common')
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
compile 'org.slf4j:slf4j-api:1.7.7'
|
||||||
|
runtime 'ch.qos.logback:logback-classic:1.1.2'
|
||||||
|
runtime 'ch.qos.logback:logback-core:1.1.2'
|
||||||
|
|
||||||
|
// Google protobufs
|
||||||
|
compile 'com.google.protobuf:protobuf-java:3.+'
|
||||||
|
|
||||||
|
testCompile 'junit:junit:4.+'
|
||||||
|
|
||||||
|
runtime 'org.codehaus.groovy:groovy:2.4.+'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*==== You probably don't have to edit below this line =======*/
|
||||||
|
|
||||||
|
// Setup test configuration that can appear as a dependency in
|
||||||
|
// other subprojects
|
||||||
|
configurations {
|
||||||
|
testOutput.extendsFrom (testCompile)
|
||||||
|
}
|
||||||
|
|
||||||
|
task testJar(type: Jar, dependsOn: testClasses) {
|
||||||
|
classifier = 'tests'
|
||||||
|
from sourceSets.test.output
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
testOutput testJar
|
||||||
|
}
|
||||||
|
|
||||||
|
// The run task added by the application plugin
|
||||||
|
// is also of type JavaExec.
|
||||||
|
tasks.withType(JavaExec) {
|
||||||
|
// Assign all Java system properties from
|
||||||
|
// the command line to the JavaExec task.
|
||||||
|
systemProperties System.properties
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
// Configure the protoc executable
|
||||||
|
protoc {
|
||||||
|
// Download from repositories
|
||||||
|
artifact = 'com.google.protobuf:protoc:3.+'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
idea {
|
||||||
|
module {
|
||||||
|
project.sourceSets.each { sourceSet ->
|
||||||
|
|
||||||
|
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
|
||||||
|
|
||||||
|
println "Adding $srcDir"
|
||||||
|
// add protobuf generated sources to generated source dir.
|
||||||
|
if ("test".equals(sourceSet.name)) {
|
||||||
|
testSourceDirs += file(srcDir)
|
||||||
|
} else {
|
||||||
|
sourceDirs += file(srcDir)
|
||||||
|
}
|
||||||
|
generatedSourceDirs += file(srcDir)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't exclude build directory
|
||||||
|
excludeDirs -= file(buildDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*===================================
|
||||||
|
* "Fat" Build targets
|
||||||
|
*===================================*/
|
||||||
|
|
||||||
|
|
||||||
|
if (project.hasProperty('mainClassName') && (mainClassName != null)) {
|
||||||
|
|
||||||
|
task mavenCapsule(type: MavenCapsule) {
|
||||||
|
description = "Generate a capsule jar that automatically downloads and caches dependencies when run."
|
||||||
|
applicationClass mainClassName
|
||||||
|
destinationDir = buildDir
|
||||||
|
}
|
||||||
|
|
||||||
|
task fatCapsule(type: FatCapsule) {
|
||||||
|
description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class"
|
||||||
|
|
||||||
|
destinationDir = buildDir
|
||||||
|
|
||||||
|
def fatMain = hasProperty('fatmain') ? fatmain : mainClassName
|
||||||
|
|
||||||
|
applicationClass fatMain
|
||||||
|
|
||||||
|
def testJar = hasProperty('test')
|
||||||
|
|
||||||
|
if (hasProperty('fatmain')) {
|
||||||
|
appendix = "fat-${fatMain}"
|
||||||
|
} else {
|
||||||
|
appendix = "fat"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testJar) {
|
||||||
|
from sourceSets.test.output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*===================================
|
||||||
|
* Repositories
|
||||||
|
*===================================*/
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
|
||||||
|
// Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral)
|
||||||
|
maven {
|
||||||
|
url nexusRepository
|
||||||
|
|
||||||
|
if (isSnapshot) {
|
||||||
|
credentials { username
|
||||||
|
password
|
||||||
|
|
||||||
|
username nexusUser
|
||||||
|
password nexusPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use local maven repository
|
||||||
|
mavenLocal()
|
||||||
|
|
||||||
|
// Use 'maven central' for other dependencies.
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
task "info" << {
|
||||||
|
println "Project: ${project.name}"
|
||||||
|
println "Description: ${project.description}"
|
||||||
|
println "--------------------------"
|
||||||
|
println "GroupId: $groupId"
|
||||||
|
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
|
||||||
|
println ""
|
||||||
|
}
|
||||||
|
info.description 'Print some information about project parameters'
|
||||||
|
|
||||||
|
|
||||||
|
/*===================================
|
||||||
|
* Publishing
|
||||||
|
*===================================*/
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
groupId project.groupId
|
||||||
|
pom.withXml {
|
||||||
|
asNode().appendNode('description', project.description)
|
||||||
|
}
|
||||||
|
from project.components.java
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
|
||||||
|
credentials { username
|
||||||
|
password
|
||||||
|
|
||||||
|
username nexusUser
|
||||||
|
password nexusPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package FeldmanVerifiableSecretSharing;
|
||||||
|
|
||||||
|
import ShamirSecretSharing.SecretSharing;
|
||||||
|
import org.bouncycastle.util.Arrays;
|
||||||
|
import org.factcenter.qilin.primitives.concrete.Zpstar;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Tzlil on 1/27/2016.
|
||||||
|
*/
|
||||||
|
public class VerifiableSecretSharing extends SecretSharing {
|
||||||
|
|
||||||
|
private final BigInteger[] commitments;
|
||||||
|
private final BigInteger g;
|
||||||
|
|
||||||
|
public VerifiableSecretSharing(Zpstar zpstar, int t, int n, BigInteger s, Random random) {
|
||||||
|
super(zpstar, t, n, s, random);
|
||||||
|
this.g = BigInteger.ONE; //ToDO zpstar.getGenerator()
|
||||||
|
this.commitments = generateCommitments();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigInteger[] generateCommitments() {
|
||||||
|
BigInteger[] coefficients = polynomial.getCoefficients();
|
||||||
|
BigInteger[] commitments = new BigInteger[coefficients.length];
|
||||||
|
for (int i = 0 ; i < commitments.length;i++){
|
||||||
|
commitments[i] = zpstar.multiply(g,coefficients[i]);
|
||||||
|
}
|
||||||
|
return commitments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger verify(int i) throws Exception {
|
||||||
|
if(i < 1 || i > n){
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
BigInteger v = BigInteger.ONE;
|
||||||
|
int power = 1;
|
||||||
|
for (int j = 0 ; j < commitments.length ; j ++){
|
||||||
|
v.multiply(commitments[i].pow(power));
|
||||||
|
power *=i;
|
||||||
|
}
|
||||||
|
return zpstar.add(BigInteger.ONE,v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BigInteger getG() {
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger[] getCommitments() {
|
||||||
|
return Arrays.clone(commitments);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package ShamirSecretSharing;
|
||||||
|
|
||||||
|
import org.bouncycastle.util.Arrays;
|
||||||
|
import org.factcenter.qilin.primitives.concrete.ECGroup;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Tzlil on 1/27/2016.
|
||||||
|
*/
|
||||||
|
public class Polynomial {
|
||||||
|
|
||||||
|
private static final Polynomial ZERO = new Polynomial(new BigInteger[]{BigInteger.ZERO});
|
||||||
|
private static final Polynomial ONE = new Polynomial(new BigInteger[]{BigInteger.ONE});
|
||||||
|
private final int degree;
|
||||||
|
private final BigInteger[] coefficients;
|
||||||
|
|
||||||
|
public Polynomial(BigInteger[] coefficients) {
|
||||||
|
this.degree = coefficients.length - 1;
|
||||||
|
this.coefficients = coefficients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Polynomial(Polynomial polynomial) {
|
||||||
|
this.degree = polynomial.getDegree();
|
||||||
|
this.coefficients = polynomial.getCoefficients();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger image(BigInteger x){
|
||||||
|
BigInteger result = BigInteger.ZERO;
|
||||||
|
BigInteger power = BigInteger.ONE;
|
||||||
|
for(int i = 0 ; i <= degree ; i++){
|
||||||
|
result.add(coefficients[i].multiply(power));
|
||||||
|
power.multiply(x);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Polynomial interpolation(Point[] points){
|
||||||
|
Polynomial[] factors = new Polynomial[points.length];
|
||||||
|
for (int i = 0 ; i < factors.length ; i++){
|
||||||
|
factors[i] = new Polynomial(new BigInteger[]{BigInteger.ONE,points[i].x}); // X - Xi
|
||||||
|
}
|
||||||
|
Polynomial result = ZERO;
|
||||||
|
BigInteger constant;
|
||||||
|
Polynomial product;
|
||||||
|
for (int i = 0 ; i < points.length; i++){
|
||||||
|
constant = points[i].y;
|
||||||
|
product = ONE;
|
||||||
|
for (int j = 0 ; j < points.length; j ++){
|
||||||
|
if(i != j ) {
|
||||||
|
constant = constant.divide(points[i].x.subtract(points[j].x));
|
||||||
|
product = product.mul(factors[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(product.mul(constant));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Polynomial add(Polynomial other){
|
||||||
|
Polynomial bigger,smaller;
|
||||||
|
if(this.degree < other.degree){
|
||||||
|
bigger = other;
|
||||||
|
smaller = this;
|
||||||
|
}else{
|
||||||
|
bigger = this;
|
||||||
|
smaller = other;
|
||||||
|
}
|
||||||
|
BigInteger[] coefficients = bigger.getCoefficients();
|
||||||
|
|
||||||
|
for (int i = 0; i <= smaller.degree ; i++){
|
||||||
|
coefficients[i] = smaller.coefficients[i].add(bigger.coefficients[i]);
|
||||||
|
}
|
||||||
|
return new Polynomial(coefficients);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Polynomial mul(BigInteger constant){
|
||||||
|
|
||||||
|
BigInteger[] coefficients = this.getCoefficients();
|
||||||
|
|
||||||
|
for (int i = 0; i <= this.degree ; i++){
|
||||||
|
coefficients[i] = constant.multiply(coefficients[i]);
|
||||||
|
}
|
||||||
|
return new Polynomial(coefficients);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Polynomial mul(Polynomial other){
|
||||||
|
|
||||||
|
BigInteger[] coefficients = new BigInteger[this.degree + other.degree + 1];
|
||||||
|
java.util.Arrays.fill(coefficients,BigInteger.ZERO);
|
||||||
|
|
||||||
|
for (int i = 0; i <= this.degree ; i++){
|
||||||
|
for (int j = 0; j <= other.degree; j++){
|
||||||
|
coefficients[i+j] = coefficients[i+j].add(this.coefficients[i].multiply(other.coefficients[j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Polynomial(coefficients);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BigInteger[] getCoefficients() {
|
||||||
|
return Arrays.clone(coefficients);
|
||||||
|
}
|
||||||
|
public int getDegree() {
|
||||||
|
return degree;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class Point{
|
||||||
|
public final BigInteger x;
|
||||||
|
public final BigInteger y;
|
||||||
|
|
||||||
|
public Point(BigInteger x, BigInteger y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package ShamirSecretSharing;
|
||||||
|
|
||||||
|
|
||||||
|
import org.factcenter.qilin.primitives.concrete.Zpstar;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Tzlil on 1/27/2016.
|
||||||
|
*/
|
||||||
|
public class SecretSharing {
|
||||||
|
protected final Zpstar zpstar;
|
||||||
|
protected final int t;
|
||||||
|
protected final int n;
|
||||||
|
protected final Polynomial polynomial;
|
||||||
|
|
||||||
|
public SecretSharing(Zpstar zpstar, int t, int n, BigInteger s, Random random) {
|
||||||
|
this.zpstar = zpstar;
|
||||||
|
this.t = t;
|
||||||
|
this.n = n;
|
||||||
|
this.polynomial = generateRandomPolynomial(s,random);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Polynomial generateRandomPolynomial(BigInteger s, Random random) {
|
||||||
|
BigInteger[] coefficients = new BigInteger[t + 1];
|
||||||
|
coefficients[0] = s;
|
||||||
|
for (int i = 1 ; i <= t; i++ ){
|
||||||
|
coefficients[i] = zpstar.sample(random);
|
||||||
|
}
|
||||||
|
return new Polynomial(coefficients);
|
||||||
|
}
|
||||||
|
|
||||||
|
//ToDo make it safe : permission to call this func + modulo calc
|
||||||
|
public Polynomial.Point getShare(int i) throws Exception {
|
||||||
|
if(i < 1 || i > n){
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
return new Polynomial.Point(BigInteger.valueOf(i), polynomial.image(BigInteger.valueOf(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigInteger getSecrete(Polynomial.Point[] shares){
|
||||||
|
Polynomial polynomial = Polynomial.interpolation(shares);
|
||||||
|
return polynomial.image(BigInteger.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getThreshold() {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getN() {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
|
@ -1,7 +1,6 @@
|
||||||
#Tue Aug 05 03:26:05 IDT 2014
|
#Wed Jan 27 00:30:26 IST 2016
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip
|
||||||
distributionSha256Sum=4647967f8de78d6d6d8093cdac50f368f8c2b8038f41a5afe1c3bce4c69219a9
|
|
||||||
|
|
|
@ -42,11 +42,6 @@ case "`uname`" in
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
|
||||||
if $cygwin ; then
|
|
||||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
PRG="$0"
|
||||||
|
@ -61,9 +56,9 @@ while [ -h "$PRG" ] ; do
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
SAVED="`pwd`"
|
||||||
cd "`dirname \"$PRG\"`/" >&-
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
APP_HOME="`pwd -P`"
|
APP_HOME="`pwd -P`"
|
||||||
cd "$SAVED" >&-
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
@ -114,6 +109,7 @@ fi
|
||||||
if $cygwin ; then
|
if $cygwin ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
|
|
@ -110,7 +110,7 @@ public class ECElGamalEncryption extends GlobalCryptoSetup implements Encryption
|
||||||
Pair<ECPoint,ECPoint> randomizer = elGamalPK.encrypt(curve.getInfinity(), rndInt);
|
Pair<ECPoint,ECPoint> randomizer = elGamalPK.encrypt(curve.getInfinity(), rndInt);
|
||||||
ConcreteCrypto.ElGamalCiphertext originalEncodedCipher= ConcreteCrypto.ElGamalCiphertext.parseFrom(msg.getData());
|
ConcreteCrypto.ElGamalCiphertext originalEncodedCipher= ConcreteCrypto.ElGamalCiphertext.parseFrom(msg.getData());
|
||||||
|
|
||||||
Pair<ECPoint,ECPoint> originalCipher = new Pair<>(
|
Pair<ECPoint,ECPoint> originalCipher = new Pair<ECPoint, ECPoint>(
|
||||||
curve.decodePoint(originalEncodedCipher.getC1().toByteArray()),
|
curve.decodePoint(originalEncodedCipher.getC1().toByteArray()),
|
||||||
curve.decodePoint(originalEncodedCipher.getC2().toByteArray()));
|
curve.decodePoint(originalEncodedCipher.getC2().toByteArray()));
|
||||||
Pair<ECPoint,ECPoint> newCipher = elGamalPK.add(originalCipher, randomizer);
|
Pair<ECPoint,ECPoint> newCipher = elGamalPK.add(originalCipher, randomizer);
|
||||||
|
|
|
@ -4,4 +4,5 @@ include 'bulletin-board-server'
|
||||||
include 'polling-station'
|
include 'polling-station'
|
||||||
include 'restful-api-common'
|
include 'restful-api-common'
|
||||||
include 'bulletin-board-client'
|
include 'bulletin-board-client'
|
||||||
|
include 'destributed-key-generation'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue