diff --git a/README.md b/README.md index d6e840ea5..47969eb93 100644 --- a/README.md +++ b/README.md @@ -53,32 +53,6 @@ Zooming into the actual mechanism of QR-Codes (Creative Commons CC0 license - sh -### Luxury Socialism -{_recent events have turned this into_ **really bad naming**} We build a DAO for a better world. Luxury socialism is an Android application built on top of [IPv8](https://github.com/Tribler/kotlin-ipv8) and [Trustchain](https://github.com/Tribler/kotlin-ipv8/blob/master/doc/TrustChainCommunity.md), and is integrated into the [Trustchain Superapp](https://github.com/Tribler/trustchain-superapp). It is a proof-of-concept implementation of a DAO system using Trustchain and Bitcoin. Trustchain is used for communication and bookkeeping while the Bitcoin blockchain is used to have collective multi-signature wallets for each DAO. The content of the app is split up in several tabs: -* **First Time Launch**: The first time the app is launched, the user must setup his bitcoin wallet. Afterwhich the chain will sync and he is routed to the main screens. -* **My DAO's**: A list of all DAO's that the user participates in. Selecting a DAO will allow a user to create a transfer proposal from that DAO. -* **All DAO's**: A list of all discovered DAO's in the network which the user can propose to join. -* **Proposals**: A list of all proposals that the user can vote on. This can either be join proposals or proposals from someone else to transfer funds from one of the DAO's. -* **My Wallet**: Overview of the used Bitcoin wallet and the ability to chain this to another. -* **Duplicate Wallet**: In case the user has wallet files for TestNet, Production or Regtest, the user is allowed to select which one to keep. After the user selected either one, the files belonging to other network type are backed up. This, thus, ensures that the wallet is not lost. - -Currently, the Luxury Socialism app only allows Regtest, since it uses a future update of Bitcoin called Taproot. Once Taproot is officially released, the app can support TestNet or Production again. Taproot allows the DAO to scale to thousands or even millions of users. The beauty of Taproot is that it uses Schnorr signatures for each transaction. This enables transaction sizes that are equal independent of the number of users in a DAO, since each user combines their signature collaberatively into one for the whole DAO. This also ensures privacy, since it is no longer possible to tell if a transaction's is from a single person, or a million of persons. - - -
- -

- - - -

-
- -https://user-images.githubusercontent.com/23526224/116259903-85efd900-a776-11eb-93b1-384936d215c4.mp4 - - -[More about Luxury Socialism](currencyii/README.md) - ### Freedom-of-Computing App Freedom-of-Computing provides users with the ability to freely distribute and execute code in the form of APK applications on the trustchain superapp. In order to facilitate the sharing of applications, Freedom-of-Computing contains a gossiping mechanism which periodically shares local applications to other users and downloads unseen applications from other users. This sharing is conducted through a torrent peer-to-peer (P2P) network and uses the EVA Protocol as a fallback. Once the application has been downloaded by the users, they can dynamically load and execute it. The application, apart from being an .APK file, needs to have a specific format for the execution to work, the requirements/constraints are listed inside [the documentation](freedomOfComputing/README.md). diff --git a/app/build.gradle b/app/build.gradle index c69939a44..e809ab51f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,9 +123,6 @@ dependencies { api(project(':common-ethereum')) { exclude group: 'net.java.dev.jna' } - api(project(':currencyii')) { - exclude group: 'net.java.dev.jna' - } api(project(':musicdao')) { exclude group: 'net.java.dev.jna' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 71798a343..625af0b03 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -80,10 +80,6 @@ android:parentActivityName=".ui.dashboard.DashboardActivity" android:theme="@style/Theme.PeerChat" /> - - - -**NOTE** -We seperated this document in two versions. The first version of Luxury Communism uses the classic approach of Multisig transactions, the second version uses Taproot to improve it. Since everything, except for the Multisig part, is the same between the two versions, we decided to keep the first version's documentation for reference. -## Version 1 -Luxury communism is an Android application built on top of [IPv8](https://github.com/Tribler/kotlin-ipv8) and [Trustchain](https://github.com/Tribler/kotlin-ipv8/blob/master/doc/TrustChainCommunity.md), and is integrated into the [Trustchain Superapp](https://github.com/Tribler/trustchain-superapp). It is a proof-of-concept implementation of a DAO system using Trustchain and Bitcoin. Trustchain is used for communication and bookkeeping while the Bitcoin blockchain is used to have collective multi-signature wallets for each DAO. The content of the app is split up in several tabs: -* **First Time Launch**: The first time the app is launched, the user must setup his bitcoin wallet. Afterwhich the chain will sync and he is routed to the main screens. -* **My DAO's**: A list of all DAO's that the user participates in. Selecting a DAO will allow a user to create a transfer proposal from that DAO. -* **All DAO's**: A list of all discovered DAO's in the network which the user can propose to join. -* **Proposals**: A list of all proposals that the user can vote on. This can either be join proposals or proposals from someone else to transfer funds from one of the DAO's. -* **My Wallet**: Overview of the used Bitcoin wallet and the ability to chane this to another. -* **Duplicate Wallet**: In case the user has wallet files for both TestNet and Production, the user is allowed to select which one to keep. After the user selected either one, the files belonging to other network type are backed up. This, thus, ensures that the wallet is not lost. - -**First Time Launch Screens** -
- - -**My DAO Screens** -
- - -**All DAOs Screens** -
- - -**Proposals Screens** -
- - -**My Wallet Screens** -
- - -**Duplicate Wallet screen** -
- - -This document contains the project structure, underlying protocol, and known issues and limitations. - -## Table of Contents -- [luxury-communism](#luxury-communism) - - [Table of Contents](#Table-of-contents) - - [Project Structure](#Project-Structure) - - [Protocol: The DAO Trustchain Communication Protocol](#Protocol-The-DAO-Trustchain-Communication-Protocol) - - [DAO Creation](#DAO-Creation) - - [DAO Joining](#DAO-Joining) - - [DAO Transfering Funds](#DAO-Transfering-Funds) - - [Trustchain Message Types](#Trustchain-Message-Types) - - [Protocol: The DAO Trustchain Communication Protocol](#Protocol-The-DAO-Blockchain-Communication-Protocol) - - [Personal Wallet and Identity](#Personal-Wallet-and-Identity) - - [Multi-signature wallets](#Multi-signature-wallets) - - [Creation](#Creation) - - [Extension](#Extension) - - [Transfer](#Transfer) - - [Known Issues and Limitations](#Known-Issues-and-Limitations) - - [Protocol Related](#Protocol-Related) - - [Implementation related](#Implementation-related) - -## Project Structure -The project and code is split-up into two parts: Trustchain related code (using [IPv8](https://github.com/Tribler/kotlin-ipv8)) and Bitcoin-related code (using [BitcoinJ](https://bitcoinj.github.io/)). It is composed of several packages, which the most important off are: -- `coin` - The Bitcoin related code that deals with the creation, signing, and broadcast of multi-signature wallets. -- `sharedWallet` - The Trustchain related code that deals with the messages that are present in the protocol. -- `ui` - The code that handles all UI interaction. -- `CoinCommunity.kt` - The code that handles most of the Trustchain part of the protocol and calls the relevant Bitcoin code. - -## Protocol: The DAO Trustchain Communication Protocol -The communication that a DAO needs is: -- Letting the users know a DAO exists (**create**) -- Letting a DAO know that a user wants to join (**join**) - - Voting on whether users can join a DAO -- Letting the DAO users know that there is a proposal for a fund transfer (**transfer funds**) - - Voting on whether a fund transfer should go through - -We explain each of the communication points in detail. - -### DAO Creation -A user that creates a DAO performs the following actions: - -- Decide a fixed entrance fee and voting threshold for the DAO -- Pay the entrance fee, create the DAO using the Bitcoin blockchain -- Wait for the DAO creation to be successful (might take some time) -- Broadcast the created DAO on trustchain (self-signed, trustchain block type: `DAO_JOIN` and we automatically add a unique and random DAO id) - -This is all fairly straightforward except for the last point. The 'genesis' DAO trustchain block contains information related to the DAO. It also contains the serialized Bitcoin transaction that created the DAO on the Bitcoin blockchain. This transaction is needed for future transactions with this DAO. Additionally, it stores an arraylist of Bitcoin and trustchain public keys of users in the DAO. Now that the DAO exists, other users can join. That protocol is explained in the following section. - -### DAO Joining -The first step of joining is finding existing shared wallets. We use `DAO_JOIN` as blocktype that stores DAO information. The created genesis wallet explained previously is also broadcasted using a `DAO_JOIN` block. The user does the following to find existing DAOs: - -- Look in the database for `DAO_JOIN` blocks. These blocks contain information about who is in the DAO, which can be used by the user to check whether he is part of the DAO. -- Request `DAO_JOIN` type blocks from other users. At the time of implementation, this was not a function supported by the framework. Therefore, we crawl all blocks and filter on `DAO_JOIN` block types. - -A DAO can be joined by a user by following these steps: - -- Find a DAO and find the *most recent* `DAO_JOIN` block. This is done using the unique DAO id and trustchain block timestamps. The newest `DAO_JOIN` is needed to fetch the most recent serialized transaction of the DAO, to use for the join DAO transaction. -- Create a Bitcoin transaction `transactionX` for: create a new Bitcoin wallet, transfer all funds from the old DAO, and pay the entrance fee. -- Propose to the existing DAO users to join that wallet, by requesting a signature of each user for `transactionX`. -- Wait for the signatures. At the time of writing this, there is no timeout for waiting. Waiting stops when the app is closed. Additional UI can be added to streamline this process. -- The waiting stops when enough signatures are gathered based on the voting threshold of the existing DAO. Use the signatures to transfer the funds to the new DAO. -- Broadcast the new DAO to the Bitcoin blockchain, with your trustchain and Bitcoin public keys added to the DAO data. - -A new DAO is needed for convenience. Joining an existing Bitcoin shared wallet is difficult. Instead, it is easier to create a new shared wallet. - -The broadcast of the new DAO is done similarly to a genesis DAO broadcast. An important difference is that the *old DAO unique id is used*. - -#### Voting -Voting is a bit more sophisticated. A vote starts by broadcasting a self-signed trustchain block to all users. This block has a certain trustchain block type, `DAO_ASK_SIGNATURE`. We want to note that we initially sent these blocks directly to the DAO users using their trustchain public keys, using a trustchain proposal halfblock. Unfortunately, this made our protocol harder since the trustchain framework automatically transforms a proposal halfblock to a self-signed block if the sender is the same as the receiver. This resulted in unexpected behaviour, since we expect the user to receive his own vote in the same way as other users. We found that the easiest solution to send a self-signed trustchain block to all users. The self-signed proposal block contains the receiver trustchain public key. - -Available votes are gathered in the same way as existing DAOs are gathered. Look in the local database and crawl the chains of other users. The found blocks are filtered on voting blocktype and whether the receiver trustchain public key of the trustchain block transaction data is correct. - -The voter can respond by replying with a self-signed agreement block. This block has the trustchain blocktype `DAO_SIGNATURE_AGREEMENT`. This is also broadcasted to all users and contains the necessary data. In this case, the signature for the join transaction. Note that we initially did this with agreement halfblocks. We changed to self-signed blocks for the same reason as explained previously. - -### DAO Transfering Funds -Individual DAO users can propose a transfer of funds. Voting is done in the same way as explained before (subsection 'Voting'). The self-signed agreement blocks contain the signature of the transfer fund Bitcoin transaction. The protocol can be described in the following way: - -- Choose a Satoshi amount to transfer. This should be larger than 5000 and smaller than the available funds (minus the Bitcoin transaction fee) -- Find the most recent `DAO_JOIN` DAO block using the unique DAO id. This block is needed to fetch the most recent serialized Bitcoin transaction -- Create a Bitcoin transaction for this transfer, using the most recent Bitcoin transaction and transfer amount -- Ask for the signatures in the form of voting (similar to join voting: there currently is no timeout for waiting) -- Wait for enough signatures, based on the DAO voting threshold -- Gather the signatures and complete the Bitcoin transaction -- Broadcast a new `DAO_JOIN` DAO block containing the new most recent serialized Bitcoin transaction. *All* other DAO block data remains the same - -This completes the create, join and transfer protocols of the trustchain communication side. More detailed information about the Bitcoin protocol can be found in the next sections. We also provide a small section about possible future improvements. - -### Trustchain Message Types -The following section includes the specification of the Trustchain message types used. - -
- Click to expand. - -The data is stored as stringified `JSON`. The `JSON` contains data, which is displayed in the tables below. The [Gson](https://github.com/google/gson) library is used for serialization and deserialization. The block `types` are constants defined in `CoinCommunity.kt`. We prefixed the types with v1 such that newer versions can be added in the future (v2, v3, ...). - -**Declared in: `SWJoinBlockTransactionData.kt`** -**Type: `v1DAO_JOIN`** -_Used for broadcasting creation (genesis) and joining shared wallet information_ - -| Key | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | Unique id that will be generated (random 128 bit string) | -| `SW_ENTRANCE_FEE` | Long | Satoshi amount required for a single vote in the DAO | -| `SW_VOTING_THRESHOLD` | Int | 0-100, voting percentage required for decisions | -| `SW_TRUSTCHAIN_PKS` | List | Trustchain public keys of users in the wallet | -| `SW_BITCOIN_PKS` | List | Bitcoin public keys of users in the wallet | - -**Declared in: `SWSignatureAskTransactionData.kt`** -**Type: `v1DAO_ASK_SIGNATURE`** -_Used for starting a (Bitcoin) transaction proposal that requires signatures_ - -| Field Name | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | The unique shared wallet id | -| `SW_UNIQUE_PROPOSAL_ID` | String | The unique proposal id | -| `SW_TRANSACTION_SERIALIZED` | String | The serialized (Bitcoin) transaction for which a signature is asked | -| `SW_PREVIOUS_BLOCK_HASH` | String | Trustchain block hash of the latest DAO_JOIN block | -| `SW_SIGNATURES_REQUIRED` | Int | The number of required signatures (converted from shared wallet voting threshold percentage) | -| `SW_RECEIVER_PK` | String | The trustchain public key of the receiver of this signature ask block | - -**Declared in: `SWResponseSignatureTransactionData.kt`** -**Type: `v1DAO_SIGNATURE_AGREEMENT`** -_Used for storing signature data_ - -| Field Name | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | The unique shared wallet id | -| `SW_UNIQUE_PROPOSAL_ID` | String | The unique proposal id | -| `SW_SIGNATURE_SERIALIZED` | String | The serialized (Bitcoin) signature for a transaction | - -**Declared in: `SWTransferFundsAskBlockTD.kt`** -**Type: `v1DAO_TRANSFER_ASK_SIGNATURE`** - -_Used for proposing a new transfer funds transaction_ - -| Field Name | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | The unique shared wallet id | -| `SW_UNIQUE_PROPOSAL_ID` | String | The unique proposal id | -| `SW_PREVIOUS_BLOCK_HASH` | String | Trustchain block hash of the latest DAO_JOIN block | -| `SW_BITCOIN_PKS` | List | Bitcoin public keys of users in the shared wallet | -| `SW_SIGNATURES_REQUIRED` | Int | Bitcoin public keys of users in the shared wallet | -| `SW_TRANSFER_FUNDS_AMOUNT` | Long | The number of required signatures (converted from shared wallet voting threshold percentage) | -| `SW_TRANSFER_FUNDS_TARGET_SERIALIZED` | String | Bitcoin public key of the wallet that received the transfer funds amount | -| `SW_RECEIVER_PK` | String | The trustchain public key of the receiver of this transfer signature ask block | - -**Declared in: `SWTransferDoneTransactionData.kt`** -**Type: `v1DAO_TRANSFER_FINAL`** -_Used for broadcasting valid Bitcoin transactions (posted on Bitcoin, enough signatures)_ - -| Field Name | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | The unique shared wallet id | -| `SW_UNIQUE_PROPOSAL_ID` | String | The unique proposal id | -| `SW_TRANSACTION_SERIALIZED` | String | The serialized (Bitcoin) transaction that is valid and done | -| `SW_BITCOIN_PKS` | List | Bitcoin public keys of users in the shared wallet | -| `SW_TRANSFER_FUNDS_AMOUNT` | Long | Satoshi amount that is transfered | -| `SW_TRANSFER_FUNDS_TARGET_SERIALIZED` | String | Bitcoin public key of the wallet that received the transfer funds amount | - -
- -## Protocol: The DAO Blockchain Communication Protocol -As mentioned earlier, every DAO has a collective Bitcoin multi-signature wallet which it derives her utility from by allowing participants to collectively manage money. This wallet is stored on the Bitcoin blockchain in the form of a single transaction. The collective funds are locked at a single output of this transaction. This is done using [the standard](https://bitcoin.org/en/transactions-guide#standard-transactions) m-n multi-signature script. - -This section will explain the Bitcoin procedures that are called upon by the different events that occur in the Trustchain protocol. - -### Personal Wallet and Identity -Before looking at the actual protocol, we will shortly describe the personal wallet and our implementation. To use the protocol, a user needs to have a personal wallet which simply is a collection of UTXOs that the user can sign with his/her keys. - -Using BitcoinJ, a wallet is made with a random mnemonic code defined by the [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) standard. From this mmnemomic code (and several intermediate steps) a set of private and public keys pairs can be created in a deterministic fashion. - -In all procedures, the *first key pair in this set* will be used to identify a user. This key pair will be used in the multi-signature output scripts and will be used to sign transactions. In other words, the mnemonic code (indirectly) represents the identity of the user in the protocol. - -However, note that any of the key pairs in the wallet can be used to pay the entrance fees to join a DAO. - -### Multi-signature wallets -As mentioned earlier, a multi-signature wallet is a Bitcoin transaction. The funds of a multi-signature wallet are locked in an output using a corresponding [`scriptPubKey` ](https://en.bitcoin.it/wiki/Transaction#Output) which conceptually includes two data items. We will name this output the multi-sig output. -- The participants in the form of a list of Bitcoin public keys -- The minimum amount of signatures of participants needed to successfully unlock the output - - - -The multi-sig output can be spent by creating a new transaction and using the output as an input. However, to unlock the output, the minimum amount of signatures from the participant's keys are needed. The signatures in question are the new transaction signed with the corresponding private keys. Using these signatures, a valid [`scriptSig`](https://en.bitcoin.it/wiki/Transaction#Input) can be created which can unlock the multi-sig output and allow it to be spent to another address - -### Creation -Creation and broadcast of the initial multi-signature wallet are always done by a single participant and can be done without any pre-requisites. The participant essentially creates a 1-1 multi-signature wallet with the funds equal to the specified entrance fee, originating from a personal wallet. - - - -### Extension -A user joining an existing multi-signature wallet does so using a single transaction. We will name this the `new transaction` and the existing multi-signature wallet the `old transaction`. This new transaction contains the following information: - -- **Input 1:** The `entrance fee` paid by the joining user from a personal wallet. -- **Input 2:** The `old funds` of the multi-sig output of the existing multi-signature wallet. -- **Output 1:** (Optional) In case `input 1` is larger than the `entrance fee`, the `change amount` can be sent to a change address owned by the joining user. -- **Output 2:** The new locked multi-signature output. This value is equal to the `entrance fee` + `old funds`. - - The list of participants is the old participants including the joining user. - - The new minimum amount of signatures needed is set by the Trustchain Protocol. - - - -Note that the joining user does not initially possess the minimum amount of signatures needed to sign `input 2` (the multi-sig output of the old transaction). The Trustchain protocol solves this problem of voting/collecting the minimum amount of signatures needed from participants. - -Note that while this two-step protocol does introduce complexity, the protocol can now perform the extension atomically using a single transaction. This is to provide guarantees to: -- The participants (which sign) that the joining user fairly pays the entrance fee. -- The joining user that the participants don't leave his/her public key out of the new multi-sig output and steal the fee. - -### Transfer -A transfer of funds from an existing multi-signature wallet is also done in a single transaction. This can, for example, be a transfer of funds from a multi-signature wallet to a developer in question, with address `payment address` and the amount we want to send as as `payment amount`. - -The new transaction contains the following information. - -- **Input 1:** The `funds` of the multi-sig output of the multi-signature wallet. -- **Output 1:** (Optional) In case `funds` is larger than the `payment amount`, the `change amount` can be sent to a new multi-sig output which has the same details as the current multi-sig output. This is to ensure no funds get lost. - - The list of participants is the same as in the old multi-sig output. - - The new minimum amount is the same as in the old multi-sig output. -- **Output 2:** The `payment amount` going to the `payment address`. - - - -Again, to sign `input 1` (the multi-sig output of the old transaction), the Trustchain protocol solves this by voting/collecting the minimum amount of signatures needed from participants. - -The new transaction does two things simultaneously: -- Send the payment amount to a payment address. -- In case there are funds left, automatically create a new multi-sig output. - -This new transaction will thus also be regarded as the latest transaction presenting the multi-signature wallet for a particular DAO. - -## Known Issues and Limitations -The project was created in a short time-span and there are several identified issues and limitations which may pose problems. - -### Protocol Related -- **Transaction Validation:** - - Broadcasted Bitcoin transactions are serialized and send to other users through Trustchain blocks. Upon receiving a block, the transaction is assumed to be valid and successfully broadcasted. There is no external validation (e.g. to a local copy of the Bitcoin blockchain) to see if the transaction was broadcasted. - - The reason for this limitation mainly is due to implementation details regarding the BitcoinJ client, which does not (easily) allow retro-actively fetching transactions from addresses that are not being watched. -- **Privacy Considerations**: - - While users are anonymous and only known by their key pair, the use of the same key pair throughout the protocol does open up the user to be tracked by a party collecting all the broadcasted information regarding the key pair. -- **MITM attack:** With the current implementation used (IPv8/Trustchain), the system is susceptible to MITM attacks. - -### Implementation related -The most important improvement is regarding the collection of votes. A UI can be added for this, such that the users can see the current status of his proposal. Currently, the vote becomes invalid whenever another transaction is done with the same DAO, since the most recent found serialized transaction would be not the most recent. A timeout can be added to make sure the vote does not go on for an unreasonable amount of time. Both improvements need to be visible to the user in the DAO UI. - -Currently, the `DAO_JOIN` DAO blocks contain the full list of public keys. This can be improved regarding space efficiency by only including the public keys of the new user. That way, the DAO blocks only contain 2 public keys at most (trustchain and Bitcoin public key). Compared to a DAO with 1M users (or even more), this is a necessary space reduction. - -To follow up on space efficiency, the serialized Bitcoin transaction should be stored elsewhere. One solution is off-chain storage, but other solutions exist. With more users, the serialized Bitcoin transaction increases in size. This can become a problem with a lot of DAO users. - -The voting protocol can be improved to properly use proposal and agreement halfblocks. We decided not to due to the limited development time that we have. Directly sending it to other DAO users (and not the entire community) reduces the number of messages in trustchain. - -## Version 2 -The second version of Luxury Communism throws out the old way of multi-sig, but the integration and communication with trustchain is still the same. Please note that at the time of writing, Taproot was still unreleased. This means that we had to run our own Bitcoin Regtest network to make it possible to use Taproot. Production and TestNet networks are thus disabled in this version until Taproot is officially released. - -Instead of explaining what Taproot exactly is and what it can do ourselves, we will give several resources which can do a much better job at that. - -For starters, work through the following workshop: [Workshop](https://github.com/bitcoinops/taproot-workshop). This workshop explains the old way of doing multisig transactions and how Taproot improves it. Furthermore, it explains all of Taproot's features in a fun and easy way with code examples for each of the features. And a high-level explanation of Taproot and what it means for Bitcoin is given here: [Bitcoinmagazine](https://bitcoinmagazine.com/technical/taproot-coming-what-it-and-how-it-will-benefit-bitcoin). - -Now that you know what Taproot is, make sure to read the following documents: -- [Threshold Signatures](https://suredbits.com/schnorr-applications-threshold-signatures/) -- [Schnorr Applications Frost](https://suredbits.com/schnorr-applications-frost/) - -These documents explain the different ways of constructing multisig transactions with Taproot. At the time of writing, MuSig (which is n-n, meaning n users need to commit n valid signatures) was the only feasable option to implement. It was acadamically reviewed, had libraries and several code reviews and did not have any major risks exposed with it's protocol. Ideally, we want t-n, meaning only t <= n users need to commit a valid signature. We identified FROST (which can be seen as the brother of MuSig) as the best candidate at that time. But FROST was not acadamically reviewed yet and the ZCash foundation (by which the protocol was developed) did not have a security audit for the protocol yet. We found implementing FROST therefore too much of a risk, but are hoping that in the future (maybe when you read this) it is reviewed and secure for implementation. - -t-n transactions allow the DAO to fullful it's actual purpose: only a percentage of users need to approve a transaction (and share their signatures) to allow a transaction to happen. Furthermore, when configured by the original DAO creator, it also allows n-n. This means that you have full flexibility, which MuSig does not have. - -To keep track of more information regarding Taproot and its current status and activation status, check out the following link: https://taprootactivation.com/. - -## System architecture - - -### Voting functionality -We also added the functionality to vote in favour or against a proposal. One can access this voting screen via the proposal list by clicking 'See votes'. Here you see all the participants of the proposal, and you can see who already voted and what their vote was. When a new ballot comes in, it automatically updates and shows the latest polls in the UI. After enough favour votes have arrived, the screen automatically goes to the proposal list again, and the proposal has been approved. When there are not enough favour votes left to succeed the proposal, it shows a red border and a red text saying that the proposal can't be met anymore. When you voted, you see a 'Voted' stamp on the concerning proposal. - -

- - - -

-
- -https://user-images.githubusercontent.com/23526224/116259903-85efd900-a776-11eb-93b1-384936d215c4.mp4 - -### Application architecture - -The application consists of two main parts, the android app and the regtest server. As we are expanding upon the previous group we will only discuss the added regtest server and how it is integrated into the app. The app makes several connections to our regtest server. First, to add bitcoin to the wallet of a user. When the user clicks on the UI button "getBTC" we call the https://taproot.tribler.org/addBTC?address="yourwalletaddres" where the python server validates the adres and transfers 10 BTC from a "bank" account to desired wallet by calling RPC commands via bash to the bitcoind regtest server. We make sure that the "bank" account has plenty resources via a cronjob which adds bitcoins every 15 minutes to the address. This setup was chosen as we can now directly transfer funds to a user rather than first mining the blocks everytime the HTTPS requests is received, which would result in much slower respond times. The balance is updated in the UI via BitcoinJ, which connects to our regtest server directly to retrieve the wallet balance. This is the onlything BitcoinJ does: keep track of balance and UTXOs. Since BitcoinJ did not have Taproot support at the time of writing, this was the only way to make it work. We wrote our own Taproot library, which can be found in the codebase, and create transactions using this library. Hopefully, BitcoinJ has Taproot support when you read this, and if so, we highly suggest refactoring the code to use that instead of our own library. Lastly, the request to https://taproot.tribler.org/generateBlock?tx_id="transaction_in_hex" can be made. This HTTPS request is made by the app once we have collected enough signature to process the transaction. - -For the Regtest server, you can use the following [repository](https://github.com/StefanWeegink/Bitcoin-Regtest-Server). This repository contains our implementation for the server and contains several scripts to keep it running. Please read the documentation there to understand how to setup the server and how to maintain it. Also note that this server is only required when running on Regtest network, once Taproot is activated, you can use Testnet or Production network without our server. - -The server uses Letsencrypt to retrieve a certificate for HTTPS. This certificate is automatically renewed via cronjob which runs on the first day of the month. To receive the signature ACME identification is needed, while this could be done via DNS, HTTP was easier for us. So also you will find code in the python server that performs the response to an ACME challenge. The code for server (and bash history for help) is all on the github linked above. All code and scripts are fully documented. In addition, our own library, as well as all other code, is fully documented. Please check out the code with the documentation to understand the flow in the app and how everything works together. A high-level diagram of the added taproot functionalities on top of version 1 can be seen in the figure below: - -![architecture diagram](https://user-images.githubusercontent.com/25341744/118876646-ca8e1080-b8ed-11eb-95b7-14e18899f6b5.png) - -## Future work -In the codebase, we left several TODOs (especially inside WalletManager) with potential improvements and extensions to the current application. - -Next to those TODOs, we highlighted before to use FROST instead of MuSig (or another threshold scheme), port the code to Production and TestNet once possible and use BitcoinJ when they add Taproot support. Lastly, we highly recommend to further refactor the codebase. - -For future work related to the server part, please check out the corresponding repository. diff --git a/currencyii/build.gradle b/currencyii/build.gradle deleted file mode 100644 index 9e5268edb..000000000 --- a/currencyii/build.gradle +++ /dev/null @@ -1,112 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'androidx.navigation.safeargs.kotlin' -apply plugin: 'org.jlleitschuh.gradle.ktlint' - -ktlint { - version = "$ktlint_version" - android = true - outputToConsole = true - ignoreFailures = false -} - -android { - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 22 - targetSdkVersion 33 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - targetCompatibility JavaVersion.VERSION_1_8 - sourceCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - } - - buildFeatures { - viewBinding = true - } - - testOptions { - unitTests.returnDefaultValues = true - } - - namespace 'nl.tudelft.trustchain.currencyii' - lint { - abortOnError false - } -} - -dependencies { - implementation project(':ipv8-android') - implementation project(':common') - - // AndroidX - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation "androidx.recyclerview:recyclerview:1.1.0" - implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" - implementation "androidx.navigation:navigation-ui-ktx:$nav_version" - implementation "androidx.fragment:fragment-ktx:$fragment_version" - implementation "androidx.preference:preference:1.1.0" - implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" - - // Material - implementation 'com.google.android.material:material:1.1.0' - - // Kotlin - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3' - implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - implementation 'androidx.preference:preference:1.1.1' - - // Logging - implementation 'io.github.microutils:kotlin-logging:1.7.7' - - implementation 'com.github.MattSkala:recyclerview-itemadapter:0.4' - - // Testing - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' - testImplementation "io.mockk:mockk:1.9.3" - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - - // BitcoinJ - implementation 'org.bitcoinj:bitcoinj-core:0.15.10' - - // Google Core -// implementation "com.google.guava:guava:28.2-android" - - // GSON - implementation 'com.google.code.gson:gson:2.8.6' - - // HTTP requests - implementation 'com.android.volley:volley:1.2.0' - -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions.freeCompilerArgs += [ - "-opt-in=kotlin.Experimental,kotlin.ExperimentalUnsignedTypes" - ] -} diff --git a/currencyii/docs/images/blockchain_creation.png b/currencyii/docs/images/blockchain_creation.png deleted file mode 100644 index 3f01e9c4e..000000000 Binary files a/currencyii/docs/images/blockchain_creation.png and /dev/null differ diff --git a/currencyii/docs/images/blockchain_extension.png b/currencyii/docs/images/blockchain_extension.png deleted file mode 100644 index 7e6ab0a96..000000000 Binary files a/currencyii/docs/images/blockchain_extension.png and /dev/null differ diff --git a/currencyii/docs/images/blockchain_multisignature_wallets.png b/currencyii/docs/images/blockchain_multisignature_wallets.png deleted file mode 100644 index c11849d56..000000000 Binary files a/currencyii/docs/images/blockchain_multisignature_wallets.png and /dev/null differ diff --git a/currencyii/docs/images/blockchain_transfer.png b/currencyii/docs/images/blockchain_transfer.png deleted file mode 100644 index 028abb94a..000000000 Binary files a/currencyii/docs/images/blockchain_transfer.png and /dev/null differ diff --git a/currencyii/docs/images/duplicate_wallet.png b/currencyii/docs/images/duplicate_wallet.png deleted file mode 100644 index cb90115d5..000000000 Binary files a/currencyii/docs/images/duplicate_wallet.png and /dev/null differ diff --git a/currencyii/docs/images/join-wallet.png b/currencyii/docs/images/join-wallet.png deleted file mode 100644 index 222ca86f4..000000000 Binary files a/currencyii/docs/images/join-wallet.png and /dev/null differ diff --git a/currencyii/docs/images/logo.png b/currencyii/docs/images/logo.png deleted file mode 100644 index ead60c618..000000000 Binary files a/currencyii/docs/images/logo.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_1.png b/currencyii/docs/images/screenshot_1.png deleted file mode 100644 index 63b781bcb..000000000 Binary files a/currencyii/docs/images/screenshot_1.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_10.png b/currencyii/docs/images/screenshot_10.png deleted file mode 100644 index 157794cf0..000000000 Binary files a/currencyii/docs/images/screenshot_10.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_11.png b/currencyii/docs/images/screenshot_11.png deleted file mode 100644 index cb55bc24a..000000000 Binary files a/currencyii/docs/images/screenshot_11.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_12.png b/currencyii/docs/images/screenshot_12.png deleted file mode 100644 index 9ca7f8f4b..000000000 Binary files a/currencyii/docs/images/screenshot_12.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_2.png b/currencyii/docs/images/screenshot_2.png deleted file mode 100644 index e9dabaafc..000000000 Binary files a/currencyii/docs/images/screenshot_2.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_3.png b/currencyii/docs/images/screenshot_3.png deleted file mode 100644 index 8c7143bdb..000000000 Binary files a/currencyii/docs/images/screenshot_3.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_4.png b/currencyii/docs/images/screenshot_4.png deleted file mode 100644 index 57c5cfccb..000000000 Binary files a/currencyii/docs/images/screenshot_4.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_5.png b/currencyii/docs/images/screenshot_5.png deleted file mode 100644 index c0abecd2b..000000000 Binary files a/currencyii/docs/images/screenshot_5.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_6.png b/currencyii/docs/images/screenshot_6.png deleted file mode 100644 index 71f833d32..000000000 Binary files a/currencyii/docs/images/screenshot_6.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_7.png b/currencyii/docs/images/screenshot_7.png deleted file mode 100644 index b1ec6aad1..000000000 Binary files a/currencyii/docs/images/screenshot_7.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_8.png b/currencyii/docs/images/screenshot_8.png deleted file mode 100644 index 4bdd422b4..000000000 Binary files a/currencyii/docs/images/screenshot_8.png and /dev/null differ diff --git a/currencyii/docs/images/screenshot_9.png b/currencyii/docs/images/screenshot_9.png deleted file mode 100644 index 54bd30b01..000000000 Binary files a/currencyii/docs/images/screenshot_9.png and /dev/null differ diff --git a/currencyii/docs/images/start-wallet.png b/currencyii/docs/images/start-wallet.png deleted file mode 100644 index c18b876ca..000000000 Binary files a/currencyii/docs/images/start-wallet.png and /dev/null differ diff --git a/currencyii/docs/images/transfer-funds.png b/currencyii/docs/images/transfer-funds.png deleted file mode 100644 index 74dab360f..000000000 Binary files a/currencyii/docs/images/transfer-funds.png and /dev/null differ diff --git a/currencyii/docs/shared-wallet-message-types.md b/currencyii/docs/shared-wallet-message-types.md deleted file mode 100644 index a40aeb237..000000000 --- a/currencyii/docs/shared-wallet-message-types.md +++ /dev/null @@ -1,65 +0,0 @@ -# Trustchain Message Types - -The data is stored as a stringified `JSON`. The `JSON` contains data, which is displayed in the tables below. The [Gson](https://github.com/google/gson) library is used for serializion and deserialization. The block `types` are constants defined in `CoinCommunity.kt`. - -**Declared in: `SWJoinBlockTransactionData.kt`** -**Type: `SHARED_WALLET_BLOCK`** -_Used for broadcasting creation (genesis) and joining shared wallet information_ - -| Key | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | Unique id that will be generated (random 128 bit string) | -| `SW_ENTRANCE_FEE` | Long | Satoshi amount required for a single vote in the DAO | -| `SW_VOTING_THRESHOLD` | Int | 0-100, voting percentage required for decisions | -| `SW_TRUSTCHAIN_PKS` | List | Trustchain public keys of users in the wallet | -| `SW_BITCOIN_PKS` | List | Bitcoin public keys of users in the wallet | - -**Declared in: `SWTransferFundsAskBlockTD.kt`** -**Type: `TRANSFER_FUNDS_ASK_BLOCK`** - -_Used for proposing a new transfer funds transaction_ - -| Field Name | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | The unique shared wallet id | -| `SW_UNIQUE_PROPOSAL_ID` | String | The unique proposal id | -| `SW_TRANSACTION_SERIALIZED_OLD` | String | The most recent valid serialized (bitcoin) transaction | -| `SW_BITCOIN_PKS` | List | Bitcoin public keys of users in the shared wallet | -| `SW_SIGNATURES_REQUIRED` | Int | Bitcoin public keys of users in the shared wallet | -| `SW_TRANSFER_FUNDS_AMOUNT` | Long | The number of required signatures (converted from shared wallet voting threshold percentage) | -| `SW_TRANSFER_FUNDS_TARGET_SERIALIZED` | String | Bitcoin public key of the wallet that received the transfer funds amount | - -**Declared in: `SWResponseSignatureTransactionData.kt`** -**Type: `SIGNATURE_AGREEMENT_BLOCK`** -_Used for storing signature data_ - -| Field Name | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | The unique shared wallet id | -| `SW_UNIQUE_PROPOSAL_ID` | String | The unique proposal id | -| `SW_SIGNATURE_SERIALIZED` | String | The serialized (bitcoin) signature for a transaction | - -**Declared in: `SWSignatureAskTransactionData.kt`** -**Type: `SIGNATURE_ASK_BLOCK`** -_Used for starting a (bitcoin) transaction proposal that requires signatures_ - -| Field Name | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | The unique shared wallet id | -| `SW_UNIQUE_PROPOSAL_ID` | String | The unique proposal id | -| `SW_TRANSACTION_SERIALIZED` | String | The serialized (bitcoin) transaction for which a signature is asked | -| `SW_TRANSACTION_SERIALIZED_OLD` | String | The most recent valid serialized (bitcoin) transaction | -| `SW_SIGNATURES_REQUIRED` | Int | The number of required signatures (converted from shared wallet voting threshold percentage) | - -**Declared in: `SWTransferDoneTransactionData.kt`** -**Type: `TRANSFER_FINAL_BLOCK`** -_Used for broadcasting valid bitcoin transactions (posted on bitcoin, enough signatures)_ - -| Field Name | Type | Description | -| ------ | ----------- | --- | -| `SW_UNIQUE_ID` | String | The unique shared wallet id | -| `SW_UNIQUE_PROPOSAL_ID` | String | The unique proposal id | -| `SW_TRANSACTION_SERIALIZED` | String | The serialized (bitcoin) transaction that is valid and done | -| `SW_BITCOIN_PKS` | List | Bitcoin public keys of users in the shared wallet | -| `SW_TRANSFER_FUNDS_AMOUNT` | Long | Satoshi amount that is transfered | -| `SW_TRANSFER_FUNDS_TARGET_SERIALIZED` | String | Bitcoin public key of the wallet that received the transfer funds amount | diff --git a/currencyii/docs/shared-wallet-protocol.md b/currencyii/docs/shared-wallet-protocol.md deleted file mode 100644 index 339cd7e24..000000000 --- a/currencyii/docs/shared-wallet-protocol.md +++ /dev/null @@ -1,25 +0,0 @@ -# Wallet Protocol - -Data is stored in the transaction data map of trustchain blocks. The `message` key is assigned to a string containing JSON data. Due to the limited time we have, our initial idea is to store as many information as possible, to make the entire process as easy as possible. Less data storage is possible (see [Future improvements](#Possible-future-improvements)) - -Shared wallet and transfer funds proposals are identified with a unique identifier. This identifier is a randomly generated 128 bit string. - -## Start a shared wallet - -![](images/start-wallet.png) - -## Join an existing shared wallet - -![](images/join-wallet.png) - -## Transfer funds from an existing shared wallet - -![](images/transfer-funds.png) - -## Possible future improvements - -We discuss some of the improvements that came to light during the development process. - -- The genesis shared wallet block is the only block that needs to store the transaction fee and voting threshold, since we do not allow the DAO users to change those values. -- Each block that indicates that a new user joined the shared wallet (after paying the fees and possible other requirements) only needs to contain the public bitcoin and trustchain key of the new user. The other keys can be derived from the previous shared wallet blocks, based on the unique trustchain shared wallet ID. -- ... much more probably diff --git a/currencyii/proguard-rules.pro b/currencyii/proguard-rules.pro deleted file mode 100644 index f1b424510..000000000 --- a/currencyii/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/currencyii/src/androidTest/java/nl/tudelft/trustchain/currencyii/ExampleInstrumentedTest.kt b/currencyii/src/androidTest/java/nl/tudelft/trustchain/currencyii/ExampleInstrumentedTest.kt deleted file mode 100644 index 1ef68272d..000000000 --- a/currencyii/src/androidTest/java/nl/tudelft/trustchain/currencyii/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package nl.tudelft.trustchain.currencyii - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("nl.tudelft.peerchat", appContext.packageName) - } -} diff --git a/currencyii/src/main/AndroidManifest.xml b/currencyii/src/main/AndroidManifest.xml deleted file mode 100644 index 59f35b226..000000000 --- a/currencyii/src/main/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/CoinCommunity.kt b/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/CoinCommunity.kt deleted file mode 100644 index 7de16ee88..000000000 --- a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/CoinCommunity.kt +++ /dev/null @@ -1,403 +0,0 @@ -package nl.tudelft.trustchain.currencyii - -import android.app.Activity -import android.content.Context -import nl.tudelft.ipv8.Community -import nl.tudelft.ipv8.android.IPv8Android -import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock -import nl.tudelft.ipv8.attestation.trustchain.TrustChainCommunity -import nl.tudelft.ipv8.attestation.trustchain.TrustChainTransaction -import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.ipv8.util.toHex -import nl.tudelft.trustchain.currencyii.sharedWallet.* -import nl.tudelft.trustchain.currencyii.util.DAOCreateHelper -import nl.tudelft.trustchain.currencyii.util.DAOJoinHelper -import nl.tudelft.trustchain.currencyii.util.DAOTransferFundsHelper - -@Suppress("UNCHECKED_CAST") -class CoinCommunity constructor(serviceId: String = "02313685c1912a141279f8248fc8db5899c5df5b") : Community() { - override val serviceId = serviceId - - private fun getTrustChainCommunity(): TrustChainCommunity { - return IPv8Android.getInstance().getOverlay() - ?: throw IllegalStateException("TrustChainCommunity is not configured") - } - - private val daoCreateHelper = DAOCreateHelper() - private val daoJoinHelper = DAOJoinHelper() - private val daoTransferFundsHelper = DAOTransferFundsHelper() - - /** - * Create a bitcoin genesis wallet and broadcast the result on trust chain. - * The bitcoin transaction may take some time to finish. - * @throws - exception if something goes wrong with creating or broadcasting bitcoin transaction. - * @param entranceFee - Long, the entrance fee for joining the DAO. - * @param threshold - Int, the percentage of members that need to vote before allowing someone in the DAO. - */ - fun createBitcoinGenesisWallet( - entranceFee: Long, - threshold: Int, - context: Context - ): SWJoinBlockTransactionData { - return daoCreateHelper.createBitcoinGenesisWallet( - myPeer, - entranceFee, - threshold, - context - ) - } - - /** - * 2.1 Send a proposal on the trust chain to join a shared wallet and to collect signatures. - * The proposal is a serialized bitcoin join transaction. - * **NOTE** the latest walletBlockData should be given, otherwise the serialized transaction is invalid. - * @param walletBlock - the latest (that you know of) shared wallet block. - */ - fun proposeJoinWallet( - walletBlock: TrustChainBlock - ): SWSignatureAskTransactionData { - return daoJoinHelper.proposeJoinWallet(myPeer, walletBlock) - } - - /** - * 2.2 Commit the join wallet transaction on the bitcoin blockchain and broadcast the result on trust chain. - * - * Note: - * There should be enough sufficient signatures, based on the multisig wallet data. - * @throws - exceptions if something goes wrong with creating or broadcasting bitcoin transaction. - * @param walletBlockData - TrustChainTransaction, describes the wallet that is joined - * @param blockData - SWSignatureAskBlockTD, the block where the other users are voting on - * @param responses - the positive responses for your request to join the wallet - */ - fun joinBitcoinWallet( - walletBlockData: TrustChainTransaction, - blockData: SWSignatureAskBlockTD, - responses: List, - context: Context - ) { - daoJoinHelper.joinBitcoinWallet( - myPeer, - walletBlockData, - blockData, - responses, - context - ) - } - - /** - * 3.1 Send a proposal block on trustchain to ask for the signatures. - * Assumed that people agreed to the transfer. - * @param walletBlock - TrustChainBlock, describes the wallet where the transfer is from - * @param receiverAddressSerialized - String, the address where the transaction needs to go - * @param satoshiAmount - Long, the amount that needs to be transferred - * @return the proposal block - */ - fun proposeTransferFunds( - walletBlock: TrustChainBlock, - receiverAddressSerialized: String, - satoshiAmount: Long - ): SWTransferFundsAskTransactionData { - return daoTransferFundsHelper.proposeTransferFunds( - myPeer, - walletBlock, - receiverAddressSerialized, - satoshiAmount - ) - } - - /** - * 3.2 Transfer funds from an existing shared wallet to a third-party. Broadcast bitcoin transaction. - * @param walletData - SWJoinBlockTD, the data about the wallet when joining the wallet - * @param walletBlockData - TrustChainTransaction, describes the wallet where the transfer is from - * @param blockData - SWTransferFundsAskBlockTD, the block where the other users are voting on - * @param responses - List, the list with positive responses on the voting - * @param receiverAddress - String, the address where the transfer needs to go - * @param satoshiAmount - Long, the amount that needs to be transferred - */ - fun transferFunds( - walletData: SWJoinBlockTD, - walletBlockData: TrustChainTransaction, - blockData: SWTransferFundsAskBlockTD, - responses: List, - receiverAddress: String, - satoshiAmount: Long, - context: Context, - activity: Activity - ) { - daoTransferFundsHelper.transferFunds( - myPeer, - walletData, - walletBlockData, - blockData, - responses, - receiverAddress, - satoshiAmount, - context, - activity - ) - } - - /** - * Discover shared wallets that you can join, return the latest blocks that the user knows of. - */ - fun discoverSharedWallets(): List { - val swBlocks = getTrustChainCommunity().database.getBlocksWithType(JOIN_BLOCK) - return swBlocks - .distinctBy { SWJoinBlockTransactionData(it.transaction).getData().SW_UNIQUE_ID } - .map { fetchLatestSharedWalletBlock(it, swBlocks) ?: it } - } - - /** - * Discover shared wallets that you can join, return the latest (known) blocks - * Fetch the latest block associated with a shared wallet. - * swBlockHash - the hash of one of the blocks associated with a shared wallet. - */ - fun fetchLatestSharedWalletBlock(swBlockHash: ByteArray): TrustChainBlock? { - val swBlock = getTrustChainCommunity().database.getBlockWithHash(swBlockHash) - ?: return null - val swBlocks = getTrustChainCommunity().database.getBlocksWithType(JOIN_BLOCK) - return fetchLatestSharedWalletBlock(swBlock, swBlocks) - } - - /** - * Fetch the latest shared wallet block, based on a given block 'block'. - * The unique shared wallet id is used to find the most recent block in - * the 'sharedWalletBlocks' list. - */ - private fun fetchLatestSharedWalletBlock( - block: TrustChainBlock, - fromBlocks: List - ): TrustChainBlock? { - if (block.type != JOIN_BLOCK) { - return null - } - val walletId = SWJoinBlockTransactionData(block.transaction).getData().SW_UNIQUE_ID - - return fromBlocks - .filter { it.type == JOIN_BLOCK } // make sure the blocks have the correct type! - .filter { SWJoinBlockTransactionData(it.transaction).getData().SW_UNIQUE_ID == walletId } - .maxByOrNull { it.timestamp.time } - } - - /** - * Fetch the shared wallet blocks that you are part of, based on your trustchain PK. - */ - fun fetchLatestJoinedSharedWalletBlocks(): List { - return discoverSharedWallets().filter { - val blockData = SWJoinBlockTransactionData(it.transaction).getData() - val userTrustchainPks = blockData.SW_TRUSTCHAIN_PKS - userTrustchainPks.contains(myPeer.publicKey.keyToBin().toHex()) - } - } - - /** - * Get the public key of the one that is receiving the request - * @return string - */ - private fun fetchSignatureRequestReceiver(block: TrustChainBlock): String { - if (block.type == SIGNATURE_ASK_BLOCK) { - return SWSignatureAskTransactionData(block.transaction).getData().SW_RECEIVER_PK - } - - if (block.type == TRANSFER_FUNDS_ASK_BLOCK) { - return SWTransferFundsAskTransactionData(block.transaction).getData().SW_RECEIVER_PK - } - - return "invalid-pk" - } - - fun fetchSignatureRequestProposalId(block: TrustChainBlock): String { - if (block.type == SIGNATURE_ASK_BLOCK) { - return SWSignatureAskTransactionData(block.transaction).getData().SW_UNIQUE_PROPOSAL_ID - } - if (block.type == TRANSFER_FUNDS_ASK_BLOCK) { - return SWTransferFundsAskTransactionData(block.transaction).getData() - .SW_UNIQUE_PROPOSAL_ID - } - - return "invalid-proposal-id" - } - - /** - * Fetch all join and transfer proposals in descending timestamp order. - * Speed assumption: each proposal has a unique proposal ID (distinct by unique proposal id, - * without taking the unique wallet id into account). - */ - fun fetchProposalBlocks(): List { - val joinProposals = getTrustChainCommunity().database.getBlocksWithType(SIGNATURE_ASK_BLOCK) - val transferProposals = getTrustChainCommunity().database.getBlocksWithType( - TRANSFER_FUNDS_ASK_BLOCK - ) - return joinProposals - .union(transferProposals) - .filter { - fetchSignatureRequestReceiver(it) == myPeer.publicKey.keyToBin() - .toHex() && !checkEnoughFavorSignatures(it) - } - .distinctBy { fetchSignatureRequestProposalId(it) } - .sortedByDescending { it.timestamp } - } - - /** - * Fetch all DAO blocks that contain a signature. These blocks are the response of a signature request. - * Signatures are fetched from [SIGNATURE_AGREEMENT_BLOCK] type blocks. - */ - fun fetchProposalResponses(walletId: String, proposalId: String): List { - return getTrustChainCommunity().database.getBlocksWithType(SIGNATURE_AGREEMENT_BLOCK) - .filter { - val blockData = SWResponseSignatureTransactionData(it.transaction) - blockData.matchesProposal(walletId, proposalId) - }.map { - SWResponseSignatureTransactionData(it.transaction).getData() - } - } - - /** - * Fetch all DAO blocks that contain a negative signature. These blocks are the response of a negative signature request. - * Signatures are fetched from [SIGNATURE_AGREEMENT_NEGATIVE_BLOCK] type blocks. - */ - fun fetchNegativeProposalResponses(walletId: String, proposalId: String): List { - return getTrustChainCommunity().database.getBlocksWithType( - SIGNATURE_AGREEMENT_NEGATIVE_BLOCK - ) - .filter { - val blockData = SWResponseNegativeSignatureTransactionData(it.transaction) - blockData.matchesProposal(walletId, proposalId) - }.map { - SWResponseNegativeSignatureTransactionData(it.transaction).getData() - } - } - - /** - * Given a shared wallet proposal block, calculate the signature and respond with a trust chain block. - */ - fun joinAskBlockReceived( - block: TrustChainBlock, - myPublicKey: ByteArray, - votedInFavor: Boolean, - context: Context - ) { - val latestHash = SWSignatureAskTransactionData(block.transaction).getData() - .SW_PREVIOUS_BLOCK_HASH - val mostRecentSWBlock = fetchLatestSharedWalletBlock(latestHash.hexToBytes()) - ?: throw IllegalStateException("Most recent DAO block not found") - val joinBlock = SWJoinBlockTransactionData(mostRecentSWBlock.transaction).getData() - val oldTransaction = joinBlock.SW_TRANSACTION_SERIALIZED - - DAOJoinHelper.joinAskBlockReceived(oldTransaction, block, joinBlock, myPublicKey, votedInFavor, context) - } - - /** - * Given a shared wallet transfer fund proposal block, calculate the signature and respond with a trust chain block. - */ - fun transferFundsBlockReceived( - block: TrustChainBlock, - myPublicKey: ByteArray, - votedInFavor: Boolean, - context: Context - ) { - val latestHash = SWTransferFundsAskTransactionData(block.transaction).getData() - .SW_PREVIOUS_BLOCK_HASH - val mostRecentSWBlock = fetchLatestSharedWalletBlock(latestHash.hexToBytes()) - ?: throw IllegalStateException("Most recent DAO block not found") - val transferBlock = SWTransferDoneTransactionData(mostRecentSWBlock.transaction).getData() - val oldTransaction = transferBlock.SW_TRANSACTION_SERIALIZED - - DAOTransferFundsHelper.transferFundsBlockReceived( - oldTransaction, - block, - transferBlock, - myPublicKey, - votedInFavor, - context - ) - } - - /** - * Given a proposal, check if the number of signatures required is met - */ - fun checkEnoughFavorSignatures(block: TrustChainBlock): Boolean { - if (block.type == SIGNATURE_ASK_BLOCK) { - val data = SWSignatureAskTransactionData(block.transaction).getData() - val signatures = - ArrayList( - fetchProposalResponses( - data.SW_UNIQUE_ID, - data.SW_UNIQUE_PROPOSAL_ID - ) - ) - return data.SW_SIGNATURES_REQUIRED <= signatures.size - } - if (block.type == TRANSFER_FUNDS_ASK_BLOCK) { - val data = SWTransferFundsAskTransactionData(block.transaction).getData() - val signatures = - ArrayList( - fetchProposalResponses( - data.SW_UNIQUE_ID, - data.SW_UNIQUE_PROPOSAL_ID - ) - ) - return data.SW_SIGNATURES_REQUIRED <= signatures.size - } - - return false - } - - /** - * Check if the number of required votes are more than the number of possible votes minus the negative votes. - */ - fun canWinJoinRequest(data: SWSignatureAskBlockTD): Boolean { - val sw = - discoverSharedWallets().filter { b -> SWJoinBlockTransactionData(b.transaction).getData().SW_UNIQUE_ID == data.SW_UNIQUE_ID }[0] - val swData = SWJoinBlockTransactionData(sw.transaction).getData() - val againstSignatures = ArrayList( - fetchNegativeProposalResponses( - data.SW_UNIQUE_ID, - data.SW_UNIQUE_PROPOSAL_ID - ) - ) - val totalVoters = swData.SW_BITCOIN_PKS - val requiredVotes = data.SW_SIGNATURES_REQUIRED - - return requiredVotes <= totalVoters.size - againstSignatures.size - } - - /** - * Check if the number of required votes are more than the number of possible votes minus the negative votes. - */ - fun canWinTransferRequest(data: SWTransferFundsAskBlockTD): Boolean { - val againstSignatures = ArrayList( - fetchNegativeProposalResponses( - data.SW_UNIQUE_ID, - data.SW_UNIQUE_PROPOSAL_ID - ) - ) - val totalVoters = data.SW_BITCOIN_PKS - val requiredVotes = data.SW_SIGNATURES_REQUIRED - - return requiredVotes <= totalVoters.size - againstSignatures.size - } - - companion object { - // Default maximum wait timeout for bitcoin transaction broadcasts in seconds - const val DEFAULT_BITCOIN_MAX_TIMEOUT: Long = 10 - - // Block type for join DAO blocks - const val JOIN_BLOCK = "v1DAO_JOIN" - - // Block type for transfer funds (from a DAO) - const val TRANSFER_FINAL_BLOCK = "v1DAO_TRANSFER_FINAL" - - // Block type for basic signature requests - const val SIGNATURE_ASK_BLOCK = "v1DAO_ASK_SIGNATURE" - - // Block type for transfer funds signature requests - const val TRANSFER_FUNDS_ASK_BLOCK = "v1DAO_TRANSFER_ASK_SIGNATURE" - - // Block type for responding to a signature request with a (should be valid) signature - const val SIGNATURE_AGREEMENT_BLOCK = "v1DAO_SIGNATURE_AGREEMENT" - - // Block type for responding with a negative vote to a signature request with a signature - const val SIGNATURE_AGREEMENT_NEGATIVE_BLOCK = "v1DAO_SIGNATURE_AGREEMENT_NEGATIVE" - } -} diff --git a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/CurrencyIIMainActivity.kt b/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/CurrencyIIMainActivity.kt deleted file mode 100644 index 4112e34f4..000000000 --- a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/CurrencyIIMainActivity.kt +++ /dev/null @@ -1,44 +0,0 @@ -package nl.tudelft.trustchain.currencyii - -import android.util.Log -import androidx.navigation.findNavController -import androidx.navigation.ui.AppBarConfiguration -import nl.tudelft.trustchain.common.BaseActivity - -class CurrencyIIMainActivity : BaseActivity() { - private var topLevelDestinationIds = setOf(R.id.blockchainDownloadFragment, R.id.daoLoginChoice) - - override val navigationGraph = R.navigation.nav_graph - override val bottomNavigationMenu = R.menu.currencyii_bottom_navigation_menu - - override val appBarConfiguration by lazy { - AppBarConfiguration(topLevelDestinationIds) - } - - override fun onBackPressed() { - val currentFragment = findNavController(R.id.navHostFragment).currentDestination - val currentDestinationId = currentFragment?.id - if (topLevelDestinationIds.contains(currentDestinationId)) { - // Do not allow hardware back press on any top level destinations. - Log.i("Coin", "Hardware back press not allowed on top level destinations.") - } else { - super.onBackPressed() - } - } - - fun addTopLevelDestinationId(id: Int) { - val topLevelDestinationIdsList = topLevelDestinationIds.toMutableList() - if (!topLevelDestinationIdsList.contains(id)) { - topLevelDestinationIdsList.add(id) - topLevelDestinationIds = topLevelDestinationIdsList.toSet() - } - } - - fun removeTopLevelDestinationId(id: Int) { - val topLevelDestinationIdsList = topLevelDestinationIds.toMutableList() - if (topLevelDestinationIdsList.contains(id)) { - topLevelDestinationIdsList.remove(id) - topLevelDestinationIds = topLevelDestinationIdsList.toSet() - } - } -} diff --git a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/sharedWallet/SWTransferDoneTransactionData.kt b/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/sharedWallet/SWTransferDoneTransactionData.kt deleted file mode 100644 index f33f4d76b..000000000 --- a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/sharedWallet/SWTransferDoneTransactionData.kt +++ /dev/null @@ -1,76 +0,0 @@ -package nl.tudelft.trustchain.currencyii.sharedWallet - -import com.google.gson.Gson -import com.google.gson.JsonObject -import nl.tudelft.ipv8.attestation.trustchain.TrustChainTransaction -import nl.tudelft.trustchain.currencyii.CoinCommunity - -data class SWTransferDoneBlockTD( - var SW_UNIQUE_ID: String, - var SW_UNIQUE_PROPOSAL_ID: String, - var SW_TRANSACTION_SERIALIZED: String, - var SW_TRUSTCHAIN_PKS: ArrayList, - var SW_BITCOIN_PKS: ArrayList, - var SW_NONCE_PKS: ArrayList, - var SW_TRANSFER_FUNDS_AMOUNT: Long, - var SW_TRANSFER_FUNDS_TARGET_SERIALIZED: String -) - -class SWTransferDoneTransactionData(data: JsonObject) : SWBlockTransactionData( - data, CoinCommunity.TRANSFER_FINAL_BLOCK -) { - fun getData(): SWTransferDoneBlockTD { - return Gson().fromJson(getJsonString(), SWTransferDoneBlockTD::class.java) - } - - fun addTrustChainPk(publicKey: String) { - val data = getData() - data.SW_TRUSTCHAIN_PKS.add(publicKey) - jsonData = SWUtil.objectToJsonObject(data) - } - - fun addBitcoinPk(publicKey: String) { - val data = getData() - data.SW_BITCOIN_PKS.add(publicKey) - jsonData = SWUtil.objectToJsonObject(data) - } - - fun addNoncePk(publicKey: String) { - val data = getData() - data.SW_NONCE_PKS.add(publicKey) - jsonData = SWUtil.objectToJsonObject(data) - } - - fun setTransactionSerialized(serializedTransaction: String) { - val data = getData() - data.SW_TRANSACTION_SERIALIZED = serializedTransaction - jsonData = SWUtil.objectToJsonObject(data) - } - - constructor( - uniqueId: String, - transactionSerialized: String, - satoshiAmount: Long, - trustChainPks: ArrayList, - bitcoinPks: ArrayList, - noncePks: ArrayList, - transferFundsAddressSerialized: String, - uniqueProposalId: String = SWUtil.randomUUID() - ) : this( - SWUtil.objectToJsonObject( - SWTransferDoneBlockTD( - uniqueId, - uniqueProposalId, - transactionSerialized, - trustChainPks, - bitcoinPks, - noncePks, - satoshiAmount, - transferFundsAddressSerialized - ) - - ) - ) - - constructor(transaction: TrustChainTransaction) : this(SWUtil.parseTransaction(transaction)) -} diff --git a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/BaseFragment.kt b/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/BaseFragment.kt deleted file mode 100644 index 5437c3510..000000000 --- a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/BaseFragment.kt +++ /dev/null @@ -1,45 +0,0 @@ -package nl.tudelft.trustchain.currencyii.ui - -import android.view.View -import androidx.annotation.LayoutRes -import androidx.fragment.app.Fragment -import nl.tudelft.ipv8.IPv8 -import nl.tudelft.ipv8.android.IPv8Android -import nl.tudelft.ipv8.attestation.trustchain.TrustChainCommunity -import nl.tudelft.trustchain.common.DemoCommunity -import nl.tudelft.trustchain.currencyii.CoinCommunity -import nl.tudelft.trustchain.currencyii.R -import nl.tudelft.trustchain.currencyii.TrustChainHelper - -abstract class BaseFragment(@LayoutRes contentLayoutId: Int = 0) : Fragment(contentLayoutId) { - protected val trustchain: TrustChainHelper by lazy { - TrustChainHelper(getTrustChainCommunity()) - } - - protected fun getIpv8(): IPv8 { - return IPv8Android.getInstance() - } - - protected fun getTrustChainCommunity(): TrustChainCommunity { - return getIpv8().getOverlay() - ?: throw IllegalStateException("TrustChainCommunity is not configured") - } - - protected fun getDemoCommunity(): DemoCommunity { - return getIpv8().getOverlay() - ?: throw IllegalStateException("DemoCommunity is not configured") - } - - fun getCoinCommunity(): CoinCommunity { - return getIpv8().getOverlay() - ?: throw IllegalStateException("CoinCommunity is not configured") - } - - protected fun hideNavBar() { - requireActivity().findViewById(R.id.bottomNavigation).visibility = View.GONE - } - - protected fun showNavBar() { - requireActivity().findViewById(R.id.bottomNavigation).visibility = View.VISIBLE - } -} diff --git a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/bitcoin/BitcoinFragment.kt b/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/bitcoin/BitcoinFragment.kt deleted file mode 100644 index c7032853f..000000000 --- a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/bitcoin/BitcoinFragment.kt +++ /dev/null @@ -1,329 +0,0 @@ -package nl.tudelft.trustchain.currencyii.ui.bitcoin - -import android.content.ClipData -import android.content.ClipboardManager -import android.graphics.Color -import android.graphics.PorterDuff -import android.os.Bundle -import android.os.Handler -import android.util.Log -import android.view.* -import android.widget.Toast -import androidx.core.content.ContextCompat.getSystemService -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import kotlinx.android.synthetic.main.bitcoin_networks.* -import kotlinx.android.synthetic.main.fragment_bitcoin.* -import nl.tudelft.trustchain.currencyii.R -import nl.tudelft.trustchain.currencyii.coin.* -import nl.tudelft.trustchain.currencyii.ui.BaseFragment -import org.bitcoinj.core.Coin -import org.bitcoinj.core.NetworkParameters -import org.bitcoinj.wallet.Wallet -import java.net.HttpURLConnection -import java.net.URL -import java.util.concurrent.* -import kotlin.Exception - -const val BALANCE_THRESHOLD = "5" -/** - * A simple [Fragment] subclass. - * Use the [BitcoinFragment.newInstance] factory method to - * create an instance of this fragment. - */ -class BitcoinFragment : - BaseFragment(R.layout.fragment_bitcoin), - ImportKeyDialog.ImportKeyDialogListener { - - private var getBitcoinPressed = false - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - initClickListeners() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - } - - override fun onResume() { - super.onResume() - Log.i("Coin", "Resuming") - refresh() - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - // TODO: Try catch not too nice. - try { - WalletManagerAndroid.getInstance() - } catch (e: Exception) { - Log.w("Coin", "Wallet Manager not initialized.") - return - } - - inflater.inflate(R.menu.bitcoin_options, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.item_bitcoin_blockchain_download -> { - Log.i("Coin", "Navigating from BitcoinFragment to BlockchainDownloadFragment") - findNavController().navigate( - BitcoinFragmentDirections.actionBitcoinFragmentToBlockchainDownloadFragment( - R.id.bitcoinFragment - ) - ) - true - } - R.id.item_bitcoin_wallet_settings -> { - Log.i("Coin", "Navigating from BitcoinFragment to DaoImportOrCreate") - findNavController().navigate(BitcoinFragmentDirections.actionBitcoinFragmentToDaoImportOrCreate()) - true - } - else -> super.onOptionsItemSelected(item) - } - } - - private fun initClickListeners() { - val walletManager = WalletManagerAndroid.getInstance() - button_copy_public_address.setOnClickListener { - copyToClipboard(walletManager.protocolAddress().toString()) - } - - button_copy_wallet_seed.setOnClickListener { - val seed = walletManager.toSeed() - copyToClipboard("${seed.seed}, ${seed.creationTime}") - } - - button_copy_bitcoin_public_key.setOnClickListener { - copyToClipboard(walletManager.networkPublicECKeyHex()) - } - - bitcoin_refresh_swiper.setOnRefreshListener { - this.refresh() - - @Suppress("DEPRECATION") - Handler().postDelayed( - { - try { - bitcoin_refresh_swiper.isRefreshing = false - } catch (e: Exception) { - } - }, - 1500 - ) - } - - // If the user has too little bitcoin, he can press the button to get more, - // the amount that is added is hardcoded on the server somewhere. - if (checkTooMuchBitcoin()) { - disableGetBitcoinButton() - } else { - enableGetBitcoinButton() - } - } - - /** - * If the balance on your wallet is higher than BALANCE_THRESHOLD than return true, otherwise false. - * @return if the balance is too large - */ - private fun checkTooMuchBitcoin(): Boolean { - val walletManager = WalletManagerAndroid.getInstance() - val balance = walletManager.kit.wallet().getBalance(Wallet.BalanceType.ESTIMATED) - return balance.isGreaterThan(Coin.parseCoin(BALANCE_THRESHOLD)) - } - - /** - * Disable the get BTC button, sets the color to gray and changes the onclick listener - */ - @Suppress("DEPRECATION") - private fun disableGetBitcoinButton() { - add_btc.isClickable = false - add_btc.background.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY) - add_btc.setOnClickListener { - Toast.makeText(this.requireContext(), "You already have enough bitcoin don't you think?", Toast.LENGTH_SHORT).show() - } - } - - /** - * Enable the get BTC button, set the color and the onclick listener correctly. - */ - private fun enableGetBitcoinButton() { - val walletManager = WalletManagerAndroid.getInstance() - add_btc.isClickable = true - add_btc.setOnClickListener { - if (!getBitcoinPressed) { - getBitcoinPressed = true - - if (!addBTC(walletManager.protocolAddress().toString())) { - Log.e("Coin", "The server response is failing") - Toast.makeText(this.requireContext(), "Something went wrong, please delete system32", Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(this.requireContext(), "Successfully added 10 BTC", Toast.LENGTH_SHORT).show() - - Thread.sleep(1000) - - Toast.makeText(this.requireContext(), "It can take up to a minute to register in your balance", Toast.LENGTH_SHORT).show() - this.refresh(true) - } - } else { - Toast.makeText(this.requireContext(), "You are already given an amount of BTC, please wait a little longer", Toast.LENGTH_SHORT).show() - } - } - } - - private fun refresh(animation: Boolean? = false) { - if (animation!!) { - bitcoin_refresh_swiper.isRefreshing = true - - @Suppress("DEPRECATION") - Handler().postDelayed( - { - try { - bitcoin_refresh_swiper.isRefreshing = false - } catch (e: Exception) { - } - }, - 1500 - ) - } - - if (!WalletManagerAndroid.isRunning) { - return - } - - val walletManager = WalletManagerAndroid.getInstance() - - walletBalance.text = walletManager.kit.wallet().balance.toFriendlyString() - walletEstimatedBalance.text = walletManager.kit.wallet().getBalance(Wallet.BalanceType.ESTIMATED).toFriendlyString() - chosenNetwork.text = when (walletManager.params.id) { - NetworkParameters.ID_MAINNET -> "Production Network" - NetworkParameters.ID_REGTEST -> "RegTest Network" - NetworkParameters.ID_TESTNET -> "TestNet Network" - else -> "Unknown Network selected" - } - val seed = walletManager.toSeed() - walletSeed.text = "${seed.seed}, ${seed.creationTime}" - yourPublicHex.text = walletManager.networkPublicECKeyHex() - protocolKey.text = walletManager.protocolAddress().toString() - - if (checkTooMuchBitcoin()) { - disableGetBitcoinButton() - } else { - enableGetBitcoinButton() - } - - requireActivity().invalidateOptionsMenu() - } - - /** - * Add bitcoin to the wallet - * @param address - The address where I have to send the BTC to. - * @return Boolean - if the transaction was successful - */ - private fun addBTC(address: String): Boolean { - val executor: ExecutorService = Executors.newCachedThreadPool(Executors.defaultThreadFactory()) - val future: Future - - val url = "https://$REG_TEST_FAUCET_DOMAIN/addBTC?address=$address" - - future = executor.submit(object : Callable { - override fun call(): Boolean { - val connection = URL(url).openConnection() as HttpURLConnection - - try { - // If it fails, check if there is enough balance available on the server - // Otherwise reset the bitcoin network on the server (there is only 15k BTC available). - // Also check if the Python server is still running! - Log.i("Coin", url) - Log.i("Coin", connection.responseMessage) - return connection.responseCode == 200 - } finally { - connection.disconnect() - } - } - }) - - return try { - future.get(10, TimeUnit.SECONDS) - } catch (e: Exception) { - false - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - showNavBar() - return inflater.inflate(R.layout.fragment_bitcoin, container, false) - } - - companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @return A new instance of fragment bitcoinFragment. - */ - @JvmStatic - fun newInstance() = BitcoinFragment() - } - - override fun onImport(address: String, privateKey: String) { - if (!WalletManagerAndroid.isRunning) { - val config = WalletManagerConfiguration( - when (bitcoin_network_radio_group.checkedRadioButtonId) { - R.id.production_radiobutton -> BitcoinNetworkOptions.PRODUCTION - R.id.testnet_radiobutton -> BitcoinNetworkOptions.TEST_NET - R.id.regtest_radiobutton -> BitcoinNetworkOptions.REG_TEST - else -> { - Toast.makeText(this.requireContext(), "Please select a bitcoin network first", Toast.LENGTH_SHORT).show() - return - } - }, - null, - AddressPrivateKeyPair(address, privateKey) - ) - - try { - WalletManagerAndroid.Factory(this.requireContext().applicationContext) - .setConfiguration(config) - .init() - } catch (t: Throwable) { - Toast.makeText( - this.requireContext(), - "Something went wrong while initializing the new wallet. ${ - t.message - ?: "No further information" - }.", - Toast.LENGTH_SHORT - ).show() - return - } - } else { - WalletManagerAndroid.getInstance().addKey(privateKey) - } - } - - override fun onImportDone() { - this.refresh(true) - - @Suppress("DEPRECATION") - Handler().postDelayed( - { - findNavController().navigate(BitcoinFragmentDirections.actionBitcoinFragmentToBlockchainDownloadFragment()) - }, - 1500 - ) - } - - private fun copyToClipboard(text: String) { - val clipboard = getSystemService(this.requireContext(), ClipboardManager::class.java)!! - val clip = ClipData.newPlainText(text, text) - clipboard.setPrimaryClip(clip) - } -} diff --git a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/bitcoin/BlockchainDownloadFragment.kt b/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/bitcoin/BlockchainDownloadFragment.kt deleted file mode 100644 index c71e1a38b..000000000 --- a/currencyii/src/main/java/nl/tudelft/trustchain/currencyii/ui/bitcoin/BlockchainDownloadFragment.kt +++ /dev/null @@ -1,110 +0,0 @@ -package nl.tudelft.trustchain.currencyii.ui.bitcoin - -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.ProgressBar -import android.widget.TextView -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import kotlinx.android.synthetic.main.fragment_blockchain_download.* -import nl.tudelft.trustchain.currencyii.R -import nl.tudelft.trustchain.currencyii.coin.WalletManagerAndroid -import nl.tudelft.trustchain.currencyii.ui.BaseFragment -import org.bitcoinj.params.MainNetParams -import org.bitcoinj.params.RegTestParams -import org.bitcoinj.params.TestNet3Params -import kotlin.concurrent.thread - -/** - * A simple [Fragment] subclass. - * Use the [BlockchainDownloadFragment.newInstance] factory method to - * create an instance of this fragment. - */ -class BlockchainDownloadFragment : BaseFragment(R.layout.fragment_blockchain_download) { - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - // TODO: The routing is cleaner to do via the previously displayed fragment. - bitcoin_progress_continue.setOnClickListener { - val navController = findNavController() - val args = BlockchainDownloadFragmentArgs.fromBundle(requireArguments()) - when (args.parent) { - -1 -> { - // Default value - Log.i("Coin", "Default value, navigating to My DAOs.") - navController.navigate(BlockchainDownloadFragmentDirections.actionBlockchainDownloadFragmentToMyDAOsFragment()) - } - R.id.myDAOsFragment -> { - Log.i("Coin", "Navigating to My DAOs.") - navController.navigate(BlockchainDownloadFragmentDirections.actionBlockchainDownloadFragmentToMyDAOsFragment()) - } - R.id.bitcoinFragment -> { - Log.i("Coin", "Navigating to My Wallet.") - navController.navigate(BlockchainDownloadFragmentDirections.actionBlockchainDownloadFragmentToBitcoinFragment()) - } - else -> { - // Else just navigate to whatever was passed. - Log.i("Coin", "Navigating to ${args.parent}.") - navController.navigate(args.parent) - } - } - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - val fragment = inflater.inflate(R.layout.fragment_blockchain_download, container, false) - if (WalletManagerAndroid.isInitialized()) { - fragment.findViewById(R.id.bitcoin_download_percentage).text = - "${WalletManagerAndroid.getInstance().progress}%" - fragment.findViewById(R.id.bitcoin_download_progress).progress = - WalletManagerAndroid.getInstance().progress - val networkName = when (WalletManagerAndroid.getInstance().params) { - RegTestParams.get() -> "RegTest" - TestNet3Params.get() -> "TestNet" - MainNetParams.get() -> "MainNet" - else -> return null - } - fragment.findViewById(R.id.downloading_chain_tv).text = "Please wait while the chain from $networkName is downloading. " - thread { - // TODO: find a better way of handling uninitialized wallet managers while not stopping the while loop - while (WalletManagerAndroid.getInstance().progress < 100) { - Thread.sleep(500) - if (!WalletManagerAndroid.isInitialized()) { - break - } - fragment.findViewById(R.id.bitcoin_download_percentage).text = - "${WalletManagerAndroid.getInstance().progress}%" - fragment.findViewById(R.id.bitcoin_download_progress).progress = - WalletManagerAndroid.getInstance().progress - } - fragment.findViewById(R.id.bitcoin_download_percentage).text = - "Fully Synced!" - fragment.findViewById