Solved hash generation

This commit is contained in:
droideparanoico
2022-10-08 12:27:21 +02:00
parent e1d9cff77a
commit 45451841ce
7 changed files with 205 additions and 26 deletions

View File

@@ -2,40 +2,37 @@ package com.rviewer.skeletons.domain.model;
import com.rviewer.skeletons.domain.exception.HashGenerationException; import com.rviewer.skeletons.domain.exception.HashGenerationException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.With;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Random;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@With @Builder
public class Block { public class Block {
private LocalDateTime timestamp; @Builder.Default
private String lasthash; private LocalDateTime timestamp = LocalDateTime.now();
private String previousHash;
private String data; private String data;
private Integer nonce; @Builder.Default
private Integer nonce = RandomNonce.getRandomNonce();
private String hash; private String hash;
public Block() {
this.timestamp = LocalDateTime.now();
this.nonce = new Random().nextInt();
this.hash = generateHashFromBlock(this);
}
public static Block getGenesisBlock() { public static Block getGenesisBlock() {
return new Block().withLasthash(""); Block genesisBlock = Block.builder().previousHash("").build();
genesisBlock.setHash(generateHashFromBlock(genesisBlock));
return genesisBlock;
} }
static String generateHashFromBlock(final Block block) throws HashGenerationException { public static String generateHashFromBlock(final Block block) throws HashGenerationException {
try { try {
final var digest = MessageDigest.getInstance("SHA-256"); final var digest = MessageDigest.getInstance("SHA-256");
final byte[] hash = digest.digest(block.getHashData().getBytes(StandardCharsets.UTF_8)); final byte[] hashData = digest.digest(block.getHashData().getBytes(StandardCharsets.UTF_8));
final var hexString = new StringBuilder(); final var hexString = new StringBuilder();
for (final byte elem : hash) { for (final byte elem : hashData) {
final var hex = Integer.toHexString(0xff & elem); final var hex = Integer.toHexString(0xff & elem);
if (hex.length() == 1) hexString.append('0'); if (hex.length() == 1) hexString.append('0');
hexString.append(hex); hexString.append(hex);
@@ -46,7 +43,11 @@ public class Block {
} }
} }
public void generateHash() {
hash = generateHashFromBlock(this);
}
private String getHashData() { private String getHashData() {
return timestamp + lasthash + data + nonce; return timestamp + previousHash + data + nonce;
} }
} }

View File

@@ -19,10 +19,35 @@ public class Blockchain {
} }
public Boolean replace(List<Block> blockchainList) { public Boolean replace(List<Block> blockchainList) {
return true; if (blockchainList.isEmpty()) {
return false;
}
if (blockchainList.size() < this.blockchainList.size()) {
return false;
}
List<Block> newBlockchainList = blockchainList.subList(0, this.blockchainList.size());
if (!newBlockchainList.equals(this.blockchainList)) {
return false;
}
return isValid(blockchainList);
} }
public Boolean isValid(List<Block> blockchainList) { public Boolean isValid(List<Block> blockchainList) {
return true; boolean valid = true;
// Validate genesis block
if (!blockchainList.get(0).getPreviousHash().equals("")) {
return false;
}
// Validate every element's last hash value matches previous block's hash
for (int i = 0; i < blockchainList.size() - 1; i++) {
valid = blockchainList.get(i).getHash().equals(blockchainList.get(i + 1).getPreviousHash());
if (!valid) return false;
}
// Validate data hasn't been tampered
for (Block block : blockchainList) {
valid = block.getHash().equals(Block.generateHashFromBlock(block));
if (!valid) break;
}
return valid;
} }
} }

View File

@@ -0,0 +1,13 @@
package com.rviewer.skeletons.domain.model;
import java.util.Random;
public class RandomNonce {
private static final Random RANDOM = new Random();
private RandomNonce() {}
public static Integer getRandomNonce() {
return RANDOM.nextInt();
}
}

View File

@@ -30,17 +30,21 @@ public class CartService implements CartServicePort {
public void save(Cart cart) { public void save(Cart cart) {
cartDatabasePort.save(cart); cartDatabasePort.save(cart);
if (blockchain.getBlockchainList().isEmpty()) { if (blockchain.getBlockchainList().isEmpty()) {
blockchain.addBlock(Block.getGenesisBlock().withData(cart.toString())); blockchain.addBlock(Block.builder().data(cart.toString()).build());
} else { } else {
blockchain.addBlock(new Block().withData(cart.toString()) Block saveBlock = Block.builder().data(cart.toString())
.withLasthash(blockchain.getBlockchainList().getLast().getHash())); .previousHash(blockchain.getBlockchainList().getLast().getHash()).build();
saveBlock.generateHash();
blockchain.addBlock(saveBlock);
} }
} }
@Override @Override
public void delete(String id) { public void delete(String id) {
cartDatabasePort.delete(id); cartDatabasePort.delete(id);
blockchain.addBlock(new Block().withData("deleted cart with id:" + id) Block deleteBlock = Block.builder().data("deleted cart with id:" + id)
.withLasthash(blockchain.getBlockchainList().getLast().getHash())); .previousHash(blockchain.getBlockchainList().getLast().getHash()).build();
deleteBlock.generateHash();
blockchain.addBlock(deleteBlock);
} }
} }

