├── settings.gradle
├── .ci
└── kontinuum.json
├── android
├── promo
│ ├── 180x120.png
│ ├── 180x120.xcf
│ └── 1024x500.png
├── src
│ ├── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ │ ├── drawable-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_action_help.png
│ │ │ │ ├── ic_action_stamp.png
│ │ │ │ └── ic_action_barcode.png
│ │ │ ├── drawable-ldpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_action_help.png
│ │ │ │ ├── ic_action_stamp.png
│ │ │ │ └── ic_action_barcode.png
│ │ │ ├── drawable-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_action_help.png
│ │ │ │ ├── ic_action_stamp.png
│ │ │ │ └── ic_action_barcode.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_action_help.png
│ │ │ │ ├── ic_action_stamp.png
│ │ │ │ └── ic_action_barcode.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── ic_action_help.png
│ │ │ │ ├── ic_action_stamp.png
│ │ │ │ └── ic_action_barcode.png
│ │ │ ├── values-sw600dp
│ │ │ │ └── dimens.xml
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-sw720dp-land
│ │ │ │ └── dimens.xml
│ │ │ ├── layout
│ │ │ │ ├── help_dialog.xml
│ │ │ │ ├── fragment_image.xml
│ │ │ │ ├── last_hash.xml
│ │ │ │ ├── fragment_text.xml
│ │ │ │ ├── last_hash_inner.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── menu
│ │ │ │ └── main.xml
│ │ │ ├── layout-land
│ │ │ │ └── last_hash.xml
│ │ │ └── raw
│ │ │ │ └── help.html
│ │ ├── kotlin
│ │ │ ├── org
│ │ │ │ └── ligi
│ │ │ │ │ ├── satoshiproof
│ │ │ │ │ ├── proof_fragments
│ │ │ │ │ │ ├── ProofFragment.kt
│ │ │ │ │ │ ├── TextProofFragment.kt
│ │ │ │ │ │ └── ImageProofFragment.kt
│ │ │ │ │ ├── util
│ │ │ │ │ │ ├── Downloader.kt
│ │ │ │ │ │ └── AddressGenerator.kt
│ │ │ │ │ ├── FileProofController.kt
│ │ │ │ │ ├── HelpDialog.kt
│ │ │ │ │ ├── LastHashActivity.kt
│ │ │ │ │ ├── ProofAsyncTask.kt
│ │ │ │ │ ├── ImageFromIntentUriExtractor.kt
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ │ └── views
│ │ │ │ │ └── SquareImageView.kt
│ │ │ ├── com
│ │ │ │ └── google
│ │ │ │ │ └── bitcoin
│ │ │ │ │ └── core
│ │ │ │ │ └── Base58.kt
│ │ │ └── de
│ │ │ │ └── schildbach
│ │ │ │ └── wallet
│ │ │ │ └── integration
│ │ │ │ └── android
│ │ │ │ └── BitcoinIntegration.kt
│ │ └── AndroidManifest.xml
│ ├── onEthereum
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ ├── onBitcoin
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ │ └── TheMainActivity.kt
│ └── test
│ │ └── kotlin
│ │ └── org
│ │ └── ligi
│ │ └── satoshiproof
│ │ └── TheAddressGenerator.kt
├── lint.xml
├── proguard-project.txt
└── build.gradle
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .travis.yml
├── README.md
└── gradlew
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':android'
2 |
--------------------------------------------------------------------------------
/.ci/kontinuum.json:
--------------------------------------------------------------------------------
1 | {
2 | "type":"android",
3 | "stages":["spoon","lint","test","assemble"]
4 | }
5 |
--------------------------------------------------------------------------------
/android/promo/180x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/promo/180x120.png
--------------------------------------------------------------------------------
/android/promo/180x120.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/promo/180x120.xcf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | .idea/
3 | android/build/
4 | gradlew.bat
5 | *.iml
6 | local.properties
7 | build/
--------------------------------------------------------------------------------
/android/promo/1024x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/promo/1024x500.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-hdpi/ic_action_help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-hdpi/ic_action_help.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-hdpi/ic_action_stamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-hdpi/ic_action_stamp.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-ldpi/ic_action_help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-ldpi/ic_action_help.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-ldpi/ic_action_stamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-ldpi/ic_action_stamp.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-mdpi/ic_action_help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-mdpi/ic_action_help.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-mdpi/ic_action_stamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-mdpi/ic_action_stamp.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-xhdpi/ic_action_help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-xhdpi/ic_action_help.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-xhdpi/ic_action_stamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-xhdpi/ic_action_stamp.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-xxhdpi/ic_action_help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-xxhdpi/ic_action_help.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-hdpi/ic_action_barcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-hdpi/ic_action_barcode.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-ldpi/ic_action_barcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-ldpi/ic_action_barcode.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-mdpi/ic_action_barcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-mdpi/ic_action_barcode.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-xhdpi/ic_action_barcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-xhdpi/ic_action_barcode.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-xxhdpi/ic_action_stamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-xxhdpi/ic_action_stamp.png
--------------------------------------------------------------------------------
/android/src/main/res/drawable-xxhdpi/ic_action_barcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ligi/SatoshiProof/HEAD/android/src/main/res/drawable-xxhdpi/ic_action_barcode.png
--------------------------------------------------------------------------------
/android/src/onEthereum/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ΞProof
4 | HΞLP
5 |
6 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/proof_fragments/ProofFragment.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof.proof_fragments
2 |
3 | interface ProofFragment {
4 |
5 | fun proof()
6 |
7 | val title: String
8 | }
9 |
--------------------------------------------------------------------------------
/android/src/onBitcoin/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Satoshi Proof
4 | Help
5 |
6 |
--------------------------------------------------------------------------------
/android/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/src/main/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Satoshi Proof
4 | Help
5 | Help
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Oct 12 19:11:45 CEST 2016
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-3.1-all.zip
7 |
--------------------------------------------------------------------------------
/android/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/android/src/main/res/values-sw720dp-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 128dp
5 |
6 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/help_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | android:
3 | components:
4 | - build-tools-22
5 | - android-22
6 | - sysimg-22
7 | - add-on
8 | - extra
9 |
10 | jdk: oraclejdk7
11 |
12 | script:
13 | # http://stackoverflow.com/questions/27377615/why-does-travis-ci-kill-the-process-for-my-script
14 | GRADLE_OPTS='-Xmx768m -Xms256m -Xss1m' ./gradlew clean build
15 |
16 | notifications:
17 | email: true
18 |
19 |
20 |
--------------------------------------------------------------------------------
/android/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/android/src/test/kotlin/org/ligi/satoshiproof/TheAddressGenerator.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof
2 |
3 | import org.assertj.core.api.Assertions.assertThat
4 | import org.junit.Test
5 | import org.ligi.satoshiproof.util.AddressGenerator.dataToAddressString
6 |
7 | class TheAddressGenerator {
8 |
9 | @Test
10 | fun shouldGenerateCorrectAddresses() {
11 | assertThat(dataToAddressString("probe".toByteArray())).isEqualTo("1KXgQMLN5ceej3ViannhJudCjJsFWrWXvs")
12 |
13 | assertThat(dataToAddressString("foo".toByteArray())).isEqualTo("1MaybZp8GRkAHmpAyWkSQEwAnohFxPBoGY")
14 | }
15 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/util/Downloader.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof.util
2 |
3 | import okhttp3.OkHttpClient
4 | import okhttp3.Request
5 |
6 | object Downloader {
7 | fun downloadURL(url : String) : String? {
8 | return try {
9 |
10 | val client = OkHttpClient()
11 |
12 | val request = Request.Builder().url(url).build()
13 |
14 | val response = client.newCall(request).execute()
15 | response.body().string()
16 |
17 | } catch (e: Exception) {
18 | e.printStackTrace()
19 | null
20 | }
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/android/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/fragment_image.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
19 |
20 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/last_hash.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/android/src/main/res/layout-land/last_hash.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/views/SquareImageView.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.views
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.widget.ImageView
6 |
7 | /**
8 | * Created by ligi on 14.10.16.
9 | */
10 | public class SquareImageView(context: Context, attrs: AttributeSet) : ImageView(context, attrs) {
11 |
12 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
13 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
14 |
15 | val parentWidth = MeasureSpec.getSize(widthMeasureSpec)
16 | val parentHeight = MeasureSpec.getSize(heightMeasureSpec)
17 | val size = Math.min(parentWidth, parentHeight)
18 | this.setMeasuredDimension(size, size)
19 | }
20 | }
--------------------------------------------------------------------------------
/android/src/main/res/layout/fragment_text.xml:
--------------------------------------------------------------------------------
1 |
5 |
9 |
10 |
17 |
18 |
--------------------------------------------------------------------------------
/android/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/proof_fragments/TextProofFragment.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof.proof_fragments
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import kotlinx.android.synthetic.main.fragment_text.*
9 | import org.ligi.satoshiproof.ProofAsyncTask
10 | import org.ligi.satoshiproof.R
11 |
12 | class TextProofFragment : Fragment(), ProofFragment {
13 |
14 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
15 | return inflater.inflate(R.layout.fragment_text, container, false)
16 | }
17 |
18 | override fun proof() {
19 | ProofAsyncTask(activity, textToProof.text.toString().toByteArray()).execute()
20 | }
21 |
22 | override val title = "text"
23 | }
24 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/last_hash_inner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
19 |
20 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/FileProofController.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof
2 |
3 | import android.app.Activity
4 | import android.support.v7.app.AlertDialog
5 | import org.apache.commons.io.FileUtils
6 | import java.io.File
7 |
8 | class FileProofController(private val context: Activity) {
9 |
10 |
11 | fun proofFile(file: File?) {
12 | context.runOnUiThread(Runnable {
13 | try {
14 | if (file == null) {
15 | failWitAlertDialog("Could not open file")
16 | return@Runnable
17 | }
18 | val imageBytes = FileUtils.readFileToByteArray(file)
19 | ProofAsyncTask(context, imageBytes).execute()
20 | } catch (e: Exception) {
21 | failWitAlertDialog("Could not open $file $e")
22 | }
23 | })
24 | }
25 |
26 | private fun failWitAlertDialog(msg: String) {
27 | AlertDialog.Builder(context).setMessage(msg).setPositiveButton(android.R.string.ok, null).show()
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/HelpDialog.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof
2 |
3 | import android.content.Context
4 | import android.support.v7.app.AlertDialog
5 | import android.text.Html
6 | import android.text.method.LinkMovementMethod
7 | import android.view.LayoutInflater
8 | import android.widget.TextView
9 | import java.io.IOException
10 |
11 | object HelpDialog {
12 |
13 | fun show(context: Context) {
14 | val helpTextView = LayoutInflater.from(context).inflate(R.layout.help_dialog,null) as TextView
15 |
16 | helpTextView.movementMethod = LinkMovementMethod.getInstance()
17 |
18 | try {
19 | val helpInputStream = context.resources.openRawResource(R.raw.help)
20 | val helpString = helpInputStream.reader().readText()
21 | helpTextView.text = Html.fromHtml(helpString)
22 | AlertDialog.Builder(context)
23 | .setTitle(R.string.help_title)
24 | .setView(helpTextView)
25 | .setPositiveButton(android.R.string.ok,null)
26 | .show()
27 | } catch (ignored: IOException) {
28 | // should not happen
29 | }
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/util/AddressGenerator.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof.util
2 |
3 | import com.google.bitcoin.core.Base58
4 | import org.spongycastle.crypto.digests.RIPEMD160Digest
5 | import java.security.MessageDigest
6 | import java.security.NoSuchAlgorithmException
7 |
8 | object AddressGenerator {
9 |
10 | fun dataToAddressString(data: ByteArray): String {
11 | try {
12 | val sha256Digest = MessageDigest.getInstance("SHA-256")
13 | val ripemd160Digest = RIPEMD160Digest()
14 |
15 | val sha256 = sha256Digest.digest(data)
16 | ripemd160Digest.update(sha256, 0, sha256.size)
17 | val bytes = ByteArray(20)
18 | ripemd160Digest.doFinal(bytes, 0)
19 |
20 | val addressBytes = ByteArray(1 + bytes.size + 4)
21 | System.arraycopy(bytes, 0, addressBytes, 1, bytes.size)
22 | val check = doubleDigest(addressBytes, 0, bytes.size + 1, sha256Digest)
23 | System.arraycopy(check, 0, addressBytes, bytes.size + 1, 4)
24 | return Base58.encode(addressBytes)
25 |
26 | } catch (e: NoSuchAlgorithmException) {
27 | throw RuntimeException(e) // Cannot happen.
28 | }
29 | }
30 |
31 | private fun doubleDigest(input: ByteArray, offset: Int, length: Int, digest: MessageDigest): ByteArray {
32 | digest.reset()
33 | digest.update(input, offset, length)
34 | val first = digest.digest()
35 | return digest.digest(first)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/android/src/androidTest/kotlin/TheMainActivity.kt:
--------------------------------------------------------------------------------
1 |
2 | import android.support.test.espresso.Espresso.onView
3 | import android.support.test.espresso.action.ViewActions.click
4 | import android.support.test.espresso.assertion.ViewAssertions.matches
5 | import android.support.test.espresso.matcher.ViewMatchers.*
6 | import android.support.test.rule.ActivityTestRule
7 | import android.view.WindowManager
8 | import com.jraska.falcon.FalconSpoon
9 | import org.junit.Before
10 | import org.junit.Rule
11 | import org.junit.Test
12 | import org.ligi.satoshiproof.MainActivity
13 | import org.ligi.satoshiproof.R
14 |
15 | class TheMainActivity {
16 |
17 | @get:Rule
18 | var activityActivityTestRule = ActivityTestRule(MainActivity::class.java)
19 |
20 | val activity by lazy { activityActivityTestRule.activity }
21 |
22 | @Before
23 | fun start() {
24 | activity.runOnUiThread { activity.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) }
25 | }
26 |
27 | @Test
28 | fun activityLaunches() {
29 | FalconSpoon.screenshot(activity,"main_activity")
30 | }
31 |
32 | @Test
33 | fun helpShows() {
34 | onView(withId(R.id.action_help)).perform(click())
35 | onView(withText(R.string.help_title)).check(matches(isDisplayed()))
36 | FalconSpoon.screenshot(activity,"help")
37 | }
38 |
39 | @Test
40 | fun lastHashShows() {
41 | onView(withId(R.id.action_hash)).perform(click())
42 | onView(withId(R.id.hash_text)).check(matches(isDisplayed()))
43 | FalconSpoon.screenshot(activity,"last_hash")
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
19 |
20 |
26 |
27 |
35 |
36 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/android/src/main/res/raw/help.html:
--------------------------------------------------------------------------------
1 | What is this all about?
2 |
3 | Satoshi Proof is your personal notary leveraging the power of the Bitcoin network to prove existence of text or images at a certain time.
4 | First you select a picture or enter text - then the system checks if the existence is already proven and prints out the time if so. If not you get the option to prove the existence now by transferring one Satoshi to a calculated address.
5 | The money is going nowhere ( or to a random stranger ) - but it is so little that this should not matter. Note: it can take some time until the existence is signed by the network - especially with these small amounts ( you can increase the amount if you want faster signing ).
6 |
7 | Examples:
8 | - Prove that a certain damage existed before a given time ( rental cars, flats, .. ) by proving the existence of an image of this damage at a certain time
9 | - Prove that a certain idea ( text ) was existing before a given time ( e.g. for prior art arguments against patents )
10 |
11 | Questions/Feedback:
12 | here is a Google+ Community
13 |
14 | License:
15 | This app is licensed GPLv3 - you can find the sourcecode on GitHub .
16 |
17 | Credits;
18 |
19 | - Volker Grabsch for presenting his project Bitcoinproof at Hack&Tell and inspiring this app this way!
20 |
21 |
22 | The Tango Project for the icon
23 |
24 |
25 | - Andreas Schildbach for Bitcoin Wallet
26 |
27 |
28 | - Google for Android, Bitcoinj & Guava
29 |
30 |
31 | - https://blockexplorer.com for the API to check for address existence
32 |
33 |
34 | - Mister Schtief for donating me 0.1BTC which made the barrier low to start this
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://play.google.com/store/apps/details?id=org.ligi.satoshiproof)
2 | [](https://f-droid.org/repository/browse/?fdid=org.ligi.satoshiproof)
3 |
4 | What is this all about?
5 |
6 | Satoshi Proof is your personal notary leveraging the power of the Bitcoin network to prove existence of text or images at a certain time.
7 | First you select a picture or enter text - then the system checks if the existence is already proven and prints out the time if so. If not you get the option to prove the existence now by transferring one Satoshi to a calculated address.
8 | The money is going nowhere ( or to a random stranger ) - but it is so little that this should not matter. Note: it can take some time until the existence is signed by the network - especially with these small amounts ( you can increase the amount if you want faster signing ).
9 |
10 | Examples:
11 | - Prove that a certain damage existed before a given time ( rental cars, flats, .. ) by proving the existence of an image of this damage at a certain time
12 | - Prove that a certain idea ( text ) was existing before a given time ( e.g. for prior art arguments against patents )
13 |
14 | Questions/Feedback:
15 | there is a Google+ Community
16 |
17 | License
18 |
19 | This app is licensed GPLv3 - you can find the sourcecode on github .
20 |
21 | Credits
22 |
23 | - [Volker Grabsch](https://github.com/vog) for presenting his project [Bitcoinproof](http://vog.github.io/bitcoinproof) at [Hack Tell](http://berlinhackandtell.rocks) and inspiring this app this way!
24 | - The Tango Project for the icon
25 | - Andreas Schildbach for Bitcoin Wallet
26 | - Google for Android, Bitcoinj & Guava
27 | - https://blockexplorer.com for the API to check for address existence
28 | - Mister Schtief for donating me 0.1BTC which made the barrier low to start this
29 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/proof_fragments/ImageProofFragment.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof.proof_fragments
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.graphics.BitmapFactory
6 | import android.os.Bundle
7 | import android.provider.MediaStore
8 | import android.support.v4.app.Fragment
9 | import android.view.LayoutInflater
10 | import android.view.View
11 | import android.view.ViewGroup
12 | import kotlinx.android.synthetic.main.fragment_image.*
13 | import org.ligi.satoshiproof.FileProofController
14 | import org.ligi.satoshiproof.ImageFromIntentUriExtractor
15 | import org.ligi.satoshiproof.R
16 | import java.io.File
17 |
18 | class ImageProofFragment : Fragment(), ProofFragment {
19 |
20 | private var selectedBitmapFile: File? = null
21 |
22 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
23 | return inflater.inflate(R.layout.fragment_image, container, false)
24 | }
25 |
26 | override fun onStart() {
27 | super.onStart()
28 |
29 | selectImageButton.setOnClickListener {
30 | val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
31 | startActivityForResult(intent, ACTIVITY_SELECT_IMAGE)
32 | }
33 | }
34 |
35 | override fun proof() {
36 | FileProofController(activity).proofFile(selectedBitmapFile)
37 | }
38 |
39 | override val title = "image"
40 |
41 | override fun onActivityResult(requestCode: Int, resultCode: Int, imageReturnedIntent: Intent?) {
42 | super.onActivityResult(requestCode, resultCode, imageReturnedIntent)
43 |
44 | when (requestCode) {
45 | ACTIVITY_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
46 | //showProgressDialog();
47 | val imageExtractor = ImageFromIntentUriExtractor(activity)
48 | Thread(Runnable {
49 | selectedBitmapFile = imageExtractor.extract(imageReturnedIntent!!.data)
50 | // proofFile(bitmapFile);
51 |
52 | activity.runOnUiThread {
53 | val bm = BitmapFactory.decodeFile(selectedBitmapFile!!.absolutePath)
54 | imageView!!.setImageBitmap(bm)
55 | }
56 | }).start()
57 | }
58 | }
59 | }
60 |
61 | companion object {
62 | private val ACTIVITY_SELECT_IMAGE = 1
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/LastHashActivity.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof
2 |
3 | import android.app.AlertDialog
4 | import android.content.DialogInterface
5 | import android.os.AsyncTask
6 | import android.os.Bundle
7 | import android.support.v7.app.AppCompatActivity
8 | import android.view.MenuItem
9 | import com.androidquery.AQuery
10 | import org.json.JSONException
11 | import org.json.JSONObject
12 | import org.ligi.satoshiproof.util.Downloader.downloadURL
13 | import java.net.MalformedURLException
14 |
15 | class LastHashActivity : AppCompatActivity() {
16 |
17 | private val aQuery by lazy { AQuery(this) }
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | title = "Last Hash"
22 | setContentView(R.layout.last_hash)
23 | FetchLastHashAsyncTask().execute()
24 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
25 | }
26 |
27 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
28 |
29 | when (item.itemId) {
30 | android.R.id.home -> {
31 | finish()
32 | return true
33 | }
34 | }
35 | return super.onOptionsItemSelected(item)
36 | }
37 |
38 | internal inner class FetchLastHashAsyncTask : AsyncTask() {
39 |
40 | override fun doInBackground(vararg voids: Void): String? {
41 | try {
42 | val s = downloadURL("https://api.biteasy.com/blockchain/v1/blocks?per_page=1")
43 | if (s != null) {
44 | val jsonObject = JSONObject(s)
45 | return jsonObject.getJSONObject("data").getJSONArray("blocks").getJSONObject(0).getString("hash")
46 | }
47 |
48 | } catch (ignored: MalformedURLException) {
49 | } catch (ignored: JSONException) {
50 | }
51 |
52 | try {
53 | // fallback - was not working recently but might come back and then be a fallback
54 | return downloadURL("https://blockexplorer.com/q/latesthash")
55 | } catch (ignored: Exception) {
56 |
57 | }
58 |
59 | return null
60 | }
61 |
62 | override fun onPostExecute(s: String?) {
63 | if (s == null) {
64 | AlertDialog.Builder(this@LastHashActivity).setMessage("Could not connect to network - please try again later.").setPositiveButton(android.R.string.ok,
65 | { dialogInterface: DialogInterface, i: Int -> this@LastHashActivity.finish() }).show()
66 | return
67 | }
68 | val url = "http://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=" + s.replace("\n", "")
69 | val hashImage = aQuery.id(R.id.hash_image)
70 | hashImage.visible()
71 | hashImage.image(url, true, false) // memcache yes - disk no as we want recent stuff
72 | aQuery.id(R.id.hash_text).text(s)
73 | super.onPostExecute(s)
74 | }
75 | }
76 |
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/ProofAsyncTask.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof
2 |
3 | import android.app.Activity
4 | import android.app.AlertDialog
5 | import android.app.ProgressDialog
6 | import android.os.AsyncTask
7 | import de.schildbach.wallet.integration.android.BitcoinIntegration
8 | import org.ligi.satoshiproof.util.AddressGenerator.dataToAddressString
9 | import org.ligi.satoshiproof.util.Downloader.downloadURL
10 | import java.text.DateFormat
11 | import java.util.*
12 |
13 | class ProofAsyncTask(private val activity: Activity, private val data: ByteArray) : AsyncTask() {
14 |
15 | private var addressString: String? = null
16 | private val progressDialog by lazy { ProgressDialog(activity) }
17 |
18 | override fun onPreExecute() {
19 | super.onPreExecute()
20 | progressDialog.setMessage("Checking existence in blockchain")
21 | progressDialog.show()
22 | }
23 |
24 | override fun onProgressUpdate(vararg values: String) {
25 | progressDialog.setMessage(values[0])
26 | super.onProgressUpdate(*values)
27 | }
28 |
29 | override fun doInBackground(vararg voids: Void): String? {
30 | addressString = dataToAddressString(data)
31 | publishProgress("searching for Address: " + addressString)
32 | return downloadURL("https://blockchain.info/de/q/addressfirstseen/$addressString")
33 | }
34 |
35 | override fun onPostExecute(firstSeenDateString: String?) {
36 |
37 | if (activity.isFinishing) {
38 | return
39 | }
40 |
41 | progressDialog.dismiss()
42 |
43 | val firstSeenLong = safelyToLong(firstSeenDateString)
44 |
45 | if (firstSeenLong == null) {
46 | var message = "there where network problems - please try again later"
47 | if (firstSeenDateString != null) {
48 | message += firstSeenDateString
49 | }
50 |
51 | AlertDialog.Builder(activity).setMessage(message).setPositiveButton(android.R.string.ok, null).show()
52 | return
53 | }
54 |
55 | val alertBuilder = AlertDialog.Builder(activity)
56 | alertBuilder.setPositiveButton(android.R.string.ok, null)
57 | if (firstSeenLong == 0L) {
58 | alertBuilder.setMessage("The existence of this is not proven yet.")
59 | alertBuilder.setNeutralButton("Add Proof") { dialogInterface, i -> BitcoinIntegration.request(activity, addressString!!, 5460) }
60 |
61 | } else {
62 | val dateString = DateFormat.getDateTimeInstance().format(Date(firstSeenLong * 1000))
63 | alertBuilder.setMessage("The existence of this was proven on:" + dateString)
64 | }
65 | alertBuilder.show()
66 | super.onPostExecute(firstSeenDateString)
67 | }
68 |
69 | private fun safelyToLong(firstSeenDateString: String?): Long? {
70 | try {
71 | return java.lang.Long.parseLong(firstSeenDateString)
72 | } catch (e: NumberFormatException) {
73 | return null
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/ImageFromIntentUriExtractor.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import android.provider.MediaStore
6 | import java.io.File
7 | import java.io.IOException
8 | import java.io.InputStream
9 | import java.net.URL
10 |
11 | class ImageFromIntentUriExtractor(private val context: Context) {
12 |
13 | fun extract(selectedImage: Uri?): File? {
14 | var selectedImage = selectedImage
15 | val filePathColumn = arrayOf(MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME)
16 | val cursor = context.contentResolver.query(selectedImage!!, filePathColumn, null, null, null)
17 | // some devices (OS versions return an URI of com.android instead of com.google.android
18 | if (selectedImage.toString().startsWith("content://com.android.gallery3d.provider")) {
19 | // use the com.google provider, not the com.android provider.
20 | selectedImage = Uri.parse(selectedImage.toString().replace("com.android.gallery3d", "com.google.android.gallery3d"))
21 | }
22 | if (cursor != null) {
23 | cursor.moveToFirst()
24 | var columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA)
25 | // if it is a picasa image on newer devices with OS 3.0 and up
26 | if (selectedImage!!.toString().startsWith("content://com.google.android.gallery3d")) {
27 | columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
28 | if (columnIndex != -1) {
29 | val uriurl = selectedImage
30 | // Do this in a background thread, since we are fetching a large image from the web
31 |
32 | return getBitmap("image_file_name.jpg", uriurl)
33 |
34 | }
35 | } else { // it is a regular local image file
36 | val filePath = cursor.getString(columnIndex)
37 | cursor.close()
38 | return File(filePath)
39 | }
40 | } else if (selectedImage != null && selectedImage.toString().length > 0) {
41 | val uriurl = selectedImage
42 | // Do this in a background thread, since we are fetching a large image from the web
43 | return getBitmap("image_file_name.jpg", uriurl)
44 | }// If it is a picasa image on devices running OS prior to 3.0
45 | return null
46 | }
47 |
48 |
49 | private fun getBitmap(tag: String, url: Uri): File? {
50 | val cacheDir = context.cacheDir
51 |
52 | if (!cacheDir.exists()) {
53 | cacheDir.mkdirs()
54 | }
55 |
56 | val f = File(cacheDir, tag)
57 |
58 | try {
59 | f.outputStream().use { getInputStreamByURL(url).copyTo(it) }
60 | return f
61 | } catch (ex: IOException) {
62 | ex.printStackTrace()
63 | return null
64 | }
65 |
66 | }
67 |
68 | @Throws(IOException::class)
69 | private fun getInputStreamByURL(url: Uri): InputStream {
70 | if (url.toString().startsWith("content://com.google.android.gallery3d")) {
71 | return context.contentResolver.openInputStream(url)
72 | } else {
73 | return URL(url.toString()).openStream()
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/org/ligi/satoshiproof/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package org.ligi.satoshiproof
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import android.os.Bundle
6 | import android.support.v4.app.Fragment
7 | import android.support.v4.app.FragmentPagerAdapter
8 | import android.support.v7.app.AppCompatActivity
9 | import android.view.Menu
10 | import android.view.MenuItem
11 | import kotlinx.android.synthetic.main.activity_main.*
12 | import org.ligi.satoshiproof.proof_fragments.ImageProofFragment
13 | import org.ligi.satoshiproof.proof_fragments.ProofFragment
14 | import org.ligi.satoshiproof.proof_fragments.TextProofFragment
15 | import org.ligi.tracedroid.TraceDroid
16 | import org.ligi.tracedroid.sending.TraceDroidEmailSender
17 |
18 | class MainActivity : AppCompatActivity() {
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 |
23 | setContentView(R.layout.activity_main)
24 |
25 | proofFAB.setOnClickListener {
26 | val page = viewPager.adapter.instantiateItem(viewPager, viewPager.currentItem) as ProofFragment
27 | page.proof()
28 | }
29 |
30 | val fragmentPagerAdapter = object : FragmentPagerAdapter(supportFragmentManager) {
31 | override fun getItem(position: Int): Fragment {
32 | when (position) {
33 | 0 -> return TextProofFragment()
34 | else -> return ImageProofFragment()
35 | }
36 | }
37 |
38 | override fun getCount(): Int {
39 | return 2
40 | }
41 |
42 | override fun getPageTitle(position: Int): CharSequence {
43 | return (getItem(position) as ProofFragment).title
44 | }
45 | }
46 |
47 | viewPager.adapter = fragmentPagerAdapter
48 |
49 | sliding_tabs.setupWithViewPager(viewPager)
50 |
51 | TraceDroid.init(this)
52 | TraceDroidEmailSender.sendStackTraces("ligi@ligi.de", this)
53 |
54 | checkForMaterialToProveFromIntent()
55 | }
56 |
57 | private fun checkForMaterialToProveFromIntent() {
58 |
59 | if (Intent.ACTION_SEND == intent.action && intent.type != null) {
60 | if ("text/plain" == intent.type) {
61 | handleSendText(intent) // Handle text being sent
62 | } else {
63 | handleSendStream(intent) // Handle single image being sent
64 | }
65 | }
66 | }
67 |
68 | internal fun handleSendText(intent: Intent) {
69 | val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
70 | if (sharedText != null) {
71 | ProofAsyncTask(this@MainActivity, sharedText.toByteArray()).execute()
72 | }
73 | }
74 |
75 | internal fun handleSendStream(intent: Intent) {
76 | val imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM)
77 | if (imageUri != null) {
78 | val imageExtractor = ImageFromIntentUriExtractor(this@MainActivity)
79 | val bitmapFile = imageExtractor.extract(imageUri)
80 | FileProofController(this).proofFile(bitmapFile)
81 | }
82 | }
83 |
84 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
85 | menuInflater.inflate(R.menu.main, menu)
86 | return true
87 | }
88 |
89 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
90 | when (item.itemId) {
91 | R.id.action_help -> {
92 | HelpDialog.show(this)
93 | return true
94 | }
95 | R.id.action_hash -> {
96 | startActivity(Intent(this,LastHashActivity::class.java))
97 | return true
98 | }
99 | }
100 | return super.onOptionsItemSelected(item)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/google/bitcoin/core/Base58.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011 Google Inc.
3 |
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.bitcoin.core
18 |
19 | import java.io.UnsupportedEncodingException
20 |
21 | /**
22 | *
23 | * Base58 is a way to encode Bitcoin addresses as numbers and letters. Note that this is not the same base58 as used by
24 | * Flickr, which you may see reference to around the internet.
25 | *
26 | * Satoshi says: why base-58 instead of standard base-64 encoding?
27 | *
28 | *
29 | *
30 | * * Don't want 0OIl characters that look the same in some fonts and
31 | * could be used to create visually identical looking account numbers.
32 | * * A string with non-alphanumeric characters is not as easily accepted as an account number.
33 | * * E-mail usually won't line-break if there's no punctuation to break at.
34 | * * Doubleclicking selects the whole number as one word if it's all alphanumeric.
35 | *
36 | */
37 | object Base58 {
38 | private val ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray()
39 |
40 | private val INDEXES = IntArray(128)
41 |
42 | init {
43 | for (i in INDEXES.indices) {
44 | INDEXES[i] = -1
45 | }
46 | for (i in ALPHABET.indices) {
47 | INDEXES[ALPHABET[i].toInt()] = i
48 | }
49 | }
50 |
51 | /** Encodes the given bytes in base58. No checksum is appended. */
52 | fun encode(input: ByteArray): String {
53 | var input = input
54 | if (input.size == 0) {
55 | return ""
56 | }
57 | input = copyOfRange(input, 0, input.size)
58 | // Count leading zeroes.
59 | var zeroCount = 0
60 | while (zeroCount < input.size && input[zeroCount].toInt() == 0) {
61 | ++zeroCount
62 | }
63 | // The actual encoding.
64 | val temp = ByteArray(input.size * 2)
65 | var j = temp.size
66 |
67 | var startAt = zeroCount
68 | while (startAt < input.size) {
69 | val mod = divmod58(input, startAt)
70 | if (input[startAt].toInt() == 0) {
71 | ++startAt
72 | }
73 | temp[--j] = ALPHABET[mod.toInt()].toByte()
74 | }
75 |
76 | // Strip extra '1' if there are some after decoding.
77 | while (j < temp.size && temp[j] == ALPHABET[0].toByte()) {
78 | ++j
79 | }
80 | // Add as many leading '1' as there were leading zeros.
81 | while (--zeroCount >= 0) {
82 | temp[--j] = ALPHABET[0].toByte()
83 | }
84 |
85 | val output = copyOfRange(temp, j, temp.size)
86 | try {
87 | return String(output)
88 | } catch (e: UnsupportedEncodingException) {
89 | throw RuntimeException(e) // Cannot happen.
90 | }
91 |
92 | }
93 |
94 | //
95 | // number -> number / 58, returns number % 58
96 | //
97 | private fun divmod58(number: ByteArray, startAt: Int): Byte {
98 | var remainder = 0
99 | for (i in startAt..number.size - 1) {
100 | val digit256 = number[i].toInt() and 0xFF
101 | val temp = remainder * 256 + digit256
102 |
103 | number[i] = (temp / 58).toByte()
104 |
105 | remainder = temp % 58
106 | }
107 |
108 | return remainder.toByte()
109 | }
110 |
111 |
112 | private fun copyOfRange(source: ByteArray, from: Int, to: Int): ByteArray {
113 | val range = ByteArray(to - from)
114 | System.arraycopy(source, from, range, 0, range.size)
115 |
116 | return range
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'android-sdk-manager'
2 | apply plugin: 'com.android.application'
3 | apply plugin: 'kotlin-android'
4 | apply plugin: 'kotlin-android-extensions'
5 | apply plugin: 'kotlin-kapt'
6 | apply plugin: 'spoon'
7 |
8 | repositories {
9 | jcenter()
10 | }
11 |
12 | android {
13 | compileSdkVersion 24
14 | buildToolsVersion '24.0.2'
15 |
16 | defaultConfig {
17 | minSdkVersion 9
18 | targetSdkVersion 22
19 |
20 | versionName '1.0.4'
21 | versionCode 104
22 |
23 | archivesBaseName = "SatoshiProof-$versionName"
24 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
25 | }
26 |
27 | flavorDimensions "chain"
28 |
29 | productFlavors {
30 |
31 | onBitcoin {
32 | dimension "chain"
33 | applicationId = "org.ligi.satoshiproof"
34 | }
35 |
36 | onEthereum {
37 | // this is only a stub at the moment
38 | dimension "chain"
39 | applicationId = "org.ligi.etherproof"
40 | }
41 | }
42 |
43 | lintOptions {
44 | warning 'InvalidPackage'
45 | }
46 |
47 | packagingOptions {
48 | // needed for assertJ
49 | exclude 'asm-license.txt'
50 | exclude 'LICENSE'
51 | exclude 'NOTICE'
52 |
53 | // for instrumentation testing
54 | exclude 'LICENSE.txt'
55 |
56 | exclude 'META-INF/maven/com.google.guava/guava/pom.properties'
57 | exclude 'META-INF/maven/com.google.guava/guava/pom.xml'
58 | }
59 | // ugly but the best workaround I found for getting the permission for spoon screenshot writing to SD -Card
60 | // http://stackoverflow.com/questions/25276537/how-do-i-change-android-permission - settings - when - running - tests
61 | sourceSets {
62 | debug {
63 | manifest.srcFile 'src/androidTest/AndroidManifest.xml'
64 | }
65 | }
66 |
67 | }
68 |
69 | dependencies {
70 | kapt "com.google.dagger:dagger-compiler:$dagger_version"
71 |
72 | compile "com.google.dagger:dagger:$dagger_version"
73 | compile 'com.madgag:sc-light-jdk15on:1.47.0.3'
74 | compile 'org.apache.commons:commons-io:1.3.2'
75 | compile 'com.googlecode.android-query:android-query:0.25.9'
76 | compile "com.android.support:support-v4:$support_version"
77 | compile "com.android.support:appcompat-v7:$support_version"
78 | compile "com.android.support:design:$support_version"
79 |
80 | compile 'org.ligi:tracedroid:1.4'
81 | compile 'com.squareup.okhttp3:okhttp:3.4.1'
82 |
83 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
84 | compile "org.jetbrains.kotlin:kotlin-runtime:$kotlin_version"
85 |
86 | androidTestCompile "com.android.support:support-annotations:$support_version"
87 |
88 | androidTestCompile "com.android.support:appcompat-v7:$support_version"
89 | androidTestCompile "com.android.support:design:$support_version"
90 | androidTestCompile 'com.google.code.findbugs:jsr305:3.0.1'
91 |
92 | androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
93 | // http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception
94 | exclude group: 'com.android.support', module: 'appcompat'
95 | exclude group: 'com.android.support', module: 'support-v4'
96 | exclude group: 'javax.inject'
97 | exclude module: 'recyclerview-v7'
98 | }
99 |
100 | androidTestCompile('com.android.support.test.espresso:espresso-web:2.2.2') {
101 | // http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception
102 | exclude group: 'javax.inject'
103 | }
104 |
105 | androidTestCompile 'com.squareup.spoon:spoon-client:1.7.0'
106 | androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
107 |
108 | androidTestCompile 'org.mockito:mockito-core:1.9.5'
109 | androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
110 |
111 | androidTestCompile 'com.jraska:falcon-spoon-compat:1.0.3'
112 |
113 | testCompile "com.android.support:support-annotations:$support_version"
114 | testCompile 'com.squareup.assertj:assertj-android:1.1.1'
115 | testCompile 'junit:junit:4.12'
116 | testCompile 'org.mockito:mockito-core:1.9.5'
117 | testCompile 'org.threeten:threetenbp:1.3.2'
118 |
119 | kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version"
120 | }
121 |
122 | spoon {
123 | debug = true
124 | }
125 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
166 | cd "$(dirname "$0")"
167 | fi
168 |
169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
170 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/de/schildbach/wallet/integration/android/BitcoinIntegration.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012-2013 the original author or authors.
3 |
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.schildbach.wallet.integration.android
18 |
19 | import android.app.Activity
20 | import android.content.Context
21 | import android.content.Intent
22 | import android.content.pm.PackageManager
23 | import android.net.Uri
24 | import android.widget.Toast
25 |
26 | /**
27 | * @author Andreas Schildbach
28 | */
29 | object BitcoinIntegration {
30 | private val INTENT_EXTRA_TRANSACTION_HASH = "transaction_hash"
31 |
32 | /**
33 | * Request any amount of Bitcoins (probably a donation) from user, without feedback from the app.
34 |
35 | * @param context
36 | * * Android context
37 | * *
38 | * @param address
39 | * * Bitcoin address
40 | */
41 | fun request(context: Context, address: String) {
42 | val intent = makeIntent(address, null)
43 |
44 | start(context, intent)
45 | }
46 |
47 | /**
48 | * Request specific amount of Bitcoins from user, without feedback from the app.
49 |
50 | * @param context
51 | * * Android context
52 | * *
53 | * @param address
54 | * * Bitcoin address
55 | * *
56 | * @param amount
57 | * * Bitcoin amount in nanocoins
58 | */
59 | fun request(context: Context, address: String, amount: Long) {
60 | val intent = makeIntent(address, amount)
61 |
62 | start(context, intent)
63 | }
64 |
65 | /**
66 | * Request any amount of Bitcoins (probably a donation) from user, with feedback from the app. Result intent can be
67 | * received by overriding [android.app.Activity.onActivityResult]. Result indicates either
68 | * [Activity.RESULT_OK] or [Activity.RESULT_CANCELED]. In the success case, use
69 | * [.transactionHashFromResult] to read the transaction hash from the intent.
70 |
71 | * Warning: A success indication is no guarantee! To be on the safe side, you must drive your own Bitcoin
72 | * infrastructure and validate the transaction.
73 |
74 | * @param context
75 | * * Android context
76 | * *
77 | * @param address
78 | * * Bitcoin address
79 | */
80 | fun requestForResult(activity: Activity, requestCode: Int, address: String) {
81 | val intent = makeIntent(address, null)
82 |
83 | startForResult(activity, requestCode, intent)
84 | }
85 |
86 | /**
87 | * Request specific amount of Bitcoins from user, with feedback from the app. Result intent can be received by
88 | * overriding [android.app.Activity.onActivityResult]. Result indicates either [Activity.RESULT_OK] or
89 | * [Activity.RESULT_CANCELED]. In the success case, use [.transactionHashFromResult] to read the
90 | * transaction hash from the intent.
91 |
92 | * Warning: A success indication is no guarantee! To be on the safe side, you must drive your own Bitcoin
93 | * infrastructure and validate the transaction.
94 |
95 | * @param context
96 | * * Android context
97 | * *
98 | * @param address
99 | * * Bitcoin address
100 | */
101 | fun requestForResult(activity: Activity, requestCode: Int, address: String, amount: Long) {
102 | val intent = makeIntent(address, amount)
103 |
104 | startForResult(activity, requestCode, intent)
105 | }
106 |
107 | /**
108 | * Put transaction hash into result intent. Meant for usage by Bitcoin wallet applications.
109 |
110 | * @param result
111 | * * result intent
112 | * *
113 | * @param txHash
114 | * * transaction hash
115 | */
116 | fun transactionHashToResult(result: Intent, txHash: String) {
117 | result.putExtra(INTENT_EXTRA_TRANSACTION_HASH, txHash)
118 | result.putExtra(INTENT_EXTRA_TRANSACTION_HASH_OLD, txHash)
119 | }
120 |
121 | /**
122 | * Get transaction hash from result intent. Meant for usage by applications initiating a Bitcoin payment.
123 |
124 | * You can use this hash to request the transaction from the Bitcoin network, in order to validate. For this, you
125 | * need your own Bitcoin infrastructure though. There is no guarantee that the transaction has ever been broadcasted
126 | * to the Bitcoin network.
127 |
128 | * @param result
129 | * * result intent
130 | * *
131 | * @return transaction hash
132 | */
133 | fun transactionHashFromResult(result: Intent): String {
134 | val txHash = result.getStringExtra(INTENT_EXTRA_TRANSACTION_HASH)
135 |
136 | return txHash
137 | }
138 |
139 | private val NANOCOINS_PER_COIN = 100000000
140 |
141 | private fun makeIntent(address: String?, amount: Long?): Intent {
142 | val uri = StringBuilder("bitcoin:")
143 | if (address != null)
144 | uri.append(address)
145 | if (amount != null)
146 | uri.append("?amount=").append(String.format("%d.%08d", amount / NANOCOINS_PER_COIN, amount % NANOCOINS_PER_COIN))
147 |
148 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri.toString()))
149 |
150 | return intent
151 | }
152 |
153 | private fun start(context: Context, intent: Intent) {
154 | val pm = context.packageManager
155 | if (pm.resolveActivity(intent, 0) != null)
156 | context.startActivity(intent)
157 | else
158 | redirectToDownload(context)
159 | }
160 |
161 | private fun startForResult(activity: Activity, requestCode: Int, intent: Intent) {
162 | val pm = activity.packageManager
163 | if (pm.resolveActivity(intent, 0) != null)
164 | activity.startActivityForResult(intent, requestCode)
165 | else
166 | redirectToDownload(activity)
167 | }
168 |
169 | private fun redirectToDownload(context: Context) {
170 | Toast.makeText(context, "No Bitcoin application found.\nPlease install Bitcoin Wallet.", Toast.LENGTH_LONG).show()
171 |
172 | val marketIntent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=de.schildbach.wallet"))
173 | val binaryIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://code.google.com/p/bitcoin-wallet/downloads/list"))
174 |
175 | val pm = context.packageManager
176 | if (pm.resolveActivity(marketIntent, 0) != null)
177 | context.startActivity(marketIntent)
178 | else if (pm.resolveActivity(binaryIntent, 0) != null)
179 | context.startActivity(binaryIntent)
180 | // else out of luck
181 | }
182 |
183 | private val INTENT_EXTRA_TRANSACTION_HASH_OLD = "transaction_id"
184 | }
185 |
--------------------------------------------------------------------------------