From e2596a9ad1ed9f08618a933351ea7a118fd3f180 Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Thu, 1 Apr 2021 14:58:46 +0200 Subject: [PATCH 1/4] Initial commit of Base58/Base58Check support --- .../java/io/warp10/script/WarpScriptLib.java | 9 ++ .../io/warp10/script/functions/B58TO.java | 144 ++++++++++++++++++ .../io/warp10/script/functions/TOB58.java | 113 ++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 warp10/src/main/java/io/warp10/script/functions/B58TO.java create mode 100644 warp10/src/main/java/io/warp10/script/functions/TOB58.java diff --git a/warp10/src/main/java/io/warp10/script/WarpScriptLib.java b/warp10/src/main/java/io/warp10/script/WarpScriptLib.java index d17ece63b..f6ed8e7d4 100644 --- a/warp10/src/main/java/io/warp10/script/WarpScriptLib.java +++ b/warp10/src/main/java/io/warp10/script/WarpScriptLib.java @@ -1094,6 +1094,10 @@ public class WarpScriptLib { public static final String TOBIN_ = "->BIN"; public static final String TOHEX_ = "->HEX"; public static final String TOB64 = "->B64"; + public static final String TOB58 = "->B58"; + public static final String TOB58C = "->B58C"; + public static final String B58TO = "B58->"; + public static final String B58CTO = "B58C->"; public static final String TOB64URL = "->B64URL"; public static final String TOENCODER = "->ENCODER"; public static final String TOENCODERS = "->ENCODERS"; @@ -1579,6 +1583,11 @@ public class WarpScriptLib { addNamedWarpScriptFunction(new OPB64TO(OPB64TO)); addNamedWarpScriptFunction(new OPB64TOHEX(OPB64TOHEX)); + addNamedWarpScriptFunction(new TOB58(TOB58, false)); + addNamedWarpScriptFunction(new TOB58(TOB58C, true)); + addNamedWarpScriptFunction(new B58TO(B58TO, false)); + addNamedWarpScriptFunction(new B58TO(B58CTO, true)); + // // Conditionals // diff --git a/warp10/src/main/java/io/warp10/script/functions/B58TO.java b/warp10/src/main/java/io/warp10/script/functions/B58TO.java new file mode 100644 index 000000000..463842071 --- /dev/null +++ b/warp10/src/main/java/io/warp10/script/functions/B58TO.java @@ -0,0 +1,144 @@ +// +// Copyright 2021 SenX S.A.S. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package io.warp10.script.functions; + +import java.math.BigInteger; +import java.util.Arrays; + +import org.bouncycastle.crypto.digests.SHA256Digest; + +import com.google.common.primitives.Bytes; + +import io.warp10.script.NamedWarpScriptFunction; +import io.warp10.script.WarpScriptException; +import io.warp10.script.WarpScriptStack; +import io.warp10.script.WarpScriptStackFunction; + +/** + * Encode a String into Base58 or Base58Check + */ +public class B58TO extends NamedWarpScriptFunction implements WarpScriptStackFunction { + + private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + + private static final BigInteger[] TEBAHPLA; + + static { + TEBAHPLA = new BigInteger[128]; + for (int i = 0; i < ALPHABET.length(); i++) { + TEBAHPLA[ALPHABET.charAt(i)] = BigInteger.valueOf(i); + } + } + + private static final BigInteger FIFTY_EIGHT = new BigInteger("58"); + + private final boolean check; + + public B58TO(String name, boolean check) { + super(name); + this.check = check; + } + + @Override + public Object apply(WarpScriptStack stack) throws WarpScriptException { + Object top = stack.pop(); + + byte[] prefix = null; + + if (this.check && !(top instanceof byte[])) { + throw new WarpScriptException(getName() + " expects a byte array prefix."); + } else if (this.check) { + prefix = (byte[]) top; + top = stack.pop(); + } + + if (!(top instanceof String)) { + throw new WarpScriptException(getName() + " operates on a STRING."); + } + + byte[] decoded = decode((String) top); + + if (check) { + if (decoded.length < prefix.length + 4) { + throw new WarpScriptException("Invalid length."); + } + + for (int i = 0; i < prefix.length; i++) { + if (i > decoded.length || decoded[i] != prefix[i]) { + throw new WarpScriptException("Invalid prefix."); + } + } + + SHA256Digest digest = new SHA256Digest(); + digest.update(decoded, 0, decoded.length - 4); + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + digest.reset(); + digest.update(hash, 0, hash.length); + digest.doFinal(hash, 0); + + // + // Check trailer + // + + for (int i = 0; i < 4; i++) { + if (hash[i] != decoded[decoded.length - 4 + i]) { + throw new WarpScriptException("Invalid checksum."); + } + } + + byte[] data = new byte[decoded.length - prefix.length - 4]; + System.arraycopy(decoded, prefix.length, data, 0, data.length); + + decoded = data; + } + + stack.push(decoded); + return stack; + } + + public static byte[] decode(String encoded) throws WarpScriptException { + // + // See https://tools.ietf.org/id/draft-msporny-base58-02.html + // + + int zero_counter = 0; + + while(zero_counter < encoded.length() && '1' == encoded.charAt(zero_counter)) { + zero_counter++; + } + + BigInteger n = BigInteger.ZERO; + + for (int i = zero_counter; i < encoded.length(); i++) { + char c = encoded.charAt(i); + if (c > 127 || null == TEBAHPLA[c]) { + throw new WarpScriptException("Invalid input '" + encoded.charAt(i) + "' at position " + i + "."); + } + n = n.multiply(FIFTY_EIGHT).add(TEBAHPLA[c]); + } + byte[] nbytes = n.toByteArray(); + // If the number is positive but would lead to an hexadecimal notation starting with a byte over 0x7F, + // the generated byte array starts with a leading 0, this must therefore be stripped. + + if ((byte) 0x00 == nbytes[0]) { + nbytes = Arrays.copyOfRange(nbytes, 1, nbytes.length); + } + + return Bytes.concat(new byte[zero_counter], nbytes); + } +} diff --git a/warp10/src/main/java/io/warp10/script/functions/TOB58.java b/warp10/src/main/java/io/warp10/script/functions/TOB58.java new file mode 100644 index 000000000..c312f3be6 --- /dev/null +++ b/warp10/src/main/java/io/warp10/script/functions/TOB58.java @@ -0,0 +1,113 @@ +// +// Copyright 2021 SenX S.A.S. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package io.warp10.script.functions; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; + +import org.bouncycastle.crypto.digests.SHA256Digest; + +import com.google.common.primitives.Bytes; + +import io.warp10.script.NamedWarpScriptFunction; +import io.warp10.script.WarpScriptException; +import io.warp10.script.WarpScriptStack; +import io.warp10.script.WarpScriptStackFunction; + +/** + * Encode a String into Base58 or Base58Check + */ +public class TOB58 extends NamedWarpScriptFunction implements WarpScriptStackFunction { + + private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + + private static final BigInteger FIFTY_EIGHT = new BigInteger("58"); + + private final boolean check; + + public TOB58(String name, boolean check) { + super(name); + this.check = check; + } + + @Override + public Object apply(WarpScriptStack stack) throws WarpScriptException { + Object top = stack.pop(); + + byte[] payload = null; + byte[] prefix = null; + + if (check && !(top instanceof byte[])) { + throw new WarpScriptException(getName() + " expects a byte array prefix."); + } else if (check) { + prefix = (byte[]) top; + top = stack.pop(); + } + + if (top instanceof byte[]) { + payload = (byte[]) top; + } else if (top instanceof String) { + payload = ((String) top).getBytes(StandardCharsets.UTF_8); + } else { + throw new WarpScriptException(getName() + " operates on STRING or a byte array."); + } + + if (check) { + SHA256Digest digest = new SHA256Digest(); + digest.update(prefix, 0, prefix.length); + digest.update(payload, 0, payload.length); + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + digest.reset(); + digest.update(hash, 0, hash.length); + digest.doFinal(hash, 0); + byte[] data = new byte[prefix.length + payload.length + 4]; + System.arraycopy(prefix, 0, data, 0, prefix.length); + System.arraycopy(payload, 0, data, prefix.length, payload.length); + System.arraycopy(hash, 0, data, prefix.length + payload.length, 4); + payload = data; + } + + stack.push(encode(payload)); + return stack; + } + + public static String encode(byte[] data) { + // + // See https://tools.ietf.org/id/draft-msporny-base58-01.html + // + + int zero_counter = 0; + + StringBuilder b58_encoding = new StringBuilder(); + + while(zero_counter < data.length && 0x00 == data[zero_counter]) { + b58_encoding.append("1"); + zero_counter++; + } + + BigInteger n = 0x00 != (data[0] & 0x80) ? new BigInteger(Bytes.concat(new byte[] { 0x00 }, data)) : new BigInteger(data); + + while(!BigInteger.ZERO.equals(n)) { + int r = n.mod(FIFTY_EIGHT).intValue(); + n = n.divide(FIFTY_EIGHT); + b58_encoding.insert(zero_counter, ALPHABET.charAt(r)); + } + + return b58_encoding.toString(); + } +} From a013429c3a2c17d7a0dbb8c574fa301bf3301472 Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Fri, 2 Apr 2021 13:27:26 +0200 Subject: [PATCH 2/4] Addressed PR comments --- .../io/warp10/script/functions/B58TO.java | 33 ++++++++++--------- .../io/warp10/script/functions/TOB58.java | 19 +++++++---- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/warp10/src/main/java/io/warp10/script/functions/B58TO.java b/warp10/src/main/java/io/warp10/script/functions/B58TO.java index 463842071..c8ffc286e 100644 --- a/warp10/src/main/java/io/warp10/script/functions/B58TO.java +++ b/warp10/src/main/java/io/warp10/script/functions/B58TO.java @@ -29,23 +29,19 @@ import io.warp10.script.WarpScriptStackFunction; /** - * Encode a String into Base58 or Base58Check + * Decode a Base58 or Base58Check encoded String */ public class B58TO extends NamedWarpScriptFunction implements WarpScriptStackFunction { - private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - private static final BigInteger[] TEBAHPLA; static { TEBAHPLA = new BigInteger[128]; - for (int i = 0; i < ALPHABET.length(); i++) { - TEBAHPLA[ALPHABET.charAt(i)] = BigInteger.valueOf(i); + for (int i = 0; i < TOB58.ALPHABET.length(); i++) { + TEBAHPLA[TOB58.ALPHABET.charAt(i)] = BigInteger.valueOf(i); } } - private static final BigInteger FIFTY_EIGHT = new BigInteger("58"); - private final boolean check; public B58TO(String name, boolean check) { @@ -59,9 +55,10 @@ public Object apply(WarpScriptStack stack) throws WarpScriptException { byte[] prefix = null; - if (this.check && !(top instanceof byte[])) { - throw new WarpScriptException(getName() + " expects a byte array prefix."); - } else if (this.check) { + if (this.check) { + if(!(top instanceof byte[])) { + throw new WarpScriptException(getName() + " expects a byte array prefix."); + } prefix = (byte[]) top; top = stack.pop(); } @@ -70,16 +67,22 @@ public Object apply(WarpScriptStack stack) throws WarpScriptException { throw new WarpScriptException(getName() + " operates on a STRING."); } - byte[] decoded = decode((String) top); + byte[] decoded; + + try { + decoded = decode((String) top); + } catch (WarpScriptException wse) { + throw new WarpScriptException(getName() + " encountered an error while decoding Base58.", wse); + } if (check) { if (decoded.length < prefix.length + 4) { - throw new WarpScriptException("Invalid length."); + throw new WarpScriptException(getName() + " Base58 STRING too short."); } for (int i = 0; i < prefix.length; i++) { if (i > decoded.length || decoded[i] != prefix[i]) { - throw new WarpScriptException("Invalid prefix."); + throw new WarpScriptException(getName() + " invalid prefix."); } } @@ -97,7 +100,7 @@ public Object apply(WarpScriptStack stack) throws WarpScriptException { for (int i = 0; i < 4; i++) { if (hash[i] != decoded[decoded.length - 4 + i]) { - throw new WarpScriptException("Invalid checksum."); + throw new WarpScriptException(getName() + " invalid checksum."); } } @@ -129,7 +132,7 @@ public static byte[] decode(String encoded) throws WarpScriptException { if (c > 127 || null == TEBAHPLA[c]) { throw new WarpScriptException("Invalid input '" + encoded.charAt(i) + "' at position " + i + "."); } - n = n.multiply(FIFTY_EIGHT).add(TEBAHPLA[c]); + n = n.multiply(TOB58.FIFTY_EIGHT).add(TEBAHPLA[c]); } byte[] nbytes = n.toByteArray(); // If the number is positive but would lead to an hexadecimal notation starting with a byte over 0x7F, diff --git a/warp10/src/main/java/io/warp10/script/functions/TOB58.java b/warp10/src/main/java/io/warp10/script/functions/TOB58.java index c312f3be6..3a7f90bfc 100644 --- a/warp10/src/main/java/io/warp10/script/functions/TOB58.java +++ b/warp10/src/main/java/io/warp10/script/functions/TOB58.java @@ -33,9 +33,9 @@ */ public class TOB58 extends NamedWarpScriptFunction implements WarpScriptStackFunction { - private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + public static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - private static final BigInteger FIFTY_EIGHT = new BigInteger("58"); + public static final BigInteger FIFTY_EIGHT = new BigInteger("58"); private final boolean check; @@ -51,9 +51,10 @@ public Object apply(WarpScriptStack stack) throws WarpScriptException { byte[] payload = null; byte[] prefix = null; - if (check && !(top instanceof byte[])) { - throw new WarpScriptException(getName() + " expects a byte array prefix."); - } else if (check) { + if (check) { + if (!(top instanceof byte[])) { + throw new WarpScriptException(getName() + " expects a byte array prefix."); + } prefix = (byte[]) top; top = stack.pop(); } @@ -100,7 +101,13 @@ public static String encode(byte[] data) { zero_counter++; } - BigInteger n = 0x00 != (data[0] & 0x80) ? new BigInteger(Bytes.concat(new byte[] { 0x00 }, data)) : new BigInteger(data); + // Add a leading 0x00 byte if the first byte is above 128 as otherwise + // the number would be considered a negative one + if (0x00 != (data[0] & 0x80)) { + data = Bytes.concat(new byte[] { 0x00 }, data); + } + + BigInteger n = new BigInteger(data); while(!BigInteger.ZERO.equals(n)) { int r = n.mod(FIFTY_EIGHT).intValue(); From 8e667b8ba664335acb3eea442134d2a959553ad3 Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Fri, 2 Apr 2021 13:30:31 +0200 Subject: [PATCH 3/4] Changed array size --- warp10/src/main/java/io/warp10/script/functions/B58TO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/warp10/src/main/java/io/warp10/script/functions/B58TO.java b/warp10/src/main/java/io/warp10/script/functions/B58TO.java index c8ffc286e..af4ebdad8 100644 --- a/warp10/src/main/java/io/warp10/script/functions/B58TO.java +++ b/warp10/src/main/java/io/warp10/script/functions/B58TO.java @@ -36,7 +36,7 @@ public class B58TO extends NamedWarpScriptFunction implements WarpScriptStackFun private static final BigInteger[] TEBAHPLA; static { - TEBAHPLA = new BigInteger[128]; + TEBAHPLA = new BigInteger[123]; for (int i = 0; i < TOB58.ALPHABET.length(); i++) { TEBAHPLA[TOB58.ALPHABET.charAt(i)] = BigInteger.valueOf(i); } From 3f7b18ca67432dcd7e7e5939afe93f7f88c6685b Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Fri, 2 Apr 2021 13:35:42 +0200 Subject: [PATCH 4/4] Added URL --- warp10/src/main/java/io/warp10/script/functions/TOB58.java | 1 + 1 file changed, 1 insertion(+) diff --git a/warp10/src/main/java/io/warp10/script/functions/TOB58.java b/warp10/src/main/java/io/warp10/script/functions/TOB58.java index 3a7f90bfc..9b379ce31 100644 --- a/warp10/src/main/java/io/warp10/script/functions/TOB58.java +++ b/warp10/src/main/java/io/warp10/script/functions/TOB58.java @@ -30,6 +30,7 @@ /** * Encode a String into Base58 or Base58Check + * See https://tools.ietf.org/html/draft-msporny-base58-03 */ public class TOB58 extends NamedWarpScriptFunction implements WarpScriptStackFunction {