├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ └── groovy │ │ └── io │ │ └── ark │ │ └── core │ │ ├── Verification.groovy │ │ ├── Slot.groovy │ │ ├── Account.groovy │ │ ├── Network.groovy │ │ ├── Block.groovy │ │ ├── Crypto.groovy │ │ ├── Peer.groovy │ │ └── Transaction.groovy └── test │ └── groovy │ ├── NetworkTest.groovy │ └── CryptoTest.groovy ├── settings.gradle ├── CONTRIBUTING.md ├── LICENSE ├── example └── Example.groovy ├── gradlew.bat ├── README.md ├── Documentation.md └── gradlew /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | .tags 4 | *.iml 5 | .idea/ 6 | out/ 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArkEcosystemArchive/ark-java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/groovy/io/ark/core/Verification.groovy: -------------------------------------------------------------------------------- 1 | package io.ark.core 2 | 3 | class Verification { 4 | List errors = [] 5 | 6 | public String toString(){ 7 | errors.length > 0 ? errors.join(", ") : "Verified" 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 23 21:01:32 EDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip 7 | -------------------------------------------------------------------------------- /src/main/groovy/io/ark/core/Slot.groovy: -------------------------------------------------------------------------------- 1 | package io.ark.core 2 | 3 | 4 | import com.google.common.io.BaseEncoding 5 | import java.text.SimpleDateFormat 6 | 7 | class Slot { 8 | 9 | static Date beginEpoch 10 | 11 | static { 12 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") 13 | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) 14 | beginEpoch = dateFormat.parse("2017-03-21 13:00:00") 15 | } 16 | 17 | static int getTime(Date date){ 18 | if(!date) 19 | date = new Date() 20 | 21 | (date.time - beginEpoch.time)/1000 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This settings file was auto generated by the Gradle buildInit task 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * In a single project build this file can be empty or even removed. 6 | * 7 | * Detailed information about configuring a multi-project build in Gradle can be found 8 | * in the user guide at http://gradle.org/docs/2.2.1/userguide/multi_project_builds.html 9 | */ 10 | 11 | /* 12 | // To declare projects as part of a multi-project build use the 'include' method 13 | include 'shared' 14 | include 'api' 15 | include 'services:webservice' 16 | */ 17 | 18 | rootProject.name = 'ark-java' 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # BOUNTY Program 2 | ARK has a bounty program for all accepted PR (Pull Requests) for this repository 3 | 4 | More information can be found at https://blog.ark.io/ark-github-development-bounty-113806ae9ffe 5 | 6 | Before pushing PR, please [jump in our slack #development](https://ark.io/slack) channel in order to discuss your contributions or to connect with other ARKvelopers. 7 | 8 | # Guidelines 9 | - pickup any of the existing issues or if you find an issue make a PR, 10 | - only one PR reward will be awarded per issue it fixes, 11 | - solving an open issue will increase your chances to be picked up as any of the monthly bounty winners. 12 | 13 | # Accepted PR 14 | - increase general code quality, 15 | - add meaningfull tests, 16 | - correct bug, 17 | - add new features, 18 | - improve documentation, 19 | - create something new for ARK. 20 | -------------------------------------------------------------------------------- /src/main/groovy/io/ark/core/Account.groovy: -------------------------------------------------------------------------------- 1 | package io.ark.core 2 | 3 | class Account extends Object { 4 | String address 5 | String publicKey 6 | long balance 7 | String username 8 | long vote 9 | List votes 10 | int rate 11 | 12 | Account(String address) 13 | { 14 | this.address = address 15 | } 16 | 17 | public boolean applyTransaction(transaction){ 18 | balance -= transaction.amount + transaction.fee 19 | balance > -1 20 | } 21 | 22 | public boolean undoTransaction(transaction){ 23 | balance += transaction.amount + transaction.fee 24 | balance > -1 25 | } 26 | 27 | public Verification verifyTransaction(transaction){ 28 | Verification v = new Verification() 29 | if(balance < transaction.amount + transaction.fee) 30 | v.error.push "Account ${address} does not have enough balance: ${balance}" 31 | // TODO: many things 32 | 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ark Ecosystem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/Example.groovy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | @GrabResolver(name='ark-java', root='https://dl.bintray.com/arkecosystem/ark-java/') 3 | @Grab('io.ark.lite:client:0.2') 4 | import io.ark.core.* 5 | 6 | // grab mainnet network settings and warm it up 7 | def mainnet = Network.Mainnet 8 | mainnet.warmup() 9 | 10 | //provision I/O with console 11 | def console = System.console() 12 | sc = new Scanner(console.reader) 13 | 14 | def ask(sentence){ 15 | println sentence 16 | def out = sc.nextLine() 17 | println "" 18 | out 19 | } 20 | 21 | try { 22 | String address = ask "Hi there! Which address are you sending to?" 23 | long amount = ask("Alright! How much ARK do you want to send to $address?") as long 24 | long satoshiamount = amount * 100000000l 25 | 26 | println "Got it! Please put your passphrase here:" 27 | String passphrase = String.valueOf(console.readPassword("[%s]", "Passphrase")) 28 | println "" 29 | 30 | // create a transaction 31 | def transaction = Transaction.createTransaction(address, satoshiamount, null, passphrase) 32 | 33 | def answer = ask "Everything in order now.\nDo you want to send $amount ARK to $address now? y/N" 34 | 35 | if(answer == "y" || answer == "Y"){ 36 | // Post transaction to a peer 37 | result = mainnet.randomPeer << transaction 38 | 39 | if(result.success){ 40 | println "Transaction sent with success!" 41 | // broadcast transaction to several peers on mainnet 42 | mainnet << transaction 43 | } 44 | else{ 45 | println "Transaction not accepted by network:" 46 | println result 47 | } 48 | } 49 | else { 50 | println "Aborted." 51 | } 52 | } catch(error) { 53 | println "Error: ${error}" 54 | println "Aborted." 55 | } 56 | println "" 57 | println "Good bye and see you soon!" 58 | System.exit(0) 59 | -------------------------------------------------------------------------------- /src/main/groovy/io/ark/core/Network.groovy: -------------------------------------------------------------------------------- 1 | package io.ark.core 2 | 3 | 4 | class Network extends Object { 5 | String nethash 6 | String name 7 | int port 8 | byte prefix 9 | String version = "1.0.1" 10 | int broadcastMax = 10 11 | List peerseed = [] 12 | List peers = [] 13 | 14 | static Random random = new Random() 15 | static Network Mainnet = new Network( 16 | nethash: '6e84d08bd299ed97c212c886c98a57e36545c8f5d645ca7eeae63a8bd62d8988', 17 | prefix: 0x17, 18 | port: 4001, 19 | name: 'mainnet', 20 | peerseed: [ 21 | "5.39.9.240:4001", 22 | "5.39.9.241:4001", 23 | "5.39.9.242:4001", 24 | "5.39.9.243:4001", 25 | "5.39.9.244:4001", 26 | "5.39.9.245:4001", 27 | "5.39.9.246:4001", 28 | "5.39.9.247:4001", 29 | "5.39.9.248:4001", 30 | "5.39.9.249:4001", 31 | "5.39.9.250:4001", 32 | "5.39.9.251:4001", 33 | "5.39.9.252:4001", 34 | "5.39.9.253:4001", 35 | "5.39.9.254:4001", 36 | "5.39.9.255:4001" 37 | ] 38 | ) 39 | 40 | static Network Devnet = new Network( 41 | nethash: '578e820911f24e039733b45e4882b73e301f813a0d2c31330dafda84534ffa23', 42 | prefix: 0x1e, 43 | port: 4002, 44 | name: 'devnet', 45 | peerseed: [ 46 | "167.114.29.52:4002", 47 | "167.114.29.53:4002", 48 | "167.114.29.55:4002" 49 | ]) 50 | 51 | public Map getHeaders(){ 52 | [nethash:nethash, version:version, port:port] 53 | } 54 | 55 | public boolean warmup(){ 56 | if(peers.size()>0) return false 57 | peerseed.each { 58 | peers << Peer.create(it, this.headers) 59 | } 60 | return true 61 | } 62 | 63 | // broadcast to many nodes 64 | public int leftShift(Transaction transaction){ 65 | [1..broadcastMax].each { 66 | getRandomPeer() << transaction 67 | } 68 | return broadcastMax 69 | } 70 | 71 | public Peer getRandomPeer(){ 72 | peers[random.nextInt(peers.size())] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/groovy/io/ark/core/Block.groovy: -------------------------------------------------------------------------------- 1 | package io.ark.core 2 | 3 | import java.nio.* 4 | import groovy.transform.* 5 | import com.google.common.io.BaseEncoding 6 | import org.bitcoinj.core.* 7 | import org.bitcoinj.crypto.* 8 | 9 | @Canonical 10 | class Block extends Object { 11 | byte version = 0 12 | int height 13 | String previousBlock 14 | long totalAmount 15 | long totalFee 16 | long reward 17 | String payloadHash 18 | int timestamp 19 | int numberOfTransactions 20 | int payloadLength 21 | int size 22 | String generatorPublicKey 23 | List transactions = [] 24 | List transactionIds = [] 25 | String blockSignature 26 | String id 27 | 28 | public byte[] getBytes(boolean includeSignature = false){ 29 | ByteBuffer buffer = ByteBuffer.allocate(1000) 30 | buffer.order(ByteOrder.LITTLE_ENDIAN) 31 | 32 | buffer.put version 33 | buffer.putInt timestamp 34 | buffer.putInt height 35 | buffer.put new BigInteger(previousBlock).bytes 36 | buffer.putInt numberOfTransactions 37 | buffer.putLong totalAmount 38 | buffer.putLong totalFee 39 | buffer.putLong reward 40 | buffer.putInt payloadLength 41 | if(!payloadHash){ 42 | //TODO: create payloadhash from transactions 43 | } 44 | buffer.put BaseEncoding.base16().lowerCase().decode(payloadHash) 45 | buffer.put BaseEncoding.base16().lowerCase().decode(generatorPublicKey) 46 | 47 | if(includeSignature){ 48 | buffer.put BaseEncoding.base16().lowerCase().decode(blockSignature) 49 | } 50 | 51 | byte[] outBuffer = new byte[buffer.position()] 52 | buffer.rewind() 53 | buffer.get(outBuffer) 54 | return outBuffer 55 | } 56 | 57 | public String sign(String passphrase){ 58 | blockSignature = BaseEncoding.base16().lowerCase().encode Crypto.signBytes(getBytes(), passphrase).encodeToDER() 59 | } 60 | 61 | public boolean verify(){ 62 | ECKey keys = ECKey.fromPublicOnly(BaseEncoding.base16().lowerCase().decode(generatorPublicKey)) 63 | byte[] signature = BaseEncoding.base16().lowerCase().decode(blockSignature) 64 | byte[] bytes = getBytes() 65 | verifyBytes(bytes, signature, keys.getPubKey()) 66 | } 67 | 68 | public String getId(){ 69 | byte[] bytes = getBytes(true) 70 | byte[] bytesid = bytes[0..7] 71 | id = new BigInteger(bytesid).toString() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/groovy/io/ark/core/Crypto.groovy: -------------------------------------------------------------------------------- 1 | package io.ark.core 2 | 3 | import org.bitcoinj.core.* 4 | import com.google.common.io.BaseEncoding 5 | import org.spongycastle.crypto.digests.RIPEMD160Digest 6 | 7 | 8 | class Crypto { 9 | 10 | static networkVersion = 0x17 11 | 12 | static ECKey.ECDSASignature sign(Transaction t, String passphrase){ 13 | byte[] txbytes = getBytes(t) 14 | signBytes(txbytes, passphrase) 15 | } 16 | 17 | static ECKey.ECDSASignature secondSign(Transaction t, String secondPassphrase){ 18 | byte[] txbytes = getBytes(t, false) 19 | signBytes(txbytes, secondPassphrase) 20 | } 21 | 22 | static ECKey.ECDSASignature signBytes(byte[] bytes, String passphrase){ 23 | ECKey keys = getKeys(passphrase) 24 | keys.sign(Sha256Hash.of(bytes)) 25 | } 26 | 27 | static boolean verify(Transaction t){ 28 | ECKey keys = ECKey.fromPublicOnly(BaseEncoding.base16().lowerCase().decode(t.senderPublicKey)) 29 | byte[] signature = BaseEncoding.base16().lowerCase().decode(t.signature) 30 | byte[] bytes = getBytes(t) 31 | verifyBytes(bytes, signature, keys.getPubKey()) 32 | } 33 | 34 | static boolean secondVerify(Transaction t, String secondPublicKeyHex){ 35 | ECKey keys = ECKey.fromPublicOnly BaseEncoding.base16().lowerCase().decode(secondPublicKeyHex) 36 | byte[] signature = BaseEncoding.base16().lowerCase().decode(t.signSignature) 37 | byte[] bytes = getBytes(t, false) 38 | verifyBytes(bytes, signature, keys.getPubKey()) 39 | } 40 | 41 | static boolean verifyBytes(byte[] bytes, byte[] signature, byte[] publicKey){ 42 | ECKey.verify(Sha256Hash.hash(bytes), signature, publicKey) 43 | } 44 | 45 | static byte[] getBytes(Transaction t, boolean skipSignature = true, boolean skipSecondSignature = true){ 46 | return t.toBytes(skipSignature, skipSecondSignature) 47 | } 48 | 49 | static String getId(Transaction t){ 50 | BaseEncoding.base16().lowerCase().encode Sha256Hash.hash(getBytes(t, false, false)) 51 | } 52 | 53 | static ECKey getKeys(String passphrase){ 54 | byte[] sha256 = Sha256Hash.hash(passphrase.bytes) 55 | ECKey keys = ECKey.fromPrivate(sha256, true) 56 | return keys 57 | } 58 | 59 | static String getAddress(ECKey keys){ 60 | getAddress(keys.getPubKey()) 61 | } 62 | 63 | static String getAddress(publicKey){ 64 | RIPEMD160Digest digest = new RIPEMD160Digest(); 65 | digest.update(publicKey, 0, publicKey.length); 66 | byte[] out = new byte[20]; 67 | digest.doFinal(out, 0); 68 | def address = new VersionedChecksummedBytes(networkVersion, out) 69 | return address.toBase58(); 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ark-java 2 | :coffee: Lite client library in Java 3 | 4 | [ ![Download](https://api.bintray.com/packages/arkecosystem/ark-java/client/images/download.svg) ](https://bintray.com/arkecosystem/ark-java/client/_latestVersion) 5 | 6 | # Authors 7 | FX Thoorens fx@ark.io 8 | 9 | 10 | # Installation 11 | ## Using Java 12 | - Download the ```.jar``` from the Maven repository `https://dl.bintray.com/arkecosystem/ark-java/` 13 | - Add it to your project and `import io.ark.*` 14 | 15 | ### Maven 16 | Add this under `` 17 | ``` 18 | 19 | io.ark.lite 20 | client 21 | 0.3 22 | compile 23 | 24 | ``` 25 | 26 | ### Gradle 27 | Add this line under `dependencies` 28 | ``` 29 | compile 'io.ark.lite:client:0.3' 30 | ``` 31 | 32 | See an example gradle app here: https://github.com/arkecosystem/ark-java-example 33 | 34 | ## Using Groovy 35 | Install groovy: http://groovy-lang.org/install.html 36 | 37 | Example: 38 | ``` 39 | @GrabResolver(name='ark-java', root='https://dl.bintray.com/arkecosystem/ark-java/') 40 | @Grab('io.ark.lite:client:0.3') 41 | 42 | import io.ark.core.* 43 | 44 | // grab mainnet network settings and warm it up 45 | def mainnet = Network.Mainnet 46 | mainnet.warmup() 47 | 48 | // create a transaction 49 | def transaction = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase") 50 | 51 | // Post transaction to a peer 52 | def peer = mainnet.randomPeer 53 | println peer << transaction 54 | 55 | // broadcast transaction to several peers on mainnet 56 | println mainnet << transaction 57 | ``` 58 | 59 | Run the example Groovy code by doing: 60 | `groovy example/Example.groovy` 61 | 62 | ## Security 63 | 64 | If you discover a security vulnerability within this library, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. 65 | 66 | # License 67 | 68 | The MIT License (MIT) 69 | 70 | Copyright (c) 2017 Ark 71 | 72 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 73 | 74 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 75 | 76 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 77 | -------------------------------------------------------------------------------- /src/main/groovy/io/ark/core/Peer.groovy: -------------------------------------------------------------------------------- 1 | package io.ark.core 2 | 3 | import groovyx.net.http.AsyncHTTPBuilder 4 | import groovy.transform.* 5 | import static groovyx.net.http.Method.* 6 | import static groovyx.net.http.ContentType.JSON 7 | import java.util.concurrent.Future 8 | 9 | @Canonical 10 | class Peer extends Object { 11 | String ip 12 | int port 13 | String protocol = "http://" 14 | String status = "NEW" 15 | 16 | private AsyncHTTPBuilder http 17 | private Map networkHeaders 18 | 19 | public static Peer create(String string, networkHeaders = Network.Mainnet.headers){ 20 | def data = string.split(":") 21 | def port = data[1] as int 22 | def protocol = "http://" 23 | if(port%1000 == 443) protocol = "https://" 24 | new Peer(ip: data[0], port: port, protocol: protocol, networkHeaders:networkHeaders) 25 | } 26 | 27 | // return Future that will deliver the JSON as a Map 28 | // the "query" argument allows to pass URL parameters as param1: 'value1', param2: 'value2'... string 29 | public Future request(String method, String path, query = [:], body = [:]){ 30 | if(!http) 31 | http = new AsyncHTTPBuilder(uri: "${protocol}${ip}:${port}") 32 | 33 | def _method 34 | switch(method){ 35 | case "POST": 36 | _method = POST 37 | break 38 | case "PUT": 39 | _method = PUT 40 | break 41 | case "GET": 42 | _method = GET 43 | } 44 | 45 | def that = this 46 | 47 | http.request(_method, JSON) { 48 | uri.path = path 49 | uri.query = query 50 | headers << networkHeaders 51 | body = body 52 | 53 | response.success = { resp, json -> 54 | that.status = "OK" 55 | json 56 | } 57 | } 58 | } 59 | 60 | public Map getStatus(){ 61 | request("GET", "/peer/status").get() 62 | } 63 | 64 | /* 65 | * TODO: Actually use this to get an updated list of peers instead of the 66 | * constantly breaking hardcoded one in the Network class 67 | */ 68 | public Map getPeers(){ 69 | request("GET", "/peer/list").get() 70 | } 71 | 72 | public Map getDelegates(){ 73 | request("GET", "/api/delegates").get() 74 | } 75 | 76 | public Map postTransaction(Transaction transaction){ 77 | if(!http) 78 | http = new AsyncHTTPBuilder(uri: "${protocol}${ip}:${port}") 79 | Future future = http.request(POST, JSON) { 80 | uri.path = "/peer/transactions" 81 | headers << networkHeaders 82 | body = [transactions:[transaction.toObject()]] 83 | 84 | response.success = { resp, json -> 85 | json 86 | } 87 | } 88 | future.get() 89 | } 90 | 91 | public Map getTransactions(Account account, int amount) 92 | { 93 | if(!http) http = new AsyncHTTPBuilder(uri: "${protocol}${ip}:${port}") 94 | 95 | Future future = http.get(path: "/api/transactions", 96 | headers: networkHeaders, 97 | contentType: JSON, 98 | query: [recipientId:account.getAddress(), 99 | senderId:account.getAddress(), 100 | limit:amount]) 101 | 102 | future.get() 103 | } 104 | 105 | public Map leftShift(Transaction transaction){ 106 | postTransaction(transaction) 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Documentation.md: -------------------------------------------------------------------------------- 1 | # Library Specification 2 | 3 | ## Networks 4 | 5 | ### Constants 6 | 7 | `Network.Mainnet` : Network 8 | 9 | Provides a Network object that represents the ARK Mainnet 10 | 11 | `Network.Devnet` : Network 12 | 13 | Provides a Network object that represents the ARK Devnet 14 | 15 | ### Capabilities 16 | 17 | * `GetHeaders()` : Map 18 | * `warmup()` : Boolean 19 | * `leftShift(Transaction)` : int 20 | * `getRandomPeer()` : Peer 21 | 22 | ## Transactions 23 | 24 | ### Fields 25 | 26 | `timestamp` : int 27 | 28 | When the transaction took place 29 | 30 | `recipientId` : String 31 | 32 | The id of the recipient of the transaction 33 | 34 | `amount` : long 35 | 36 | Amount associated with the transaction 37 | 38 | `fee` : long 39 | 40 | Fee charged to send the transaction 41 | 42 | `type` : TransactionType 43 | 44 | Specifies if transaction is a normal, vote, or delegate creation 45 | 46 | `vendorField` : String 47 | 48 | Data to include with a transaction 49 | 50 | `signature` : String 51 | 52 | The signature using passphrase from sender 53 | 54 | `signSignature` : String 55 | 56 | The signature using passphrase from sender 57 | 58 | `senderPublicKey` : String 59 | 60 | The senders public key 61 | 62 | `requesterPublicKey` : String 63 | 64 | The receiver public key 65 | 66 | `asset` : Map 67 | 68 | TODO 69 | 70 | `id` : String 71 | 72 | ID of the transaction itself 73 | 74 | 75 | ### Capabilities 76 | 77 | * `toBytes()` : byte[] 78 | * `toObject()` : Map 79 | * `sign(passphrase)` : String 80 | * `secondSign(passphrase)` : String 81 | * `toJson()` : String 82 | * `fromJson(json)` : Transaction 83 | * `createTransaction(recipientId, satoshiAmount, vendorField, passphrase)` : Transaction 84 | * `createVote(ArrayList votes, passphrase)` : Transaction 85 | * `createDelegate(String username, String passphrase` : Transaction 86 | * `createSecondSignature(String secondPassphrase, String passphrase)` : Transaction 87 | 88 | ## TransactionTypes 89 | 90 | ### Defined Enums 91 | 92 | * `TransactionType.NORAML` : 0 93 | * `TransactionType.SECONDSIGNATURE` : 1 94 | * `TransactionType.VOTE` : 2 95 | * `TransactionType.DELEGATE` : 3 96 | 97 | ### Capabilities 98 | 99 | * `getByteValue()` : Byte 100 | 101 | Returns a byte representation of the enum value. 102 | 103 | 104 | ## Accounts 105 | 106 | ### Fields 107 | 108 | `address` : String 109 | 110 | The address of the account 111 | 112 | `publicKey` : String 113 | 114 | The public key of the account 115 | 116 | `balance` : long 117 | 118 | The balance in the account 119 | 120 | `username` : String 121 | 122 | Username associated with this account 123 | 124 | `vote` : long 125 | 126 | The id who this account is voting for 127 | 128 | `votes` : List 129 | 130 | The id's of previous votes 131 | 132 | `rate` : int 133 | 134 | TODO 135 | 136 | ### Capabilities 137 | 138 | * `applyTransaction()` : boolean 139 | * `undoTransaction()` : boolean 140 | * `verifyTransaction()` : Verification 141 | 142 | ## Crypto 143 | 144 | ### Constants 145 | 146 | `networkVersion` : Byte 147 | 148 | ### Capabilities 149 | 150 | * `sign(Transaction t, String passphrase)` : ECKey.DCDSASignature 151 | * `secondSign(Transaction t, String passphrase)` : ECKey.DCDSASignature 152 | * `signBytes(bytes[] bytes, String passphrase)` : ECKey.DCDSASignature 153 | * `verify(Transaction t)` : boolean 154 | * `Secondverify(Transaction t, String secondPublicKeyHex)` : boolean 155 | * `verifyBytes(bytes[] bytes, byte[] signature, byte[] publicKey)` : boolean 156 | * `getBytes(Transaction t)` : byte[] 157 | * `getId(Transaction t)` : String 158 | * `getKeys(String passphrase)` : ECKey 159 | * `getAddress(ECKey keys)` : String 160 | * `getAddress(String publicKey)` : String 161 | 162 | ## Peers 163 | 164 | ### Constants 165 | 166 | `ip` : String 167 | 168 | `port` : int 169 | 170 | `protocol` : String 171 | 172 | `status` : String 173 | 174 | ### Capabilities 175 | 176 | * `create(String string, networkHeaders = Network.Mainnet.headers)` : Peer 177 | * `request(String method, String path, body = [:]))` : Future 178 | * `getStatus()` : Map 179 | * `postTransaction(Transaction transaction)` : Map 180 | * `getPeers()` : Map 181 | * `leftShift(Transaction transaction)` : Map 182 | 183 | -------------------------------------------------------------------------------- /src/test/groovy/NetworkTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * This Spock specification was auto generated by running 'gradle init --type groovy-library' 3 | */ 4 | 5 | 6 | import spock.lang.Shared 7 | import spock.lang.Specification 8 | import io.ark.core.* 9 | import spock.lang.Timeout 10 | 11 | import java.util.concurrent.TimeUnit 12 | 13 | class NetworkTest extends Specification { 14 | 15 | @Shared mainnet = Network.Mainnet 16 | @Shared devnet = Network.Devnet 17 | 18 | def setup() 19 | { 20 | mainnet.warmup() 21 | devnet.warmup() 22 | } 23 | 24 | //Fails if any peer is unreachable. 25 | //Fails if peer lookup takes too long 26 | //Fails if any peer doesnt report back success status 27 | @Timeout(value = 5, unit = TimeUnit.MINUTES) 28 | def "All peers should be reachable"() 29 | { 30 | setup: 31 | boolean success = true 32 | 33 | when: 34 | (mainnet.peers + devnet.peers).each { peer -> 35 | success = success && peer.getStatus().get("success") as boolean 36 | } 37 | 38 | then: 39 | //Fail fast if an exception is thrown 40 | noExceptionThrown() 41 | success 42 | } 43 | 44 | // TODO: stub 45 | def "Should connect to Mainnet"(){ 46 | setup: 47 | def peer = mainnet.randomPeer 48 | when: 49 | def status = peer.getStatus() 50 | then: 51 | status.currentSlot > status.height 52 | } 53 | 54 | // TODO: stub 55 | def "Should connect to Devnet"(){ 56 | setup: 57 | def peer = devnet.randomPeer 58 | when: 59 | def status = peer.getStatus() 60 | then: 61 | status.currentSlot > status.height 62 | } 63 | 64 | // TODO: stub 65 | def "Should post a transaction to a Mainnet Peer"(){ 66 | setup: 67 | def peer = mainnet.randomPeer 68 | when: 69 | def transaction = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase") 70 | def result = peer << transaction 71 | then: 72 | result.error == "Account does not have enough ARK: AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC balance: 0" 73 | } 74 | 75 | // TODO: stub 76 | def "Should post a vote transaction to a Mainnet Peer"(){ 77 | setup: 78 | def peer = mainnet.randomPeer 79 | when: 80 | def transaction = Transaction.createVote(["+123456789"], "this is a top secret passphrase") 81 | def result = peer << transaction 82 | then: 83 | result.error == "Account does not have enough ARK: AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC balance: 0" 84 | } 85 | 86 | // TODO: stub 87 | def "Should broadcast a transaction to Mainnet"(){ 88 | when: 89 | def transaction = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase") 90 | def result = mainnet << transaction 91 | then: 92 | result == mainnet.broadcastMax 93 | } 94 | 95 | // TODO: stub 96 | def "Should filter get request with query params"(){ 97 | setup: 98 | def peer = mainnet.randomPeer 99 | when: 100 | def result = peer.request("GET", "/api/blocks/", [ offset:'100', limit:'50' ]).get() 101 | then: 102 | result.get("success") == true 103 | (result.get("blocks") as List).size() == 50 104 | } 105 | 106 | // TODO: stub 107 | def "Should Get transactions associated with an Account"(){ 108 | setup: 109 | def peer = mainnet.randomPeer 110 | when: 111 | def account = Account.newInstance([address:'AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25']) 112 | def result = peer.getTransactions(account, 2) 113 | then: 114 | result.get("success") == true 115 | result.get("count").equals("2") //Count doesnt include filtering from api call 116 | (result.get("transactions") as List).size() == 2 117 | } 118 | 119 | // TODO: stub 120 | def "Should only return one transaction from an Account"(){ 121 | setup: 122 | def peer = mainnet.randomPeer 123 | when: 124 | def account = Account.newInstance([address:'AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25']) 125 | def result = peer.getTransactions(account, 1) 126 | then: 127 | result.get("success") == true 128 | result.get("count").equals("2") //Count doesnt include filtering from api call 129 | (result.get("transactions") as List).size() == 1 130 | } 131 | 132 | // TODO: stub 133 | def "Should be able to fetch a list of peers"(){ 134 | setup: 135 | def peer = mainnet.randomPeer 136 | when: 137 | def result = peer.getPeers() 138 | then: 139 | result.get("success") == true 140 | (result.get("peers") as List).size() > 0 141 | } 142 | 143 | // TODO: stub 144 | def "Should be able to fetch a list of delegates"(){ 145 | setup: 146 | def peer = mainnet.randomPeer 147 | when: 148 | def result = peer.getDelegates() 149 | then: 150 | result.get("success") == true 151 | (result.get("totalCount") as int) > 0 152 | (result.get("delegates") as List).size() > 0 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/test/groovy/CryptoTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * This Spock specification was auto generated by running 'gradle init --type groovy-library' 3 | */ 4 | 5 | import spock.lang.Specification 6 | import io.ark.core.* 7 | import com.google.common.io.BaseEncoding 8 | 9 | class CryptoTest extends Specification { 10 | 11 | def "Passphrase 'this is a top secret passphrase' should generate address 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' on Mainnet"(){ 12 | setup: 13 | Crypto.networkVersion = Network.Mainnet.prefix 14 | when: 15 | def address = Crypto.getAddress(Crypto.getKeys("this is a top secret passphrase")) 16 | then: 17 | address == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' 18 | 19 | } 20 | 21 | def "Passphrase 'this is a top secret passphrase' should generate address 'D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib' on Devnet"(){ 22 | setup: 23 | Crypto.networkVersion = Network.Devnet.prefix 24 | when: 25 | def address = Crypto.getAddress(Crypto.getKeys("this is a top secret passphrase")) 26 | then: 27 | address == 'D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib' 28 | } 29 | 30 | def "Transaction should create and verify"() { 31 | setup: 32 | Crypto.networkVersion = Network.Mainnet.prefix 33 | when: 34 | def tx = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase") 35 | println tx.toJson() 36 | then: 37 | Crypto.verify(tx) 38 | } 39 | 40 | def "Transaction should create and serialize/deserialize to JSON"() { 41 | setup: 42 | Crypto.networkVersion = Network.Mainnet.prefix 43 | when: 44 | def tx = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase", "this is a top secret second passphrase") 45 | then: 46 | tx == Transaction.fromJson(tx.toJson()) 47 | } 48 | 49 | def "Verify signatures on second signature transaction"() { 50 | setup: 51 | Crypto.networkVersion = Network.Mainnet.prefix 52 | when: 53 | def signatureTx = Transaction.createSecondSignature("second passphrase", "first passphrase") 54 | then: 55 | Crypto.verify(signatureTx) && signatureTx.signSignature == null && signatureTx.asset?.get("signature")?.get("publicKey") == BaseEncoding.base16().lowerCase().encode(Crypto.getKeys("second passphrase").getPubKey()) 56 | } 57 | 58 | def "Transaction with second passphrase should create and verify"() { 59 | setup: 60 | Crypto.networkVersion = Network.Mainnet.prefix 61 | when: 62 | def tx = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase", "this is a top secret second passphrase") 63 | def secondPublicKeyHex=Crypto.getKeys("this is a top secret second passphrase").getPubKey() 64 | secondPublicKeyHex = BaseEncoding.base16().lowerCase().encode secondPublicKeyHex 65 | then: 66 | Crypto.verify(tx) && Crypto.secondVerify(tx, secondPublicKeyHex) 67 | } 68 | 69 | def "Transaction should create and verify should fail if amount is modified"() { 70 | setup: 71 | Crypto.networkVersion = Network.Mainnet.prefix 72 | when: 73 | def tx = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase") 74 | tx.amount = 100000000000000 75 | then: 76 | !Crypto.verify(tx) && tx == Transaction.fromJson(tx.toJson()) 77 | } 78 | 79 | def "Transaction should create and verify should fail if fee is modified"() { 80 | setup: 81 | Crypto.networkVersion = Network.Mainnet.prefix 82 | when: 83 | def tx = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase") 84 | tx.fee = 11 85 | then: 86 | !Crypto.verify(tx) && tx == Transaction.fromJson(tx.toJson()) 87 | } 88 | 89 | def "Transaction should create and verify should fail if recipientId is modified"() { 90 | setup: 91 | Crypto.networkVersion = Network.Mainnet.prefix 92 | when: 93 | def tx = Transaction.createTransaction("AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25", 133380000000, "This is first transaction from JAVA", "this is a top secret passphrase") 94 | tx.recipientId = "AavdJLxqBnWqaFXWm2xNirNArJNUmyUpup" 95 | then: 96 | !Crypto.verify(tx) && tx == Transaction.fromJson(tx.toJson()) 97 | } 98 | 99 | def "Transaction vote should create and verify"() { 100 | setup: 101 | Crypto.networkVersion = Network.Mainnet.prefix 102 | when: 103 | def tx = Transaction.createVote(["+034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192"], "this is a top secret passphrase") 104 | then: 105 | Crypto.verify(tx) && tx == Transaction.fromJson(tx.toJson()) 106 | } 107 | 108 | def "Transaction delegate should create and verify"() { 109 | setup: 110 | Crypto.networkVersion = Network.Mainnet.prefix 111 | when: 112 | def tx = Transaction.createDelegate("polopolo", "this is a top secret passphrase") 113 | then: 114 | Crypto.verify(tx) && tx == Transaction.fromJson(tx.toJson()) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/groovy/io/ark/core/Transaction.groovy: -------------------------------------------------------------------------------- 1 | package io.ark.core 2 | 3 | import java.nio.* 4 | import com.google.common.io.BaseEncoding 5 | import com.google.gson.Gson 6 | import org.bitcoinj.core.* 7 | import groovy.transform.* 8 | 9 | enum TransactionType { 10 | NORMAL(0), 11 | SECONDSIGNATURE(1), 12 | DELEGATE(2), 13 | VOTE(3), 14 | 15 | /** 16 | * @deprecated use SECONDSIGNATURE 17 | */ 18 | @Deprecated SECONDSIGNITURE(1) 19 | 20 | private final int value 21 | 22 | TransactionType(int value) 23 | { 24 | this.value = value as byte 25 | } 26 | 27 | byte getByteValue() { 28 | return value 29 | } 30 | } 31 | 32 | @Canonical 33 | class Transaction extends Object { 34 | int timestamp 35 | String recipientId 36 | Long amount 37 | Long fee 38 | TransactionType type 39 | String vendorField 40 | String signature 41 | String signSignature 42 | String senderPublicKey 43 | String requesterPublicKey 44 | Map asset = [:] 45 | String id 46 | 47 | /** 48 | * Serializes this transaction object into a byte array 49 | * 50 | * @param skipSignature 51 | * @param skipSecondSignature 52 | * @return an array of bytes representing this object 53 | */ 54 | public byte[] toBytes(boolean skipSignature = true, boolean skipSecondSignature = true){ 55 | ByteBuffer buffer = ByteBuffer.allocate(1000) 56 | buffer.order(ByteOrder.LITTLE_ENDIAN) 57 | 58 | buffer.put type.getByteValue() 59 | buffer.putInt timestamp 60 | buffer.put BaseEncoding.base16().lowerCase().decode(senderPublicKey) 61 | 62 | if(requesterPublicKey){ 63 | buffer.put BaseEncoding.base16().lowerCase().decode(requesterPublicKey) 64 | } 65 | 66 | if(recipientId){ 67 | buffer.put Base58.decodeChecked(recipientId) 68 | } 69 | else { 70 | buffer.put new byte[21] 71 | } 72 | 73 | if(vendorField){ 74 | byte[] vbytes = vendorField.bytes 75 | if(vbytes.size()<65){ 76 | buffer.put vbytes 77 | buffer.put new byte[64-vbytes.size()] 78 | } 79 | } 80 | else { 81 | buffer.put new byte[64] 82 | } 83 | 84 | buffer.putLong amount 85 | buffer.putLong fee 86 | 87 | if(type == TransactionType.SECONDSIGNITURE || type == TransactionType.SECONDSIGNATURE){ 88 | buffer.put BaseEncoding.base16().lowerCase().decode(asset?.get("signature")?.get("publicKey")) 89 | } 90 | else if(type == TransactionType.DELEGATE){ 91 | buffer.put asset.username.bytes 92 | } 93 | else if(type == TransactionType.VOTE){ 94 | buffer.put asset.votes.join("").bytes 95 | } 96 | // TODO: multisignature 97 | // else if(type==4){ 98 | // buffer.put BaseEncoding.base16().lowerCase().decode(asset.signature) 99 | // } 100 | 101 | if(!skipSignature && signature){ 102 | buffer.put BaseEncoding.base16().lowerCase().decode(signature) 103 | } 104 | if(!skipSecondSignature && signSignature){ 105 | buffer.put BaseEncoding.base16().lowerCase().decode(signSignature) 106 | } 107 | 108 | def outBuffer = new byte[buffer.position()] 109 | buffer.rewind() 110 | buffer.get(outBuffer) 111 | return outBuffer 112 | } 113 | 114 | public Map toObject(){ 115 | this.properties.subMap(['id', 'timestamp', 'recipientId', 'amount', 'fee', 'type', 'vendorField', 'signature', 'signSignature', 'senderPublicKey', 'requesterPublicKey', 'asset']) 116 | } 117 | 118 | String sign(passphrase){ 119 | senderPublicKey = BaseEncoding.base16().lowerCase().encode(Crypto.getKeys(passphrase).getPubKey()) 120 | signature = BaseEncoding.base16().lowerCase().encode Crypto.sign(this, passphrase).encodeToDER() 121 | } 122 | 123 | String secondSign(passphrase){ 124 | signSignature = BaseEncoding.base16().lowerCase().encode Crypto.secondSign(this, passphrase).encodeToDER() 125 | } 126 | 127 | String toJson(){ 128 | Gson gson = new Gson() 129 | gson.toJson(this) 130 | } 131 | 132 | static Transaction fromJson(json){ 133 | Gson gson = new Gson() 134 | gson.fromJson(json, Transaction.class) 135 | } 136 | 137 | static Transaction createTransaction(String recipientId, long satoshiAmount, String vendorField, String passphrase, String secondPassphrase = null){ 138 | def tx = new Transaction(type:TransactionType.NORMAL, recipientId:recipientId, amount:satoshiAmount, fee:10000000, vendorField:vendorField) 139 | tx.timestamp = Slot.getTime() 140 | tx.sign(passphrase) 141 | if(secondPassphrase) 142 | tx.secondSign(secondPassphrase) 143 | tx.id = Crypto.getId(tx) 144 | return tx 145 | } 146 | 147 | static Transaction createVote(List votes, String passphrase, String secondPassphrase = null){ 148 | def tx = new Transaction(type:TransactionType.VOTE, amount:0, fee:100000000) 149 | tx.asset.votes = votes 150 | tx.recipientId = Crypto.getAddress(Crypto.getKeys(passphrase)) 151 | tx.timestamp = Slot.getTime() 152 | tx.sign(passphrase) 153 | if(secondPassphrase) 154 | tx.secondSign(secondPassphrase) 155 | tx.id = Crypto.getId(tx) 156 | return tx 157 | } 158 | 159 | static Transaction createDelegate(String username, String passphrase, String secondPassphrase = null){ 160 | def tx = new Transaction(type:TransactionType.DELEGATE, amount:0, fee:2500000000) 161 | tx.asset.username = username 162 | tx.timestamp = Slot.getTime() 163 | tx.sign(passphrase) 164 | if(secondPassphrase) 165 | tx.secondSign(secondPassphrase) 166 | tx.id = Crypto.getId(tx) 167 | return tx 168 | } 169 | 170 | static Transaction createSecondSignature(String secondPassphrase, String passphrase){ 171 | def tx = new Transaction(type:TransactionType.SECONDSIGNATURE, amount:0, fee:500000000) 172 | tx.asset.signature = [publicKey:BaseEncoding.base16().lowerCase().encode(Crypto.getKeys(secondPassphrase).getPubKey())] 173 | tx.timestamp = Slot.getTime() 174 | tx.sign(passphrase) 175 | tx.id = Crypto.getId(tx) 176 | return tx 177 | } 178 | 179 | //TODO: create multisignature 180 | 181 | //Custom getter to map type to byte value 182 | byte getType() 183 | { 184 | return type.getByteValue() 185 | } 186 | 187 | } 188 | --------------------------------------------------------------------------------