Skip to content

Commit

Permalink
feat: add schnorr signature (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
alfonsobries authored Aug 12, 2024
1 parent 8994f04 commit cf8fa10
Show file tree
Hide file tree
Showing 178 changed files with 878 additions and 4,184 deletions.
27 changes: 17 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ on:
jobs:
format:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2
Expand All @@ -21,11 +20,11 @@ jobs:
- name: Merge Conflict finder
uses: olivernybroe/action-conflict-finder@v1.1

- name: Use Java Version ${{ matrix.java }}
- name: Use Java Version 22
uses: actions/setup-java@v2
with:
distribution: "adopt"
java-version: 22
java-version: "22"
cache: "gradle"

- name: Format code
Expand All @@ -39,10 +38,6 @@ jobs:

unit:
runs-on: ubuntu-latest
strategy:
matrix:
java: [11, 15, 16, 17, 18, 19, 20, 21, 22]

steps:
- name: Checkout code
uses: actions/checkout@v2
Expand All @@ -52,11 +47,23 @@ jobs:
- name: Merge Conflict finder
uses: olivernybroe/action-conflict-finder@v1.1

- name: Use Java Version ${{ matrix.java }}
- name: Install Nix
uses: cachix/install-nix-action@v27

- name: Install libsecp256k1
run: |
nix profile install nixpkgs#secp256k1
env:
NIX_PATH: $HOME/.nix-profile/bin

- name: Set LD_LIBRARY_PATH
run: echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/.nix-profile/lib" >> $GITHUB_ENV

- name: Use Java Version 22
uses: actions/setup-java@v2
with:
distribution: "zulu"
java-version: ${{ matrix.java }}
distribution: "adopt"
java-version: "22"
cache: "gradle"

- name: Install
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ repositories {
dependencies {
implementation 'org.web3j:core:4.8.7'
implementation 'org.bitcoinj:bitcoinj-core:0.16.3'
implementation files('libs/secp256k1-api-0.0.1.jar')
implementation files('libs/secp256k1-foreign-0.0.1.jar')
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'com.google.guava:guava:30.2.0-jre'

Expand All @@ -40,6 +42,7 @@ test {
useJUnitPlatform()
testLogging {
events 'PASSED', 'FAILED', 'SKIPPED'
showStandardStreams = true
}
}

Expand Down
Binary file added libs/secp256k1-api-0.0.1.jar
Binary file not shown.
Binary file added libs/secp256k1-foreign-0.0.1.jar
Binary file not shown.
180 changes: 0 additions & 180 deletions src/main/java/org/arkecosystem/crypto/Schnorr.java
Original file line number Diff line number Diff line change
@@ -1,166 +1,6 @@
package org.arkecosystem.crypto;

import java.math.BigInteger;
import org.bitcoinj.core.Sha256Hash;

/** Heavily inspired in https://github.com/miketwk/bip-schnorr-java/blob/master/Schnorr.java */
public class Schnorr {
public static final BigInteger p =
new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
public static final BigInteger n =
new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
public static final BigInteger[] G = {
new BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16),
new BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
};

public static final BigInteger TWO = BigInteger.valueOf(2);
public static final BigInteger THREE = BigInteger.valueOf(3);

private static final char[] hexArray = "0123456789ABCDEF".toCharArray();

public static BigInteger[] addPoint(BigInteger[] p1, BigInteger[] p2) {
if (p1 == null || p1.length != 2) return p2;

if (p2 == null || p2.length != 2) return p1;

if (p1[0].compareTo(p2[0]) == 0 && p1[1].compareTo(p2[1]) != 0) return null;

BigInteger lam;
if (p1[0].compareTo(p2[0]) == 0 && p1[1].compareTo(p2[1]) == 0)
lam =
(THREE.multiply(p1[0])
.multiply(p1[0])
.multiply(TWO.multiply(p1[1]).modPow(p.subtract(TWO), p)))
.mod(p);
else
lam =
(p2[1].subtract(p1[1])
.multiply(p2[0].subtract(p1[0]).modPow(p.subtract(TWO), p)))
.mod(p);

BigInteger x3 = (lam.multiply(lam).subtract(p1[0]).subtract(p2[0])).mod(p);

return new BigInteger[] {x3, lam.multiply(p1[0].subtract(x3)).subtract(p1[1]).mod(p)};
}

public static BigInteger[] multiplyPoint(BigInteger[] P, BigInteger n) {
BigInteger[] R = null;
for (int i = 0; i < 256; i++) {
if (BigInteger.ONE.compareTo(n.shiftRight(i).and(BigInteger.ONE)) == 0)
R = addPoint(R, P);
P = addPoint(P, P);
}
return R;
}

public static BigInteger jacobi(BigInteger x) {
return x.modPow(p.subtract(BigInteger.ONE).divide(TWO), p);
}

public static BigInteger[] bytesToPoint(byte[] b) {
if (b[0] != 2 && b[0] != 3) return null;

BigInteger odd = b[0] == 3 ? BigInteger.ONE : BigInteger.ZERO;
BigInteger x = toBigInteger(b, 1, 32);
BigInteger y_sq = x.modPow(THREE, p).add(BigInteger.valueOf(7)).mod(p);
BigInteger y0 = y_sq.modPow(p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)), p);
if (y_sq.compareTo(y0.modPow(TWO, p)) != 0) return null;

BigInteger y = y0.and(BigInteger.ONE).compareTo(odd) != 0 ? p.subtract(y0) : y0;

return new BigInteger[] {x, y};
}

public static byte[] to32BytesData(BigInteger num) {
String hexNum = num.toString(16);
if (hexNum.length() < 64) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 64 - hexNum.length(); i++) sb.append("0");

hexNum = sb.append(hexNum).toString();
}
return hexStringToByteArray(hexNum);
}

