├── .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 | ![collection](assets/images/example/0.gif) 3 | 4 | States of admin, read/writers, and readers are A0, W0, R0. 5 | 6 | ## Charlie emits a request adding Erin to admin 7 | ![collection](assets/images/example/1.gif) 8 | 9 | ## State of "admin" is incremented to A1 10 | ![collection](assets/images/example/2.gif) 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 | ![collection](assets/images/example/3.gif) 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 | ![collection](assets/images/example/4.gif) 21 | 22 | QUESTION: What are they checking? 23 | 24 | ## New consistent view of state A1 25 | ![collection](assets/images/example/5.gif) 26 | 27 | ## Erin emits a request adding Dan to RW 28 | ![collection](assets/images/example/6.gif) 29 | 30 | ## State of "read/writers" is incremented to W1 31 | ![collection](assets/images/example/7.gif) 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 | ![collection](assets/images/example/8.gif) 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 | ![collection](assets/images/example/9.gif) 42 | 43 | ## Consistent view of read/writers state == W1 44 | ![collection](assets/images/example/10.gif) 45 | 46 | ## Erin emits request to add Alice to RW 47 | ![collection](assets/images/example/11.gif) 48 | 49 | ## State of "Read/writers" is incremented to W2 50 | ![collection](assets/images/example/12.gif) 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 | ![collection](assets/images/example/13.gif) 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 | ![collection](assets/images/example/14.gif) 61 | 62 | ## Consistent view of read/writers state == W2 63 | ![collection](assets/images/example/15.gif) 64 | 65 | ## Charlie emits a request "moving" Dan to read 66 | ![collection](assets/images/example/16.gif) 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 | ![collection](assets/images/example/17.gif) 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 | ![collection](assets/images/example/18.gif) 77 | 78 | ## Verifiers accept the proofs and apply the mutations 79 | ![collection](assets/images/example/19.gif) 80 | 81 | ## Consistent view 82 | ![collection](assets/images/example/20.gif) 83 | 84 | ## Server emits false proof and fraudulent mutation to all 85 | ![collection](assets/images/example/21.gif) 86 | 87 | Proof: Mallory is in A1 (false). Mutation: add "NotAHacker" to read/writers. 88 | 89 | ## Verifiers do not find Mallory in A1 90 | ![collection](assets/images/example/22.gif) 91 | 92 | Thus the fraudulent mutation is refused. 93 | 94 | ## State remains consistent and unmodified 95 | ![collection](assets/images/example/23.gif) 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 | --------------------------------------------------------------------------------