From 8954b2712790725ac74f4bec49c9dbab07b7d96f Mon Sep 17 00:00:00 2001 From: Essbante Date: Wed, 29 Dec 2021 20:35:37 -0600 Subject: [PATCH] Initial commit --- LICENSE | 214 +----------- build.gradle.kts | 68 ++++ gradle.properties | 1 + gradlew | 185 +++++++++++ gradlew.bat | 89 +++++ settings.gradle.kts | 2 + .../com/rootsid/wal/library/Constant.kt | 20 ++ .../kotlin/com/rootsid/wal/library/DLT.kt | 304 ++++++++++++++++++ .../kotlin/com/rootsid/wal/library/Model.kt | 110 +++++++ src/main/kotlin/com/rootsid/wal/library/QR.kt | 100 ++++++ .../kotlin/com/rootsid/wal/library/Storage.kt | 109 +++++++ .../com/rootsid/wal/library/DLTKtTest.kt | 10 + 12 files changed, 1011 insertions(+), 201 deletions(-) create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts create mode 100644 src/main/kotlin/com/rootsid/wal/library/Constant.kt create mode 100644 src/main/kotlin/com/rootsid/wal/library/DLT.kt create mode 100644 src/main/kotlin/com/rootsid/wal/library/Model.kt create mode 100644 src/main/kotlin/com/rootsid/wal/library/QR.kt create mode 100644 src/main/kotlin/com/rootsid/wal/library/Storage.kt create mode 100644 src/test/kotlin/com/rootsid/wal/library/DLTKtTest.kt diff --git a/LICENSE b/LICENSE index 261eeb9..7abe6d9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,13 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. + Copyright 2021 Esteban Garcia (esteban.garcia@gmail.com) + + 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. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..c8c6844 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,68 @@ +plugins { + kotlin("jvm") version "1.6.10" + id("maven-publish") +} + +group = "com.rootsid.wal" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() + google() + // Required to resolve com.soywiz.korlibs.krypto:krypto-jvm:2.0.6 + maven("https://plugins.gradle.org/m2/") + maven { + url = uri("https://maven.pkg.github.com/input-output-hk/better-parse") + credentials { + username = System.getenv("PRISM_SDK_USER") + password = System.getenv("PRISM_SDK_PASSWORD") + } + } +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.10") + + implementation("org.litote.kmongo:kmongo:4.4.0") + + // needed for cryptography primitives implementation + implementation("io.iohk.atala:prism-crypto:1.2.0") + implementation("io.iohk.atala:prism-identity:1.2.0") + implementation("io.iohk.atala:prism-credentials:1.2.0") + implementation("io.iohk.atala:prism-api:1.2.0") + + // Fixes a build issue + implementation("com.soywiz.korlibs.krypto:krypto-jvm:2.0.6") + + // QR Code + implementation("org.boofcv:boofcv-core:0.39.1") + implementation("org.boofcv:boofcv-swing:0.39.1") + implementation("org.boofcv:boofcv-kotlin:0.39.1") + implementation("org.boofcv:boofcv-WebcamCapture:0.39.1") + + implementation("org.junit.jupiter:junit-jupiter:5.8.2") +} + +publishing { + publications { + register("gpr") { + from(components["java"]) + artifact(tasks["kotlinSourcesJar"]) + } + } + + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/roots-id/wal-library") + credentials { + username = System.getenv("ROOTS-ID_USER") + password = System.getenv("ROOTS-ID_PASSWORD") + } + } + } +} + +tasks.withType().configureEach { + kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..744e882 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..33f4535 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "wal-library" + diff --git a/src/main/kotlin/com/rootsid/wal/library/Constant.kt b/src/main/kotlin/com/rootsid/wal/library/Constant.kt new file mode 100644 index 0000000..2f52969 --- /dev/null +++ b/src/main/kotlin/com/rootsid/wal/library/Constant.kt @@ -0,0 +1,20 @@ +package com.rootsid.wal.library + +import io.iohk.atala.prism.protos.GrpcOptions + +/** + * Constant + * + * @constructor Create empty Constant + */ +object Constant { + const val MNEMONIC_SEPARATOR = "," + const val TESTNET_URL = "https://explorer.cardano-testnet.iohkdev.io/en/transaction?id=" +} + +// TODO: get values from environment or config file +class EnvVar { + companion object { + val grpcOptions = GrpcOptions("https", "ppp.atalaprism.io", 50053) + } +} diff --git a/src/main/kotlin/com/rootsid/wal/library/DLT.kt b/src/main/kotlin/com/rootsid/wal/library/DLT.kt new file mode 100644 index 0000000..b0e7f18 --- /dev/null +++ b/src/main/kotlin/com/rootsid/wal/library/DLT.kt @@ -0,0 +1,304 @@ +package com.rootsid.wal.library + +import io.iohk.atala.prism.api.CredentialClaim +import io.iohk.atala.prism.api.KeyGenerator +import io.iohk.atala.prism.api.VerificationResult +import io.iohk.atala.prism.api.models.AtalaOperationId +import io.iohk.atala.prism.api.models.AtalaOperationStatus +import io.iohk.atala.prism.api.node.NodeAuthApiImpl +import io.iohk.atala.prism.api.node.NodePayloadGenerator +import io.iohk.atala.prism.api.node.NodePublicApi +import io.iohk.atala.prism.common.PrismSdkInternal +import io.iohk.atala.prism.credentials.json.JsonBasedCredential +import io.iohk.atala.prism.crypto.MerkleInclusionProof +import io.iohk.atala.prism.crypto.derivation.KeyDerivation +import io.iohk.atala.prism.crypto.derivation.MnemonicCode +import io.iohk.atala.prism.crypto.keys.ECKeyPair +import io.iohk.atala.prism.identity.LongFormPrismDid +import io.iohk.atala.prism.identity.PrismDid +import io.iohk.atala.prism.identity.PrismDidDataModel +import io.iohk.atala.prism.identity.PrismKeyType +import io.iohk.atala.prism.protos.GetOperationInfoRequest +import io.iohk.atala.prism.protos.GrpcClient +import io.iohk.atala.prism.protos.NodeServiceCoroutine +import kotlinx.coroutines.runBlocking +import pbandk.ByteArr + +/** + * Transaction id + * + * @param oid + * @return + */ +@OptIn(PrismSdkInternal::class) +private fun transactionId(oid: AtalaOperationId): String { + val node = NodeServiceCoroutine.Client(GrpcClient(EnvVar.grpcOptions)) + val response = runBlocking { + node.GetOperationInfo(GetOperationInfoRequest(ByteArr(oid.value()))) + } + return response.transactionId +} + +/** + * Wait until confirmed + * + * @param nodePublicApi + * @param operationId + */ +private fun waitUntilConfirmed(nodePublicApi: NodePublicApi, operationId: AtalaOperationId) { + var tid = "" + var status = runBlocking { + nodePublicApi.getOperationStatus(operationId) + } + while (status != AtalaOperationStatus.CONFIRMED_AND_APPLIED && + status != AtalaOperationStatus.CONFIRMED_AND_REJECTED + ) { + if (status == AtalaOperationStatus.AWAIT_CONFIRMATION && tid.isEmpty()) { + tid = transactionId(operationId) + println("Track the transaction in:\n- ${Constant.TESTNET_URL}$tid\n") + } + println("Current operation status: ${AtalaOperationStatus.asString(status)}\n") + Thread.sleep(10000) + status = runBlocking { + nodePublicApi.getOperationStatus(operationId) + } + } +} + +/** + * Derive key pair + * + * @param keyPaths + * @param seed + * @param keyId + * @return + */ +private fun deriveKeyPair(keyPaths: MutableList, seed: ByteArray, keyId: String): ECKeyPair { + val keyPathList = keyPaths.filter { it.keyId == keyId } + if (keyPathList.isNotEmpty()) { + val keyPath = keyPathList[0] + return KeyGenerator.deriveKeyFromFullPath(seed, keyPath.didIdx, keyPath.keyType, keyPath.keyIdx) + } else { + throw NoSuchElementException("Key ID '$keyId' not found.") + } +} + +/** + * New wallet + * + * @param name + * @param mnemonic + * @param passphrase + * @return + */ +fun newWallet(name: String, mnemonic: String, passphrase: String): Wallet { + return if (mnemonic.isBlank()) { + Wallet(name, KeyDerivation.randomMnemonicCode().words, passphrase) + } else { + try { + val mnemonicList = mnemonic.split(Constant.MNEMONIC_SEPARATOR) + .map { it.trim() } + KeyDerivation.binarySeed(MnemonicCode(mnemonicList), passphrase) + Wallet(name, mnemonicList, passphrase) + } catch (e: Exception) { + throw IllegalArgumentException("Invalid mnemonic phrase") + } + } +} + +/** + * New did + * + * @param wallet + * @param didAlias + * @param issuer + * @return + */ +fun newDid(wallet: Wallet, didAlias: String, issuer: Boolean): Wallet { + // To keep DID index sequential + val didIdx = wallet.dids.size + val keyPaths = mutableListOf() + val masterKeyPathData = KeyPath(PrismDid.DEFAULT_MASTER_KEY_ID, didIdx, PrismKeyType.MASTER_KEY, 0) + + val seed = KeyDerivation.binarySeed(MnemonicCode(wallet.mnemonic), wallet.passphrase) + val masterKeyPair = KeyGenerator.deriveKeyFromFullPath( + seed, masterKeyPathData.didIdx, masterKeyPathData.keyType, masterKeyPathData.keyIdx + ) + keyPaths.add(masterKeyPathData) + + val unpublishedDid = if (issuer) { + val issuingKeyPathData = KeyPath(PrismDid.DEFAULT_ISSUING_KEY_ID, didIdx, PrismKeyType.ISSUING_KEY, 0) + val revocationKeyPathData = KeyPath(PrismDid.DEFAULT_REVOCATION_KEY_ID, didIdx, PrismKeyType.REVOCATION_KEY, 0) + + keyPaths.add(issuingKeyPathData) + keyPaths.add(revocationKeyPathData) + + val issuingKeyPair = KeyGenerator.deriveKeyFromFullPath( + seed, issuingKeyPathData.didIdx, issuingKeyPathData.keyType, issuingKeyPathData.keyIdx + ) + val revocationKeyPair = KeyGenerator.deriveKeyFromFullPath( + seed, revocationKeyPathData.didIdx, revocationKeyPathData.keyType, revocationKeyPathData.keyIdx + ) + PrismDid.buildExperimentalLongFormFromKeys( + masterKeyPair.publicKey, issuingKeyPair.publicKey, revocationKeyPair.publicKey + ) + } else { + PrismDid.buildLongFormFromMasterPublicKey(masterKeyPair.publicKey) + } + wallet.dids.add( + DID(didAlias, didIdx, unpublishedDid.asCanonical().did.toString(), unpublishedDid.did.toString(), "", keyPaths) + ) + return wallet +} + +/** + * Get did document + * + * @param wallet + * @param didAlias + * @return + */ +fun getDidDocument(wallet: Wallet, didAlias: String): PrismDidDataModel { + val didList = wallet.dids.filter { it.alias == didAlias } + if (didList.isNotEmpty()) { + val did = didList[0] + val nodeAuthApi = NodeAuthApiImpl(EnvVar.grpcOptions) + val prismDid = try { + PrismDid.fromString(did.uriLongForm) + } catch (e: Exception) { + throw Exception("not a Prism DID: $did") + } + println("trying to retrieve document for $did\n") + try { + val model = runBlocking { nodeAuthApi.getDidDocument(prismDid) } + println("Public Keys size: ${model.publicKeys.size}\n") + println("Model: ${model.didDataModel}\n") + return model + } catch (e: Exception) { + throw NoSuchElementException("DID '$didAlias' not found.") + } + } else { + throw NoSuchElementException("DID '$didAlias' not found.") + } +} + +/** + * Publish did + * + * @param wallet + * @param didAlias + * @return + */ +fun publishDid(wallet: Wallet, didAlias: String): Wallet { + val didList = wallet.dids.filter { it.alias == didAlias } + if (didList.isNotEmpty()) { + val did = didList[0] + val nodeAuthApi = NodeAuthApiImpl(EnvVar.grpcOptions) + val prismDid = PrismDid.fromString(did.uriLongForm) + // Key pairs to get private keys + val seed = KeyDerivation.binarySeed(MnemonicCode(wallet.mnemonic), wallet.passphrase) + val masterKeyPair = deriveKeyPair(did.keyPaths, seed, PrismDid.DEFAULT_MASTER_KEY_ID) + val issuingKeyPair = deriveKeyPair(did.keyPaths, seed, PrismDid.DEFAULT_ISSUING_KEY_ID) + val revocationKeyPair = deriveKeyPair(did.keyPaths, seed, PrismDid.DEFAULT_REVOCATION_KEY_ID) + + val nodePayloadGenerator = NodePayloadGenerator( + prismDid as LongFormPrismDid, + mapOf( + PrismDid.DEFAULT_MASTER_KEY_ID to masterKeyPair.privateKey, + PrismDid.DEFAULT_ISSUING_KEY_ID to issuingKeyPair.privateKey, + PrismDid.DEFAULT_REVOCATION_KEY_ID to revocationKeyPair.privateKey + ) + ) + val createDidInfo = nodePayloadGenerator.createDid() + + val createDidOperationId = runBlocking { + nodeAuthApi.createDid( + createDidInfo.payload, + prismDid, + PrismDid.DEFAULT_MASTER_KEY_ID + ) + } + waitUntilConfirmed(nodeAuthApi, createDidOperationId) + + val status = runBlocking { nodeAuthApi.getOperationStatus(createDidOperationId) } + require(status == AtalaOperationStatus.CONFIRMED_AND_APPLIED) { + "expected publishing to be applied" + } + did.hash = createDidInfo.operationHash.hexValue + println("DID published") + return wallet + } else { + throw NoSuchElementException("DID alias '$didAlias' not found.") + } +} + +/** + * Issue credential + * + * @param wallet + * @param didAlias + * @param credential + * @return + */ +fun issueCredential(wallet: Wallet, didAlias: String, credential: Credential): Pair { + val didList = wallet.dids.filter { it.alias == didAlias } + if (didList.isNotEmpty()) { + val issuerDid = didList[0] + val nodeAuthApi = NodeAuthApiImpl(EnvVar.grpcOptions) + val claims = mutableListOf() + // Key pairs to get private keys + val seed = KeyDerivation.binarySeed(MnemonicCode(wallet.mnemonic), wallet.passphrase) + val issuingKeyPair = deriveKeyPair(issuerDid.keyPaths, seed, PrismDid.DEFAULT_ISSUING_KEY_ID) + + claims.add(credential.claim.toCredentialClaim()) + + val nodePayloadGenerator = NodePayloadGenerator( + PrismDid.fromString(issuerDid.uriLongForm) as LongFormPrismDid, + mapOf(PrismDid.DEFAULT_ISSUING_KEY_ID to issuingKeyPair.privateKey) + ) + val credentialsInfo = nodePayloadGenerator.issueCredentials( + PrismDid.DEFAULT_ISSUING_KEY_ID, + claims.toTypedArray() + ) + val info = credentialsInfo.credentialsAndProofs[0] + + credential.verifiedCredential = VerifiedCredential( + info.signedCredential.canonicalForm, + info.inclusionProof.encode() + ) + val issueCredentialsOperationId = runBlocking { + nodeAuthApi.issueCredentials( + credentialsInfo.payload, + PrismDid.fromString(issuerDid.uriCanonical).asCanonical(), + PrismDid.DEFAULT_ISSUING_KEY_ID, + credentialsInfo.merkleRoot + ) + } + waitUntilConfirmed(nodeAuthApi, issueCredentialsOperationId) + + val status = runBlocking { nodeAuthApi.getOperationStatus(issueCredentialsOperationId) } + require(status == AtalaOperationStatus.CONFIRMED_AND_APPLIED) { + "expected credentials to be issued" + } + issuerDid.hash = credentialsInfo.operationHash.hexValue + return Pair(wallet, credential) + } else { + throw NoSuchElementException("DID alias '$didAlias' not found.") + } +} + +/** + * Verify credential + * + * @param credential + * @return + */ +fun verifyCredential(credential: Credential): VerificationResult { + val nodeAuthApi = NodeAuthApiImpl(EnvVar.grpcOptions) + val signed = JsonBasedCredential.fromString(credential.verifiedCredential.encodedSignedCredential) + val proof = MerkleInclusionProof.decode(credential.verifiedCredential.proof) + + return runBlocking { + nodeAuthApi.verify(signed, proof) + } +} diff --git a/src/main/kotlin/com/rootsid/wal/library/Model.kt b/src/main/kotlin/com/rootsid/wal/library/Model.kt new file mode 100644 index 0000000..55b97cd --- /dev/null +++ b/src/main/kotlin/com/rootsid/wal/library/Model.kt @@ -0,0 +1,110 @@ +package com.rootsid.wal.library + +import io.iohk.atala.prism.api.CredentialClaim +import io.iohk.atala.prism.identity.PrismDid +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +/** + * Wallet + * + * @property _id + * @property mnemonic + * @property passphrase + * @property dids + * @constructor Create empty Wallet + */ +data class Wallet( + val _id: String, // name + val mnemonic: List, + val passphrase: String, + var dids: MutableList = mutableListOf() +) + +/** + * D i d + * + * @property alias + * @property didIdx + * @property uriCanonical + * @property uriLongForm + * @property hash + * @property keyPaths + * @constructor Create empty D i d + */ +data class DID( + val alias: String, + val didIdx: Int, + val uriCanonical: String, + val uriLongForm: String, + var hash: String = "", + var keyPaths: MutableList = mutableListOf() +) + +/** + * Key path + * + * @property keyId + * @property didIdx + * @property keyType + * @property keyIdx + * @constructor Create empty Key path + */ +data class KeyPath( + val keyId: String, + val didIdx: Int, + val keyType: Int, + val keyIdx: Int +) + +/** + * Verified credential + * + * @property encodedSignedCredential + * @property proof + * @constructor Create empty Verified credential + */ +data class VerifiedCredential( + val encodedSignedCredential: String, + val proof: String +) + +/** + * Claim + * + * @property subjectDid + * @property content + * @constructor Create empty Claim + */ +data class Claim( + val subjectDid: String, + val content: String +) + +/** + * To credential claim + * + * Convert a Claim to PRISM CredentialClaim + */ +@OptIn(ExperimentalSerializationApi::class) +fun Claim.toCredentialClaim() = CredentialClaim( + PrismDid.fromString(this.subjectDid), + Json.decodeFromString(this.content) +) + +/** + * Credential + * + * @property _id + * @property claim + * @property verifiedCredential + * @constructor Create empty Credential + */ +data class Credential( + val _id: String, + // Plain json claim + val claim: Claim, + // Signed VC and proof + var verifiedCredential: VerifiedCredential +) diff --git a/src/main/kotlin/com/rootsid/wal/library/QR.kt b/src/main/kotlin/com/rootsid/wal/library/QR.kt new file mode 100644 index 0000000..03dfd40 --- /dev/null +++ b/src/main/kotlin/com/rootsid/wal/library/QR.kt @@ -0,0 +1,100 @@ +package com.rootsid.wal.library + +import boofcv.alg.fiducial.qrcode.QrCodeEncoder +import boofcv.alg.fiducial.qrcode.QrCodeGeneratorImage +import boofcv.factory.fiducial.FactoryFiducial +import boofcv.gui.image.ImagePanel +import boofcv.gui.image.ShowImages +import boofcv.io.image.UtilImageIO +import boofcv.io.webcamcapture.UtilWebcamCapture +import boofcv.kotlin.asBufferedImage +import boofcv.kotlin.asGrayU8 +import boofcv.struct.image.GrayU8 +import java.time.LocalDateTime + +/** + * Show QR image + * + * @param message Message to embed on the QR code + */ +fun showQRImage(message: String) { + val qr = QrCodeEncoder().addAutomatic(message).fixate() + val generator = QrCodeGeneratorImage(15).render(qr) + ShowImages.showWindow(generator.gray.asBufferedImage(), "QR Code", true) +} + +/** + * Save QR image + * + * @param message Message to embed on the QR code + * @param filename Output QR filename + */ +fun saveQRImage(message: String, filename: String) { + val qr = QrCodeEncoder().addAutomatic(message).fixate() + val generator = QrCodeGeneratorImage(15).render(qr) + UtilImageIO.saveImage(generator.gray.asBufferedImage(), filename) +} + +/** + * Web cam QR scan + * + * @param seconds Amount of seconds to wait for a scan (timeout) + * @return Message retrieved from the scaned QR image + */ +fun webCamQRScan(seconds: Long): String { + // Open a webcam and create the detector + val webcam = UtilWebcamCapture.openDefault(800, 600) + val detector = FactoryFiducial.qrcode(null, GrayU8::class.java) + // Create the panel used to display the image and + val gui = ImagePanel() + gui.preferredSize = webcam.viewSize + val frame = ShowImages.showWindow(gui, "Gradient", true) + val stopAt = LocalDateTime.now().plusSeconds(seconds) + var message = "" + var found = false + + while (!found) { + if (LocalDateTime.now() > stopAt) { + frame.isVisible = false + frame.dispose() + throw Exception("Scan QR timeout after $seconds seconds.") + } + // Load the image from the webcam + val image = webcam.image ?: break + // Convert to gray scale and detect QR codes inside + detector.process(image.asGrayU8()) + for (qr in detector.detections) { + message = qr.message + found = true + } + gui.setImageRepaint(image) + } + frame.isVisible = false + frame.dispose() + return message +} + +// TODO: Implement read qr image file +// fun readQRfile() { +// // Opens a dialog and let's you select a directory +// val directory = BoofSwingUtil.openFileChooser("QR Disk Scanning",BoofSwingUtil.FileTypes.DIRECTORIES) ?: return +// +// // Create the scanner class +// val detector = FactoryFiducial.qrcode(null,GrayU8::class.java) +// +// // Walk through the path recursively, finding all image files, load them, scan for QR codes, add results to a map +// val imageToMessages = mutableMapOf>() +// val elapsedTime = measureTimeMillis { +// directory.walk().filter {UtilImageIO.isImage(it)}.forEach { f -> +// val image = f.absoluteFile.loadImage(ImageType.SB_U8) +// detector.process(image) +// imageToMessages[f.absolutePath] = detector.detections.map { it.message } +// println(f.name) // print so we can see something is happening +// } +// } +// +// // Print a results summary +// val totalMessages = imageToMessages.values.sumBy{it.size} +// println("\nFound ${imageToMessages.size} images with $totalMessages messages averaging %.2f img/s". +// format(imageToMessages.size/(elapsedTime*1e-3))) +// } diff --git a/src/main/kotlin/com/rootsid/wal/library/Storage.kt b/src/main/kotlin/com/rootsid/wal/library/Storage.kt new file mode 100644 index 0000000..e56cd0b --- /dev/null +++ b/src/main/kotlin/com/rootsid/wal/library/Storage.kt @@ -0,0 +1,109 @@ +package com.rootsid.wal.library + +import com.mongodb.client.MongoDatabase +import org.litote.kmongo.KMongo +import org.litote.kmongo.MongoOperator +import org.litote.kmongo.eq +import org.litote.kmongo.findOne +import org.litote.kmongo.getCollection +import org.litote.kmongo.updateOne + +/** + * Open db + * TODO: Add Parameters for Client configuration. + * @return + */ +fun openDb(): MongoDatabase { + val client = KMongo.createClient() // get com.mongodb.MongoClient new instance + return client.getDatabase("wal") // normal java driver usage +} + +/** + * Insert wallet + * + * @param db + * @param wallet + * @return + */ +fun insertWallet(db: MongoDatabase, wallet: Wallet): Boolean { + val collection = db.getCollection("wallet") + val result = collection.insertOne(wallet) + return result.wasAcknowledged() +} + +/** + * Insert credential + * + * @param db + * @param credential + * @return + */ +fun insertCredential(db: MongoDatabase, credential: Credential): Boolean { + val collection = db.getCollection("credential") + val result = collection.insertOne(credential) + return result.wasAcknowledged() +} + +/** + * Find wallet + * + * @param db + * @param walletName + * @return + */ +fun findWallet(db: MongoDatabase, walletName: String): Wallet { + val collection = db.getCollection("wallet") + return collection.findOne(Wallet::_id eq walletName) + ?: throw NoSuchElementException("Wallet '$walletName' not found.") +} + +/** + * Find credential + * + * @param db + * @param credentialAlias + * @return + */ +fun findCredential(db: MongoDatabase, credentialAlias: String): Credential { + val collection = db.getCollection("credential") + return collection.findOne(Wallet::_id eq credentialAlias) + ?: throw NoSuchElementException("Credential '$credentialAlias' not found.") +} + +/** + * Find wallets + * + * @param db + * @return + */ +fun findWallets(db: MongoDatabase): List { + val collection = db.getCollection("wallet") + return collection.find().toList() +} + +/** + * Did alias exists + * + * @param db + * @param walletName + * @param didAlias + * @return + */ +fun didAliasExists(db: MongoDatabase, walletName: String, didAlias: String): Boolean { + val collection = db.getCollection("wallet") + val wallet = collection.findOne("{_id:'$walletName','dids':{${MongoOperator.elemMatch}: {'alias':'$didAlias'}}}") + return wallet != null +} + +/** + * Update wallet + * + * @param db + * @param wallet + * @return + */ +fun updateWallet(db: MongoDatabase, wallet: Wallet): Boolean { + val collection = db.getCollection("wallet") + val result = collection.updateOne(wallet) + return result.wasAcknowledged() +} diff --git a/src/test/kotlin/com/rootsid/wal/library/DLTKtTest.kt b/src/test/kotlin/com/rootsid/wal/library/DLTKtTest.kt new file mode 100644 index 0000000..e244574 --- /dev/null +++ b/src/test/kotlin/com/rootsid/wal/library/DLTKtTest.kt @@ -0,0 +1,10 @@ +package com.rootsid.wal.library + +import org.junit.jupiter.api.Assertions.* + +internal class DLTKtTest { + + @org.junit.jupiter.api.Test + fun newWallet() { + } +}