public static BigInteger toBigInteger(byte[] data, int startPos, int len) {
return new BigInteger(bytesToHex(data, startPos, len), 16);
}

public static BigInteger toBigInteger(byte[] data) {
return new BigInteger(bytesToHex(data), 16);
}

public static byte[] pointToBytes(BigInteger[] point) {
byte[] res = new byte[33];
res[0] =
BigInteger.ONE.compareTo(point[1].and(BigInteger.ONE)) == 0
? (byte) 0x03
: (byte) 0x02;
System.arraycopy(to32BytesData(point[0]), 0, res, 1, 32);
return res;
}

public static byte[] schnorrSign(byte[] msg, BigInteger seckey) {
if (msg.length != 32) throw new RuntimeException("The message must be a 32-byte array.");

if (BigInteger.ZERO.compareTo(seckey) > 0
|| seckey.compareTo(n.subtract(BigInteger.ONE)) > 0)
throw new RuntimeException("The secret key must be an integer in the range 1..n-1.");

byte[] resultData = new byte[32 + msg.length];
System.arraycopy(to32BytesData(seckey), 0, resultData, 0, 32);
System.arraycopy(msg, 0, resultData, 32, msg.length);

BigInteger k0 = toBigInteger(Sha256Hash.hash(resultData)).mod(n);
if (BigInteger.ZERO.compareTo(k0) == 0)
throw new RuntimeException("Failure. This happens only with negligible probability.");

BigInteger[] R = multiplyPoint(G, k0);

BigInteger k = BigInteger.ONE.compareTo(jacobi(R[1])) != 0 ? n.subtract(k0) : k0;
byte[] R0Bytes = to32BytesData(R[0]);
byte[] eData = new byte[32 + 33 + 32];
System.arraycopy(R0Bytes, 0, eData, 0, 32);
System.arraycopy(pointToBytes(multiplyPoint(G, seckey)), 0, eData, 32, 33);
System.arraycopy(msg, 0, eData, 65, 32);
eData = Sha256Hash.hash(eData);
BigInteger e = toBigInteger(eData).mod(n);

byte[] finalData = new byte[64];
System.arraycopy(R0Bytes, 0, finalData, 0, 32);
System.arraycopy(to32BytesData(e.multiply(seckey).add(k).mod(n)), 0, finalData, 32, 32);

return finalData;
}

