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
|
||||
|
||||
*.log
|
||||
|
||||
### 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
|
||||
|
||||
_site
|
||||
.sass-cache
|
||||
.jekyll-metadata
|
||||
|
||||
@@ -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