View File

@@ -31,7 +31,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
class CartControllerTest { class CartControllerTest {
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean

View File

@@ -0,0 +1,138 @@
package com.rviewer.skeletons.domain.model;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class BlockchainTest {
private Blockchain blockchain;
@BeforeEach
void init() {
blockchain = new Blockchain();
}
@Test
void should_add_block() {
Block genesisBlock = Block.getGenesisBlock();
blockchain.addBlock(genesisBlock);
assertEquals(blockchain.getBlockchainList().get(0), genesisBlock);
}
@Test
void should_replace_blockchainList() {
List<Block> newBlockchainList = new ArrayList<>();
Block genesisBlock = Block.getGenesisBlock();
blockchain.addBlock(genesisBlock);
newBlockchainList.add(genesisBlock);
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
secondBlock.generateHash();
blockchain.addBlock(secondBlock);
newBlockchainList.add(secondBlock);
Block thirdBlock = Block.builder().previousHash(secondBlock.getHash()).data(null).build();
thirdBlock.generateHash();
newBlockchainList.add(thirdBlock);
assertTrue(blockchain.replace(newBlockchainList));
}
@Test
void should_not_replace_blockchainList_because_is_empty() {
List<Block> newBlockchainList = new ArrayList<>();
Block genesisBlock = Block.getGenesisBlock();
blockchain.addBlock(genesisBlock);
assertFalse(blockchain.replace(newBlockchainList));
}
@Test
void should_not_replace_blockchainList_because_smaller() {
List<Block> newBlockchainList = new ArrayList<>();
Block genesisBlock = Block.getGenesisBlock();
blockchain.addBlock(genesisBlock);
newBlockchainList.add(genesisBlock);
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
secondBlock.generateHash();
blockchain.addBlock(secondBlock);
assertFalse(blockchain.replace(newBlockchainList));
}
@Test
void should_not_replace_blockchainList_because_not_equals() {
List<Block> newBlockchainList = new ArrayList<>();
Block genesisBlock = Block.getGenesisBlock();
blockchain.addBlock(genesisBlock);
newBlockchainList.add(genesisBlock);
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
secondBlock.generateHash();
blockchain.addBlock(secondBlock);
newBlockchainList.add(Block.builder().previousHash(genesisBlock.getHash()).data("data").build());
assertFalse(blockchain.replace(newBlockchainList));
}
@Test
void should_not_replace_blockchainList_because_invalid_genesis() {
List<Block> newBlockchainList = new ArrayList<>();
Block genesisBlock = Block.builder().previousHash("non empty").data(null).build();
genesisBlock.generateHash();
blockchain.addBlock(genesisBlock);
newBlockchainList.add(genesisBlock);
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
secondBlock.generateHash();
blockchain.addBlock(secondBlock);
newBlockchainList.add(secondBlock);
assertFalse(blockchain.replace(newBlockchainList));
}
@Test
void should_not_replace_blockchainList_because_hash_mismatch() {
List<Block> newBlockchainList = new ArrayList<>();
Block genesisBlock = Block.getGenesisBlock();
blockchain.addBlock(genesisBlock);
newBlockchainList.add(genesisBlock);
Block secondBlock = Block.builder().previousHash("wrong hash").data(null).build();
secondBlock.generateHash();
blockchain.addBlock(secondBlock);
newBlockchainList.add(secondBlock);
assertFalse(blockchain.replace(newBlockchainList));
}
@Test
void should_not_replace_blockchainList_because_tampered_data() {
List<Block> newBlockchainList = new ArrayList<>();
Block genesisBlock = Block.getGenesisBlock();
blockchain.addBlock(genesisBlock);
newBlockchainList.add(genesisBlock);
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
secondBlock.generateHash();
blockchain.addBlock(secondBlock);
secondBlock.setData("tampered data");
newBlockchainList.add(secondBlock);
assertFalse(blockchain.replace(newBlockchainList));
}
}

View File

@@ -23,7 +23,6 @@ import static org.mockito.Mockito.when;
@SpringBootTest @SpringBootTest
class CartDatabaseAdapterTest { class CartDatabaseAdapterTest {
@Autowired @Autowired
private CartDatabaseAdapter cartDatabaseAdapter; private CartDatabaseAdapter cartDatabaseAdapter;
@Mock @Mock