public static boolean schnorrVerify(byte[] msg, byte[] pubkey, byte[] sig) {
if (msg.length != 32) throw new RuntimeException("The message must be a 32-byte array.");

if (pubkey.length != 33)
throw new RuntimeException("The public key must be a 33-byte array.");

if (sig.length != 64) throw new RuntimeException("The signature must be a 64-byte array.");

BigInteger[] P = bytesToPoint(pubkey);
if (P == null) return false;

BigInteger r = toBigInteger(sig, 0, 32);
BigInteger s = toBigInteger(sig, 32, 32);

if (r.compareTo(p) >= 0 || s.compareTo(n) >= 0) return false;

byte[] eData = new byte[32 + 33 + 32];
System.arraycopy(sig, 0, eData, 0, 32);
System.arraycopy(pointToBytes(P), 0, eData, 32, 33);
System.arraycopy(msg, 0, eData, 65, 32);
eData = Sha256Hash.hash(eData);
BigInteger e = toBigInteger(eData).mod(n);

BigInteger[] R = addPoint(multiplyPoint(G, s), multiplyPoint(P, n.subtract(e)));

return R != null && BigInteger.ONE.compareTo(jacobi(R[1])) == 0 && r.compareTo(R[0]) == 0;
}

public static byte[] hexStringToByteArray(String s) {
int len = s.length();
Expand All @@ -173,24 +13,4 @@ public static byte[] hexStringToByteArray(String s) {
}
return data;
}

public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}

public static String bytesToHex(byte[] bytes, int startPos, int len) {
char[] hexChars = new char[len * 2];
for (int j = 0, i = startPos; j < len; j++, i++) {
int v = bytes[i] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
public enum CoreTransactionTypes {
TRANSFER(0),
SECOND_SIGNATURE_REGISTRATION(1),
DELEGATE_REGISTRATION(2),
VALIDATOR_REGISTRATION(2),
VOTE(3),
MULTI_SIGNATURE_REGISTRATION(4),
IPFS(5),
MULTI_PAYMENT(6),
DELEGATE_RESIGNATION(7),
VALIDATOR_RESIGNATION(7),
HTLC_LOCK(8),
HTLC_CLAIM(9),
HTLC_REFUND(10);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/arkecosystem/crypto/enums/Fees.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
public enum Fees {
TRANSFER(10_000_000L),
SECOND_SIGNATURE_REGISTRATION(500_000_000L),
DELEGATE_REGISTRATION(2_500_000_000L),
VALIDATOR_REGISTRATION(2_500_000_000L),
VOTE(100_000_000L),
MULTI_SIGNATURE_REGISTRATION(500_000_000L),
IPFS(500_000_000L),
MULTI_PAYMENT(10_000_000L),
DELEGATE_RESIGNATION(2_500_000_000L),
VALIDATOR_RESIGNATION(2_500_000_000L),
HTLC_LOCK(10_000_000L),
HTLC_CLAIM(0L),
HTLC_REFUND(0L);
Expand Down

This file was deleted.

7 changes: 0 additions & 7 deletions src/main/java/org/arkecosystem/crypto/identities/Address.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,4 @@ public static String fromPrivateKey(ECKey privateKey) {
byte[] publicKeyBytes = privateKey.getPubKey();
return fromPublicKey(Hex.encode(publicKeyBytes));
}

public static Boolean validate(String address) {
if (address == null || !address.matches("^0x[a-fA-F0-9]{40}$")) {
return false;
}
return address.equals(Keys.toChecksumAddress(address));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.arkecosystem.crypto.signature;

import java.util.Arrays;
import org.bitcoinj.secp256k1.api.CompressedPubKeyData;

public class CompressedPubKeyDataImpl implements CompressedPubKeyData {

private final byte[] bytes;

public CompressedPubKeyDataImpl(byte[] bytes) {
if (bytes.length != 33) {
throw new IllegalArgumentException("Compressed public key must be 33 bytes");
}
this.bytes = Arrays.copyOf(bytes, bytes.length);
}

@Override
public byte[] bytes() {
return Arrays.copyOf(bytes, bytes.length);
}
}
11 changes: 0 additions & 11 deletions src/main/java/org/arkecosystem/crypto/signature/ECDSASigner.java

This file was deleted.

15 changes: 0 additions & 15 deletions src/main/java/org/arkecosystem/crypto/signature/ECDSAVerifier.java

This file was deleted.

Loading

0 comments on commit cf8fa10

Please sign in to comment.