Compare commits

...

157 Commits

Author SHA1 Message Date
8d17aed785 Process timestamps as they are added 2018-03-08 15:39:03 -07:00
7ef8d5ddde Lock down dependencies 2018-03-08 13:25:40 -07:00
9930a2e167 With v0.3.1 published to crates.io, you can now run silk without git 2018-03-08 11:42:06 -07:00
a86be9ebf2 Merge pull request #56 from garious/add-conditions
Add conditions to transactions
2018-03-08 11:15:31 -07:00
ad6665c8b6 Complete timestamp and signature transactions 2018-03-08 11:06:52 -07:00
923162ae9d WIP: process timestamps 2018-03-08 10:19:54 -07:00
dd2bd67049 Add a barebones test for transaction conditions 2018-03-08 08:58:34 -07:00
d500bbff04 Add public key to mint
This turns the mint into a handy way to generate public keys
without throwing the private key away.
2018-03-08 08:33:00 -07:00
e759bd1a99 Add conditions to the signature to reject duplicates 2018-03-08 08:18:34 -07:00
94daf4cea4 Add Cancel and Timestamp events
Fixes #31, #34, #39
2018-03-08 08:17:34 -07:00
2379792e0a Add DateTime and Cancel conditions
Fixes #32, #33
2018-03-08 08:17:08 -07:00
dba6d7a8a6 Update README.md 2018-03-07 17:20:40 -07:00
086c206b76 Merge pull request #55 from garious/the-mint
More intuitive demo, introducing The Mint
2018-03-07 17:18:24 -07:00
5dd567deef Rename Genesis to Mint
Genesis is a story of creation. We should only use that term to
for the event log that bootstraps the system.
2018-03-07 17:08:15 -07:00
b6d8f737ca Introducing, the mint
Use the mint to pair a new private key with new tokens.
2018-03-07 16:58:04 -07:00
491ba9da84 Add accessors to keypairs and signatures 2018-03-07 15:32:22 -07:00
a420a9293f Fix demo 2018-03-07 11:37:30 -07:00
c1bc5f6a07 Merge pull request #54 from garious/imperative-genesis
Boot genesis block helper
2018-03-07 11:19:16 -07:00
9834c251d0 Boot genesis block helper
Before this change, if you wanted to use a new Transaction
feature in the genesis block, you'd need to extend its
Creator object and associated methods.  With yesterday's
addtions to Transcation, it's now so easy to work with
Transactions directly that we can get rid of the middleman.

Also added a KeyPair type alias, so that ring could be easily swapped
out with a competing library, if needed.
2018-03-07 11:10:15 -07:00
54340ed4c6 Delete debugging println
Thanks @jackson-sandland!
2018-03-06 21:17:41 -07:00
96a0a9202c Update README.md 2018-03-06 21:12:50 -07:00
a4c081d3a1 Merge pull request #53 from garious/monorphic-entry
Monomorphisize Entry and Event
2018-03-06 20:39:11 -07:00
d1b6206858 Monomorphisize Entry and Event
Transaction turned out to be the only struct worth making generic.
2018-03-06 20:29:18 -07:00
0eb6849fe3 Merge pull request #52 from garious/add-transaction-struct
Break dependency cycle
2018-03-06 17:53:48 -07:00
b725fdb093 Sha256Hash -> Hash
Because in Loom, there's just the one. Hopefully no worries that it
shares a name with std::Hash.
2018-03-06 17:40:01 -07:00
1436bb1ff2 Move entry into its own module
Hmm, Logger doesn't depend on log.
2018-03-06 17:40:01 -07:00
5a44c36b1f Move hash into its own module 2018-03-06 17:40:01 -07:00
5d990502cb Merge pull request #51 from jackson-sandland/50-proof-read-README
Issue #50 - proof read README
2018-03-06 17:39:33 -07:00
64735da716 Issue #50 - proof read README 2018-03-06 16:21:45 -08:00
95b82aa6dc Merge pull request #49 from garious/add-transaction-struct
DRY up transaction signing
2018-03-06 16:48:27 -07:00
f09952f3d7 DRY up transaction signing
Cleanup the big mess I copy-pasted myself into.
2018-03-06 16:34:25 -07:00
b98e04dc56 Update README.md 2018-03-06 15:03:06 -07:00
cb436250da Merge pull request #48 from garious/add-transaction-struct
data -> asset
2018-03-06 15:01:56 -07:00
4376032e3a data -> asset
'data' is too vague.
2018-03-06 14:50:32 -07:00
c231331e05 Merge pull request #47 from garious/add-transaction-struct
Reorg
2018-03-06 12:57:49 -07:00
624c151ca2 Add signature module
Because things other than transactions can be signed.
2018-03-06 12:48:28 -07:00
5d0356f74b Move verify_entry to a method as well 2018-03-06 12:35:12 -07:00
b019416518 Move verify into methods
A little overly-coupled to Serialize, but makes the code a lot tighter
2018-03-06 12:27:08 -07:00
4fcd9e3bd6 Give Transaction its own module 2018-03-06 12:18:17 -07:00
66bf889c39 Rename Transfer to Transaction
struct names should be nouns
2018-03-06 11:54:47 -07:00
a2811842c8 More cleanup
Far fewer branches when we process transfers outside the context
of events.
2018-03-06 11:43:55 -07:00
1929601425 Cleanup
Now that Transfer is out of the enum, we don't need to pattern
match to access its fields.
2018-03-06 11:19:59 -07:00
282afee47e Use Transfer struct on the client side too
Sharing is caring.
2018-03-06 11:03:43 -07:00
e701ccc949 Rename Request::Transfer to Request::Transaction 2018-03-06 10:59:47 -07:00
6543497c17 Move Transaction data into its own struct
This will allow us to add addition transfer types to the log.
2018-03-06 10:50:32 -07:00
7d9af5a937 Merge pull request #46 from garious/be-negative
Allow balances to be negative
2018-03-05 23:47:02 -07:00
720c54a5bb Allow balances to be negative
* Will allow owners to loan token to others.
* Will allow for parallel verification of balances without spilling
  over 64 bits.

Fixes #43
2018-03-05 17:30:53 -07:00
5dca3c41f2 Update README.md 2018-03-05 16:19:26 -07:00
929546f60b Update README.md 2018-03-05 16:18:46 -07:00
cb0ce9986c Merge pull request #45 from garious/init-from-log
Towards sending the log to clients
2018-03-05 16:17:41 -07:00
064eba00fd Update readme 2018-03-05 16:05:16 -07:00
a4336a39d6 Initialize the testnode from a log
$ cargo run --bin silk-genesis-file-demo > demo-genesis.json
$ cat demo-genesis.json | cargo run --bin silk-genesis-block > demo-genesis.log
$ cat demo-genesis.log | cargo run --bin silk-testnode
2018-03-05 15:34:44 -07:00
298989c4b9 Generate log from Genesis 2018-03-05 13:03:56 -07:00
48c28c2267 Transactions now require a hash of the last entry they've seen
This ensures the transaction cannot be processed on a chain
that forked before that ID. It will also provide a basis for
expiration constraints. A client may want their transaction
to expire, and the generators may want to reject transactions
that have been floating in the ether for years.
2018-03-05 12:48:14 -07:00
d76ecbc9c9 Don't block the server 2018-03-05 11:39:59 -07:00
79fb9c00aa Boot wait_on_signature() from accountant
Instead, there should be a way to query for a page of log data,
and checking whether it has a signature should be done client-side.
2018-03-05 10:45:18 -07:00
c9e03f37ce Logger now only speaks when spoken to
Before this change, the logger's send channel could quickly be
flooded with Tick events. Those events should only be passed to
a writer.

Also, the log_event() function no longer sends entries. That
functionality moved to the new process_events() function. This
will allow us to initialize the with the genesis block without
flooding the send channel with events the historian won't read.
2018-03-05 10:33:12 -07:00
aa5f1699a7 Update the set of unique signatures when loading an existing log. 2018-03-04 22:31:12 -07:00
e1e9126d03 Merge pull request #44 from garious/genesis
Finally, genesis block generation without channels
2018-03-04 14:39:28 -07:00
672a4b3723 Update historian diagram 2018-03-04 14:36:55 -07:00
955f76baab Finally, genesis block generation without channels 2018-03-04 14:32:30 -07:00
7da8a5e2d1 Merge pull request #42 from garious/genesis
Make num_hashes more intuitive
2018-03-04 13:05:38 -07:00
ff82fbf112 Make num_hashes mean the num_hashes since the last ID
Before this change, num_hashes meant the number of hashes since
the last ID, minus any hashing done on the event data. It made
no difference for Tick events, but logged Transaction events with
one less hash than actually occurred.
2018-03-04 09:52:36 -07:00
8503a0a58f Refactor 2018-03-04 09:21:45 -07:00
b1e9512f44 Rename end_hash to id 2018-03-04 07:50:26 -07:00
608def9c78 Consolidate imports 2018-03-04 07:28:51 -07:00
bcb21bc1d8 Delete dead code 2018-03-04 07:20:17 -07:00
f63096620a Merge pull request #41 from garious/genesis
Add command-line tool for generating a genesis block
2018-03-04 01:27:59 -07:00
9b26892bae Add a demo app to generate the genesis file 2018-03-04 01:21:40 -07:00
572475ce14 Load the genesis block 2018-03-04 00:15:17 -07:00
876d7995e1 Refactor to support loading an existing ledger 2018-03-03 22:25:40 -07:00
b8655e30d4 Make client-demo standalone
And remove deposit() methods from the API. Those should only be
used on the server to bootstrap.
2018-03-03 21:15:51 -07:00
7cf0d55546 Remove optional 'from' field 2018-03-03 20:41:07 -07:00
ce60b960c0 Special case sending money to self
In the genesis block, let matching 'from' and 'to' keys be used
to mint new coin.
2018-03-03 20:27:12 -07:00
cebcb5b92d Start genesis with a Tick, so that its hash can be used to bootstrap verification 2018-03-03 19:57:22 -07:00
11a0f96f5e Add command-line tool for generating a genesis block 2018-03-03 17:35:05 -07:00
74ebaf1744 Merge pull request #40 from garious/add-logger
Add logger
2018-03-03 14:37:15 -07:00
f7496ea6d1 Make create_logger a static method
Allows us to share the super long type signature in impl.
2018-03-03 14:26:59 -07:00
bebba7dc1f Give logger its own crate 2018-03-03 14:24:32 -07:00
afb2bf442c Use Instant instead of SystemTime for more precise ticking
And convert log_event from function to method
2018-03-03 14:08:53 -07:00
c7de48c982 Convert log_events from function to method 2018-03-03 14:00:37 -07:00
f906112c03 Move logging thread's state into a struct 2018-03-03 13:52:57 -07:00
8ef864fb39 Merge pull request #37 from garious/split-benchmark
Split benchmark
2018-03-03 12:13:54 -07:00
1c9b5ab53c Report performance of signature verification too 2018-03-03 11:59:34 -07:00
c10faae3b5 More readable metrics 2018-03-03 11:52:50 -07:00
2104dd5a0a Fix benchmark
Was measuring the creation of the iterator, not running it.
2018-03-03 11:45:23 -07:00
fbe64037db Merge pull request #35 from garious/split-benchmark
Move key generation and signing from transaction benchmark
2018-03-03 11:25:58 -07:00
d8c50b150c Move key generation and signing from transaction benchmark
Key generation, signing and verification are not the performance
bottleneck. Something is probably wrong here.
2018-03-03 11:11:46 -07:00
8871bb2d8e Merge pull request #30 from garious/simplify
Unify Claim and Transaction handling
2018-03-02 12:24:44 -07:00
a148454376 Update readme 2018-03-02 12:07:05 -07:00
be518b569b Remove cyclic dependency between event and log 2018-03-02 12:03:59 -07:00
c998fbe2ae Sign the owner's public key
Without this, the accountant will reject transfers from different
entities if they are for the same amount and to the same entity.
2018-03-02 11:56:42 -07:00
9f12cd0c09 Purge the Claim event type
It's now represented as a Transaction from an unknown party.
2018-03-02 11:48:58 -07:00
0d0fee1ca1 Sign Claim's 'to' field
Otherwise, the accountant will treat deposits of the same amount as
duplicates.
2018-03-02 11:46:22 -07:00
a0410c4677 Pipe all Claim constructors through a function 2018-03-02 10:58:43 -07:00
8fe464cfa3 Rename Claim's key field to match same field in Transaction 2018-03-02 10:47:21 -07:00
3e2d6d9e8b Generalize Transaction to express a Claim
If a Transaction doesn't have an existing address, it's being used
to create new funds.
2018-03-02 10:41:15 -07:00
32d677787b Reduce transactions sent by demo
We don't do retries yet, so keep tx count to something that won't
trigger any packet loss.
2018-03-02 10:35:38 -07:00
dfd1c4eab3 Don't process transaction if channel.send() fails.
Do all input validation first, then log (which can fail). If all
goes swimmingly, process the transaction.
2018-03-02 10:17:52 -07:00
36bb1f989d More defense against a double-spend attack
Before this change, a client could spend funds before the accountant
processed a previous spend. With this change in place, the accountant
updates balances immediately, but that comes at an architectural cost.
The accountant now verifies signatures on behalf of the historian, so
that it can ensure logging will not fail.
2018-03-02 09:55:44 -07:00
684f4c59e0 Delete commented out code
accountant crate shouldn't verify the log. Instead, it should
only add valid entries and leave verification to network nodes.
2018-03-02 08:51:29 -07:00
1b77e8a69a Move Event into its own crate
The log crate was starting to be the catch-all for all things
related to entries, events, signatures, and hashes. This split
shows us that:

* Event depends only on signatures, not on hashes [directly]
* All event testing was done via log testing (shame on me)
* Accounting depends only on events
2018-03-02 08:43:57 -07:00
662e10c3e0 Merge pull request #29 from garious/simplify
Remove Discovery event
2018-03-01 18:53:25 -07:00
c935fdb12f Move signature duplicate detection into the historian 2018-03-01 17:44:10 -07:00
9e16937914 Delete the Discovery event
Not useful to the accountant.
2018-03-01 17:02:41 -07:00
f705202381 No need to hash data that's already hashed to create the signature 2018-03-01 16:39:09 -07:00
f5532ad9f7 Merge pull request #28 from garious/go-udp
Switch to UDP
2018-03-01 14:25:20 -07:00
570e71f050 Check for duplicate signatures
TODO: have client add recent hash to each message
2018-03-01 14:07:39 -07:00
c9cc4b4369 Switch to UDP from TCP
And remove all the sleep()'ing around.
2018-03-01 13:47:53 -07:00
7111aa3b18 Copy disclaimer from the loom repository
Per @aeyakovenko, added Loom's disclaimer.
2018-03-01 09:16:39 -07:00
12eba4bcc7 Merge pull request #26 from garious/add-accountant
Add testnode and client-demo
2018-02-28 19:48:05 -07:00
4610de8fdd Switch to sync_channel to preserve order 2018-02-28 19:33:28 -07:00
3fcc2dd944 Add testnode
Fixes #20
2018-02-28 18:05:20 -07:00
8299bae2d4 Add accountant stub 2018-02-28 16:01:12 -07:00
604ccf7552 Add network interface for accountant 2018-02-28 14:00:04 -07:00
f3dd47948a Merge pull request #25 from garious/verify-historian-input
Verify event signatures before adding log entries
2018-02-28 10:34:10 -07:00
c3bb207488 Verify event signatures before adding log entries 2018-02-28 10:23:01 -07:00
9009d1bfb3 Merge pull request #24 from garious/add-accountant
Add accountant
2018-02-27 11:41:40 -07:00
fa4d9e8bcb Add more tests 2018-02-27 11:28:10 -07:00
34b77efc87 Sleep longer for TravisCI 2018-02-27 11:08:28 -07:00
5ca0ccbcd2 Add accountant 2018-02-27 10:54:06 -07:00
6aa4e52480 Merge pull request #23 from garious/add-transaction
Generalize the event log
2018-02-26 17:40:55 -07:00
f98e9a2ad7 Fix overuse of search-and-replace 2018-02-26 17:03:50 -07:00
c6134cc25b Allow the historian to track ownership of any type of data 2018-02-26 17:01:22 -07:00
0443b39264 Allow event log to hold events of any serializable (hashable) type 2018-02-26 16:42:31 -07:00
8b0b8efbcb Allow Entry to hold events of any kind of data 2018-02-26 15:37:33 -07:00
97449cee43 Allow events to hold any kind of data 2018-02-26 15:31:01 -07:00
ab5252c750 Move entry verification out of Entry impl 2018-02-26 14:39:01 -07:00
05a27cb34d Merge pull request #22 from garious/add-transaction
Extend the event log with a Transaction event to transfer possession
2018-02-26 11:26:58 -07:00
b02eab57d2 Extend the event log with a Transaction event to transfer possession
This implementation assumes 'from' is the current owner of 'data'.
Once that's verified, the signature ensures that nobody modified
'data' (the asset being transferred) or 'to' the entity taking
ownership.

Fixes #14
2018-02-26 11:09:11 -07:00
b8d52cc3e4 Make the Discovery event into a struct instead of a tuple 2018-02-24 11:15:03 -07:00
7d9bab9508 Update rendered demo diagram 2018-02-24 11:09:00 -07:00
944181a30e Version bump 2018-02-24 11:06:08 -07:00
d8dd50505a Merge pull request #21 from garious/add-signatures
Add signatures
2018-02-24 10:47:25 -07:00
d78082f5e4 Test bad signature 2018-02-24 10:27:51 -07:00
08e501e57b Extend the event log with a Claim event to claim possession
Unlike a Discovery event, a Claim event associates a public key
with a hash. It's intended to to be used to claim ownership of
some hashable data. For example, a graphic designer could claim
copyright by hashing some image they created, signing it with
their private key, and publishing the hash-signature pair via
the historian. If someone else tries to claim it as their own,
the designer can point to the historian's log as cryptographically
secure evidence that the designer's copy existed before anyone
else's.

Note there's nothing here that verifies the first claim is the actual
content owner, only that the first claim almost certainly happened
before a second.
2018-02-24 10:09:49 -07:00
29a607427d Rename UserDataKey to Discovery
From the perspective of the log, when some data's hash is added,
that data is "discovered" by the historian.  Another event
might be a "claim" that some signed data belongs to the owner of a
public key.
2018-02-24 05:25:19 -07:00
afb830c91f Merge pull request #18 from garious/add-historian
self-ticking logger
2018-02-21 12:30:10 -07:00
c1326ac3d5 Up the time to sleep so that ticks are generated 2018-02-21 12:22:23 -07:00
513a1adf57 Version bump 2018-02-21 12:01:17 -07:00
7871b38c80 Update demo to use self-ticking logger 2018-02-21 11:52:03 -07:00
b34d2d7dee Allow the logger to inject Tick events on its own 2018-02-21 11:33:42 -07:00
d7dfa8c22d Readme cleanup 2018-02-21 10:07:32 -07:00
8df274f0af Add hash seed to verify_slice() 2018-02-21 09:43:34 -07:00
07c4ebb7f2 Add message sequence chart for readme demo
Fixes #17
2018-02-21 09:33:50 -07:00
49605b257d Merge pull request #16 from garious/add-serde
Add serialization/deseriation support to event log
2018-02-20 16:55:46 -07:00
fa4e232d73 Add serialization/deseriation support to event log
See bincode and serde_json for usage:
https://github.com/TyOverby/bincode

Fixes #1
2018-02-20 16:26:13 -07:00
bd84cf6586 Merge pull request #15 from garious/add-historian
Demo proof-of-history and reordering attack
2018-02-20 15:05:20 -07:00
6e37f70d55 Test reorder attack 2018-02-20 14:46:36 -07:00
d97112d7f0 Explain proof-of-history in the readme
Also:
* Hash userdata so that verification works as the readme describes.
* Drop itertools package. Found a way to use std::iter instead.

Fixes #8
2018-02-20 14:04:49 -07:00
e57bba17c1 Version bump 2018-02-19 16:59:41 -07:00
959da300cc Shorten readme lines 2018-02-19 16:53:58 -07:00
ba90e43f72 Update benchmark
* Add asm, though it doesn't make it faster. TODO: use avx instructions.
* Do 10x less hashes, since sha256 is more expensive.
2018-02-19 16:51:35 -07:00
6effd64ab0 Update readme with sha256 usage 2018-02-19 16:48:29 -07:00
e18da7c7c1 Merge pull request #13 from garious/sha256-hash
Use sha256 hashes instead of Rust's builtin hasher.
2018-02-19 16:43:26 -07:00
0297edaf1f Use sha256 hashes instead of Rust's builtin hasher.
Causes a 20x performance degradation. Enabling asm did not
speed things up.
2018-02-19 16:23:53 -07:00
b317d13b44 Add codecov configuration 2018-02-19 13:02:59 -07:00
28 changed files with 2165 additions and 273 deletions

2
.codecov.yml Normal file
View File

