Skip to content

Commit 59c4fbd

Browse files
authored
Merge pull request #98 from aslesarenko/scala-js
Cross-compile to Scala.js
2 parents 88fc0ee + 0c84965 commit 59c4fbd

File tree

114 files changed

+992
-595
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+992
-595
lines changed

.github/workflows/ci.yml

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ env:
1515

1616
jobs:
1717
build:
18-
name: Test and publish a snapshot
18+
name: JVM - Test and publish a snapshot
1919
env:
2020
HAS_SECRETS: ${{ secrets.SONATYPE_PASSWORD != '' }}
2121
strategy:
2222
matrix:
2323
os: [ubuntu-latest]
24-
scala: [2.12.10, 2.11.12, 2.13.1]
24+
scala: [2.13.8, 2.12.15, 2.11.12]
2525
java: [adopt@1.8]
2626
runs-on: ${{ matrix.os }}
2727
steps:
@@ -48,11 +48,60 @@ jobs:
4848
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}
4949

5050
- name: Runs tests
51-
run: sbt ++${{ matrix.scala }} test
51+
run: sbt ++${{ matrix.scala }} scryptoJVM/test
5252

5353
- name: Publish a snapshot ${{ github.ref }}
5454
if: env.HAS_SECRETS == 'true'
55-
run: sbt ++${{ matrix.scala }} publish
55+
run: sbt ++${{ matrix.scala }} scryptoJVM/publish
56+
env:
57+
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
58+
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
59+
60+
buildJs:
61+
name: JS - Test and publish a snapshot
62+
env:
63+
HAS_SECRETS: ${{ secrets.SONATYPE_PASSWORD != '' }}
64+
strategy:
65+
matrix:
66+
os: [ubuntu-latest]
67+
scala: [2.13.8, 2.12.15]
68+
java: [adopt@1.8]
69+
node-version: [16.x]
70+
runs-on: ${{ matrix.os }}
71+
steps:
72+
- uses: actions/checkout@v2
73+
74+
- name: Setup NPM
75+
uses: pnpm/action-setup@v2
76+
with:
77+
version: 7.21.0
78+
node-version: ${{ matrix.node-version }}
79+
cache: "pnpm"
80+
- run: pnpm install
81+
82+
- name: Setup Java and Scala
83+
uses: olafurpg/setup-scala@v10
84+
with:
85+
java-version: ${{ matrix.java }}
86+
87+
- name: Cache sbt
88+
uses: actions/cache@v2
89+
with:
90+
path: |
91+
~/.sbt
92+
~/.ivy2/cache
93+
~/.coursier/cache/v1
94+
~/.cache/coursier/v1
95+
~/AppData/Local/Coursier/Cache/v1
96+
~/Library/Caches/Coursier/v1
97+
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}
98+
99+
- name: Runs tests
100+
run: sbt ++${{ matrix.scala }} scryptoJS/test
101+
102+
- name: Publish a snapshot ${{ github.ref }}
103+
if: env.HAS_SECRETS == 'true'
104+
run: sbt ++${{ matrix.scala }} scryptoJS/publish
56105
env:
57106
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
58107
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Here are code examples for generating proofs and checking them. In this example
9595
* First, we create a prover and get an initial digest from it (in a real application, this value is a public constant because anyone, including verifiers, can compute it by using the same two lines of code)
9696

