├── .gitignore
├── LICENSE
├── README.md
├── code
├── Fts.java
├── FtsResult.java
├── Node.java
├── ProofEntry.java
└── Stakeholder.java
└── stake-tree.drawio.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.jar
8 | *.war
9 | *.ear
10 |
11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
12 | hs_err_pid*
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Right to copy (c) Bastian Fredriksson 2017
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repository contains an implementation of an algorithm called `FTS-TREE` which can be used to sample block leaders from a set of stakeholders stored in a Merkle tree. The algorithm builds upon an idea called follow-the-satoshi introduced in [1].
2 |
3 | The idea is simple: The edges the Merkle tree are labelled with the amount of coins in the left and right subtree respectively. Given a psuedo-random number generator, one can randomly select a stakeholder from the tree, weighted by the amount of coins they own, by traversing the tree down to a leaf node, containing one of the stakeholders. Each stakeholder controls a number of coins and a private key used to sign blocks.
4 |
5 | 
6 |
7 | The image above shows a Merkle tree with eight stakeholders, controlling a total of 95 coins. The nodes highlighted are the nodes visited after an execution of `FTS-TREE` where the stakeholder with the blue key was chosen as the next block leader.
8 |
9 | The siblings to the nodes traversed by `FTS-TREE` constitutes a Merkle proof which can be used to prove that the stakeholder was chosen correctly. This Merkle proof can then be put into the block header of the block and other blockchain nodes can use the Merkle root hash of the tree to verify the block.
10 |
11 | This makes it possible to prune away old transactions and still be able to verify old blocks, enabling a lightweight blockchain scheme based on proof of stake.
12 |
13 | It is fairly easy to prove that `FTS-TREE` is fair. More precisely; given a Merkle tree with N stakeholders, containing a total of x1 + x2... + xN coins, `FTS-TREE` selects the *k*:th stakeholder 1 ≤ k ≤ N controlling xk coins with probability xk / (x1 + x2... + xN).
14 |
15 | Example output from `FTS-TREE`
16 |
17 | > Creating Merkle tree with 15 nodes.
18 | > Hash 1: 04CA42FBD140569E6B647B13DDAC3D85B9626FD0B2D98AE6717EBA5B96EB0CCE
19 | > Hash 2: 0FF2AA9DF6C8845E89A8952A4EC5C74E64FFA39E2813C3D22EB9D31DDE1DE39A
20 | > Hash 3: 7333612E8DD6A02C9B0595E27B537851FCB27B44FF25FFBC024BF554C3E9B939
21 | > Hash 4: AE0668DDE2F58A9E913480D0B5D3F4F15C4A4945068DD524D9D4B9BFB6480F3E
22 | > Hash 5: 2F4DE43A312F30EA4303F1088E6F4D6EBEB4FFFECF669286423AD2C4D2A3AC18
23 | > Hash 6: 3EF21DAF7CB37B8E68929872C4132E9021812CC3DE60A5C4345541728CECCE50
24 | > Hash 7: 44E8B580F897CCAA525E7CA6E05D07350F53681FB4D6B4223032C0D6FBFE9537
25 | > Hash 8: 3AF7519C8A2EDE05A8F5E63041824B453EF989ED616FD80F99A7043DA4303E24
26 | > Hash 9: 67BEBFBB2B7A3312E1C330A5E904FE6B24868D30CB1E78DA16C13C25FE0F7E64
27 | > Hash 10: 33C51A085F5A65ADB2A6096D8140CBF8E9AFA635D384C2C393A66C262112835B
28 | > Hash 11: 36195B3A1FE4BFAD55817C40C2041B0EF98005BAE6C79F7DE21BF190801A2A56
29 | > Hash 12: 4F171E075FB62B62EBDA820D3FFCA8287922FEE53710D8FEACD42DEAD202C29B
30 | > Hash 13: D0D8072D186E08048C23B3E92C80F1AB50DD61F8A7E2E8910B64E757A604B604
31 | > Hash 14: 35F9843C6BF03C26052F4589CF2F11F4A709A02CBC4035E2941E34A2E5B5BA16
32 | > Hash 15: BC1D39F92E64DB7005809B17E2A79FC181BC929968FEAE7B6393420768AFB419
33 | > Doing follow-the-satoshi in the stake tree
34 | > Left subtree 51 coins / right subtree 15 coins. Picking coin number 63
35 | > Choosing right subtree...
36 | > Left subtree 12 coins / right subtree 3 coins. Picking coin number 4
37 | > Choosing left subtree...
38 | > Left subtree 8 coins / right subtree 4 coins. Picking coin number 1
39 | > Choosing left subtree...
40 | > merkleProof {
41 | > ...(0FF2AA9DF6C8845E89A8952A4EC5C74E64FFA39E2813C3D22EB9D31DDE1DE39A, 51, 15)
42 | > ...(44E8B580F897CCAA525E7CA6E05D07350F53681FB4D6B4223032C0D6FBFE9537, 12, 3)
43 | > ...(D0D8072D186E08048C23B3E92C80F1AB50DD61F8A7E2E8910B64E757A604B604, 8, 4)
44 | > }
45 | > stakeholder {
46 | > ...Stakeholder 4
47 | > }
48 | > Verifying the result
49 | > Building audit path... 1 0 0 OK
50 | > Next hash: 3EF21DAF7CB37B8E68929872C4132E9021812CC3DE60A5C4345541728CECCE50
51 | > Next hash: 7333612E8DD6A02C9B0595E27B537851FCB27B44FF25FFBC024BF554C3E9B939
52 | > Next hash: 04CA42FBD140569E6B647B13DDAC3D85B9626FD0B2D98AE6717EBA5B96EB0CCE
53 | > Root hash matches!
54 |
55 | ----------
56 |
57 | [1] Bentov, Iddo, et al. "Proof of Activity: Extending Bitcoin's Proof of Work via Proof of Stake [Extended Abstract] y." ACM SIGMETRICS Performance Evaluation Review 42.3 (2014): 34-37.
58 |
--------------------------------------------------------------------------------
/code/Fts.java:
--------------------------------------------------------------------------------
1 | import java.nio.charset.StandardCharsets;
2 | import java.security.MessageDigest;
3 | import java.security.NoSuchAlgorithmException;
4 | import java.util.ArrayList;
5 | import java.util.Arrays;
6 | import java.util.List;
7 | import java.util.Random;
8 |
9 | import javax.xml.bind.DatatypeConverter;
10 |
11 | public class Fts {
12 | public static void main(String[] args) {
13 | new Fts();
14 | }
15 |
16 | /**
17 | * Computes the message digest SHA2(b1 | b2 | b3 | b4) where
18 | * | denotes concatenation.
19 | */
20 | public static byte[] SHA2(byte[] b1, byte[] b2, byte[] b3, byte[] b4) {
21 | try {
22 | MessageDigest sha2 = MessageDigest.getInstance("SHA-256");
23 | byte[] b = new byte[b1.length + b2.length + b3.length + b4.length];
24 | System.arraycopy(b1, 0, b, 0, b1.length);
25 | System.arraycopy(b2, 0, b, b1.length, b2.length);
26 | System.arraycopy(b3, 0, b, b1.length + b2.length, b3.length);
27 | System.arraycopy(b4, 0, b, b1.length + b2.length + b3.length, b4.length);
28 | return sha2.digest(b);
29 | } catch (NoSuchAlgorithmException e) {
30 | e.printStackTrace();
31 | return null;
32 | }
33 | }
34 |
35 | /**
36 | * Computes the message digest SHA2(b).
37 | */
38 | public static byte[] SHA2(byte[] b) {
39 | try {
40 | MessageDigest sha2 = MessageDigest.getInstance("SHA-256");
41 | return sha2.digest(b);
42 | } catch (NoSuchAlgorithmException e) {
43 | e.printStackTrace();
44 | return null;
45 | }
46 | }
47 |
48 | public Fts() {
49 | // Create some stakeholders
50 | List stakeholders = new ArrayList<>();
51 | for (int i = 0, c = 20; i < 8; i++, c = c % 2 == 0 ? c / 2 : c*3 + 1) {
52 | final String name = String.format("Stakeholder %d", i);
53 | final int coins = c;
54 | stakeholders.add(new Stakeholder(name, coins));
55 | }
56 | // Create the Merkle tree
57 | final Node[] tree = createMerkleTree(stakeholders);
58 | System.out.println("Doing follow-the-satoshi in the stake tree.");
59 | FtsResult ftsResult = ftsTree(tree, new Random(42));
60 | System.out.println(ftsResult);
61 | System.out.println("Verifying the result.");
62 | ftsVerify(new Random(42), tree[1].getMerkleHash(), ftsResult);
63 | }
64 |
65 | public Node[] createMerkleTree(List stakeholders) {
66 | final Node[] tree = new Node[stakeholders.size() * 2];
67 | System.out.println(String.format("Creating Merkle tree with %d nodes.", tree.length - 1));
68 | for (int i = 0; i < stakeholders.size(); i++) {
69 | tree[stakeholders.size() + i] = new Node(stakeholders.get(i));
70 | }
71 | for (int i = stakeholders.size() - 1; i > 0; i--) {
72 | final Node left = tree[i * 2];
73 | final Node right = tree[i * 2 + 1];
74 | final byte[] hash = SHA2(left.getMerkleHash(),
75 | right.getMerkleHash(),
76 | String.valueOf(left.getCoins()).getBytes(StandardCharsets.US_ASCII),
77 | String.valueOf(right.getCoins()).getBytes(StandardCharsets.US_ASCII));
78 | tree[i] = new Node(left, right, hash);
79 | }
80 | for (int i = 1; i < tree.length; i++) {
81 | System.out.println(String.format("Hash %d: %s", i, DatatypeConverter.printHexBinary(tree[i].getMerkleHash())));
82 | }
83 | return tree;
84 | }
85 |
86 | public FtsResult ftsTree(Node[] tree, Random rng) {
87 | int i = 1;
88 | List merkleProof = new ArrayList<>();
89 | while (true) {
90 | if (tree[i].isLeaf()) {
91 | return new FtsResult(merkleProof, tree[i].getStakeholder());
92 | }
93 | final int x1 = tree[i].getLeftNode().getCoins();
94 | final int x2 = tree[i].getRightNode().getCoins();
95 | System.out.println(String.format("Left subtree %d coins / right subtree %d coins.", x1, x2));
96 | final int r = rng.nextInt(x1 + x2) + 1;
97 | System.out.println(String.format("Picking coin number %d", r));
98 | if (r <= x1) {
99 | System.out.println("Choosing left subtree...");
100 | i *= 2;
101 | merkleProof.add(new ProofEntry(tree[i + 1].getMerkleHash(), x1, x2));
102 | } else {
103 | System.out.println("Choosing right subtree...");
104 | i = i*2 + 1;
105 | merkleProof.add(new ProofEntry(tree[i -1].getMerkleHash(), x1, x2));
106 | }
107 | }
108 | }
109 |
110 | public boolean ftsVerify(Random rng, byte[] merkleRootHash, FtsResult ftsResult) {
111 | StringBuilder auditPath = new StringBuilder();
112 | System.out.print("Building audit path... ");
113 | for (ProofEntry proofEntry : ftsResult.getMerkleProof()) {
114 | final int x1 = proofEntry.getLeftBound();
115 | final int x2 = proofEntry.getRightBound();
116 | final int r = rng.nextInt(x1 + x2) + 1;
117 | if (r <= x1) {
118 | System.out.print("0 ");
119 | auditPath.append('0');
120 | } else {
121 | System.out.print("1 ");
122 | auditPath.append('1');
123 | }
124 | }
125 | System.out.println("OK");
126 | byte[] hx = SHA2(ftsResult.getStakeholder().toBytes());
127 | for (int i = ftsResult.getMerkleProof().size() - 1; i >= 0; i--) {
128 | final ProofEntry proofEntry = ftsResult.getMerkleProof().get(i);
129 | final byte[] x1 = String.valueOf(proofEntry.getLeftBound()).getBytes(StandardCharsets.US_ASCII);
130 | final byte[] x2 = String.valueOf(proofEntry.getRightBound()).getBytes(StandardCharsets.US_ASCII);
131 | final byte[] hy = proofEntry.getMerkleHash();
132 | if (auditPath.charAt(i) == '0') {
133 | hx = SHA2(hx, hy, x1, x2);
134 | } else {
135 | hx = SHA2(hy, hx, x1, x2);
136 | }
137 | System.out.println(String.format("Next hash: %s", DatatypeConverter.printHexBinary(hx)));
138 | }
139 | boolean result = Arrays.equals(hx, merkleRootHash);
140 | System.out.println(result ? "Root hash matches!" : "Invalid Merkle proof.");
141 | return result;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/code/FtsResult.java:
--------------------------------------------------------------------------------
1 | import java.util.List;
2 |
3 | /**
4 | * A class containing the result produced by follow-the-satoshi.
5 | */
6 | public class FtsResult {
7 | private final List merkleProof;
8 | private final Stakeholder stakeholder;
9 |
10 | public FtsResult(List merkleProof, Stakeholder stakeholder) {
11 | this.merkleProof = merkleProof;
12 | this.stakeholder = stakeholder;
13 | }
14 |
15 | public Stakeholder getStakeholder() {
16 | return stakeholder;
17 | }
18 |
19 | public List getMerkleProof() {
20 | return merkleProof;
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | final StringBuilder sb = new StringBuilder();
26 | sb.append("merkleProof {\n");
27 | for (ProofEntry proofEntry : merkleProof) {
28 | sb.append(String.format(" %s\n", proofEntry.toString()));
29 | }
30 | sb.append("}\n");
31 | sb.append("stakeholder {\n");
32 | sb.append(String.format(" %s\n", stakeholder.toString()));
33 | sb.append("}");
34 | return sb.toString();
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/code/Node.java:
--------------------------------------------------------------------------------
1 | public class Node {
2 | private final Node left, right;
3 | private final Stakeholder stakeholder;
4 | private final byte[] hash;
5 |
6 | private Node(Node left, Node right, Stakeholder stakeholder, byte[] hash) {
7 | this.stakeholder = stakeholder;
8 | this.left = left;
9 | this.right = right;
10 | this.hash = hash;
11 | }
12 |
13 | public Node(Node left, Node right, byte[] hash) {
14 | this(left, right, null, hash);
15 | }
16 |
17 | public Node(Stakeholder stakeholder) {
18 | this(null, null, stakeholder, Fts.SHA2(stakeholder.toBytes()));
19 | }
20 |
21 | public boolean isLeaf() {
22 | return stakeholder != null;
23 | }
24 |
25 | public Stakeholder getStakeholder() {
26 | return stakeholder;
27 | }
28 |
29 | public Node getLeftNode() {
30 | return left;
31 | }
32 |
33 | public Node getRightNode() {
34 | return right;
35 | }
36 |
37 | public byte[] getMerkleHash() {
38 | return hash;
39 | }
40 |
41 | public int getCoins() {
42 | if (isLeaf()) {
43 | return stakeholder.getCoins();
44 | }
45 | return left.getCoins() + right.getCoins();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/code/ProofEntry.java:
--------------------------------------------------------------------------------
1 | import javax.xml.bind.DatatypeConverter;
2 |
3 | /**
4 | * A class containing an entry (x1, x2, hash) in a Merkle proof, where
5 | * "x1" is the number of coins in the left subtree, "x2" is the number
6 | * of coins the right subtree and "hash" is the Merkle hash
7 | * H(left hash | right hash | x1 | x2).
8 | */
9 | public class ProofEntry {
10 | private final byte[] hash;
11 | private final int x1, x2;
12 |
13 | public ProofEntry(byte[] hash, int x1, int x2) {
14 | this.hash = hash;
15 | this.x1 = x1;
16 | this.x2 = x2;
17 | }
18 |
19 | public int getLeftBound() {
20 | return x1;
21 | }
22 |
23 | public int getRightBound() {
24 | return x2;
25 | }
26 |
27 | public byte[] getMerkleHash() {
28 | return hash;
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return String.format("(%s, %d, %d)",
34 | DatatypeConverter.printHexBinary(hash),
35 | x1,
36 | x2);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/code/Stakeholder.java:
--------------------------------------------------------------------------------
1 | import java.nio.charset.StandardCharsets;
2 |
3 | /**
4 | * A stakeholder in the Merkle tree. Each stakeholder has a name,
5 | * and controls an amount of coins.
6 | */
7 | public class Stakeholder {
8 | private final String name;
9 | private final int coins;
10 |
11 | public Stakeholder(String name, int coins) {
12 | this.name = name;
13 | this.coins = coins;
14 | }
15 |
16 | public String getName() {
17 | return name;
18 | }
19 |
20 | public int getCoins() {
21 | return coins;
22 | }
23 |
24 | public byte[] toBytes() {
25 | return String.format("%s%d", name, coins).
26 | getBytes(StandardCharsets.US_ASCII);
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | return name;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/stake-tree.drawio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realiserad/fts-tree/ada4ff831e55e05dbe55ecb5838b946582f7c1a0/stake-tree.drawio.png
--------------------------------------------------------------------------------