├── .gitignore
├── .travis.yml
├── Makefile
├── README.md
├── conode
├── .gitignore
├── README.md
├── co1
│ ├── private.toml
│ └── public.toml
├── co2
│ ├── private.toml
│ └── public.toml
├── co3
│ ├── private.toml
│ └── public.toml
├── conode
├── conode.go
├── conode_test.go
├── run_conode.sh
└── test.sh
├── external
├── Makefile
├── java
│ ├── .gitignore
│ ├── pom.xml
│ └── src
│ │ └── main
│ │ └── java
│ │ └── ch
│ │ └── epfl
│ │ └── dedis
│ │ ├── lib
│ │ ├── HashId.java
│ │ ├── README.md
│ │ ├── Roster.java
│ │ ├── ServerIdentity.java
│ │ ├── Sha256id.java
│ │ ├── SkipblockId.java
│ │ ├── UUIDType5.java
│ │ ├── crypto
│ │ │ ├── Ed25519.java
│ │ │ ├── Encryption.java
│ │ │ ├── KeyPair.java
│ │ │ ├── Point.java
│ │ │ ├── Scalar.java
│ │ │ └── SchnorrSig.java
│ │ └── exception
│ │ │ ├── CothorityCommunicationException.java
│ │ │ ├── CothorityCryptoException.java
│ │ │ ├── CothorityException.java
│ │ │ ├── CothorityNotFoundException.java
│ │ │ └── CothorityPermissionException.java
│ │ └── proto
│ │ ├── DarcProto.java
│ │ ├── RosterProto.java
│ │ ├── ServerIdentityProto.java
│ │ ├── SkipBlockProto.java
│ │ ├── SkipchainProto.java
│ │ └── StatusProto.java
└── proto
│ ├── roster.proto
│ ├── server-identity.proto
│ ├── sicpa.proto
│ ├── skipblock.proto
│ ├── skipchain.proto
│ └── status.proto
└── omniledger
├── .gitignore
├── README.md
├── app.go
├── app_test.go
├── collection
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── TODO.md
├── assets
│ └── images
│ │ ├── collection.gif
│ │ ├── example
│ │ ├── 0.gif
│ │ ├── 1.gif
│ │ ├── 10.gif
│ │ ├── 11.gif
│ │ ├── 12.gif
│ │ ├── 13.gif
│ │ ├── 14.gif
│ │ ├── 15.gif
│ │ ├── 16.gif
│ │ ├── 17.gif
│ │ ├── 18.gif
│ │ ├── 19.gif
│ │ ├── 2.gif
│ │ ├── 20.gif
│ │ ├── 21.gif
│ │ ├── 22.gif
│ │ ├── 23.gif
│ │ ├── 3.gif
│ │ ├── 4.gif
│ │ ├── 5.gif
│ │ ├── 6.gif
│ │ ├── 7.gif
│ │ ├── 8.gif
│ │ └── 9.gif
│ │ ├── naivetree.png
│ │ ├── navigation.gif
│ │ ├── nonexistingnavigation.gif
│ │ ├── tree.png
│ │ ├── unknown.png
│ │ └── unknownroot.png
├── byteslice.go
├── byteslice_test.go
├── codecov.yml
├── collection.go
├── collection_test.go
├── example.md
├── field.go
├── field_test.go
├── flag.go
├── flag_test.go
├── getters.go
├── getters_test.go
├── manipulators.go
├── manipulators_test.go
├── navigators.go
├── navigators_test.go
├── node.go
├── node_test.go
├── proof.go
├── proof_test.go
├── record.go
├── record_test.go
├── scope.go
├── scope_test.go
├── singlenode.go
├── singlenode_test.go
├── testctx.go
├── transaction.go
├── transaction_test.go
├── update.go
├── update_test.go
├── verifiers.go
└── verifiers_test.go
├── darc
├── darc.go
├── darc_example_test.go
├── darc_test.go
├── expression
│ ├── expression.go
│ └── expression_test.go
└── struct.go
├── service
├── api.go
├── api_test.go
├── contracts.go
├── doc.go
├── messages.go
├── proof.go
├── proof_test.go
├── proto.awk
├── service.go
├── service_test.go
├── struct.go
├── struct_test.go
├── transaction.go
└── transaction_test.go
└── test.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .tags*
2 | *~
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | install:
4 | - go get -t ./...
5 | - go get github.com/dedis/Coding || true
6 |
7 | before_install:
8 | - cd $TRAVIS_BUILD_DIR
9 |
10 | script:
11 | - make test_fmt test_lint test_goveralls
12 | - cd external/java
13 | - mvn test
14 |
15 | notifications:
16 | email: false
17 |
18 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: test_fmt test_lint test_local
2 |
3 | # gopkg fits all v1.1, v1.2, ... in v1
4 | gopath=$(shell go env GOPATH)
5 | include $(gopath)/src/github.com/dedis/Coding/bin/Makefile.base
6 |
7 | # You can use `test_playground` to run any test or part of cothority
8 | # for more than once in Travis. Change `make test` in .travis.yml
9 | # to `make test_playground`.
10 | test_playground:
11 | cd skipchain; \
12 | for a in $$( seq 100 ); do \
13 | go test -v -race -short || exit 1 ; \
14 | done;
15 |
16 | proto:
17 | awk -f proto.awk struct.go > external/proto/sicpa.proto
18 | cd external; make
19 |
20 | test_simple: test_fmt test_lint test_goveralls
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ByzCoin - previously OmniLedger
2 |
3 | This repo is the development of the omniledger project, that aims to bring
4 | skipchains to a new level of performance and functionality. Broadly speaking,
5 | OmniLedger will implement:
6 |
7 | 1. multiple transactions per block
8 | 2. queuing of transactions at each node and periodical creation of a new
9 | block by the leader
10 | 3. allow for verification functions that apply to different kinds of data
11 | 4. view-change in case the leader fails
12 | 5. sharding of the nodes
13 | 6. inter-shard transactions
14 |
15 | 1-4 are issues that should've been included in skipchains for a long time, but
16 | never got in. Only 5-6 are 'real' omniledger improvements as described in the
17 | [OmniLedger Paper](https://eprint.iacr.org/2017/406.pdf).
18 |
19 | # Implementation
20 |
21 | ## Schedule
22 |
23 | We will follow the above list to implement the different parts of OmniLedger.
24 | Once we reach point 3 or 4, we'll start porting services over to the new
25 | omniledger blockchain. As we still want to keep downwards-compatibility, we
26 | probably will need to create new services.
27 |
28 | Work on 1. is finished, work on 2. has been started.
29 |
30 | To find the current state of omniledger, use the [README](omniledger/README.md).
31 |
32 | ## Sub-tasks
33 |
34 | For 2. to work, we go in steps:
35 | - implement queueing at the leader
36 | - implement queues at the followers
37 | - leader regularly asks followers for new transactions
38 |
39 | In addition to this, the ByzCoinX protocol needs to be improved.
40 |
41 | ### Queueing at the leader
42 |
43 | Whenever a leader gets a new transaction request, he puts it in a queue and
44 | waits for further transactions to come in. After a timeout, the leader collects
45 | all transactions and proposes them in a new block to the followers who will sign
46 | the new block by creating a forward-link.
47 |
48 | ### Queueing at the followers
49 |
50 | The followers hold a queue
51 |
52 | ### ByzCoinX
53 |
54 | This protocol handles the consensus algorithm of OmniLedger and is described
55 | in the paper. One thing that is missing in the paper is possible improvements
56 | to make the protocol more usable in a real-world environment:
57 | - sub-leaders propagate the commit once a threshold of leafs replied
58 | - the leader can accept to go on if there are enough commits from the subleaders
59 | to reach 2/3 consensus with a high probability
60 |
61 | ## People
62 |
63 | This effort is lead by the following people:
64 | - Semester students:
65 | - Pablo Lorenceau - working on multpile transactions per block
66 | - Raphael Dunant - improving ByzCoinX
67 | - Engineers:
68 | - Linus Gasser - documentation and communication
69 | - Kelong Cong - working on Darc
70 | - Jeff Allen - testing and code review
71 | - PhD students:
72 | - Eleftherios Kokoris - making sure we don't do stupid things
73 |
--------------------------------------------------------------------------------
/conode/.gitignore:
--------------------------------------------------------------------------------
1 | public.toml
--------------------------------------------------------------------------------
/conode/README.md:
--------------------------------------------------------------------------------
1 | # Conode
2 |
3 | This package implements the cothority server. Conodes are linked together to form cothorities, run decentralized protocols, and offer services to clients.
4 |
5 | ## Getting Started
6 |
7 | To use the code of this package you need to:
8 |
9 | - Install [Golang](https://golang.org/doc/install)
10 | - Optional: Set [`$GOPATH`](https://golang.org/doc/code.html#GOPATH) to point to your Go workspace directory
11 | - Add `$(go env GOPATH)/bin` to `$PATH`
12 |
13 | ## run_conode.sh
14 |
15 | The simplest way of using the conode is through the `run_conode.sh`-script. You can
16 | run it either in local-mode for local testing, or in public-mode on a server
17 | with a public IP-address.
18 |
19 | ### local mode
20 |
21 | When running in local mode, `run_conode.sh` will create one directory for each
22 | node you ask it to run. It is best if you create a new directory and run
23 | the script from there:
24 |
25 | ```bash
26 | cd ~
27 | mkdir myconodes
28 | $(go env GOPATH)/src/github.com/dedis/cothority_template/conode/run_conode.sh local 3
29 | ```
30 |
31 | This will create three nodes and configure them with default values, then run
32 | them in background. To check if they're running correctly, use:
33 |
34 | ```bash
35 | $(go env GOPATH)/src/github.com/dedis/cothority_template/conode/run_conode.sh check
36 | ```
37 |
38 | If you need some debugging information, you can add another argument to print
39 | few (1), reasonable (3) or lots (5) information:
40 |
41 | ```bash
42 | $(go env GOPATH)/src/github.com/dedis/cothority_template/conode/run_conode.sh local 3 3
43 | ```
44 |
45 | The file `public.toml` contains the definition of all nodes that are being run.
46 |
47 | ### public mode
48 |
49 | If you have a public server and want to run a node on it, simply use:
50 |
51 | ```bash
52 | $(go env GOPATH)/src/github.com/dedis/cothority_template/conode/run_conode.sh public
53 | ```
54 |
55 | The first time this runs it will ask you a couple of questions and verify if
56 | the node is available from the internet. If you plan to run a node for a long
57 | time, be sure to contact us at dedis@epfl.ch!
--------------------------------------------------------------------------------
/conode/co1/private.toml:
--------------------------------------------------------------------------------
1 | # This file contains your private key.
2 | # Do not give it away lightly!
3 | Suite = "Ed25519"
4 | Public = "a863cf64422ab15f405369134cd057f99e2b40cb45afe7848dde11f34853f708"
5 | Private = "4ac8a032ef58cb0c74e5bb5366a01108e8e2af9934d9d5962b36c3197604790c"
6 | Address = "tcp://localhost:15002"
7 | Description = "Conode_1"
8 |
--------------------------------------------------------------------------------
/conode/co1/public.toml:
--------------------------------------------------------------------------------
1 | [[servers]]
2 | Address = "tcp://localhost:15002"
3 | Suite = "Ed25519"
4 | Public = "a863cf64422ab15f405369134cd057f99e2b40cb45afe7848dde11f34853f708"
5 | Description = "Conode_1"
6 |
--------------------------------------------------------------------------------
/conode/co2/private.toml:
--------------------------------------------------------------------------------
1 | # This file contains your private key.
2 | # Do not give it away lightly!
3 | Suite = "Ed25519"
4 | Public = "4706d99de05a58179ccc11ea3c452d9e44b43290de696f83f0fbc8ae26b6679a"
5 | Private = "c256784c3a5e35498e6699f86d49facaf0c3cb9e312746dfcdfb6b15081e9100"
6 | Address = "tcp://localhost:15004"
7 | Description = "Conode_2"
8 |
--------------------------------------------------------------------------------
/conode/co2/public.toml:
--------------------------------------------------------------------------------
1 | [[servers]]
2 | Address = "tcp://localhost:15004"
3 | Suite = "Ed25519"
4 | Public = "4706d99de05a58179ccc11ea3c452d9e44b43290de696f83f0fbc8ae26b6679a"
5 | Description = "Conode_2"
6 |
--------------------------------------------------------------------------------
/conode/co3/private.toml:
--------------------------------------------------------------------------------
1 | # This file contains your private key.
2 | # Do not give it away lightly!
3 | Suite = "Ed25519"
4 | Public = "4c4d5dd6fa750d5fb32f005b0a357a39d3886454d9fe63255a89ef0542f835d9"
5 | Private = "5c816924b82df5635292ca200888a6c0fe6f0be03356d2111cc9740834b33503"
6 | Address = "tcp://localhost:15006"
7 | Description = "Conode_3"
8 |
--------------------------------------------------------------------------------
/conode/co3/public.toml:
--------------------------------------------------------------------------------
1 | [[servers]]
2 | Address = "tcp://localhost:15006"
3 | Suite = "Ed25519"
4 | Public = "4c4d5dd6fa750d5fb32f005b0a357a39d3886454d9fe63255a89ef0542f835d9"
5 | Description = "Conode_3"
6 |
--------------------------------------------------------------------------------
/conode/conode:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/conode/conode
--------------------------------------------------------------------------------
/conode/conode.go:
--------------------------------------------------------------------------------
1 | // Conode is the main binary for running a Cothority server.
2 | // A conode can participate in various distributed protocols using the
3 | // *onet* library as a network and overlay library and the *dedis/crypto*
4 | // library for all cryptographic primitives.
5 | // Basically, you first need to setup a config file for the server by using:
6 | //
7 | // ./conode setup
8 | //
9 | // Then you can launch the daemon with:
10 | //
11 | // ./conode
12 | //
13 | package main
14 |
15 | import (
16 | "os"
17 | "path"
18 |
19 | "gopkg.in/dedis/onet.v2/app"
20 | "gopkg.in/dedis/onet.v2/cfgpath"
21 | "gopkg.in/dedis/onet.v2/log"
22 | cli "gopkg.in/urfave/cli.v1"
23 |
24 | "gopkg.in/dedis/cothority.v2"
25 | // Import your service:
26 | _ "github.com/dedis/student_18_omniledger/omniledger/service"
27 | // Here you can import any other needed service for your conode.
28 | // For example, if your service needs cosi available in the server
29 | // as well, uncomment this:
30 | _ "gopkg.in/dedis/cothority.v2/skipchain"
31 | _ "gopkg.in/dedis/cothority.v2/status/service"
32 | )
33 |
34 | func main() {
35 | cliApp := cli.NewApp()
36 | cliApp.Name = "omniledger"
37 | cliApp.Usage = "basic file for an app"
38 | cliApp.Version = "0.1"
39 |
40 | cliApp.Commands = []cli.Command{
41 | {
42 | Name: "setup",
43 | Aliases: []string{"s"},
44 | Usage: "Setup server configuration (interactive)",
45 | Action: func(c *cli.Context) error {
46 | if c.String("config") != "" {
47 | log.Fatal("[-] Configuration file option cannot be used for the 'setup' command")
48 | }
49 | if c.String("debug") != "" {
50 | log.Fatal("[-] Debug option cannot be used for the 'setup' command")
51 | }
52 | app.InteractiveConfig(cothority.Suite, "omniledger")
53 | return nil
54 | },
55 | },
56 | {
57 | Name: "server",
58 | Usage: "Start cothority server",
59 | Action: func(c *cli.Context) {
60 | runServer(c)
61 | },
62 | },
63 | }
64 | cliApp.Flags = []cli.Flag{
65 | cli.IntFlag{
66 | Name: "debug, d",
67 | Value: 0,
68 | Usage: "debug-level: 1 for terse, 5 for maximal",
69 | },
70 | cli.StringFlag{
71 | Name: "config, c",
72 | Value: path.Join(cfgpath.GetConfigPath("omniledger"), "config.bin"),
73 | Usage: "Configuration file of the server",
74 | },
75 | }
76 | cliApp.Before = func(c *cli.Context) error {
77 | log.SetDebugVisible(c.Int("debug"))
78 | return nil
79 | }
80 |
81 | log.ErrFatal(cliApp.Run(os.Args))
82 | }
83 |
84 | func runServer(c *cli.Context) error {
85 | app.RunServer(c.GlobalString("config"))
86 | return nil
87 | }
88 |
--------------------------------------------------------------------------------
/conode/conode_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 |
6 | "os"
7 |
8 | "gopkg.in/dedis/onet.v2/log"
9 | )
10 |
11 | func TestMain(m *testing.M) {
12 | log.MainTest(m)
13 | }
14 |
15 | func TestRun(t *testing.T) {
16 | os.Args = []string{os.Args[0], "--help"}
17 | main()
18 | }
19 |
--------------------------------------------------------------------------------
/conode/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | DBG_TEST=1
4 | DBG_APP=2
5 |
6 | . $(go env GOPATH)/src/github.com/dedis/onet/app/libtest.sh
7 |
8 | main(){
9 | startTest
10 | setupConode
11 | test Build
12 | stopTest
13 | }
14 |
15 | testBuild(){
16 | cp co1/public.toml .
17 | testOK dbgRun runCo 1 --help
18 | }
19 |
20 | main
21 |
--------------------------------------------------------------------------------
/external/Makefile:
--------------------------------------------------------------------------------
1 | all: java
2 |
3 | java: proto/*
4 | protoc -I=proto --java_out=java/src/main/java proto/*proto
5 |
--------------------------------------------------------------------------------
/external/java/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 | target/
--------------------------------------------------------------------------------
/external/java/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | cothority
8 | cothority
9 | 0.1.
10 |
11 |
12 |
13 | org.apache.maven.plugins
14 | maven-compiler-plugin
15 | 3.7.0
16 |
17 | 1.8
18 | 1.8
19 |
20 |
21 |
22 | org.apache.maven.plugins
23 | maven-source-plugin
24 | 3.0.1
25 |
26 |
27 | attach-sources
28 |
29 | jar
30 |
31 |
32 |
33 |
34 |
35 | maven-surefire-plugin
36 | 2.19
37 |
38 |
39 | org.junit.platform
40 | junit-platform-surefire-provider
41 | 1.0.1
42 |
43 |
44 | org.junit.jupiter
45 | junit-jupiter-engine
46 | 5.0.1
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | com.google.protobuf
56 | protobuf-java
57 | 3.4.0
58 |
59 |
60 | net.i2p.crypto
61 | eddsa
62 | 0.2.0
63 |
64 |
65 | org.java-websocket
66 | Java-WebSocket
67 | 1.3.4
68 |
69 |
70 | org.slf4j
71 | slf4j-api
72 | 1.7.25
73 |
74 |
75 | com.moandjiezana.toml
76 | toml4j
77 | 0.7.2
78 |
79 |
80 | net.i2p.crypto
81 | eddsa
82 | 0.2.0
83 |
84 |
85 | javax.xml.bind
86 | jaxb-api
87 | 2.3.0
88 |
89 |
90 |
93 |
94 | org.slf4j
95 | slf4j-simple
96 | 1.7.25
97 | test
98 |
99 |
100 | org.junit.jupiter
101 | junit-jupiter-api
102 | 5.0.1
103 | test
104 |
105 |
106 | org.junit.jupiter
107 | junit-jupiter-params
108 | 5.0.1
109 | test
110 |
111 |
112 |
113 | com.google.code.findbugs
114 | jsr305
115 | 3.0.2
116 | provided
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/HashId.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib;
2 |
3 |
4 | public interface HashId {
5 | /**
6 | * Return binary form of block getId used by skipchain
7 | *
8 | * @return binary form of block getId
9 | */
10 | byte[] getId();
11 | }
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/README.md:
--------------------------------------------------------------------------------
1 | # dedis-java-library
2 |
3 | These are a couple of libraries that are to be used with different java-
4 | implementations. They will probably migrate sooner or later to
5 | github.com/dedis/cothority.
6 |
7 | ## ServerIdentity
8 |
9 | The node-definition for a node in a cothority. It contains the IP-address
10 | and a description.
11 |
12 | ## Roster
13 |
14 | A list of ServerIdentities make up a roster that can be used as a temporary
15 | cothority.
16 |
17 | ## UUIDType5
18 |
19 | I couldn't find a uuid-type5 library, so I copied this one from the web.
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/Roster.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib;
2 |
3 | import ch.epfl.dedis.lib.crypto.Ed25519;
4 | import ch.epfl.dedis.lib.crypto.Point;
5 | import ch.epfl.dedis.lib.exception.CothorityCommunicationException;
6 | import ch.epfl.dedis.proto.RosterProto;
7 | import com.google.protobuf.ByteString;
8 | import com.moandjiezana.toml.Toml;
9 |
10 | import java.net.URISyntaxException;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * dedis/lib
16 | * Roster.java
17 | * Purpose: A list of ServerIdentities make up a roster that can be used as a temporary
18 | * cothority.
19 | */
20 |
21 | public class Roster {
22 | private List nodes = new ArrayList<>();
23 | private Point aggregate; // TODO: can we find better name for it? like aggregatePublicKey or aggregatedKey?
24 |
25 | public Roster(List servers) {
26 | nodes.addAll(servers);
27 |
28 | for (final ServerIdentity serverIdentity : nodes) {
29 | if (aggregate == null) {
30 | // TODO: it will be much better if there is some kind of 'zero' element for Point type. Is it possible to use just a new created Point
31 | aggregate = serverIdentity.Public;
32 | } else {
33 | aggregate = aggregate.add(serverIdentity.Public);
34 | }
35 | }
36 | }
37 |
38 | public List getNodes() {
39 | return nodes;
40 | }
41 |
42 | public RosterProto.Roster getProto() {
43 | RosterProto.Roster.Builder r = RosterProto.Roster.newBuilder();
44 | r.setId(ByteString.copyFrom(Ed25519.uuid4()));
45 | nodes.forEach(n -> r.addList(n.getProto()));
46 | r.setAggregate(aggregate.toProto());
47 |
48 | return r.build();
49 | }
50 |
51 | public ByteString sendMessage(String path, com.google.protobuf.GeneratedMessageV3 proto) throws CothorityCommunicationException {
52 | // TODO - fetch a random node.
53 | return ByteString.copyFrom(nodes.get(0).SendMessage(path, proto.toByteArray()));
54 | }
55 |
56 | public static Roster FromToml(String groupToml) {
57 | Toml toml = new Toml().read(groupToml);
58 | List cothority = new ArrayList<>();
59 | List servers = toml.getTables("servers");
60 |
61 | for (Toml s : servers) {
62 | try {
63 | cothority.add(new ServerIdentity(s));
64 | } catch (URISyntaxException e) {
65 | }
66 | }
67 | return new Roster(cothority);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/ServerIdentity.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib;
2 |
3 | import ch.epfl.dedis.lib.crypto.Point;
4 | import ch.epfl.dedis.lib.exception.CothorityCommunicationException;
5 | import ch.epfl.dedis.proto.ServerIdentityProto;
6 | import ch.epfl.dedis.proto.StatusProto;
7 | import com.google.protobuf.ByteString;
8 | import com.google.protobuf.InvalidProtocolBufferException;
9 | import com.moandjiezana.toml.Toml;
10 | import org.java_websocket.client.WebSocketClient;
11 | import org.java_websocket.handshake.ServerHandshake;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 |
15 | import javax.xml.bind.DatatypeConverter;
16 | import java.net.URI;
17 | import java.net.URISyntaxException;
18 | import java.nio.ByteBuffer;
19 | import java.util.concurrent.CountDownLatch;
20 |
21 | /**
22 | * dedis/lib
23 | * ServerIdentity.java
24 | * Purpose: The node-definition for a node in a cothority. It contains the IP-address
25 | * and a description.
26 | */
27 |
28 | public class ServerIdentity {
29 | private final URI conodeAddress;
30 | public Point Public;
31 | private final Logger logger = LoggerFactory.getLogger(ServerIdentity.class);
32 |
33 | public ServerIdentity(final URI serverWsAddress, final String publicKey) {
34 | this.conodeAddress = serverWsAddress;
35 | // TODO: It will be better to use some class for server key and move this conversion outside of this class
36 | this.Public = new Point(DatatypeConverter.parseHexBinary(publicKey));
37 | }
38 |
39 | public ServerIdentity(Toml siToml) throws URISyntaxException {
40 | this(new URI(siToml.getString("Address")), siToml.getString("Public"));
41 | }
42 |
43 | public URI getAddress() {
44 | return conodeAddress;
45 | }
46 |
47 | public StatusProto.Response GetStatus() throws CothorityCommunicationException {
48 | StatusProto.Request request =
49 | StatusProto.Request.newBuilder().build();
50 | try {
51 | SyncSendMessage msg = new SyncSendMessage("Status/Request", request.toByteArray());
52 | return StatusProto.Response.parseFrom(msg.response);
53 | } catch (InvalidProtocolBufferException e) {
54 | throw new CothorityCommunicationException(e.toString());
55 | }
56 | }
57 |
58 | public ServerIdentityProto.ServerIdentity getProto() {
59 | ServerIdentityProto.ServerIdentity.Builder si =
60 | ServerIdentityProto.ServerIdentity.newBuilder();
61 | si.setPublic(Public.toProto());
62 | String pubStr = "https://dedis.epfl.ch/id/" + Public.toString().toLowerCase();
63 | byte[] id = UUIDType5.toBytes(UUIDType5.nameUUIDFromNamespaceAndString(UUIDType5.NAMESPACE_URL, pubStr));
64 | si.setId(ByteString.copyFrom(id));
65 | si.setAddress(getAddress().toString());
66 | si.setDescription("");
67 | return si.build();
68 | }
69 |
70 | public byte[] SendMessage(String path, byte[] data) throws CothorityCommunicationException {
71 | return new SyncSendMessage(path, data).response.array();
72 | }
73 |
74 | public class SyncSendMessage {
75 | public ByteBuffer response;
76 | public String error;
77 |
78 | public SyncSendMessage(String path, byte[] msg) throws CothorityCommunicationException {
79 | final CountDownLatch statusLatch = new CountDownLatch(1);
80 | try {
81 | WebSocketClient ws = new WebSocketClient(buildWebSocketAdddress(path)) {
82 | @Override
83 | public void onMessage(String msg) {
84 | error = "This should never happen:" + msg;
85 | statusLatch.countDown();
86 | }
87 |
88 | @Override
89 | public void onMessage(ByteBuffer message) {
90 | response = message;
91 | close();
92 | statusLatch.countDown();
93 | }
94 |
95 | @Override
96 | public void onOpen(ServerHandshake handshake) {
97 | this.send(msg);
98 | }
99 |
100 | @Override
101 | public void onClose(int code, String reason, boolean remote) {
102 | if (reason != "") {
103 | error = reason;
104 | }
105 | statusLatch.countDown();
106 | }
107 |
108 | @Override
109 | public void onError(Exception ex) {
110 | error = "Error: " + ex.toString();
111 | statusLatch.countDown();
112 | }
113 | };
114 |
115 | // open websocket and send message.
116 | ws.connect();
117 | // wait for error or message returned.
118 | statusLatch.await();
119 | } catch (InterruptedException e) {
120 | throw new CothorityCommunicationException(e.toString());
121 | } catch (URISyntaxException e) {
122 | throw new CothorityCommunicationException(e.toString());
123 | }
124 | if (error != null) {
125 | throw new CothorityCommunicationException(error);
126 | }
127 | }
128 |
129 | private URI buildWebSocketAdddress(final String servicePath) throws URISyntaxException {
130 | return new URI("ws",
131 | conodeAddress.getUserInfo(),
132 | conodeAddress.getHost(),
133 | conodeAddress.getPort() + 1, // client operation use higher port number
134 | servicePath.startsWith("/") ? servicePath : "/".concat(servicePath),
135 | conodeAddress.getQuery(),
136 | conodeAddress.getFragment());
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/Sha256id.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib;
2 |
3 | import ch.epfl.dedis.lib.exception.CothorityCryptoException;
4 | import com.google.protobuf.ByteString;
5 |
6 | import javax.annotation.Nonnull;
7 | import javax.xml.bind.DatatypeConverter;
8 | import java.nio.ByteBuffer;
9 | import java.util.Arrays;
10 |
11 | /**
12 | * Implementation of {@link HashId}. This implementation is immutable and is can be used as key for collections
13 | */
14 | public class Sha256id implements HashId {
15 | private final byte[] id;
16 | public final static int length = 32;
17 |
18 | public Sha256id(byte[] id) throws CothorityCryptoException {
19 | if (id.length != length) {
20 | throw new CothorityCryptoException("need 32 bytes for sha256-hash");
21 | }
22 | this.id = Arrays.copyOf(id, id.length);
23 | }
24 |
25 | @Override
26 | @Nonnull
27 | public byte[] getId() {
28 | return Arrays.copyOf(id, id.length);
29 | }
30 |
31 | @Override
32 | public boolean equals(Object o) {
33 | if (this == o) return true;
34 | if (o == null || getClass() != o.getClass()) return false;
35 |
36 | return Arrays.equals(id, ((Sha256id) o).id);
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | return Arrays.hashCode(id);
42 | }
43 |
44 | @Override
45 | public String toString(){
46 |
47 | return DatatypeConverter.printHexBinary(id);
48 | }
49 |
50 | public ByteString toBS(){
51 | return ByteString.copyFrom(id);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/SkipblockId.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib;
2 |
3 | import ch.epfl.dedis.lib.exception.CothorityCryptoException;
4 |
5 | /**
6 | * This class represents a SkipblockId, which is a sha256-hash of
7 | * the static fields of the skipblock.
8 | */
9 | public class SkipblockId extends Sha256id {
10 | public SkipblockId(byte[] id) throws CothorityCryptoException {
11 | super(id);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/UUIDType5.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib;
2 |
3 | import java.nio.charset.Charset;
4 | import java.security.MessageDigest;
5 | import java.security.NoSuchAlgorithmException;
6 | import java.util.Objects;
7 | import java.util.UUID;
8 |
9 | /**
10 | * dedis/lib
11 | * UUIDType5.java
12 | * Purpose: I couldn't find a uuid-type5 library, so I copied this one from the web.
13 | *
14 | * @author Somebody on StackOverflow
15 | * @version 0.2 17/09/19
16 | */
17 |
18 | public class UUIDType5 {
19 | private static final Charset UTF8 = Charset.forName("UTF-8");
20 | public static final UUID NAMESPACE_DNS = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
21 | public static final UUID NAMESPACE_URL = UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
22 | public static final UUID NAMESPACE_OID = UUID.fromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8");
23 | public static final UUID NAMESPACE_X500 = UUID.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8");
24 |
25 | public static UUID nameUUIDFromNamespaceAndString(UUID namespace, String name) {
26 | return nameUUIDFromNamespaceAndBytes(namespace, Objects.requireNonNull(name, "name == null").getBytes(UTF8));
27 | }
28 |
29 | public static UUID nameUUIDFromNamespaceAndBytes(UUID namespace, byte[] name) {
30 | MessageDigest md;
31 | try {
32 | md = MessageDigest.getInstance("SHA-1");
33 | } catch (NoSuchAlgorithmException nsae) {
34 | throw new InternalError("SHA-1 not supported");
35 | }
36 | md.update(toBytes(Objects.requireNonNull(namespace, "namespace is null")));
37 | md.update(Objects.requireNonNull(name, "name is null"));
38 | byte[] sha1Bytes = md.digest();
39 | sha1Bytes[6] &= 0x0f; /* clear version */
40 | sha1Bytes[6] |= 0x50; /* set to version 5 */
41 | sha1Bytes[8] &= 0x3f; /* clear variant */
42 | sha1Bytes[8] |= 0x80; /* set to IETF variant */
43 | return fromBytes(sha1Bytes);
44 | }
45 |
46 | public static UUID fromBytes(byte[] data) {
47 | // Based on the private UUID(bytes[]) constructor
48 | long msb = 0;
49 | long lsb = 0;
50 | assert data.length >= 16;
51 | for (int i = 0; i < 8; i++)
52 | msb = (msb << 8) | (data[i] & 0xff);
53 | for (int i = 8; i < 16; i++)
54 | lsb = (lsb << 8) | (data[i] & 0xff);
55 | return new UUID(msb, lsb);
56 | }
57 |
58 | public static byte[] toBytes(UUID uuid) {
59 | // inverted logic of fromBytes()
60 | byte[] out = new byte[16];
61 | long msb = uuid.getMostSignificantBits();
62 | long lsb = uuid.getLeastSignificantBits();
63 | for (int i = 0; i < 8; i++)
64 | out[i] = (byte) ((msb >> ((7 - i) * 8)) & 0xff);
65 | for (int i = 8; i < 16; i++)
66 | out[i] = (byte) ((lsb >> ((15 - i) * 8)) & 0xff);
67 | return out;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/crypto/Ed25519.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.crypto;
2 |
3 | import net.i2p.crypto.eddsa.math.Curve;
4 | import net.i2p.crypto.eddsa.math.Field;
5 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
6 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import java.nio.ByteBuffer;
11 | import java.util.UUID;
12 |
13 | /**
14 | * dedis/lib
15 | * Ed25519.java
16 | * Purpose: Getting the warm cozy feeling of having the power to add, subtract,
17 | * scalar multiply and do other fancy things with points and scalars.
18 | */
19 | public class Ed25519 {
20 | private final static Logger logger = LoggerFactory.getLogger(Ed25519.class);
21 |
22 | public static final int pubLen = 30;
23 | public static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
24 | public static Curve curve = ed25519.getCurve();
25 | public static Field field = curve.getField();
26 | public static Point base = new Point(Ed25519.ed25519.getB());
27 | public static Scalar prime_order = new Scalar("EDD3F55C1A631258D69CF7A2DEF9DE1400000000000000000000000000000010", false);
28 |
29 | public static byte[] uuid4() {
30 | ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
31 | UUID uuid = UUID.randomUUID();
32 | bb.putLong(uuid.getMostSignificantBits());
33 | bb.putLong(uuid.getLeastSignificantBits());
34 | return bb.array();
35 | }
36 |
37 | public static byte[] reverse(byte[] little_endian) {
38 | byte[] big_endian = new byte[32];
39 | for (int i = 0; i < 32; i++) {
40 | big_endian[i] = little_endian[31 - i];
41 | }
42 | return big_endian;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/crypto/Encryption.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.crypto;
2 |
3 | import ch.epfl.dedis.lib.exception.CothorityCryptoException;
4 | import com.google.protobuf.ByteString;
5 |
6 | import javax.crypto.BadPaddingException;
7 | import javax.crypto.Cipher;
8 | import javax.crypto.IllegalBlockSizeException;
9 | import javax.crypto.NoSuchPaddingException;
10 | import javax.crypto.spec.IvParameterSpec;
11 | import javax.crypto.spec.SecretKeySpec;
12 | import java.security.InvalidAlgorithmParameterException;
13 | import java.security.InvalidKeyException;
14 | import java.security.NoSuchAlgorithmException;
15 | import java.security.SecureRandom;
16 |
17 | public class Encryption {
18 | public static String algo = "AES/CBC/PKCS5Padding";
19 | public static String algoKey = "AES";
20 | public static int ivLength = 16;
21 |
22 | public static class keyIv{
23 | public byte[] symmetricKey;
24 | public byte[] iv;
25 | public IvParameterSpec ivSpec;
26 | public SecretKeySpec keySpec;
27 |
28 | public keyIv(byte[] keyMaterial) throws CothorityCryptoException {
29 | int symmetricLength = keyMaterial.length - ivLength;
30 | if (symmetricLength <= 0){
31 | throw new CothorityCryptoException("too short symmetricKey material");
32 | }
33 | iv = new byte[ivLength];
34 | System.arraycopy(keyMaterial, 0, iv, 0, ivLength);
35 | ivSpec = new IvParameterSpec(iv);
36 | symmetricKey = new byte[keyMaterial.length - ivLength];
37 | keySpec = new SecretKeySpec(symmetricKey, algoKey);
38 | }
39 |
40 | public keyIv(int keylength){
41 | symmetricKey = new byte[keylength];
42 | iv = new byte[ivLength];
43 | new SecureRandom().nextBytes(symmetricKey);
44 | new SecureRandom().nextBytes(iv);
45 | ivSpec = new IvParameterSpec(iv);
46 | keySpec = new SecretKeySpec(symmetricKey, algoKey);
47 | }
48 |
49 | public byte[] getKeyMaterial(){
50 | byte[] keyMaterial = new byte[ivLength + symmetricKey.length];
51 | System.arraycopy(iv, 0, keyMaterial, 0, ivLength);
52 | System.arraycopy(symmetricKey, 0, keyMaterial, ivLength, symmetricKey.length);
53 | return keyMaterial;
54 | }
55 | }
56 |
57 | /**
58 | * Encrypts the data using the encryption defined in the header.
59 | * @param data the data to encrypt
60 | * @param keyMaterial random string of length ivLength + keylength.
61 | * The first ivLength bytes are taken as iv, the
62 | * rest is taken as the symmetric symmetricKey.
63 | * @return a combined
64 | * @throws Exception
65 | */
66 | public static byte[] encryptData(byte[] data, byte[] keyMaterial) throws CothorityCryptoException {
67 | keyIv key = new keyIv(keyMaterial);
68 |
69 | try {
70 | Cipher cipher = Cipher.getInstance(Encryption.algo);
71 | SecretKeySpec secKey = new SecretKeySpec(key.symmetricKey, Encryption.algoKey);
72 | cipher.init(Cipher.ENCRYPT_MODE, secKey, key.ivSpec);
73 | return cipher.doFinal(data);
74 | } catch (NoSuchAlgorithmException e){
75 | throw new CothorityCryptoException(e.getMessage());
76 | } catch (NoSuchPaddingException e){
77 | throw new CothorityCryptoException(e.getMessage());
78 | } catch (InvalidKeyException e){
79 | throw new CothorityCryptoException(e.getMessage());
80 | } catch (InvalidAlgorithmParameterException e){
81 | throw new CothorityCryptoException(e.getMessage());
82 | } catch (IllegalBlockSizeException e){
83 | throw new CothorityCryptoException(e.getMessage());
84 | } catch (BadPaddingException e){
85 | throw new CothorityCryptoException(e.getMessage());
86 | }
87 | }
88 |
89 | /**
90 | * This method decrypts the data using the same encryption-method
91 | * as is defined in the header of this class.
92 | *
93 | * @param dataEnc the encrypted data from the skipchain
94 | * @param keyMaterial the decrypted keyMaterial
95 | * @return decrypted data
96 | * @throws Exception
97 | */
98 | public static byte[] decryptData(byte[] dataEnc, byte[] keyMaterial) throws CothorityCryptoException {
99 | keyIv key = new keyIv(keyMaterial);
100 | try {
101 | Cipher cipher = Cipher.getInstance(algo);
102 | cipher.init(Cipher.DECRYPT_MODE, key.keySpec, key.ivSpec);
103 | return cipher.doFinal(dataEnc);
104 | } catch (NoSuchAlgorithmException e){
105 | throw new CothorityCryptoException(e.getMessage());
106 | } catch (NoSuchPaddingException e){
107 | throw new CothorityCryptoException(e.getMessage());
108 | } catch (InvalidKeyException e){
109 | throw new CothorityCryptoException(e.getMessage());
110 | } catch (InvalidAlgorithmParameterException e){
111 | throw new CothorityCryptoException(e.getMessage());
112 | } catch (IllegalBlockSizeException e){
113 | throw new CothorityCryptoException(e.getMessage());
114 | } catch (BadPaddingException e){
115 | throw new CothorityCryptoException(e.getMessage());
116 | }
117 | }
118 |
119 | /**
120 | * Convenience method for use with googles-protobuf bytestring.
121 | *
122 | * @param dataEnc as google protobuf bytestring
123 | * @param keyMaterial the decrypted keyMaterial
124 | * @return decypted data
125 | * @throws Exception
126 | */
127 | public static byte[] decryptData(ByteString dataEnc, byte[] keyMaterial) throws CothorityCryptoException {
128 | return decryptData(dataEnc.toByteArray(), keyMaterial);
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/crypto/KeyPair.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.crypto;
2 |
3 | import java.util.Random;
4 |
5 | public class KeyPair {
6 | public ch.epfl.dedis.lib.crypto.Scalar Scalar;
7 | public ch.epfl.dedis.lib.crypto.Point Point;
8 |
9 | public KeyPair() {
10 | byte[] seed = new byte[Ed25519.field.getb() / 8];
11 | new Random().nextBytes(seed);
12 | Scalar = new Scalar(seed);
13 | Point = Scalar.scalarMult(null);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/crypto/Point.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.crypto;
2 |
3 | import ch.epfl.dedis.lib.exception.CothorityCryptoException;
4 | import com.google.protobuf.ByteString;
5 | import net.i2p.crypto.eddsa.EdDSAPublicKey;
6 | import net.i2p.crypto.eddsa.math.GroupElement;
7 | import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import javax.xml.bind.DatatypeConverter;
12 | import java.security.PublicKey;
13 | import java.util.Arrays;
14 |
15 | public class Point {
16 | private final static Logger logger = LoggerFactory.getLogger(Point.class);
17 |
18 | public GroupElement element;
19 |
20 | public Point(ByteString pub) {
21 | this(pub.toByteArray());
22 | }
23 |
24 | public Point(PublicKey pub) {
25 | this(Arrays.copyOfRange(pub.getEncoded(), 12, 44));
26 | }
27 |
28 | public Point(String str) {
29 | this(DatatypeConverter.parseHexBinary(str));
30 | }
31 |
32 | public Point(byte[] b) {
33 | element = new GroupElement(Ed25519.curve, b);
34 | }
35 |
36 | public Point(Point p) {
37 | this(p.element);
38 | }
39 |
40 | public Point(GroupElement e) {
41 | element = e;
42 | }
43 |
44 | public Point copy() {
45 | return new Point(this);
46 | }
47 |
48 | public boolean equals(Point other) {
49 | return Arrays.equals(element.toByteArray(), other.element.toByteArray());
50 | }
51 |
52 | public Point scalarMult(Scalar s) {
53 | element = element.toP3();
54 | element.precompute(true);
55 | return new Point(element.scalarMultiply(s.getLittleEndian()));
56 | }
57 |
58 | public Point add(Point other) {
59 | return new Point(element.toP3().add(other.element.toCached()));
60 | }
61 |
62 | public ByteString toProto() {
63 | return ByteString.copyFrom(toBytes());
64 | }
65 |
66 | public byte[] toBytes() {
67 | return element.toByteArray();
68 | }
69 |
70 | public boolean isZero() {
71 | return !element.toP2().getY().isNonZero();
72 | }
73 |
74 | public String toString() {
75 | return DatatypeConverter.printHexBinary(toBytes());
76 | }
77 |
78 | public EdDSAPublicKey toEdDSAPub() {
79 | EdDSAPublicKeySpec spec = new EdDSAPublicKeySpec(element, Ed25519.ed25519);
80 | return new EdDSAPublicKey(spec);
81 | }
82 |
83 | public Point negate() {
84 | return new Point(element.toP3().negate());
85 | }
86 |
87 | public byte[] pubLoad() throws CothorityCryptoException {
88 | byte[] bytes = toBytes();
89 | int len = bytes[0];
90 | if (len > Ed25519.pubLen || len < 0) {
91 | logger.info(DatatypeConverter.printHexBinary(bytes));
92 | throw new CothorityCryptoException("doesn't seem to be a valid point");
93 | }
94 | return Arrays.copyOfRange(bytes, 1, len + 1);
95 | }
96 |
97 |
98 | public static Point pubStore(byte[] data) throws CothorityCryptoException {
99 | if (data.length > Ed25519.pubLen) {
100 | throw new CothorityCryptoException("too much data for point");
101 | }
102 |
103 | byte[] bytes = new byte[32];
104 | bytes[0] = (byte) data.length;
105 | System.arraycopy(data, 0, bytes, 1, data.length);
106 | for (bytes[31] = (byte) 0; bytes[31] < (byte) 127; bytes[31]++) {
107 | try {
108 | Point e = new Point(bytes);
109 | if (!e.scalarMult(Ed25519.prime_order).isZero()) {
110 | continue;
111 | }
112 | return e;
113 | } catch (IllegalArgumentException e) {
114 | // Will fail in about 87.5%, so try again.
115 | }
116 | }
117 | throw new CothorityCryptoException("did not find matching point!?!");
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/crypto/Scalar.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.crypto;
2 |
3 | import com.google.protobuf.ByteString;
4 | import net.i2p.crypto.eddsa.EdDSAPrivateKey;
5 | import net.i2p.crypto.eddsa.math.FieldElement;
6 | import net.i2p.crypto.eddsa.math.ed25519.Ed25519ScalarOps;
7 | import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
8 |
9 | import javax.xml.bind.DatatypeConverter;
10 | import java.util.Arrays;
11 |
12 | public class Scalar {
13 | public FieldElement fieldElement;
14 |
15 | public Scalar(String str) {
16 | this(str, true);
17 | }
18 |
19 | public Scalar(String str, boolean reduce) {
20 | this(DatatypeConverter.parseHexBinary(str), reduce);
21 | }
22 |
23 | public Scalar(byte[] b) {
24 | this(b, true);
25 | }
26 |
27 | public Scalar(byte[] b, boolean reduce) {
28 | if (reduce) {
29 | byte[] reduced = new Ed25519ScalarOps().reduce(Arrays.copyOfRange(b, 0, 64));
30 | fieldElement = Ed25519.field.fromByteArray(reduced);
31 | } else {
32 | fieldElement = Ed25519.field.fromByteArray(b);
33 | }
34 | }
35 |
36 | public Scalar(FieldElement f) {
37 | fieldElement = f;
38 | }
39 |
40 | public String toString() {
41 | return DatatypeConverter.printHexBinary(getLittleEndian());
42 | }
43 |
44 | public ByteString toProto() {
45 | return ByteString.copyFrom(reduce().getLittleEndian());
46 | }
47 |
48 | public byte[] toBytes() {
49 | return reduce().getLittleEndian();
50 | }
51 |
52 | public Scalar reduce() {
53 | return new Scalar(Ed25519.ed25519.getScalarOps().reduce(getLittleEndianFull()));
54 | }
55 |
56 | public Scalar copy() {
57 | return new Scalar(getLittleEndian());
58 | }
59 |
60 | public boolean equals(Scalar other) {
61 | return Arrays.equals(fieldElement.toByteArray(), other.fieldElement.toByteArray());
62 | }
63 |
64 | public Scalar addOne() {
65 | return new Scalar(fieldElement.addOne());
66 | }
67 |
68 | public byte[] getBigEndian() {
69 | return Ed25519.reverse(getLittleEndian());
70 | }
71 |
72 | public byte[] getLittleEndian() {
73 | return fieldElement.toByteArray();
74 | }
75 |
76 | public byte[] getLittleEndianFull() {
77 | return Arrays.copyOfRange(getLittleEndian(), 0, 64);
78 | }
79 |
80 | public Scalar add(Scalar b) {
81 | return new Scalar(fieldElement.add(b.fieldElement));
82 | }
83 |
84 | public Scalar sub(Scalar b) {
85 | return new Scalar(fieldElement.subtract(b.fieldElement));
86 | }
87 |
88 | public Scalar invert() {
89 | return new Scalar(fieldElement.invert());
90 | }
91 |
92 | public Scalar negate() {
93 | return Ed25519.prime_order.sub(this.reduce()).reduce();
94 | }
95 |
96 | public boolean isZero() {
97 | return !reduce().fieldElement.isNonZero();
98 | }
99 |
100 | public Point scalarMult(Point p) {
101 | if (p == null) {
102 | p = Ed25519.base;
103 | }
104 | return p.scalarMult(this);
105 | }
106 |
107 | public Scalar mul(Scalar s) {
108 | return new Scalar(Ed25519.ed25519.getScalarOps().multiplyAndAdd(fieldElement.toByteArray(), s.fieldElement.toByteArray(),
109 | Ed25519.field.ZERO.toByteArray()));
110 | }
111 |
112 | public EdDSAPrivateKey getPrivate() {
113 | EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(Ed25519.ed25519, getLittleEndianFull());
114 | return new EdDSAPrivateKey(spec);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/crypto/SchnorrSig.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.crypto;
2 |
3 | import ch.epfl.dedis.proto.SkipBlockProto;
4 |
5 | import java.security.MessageDigest;
6 | import java.security.NoSuchAlgorithmException;
7 | import java.util.Arrays;
8 |
9 | public class SchnorrSig {
10 | public Point challenge;
11 | public Scalar response;
12 |
13 | public SchnorrSig(byte[] msg, Scalar priv) {
14 | KeyPair kp = new KeyPair();
15 | challenge = kp.Point;
16 |
17 | Point pub = priv.scalarMult(null);
18 | Scalar xh = priv.mul(toHash(challenge, pub, msg));
19 | response = kp.Scalar.add(xh);
20 | }
21 |
22 | public SchnorrSig(byte[] data) {
23 | challenge = new Point(Arrays.copyOfRange(data, 0, 32));
24 | response = new Scalar(Arrays.copyOfRange(data, 32, 64));
25 | }
26 |
27 | public boolean verify(byte[] msg, Point pub) {
28 | Scalar hash = toHash(challenge, pub, msg);
29 | Point S = response.scalarMult(null);
30 | Point Ah = pub.scalarMult(hash);
31 | Point RAs = challenge.add(Ah);
32 | return S.equals(RAs);
33 | }
34 |
35 | public byte[] toBytes() {
36 | byte[] buf = new byte[64];
37 | System.arraycopy(challenge.toBytes(), 0, buf, 0, 32);
38 | System.arraycopy(response.toBytes(), 0, buf, 32, 32);
39 | return buf;
40 | }
41 |
42 | public Scalar toHash(Point challenge, Point pub, byte[] msg) {
43 | try {
44 | MessageDigest digest = MessageDigest.getInstance("SHA-512");
45 | digest.update(challenge.toBytes());
46 | digest.update(pub.toBytes());
47 | digest.update(msg);
48 | byte[] hash = Arrays.copyOfRange(digest.digest(), 0, 64);
49 | Scalar s = new Scalar(hash);
50 | return s;
51 | } catch (NoSuchAlgorithmException e) {
52 | return null;
53 | }
54 | }
55 |
56 | public SkipBlockProto.SchnorrSig toProto() {
57 | SkipBlockProto.SchnorrSig.Builder ss =
58 | SkipBlockProto.SchnorrSig.newBuilder();
59 | ss.setChallenge(challenge.toProto());
60 | ss.setResponse(response.toProto());
61 | return ss.build();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/exception/CothorityCommunicationException.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.exception;
2 |
3 | import com.google.protobuf.InvalidProtocolBufferException;
4 |
5 | public class CothorityCommunicationException extends CothorityException {
6 | public CothorityCommunicationException(String message) {
7 | super(message);
8 | }
9 | public CothorityCommunicationException(String message, Throwable cause) { super(message, cause);}
10 |
11 | public CothorityCommunicationException(InvalidProtocolBufferException protobufProtocolException) {
12 | super(protobufProtocolException.getMessage(), protobufProtocolException);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/exception/CothorityCryptoException.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.exception;
2 |
3 | public class CothorityCryptoException extends CothorityException{
4 | public CothorityCryptoException(String m) {
5 | super(m);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/exception/CothorityException.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.exception;
2 |
3 | public class CothorityException extends Exception {
4 | public CothorityException() {
5 | }
6 |
7 | public CothorityException(String message) {
8 | super(message);
9 | }
10 |
11 | public CothorityException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public CothorityException(Throwable cause) {
16 | super(cause);
17 | }
18 |
19 | public CothorityException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
20 | super(message, cause, enableSuppression, writableStackTrace);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/exception/CothorityNotFoundException.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.exception;
2 |
3 | public class CothorityNotFoundException extends CothorityException {
4 | public CothorityNotFoundException() {
5 | }
6 |
7 | public CothorityNotFoundException(String message) {
8 | super(message);
9 | }
10 |
11 | public CothorityNotFoundException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public CothorityNotFoundException(Throwable cause) {
16 | super(cause);
17 | }
18 |
19 | public CothorityNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
20 | super(message, cause, enableSuppression, writableStackTrace);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/external/java/src/main/java/ch/epfl/dedis/lib/exception/CothorityPermissionException.java:
--------------------------------------------------------------------------------
1 | package ch.epfl.dedis.lib.exception;
2 |
3 | public class CothorityPermissionException extends CothorityException {
4 | public CothorityPermissionException() {
5 | }
6 |
7 | public CothorityPermissionException(String message) {
8 | super(message);
9 | }
10 |
11 | public CothorityPermissionException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public CothorityPermissionException(Throwable cause) {
16 | super(cause);
17 | }
18 |
19 | public CothorityPermissionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
20 | super(message, cause, enableSuppression, writableStackTrace);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/external/proto/roster.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | import "server-identity.proto";
4 |
5 | option java_package = "ch.epfl.dedis.proto";
6 | option java_outer_classname = "RosterProto";
7 |
8 | message Roster {
9 | required bytes id = 1;
10 | repeated ServerIdentity list = 2;
11 | required bytes aggregate = 3;
12 | }
13 |
--------------------------------------------------------------------------------
/external/proto/server-identity.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | option java_package = "ch.epfl.dedis.proto";
4 | option java_outer_classname = "ServerIdentityProto";
5 |
6 | message ServerIdentity{
7 | required bytes public = 1;
8 | required bytes id = 2;
9 | required string address = 3;
10 | required string description = 4;
11 | }
12 |
13 |
14 |
--------------------------------------------------------------------------------
/external/proto/sicpa.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | import "skipblock.proto";
3 | import "roster.proto";
4 |
5 | option java_package = "ch.epfl.dedis.proto";
6 | option java_outer_classname = "LleapProto";
7 |
8 | // ***
9 | // These are the messages used in the API-calls
10 | // ***
11 |
12 | // CreateSkipchain asks the cisc-service to set up a new skipchain.
13 | message CreateSkipchain {
14 | // Version of the protocol
15 | required sint32 version = 1;
16 | // Roster defines which nodes participate in the skipchain.
17 | required Roster roster = 2;
18 | // Writers represent keys that are allowed to add new key/value pairs to the skipchain.
19 | repeated bytes writers = 3;
20 | // Signature, if available, will have to include the nonce sent by cisc.
21 | optional bytes signature = 4;
22 | }
23 |
24 | // CreateSkipchainResponse holds the genesis-block of the new skipchain.
25 | message CreateSkipchainResponse {
26 | // Version of the protocol
27 | required sint32 version = 1;
28 | // Skipblock of the created skipchain or empty if there was an error.
29 | optional SkipBlock skipblock = 2;
30 | }
31 |
32 | // SetKeyValue asks for inclusion for a new key/value pair. The value needs
33 | // to be signed by one of the Writers from the createSkipchain call.
34 | message SetKeyValue {
35 | // Version of the protocol
36 | required sint32 version = 1;
37 | // SkipchainID is the hash of the first skipblock
38 | required bytes skipchainid = 2;
39 | // Key where the value is stored
40 | required bytes key = 3;
41 | // Value, if Writers were present in CreateSkipchain, the value should be
42 | // signed by one of the keys.
43 | required bytes value = 4;
44 | // Signature is an RSA-sha384 signature on the key/value pair concatenated
45 | required bytes signature = 5;
46 | }
47 |
48 | // SetKeyValueResponse gives the timestamp and the skipblock-id
49 | message SetKeyValueResponse {
50 | // Version of the protocol
51 | required sint32 version = 1;
52 | // Timestamp is milliseconds since the unix epoch (1/1/1970, 12am UTC)
53 | optional sint64 timestamp = 2;
54 | // Skipblock ID is the hash of the block where the value is stored
55 | optional bytes skipblockid = 3;
56 | }
57 |
58 | // GetValue looks up the value in the given skipchain and returns the
59 | // stored value, or an error if either the skipchain or the key doesn't exist.
60 | message GetValue {
61 | // Version of the protocol
62 | required sint32 version = 1;
63 | // SkipchainID represents the skipchain where the value is stored
64 | required bytes skipchainid = 2;
65 | // Key to retrieve
66 | required bytes key = 3;
67 | }
68 |
69 | // GetValueResponse returns the value or an error if the key hasn't been found.
70 | message GetValueResponse {
71 | // Version of the protocol
72 | required sint32 version = 1;
73 | // Value of the key
74 | optional bytes value = 2;
75 | // Signature as sent when the value was stored
76 | optional bytes signature = 3;
77 | // Proof the value is correct
78 | optional bytes proof = 4;
79 | }
80 |
--------------------------------------------------------------------------------
/external/proto/skipblock.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | import "server-identity.proto";
4 | import "roster.proto";
5 |
6 | option java_package = "ch.epfl.dedis.proto";
7 | option java_outer_classname = "SkipBlockProto";
8 |
9 | message SkipBlock {
10 | required int32 index = 1;
11 | required int32 height = 2;
12 | required int32 max_height = 3;
13 | required int32 base_height = 4;
14 | repeated bytes backlinks = 5;
15 | repeated bytes verifiers = 6;
16 | optional bytes parent = 7;
17 | required bytes genesis = 8;
18 | required bytes data = 9;
19 | required Roster roster = 10;
20 | required bytes hash = 11;
21 | repeated BlockLink forward = 12;
22 | repeated BlockLink children = 13;
23 | }
24 |
25 | message BlockLink {
26 | required bytes sig = 1;
27 | required bytes msg = 2;
28 | repeated Exception exceptions = 3;
29 | }
30 |
31 | message SchnorrSig {
32 | required bytes challenge = 1;
33 | required bytes response = 2;
34 | }
35 |
36 | message Exception {
37 | required int32 index = 1;
38 | required bytes commitment = 2;
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/external/proto/skipchain.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | option java_package = "ch.epfl.dedis.proto";
4 | option java_outer_classname = "SkipchainProto";
5 |
6 | message GetSingleBlock {
7 | required bytes id = 1;
8 | }
9 |
--------------------------------------------------------------------------------
/external/proto/status.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | import "server-identity.proto";
4 |
5 | option java_package = "ch.epfl.dedis.proto";
6 | option java_outer_classname = "StatusProto";
7 |
8 | message Request{
9 |
10 | }
11 |
12 | message Response {
13 | map system = 1;
14 | optional ServerIdentity server = 2;
15 |
16 | message Status {
17 | map field = 1;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/omniledger/.gitignore:
--------------------------------------------------------------------------------
1 | omniledger
--------------------------------------------------------------------------------
/omniledger/app.go:
--------------------------------------------------------------------------------
1 | // Package main is an app to interact with an OmniLedger service. It can set up
2 | // a new skipchain, store transactions and retrieve values given a key.
3 | package main
4 |
5 | import (
6 | "errors"
7 | "os"
8 |
9 | "github.com/dedis/student_18_omniledger/omniledger/darc"
10 | "github.com/dedis/student_18_omniledger/omniledger/service"
11 |
12 | "gopkg.in/dedis/cothority.v2"
13 | "gopkg.in/dedis/kyber.v2/util/key"
14 | "gopkg.in/dedis/onet.v2/app"
15 | "gopkg.in/dedis/onet.v2/log"
16 | "gopkg.in/urfave/cli.v1"
17 | )
18 |
19 | func main() {
20 | cliApp := cli.NewApp()
21 | cliApp.Name = "OmniLedger app"
22 | cliApp.Usage = "Key/value storage for OmniLedger"
23 | cliApp.Version = "0.1"
24 | cliApp.Commands = []cli.Command{
25 | {
26 | Name: "create",
27 | Usage: "creates a new skipchain",
28 | Aliases: []string{"c"},
29 | ArgsUsage: "group.toml",
30 | Action: create,
31 | },
32 | }
33 | cliApp.Flags = []cli.Flag{
34 | cli.IntFlag{
35 | Name: "debug, d",
36 | Value: 0,
37 | Usage: "debug-level: 1 for terse, 5 for maximal",
38 | },
39 | }
40 | cliApp.Before = func(c *cli.Context) error {
41 | log.SetDebugVisible(c.Int("debug"))
42 | return nil
43 | }
44 | log.ErrFatal(cliApp.Run(os.Args))
45 | }
46 |
47 | // Creates a new skipchain
48 | func create(c *cli.Context) error {
49 | log.Info("Create a new skipchain")
50 |
51 | if c.NArg() != 1 {
52 | return errors.New("please give: group.toml")
53 | }
54 | group := readGroup(c)
55 |
56 | kp := key.NewKeyPair(cothority.Suite)
57 |
58 | client := service.NewClient()
59 | signer := darc.NewSignerEd25519(kp.Public, kp.Private)
60 | msg, err := service.DefaultGenesisMsg(service.CurrentVersion, group.Roster, []string{"Spawn_dummy"}, signer.Identity())
61 | if err != nil {
62 | return err
63 | }
64 | resp, err := client.CreateGenesisBlock(group.Roster, msg)
65 | if err != nil {
66 | return errors.New("during creation of skipchain: " + err.Error())
67 | }
68 | log.Infof("Created new skipchain on roster %s with ID: %x", group.Roster.List, resp.Skipblock.Hash)
69 | log.Infof("Private: %s", kp.Private)
70 | log.Infof(" Public: %s", kp.Public)
71 | return nil
72 | }
73 |
74 | // readGroup decodes the group given in the file with the name in the
75 | // first argument of the cli.Context.
76 | func readGroup(c *cli.Context) *app.Group {
77 | name := c.Args().First()
78 | f, err := os.Open(name)
79 | log.ErrFatal(err, "Couldn't open group definition file")
80 | group, err := app.ReadGroupDescToml(f)
81 | log.ErrFatal(err, "Error while reading group definition file", err)
82 | if len(group.Roster.List) == 0 {
83 | log.ErrFatalf(err, "Empty entity or invalid group defintion in: %s",
84 | name)
85 | }
86 | return group
87 | }
88 |
--------------------------------------------------------------------------------
/omniledger/app_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 |
6 | "gopkg.in/dedis/onet.v2/log"
7 | )
8 |
9 | // You may put normal Go tests in this file. For more information
10 | // on testing in Go see: https://golang.org/doc/code.html#Testing
11 | func TestAddition(t *testing.T) {
12 | if 1+1 != 2 {
13 | t.Fatal("Addition does not work.")
14 | }
15 | }
16 |
17 | // It is useful for Onet applications to run code before and after the
18 | // Go test framework, for example in order to configure logging, to
19 | // set a global time limit, and to check for leftover goroutines.
20 | //
21 | // See:
22 | // - https://godoc.org/testing#hdr-Main
23 | // - https://godoc.org/gopkg.in/dedis/onet.v2/log#MainTest
24 | func TestMain(m *testing.M) {
25 | log.MainTest(m)
26 | }
27 |
--------------------------------------------------------------------------------
/omniledger/collection/.gitignore:
--------------------------------------------------------------------------------
1 | reated by https://www.gitignore.io/api/go,osx,linux,windows
2 |
3 | ### Go ###
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.dll
7 | *.so
8 | *.dylib
9 |
10 | # Test binary, build with `go test -c`
11 | *.test
12 |
13 | # Output of the go coverage tool, specifically when used with LiteIDE
14 | *.out
15 |
16 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
17 | .glide/
18 |
19 | # Golang project vendor packages which should be ignored
20 | vendor/
21 |
22 | ### Linux ###
23 | *~
24 |
25 | # temporary files which can be created if a process still has a handle open of a deleted file
26 | .fuse_hidden*
27 |
28 | # KDE directory preferences
29 | .directory
30 |
31 | # Linux trash folder which might appear on any partition or disk
32 | .Trash-*
33 |
34 | # .nfs files are created when an open file is removed but is still being accessed
35 | .nfs*
36 |
37 | ### OSX ###
38 | *.DS_Store
39 | .AppleDouble
40 | .LSOverride
41 |
42 | # Icon must end with two \r
43 | Icon
44 |
45 | # Thumbnails
46 | ._*
47 |
48 | # Files that might appear in the root of a volume
49 | .DocumentRevisions-V100
50 | .fseventsd
51 | .Spotlight-V100
52 | .TemporaryItems
53 | .Trashes
54 | .VolumeIcon.icns
55 | .com.apple.timemachine.donotpresent
56 |
57 | # Directories potentially created on remote AFP share
58 | .AppleDB
59 | .AppleDesktop
60 | Network Trash Folder
61 | Temporary Items
62 | .apdisk
63 |
64 | ### Windows ###
65 | # Windows thumbnail cache files
66 | Thumbs.db
67 | ehthumbs.db
68 | ehthumbs_vista.db
69 |
70 | # Folder config file
71 | Desktop.ini
72 |
73 | # Recycle Bin used on file shares
74 | $RECYCLE.BIN/
75 |
76 | # Windows Installer files
77 | *.cab
78 | *.msi
79 | *.msm
80 | *.msp
81 |
82 | # Windows shortcuts
83 | *.lnk
84 |
85 | # End of https://www.gitignore.io/api/go,osx,linux,windows
86 |
--------------------------------------------------------------------------------
/omniledger/collection/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | sudo: false
3 |
4 | go:
5 | - 1.8.x
6 | - tip
7 |
8 | before_install:
9 | - go get -t -v ./...
10 |
11 | script:
12 | - go test -race -coverprofile=coverage.txt -covermode=atomic
13 |
14 | after_success:
15 | - bash <(curl -s https://codecov.io/bash) -t 9a520e59-5f33-4d21-9d88-fabbf3f6572a
16 |
--------------------------------------------------------------------------------
/omniledger/collection/TODO.md:
--------------------------------------------------------------------------------
1 | - make golint and go vet happy:
2 | - change all methods to follow golint's suggestions
3 | - add comments to all Public methods
4 |
5 | - sha256.go should go away. If we ever want to implement this in another
6 | language, then we need to have basic, step-by-step hashes, that can easily
7 | be copied to other languages.
8 |
9 | - remove all `csha256`, as go implementations are the basic implementations,
10 | if we do something with the same name (sha256.go), then we need to change _our_
11 | name, not golang's name.
12 |
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/collection.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/collection.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/0.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/0.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/1.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/10.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/10.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/11.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/11.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/12.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/12.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/13.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/13.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/14.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/14.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/15.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/15.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/16.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/16.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/17.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/17.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/18.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/18.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/19.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/19.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/2.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/20.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/20.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/21.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/21.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/22.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/22.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/23.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/23.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/3.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/4.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/5.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/6.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/6.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/7.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/7.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/8.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/8.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/example/9.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/example/9.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/naivetree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/naivetree.png
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/navigation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/navigation.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/nonexistingnavigation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/nonexistingnavigation.gif
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/tree.png
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/unknown.png
--------------------------------------------------------------------------------
/omniledger/collection/assets/images/unknownroot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dedis/student_18_byzcoin/1302954f1ef1aaa4e4f1b4af73461af547a5f4ad/omniledger/collection/assets/images/unknownroot.png
--------------------------------------------------------------------------------
/omniledger/collection/byteslice.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "crypto/sha256"
4 |
5 | // utility functions for slices of bytes
6 |
7 | func equal(lho []byte, rho []byte) bool {
8 | if len(lho) != len(rho) {
9 | return false
10 | }
11 |
12 | for index := 0; index < len(lho); index++ {
13 | if lho[index] != rho[index] {
14 | return false
15 | }
16 | }
17 |
18 | return true
19 | }
20 |
21 | func bit(buffer []byte, index int) bool {
22 | byteIdx := uint(index) / 8
23 | bitIdx := 7 - (uint(index) % 8)
24 |
25 | return ((buffer[byteIdx] & (uint8(1) << bitIdx)) != 0)
26 | }
27 |
28 | func setBit(buffer []byte, index int, value bool) {
29 | byteIdx := uint(index) / 8
30 | bitIdx := 7 - (uint(index) % 8)
31 |
32 | if value {
33 | buffer[byteIdx] |= (uint8(1) << bitIdx)
34 | } else {
35 | buffer[byteIdx] &^= (uint8(1) << bitIdx)
36 | }
37 | }
38 |
39 | // identical up to some bits
40 | func match(lho []byte, rho []byte, bits int) bool {
41 | for index := 0; index < bits; {
42 | if index < bits-8 {
43 | if lho[index/8] != rho[index/8] {
44 | return false
45 | }
46 |
47 | index += 8
48 | } else {
49 | if bit(lho, index) != bit(rho, index) {
50 | return false
51 | }
52 |
53 | index++
54 | }
55 | }
56 |
57 | return true
58 | }
59 |
60 | func digest(buffer []byte) [sha256.Size]byte {
61 | if len(buffer) != sha256.Size {
62 | panic("Wrong slice length.")
63 | }
64 |
65 | var digest [sha256.Size]byte
66 |
67 | for index := 0; index < sha256.Size; index++ {
68 | digest[index] = buffer[index]
69 | }
70 |
71 | return digest
72 | }
73 |
--------------------------------------------------------------------------------
/omniledger/collection/byteslice_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/hex"
6 | "math/rand"
7 | "testing"
8 | )
9 |
10 | func TestBytesliceEqual(test *testing.T) {
11 | lho, _ := hex.DecodeString("85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398")
12 | rho, _ := hex.DecodeString("85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398")
13 | cutRho, _ := hex.DecodeString("85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c168939063")
14 | alterRho, _ := hex.DecodeString("85f46bd1ba19d1014b1179edd452ece95296e4a8c765ba8bba86c16893906398")
15 |
16 | if !(equal(lho, rho)) {
17 | test.Error("[byteslice.go]", "[equal]", "equal() returns false on two equal buffers.")
18 | }
19 |
20 | if equal(lho, cutRho) {
21 | test.Error("[byteslice.go]", "[equal]", "equal() returns true on two buffers of different length.")
22 | }
23 |
24 | if equal(lho, alterRho) {
25 | test.Error("[byteslice.go]", "[equal]", "equal() returns true on two different buffers.")
26 | }
27 | }
28 |
29 | func TestByteSliceBit(test *testing.T) {
30 | buffer, _ := hex.DecodeString("85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398")
31 | reference := "1000010111110100011010111101000110111010000110011101000100000001010010110001000101111001111011011101010001010001111011001110100101010010100101101110010010101000110001110110010110111010100010111011101010000110110000010110100010010011100100000110001110011000"
32 |
33 | for index := 0; index < 8*len(buffer); index++ {
34 | bit := bit(buffer, index)
35 |
36 | if (bit && reference[index:index+1] == "0") || (!bit && reference[index:index+1] == "1") {
37 | test.Error("[byteslice.go]", "[bit]", "Wrong bit detected on test buffer.")
38 | break
39 | }
40 | }
41 | }
42 |
43 | func TestByteSliceSetBit(test *testing.T) {
44 | source, _ := hex.DecodeString("85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398")
45 | destination := make([]byte, len(source))
46 |
47 | for index := 0; index < 8*len(destination); index++ {
48 | setBit(destination, index, bit(source, index))
49 | }
50 |
51 | if !(equal(source, destination)) {
52 | test.Error("[byteslice.go]", "[setBit]", "Wrong bit set by setBit.")
53 | }
54 |
55 | for index := 0; index < len(destination); index++ {
56 | destination[index] = 0xff
57 | }
58 |
59 | for index := 0; index < 8*len(destination); index++ {
60 | setBit(destination, index, bit(source, index))
61 | }
62 |
63 | if !(equal(source, destination)) {
64 | test.Error("[byteslice.go]", "[setBit]", "Wrong bit set by setBit.")
65 | }
66 | }
67 |
68 | func TestByteSliceMatch(test *testing.T) {
69 | min := func(lho, rho int) int {
70 | if lho < rho {
71 | return lho
72 | }
73 | return rho
74 | }
75 |
76 | type round struct {
77 | lho string
78 | rho string
79 | bits int
80 | }
81 |
82 | rounds := []round{
83 | {"85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 256},
84 | {"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 252},
85 | {"85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906390", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 252},
86 | {"85f46bd1ba1ad1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 46},
87 | {"85f46bd1ba18d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 47},
88 | }
89 |
90 | for _, round := range rounds {
91 | lho, _ := hex.DecodeString(round.lho)
92 | rho, _ := hex.DecodeString(round.rho)
93 |
94 | for index := 0; index <= 8*min(len(lho), len(rho)); index++ {
95 | if (match(lho, rho, index) && index > round.bits) || (!match(lho, rho, index) && index <= round.bits) {
96 | test.Error("[byteslice.go]", "[match]", "Wrong matching on test buffers.")
97 | }
98 | }
99 | }
100 | }
101 |
102 | func TestByteSliceDigest(test *testing.T) {
103 | ctx := testCtx("[byteslice.go]", test)
104 |
105 | for round := 0; round < 16; round++ {
106 | slice := make([]byte, sha256.Size)
107 | for index := 0; index < sha256.Size; index++ {
108 | slice[index] = byte(rand.Uint32())
109 | }
110 |
111 | digest := digest(slice)
112 |
113 | for index := 0; index < sha256.Size; index++ {
114 | if digest[index] != slice[index] {
115 | test.Error("[byteslice.go]", "[digest]", "digest() does not provide correct copy of the slice provided.")
116 | }
117 | }
118 | }
119 |
120 | ctx.shouldPanic("[wrongsize]", func() {
121 | digest(make([]byte, 0))
122 | })
123 |
124 | ctx.shouldPanic("[wrongsize]", func() {
125 | digest(make([]byte, 1))
126 | })
127 |
128 | ctx.shouldPanic("[wrongsize]", func() {
129 | digest(make([]byte, sha256.Size-1))
130 | })
131 | }
132 |
--------------------------------------------------------------------------------
/omniledger/collection/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "testctx.go"
3 |
--------------------------------------------------------------------------------
/omniledger/collection/collection.go:
--------------------------------------------------------------------------------
1 | // Package collection is a Merkle-tree based data structure to securely and
2 | // verifiably store key / value associations on untrusted nodes. The library
3 | // in this package focuses on ease of use and flexibility, allowing to easily
4 | // develop applications ranging from simple client-server storage to fully
5 | // distributed and decentralized ledgers with minimal bootstrapping time.
6 | package collection
7 |
8 | // Collection represents the Merkle-tree based data structure.
9 | // The data is defined by a pointer to its root.
10 | type Collection struct {
11 | root *node
12 | fields []Field
13 | scope scope
14 |
15 | autoCollect flag
16 | transaction struct {
17 | ongoing bool
18 | id uint64
19 | }
20 | }
21 |
22 | // Constructors
23 |
24 | // New creates a new collection, with one root node and the given Fields
25 | func New(fields ...Field) (collection Collection) {
26 | collection.fields = fields
27 |
28 | collection.scope.All()
29 | collection.autoCollect.Enable()
30 |
31 | collection.root = new(node)
32 | collection.root.known = true
33 |
34 | collection.root.branch()
35 |
36 | err := collection.setPlaceholder(collection.root.children.left)
37 | if err != nil {
38 | panic("error while generating left placeholder:" + err.Error())
39 | }
40 | err = collection.setPlaceholder(collection.root.children.right)
41 | if err != nil {
42 | panic("error while generating right placeholder:" + err.Error())
43 | }
44 | collection.update(collection.root)
45 |
46 | return
47 | }
48 |
49 | // NewVerifier creates a verifier. A verifier is defined as a collection that stores no data and no nodes.
50 | // A verifiers is used to verify a query (i.e. that some data is or is not on the database).
51 | func NewVerifier(fields ...Field) (verifier Collection) {
52 | verifier.fields = fields
53 |
54 | verifier.scope.None()
55 | verifier.autoCollect.Enable()
56 |
57 | empty := New(fields...)
58 |
59 | verifier.root = new(node)
60 | verifier.root.known = false
61 | verifier.root.label = empty.root.label
62 |
63 | return
64 | }
65 |
66 | // Methods
67 |
68 | // Clone returns a deep copy of the collection.
69 | // Note that the transaction id are restarted from 0 for the copy.
70 | func (c *Collection) Clone() (collection Collection) {
71 | if c.transaction.ongoing {
72 | panic("Cannot clone a collection while a transaction is ongoing.")
73 | }
74 |
75 | collection.root = new(node)
76 |
77 | collection.fields = make([]Field, len(c.fields))
78 | copy(collection.fields, c.fields)
79 |
80 | collection.scope = c.scope.clone()
81 | collection.autoCollect = c.autoCollect
82 |
83 | collection.transaction.ongoing = false
84 | collection.transaction.id = 0
85 |
86 | var explore func(*node, *node)
87 | explore = func(dstCursor *node, srcCursor *node) {
88 | dstCursor.label = srcCursor.label
89 | dstCursor.known = srcCursor.known
90 |
91 | dstCursor.transaction.inconsistent = false
92 | dstCursor.transaction.backup = nil
93 |
94 | dstCursor.key = srcCursor.key
95 | dstCursor.values = make([][]byte, len(srcCursor.values))
96 | copy(dstCursor.values, srcCursor.values)
97 |
98 | if !(srcCursor.leaf()) {
99 | dstCursor.branch()
100 | explore(dstCursor.children.left, srcCursor.children.left)
101 | explore(dstCursor.children.right, srcCursor.children.right)
102 | }
103 | }
104 |
105 | explore(collection.root, c.root)
106 |
107 | return
108 | }
109 |
110 | // GetRoot returns the root hash of the collection, which cryptographically
111 | // represents the whole set of key/value pairs in the collection.
112 | func (c *Collection) GetRoot() []byte {
113 | return c.root.key
114 | }
115 |
--------------------------------------------------------------------------------
/omniledger/collection/collection_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "encoding/binary"
5 | "testing"
6 | )
7 |
8 | func TestCollectionEmptyCollection(test *testing.T) {
9 | ctx := testCtx("[collection.go]", test)
10 |
11 | baseCollection := New()
12 |
13 | if !(baseCollection.autoCollect.value) {
14 | test.Error("[collection.go]", "[autocollect]", "AutoCollect does not have true as default value.")
15 | }
16 |
17 | if !(baseCollection.root.known) || !(baseCollection.root.children.left.known) || !(baseCollection.root.children.right.known) {
18 | test.Error("[collection.go]", "[known]", "New collection has unknown nodes.")
19 | }
20 |
21 | if !(baseCollection.root.root()) {
22 | test.Error("[collection.go]", "[root]", "Collection root is not a root.")
23 | }
24 |
25 | if baseCollection.root.leaf() {
26 | test.Error("[collection.go]", "[root]", "Collection root doesn't have children.")
27 | }
28 |
29 | if !(baseCollection.root.children.left.placeholder()) || !(baseCollection.root.children.right.placeholder()) {
30 | test.Error("[collection.go]", "[leaves]", "Collection leaves are not placeholder leaves.")
31 | }
32 |
33 | if len(baseCollection.root.values) != 0 || len(baseCollection.root.children.left.values) != 0 || len(baseCollection.root.children.right.values) != 0 {
34 | test.Error("[collection.go]", "[values]", "Nodes of a collection without fields have values.")
35 | }
36 |
37 | ctx.verify.tree("[baseCollection]", &baseCollection)
38 |
39 | stake64 := Stake64{}
40 | stakeCollection := New(stake64)
41 |
42 | if len(stakeCollection.root.values) != 1 || len(stakeCollection.root.children.left.values) != 1 || len(stakeCollection.root.children.right.values) != 1 {
43 | test.Error("[collection.go]", "[values]", "Nodes of a stake collection don't have exactly one value.")
44 | }
45 |
46 | rootStake, rootError := stake64.Decode(stakeCollection.root.values[0])
47 |
48 | if rootError != nil {
49 | test.Error("[collection.go]", "[stake]", "Malformed stake root value.")
50 | }
51 |
52 | leftStake, leftError := stake64.Decode(stakeCollection.root.children.left.values[0])
53 |
54 | if leftError != nil {
55 | test.Error("[collection.go]", "[stake]", "Malformed stake left child value.")
56 | }
57 |
58 | rightStake, rightError := stake64.Decode(stakeCollection.root.children.right.values[0])
59 |
60 | if rightError != nil {
61 | test.Error("[collection.go]", "[stake]", "Malformed stake right child value")
62 | }
63 |
64 | if rootStake.(uint64) != 0 || leftStake.(uint64) != 0 || rightStake.(uint64) != 0 {
65 | test.Error("[collection.go]", "[stake]", "Nodes of an empty stake collection don't have zero stake.")
66 | }
67 |
68 | ctx.verify.tree("[stakeCollection]", &stakeCollection)
69 |
70 | data := Data{}
71 | stakeDataCollection := New(stake64, data)
72 |
73 | if len(stakeDataCollection.root.values) != 2 || len(stakeDataCollection.root.children.left.values) != 2 || len(stakeDataCollection.root.children.right.values) != 2 {
74 | test.Error("[collection.go]", "[values]", "Nodes of a data and stake collection don't have exactly one value.")
75 | }
76 |
77 | if len(stakeDataCollection.root.values[1]) != 0 || len(stakeDataCollection.root.children.left.values[1]) != 0 || len(stakeDataCollection.root.children.right.values[1]) != 0 {
78 | test.Error("[collection.go]", "[values]", "Nodes of a data and stake collection don't have empty data value.")
79 | }
80 |
81 | ctx.verify.tree("[stakeDataCollection]", &stakeDataCollection)
82 | }
83 |
84 | func TestCollectionEmptyVerifier(test *testing.T) {
85 | baseCollection := New()
86 | baseVerifier := NewVerifier()
87 |
88 | if !(baseVerifier.autoCollect.value) {
89 | test.Error("[collection.go]", "[autocollect]", "AutoCollect does not have true as default value.")
90 | }
91 |
92 | if baseVerifier.root.known {
93 | test.Error("[collection.go]", "[known]", "Empty verifier has known root.")
94 | }
95 |
96 | if (baseVerifier.root.children.left != nil) || (baseVerifier.root.children.right != nil) {
97 | test.Error("[collection.go]", "[root]", "Empty verifier root has children.")
98 | }
99 |
100 | if baseVerifier.root.label != baseCollection.root.label {
101 | test.Error("[collection.go]", "[Label]", "Wrong verifier label.")
102 | }
103 |
104 | stake64 := Stake64{}
105 |
106 | stakeCollection := New(stake64)
107 | stakeVerifier := NewVerifier(stake64)
108 |
109 | if stakeVerifier.root.known {
110 | test.Error("[collection.go]", "[known]", "Empty stake verifier has known root.")
111 | }
112 |
113 | if (stakeVerifier.root.children.left != nil) || (stakeVerifier.root.children.right != nil) {
114 | test.Error("[collection.go]", "[root]", "Empty stake verifier root has children.")
115 | }
116 |
117 | if stakeVerifier.root.label != stakeCollection.root.label {
118 | test.Error("[collection.go]", "[Label]", "Wrong stake verifier label.")
119 | }
120 |
121 | data := Data{}
122 |
123 | stakeDataCollection := New(stake64, data)
124 | stakeDataVerifier := NewVerifier(stake64, data)
125 |
126 | if stakeDataVerifier.root.known {
127 | test.Error("[collection.go]", "[known]", "Empty stake and data verifier has known root.")
128 | }
129 |
130 | if (stakeDataVerifier.root.children.left != nil) || (stakeDataVerifier.root.children.right != nil) {
131 | test.Error("[collection.go]", "[root]", "Empty stake and data verifier root has children.")
132 | }
133 |
134 | if stakeDataVerifier.root.label != stakeDataCollection.root.label {
135 | test.Error("[collection.go]", "[Label]", "Wrong stake and data verifier label.")
136 | }
137 | }
138 |
139 | func TestCollectionClone(test *testing.T) {
140 | ctx := testCtx("[collection.go]", test)
141 |
142 | stake64 := Stake64{}
143 | data := Data{}
144 |
145 | collection := New(stake64, data)
146 |
147 | for index := 0; index < 512; index++ {
148 | key := make([]byte, 8)
149 | binary.BigEndian.PutUint64(key, uint64(index))
150 |
151 | collection.Add(key, uint64(index), key)
152 | }
153 |
154 | clone := collection.Clone()
155 |
156 | ctx.verify.tree("[clone]", &clone)
157 |
158 | for index := 0; index < 512; index++ {
159 | key := make([]byte, 8)
160 | binary.BigEndian.PutUint64(key, uint64(index))
161 |
162 | ctx.verify.values("[clone]", &clone, key, uint64(index), key)
163 | }
164 |
165 | ctx.shouldPanic("[clone]", func() {
166 | collection.Begin()
167 | collection.Clone()
168 | collection.End()
169 | })
170 | }
171 |
--------------------------------------------------------------------------------
/omniledger/collection/example.md:
--------------------------------------------------------------------------------
1 | ## Initial state
2 | 
3 |
4 | States of admin, read/writers, and readers are A0, W0, R0.
5 |
6 | ## Charlie emits a request adding Erin to admin
7 | 
8 |
9 | ## State of "admin" is incremented to A1
10 | 
11 |
12 | Because Charlie is in the server's admin collection at state A0, the request is valid. State A0 is mutated to A1.
13 |
14 | ## Server emits proof + state change, floods to all
15 | 
16 |
17 | Proof: Charlie is in A0, thus you are requested to: mutate A0, adding Erin.
18 |
19 | ## Verifiers accept the proof and mutation
20 | 
21 |
22 | QUESTION: What are they checking?
23 |
24 | ## New consistent view of state A1
25 | 
26 |
27 | ## Erin emits a request adding Dan to RW
28 | 
29 |
30 | ## State of "read/writers" is incremented to W1
31 | 
32 |
33 | Because Erin is in the admin collection at state A1, the request is valid. State W0 is mutated to W1.
34 |
35 | ## Server emits proof + state change, floods to all
36 | 
37 |
38 | Proof: Erin is in A1, thus you are requested to: mutate W0, adding Dan.
39 |
40 | ## Verifiers accept the proof and mutation
41 | 
42 |
43 | ## Consistent view of read/writers state == W1
44 | 
45 |
46 | ## Erin emits request to add Alice to RW
47 | 
48 |
49 | ## State of "Read/writers" is incremented to W2
50 | 
51 |
52 | Because Erin is in A1, the request is valid. State W1 is mutated to state W2.
53 |
54 | ## Proof and mutation W1->W2 flooded
55 | 
56 |
57 | Proof: Erin is in A1, thus you are requested to: mutate W1 adding Alice.
58 |
59 | ## Verifiers accept the proof and mutation
60 | 
61 |
62 | ## Consistent view of read/writers state == W2
63 | 
64 |
65 | ## Charlie emits a request "moving" Dan to read
66 | 
67 |
68 | QUESTION: What is this new "move" verb? It seems like it is an Update which atomically combines a delete and an add?
69 |
70 | ## State of readers is incremented to R1, read/writers to W3
71 | 
72 |
73 | Because Charlie is in A1, the request is valid. Decompose it into two proofs, W2-Dan = W3, and R0+Dan=R1.
74 |
75 | ## Server emits proof + state change, floods
76 | 
77 |
78 | ## Verifiers accept the proofs and apply the mutations
79 | 
80 |
81 | ## Consistent view
82 | 
83 |
84 | ## Server emits false proof and fraudulent mutation to all
85 | 
86 |
87 | Proof: Mallory is in A1 (false). Mutation: add "NotAHacker" to read/writers.
88 |
89 | ## Verifiers do not find Mallory in A1
90 | 
91 |
92 | Thus the fraudulent mutation is refused.
93 |
94 | ## State remains consistent and unmodified
95 | 
96 |
97 |
--------------------------------------------------------------------------------
/omniledger/collection/field.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | )
7 |
8 | // Enums
9 |
10 | const (
11 | // Left is a constant representing the case where the Navigator should go to the left child.
12 | Left = false
13 | // Right is a constant representing the case where the Navigator should go to the right child.
14 | Right = true
15 | )
16 |
17 | // Field describes a type.
18 | // It describes how the type is encoded, how it is decoded,
19 | // how it propagates up the tree and other needed functionality.
20 | type Field interface {
21 | Encode(generic interface{}) []byte // Describes how the type is encoded into bytes
22 | Decode(raw []byte) (interface{}, error) // Inverse of Encode. Describes how to decode the byte to the original type
23 | Placeholder() []byte // returns the byte representation of a placeholder (no value)
24 |
25 | // returns the representation in byte of a parent value given its two children values.
26 | // Used to store data on non-leaf node.
27 | // Can return an empty array if this operation is not supported.
28 | Parent(left []byte, right []byte) ([]byte, error)
29 |
30 | // Navigate returns a navigation boolean indicating the direction a Navigator should go to
31 | // from a parent node with a given query.
32 | Navigate(query []byte, parent []byte, left []byte, right []byte) (bool, error)
33 | }
34 |
35 | // Structures
36 |
37 | // Data
38 |
39 | // Data is the generic type of Field.
40 | // It can contain any interface convertible to bytes, for example string or []uint8.
41 | type Data struct {
42 | }
43 |
44 | // Encode returns the basic conversion into a byte array of the parameter.
45 | func (d Data) Encode(generic interface{}) []byte {
46 | value := generic.([]byte)
47 | return value
48 | }
49 |
50 | // Decode returns the bytes without modifying them.
51 | // It can never returns an error
52 | func (d Data) Decode(raw []byte) (interface{}, error) {
53 | return raw, nil
54 | }
55 |
56 | // Placeholder defines the placeholder value as an empty array of byte.
57 | func (d Data) Placeholder() []byte {
58 | return []byte{}
59 | }
60 |
61 | // Parent returns an empty array of byte.
62 | // Indeed with this generic type, the tree doesn't need a propagation up the tree.
63 | func (d Data) Parent(left []byte, right []byte) ([]byte, error) {
64 | return []byte{}, nil
65 | }
66 |
67 | // Navigate will return an error as the Data values cannot be navigated
68 | func (d Data) Navigate(query []byte, parent []byte, left []byte, right []byte) (bool, error) {
69 | return false, errors.New("data values cannot be navigated")
70 | }
71 |
72 | // Stake64 represents stakes.
73 | // Each stake is stored in a final leaf and the intermediary nodes contain the sum of its children.
74 | // This structure allows an easy random selection of a stake from the root proportionally to the stake size.
75 | type Stake64 struct {
76 | }
77 |
78 | // Encode returns array of bytes representation of the the uint64 value of the stake, in big endian.
79 | func (s Stake64) Encode(generic interface{}) []byte {
80 | value := generic.(uint64)
81 | raw := make([]byte, 8)
82 |
83 | binary.BigEndian.PutUint64(raw, value)
84 | return raw
85 | }
86 |
87 | // Decode is the inverse of Encode. It returns the uint64 value of the encoded bytes.
88 | // It may return an error if the parameter raw has a number of bytes different from four.
89 | func (s Stake64) Decode(raw []byte) (interface{}, error) {
90 | if len(raw) != 8 {
91 | return 0, errors.New("wrong buffer length")
92 | }
93 | return binary.BigEndian.Uint64(raw), nil
94 | }
95 |
96 | // Placeholder returns the placeholder value for stakes.
97 | // It is represented by the number 0 encoded into bytes, which represents an empty stake.
98 | func (s Stake64) Placeholder() []byte {
99 | return s.Encode(uint64(0))
100 | }
101 |
102 | // Parent returns the value each non-leaf node should hold.
103 | // The returned value is the sum of stakes of its children or an error if a decoding error occurred.
104 | func (s Stake64) Parent(left []byte, right []byte) ([]byte, error) {
105 | leftValue, leftError := s.Decode(left)
106 |
107 | if leftError != nil {
108 | return []byte{}, leftError
109 | }
110 |
111 | rightValue, rightError := s.Decode(right)
112 |
113 | if rightError != nil {
114 | return []byte{}, rightError
115 | }
116 |
117 | return s.Encode(leftValue.(uint64) + rightValue.(uint64)), nil
118 | }
119 |
120 | // Navigate returns a navigation boolean indicating the direction a Navigator should go to with a given query.
121 | // The first parameter is the query, representing a unsigned integer between 0 and the value of the parent parameter.
122 | // the function will see if the query value is greater or equal than the left value and return a left navigation boolean if so
123 | // and a right navigation boolean otherwise.
124 | // This behavior allows to find a stake randomly and proportionally to the stake value of each leaf.
125 | // To do this, one must select a random stake between 0 and the value of the root and input it repeatedly to the function to navigate down the tree.
126 | func (s Stake64) Navigate(query []byte, parent []byte, left []byte, right []byte) (bool, error) {
127 | queryValue, queryError := s.Decode(query)
128 |
129 | if queryError != nil {
130 | return false, queryError
131 | }
132 |
133 | parentValue, parentError := s.Decode(parent)
134 |
135 | if parentError != nil {
136 | return false, parentError
137 | }
138 |
139 | if queryValue.(uint64) >= parentValue.(uint64) {
140 | return false, errors.New("query exceeds parent stake")
141 | }
142 |
143 | leftValue, leftError := s.Decode(left)
144 |
145 | if leftError != nil {
146 | return false, leftError
147 | }
148 |
149 | if queryValue.(uint64) >= leftValue.(uint64) {
150 | copy(query, s.Encode(queryValue.(uint64)-leftValue.(uint64)))
151 | return Right, nil
152 | }
153 | return Left, nil
154 | }
155 |
--------------------------------------------------------------------------------
/omniledger/collection/flag.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | type flag struct {
4 | value bool
5 | }
6 |
7 | // Methods
8 |
9 | func (f *flag) Enable() {
10 | f.value = true
11 | }
12 |
13 | func (f *flag) Disable() {
14 | f.value = false
15 | }
16 |
--------------------------------------------------------------------------------
/omniledger/collection/flag_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "testing"
4 |
5 | func TestFlag(test *testing.T) {
6 | flag := flag{true}
7 |
8 | flag.Disable()
9 | if flag.value {
10 | test.Error("[flag.go]", "[disable]", "Disable() has no effect on flag.")
11 | }
12 |
13 | flag.Enable()
14 | if !(flag.value) {
15 | test.Error("[flag.go]", "[enable]", "Enable() has no effect on flag.")
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/omniledger/collection/getters.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "crypto/sha256"
5 | "errors"
6 | )
7 |
8 | // Getter is the result of a get on a collection.
9 | // It allows to get a record of the given key/value pair
10 | // and prove, if that is the case, that the given key is in the collection.
11 | type Getter struct {
12 | collection *Collection
13 | key []byte
14 | }
15 |
16 | // Constructors
17 |
18 | // Get returns a getter object, the result of a search of a given key on the collection.
19 | // It takes as parameter the key we search in the collection.
20 | func (c *Collection) Get(key []byte) Getter {
21 | return Getter{c, key}
22 | }
23 |
24 | // Methods
25 |
26 | // Record returns a Record object that correspond to the result of the key search.
27 | // The Record will contain a boolean "match" that is true if the search was successful and false otherwise.
28 | func (g Getter) Record() (Record, error) {
29 | if len(g.key) == 0 {
30 | return Record{}, errors.New("cannot create a record with no key")
31 | }
32 | path := sha256.Sum256(g.key)
33 |
34 | depth := 0
35 | cursor := g.collection.root
36 |
37 | for {
38 | if !(cursor.known) {
39 | return Record{}, errors.New("record lies in an unknown subtree")
40 | }
41 |
42 | if cursor.leaf() {
43 | if equal(cursor.key, g.key) {
44 | return recordKeyMatch(g.collection, cursor), nil
45 | }
46 | return recordKeyMismatch(g.collection, g.key), nil
47 | }
48 | if bit(path[:], depth) {
49 | cursor = cursor.children.right
50 | } else {
51 | cursor = cursor.children.left
52 | }
53 |
54 | depth++
55 | }
56 | }
57 |
58 | // Proof returns a Proof of the presence or absence of a given key in the collection.
59 | // The location the proof points to can contains the actual key.
60 | // It can also contain another key, effectively proving that the key is absent from the collection.
61 | func (g Getter) Proof() (Proof, error) {
62 | if len(g.key) == 0 {
63 | return Proof{}, errors.New("cannot create a proof with no key")
64 | }
65 | var proof Proof
66 |
67 | proof.collection = g.collection
68 | proof.Key = g.key
69 |
70 | proof.Root = dumpNode(g.collection.root)
71 |
72 | path := sha256.Sum256(g.key)
73 |
74 | depth := 0
75 | cursor := g.collection.root
76 |
77 | if !(cursor.known) {
78 | return proof, errors.New("record lies in unknown subtree")
79 | }
80 |
81 | for {
82 | if !(cursor.children.left.known) || !(cursor.children.right.known) {
83 | return proof, errors.New("record lies in unknown subtree")
84 | }
85 |
86 | proof.Steps = append(proof.Steps,
87 | step{dumpNode(cursor.children.left),
88 | dumpNode(cursor.children.right)})
89 |
90 | if bit(path[:], depth) {
91 | cursor = cursor.children.right
92 | } else {
93 | cursor = cursor.children.left
94 | }
95 |
96 | depth++
97 |
98 | if cursor.leaf() {
99 | break
100 | }
101 | }
102 |
103 | return proof, nil
104 | }
105 |
--------------------------------------------------------------------------------
/omniledger/collection/getters_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/binary"
6 | "testing"
7 | )
8 |
9 | func TestGettersConstructors(test *testing.T) {
10 | collection := New()
11 | getter := collection.Get([]byte("mykey"))
12 |
13 | if getter.collection != &collection {
14 | test.Error("[getters.go]", "[constructors]", "Getter constructor sets wrong collection pointer.")
15 | }
16 |
17 | if !equal(getter.key, []byte("mykey")) {
18 | test.Error("[getters.go]", "[constructors]", "Getter constructor sets wrong key.")
19 | }
20 | }
21 |
22 | func TestGettersRecord(test *testing.T) {
23 | collection := New()
24 |
25 | for index := 0; index < 512; index++ {
26 | key := make([]byte, 8)
27 | binary.BigEndian.PutUint64(key, uint64(index))
28 |
29 | collection.Add(key)
30 | }
31 |
32 | for index := 0; index < 1024; index++ {
33 | key := make([]byte, 8)
34 | binary.BigEndian.PutUint64(key, uint64(index))
35 |
36 | record, err := collection.Get(key).Record()
37 |
38 | if err != nil {
39 | test.Error("[getters.go]", "[record]", "Record() yields an error on valid key query.")
40 | }
41 |
42 | if !equal(record.Key(), key) {
43 | test.Error("[getters.go]", "[record]", "Record() returns a record with wrong key.")
44 | }
45 |
46 | if (index < 512) && !(record.Match()) {
47 | test.Error("[getters.go]", "[record]", "Record() yields a non-matching record on existing key.")
48 | }
49 |
50 | if (index >= 512) && record.Match() {
51 | test.Error("[getters.go]", "[record]", "Record() yields a matching record on non-existing key.")
52 | }
53 | }
54 |
55 | collection.scope.None()
56 | collection.Collect()
57 |
58 | _, err := collection.Get([]byte("mykey")).Record()
59 |
60 | if err == nil {
61 | test.Error("[getters.go]", "[record]", "Record() does not yield an error on unknown collection.")
62 | }
63 | }
64 |
65 | func TestGettersProof(test *testing.T) {
66 | collection := New()
67 |
68 | for index := 0; index < 512; index++ {
69 | key := make([]byte, 8)
70 | binary.BigEndian.PutUint64(key, uint64(index))
71 |
72 | collection.Add(key)
73 | }
74 |
75 | for index := 0; index < 1024; index++ {
76 | key := make([]byte, 8)
77 | binary.BigEndian.PutUint64(key, uint64(index))
78 |
79 | proof, err := collection.Get(key).Proof()
80 |
81 | if err != nil {
82 | test.Error("[getters.go]", "[proof]", "Proof() yields an error on valid key query.")
83 | }
84 |
85 | if !equal(proof.Key, key) {
86 | test.Error("[getters.go]", "[proof]", "Proof() returns a record with wrong key.")
87 | }
88 |
89 | if (index < 512) && !(proof.Match()) {
90 | test.Error("[getters.go]", "[proof]", "Proof() yields a non-matching record on existing key.")
91 | }
92 |
93 | if (index >= 512) && proof.Match() {
94 | test.Error("[getters.go]", "[proof]", "Proof() yields a matching record on non-existing key.")
95 | }
96 |
97 | if proof.collection != &collection {
98 | test.Error("[getters.go]", "[proof]", "Proof() returns proof with wrong collection pointer.")
99 | }
100 |
101 | if proof.Root.Label != collection.root.label {
102 | test.Error("[getters.go]", "[proof]", "Proof() returns a proof with wrong root.")
103 | }
104 |
105 | if !(proof.Root.consistent()) {
106 | test.Error("[getters.go]", "[proof]", "Proof() returns a proof with inconsistent root.")
107 | }
108 |
109 | if len(proof.Steps) == 0 {
110 | test.Error("[getters.go]", "[proof]", "Proof() returns a proof with no steps.")
111 | }
112 |
113 | if (proof.Steps[0].Left.Label != proof.Root.Children.Left) || (proof.Steps[0].Right.Label != proof.Root.Children.Right) {
114 | test.Error("[getters.go]", "[proof]", "Label mismatch between root children and first step.")
115 | }
116 |
117 | path := sha256.Sum256(key)
118 |
119 | for depth := 0; depth < len(proof.Steps)-1; depth++ {
120 | if !(proof.Steps[depth].Left.consistent()) || !(proof.Steps[depth].Right.consistent()) {
121 | test.Error("[getters.go]", "[proof]", "Inconsistent step.")
122 | }
123 |
124 | if bit(path[:], depth) {
125 | if (proof.Steps[depth].Right.Children.Left != proof.Steps[depth+1].Left.Label) || (proof.Steps[depth].Right.Children.Right != proof.Steps[depth+1].Right.Label) {
126 | test.Error("[getters.go]", "[proof]", "Step label mismatch given path.")
127 | }
128 | } else {
129 | if (proof.Steps[depth].Left.Children.Left != proof.Steps[depth+1].Left.Label) || (proof.Steps[depth].Left.Children.Right != proof.Steps[depth+1].Right.Label) {
130 | test.Error("[getters.go]", "[proof]", "Step label mismatch given path.")
131 | }
132 | }
133 | }
134 |
135 | if !(proof.Steps[len(proof.Steps)-1].Left.consistent()) || !(proof.Steps[len(proof.Steps)-1].Right.consistent()) {
136 | test.Error("[getters.go]", "[proof]", "Last inconsistent step.")
137 | }
138 | }
139 |
140 | collection.scope.Add([]byte{0xff}, 1)
141 | collection.Collect()
142 |
143 | for index := 0; index < 512; index++ {
144 | key := make([]byte, 8)
145 | binary.BigEndian.PutUint64(key, uint64(index))
146 |
147 | path := sha256.Sum256(key)
148 |
149 | if bit(path[:], 0) {
150 | continue
151 | }
152 |
153 | _, err := collection.Get(key).Proof()
154 | if err == nil {
155 | test.Error("[getters.go]", "[proof]", "Proof() does not yield an error when querying an unknown subtree.")
156 | }
157 | }
158 |
159 | collection.scope.None()
160 | collection.Collect()
161 |
162 | _, err := collection.Get([]byte("mykey")).Proof()
163 | if err == nil {
164 | test.Error("[getters.go]", "[proof]", "Proof() does not yield an error when querying a tree with unknown root.")
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/omniledger/collection/navigators.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "errors"
4 |
5 | // Navigator is an object representing a search of a field's value on the collection.
6 | // It allows to get the record associated with a given value, as searched by the Navigate function of the field.
7 | type Navigator struct {
8 | collection *Collection
9 | field int
10 | query []byte
11 | }
12 |
13 | // Constructors
14 |
15 | // Navigate creates a Navigator associated with a given field and value.
16 | func (c *Collection) Navigate(field int, value interface{}) Navigator {
17 | if (field < 0) || (field >= len(c.fields)) {
18 | panic("Field unknown.")
19 | }
20 |
21 | return Navigator{c, field, c.fields[field].Encode(value)}
22 | }
23 |
24 | // Methods
25 |
26 | // Record returns the Record obtained by navigating the tree to the searched field's value.
27 | // It returns an error if the value in question is in an unknown subtree or if the Navigate function of the field returns an error.
28 | func (n Navigator) Record() (Record, error) {
29 | cursor := n.collection.root
30 |
31 | for {
32 | if !(cursor.known) {
33 | return Record{}, errors.New("record lies in an unknown subtree")
34 | }
35 |
36 | if cursor.leaf() {
37 | return recordQueryMatch(n.collection, n.field, n.query, cursor), nil
38 | }
39 | if !(cursor.children.left.known) || !(cursor.children.right.known) {
40 | return Record{}, errors.New("record lies in an unknown subtree")
41 | }
42 |
43 | navigation, err := n.collection.fields[n.field].Navigate(n.query, cursor.values[n.field], cursor.children.left.values[n.field], cursor.children.right.values[n.field])
44 | if err != nil {
45 | return Record{}, err
46 | }
47 |
48 | if navigation == Right {
49 | cursor = cursor.children.right
50 | } else {
51 | cursor = cursor.children.left
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/omniledger/collection/navigators_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/binary"
6 | "sort"
7 | "testing"
8 | )
9 |
10 | func TestNavigatorsConstructors(test *testing.T) {
11 | ctx := testCtx("[navigators.go]", test)
12 |
13 | stake64 := Stake64{}
14 | data := Data{}
15 |
16 | collection := New(stake64, data, stake64)
17 | navigator := collection.Navigate(2, uint64(14))
18 |
19 | if navigator.collection != &collection {
20 | test.Error("[navigators.go]", "[constructors]", "Navigator constructor sets wrong collection pointer.")
21 | }
22 |
23 | if navigator.field != 2 {
24 | test.Error("[navigators.go]", "[constructors]", "Navigator constructor sets wrong field number.")
25 | }
26 |
27 | if !equal(navigator.query, stake64.Encode(uint64(14))) {
28 | test.Error("[navigators.go]", "[constructors]", "Navigator constructor sets wrong field value.")
29 | }
30 |
31 | ctx.shouldPanic("[constructors]", func() {
32 | collection.Navigate(3, uint64(14))
33 | })
34 |
35 | ctx.shouldPanic("[constructors]", func() {
36 | collection.Navigate(-1, uint64(14))
37 | })
38 |
39 | ctx.shouldPanic("[constructors]", func() {
40 | collection.Navigate(1, "wrongtype")
41 | })
42 | }
43 |
44 | func TestNavigatorsRecord(test *testing.T) {
45 | stake64 := Stake64{}
46 | collection := New(stake64)
47 |
48 | entries := make([]int, 512)
49 |
50 | for index := 0; index < 512; index++ {
51 | entries[index] = index
52 | key := make([]byte, 8)
53 | binary.BigEndian.PutUint64(key, uint64(index))
54 | collection.Add(key, uint64(index))
55 | }
56 |
57 | sort.Slice(entries, func(i int, j int) bool {
58 | keyi := make([]byte, 8)
59 | keyj := make([]byte, 8)
60 |
61 | binary.BigEndian.PutUint64(keyi, uint64(entries[i]))
62 | binary.BigEndian.PutUint64(keyj, uint64(entries[j]))
63 |
64 | pathi := sha256.Sum256(keyi)
65 | pathj := sha256.Sum256(keyj)
66 |
67 | for index := 0; index < sha256.Size; index++ {
68 | if pathi[index] < pathj[index] {
69 | return true
70 | } else if pathi[index] > pathj[index] {
71 | return false
72 | }
73 | }
74 |
75 | return false
76 | })
77 |
78 | query := uint64(0)
79 |
80 | for index := 0; index < 512; index++ {
81 | for stake := 0; stake < entries[index]; stake++ {
82 | record, err := collection.Navigate(0, query).Record()
83 |
84 | if err != nil {
85 | test.Error("[navigators.go]", "[record]", "Navigation fails on valid query.")
86 | }
87 |
88 | values, _ := record.Values()
89 | if int(values[0].(uint64)) != entries[index] {
90 | test.Error("[navigators.go]", "[record]", "Navigation yields wrong record.")
91 | }
92 |
93 | query++
94 | }
95 | }
96 |
97 | rootvalue, _ := stake64.Decode(collection.root.values[0])
98 | _, err := collection.Navigate(0, rootvalue.(uint64)+1).Record()
99 |
100 | if err == nil {
101 | test.Error("[navigators.go]", "[record]", "Navigation does not yield an error on invalid query.")
102 | }
103 |
104 | collection.root.children.left.known = false
105 |
106 | _, err = collection.Navigate(0, uint64(0)).Record()
107 |
108 | if err == nil {
109 | test.Error("[navigators.go]", "[record]", "Navigation does not yield an error on unknown subtree.")
110 | }
111 |
112 | collection.scope.None()
113 | collection.Collect()
114 |
115 | _, err = collection.Navigate(0, uint64(0)).Record()
116 |
117 | if err == nil {
118 | test.Error("[navigators.go]", "[record]", "Navigation does not yield an error on unknown tree.")
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/omniledger/collection/node.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "crypto/sha256"
4 |
5 | //A node represents one element of the Merkle tree like data-structure.
6 | type node struct {
7 | label [sha256.Size]byte
8 |
9 | known bool
10 |
11 | transaction struct {
12 | inconsistent bool
13 | backup *node
14 | }
15 |
16 | key []byte
17 | values [][]byte
18 |
19 | parent *node
20 | children struct {
21 | left *node
22 | right *node
23 | }
24 | }
25 |
26 | // Getters
27 |
28 | func (n *node) root() bool {
29 | return n.parent == nil
30 | }
31 |
32 | func (n *node) leaf() bool {
33 | return n.children.left == nil
34 | }
35 |
36 | func (n *node) placeholder() bool {
37 | return n.leaf() && (len(n.key) == 0)
38 | }
39 |
40 | // Methods
41 |
42 | func (n *node) backup() {
43 | if n.transaction.backup == nil {
44 | n.transaction.backup = new(node)
45 |
46 | n.transaction.backup.label = n.label
47 | n.transaction.backup.known = n.known
48 | n.transaction.backup.transaction.inconsistent = n.transaction.inconsistent
49 |
50 | n.transaction.backup.key = n.key
51 | n.transaction.backup.values = make([][]byte, len(n.values))
52 | copy(n.transaction.backup.values, n.values)
53 |
54 | n.transaction.backup.parent = n.parent
55 | n.transaction.backup.children.left = n.children.left
56 | n.transaction.backup.children.right = n.children.right
57 | }
58 | }
59 |
60 | func (n *node) restore() {
61 | if n.transaction.backup != nil {
62 | backup := n.transaction.backup
63 | (*n) = (*backup)
64 | n.transaction.backup = nil
65 | }
66 | }
67 |
68 | func (n *node) branch() {
69 | n.children.left = new(node)
70 | n.children.right = new(node)
71 |
72 | n.children.left.parent = n
73 | n.children.right.parent = n
74 | }
75 |
76 | func (n *node) prune() {
77 | n.children.left = nil
78 | n.children.right = nil
79 | }
80 |
--------------------------------------------------------------------------------
/omniledger/collection/node_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "testing"
4 |
5 | func TestNodeGetters(test *testing.T) {
6 | root := node{}
7 | left := node{}
8 | right := node{}
9 |
10 | root.children.left = &left
11 | root.children.right = &right
12 |
13 | left.parent = &root
14 | right.parent = &root
15 |
16 | if !(root.root()) {
17 | test.Error("[node.go]", "[root]", "root() returns false on root node.")
18 | }
19 |
20 | if left.root() || right.root() {
21 | test.Error("[node.go]", "[root]", "root() returns true on children nodes.")
22 | }
23 |
24 | if root.leaf() {
25 | test.Error("[node.go]", "[leaf]", "leaf() returns true on non-leaf node.")
26 | }
27 |
28 | if !(left.leaf()) || !(right.leaf()) {
29 | test.Error("[node.go]", "[leaf]", "leaf() returns false on leaf node.")
30 | }
31 |
32 | if root.placeholder() {
33 | test.Error("[node.go]", "[placeholder]", "placeholder() returns true on non-leaf node.")
34 | }
35 |
36 | if !(left.placeholder()) || !(right.placeholder()) {
37 | test.Error("[node.go]", "[placeholder]", "placeholder() returns false on placeholder node.")
38 | }
39 |
40 | left.key = []byte("leftkey")
41 | right.key = []byte("rightkey")
42 |
43 | if left.placeholder() || right.placeholder() {
44 | test.Error("[node.go]", "[placeholder]", "placeholder() returns true on non-placeholder leaf node.")
45 | }
46 | }
47 |
48 | func TestNodeBackupRestore(test *testing.T) {
49 | root := node{}
50 | root.key = []byte("mykey")
51 |
52 | root.label[0] = 11
53 | root.label[1] = 12
54 |
55 | root.children.left = new(node)
56 | root.children.right = new(node)
57 | root.children.left.key = []byte("leftkey")
58 | root.children.right.key = []byte("rightkey")
59 |
60 | root.backup()
61 |
62 | if root.transaction.backup == nil {
63 | test.Error("[node.go]", "[backup]", "backup() has no effect on non-previously backed up node.")
64 | }
65 |
66 | if !equal(root.transaction.backup.key, root.key) || (root.transaction.backup.label[0] != 11) || (root.transaction.backup.label[1] != 12) {
67 | test.Error("[node.go]", "[backup]", "backup() doesn't properly copy data to the backup item.")
68 | }
69 |
70 | root.key = []byte("myotherkey")
71 | root.label[0] = 0
72 | root.label[1] = 0
73 |
74 | root.children.left = nil
75 | root.children.right = nil
76 |
77 | root.backup()
78 |
79 | if equal(root.transaction.backup.key, root.key) || (root.transaction.backup.label[0] == 0) || (root.transaction.backup.label[1] == 0) {
80 | test.Error("[node.go]", "[backup]", "backup() is run again on a node that was already backed up.")
81 | }
82 |
83 | root.restore()
84 |
85 | if !equal(root.key, []byte("mykey")) || (root.label[0] != 11) || (root.label[1] != 12) {
86 | test.Error("[node.go]", "[restore]", "restore() does not restore values on previously backed up node.")
87 | }
88 |
89 | if root.transaction.backup != nil {
90 | test.Error("[node.go]", "[restore]", "restore() does not remove previous backup.")
91 | }
92 |
93 | if (root.children.left == nil) || (root.children.right == nil) {
94 | test.Error("[node.go]", "[restore]", "restore() does not restore children.")
95 | }
96 |
97 | if !equal(root.children.left.key, []byte("leftkey")) || !equal(root.children.right.key, []byte("rightkey")) {
98 | test.Error("[node.go]", "[restore]", "restore() does not correctly restore children.")
99 | }
100 | }
101 |
102 | func TestNodeBranchPrune(test *testing.T) {
103 | root := node{}
104 | root.branch()
105 |
106 | if (root.children.left == nil) || (root.children.right == nil) {
107 | test.Error("[node.go]", "[branch]", "Branch does not produce new children on a node.")
108 | }
109 |
110 | if (root.children.left.parent != &root) || (root.children.right.parent != &root) {
111 | test.Error("[node.go]", "[branch]", "Branch does not properly set children's parent.")
112 | }
113 |
114 | root.prune()
115 |
116 | if (root.children.left != nil) || (root.children.right != nil) {
117 | test.Error("[node.go]", "[prune]", "Prune does not remove a node's children.")
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/omniledger/collection/proof.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "crypto/sha256"
5 | "errors"
6 |
7 | "github.com/dedis/protobuf"
8 | )
9 |
10 | // dump
11 |
12 | type dump struct {
13 | Key []byte `protobuf:"opt"`
14 | Values [][]byte `protobuf:"opt"`
15 |
16 | Children struct {
17 | Left [sha256.Size]byte
18 | Right [sha256.Size]byte
19 | }
20 |
21 | Label [sha256.Size]byte
22 | }
23 |
24 | // Constructors
25 |
26 | func dumpNode(node *node) (dump dump) {
27 | dump.Label = node.label
28 | dump.Values = node.values
29 |
30 | if node.leaf() {
31 | dump.Key = node.key
32 | } else {
33 | dump.Children.Left = node.children.left.label
34 | dump.Children.Right = node.children.right.label
35 | }
36 |
37 | return
38 | }
39 |
40 | // Getters
41 |
42 | func (d *dump) leaf() bool {
43 | var empty [sha256.Size]byte
44 | return (d.Children.Left == empty) && (d.Children.Right == empty)
45 | }
46 |
47 | // Methods
48 |
49 | func (d *dump) consistent() bool {
50 | var toEncode toHash
51 | if d.leaf() {
52 | toEncode = toHash{true, d.Key, d.Values, [sha256.Size]byte{}, [sha256.Size]byte{}}
53 | } else {
54 | toEncode = toHash{false, []byte{}, d.Values, d.Children.Left, d.Children.Right}
55 | }
56 |
57 | return d.Label == toEncode.hash()
58 | }
59 |
60 | func (d *dump) to(node *node) {
61 | if !(node.known) && (node.label == d.Label) {
62 | node.known = true
63 | node.label = d.Label
64 | node.values = d.Values
65 |
66 | if d.leaf() {
67 | node.key = d.Key
68 | } else {
69 | node.branch()
70 |
71 | node.children.left.known = false
72 | node.children.right.known = false
73 |
74 | node.children.left.label = d.Children.Left
75 | node.children.right.label = d.Children.Right
76 | }
77 | }
78 | }
79 |
80 | // step
81 |
82 | type step struct {
83 | Left dump
84 | Right dump
85 | }
86 |
87 | // Proof
88 |
89 | // Proof is an object representing the proof of presence or absence of a given key in a collection.
90 | type Proof struct {
91 | Key []byte // Key is the key that this proof is representing
92 | Root dump // Root is the root node
93 | Steps []step // Steps are the steps to go from root to key
94 |
95 | collection *Collection
96 | }
97 |
98 | // Getters
99 |
100 | // TreeRootHash returns the hash of the merkle tree root.
101 | func (p Proof) TreeRootHash() []byte {
102 | return p.Root.Key
103 | }
104 |
105 | // Methods
106 |
107 | //Match returns true if the Proof asserts the presence of the key in the collection
108 | // and false if it asserts its absence.
109 | func (p Proof) Match() bool {
110 | if len(p.Steps) == 0 {
111 | return false
112 | }
113 |
114 | path := sha256.Sum256(p.Key)
115 | depth := len(p.Steps) - 1
116 |
117 | if bit(path[:], depth) {
118 | return equal(p.Key, p.Steps[depth].Right.Key)
119 | }
120 | return equal(p.Key, p.Steps[depth].Left.Key)
121 | }
122 |
123 | // RawValues returns the raw values stored in the proof. This can be used if
124 | // not the whole collection is present in the proof.
125 | func (p Proof) RawValues() ([][]byte, error) {
126 | if len(p.Steps) == 0 {
127 | return [][]byte{}, errors.New("proof has no steps")
128 | }
129 |
130 | path := sha256.Sum256(p.Key)
131 | depth := len(p.Steps) - 1
132 |
133 | match := false
134 | var rawValues [][]byte
135 |
136 | if bit(path[:], depth) {
137 | if equal(p.Key, p.Steps[depth].Right.Key) {
138 | match = true
139 | rawValues = p.Steps[depth].Right.Values
140 | }
141 | } else {
142 | if equal(p.Key, p.Steps[depth].Left.Key) {
143 | match = true
144 | rawValues = p.Steps[depth].Left.Values
145 | }
146 | }
147 |
148 | if !match {
149 | return [][]byte{}, errors.New("no match found")
150 | }
151 |
152 | return rawValues, nil
153 | }
154 |
155 | // Values returns a copy of the values of the key which presence is proved by the Proof.
156 | // It returns an error if the Proof proves the absence of the key.
157 | func (p Proof) Values() ([]interface{}, error) {
158 | rawValues, err := p.RawValues()
159 | if err != nil {
160 | return []interface{}{}, err
161 | }
162 | if len(rawValues) != len(p.collection.fields) {
163 | return []interface{}{}, errors.New("wrong number of values")
164 | }
165 |
166 | var values []interface{}
167 |
168 | for index := 0; index < len(rawValues); index++ {
169 | value, err := p.collection.fields[index].Decode(rawValues[index])
170 |
171 | if err != nil {
172 | return []interface{}{}, err
173 | }
174 |
175 | values = append(values, value)
176 | }
177 |
178 | return values, nil
179 | }
180 |
181 | // Consistent returns true if the given proof is correct, that is, if it is
182 | // a valid representation and all steps are valid.
183 | func (p Proof) Consistent() bool {
184 | if len(p.Steps) == 0 {
185 | return false
186 | }
187 |
188 | if !(p.Root.consistent()) {
189 | return false
190 | }
191 |
192 | cursor := &(p.Root)
193 | path := sha256.Sum256(p.Key)
194 |
195 | for depth := 0; depth < len(p.Steps); depth++ {
196 | if (cursor.Children.Left != p.Steps[depth].Left.Label) || (cursor.Children.Right != p.Steps[depth].Right.Label) {
197 | return false
198 | }
199 |
200 | if !(p.Steps[depth].Left.consistent()) || !(p.Steps[depth].Right.consistent()) {
201 | return false
202 | }
203 |
204 | if bit(path[:], depth) {
205 | cursor = &(p.Steps[depth].Right)
206 | } else {
207 | cursor = &(p.Steps[depth].Left)
208 | }
209 | }
210 |
211 | return cursor.leaf()
212 | }
213 |
214 | // collection
215 |
216 | // Methods (collection) (serialization)
217 |
218 | // Serialize serialize a proof.
219 | // It transforms a given Proof into an array of byte, to allow easy exchange of proof, for example on a network.
220 | func (c *Collection) Serialize(proof Proof) []byte {
221 | serializable := struct {
222 | Key []byte
223 | Root dump
224 | Steps []step
225 | }{proof.Key, proof.Root, proof.Steps}
226 |
227 | buffer, _ := protobuf.Encode(&serializable)
228 | return buffer
229 | }
230 |
231 | // Deserialize is the inverse of serialize.
232 | // It tansforms back a byte representation of a proof to a Proof object.
233 | // It will generate an error if the given byte array doesn't represent a Proof.
234 | func (c *Collection) Deserialize(buffer []byte) (Proof, error) {
235 | deserializable := struct {
236 | Key []byte
237 | Root dump
238 | Steps []step
239 | }{}
240 |
241 | err := protobuf.Decode(buffer, &deserializable)
242 |
243 | if err != nil {
244 | return Proof{}, err
245 | }
246 |
247 | return Proof{deserializable.Key, deserializable.Root, deserializable.Steps, c}, nil
248 | }
249 |
--------------------------------------------------------------------------------
/omniledger/collection/record.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "errors"
4 |
5 | // Record holds the result of a search query.
6 | // It has getters to see the query, if the search was successful, the key and the value.
7 | type Record struct {
8 | collection *Collection
9 |
10 | field int
11 | query []byte
12 | match bool //if matched the query
13 |
14 | key []byte
15 | values [][]byte
16 | }
17 |
18 | // Constructors
19 |
20 | func recordKeyMatch(collection *Collection, node *node) Record {
21 | return Record{collection, 0, []byte{}, true, node.key, node.values}
22 | }
23 |
24 | func recordQueryMatch(collection *Collection, field int, query []byte, node *node) Record {
25 | return Record{collection, field, query, true, node.key, node.values}
26 | }
27 |
28 | func recordKeyMismatch(collection *Collection, key []byte) Record {
29 | return Record{collection, 0, []byte{}, false, key, [][]byte{}}
30 | }
31 |
32 | // Getters
33 |
34 | // Query returns the original query, decoded, that generated the record.
35 | // It returns an error if the record was generated from a getter (key search).
36 | func (r Record) Query() (interface{}, error) {
37 | if len(r.query) == 0 {
38 | return nil, errors.New("no query specified")
39 | }
40 |
41 | if len(r.values) <= r.field {
42 | return nil, errors.New("field out of range")
43 | }
44 |
45 | value, err := r.collection.fields[r.field].Decode(r.query)
46 |
47 | if err != nil {
48 | return nil, err
49 | }
50 |
51 | return value, nil
52 | }
53 |
54 | // Match returns true if the record match the query that generated it, and false otherwise.
55 | func (r Record) Match() bool {
56 | return r.match
57 | }
58 |
59 | // Key returns the key of the record
60 | func (r Record) Key() []byte {
61 | return r.key
62 | }
63 |
64 | // Values returns a copy of the values of a record.
65 | // If the record didn't match the query, an error will be returned.
66 | func (r Record) Values() ([]interface{}, error) {
67 | if !(r.match) {
68 | return []interface{}{}, errors.New("no match found")
69 | }
70 |
71 | if len(r.values) != len(r.collection.fields) {
72 | return []interface{}{}, errors.New("wrong number of values")
73 | }
74 |
75 | var values []interface{}
76 |
77 | for index := 0; index < len(r.values); index++ {
78 | value, err := r.collection.fields[index].Decode(r.values[index])
79 |
80 | if err != nil {
81 | return []interface{}{}, err
82 | }
83 |
84 | values = append(values, value)
85 | }
86 |
87 | return values, nil
88 | }
89 |
--------------------------------------------------------------------------------
/omniledger/collection/record_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "github.com/stretchr/testify/require"
5 | "testing"
6 | )
7 |
8 | func TestRecord(test *testing.T) {
9 | stake64 := Stake64{}
10 | data := Data{}
11 |
12 | collection := New(stake64, data)
13 | collection.Add([]byte("mykey"), uint64(66), []byte("mydata"))
14 |
15 | var leaf *node
16 |
17 | if collection.root.children.left.placeholder() {
18 | leaf = collection.root.children.right
19 | } else {
20 | leaf = collection.root.children.left
21 | }
22 |
23 | keyMatch := recordKeyMatch(&collection, leaf)
24 | queryMatch := recordQueryMatch(&collection, 0, stake64.Encode(uint64(99)), leaf)
25 | keyMismatch := recordKeyMismatch(&collection, []byte("wrongkey"))
26 |
27 | if (keyMatch.collection != &collection) || (queryMatch.collection != &collection) || (keyMismatch.collection != &collection) {
28 | test.Error("[record.go]", "[constructors]", "Constructors don't set collection appropriately.")
29 | }
30 |
31 | if !(keyMatch.match) || !(queryMatch.match) || keyMismatch.match {
32 | test.Error("[record.go]", "[constructors]", "Constructors don't set match appropriately.")
33 | }
34 |
35 | if !equal(keyMatch.key, []byte("mykey")) || !equal(queryMatch.key, []byte("mykey")) || !equal(keyMismatch.key, []byte("wrongkey")) {
36 | test.Error("[record.go]", "[constructors]", "Constructors don't set key appropriately")
37 | }
38 |
39 | if len(keyMatch.values) != 2 || len(queryMatch.values) != 2 || len(keyMismatch.values) != 0 {
40 | test.Error("[record.go]", "[constructors]", "Constructors don't set the appropriate number of values.")
41 | }
42 |
43 | if !equal(keyMatch.values[0], leaf.values[0]) || !equal(keyMatch.values[1], leaf.values[1]) || !equal(queryMatch.values[0], leaf.values[0]) || !equal(queryMatch.values[1], leaf.values[1]) {
44 | test.Error("[record.go]", "[constructors]", "Constructors set the wrong values.")
45 | }
46 |
47 | if !(keyMatch.Match()) || !(queryMatch.Match()) || keyMismatch.Match() {
48 | test.Error("[record.go]", "[match]", "Match() returns the wrong value.")
49 | }
50 |
51 | if !equal(keyMatch.Key(), []byte("mykey")) || !equal(queryMatch.Key(), []byte("mykey")) || !equal(keyMismatch.Key(), []byte("wrongkey")) {
52 | test.Error("[record.go]", "[key]", "Key() returns the wrong value.")
53 | }
54 |
55 | keyMatchValues, keyMatchError := keyMatch.Values()
56 | queryMatchValues, queryMatchError := queryMatch.Values()
57 |
58 | if (keyMatchError != nil) || (queryMatchError != nil) {
59 | test.Error("[record.go]", "[values]", "Values() yields an error on matching record.")
60 | }
61 |
62 | if (len(keyMatchValues) != 2) || (len(queryMatchValues) != 2) {
63 | test.Error("[record.go]", "[values]", "Values() returns the wrong number of values")
64 | }
65 |
66 | if (keyMatchValues[0].(uint64) != 66) || !equal(keyMatchValues[1].([]byte), leaf.values[1]) || (queryMatchValues[0].(uint64) != 66) || !equal(queryMatchValues[1].([]byte), leaf.values[1]) {
67 | test.Error("[record.go]", "[values]", "Values() returns the wrong values.")
68 | }
69 |
70 | _, keymismatcherror := keyMismatch.Values()
71 |
72 | if keymismatcherror == nil {
73 | test.Error("[record.go]", "[values]", "Values() does not yield an error on mismatching record.")
74 | }
75 |
76 | keyMatch.values[0] = keyMatch.values[0][:6]
77 | queryMatch.values[0] = queryMatch.values[0][:6]
78 |
79 | _, keyIllFormedError := keyMatch.Values()
80 | _, queryIllFormedError := queryMatch.Values()
81 |
82 | if (keyIllFormedError == nil) || (queryIllFormedError) == nil {
83 | test.Error("[record.go]", "[values]", "Values() does not yield an error on record with ill-formed values.")
84 | }
85 |
86 | keyMatch.values = keyMatch.values[:1]
87 | queryMatch.values = queryMatch.values[:1]
88 |
89 | _, keyFewError := keyMatch.Values()
90 | _, queryFewError := queryMatch.Values()
91 |
92 | if (keyFewError == nil) || (queryFewError == nil) {
93 | test.Error("[record.go]", "[values]", "Values() does not yield an error on record with wrong number of values.")
94 | }
95 |
96 | _, keyMatchQueryError := keyMatch.Query()
97 |
98 | if keyMatchQueryError == nil {
99 | test.Error("[record.go]", "[query]", "Query() does not yield an error on a record without query.")
100 | }
101 |
102 | queryMatchQuery, queryMatchQueryError := queryMatch.Query()
103 |
104 | if queryMatchQueryError != nil {
105 | test.Error("[record.go]", "[query]", "Query() yields an error on a valid record with query.")
106 | }
107 |
108 | if queryMatchQuery.(uint64) != 99 {
109 | test.Error("[record.go]", "[query]", "Query() returns wrong query.")
110 | }
111 |
112 | queryMatch.field = 48
113 |
114 | _, queryMatchQueryError = queryMatch.Query()
115 |
116 | if queryMatchQueryError == nil {
117 | test.Error("[record.go]", "[query]", "Query() does not yield an error when field is out of range.")
118 | }
119 |
120 | queryMatch.field = 0
121 | queryMatch.query = queryMatch.query[:6]
122 |
123 | _, queryMatchQueryError = queryMatch.Query()
124 |
125 | if queryMatchQueryError == nil {
126 | test.Error("[record.go]", "[query]", "Query() does not yield an error when query is malformed.")
127 | }
128 | }
129 |
130 | func TestRecordEmpty(test *testing.T) {
131 | collection := New(Data{})
132 |
133 | _, err := collection.Get([]byte{}).Record()
134 | require.NotNil(test, err)
135 | }
136 |
--------------------------------------------------------------------------------
/omniledger/collection/scope.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "crypto/sha256"
4 |
5 | // mask
6 |
7 | type mask struct {
8 | value []byte
9 | bits int
10 | }
11 |
12 | // Private methods
13 |
14 | func (m *mask) match(path [sha256.Size]byte, bits int) bool {
15 | if bits < m.bits {
16 | return match(path[:], m.value, bits)
17 | }
18 | return match(path[:], m.value, m.bits)
19 | }
20 |
21 | // scope
22 |
23 | type scope struct {
24 | masks []mask
25 | all bool
26 | }
27 |
28 | // Methods
29 |
30 | func (s *scope) All() {
31 | s.all = true
32 | s.masks = []mask{}
33 | }
34 |
35 | func (s *scope) None() {
36 | s.all = false
37 | s.masks = []mask{}
38 | }
39 |
40 | func (s *scope) Add(value []byte, bits int) {
41 | s.masks = append(s.masks, mask{value, bits})
42 | }
43 |
44 | // Private methods
45 |
46 | func (s *scope) match(path [sha256.Size]byte, bits int) bool {
47 | if len(s.masks) == 0 {
48 | return s.all
49 | }
50 |
51 | for index := 0; index < len(s.masks); index++ {
52 | if s.masks[index].match(path, bits) {
53 | return true
54 | }
55 | }
56 |
57 | return false
58 | }
59 |
60 | func (s *scope) clone() (scope scope) {
61 | scope.masks = make([]mask, len(s.masks))
62 | copy(scope.masks, s.masks)
63 | scope.all = s.all
64 |
65 | return
66 | }
67 |
--------------------------------------------------------------------------------
/omniledger/collection/scope_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "testing"
4 | import "encoding/hex"
5 |
6 | func TestScopeMask(test *testing.T) {
7 | type round struct {
8 | path string
9 | mask string
10 | bits int
11 | expected bool
12 | }
13 |
14 | rounds := []round{
15 | {"85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 256, true},
16 | {"85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 25, true},
17 | {"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 252, true},
18 | {"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 253, false},
19 | {"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0, true},
20 | {"85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906390", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 252, true},
21 | {"85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906390", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 254, false},
22 | {"85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906390", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 2, true},
23 | {"85f46bd1ba1ad1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 46, true},
24 | {"85f46bd1ba1ad1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 47, false},
25 | {"85f46bd1ba1ad1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 45, true},
26 | {"85f46bd1ba18d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 47, true},
27 | {"85f46bd1ba18d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 48, false},
28 | {"85f46bd1ba18d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", "85f46bd1ba19d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398", 1, true},
29 | }
30 |
31 | for _, round := range rounds {
32 | maskvalue, _ := hex.DecodeString(round.mask)
33 | mask := mask{maskvalue, round.bits}
34 |
35 | pathslice, _ := hex.DecodeString(round.path)
36 | path := digest(pathslice)
37 |
38 | if mask.match(path, 256) != round.expected {
39 | test.Error("[scope.go]", "[match]", "Wrong match on reference round.")
40 | }
41 |
42 | if !(mask.match(path, 0)) {
43 | test.Error("[scope.go]", "[match]", "No match on zero-bit match.")
44 | }
45 | }
46 | }
47 |
48 | func TestScopeMethods(test *testing.T) {
49 | scope := scope{}
50 |
51 | value, _ := hex.DecodeString("1234567890")
52 | scope.Add(value, 3)
53 |
54 | if len(scope.masks) != 1 {
55 | test.Error("[scope.go]", "[add]", "Add does not add to masks.")
56 | }
57 |
58 | if !match(scope.masks[0].value, value, 24) || (scope.masks[0].bits != 3) {
59 | test.Error("[scope.go]", "[add]", "Add adds wrong mask.")
60 | }
61 |
62 | value, _ = hex.DecodeString("0987654321")
63 | scope.Add(value, 40)
64 |
65 | if len(scope.masks) != 2 {
66 | test.Error("[scope.go]", "[add]", "Add does not add to masks.")
67 | }
68 |
69 | if !match(scope.masks[1].value, value, 40) || (scope.masks[1].bits != 40) {
70 | test.Error("[scope.go]", "[add]", "Add adds wrong mask.")
71 | }
72 |
73 | scope.All()
74 |
75 | if len(scope.masks) != 0 {
76 | test.Error("[scope.go]", "[all]", "All does not wipe masks.")
77 | }
78 |
79 | if !(scope.all) {
80 | test.Error("[scope.go]", "[all]", "All does not properly set the all flag.")
81 | }
82 |
83 | scope.Add(value, 40)
84 | scope.None()
85 |
86 | if len(scope.masks) != 0 {
87 | test.Error("[scope.go]", "[none]", "None does not wipe masks.")
88 | }
89 |
90 | if scope.all {
91 | test.Error("[scope.go]", "[none]", "None does not properly set the all flag.")
92 | }
93 | }
94 |
95 | func TestScopeMatch(test *testing.T) {
96 | scope := scope{}
97 |
98 | pathslice, _ := hex.DecodeString("85f46bd1ba18d1014b1179edd451ece95296e4a8c765ba8bba86c16893906398")
99 | path := digest(pathslice)
100 |
101 | scope.None()
102 | if scope.match(path, 12) {
103 | test.Error("[scope.go]", "[all]", "No-mask match succeeds after None().")
104 | }
105 |
106 | scope.All()
107 | if !(scope.match(path, 13)) {
108 | test.Error("[scope.go]", "[all]", "No-mask match fails after All().")
109 | }
110 |
111 | nomatch, _ := hex.DecodeString("fa91")
112 | scope.Add(nomatch, 16)
113 |
114 | if scope.match(path, 256) {
115 | test.Error("[scope.go]", "[match]", "Scope match succeeds on a non-matching mask.")
116 | }
117 |
118 | maybematch, _ := hex.DecodeString("86")
119 | scope.Add(maybematch, 8)
120 |
121 | if scope.match(path, 256) {
122 | test.Error("[scope.go]", "[match]", "Scope match succeeds on a non-matching mask.")
123 | }
124 |
125 | if !(scope.match(path, 6)) {
126 | test.Error("[scope.go]", "[match]", "Scope match fails on a matching mask.")
127 | }
128 |
129 | scope.Add(maybematch, 6)
130 |
131 | if !(scope.match(path, 44)) {
132 | test.Error("[scope.go]", "[match]", "Scope match fails on matching mask.")
133 | }
134 | }
135 |
136 | func TestScopeClone(test *testing.T) {
137 | scope := scope{}
138 | scope.All()
139 |
140 | path, _ := hex.DecodeString("fa91")
141 | scope.Add(path, 5)
142 |
143 | path, _ = hex.DecodeString("0987654321")
144 | scope.Add(path, 37)
145 |
146 | path, _ = hex.DecodeString("1234567890")
147 | scope.Add(path, 42)
148 |
149 | clone := scope.clone()
150 |
151 | if !(clone.all) {
152 | test.Error("[scope.go]", "[clone]", "clone() does not copy all flag.")
153 | }
154 |
155 | if len(clone.masks) != len(scope.masks) {
156 | test.Error("[scope.go]", "[clone]", "clone() does not copy the correct number of masks")
157 | }
158 |
159 | for index := 0; index < len(clone.masks); index++ {
160 | if clone.masks[index].bits != scope.masks[index].bits {
161 | test.Error("[scope.go]", "[clone]", "clone() does not properly copy the number of bits in a mask.")
162 | }
163 |
164 | if !equal(clone.masks[index].value, scope.masks[index].value) {
165 | test.Error("[scope.go]", "[clone]", "clone() does not properly copy the mask value.")
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/omniledger/collection/singlenode.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "crypto/sha256"
5 | "errors"
6 | "github.com/dedis/protobuf"
7 | )
8 |
9 | type toHash struct {
10 | IsLeaf bool
11 | Key []byte
12 | Values [][]byte
13 |
14 | LeftLabel [sha256.Size]byte
15 | RightLabel [sha256.Size]byte
16 | }
17 |
18 | // Private methods (collection) (single node operations)
19 |
20 | func (c *Collection) update(node *node) error {
21 | if !(node.known) {
22 | return errors.New("updating an unknown node")
23 | }
24 |
25 | if !node.leaf() {
26 | if !(node.children.left.known) || !(node.children.right.known) {
27 | return errors.New("updating internal node with unknown children")
28 | }
29 |
30 | node.values = make([][]byte, len(c.fields))
31 |
32 | for index := 0; index < len(c.fields); index++ {
33 | parentValue, parentError := c.fields[index].Parent(node.children.left.values[index], node.children.right.values[index])
34 |
35 | if parentError != nil {
36 | return parentError
37 | }
38 |
39 | node.values[index] = parentValue
40 | }
41 | }
42 |
43 | label := node.generateHash()
44 | node.label = label
45 |
46 | return nil
47 | }
48 |
49 | func (c *Collection) setPlaceholder(node *node) error {
50 | node.known = true
51 | node.key = []byte{}
52 | node.values = make([][]byte, len(c.fields))
53 |
54 | for index := 0; index < len(c.fields); index++ {
55 | node.values[index] = c.fields[index].Placeholder()
56 | }
57 |
58 | node.children.left = nil
59 | node.children.right = nil
60 |
61 | err := c.update(node)
62 | if err != nil {
63 | return err
64 | }
65 | return nil
66 | }
67 |
68 | func (n *node) generateHash() [sha256.Size]byte {
69 |
70 | var toEncode toHash
71 | if n.leaf() {
72 | toEncode = toHash{true, n.key, n.values, [sha256.Size]byte{}, [sha256.Size]byte{}}
73 | } else {
74 | toEncode = toHash{false, []byte{}, n.values, n.children.left.label, n.children.right.label}
75 | }
76 |
77 | return toEncode.hash()
78 | }
79 |
80 | func (data *toHash) hash() [sha256.Size]byte {
81 | buff, err := protobuf.Encode(data)
82 | if err != nil {
83 | panic("couldn't encode: " + err.Error())
84 | }
85 |
86 | return sha256.Sum256(buff)
87 | }
88 |
--------------------------------------------------------------------------------
/omniledger/collection/singlenode_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "testing"
4 |
5 | func TestSingleNodeSetPlaceholder(test *testing.T) {
6 | baseCollection := New()
7 | baseNode := node{}
8 | err := baseCollection.setPlaceholder(&baseNode)
9 | if err != nil {
10 | test.Error("[singlenode.go]", "[setPlaceholder]", "SetPlaceholder returns an error:", err)
11 | }
12 |
13 | if !(baseNode.placeholder()) {
14 | test.Error("[singlenode.go]", "[placeholder]", "placeholder() does not produce a placeholder node.")
15 | }
16 |
17 | if len(baseNode.values) != 0 {
18 | test.Error("[singlenode.go]", "[values]", "Placeholder nodes of a collection without fields should not have values.")
19 | }
20 |
21 | stake64 := Stake64{}
22 |
23 | stakeCollection := New(stake64)
24 | stakeNode := node{}
25 | err = stakeCollection.setPlaceholder(&stakeNode)
26 | if err != nil {
27 | test.Error("[singlenode.go]", "[setPlaceholder]", "SetPlaceholder returns an error:", err)
28 | }
29 |
30 | if !(stakeNode.placeholder()) {
31 | test.Error("[singlenode.go]", "[placeholder]", "placeholder() does not produce a placeholder node.")
32 | }
33 |
34 | if len(stakeNode.values) != 1 {
35 | test.Error("[singlenode.go]", "[values]", "Placeholder nodes of a stake collection should have exactly one value.")
36 | }
37 |
38 | placeholderStake, _ := stake64.Decode(stakeNode.values[0])
39 | if placeholderStake.(uint64) != 0 {
40 | test.Error("[singlenode.go]", "[value]", "Placeholder nodes of a stake collection should have zero stake.")
41 | }
42 |
43 | data := Data{}
44 |
45 | stakeDataCollection := New(stake64, data)
46 | stakeDataNode := node{}
47 | err = stakeDataCollection.setPlaceholder(&stakeDataNode)
48 | if err != nil {
49 | test.Error("[singlenode.go]", "[setPlaceholder]", "setPlaceholder returns an error:", err)
50 | }
51 |
52 | if !(stakeDataNode.placeholder()) {
53 | test.Error("[singlenode.go]", "[placeholder]", "setPlaceholder() does not produce a placeholder node.")
54 | }
55 |
56 | if len(stakeDataNode.values) != 2 {
57 | test.Error("[singlenode.go]", "[values]", "Placeholder nodes of a stake and data collection should have exactly two values.")
58 | }
59 |
60 | placeholderStake, _ = stake64.Decode(stakeDataNode.values[0])
61 | if (placeholderStake.(uint64) != 0) || (len(stakeDataNode.values[1]) != 0) {
62 | test.Error("[singlenode.go]", "[value]", "Placeholder nodes of a stake and data collection should have zero stake and empty data value.")
63 | }
64 | }
65 |
66 | func TestSingleNodeUpdate(test *testing.T) {
67 | ctx := testCtx("[singlenode.go]", test)
68 |
69 | baseCollection := New()
70 |
71 | err := baseCollection.update(&node{})
72 |
73 | if err == nil {
74 | test.Error("[singlenode.go]", "[known]", "Update doesn't yield error on unknown node.")
75 | }
76 |
77 | baseCollection.root.children.left.known = false
78 | err = baseCollection.update(baseCollection.root)
79 |
80 | if err == nil {
81 | test.Error("[singlenode.go]", "[known]", "Update doesn't yield error on node with unknown children.")
82 | }
83 |
84 | stake64 := Stake64{}
85 | stakeCollection := New(stake64)
86 |
87 | stakeCollection.root.children.left.values[0] = stake64.Encode(uint64(66))
88 |
89 | if stakeCollection.update(stakeCollection.root.children.left) != nil {
90 | test.Error("[singlenode.go]", "[stake]", "Update fails on stake leaf.")
91 | }
92 |
93 | if stakeCollection.update(stakeCollection.root) != nil {
94 | test.Error("[singlenode.go]", "[stake]", "Update fails on stake root.")
95 | }
96 |
97 | rootStake, _ := stake64.Decode(stakeCollection.root.values[0])
98 |
99 | if rootStake.(uint64) != 66 {
100 | test.Error("[singlenode.go]", "[stake]", "Wrong value on stake root.")
101 | }
102 |
103 | stakeCollection.root.children.right.values[0] = stake64.Encode(uint64(33))
104 |
105 | if stakeCollection.update(stakeCollection.root.children.right) != nil {
106 | test.Error("[singlenode.go]", "[stake]", "Update fails on stake leaf.")
107 | }
108 |
109 | if stakeCollection.update(stakeCollection.root) != nil {
110 | test.Error("[singlenode.go]", "[stake]", "Update fails on stake root.")
111 | }
112 |
113 | rootStake, _ = stake64.Decode(stakeCollection.root.values[0])
114 |
115 | if rootStake.(uint64) != 99 {
116 | test.Error("[singlenode.go]", "[stake]", "Wrong value on stake root.")
117 | }
118 |
119 | ctx.verify.tree("[tree]", &stakeCollection)
120 |
121 | stakeCollection.root.children.left.values[0] = make([]byte, 5)
122 |
123 | if stakeCollection.update(stakeCollection.root) == nil {
124 | test.Error("[singlenode.go]", "[stake]", "Update() does not yield an error when updating a node with ill-formed children values.")
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/omniledger/collection/transaction.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "crypto/sha256"
4 |
5 | // Methods (collection) (transaction methods)
6 |
7 | // Begin indicates the start of a transaction.
8 | // It raises a flag preventing other transaction to take place on the same collection.
9 | func (c *Collection) Begin() {
10 | if c.transaction.ongoing {
11 | panic("Transaction already in progress.")
12 | }
13 |
14 | c.transaction.ongoing = true
15 | }
16 |
17 | // Rollback cancels the transaction.
18 | // It effectively replaces all nodes by their backup, if any and stops the transaction.
19 | func (c *Collection) Rollback() {
20 | if !(c.transaction.ongoing) {
21 | panic("Transaction not in progress")
22 | }
23 |
24 | var explore func(*node)
25 | explore = func(node *node) {
26 | if node.transaction.inconsistent || (node.transaction.backup != nil) {
27 | node.restore()
28 |
29 | if !(node.leaf()) {
30 | explore(node.children.left)
31 | explore(node.children.right)
32 | }
33 | }
34 | }
35 |
36 | explore(c.root)
37 |
38 | c.transaction.id++
39 | c.transaction.ongoing = false
40 | }
41 |
42 | // End ends the transaction.
43 | // It validates new states of nodes, fixes inconsistencies and increment the transaction id counter.
44 | func (c *Collection) End() {
45 | if !(c.transaction.ongoing) {
46 | panic("Transaction not in progress.")
47 | }
48 |
49 | c.confirm()
50 | c.fix()
51 |
52 | if c.autoCollect.value {
53 | c.Collect()
54 | }
55 |
56 | c.transaction.id++
57 | c.transaction.ongoing = false
58 | }
59 |
60 | // Collect performs the garbage collection of the nodes out of the scope.
61 | // It removes all nodes that are meant to be stored temporarily.
62 | func (c *Collection) Collect() {
63 | var explore func(*node, [sha256.Size]byte, int)
64 | explore = func(node *node, path [sha256.Size]byte, bit int) {
65 | if !(node.known) {
66 | return
67 | }
68 |
69 | if bit > 0 && !(c.scope.match(path, bit-1)) {
70 | node.known = false
71 | node.key = []byte{}
72 | node.values = [][]byte{}
73 |
74 | node.prune()
75 | } else if !(node.leaf()) {
76 | setBit(path[:], bit+1, false)
77 | explore(node.children.left, path, bit+1)
78 |
79 | setBit(path[:], bit+1, true)
80 | explore(node.children.right, path, bit+1)
81 | }
82 | }
83 |
84 | if !(c.root.known) {
85 | return
86 | }
87 |
88 | var path [sha256.Size]byte
89 | none := true
90 |
91 | setBit(path[:], 0, false)
92 | if c.scope.match(path, 0) {
93 | none = false
94 | }
95 |
96 | setBit(path[:], 0, true)
97 | if c.scope.match(path, 0) {
98 | none = false
99 | }
100 |
101 | if none {
102 | c.root.known = false
103 | c.root.key = []byte{}
104 | c.root.values = [][]byte{}
105 |
106 | c.root.prune()
107 | } else {
108 | setBit(path[:], 0, false)
109 | explore(c.root.children.left, path, 0)
110 |
111 | setBit(path[:], 0, true)
112 | explore(c.root.children.right, path, 0)
113 | }
114 | }
115 |
116 | // Private methods (collection) (transaction methods)
117 |
118 | func (c *Collection) confirm() {
119 | var explore func(*node)
120 | explore = func(node *node) {
121 | if node.transaction.inconsistent || (node.transaction.backup != nil) {
122 | node.transaction.backup = nil
123 |
124 | if !(node.leaf()) {
125 | explore(node.children.left)
126 | explore(node.children.right)
127 | }
128 | }
129 | }
130 |
131 | explore(c.root)
132 | }
133 |
134 | func (c *Collection) fix() {
135 | var explore func(*node)
136 | explore = func(node *node) {
137 | if node.transaction.inconsistent {
138 | if !(node.leaf()) {
139 | explore(node.children.left)
140 | explore(node.children.right)
141 | }
142 |
143 | c.update(node)
144 | node.transaction.inconsistent = false
145 | }
146 | }
147 |
148 | explore(c.root)
149 | }
150 |
--------------------------------------------------------------------------------
/omniledger/collection/update.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "crypto/sha256"
5 | "errors"
6 | )
7 |
8 | // Interfaces
9 |
10 | type userUpdate interface {
11 | Records() []Proof
12 | Check(ReadOnly) bool
13 | Apply(ReadWrite)
14 | }
15 |
16 | // ReadOnly defines a set of methods needed to have a read-only collection.
17 | // It contains only a getter method, as it is the only method not modifying the collection.
18 | type ReadOnly interface {
19 | Get([]byte) Record
20 | }
21 |
22 | // ReadWrite defines the set of methods needed to have a read-write collection.
23 | type ReadWrite interface {
24 | Get([]byte) Record
25 | Add([]byte, ...interface{}) error
26 | Set([]byte, ...interface{}) error
27 | SetField([]byte, int, interface{}) error
28 | Remove([]byte) error
29 | }
30 |
31 | // Structs
32 |
33 | // Update stores an update.
34 | // That is a bunch of transactions to be applied on the same collection.
35 | type Update struct {
36 | transaction uint64
37 | update userUpdate
38 | proxy proxy
39 | }
40 |
41 | // proxy to the actual collection for the getters.
42 | type proxy struct {
43 | collection *Collection
44 | paths map[[sha256.Size]byte]bool
45 | }
46 |
47 | // proxy
48 |
49 | // Constructors
50 |
51 | func (c *Collection) proxy(keys [][]byte) (proxy proxy) {
52 | proxy.collection = c
53 | proxy.paths = make(map[[sha256.Size]byte]bool)
54 |
55 | for index := 0; index < len(keys); index++ {
56 | proxy.paths[sha256.Sum256(keys[index])] = true
57 | }
58 |
59 | return
60 | }
61 |
62 | // Methods
63 |
64 | func (p proxy) Get(key []byte) Record {
65 | if !(p.has(key)) {
66 | panic("accessing undeclared key from update")
67 | }
68 |
69 | record, _ := p.collection.Get(key).Record()
70 | return record
71 | }
72 |
73 | func (p proxy) Add(key []byte, values ...interface{}) error {
74 | if !(p.has(key)) {
75 | panic("accessing undeclared key from update")
76 | }
77 |
78 | return p.collection.Add(key, values...)
79 | }
80 |
81 | func (p proxy) Set(key []byte, values ...interface{}) error {
82 | if !(p.has(key)) {
83 | panic("accessing undeclared key from update")
84 | }
85 |
86 | return p.collection.Set(key, values...)
87 | }
88 |
89 | func (p proxy) SetField(key []byte, field int, value interface{}) error {
90 | if !(p.has(key)) {
91 | panic("accessing undeclared key from update")
92 | }
93 |
94 | return p.collection.SetField(key, field, value)
95 | }
96 |
97 | func (p proxy) Remove(key []byte) error {
98 | if !(p.has(key)) {
99 | panic("accessing undeclared key from update")
100 | }
101 |
102 | return p.collection.Remove(key)
103 | }
104 |
105 | // Private methods
106 |
107 | func (p proxy) has(key []byte) bool {
108 | path := sha256.Sum256(key)
109 | return p.paths[path]
110 | }
111 |
112 | // collection
113 |
114 | // Methods (collection) (update)
115 |
116 | // Prepare prepares the userUpdate to do an update.
117 | // It checks that every proof of the userUpdate is valid
118 | // and then creates an Update object ready to apply the collection update.
119 | func (c *Collection) Prepare(update userUpdate) (Update, error) {
120 | if c.root.transaction.inconsistent {
121 | panic("prepare() called on inconsistent root")
122 | }
123 |
124 | proofs := update.Records()
125 | keys := make([][]byte, len(proofs))
126 |
127 | for index := 0; index < len(proofs); index++ {
128 | if !(c.Verify(proofs[index])) {
129 | return Update{}, errors.New("invalid update: proof invalid")
130 | }
131 |
132 | keys[index] = proofs[index].Key
133 | }
134 |
135 | return Update{c.transaction.id, update, c.proxy(keys)}, nil
136 | }
137 |
138 | // Apply applies the update on the collection.
139 | // If the update is not prepared, it will prepare it using the Prepare method.
140 | func (c *Collection) Apply(object interface{}) error {
141 | switch update := object.(type) {
142 | case Update:
143 | return c.applyUpdate(update)
144 | case userUpdate:
145 | return c.applyUserUpdate(update)
146 | }
147 |
148 | panic("apply() only accepts Update objects or objects that implement the update interface")
149 | }
150 |
151 | // Private methods (collection) (update)
152 |
153 | func (c *Collection) applyUpdate(update Update) error {
154 | if update.transaction != c.transaction.id {
155 | panic("update was not prepared during the current transaction")
156 | }
157 |
158 | if !(update.update.Check(update.proxy)) {
159 | return errors.New("update check failed")
160 | }
161 |
162 | if c.transaction.ongoing {
163 | update.update.Apply(update.proxy)
164 | } else {
165 | c.Begin()
166 | update.update.Apply(update.proxy)
167 | c.End()
168 | }
169 |
170 | return nil
171 | }
172 |
173 | func (c *Collection) applyUserUpdate(update userUpdate) error {
174 | preparedUpdate, err := c.Prepare(update)
175 | if err != nil {
176 | return err
177 | }
178 |
179 | return c.Apply(preparedUpdate)
180 | }
181 |
--------------------------------------------------------------------------------
/omniledger/collection/verifiers.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "crypto/sha256"
4 |
5 | // Methods (collection) (verifiers)
6 |
7 | // Verify verifies that a given Proof is correct.
8 | // It moreover adds the nodes from the Proof to the temporary nodes of the collection.
9 | func (c *Collection) Verify(proof Proof) bool {
10 | if c.root.transaction.inconsistent {
11 | panic("Verify() called on inconsistent root.")
12 | }
13 |
14 | if (proof.Root.Label != c.root.label) || !(proof.Consistent()) {
15 | return false
16 | }
17 |
18 | if !(c.root.known) {
19 | proof.Root.to(c.root)
20 | }
21 |
22 | path := sha256.Sum256(proof.Key)
23 | cursor := c.root
24 |
25 | for depth := 0; depth < len(proof.Steps); depth++ {
26 | if !(cursor.children.left.known) {
27 | proof.Steps[depth].Left.to(cursor.children.left)
28 | }
29 |
30 | if !(cursor.children.right.known) {
31 | proof.Steps[depth].Right.to(cursor.children.right)
32 | }
33 |
34 | if bit(path[:], depth) {
35 | cursor = cursor.children.right
36 | } else {
37 | cursor = cursor.children.left
38 | }
39 | }
40 |
41 | return true
42 | }
43 |
--------------------------------------------------------------------------------
/omniledger/collection/verifiers_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import "testing"
4 | import "encoding/binary"
5 |
6 | func TestVerifiersVerify(test *testing.T) {
7 | ctx := testCtx("[verifiers.go]", test)
8 |
9 | stake64 := Stake64{}
10 | data := Data{}
11 |
12 | collection := New(stake64, data)
13 | unknown := New(stake64, data)
14 | unknown.scope.None()
15 |
16 | collection.Begin()
17 | unknown.Begin()
18 |
19 | for index := 0; index < 512; index++ {
20 | key := make([]byte, 8)
21 | binary.BigEndian.PutUint64(key, uint64(index))
22 |
23 | collection.Add(key, uint64(index), key)
24 | unknown.Add(key, uint64(index), key)
25 | }
26 |
27 | collection.End()
28 | unknown.End()
29 |
30 | for index := 0; index < 512; index++ {
31 | key := make([]byte, 8)
32 | binary.BigEndian.PutUint64(key, uint64(index))
33 |
34 | proof, _ := collection.Get(key).Proof()
35 | if !(unknown.Verify(proof)) {
36 | test.Error("[verifiers.go]", "[verify]", "Verify() fails on valid proof.")
37 | }
38 | }
39 |
40 | ctx.verify.tree("[verify]", &unknown)
41 |
42 | for index := 0; index < 512; index++ {
43 | key := make([]byte, 8)
44 | binary.BigEndian.PutUint64(key, uint64(index))
45 |
46 | ctx.verify.values("[verify]", &unknown, key, uint64(index), key)
47 | }
48 |
49 | proof, _ := collection.Get(make([]byte, 8)).Proof()
50 | proof.Steps[0].Left.Label[0]++
51 |
52 | if unknown.Verify(proof) {
53 | test.Error("[verifiers.go]", "[verify]", "Verify() accepts an inconsistent proof.")
54 | }
55 |
56 | collection.Add([]byte("mykey"), uint64(1066), []byte("myvalue"))
57 |
58 | proof, _ = collection.Get(make([]byte, 8)).Proof()
59 |
60 | if unknown.Verify(proof) {
61 | test.Error("[verifiers.go]", "[verify]", "Verify() accepts a consistent proof from a wrong root.")
62 | }
63 |
64 | collection.root.transaction.inconsistent = true
65 | ctx.shouldPanic("[verify]", func() {
66 | collection.Verify(proof)
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/omniledger/darc/darc_example_test.go:
--------------------------------------------------------------------------------
1 | package darc_test
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 |
7 | "github.com/dedis/student_18_omniledger/omniledger/darc"
8 | "github.com/dedis/student_18_omniledger/omniledger/darc/expression"
9 | )
10 |
11 | func Example() {
12 | // Consider a client-server configuration. Where the client holds the
13 | // credentials and wants the server to check for requests or evolve
14 | // darcs. We begin by creating a darc on the server.
15 | // We can create a new darc like so.
16 | owner1 := darc.NewSignerEd25519(nil, nil)
17 | rules1 := darc.InitRules([]*darc.Identity{owner1.Identity()}, []*darc.Identity{})
18 | d1 := darc.NewDarc(rules1, []byte("example darc"))
19 | fmt.Println(d1.Verify())
20 |
21 | // Now the client wants to evolve the darc (change the owner), so it
22 | // creates a request and then sends it to the server.
23 | owner2 := darc.NewSignerEd25519(nil, nil)
24 | rules2 := darc.InitRules([]*darc.Identity{owner2.Identity()}, []*darc.Identity{})
25 | d2 := darc.NewDarc(rules2, []byte("example darc 2"))
26 | d2.EvolveFrom([]*darc.Darc{d1})
27 | r, d2Buf, err := d2.MakeEvolveRequest(owner1)
28 | fmt.Println(err)
29 |
30 | // Client sends request r and serialised darc d2Buf to the server, and
31 | // the server must verify it. Usually the server will look in its
32 | // database for the base ID of the darc in the request and find the
33 | // latest one. But in this case we assume it already knows. If the
34 | // verification is successful, then the server should add the darc in
35 | // the request to its database.
36 | fmt.Println(r.Verify(d1)) // Assume we can find d1 given r.
37 | d2Server, _ := r.MsgToDarc(d2Buf, []*darc.Darc{d1})
38 | fmt.Println(bytes.Equal(d2Server.GetID(), d2.GetID()))
39 |
40 | // If the darcs stored on the server are trustworthy, then using
41 | // `Request.Verify` is enough. To do a complete verification,
42 | // Darc.Verify should be used. This will traverse the chain of
43 | // evolution and verify every evolution. However, the Darc.Path
44 | // attribute must be set.
45 | fmt.Println(d2Server.Verify())
46 |
47 | // The above illustrates the basic use of darcs, in the following
48 | // examples, we show how to create custom rules to enforce custom
49 | // policies. We begin by making another evolution that has a custom
50 | // action.
51 | owner3 := darc.NewSignerEd25519(nil, nil)
52 | action3 := darc.Action("custom_action")
53 | expr3 := expression.InitAndExpr(
54 | owner1.Identity().String(),
55 | owner2.Identity().String(),
56 | owner3.Identity().String())
57 | d3 := d1.Copy()
58 | d3.Rules.AddRule(action3, expr3)
59 |
60 | // Typically the Msg part of the request is a digest of the actual
61 | // message. For simplicity in this example, we put the actual message
62 | // in there.
63 | r, _ = darc.InitAndSignRequest(d3.GetID(), action3, []byte("example request"), owner3)
64 | if err := r.Verify(d3); err != nil {
65 | // not ok because the expression is created using logical and
66 | fmt.Println("not ok!")
67 | }
68 |
69 | r, _ = darc.InitAndSignRequest(d3.GetID(), action3, []byte("example request"), owner1, owner2, owner3)
70 | if err := r.Verify(d3); err == nil {
71 | fmt.Println("ok!")
72 | }
73 |
74 | // Output:
75 | //
76 | //
77 | //
78 | // true
79 | //
80 | // not ok!
81 | // ok!
82 | }
83 |
--------------------------------------------------------------------------------
/omniledger/darc/expression/expression.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package expression contains the definition and implementation of a simple
3 | language for defining complex policies. We define the language in extended-BNF notation,
4 | the syntax we use is from: https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form
5 |
6 | expr = term, [ '&', term ]*
7 | term = factor, [ '|', factor ]*
8 | factor = '(', expr, ')' | id
9 | id = [0-9a-z]+, ':', [0-9a-f]+
10 |
11 | Examples:
12 |
13 | ed25519:deadbeef // every id evaluates to a boolean
14 | (a:a & b:b) | (c:c & d:d)
15 |
16 | In the simplest case, the evaluation of an expression is performed against a
17 | set of valid ids. Suppose we have the expression (a:a & b:b) | (c:c & d:d),
18 | and the set of valid ids is [a:a, b:b], then the expression will evaluate to
19 | true. If the set of valid ids is [a:a, c:c], then the expression will evaluate
20 | to false. However, the user is able to provide a ValueCheckFn to customise how
21 | the expressions are evaluated.
22 |
23 | EXTENSION - NOT YET IMPLEMENTED:
24 | To support threshold signatures, we extend the syntax to include the following.
25 | thexpr = '[', id, [ ',', id ]*, ']', '/', digit
26 | */
27 | package expression
28 |
29 | import (
30 | "errors"
31 | "strings"
32 |
33 | parsec "github.com/prataprc/goparsec"
34 | )
35 |
36 | const scannerNotEmpty = "parsing failed - scanner is not empty"
37 | const failedToCast = "evauluation failed - result is not bool"
38 |
39 | // ValueCheckFn is a function that will be called when the parser is
40 | // parsing/evaluating an expression.
41 | // TODO it is useful if we can return (bool, error).
42 | type ValueCheckFn func(string) bool
43 |
44 | // Expr represents the unprocess expression of our DSL.
45 | type Expr []byte
46 |
47 | // InitParser creates the root parser
48 | func InitParser(fn ValueCheckFn) parsec.Parser {
49 | // Y is root Parser, usually called as `s` in CFG theory.
50 | var Y parsec.Parser
51 | var sum, value parsec.Parser // circular rats
52 |
53 | // Terminal rats
54 | var openparan = parsec.Token(`\(`, "OPENPARAN")
55 | var closeparan = parsec.Token(`\)`, "CLOSEPARAN")
56 | var andop = parsec.Token(`&`, "AND")
57 | var orop = parsec.Token(`\|`, "OR")
58 |
59 | // NonTerminal rats
60 | // andop -> "&" | "|"
61 | var sumOp = parsec.OrdChoice(one2one, andop, orop)
62 |
63 | // value -> "(" expr ")"
64 | var groupExpr = parsec.And(exprNode, openparan, &sum, closeparan)
65 |
66 | // (andop prod)*
67 | var prodK = parsec.Kleene(nil, parsec.And(many2many, sumOp, &value), nil)
68 |
69 | // Circular rats come to life
70 | // sum -> prod (andop prod)*
71 | sum = parsec.And(sumNode(fn), &value, prodK)
72 | // value -> id | "(" expr ")"
73 | value = parsec.OrdChoice(exprValueNode(fn), id(), groupExpr)
74 | // expr -> sum
75 | Y = parsec.OrdChoice(one2one, sum)
76 | return Y
77 | }
78 |
79 | // Evaluate uses the input parser to evaluate the expression expr. It returns
80 | // the result of the evaluate (a boolean), but the result is only valid if
81 | // there are no errors.
82 | func Evaluate(parser parsec.Parser, expr Expr) (bool, error) {
83 | v, s := parser(parsec.NewScanner(expr))
84 | _, s = s.SkipWS()
85 | if !s.Endof() {
86 | return false, errors.New(scannerNotEmpty)
87 | }
88 | vv, ok := v.(bool)
89 | if !ok {
90 | return false, errors.New(failedToCast)
91 | }
92 | return vv, nil
93 | }
94 |
95 | // DefaultParser creates a parser and evaluates the expression expr, every id
96 | // in pks will evaluate to true.
97 | func DefaultParser(expr Expr, ids ...string) (bool, error) {
98 | return Evaluate(InitParser(func(s string) bool {
99 | for _, k := range ids {
100 | if k == s {
101 | return true
102 | }
103 | }
104 | return false
105 | }), expr)
106 | }
107 |
108 | // InitAndExpr creates an expression where & (and) is used to combine all the
109 | // IDs.
110 | func InitAndExpr(ids ...string) Expr {
111 | return Expr(strings.Join(ids, " & "))
112 | }
113 |
114 | // InitOrExpr creates an expression where | (or) is used to combine all the
115 | // IDs.
116 | func InitOrExpr(ids ...string) Expr {
117 | return Expr(strings.Join(ids, " | "))
118 | }
119 |
120 | func id() parsec.Parser {
121 | return func(s parsec.Scanner) (parsec.ParsecNode, parsec.Scanner) {
122 | _, s = s.SkipAny(`^[ \n\t]+`)
123 | p := parsec.Token(`[0-9a-z]+:[0-9a-f]+`, "ID")
124 | return p(s)
125 | }
126 | }
127 |
128 | func sumNode(fn ValueCheckFn) func(ns []parsec.ParsecNode) parsec.ParsecNode {
129 | return func(ns []parsec.ParsecNode) parsec.ParsecNode {
130 | if len(ns) > 0 {
131 | val := ns[0].(bool)
132 | for _, x := range ns[1].([]parsec.ParsecNode) {
133 | y := x.([]parsec.ParsecNode)
134 | n := y[1].(bool)
135 | switch y[0].(*parsec.Terminal).Name {
136 | case "AND":
137 | val = val && n
138 | case "OR":
139 | val = val || n
140 | }
141 | }
142 | return val
143 | }
144 | return nil
145 | }
146 | }
147 |
148 | func exprValueNode(fn ValueCheckFn) func(ns []parsec.ParsecNode) parsec.ParsecNode {
149 | return func(ns []parsec.ParsecNode) parsec.ParsecNode {
150 | if len(ns) == 0 {
151 | return nil
152 | } else if term, ok := ns[0].(*parsec.Terminal); ok {
153 | return fn(term.Value)
154 | }
155 | return ns[0]
156 | }
157 | }
158 |
159 | func exprNode(ns []parsec.ParsecNode) parsec.ParsecNode {
160 | if len(ns) == 0 {
161 | return nil
162 | }
163 | return ns[1]
164 | }
165 |
166 | func one2one(ns []parsec.ParsecNode) parsec.ParsecNode {
167 | if ns == nil || len(ns) == 0 {
168 | return nil
169 | }
170 | return ns[0]
171 | }
172 |
173 | func many2many(ns []parsec.ParsecNode) parsec.ParsecNode {
174 | if ns == nil || len(ns) == 0 {
175 | return nil
176 | }
177 | return ns
178 | }
179 |
--------------------------------------------------------------------------------
/omniledger/darc/expression/expression_test.go:
--------------------------------------------------------------------------------
1 | package expression
2 |
3 | import (
4 | "testing"
5 |
6 | parsec "github.com/prataprc/goparsec"
7 | )
8 |
9 | func trueFn(s string) bool {
10 | return true
11 | }
12 |
13 | func falseFn(s string) bool {
14 | return false
15 | }
16 |
17 | func TestExprAllTrue(t *testing.T) {
18 | Y := InitParser(trueFn)
19 | s := parsec.NewScanner([]byte("a:abc & b:bb"))
20 | v, s := Y(s)
21 | if v.(bool) != true {
22 | t.Fatalf("Mismatch value %v\n", v)
23 | }
24 | if !s.Endof() {
25 | t.Fatal("Scanner did not end")
26 | }
27 | }
28 |
29 | func TestExprAllFalse(t *testing.T) {
30 | Y := InitParser(falseFn)
31 | s := parsec.NewScanner([]byte("a:abc & b:bb"))
32 | v, s := Y(s)
33 | if v.(bool) != false {
34 | t.Fatalf("Mismatch value %v\n", v)
35 | }
36 | if !s.Endof() {
37 | t.Fatal("Scanner did not end")
38 | }
39 | }
40 |
41 | func TestInitOr(t *testing.T) {
42 | // TODO
43 | }
44 |
45 | func TestInitAnd(t *testing.T) {
46 | // TODO
47 | }
48 |
49 | func TestParsing_One(t *testing.T) {
50 | expr := []byte("a:abc")
51 | fn := func(s string) bool {
52 | if s == "a:abc" {
53 | return true
54 | }
55 | return false
56 | }
57 | v, s := InitParser(fn)(parsec.NewScanner(expr))
58 | if v.(bool) != true {
59 | t.Fatalf("Mismatch value %v\n", v)
60 | }
61 | if !s.Endof() {
62 | t.Fatal("Scanner did not end")
63 | }
64 | }
65 |
66 | func TestParsing_Or(t *testing.T) {
67 | expr := []byte("a:abc | b:abc | c:abc")
68 | fn := func(s string) bool {
69 | if s == "b:abc" {
70 | return true
71 | }
72 | return false
73 | }
74 | v, s := InitParser(fn)(parsec.NewScanner(expr))
75 | if v.(bool) != true {
76 | t.Fatalf("Mismatch value %v\n", v)
77 | }
78 | if !s.Endof() {
79 | t.Fatal("Scanner did not end")
80 | }
81 | }
82 |
83 | func TestParsing_InvalidID_1(t *testing.T) {
84 | expr := []byte("x")
85 | _, err := Evaluate(InitParser(trueFn), expr)
86 | if err == nil {
87 | t.Fatal("expect an error")
88 | }
89 | if err.Error() != scannerNotEmpty {
90 | t.Fatalf("wrong error message, got %s", err.Error())
91 | }
92 | }
93 |
94 | func TestParsing_InvalidID_2(t *testing.T) {
95 | expr := []byte("a: b")
96 | _, err := Evaluate(InitParser(trueFn), expr)
97 | if err == nil {
98 | t.Fatal("expect an error")
99 | }
100 | }
101 |
102 | func TestParsing_InvalidOp(t *testing.T) {
103 | expr := []byte("a:abc / b:abc")
104 | _, err := Evaluate(InitParser(trueFn), expr)
105 | if err == nil {
106 | t.Fatal("expect an error")
107 | }
108 | if err.Error() != scannerNotEmpty {
109 | t.Fatalf("wrong error message, got %s", err.Error())
110 | }
111 | }
112 |
113 | func TestParsing_Paran(t *testing.T) {
114 | expr := []byte("(a:b)")
115 | x, err := Evaluate(InitParser(trueFn), expr)
116 | if err != nil {
117 | t.Fatal(err)
118 | }
119 | if x != true {
120 | t.Fatal("wrong result")
121 | }
122 | }
123 |
124 | func TestParsing_Nesting(t *testing.T) {
125 | expr := []byte("(a:b | (b:c & c:d))")
126 | x, err := Evaluate(InitParser(func(s string) bool {
127 | if s == "b:c" || s == "c:d" {
128 | return true
129 | }
130 | return false
131 | }), expr)
132 | if err != nil {
133 | t.Fatal(err)
134 | }
135 | if x != true {
136 | t.Fatal("wrong result")
137 | }
138 | }
139 |
140 | func TestParsing_Imbalance(t *testing.T) {
141 | expr := []byte("(a:b | b:c & c:d))")
142 | _, err := Evaluate(InitParser(trueFn), expr)
143 | if err == nil {
144 | t.Fatal("error is expected")
145 | }
146 | }
147 |
148 | func TestParsing_LeftSpace(t *testing.T) {
149 | expr := []byte(" a:b")
150 | _, err := Evaluate(InitParser(trueFn), expr)
151 | if err != nil {
152 | t.Fatal(err)
153 | }
154 | }
155 |
156 | func TestParsing_RightSpace(t *testing.T) {
157 | expr := []byte("a:b ")
158 | _, err := Evaluate(InitParser(trueFn), expr)
159 | if err != nil {
160 | t.Fatal(err)
161 | }
162 | }
163 |
164 | func TestParsing_NoSpace(t *testing.T) {
165 | expr := []byte("a:b|a:c&b:b")
166 | _, err := Evaluate(InitParser(trueFn), expr)
167 | if err != nil {
168 | t.Fatal(err)
169 | }
170 | }
171 |
172 | func TestParsing_RealIDs(t *testing.T) {
173 | expr := []byte("ed25519:5764e85642c3bda8748c5cf3d7f14c6d5c18e193228d70f4c58dd80ed4582748 | ed25519:bf58ca4b1ddb07a7a9bbf57fe9b856f214a38dd872b6ec07efbeb0a01003fae9")
174 | _, err := Evaluate(InitParser(trueFn), expr)
175 | if err != nil {
176 | t.Fatal(err)
177 | }
178 | }
179 |
180 | func TestParsing_Empty(t *testing.T) {
181 | expr := []byte{}
182 | _, err := Evaluate(InitParser(trueFn), expr)
183 | if err == nil {
184 | t.Fatal("empty expr should fail")
185 | }
186 | }
187 |
188 | func TestEval_DefaultParser(t *testing.T) {
189 | keys := []string{"a:a", "b:b", "c:c", "d:d"}
190 | expr := InitAndExpr(keys...)
191 | ok, err := DefaultParser(expr, keys...)
192 | if err != nil {
193 | t.Fatal(err)
194 | }
195 | if ok != true {
196 | t.Fatal("evaluation should return to true")
197 | }
198 |
199 | // If the expression has an extra term, then the evaluation should fail
200 | // because the extra term is not a part of the valid keys.
201 | expr = append(expr, []byte(" & e:e")...)
202 | ok, err = DefaultParser(expr, keys...)
203 | if err != nil {
204 | t.Fatal(err)
205 | }
206 | if ok != false {
207 | t.Fatal("evaluation should return false")
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/omniledger/darc/struct.go:
--------------------------------------------------------------------------------
1 | package darc
2 |
3 | import (
4 | "gopkg.in/dedis/kyber.v2"
5 | "gopkg.in/dedis/onet.v2/network"
6 |
7 | "github.com/dedis/student_18_omniledger/omniledger/darc/expression"
8 | )
9 |
10 | func init() {
11 | network.RegisterMessages(
12 | Darc{}, Identity{}, Signature{},
13 | )
14 | }
15 |
16 | // ID is the identity of a Darc - which is the sha256 of its protobuf representation
17 | // over invariant fields [Owners, Users, Version, Description]. Signature is excluded.
18 | // An evolving Darc will change its identity.
19 | type ID []byte
20 |
21 | // PROTOSTART
22 | //
23 | // option java_package = "ch.epfl.dedis.proto";
24 | // option java_outer_classname = "DarcProto";
25 |
26 | // ***
27 | // These are the messages used in the API-calls
28 | // ***
29 |
30 | // Darc is the basic structure representing an access control. A Darc can evolve in the way that
31 | // a new Darc points to the previous one and is signed by the owner(s) of the previous Darc.
32 | type Darc struct {
33 | // Version should be monotonically increasing over the evolution of a Darc.
34 | Version uint64
35 | // Description is a free-form field that can hold any data as required by the user.
36 | // Darc itself will never depend on any of the data in here.
37 | Description []byte
38 | // BaseID is the ID of the first darc of this Series
39 | BaseID ID
40 | // Rules map an action to an expression.
41 | Rules Rules
42 | // Represents the path to get up to information to be able to verify
43 | // this signature. These justify the right of the signer to push a new
44 | // Darc. These are ordered from the oldest to the newest, i.e.
45 | // Path[0] should be the base Darc.
46 | Path []*Darc
47 | // PathDigest is the digest of Path, it should be set when Path has
48 | // length 0.
49 | PathDigest []byte
50 | // Signature is calculated on the Request-representation of the darc.
51 | // It needs to be created by identities that have the "_evolve" action
52 | // from the previous valid Darc.
53 | Signatures []*Signature
54 | }
55 |
56 | // Action is a string that should be associated with an expression. The
57 | // application typically will define the action but there are two actions that
58 | // are in all the darcs, "_evolve" and "_sign". The application can modify
59 | // these actions but should not change the semantics of these actions.
60 | type Action string
61 |
62 | // Rules are action-expression associations.
63 | type Rules map[Action]expression.Expr
64 |
65 | // Identity is a generic structure can be either an Ed25519 public key, a Darc
66 | // or a X509 Identity.
67 | type Identity struct {
68 | // Darc identity
69 | Darc *IdentityDarc
70 | // Public-key identity
71 | Ed25519 *IdentityEd25519
72 | // Public-key identity
73 | X509EC *IdentityX509EC
74 | }
75 |
76 | // IdentityEd25519 holds a Ed25519 public key (Point)
77 | type IdentityEd25519 struct {
78 | Point kyber.Point
79 | }
80 |
81 | // IdentityX509EC holds a public key from a X509EC
82 | type IdentityX509EC struct {
83 | Public []byte
84 | }
85 |
86 | // IdentityDarc is a structure that points to a Darc with a given ID on a
87 | // skipchain. The signer should belong to the Darc.
88 | type IdentityDarc struct {
89 | // Signer SignerEd25519
90 | ID ID
91 | }
92 |
93 | // Signature is a signature on a Darc to accept a given decision.
94 | // can be verified using the appropriate identity.
95 | type Signature struct {
96 | // The signature itself
97 | Signature []byte
98 | // Signer is the Idenity (public key or another Darc) of the signer
99 | Signer Identity
100 | }
101 |
102 | // Signer is a generic structure that can hold different types of signers
103 | type Signer struct {
104 | Ed25519 *SignerEd25519
105 | X509EC *SignerX509EC
106 | }
107 |
108 | // SignerEd25519 holds a public and private keys necessary to sign Darcs
109 | type SignerEd25519 struct {
110 | Point kyber.Point
111 | Secret kyber.Scalar
112 | }
113 |
114 | // SignerX509EC holds a public and private keys necessary to sign Darcs,
115 | // but the private key will not be given out.
116 | type SignerX509EC struct {
117 | Point []byte
118 | secret []byte
119 | }
120 |
121 | type innerRequest struct {
122 | BaseID ID
123 | Action Action
124 | Msg []byte
125 | Identities []*Identity
126 | }
127 |
128 | // Request is the structure that the client must provide to be verified
129 | type Request struct {
130 | innerRequest
131 | Signatures [][]byte
132 | }
133 |
--------------------------------------------------------------------------------
/omniledger/service/api.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | /*
4 | * The Sicpa service uses a CISC (https://gopkg.in/dedis/cothority.v2/cisc) to store
5 | * key/value pairs on a skipchain.
6 | */
7 |
8 | import (
9 | "errors"
10 |
11 | "github.com/dedis/student_18_omniledger/omniledger/darc"
12 |
13 | "gopkg.in/dedis/cothority.v2"
14 | "gopkg.in/dedis/cothority.v2/skipchain"
15 | "gopkg.in/dedis/onet.v2"
16 | )
17 |
18 | // ServiceName is used for registration on the onet.
19 | const ServiceName = "OmniLedger"
20 |
21 | // Client is a structure to communicate with the CoSi
22 | // service
23 | type Client struct {
24 | *onet.Client
25 | }
26 |
27 | // NewClient instantiates a new cosi.Client
28 | func NewClient() *Client {
29 | return &Client{Client: onet.NewClient(cothority.Suite, ServiceName)}
30 | }
31 |
32 | // CreateGenesisBlock sets up a new skipchain to hold the key/value pairs. If
33 | // a key is given, it is used to authenticate towards the cothority.
34 | func (c *Client) CreateGenesisBlock(r *onet.Roster, msg *CreateGenesisBlock) (*CreateGenesisBlockResponse, error) {
35 | reply := &CreateGenesisBlockResponse{}
36 | if err := c.SendProtobuf(r.List[0], msg, reply); err != nil {
37 | return nil, err
38 | }
39 | return reply, nil
40 | }
41 |
42 | // AddTransaction adds a transaction. It does not return any feedback
43 | // on the transaction. Use GetProof to find out if the transaction
44 | // was committed.
45 | func (c *Client) AddTransaction(r *onet.Roster, id skipchain.SkipBlockID,
46 | tx ClientTransaction) (*AddTxResponse, error) {
47 | reply := &AddTxResponse{}
48 | err := c.SendProtobuf(r.List[0], &AddTxRequest{
49 | Version: CurrentVersion,
50 | SkipchainID: id,
51 | Transaction: tx,
52 | }, reply)
53 | if err != nil {
54 | return nil, err
55 | }
56 | return reply, nil
57 | }
58 |
59 | // GetProof returns a proof for the key stored in the skipchain.
60 | // The proof can be verified with the genesis skipblock and
61 | // can prove the existence or the absence of the key.
62 | func (c *Client) GetProof(r *onet.Roster, id skipchain.SkipBlockID, key []byte) (*GetProofResponse, error) {
63 | reply := &GetProofResponse{}
64 | err := c.SendProtobuf(r.List[0], &GetProof{
65 | Version: CurrentVersion,
66 | ID: id,
67 | Key: key,
68 | }, reply)
69 | if err != nil {
70 | return nil, err
71 | }
72 | return reply, nil
73 | }
74 |
75 | // DefaultGenesisMsg creates the message that is used to for creating the
76 | // genesis darc and block.
77 | func DefaultGenesisMsg(v Version, r *onet.Roster, rules []string, ids ...*darc.Identity) (*CreateGenesisBlock, error) {
78 | if len(ids) == 0 {
79 | return nil, errors.New("no identities ")
80 | }
81 | d := darc.NewDarc(darc.InitRules(ids, ids), []byte("genesis darc"))
82 | for _, r := range rules {
83 | d.Rules.AddRule(darc.Action(r), d.Rules.GetSignExpr())
84 | }
85 |
86 | m := CreateGenesisBlock{
87 | Version: v,
88 | Roster: *r,
89 | GenesisDarc: *d,
90 | BlockInterval: defaultInterval,
91 | }
92 | return &m, nil
93 | }
94 |
--------------------------------------------------------------------------------
/omniledger/service/api_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/dedis/student_18_omniledger/omniledger/darc"
8 | "github.com/stretchr/testify/require"
9 | "gopkg.in/dedis/cothority.v2"
10 | "gopkg.in/dedis/onet.v2"
11 | )
12 |
13 | func TestClient_GetProof(t *testing.T) {
14 | l := onet.NewTCPTest(cothority.Suite)
15 | servers, roster, _ := l.GenTree(3, true)
16 | registerDummy(servers)
17 | defer l.CloseAll()
18 | defer closeQueues(l)
19 |
20 | // Initialise the genesis message and send it to the service.
21 | signer := darc.NewSignerEd25519(nil, nil)
22 | msg, err := DefaultGenesisMsg(CurrentVersion, roster, []string{"Spawn_dummy"}, signer.Identity())
23 | msg.BlockInterval = 100 * time.Millisecond
24 | require.Nil(t, err)
25 |
26 | // The darc inside it should be valid.
27 | d := msg.GenesisDarc
28 | require.Nil(t, d.Verify())
29 |
30 | c := NewClient()
31 | csr, err := c.CreateGenesisBlock(roster, msg)
32 | require.Nil(t, err)
33 |
34 | // Create a new transaction.
35 | value := []byte{5, 6, 7, 8}
36 | kind := "dummy"
37 | tx, err := createOneClientTx(d.GetBaseID(), kind, value, signer)
38 | require.Nil(t, err)
39 | _, err = c.AddTransaction(roster, csr.Skipblock.SkipChainID(), tx)
40 | require.Nil(t, err)
41 |
42 | // We should have a proof of our transaction in the skipchain.
43 | var p *GetProofResponse
44 | var i int
45 | for i = 0; i < 10; i++ {
46 | time.Sleep(4 * msg.BlockInterval)
47 | var err error
48 | p, err = c.GetProof(roster, csr.Skipblock.SkipChainID(), tx.Instructions[0].ObjectID.Slice())
49 | if err != nil {
50 | continue
51 | }
52 | if p.Proof.InclusionProof.Match() {
53 | break
54 | }
55 | }
56 | require.NotEqual(t, 10, i, "didn't get proof in time")
57 | require.Nil(t, p.Proof.Verify(csr.Skipblock.SkipChainID()))
58 | k, vs, err := p.Proof.KeyValue()
59 | require.Nil(t, err)
60 | require.Equal(t, k, tx.Instructions[0].ObjectID.Slice())
61 | require.Equal(t, value, vs[0])
62 | }
63 |
--------------------------------------------------------------------------------
/omniledger/service/contracts.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "time"
7 |
8 | "github.com/dedis/protobuf"
9 | "github.com/dedis/student_18_omniledger/omniledger/collection"
10 | "github.com/dedis/student_18_omniledger/omniledger/darc"
11 | "gopkg.in/dedis/onet.v2/log"
12 | )
13 |
14 | // Here we give a definition of pre-defined contracts.
15 |
16 | // ZeroNonce is 32 bytes of zeroes and can have a special meaning.
17 | var ZeroNonce = Nonce([32]byte{})
18 |
19 | // OneNonce has 32 bytes of zeros except the LSB is set to one.
20 | var OneNonce = Nonce(func() [32]byte {
21 | var nonce [32]byte
22 | nonce[31] = 1
23 | return nonce
24 | }())
25 |
26 | // ZeroDarc is a DarcID with all zeroes.
27 | var ZeroDarc = darc.ID(make([]byte, 32))
28 |
29 | // GenesisReferenceID is 64 bytes of zeroes. Its value is a reference to the
30 | // genesis-darc.
31 | var GenesisReferenceID = ObjectID{ZeroDarc, ZeroNonce}
32 |
33 | // ContractConfigID denotes a config-contract
34 | var ContractConfigID = "config"
35 |
36 | // ContractDarcID denotes a darc-contract
37 | var ContractDarcID = "darc"
38 |
39 | // CmdDarcEvolve is needed to evolve a darc.
40 | var CmdDarcEvolve = "Evolve"
41 |
42 | // Config stores all the configuration information for one skipchain. It will
43 | // be stored under the key "GenesisDarcID || OneNonce", in the collections. The
44 | // GenesisDarcID is the value of GenesisReferenceID.
45 | type Config struct {
46 | BlockInterval time.Duration
47 | }
48 |
49 | // ContractConfig can only be instantiated once per skipchain, and only for
50 | // the genesis block.
51 | func (s *Service) ContractConfig(cdb collection.Collection, tx Instruction, coins []Coin) (sc []StateChange, c []Coin, err error) {
52 | if tx.Spawn == nil {
53 | return nil, nil, errors.New("Config can only be spawned")
54 | }
55 | darcBuf := tx.Spawn.Args.Search("darc")
56 | d, err := darc.NewDarcFromProto(darcBuf)
57 | if err != nil {
58 | log.Error("couldn't decode darc")
59 | return
60 | }
61 | if len(d.Rules) == 0 {
62 | return nil, nil, errors.New("don't accept darc with empty rules")
63 | }
64 | if err = d.Verify(); err != nil {
65 | log.Error("couldn't verify darc")
66 | return
67 | }
68 |
69 | // sanity check the block interval
70 | intervalBuf := tx.Spawn.Args.Search("block_interval")
71 | interval, _ := binary.Varint(intervalBuf)
72 | if interval == 0 {
73 | err = errors.New("block interval is zero")
74 | return
75 | }
76 |
77 | // create the config to be stored by state changes
78 | config := Config{
79 | BlockInterval: time.Duration(interval),
80 | }
81 | configBuf, err := protobuf.Encode(&config)
82 | if err != nil {
83 | return
84 | }
85 |
86 | return []StateChange{
87 | NewStateChange(Create, GenesisReferenceID, ContractConfigID, tx.ObjectID.DarcID),
88 | NewStateChange(Create, tx.ObjectID, ContractDarcID, darcBuf),
89 | NewStateChange(Create,
90 | ObjectID{
91 | DarcID: tx.ObjectID.DarcID,
92 | InstanceID: OneNonce,
93 | }, ContractConfigID, configBuf),
94 | }, nil, nil
95 | }
96 |
97 | // ContractDarc accepts the following instructions:
98 | // - Spawn - creates a new darc
99 | // - Invoke.Evolve - evolves an existing darc
100 | func (s *Service) ContractDarc(cdb collection.Collection, tx Instruction, coins []Coin) (sc []StateChange, c []Coin, err error) {
101 | return nil, nil, errors.New("Not yet implemented")
102 | }
103 |
--------------------------------------------------------------------------------
/omniledger/service/doc.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | /*
4 | A service interacts with the outer world through an API that defines the
5 | methods that are callable from the outside. The service can hold data that
6 | will survive a restart of the cothority and instantiate any number of
7 | protocols before returning a value.
8 | */
9 |
--------------------------------------------------------------------------------
/omniledger/service/messages.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | /*
4 | This holds the messages used to communicate with the service over the network.
5 | */
6 |
7 | import (
8 | "time"
9 |
10 | "github.com/dedis/student_18_omniledger/omniledger/darc"
11 | "gopkg.in/dedis/cothority.v2/skipchain"
12 | "gopkg.in/dedis/onet.v2"
13 | "gopkg.in/dedis/onet.v2/network"
14 | )
15 |
16 | // We need to register all messages so the network knows how to handle them.
17 | func init() {
18 | network.RegisterMessages(
19 | &CreateGenesisBlock{}, &CreateGenesisBlockResponse{},
20 | &AddTxRequest{}, &AddTxResponse{},
21 | )
22 | }
23 |
24 | // Version indicates what version this client runs. In the first development
25 | // phase, each next version will break the preceeding versions. Later on,
26 | // new versions might correctly interpret earlier versions.
27 | type Version int
28 |
29 | // CurrentVersion is what we're running now
30 | const CurrentVersion Version = 1
31 |
32 | // PROTOSTART
33 | // import "skipblock.proto";
34 | // import "roster.proto";
35 | //
36 | // option java_package = "ch.epfl.dedis.proto";
37 | // option java_outer_classname = "OmniLedgerProto";
38 |
39 | // ***
40 | // These are the messages used in the API-calls
41 | // ***
42 |
43 | // CreateGenesisBlock asks the cisc-service to set up a new skipchain.
44 | type CreateGenesisBlock struct {
45 | // Version of the protocol
46 | Version Version
47 | // Roster defines which nodes participate in the skipchain.
48 | Roster onet.Roster
49 | // GenesisDarc defines who is allowed to write to this skipchain.
50 | GenesisDarc darc.Darc
51 | // BlockInterval in int64.
52 | BlockInterval time.Duration
53 | }
54 |
55 | // CreateGenesisBlockResponse holds the genesis-block of the new skipchain.
56 | type CreateGenesisBlockResponse struct {
57 | // Version of the protocol
58 | Version Version
59 | // Skipblock of the created skipchain or empty if there was an error.
60 | Skipblock *skipchain.SkipBlock
61 | }
62 |
63 | // AddTxRequest requests to apply a new transaction to the ledger.
64 | type AddTxRequest struct {
65 | // Version of the protocol
66 | Version Version
67 | // SkipchainID is the hash of the first skipblock
68 | SkipchainID skipchain.SkipBlockID
69 | // Transaction to be applied to the kv-store
70 | Transaction ClientTransaction
71 | }
72 |
73 | // AddTxResponse is the reply after an AddTxRequest is finished.
74 | type AddTxResponse struct {
75 | // Version of the protocol
76 | Version Version
77 | }
78 |
79 | // GetProof returns the proof that the given key is in the collection.
80 | type GetProof struct {
81 | // Version of the protocol
82 | Version Version
83 | // Key is the key we want to look up
84 | Key []byte
85 | // ID is any block that is know to us in the skipchain, can be the genesis
86 | // block or any later block. The proof returned will be starting at this block.
87 | ID skipchain.SkipBlockID
88 | }
89 |
90 | // GetProofResponse can be used together with the Genesis block to proof that
91 | // the returned key/value pair is in the collection.
92 | type GetProofResponse struct {
93 | // Version of the protocol
94 | Version Version
95 | // Proof contains everything necessary to prove the inclusion
96 | // of the included key/value pair given a genesis skipblock.
97 | Proof Proof
98 | }
99 |
--------------------------------------------------------------------------------
/omniledger/service/proof.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 |
7 | "github.com/dedis/student_18_omniledger/omniledger/collection"
8 | "gopkg.in/dedis/cothority.v2"
9 | "gopkg.in/dedis/cothority.v2/skipchain"
10 | "gopkg.in/dedis/kyber.v2"
11 | "gopkg.in/dedis/onet.v2/network"
12 | )
13 |
14 | // Proof represents everything necessary to verify a given
15 | // key/value pair is stored in a skipchain. The proof is in three parts:
16 | // 1. InclusionProof proofs the presence or absence of the key. In case of
17 | // the key being present, the value is included in the proof
18 | // 2. Latest is used to verify the merkle tree root used in the collection-proof
19 | // is stored in the latest skipblock
20 | // 3. Links proves that the latest skipblock is part of the skipchain
21 | //
22 | // This Structure could later be moved to cothority/skipchain.
23 | type Proof struct {
24 | // InclusionProof is the deserialized InclusionProof
25 | InclusionProof collection.Proof
26 | // Providing the latest skipblock to retrieve the Merkle tree root.
27 | Latest skipchain.SkipBlock
28 | // Proving the path to the latest skipblock. The first ForwardLink has an
29 | // empty-sliced `From` and the genesis-block in `To`, together with the
30 | // roster of the genesis-block in the `NewRoster`.
31 | Links []skipchain.ForwardLink
32 | }
33 |
34 | // NewProof creates a proof for key in the skipchain with the given id. It uses
35 | // the collectionDB to look up the key and the skipblockdb to create the correct
36 | // proof for the forward links.
37 | func NewProof(c *collectionDB, s *skipchain.SkipBlockDB, id skipchain.SkipBlockID,
38 | key []byte) (p *Proof, err error) {
39 | p = &Proof{}
40 | p.InclusionProof, err = c.coll.Get(key).Proof()
41 | if err != nil {
42 | return
43 | }
44 | sb := s.GetByID(id)
45 | if sb == nil {
46 | return nil, errors.New("didn't find skipchain")
47 | }
48 | p.Links = []skipchain.ForwardLink{{
49 | From: []byte{},
50 | To: id,
51 | NewRoster: sb.Roster,
52 | }}
53 | for len(sb.ForwardLink) > 0 {
54 | link := sb.ForwardLink[len(sb.ForwardLink)-1]
55 | p.Links = append(p.Links, *link)
56 | sb = s.GetByID(link.To)
57 | if sb == nil {
58 | return nil, errors.New("missing block in chain")
59 | }
60 | }
61 | p.Latest = *sb
62 | // p.ProofBytes = p.proof.Consistent()
63 | return
64 | }
65 |
66 | // ErrorVerifyCollection is returned if the collection-proof itself
67 | // is not properly set up.
68 | var ErrorVerifyCollection = errors.New("collection inclusion proof is wrong")
69 |
70 | // ErrorVerifyCollectionRoot is returned if the root of the collection
71 | // is different than the stored value in the skipblock.
72 | var ErrorVerifyCollectionRoot = errors.New("root of collection is not in skipblock")
73 |
74 | // ErrorVerifySkipchain is returned if the stored skipblock doesn't
75 | // have a proper proof that it comes from the genesis block.
76 | var ErrorVerifySkipchain = errors.New("stored skipblock is not properly evolved from genesis block")
77 |
78 | // Verify takes a skipchain id and verifies that the proof is valid for this skipchain.
79 | // It verifies the collection-proof, that the merkle-root is stored in the skipblock
80 | // of the proof and the fact that the skipblock is indeed part of the skipchain.
81 | // If all verifications are correct, the error will be nil.
82 | func (p Proof) Verify(scID skipchain.SkipBlockID) error {
83 | if !p.InclusionProof.Consistent() {
84 | return ErrorVerifyCollection
85 | }
86 | _, d, err := network.Unmarshal(p.Latest.Data, cothority.Suite)
87 | if err != nil {
88 | return err
89 | }
90 | if !bytes.Equal(p.InclusionProof.TreeRootHash(), d.(*DataHeader).CollectionRoot) {
91 | return ErrorVerifyCollectionRoot
92 | }
93 | var sbID skipchain.SkipBlockID
94 | var publics []kyber.Point
95 | for i, l := range p.Links {
96 | if i == 0 {
97 | // The first forward link is a pointer from []byte{} to the genesis
98 | // block and holds the roster of the genesis block.
99 | sbID = scID
100 | publics = l.NewRoster.Publics()
101 | continue
102 | }
103 | if err = l.Verify(cothority.Suite, publics); err != nil {
104 | return ErrorVerifySkipchain
105 | }
106 | if !l.From.Equal(sbID) {
107 | return ErrorVerifySkipchain
108 | }
109 | sbID = l.To
110 | if l.NewRoster != nil {
111 | publics = l.NewRoster.Publics()
112 | }
113 | }
114 | return nil
115 | }
116 |
117 | // KeyValue returns the key and the values stored in the proof.
118 | func (p Proof) KeyValue() (key []byte, values [][]byte, err error) {
119 | key = p.InclusionProof.Key
120 | values, err = p.InclusionProof.RawValues()
121 | return
122 | }
123 |
--------------------------------------------------------------------------------
/omniledger/service/proof_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "crypto/sha256"
5 | "fmt"
6 | "io/ioutil"
7 | "testing"
8 |
9 | bolt "github.com/coreos/bbolt"
10 | "github.com/stretchr/testify/require"
11 | "gopkg.in/dedis/cothority.v2"
12 | "gopkg.in/dedis/cothority.v2/byzcoinx"
13 | "gopkg.in/dedis/cothority.v2/skipchain"
14 | "gopkg.in/dedis/kyber.v2"
15 | "gopkg.in/dedis/kyber.v2/sign/cosi"
16 | "gopkg.in/dedis/kyber.v2/util/key"
17 | "gopkg.in/dedis/onet.v2"
18 | "gopkg.in/dedis/onet.v2/network"
19 | )
20 |
21 | func TestNewProof(t *testing.T) {
22 | s := createSC(t)
23 | p, err := NewProof(s.c, s.s, skipchain.SkipBlockID{}, []byte{})
24 | require.NotNil(t, err)
25 | p, err = NewProof(s.c, s.s, s.genesis.Hash, []byte{1})
26 | require.Nil(t, err)
27 | require.False(t, p.InclusionProof.Match())
28 | p, err = NewProof(s.c, s.s, s.genesis.Hash, s.key)
29 | require.Nil(t, err)
30 | require.True(t, p.InclusionProof.Match())
31 | }
32 |
33 | func TestVerify(t *testing.T) {
34 | s := createSC(t)
35 | p, err := NewProof(s.c, s.s, s.genesis.Hash, s.key)
36 | require.Nil(t, err)
37 | require.True(t, p.InclusionProof.Match())
38 | require.Nil(t, p.Verify(s.genesis.SkipChainID()))
39 | key, values, err := p.KeyValue()
40 | require.Nil(t, err)
41 | require.Equal(t, s.key, key)
42 | require.Equal(t, s.value, values[0])
43 |
44 | require.Equal(t, ErrorVerifySkipchain, p.Verify(s.genesis2.SkipChainID()))
45 |
46 | p.Latest.Data, err = network.Marshal(&DataHeader{
47 | CollectionRoot: getSBID("123"),
48 | })
49 | require.Nil(t, err)
50 | require.Equal(t, ErrorVerifyCollectionRoot, p.Verify(s.genesis.SkipChainID()))
51 | }
52 |
53 | type sc struct {
54 | c *collectionDB // a usable collectionDB to store key/value pairs
55 | s *skipchain.SkipBlockDB // a usable skipchain DB to store blocks
56 | genesis *skipchain.SkipBlock // the first genesis block, doesn't hold any data
57 | genesisPrivs []kyber.Scalar // private keys of genesis roster
58 | // second block of skipchain defined by 'genesis'. It holds a key/value
59 | // in its data and a roster different from the genesis-block.
60 | sb2 *skipchain.SkipBlock
61 | genesis2 *skipchain.SkipBlock // a second genesis block with a different roster
62 | key []byte // key stored in sb2
63 | value []byte // value stored in sb2
64 | }
65 |
66 | // sc creates an sc structure ready to be used in tests.
67 | func createSC(t *testing.T) (s sc) {
68 | bnsc := []byte("skipblock-test")
69 | f, err := ioutil.TempFile("", string(bnsc))
70 | require.Nil(t, err)
71 | fname := f.Name()
72 | require.Nil(t, f.Close())
73 |
74 | db, err := bolt.Open(fname, 0600, nil)
75 | require.Nil(t, err)
76 |
77 | err = db.Update(func(tx *bolt.Tx) error {
78 | _, err := tx.CreateBucket(bnsc)
79 | return err
80 | })
81 | require.Nil(t, err)
82 | s.s = skipchain.NewSkipBlockDB(db, bnsc)
83 |
84 | bnol := []byte("omniledger-test")
85 | err = db.Update(func(tx *bolt.Tx) error {
86 | _, err := tx.CreateBucket(bnol)
87 | return err
88 | })
89 | require.Nil(t, err)
90 | s.c = newCollectionDB(db, bnol)
91 |
92 | s.key = []byte("key")
93 | s.value = []byte("value")
94 | s.c.Store(&StateChange{StateAction: Create, ObjectID: s.key, Value: s.value})
95 |
96 | s.genesis = skipchain.NewSkipBlock()
97 | s.genesis.Roster, s.genesisPrivs = genRoster(1)
98 | s.genesis.Hash = s.genesis.CalculateHash()
99 |
100 | s.sb2 = skipchain.NewSkipBlock()
101 | s.sb2.Roster, _ = genRoster(2)
102 | s.sb2.Data, err = network.Marshal(&DataHeader{
103 | CollectionRoot: s.c.RootHash(),
104 | })
105 | require.Nil(t, err)
106 | s.sb2.Hash = s.sb2.CalculateHash()
107 | s.genesis.ForwardLink = genForwardLink(t, s.genesis, s.sb2, s.genesisPrivs)
108 |
109 | s.s.Store(s.genesis)
110 | s.s.Store(s.sb2)
111 |
112 | s.genesis2 = skipchain.NewSkipBlock()
113 | s.genesis2.Roster, _ = genRoster(2)
114 | s.genesis2.Hash = s.genesis2.CalculateHash()
115 | s.s.Store(s.genesis2)
116 | return
117 | }
118 |
119 | func genForwardLink(t *testing.T, from, to *skipchain.SkipBlock, privs []kyber.Scalar) []*skipchain.ForwardLink {
120 | fwd := &skipchain.ForwardLink{
121 | From: from.Hash,
122 | To: to.Hash,
123 | }
124 | if !from.Roster.ID.Equal(to.Roster.ID) {
125 | fwd.NewRoster = to.Roster
126 | }
127 | v, V := cosi.Commit(cothority.Suite)
128 | ch, err := cosi.Challenge(cothority.Suite, V, from.Roster.Aggregate, fwd.Hash())
129 | require.Nil(t, err)
130 | resp, err := cosi.Response(cothority.Suite, privs[0], v, ch)
131 | require.Nil(t, err)
132 | mask, err := cosi.NewMask(cothority.Suite, from.Roster.Publics(), from.Roster.Publics()[0])
133 | require.Nil(t, err)
134 | sig, err := cosi.Sign(cothority.Suite, V, resp, mask)
135 | require.Nil(t, err)
136 | fwd.Signature = byzcoinx.FinalSignature{
137 | Msg: fwd.Hash(),
138 | Sig: sig,
139 | }
140 | require.Nil(t, err)
141 | return []*skipchain.ForwardLink{fwd}
142 | }
143 |
144 | func getSBID(s string) skipchain.SkipBlockID {
145 | s256 := sha256.Sum256([]byte(s))
146 | return skipchain.SkipBlockID(s256[:])
147 | }
148 |
149 | func genRoster(num int) (*onet.Roster, []kyber.Scalar) {
150 | var ids []*network.ServerIdentity
151 | var privs []kyber.Scalar
152 | for i := 0; i < num; i++ {
153 | n := network.Address(fmt.Sprintf("tls://0.0.0.%d:2000", 2*i+1))
154 | kp := key.NewKeyPair(cothority.Suite)
155 | ids = append(ids, network.NewServerIdentity(kp.Public, n))
156 | privs = append(privs, kp.Private)
157 | }
158 | return onet.NewRoster(ids), privs
159 | }
160 |
161 | func overwriteSB(t *testing.T, s sc, sb *skipchain.SkipBlock) {
162 | // We have to update manually, because s.s.Store checks
163 | // the validity of the block.
164 | err := s.s.Update(func(tx *bolt.Tx) error {
165 | key := sb.Hash
166 | val, err := network.Marshal(sb)
167 | if err != nil {
168 | return err
169 | }
170 | return tx.Bucket([]byte("skipblock-test")).Put(key, val)
171 | })
172 | require.Nil(t, err)
173 | }
174 |
--------------------------------------------------------------------------------
/omniledger/service/proto.awk:
--------------------------------------------------------------------------------
1 | BEGIN {
2 | a = 0
3 | ar="[]"
4 | tr["[]byte"]="bytes"
5 | tr["abstract.Point"]="bytes"
6 | tr["Version"]="sint32"
7 | print "syntax = \"proto2\";"
8 | }
9 |
10 | a == 0 && /PROTOSTART/{ a = 1; next }
11 |
12 | a == 1 && /^\/\// { sub( "^\/\/ *", "" ); print; next }
13 | a == 1 { a = 2; print; next }
14 |
15 | a == 2 && /^type.*struct/ { print "message", $2, "{"; a = 3; i = 1; next }
16 | a == 2 { print; next }
17 |
18 | a == 3 && /^\}/ { print; a = 2; next }
19 | a == 3 && / *\/\// { sub( " *\/\/\s*", "" ); print " //", $0; next }
20 | a == 3 && /\*/ { sub( "\\*", "", $2 )
21 | print_field("optional", $2, $1, i)
22 | i = i + 1
23 | next
24 | }
25 | a == 3 { print_field("required", $2, $1, i)
26 | i = i + 1
27 | next
28 | }
29 |
30 | function print_field( optional, typ, name, ind ){
31 | if ( typ in tr )
32 | typ = tr[typ]
33 | if ( name ~ /bytes/ ){
34 | optional = "repeated"
35 | }
36 | if ( typ ~ /^\[\]/ ){
37 | optional = "repeated"
38 | sub(/^\[\]/, "", typ)
39 | }
40 | sub(/^.*\./, "", typ)
41 | sub(/^Point$/, "bytes", typ)
42 | sub(/^Scalar$/, "bytes", typ)
43 | sub(/^ID$/, "bytes", typ)
44 | sub(/^SkipBlockID$/, "bytes", typ)
45 | sub(/^Role$/, "int", typ)
46 | sub(/^int32$/, "sint32", typ)
47 | sub(/^int64$/, "sint64", typ)
48 | sub(/^int$/, "sint32", typ)
49 | sub(/^\[\]byte$/, "bytes", typ)
50 | sub(/^\*/, "", typ)
51 | print sprintf(" %s %s %s = %d;", optional, typ, tolower(name), ind )
52 | }
53 |
--------------------------------------------------------------------------------
/omniledger/service/struct.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | bolt "github.com/coreos/bbolt"
8 | "github.com/dedis/student_18_omniledger/omniledger/collection"
9 | "github.com/dedis/student_18_omniledger/omniledger/darc"
10 | "gopkg.in/dedis/cothority.v2/skipchain"
11 | "gopkg.in/dedis/onet.v2/network"
12 | )
13 |
14 | func init() {
15 | network.RegisterMessages(&darc.Signature{},
16 | DataHeader{}, DataBody{})
17 | }
18 |
19 | type collectionDB struct {
20 | db *bolt.DB
21 | bucketName []byte
22 | coll collection.Collection
23 | }
24 |
25 | // OmniLedgerContract is the type signature of the class functions
26 | // which can be registered with the omniledger service.
27 | // Since the outcome of the verification depends on the state of the collection
28 | // which is to be modified, we pass it as a pointer here.
29 | type OmniLedgerContract func(cdb collection.Collection, tx Instruction, c []Coin) ([]StateChange, []Coin, error)
30 |
31 | // newCollectionDB initialises a structure and reads all key/value pairs to store
32 | // it in the collection.
33 | func newCollectionDB(db *bolt.DB, name []byte) *collectionDB {
34 | c := &collectionDB{
35 | db: db,
36 | bucketName: name,
37 | coll: collection.New(collection.Data{}, collection.Data{}),
38 | }
39 | c.db.Update(func(tx *bolt.Tx) error {
40 | _, err := tx.CreateBucket(name)
41 | if err != nil {
42 | return fmt.Errorf("create bucket: %s", err)
43 | }
44 | return nil
45 | })
46 | c.loadAll()
47 | // TODO: Check the merkle tree root.
48 | return c
49 | }
50 |
51 | func (c *collectionDB) loadAll() {
52 | c.db.View(func(tx *bolt.Tx) error {
53 | // Assume bucket exists and has keys
54 | b := tx.Bucket([]byte(c.bucketName))
55 | cur := b.Cursor()
56 |
57 | for k, v := cur.First(); k != nil; k, v = cur.Next() {
58 | sig := b.Get(append(k, []byte("sig")...))
59 | ck := make([]byte, len(k))
60 | vk := make([]byte, len(v))
61 | csig := make([]byte, len(sig))
62 | copy(ck, k)
63 | copy(vk, v)
64 | copy(csig, sig)
65 | c.coll.Add(ck, vk, csig)
66 | }
67 |
68 | return nil
69 | })
70 | }
71 |
72 | func storeInColl(coll collection.Collection, t *StateChange) error {
73 | switch t.StateAction {
74 | case Create:
75 | return coll.Add(t.ObjectID, t.Value, t.ContractID)
76 | case Update:
77 | return coll.Set(t.ObjectID, t.Value, t.ContractID)
78 | case Remove:
79 | return coll.Remove(t.ObjectID)
80 | default:
81 | return errors.New("invalid state action")
82 | }
83 | }
84 |
85 | func (c *collectionDB) Store(t *StateChange) error {
86 | if err := storeInColl(c.coll, t); err != nil {
87 | return err
88 | }
89 | err := c.db.Update(func(tx *bolt.Tx) error {
90 | bucket := tx.Bucket([]byte(c.bucketName))
91 | keykind := make([]byte, len(t.ObjectID)+4)
92 | copy(keykind, t.ObjectID)
93 | keykind = append(keykind, []byte("kind")...)
94 |
95 | switch t.StateAction {
96 | case Create, Update:
97 | if err := bucket.Put(t.ObjectID, t.Value); err != nil {
98 | return err
99 | }
100 | return bucket.Put(keykind, t.ContractID)
101 | case Remove:
102 | if err := bucket.Delete(t.ObjectID); err != nil {
103 | return err
104 | }
105 | return bucket.Delete(keykind)
106 | default:
107 | return errors.New("invalid state action")
108 | }
109 | })
110 | return err
111 | }
112 |
113 | func (c *collectionDB) GetValueContract(key []byte) (value, contract []byte, err error) {
114 | proof, err := c.coll.Get(key).Record()
115 | if err != nil {
116 | return
117 | }
118 | hashes, err := proof.Values()
119 | if err != nil {
120 | return
121 | }
122 | if len(hashes) == 0 {
123 | err = errors.New("nothing stored under that key")
124 | return
125 | }
126 | value, ok := hashes[0].([]byte)
127 | if !ok {
128 | err = errors.New("the value is not of type []byte")
129 | return
130 | }
131 | contract, ok = hashes[1].([]byte)
132 | if !ok {
133 | err = errors.New("the contract is not of type []byte")
134 | return
135 | }
136 | return
137 | }
138 |
139 | // RootHash returns the hash of the root node in the merkle tree.
140 | func (c *collectionDB) RootHash() []byte {
141 | return c.coll.GetRoot()
142 | }
143 |
144 | // tryHash returns the merkle root of the collection as if the key value pairs
145 | // in the transactions had been added, without actually adding it.
146 | func (c *collectionDB) tryHash(ts []StateChange) (mr []byte, rerr error) {
147 | for _, sc := range ts {
148 | err := c.coll.Add(sc.ObjectID, sc.Value, sc.ContractID)
149 | if err != nil {
150 | rerr = err
151 | return
152 | }
153 | // remove the pair after we got the merkle root.
154 | defer func(k []byte) {
155 | err = c.coll.Remove(k)
156 | if err != nil {
157 | rerr = err
158 | mr = nil
159 | }
160 | }(sc.ObjectID)
161 | }
162 | mr = c.coll.GetRoot()
163 | return
164 | }
165 |
166 | // RegisterContract stores the contract in a map and will
167 | // call it whenever a contract needs to be done.
168 | // GetService makes it possible to give either an `onet.Context` or
169 | // `onet.Server` to `RegisterContract`.
170 | func RegisterContract(s skipchain.GetService, kind string, f OmniLedgerContract) error {
171 | scs := s.Service(ServiceName)
172 | if scs == nil {
173 | return errors.New("Didn't find our service: " + ServiceName)
174 | }
175 | return scs.(*Service).registerContract(kind, f)
176 | }
177 |
178 | // DataHeader is the data passed to the Skipchain
179 | type DataHeader struct {
180 | // CollectionRoot is the root of the merkle tree of the colleciton after
181 | // applying the valid transactions.
182 | CollectionRoot []byte
183 | // ClientTransactionHash is the sha256 hash of all the transactions in the body
184 | ClientTransactionHash []byte
185 | // StateChangesHash is the sha256 of all the stateChanges occuring through the
186 | // clientTransactions.
187 | StateChangesHash []byte
188 | // Timestamp is a unix timestamp in nanoseconds.
189 | Timestamp int64
190 | }
191 |
192 | // DataBody is stored in the body of the skipblock but is not hashed. This reduces
193 | // the proof needed for a key/value pair.
194 | type DataBody struct {
195 | Transactions ClientTransactions
196 | }
197 |
--------------------------------------------------------------------------------
/omniledger/service/struct_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "testing"
8 |
9 | bolt "github.com/coreos/bbolt"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | var testName = []byte("coll1")
14 |
15 | func TestCollectionDBStrange(t *testing.T) {
16 | tmpDB, err := ioutil.TempFile("", "tmpDB")
17 | require.Nil(t, err)
18 | tmpDB.Close()
19 | defer os.Remove(tmpDB.Name())
20 |
21 | db, err := bolt.Open(tmpDB.Name(), 0600, nil)
22 | require.Nil(t, err)
23 |
24 | cdb := newCollectionDB(db, testName)
25 | key := []byte("first")
26 | value := []byte("value")
27 | contract := []byte("mycontract")
28 | err = cdb.Store(&StateChange{
29 | StateAction: Create,
30 | ObjectID: key,
31 | Value: value,
32 | ContractID: contract,
33 | })
34 | require.Nil(t, err)
35 | v, c, err := cdb.GetValueContract([]byte("first"))
36 | require.Nil(t, err)
37 | require.Equal(t, value, v)
38 | require.Equal(t, contract, c)
39 | }
40 |
41 | func TestCollectionDB(t *testing.T) {
42 | kvPairs := 16
43 |
44 | tmpDB, err := ioutil.TempFile("", "tmpDB")
45 | require.Nil(t, err)
46 | tmpDB.Close()
47 | defer os.Remove(tmpDB.Name())
48 |
49 | db, err := bolt.Open(tmpDB.Name(), 0600, nil)
50 | require.Nil(t, err)
51 |
52 | cdb := newCollectionDB(db, testName)
53 | pairs := map[string]string{}
54 | myContract := []byte("myContract")
55 | for i := 0; i < kvPairs; i++ {
56 | pairs[fmt.Sprintf("Key%d", i)] = fmt.Sprintf("value%d", i)
57 | }
58 |
59 | // Store all key/value pairs
60 | for k, v := range pairs {
61 | sc := &StateChange{
62 | StateAction: Create,
63 | ObjectID: []byte(k),
64 | Value: []byte(v),
65 | ContractID: myContract,
66 | }
67 | require.Nil(t, cdb.Store(sc))
68 | }
69 |
70 | // Verify it's all there
71 | for c, v := range pairs {
72 | stored, contract, err := cdb.GetValueContract([]byte(c))
73 | require.Nil(t, err)
74 | require.Equal(t, v, string(stored))
75 | require.Equal(t, myContract, contract)
76 | }
77 |
78 | // Get a new db handler
79 | cdb2 := newCollectionDB(db, testName)
80 |
81 | // Verify it's all there
82 | for c, v := range pairs {
83 | stored, _, err := cdb2.GetValueContract([]byte(c))
84 | require.Nil(t, err)
85 | require.Equal(t, v, string(stored))
86 | }
87 |
88 | // Update
89 | for k, v := range pairs {
90 | pairs[k] = v + "-2"
91 | }
92 | for k, v := range pairs {
93 | sc := &StateChange{
94 | StateAction: Update,
95 | ObjectID: []byte(k),
96 | Value: []byte(v),
97 | ContractID: myContract,
98 | }
99 | require.Nil(t, cdb2.Store(sc), k)
100 | }
101 | for k, v := range pairs {
102 | stored, contract, err := cdb2.GetValueContract([]byte(k))
103 | require.Nil(t, err)
104 | require.Equal(t, v, string(stored))
105 | require.Equal(t, myContract, contract)
106 | }
107 |
108 | // Delete
109 | for c := range pairs {
110 | sc := &StateChange{
111 | StateAction: Remove,
112 | ObjectID: []byte(c),
113 | ContractID: myContract,
114 | }
115 | require.Nil(t, cdb2.Store(sc))
116 | }
117 | for c := range pairs {
118 | _, _, err := cdb2.GetValueContract([]byte(c))
119 | require.NotNil(t, err, c)
120 | }
121 | }
122 |
123 | // TODO: Test good case, bad add case, bad remove case
124 | func TestCollectionDBtryHash(t *testing.T) {
125 | tmpDB, err := ioutil.TempFile("", "tmpDB")
126 | require.Nil(t, err)
127 | tmpDB.Close()
128 | defer os.Remove(tmpDB.Name())
129 |
130 | db, err := bolt.Open(tmpDB.Name(), 0600, nil)
131 | require.Nil(t, err)
132 |
133 | cdb := newCollectionDB(db, testName)
134 | scs := []StateChange{{
135 | StateAction: Create,
136 | ObjectID: []byte("key1"),
137 | ContractID: []byte("kind1"),
138 | Value: []byte("value1"),
139 | },
140 | {
141 | StateAction: Create,
142 | ObjectID: []byte("key2"),
143 | ContractID: []byte("kind2"),
144 | Value: []byte("value2"),
145 | },
146 | }
147 | mrTrial, err := cdb.tryHash(scs)
148 | require.Nil(t, err)
149 | _, _, err = cdb.GetValueContract([]byte("key1"))
150 | require.EqualError(t, err, "no match found")
151 | _, _, err = cdb.GetValueContract([]byte("key2"))
152 | require.EqualError(t, err, "no match found")
153 | cdb.Store(&scs[0])
154 | cdb.Store(&scs[1])
155 | mrReal := cdb.RootHash()
156 | require.Equal(t, mrTrial, mrReal)
157 | }
158 |
--------------------------------------------------------------------------------
/omniledger/service/transaction_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/dedis/student_18_omniledger/omniledger/darc"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func nonceStr(s string) (n Nonce) {
11 | copy(n[:], s)
12 | return n
13 | }
14 | func darcidStr(s string) (n darc.ID) {
15 | n = make([]byte, 32)
16 | copy(n, s)
17 | return n
18 | }
19 |
20 | func TestSortTransactions(t *testing.T) {
21 | ts1 := []ClientTransaction{
22 | {
23 | Instructions: []Instruction{{
24 | ObjectID: ObjectID{
25 | DarcID: darcidStr("key1"),
26 | InstanceID: nonceStr("nonce1"),
27 | },
28 | Spawn: &Spawn{
29 | ContractID: "kind1",
30 | },
31 | }}},
32 | {
33 | Instructions: []Instruction{{
34 | ObjectID: ObjectID{
35 | DarcID: darcidStr("key2"),
36 | InstanceID: nonceStr("nonce2"),
37 | },
38 | Spawn: &Spawn{
39 | ContractID: "kind2",
40 | },
41 | }}},
42 | {
43 | Instructions: []Instruction{{
44 | ObjectID: ObjectID{
45 | DarcID: darcidStr("key3"),
46 | InstanceID: nonceStr("nonce3"),
47 | },
48 | Spawn: &Spawn{
49 | ContractID: "kind3",
50 | },
51 | }}},
52 | }
53 | ts2 := []ClientTransaction{
54 | {
55 | Instructions: []Instruction{{
56 | ObjectID: ObjectID{
57 | DarcID: darcidStr("key2"),
58 | InstanceID: nonceStr("nonce2"),
59 | },
60 | Spawn: &Spawn{
61 | ContractID: "kind2",
62 | },
63 | }}},
64 | {
65 | Instructions: []Instruction{{
66 | ObjectID: ObjectID{
67 | DarcID: darcidStr("key1"),
68 | InstanceID: nonceStr("nonce1"),
69 | },
70 | Spawn: &Spawn{
71 | ContractID: "kind1",
72 | },
73 | }}},
74 | {
75 | Instructions: []Instruction{{
76 | ObjectID: ObjectID{
77 | DarcID: darcidStr("key3"),
78 | InstanceID: nonceStr("nonce3"),
79 | },
80 | Spawn: &Spawn{
81 | ContractID: "kind3",
82 | },
83 | }}},
84 | }
85 | err := sortTransactions(ts1)
86 | require.Nil(t, err)
87 | err = sortTransactions(ts2)
88 | require.Nil(t, err)
89 | for i := range ts1 {
90 | require.Equal(t, ts1[i], ts2[i])
91 | }
92 | }
93 |
94 | func TestTransaction_Signing(t *testing.T) {
95 | signer := darc.NewSignerEd25519(nil, nil)
96 | ids := []*darc.Identity{signer.Identity()}
97 | d := darc.NewDarc(darc.InitRules(ids, ids), []byte("genesis darc"))
98 | d.Rules.AddRule("Spawn_dummy_kind", d.Rules.GetSignExpr())
99 | require.Nil(t, d.Verify())
100 |
101 | instr, err := createInstr(d.GetBaseID(), "dummy_kind", []byte("dummy_value"), signer)
102 | require.Nil(t, err)
103 |
104 | require.Nil(t, instr.SignBy(signer))
105 |
106 | req, err := instr.ToDarcRequest()
107 | require.Nil(t, err)
108 | require.Nil(t, req.Verify(d))
109 | }
110 |
111 | func createOneClientTx(dID darc.ID, kind string, value []byte, signer *darc.Signer) (ClientTransaction, error) {
112 | instr, err := createInstr(dID, kind, value, signer)
113 | t := ClientTransaction{
114 | Instructions: []Instruction{instr},
115 | }
116 | return t, err
117 | }
118 |
119 | func createInstr(dID darc.ID, contractID string, value []byte, signer *darc.Signer) (Instruction, error) {
120 | instr := Instruction{
121 | ObjectID: ObjectID{
122 | DarcID: dID,
123 | InstanceID: GenNonce(),
124 | },
125 | Spawn: &Spawn{
126 | ContractID: contractID,
127 | Args: Arguments{{Name: "data", Value: value}},
128 | },
129 | }
130 | err := instr.SignBy(signer)
131 | return instr, err
132 | }
133 |
--------------------------------------------------------------------------------
/omniledger/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | DBG_TEST=2
4 | # Debug-level for app
5 | DBG_APP=2
6 | DBG_SRV=2
7 |
8 | . $(go env GOPATH)/src/gopkg.in/dedis/onet.v2/app/libtest.sh
9 |
10 | main(){
11 | startTest
12 | buildConode github.com/dedis/student_18_omniledger/omniledger/service
13 | test CreateStoreRead
14 | stopTest
15 | }
16 |
17 | testCreateStoreRead(){
18 | pair=$(./"$APP" keypair)
19 | pk=$(echo "$pair" | grep Public | sed 's/Public: //')
20 | # sk=$(echo "$pair" | grep Private | sed 's/Private: //')
21 | runCoBG 1 2 3
22 | runGrepSed ID "s/.* //" ./"$APP" create public.toml "$pk"
23 | ID=$SED
24 | echo ID is "$ID"
25 | testOK runOmni set public.toml "$ID" one two
26 | testOK runOmni get public.toml "$ID" one
27 | testFail runOmni get public.toml "$ID" two
28 | }
29 |
30 | runOmni(){
31 | dbgRun ./"$APP" -d $DBG_APP "$@"
32 | }
33 |
34 | main
35 |
--------------------------------------------------------------------------------