9797
```scala
98-
import com.google.common.primitives.Longs
98+
import scorex.utils.Longs
9999
import scorex.crypto.authds.{ADKey, ADValue}
100100
import scorex.crypto.authds.avltree.batch._
101101
import scorex.crypto.hash.{Blake2b256, Digest32}
@@ -190,4 +190,30 @@ The code is under Public Domain CC0 license means you can do anything with it. F
190190

191191
# Contributing
192192

193-
Your contributions are always welcome! Please submit a pull request or create an issue to add a new cryptographic primitives or better implementations.
193+
Clone the repository
194+
```
195+
$git clone <repository> scrypto
196+
$cd scrypto
197+
```
198+
199+
The code uses [Scalablytyped](https://scalablytyped.org/docs/readme) to generate Scala.js bindings
200+
for `@noble/hashes`. This [video](https://youtu.be/hWUAVrNj65c?t=1341) explains how the
201+
environment for ScalablyTyped is configured in this repository.
202+
203+
Before compiling the library with SBT, you need to install JS dependencies for ScalablyTyped.
204+
The configuration is in `package.json`.
205+
```
206+
$npm install
207+
added 285 packages, and audited 286 packages in 20s
208+
found 0 vulnerabilities
209+
```
210+
211+
Then you can compile the library with SBT and run tests.
212+
```
213+
$sbt
214+
sbt:scrypto> compile
215+
sbt:scrypto> test
216+
```
217+
218+
Your contributions are always welcome!
219+
Please submit a pull request or create an issue to add a new cryptographic primitives or better implementations.

benchmarks/src/main/scala/scorex.benchmarks/Base16Benchmark.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ object Base16Benchmark {
4545
.view
4646
.map(_ => Random.nextString(200).getBytes("UTF-8"))
4747
.map(Base16.encode)
48-
.force
48+
.force.toSeq
4949

5050
val xab: Seq[Array[Byte]] = (1 to 1000)
5151
.view
5252
.map(_ => Random.nextString(200).getBytes("UTF-8"))
53-
.force
53+
.force.toSeq
5454
}
5555

5656
}

benchmarks/src/main/scala/scorex.benchmarks/ByteArrayComparePerformance.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package scorex.benchmarks
22

33
import java.util.concurrent.TimeUnit
44

5-
import com.google.common.primitives.Shorts
5+
import scorex.utils.Shorts
66
import org.openjdk.jmh.annotations._
77
import org.openjdk.jmh.infra.Blackhole
88
import scorex.utils.ByteArray

benchmarks/src/main/scala/scorex.benchmarks/Helpers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package scorex.benchmarks
22

3-
import com.google.common.primitives.Longs
3+
import scorex.utils.Longs
44
import scorex.crypto.authds.{ADKey, ADValue}
55
import scorex.crypto.authds.avltree.batch.{BatchAVLProver, Insert, Operation, Remove}
66
import scorex.crypto.hash.{Blake2b256, Digest32}

build.sbt

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@ import sbt.Keys.{homepage, scalaVersion}
33
name := "scrypto"
44
description := "Cryptographic primitives for Scala"
55

6+
lazy val scala213 = "2.13.8"
67
lazy val scala212 = "2.12.15"
78
lazy val scala211 = "2.11.12"
8-
lazy val scala213 = "2.13.7"
9-
10-
crossScalaVersions := Seq(scala212, scala211, scala213)
11-
scalaVersion := scala212
129

1310
javacOptions ++=
1411
"-source" :: "1.8" ::
@@ -34,48 +31,74 @@ lazy val commonSettings = Seq(
3431
"scm:git@github.com:input-output-hk/scrypto.git"
3532
)
3633
),
34+
libraryDependencies ++= Seq(
35+
"org.rudogma" %%% "supertagged" % "2.0-RC2",
36+
"org.scorexfoundation" %%% "scorex-util" % "0.1.8-20-565873cd-SNAPSHOT",
37+
"org.scalatest" %%% "scalatest" % "3.3.0-SNAP3" % Test,
38+
"org.scalatest" %%% "scalatest-propspec" % "3.3.0-SNAP3" % Test,
39+
"org.scalatest" %%% "scalatest-shouldmatchers" % "3.3.0-SNAP3" % Test,
40+
"org.scalatestplus" %%% "scalacheck-1-15" % "3.3.0.0-SNAP3" % Test,
41+
"org.scalacheck" %%% "scalacheck" % "1.15.2" % Test
42+
),
43+
publishMavenStyle := true,
44+
publishTo := sonatypePublishToBundle.value
3745
)
3846

39-
libraryDependencies ++= Seq(
40-
"org.rudogma" %% "supertagged" % "1.5",
41-
"com.google.guava" % "guava" % "23.0",
42-
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
43-
"org.whispersystems" % "curve25519-java" % "0.5.0",
44-
"org.bouncycastle" % "bcprov-jdk15to18" % "1.66",
45-
"org.scorexfoundation" %% "scorex-util" % "0.1.8"
46-
)
47-
48-
libraryDependencies ++= Seq(
49-
"org.scalatest" %% "scalatest" % "3.1.+" % Test,
50-
"org.scalacheck" %% "scalacheck" % "1.14.+" % Test,
51-
// https://mvnrepository.com/artifact/org.scalatestplus/scalatestplus-scalacheck
52-
"org.scalatestplus" %% "scalatestplus-scalacheck" % "3.1.0.0-RC2" % Test
53-
)
54-
55-
publishMavenStyle := true
5647

57-
publishArtifact in Test := false
48+
Test / publishArtifact := false
5849

59-
publishTo := sonatypePublishToBundle.value
6050

6151
pomIncludeRepository := { _ => false }
6252

63-
lazy val scrypto = (project in file(".")).settings(commonSettings: _*)
64-
65-
lazy val benchmarks = (project in file("benchmarks"))
66-
.settings(commonSettings, name := "scrypto-benchmarks")
67-
.dependsOn(scrypto)
68-
.enablePlugins(JmhPlugin)
53+
lazy val scrypto = crossProject(JVMPlatform, JSPlatform)
54+
.in(file("."))
55+
.settings(commonSettings: _*)
56+
.jvmSettings(
57+
libraryDependencies ++= Seq(
58+
"org.bouncycastle" % "bcprov-jdk15to18" % "1.66"
59+
),
60+
scalaVersion := scala213,
61+
crossScalaVersions := Seq(scala211, scala212, scala213)
62+
)
63+
64+
lazy val scryptoJS = scrypto.js
65+
.enablePlugins(ScalaJSBundlerPlugin)
66+
.enablePlugins(ScalablyTypedConverterExternalNpmPlugin)
67+
.settings(
68+
scalaVersion := scala213,
69+
crossScalaVersions := Seq(scala212, scala213),
70+
libraryDependencies ++= Seq(
71+
"org.scala-js" %%% "scala-js-macrotask-executor" % "1.0.0",
72+
("org.scala-js" %%% "scalajs-java-securerandom" % "1.0.0").cross(CrossVersion.for3Use2_13)
73+
),
74+
Test / parallelExecution := false,
75+
// how to setup ScalablyTyped https://youtu.be/hWUAVrNj65c?t=1341
76+
externalNpm := { file(s"${baseDirectory.value}/..") },
77+
Compile / npmDependencies ++= Seq(
78+
"@noble/hashes" -> "^1.1.4"
79+
),
80+
useYarn := true
81+
)
82+
83+
lazy val benchmarks = project
84+
.in(file("benchmarks"))
85+
.dependsOn(scrypto.jvm)
86+
.settings(
87+
moduleName := "scrypto-benchmarks",
88+
crossScalaVersions := Seq(scala211, scala212, scala213),
89+
scalaVersion := scala213,
90+
)
91+
.enablePlugins(JmhPlugin)
6992

7093
credentials ++= (for {
7194
username <- Option(System.getenv().get("SONATYPE_USERNAME"))
7295
password <- Option(System.getenv().get("SONATYPE_PASSWORD"))
7396
} yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq
7497

7598
// prefix version with "-SNAPSHOT" for builds without a git tag
76-
dynverSonatypeSnapshots in ThisBuild := true
99+
ThisBuild / dynverSonatypeSnapshots := true
77100
// use "-" instead of default "+"
78-
dynverSeparator in ThisBuild := "-"
101+
ThisBuild / dynverSeparator := "-"
79102

80103
// PGP key for signing a release build published to sonatype
81104
// signing is done by sbt-pgp plugin
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package scorex.crypto.hash
2+
3+
import typings.nobleHashes.blake2Mod.BlakeOpts
4+
import typings.nobleHashes.mod.blake2b
5+
import typings.nobleHashes.sha256Mod.sha256
6+
import typings.nobleHashes.utilsMod
7+
8+
import scala.scalajs.js.typedarray.Uint8Array
9+
10+
/** JS platform specific implementation of methods.
11+
* When shared code is compiled to JS, this implementation is used.
12+
*
13+
* The JS implementation is based on type wrappers generated by ScalablyTyped for the
14+
* @noble/hashes library. (See configuration in build.sbt.)
15+
*
16+
* @see jvm/src/main/scala/scorex/crypto/hash/Platform.scala for JVM implementation
17+
*/
18+
object Platform {
19+
20+
/** Represents abstract digest from @noble.
21+
* See createBlake2bDigest, createSha256Digest methods.
22+
*/
23+
type Digest = utilsMod.Hash[_]
24+
25+
private def bytesToShorts(xs: Array[Byte]): Array[Short] =
26+
xs.map(x => (x & 0xFF).toShort)
27+
28+
private def uint8ArrayToBytes(jsShorts: Uint8Array): Array[Byte] = {
29+
jsShorts.toArray[Short].map(x => x.toByte)
30+
}
31+
32+
/** Creates an implementation of the cryptographic hash function Blakbe2b.
33+
*
34+
* @param bitSize the bit size of the digest
35+
* @return the digest implementation
36+
*/
37+
def createBlake2bDigest(bitSize: Int): Digest = {
38+
val opts = BlakeOpts().setDkLen(bitSize / 8)
39+
blake2b.create(opts)
40+
}
41+
42+
/** Creates an implementation of the cryptographic hash function SHA-256.
43+
*
44+
* @return the digest implementation
45+
*/
46+
def createSha256Digest(): Digest = {
47+
sha256.create()
48+
}
49+
50+
/** Update the message digest with a single byte.
51+
*
52+
* @param digest the digest to be updated
53+
* @param b the input byte to be entered.
54+
*/
55+
def updateDigest(digest: Digest, b: Byte): Unit = {
56+
digest.update(Uint8Array.of((b & 0xFF).toShort))
57+
}
58+
59+
/** Update the message digest with a block of bytes.
60+
*
61+
* @param digest the digest to be updated
62+
* @param bytes the byte array containing the data.
63+
* @param inOff the offset into the byte array where the data starts.
64+
* @param inLen the length of the data.
65+
*/
66+
def updateDigest(digest: Digest,
67+
bytes: Array[Byte],
68+
inOff: Int,
69+
inLen: Int): Unit = {
70+
val in = Uint8Array.of(bytesToShorts(bytes.slice(inOff, inOff + inLen)): _*)
71+
digest.update(in)
72+
}
73+
74+
/** Close the digest, producing the final digest value. The doFinal
75+
* call leaves the digest reset.
76+
* A new array is created to store the result.
77+
*
78+
* @param digest the digest to be finalized
79+
*/
80+
def doFinalDigest(digest: Digest): Array[Byte] = {
81+
val res = digest.digest()
82+
uint8ArrayToBytes(res)
83+
}
84+
}

0 commit comments

Comments
 (0)