@ -0,0 +1,2 @@
ignore:
- "src/bin"

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/target/
**/*.rs.bk
Cargo.lock

View File

@ -9,7 +9,7 @@ matrix:
- rust: stable
- rust: nightly
env:
- FEATURES='unstable'
- FEATURES='asm,unstable'
before_script: |
export PATH="$PATH:$HOME/.cargo/bin"
rustup component add rustfmt-preview

482
Cargo.lock generated Normal file
View File

@ -0,0 +1,482 @@
[[package]]
name = "arrayref"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arrayvec"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bincode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "block-buffer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "byte-tools"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-epoch"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dtoa"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "either"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "gcc"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "generic-array"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "generic-array"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memoffset"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nodrop"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-iter"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num_cpus"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon-core"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ring"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "scopeguard"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive_internals 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive_internals"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha2-asm"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "silk"
version = "0.3.2"
dependencies = [
"bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2-asm 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.12.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typenum"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "untrusted"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f"
"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
"checksum bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bda13183df33055cbb84b847becce220d392df502ebe7a4a78d7021771ed94d0"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9"
"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
"checksum crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "59796cc6cbbdc6bb319161349db0c3250ec73ec7fcb763a51065ec4e2e158552"
"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
"checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
"checksum generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fceb69994e330afed50c93524be68c42fa898c2d9fd4ee8da03bd7363acd26f2"
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe"
"checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593"
"checksum num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3c2bd9b9d21e48e956b763c9f37134dc62d9e95da6edb3f672cacb6caf3cd3"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b614fe08b6665cb9a231d07ac1364b0ef3cb3698f1239ee0c4c3a88a524f54c8"
"checksum rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "485541959c8ecc49865526fe6c4de9653dd6e60d829d6edf0be228167b60372d"
"checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6f7d28b30a72c01b458428e0ae988d4149c20d902346902be881e3edc4bb325c"
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
"checksum serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "e928fecdb00fe608c96f83a012633383564e730962fc7a0b79225a6acf056798"
"checksum serde_derive 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "95f666a2356d87ce4780ea15b14b13532785579a5cad2dcba5292acc75f6efe2"
"checksum serde_derive_internals 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc848d073be32cd982380c06587ea1d433bc1a4c4a111de07ec2286a3ddade8"
"checksum serde_json 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "57781ed845b8e742fc2bf306aba8e3b408fe8c366b900e3769fbc39f49eb8b39"
"checksum sha2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7daca11f2fdb8559c4f6c588386bed5e2ad4b6605c1442935a7f08144a918688"
"checksum sha2-asm 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e319010fd740857efd4428b8ee1b74f311aeb0fda1ece174a9bad6741182d26"
"checksum syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)" = "517f6da31bc53bf080b9a77b29fbd0ff8da2f5a2ebd24c73c2238274a94ac7cb"
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
"checksum typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae"
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -1,7 +1,7 @@
[package]
name = "silk"
description = "A silky smooth implementation of the Loom architecture"
version = "0.2.0"
version = "0.3.2"
documentation = "https://docs.rs/silk"
homepage = "http://loomprotocol.com/"
repository = "https://github.com/loomprotocol/silk"
@ -12,15 +12,45 @@ authors = [
license = "Apache-2.0"
[[bin]]
name = "silk-demo"
path = "src/bin/demo.rs"
name = "silk-historian-demo"
path = "src/bin/historian-demo.rs"
[[bin]]
name = "silk-client-demo"
path = "src/bin/client-demo.rs"
[[bin]]
name = "silk-testnode"
path = "src/bin/testnode.rs"
[[bin]]
name = "silk-genesis"
path = "src/bin/genesis.rs"
[[bin]]
name = "silk-genesis-demo"
path = "src/bin/genesis-demo.rs"
[[bin]]
name = "silk-mint"
path = "src/bin/mint.rs"
[badges]
codecov = { repository = "loomprotocol/silk", branch = "master", service = "github" }
[features]
unstable = []
asm = ["sha2-asm"]
[dependencies]
rayon = "1.0.0"
itertools = "0.7.6"
sha2 = "0.7.0"
sha2-asm = {version="0.3", optional=true}
generic-array = { version = "0.9.0", default-features = false, features = ["serde"] }
serde = "1.0.27"
serde_derive = "1.0.27"
serde_json = "1.0.10"
ring = "0.12.1"
untrusted = "0.5.1"
bincode = "1.0.0"
chrono = { version = "0.4.0", features = ["serde"] }

116
README.md
View File

@ -3,67 +3,91 @@
[![Build Status](https://travis-ci.org/loomprotocol/silk.svg?branch=master)](https://travis-ci.org/loomprotocol/silk)
[![codecov](https://codecov.io/gh/loomprotocol/silk/branch/master/graph/badge.svg)](https://codecov.io/gh/loomprotocol/silk)
# Silk, a silky smooth implementation of the Loom specification
Disclaimer
===
Loom is a new achitecture for a high performance blockchain. Its whitepaper boasts a theoretical
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.
Silk, a silky smooth implementation of the Loom specification
===
Loom™ is a new architecture for a high performance blockchain. Its white paper boasts a theoretical
throughput of 710k transactions per second on a 1 gbps network. The specification is implemented
in two git repositories. Reserach is performed in the loom repository. That work drives the
in two git repositories. Research is performed in the loom repository. That work drives the
Loom specification forward. This repository, on the other hand, aims to implement the specification
as-is. We care a great deal about quality, clarity and short learning curve. We avoid the use
of `unsafe` Rust and write tests for *everything*. Optimizations are only added when
corresponding benchmarks are also added that demonstrate real performance boots. We expect the
corresponding benchmarks are also added that demonstrate real performance boosts. We expect the
feature set here will always be a ways behind the loom repo, but that this is an implementation
you can take to the bank, literally.
# Usage
Running the demo
===
Add the latest [silk package](https://crates.io/crates/silk) to the `[dependencies]` section
of your Cargo.toml.
First, install Rust's package manager Cargo.
Create a *Historian* and send it *events* to generate an *event log*, where each log *entry*
is tagged with the historian's latest *hash*. Then ensure the order of events was not tampered
with by verifying each entry's hash can be generated from the hash in the previous entry:
```rust
extern crate silk;
use silk::historian::Historian;
use silk::log::{verify_slice, Entry, Event};
use std::{thread, time};
use std::sync::mpsc::SendError;
fn create_log(hist: &Historian) -> Result<(), SendError<Event>> {
hist.sender.send(Event::Tick)?;
thread::sleep(time::Duration::new(0, 100_000));
hist.sender.send(Event::UserDataKey(0xdeadbeef))?;
thread::sleep(time::Duration::new(0, 100_000));
hist.sender.send(Event::Tick)?;
Ok(())
}
fn main() {
let seed = 0;
let hist = Historian::new(seed);
create_log(&hist).expect("send error");
drop(hist.sender);
let entries: Vec<Entry> = hist.receiver.iter().collect();
for entry in &entries {
println!("{:?}", entry);
}
assert!(verify_slice(&entries, seed));
}
```bash
$ curl https://sh.rustup.rs -sSf | sh
$ source $HOME/.cargo/env
```
Running the program should produce a log similar to:
Install the silk executables:
```
Entry { num_hashes: 0, end_hash: 0, event: Tick }
Entry { num_hashes: 245, end_hash: 11504657626326377539, event: UserDataKey(3735928559) }
Entry { num_hashes: 154, end_hash: 13410333856574024888, event: Tick }
```bash
$ cargo install silk
```
The testnode server is initialized with a transaction log from stdin and
generates new log entries on stdout. To create the input log, we'll need
to create *the mint* and use it to generate a *genesis log*. It's done in
two steps because the mint.json file contains a private key that will be
used later in this demo.
# Developing
```bash
$ echo 500 | silk-mint > mint.json
$ cat mint.json | silk-genesis > genesis.log
```
Now you can start the server:
```bash
$ cat genesis.log | silk-testnode > transactions0.log
```
Then, in a separate shell, let's execute some transactions. Note we pass in
the JSON configuration file here, not the genesis log.
```bash
$ cat mint.json | silk-client-demo
```
Now kill the server with Ctrl-C, and take a look at the transaction log. You should
see something similar to:
```json
{"num_hashes":27,"id":[0, "..."],"event":"Tick"}
{"num_hashes":3,"id":[67, "..."],"event":{"Transaction":{"asset":42}}}
{"num_hashes":27,"id":[0, "..."],"event":"Tick"}
```
Now restart the server from where we left off. Pass it both the genesis log, and
the transaction log.
```bash
$ cat genesis.log transactions0.log | silk-testnode > transactions1.log
```
Lastly, run the client demo again, and verify that all funds were spent in the
previous round, and so no additional transactions are added.
```bash
$ cat mint.json | silk-client-demo
```
Stop the server again, and verify there are only Tick entries, and no Transaction entries.
Developing
===
Building
---
@ -104,5 +128,5 @@ $ rustup install nightly
Run the benchmarks:
```bash
$ cargo +nightly bench --features="unstable"
$ cargo +nightly bench --features="asm,unstable"
```

65
doc/historian.md Normal file
View File

@ -0,0 +1,65 @@
The Historian
===
Create a *Historian* and send it *events* to generate an *event log*, where each log *entry*
is tagged with the historian's latest *hash*. Then ensure the order of events was not tampered
with by verifying each entry's hash can be generated from the hash in the previous entry:
![historian](https://user-images.githubusercontent.com/55449/36950845-459bdb58-1fb9-11e8-850e-894586f3729b.png)
```rust
extern crate silk;
use silk::historian::Historian;
use silk::log::{verify_slice, Entry, Hash};
use silk::event::{generate_keypair, get_pubkey, sign_claim_data, Event};
use std::thread::sleep;
use std::time::Duration;
use std::sync::mpsc::SendError;
fn create_log(hist: &Historian<Hash>) -> Result<(), SendError<Event<Hash>>> {
sleep(Duration::from_millis(15));
let asset = Hash::default();
let keypair = generate_keypair();
let event0 = Event::new_claim(get_pubkey(&keypair), asset, sign_claim_data(&asset, &keypair));
hist.sender.send(event0)?;
sleep(Duration::from_millis(10));
Ok(())
}
fn main() {
let seed = Hash::default();
let hist = Historian::new(&seed, Some(10));
create_log(&hist).expect("send error");
drop(hist.sender);
let entries: Vec<Entry<Hash>> = hist.receiver.iter().collect();
for entry in &entries {
println!("{:?}", entry);
}
// Proof-of-History: Verify the historian learned about the events
// in the same order they appear in the vector.
assert!(verify_slice(&entries, &seed));
}
```
Running the program should produce a log similar to:
```rust
Entry { num_hashes: 0, id: [0, ...], event: Tick }
Entry { num_hashes: 3, id: [67, ...], event: Transaction { asset: [37, ...] } }
Entry { num_hashes: 3, id: [123, ...], event: Tick }
```
Proof-of-History
---
Take note of the last line:
```rust
assert!(verify_slice(&entries, &seed));
```
[It's a proof!](https://en.wikipedia.org/wiki/CurryHoward_correspondence) For each entry returned by the
historian, we can verify that `id` is the result of applying a sha256 hash to the previous `id`
exactly `num_hashes` times, and then hashing then event data on top of that. Because the event data is
included in the hash, the events cannot be reordered without regenerating all the hashes.

18
doc/historian.msc Normal file
View File

@ -0,0 +1,18 @@
msc {
client,historian,logger;
logger=>historian [ label = "e0 = Entry{id: h0, n: 0, event: Tick}" ] ;
logger=>logger [ label = "h1 = hash(h0)" ] ;
logger=>logger [ label = "h2 = hash(h1)" ] ;
client=>historian [ label = "Transaction(d0)" ] ;
historian=>logger [ label = "Transaction(d0)" ] ;
logger=>logger [ label = "h3 = hash(h2 + d0)" ] ;
logger=>historian [ label = "e1 = Entry{id: hash(h3), n: 3, event: Transaction(d0)}" ] ;
logger=>logger [ label = "h4 = hash(h3)" ] ;
logger=>logger [ label = "h5 = hash(h4)" ] ;
logger=>logger [ label = "h6 = hash(h5)" ] ;
logger=>historian [ label = "e2 = Entry{id: h6, n: 3, event: Tick}" ] ;
client=>historian [ label = "collect()" ] ;
historian=>client [ label = "entries = [e0, e1, e2]" ] ;
client=>client [ label = "verify_slice(entries, h0)" ] ;
}

409
src/accountant.rs Normal file
View File

@ -0,0 +1,409 @@
//! The `accountant` is a client of the `historian`. It uses the historian's
//! event log to record transactions. Its users can deposit funds and
//! transfer funds to other users.
use hash::Hash;
use entry::Entry;
use event::Event;
use transaction::{Condition, Transaction};
use signature::{KeyPair, PublicKey, Signature};
use mint::Mint;
use historian::{reserve_signature, Historian};
use std::sync::mpsc::SendError;
use std::collections::{HashMap, HashSet};
use std::result;
use chrono::prelude::*;
#[derive(Debug, PartialEq, Eq)]
pub enum AccountingError {
InsufficientFunds,
InvalidTransfer,
InvalidTransferSignature,
SendError,
}
pub type Result<T> = result::Result<T, AccountingError>;
pub struct Accountant {
pub historian: Historian,
pub balances: HashMap<PublicKey, i64>,
pub first_id: Hash,
pub last_id: Hash,
pending: HashMap<Signature, Transaction<i64>>,
time_sources: HashSet<PublicKey>,
last_time: DateTime<Utc>,
}
impl Accountant {
pub fn new_from_entries<I>(entries: I, ms_per_tick: Option<u64>) -> Self
where
I: IntoIterator<Item = Entry>,
{
let mut entries = entries.into_iter();
// The first item in the log is required to be an entry with zero num_hashes,
// which implies its id can be used as the log's seed.
let entry0 = entries.next().unwrap();
let start_hash = entry0.id;
let hist = Historian::new(&start_hash, ms_per_tick);
let mut acc = Accountant {
historian: hist,
balances: HashMap::new(),
first_id: start_hash,
last_id: start_hash,
pending: HashMap::new(),
time_sources: HashSet::new(),
last_time: Utc.timestamp(0, 0),
};
// The second item in the log is a special transaction where the to and from
// fields are the same. That entry should be treated as a deposit, not a
// transfer to oneself.
let entry1 = entries.next().unwrap();
acc.process_verified_event(&entry1.event, true).unwrap();
for entry in entries {
acc.process_verified_event(&entry.event, false).unwrap();
}
acc
}
pub fn new(mint: &Mint, ms_per_tick: Option<u64>) -> Self {
Self::new_from_entries(mint.create_entries(), ms_per_tick)
}
pub fn sync(self: &mut Self) -> Hash {
while let Ok(entry) = self.historian.receiver.try_recv() {
self.last_id = entry.id;
}
self.last_id
}
fn is_deposit(allow_deposits: bool, from: &PublicKey, to: &PublicKey) -> bool {
allow_deposits && from == to
}
pub fn process_transaction(self: &mut Self, tr: Transaction<i64>) -> Result<()> {
if !tr.verify() {
return Err(AccountingError::InvalidTransfer);
}
if self.get_balance(&tr.from).unwrap_or(0) < tr.asset {
return Err(AccountingError::InsufficientFunds);
}
self.process_verified_transaction(&tr, false)?;
if let Err(SendError(_)) = self.historian.sender.send(Event::Transaction(tr)) {
return Err(AccountingError::SendError);
}
Ok(())
}
/// Commit funds to the 'to' party.
fn complete_transaction(self: &mut Self, tr: &Transaction<i64>) {
if self.balances.contains_key(&tr.to) {
if let Some(x) = self.balances.get_mut(&tr.to) {
*x += tr.asset;
}
} else {
self.balances.insert(tr.to, tr.asset);
}
}
/// Return funds to the 'from' party.
fn cancel_transaction(self: &mut Self, tr: &Transaction<i64>) {
if let Some(x) = self.balances.get_mut(&tr.from) {
*x += tr.asset;
}
}
// TODO: Move this to transaction.rs
fn all_satisfied(&self, conds: &[Condition]) -> bool {
let mut satisfied = true;
for cond in conds {
if let &Condition::Timestamp(dt) = cond {
if dt > self.last_time {
satisfied = false;
}
} else {
satisfied = false;
}
}
satisfied
}
fn process_verified_transaction(
self: &mut Self,
tr: &Transaction<i64>,
allow_deposits: bool,
) -> Result<()> {
if !reserve_signature(&mut self.historian.signatures, &tr.sig) {
return Err(AccountingError::InvalidTransferSignature);
}
if !tr.unless_any.is_empty() {
// TODO: Check to see if the transaction is expired.
}
if !Self::is_deposit(allow_deposits, &tr.from, &tr.to) {
if let Some(x) = self.balances.get_mut(&tr.from) {
*x -= tr.asset;
}
}
if !self.all_satisfied(&tr.if_all) {
self.pending.insert(tr.sig, tr.clone());
return Ok(());
}
self.complete_transaction(tr);
Ok(())
}
fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> {
let mut cancel = false;
if let Some(tr) = self.pending.get(&tx_sig) {
// Cancel:
// if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map.
// TODO: Use find().
for cond in &tr.unless_any {
if let Condition::Signature(pubkey) = *cond {
if from == pubkey {
cancel = true;
break;
}
}
}
}
if cancel {
if let Some(tr) = self.pending.remove(&tx_sig) {
self.cancel_transaction(&tr);
}
}
// Process Multisig:
// otherwise, if "Signature(from) is in if_all, remove it. If that causes that list
// to be empty, add the asset to to, and remove the tx from this map.
Ok(())
}
fn process_verified_timestamp(&mut self, from: PublicKey, dt: DateTime<Utc>) -> Result<()> {
// If this is the first timestamp we've seen, it probably came from the genesis block,
// so we'll trust it.
if self.last_time == Utc.timestamp(0, 0) {
self.time_sources.insert(from);
}
if self.time_sources.contains(&from) {
if dt > self.last_time {
self.last_time = dt;
}
} else {
return Ok(());
}
// TODO: Lookup pending Transaction waiting on time, signed by a whitelisted PublicKey.
// Expire:
// if a Timestamp after this DateTime is in unless_any, return funds to tx.from,
// and remove the tx from this map.
// Check to see if any timelocked transactions can be completed.
let mut completed = vec![];
for (key, tr) in &self.pending {
for cond in &tr.if_all {
if let Condition::Timestamp(dt) = *cond {
if self.last_time >= dt {
if tr.if_all.len() == 1 {
completed.push(*key);
}
}
}
}
// TODO: Add this in once we start removing constraints
//if tr.if_all.is_empty() {
// // TODO: Remove tr from pending
// self.complete_transaction(tr);
//}
}
for key in completed {
if let Some(tr) = self.pending.remove(&key) {
self.complete_transaction(&tr);
}
}
Ok(())
}
fn process_verified_event(self: &mut Self, event: &Event, allow_deposits: bool) -> Result<()> {
match *event {
Event::Tick => Ok(()),
Event::Transaction(ref tr) => self.process_verified_transaction(tr, allow_deposits),
Event::Signature { from, tx_sig, .. } => self.process_verified_sig(from, tx_sig),
Event::Timestamp { from, dt, .. } => self.process_verified_timestamp(from, dt),
}
}
pub fn transfer(
self: &mut Self,
n: i64,
keypair: &KeyPair,
to: PublicKey,
) -> Result<Signature> {
let tr = Transaction::new(keypair, to, n, self.last_id);
let sig = tr.sig;
self.process_transaction(tr).map(|_| sig)
}
pub fn transfer_on_date(
self: &mut Self,
n: i64,
keypair: &KeyPair,
to: PublicKey,
dt: DateTime<Utc>,
) -> Result<Signature> {
let tr = Transaction::new_on_date(keypair, to, dt, n, self.last_id);
let sig = tr.sig;
self.process_transaction(tr).map(|_| sig)
}
pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option<i64> {
self.balances.get(pubkey).map(|x| *x)
}
}
#[cfg(test)]
mod tests {
use super::*;
use signature::KeyPairUtil;
use logger::ExitReason;
#[test]
fn test_accountant() {
let alice = Mint::new(10_000);
let bob_pubkey = KeyPair::new().pubkey();
let mut acc = Accountant::new(&alice, Some(2));
acc.transfer(1_000, &alice.keypair(), bob_pubkey).unwrap();
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000);
acc.transfer(500, &alice.keypair(), bob_pubkey).unwrap();
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500);
drop(acc.historian.sender);
assert_eq!(
acc.historian.thread_hdl.join().unwrap(),
ExitReason::RecvDisconnected
);
}
#[test]
fn test_invalid_transfer() {
let alice = Mint::new(11_000);
let mut acc = Accountant::new(&alice, Some(2));
let bob_pubkey = KeyPair::new().pubkey();
acc.transfer(1_000, &alice.keypair(), bob_pubkey).unwrap();
assert_eq!(
acc.transfer(10_001, &alice.keypair(), bob_pubkey),
Err(AccountingError::InsufficientFunds)
);
let alice_pubkey = alice.keypair().pubkey();
assert_eq!(acc.get_balance(&alice_pubkey).unwrap(), 10_000);
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000);
drop(acc.historian.sender);
assert_eq!(
acc.historian.thread_hdl.join().unwrap(),
ExitReason::RecvDisconnected
);
}
#[test]
fn test_transfer_to_newb() {
let alice = Mint::new(10_000);
let mut acc = Accountant::new(&alice, Some(2));
let alice_keypair = alice.keypair();
let bob_pubkey = KeyPair::new().pubkey();
acc.transfer(500, &alice_keypair, bob_pubkey).unwrap();
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500);
drop(acc.historian.sender);
assert_eq!(
acc.historian.thread_hdl.join().unwrap(),
ExitReason::RecvDisconnected
);
}
#[test]
fn test_transfer_on_date() {
let alice = Mint::new(1);
let mut acc = Accountant::new(&alice, Some(2));
let alice_keypair = alice.keypair();
let bob_pubkey = KeyPair::new().pubkey();
let dt = Utc::now();
acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt)
.unwrap();
// Alice's balance will be zero because all funds are locked up.
assert_eq!(acc.get_balance(&alice.pubkey()), Some(0));
// Bob's balance will be None because the funds have not been
// sent.
assert_eq!(acc.get_balance(&bob_pubkey), None);
// Now, acknowledge the time in the condition occurred and
// that bob's funds are now available.
acc.process_verified_timestamp(alice.pubkey(), dt).unwrap();
assert_eq!(acc.get_balance(&bob_pubkey), Some(1));
acc.process_verified_timestamp(alice.pubkey(), dt).unwrap(); // <-- Attack! Attempt to process completed transaction.
assert_ne!(acc.get_balance(&bob_pubkey), Some(2));
}
#[test]
fn test_transfer_after_date() {
let alice = Mint::new(1);
let mut acc = Accountant::new(&alice, Some(2));
let alice_keypair = alice.keypair();
let bob_pubkey = KeyPair::new().pubkey();
let dt = Utc::now();
acc.process_verified_timestamp(alice.pubkey(), dt).unwrap();
// It's now past now, so this transfer should be processed immediately.
acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt)
.unwrap();
assert_eq!(acc.get_balance(&alice.pubkey()), Some(0));
assert_eq!(acc.get_balance(&bob_pubkey), Some(1));
}
#[test]
fn test_cancel_transfer() {
let alice = Mint::new(1);
let mut acc = Accountant::new(&alice, Some(2));
let alice_keypair = alice.keypair();
let bob_pubkey = KeyPair::new().pubkey();
let dt = Utc::now();
let sig = acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt)
.unwrap();
// Alice's balance will be zero because all funds are locked up.
assert_eq!(acc.get_balance(&alice.pubkey()), Some(0));
// Bob's balance will be None because the funds have not been
// sent.
assert_eq!(acc.get_balance(&bob_pubkey), None);
// Now, cancel the trancaction. Alice gets her funds back, Bob never sees them.
acc.process_verified_sig(alice.pubkey(), sig).unwrap();
assert_eq!(acc.get_balance(&alice.pubkey()), Some(1));
assert_eq!(acc.get_balance(&bob_pubkey), None);
acc.process_verified_sig(alice.pubkey(), sig).unwrap(); // <-- Attack! Attempt to cancel completed transaction.
assert_ne!(acc.get_balance(&alice.pubkey()), Some(2));
}
}

75
src/accountant_skel.rs Normal file
View File

@ -0,0 +1,75 @@
use std::io;
use accountant::Accountant;
use transaction::Transaction;
use signature::PublicKey;
use hash::Hash;
use entry::Entry;
use std::net::UdpSocket;
use bincode::{deserialize, serialize};
pub struct AccountantSkel {
pub acc: Accountant,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum Request {
Transaction(Transaction<i64>),
GetBalance { key: PublicKey },
GetEntries { last_id: Hash },
GetId { is_last: bool },
}
#[derive(Serialize, Deserialize, Debug)]
pub enum Response {
Balance { key: PublicKey, val: Option<i64> },
Entries { entries: Vec<Entry> },
Id { id: Hash, is_last: bool },
}
impl AccountantSkel {
pub fn new(acc: Accountant) -> Self {
AccountantSkel { acc }
}
pub fn process_request(self: &mut Self, msg: Request) -> Option<Response> {
match msg {
Request::Transaction(tr) => {
if let Err(err) = self.acc.process_transaction(tr) {
eprintln!("Transaction error: {:?}", err);
}
None
}
Request::GetBalance { key } => {
let val = self.acc.get_balance(&key);
Some(Response::Balance { key, val })
}
Request::GetEntries { .. } => Some(Response::Entries { entries: vec![] }),
Request::GetId { is_last } => Some(Response::Id {
id: if is_last {
self.acc.sync();
self.acc.last_id
} else {
self.acc.first_id
},
is_last,
}),
}
}
/// UDP Server that forwards messages to Accountant methods.
pub fn serve(self: &mut Self, addr: &str) -> io::Result<()> {
let socket = UdpSocket::bind(addr)?;
let mut buf = vec![0u8; 1024];
loop {
//println!("skel: Waiting for incoming packets...");
let (_sz, src) = socket.recv_from(&mut buf)?;
// TODO: Return a descriptive error message if deserialization fails.
let req = deserialize(&buf).expect("deserialize request");
if let Some(resp) = self.process_request(req) {
socket.send_to(&serialize(&resp).expect("serialize response"), &src)?;
}
}
}
}

139
src/accountant_stub.rs Normal file
View File

@ -0,0 +1,139 @@
//! The `accountant` is a client of the `historian`. It uses the historian's
//! event log to record transactions. Its users can deposit funds and
//! transfer funds to other users.
use std::net::UdpSocket;
use std::io;
use bincode::{deserialize, serialize};
use transaction::Transaction;
use signature::{KeyPair, PublicKey, Signature};
use hash::Hash;
use entry::Entry;
use accountant_skel::{Request, Response};
pub struct AccountantStub {
pub addr: String,
pub socket: UdpSocket,
pub last_id: Option<Hash>,
}
impl AccountantStub {
pub fn new(addr: &str, socket: UdpSocket) -> Self {
AccountantStub {
addr: addr.to_string(),
socket,
last_id: None,
}
}
pub fn transfer_signed(&self, tr: Transaction<i64>) -> io::Result<usize> {
let req = Request::Transaction(tr);
let data = serialize(&req).unwrap();
self.socket.send_to(&data, &self.addr)
}
pub fn transfer(
&self,
n: i64,
keypair: &KeyPair,
to: PublicKey,
last_id: &Hash,
) -> io::Result<Signature> {
let tr = Transaction::new(keypair, to, n, *last_id);
let sig = tr.sig;
self.transfer_signed(tr).map(|_| sig)
}
pub fn get_balance(&self, pubkey: &PublicKey) -> io::Result<Option<i64>> {
let req = Request::GetBalance { key: *pubkey };
let data = serialize(&req).expect("serialize GetBalance");
self.socket.send_to(&data, &self.addr)?;
let mut buf = vec![0u8; 1024];
self.socket.recv_from(&mut buf)?;
let resp = deserialize(&buf).expect("deserialize balance");
if let Response::Balance { key, val } = resp {
assert_eq!(key, *pubkey);
return Ok(val);
}
Ok(None)
}
fn get_id(&self, is_last: bool) -> io::Result<Hash> {
let req = Request::GetId { is_last };
let data = serialize(&req).expect("serialize GetId");
self.socket.send_to(&data, &self.addr)?;
let mut buf = vec![0u8; 1024];
self.socket.recv_from(&mut buf)?;
let resp = deserialize(&buf).expect("deserialize Id");
if let Response::Id { id, .. } = resp {
return Ok(id);
}
Ok(Default::default())
}
pub fn get_last_id(&self) -> io::Result<Hash> {
self.get_id(true)
}
pub fn wait_on_signature(&mut self, wait_sig: &Signature) -> io::Result<()> {
let last_id = match self.last_id {
None => {
let first_id = self.get_id(false)?;
self.last_id = Some(first_id);
first_id
}
Some(last_id) => last_id,
};
let req = Request::GetEntries { last_id };
let data = serialize(&req).unwrap();
self.socket.send_to(&data, &self.addr).map(|_| ())?;
let mut buf = vec![0u8; 1024];
self.socket.recv_from(&mut buf)?;
let resp = deserialize(&buf).expect("deserialize signature");
if let Response::Entries { entries } = resp {
for Entry { id, event, .. } in entries {
self.last_id = Some(id);
if let Some(sig) = event.get_signature() {
if sig == *wait_sig {
return Ok(());
}
}
}
}
// TODO: Loop until we found it.
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use accountant::Accountant;
use accountant_skel::AccountantSkel;
use std::thread::{sleep, spawn};
use std::time::Duration;
use mint::Mint;
use signature::{KeyPair, KeyPairUtil};
#[test]
fn test_accountant_stub() {
let addr = "127.0.0.1:9000";
let send_addr = "127.0.0.1:9001";
let alice = Mint::new(10_000);
let acc = Accountant::new(&alice, None);
let bob_pubkey = KeyPair::new().pubkey();
spawn(move || AccountantSkel::new(acc).serve(addr).unwrap());
sleep(Duration::from_millis(30));
let socket = UdpSocket::bind(send_addr).unwrap();
let mut acc = AccountantStub::new(addr, socket);
let last_id = acc.get_last_id().unwrap();
let sig = acc.transfer(500, &alice.keypair(), bob_pubkey, &last_id)
.unwrap();
acc.wait_on_signature(&sig).unwrap();
assert_eq!(acc.get_balance(&bob_pubkey).unwrap().unwrap(), 500);
}
}

77
src/bin/client-demo.rs Normal file
View File

@ -0,0 +1,77 @@
extern crate serde_json;
extern crate silk;
use silk::accountant_stub::AccountantStub;
use silk::signature::{KeyPair, KeyPairUtil};
use silk::transaction::Transaction;
use silk::mint::Mint;
use std::time::Instant;
use std::net::UdpSocket;
use std::io::stdin;
fn main() {
let addr = "127.0.0.1:8000";
let send_addr = "127.0.0.1:8001";
let mint: Mint = serde_json::from_reader(stdin()).unwrap();
let mint_keypair = mint.keypair();
let mint_pubkey = mint.pubkey();
let socket = UdpSocket::bind(send_addr).unwrap();
let mut acc = AccountantStub::new(addr, socket);
let last_id = acc.get_last_id().unwrap();
let txs = acc.get_balance(&mint_pubkey).unwrap().unwrap();
println!("Mint's Initial Balance {}", txs);
println!("Signing transactions...");
let now = Instant::now();
let transactions: Vec<_> = (0..txs)
.map(|_| {
let rando_pubkey = KeyPair::new().pubkey();
Transaction::new(&mint_keypair, rando_pubkey, 1, last_id)
})
.collect();
let duration = now.elapsed();
let ns = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64;
let bsps = txs as f64 / ns as f64;
let nsps = ns as f64 / txs as f64;
println!(
"Done. {} thousand signatures per second, {}us per signature",
bsps * 1_000_000_f64,
nsps / 1_000_f64
);
println!("Verify signatures...");
let now = Instant::now();
for tr in &transactions {
assert!(tr.verify());
}
let duration = now.elapsed();
let ns = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64;
let bsvps = txs as f64 / ns as f64;
let nspsv = ns as f64 / txs as f64;
println!(
"Done. {} thousand signature verifications per second, {}us per signature verification",
bsvps * 1_000_000_f64,
nspsv / 1_000_f64
);
println!("Transferring 1 unit {} times...", txs);
let now = Instant::now();
let mut sig = Default::default();
for tr in transactions {
sig = tr.sig;
acc.transfer_signed(tr).unwrap();
}
println!("Waiting for last transaction to be confirmed...",);
acc.wait_on_signature(&sig).unwrap();
let duration = now.elapsed();
let ns = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64;
let tps = (txs * 1_000_000_000) as f64 / ns as f64;
println!("Done. {} tps!", tps);
let val = acc.get_balance(&mint_pubkey).unwrap().unwrap();
println!("Mint's Final Balance {}", val);
assert_eq!(val, 0);
}

View File

@ -1,27 +0,0 @@
extern crate silk;
use silk::historian::Historian;
use silk::log::{verify_slice, Entry, Event};
use std::{thread, time};
use std::sync::mpsc::SendError;
fn create_log(hist: &Historian) -> Result<(), SendError<Event>> {
hist.sender.send(Event::Tick)?;
thread::sleep(time::Duration::new(0, 100_000));
hist.sender.send(Event::UserDataKey(0xdeadbeef))?;
thread::sleep(time::Duration::new(0, 100_000));
hist.sender.send(Event::Tick)?;
Ok(())
}
fn main() {
let seed = 0;
let hist = Historian::new(seed);
create_log(&hist).expect("send error");
drop(hist.sender);
let entries: Vec<Entry> = hist.receiver.iter().collect();
for entry in &entries {
println!("{:?}", entry);
}
assert!(verify_slice(&entries, seed));
}

30
src/bin/genesis-demo.rs Normal file
View File

@ -0,0 +1,30 @@
extern crate serde_json;
extern crate silk;
use silk::mint::Mint;
use silk::event::Event;
use silk::transaction::Transaction;
use silk::log::create_entries;
use silk::signature::{KeyPair, KeyPairUtil, PublicKey};
use silk::hash::Hash;
use std::io::stdin;
fn transfer(from: &KeyPair, (to, tokens): (PublicKey, i64), last_id: Hash) -> Event {
Event::Transaction(Transaction::new(&from, to, tokens, last_id))
}
fn main() {
let alice = (KeyPair::new().pubkey(), 200);
let bob = (KeyPair::new().pubkey(), 100);
let mint: Mint = serde_json::from_reader(stdin()).unwrap();
let from = mint.keypair();
let seed = mint.seed();
let mut events = mint.create_events();
events.push(transfer(&from, alice, seed));
events.push(transfer(&from, bob, seed));
for entry in create_entries(&seed, events) {
println!("{}", serde_json::to_string(&entry).unwrap());
}
}

14
src/bin/genesis.rs Normal file
View File

@ -0,0 +1,14 @@
//! A command-line executable for generating the chain's genesis block.
extern crate serde_json;
extern crate silk;
use silk::mint::Mint;
use std::io::stdin;
fn main() {
let mint: Mint = serde_json::from_reader(stdin()).unwrap();
for x in mint.create_entries() {
println!("{}", serde_json::to_string(&x).unwrap());
}
}

36
src/bin/historian-demo.rs Normal file
View File

@ -0,0 +1,36 @@
extern crate silk;
use silk::historian::Historian;
use silk::hash::Hash;
use silk::entry::Entry;
use silk::log::verify_slice;
use silk::signature::{KeyPair, KeyPairUtil};
use silk::transaction::Transaction;
use silk::event::Event;
use std::thread::sleep;
use std::time::Duration;
use std::sync::mpsc::SendError;
fn create_log(hist: &Historian, seed: &Hash) -> Result<(), SendError<Event>> {
sleep(Duration::from_millis(15));
let keypair = KeyPair::new();
let tr = Transaction::new(&keypair, keypair.pubkey(), 42, *seed);
let event0 = Event::Transaction(tr);
hist.sender.send(event0)?;
sleep(Duration::from_millis(10));
Ok(())
}
fn main() {
let seed = Hash::default();
let hist = Historian::new(&seed, Some(10));
create_log(&hist, &seed).expect("send error");
drop(hist.sender);
let entries: Vec<Entry> = hist.receiver.iter().collect();
for entry in &entries {
println!("{:?}", entry);
}
// Proof-of-History: Verify the historian learned about the events
// in the same order they appear in the vector.
assert!(verify_slice(&entries, &seed));
}

15
src/bin/mint.rs Normal file
View File

@ -0,0 +1,15 @@
extern crate serde_json;
extern crate silk;
use silk::mint::Mint;
use std::io;
fn main() {
let mut input_text = String::new();
io::stdin().read_line(&mut input_text).unwrap();
let trimmed = input_text.trim();
let tokens = trimmed.parse::<i64>().unwrap();
let mint = Mint::new(tokens);
println!("{}", serde_json::to_string(&mint).unwrap());
}

19
src/bin/testnode.rs Normal file
View File

@ -0,0 +1,19 @@
extern crate serde_json;
extern crate silk;
use silk::accountant_skel::AccountantSkel;
use silk::accountant::Accountant;
use std::io::{self, BufRead};
fn main() {
let addr = "127.0.0.1:8000";
let stdin = io::stdin();
let entries = stdin
.lock()
.lines()
.map(|line| serde_json::from_str(&line.unwrap()).unwrap());
let acc = Accountant::new_from_entries(entries, Some(1000));
let mut skel = AccountantSkel::new(acc);
eprintln!("Listening on {}", addr);
skel.serve(addr).unwrap();
}

97
src/entry.rs Normal file
View File

@ -0,0 +1,97 @@
use hash::{extend_and_hash, hash, Hash};
use event::Event;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Entry {
pub num_hashes: u64,
pub id: Hash,
pub event: Event,
}
impl Entry {
/// Creates a Entry from the number of hashes 'num_hashes' since the previous event
/// and that resulting 'id'.
pub fn new_tick(num_hashes: u64, id: &Hash) -> Self {
Entry {
num_hashes,
id: *id,
event: Event::Tick,
}
}
/// Verifies self.id is the result of hashing a 'start_hash' 'self.num_hashes' times.
/// If the event is not a Tick, then hash that as well.
pub fn verify(&self, start_hash: &Hash) -> bool {
if !self.event.verify() {
return false;
}
self.id == next_hash(start_hash, self.num_hashes, &self.event)
}
}
/// Creates the hash 'num_hashes' after start_hash. If the event contains
/// signature, the final hash will be a hash of both the previous ID and
/// the signature.
pub fn next_hash(start_hash: &Hash, num_hashes: u64, event: &Event) -> Hash {
let mut id = *start_hash;
let sig = event.get_signature();
let start_index = if sig.is_some() { 1 } else { 0 };
for _ in start_index..num_hashes {
id = hash(&id);
}
if let Some(sig) = sig {
id = extend_and_hash(&id, &sig);
}
id
}
/// Creates the next Entry 'num_hashes' after 'start_hash'.
pub fn create_entry(start_hash: &Hash, cur_hashes: u64, event: Event) -> Entry {
let sig = event.get_signature();
let num_hashes = cur_hashes + if sig.is_some() { 1 } else { 0 };
let id = next_hash(start_hash, 0, &event);
Entry {
num_hashes,
id,
event,
}
}
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
pub fn create_entry_mut(start_hash: &mut Hash, cur_hashes: &mut u64, event: Event) -> Entry {
let entry = create_entry(start_hash, *cur_hashes, event);
*start_hash = entry.id;
*cur_hashes = 0;
entry
}
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
pub fn next_tick(start_hash: &Hash, num_hashes: u64) -> Entry {
let event = Event::Tick;
Entry {
num_hashes,
id: next_hash(start_hash, num_hashes, &event),
event,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_verify() {
let zero = Hash::default();
let one = hash(&zero);
assert!(Entry::new_tick(0, &zero).verify(&zero)); // base case
assert!(!Entry::new_tick(0, &zero).verify(&one)); // base case, bad
assert!(next_tick(&zero, 1).verify(&zero)); // inductive step
assert!(!next_tick(&zero, 1).verify(&one)); // inductive step, bad
}
#[test]
fn test_next_tick() {
let zero = Hash::default();
assert_eq!(next_tick(&zero, 1).num_hashes, 1)
}
}

58
src/event.rs Normal file
View File

@ -0,0 +1,58 @@
//! The `event` crate provides the data structures for log events.
use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
use transaction::Transaction;
use chrono::prelude::*;
use bincode::serialize;
/// When 'event' is Tick, the event represents a simple clock tick, and exists for the
/// sole purpose of improving the performance of event log verification. A tick can
/// be generated in 'num_hashes' hashes and verified in 'num_hashes' hashes. By logging
/// a hash alongside the tick, each tick and be verified in parallel using the 'id'
/// of the preceding tick to seed its hashing.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Event {
Tick,
Transaction(Transaction<i64>),
Signature {
from: PublicKey,
tx_sig: Signature,
sig: Signature,
},
Timestamp {
from: PublicKey,
dt: DateTime<Utc>,
sig: Signature,
},
}
impl Event {
pub fn new_timestamp(from: &KeyPair, dt: DateTime<Utc>) -> Self {
let sign_data = serialize(&dt).unwrap();
let sig = Signature::clone_from_slice(from.sign(&sign_data).as_ref());
Event::Timestamp {
from: from.pubkey(),
dt,
sig,
}
}
// TODO: Rename this to transaction_signature().
pub fn get_signature(&self) -> Option<Signature> {
match *self {
Event::Tick => None,
Event::Transaction(ref tr) => Some(tr.sig),
Event::Signature { .. } => None,
Event::Timestamp { .. } => None,
}
}
pub fn verify(&self) -> bool {
match *self {
Event::Tick => true,
Event::Transaction(ref tr) => tr.verify(),
Event::Signature { from, tx_sig, sig } => sig.verify(&from, &tx_sig),
Event::Timestamp { from, dt, sig } => sig.verify(&from, &serialize(&dt).unwrap()),
}
}
}

19
src/hash.rs Normal file
View File

@ -0,0 +1,19 @@
use generic_array::GenericArray;
use generic_array::typenum::U32;
use sha2::{Digest, Sha256};
pub type Hash = GenericArray<u8, U32>;
/// Return a Sha256 hash for the given data.
pub fn hash(val: &[u8]) -> Hash {
let mut hasher = Sha256::default();
hasher.input(val);
hasher.result()
}
/// Return the hash of the given hash extended with the given value.
pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash {
let mut hash_data = id.to_vec();
hash_data.extend_from_slice(val);
hash(&hash_data)
}

View File

@ -1,113 +1,84 @@
//! The `historian` crate provides a microservice for generating a Proof-of-History.
//! It logs Event items on behalf of its users. It continuously generates
//! new hashes, only stopping to check if it has been sent an Event item. It
//! tags each Event with an Entry and sends it back. The Entry includes the
//! Event, the latest hash, and the number of hashes since the last event.
//! The resulting stream of entries represents ordered events in time.
//! It manages a thread containing a Proof-of-History Logger.
use std::thread::JoinHandle;
use std::sync::mpsc::{Receiver, Sender};
use log::{hash, Entry, Event};
use std::thread::{spawn, JoinHandle};
use std::collections::HashSet;
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
use std::time::Instant;
use hash::{hash, Hash};
use entry::Entry;
use logger::{ExitReason, Logger};
use signature::Signature;
use event::Event;
pub struct Historian {
pub sender: Sender<Event>,
pub sender: SyncSender<Event>,
pub receiver: Receiver<Entry>,
pub thread_hdl: JoinHandle<(Entry, ExitReason)>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ExitReason {
RecvDisconnected,
SendDisconnected,
}
fn log_events(
receiver: &Receiver<Event>,
sender: &Sender<Entry>,
num_hashes: u64,
end_hash: u64,
) -> Result<u64, (Entry, ExitReason)> {
use std::sync::mpsc::TryRecvError;
let mut num_hashes = num_hashes;
loop {
match receiver.try_recv() {
Ok(event) => {
let entry = Entry {
end_hash,
num_hashes,
event,
};
if let Err(_) = sender.send(entry.clone()) {
return Err((entry, ExitReason::SendDisconnected));
}
num_hashes = 0;
}
Err(TryRecvError::Empty) => {
return Ok(num_hashes);
}
Err(TryRecvError::Disconnected) => {
let entry = Entry {
end_hash,
num_hashes,
event: Event::Tick,
};
return Err((entry, ExitReason::RecvDisconnected));
}
}
}
}
/// A background thread that will continue tagging received Event messages and
/// sending back Entry messages until either the receiver or sender channel is closed.
pub fn create_logger(
start_hash: u64,
receiver: Receiver<Event>,
sender: Sender<Entry>,
) -> JoinHandle<(Entry, ExitReason)> {
use std::thread;
thread::spawn(move || {
let mut end_hash = start_hash;
let mut num_hashes = 0;
loop {
match log_events(&receiver, &sender, num_hashes, end_hash) {
Ok(n) => num_hashes = n,
Err(err) => return err,
}
end_hash = hash(end_hash);
num_hashes += 1;
}
})
pub thread_hdl: JoinHandle<ExitReason>,
pub signatures: HashSet<Signature>,
}
impl Historian {
pub fn new(start_hash: u64) -> Self {
use std::sync::mpsc::channel;
let (sender, event_receiver) = channel();
let (entry_sender, receiver) = channel();
let thread_hdl = create_logger(start_hash, event_receiver, entry_sender);
pub fn new(start_hash: &Hash, ms_per_tick: Option<u64>) -> Self {
let (sender, event_receiver) = sync_channel(1000);
let (entry_sender, receiver) = sync_channel(1000);
let thread_hdl =
Historian::create_logger(*start_hash, ms_per_tick, event_receiver, entry_sender);
let signatures = HashSet::new();
Historian {
sender,
receiver,
thread_hdl,
signatures,
}
}
/// A background thread that will continue tagging received Event messages and
/// sending back Entry messages until either the receiver or sender channel is closed.
fn create_logger(
start_hash: Hash,
ms_per_tick: Option<u64>,
receiver: Receiver<Event>,
sender: SyncSender<Entry>,
) -> JoinHandle<ExitReason> {
spawn(move || {
let mut logger = Logger::new(receiver, sender, start_hash);
let now = Instant::now();
loop {
if let Err(err) = logger.process_events(now, ms_per_tick) {
return err;
}
logger.last_id = hash(&logger.last_id);
logger.num_hashes += 1;
}
})
}
}
pub fn reserve_signature(sigs: &mut HashSet<Signature>, sig: &Signature) -> bool {
if sigs.contains(sig) {
return false;
}
sigs.insert(*sig);
true
}
#[cfg(test)]
mod tests {
use super::*;
use log::*;
use event::*;
use std::thread::sleep;
use std::time::Duration;
#[test]
fn test_historian() {
use std::thread::sleep;
use std::time::Duration;
let hist = Historian::new(0);
let zero = Hash::default();
let hist = Historian::new(&zero, None);
hist.sender.send(Event::Tick).unwrap();
sleep(Duration::new(0, 1_000_000));
hist.sender.send(Event::UserDataKey(0xdeadbeef)).unwrap();
hist.sender.send(Event::Tick).unwrap();
sleep(Duration::new(0, 1_000_000));
hist.sender.send(Event::Tick).unwrap();
@ -117,21 +88,47 @@ mod tests {
drop(hist.sender);
assert_eq!(
hist.thread_hdl.join().unwrap().1,
hist.thread_hdl.join().unwrap(),
ExitReason::RecvDisconnected
);
assert!(verify_slice(&[entry0, entry1, entry2], 0));
assert!(verify_slice(&[entry0, entry1, entry2], &zero));
}
#[test]
fn test_historian_closed_sender() {
let hist = Historian::new(0);
let zero = Hash::default();
let hist = Historian::new(&zero, None);
drop(hist.receiver);
hist.sender.send(Event::Tick).unwrap();
assert_eq!(
hist.thread_hdl.join().unwrap().1,
hist.thread_hdl.join().unwrap(),
ExitReason::SendDisconnected
);
}
#[test]
fn test_duplicate_event_signature() {
let mut sigs = HashSet::new();
let sig = Signature::default();
assert!(reserve_signature(&mut sigs, &sig));
assert!(!reserve_signature(&mut sigs, &sig));
}
#[test]
fn test_ticking_historian() {
let zero = Hash::default();
let hist = Historian::new(&zero, Some(20));
sleep(Duration::from_millis(30));
hist.sender.send(Event::Tick).unwrap();
drop(hist.sender);
let entries: Vec<Entry> = hist.receiver.iter().collect();
// Ensure one entry is sent back for each tick sent in.
assert_eq!(entries.len(), 1);
// Ensure the ID is not the seed, which indicates another Tick
// was logged before the one we sent.
assert_ne!(entries[0].id, zero);
}
}

View File

@ -1,5 +1,24 @@
#![cfg_attr(feature = "unstable", feature(test))]
pub mod signature;
pub mod hash;
pub mod transaction;
pub mod event;
pub mod entry;
pub mod log;
pub mod mint;
pub mod logger;
pub mod historian;
extern crate itertools;
pub mod accountant;
pub mod accountant_skel;
pub mod accountant_stub;
extern crate bincode;
extern crate chrono;
extern crate generic_array;
extern crate rayon;
extern crate ring;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate sha2;
extern crate untrusted;

View File

@ -2,9 +2,9 @@
//! an ordered log of events in time.
/// Each log entry contains three pieces of data. The 'num_hashes' field is the number
/// of hashes performed since the previous entry. The 'end_hash' field is the result
/// of hashing 'end_hash' from the previous entry 'num_hashes' times. The 'event'
/// field points to an Event that took place shortly after 'end_hash' was generated.
/// of hashes performed since the previous entry. The 'id' field is the result
/// of hashing 'id' from the previous entry 'num_hashes' times. The 'event'
/// field points to an Event that took place shortly after 'id' was generated.
///
/// If you divide 'num_hashes' by the amount of time it takes to generate a new hash, you
/// get a duration estimate since the last event. Since processing power increases
@ -12,123 +12,79 @@
/// Though processing power varies across nodes, the network gives priority to the
/// fastest processor. Duration should therefore be estimated by assuming that the hash
/// was generated by the fastest processor at the time the entry was logged.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Entry {
pub num_hashes: u64,
pub end_hash: u64,
pub event: Event,
}
/// When 'event' is Tick, the event represents a simple clock tick, and exists for the
/// sole purpose of improving the performance of event log verification. A tick can
/// be generated in 'num_hashes' hashes and verified in 'num_hashes' hashes. By logging
/// a hash alongside the tick, each tick and be verified in parallel using the 'end_hash'
/// of the preceding tick to seed its hashing.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Event {
Tick,
UserDataKey(u64),
}
impl Entry {
/// Creates a Entry from the number of hashes 'num_hashes' since the previous event
/// and that resulting 'end_hash'.
pub fn new_tick(num_hashes: u64, end_hash: u64) -> Self {
let event = Event::Tick;
Entry {
num_hashes,
end_hash,
event,
}
}
/// Verifies self.end_hash is the result of hashing a 'start_hash' 'self.num_hashes' times.
pub fn verify(self: &Self, start_hash: u64) -> bool {
self.end_hash == next_tick(start_hash, self.num_hashes).end_hash
}
}
pub fn hash(val: u64) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
val.hash(&mut hasher);
hasher.finish()
}
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
pub fn next_tick(start_hash: u64, num_hashes: u64) -> Entry {
let mut end_hash = start_hash;
for _ in 0..num_hashes {
end_hash = hash(end_hash);
}
Entry::new_tick(num_hashes, end_hash)
}
use hash::Hash;
use entry::{create_entry_mut, next_tick, Entry};
use event::Event;
use rayon::prelude::*;
/// Verifies the hashes and counts of a slice of events are all consistent.
pub fn verify_slice(events: &[Entry], start_hash: u64) -> bool {
use rayon::prelude::*;
let genesis = [Entry::new_tick(0, start_hash)];
pub fn verify_slice(events: &[Entry], start_hash: &Hash) -> bool {
let genesis = [Entry::new_tick(Default::default(), start_hash)];
let event_pairs = genesis.par_iter().chain(events).zip(events);
event_pairs.all(|(x0, x1)| x1.verify(x0.end_hash))
event_pairs.all(|(x0, x1)| x1.verify(&x0.id))
}
/// Verifies the hashes and events serially. Exists only for reference.
pub fn verify_slice_seq(events: &[Entry], start_hash: u64) -> bool {
let genesis = [Entry::new_tick(0, start_hash)];
let mut event_pairs = genesis.iter().chain(events).zip(events);
event_pairs.all(|(x0, x1)| x1.verify(x0.end_hash))
pub fn create_entries(start_hash: &Hash, events: Vec<Event>) -> Vec<Entry> {
let mut id = *start_hash;
events
.into_iter()
.map(|event| create_entry_mut(&mut id, &mut 0, event))
.collect()
}
/// Create a vector of Ticks of length 'len' from 'start_hash' hash and 'num_hashes'.
pub fn create_ticks(start_hash: u64, num_hashes: u64, len: usize) -> Vec<Entry> {
use itertools::unfold;
let mut events = unfold(start_hash, |state| {
let event = next_tick(*state, num_hashes);
*state = event.end_hash;
return Some(event);
});
events.by_ref().take(len).collect()
pub fn next_ticks(start_hash: &Hash, num_hashes: u64, len: usize) -> Vec<Entry> {
let mut id = *start_hash;
let mut ticks = vec![];
for _ in 0..len {
let entry = next_tick(&id, num_hashes);
id = entry.id;
ticks.push(entry);
}
ticks
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_verify() {
assert!(Entry::new_tick(0, 0).verify(0)); // base case
assert!(!Entry::new_tick(0, 0).verify(1)); // base case, bad
assert!(next_tick(0, 1).verify(0)); // inductive step
assert!(!next_tick(0, 1).verify(1)); // inductive step, bad
}
#[test]
fn test_next_tick() {
assert_eq!(next_tick(0, 1).num_hashes, 1)
}
fn verify_slice_generic(verify_slice: fn(&[Entry], u64) -> bool) {
assert!(verify_slice(&vec![], 0)); // base case
assert!(verify_slice(&vec![Entry::new_tick(0, 0)], 0)); // singleton case 1
assert!(!verify_slice(&vec![Entry::new_tick(0, 0)], 1)); // singleton case 2, bad
assert!(verify_slice(&create_ticks(0, 0, 2), 0)); // inductive step
let mut bad_ticks = create_ticks(0, 0, 2);
bad_ticks[1].end_hash = 1;
assert!(!verify_slice(&bad_ticks, 0)); // inductive step, bad
}
use signature::{KeyPair, KeyPairUtil};
use transaction::Transaction;
use hash::hash;
#[test]
fn test_verify_slice() {
verify_slice_generic(verify_slice);
let zero = Hash::default();
let one = hash(&zero);
assert!(verify_slice(&vec![], &zero)); // base case
assert!(verify_slice(&vec![Entry::new_tick(0, &zero)], &zero)); // singleton case 1
assert!(!verify_slice(&vec![Entry::new_tick(0, &zero)], &one)); // singleton case 2, bad
assert!(verify_slice(&next_ticks(&zero, 0, 2), &zero)); // inductive step
let mut bad_ticks = next_ticks(&zero, 0, 2);
bad_ticks[1].id = one;
assert!(!verify_slice(&bad_ticks, &zero)); // inductive step, bad
}
#[test]
fn test_verify_slice_seq() {
verify_slice_generic(verify_slice_seq);
}
fn test_reorder_attack() {
let zero = Hash::default();
// First, verify entries
let keypair = KeyPair::new();
let tr0 = Transaction::new(&keypair, keypair.pubkey(), 0, zero);
let tr1 = Transaction::new(&keypair, keypair.pubkey(), 1, zero);
let events = vec![Event::Transaction(tr0), Event::Transaction(tr1)];
let mut entries = create_entries(&zero, events);
assert!(verify_slice(&entries, &zero));
// Next, swap two events and ensure verification fails.
let event0 = entries[0].event.clone();
let event1 = entries[1].event.clone();
entries[0].event = event1;
entries[1].event = event0;
assert!(!verify_slice(&entries, &zero));
}
}
#[cfg(all(feature = "unstable", test))]
@ -139,19 +95,19 @@ mod bench {
#[bench]
fn event_bench(bencher: &mut Bencher) {
let start_hash = 0;
let events = create_ticks(start_hash, 100_000, 8);
let start_hash = Default::default();
let events = next_ticks(&start_hash, 10_000, 8);
bencher.iter(|| {
assert!(verify_slice(&events, start_hash));
assert!(verify_slice(&events, &start_hash));
});
}
#[bench]
fn event_bench_seq(bencher: &mut Bencher) {
let start_hash = 0;
let events = create_ticks(start_hash, 100_000, 8);
let start_hash = Default::default();
let events = next_ticks(&start_hash, 10_000, 8);
bencher.iter(|| {
assert!(verify_slice_seq(&events, start_hash));
assert!(verify_slice_seq(&events, &start_hash));
});
}
}

71
src/logger.rs Normal file
View File

@ -0,0 +1,71 @@
//! The `logger` crate provides an object for generating a Proof-of-History.
//! It logs Event items on behalf of its users. It continuously generates
//! new hashes, only stopping to check if it has been sent an Event item. It
//! tags each Event with an Entry and sends it back. The Entry includes the
//! Event, the latest hash, and the number of hashes since the last event.
//! The resulting stream of entries represents ordered events in time.
use std::sync::mpsc::{Receiver, SyncSender, TryRecvError};
use std::time::{Duration, Instant};
use hash::Hash;
use entry::{create_entry_mut, Entry};
use event::Event;
use serde_json;
#[derive(Debug, PartialEq, Eq)]
pub enum ExitReason {
RecvDisconnected,
SendDisconnected,
}
pub struct Logger {
pub sender: SyncSender<Entry>,
pub receiver: Receiver<Event>,
pub last_id: Hash,
pub num_hashes: u64,
pub num_ticks: u64,
}
impl Logger {
pub fn new(receiver: Receiver<Event>, sender: SyncSender<Entry>, start_hash: Hash) -> Self {
Logger {
receiver,
sender,
last_id: start_hash,
num_hashes: 0,
num_ticks: 0,
}
}
pub fn log_event(&mut self, event: Event) -> Result<Entry, ExitReason> {
let entry = create_entry_mut(&mut self.last_id, &mut self.num_hashes, event);
println!("{}", serde_json::to_string(&entry).unwrap());
Ok(entry)
}
pub fn process_events(
&mut self,
epoch: Instant,
ms_per_tick: Option<u64>,
) -> Result<(), ExitReason> {
loop {
if let Some(ms) = ms_per_tick {
if epoch.elapsed() > Duration::from_millis((self.num_ticks + 1) * ms) {
self.log_event(Event::Tick)?;
self.num_ticks += 1;
}
}
match self.receiver.try_recv() {
Ok(event) => {
let entry = self.log_event(event)?;
self.sender
.send(entry)
.or(Err(ExitReason::SendDisconnected))?;
}
Err(TryRecvError::Empty) => return Ok(()),
Err(TryRecvError::Disconnected) => return Err(ExitReason::RecvDisconnected),
};
}
}
}

77
src/mint.rs Normal file
View File

@ -0,0 +1,77 @@
//! A library for generating the chain's genesis block.
use event::Event;
use transaction::Transaction;
use signature::{KeyPair, KeyPairUtil, PublicKey};
use entry::Entry;
use log::create_entries;
use hash::{hash, Hash};
use ring::rand::SystemRandom;
use untrusted::Input;
#[derive(Serialize, Deserialize, Debug)]
pub struct Mint {
pub pkcs8: Vec<u8>,
pubkey: PublicKey,
pub tokens: i64,
}
impl Mint {
pub fn new(tokens: i64) -> Self {
let rnd = SystemRandom::new();
let pkcs8 = KeyPair::generate_pkcs8(&rnd).unwrap().to_vec();
let keypair = KeyPair::from_pkcs8(Input::from(&pkcs8)).unwrap();
let pubkey = keypair.pubkey();
Mint {
pkcs8,
pubkey,
tokens,
}
}
pub fn seed(&self) -> Hash {
hash(&self.pkcs8)
}
pub fn keypair(&self) -> KeyPair {
KeyPair::from_pkcs8(Input::from(&self.pkcs8)).unwrap()
}
pub fn pubkey(&self) -> PublicKey {
self.pubkey
}
pub fn create_events(&self) -> Vec<Event> {
let keypair = self.keypair();
let tr = Transaction::new(&keypair, self.pubkey(), self.tokens, self.seed());
vec![Event::Tick, Event::Transaction(tr)]
}
pub fn create_entries(&self) -> Vec<Entry> {
create_entries(&self.seed(), self.create_events())
}
}
#[cfg(test)]
mod tests {
use super::*;
use log::verify_slice;
#[test]
fn test_create_events() {
let mut events = Mint::new(100).create_events().into_iter();
assert_eq!(events.next().unwrap(), Event::Tick);
if let Event::Transaction(tr) = events.next().unwrap() {
assert_eq!(tr.from, tr.to);
} else {
assert!(false);
}
assert_eq!(events.next(), None);
}
#[test]
fn test_verify_entries() {
let entries = Mint::new(100).create_entries();
assert!(verify_slice(&entries, &entries[0].id));
}
}

43
src/signature.rs Normal file
View File

@ -0,0 +1,43 @@
//! The `signature` crate provides functionality for public and private keys
use generic_array::GenericArray;
use generic_array::typenum::{U32, U64};
use ring::signature::Ed25519KeyPair;
use ring::{rand, signature};
use untrusted;
pub type KeyPair = Ed25519KeyPair;
pub type PublicKey = GenericArray<u8, U32>;
pub type Signature = GenericArray<u8, U64>;
pub trait KeyPairUtil {
fn new() -> Self;
fn pubkey(&self) -> PublicKey;
}
impl KeyPairUtil for Ed25519KeyPair {
/// Return a new ED25519 keypair
fn new() -> Self {
let rng = rand::SystemRandom::new();
let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&pkcs8_bytes)).unwrap()
}
/// Return the public key for the given keypair
fn pubkey(&self) -> PublicKey {
GenericArray::clone_from_slice(self.public_key_bytes())
}
}
pub trait SignatureUtil {
fn verify(&self, peer_public_key_bytes: &[u8], msg_bytes: &[u8]) -> bool;
}
impl SignatureUtil for GenericArray<u8, U64> {
fn verify(&self, peer_public_key_bytes: &[u8], msg_bytes: &[u8]) -> bool {
let peer_public_key = untrusted::Input::from(peer_public_key_bytes);
let msg = untrusted::Input::from(msg_bytes);
let sig = untrusted::Input::from(self);
signature::verify(&signature::ED25519, peer_public_key, msg, sig).is_ok()
}
}

148
src/transaction.rs Normal file
View File

@ -0,0 +1,148 @@
//! The `transaction` crate provides functionality for creating log transactions.
use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
use serde::Serialize;
use bincode::serialize;
use hash::Hash;
use chrono::prelude::*;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Condition {
Timestamp(DateTime<Utc>),
Signature(PublicKey),
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Transaction<T> {
pub from: PublicKey,
pub to: PublicKey,
pub if_all: Vec<Condition>,
pub unless_any: Vec<Condition>,
pub asset: T,
pub last_id: Hash,
pub sig: Signature,
}
impl<T: Serialize> Transaction<T> {
pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self {
let mut tr = Transaction {
from: from_keypair.pubkey(),
to,
if_all: vec![],
unless_any: vec![],
asset,
last_id,
sig: Signature::default(),
};
tr.sign(from_keypair);
tr
}
pub fn new_on_date(
from_keypair: &KeyPair,
to: PublicKey,
dt: DateTime<Utc>,
asset: T,
last_id: Hash,
) -> Self {
let from = from_keypair.pubkey();
let mut tr = Transaction {
from,
to,
if_all: vec![Condition::Timestamp(dt)],
unless_any: vec![Condition::Signature(from)],
asset,
last_id,
sig: Signature::default(),
};
tr.sign(from_keypair);
tr
}
fn get_sign_data(&self) -> Vec<u8> {
serialize(&(
&self.from,
&self.to,
&self.if_all,
&self.unless_any,
&self.asset,
&self.last_id,
)).unwrap()
}
pub fn sign(&mut self, keypair: &KeyPair) {
let sign_data = self.get_sign_data();
self.sig = Signature::clone_from_slice(keypair.sign(&sign_data).as_ref());
}
pub fn verify(&self) -> bool {
self.sig.verify(&self.from, &self.get_sign_data())
}
}
#[cfg(test)]
mod tests {
use super::*;
use bincode::{deserialize, serialize};
use hash::hash;
#[test]
fn test_claim() {
let keypair = KeyPair::new();
let asset = hash(b"hello, world");
let zero = Hash::default();
let tr0 = Transaction::new(&keypair, keypair.pubkey(), asset, zero);
assert!(tr0.verify());
}
#[test]
fn test_transfer() {
let zero = Hash::default();
let keypair0 = KeyPair::new();
let keypair1 = KeyPair::new();
let pubkey1 = keypair1.pubkey();
let asset = hash(b"hello, world");
let tr0 = Transaction::new(&keypair0, pubkey1, asset, zero);
assert!(tr0.verify());
}
#[test]
fn test_serialize_claim() {
let claim0 = Transaction {
from: Default::default(),
to: Default::default(),
if_all: Default::default(),
unless_any: Default::default(),
asset: 0u8,
last_id: Default::default(),
sig: Default::default(),
};
let buf = serialize(&claim0).unwrap();
let claim1: Transaction<u8> = deserialize(&buf).unwrap();
assert_eq!(claim1, claim0);
}
#[test]
fn test_bad_event_signature() {
let zero = Hash::default();
let keypair = KeyPair::new();
let pubkey = keypair.pubkey();
let mut tr = Transaction::new(&keypair, pubkey, hash(b"hello, world"), zero);
tr.sign(&keypair);
tr.asset = hash(b"goodbye cruel world"); // <-- attack!
assert!(!tr.verify());
}
#[test]
fn test_hijack_attack() {
let keypair0 = KeyPair::new();
let keypair1 = KeyPair::new();
let thief_keypair = KeyPair::new();
let pubkey1 = keypair1.pubkey();
let zero = Hash::default();
let mut tr = Transaction::new(&keypair0, pubkey1, hash(b"hello, world"), zero);
tr.sign(&keypair0);
tr.to = thief_keypair.pubkey(); // <-- attack!
assert!(!tr.verify());
}
}