Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v6: Merkle tree hashing #1223

Open
wants to merge 2 commits into
base: v6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions diem-move/diem-framework/DPN/sources/0L/MerkleHash.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/// Implements a Merkle Tree Hash using SHA3-256.
module DiemFramework::MerkleHash {
use Std::Vector;
use Std::Hash;

/// An element was added past the max supported size.
const ELENGTH: u64 = 0;
/// The MerkleHash elements vector was empty when calling finalize.
const EEMPTY: u64 = 1;

/// REVIEW: Picked a random sensible sounding number.
const MAX_SIZE: u64 = 32;

struct MerkleHash has copy, drop, store {
elements: vector<vector<u8>>,
}

/// Creates an empty MerkleHash (cannot be finalized).
public fun empty(): MerkleHash {
MerkleHash {
elements: Vector::empty(),
}
}

/// Hashes and pushes a byte vector into the MerkleHash struct.
public fun push_back(merkle_hash: &mut MerkleHash, element: vector<u8>) {
assert!(Vector::length(&merkle_hash.elements) < MAX_SIZE, ELENGTH);
let h = Hash::sha3_256(element);
Vector::push_back(&mut merkle_hash.elements, h);
}

/// Computes the merkle tree root and returns it.
public fun finalize(merkle_hash: &mut MerkleHash): vector<u8> {
assert!(Vector::length(&merkle_hash.elements) > 0, EEMPTY);

// A new temporary vector is setup to hold the roots which will form the elements of the
// MerkleHash (a vector of length 1 containing the merkle root) once finalize is complete.
let roots = Vector::empty();

while (Vector::length(&merkle_hash.elements) > 1) {
// The elements are reversed such that they are returned in FIFO rather than LIFO
// order.
Vector::reverse(&mut merkle_hash.elements);

while (Vector::length(&merkle_hash.elements) > 0) {
let len = Vector::length(&merkle_hash.elements);
if (len >= 2) {
// The current length >= 2, can pop two elements from the back and hash.
let lhs = Vector::pop_back(&mut merkle_hash.elements);
let rhs = Vector::pop_back(&mut merkle_hash.elements);
// H(lhs || rhs)
Vector::append(&mut lhs, rhs);
let root = Hash::sha3_256(lhs);
Vector::push_back(&mut roots, root);
} else {
assert!(Vector::length(&merkle_hash.elements) == 1, ELENGTH);
// The current length == 1 so we can only pop one element and hash it.
let singleton = Vector::pop_back(&mut merkle_hash.elements);
// H(singleton)
let root = Hash::sha3_256(singleton);
Vector::push_back(&mut roots, root);
}
};

// The current length of elements should now be 0 since all its elements were popped,
// mutate the elements to be the next generation of roots and set the temporary root
// vector to the empty vector.
assert!(Vector::length(&merkle_hash.elements) == 0, ELENGTH);
assert!(Vector::length(&roots) > 0, ELENGTH);
merkle_hash.elements = roots;
roots = Vector::empty();
};

// The result is the first element in merkle_hash.elements.
Vector::remove(&mut merkle_hash.elements, 0)
}
}
91 changes: 91 additions & 0 deletions diem-move/diem-framework/DPN/tests/MerkleHashTests.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#[test_only]
module DiemFramework::MerkleHashTests {
use DiemFramework::MerkleHash;
use Std::Hash;
use Std::Vector;

const EEXPECTED_EQUALITY: u64 = 0;

#[test]
fun merkle_hash_push_back() {
let mh = MerkleHash::empty();
MerkleHash::push_back(&mut mh, b"1234567890");
let root = MerkleHash::finalize(&mut mh);
let r1 = Hash::sha3_256(b"1234567890");
assert!(root == r1, EEXPECTED_EQUALITY);
}

#[test]
fun merkle_hash_two_elements_one_root() {
let mh = MerkleHash::empty();
MerkleHash::push_back(&mut mh, b"1234567890");
MerkleHash::push_back(&mut mh, b"0987654321");
let root = MerkleHash::finalize(&mut mh);
let h1 = Hash::sha3_256(b"1234567890");
let h2 = Hash::sha3_256(b"0987654321");
Vector::append(&mut h1, h2);
let r1 = Hash::sha3_256(h1);
assert!(root == r1, EEXPECTED_EQUALITY);
}

#[test]
fun merkle_hash_three_elements_two_roots() {
let mh = MerkleHash::empty();
MerkleHash::push_back(&mut mh, b"1234567890");
MerkleHash::push_back(&mut mh, b"0987654321");
MerkleHash::push_back(&mut mh, b"1231231231");
let root = MerkleHash::finalize(&mut mh);
// (level_0) root_1 = H(H(A) || H(B))
let h1 = Hash::sha3_256(b"1234567890");
let h2 = Hash::sha3_256(b"0987654321");
Vector::append(&mut h1, h2);
let r1 = Hash::sha3_256(h1);
// (level_0) root_2 = H(H(C))
let h3 = Hash::sha3_256(b"1231231231");
let r2 = Hash::sha3_256(h3);
Vector::append(&mut r1, r2);
// (level_1) root_1 = H(root_1 || root_2)
let t1 = Hash::sha3_256(r1);
assert!(root == t1, EEXPECTED_EQUALITY);
}

#[test]
fun merkle_hash_five_elements_three_roots() {
let mh = MerkleHash::empty();
MerkleHash::push_back(&mut mh, b"1234567890");
MerkleHash::push_back(&mut mh, b"0987654321");
MerkleHash::push_back(&mut mh, b"1231231231");
MerkleHash::push_back(&mut mh, b"1234567890");
MerkleHash::push_back(&mut mh, b"0987654321");
let root = MerkleHash::finalize(&mut mh);
// (level 0) root 1 = H(H(A) || H(B))
let h1 = Hash::sha3_256(b"1234567890");
let h2 = Hash::sha3_256(b"0987654321");
Vector::append(&mut h1, h2);
let l0_r1 = Hash::sha3_256(h1);
// (level 0) root 2 = H(H(C) || H(D))
let h3 = Hash::sha3_256(b"1231231231");
let h4 = Hash::sha3_256(b"1234567890");
Vector::append(&mut h3, h4);
let l0_r2 = Hash::sha3_256(h3);
// (level 0) root 3 = H(H(E))
let h5 = Hash::sha3_256(b"0987654321");
let l0_r3 = Hash::sha3_256(h5);
// (level 1) root 1 = H(root 1 || root 2)
Vector::append(&mut l0_r1, l0_r2);
let l1_r1 = Hash::sha3_256(l0_r1);
// (level 1) root 2 = H(root 3)
let l1_r2 = Hash::sha3_256(l0_r3);
// (level 2) root = H(level 1 root 1 || level 1 root 2)
Vector::append(&mut l1_r1, l1_r2);
let l2_r = Hash::sha3_256(l1_r1);
assert!(root == l2_r, EEXPECTED_EQUALITY);
}

#[test]
#[expected_failure(abort_code = 1)] // EEMPTY == 1
fun finalize_when_empty() {
let mh = MerkleHash::empty();
MerkleHash::finalize(&mut mh);
}
}
20 changes: 10 additions & 10 deletions diem-move/diem-framework/experimental/tests/VoteTests.move
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ module ExperimentalFramework::VoteTests {
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 263)]
#[expected_failure(abort_code = 1)] // EINVALID_TIMESTAMP == 1
fun create_ballot_expired_timestamp(dr: signer) {
let (proposer, _, addr_bcs) = ballot_setup(&dr);
Vote::create_ballot(
Expand Down Expand Up @@ -172,7 +172,7 @@ module ExperimentalFramework::VoteTests {
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 520)]
#[expected_failure(abort_code = 2)] // ETOO_MANY_BALLOTS == 2
fun create_ballots_too_many(dr: signer) {
let (proposer, _, addr_bcs) = ballot_setup(&dr);
let i = 0;
Expand All @@ -193,7 +193,7 @@ module ExperimentalFramework::VoteTests {
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 769)]
#[expected_failure(abort_code = 3)] // EBALLOT_NOT_FOUND == 3
fun remove_ballot(dr: signer) {
let (voter1, _voter2, _voter3, ballot_id, proposal) = vote_test_helper(&dr, 10);
Vote::remove_ballot_internal<TestProposal>(get_proposer(), *(&ballot_id));
Expand All @@ -202,7 +202,7 @@ module ExperimentalFramework::VoteTests {
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 769)]
#[expected_failure(abort_code = 3)] // EBALLOT_NOT_FOUND == 3
fun vote_simple(dr: signer) {
let (voter1, voter2, voter3, ballot_id, proposal) = vote_test_helper(&dr, 10);
// First vote does not approve the ballot
Expand Down Expand Up @@ -242,15 +242,15 @@ module ExperimentalFramework::VoteTests {
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 263)]
#[expected_failure(abort_code = 1)] // EINVALID_TIMESTAMP == 1
fun vote_expired_ts(dr: signer) {
let (voter1, _voter2, _voter3, ballot_id, proposal) = vote_test_helper(&dr, 0);
// Ballot has expired
Vote::vote(&voter1, *(&ballot_id), b"test_proposal", *(&proposal));
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 2049)]
#[expected_failure(abort_code = 8)] // EALREADY_VOTED == 8
fun vote_repeat(dr: signer) {
let (voter1, _voter2, _voter3, ballot_id, proposal) = vote_test_helper(&dr, 10);
// First vote does not approve the ballot
Expand All @@ -260,15 +260,15 @@ module ExperimentalFramework::VoteTests {
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 1031)]
#[expected_failure(abort_code = 4)] // EBALLOT_PROPOSAL_MISMATCH == 4
fun vote_invalid_proposal_type(dr: signer) {
let (voter1, _voter2, _voter3, ballot_id, proposal) = vote_test_helper(&dr, 10);
// Invalid proposal type
Vote::vote(&voter1, *(&ballot_id), b"invalid", *(&proposal));
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 1031)]
#[expected_failure(abort_code = 4)] // EBALLOT_PROPOSAL_MISMATCH == 4
fun vote_invalid_proposal(dr: signer) {
let (voter1, _voter2, _voter3, ballot_id, _proposal) = vote_test_helper(&dr, 10);
let invalid_proposal = TestProposal {
Expand All @@ -279,7 +279,7 @@ module ExperimentalFramework::VoteTests {
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 769)]
#[expected_failure(abort_code = 3)] // EBALLOT_NOT_FOUND == 3
fun vote_invalid_ballotid(dr: signer) {
let proposer = get_proposer();
let (voter1, _voter2, _voter3, _ballot_id, proposal) = vote_test_helper(&dr, 10);
Expand All @@ -289,7 +289,7 @@ module ExperimentalFramework::VoteTests {
}

#[test(dr = @CoreResources)]
#[expected_failure(abort_code = 1281)]
#[expected_failure(abort_code = 5)] // EINVALID_VOTER == 5
fun vote_invalid_voter(dr: signer) {
let (_voter1, _voter2, _voter3, ballot_id, proposal) = vote_test_helper(&dr, 10);
let invalid_voter = Vector::pop_back(&mut UnitTest::create_signers_for_testing(4));
Expand Down