Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8431f984a6 | |||
| 207f761196 | |||
| 3d6ff5993e | |||
| d10412346d | |||
| 9b7c456435 | |||
| d12ffcfdf8 | |||
| 277b1bdca7 | |||
| 6cd50e054e | |||
| a44a562a07 | |||
| 712151b0ea |
+3
-161
@@ -1,161 +1,3 @@
|
|||||||
# Created by https://www.gitignore.io
|
_site
|
||||||
|
.sass-cache
|
||||||
*.log
|
.jekyll-metadata
|
||||||
|
|
||||||
### Gradle ###
|
|
||||||
.gradle
|
|
||||||
build/
|
|
||||||
|
|
||||||
# Ignore Gradle GUI config
|
|
||||||
gradle-app.setting
|
|
||||||
|
|
||||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
|
||||||
!gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
### Intellij ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
|
|
||||||
|
|
||||||
*.iml
|
|
||||||
|
|
||||||
## Directory-based project format:
|
|
||||||
.idea/
|
|
||||||
# if you remove the above rule, at least ignore the following:
|
|
||||||
|
|
||||||
# User-specific stuff:
|
|
||||||
# .idea/workspace.xml
|
|
||||||
# .idea/tasks.xml
|
|
||||||
# .idea/dictionaries
|
|
||||||
|
|
||||||
# Sensitive or high-churn files:
|
|
||||||
# .idea/dataSources.ids
|
|
||||||
# .idea/dataSources.xml
|
|
||||||
# .idea/sqlDataSources.xml
|
|
||||||
# .idea/dynamic.xml
|
|
||||||
# .idea/uiDesigner.xml
|
|
||||||
|
|
||||||
# Gradle:
|
|
||||||
# .idea/gradle.xml
|
|
||||||
# .idea/libraries
|
|
||||||
|
|
||||||
# Mongo Explorer plugin:
|
|
||||||
# .idea/mongoSettings.xml
|
|
||||||
|
|
||||||
## File-based project format:
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
|
|
||||||
|
|
||||||
### Eclipse ###
|
|
||||||
*.pydevproject
|
|
||||||
.metadata
|
|
||||||
.gradle
|
|
||||||
bin/
|
|
||||||
tmp/
|
|
||||||
*.tmp
|
|
||||||
*.bak
|
|
||||||
*.swp
|
|
||||||
*~.nib
|
|
||||||
local.properties
|
|
||||||
.settings/
|
|
||||||
.loadpath
|
|
||||||
|
|
||||||
# Eclipse Core
|
|
||||||
.project
|
|
||||||
|
|
||||||
# External tool builders
|
|
||||||
.externalToolBuilders/
|
|
||||||
|
|
||||||
# Locally stored "Eclipse launch configurations"
|
|
||||||
*.launch
|
|
||||||
|
|
||||||
# CDT-specific
|
|
||||||
.cproject
|
|
||||||
|
|
||||||
# JDT-specific (Eclipse Java Development Tools)
|
|
||||||
.classpath
|
|
||||||
|
|
||||||
# PDT-specific
|
|
||||||
.buildpath
|
|
||||||
|
|
||||||
# sbteclipse plugin
|
|
||||||
.target
|
|
||||||
|
|
||||||
# TeXlipse plugin
|
|
||||||
.texlipse
|
|
||||||
|
|
||||||
|
|
||||||
### Linux ###
|
|
||||||
*~
|
|
||||||
|
|
||||||
# KDE directory preferences
|
|
||||||
.directory
|
|
||||||
|
|
||||||
# Linux trash folder which might appear on any partition or disk
|
|
||||||
.Trash-*
|
|
||||||
|
|
||||||
|
|
||||||
### OSX ###
|
|
||||||
.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Icon must end with two \r
|
|
||||||
Icon
|
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
|
||||||
.DocumentRevisions-V100
|
|
||||||
.fseventsd
|
|
||||||
.Spotlight-V100
|
|
||||||
.TemporaryItems
|
|
||||||
.Trashes
|
|
||||||
.VolumeIcon.icns
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
|
|
||||||
### Windows ###
|
|
||||||
# Windows image file caches
|
|
||||||
Thumbs.db
|
|
||||||
ehthumbs.db
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
Desktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
language: java
|
|
||||||
jdk:
|
|
||||||
- oraclejdk8
|
|
||||||
-1308
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
We love pull requests from everyone. Please be nice and forgive us
|
|
||||||
if we can't process your request right away.
|
|
||||||
|
|
||||||
Fork, then clone the repo:
|
|
||||||
|
|
||||||
git clone git@github.com:your-username/Jabit.git
|
|
||||||
|
|
||||||
Make sure the tests pass:
|
|
||||||
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
Make your change. Add tests for your change. Make the tests pass:
|
|
||||||
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
Push to your fork and [submit a pull request][pr].
|
|
||||||
|
|
||||||
[pr]: https://github.com/Dissem/Jabit/compare/
|
|
||||||
|
|
||||||
Unfortunately we can't always answer right away, so we ask you to have
|
|
||||||
some patience. Then we may suggest some changes or improvements or
|
|
||||||
alternatives.
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
# Welcome to Jekyll!
|
||||||
|
#
|
||||||
|
# This config file is meant for settings that affect your whole blog, values
|
||||||
|
# which you are expected to set up once and rarely need to edit after that.
|
||||||
|
# For technical reasons, this file is *NOT* reloaded automatically when you use
|
||||||
|
# 'jekyll serve'. If you change this file, please restart the server process.
|
||||||
|
|
||||||
|
# Site settings
|
||||||
|
title: Jabit
|
||||||
|
email: chrigu.meyer@gmail.com
|
||||||
|
description: > # this means to ignore newlines until "baseurl:"
|
||||||
|
You want to create an awesome Bitmessage app using Jabit? Then you've come
|
||||||
|
to the right place!
|
||||||
|
baseurl: "/Jabit" # the subpath of your site, e.g. /blog
|
||||||
|
url: "http://dissem.github.io" # the base hostname & protocol for your site
|
||||||
|
# twitter_username: jekyllrb
|
||||||
|
github_username: Dissem
|
||||||
|
|
||||||
|
# Build settings
|
||||||
|
markdown: kramdown
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: "Setting up Jabit for Your Project"
|
||||||
|
date: 2016-06-22 00:01:00 +0200
|
||||||
|
categories: setup
|
||||||
|
---
|
||||||
|
|
||||||
|
Add Jabit as Gradle dependency:
|
||||||
|
|
||||||
|
{% highlight groovy %}
|
||||||
|
compile 'ch.dissem.jabit:jabit-core:1.0.0'
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
Unless you want to implement your own, also add the following:
|
||||||
|
|
||||||
|
{% highlight groovy %}
|
||||||
|
compile 'ch.dissem.jabit:jabit-networking:1.0.0'
|
||||||
|
compile 'ch.dissem.jabit:jabit-repositories:1.0.0'
|
||||||
|
compile 'ch.dissem.jabit:jabit-cryptography-bouncy:1.0.0'
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
And if you want to import from or export to the Wallet Import Format (used by PyBitmessage) you might also want to add:
|
||||||
|
|
||||||
|
{% highlight groovy %}
|
||||||
|
compile 'ch.dissem.jabit:jabit-wif:1.0.0'
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
For Android clients use `jabit-cryptography-spongy` instead of `jabit-cryptography-bouncy`.
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: "Using the Jabit API"
|
||||||
|
date: 2016-06-22 00:02:00 +0200
|
||||||
|
categories: setup
|
||||||
|
---
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
First, you'll need to create a `BitmessageContext`:
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
JdbcConfig jdbcConfig = new JdbcConfig();
|
||||||
|
BitmessageContext ctx = new BitmessageContext.Builder()
|
||||||
|
.addressRepo(new JdbcAddressRepository(jdbcConfig))
|
||||||
|
.inventory(new JdbcInventory(jdbcConfig))
|
||||||
|
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
||||||
|
.nodeRegistry(new MemoryNodeRegistry())
|
||||||
|
.networkHandler(new NetworkNode())
|
||||||
|
.cryptography(new BouncyCryptography())
|
||||||
|
.build();
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
This creates a simple context using a H2 database that will be created in the user's home directory. Next you'll need to
|
||||||
|
start the context and decide what happens if a message arrives:
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
ctx.startup(new BitmessageContext.Listener() {
|
||||||
|
@Override
|
||||||
|
public void receive(Plaintext plaintext) {
|
||||||
|
// TODO: Notify the user
|
||||||
|
}
|
||||||
|
});
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
Then you might want to create an identity
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
BitmessageAddress identity = ctx.createIdentity(false, Pubkey.Feature.DOES_ACK);
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
or add some contacts
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
BitmessageAddress contact = new BitmessageAddress("BM-2cTarrmjMdRicKZ4qQ8A13JhoR3Uq6Zh5j");
|
||||||
|
address.setAlias("Chris");
|
||||||
|
ctx.addContact(contact);
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
to which you can send some messages
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
ctx.send(identity, contact, "Test", "Hello Chris, this is a message.");
|
||||||
|
{% endhighlight %}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<footer class="site-footer">
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
|
||||||
|
<h2 class="footer-heading">{{ site.title }}</h2>
|
||||||
|
|
||||||
|
<div class="footer-col-wrapper">
|
||||||
|
<div class="footer-col footer-col-1">
|
||||||
|
<ul class="contact-list">
|
||||||
|
<li>{{ site.title }}</li>
|
||||||
|
<li><a href="mailto:{{ site.email }}">{{ site.email }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-col footer-col-2">
|
||||||
|
<ul class="social-media-list">
|
||||||
|
{% if site.github_username %}
|
||||||
|
<li>
|
||||||
|
{% include icon-github.html username=site.github_username %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if site.twitter_username %}
|
||||||
|
<li>
|
||||||
|
{% include icon-twitter.html username=site.twitter_username %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-col footer-col-3">
|
||||||
|
<p>{{ site.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</footer>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
|
||||||
|
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}">
|
||||||
|
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}">
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}">
|
||||||
|
</head>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<header class="site-header">
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
|
||||||
|
<a class="site-title" href="{{ site.baseurl }}/">{{ site.title }}</a>
|
||||||
|
|
||||||
|
<nav class="site-nav">
|
||||||
|
<a href="#" class="menu-icon">
|
||||||
|
<svg viewBox="0 0 18 15">
|
||||||
|
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
|
||||||
|
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
|
||||||
|
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="trigger">
|
||||||
|
{% assign sorted_pages = site.pages | sort: 'order' %}
|
||||||
|
{% for my_page in sorted_pages %}
|
||||||
|
{% if my_page.title %}
|
||||||
|
<a class="page-link" href="{{ my_page.url | prepend: site.baseurl }}">{{ my_page.title }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</header>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<a href="https://github.com/{{ include.username }}"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">{{ include.username }}</span></a>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 16 16"><path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z"/></svg>
|
||||||
|
After Width: | Height: | Size: 926 B |
@@ -0,0 +1 @@
|
|||||||
|
<a href="https://twitter.com/{{ include.username }}"><span class="icon icon--twitter">{% include icon-twitter.svg %}</span><span class="username">{{ include.username }}</span></a>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 16 16"><path fill="#828282" d="M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z"/></svg>
|
||||||
|
After Width: | Height: | Size: 787 B |
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
{% include head.html %}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{% include header.html %}
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="wrapper">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include footer.html %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
---
|
||||||
|
<article class="post">
|
||||||
|
|
||||||
|
<header class="post-header">
|
||||||
|
<h1 class="post-title">{{ page.title }}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="post-content">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</article>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
---
|
||||||
|
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
|
||||||
|
|
||||||
|
<header class="post-header">
|
||||||
|
<h1 class="post-title" itemprop="name headline">{{ page.title }}</h1>
|
||||||
|
<p class="post-meta"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time>{% if page.author %} • <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">{{ page.author }}</span></span>{% endif %}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="post-content" itemprop="articleBody">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</article>
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* Reset some basic elements
|
||||||
|
*/
|
||||||
|
body, h1, h2, h3, h4, h5, h6,
|
||||||
|
p, blockquote, pre, hr,
|
||||||
|
dl, dd, ol, ul, figure {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic styling
|
||||||
|
*/
|
||||||
|
body {
|
||||||
|
font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family;
|
||||||
|
color: $text-color;
|
||||||
|
background-color: $background-color;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-font-feature-settings: "kern" 1;
|
||||||
|
-moz-font-feature-settings: "kern" 1;
|
||||||
|
-o-font-feature-settings: "kern" 1;
|
||||||
|
font-feature-settings: "kern" 1;
|
||||||
|
font-kerning: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set `margin-bottom` to maintain vertical rhythm
|
||||||
|
*/
|
||||||
|
h1, h2, h3, h4, h5, h6,
|
||||||
|
p, blockquote, pre,
|
||||||
|
ul, ol, dl, figure,
|
||||||
|
%vertical-rhythm {
|
||||||
|
margin-bottom: $spacing-unit / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Images
|
||||||
|
*/
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures
|
||||||
|
*/
|
||||||
|
figure > img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
font-size: $small-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists
|
||||||
|
*/
|
||||||
|
ul, ol {
|
||||||
|
margin-left: $spacing-unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
> ul,
|
||||||
|
> ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Headings
|
||||||
|
*/
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight: $base-font-weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links
|
||||||
|
*/
|
||||||
|
a {
|
||||||
|
color: $brand-color;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: darken($brand-color, 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $text-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blockquotes
|
||||||
|
*/
|
||||||
|
blockquote {
|
||||||
|
color: $grey-color;
|
||||||
|
border-left: 4px solid $grey-color-light;
|
||||||
|
padding-left: $spacing-unit / 2;
|
||||||
|
font-size: 18px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
> :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
color: $text-color;
|
||||||
|
background-color: $blue-color-light;
|
||||||
|
border-left: 4px solid $blue-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code formatting
|
||||||
|
*/
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
font-size: 15px;
|
||||||
|
border: 1px solid $grey-color-light;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #eef;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 8px 12px;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
> code {
|
||||||
|
border: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper
|
||||||
|
*/
|
||||||
|
.wrapper {
|
||||||
|
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
|
||||||
|
max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: $spacing-unit;
|
||||||
|
padding-left: $spacing-unit;
|
||||||
|
@extend %clearfix;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
|
||||||
|
max-width: calc(#{$content-width} - (#{$spacing-unit}));
|
||||||
|
padding-right: $spacing-unit / 2;
|
||||||
|
padding-left: $spacing-unit / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clearfix
|
||||||
|
*/
|
||||||
|
%clearfix {
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icons
|
||||||
|
*/
|
||||||
|
.icon {
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: $grey-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* Site header
|
||||||
|
*/
|
||||||
|
.site-header {
|
||||||
|
border-top: 5px solid $grey-color-dark;
|
||||||
|
border-bottom: 1px solid $grey-color-light;
|
||||||
|
min-height: 56px;
|
||||||
|
|
||||||
|
// Positioning context for the mobile navigation icon
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 56px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
&,
|
||||||
|
&:visited {
|
||||||
|
color: $grey-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav {
|
||||||
|
float: right;
|
||||||
|
line-height: 56px;
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
color: $text-color;
|
||||||
|
line-height: $base-line-height;
|
||||||
|
|
||||||
|
// Gaps between nav items, but not on the last one
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-query($on-palm) {
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
right: $spacing-unit / 2;
|
||||||
|
background-color: $background-color;
|
||||||
|
border: 1px solid $grey-color-light;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
width: 36px;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 0;
|
||||||
|
padding-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 15px;
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: $grey-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
clear: both;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .trigger {
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Site footer
|
||||||
|
*/
|
||||||
|
.site-footer {
|
||||||
|
border-top: 1px solid $grey-color-light;
|
||||||
|
padding: $spacing-unit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-heading {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: $spacing-unit / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-list,
|
||||||
|
.social-media-list {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-wrapper {
|
||||||
|
font-size: 15px;
|
||||||
|
color: $grey-color;
|
||||||
|
margin-left: -$spacing-unit / 2;
|
||||||
|
@extend %clearfix;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col {
|
||||||
|
float: left;
|
||||||
|
margin-bottom: $spacing-unit / 2;
|
||||||
|
padding-left: $spacing-unit / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-1 {
|
||||||
|
width: -webkit-calc(35% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(35% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-2 {
|
||||||
|
width: -webkit-calc(20% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(20% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-3 {
|
||||||
|
width: -webkit-calc(45% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(45% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
.footer-col-1,
|
||||||
|
.footer-col-2 {
|
||||||
|
width: -webkit-calc(50% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(50% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-3 {
|
||||||
|
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(100% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-query($on-palm) {
|
||||||
|
.footer-col {
|
||||||
|
float: none;
|
||||||
|
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(100% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page content
|
||||||
|
*/
|
||||||
|
.page-content {
|
||||||
|
padding: $spacing-unit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-heading {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-list {
|
||||||
|
margin-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
margin-bottom: $spacing-unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-meta {
|
||||||
|
font-size: $small-font-size;
|
||||||
|
color: $grey-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-link {
|
||||||
|
display: block;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts
|
||||||
|
*/
|
||||||
|
.post-header {
|
||||||
|
margin-bottom: $spacing-unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title {
|
||||||
|
font-size: 42px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content {
|
||||||
|
margin-bottom: $spacing-unit;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 26px;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Syntax highlighting styles
|
||||||
|
*/
|
||||||
|
.highlight {
|
||||||
|
background: #fff;
|
||||||
|
@extend %vertical-rhythm;
|
||||||
|
|
||||||
|
.highlighter-rouge & {
|
||||||
|
background: #eef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c { color: #998; font-style: italic } // Comment
|
||||||
|
.err { color: #a61717; background-color: #e3d2d2 } // Error
|
||||||
|
.k { font-weight: bold } // Keyword
|
||||||
|
.o { font-weight: bold } // Operator
|
||||||
|
.cm { color: #998; font-style: italic } // Comment.Multiline
|
||||||
|
.cp { color: #999; font-weight: bold } // Comment.Preproc
|
||||||
|
.c1 { color: #998; font-style: italic } // Comment.Single
|
||||||
|
.cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special
|
||||||
|
.gd { color: #000; background-color: #fdd } // Generic.Deleted
|
||||||
|
.gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific
|
||||||
|
.ge { font-style: italic } // Generic.Emph
|
||||||
|
.gr { color: #a00 } // Generic.Error
|
||||||
|
.gh { color: #999 } // Generic.Heading
|
||||||
|
.gi { color: #000; background-color: #dfd } // Generic.Inserted
|
||||||
|
.gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific
|
||||||
|
.go { color: #888 } // Generic.Output
|
||||||
|
.gp { color: #555 } // Generic.Prompt
|
||||||
|
.gs { font-weight: bold } // Generic.Strong
|
||||||
|
.gu { color: #aaa } // Generic.Subheading
|
||||||
|
.gt { color: #a00 } // Generic.Traceback
|
||||||
|
.kc { font-weight: bold } // Keyword.Constant
|
||||||
|
.kd { font-weight: bold } // Keyword.Declaration
|
||||||
|
.kp { font-weight: bold } // Keyword.Pseudo
|
||||||
|
.kr { font-weight: bold } // Keyword.Reserved
|
||||||
|
.kt { color: #458; font-weight: bold } // Keyword.Type
|
||||||
|
.m { color: #099 } // Literal.Number
|
||||||
|
.s { color: #d14 } // Literal.String
|
||||||
|
.na { color: #008080 } // Name.Attribute
|
||||||
|
.nb { color: #0086B3 } // Name.Builtin
|
||||||
|
.nc { color: #458; font-weight: bold } // Name.Class
|
||||||
|
.no { color: #008080 } // Name.Constant
|
||||||
|
.ni { color: #800080 } // Name.Entity
|
||||||
|
.ne { color: #900; font-weight: bold } // Name.Exception
|
||||||
|
.nf { color: #900; font-weight: bold } // Name.Function
|
||||||
|
.nn { color: #555 } // Name.Namespace
|
||||||
|
.nt { color: #000080 } // Name.Tag
|
||||||
|
.nv { color: #008080 } // Name.Variable
|
||||||
|
.ow { font-weight: bold } // Operator.Word
|
||||||
|
.w { color: #bbb } // Text.Whitespace
|
||||||
|
.mf { color: #099 } // Literal.Number.Float
|
||||||
|
.mh { color: #099 } // Literal.Number.Hex
|
||||||
|
.mi { color: #099 } // Literal.Number.Integer
|
||||||
|
.mo { color: #099 } // Literal.Number.Oct
|
||||||
|
.sb { color: #d14 } // Literal.String.Backtick
|
||||||
|
.sc { color: #d14 } // Literal.String.Char
|
||||||
|
.sd { color: #d14 } // Literal.String.Doc
|
||||||
|
.s2 { color: #d14 } // Literal.String.Double
|
||||||
|
.se { color: #d14 } // Literal.String.Escape
|
||||||
|
.sh { color: #d14 } // Literal.String.Heredoc
|
||||||
|
.si { color: #d14 } // Literal.String.Interpol
|
||||||
|
.sx { color: #d14 } // Literal.String.Other
|
||||||
|
.sr { color: #009926 } // Literal.String.Regex
|
||||||
|
.s1 { color: #d14 } // Literal.String.Single
|
||||||
|
.ss { color: #990073 } // Literal.String.Symbol
|
||||||
|
.bp { color: #999 } // Name.Builtin.Pseudo
|
||||||
|
.vc { color: #008080 } // Name.Variable.Class
|
||||||
|
.vg { color: #008080 } // Name.Variable.Global
|
||||||
|
.vi { color: #008080 } // Name.Variable.Instance
|
||||||
|
.il { color: #099 } // Literal.Number.Integer.Long
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: About
|
||||||
|
permalink: /about/
|
||||||
|
order: 1000
|
||||||
|
---
|
||||||
|
|
||||||
|
Jabit is a Bitmessage library for Java. It aims to be easy to use, so you can create great Bitmessage apps.
|
||||||
|
|
||||||
|
You can find the source code for the Jabit at:
|
||||||
|
{% include icon-github.html username="Dissem" %} /
|
||||||
|
[Jabit](https://github.com/Dissem/Jabit)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: "API"
|
||||||
|
permalink: "/api"
|
||||||
|
order: 200
|
||||||
|
categories: api
|
||||||
|
---
|
||||||
|
> See [Jabit's JavaDoc](http://www.javadoc.io/doc/ch.dissem.jabit/jabit-core/1.0.1) for a detailed documentation of all classes and methods. Feel free to either open an issue or create a pull request if you find something to be unclear or incorrect.
|
||||||
|
{: .info}
|
||||||
|
|
||||||
|
# Context Setup
|
||||||
|
The `BitmessageContext` provides all methods a usual Bitmessage client needs. Note that all its methods should be thread safe, but you'll need to check your `BitmessageContext.Listener` for thread safety. The listener may even be called simultaneously from different threads.
|
||||||
|
|
||||||
|
To keep the library small, flexible and independent from (but compatible to) heavy-duty frameworks such as Spring, the `BitmessageContext` needs to be initialized with all its dependencies.
|
||||||
|
|
||||||
|
# Creating Identities
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
BitmessageAddress identity = ctx.createIdentity(false, Pubkey.Feature.DOES_ACK);
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
If _shorter_ is enabled, Jabit searches a private key that yields a slightly shorter address. This is not optimized and may take a long time to complete.
|
||||||
|
|
||||||
|
# Importing Identities
|
||||||
|
Identities can be imported from PyBitmessages keys.dat by using the WifImporter from the library `jabit-wif`. The constructor needs a `BitmessageContext` and either a file, a string representation of a keys.dat or an input stream. The importer will throw an exception if the data can't be parsed.
|
||||||
|
|
||||||
|
Once constructed you can choose to list the contained identities, import all, a subset or a specific identity into the given `BitmessageContext`.
|
||||||
|
|
||||||
|
# Exporting Identities
|
||||||
|
Exports work very similar to imports. A `WifExporter` also needs a `BitmessageContext` to construct. You then can add either all or specific identities to the importer. Id you provide specific identities, make sure they have a private key. They will not be reloaded.
|
||||||
|
|
||||||
|
If you're done adding identities, call one of the `write` methods to save the exported identities, or `toString()` for the string representation.
|
||||||
|
|
||||||
|
# Threads
|
||||||
|
> TODO
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
subprojects {
|
|
||||||
apply plugin: 'java'
|
|
||||||
apply plugin: 'maven'
|
|
||||||
apply plugin: 'signing'
|
|
||||||
|
|
||||||
sourceCompatibility = 1.7
|
|
||||||
group = 'ch.dissem.jabit'
|
|
||||||
version = '1.0.1'
|
|
||||||
|
|
||||||
ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
testLogging {
|
|
||||||
exceptionFormat = 'full'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task javadocJar(type: Jar) {
|
|
||||||
classifier = 'javadoc'
|
|
||||||
from javadoc
|
|
||||||
}
|
|
||||||
|
|
||||||
task sourcesJar(type: Jar) {
|
|
||||||
classifier = 'sources'
|
|
||||||
from sourceSets.main.allSource
|
|
||||||
}
|
|
||||||
|
|
||||||
artifacts {
|
|
||||||
archives javadocJar, sourcesJar
|
|
||||||
}
|
|
||||||
|
|
||||||
signing {
|
|
||||||
required { isReleaseVersion && project.getProperties().get("signing.keyId")?.length() > 0 }
|
|
||||||
sign configurations.archives
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadArchives {
|
|
||||||
repositories {
|
|
||||||
mavenDeployer {
|
|
||||||
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
|
|
||||||
|
|
||||||
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
|
|
||||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
|
|
||||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
pom.project {
|
|
||||||
name 'Jabit'
|
|
||||||
packaging 'jar'
|
|
||||||
url 'https://github.com/Dissem/Jabit'
|
|
||||||
|
|
||||||
scm {
|
|
||||||
connection 'scm:git:https://github.com/Dissem/Jabit.git'
|
|
||||||
developerConnection 'scm:git:git@github.com:Dissem/Jabit.git'
|
|
||||||
url 'https://github.com/Dissem/Jabit.git'
|
|
||||||
}
|
|
||||||
|
|
||||||
licenses {
|
|
||||||
license {
|
|
||||||
name 'The Apache License, Version 2.0'
|
|
||||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
developers {
|
|
||||||
developer {
|
|
||||||
name 'Christian Basler'
|
|
||||||
email 'chrigu.meyer@gmail.com'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
uploadArchives {
|
|
||||||
repositories {
|
|
||||||
mavenDeployer {
|
|
||||||
pom.project {
|
|
||||||
name 'Jabit Core'
|
|
||||||
artifactId = 'jabit-core'
|
|
||||||
description 'A Java implementation of the Bitmessage protocol. This is the core part. You\'ll either need the networking and repositories modules, too, or implement your own.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
testArtifacts.extendsFrom testRuntime
|
|
||||||
}
|
|
||||||
|
|
||||||
task testJar(type: Jar) {
|
|
||||||
classifier = 'test'
|
|
||||||
from sourceSets.test.output
|
|
||||||
}
|
|
||||||
|
|
||||||
artifacts {
|
|
||||||
testArtifacts testJar
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile 'org.slf4j:slf4j-api:1.7.12'
|
|
||||||
testCompile 'junit:junit:4.11'
|
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
|
||||||
testCompile project(':cryptography-bc')
|
|
||||||
}
|
|
||||||
@@ -1,435 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.*;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Broadcast;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Msg;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.ports.*;
|
|
||||||
import ch.dissem.bitmessage.utils.Property;
|
|
||||||
import ch.dissem.bitmessage.utils.TTL;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Use this class if you want to create a Bitmessage client.</p>
|
|
||||||
* You'll need the Builder to create a BitmessageContext, and set the following properties:
|
|
||||||
* <ul>
|
|
||||||
* <li>addressRepo</li>
|
|
||||||
* <li>inventory</li>
|
|
||||||
* <li>nodeRegistry</li>
|
|
||||||
* <li>networkHandler</li>
|
|
||||||
* <li>messageRepo</li>
|
|
||||||
* <li>streams</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>The default implementations in the different module builds can be used.</p>
|
|
||||||
* <p>The port defaults to 8444 (the default Bitmessage port)</p>
|
|
||||||
*/
|
|
||||||
public class BitmessageContext {
|
|
||||||
public static final int CURRENT_VERSION = 3;
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
|
|
||||||
|
|
||||||
private final ExecutorService pool;
|
|
||||||
|
|
||||||
private final InternalContext ctx;
|
|
||||||
|
|
||||||
private final Listener listener;
|
|
||||||
private final NetworkHandler.MessageListener networkListener;
|
|
||||||
|
|
||||||
private final boolean sendPubkeyOnIdentityCreation;
|
|
||||||
|
|
||||||
private BitmessageContext(Builder builder) {
|
|
||||||
ctx = new InternalContext(builder);
|
|
||||||
listener = builder.listener;
|
|
||||||
networkListener = new DefaultMessageListener(ctx, listener);
|
|
||||||
|
|
||||||
// As this thread is used for parts that do POW, which itself uses parallel threads, only
|
|
||||||
// one should be executed at any time.
|
|
||||||
pool = Executors.newFixedThreadPool(1);
|
|
||||||
|
|
||||||
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
|
|
||||||
|
|
||||||
new Timer().schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ctx.getProofOfWorkService().doMissingProofOfWork();
|
|
||||||
}
|
|
||||||
}, 30_000); // After 30 seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressRepository addresses() {
|
|
||||||
return ctx.getAddressRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageRepository messages() {
|
|
||||||
return ctx.getMessageRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
|
|
||||||
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
|
|
||||||
shorter,
|
|
||||||
ctx.getStreams()[0],
|
|
||||||
ctx.getNetworkNonceTrialsPerByte(),
|
|
||||||
ctx.getNetworkExtraBytes(),
|
|
||||||
features
|
|
||||||
));
|
|
||||||
ctx.getAddressRepository().save(identity);
|
|
||||||
if (sendPubkeyOnIdentityCreation) {
|
|
||||||
pool.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ctx.sendPubkey(identity, identity.getStream());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addDistributedMailingList(String address, String alias) {
|
|
||||||
// TODO
|
|
||||||
throw new RuntimeException("not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
|
|
||||||
Plaintext msg = new Plaintext.Builder(BROADCAST)
|
|
||||||
.from(from)
|
|
||||||
.message(subject, message)
|
|
||||||
.build();
|
|
||||||
send(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) {
|
|
||||||
if (from.getPrivateKey() == null) {
|
|
||||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
|
||||||
}
|
|
||||||
Plaintext msg = new Plaintext.Builder(MSG)
|
|
||||||
.from(from)
|
|
||||||
.to(to)
|
|
||||||
.message(subject, message)
|
|
||||||
.labels(messages().getLabels(Label.Type.SENT))
|
|
||||||
.build();
|
|
||||||
send(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final Plaintext msg) {
|
|
||||||
if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
|
|
||||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
|
||||||
}
|
|
||||||
pool.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
BitmessageAddress to = msg.getTo();
|
|
||||||
if (to != null) {
|
|
||||||
if (to.getPubkey() == null) {
|
|
||||||
LOG.info("Public key is missing from recipient. Requesting.");
|
|
||||||
ctx.requestPubkey(to);
|
|
||||||
}
|
|
||||||
if (to.getPubkey() == null) {
|
|
||||||
msg.setStatus(PUBKEY_REQUESTED);
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX));
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (to == null || to.getPubkey() != null) {
|
|
||||||
LOG.info("Sending message.");
|
|
||||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
ctx.send(
|
|
||||||
msg.getFrom(),
|
|
||||||
to,
|
|
||||||
new Msg(msg),
|
|
||||||
TTL.msg()
|
|
||||||
);
|
|
||||||
msg.setStatus(SENT);
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startup() {
|
|
||||||
ctx.getNetworkHandler().start(networkListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
ctx.getNetworkHandler().stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param host a trusted node that must be reliable (it's used for every synchronization)
|
|
||||||
* @param port of the trusted host, default is 8444
|
|
||||||
* @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even
|
|
||||||
* if not all objects were fetched
|
|
||||||
* @param wait waits for the synchronization thread to finish
|
|
||||||
*/
|
|
||||||
public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) {
|
|
||||||
Future<?> future = ctx.getNetworkHandler().synchronize(host, port, networkListener, timeoutInSeconds);
|
|
||||||
if (wait) {
|
|
||||||
try {
|
|
||||||
future.get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.");
|
|
||||||
future.cancel(true);
|
|
||||||
} catch (CancellationException | ExecutionException e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
|
||||||
* the response, which in turn is expected to be a {@link CustomMessage}.
|
|
||||||
*
|
|
||||||
* @param server the node's address
|
|
||||||
* @param port the node's port
|
|
||||||
* @param request the request
|
|
||||||
* @return the response
|
|
||||||
*/
|
|
||||||
public CustomMessage send(InetAddress server, int port, CustomMessage request) {
|
|
||||||
return ctx.getNetworkHandler().send(server, port, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cleanup() {
|
|
||||||
ctx.getInventory().cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRunning() {
|
|
||||||
return ctx.getNetworkHandler().isRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addContact(BitmessageAddress contact) {
|
|
||||||
ctx.getAddressRepository().save(contact);
|
|
||||||
if (contact.getPubkey() == null) {
|
|
||||||
ctx.requestPubkey(contact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSubscribtion(BitmessageAddress address) {
|
|
||||||
address.setSubscribed(true);
|
|
||||||
ctx.getAddressRepository().save(address);
|
|
||||||
tryToFindBroadcastsForAddress(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
|
|
||||||
for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) {
|
|
||||||
try {
|
|
||||||
Broadcast broadcast = (Broadcast) object.getPayload();
|
|
||||||
broadcast.decrypt(address);
|
|
||||||
listener.receive(broadcast.getPlaintext());
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Property status() {
|
|
||||||
return new Property("status", null,
|
|
||||||
ctx.getNetworkHandler().getNetworkStatus()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link InternalContext} - normally you wouldn't need it,
|
|
||||||
* unless you are doing something crazy with the protocol.
|
|
||||||
*/
|
|
||||||
public InternalContext internals() {
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
void receive(Plaintext plaintext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
int port = 8444;
|
|
||||||
Inventory inventory;
|
|
||||||
NodeRegistry nodeRegistry;
|
|
||||||
NetworkHandler networkHandler;
|
|
||||||
AddressRepository addressRepo;
|
|
||||||
MessageRepository messageRepo;
|
|
||||||
ProofOfWorkRepository proofOfWorkRepository;
|
|
||||||
ProofOfWorkEngine proofOfWorkEngine;
|
|
||||||
Cryptography cryptography;
|
|
||||||
MessageCallback messageCallback;
|
|
||||||
CustomCommandHandler customCommandHandler;
|
|
||||||
Listener listener;
|
|
||||||
int connectionLimit = 150;
|
|
||||||
long connectionTTL = 30 * MINUTE;
|
|
||||||
boolean sendPubkeyOnIdentityCreation = true;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder port(int port) {
|
|
||||||
this.port = port;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder inventory(Inventory inventory) {
|
|
||||||
this.inventory = inventory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nodeRegistry(NodeRegistry nodeRegistry) {
|
|
||||||
this.nodeRegistry = nodeRegistry;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder networkHandler(NetworkHandler networkHandler) {
|
|
||||||
this.networkHandler = networkHandler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addressRepo(AddressRepository addressRepo) {
|
|
||||||
this.addressRepo = addressRepo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder messageRepo(MessageRepository messageRepo) {
|
|
||||||
this.messageRepo = messageRepo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
|
|
||||||
this.proofOfWorkRepository = proofOfWorkRepository;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder cryptography(Cryptography cryptography) {
|
|
||||||
this.cryptography = cryptography;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder messageCallback(MessageCallback callback) {
|
|
||||||
this.messageCallback = callback;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder customCommandHandler(CustomCommandHandler handler) {
|
|
||||||
this.customCommandHandler = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
|
|
||||||
this.proofOfWorkEngine = proofOfWorkEngine;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder listener(Listener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder connectionLimit(int connectionLimit) {
|
|
||||||
this.connectionLimit = connectionLimit;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder connectionTTL(int hours) {
|
|
||||||
this.connectionTTL = hours * HOUR;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
|
||||||
* this behaviour might not be desirable.
|
|
||||||
*/
|
|
||||||
public Builder doNotSendPubkeyOnIdentityCreation() {
|
|
||||||
this.sendPubkeyOnIdentityCreation = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time to live in seconds for public keys the client sends. Defaults to the maximum of 28 days,
|
|
||||||
* but on weak devices smaller values might be desirable.
|
|
||||||
* <p>
|
|
||||||
* Please be aware that this might cause some problems where you can't receive a message (the
|
|
||||||
* sender can't receive your public key) in some special situations. Also note that it's probably
|
|
||||||
* not a good idea to set it too low.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public Builder pubkeyTTL(long days) {
|
|
||||||
if (days < 0 || days > 28 * DAY) throw new IllegalArgumentException("TTL must be between 1 and 28 days");
|
|
||||||
TTL.pubkey(days);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageContext build() {
|
|
||||||
nonNull("inventory", inventory);
|
|
||||||
nonNull("nodeRegistry", nodeRegistry);
|
|
||||||
nonNull("networkHandler", networkHandler);
|
|
||||||
nonNull("addressRepo", addressRepo);
|
|
||||||
nonNull("messageRepo", messageRepo);
|
|
||||||
nonNull("proofOfWorkRepo", proofOfWorkRepository);
|
|
||||||
if (proofOfWorkEngine == null) {
|
|
||||||
proofOfWorkEngine = new MultiThreadedPOWEngine();
|
|
||||||
}
|
|
||||||
if (messageCallback == null) {
|
|
||||||
messageCallback = new MessageCallback() {
|
|
||||||
@Override
|
|
||||||
public void proofOfWorkStarted(ObjectPayload message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void proofOfWorkCompleted(ObjectPayload message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageOffered(ObjectPayload message, InventoryVector iv) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageAcknowledged(InventoryVector iv) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (customCommandHandler == null) {
|
|
||||||
customCommandHandler = new CustomCommandHandler() {
|
|
||||||
@Override
|
|
||||||
public MessagePayload handle(CustomMessage request) {
|
|
||||||
throw new RuntimeException("Received custom request, but no custom command handler configured.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return new BitmessageContext(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void nonNull(String name, Object o) {
|
|
||||||
if (o == null) throw new IllegalStateException(name + " must not be null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.*;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.ports.NetworkHandler;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
|
||||||
|
|
||||||
class DefaultMessageListener implements NetworkHandler.MessageListener {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class);
|
|
||||||
private final InternalContext ctx;
|
|
||||||
private final BitmessageContext.Listener listener;
|
|
||||||
|
|
||||||
public DefaultMessageListener(InternalContext context, BitmessageContext.Listener listener) {
|
|
||||||
this.ctx = context;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive(ObjectMessage object) throws IOException {
|
|
||||||
ObjectPayload payload = object.getPayload();
|
|
||||||
if (payload.getType() == null) return;
|
|
||||||
|
|
||||||
switch (payload.getType()) {
|
|
||||||
case GET_PUBKEY: {
|
|
||||||
receive(object, (GetPubkey) payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PUBKEY: {
|
|
||||||
receive(object, (Pubkey) payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MSG: {
|
|
||||||
receive(object, (Msg) payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BROADCAST: {
|
|
||||||
receive(object, (Broadcast) payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
|
|
||||||
BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
|
|
||||||
if (identity != null && identity.getPrivateKey() != null) {
|
|
||||||
LOG.info("Got pubkey request for identity " + identity);
|
|
||||||
// FIXME: only send pubkey if it wasn't sent in the last 28 days
|
|
||||||
ctx.sendPubkey(identity, object.getStream());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, Pubkey pubkey) throws IOException {
|
|
||||||
BitmessageAddress address;
|
|
||||||
try {
|
|
||||||
if (pubkey instanceof V4Pubkey) {
|
|
||||||
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
|
|
||||||
address = ctx.getAddressRepository().findContact(v4Pubkey.getTag());
|
|
||||||
if (address != null) {
|
|
||||||
v4Pubkey.decrypt(address.getPublicDecryptionKey());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
address = ctx.getAddressRepository().findContact(pubkey.getRipe());
|
|
||||||
}
|
|
||||||
if (address != null) {
|
|
||||||
updatePubkey(address, pubkey);
|
|
||||||
}
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePubkey(BitmessageAddress address, Pubkey pubkey){
|
|
||||||
address.setPubkey(pubkey);
|
|
||||||
LOG.info("Got pubkey for contact " + address);
|
|
||||||
ctx.getAddressRepository().save(address);
|
|
||||||
List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
|
|
||||||
LOG.info("Sending " + messages.size() + " messages for contact " + address);
|
|
||||||
for (Plaintext msg : messages) {
|
|
||||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
ctx.send(
|
|
||||||
msg.getFrom(),
|
|
||||||
msg.getTo(),
|
|
||||||
new Msg(msg),
|
|
||||||
+2 * DAY
|
|
||||||
);
|
|
||||||
msg.setStatus(SENT);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, Msg msg) throws IOException {
|
|
||||||
for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
|
|
||||||
try {
|
|
||||||
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
|
|
||||||
msg.getPlaintext().setTo(identity);
|
|
||||||
if (!object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey())) {
|
|
||||||
LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
|
|
||||||
} else {
|
|
||||||
msg.getPlaintext().setStatus(RECEIVED);
|
|
||||||
msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD));
|
|
||||||
msg.getPlaintext().setInventoryVector(object.getInventoryVector());
|
|
||||||
ctx.getMessageRepository().save(msg.getPlaintext());
|
|
||||||
listener.receive(msg.getPlaintext());
|
|
||||||
updatePubkey(msg.getPlaintext().getFrom(), msg.getPlaintext().getFrom().getPubkey());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
|
|
||||||
byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
|
|
||||||
for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
|
|
||||||
if (tag != null && !Arrays.equals(tag, subscription.getTag())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
broadcast.decrypt(subscription.getPublicDecryptionKey());
|
|
||||||
if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) {
|
|
||||||
LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
|
|
||||||
} else {
|
|
||||||
broadcast.getPlaintext().setStatus(RECEIVED);
|
|
||||||
broadcast.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD));
|
|
||||||
broadcast.getPlaintext().setInventoryVector(object.getInventoryVector());
|
|
||||||
ctx.getMessageRepository().save(broadcast.getPlaintext());
|
|
||||||
listener.receive(broadcast.getPlaintext());
|
|
||||||
updatePubkey(broadcast.getPlaintext().getFrom(), broadcast.getPlaintext().getFrom().getPubkey());
|
|
||||||
}
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Encrypted;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.*;
|
|
||||||
import ch.dissem.bitmessage.ports.*;
|
|
||||||
import ch.dissem.bitmessage.utils.Singleton;
|
|
||||||
import ch.dissem.bitmessage.utils.TTL;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The internal context should normally only be used for port implementations. If you need it in your client
|
|
||||||
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
|
|
||||||
* get extended.
|
|
||||||
* <p>
|
|
||||||
* On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class InternalContext {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
|
|
||||||
|
|
||||||
private final Cryptography cryptography;
|
|
||||||
private final Inventory inventory;
|
|
||||||
private final NodeRegistry nodeRegistry;
|
|
||||||
private final NetworkHandler networkHandler;
|
|
||||||
private final AddressRepository addressRepository;
|
|
||||||
private final MessageRepository messageRepository;
|
|
||||||
private final ProofOfWorkRepository proofOfWorkRepository;
|
|
||||||
private final ProofOfWorkEngine proofOfWorkEngine;
|
|
||||||
private final MessageCallback messageCallback;
|
|
||||||
private final CustomCommandHandler customCommandHandler;
|
|
||||||
private final ProofOfWorkService proofOfWorkService;
|
|
||||||
|
|
||||||
private final TreeSet<Long> streams = new TreeSet<>();
|
|
||||||
private final int port;
|
|
||||||
private final long clientNonce;
|
|
||||||
private final long networkNonceTrialsPerByte = 1000;
|
|
||||||
private final long networkExtraBytes = 1000;
|
|
||||||
private long connectionTTL;
|
|
||||||
private int connectionLimit;
|
|
||||||
|
|
||||||
public InternalContext(BitmessageContext.Builder builder) {
|
|
||||||
this.cryptography = builder.cryptography;
|
|
||||||
this.inventory = builder.inventory;
|
|
||||||
this.nodeRegistry = builder.nodeRegistry;
|
|
||||||
this.networkHandler = builder.networkHandler;
|
|
||||||
this.addressRepository = builder.addressRepo;
|
|
||||||
this.messageRepository = builder.messageRepo;
|
|
||||||
this.proofOfWorkRepository = builder.proofOfWorkRepository;
|
|
||||||
this.proofOfWorkService = new ProofOfWorkService();
|
|
||||||
this.proofOfWorkEngine = builder.proofOfWorkEngine;
|
|
||||||
this.clientNonce = cryptography.randomNonce();
|
|
||||||
this.messageCallback = builder.messageCallback;
|
|
||||||
this.customCommandHandler = builder.customCommandHandler;
|
|
||||||
this.port = builder.port;
|
|
||||||
this.connectionLimit = builder.connectionLimit;
|
|
||||||
this.connectionTTL = builder.connectionTTL;
|
|
||||||
|
|
||||||
Singleton.initialize(cryptography);
|
|
||||||
|
|
||||||
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
|
|
||||||
for (BitmessageAddress address : addressRepository.getIdentities()) {
|
|
||||||
streams.add(address.getStream());
|
|
||||||
}
|
|
||||||
for (BitmessageAddress address : addressRepository.getSubscriptions()) {
|
|
||||||
streams.add(address.getStream());
|
|
||||||
}
|
|
||||||
if (streams.isEmpty()) {
|
|
||||||
streams.add(1L);
|
|
||||||
}
|
|
||||||
|
|
||||||
init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
|
|
||||||
proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine,
|
|
||||||
messageCallback, customCommandHandler);
|
|
||||||
for (BitmessageAddress identity : addressRepository.getIdentities()) {
|
|
||||||
streams.add(identity.getStream());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(Object... objects) {
|
|
||||||
for (Object o : objects) {
|
|
||||||
if (o instanceof ContextHolder) {
|
|
||||||
((ContextHolder) o).setContext(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cryptography getCryptography() {
|
|
||||||
return cryptography;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Inventory getInventory() {
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeRegistry getNodeRegistry() {
|
|
||||||
return nodeRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkHandler getNetworkHandler() {
|
|
||||||
return networkHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressRepository getAddressRepository() {
|
|
||||||
return addressRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageRepository getMessageRepository() {
|
|
||||||
return messageRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProofOfWorkRepository getProofOfWorkRepository() {
|
|
||||||
return proofOfWorkRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProofOfWorkEngine getProofOfWorkEngine() {
|
|
||||||
return proofOfWorkEngine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProofOfWorkService getProofOfWorkService() {
|
|
||||||
return proofOfWorkService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long[] getStreams() {
|
|
||||||
long[] result = new long[streams.size()];
|
|
||||||
int i = 0;
|
|
||||||
for (long stream : streams) {
|
|
||||||
result[i++] = stream;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNetworkNonceTrialsPerByte() {
|
|
||||||
return networkNonceTrialsPerByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNetworkExtraBytes() {
|
|
||||||
return networkExtraBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
|
|
||||||
final long timeToLive) {
|
|
||||||
try {
|
|
||||||
if (to == null) to = from;
|
|
||||||
long expires = UnixTime.now(+timeToLive);
|
|
||||||
LOG.info("Expires at " + expires);
|
|
||||||
final ObjectMessage object = new ObjectMessage.Builder()
|
|
||||||
.stream(to.getStream())
|
|
||||||
.expiresTime(expires)
|
|
||||||
.payload(payload)
|
|
||||||
.build();
|
|
||||||
if (object.isSigned()) {
|
|
||||||
object.sign(from.getPrivateKey());
|
|
||||||
}
|
|
||||||
if (payload instanceof Broadcast) {
|
|
||||||
((Broadcast) payload).encrypt();
|
|
||||||
} else if (payload instanceof Encrypted) {
|
|
||||||
object.encrypt(to.getPubkey());
|
|
||||||
}
|
|
||||||
messageCallback.proofOfWorkStarted(payload);
|
|
||||||
proofOfWorkService.doProofOfWork(to, object);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
|
|
||||||
try {
|
|
||||||
long expires = UnixTime.now(TTL.pubkey());
|
|
||||||
LOG.info("Expires at " + expires);
|
|
||||||
final ObjectMessage response = new ObjectMessage.Builder()
|
|
||||||
.stream(targetStream)
|
|
||||||
.expiresTime(expires)
|
|
||||||
.payload(identity.getPubkey())
|
|
||||||
.build();
|
|
||||||
response.sign(identity.getPrivateKey());
|
|
||||||
response.encrypt(cryptography.createPublicKey(identity.getPublicDecryptionKey()));
|
|
||||||
messageCallback.proofOfWorkStarted(identity.getPubkey());
|
|
||||||
// TODO: remember that the pubkey is just about to be sent, and on which stream!
|
|
||||||
proofOfWorkService.doProofOfWork(response);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback
|
|
||||||
* for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB.
|
|
||||||
*/
|
|
||||||
public void requestPubkey(final BitmessageAddress contact) {
|
|
||||||
BitmessageAddress stored = addressRepository.getAddress(contact.getAddress());
|
|
||||||
|
|
||||||
tryToFindMatchingPubkey(contact);
|
|
||||||
if (contact.getPubkey() != null) {
|
|
||||||
if (stored != null) {
|
|
||||||
stored.setPubkey(contact.getPubkey());
|
|
||||||
addressRepository.save(stored);
|
|
||||||
} else {
|
|
||||||
addressRepository.save(contact);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stored == null) {
|
|
||||||
addressRepository.save(contact);
|
|
||||||
}
|
|
||||||
|
|
||||||
long expires = UnixTime.now(TTL.getpubkey());
|
|
||||||
LOG.info("Expires at " + expires);
|
|
||||||
final ObjectMessage request = new ObjectMessage.Builder()
|
|
||||||
.stream(contact.getStream())
|
|
||||||
.expiresTime(expires)
|
|
||||||
.payload(new GetPubkey(contact))
|
|
||||||
.build();
|
|
||||||
messageCallback.proofOfWorkStarted(request.getPayload());
|
|
||||||
proofOfWorkService.doProofOfWork(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToFindMatchingPubkey(BitmessageAddress address) {
|
|
||||||
BitmessageAddress stored = addressRepository.getAddress(address.getAddress());
|
|
||||||
if (stored != null) {
|
|
||||||
address.setAlias(stored.getAlias());
|
|
||||||
address.setSubscribed(stored.isSubscribed());
|
|
||||||
}
|
|
||||||
for (ObjectMessage object : inventory.getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) {
|
|
||||||
try {
|
|
||||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
|
||||||
if (address.getVersion() == 4) {
|
|
||||||
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
|
|
||||||
if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) {
|
|
||||||
v4Pubkey.decrypt(address.getPublicDecryptionKey());
|
|
||||||
if (object.isSignatureValid(v4Pubkey)) {
|
|
||||||
address.setPubkey(v4Pubkey);
|
|
||||||
addressRepository.save(address);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
LOG.info("Found pubkey for " + address + " but signature is invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
|
|
||||||
address.setPubkey(pubkey);
|
|
||||||
addressRepository.save(address);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getClientNonce() {
|
|
||||||
return clientNonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getConnectionTTL() {
|
|
||||||
return connectionTTL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getConnectionLimit() {
|
|
||||||
return connectionLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomCommandHandler getCustomCommandHandler() {
|
|
||||||
return customCommandHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ContextHolder {
|
|
||||||
void setContext(InternalContext context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for message sending events, mostly so the user can be notified when POW is done.
|
|
||||||
*/
|
|
||||||
public interface MessageCallback {
|
|
||||||
/**
|
|
||||||
* Called before calculation of proof of work begins.
|
|
||||||
*/
|
|
||||||
void proofOfWorkStarted(ObjectPayload message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after calculation of proof of work finished.
|
|
||||||
*/
|
|
||||||
void proofOfWorkCompleted(ObjectPayload message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once the message is offered to the network. Please note that this doesn't mean the message was sent,
|
|
||||||
* if the client is not connected to the network it's just stored in the inventory.
|
|
||||||
* <p>
|
|
||||||
* Also, please note that this is where the original payload as well as the {@link InventoryVector} of the sent
|
|
||||||
* message is available. If the callback needs the IV for some reason, it should be retrieved here. (Plaintext
|
|
||||||
* and Broadcast messages will have their IV property set automatically though.)
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
void messageOffered(ObjectPayload message, InventoryVector iv);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This isn't called yet, as ACK messages aren't being processed yet. Also, this is only relevant for Plaintext
|
|
||||||
* messages.
|
|
||||||
*/
|
|
||||||
void messageAcknowledged(InventoryVector iv);
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
|
||||||
import ch.dissem.bitmessage.ports.Cryptography;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
|
|
||||||
|
|
||||||
private Cryptography cryptography;
|
|
||||||
private InternalContext ctx;
|
|
||||||
private ProofOfWorkRepository powRepo;
|
|
||||||
private MessageRepository messageRepo;
|
|
||||||
|
|
||||||
public void doMissingProofOfWork() {
|
|
||||||
List<byte[]> items = powRepo.getItems();
|
|
||||||
if (items.isEmpty()) return;
|
|
||||||
|
|
||||||
LOG.info("Doing POW for " + items.size() + " tasks.");
|
|
||||||
for (byte[] initialHash : items) {
|
|
||||||
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
|
|
||||||
cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doProofOfWork(ObjectMessage object) {
|
|
||||||
doProofOfWork(null, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
|
|
||||||
Pubkey pubkey = recipient == null ? null : recipient.getPubkey();
|
|
||||||
|
|
||||||
long nonceTrialsPerByte = pubkey == null ? ctx.getNetworkNonceTrialsPerByte() : pubkey.getNonceTrialsPerByte();
|
|
||||||
long extraBytes = pubkey == null ? ctx.getNetworkExtraBytes() : pubkey.getExtraBytes();
|
|
||||||
|
|
||||||
powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
|
|
||||||
if (object.getPayload() instanceof PlaintextHolder) {
|
|
||||||
Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
|
|
||||||
plaintext.setInitialHash(cryptography.getInitialHash(object));
|
|
||||||
messageRepo.save(plaintext);
|
|
||||||
}
|
|
||||||
cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
|
||||||
ObjectMessage object = powRepo.getItem(initialHash).object;
|
|
||||||
object.setNonce(nonce);
|
|
||||||
// messageCallback.proofOfWorkCompleted(payload);
|
|
||||||
Plaintext plaintext = messageRepo.getMessage(initialHash);
|
|
||||||
if (plaintext != null) {
|
|
||||||
plaintext.setInventoryVector(object.getInventoryVector());
|
|
||||||
messageRepo.save(plaintext);
|
|
||||||
}
|
|
||||||
ctx.getInventory().storeObject(object);
|
|
||||||
ctx.getProofOfWorkRepository().removeObject(initialHash);
|
|
||||||
ctx.getNetworkHandler().offer(object.getInventoryVector());
|
|
||||||
// messageCallback.messageOffered(payload, object.getInventoryVector());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.cryptography = security();
|
|
||||||
this.powRepo = ctx.getProofOfWorkRepository();
|
|
||||||
this.messageRepo = ctx.getMessageRepository();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'addr' command holds a list of known active Bitmessage nodes.
|
|
||||||
*/
|
|
||||||
public class Addr implements MessagePayload {
|
|
||||||
private final List<NetworkAddress> addresses;
|
|
||||||
|
|
||||||
private Addr(Builder builder) {
|
|
||||||
addresses = builder.addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.ADDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<NetworkAddress> getAddresses() {
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
Encode.varInt(addresses.size(), stream);
|
|
||||||
for (NetworkAddress address : addresses) {
|
|
||||||
address.write(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<NetworkAddress> addresses = new ArrayList<NetworkAddress>();
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addresses(Collection<NetworkAddress> addresses){
|
|
||||||
this.addresses.addAll(addresses);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addAddress(final NetworkAddress address) {
|
|
||||||
this.addresses.add(address);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Addr build() {
|
|
||||||
return new Addr(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
|
||||||
import ch.dissem.bitmessage.utils.Base58;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.varInt;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
|
|
||||||
* holding private keys.
|
|
||||||
*/
|
|
||||||
public class BitmessageAddress implements Serializable {
|
|
||||||
private final long version;
|
|
||||||
private final long stream;
|
|
||||||
private final byte[] ripe;
|
|
||||||
private final byte[] tag;
|
|
||||||
/**
|
|
||||||
* Used for V4 address encryption. It's easier to just create it regardless of address version.
|
|
||||||
*/
|
|
||||||
private final byte[] publicDecryptionKey;
|
|
||||||
|
|
||||||
private String address;
|
|
||||||
|
|
||||||
private PrivateKey privateKey;
|
|
||||||
private Pubkey pubkey;
|
|
||||||
|
|
||||||
private String alias;
|
|
||||||
private boolean subscribed;
|
|
||||||
|
|
||||||
BitmessageAddress(long version, long stream, byte[] ripe) {
|
|
||||||
try {
|
|
||||||
this.version = version;
|
|
||||||
this.stream = stream;
|
|
||||||
this.ripe = ripe;
|
|
||||||
|
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
||||||
Encode.varInt(version, os);
|
|
||||||
Encode.varInt(stream, os);
|
|
||||||
if (version < 4) {
|
|
||||||
byte[] checksum = security().sha512(os.toByteArray(), ripe);
|
|
||||||
this.tag = null;
|
|
||||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
||||||
} else {
|
|
||||||
// for tag and decryption key, the checksum has to be created with 0x00 padding
|
|
||||||
byte[] checksum = security().doubleSha512(os.toByteArray(), ripe);
|
|
||||||
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
|
||||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
||||||
}
|
|
||||||
// but for the address and its checksum they need to be stripped
|
|
||||||
int offset = Bytes.numberOfLeadingZeros(ripe);
|
|
||||||
os.write(ripe, offset, ripe.length - offset);
|
|
||||||
byte[] checksum = security().doubleSha512(os.toByteArray());
|
|
||||||
os.write(checksum, 0, 4);
|
|
||||||
this.address = "BM-" + Base58.encode(os.toByteArray());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress(Pubkey publicKey) {
|
|
||||||
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
|
|
||||||
this.pubkey = publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress(PrivateKey privateKey) {
|
|
||||||
this(privateKey.getPubkey());
|
|
||||||
this.privateKey = privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress(String address) {
|
|
||||||
try {
|
|
||||||
this.address = address;
|
|
||||||
byte[] bytes = Base58.decode(address.substring(3));
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
|
|
||||||
AccessCounter counter = new AccessCounter();
|
|
||||||
this.version = varInt(in, counter);
|
|
||||||
this.stream = varInt(in, counter);
|
|
||||||
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
|
|
||||||
|
|
||||||
// test checksum
|
|
||||||
byte[] checksum = security().doubleSha512(bytes, bytes.length - 4);
|
|
||||||
byte[] expectedChecksum = bytes(in, 4);
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
if (expectedChecksum[i] != checksum[i])
|
|
||||||
throw new IllegalArgumentException("Checksum of address failed");
|
|
||||||
}
|
|
||||||
if (version < 4) {
|
|
||||||
checksum = security().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
|
||||||
this.tag = null;
|
|
||||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
||||||
} else {
|
|
||||||
checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
|
||||||
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
|
||||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] calculateTag(long version, long stream, byte[] ripe) {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
Encode.varInt(version, out);
|
|
||||||
Encode.varInt(stream, out);
|
|
||||||
out.write(ripe);
|
|
||||||
return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pubkey getPubkey() {
|
|
||||||
return pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPubkey(Pubkey pubkey) {
|
|
||||||
if (pubkey instanceof V4Pubkey) {
|
|
||||||
if (!Arrays.equals(tag, ((V4Pubkey) pubkey).getTag()))
|
|
||||||
throw new IllegalArgumentException("Pubkey has incompatible tag");
|
|
||||||
}
|
|
||||||
if (!Arrays.equals(ripe, pubkey.getRipe()))
|
|
||||||
throw new IllegalArgumentException("Pubkey has incompatible ripe");
|
|
||||||
this.pubkey = pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts.
|
|
||||||
*/
|
|
||||||
public byte[] getPublicDecryptionKey() {
|
|
||||||
return publicDecryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey getPrivateKey() {
|
|
||||||
return privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAlias() {
|
|
||||||
return alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAlias(String alias) {
|
|
||||||
this.alias = alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return alias != null ? alias : address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getRipe() {
|
|
||||||
return ripe;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getTag() {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
BitmessageAddress address = (BitmessageAddress) o;
|
|
||||||
return Objects.equals(version, address.version) &&
|
|
||||||
Objects.equals(stream, address.stream) &&
|
|
||||||
Arrays.equals(ripe, address.ripe);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Arrays.hashCode(ripe);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSubscribed() {
|
|
||||||
return subscribed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubscribed(boolean subscribed) {
|
|
||||||
this.subscribed = subscribed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.varString;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class CustomMessage implements MessagePayload {
|
|
||||||
public static final String COMMAND_ERROR = "ERROR";
|
|
||||||
|
|
||||||
private final String command;
|
|
||||||
private final byte[] data;
|
|
||||||
|
|
||||||
public CustomMessage(String command) {
|
|
||||||
this.command = command;
|
|
||||||
this.data = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomMessage(String command, byte[] data) {
|
|
||||||
this.command = command;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CustomMessage read(InputStream in, int length) throws IOException {
|
|
||||||
AccessCounter counter = new AccessCounter();
|
|
||||||
return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.CUSTOM;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCustomCommand() {
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getData() {
|
|
||||||
if (data != null) {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
write(out);
|
|
||||||
return out.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
if (data != null) {
|
|
||||||
Encode.varString(command, out);
|
|
||||||
out.write(data);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Tried to write custom message without data. " +
|
|
||||||
"Programmer: did you forget to override #write()?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isError() {
|
|
||||||
return COMMAND_ERROR.equals(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CustomMessage error(String message) {
|
|
||||||
try {
|
|
||||||
return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for objects that have encrypted content
|
|
||||||
*/
|
|
||||||
public interface Encrypted {
|
|
||||||
void encrypt(byte[] publicKey) throws IOException;
|
|
||||||
|
|
||||||
void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException;
|
|
||||||
|
|
||||||
boolean isDecrypted();
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'getdata' command is used to request objects from a node.
|
|
||||||
*/
|
|
||||||
public class GetData implements MessagePayload {
|
|
||||||
List<InventoryVector> inventory;
|
|
||||||
|
|
||||||
private GetData(Builder builder) {
|
|
||||||
inventory = builder.inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.GETDATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<InventoryVector> getInventory() {
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
Encode.varInt(inventory.size(), out);
|
|
||||||
for (InventoryVector iv : inventory) {
|
|
||||||
iv.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<InventoryVector> inventory = new LinkedList<>();
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addInventoryVector(InventoryVector inventoryVector) {
|
|
||||||
this.inventory.add(inventoryVector);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder inventory(List<InventoryVector> inventory) {
|
|
||||||
this.inventory = inventory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetData build() {
|
|
||||||
return new GetData(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
|
|
||||||
*/
|
|
||||||
public class Inv implements MessagePayload {
|
|
||||||
private List<InventoryVector> inventory;
|
|
||||||
|
|
||||||
private Inv(Builder builder) {
|
|
||||||
inventory = builder.inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<InventoryVector> getInventory() {
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.INV;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
Encode.varInt(inventory.size(), out);
|
|
||||||
for (InventoryVector iv : inventory) {
|
|
||||||
iv.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<InventoryVector> inventory = new LinkedList<>();
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addInventoryVector(InventoryVector inventoryVector) {
|
|
||||||
this.inventory.add(inventoryVector);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder inventory(List<InventoryVector> inventory) {
|
|
||||||
this.inventory = inventory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Inv build() {
|
|
||||||
return new Inv(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A command can hold a network message payload
|
|
||||||
*/
|
|
||||||
public interface MessagePayload extends Streamable {
|
|
||||||
Command getCommand();
|
|
||||||
|
|
||||||
enum Command {
|
|
||||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A network message is exchanged between two nodes.
|
|
||||||
*/
|
|
||||||
public class NetworkMessage implements Streamable {
|
|
||||||
/**
|
|
||||||
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
|
|
||||||
*/
|
|
||||||
public final static int MAGIC = 0xE9BEB4D9;
|
|
||||||
public final static byte[] MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array();
|
|
||||||
|
|
||||||
private final MessagePayload payload;
|
|
||||||
|
|
||||||
public NetworkMessage(MessagePayload payload) {
|
|
||||||
this.payload = payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First 4 bytes of sha512(payload)
|
|
||||||
*/
|
|
||||||
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
|
|
||||||
byte[] d = security().sha512(bytes);
|
|
||||||
return new byte[]{d[0], d[1], d[2], d[3]};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The actual data, a message or an object. Not to be confused with objectPayload.
|
|
||||||
*/
|
|
||||||
public MessagePayload getPayload() {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
// magic
|
|
||||||
Encode.int32(MAGIC, out);
|
|
||||||
|
|
||||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
|
||||||
String command = payload.getCommand().name().toLowerCase();
|
|
||||||
out.write(command.getBytes("ASCII"));
|
|
||||||
for (int i = command.length(); i < 12; i++) {
|
|
||||||
out.write('\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream payloadStream = new ByteArrayOutputStream();
|
|
||||||
payload.write(payloadStream);
|
|
||||||
byte[] payloadBytes = payloadStream.toByteArray();
|
|
||||||
|
|
||||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
|
||||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
|
||||||
// larger than this.
|
|
||||||
Encode.int32(payloadBytes.length, out);
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
try {
|
|
||||||
out.write(getChecksum(payloadBytes));
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// message payload
|
|
||||||
out.write(payloadBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'object' command sends an object that is shared throughout the network.
|
|
||||||
*/
|
|
||||||
public class ObjectMessage implements MessagePayload {
|
|
||||||
private byte[] nonce;
|
|
||||||
private long expiresTime;
|
|
||||||
private long objectType;
|
|
||||||
/**
|
|
||||||
* The object's version
|
|
||||||
*/
|
|
||||||
private long version;
|
|
||||||
private long stream;
|
|
||||||
|
|
||||||
private ObjectPayload payload;
|
|
||||||
private byte[] payloadBytes;
|
|
||||||
|
|
||||||
private ObjectMessage(Builder builder) {
|
|
||||||
nonce = builder.nonce;
|
|
||||||
expiresTime = builder.expiresTime;
|
|
||||||
objectType = builder.objectType;
|
|
||||||
version = builder.payload.getVersion();
|
|
||||||
stream = builder.streamNumber;
|
|
||||||
payload = builder.payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.OBJECT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getNonce() {
|
|
||||||
return nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNonce(byte[] nonce) {
|
|
||||||
this.nonce = nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExpiresTime() {
|
|
||||||
return expiresTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getType() {
|
|
||||||
return objectType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectPayload getPayload() {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InventoryVector getInventoryVector() {
|
|
||||||
return new InventoryVector(
|
|
||||||
Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEncrypted() {
|
|
||||||
return payload instanceof Encrypted && !((Encrypted) payload).isDecrypted();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSigned() {
|
|
||||||
return payload.isSigned();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getBytesToSign() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
writeHeaderWithoutNonce(out);
|
|
||||||
payload.writeBytesToSign(out);
|
|
||||||
return out.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sign(PrivateKey key) {
|
|
||||||
if (payload.isSigned()) {
|
|
||||||
payload.setSignature(security().getSignature(getBytesToSign(), key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrypt(PrivateKey key) throws IOException, DecryptionFailedException {
|
|
||||||
if (payload instanceof Encrypted) {
|
|
||||||
((Encrypted) payload).decrypt(key.getPrivateEncryptionKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrypt(byte[] privateEncryptionKey) throws IOException, DecryptionFailedException {
|
|
||||||
if (payload instanceof Encrypted) {
|
|
||||||
((Encrypted) payload).decrypt(privateEncryptionKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void encrypt(byte[] publicEncryptionKey) throws IOException {
|
|
||||||
if (payload instanceof Encrypted) {
|
|
||||||
((Encrypted) payload).encrypt(publicEncryptionKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void encrypt(Pubkey publicKey) {
|
|
||||||
try {
|
|
||||||
if (payload instanceof Encrypted) {
|
|
||||||
((Encrypted) payload).encrypt(publicKey.getEncryptionKey());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
|
|
||||||
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
|
|
||||||
return security().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
if (nonce != null) {
|
|
||||||
out.write(nonce);
|
|
||||||
} else {
|
|
||||||
out.write(new byte[8]);
|
|
||||||
}
|
|
||||||
out.write(getPayloadBytesWithoutNonce());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeHeaderWithoutNonce(OutputStream out) throws IOException {
|
|
||||||
Encode.int64(expiresTime, out);
|
|
||||||
Encode.int32(objectType, out);
|
|
||||||
Encode.varInt(version, out);
|
|
||||||
Encode.varInt(stream, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPayloadBytesWithoutNonce() {
|
|
||||||
try {
|
|
||||||
if (payloadBytes == null) {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
writeHeaderWithoutNonce(out);
|
|
||||||
payload.write(out);
|
|
||||||
payloadBytes = out.toByteArray();
|
|
||||||
}
|
|
||||||
return payloadBytes;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private byte[] nonce;
|
|
||||||
private long expiresTime;
|
|
||||||
private long objectType = -1;
|
|
||||||
private long streamNumber;
|
|
||||||
private ObjectPayload payload;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nonce(byte[] nonce) {
|
|
||||||
this.nonce = nonce;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder expiresTime(long expiresTime) {
|
|
||||||
this.expiresTime = expiresTime;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder objectType(long objectType) {
|
|
||||||
this.objectType = objectType;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder objectType(ObjectType objectType) {
|
|
||||||
this.objectType = objectType.getNumber();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stream(long streamNumber) {
|
|
||||||
this.streamNumber = streamNumber;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder payload(ObjectPayload payload) {
|
|
||||||
this.payload = payload;
|
|
||||||
if (this.objectType == -1)
|
|
||||||
this.objectType = payload.getType().getNumber();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectMessage build() {
|
|
||||||
return new ObjectMessage(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,464 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The unencrypted message to be sent by 'msg' or 'broadcast'.
|
|
||||||
*/
|
|
||||||
public class Plaintext implements Streamable {
|
|
||||||
private final Type type;
|
|
||||||
private final BitmessageAddress from;
|
|
||||||
private final long encoding;
|
|
||||||
private final byte[] message;
|
|
||||||
private final byte[] ack;
|
|
||||||
private Object id;
|
|
||||||
private InventoryVector inventoryVector;
|
|
||||||
private BitmessageAddress to;
|
|
||||||
private byte[] signature;
|
|
||||||
private Status status;
|
|
||||||
private Long sent;
|
|
||||||
private Long received;
|
|
||||||
|
|
||||||
private Set<Label> labels;
|
|
||||||
private byte[] initialHash;
|
|
||||||
|
|
||||||
private Plaintext(Builder builder) {
|
|
||||||
id = builder.id;
|
|
||||||
inventoryVector = builder.inventoryVector;
|
|
||||||
type = builder.type;
|
|
||||||
from = builder.from;
|
|
||||||
to = builder.to;
|
|
||||||
encoding = builder.encoding;
|
|
||||||
message = builder.message;
|
|
||||||
ack = builder.ack;
|
|
||||||
signature = builder.signature;
|
|
||||||
status = builder.status;
|
|
||||||
sent = builder.sent;
|
|
||||||
received = builder.received;
|
|
||||||
labels = builder.labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Plaintext read(Type type, InputStream in) throws IOException {
|
|
||||||
return readWithoutSignature(type, in)
|
|
||||||
.signature(Decode.varBytes(in))
|
|
||||||
.received(UnixTime.now())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException {
|
|
||||||
long version = Decode.varInt(in);
|
|
||||||
return new Builder(type)
|
|
||||||
.addressVersion(version)
|
|
||||||
.stream(Decode.varInt(in))
|
|
||||||
.behaviorBitfield(Decode.int32(in))
|
|
||||||
.publicSigningKey(Decode.bytes(in, 64))
|
|
||||||
.publicEncryptionKey(Decode.bytes(in, 64))
|
|
||||||
.nonceTrialsPerByte(version >= 3 ? Decode.varInt(in) : 0)
|
|
||||||
.extraBytes(version >= 3 ? Decode.varInt(in) : 0)
|
|
||||||
.destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null)
|
|
||||||
.encoding(Decode.varInt(in))
|
|
||||||
.message(Decode.varBytes(in))
|
|
||||||
.ack(type == Type.MSG ? Decode.varBytes(in) : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InventoryVector getInventoryVector() {
|
|
||||||
return inventoryVector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInventoryVector(InventoryVector inventoryVector) {
|
|
||||||
this.inventoryVector = inventoryVector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress getFrom() {
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress getTo() {
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTo(BitmessageAddress to) {
|
|
||||||
if (this.to.getVersion() != 0)
|
|
||||||
throw new RuntimeException("Correct address already set");
|
|
||||||
if (!Arrays.equals(this.to.getRipe(), to.getRipe())) {
|
|
||||||
throw new RuntimeException("RIPEs don't match");
|
|
||||||
}
|
|
||||||
this.to = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Label> getLabels() {
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStream() {
|
|
||||||
return from.getStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUnread() {
|
|
||||||
for (Label label : labels) {
|
|
||||||
if (label.getType() == Label.Type.UNREAD) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(OutputStream out, boolean includeSignature) throws IOException {
|
|
||||||
Encode.varInt(from.getVersion(), out);
|
|
||||||
Encode.varInt(from.getStream(), out);
|
|
||||||
Encode.int32(from.getPubkey().getBehaviorBitfield(), out);
|
|
||||||
out.write(from.getPubkey().getSigningKey(), 1, 64);
|
|
||||||
out.write(from.getPubkey().getEncryptionKey(), 1, 64);
|
|
||||||
if (from.getVersion() >= 3) {
|
|
||||||
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out);
|
|
||||||
Encode.varInt(from.getPubkey().getExtraBytes(), out);
|
|
||||||
}
|
|
||||||
if (type == Type.MSG) {
|
|
||||||
out.write(to.getRipe());
|
|
||||||
}
|
|
||||||
Encode.varInt(encoding, out);
|
|
||||||
Encode.varInt(message.length, out);
|
|
||||||
out.write(message);
|
|
||||||
if (type == Type.MSG) {
|
|
||||||
Encode.varInt(ack.length, out);
|
|
||||||
out.write(ack);
|
|
||||||
}
|
|
||||||
if (includeSignature) {
|
|
||||||
if (signature == null) {
|
|
||||||
Encode.varInt(0, out);
|
|
||||||
} else {
|
|
||||||
Encode.varInt(signature.length, out);
|
|
||||||
out.write(signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
write(out, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(long id) {
|
|
||||||
if (this.id != null) throw new IllegalStateException("ID already set");
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getSent() {
|
|
||||||
return sent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getReceived() {
|
|
||||||
return received;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(Status status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubject() {
|
|
||||||
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
|
|
||||||
String firstLine = s.nextLine();
|
|
||||||
if (encoding == 2) {
|
|
||||||
return firstLine.substring("Subject:".length()).trim();
|
|
||||||
} else if (firstLine.length() > 50) {
|
|
||||||
return firstLine.substring(0, 50).trim() + "...";
|
|
||||||
} else {
|
|
||||||
return firstLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getText() {
|
|
||||||
try {
|
|
||||||
String text = new String(message, "UTF-8");
|
|
||||||
if (encoding == 2) {
|
|
||||||
return text.substring(text.indexOf("\nBody:") + 6);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Plaintext plaintext = (Plaintext) o;
|
|
||||||
return Objects.equals(encoding, plaintext.encoding) &&
|
|
||||||
Objects.equals(from, plaintext.from) &&
|
|
||||||
Arrays.equals(message, plaintext.message) &&
|
|
||||||
Arrays.equals(ack, plaintext.ack) &&
|
|
||||||
Arrays.equals(to.getRipe(), plaintext.to.getRipe()) &&
|
|
||||||
Arrays.equals(signature, plaintext.signature) &&
|
|
||||||
Objects.equals(status, plaintext.status) &&
|
|
||||||
Objects.equals(sent, plaintext.sent) &&
|
|
||||||
Objects.equals(received, plaintext.received) &&
|
|
||||||
Objects.equals(labels, plaintext.labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(from, encoding, message, ack, to, signature, status, sent, received, labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLabels(Label... labels) {
|
|
||||||
if (labels != null) {
|
|
||||||
Collections.addAll(this.labels, labels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLabels(Collection<Label> labels) {
|
|
||||||
if (labels != null) {
|
|
||||||
this.labels.addAll(labels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInitialHash(byte[] initialHash) {
|
|
||||||
this.initialHash = initialHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getInitialHash() {
|
|
||||||
return initialHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Encoding {
|
|
||||||
IGNORE(0), TRIVIAL(1), SIMPLE(2);
|
|
||||||
|
|
||||||
long code;
|
|
||||||
|
|
||||||
Encoding(long code) {
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Status {
|
|
||||||
DRAFT,
|
|
||||||
// For sent messages
|
|
||||||
PUBKEY_REQUESTED,
|
|
||||||
DOING_PROOF_OF_WORK,
|
|
||||||
SENT,
|
|
||||||
SENT_ACKNOWLEDGED,
|
|
||||||
RECEIVED
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
MSG, BROADCAST
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private Object id;
|
|
||||||
private InventoryVector inventoryVector;
|
|
||||||
private Type type;
|
|
||||||
private BitmessageAddress from;
|
|
||||||
private BitmessageAddress to;
|
|
||||||
private long addressVersion;
|
|
||||||
private long stream;
|
|
||||||
private int behaviorBitfield;
|
|
||||||
private byte[] publicSigningKey;
|
|
||||||
private byte[] publicEncryptionKey;
|
|
||||||
private long nonceTrialsPerByte;
|
|
||||||
private long extraBytes;
|
|
||||||
private byte[] destinationRipe;
|
|
||||||
private long encoding;
|
|
||||||
private byte[] message = new byte[0];
|
|
||||||
private byte[] ack = new byte[0];
|
|
||||||
private byte[] signature;
|
|
||||||
private long sent;
|
|
||||||
private long received;
|
|
||||||
private Status status;
|
|
||||||
private Set<Label> labels = new HashSet<>();
|
|
||||||
|
|
||||||
public Builder(Type type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder id(Object id) {
|
|
||||||
this.id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder IV(InventoryVector iv) {
|
|
||||||
this.inventoryVector = iv;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder from(BitmessageAddress address) {
|
|
||||||
from = address;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder to(BitmessageAddress address) {
|
|
||||||
if (type != Type.MSG && to != null)
|
|
||||||
throw new IllegalArgumentException("recipient address only allowed for msg");
|
|
||||||
to = address;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder addressVersion(long addressVersion) {
|
|
||||||
this.addressVersion = addressVersion;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder stream(long stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder behaviorBitfield(int behaviorBitfield) {
|
|
||||||
this.behaviorBitfield = behaviorBitfield;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder publicSigningKey(byte[] publicSigningKey) {
|
|
||||||
this.publicSigningKey = publicSigningKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
|
||||||
this.publicEncryptionKey = publicEncryptionKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
|
|
||||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder extraBytes(long extraBytes) {
|
|
||||||
this.extraBytes = extraBytes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder destinationRipe(byte[] ripe) {
|
|
||||||
if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg");
|
|
||||||
this.destinationRipe = ripe;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder encoding(Encoding encoding) {
|
|
||||||
this.encoding = encoding.getCode();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder encoding(long encoding) {
|
|
||||||
this.encoding = encoding;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder message(String subject, String message) {
|
|
||||||
try {
|
|
||||||
this.encoding = Encoding.SIMPLE.getCode();
|
|
||||||
this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder message(byte[] message) {
|
|
||||||
this.message = message;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ack(byte[] ack) {
|
|
||||||
if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ack only allowed for msg");
|
|
||||||
this.ack = ack;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder signature(byte[] signature) {
|
|
||||||
this.signature = signature;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder sent(long sent) {
|
|
||||||
this.sent = sent;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder received(long received) {
|
|
||||||
this.received = received;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder status(Status status) {
|
|
||||||
this.status = status;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder labels(Collection<Label> labels) {
|
|
||||||
this.labels.addAll(labels);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Plaintext build() {
|
|
||||||
if (from == null) {
|
|
||||||
from = new BitmessageAddress(Factory.createPubkey(
|
|
||||||
addressVersion,
|
|
||||||
stream,
|
|
||||||
publicSigningKey,
|
|
||||||
publicEncryptionKey,
|
|
||||||
nonceTrialsPerByte,
|
|
||||||
extraBytes,
|
|
||||||
behaviorBitfield
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if (to == null && type != Type.BROADCAST) {
|
|
||||||
to = new BitmessageAddress(0, 0, destinationRipe);
|
|
||||||
}
|
|
||||||
return new Plaintext(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
public interface PlaintextHolder {
|
|
||||||
Plaintext getPlaintext();
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object that can be written to an {@link OutputStream}
|
|
||||||
*/
|
|
||||||
public interface Streamable extends Serializable {
|
|
||||||
void write(OutputStream stream) throws IOException;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
|
||||||
*/
|
|
||||||
public class VerAck implements MessagePayload {
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.VERACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
// 'verack' doesn't have any payload, so there is nothing to write
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'version' command advertises this node's latest supported protocol version upon initiation.
|
|
||||||
*/
|
|
||||||
public class Version implements MessagePayload {
|
|
||||||
/**
|
|
||||||
* Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's
|
|
||||||
* version is lower but continue with the connection if it is higher.
|
|
||||||
*/
|
|
||||||
private final int version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bitfield of features to be enabled for this connection
|
|
||||||
*/
|
|
||||||
private final long services;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* standard UNIX timestamp in seconds
|
|
||||||
*/
|
|
||||||
private final long timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The network address of the node receiving this message (not including the time or stream number)
|
|
||||||
*/
|
|
||||||
private final NetworkAddress addrRecv;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The network address of the node emitting this message (not including the time or stream number and the ip itself
|
|
||||||
* is ignored by the receiver)
|
|
||||||
*/
|
|
||||||
private final NetworkAddress addrFrom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Random nonce used to detect connections to self.
|
|
||||||
*/
|
|
||||||
private final long nonce;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes.
|
|
||||||
*/
|
|
||||||
private final String userAgent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000
|
|
||||||
* stream numbers.
|
|
||||||
*/
|
|
||||||
private final long[] streams;
|
|
||||||
|
|
||||||
private Version(Builder builder) {
|
|
||||||
version = builder.version;
|
|
||||||
services = builder.services;
|
|
||||||
timestamp = builder.timestamp;
|
|
||||||
addrRecv = builder.addrRecv;
|
|
||||||
addrFrom = builder.addrFrom;
|
|
||||||
nonce = builder.nonce;
|
|
||||||
userAgent = builder.userAgent;
|
|
||||||
streams = builder.streamNumbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getServices() {
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkAddress getAddrRecv() {
|
|
||||||
return addrRecv;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkAddress getAddrFrom() {
|
|
||||||
return addrFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonce() {
|
|
||||||
return nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserAgent() {
|
|
||||||
return userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long[] getStreams() {
|
|
||||||
return streams;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
Encode.int32(version, stream);
|
|
||||||
Encode.int64(services, stream);
|
|
||||||
Encode.int64(timestamp, stream);
|
|
||||||
addrRecv.write(stream, true);
|
|
||||||
addrFrom.write(stream, true);
|
|
||||||
Encode.int64(nonce, stream);
|
|
||||||
Encode.varString(userAgent, stream);
|
|
||||||
Encode.varIntList(streams, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private int version;
|
|
||||||
private long services;
|
|
||||||
private long timestamp;
|
|
||||||
private NetworkAddress addrRecv;
|
|
||||||
private NetworkAddress addrFrom;
|
|
||||||
private long nonce;
|
|
||||||
private String userAgent;
|
|
||||||
private long[] streamNumbers;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder defaults() {
|
|
||||||
version = BitmessageContext.CURRENT_VERSION;
|
|
||||||
services = 1;
|
|
||||||
timestamp = UnixTime.now();
|
|
||||||
nonce = new Random().nextInt();
|
|
||||||
userAgent = "/Jabit:0.0.1/";
|
|
||||||
streamNumbers = new long[]{1};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder version(int version) {
|
|
||||||
this.version = version;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder services(long services) {
|
|
||||||
this.services = services;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder timestamp(long timestamp) {
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addrRecv(NetworkAddress addrRecv) {
|
|
||||||
this.addrRecv = addrRecv;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addrFrom(NetworkAddress addrFrom) {
|
|
||||||
this.addrFrom = addrFrom;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nonce(long nonce) {
|
|
||||||
this.nonce = nonce;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder userAgent(String userAgent) {
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder streams(long... streamNumbers) {
|
|
||||||
this.streamNumbers = streamNumbers;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Version build() {
|
|
||||||
return new Version(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Encrypted;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
|
||||||
* Broadcasts are version 4 or 5.
|
|
||||||
*/
|
|
||||||
public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder {
|
|
||||||
protected final long stream;
|
|
||||||
protected CryptoBox encrypted;
|
|
||||||
protected Plaintext plaintext;
|
|
||||||
|
|
||||||
protected Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
|
|
||||||
super(version);
|
|
||||||
this.stream = stream;
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
this.plaintext = plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getVersion(BitmessageAddress address) {
|
|
||||||
return address.getVersion() < 4 ? 4 : 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSigned() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return plaintext.getSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
plaintext.setSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plaintext getPlaintext() {
|
|
||||||
return plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encrypt(byte[] publicKey) throws IOException {
|
|
||||||
this.encrypted = new CryptoBox(plaintext, publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void encrypt() throws IOException {
|
|
||||||
encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
|
||||||
plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException {
|
|
||||||
decrypt(address.getPublicDecryptionKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDecrypted() {
|
|
||||||
return plaintext != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.*;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
|
|
||||||
public class CryptoBox implements Streamable {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class);
|
|
||||||
|
|
||||||
private final byte[] initializationVector;
|
|
||||||
private final int curveType;
|
|
||||||
private final byte[] R;
|
|
||||||
private final byte[] mac;
|
|
||||||
private byte[] encrypted;
|
|
||||||
|
|
||||||
private long addressVersion;
|
|
||||||
|
|
||||||
|
|
||||||
public CryptoBox(Streamable data, byte[] K) throws IOException {
|
|
||||||
this(Encode.bytes(data), K);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CryptoBox(byte[] data, byte[] K) throws IOException {
|
|
||||||
curveType = 0x02CA;
|
|
||||||
|
|
||||||
// 1. The destination public key is called K.
|
|
||||||
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
|
|
||||||
initializationVector = security().randomBytes(16);
|
|
||||||
|
|
||||||
// 3. Generate a new random EC key pair with private key called r and public key called R.
|
|
||||||
byte[] r = security().randomBytes(PRIVATE_KEY_SIZE);
|
|
||||||
R = security().createPublicKey(r);
|
|
||||||
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
|
|
||||||
byte[] P = security().multiply(K, r);
|
|
||||||
byte[] X = Points.getX(P);
|
|
||||||
// 5. Use the X component of public key P and calculate the SHA512 hash H.
|
|
||||||
byte[] H = security().sha512(X);
|
|
||||||
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
|
||||||
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
|
|
||||||
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
|
|
||||||
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
|
|
||||||
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
|
|
||||||
encrypted = security().crypt(true, data, key_e, initializationVector);
|
|
||||||
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
|
|
||||||
mac = calculateMac(key_m);
|
|
||||||
|
|
||||||
// The resulting data is: IV + R + cipher text + MAC
|
|
||||||
}
|
|
||||||
|
|
||||||
private CryptoBox(Builder builder) {
|
|
||||||
initializationVector = builder.initializationVector;
|
|
||||||
curveType = builder.curveType;
|
|
||||||
R = security().createPoint(builder.xComponent, builder.yComponent);
|
|
||||||
encrypted = builder.encrypted;
|
|
||||||
mac = builder.mac;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CryptoBox read(InputStream stream, int length) throws IOException {
|
|
||||||
AccessCounter counter = new AccessCounter();
|
|
||||||
return new Builder()
|
|
||||||
.IV(Decode.bytes(stream, 16, counter))
|
|
||||||
.curveType(Decode.uint16(stream, counter))
|
|
||||||
.X(Decode.shortVarBytes(stream, counter))
|
|
||||||
.Y(Decode.shortVarBytes(stream, counter))
|
|
||||||
.encrypted(Decode.bytes(stream, length - counter.length() - 32))
|
|
||||||
.MAC(Decode.bytes(stream, 32))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param k a private key, typically should be 32 bytes long
|
|
||||||
* @return an InputStream yielding the decrypted data
|
|
||||||
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
|
|
||||||
* @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a>
|
|
||||||
*/
|
|
||||||
public InputStream decrypt(byte[] k) throws DecryptionFailedException {
|
|
||||||
// 1. The private key used to decrypt is called k.
|
|
||||||
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
|
|
||||||
byte[] P = security().multiply(R, k);
|
|
||||||
// 3. Use the X component of public key P and calculate the SHA512 hash H.
|
|
||||||
byte[] H = security().sha512(Arrays.copyOfRange(P, 1, 33));
|
|
||||||
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
|
||||||
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
|
|
||||||
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
|
|
||||||
|
|
||||||
// 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data.
|
|
||||||
// 6. Compare MAC with MAC'. If not equal, decryption will fail.
|
|
||||||
if (!Arrays.equals(mac, calculateMac(key_m))) {
|
|
||||||
throw new DecryptionFailedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
|
|
||||||
// and the cipher text as payload. The output is the padded input text.
|
|
||||||
return new ByteArrayInputStream(security().crypt(false, encrypted, key_e, initializationVector));
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] calculateMac(byte[] key_m) {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream macData = new ByteArrayOutputStream();
|
|
||||||
writeWithoutMAC(macData);
|
|
||||||
return security().mac(key_m, macData.toByteArray());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeWithoutMAC(OutputStream out) throws IOException {
|
|
||||||
out.write(initializationVector);
|
|
||||||
Encode.int16(curveType, out);
|
|
||||||
writeCoordinateComponent(out, Points.getX(R));
|
|
||||||
writeCoordinateComponent(out, Points.getY(R));
|
|
||||||
out.write(encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException {
|
|
||||||
int offset = Bytes.numberOfLeadingZeros(x);
|
|
||||||
int length = x.length - offset;
|
|
||||||
Encode.int16(length, out);
|
|
||||||
out.write(x, offset, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
writeWithoutMAC(stream);
|
|
||||||
stream.write(mac);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private byte[] initializationVector;
|
|
||||||
private int curveType;
|
|
||||||
private byte[] xComponent;
|
|
||||||
private byte[] yComponent;
|
|
||||||
private byte[] encrypted;
|
|
||||||
private byte[] mac;
|
|
||||||
|
|
||||||
public Builder IV(byte[] initializationVector) {
|
|
||||||
this.initializationVector = initializationVector;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder curveType(int curveType) {
|
|
||||||
if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType);
|
|
||||||
this.curveType = curveType;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder X(byte[] xComponent) {
|
|
||||||
this.xComponent = xComponent;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder Y(byte[] yComponent) {
|
|
||||||
this.yComponent = yComponent;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder encrypted(byte[] encrypted) {
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder MAC(byte[] mac) {
|
|
||||||
this.mac = mac;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CryptoBox build() {
|
|
||||||
return new CryptoBox(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really
|
|
||||||
* have to know what it is.
|
|
||||||
*/
|
|
||||||
public class GenericPayload extends ObjectPayload {
|
|
||||||
private long stream;
|
|
||||||
private byte[] data;
|
|
||||||
|
|
||||||
public GenericPayload(long version, long stream, byte[] data) {
|
|
||||||
super(version);
|
|
||||||
this.stream = stream;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GenericPayload read(long version, InputStream is, long stream, int length) throws IOException {
|
|
||||||
return new GenericPayload(version, stream, Decode.bytes(is, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
GenericPayload that = (GenericPayload) o;
|
|
||||||
|
|
||||||
if (stream != that.stream) return false;
|
|
||||||
return Arrays.equals(data, that.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = (int) (stream ^ (stream >>> 32));
|
|
||||||
result = 31 * result + Arrays.hashCode(data);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request for a public key.
|
|
||||||
*/
|
|
||||||
public class GetPubkey extends ObjectPayload {
|
|
||||||
private long stream;
|
|
||||||
private byte[] ripeTag;
|
|
||||||
|
|
||||||
public GetPubkey(BitmessageAddress address) {
|
|
||||||
super(address.getVersion());
|
|
||||||
this.stream = address.getStream();
|
|
||||||
if (address.getVersion() < 4)
|
|
||||||
this.ripeTag = address.getRipe();
|
|
||||||
else
|
|
||||||
this.ripeTag = address.getTag();
|
|
||||||
}
|
|
||||||
|
|
||||||
private GetPubkey(long version, long stream, byte[] ripeOrTag) {
|
|
||||||
super(version);
|
|
||||||
this.stream = stream;
|
|
||||||
this.ripeTag = ripeOrTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException {
|
|
||||||
return new GetPubkey(version, stream, Decode.bytes(is, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return an array of bytes that represent either the ripe, or the tag of an address, depending on the
|
|
||||||
* address version.
|
|
||||||
*/
|
|
||||||
public byte[] getRipeTag() {
|
|
||||||
return ripeTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.GET_PUBKEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(ripeTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Encrypted;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for person-to-person messages.
|
|
||||||
*/
|
|
||||||
public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
|
|
||||||
private long stream;
|
|
||||||
private CryptoBox encrypted;
|
|
||||||
private Plaintext plaintext;
|
|
||||||
|
|
||||||
private Msg(long stream, CryptoBox encrypted) {
|
|
||||||
super(1);
|
|
||||||
this.stream = stream;
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Msg(Plaintext plaintext) {
|
|
||||||
super(1);
|
|
||||||
this.stream = plaintext.getStream();
|
|
||||||
this.plaintext = plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Msg read(InputStream in, long stream, int length) throws IOException {
|
|
||||||
return new Msg(stream, CryptoBox.read(in, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plaintext getPlaintext() {
|
|
||||||
return plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.MSG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSigned() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
plaintext.write(out, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return plaintext.getSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
plaintext.setSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encrypt(byte[] publicKey) throws IOException {
|
|
||||||
this.encrypted = new CryptoBox(plaintext, publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
|
||||||
plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDecrypted() {
|
|
||||||
return plaintext != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it.");
|
|
||||||
encrypted.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The payload of an 'object' command. This is shared by the network.
|
|
||||||
*/
|
|
||||||
public abstract class ObjectPayload implements Streamable {
|
|
||||||
private final long version;
|
|
||||||
|
|
||||||
protected ObjectPayload(long version) {
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public abstract ObjectType getType();
|
|
||||||
|
|
||||||
public abstract long getStream();
|
|
||||||
|
|
||||||
public long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSigned() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
|
|
||||||
* appended with the data described in this table down to the extra_bytes. Therefore, this must
|
|
||||||
* be checked and set in the {@link ObjectMessage} object.
|
|
||||||
*/
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Known types for 'object' messages. Must not be used where an unknown type must be resent.
|
|
||||||
*/
|
|
||||||
public enum ObjectType {
|
|
||||||
GET_PUBKEY(0),
|
|
||||||
PUBKEY(1),
|
|
||||||
MSG(2),
|
|
||||||
BROADCAST(3);
|
|
||||||
|
|
||||||
int number;
|
|
||||||
|
|
||||||
ObjectType(int number) {
|
|
||||||
this.number = number;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectType fromNumber(long number) {
|
|
||||||
for (ObjectType type : values()) {
|
|
||||||
if (type.number == number) return type;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNumber() {
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
|
||||||
*/
|
|
||||||
public abstract class Pubkey extends ObjectPayload {
|
|
||||||
public final static long LATEST_VERSION = 4;
|
|
||||||
|
|
||||||
protected Pubkey(long version) {
|
|
||||||
super(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
|
|
||||||
return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract byte[] getSigningKey();
|
|
||||||
|
|
||||||
public abstract byte[] getEncryptionKey();
|
|
||||||
|
|
||||||
public abstract int getBehaviorBitfield();
|
|
||||||
|
|
||||||
public byte[] getRipe() {
|
|
||||||
return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonceTrialsPerByte() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExtraBytes() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUnencrypted(OutputStream out) throws IOException {
|
|
||||||
write(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] add0x04(byte[] key) {
|
|
||||||
if (key.length == 65) return key;
|
|
||||||
byte[] result = new byte[65];
|
|
||||||
result[0] = 4;
|
|
||||||
System.arraycopy(key, 0, result, 1, 64);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bits 0 through 29 are yet undefined
|
|
||||||
*/
|
|
||||||
public enum Feature {
|
|
||||||
/**
|
|
||||||
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
|
|
||||||
* messages bound for them.
|
|
||||||
*/
|
|
||||||
INCLUDE_DESTINATION(1 << 30),
|
|
||||||
/**
|
|
||||||
* If true, the receiving node does send acknowledgements (rather than dropping them).
|
|
||||||
*/
|
|
||||||
DOES_ACK(1 << 31);
|
|
||||||
|
|
||||||
private int bit;
|
|
||||||
|
|
||||||
Feature(int bit) {
|
|
||||||
this.bit = bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int bitfield(Feature... features) {
|
|
||||||
int bits = 0;
|
|
||||||
for (Feature feature : features) {
|
|
||||||
bits |= feature.bit;
|
|
||||||
}
|
|
||||||
return bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Feature[] features(int bitfield) {
|
|
||||||
ArrayList<Feature> features = new ArrayList<>(Feature.values().length);
|
|
||||||
for (Feature feature : Feature.values()) {
|
|
||||||
if ((bitfield & feature.bit) != 0) {
|
|
||||||
features.add(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return features.toArray(new Feature[features.size()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A version 2 public key.
|
|
||||||
*/
|
|
||||||
public class V2Pubkey extends Pubkey {
|
|
||||||
protected long stream;
|
|
||||||
protected int behaviorBitfield;
|
|
||||||
protected byte[] publicSigningKey; // 64 Bytes
|
|
||||||
protected byte[] publicEncryptionKey; // 64 Bytes
|
|
||||||
|
|
||||||
protected V2Pubkey(long version) {
|
|
||||||
super(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
private V2Pubkey(long version, Builder builder) {
|
|
||||||
super(version);
|
|
||||||
stream = builder.streamNumber;
|
|
||||||
behaviorBitfield = builder.behaviorBitfield;
|
|
||||||
publicSigningKey = add0x04(builder.publicSigningKey);
|
|
||||||
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V2Pubkey read(InputStream is, long stream) throws IOException {
|
|
||||||
return new V2Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.behaviorBitfield((int) Decode.uint32(is))
|
|
||||||
.publicSigningKey(Decode.bytes(is, 64))
|
|
||||||
.publicEncryptionKey(Decode.bytes(is, 64))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getVersion() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.PUBKEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSigningKey() {
|
|
||||||
return publicSigningKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncryptionKey() {
|
|
||||||
return publicEncryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBehaviorBitfield() {
|
|
||||||
return behaviorBitfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream os) throws IOException {
|
|
||||||
Encode.int32(behaviorBitfield, os);
|
|
||||||
os.write(publicSigningKey, 1, 64);
|
|
||||||
os.write(publicEncryptionKey, 1, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private long streamNumber;
|
|
||||||
private int behaviorBitfield;
|
|
||||||
private byte[] publicSigningKey;
|
|
||||||
private byte[] publicEncryptionKey;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stream(long streamNumber) {
|
|
||||||
this.streamNumber = streamNumber;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder behaviorBitfield(int behaviorBitfield) {
|
|
||||||
this.behaviorBitfield = behaviorBitfield;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder publicSigningKey(byte[] publicSigningKey) {
|
|
||||||
this.publicSigningKey = publicSigningKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
|
||||||
this.publicEncryptionKey = publicEncryptionKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V2Pubkey build() {
|
|
||||||
return new V2Pubkey(2, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A version 3 public key.
|
|
||||||
*/
|
|
||||||
public class V3Pubkey extends V2Pubkey {
|
|
||||||
long nonceTrialsPerByte;
|
|
||||||
long extraBytes;
|
|
||||||
byte[] signature;
|
|
||||||
|
|
||||||
protected V3Pubkey(long version, Builder builder) {
|
|
||||||
super(version);
|
|
||||||
stream = builder.streamNumber;
|
|
||||||
behaviorBitfield = builder.behaviorBitfield;
|
|
||||||
publicSigningKey = add0x04(builder.publicSigningKey);
|
|
||||||
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
|
|
||||||
nonceTrialsPerByte = builder.nonceTrialsPerByte;
|
|
||||||
extraBytes = builder.extraBytes;
|
|
||||||
signature = builder.signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V3Pubkey read(InputStream is, long stream) throws IOException {
|
|
||||||
return new V3Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.behaviorBitfield(Decode.int32(is))
|
|
||||||
.publicSigningKey(Decode.bytes(is, 64))
|
|
||||||
.publicEncryptionKey(Decode.bytes(is, 64))
|
|
||||||
.nonceTrialsPerByte(Decode.varInt(is))
|
|
||||||
.extraBytes(Decode.varInt(is))
|
|
||||||
.signature(Decode.varBytes(is))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
writeBytesToSign(out);
|
|
||||||
Encode.varInt(signature.length, out);
|
|
||||||
out.write(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getVersion() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonceTrialsPerByte() {
|
|
||||||
return nonceTrialsPerByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExtraBytes() {
|
|
||||||
return extraBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSigned() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
super.write(out);
|
|
||||||
Encode.varInt(nonceTrialsPerByte, out);
|
|
||||||
Encode.varInt(extraBytes, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
V3Pubkey pubkey = (V3Pubkey) o;
|
|
||||||
return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) &&
|
|
||||||
Objects.equals(extraBytes, pubkey.extraBytes) &&
|
|
||||||
stream == pubkey.stream &&
|
|
||||||
behaviorBitfield == pubkey.behaviorBitfield &&
|
|
||||||
Arrays.equals(publicSigningKey, pubkey.publicSigningKey) &&
|
|
||||||
Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(nonceTrialsPerByte, extraBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private long streamNumber;
|
|
||||||
private int behaviorBitfield;
|
|
||||||
private byte[] publicSigningKey;
|
|
||||||
private byte[] publicEncryptionKey;
|
|
||||||
private long nonceTrialsPerByte;
|
|
||||||
private long extraBytes;
|
|
||||||
private byte[] signature = new byte[0];
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stream(long streamNumber) {
|
|
||||||
this.streamNumber = streamNumber;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder behaviorBitfield(int behaviorBitfield) {
|
|
||||||
this.behaviorBitfield = behaviorBitfield;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder publicSigningKey(byte[] publicSigningKey) {
|
|
||||||
this.publicSigningKey = publicSigningKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
|
||||||
this.publicEncryptionKey = publicEncryptionKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
|
|
||||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder extraBytes(long extraBytes) {
|
|
||||||
this.extraBytes = extraBytes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder signature(byte[] signature) {
|
|
||||||
this.signature = signature;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V3Pubkey build() {
|
|
||||||
return new V3Pubkey(3, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
|
||||||
* Broadcasts are version 4 or 5.
|
|
||||||
*/
|
|
||||||
public class V4Broadcast extends Broadcast {
|
|
||||||
protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
|
|
||||||
super(version, stream, encrypted, plaintext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
|
|
||||||
super(4, senderAddress.getStream(), null, plaintext);
|
|
||||||
if (senderAddress.getVersion() >= 4)
|
|
||||||
throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V4Broadcast read(InputStream in, long stream, int length) throws IOException {
|
|
||||||
return new V4Broadcast(4, stream, CryptoBox.read(in, length), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.BROADCAST;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
plaintext.write(out, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
encrypted.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Encrypted;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
|
|
||||||
* done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and
|
|
||||||
* use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them
|
|
||||||
* to create messages to be used in spam or in flooding attacks.
|
|
||||||
*/
|
|
||||||
public class V4Pubkey extends Pubkey implements Encrypted {
|
|
||||||
private long stream;
|
|
||||||
private byte[] tag;
|
|
||||||
private CryptoBox encrypted;
|
|
||||||
private V3Pubkey decrypted;
|
|
||||||
|
|
||||||
private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) {
|
|
||||||
super(4);
|
|
||||||
this.stream = stream;
|
|
||||||
this.tag = tag;
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V4Pubkey(V3Pubkey decrypted) {
|
|
||||||
super(4);
|
|
||||||
this.decrypted = decrypted;
|
|
||||||
this.stream = decrypted.stream;
|
|
||||||
this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException {
|
|
||||||
if (encrypted)
|
|
||||||
return new V4Pubkey(stream,
|
|
||||||
Decode.bytes(in, 32),
|
|
||||||
CryptoBox.read(in, length - 32));
|
|
||||||
else
|
|
||||||
return new V4Pubkey(V3Pubkey.read(in, stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encrypt(byte[] publicKey) throws IOException {
|
|
||||||
if (getSignature() == null) throw new IllegalStateException("Pubkey must be signed before encryption.");
|
|
||||||
this.encrypted = new CryptoBox(decrypted, publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
|
||||||
decrypted = V3Pubkey.read(encrypted.decrypt(privateKey), stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDecrypted() {
|
|
||||||
return decrypted != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(tag);
|
|
||||||
encrypted.write(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeUnencrypted(OutputStream out) throws IOException {
|
|
||||||
decrypted.write(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
out.write(tag);
|
|
||||||
decrypted.writeBytesToSign(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getVersion() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.PUBKEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getTag() {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSigningKey() {
|
|
||||||
return decrypted.getSigningKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncryptionKey() {
|
|
||||||
return decrypted.getEncryptionKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBehaviorBitfield() {
|
|
||||||
return decrypted.getBehaviorBitfield();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSignature() {
|
|
||||||
if (decrypted != null)
|
|
||||||
return decrypted.getSignature();
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
decrypted.setSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSigned() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonceTrialsPerByte() {
|
|
||||||
return decrypted.getNonceTrialsPerByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExtraBytes() {
|
|
||||||
return decrypted.getExtraBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
V4Pubkey v4Pubkey = (V4Pubkey) o;
|
|
||||||
|
|
||||||
if (stream != v4Pubkey.stream) return false;
|
|
||||||
if (!Arrays.equals(tag, v4Pubkey.tag)) return false;
|
|
||||||
return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = (int) (stream ^ (stream >>> 32));
|
|
||||||
result = 31 * result + Arrays.hashCode(tag);
|
|
||||||
result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
|
||||||
*/
|
|
||||||
public class V5Broadcast extends V4Broadcast {
|
|
||||||
private byte[] tag;
|
|
||||||
|
|
||||||
private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) {
|
|
||||||
super(5, stream, encrypted, null);
|
|
||||||
this.tag = tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
|
|
||||||
super(5, senderAddress.getStream(), null, plaintext);
|
|
||||||
if (senderAddress.getVersion() < 4)
|
|
||||||
throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion());
|
|
||||||
this.tag = senderAddress.getTag();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V5Broadcast read(InputStream is, long stream, int length) throws IOException {
|
|
||||||
return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getTag() {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
out.write(tag);
|
|
||||||
super.writeBytesToSign(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
out.write(tag);
|
|
||||||
super.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.valueobject;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
import ch.dissem.bitmessage.utils.Strings;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class InventoryVector implements Streamable, Serializable {
|
|
||||||
/**
|
|
||||||
* Hash of the object
|
|
||||||
*/
|
|
||||||
private final byte[] hash;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (!(o instanceof InventoryVector)) return false;
|
|
||||||
|
|
||||||
InventoryVector that = (InventoryVector) o;
|
|
||||||
|
|
||||||
return Arrays.equals(hash, that.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hash != null ? Arrays.hashCode(hash) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getHash() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InventoryVector(byte[] hash) {
|
|
||||||
this.hash = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Strings.hex(hash).toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.valueobject;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class Label implements Serializable {
|
|
||||||
private Object id;
|
|
||||||
private String label;
|
|
||||||
private Type type;
|
|
||||||
private int color;
|
|
||||||
|
|
||||||
public Label(String label, Type type, int color) {
|
|
||||||
this.label = label;
|
|
||||||
this.type = type;
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return RGBA representation for the color.
|
|
||||||
*/
|
|
||||||
public int getColor() {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param color RGBA representation for the color.
|
|
||||||
*/
|
|
||||||
public void setColor(int color) {
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Object id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Label label1 = (Label) o;
|
|
||||||
return Objects.equals(label, label1.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
INBOX,
|
|
||||||
BROADCAST,
|
|
||||||
DRAFT,
|
|
||||||
OUTBOX,
|
|
||||||
SENT,
|
|
||||||
UNREAD,
|
|
||||||
TRASH
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.valueobject;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A node's address. It's written in IPv6 format.
|
|
||||||
*/
|
|
||||||
public class NetworkAddress implements Streamable {
|
|
||||||
private long time;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stream number for this node
|
|
||||||
*/
|
|
||||||
private long stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* same service(s) listed in version
|
|
||||||
*/
|
|
||||||
private long services;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address
|
|
||||||
* (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address).
|
|
||||||
*/
|
|
||||||
private byte[] ipv6;
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
private NetworkAddress(Builder builder) {
|
|
||||||
time = builder.time;
|
|
||||||
stream = builder.stream;
|
|
||||||
services = builder.services;
|
|
||||||
ipv6 = builder.ipv6;
|
|
||||||
port = builder.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getIPv6() {
|
|
||||||
return ipv6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getServices() {
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTime() {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTime(long time) {
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InetAddress toInetAddress() {
|
|
||||||
try {
|
|
||||||
return InetAddress.getByAddress(ipv6);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
NetworkAddress that = (NetworkAddress) o;
|
|
||||||
|
|
||||||
return port == that.port && Arrays.equals(ipv6, that.ipv6);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = ipv6 != null ? Arrays.hashCode(ipv6) : 0;
|
|
||||||
result = 31 * result + port;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "[" + toInetAddress() + "]:" + port;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
write(stream, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(OutputStream stream, boolean light) throws IOException {
|
|
||||||
if (!light) {
|
|
||||||
Encode.int64(time, stream);
|
|
||||||
Encode.int32(this.stream, stream);
|
|
||||||
}
|
|
||||||
Encode.int64(services, stream);
|
|
||||||
stream.write(ipv6);
|
|
||||||
Encode.int16(port, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private long time;
|
|
||||||
private long stream;
|
|
||||||
private long services = 1;
|
|
||||||
private byte[] ipv6;
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder time(final long time) {
|
|
||||||
this.time = time;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stream(final long stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder services(final long services) {
|
|
||||||
this.services = services;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ip(InetAddress inetAddress) {
|
|
||||||
byte[] addr = inetAddress.getAddress();
|
|
||||||
if (addr.length == 16) {
|
|
||||||
this.ipv6 = addr;
|
|
||||||
} else if (addr.length == 4) {
|
|
||||||
this.ipv6 = new byte[16];
|
|
||||||
this.ipv6[10] = (byte) 0xff;
|
|
||||||
this.ipv6[11] = (byte) 0xff;
|
|
||||||
System.arraycopy(addr, 0, this.ipv6, 12, 4);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Weird address " + inetAddress);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ipv6(byte[] ipv6) {
|
|
||||||
this.ipv6 = ipv6;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ipv6(int p00, int p01, int p02, int p03,
|
|
||||||
int p04, int p05, int p06, int p07,
|
|
||||||
int p08, int p09, int p10, int p11,
|
|
||||||
int p12, int p13, int p14, int p15) {
|
|
||||||
this.ipv6 = new byte[]{
|
|
||||||
(byte) p00, (byte) p01, (byte) p02, (byte) p03,
|
|
||||||
(byte) p04, (byte) p05, (byte) p06, (byte) p07,
|
|
||||||
(byte) p08, (byte) p09, (byte) p10, (byte) p11,
|
|
||||||
(byte) p12, (byte) p13, (byte) p14, (byte) p15
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ipv4(int p00, int p01, int p02, int p03) {
|
|
||||||
this.ipv6 = new byte[]{
|
|
||||||
(byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00,
|
|
||||||
(byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00,
|
|
||||||
(byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff,
|
|
||||||
(byte) p00, (byte) p01, (byte) p02, (byte) p03
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder port(final int port) {
|
|
||||||
this.port = port;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkAddress build() {
|
|
||||||
if (time == 0) {
|
|
||||||
time = UnixTime.now();
|
|
||||||
}
|
|
||||||
return new NetworkAddress(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.valueobject;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
|
|
||||||
* {@link Pubkey} object.
|
|
||||||
*/
|
|
||||||
public class PrivateKey implements Streamable {
|
|
||||||
public static final int PRIVATE_KEY_SIZE = 32;
|
|
||||||
private final byte[] privateSigningKey;
|
|
||||||
private final byte[] privateEncryptionKey;
|
|
||||||
|
|
||||||
private final Pubkey pubkey;
|
|
||||||
|
|
||||||
public PrivateKey(boolean shorter, long stream, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
|
||||||
byte[] privSK;
|
|
||||||
byte[] pubSK;
|
|
||||||
byte[] privEK;
|
|
||||||
byte[] pubEK;
|
|
||||||
byte[] ripe;
|
|
||||||
do {
|
|
||||||
privSK = security().randomBytes(PRIVATE_KEY_SIZE);
|
|
||||||
privEK = security().randomBytes(PRIVATE_KEY_SIZE);
|
|
||||||
pubSK = security().createPublicKey(privSK);
|
|
||||||
pubEK = security().createPublicKey(privEK);
|
|
||||||
ripe = Pubkey.getRipe(pubSK, pubEK);
|
|
||||||
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
|
|
||||||
this.privateSigningKey = privSK;
|
|
||||||
this.privateEncryptionKey = privEK;
|
|
||||||
this.pubkey = security().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
|
|
||||||
nonceTrialsPerByte, extraBytes, features);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey(byte[] privateSigningKey, byte[] privateEncryptionKey, Pubkey pubkey) {
|
|
||||||
this.privateSigningKey = privateSigningKey;
|
|
||||||
this.privateEncryptionKey = privateEncryptionKey;
|
|
||||||
this.pubkey = pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey(long version, long stream, String passphrase, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
|
||||||
try {
|
|
||||||
// FIXME: this is most definitely wrong
|
|
||||||
this.privateSigningKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
|
|
||||||
this.privateEncryptionKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
|
|
||||||
this.pubkey = security().createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
|
|
||||||
nonceTrialsPerByte, extraBytes, features);
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrivateKey read(InputStream is) throws IOException {
|
|
||||||
int version = (int) Decode.varInt(is);
|
|
||||||
long stream = Decode.varInt(is);
|
|
||||||
int len = (int) Decode.varInt(is);
|
|
||||||
Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false);
|
|
||||||
len = (int) Decode.varInt(is);
|
|
||||||
byte[] signingKey = Decode.bytes(is, len);
|
|
||||||
len = (int) Decode.varInt(is);
|
|
||||||
byte[] encryptionKey = Decode.bytes(is, len);
|
|
||||||
return new PrivateKey(signingKey, encryptionKey, pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPrivateSigningKey() {
|
|
||||||
return privateSigningKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPrivateEncryptionKey() {
|
|
||||||
return privateEncryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pubkey getPubkey() {
|
|
||||||
return pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
Encode.varInt(pubkey.getVersion(), out);
|
|
||||||
Encode.varInt(pubkey.getStream(), out);
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
pubkey.writeUnencrypted(baos);
|
|
||||||
Encode.varInt(baos.size(), out);
|
|
||||||
out.write(baos.toByteArray());
|
|
||||||
Encode.varInt(privateSigningKey.length, out);
|
|
||||||
out.write(privateSigningKey);
|
|
||||||
Encode.varInt(privateEncryptionKey.length, out);
|
|
||||||
out.write(privateEncryptionKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates an illegal Bitmessage address
|
|
||||||
*/
|
|
||||||
public class AddressFormatException extends RuntimeException {
|
|
||||||
public AddressFormatException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.exception;
|
|
||||||
|
|
||||||
public class DecryptionFailedException extends Exception {
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.exception;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Strings;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class InsufficientProofOfWorkException extends IOException {
|
|
||||||
public InsufficientProofOfWorkException(byte[] target, byte[] hash) {
|
|
||||||
super("Insufficient proof of work: " + Strings.hex(target) + " required, " + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception on the node that's severe enough to cause the client to disconnect this node.
|
|
||||||
*
|
|
||||||
* @author Ch. Basler
|
|
||||||
*/
|
|
||||||
public class NodeException extends RuntimeException {
|
|
||||||
public NodeException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.factory;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.NetworkMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.*;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.NodeException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
|
|
||||||
*/
|
|
||||||
public class Factory {
|
|
||||||
public static final Logger LOG = LoggerFactory.getLogger(Factory.class);
|
|
||||||
|
|
||||||
public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException {
|
|
||||||
try {
|
|
||||||
return V3MessageFactory.read(stream);
|
|
||||||
} catch (SocketTimeoutException | NodeException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (SocketException e) {
|
|
||||||
throw new NodeException(e.getMessage(), e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectMessage getObjectMessage(int version, InputStream stream, int length) {
|
|
||||||
try {
|
|
||||||
return V3MessageFactory.readObject(stream, length);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
|
||||||
return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes,
|
|
||||||
Pubkey.Feature.bitfield(features));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) {
|
|
||||||
if (publicSigningKey.length != 64 && publicSigningKey.length != 65)
|
|
||||||
throw new IllegalArgumentException("64 bytes signing key expected, but it was "
|
|
||||||
+ publicSigningKey.length + " bytes long.");
|
|
||||||
if (publicEncryptionKey.length != 64 && publicEncryptionKey.length != 65)
|
|
||||||
throw new IllegalArgumentException("64 bytes encryption key expected, but it was "
|
|
||||||
+ publicEncryptionKey.length + " bytes long.");
|
|
||||||
|
|
||||||
switch ((int) version) {
|
|
||||||
case 2:
|
|
||||||
return new V2Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.publicSigningKey(publicSigningKey)
|
|
||||||
.publicEncryptionKey(publicEncryptionKey)
|
|
||||||
.behaviorBitfield(behaviourBitfield)
|
|
||||||
.build();
|
|
||||||
case 3:
|
|
||||||
return new V3Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.publicSigningKey(publicSigningKey)
|
|
||||||
.publicEncryptionKey(publicEncryptionKey)
|
|
||||||
.behaviorBitfield(behaviourBitfield)
|
|
||||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
|
||||||
.extraBytes(extraBytes)
|
|
||||||
.build();
|
|
||||||
case 4:
|
|
||||||
return new V4Pubkey(
|
|
||||||
new V3Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.publicSigningKey(publicSigningKey)
|
|
||||||
.publicEncryptionKey(publicEncryptionKey)
|
|
||||||
.behaviorBitfield(behaviourBitfield)
|
|
||||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
|
||||||
.extraBytes(extraBytes)
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unexpected pubkey version " + version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BitmessageAddress createIdentityFromPrivateKey(String address,
|
|
||||||
byte[] privateSigningKey, byte[] privateEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes,
|
|
||||||
int behaviourBitfield) {
|
|
||||||
BitmessageAddress temp = new BitmessageAddress(address);
|
|
||||||
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
|
|
||||||
createPubkey(temp.getVersion(), temp.getStream(),
|
|
||||||
security().createPublicKey(privateSigningKey),
|
|
||||||
security().createPublicKey(privateEncryptionKey),
|
|
||||||
nonceTrialsPerByte, extraBytes, behaviourBitfield));
|
|
||||||
BitmessageAddress result = new BitmessageAddress(privateKey);
|
|
||||||
if (!result.getAddress().equals(address)) {
|
|
||||||
throw new IllegalArgumentException("Address not matching private key. Address: " + address
|
|
||||||
+ "; Address derived from private key: " + result.getAddress());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BitmessageAddress generatePrivateAddress(boolean shorter,
|
|
||||||
long stream,
|
|
||||||
Pubkey.Feature... features) {
|
|
||||||
return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features));
|
|
||||||
}
|
|
||||||
|
|
||||||
static ObjectPayload getObjectPayload(long objectType,
|
|
||||||
long version,
|
|
||||||
long streamNumber,
|
|
||||||
InputStream stream,
|
|
||||||
int length) throws IOException {
|
|
||||||
ObjectType type = ObjectType.fromNumber(objectType);
|
|
||||||
if (type != null) {
|
|
||||||
switch (type) {
|
|
||||||
case GET_PUBKEY:
|
|
||||||
return parseGetPubkey(version, streamNumber, stream, length);
|
|
||||||
case PUBKEY:
|
|
||||||
return parsePubkey(version, streamNumber, stream, length);
|
|
||||||
case MSG:
|
|
||||||
return parseMsg(version, streamNumber, stream, length);
|
|
||||||
case BROADCAST:
|
|
||||||
return parseBroadcast(version, streamNumber, stream, length);
|
|
||||||
default:
|
|
||||||
LOG.error("This should not happen, someone broke something in the code!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// fallback: just store the message - we don't really care what it is
|
|
||||||
LOG.trace("Unexpected object type: " + objectType);
|
|
||||||
return GenericPayload.read(version, stream, streamNumber, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
|
||||||
return GetPubkey.read(stream, streamNumber, length, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException {
|
|
||||||
switch ((int) version) {
|
|
||||||
case 2:
|
|
||||||
return V2Pubkey.read(is, stream);
|
|
||||||
case 3:
|
|
||||||
return V3Pubkey.read(is, stream);
|
|
||||||
case 4:
|
|
||||||
return V4Pubkey.read(is, stream, length, encrypted);
|
|
||||||
}
|
|
||||||
LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
|
||||||
Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true);
|
|
||||||
return pubkey != null ? pubkey : GenericPayload.read(version, stream, streamNumber, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
|
||||||
return Msg.read(stream, streamNumber, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
|
||||||
switch ((int) version) {
|
|
||||||
case 4:
|
|
||||||
return V4Broadcast.read(stream, streamNumber, length);
|
|
||||||
case 5:
|
|
||||||
return V5Broadcast.read(stream, streamNumber, length);
|
|
||||||
default:
|
|
||||||
LOG.debug("Encountered unknown broadcast version " + version);
|
|
||||||
return GenericPayload.read(version, stream, streamNumber, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectPayload getBroadcast(BitmessageAddress sendingAddress, Plaintext plaintext) {
|
|
||||||
if (sendingAddress.getVersion() < 4) {
|
|
||||||
return new V4Broadcast(sendingAddress, plaintext);
|
|
||||||
} else {
|
|
||||||
return new V5Broadcast(sendingAddress, plaintext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.factory;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.*;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.GenericPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.exception.NodeException;
|
|
||||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates protocol v3 network messages from {@link InputStream InputStreams}
|
|
||||||
*/
|
|
||||||
class V3MessageFactory {
|
|
||||||
private static Logger LOG = LoggerFactory.getLogger(V3MessageFactory.class);
|
|
||||||
|
|
||||||
public static NetworkMessage read(InputStream in) throws IOException {
|
|
||||||
findMagic(in);
|
|
||||||
String command = getCommand(in);
|
|
||||||
int length = (int) Decode.uint32(in);
|
|
||||||
if (length > 1600003) {
|
|
||||||
throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
|
|
||||||
}
|
|
||||||
byte[] checksum = Decode.bytes(in, 4);
|
|
||||||
|
|
||||||
byte[] payloadBytes = Decode.bytes(in, length);
|
|
||||||
|
|
||||||
if (testChecksum(checksum, payloadBytes)) {
|
|
||||||
MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length);
|
|
||||||
if (payload != null)
|
|
||||||
return new NetworkMessage(payload);
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
throw new IOException("Checksum failed for message '" + command + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException {
|
|
||||||
switch (command) {
|
|
||||||
case "version":
|
|
||||||
return parseVersion(stream);
|
|
||||||
case "verack":
|
|
||||||
return new VerAck();
|
|
||||||
case "addr":
|
|
||||||
return parseAddr(stream);
|
|
||||||
case "inv":
|
|
||||||
return parseInv(stream);
|
|
||||||
case "getdata":
|
|
||||||
return parseGetData(stream);
|
|
||||||
case "object":
|
|
||||||
return readObject(stream, length);
|
|
||||||
case "custom":
|
|
||||||
return readCustom(stream, length);
|
|
||||||
default:
|
|
||||||
LOG.debug("Unknown command: " + command);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MessagePayload readCustom(InputStream in, int length) throws IOException {
|
|
||||||
return CustomMessage.read(in, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectMessage readObject(InputStream in, int length) throws IOException {
|
|
||||||
AccessCounter counter = new AccessCounter();
|
|
||||||
byte nonce[] = Decode.bytes(in, 8, counter);
|
|
||||||
long expiresTime = Decode.int64(in, counter);
|
|
||||||
long objectType = Decode.uint32(in, counter);
|
|
||||||
long version = Decode.varInt(in, counter);
|
|
||||||
long stream = Decode.varInt(in, counter);
|
|
||||||
|
|
||||||
byte[] data = Decode.bytes(in, length - counter.length());
|
|
||||||
ObjectPayload payload;
|
|
||||||
try {
|
|
||||||
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
|
|
||||||
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.trace("Could not parse object payload - using generic payload instead", e);
|
|
||||||
payload = new GenericPayload(version, stream, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ObjectMessage.Builder()
|
|
||||||
.nonce(nonce)
|
|
||||||
.expiresTime(expiresTime)
|
|
||||||
.objectType(objectType)
|
|
||||||
.stream(stream)
|
|
||||||
.payload(payload)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetData parseGetData(InputStream stream) throws IOException {
|
|
||||||
long count = Decode.varInt(stream);
|
|
||||||
GetData.Builder builder = new GetData.Builder();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
builder.addInventoryVector(parseInventoryVector(stream));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Inv parseInv(InputStream stream) throws IOException {
|
|
||||||
long count = Decode.varInt(stream);
|
|
||||||
Inv.Builder builder = new Inv.Builder();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
builder.addInventoryVector(parseInventoryVector(stream));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Addr parseAddr(InputStream stream) throws IOException {
|
|
||||||
long count = Decode.varInt(stream);
|
|
||||||
Addr.Builder builder = new Addr.Builder();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
builder.addAddress(parseAddress(stream, false));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Version parseVersion(InputStream stream) throws IOException {
|
|
||||||
int version = Decode.int32(stream);
|
|
||||||
long services = Decode.int64(stream);
|
|
||||||
long timestamp = Decode.int64(stream);
|
|
||||||
NetworkAddress addrRecv = parseAddress(stream, true);
|
|
||||||
NetworkAddress addrFrom = parseAddress(stream, true);
|
|
||||||
long nonce = Decode.int64(stream);
|
|
||||||
String userAgent = Decode.varString(stream);
|
|
||||||
long[] streamNumbers = Decode.varIntList(stream);
|
|
||||||
|
|
||||||
return new Version.Builder()
|
|
||||||
.version(version)
|
|
||||||
.services(services)
|
|
||||||
.timestamp(timestamp)
|
|
||||||
.addrRecv(addrRecv).addrFrom(addrFrom)
|
|
||||||
.nonce(nonce)
|
|
||||||
.userAgent(userAgent)
|
|
||||||
.streams(streamNumbers).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InventoryVector parseInventoryVector(InputStream stream) throws IOException {
|
|
||||||
return new InventoryVector(Decode.bytes(stream, 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NetworkAddress parseAddress(InputStream stream, boolean light) throws IOException {
|
|
||||||
long time;
|
|
||||||
long streamNumber;
|
|
||||||
if (!light) {
|
|
||||||
time = Decode.int64(stream);
|
|
||||||
streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct
|
|
||||||
} else {
|
|
||||||
time = 0;
|
|
||||||
streamNumber = 0;
|
|
||||||
}
|
|
||||||
long services = Decode.int64(stream);
|
|
||||||
byte[] ipv6 = Decode.bytes(stream, 16);
|
|
||||||
int port = Decode.uint16(stream);
|
|
||||||
return new NetworkAddress.Builder().time(time).stream(streamNumber).services(services).ipv6(ipv6).port(port).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean testChecksum(byte[] checksum, byte[] payload) {
|
|
||||||
byte[] payloadChecksum = security().sha512(payload);
|
|
||||||
for (int i = 0; i < checksum.length; i++) {
|
|
||||||
if (checksum[i] != payloadChecksum[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getCommand(InputStream stream) throws IOException {
|
|
||||||
byte[] bytes = new byte[12];
|
|
||||||
int end = bytes.length;
|
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
|
||||||
bytes[i] = (byte) stream.read();
|
|
||||||
if (end == bytes.length) {
|
|
||||||
if (bytes[i] == 0) end = i;
|
|
||||||
} else {
|
|
||||||
if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new String(bytes, 0, end, "ASCII");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void findMagic(InputStream in) throws IOException {
|
|
||||||
int pos = 0;
|
|
||||||
for (int i = 0; i < 1620000; i++) {
|
|
||||||
byte b = (byte) in.read();
|
|
||||||
if (b == MAGIC_BYTES[pos]) {
|
|
||||||
if (pos + 1 == MAGIC_BYTES.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (pos > 0 && b == MAGIC_BYTES[0]) {
|
|
||||||
pos = 1;
|
|
||||||
} else {
|
|
||||||
pos = 0;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
throw new NodeException("Failed to find MAGIC bytes in stream");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.InternalContext;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Numbers.max;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
|
||||||
*/
|
|
||||||
public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder {
|
|
||||||
public static final Logger LOG = LoggerFactory.getLogger(Cryptography.class);
|
|
||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
|
||||||
private static final BigInteger TWO = BigInteger.valueOf(2);
|
|
||||||
private static final BigInteger TWO_POW_64 = TWO.pow(64);
|
|
||||||
private static final BigInteger TWO_POW_16 = TWO.pow(16);
|
|
||||||
|
|
||||||
private final String provider;
|
|
||||||
private InternalContext context;
|
|
||||||
|
|
||||||
protected AbstractCryptography(String provider) {
|
|
||||||
this.provider = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] sha512(byte[]... data) {
|
|
||||||
return hash("SHA-512", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] doubleSha512(byte[]... data) {
|
|
||||||
MessageDigest mda = md("SHA-512");
|
|
||||||
for (byte[] d : data) {
|
|
||||||
mda.update(d);
|
|
||||||
}
|
|
||||||
return mda.digest(mda.digest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] doubleSha512(byte[] data, int length) {
|
|
||||||
MessageDigest mda = md("SHA-512");
|
|
||||||
mda.update(data, 0, length);
|
|
||||||
return mda.digest(mda.digest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ripemd160(byte[]... data) {
|
|
||||||
return hash("RIPEMD160", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] doubleSha256(byte[] data, int length) {
|
|
||||||
MessageDigest mda = md("SHA-256");
|
|
||||||
mda.update(data, 0, length);
|
|
||||||
return mda.digest(mda.digest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] sha1(byte[]... data) {
|
|
||||||
return hash("SHA-1", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] randomBytes(int length) {
|
|
||||||
byte[] result = new byte[length];
|
|
||||||
RANDOM.nextBytes(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
|
||||||
long extraBytes, ProofOfWorkEngine.Callback callback) {
|
|
||||||
nonceTrialsPerByte = max(nonceTrialsPerByte, context.getNetworkNonceTrialsPerByte());
|
|
||||||
extraBytes = max(extraBytes, context.getNetworkExtraBytes());
|
|
||||||
|
|
||||||
byte[] initialHash = getInitialHash(object);
|
|
||||||
|
|
||||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
|
||||||
|
|
||||||
context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
|
||||||
throws IOException {
|
|
||||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
|
||||||
byte[] value = doubleSha512(object.getNonce(), getInitialHash(object));
|
|
||||||
if (Bytes.lt(target, value, 8)) {
|
|
||||||
throw new InsufficientProofOfWorkException(target, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getInitialHash(ObjectMessage object) {
|
|
||||||
return sha512(object.getPayloadBytesWithoutNonce());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
|
||||||
if (nonceTrialsPerByte == 0) nonceTrialsPerByte = context.getNetworkNonceTrialsPerByte();
|
|
||||||
if (extraBytes == 0) extraBytes = context.getNetworkExtraBytes();
|
|
||||||
|
|
||||||
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
|
|
||||||
BigInteger numerator = TWO_POW_64;
|
|
||||||
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
|
|
||||||
BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte)
|
|
||||||
.multiply(
|
|
||||||
powLength.add(
|
|
||||||
powLength.multiply(TTL).divide(TWO_POW_16)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] hash(String algorithm, byte[]... data) {
|
|
||||||
MessageDigest mda = md(algorithm);
|
|
||||||
for (byte[] d : data) {
|
|
||||||
mda.update(d);
|
|
||||||
}
|
|
||||||
return mda.digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageDigest md(String algorithm) {
|
|
||||||
try {
|
|
||||||
return MessageDigest.getInstance(algorithm, provider);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] mac(byte[] key_m, byte[] data) {
|
|
||||||
try {
|
|
||||||
Mac mac = Mac.getInstance("HmacSHA256", provider);
|
|
||||||
mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
|
|
||||||
return mac.doFinal(data);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
|
||||||
return Factory.createPubkey(version, stream,
|
|
||||||
createPublicKey(privateSigningKey),
|
|
||||||
createPublicKey(privateEncryptionKey),
|
|
||||||
nonceTrialsPerByte, extraBytes, features);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigInteger keyToBigInt(byte[] privateKey) {
|
|
||||||
return new BigInteger(1, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long randomNonce() {
|
|
||||||
return RANDOM.nextLong();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface AddressRepository {
|
|
||||||
BitmessageAddress findContact(byte[] ripeOrTag);
|
|
||||||
|
|
||||||
BitmessageAddress findIdentity(byte[] ripeOrTag);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return all Bitmessage addresses that belong to this user, i.e. have a private key.
|
|
||||||
*/
|
|
||||||
List<BitmessageAddress> getIdentities();
|
|
||||||
|
|
||||||
List<BitmessageAddress> getSubscriptions();
|
|
||||||
|
|
||||||
List<BitmessageAddress> getSubscriptions(long broadcastVersion);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return all Bitmessage addresses that have no private key.
|
|
||||||
*/
|
|
||||||
List<BitmessageAddress> getContacts();
|
|
||||||
|
|
||||||
void save(BitmessageAddress address);
|
|
||||||
|
|
||||||
void remove(BitmessageAddress address);
|
|
||||||
|
|
||||||
BitmessageAddress getAddress(String address);
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
|
|
||||||
* which should be secure enough.
|
|
||||||
*/
|
|
||||||
public interface Cryptography {
|
|
||||||
/**
|
|
||||||
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
|
||||||
* success on the same thread.
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @return SHA-512 hash of data
|
|
||||||
*/
|
|
||||||
byte[] sha512(byte[]... data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
|
|
||||||
* at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
|
||||||
* success on the same thread.
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @return SHA-512 hash of data
|
|
||||||
*/
|
|
||||||
byte[] doubleSha512(byte[]... data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
|
|
||||||
* to use for the hash calculation.
|
|
||||||
* <p>
|
|
||||||
* Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
|
|
||||||
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @param length number of bytes to be taken into account
|
|
||||||
* @return SHA-512 hash of data
|
|
||||||
*/
|
|
||||||
byte[] doubleSha512(byte[] data, int length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
|
|
||||||
* concatenation of all arrays, but might perform better.
|
|
||||||
* <p>
|
|
||||||
* Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
|
||||||
* order on the same thread.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @return RIPEMD-160 hash of data
|
|
||||||
*/
|
|
||||||
byte[] ripemd160(byte[]... data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
|
|
||||||
* to use for the hash calculation.
|
|
||||||
* <p>
|
|
||||||
* Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
|
||||||
* order on the same thread.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @param length number of bytes to be taken into account
|
|
||||||
* @return SHA-256 hash of data
|
|
||||||
*/
|
|
||||||
byte[] doubleSha256(byte[] data, int length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
|
|
||||||
* concatenation of all arrays, but might perform better.
|
|
||||||
* <p>
|
|
||||||
* Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
|
||||||
* order on the same thread.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @return SHA hash of data
|
|
||||||
*/
|
|
||||||
byte[] sha1(byte[]... data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param length number of bytes to return
|
|
||||||
* @return an array of the given size containing random bytes
|
|
||||||
*/
|
|
||||||
byte[] randomBytes(int length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
|
|
||||||
* live.
|
|
||||||
*
|
|
||||||
* @param object to do the proof of work for
|
|
||||||
* @param nonceTrialsPerByte difficulty
|
|
||||||
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
|
|
||||||
* @param callback to handle nonce once it's calculated
|
|
||||||
*/
|
|
||||||
void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
|
||||||
long extraBytes, ProofOfWorkEngine.Callback callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param object to be checked
|
|
||||||
* @param nonceTrialsPerByte difficulty
|
|
||||||
* @param extraBytes bytes to add to the object size
|
|
||||||
* @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
|
|
||||||
*/
|
|
||||||
void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
byte[] getInitialHash(ObjectMessage object);
|
|
||||||
|
|
||||||
byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the MAC for a message (data)
|
|
||||||
*
|
|
||||||
* @param key_m the symmetric key used
|
|
||||||
* @param data the message data to calculate the MAC for
|
|
||||||
* @return the MAC
|
|
||||||
*/
|
|
||||||
byte[] mac(byte[] key_m, byte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param encrypt if true, encrypts data, otherwise tries to decrypt it.
|
|
||||||
* @param data
|
|
||||||
* @param key_e
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new public key fom given private keys.
|
|
||||||
*
|
|
||||||
* @param version of the public key / address
|
|
||||||
* @param stream of the address
|
|
||||||
* @param privateSigningKey private key used for signing
|
|
||||||
* @param privateEncryptionKey private key used for encryption
|
|
||||||
* @param nonceTrialsPerByte proof of work difficulty
|
|
||||||
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
|
|
||||||
* @param features of the address
|
|
||||||
* @return a public key object
|
|
||||||
*/
|
|
||||||
Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param privateKey private key as byte array
|
|
||||||
* @return a public key corresponding to the given private key
|
|
||||||
*/
|
|
||||||
byte[] createPublicKey(byte[] privateKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param privateKey private key as byte array
|
|
||||||
* @return a big integer representation (unsigned) of the given bytes
|
|
||||||
*/
|
|
||||||
BigInteger keyToBigInt(byte[] privateKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param data to check
|
|
||||||
* @param signature the signature of the message
|
|
||||||
* @param pubkey the sender's public key
|
|
||||||
* @return true if the signature is valid, false otherwise
|
|
||||||
*/
|
|
||||||
boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the signature of data, using the given private key.
|
|
||||||
*
|
|
||||||
* @param data to be signed
|
|
||||||
* @param privateKey to be used for signing
|
|
||||||
* @return the signature
|
|
||||||
*/
|
|
||||||
byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a random number of type long
|
|
||||||
*/
|
|
||||||
long randomNonce();
|
|
||||||
|
|
||||||
byte[] multiply(byte[] k, byte[] r);
|
|
||||||
|
|
||||||
byte[] createPoint(byte[] x, byte[] y);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.MessagePayload;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public interface CustomCommandHandler {
|
|
||||||
MessagePayload handle(CustomMessage request);
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Inventory stores and retrieves objects, cleans up outdated objects and can tell which objects are still missing.
|
|
||||||
*/
|
|
||||||
public interface Inventory {
|
|
||||||
/**
|
|
||||||
* Returns the IVs of all valid objects we have for the given streams
|
|
||||||
*/
|
|
||||||
List<InventoryVector> getInventory(long... streams);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IVs of all objects in the offer that we don't have already. Implementations are allowed to
|
|
||||||
* ignore the streams parameter, but it must be set when calling this method.
|
|
||||||
*/
|
|
||||||
List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams);
|
|
||||||
|
|
||||||
ObjectMessage getObject(InventoryVector vector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is mainly used to search for public keys to newly added addresses or broadcasts from new
|
|
||||||
* subscriptions.
|
|
||||||
*/
|
|
||||||
List<ObjectMessage> getObjects(long stream, long version, ObjectType... types);
|
|
||||||
|
|
||||||
void storeObject(ObjectMessage object);
|
|
||||||
|
|
||||||
boolean contains(ObjectMessage object);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all objects that expired 5 minutes ago or earlier
|
|
||||||
* (so we don't accidentally request objects we just deleted)
|
|
||||||
*/
|
|
||||||
void cleanup();
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Collections.selectRandom;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.HOUR;
|
|
||||||
import static java.util.Collections.newSetFromMap;
|
|
||||||
|
|
||||||
public class MemoryNodeRegistry implements NodeRegistry {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MemoryNodeRegistry.class);
|
|
||||||
|
|
||||||
private final Map<Long, Set<NetworkAddress>> stableNodes = new ConcurrentHashMap<>();
|
|
||||||
private final Map<Long, Set<NetworkAddress>> knownNodes = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private void loadStableNodes() {
|
|
||||||
try (InputStream in = getClass().getClassLoader().getResourceAsStream("nodes.txt")) {
|
|
||||||
Scanner scanner = new Scanner(in);
|
|
||||||
long stream = 0;
|
|
||||||
Set<NetworkAddress> streamSet = null;
|
|
||||||
while (scanner.hasNext()) {
|
|
||||||
try {
|
|
||||||
String line = scanner.nextLine().trim();
|
|
||||||
if (line.startsWith("#") || line.isEmpty()) {
|
|
||||||
// Ignore
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (line.startsWith("[stream")) {
|
|
||||||
stream = Long.parseLong(line.substring(8, line.lastIndexOf(']')));
|
|
||||||
streamSet = new HashSet<>();
|
|
||||||
stableNodes.put(stream, streamSet);
|
|
||||||
} else if (streamSet != null) {
|
|
||||||
int portIndex = line.lastIndexOf(':');
|
|
||||||
InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex));
|
|
||||||
int port = Integer.valueOf(line.substring(portIndex + 1));
|
|
||||||
for (InetAddress inetAddress : inetAddresses) {
|
|
||||||
streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
for (Map.Entry<Long, Set<NetworkAddress>> e : stableNodes.entrySet()) {
|
|
||||||
LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
|
||||||
List<NetworkAddress> result = new LinkedList<>();
|
|
||||||
for (long stream : streams) {
|
|
||||||
Set<NetworkAddress> known = knownNodes.get(stream);
|
|
||||||
if (known != null && !known.isEmpty()) {
|
|
||||||
for (NetworkAddress node : known) {
|
|
||||||
if (node.getTime() > UnixTime.now(-3 * HOUR)) {
|
|
||||||
result.add(node);
|
|
||||||
} else {
|
|
||||||
known.remove(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Set<NetworkAddress> nodes = stableNodes.get(stream);
|
|
||||||
if (nodes == null || nodes.isEmpty()) {
|
|
||||||
loadStableNodes();
|
|
||||||
nodes = stableNodes.get(stream);
|
|
||||||
}
|
|
||||||
if (nodes != null && !nodes.isEmpty()) {
|
|
||||||
// To reduce load on stable nodes, only return one
|
|
||||||
result.add(selectRandom(nodes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return selectRandom(limit, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void offerAddresses(List<NetworkAddress> addresses) {
|
|
||||||
for (NetworkAddress node : addresses) {
|
|
||||||
if (node.getTime() <= UnixTime.now()) {
|
|
||||||
if (!knownNodes.containsKey(node.getStream())) {
|
|
||||||
synchronized (knownNodes) {
|
|
||||||
if (!knownNodes.containsKey(node.getStream())) {
|
|
||||||
knownNodes.put(
|
|
||||||
node.getStream(),
|
|
||||||
newSetFromMap(new ConcurrentHashMap<NetworkAddress, Boolean>())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.getTime() <= UnixTime.now()) {
|
|
||||||
// TODO: This isn't quite correct
|
|
||||||
// If the node is already known, the one with the more recent time should be used
|
|
||||||
knownNodes.get(node.getStream()).add(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext.Status;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface MessageRepository {
|
|
||||||
List<Label> getLabels();
|
|
||||||
|
|
||||||
List<Label> getLabels(Label.Type... types);
|
|
||||||
|
|
||||||
int countUnread(Label label);
|
|
||||||
|
|
||||||
Plaintext getMessage(byte[] initialHash);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Label label);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Status status);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Status status, BitmessageAddress recipient);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(BitmessageAddress sender);
|
|
||||||
|
|
||||||
void save(Plaintext message);
|
|
||||||
|
|
||||||
void remove(Plaintext message);
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Semaphore;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A POW engine using all available CPU cores.
|
|
||||||
*/
|
|
||||||
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
|
|
||||||
private static final Semaphore semaphore = new Semaphore(1, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will block until all pending nonce calculations are done, but not wait for its own calculation
|
|
||||||
* to finish.
|
|
||||||
* (This implementation becomes very inefficient if multiple nonce are calculated at the same time.)
|
|
||||||
*
|
|
||||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
|
||||||
* @param target the target, representing an unsigned long
|
|
||||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
|
||||||
try {
|
|
||||||
semaphore.acquire();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
callback = new CallbackWrapper(callback);
|
|
||||||
int cores = Runtime.getRuntime().availableProcessors();
|
|
||||||
if (cores > 255) cores = 255;
|
|
||||||
LOG.info("Doing POW using " + cores + " cores");
|
|
||||||
List<Worker> workers = new ArrayList<>(cores);
|
|
||||||
for (int i = 0; i < cores; i++) {
|
|
||||||
Worker w = new Worker(workers, (byte) cores, i, initialHash, target, callback);
|
|
||||||
workers.add(w);
|
|
||||||
}
|
|
||||||
for (Worker w : workers) {
|
|
||||||
// Doing this in the previous loop might cause a ConcurrentModificationException in the worker
|
|
||||||
// if a worker finds a nonce while new ones are still being added.
|
|
||||||
w.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Worker extends Thread {
|
|
||||||
private final Callback callback;
|
|
||||||
private final byte numberOfCores;
|
|
||||||
private final List<Worker> workers;
|
|
||||||
private final byte[] initialHash;
|
|
||||||
private final byte[] target;
|
|
||||||
private final MessageDigest mda;
|
|
||||||
private final byte[] nonce = new byte[8];
|
|
||||||
|
|
||||||
public Worker(List<Worker> workers, byte numberOfCores, int core, byte[] initialHash, byte[] target,
|
|
||||||
Callback callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
this.numberOfCores = numberOfCores;
|
|
||||||
this.workers = workers;
|
|
||||||
this.initialHash = initialHash;
|
|
||||||
this.target = target;
|
|
||||||
this.nonce[7] = (byte) core;
|
|
||||||
try {
|
|
||||||
mda = MessageDigest.getInstance("SHA-512");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
do {
|
|
||||||
inc(nonce, numberOfCores);
|
|
||||||
mda.update(nonce);
|
|
||||||
mda.update(initialHash);
|
|
||||||
if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) {
|
|
||||||
synchronized (callback) {
|
|
||||||
if (!Thread.interrupted()) {
|
|
||||||
for (Worker w : workers) {
|
|
||||||
w.interrupt();
|
|
||||||
}
|
|
||||||
// Clear interrupted flag for callback
|
|
||||||
Thread.interrupted();
|
|
||||||
callback.onNonceCalculated(initialHash, nonce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} while (!Thread.interrupted());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CallbackWrapper implements Callback {
|
|
||||||
private final Callback callback;
|
|
||||||
private final long startTime;
|
|
||||||
private boolean waiting = true;
|
|
||||||
|
|
||||||
public CallbackWrapper(Callback callback) {
|
|
||||||
this.startTime = System.currentTimeMillis();
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
|
||||||
// Prevents the callback from being called twice if two nonces are found simultaneously
|
|
||||||
synchronized (this) {
|
|
||||||
if (waiting) {
|
|
||||||
semaphore.release();
|
|
||||||
LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds");
|
|
||||||
waiting = false;
|
|
||||||
callback.onNonceCalculated(initialHash, nonce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.utils.Property;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles incoming messages
|
|
||||||
*/
|
|
||||||
public interface NetworkHandler {
|
|
||||||
/**
|
|
||||||
* Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
|
|
||||||
* <p>
|
|
||||||
* An implementation should disconnect if either the timeout is reached or the returned thread is interrupted.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
Future<?> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
|
||||||
* the response, which in turn is expected to be a {@link CustomMessage}.
|
|
||||||
*
|
|
||||||
* @param server the node's address
|
|
||||||
* @param port the node's port
|
|
||||||
* @param request the request
|
|
||||||
* @return the response
|
|
||||||
*/
|
|
||||||
CustomMessage send(InetAddress server, int port, CustomMessage request);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a full network node, accepting incoming connections and relaying objects.
|
|
||||||
*/
|
|
||||||
void start(MessageListener listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the full network node.
|
|
||||||
*/
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Offer new objects to up to 8 random nodes.
|
|
||||||
*/
|
|
||||||
void offer(InventoryVector iv);
|
|
||||||
|
|
||||||
Property getNetworkStatus();
|
|
||||||
|
|
||||||
boolean isRunning();
|
|
||||||
|
|
||||||
interface MessageListener {
|
|
||||||
void receive(ObjectMessage object) throws IOException;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores and provides known peers.
|
|
||||||
*/
|
|
||||||
public interface NodeRegistry {
|
|
||||||
List<NetworkAddress> getKnownAddresses(int limit, long... streams);
|
|
||||||
|
|
||||||
void offerAddresses(List<NetworkAddress> addresses);
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the proof of work necessary to send an object.
|
|
||||||
*/
|
|
||||||
public interface ProofOfWorkEngine {
|
|
||||||
/**
|
|
||||||
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
|
|
||||||
* smaller than target.
|
|
||||||
*
|
|
||||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
|
||||||
* @param target the target, representing an unsigned long
|
|
||||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
|
||||||
* sure this is only called once.
|
|
||||||
*/
|
|
||||||
void calculateNonce(byte[] initialHash, byte[] target, Callback callback);
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
/**
|
|
||||||
* @param nonce 8 bytes nonce
|
|
||||||
*/
|
|
||||||
void onNonceCalculated(byte[] initialHash, byte[] nonce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Objects that proof of work is currently being done for.
|
|
||||||
*
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public interface ProofOfWorkRepository {
|
|
||||||
Item getItem(byte[] initialHash);
|
|
||||||
|
|
||||||
List<byte[]> getItems();
|
|
||||||
|
|
||||||
void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
|
|
||||||
|
|
||||||
void removeObject(byte[] initialHash);
|
|
||||||
|
|
||||||
class Item {
|
|
||||||
public final ObjectMessage object;
|
|
||||||
public final long nonceTrialsPerByte;
|
|
||||||
public final long extraBytes;
|
|
||||||
|
|
||||||
public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
|
||||||
this.object = object;
|
|
||||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
|
||||||
this.extraBytes = extraBytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
|
|
||||||
* <p>
|
|
||||||
* <strong>Warning:</strong> implementations probably depend on POW being asynchronous, that's
|
|
||||||
* another reason not to use this one.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class SimplePOWEngine implements ProofOfWorkEngine {
|
|
||||||
@Override
|
|
||||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
|
||||||
byte[] nonce = new byte[8];
|
|
||||||
MessageDigest mda;
|
|
||||||
try {
|
|
||||||
mda = MessageDigest.getInstance("SHA-512");
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
inc(nonce);
|
|
||||||
mda.update(nonce);
|
|
||||||
mda.update(initialHash);
|
|
||||||
} while (Bytes.lt(target, mda.digest(mda.digest()), 8));
|
|
||||||
callback.onNonceCalculated(initialHash, nonce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intended to count the bytes read or written during (de-)serialization.
|
|
||||||
*/
|
|
||||||
public class AccessCounter {
|
|
||||||
private int count;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the counter by one, if not null.
|
|
||||||
*/
|
|
||||||
public static void inc(AccessCounter counter) {
|
|
||||||
if (counter != null) counter.inc();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the counter by length, if not null.
|
|
||||||
*/
|
|
||||||
public static void inc(AccessCounter counter, int length) {
|
|
||||||
if (counter != null) counter.inc(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the counter by one.
|
|
||||||
*/
|
|
||||||
private void inc() {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the counter by length.
|
|
||||||
*/
|
|
||||||
private void inc(int length) {
|
|
||||||
count += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int length() {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.valueOf(count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2011 Google Inc.
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.exception.AddressFormatException;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
import static java.util.Arrays.copyOfRange;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base58 encoder and decoder.
|
|
||||||
*
|
|
||||||
* @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily.
|
|
||||||
*/
|
|
||||||
public class Base58 {
|
|
||||||
private static final int[] INDEXES = new int[128];
|
|
||||||
private static char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (int i = 0; i < INDEXES.length; i++) {
|
|
||||||
INDEXES[i] = -1;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < ALPHABET.length; i++) {
|
|
||||||
INDEXES[ALPHABET[i]] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes the given bytes in base58. No checksum is appended.
|
|
||||||
*
|
|
||||||
* @param input to encode
|
|
||||||
* @return base58 encoded input
|
|
||||||
*/
|
|
||||||
public static String encode(byte[] input) {
|
|
||||||
if (input.length == 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
input = copyOfRange(input, 0, input.length);
|
|
||||||
// Count leading zeroes.
|
|
||||||
int zeroCount = 0;
|
|
||||||
while (zeroCount < input.length && input[zeroCount] == 0) {
|
|
||||||
++zeroCount;
|
|
||||||
}
|
|
||||||
// The actual encoding.
|
|
||||||
byte[] temp = new byte[input.length * 2];
|
|
||||||
int j = temp.length;
|
|
||||||
|
|
||||||
int startAt = zeroCount;
|
|
||||||
while (startAt < input.length) {
|
|
||||||
byte mod = divmod58(input, startAt);
|
|
||||||
if (input[startAt] == 0) {
|
|
||||||
++startAt;
|
|
||||||
}
|
|
||||||
temp[--j] = (byte) ALPHABET[mod];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip extra '1' if there are some after decoding.
|
|
||||||
while (j < temp.length && temp[j] == ALPHABET[0]) {
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
// Add as many leading '1' as there were leading zeros.
|
|
||||||
while (--zeroCount >= 0) {
|
|
||||||
temp[--j] = (byte) ALPHABET[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] output = copyOfRange(temp, j, temp.length);
|
|
||||||
try {
|
|
||||||
return new String(output, "US-ASCII");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e); // Cannot happen.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] decode(String input) throws AddressFormatException {
|
|
||||||
if (input.length() == 0) {
|
|
||||||
return new byte[0];
|
|
||||||
}
|
|
||||||
byte[] input58 = new byte[input.length()];
|
|
||||||
// Transform the String to a base58 byte sequence
|
|
||||||
for (int i = 0; i < input.length(); ++i) {
|
|
||||||
char c = input.charAt(i);
|
|
||||||
|
|
||||||
int digit58 = -1;
|
|
||||||
if (c >= 0 && c < 128) {
|
|
||||||
digit58 = INDEXES[c];
|
|
||||||
}
|
|
||||||
if (digit58 < 0) {
|
|
||||||
throw new AddressFormatException("Illegal character " + c + " at " + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
input58[i] = (byte) digit58;
|
|
||||||
}
|
|
||||||
// Count leading zeroes
|
|
||||||
int zeroCount = 0;
|
|
||||||
while (zeroCount < input58.length && input58[zeroCount] == 0) {
|
|
||||||
++zeroCount;
|
|
||||||
}
|
|
||||||
// The encoding
|
|
||||||
byte[] temp = new byte[input.length()];
|
|
||||||
int j = temp.length;
|
|
||||||
|
|
||||||
int startAt = zeroCount;
|
|
||||||
while (startAt < input58.length) {
|
|
||||||
byte mod = divmod256(input58, startAt);
|
|
||||||
if (input58[startAt] == 0) {
|
|
||||||
++startAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
temp[--j] = mod;
|
|
||||||
}
|
|
||||||
// Do no add extra leading zeroes, move j to first non null byte.
|
|
||||||
while (j < temp.length && temp[j] == 0) {
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
return copyOfRange(temp, j - zeroCount, temp.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// number -> number / 58, returns number % 58
|
|
||||||
//
|
|
||||||
private static byte divmod58(byte[] number, int startAt) {
|
|
||||||
int remainder = 0;
|
|
||||||
for (int i = startAt; i < number.length; i++) {
|
|
||||||
int digit256 = (int) number[i] & 0xFF;
|
|
||||||
int temp = remainder * 256 + digit256;
|
|
||||||
|
|
||||||
number[i] = (byte) (temp / 58);
|
|
||||||
|
|
||||||
remainder = temp % 58;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (byte) remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// number -> number / 256, returns number % 256
|
|
||||||
//
|
|
||||||
private static byte divmod256(byte[] number58, int startAt) {
|
|
||||||
int remainder = 0;
|
|
||||||
for (int i = startAt; i < number58.length; i++) {
|
|
||||||
int digit58 = (int) number58[i] & 0xFF;
|
|
||||||
int temp = remainder * 58 + digit58;
|
|
||||||
|
|
||||||
number58[i] = (byte) (temp / 256);
|
|
||||||
|
|
||||||
remainder = temp % 256;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (byte) remainder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper class for working with byte arrays interpreted as unsigned big endian integers.
|
|
||||||
* This is one part due to the fact that Java doesn't support unsigned numbers, and another
|
|
||||||
* part so we don't have to convert between byte arrays and numbers in time critical
|
|
||||||
* situations.
|
|
||||||
*/
|
|
||||||
public class Bytes {
|
|
||||||
public static void inc(byte[] nonce) {
|
|
||||||
for (int i = nonce.length - 1; i >= 0; i--) {
|
|
||||||
nonce[i]++;
|
|
||||||
if (nonce[i] != 0) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases nonce by value, which is used as an unsigned byte value.
|
|
||||||
*
|
|
||||||
* @param nonce an unsigned number
|
|
||||||
* @param value to be added to nonce
|
|
||||||
*/
|
|
||||||
public static void inc(byte[] nonce, byte value) {
|
|
||||||
int i = nonce.length - 1;
|
|
||||||
nonce[i] += value;
|
|
||||||
if (value > 0 && (nonce[i] < 0 || nonce[i] >= value))
|
|
||||||
return;
|
|
||||||
if (value < 0 && (nonce[i] < 0 && nonce[i] >= value))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (i = i - 1; i >= 0; i--) {
|
|
||||||
nonce[i]++;
|
|
||||||
if (nonce[i] != 0) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if a < b.
|
|
||||||
*/
|
|
||||||
public static boolean lt(byte[] a, byte[] b) {
|
|
||||||
byte[] max = (a.length > b.length ? a : b);
|
|
||||||
byte[] min = (max == a ? b : a);
|
|
||||||
int diff = max.length - min.length;
|
|
||||||
|
|
||||||
for (int i = 0; i < max.length - min.length; i++) {
|
|
||||||
if (max[i] != 0) return a != max;
|
|
||||||
}
|
|
||||||
for (int i = diff; i < max.length; i++) {
|
|
||||||
if (max[i] != min[i - diff]) {
|
|
||||||
return lt(max[i], min[i - diff]) == (a == max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if a < b, where the first [size] bytes are used as the numbers to check.
|
|
||||||
*/
|
|
||||||
public static boolean lt(byte[] a, byte[] b, int size) {
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
if (a[i] != b[i]) {
|
|
||||||
return lt(a[i], b[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean lt(byte a, byte b) {
|
|
||||||
if (a < 0) return b < 0 && a < b;
|
|
||||||
if (b < 0) return a >= 0 || a < b;
|
|
||||||
return a < b;
|
|
||||||
// This would be easier to understand, but is (slightly) slower:
|
|
||||||
// return (a & 0xff) < (b & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a new byte array of length, left-padded with '0'.
|
|
||||||
*/
|
|
||||||
public static byte[] expand(byte[] source, int size) {
|
|
||||||
byte[] result = new byte[size];
|
|
||||||
System.arraycopy(source, 0, result, size - source.length, source.length);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a new byte array containing the first <em>size</em> bytes of the given array.
|
|
||||||
*/
|
|
||||||
public static byte[] truncate(byte[] source, int size) {
|
|
||||||
byte[] result = new byte[size];
|
|
||||||
System.arraycopy(source, 0, result, 0, size);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the byte array that hex represents. This is meant for test use and should be rewritten if used in
|
|
||||||
* production code.
|
|
||||||
*/
|
|
||||||
public static byte[] fromHex(String hex) {
|
|
||||||
if (hex.length() % 2 != 0) throw new IllegalArgumentException("expected even number of characters");
|
|
||||||
byte[] result = new byte[hex.length() / 2];
|
|
||||||
for (int i = 0; i < result.length; i++) {
|
|
||||||
result[i] = (byte) (hexValue(hex.charAt(i * 2)) * 16);
|
|
||||||
result[i] += hexValue(hex.charAt(i * 2 + 1));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int hexValue(char c) {
|
|
||||||
if (c >= '0' && c <= '9') {
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
if (c >= 'a' && c <= 'f') {
|
|
||||||
return 10 + c - 'a';
|
|
||||||
}
|
|
||||||
if (c >= 'A' && c <= 'F') {
|
|
||||||
return 10 + c - 'A';
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("'" + c + "' is not a valid hex value");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the number of leading '0' of a byte array.
|
|
||||||
*/
|
|
||||||
public static int numberOfLeadingZeros(byte[] bytes) {
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < bytes.length; i++) {
|
|
||||||
if (bytes[i] != 0) return i;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for a value within a callback method to be set.
|
|
||||||
*/
|
|
||||||
public class CallbackWaiter<T> {
|
|
||||||
private final long startTime = System.currentTimeMillis();
|
|
||||||
private volatile boolean isSet;
|
|
||||||
private T value;
|
|
||||||
private long time;
|
|
||||||
|
|
||||||
public void setValue(T value) {
|
|
||||||
synchronized (this) {
|
|
||||||
this.time = System.currentTimeMillis() - startTime;
|
|
||||||
this.value = value;
|
|
||||||
this.isSet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public T waitForValue() throws InterruptedException {
|
|
||||||
while (!isSet) {
|
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
synchronized (this) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTime() {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
public class Collections {
|
|
||||||
private final static Random RANDOM = new Random();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param count the number of elements to return (if possible)
|
|
||||||
* @param collection the collection to take samples from
|
|
||||||
* @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The
|
|
||||||
* result is by no means securely random, but should be random enough so not the same objects get selected over
|
|
||||||
* and over again.
|
|
||||||
*/
|
|
||||||
public static <T> List<T> selectRandom(int count, Collection<T> collection) {
|
|
||||||
ArrayList<T> result = new ArrayList<>(count);
|
|
||||||
if (collection.size() <= count) {
|
|
||||||
result.addAll(collection);
|
|
||||||
} else {
|
|
||||||
double collectionRest = collection.size();
|
|
||||||
double resultRest = count;
|
|
||||||
int skipMax = (int) Math.ceil(collectionRest / resultRest);
|
|
||||||
int skip = RANDOM.nextInt(skipMax);
|
|
||||||
for (T item : collection) {
|
|
||||||
collectionRest--;
|
|
||||||
if (skip > 0) {
|
|
||||||
skip--;
|
|
||||||
} else {
|
|
||||||
result.add(item);
|
|
||||||
resultRest--;
|
|
||||||
if (resultRest == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skipMax = (int) Math.ceil(collectionRest / resultRest);
|
|
||||||
skip = RANDOM.nextInt(skipMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T selectRandom(Collection<T> collection) {
|
|
||||||
int index = RANDOM.nextInt(collection.size());
|
|
||||||
for (T item : collection) {
|
|
||||||
if (index == 0) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
index--;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Empty collection? Size: " + collection.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class DebugUtils {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class);
|
|
||||||
|
|
||||||
public static void saveToFile(ObjectMessage objectMessage) {
|
|
||||||
try {
|
|
||||||
File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv");
|
|
||||||
f.createNewFile();
|
|
||||||
objectMessage.write(new FileOutputStream(f));
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <K> void inc(Map<K, Integer> map, K key) {
|
|
||||||
if (map.containsKey(key)) {
|
|
||||||
map.put(key, map.get(key) + 1);
|
|
||||||
} else {
|
|
||||||
map.put(key, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class handles decoding simple types from byte stream, according to
|
|
||||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
|
||||||
*/
|
|
||||||
public class Decode {
|
|
||||||
public static byte[] shortVarBytes(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
int length = uint16(stream, counter);
|
|
||||||
return bytes(stream, length, counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] varBytes(InputStream stream) throws IOException {
|
|
||||||
int length = (int) varInt(stream, null);
|
|
||||||
return bytes(stream, length, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] varBytes(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
int length = (int) varInt(stream, counter);
|
|
||||||
return bytes(stream, length, counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] bytes(InputStream stream, int count) throws IOException {
|
|
||||||
return bytes(stream, count, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] bytes(InputStream stream, int count, AccessCounter counter) throws IOException {
|
|
||||||
byte[] result = new byte[count];
|
|
||||||
int off = 0;
|
|
||||||
while (off < count) {
|
|
||||||
int read = stream.read(result, off, count - off);
|
|
||||||
if (read < 0) {
|
|
||||||
throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off);
|
|
||||||
}
|
|
||||||
off += read;
|
|
||||||
}
|
|
||||||
inc(counter, count);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long[] varIntList(InputStream stream) throws IOException {
|
|
||||||
int length = (int) varInt(stream);
|
|
||||||
long[] result = new long[length];
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
result[i] = varInt(stream);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long varInt(InputStream stream) throws IOException {
|
|
||||||
return varInt(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long varInt(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
int first = stream.read();
|
|
||||||
inc(counter);
|
|
||||||
switch (first) {
|
|
||||||
case 0xfd:
|
|
||||||
return uint16(stream, counter);
|
|
||||||
case 0xfe:
|
|
||||||
return uint32(stream, counter);
|
|
||||||
case 0xff:
|
|
||||||
return int64(stream, counter);
|
|
||||||
default:
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint8(InputStream stream) throws IOException {
|
|
||||||
return stream.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint16(InputStream stream) throws IOException {
|
|
||||||
return uint16(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint16(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 2);
|
|
||||||
return stream.read() * 256 + stream.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long uint32(InputStream stream) throws IOException {
|
|
||||||
return uint32(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long uint32(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 4);
|
|
||||||
return stream.read() * 16777216L + stream.read() * 65536L + stream.read() * 256L + stream.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int int32(InputStream stream) throws IOException {
|
|
||||||
return int32(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int int32(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 4);
|
|
||||||
return ByteBuffer.wrap(bytes(stream, 4)).getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long int64(InputStream stream) throws IOException {
|
|
||||||
return int64(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long int64(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 8);
|
|
||||||
return ByteBuffer.wrap(bytes(stream, 8)).getLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String varString(InputStream stream) throws IOException {
|
|
||||||
return varString(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String varString(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
int length = (int) varInt(stream, counter);
|
|
||||||
// FIXME: technically, it says the length in characters, but I think this one might be correct
|
|
||||||
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
|
|
||||||
return new String(bytes(stream, length, counter), "utf-8");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class handles encoding simple types from byte stream, according to
|
|
||||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
|
||||||
*/
|
|
||||||
public class Encode {
|
|
||||||
public static void varIntList(long[] values, OutputStream stream) throws IOException {
|
|
||||||
varInt(values.length, stream);
|
|
||||||
for (long value : values) {
|
|
||||||
varInt(value, stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varInt(long value, OutputStream stream) throws IOException {
|
|
||||||
varInt(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
if (value < 0) {
|
|
||||||
// This is due to the fact that Java doesn't really support unsigned values.
|
|
||||||
// Please be aware that this might be an error due to a smaller negative value being cast to long.
|
|
||||||
// Normally, negative values shouldn't occur within the protocol, and I large enough longs
|
|
||||||
// to being recognized as negatives aren't realistic.
|
|
||||||
stream.write(0xff);
|
|
||||||
inc(counter);
|
|
||||||
int64(value, stream, counter);
|
|
||||||
} else if (value < 0xfd) {
|
|
||||||
int8(value, stream, counter);
|
|
||||||
} else if (value <= 0xffffL) {
|
|
||||||
stream.write(0xfd);
|
|
||||||
inc(counter);
|
|
||||||
int16(value, stream, counter);
|
|
||||||
} else if (value <= 0xffffffffL) {
|
|
||||||
stream.write(0xfe);
|
|
||||||
inc(counter);
|
|
||||||
int32(value, stream, counter);
|
|
||||||
} else {
|
|
||||||
stream.write(0xff);
|
|
||||||
inc(counter);
|
|
||||||
int64(value, stream, counter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int8(long value, OutputStream stream) throws IOException {
|
|
||||||
int8(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int8(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
stream.write((int) value);
|
|
||||||
inc(counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int16(long value, OutputStream stream) throws IOException {
|
|
||||||
int16(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int16(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
stream.write(ByteBuffer.allocate(4).putInt((int) value).array(), 2, 2);
|
|
||||||
inc(counter, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int32(long value, OutputStream stream) throws IOException {
|
|
||||||
int32(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int32(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
stream.write(ByteBuffer.allocate(4).putInt((int) value).array());
|
|
||||||
inc(counter, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int64(long value, OutputStream stream) throws IOException {
|
|
||||||
int64(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int64(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
stream.write(ByteBuffer.allocate(8).putLong(value).array());
|
|
||||||
inc(counter, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varString(String value, OutputStream out) throws IOException {
|
|
||||||
byte[] bytes = value.getBytes("utf-8");
|
|
||||||
// Technically, it says the length in characters, but I think this one might be correct.
|
|
||||||
// It doesn't really matter, as only ASCII characters are being used.
|
|
||||||
// see also Decode#varString()
|
|
||||||
varInt(bytes.length, out);
|
|
||||||
out.write(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varBytes(byte[] data, OutputStream out) throws IOException {
|
|
||||||
varInt(data.length, out);
|
|
||||||
out.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes a {@link Streamable} object and returns the byte array.
|
|
||||||
*
|
|
||||||
* @param streamable the object to be serialized
|
|
||||||
* @return an array of bytes representing the given streamable object.
|
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
*/
|
|
||||||
public static byte[] bytes(Streamable streamable) throws IOException {
|
|
||||||
if (streamable == null) return null;
|
|
||||||
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
streamable.write(stream);
|
|
||||||
return stream.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param streamable the object to be serialized
|
|
||||||
* @param padding the result will be padded such that its length is a multiple of <em>padding</em>
|
|
||||||
* @return the bytes of the given {@link Streamable} object, 0-padded such that the final length is x*padding.
|
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
*/
|
|
||||||
public static byte[] bytes(Streamable streamable, int padding) throws IOException {
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
streamable.write(stream);
|
|
||||||
int offset = padding - stream.size() % padding;
|
|
||||||
int length = stream.size() + offset;
|
|
||||||
byte[] result = new byte[length];
|
|
||||||
stream.write(result, offset, stream.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class Numbers {
|
|
||||||
public static long max(long a, long b) {
|
|
||||||
return a > b ? a : b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by chris on 20.07.15.
|
|
||||||
*/
|
|
||||||
public class Points {
|
|
||||||
public static byte[] getX(byte[] P) {
|
|
||||||
return Arrays.copyOfRange(P, 1, ((P.length - 1) / 2) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] getY(byte[] P) {
|
|
||||||
return Arrays.copyOfRange(P, ((P.length - 1) / 2) + 1, P.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now
|
|
||||||
* used to contain different status information. It is by default displayed in some JSON inspired human readable
|
|
||||||
* notation, but you might only want to rely on the 'human readable' part.
|
|
||||||
* <p>
|
|
||||||
* If you need a real JSON representation, please add a method <code>toJson()</code>.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class Property {
|
|
||||||
private String name;
|
|
||||||
private Object value;
|
|
||||||
private Property[] properties;
|
|
||||||
|
|
||||||
public Property(String name, Object value, Property... properties) {
|
|
||||||
this.name = name;
|
|
||||||
this.value = value;
|
|
||||||
this.properties = properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the property if available or <code>null</code> otherwise.
|
|
||||||
* Subproperties can be requested by submitting the sequence of properties.
|
|
||||||
*/
|
|
||||||
public Property getProperty(String... name) {
|
|
||||||
if (name == null || name.length == 0) return null;
|
|
||||||
|
|
||||||
for (Property p : properties) {
|
|
||||||
if (Objects.equals(name[0], p.name)) {
|
|
||||||
if (name.length == 1)
|
|
||||||
return p;
|
|
||||||
else
|
|
||||||
return p.getProperty(Arrays.copyOfRange(name, 1, name.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Property[] getProperties() {
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return toString("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String toString(String indentation) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
result.append(indentation).append(name).append(": ");
|
|
||||||
if (value != null || properties.length == 0) {
|
|
||||||
result.append(value);
|
|
||||||
}
|
|
||||||
if (properties.length > 0) {
|
|
||||||
result.append("{\n");
|
|
||||||
for (Property property : properties) {
|
|
||||||
result.append(property.toString(indentation + " ")).append('\n');
|
|
||||||
}
|
|
||||||
result.append(indentation).append("}");
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.ports.Cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by chris on 20.07.15.
|
|
||||||
*/
|
|
||||||
public class Singleton {
|
|
||||||
private static Cryptography cryptography;
|
|
||||||
|
|
||||||
public static void initialize(Cryptography cryptography) {
|
|
||||||
synchronized (Singleton.class) {
|
|
||||||
if (Singleton.cryptography == null) {
|
|
||||||
Singleton.cryptography = cryptography;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Cryptography security() {
|
|
||||||
return cryptography;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some utilities to handle strings.
|
|
||||||
* TODO: Probably this should be split in a GUI related and an SQL related utility class.
|
|
||||||
*/
|
|
||||||
public class Strings {
|
|
||||||
public static StringBuilder join(Object... objects) {
|
|
||||||
StringBuilder streamList = new StringBuilder();
|
|
||||||
for (int i = 0; i < objects.length; i++) {
|
|
||||||
if (i > 0) streamList.append(", ");
|
|
||||||
streamList.append(objects[i]);
|
|
||||||
}
|
|
||||||
return streamList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StringBuilder hex(byte[] bytes) {
|
|
||||||
StringBuilder hex = new StringBuilder(bytes.length + 2);
|
|
||||||
for (byte b : bytes) {
|
|
||||||
hex.append(String.format("%02x", b));
|
|
||||||
}
|
|
||||||
return hex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores times to live for different object types. Usually this shouldn't be messed with,
|
|
||||||
* but for tests it might be a good idea to reduce it to a minimum, and on mobile clients
|
|
||||||
* you might want to optimize it as well.
|
|
||||||
*
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class TTL {
|
|
||||||
private static long msg = 2 * DAY;
|
|
||||||
private static long getpubkey = 2 * DAY;
|
|
||||||
private static long pubkey = 28 * DAY;
|
|
||||||
|
|
||||||
public static long msg() {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void msg(long msg) {
|
|
||||||
TTL.msg = msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getpubkey() {
|
|
||||||
return getpubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void getpubkey(long getpubkey) {
|
|
||||||
TTL.getpubkey = getpubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long pubkey() {
|
|
||||||
return pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void pubkey(long pubkey) {
|
|
||||||
TTL.pubkey = pubkey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple utility class that simplifies using the second based time used in Bitmessage.
|
|
||||||
*/
|
|
||||||
public class UnixTime {
|
|
||||||
/**
|
|
||||||
* Length of a minute in seconds, intended for use with {@link #now(long)}.
|
|
||||||
*/
|
|
||||||
public static final int MINUTE = 60;
|
|
||||||
/**
|
|
||||||
* Length of an hour in seconds, intended for use with {@link #now(long)}.
|
|
||||||
*/
|
|
||||||
public static final long HOUR = 60 * MINUTE;
|
|
||||||
/**
|
|
||||||
* Length of a day in seconds, intended for use with {@link #now(long)}.
|
|
||||||
*/
|
|
||||||
public static final long DAY = 24 * HOUR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the time in second based Unix time ({@link System#currentTimeMillis()}/1000)
|
|
||||||
*/
|
|
||||||
public static long now() {
|
|
||||||
return System.currentTimeMillis() / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as {@link #now()} + shiftSeconds, but might be more readable.
|
|
||||||
*
|
|
||||||
* @param shiftSeconds number of seconds from now we're interested in
|
|
||||||
* @return the Unix time in shiftSeconds seconds / shiftSeconds seconds ago
|
|
||||||
*/
|
|
||||||
public static long now(long shiftSeconds) {
|
|
||||||
return (System.currentTimeMillis() / 1000) + shiftSeconds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
[stream 1]
|
|
||||||
|
|
||||||
dissem.ch:8444
|
|
||||||
bootstrap8080.bitmessage.org:8080
|
|
||||||
bootstrap8444.bitmessage.org:8444
|
|
||||||
|
|
||||||
[stream 2]
|
|
||||||
# none yet
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Broadcast;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V5Broadcast;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.TestBase;
|
|
||||||
import ch.dissem.bitmessage.utils.TestUtils;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
public class DecryptionTest extends TestBase {
|
|
||||||
@Test
|
|
||||||
public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
|
|
||||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
|
||||||
TestUtils.loadPubkey(address);
|
|
||||||
ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V4Broadcast.payload");
|
|
||||||
V4Broadcast broadcast = (V4Broadcast) objectMessage.getPayload();
|
|
||||||
broadcast.decrypt(address);
|
|
||||||
assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject());
|
|
||||||
assertTrue(objectMessage.isSignatureValid(address.getPubkey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureV5BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
|
|
||||||
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
|
|
||||||
TestUtils.loadPubkey(address);
|
|
||||||
ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V5Broadcast.payload");
|
|
||||||
V5Broadcast broadcast = (V5Broadcast) objectMessage.getPayload();
|
|
||||||
broadcast.decrypt(address);
|
|
||||||
assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject());
|
|
||||||
assertTrue(objectMessage.isSignatureValid(address.getPubkey()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.CryptoBox;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.GenericPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Msg;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.TestBase;
|
|
||||||
import ch.dissem.bitmessage.utils.TestUtils;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
|
|
||||||
public class EncryptionTest extends TestBase {
|
|
||||||
@Test
|
|
||||||
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException {
|
|
||||||
GenericPayload before = new GenericPayload(0, 1, security().randomBytes(100));
|
|
||||||
|
|
||||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
|
||||||
CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey());
|
|
||||||
|
|
||||||
GenericPayload after = GenericPayload.read(0, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 1, 100);
|
|
||||||
|
|
||||||
assertEquals(before, after);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureMessageCanBeDecrypted() throws IOException, DecryptionFailedException {
|
|
||||||
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
|
|
||||||
BitmessageAddress identity = new BitmessageAddress(privateKey);
|
|
||||||
assertEquals("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8", identity.getAddress());
|
|
||||||
|
|
||||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload");
|
|
||||||
Msg msg = (Msg) object.getPayload();
|
|
||||||
msg.decrypt(privateKey.getPrivateEncryptionKey());
|
|
||||||
Plaintext plaintext = msg.getPlaintext();
|
|
||||||
assertNotNull(plaintext);
|
|
||||||
assertEquals("Test", plaintext.getSubject());
|
|
||||||
assertEquals("Hallo, das ist ein Test von der v4-Adresse", plaintext.getText());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Msg;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.TestBase;
|
|
||||||
import ch.dissem.bitmessage.utils.TestUtils;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class SignatureTest extends TestBase {
|
|
||||||
@Test
|
|
||||||
public void ensureValidationWorks() throws IOException {
|
|
||||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
|
|
||||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
|
||||||
assertTrue(object.isSignatureValid(pubkey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureSigningWorks() throws IOException {
|
|
||||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
|
||||||
|
|
||||||
ObjectMessage objectMessage = new ObjectMessage.Builder()
|
|
||||||
.objectType(ObjectType.PUBKEY)
|
|
||||||
.stream(1)
|
|
||||||
.payload(privateKey.getPubkey())
|
|
||||||
.build();
|
|
||||||
objectMessage.sign(privateKey);
|
|
||||||
|
|
||||||
assertTrue(objectMessage.isSignatureValid(privateKey.getPubkey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureMessageIsProperlySigned() throws IOException, DecryptionFailedException {
|
|
||||||
BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8");
|
|
||||||
|
|
||||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload");
|
|
||||||
Msg msg = (Msg) object.getPayload();
|
|
||||||
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
|
|
||||||
Plaintext plaintext = msg.getPlaintext();
|
|
||||||
assertEquals(TestUtils.loadContact().getPubkey(), plaintext.getFrom().getPubkey());
|
|
||||||
assertNotNull(plaintext);
|
|
||||||
assertTrue(object.isSignatureValid(plaintext.getFrom().getPubkey()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.*;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class BitmessageAddressTest {
|
|
||||||
@Test
|
|
||||||
public void ensureBase58DecodesCorrectly() {
|
|
||||||
assertHexEquals("800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D",
|
|
||||||
Base58.decode("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureAddressStaysSame() {
|
|
||||||
String address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ";
|
|
||||||
assertEquals(address, new BitmessageAddress(address).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureStreamAndVersionAreParsed() {
|
|
||||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
|
||||||
assertEquals(1, address.getStream());
|
|
||||||
assertEquals(3, address.getVersion());
|
|
||||||
|
|
||||||
address = new BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e");
|
|
||||||
assertEquals(1, address.getStream());
|
|
||||||
assertEquals(4, address.getVersion());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateAddress() {
|
|
||||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK));
|
|
||||||
assertNotNull(address.getPubkey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testV2PubkeyImport() throws IOException {
|
|
||||||
ObjectMessage object = TestUtils.loadObjectMessage(2, "V2Pubkey.payload");
|
|
||||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
|
||||||
BitmessageAddress address = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT");
|
|
||||||
address.setPubkey(pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testV3PubkeyImport() throws IOException {
|
|
||||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
|
||||||
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe());
|
|
||||||
|
|
||||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
|
|
||||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
|
||||||
assertTrue(object.isSignatureValid(pubkey));
|
|
||||||
address.setPubkey(pubkey);
|
|
||||||
|
|
||||||
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.getRipe());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testV4PubkeyImport() throws IOException, DecryptionFailedException {
|
|
||||||
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
|
|
||||||
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
|
|
||||||
object.decrypt(address.getPublicDecryptionKey());
|
|
||||||
V4Pubkey pubkey = (V4Pubkey) object.getPayload();
|
|
||||||
assertTrue(object.isSignatureValid(pubkey));
|
|
||||||
address.setPubkey(pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testV3AddressImport() throws IOException {
|
|
||||||
String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn";
|
|
||||||
assertEquals(3, new BitmessageAddress(address_string).getVersion());
|
|
||||||
assertEquals(1, new BitmessageAddress(address_string).getStream());
|
|
||||||
|
|
||||||
byte[] privsigningkey = getSecret("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9");
|
|
||||||
byte[] privencryptionkey = getSecret("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck");
|
|
||||||
|
|
||||||
System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n");
|
|
||||||
|
|
||||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
|
|
||||||
security().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
|
||||||
assertEquals(address_string, address.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetSecret() throws IOException {
|
|
||||||
assertHexEquals("0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D",
|
|
||||||
getSecret("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getSecret(String walletImportFormat) throws IOException {
|
|
||||||
byte[] bytes = Base58.decode(walletImportFormat);
|
|
||||||
if (bytes[0] != (byte) 0x80)
|
|
||||||
throw new IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + " was " + bytes[0]);
|
|
||||||
if (bytes.length != 37)
|
|
||||||
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
|
|
||||||
|
|
||||||
byte[] hash = security().doubleSha256(bytes, 33);
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
|
|
||||||
}
|
|
||||||
return Arrays.copyOfRange(bytes, 1, 33);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testV4AddressImport() throws IOException {
|
|
||||||
assertEquals(4, new BitmessageAddress("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke").getVersion());
|
|
||||||
byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU");
|
|
||||||
byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz");
|
|
||||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
|
|
||||||
security().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
|
||||||
assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertHexEquals(String hex, byte[] bytes) {
|
|
||||||
assertEquals(hex.toLowerCase(), Strings.hex(bytes).toString().toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user