first check in

This commit is contained in:
LOLOLLO
2025-03-02 16:10:24 +01:00
commit 5a0e0ec5e9
14 changed files with 559 additions and 0 deletions

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

85
pom.xml Normal file
View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hypernode</groupId>
<artifactId>ledger</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ledger</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,32 @@
package com.hypernode.ledger;
public class ClientMethods {
//client functions
public static String createNewPrivateKey()
{
return "PrivateKey";
}
public static String createPublicKey(String _privateKey)
{
return "publicKey";
}
public static Payment walletSpendCurrency(Currency _currency,String _privateKey, String _recipient, String _comment)
{
Payment payment = ClientMethods.createPayment(_currency,_privateKey,_recipient,_comment);
return payment;
}
public static Payment createPayment(Currency _currency, String _privateKey, String _recipient, String _comment)
{
Payment payment = new Payment();
payment.publicKeyFrom = _currency.publicKeyFrom;
payment.publicKeyTo = _recipient;
payment.paymentComment = _comment;
payment.amount = _currency.amount;
payment.signature = WebServiceMethods.encodeMessage(payment.getSignableMessage(), _privateKey);
return payment;
}
}

View File

@@ -0,0 +1,13 @@
package com.hypernode.ledger;
import java.math.BigDecimal;
public class Currency {
public static final String COLLECTION_NAME = "Currency";
String publicKeyFrom;//public key
//String bankTo;//public key
String publicKeyServer;//vote for a specific validator server
BigDecimal amount;
}

View File

@@ -0,0 +1,27 @@
package com.hypernode.ledger;
import java.time.Instant;
import java.util.List;
public class DataContract {
int id;
int version;
List<ValidatorNode> validatorNodeList;
List<Currency> currencyList;
List<MessageElement> paymentList;
List<Currency> currencyMintProposal;
String publicKeySender;
String signature;
Instant received;
public void mergeDataContract(DataContract _newContract)
{
//find differences in validatornodelist
//validate currencyList
}
}

View File

@@ -0,0 +1,13 @@
package com.hypernode.ledger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LedgerApplication {
public static void main(String[] args) {
SpringApplication.run(LedgerApplication.class, args);
}
}

View File

@@ -0,0 +1,5 @@
package com.hypernode.ledger;
public class LedgerParameters {
//int groupSize;
}

View File

@@ -0,0 +1,8 @@
package com.hypernode.ledger;
import java.util.List;
public class MessageElement {
Payment payment;
List<ValidationSignature> validationSignatures;
}

View File

@@ -0,0 +1,24 @@
package com.hypernode.ledger;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
public class Payment {
public static final String COLLECTION_NAME = "Payment";
String publicKeyFrom;//public key
String publicKeyTo;//public key
String paymentComment;//comment of the transaction
BigDecimal amount;//value of the exchanged currency
String signature;//digital signature to validate the emission from the PublicKeyFrom
public String getSignableMessage()
{
return this.publicKeyFrom
+ this.amount + this.publicKeyTo +this.paymentComment;
}
}

View File

@@ -0,0 +1,4 @@
package com.hypernode.ledger;
public class ValidationSignature {
}

View File

@@ -0,0 +1,25 @@
package com.hypernode.ledger;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.util.List;
@Getter
@Setter
public class ValidatorNode {
public static final String COLLECTION_NAME = "ValidatorNode";
int id;
String Name;
String address;//ipv6 like address, first 2 numbers determine your layer1, then layer2, and finally your position on layer3
String publicKey;
String representedBy;
public static ValidatorNode initialize()
{
ValidatorNode validatorNode = new ValidatorNode();
return validatorNode;
}
}

View File

@@ -0,0 +1,290 @@
package com.hypernode.ledger;
import java.time.Instant;
import java.util.List;
/***
* How to use this software
*
* This program aims to create a distributed ledger to illustrate the Hypernode validation algorithm.
* This ledger will keep track of Currency objects, which are the equivalent of digital bank notes.
* Either you or your wallet program, by keeping track of the private keys which correspond to
* the currency objects under your control, can assign them to its new public key (spend them).
* The recipient will be able to spend that same bank note at a later date because by writing
* the transaction on the ledger the old public key will be replaced with his own new public key.
*
*
* How to host a node
*
* This class contains all the logic required to keep the distributed ledger running.
* The program works like this:
*
* 0 - Configurations
* 0a - GroupSize
* The variable groupSize is used in the code to determine how many connections will be required
* to be performed by this server for every group.
* It will also determine the maximum number of validators as (groupSize^groupSize)
* and the amount required to be staked as totalCurrency / (groupSize^groupSize)
*
* 1 - Initializing
* Whenever you launch this server program
* you can connect to one of the nodes you trust
* from any compatible ledger,
* or you can create your own ledger.
* 1.a - Connect to an existing ledger:
* Please call ConnectToExistingLedger to connect to the target node
* It will require specifying an IP address/web address of the target ledger.
* You could instantiate multiple instances of WebServiceMethod and add a ledgerName if you wanted to
* 1.b - Create a new ledger:
* Call the method CreateNewLedger, specifying the parameters in the origin DataContract.
* You will then be able to connect to this server from another instance of this program as described in 1.a
* 1.c - Register as a validator
* To balance the need of accountability with the need for decentralization and the need for performance
* this decentralized ledger uses ProofOfStake in combination with the Hypernode validation algorithm.
* Every server who wants to be a validator must stake at least
* totalCurrency / ((ledgerparameter.groupSize)^ledgerparameter.groupSize)
* which means that the public key of the server must correspond to a public key in the ledger
* for currency element worth at least that amount
*
* 2 - Wallet Operation
* Once the network is initialized it is possible to register transactions
* 2a - Managing private keys
* Every Currency object has a corresponding private key that will allow its spending
* Therefore the wallet will need to maintain a reference map between those items (Currencu,PrivateKey)
* 2b - Initiating a transaction
* To assign a new Public Key to a currency you control, call the method walletSpendCurrency()
* This should be done in the wallet, and the result should be communicated to a server
* by calling the method notifySpendCurrency().
*
* 3 - Server operation
* 3a - Initialization completed
* The context of registering the transactions is divided into blocks.
* Every validator will be assigned an ID when they connect to the network.
* This id will be an array of integers of size groupSize
* It will also get a list of all the IP address of the other servers and their public key.
* 3b - Processing transaction
* After receiving calls on notifySpendCurrency() your first step will be to verify
* the authenticity of the signature on those transactions, and eventually block them
* Those who pass the signature verification and that you also see as available to process in the blockchain
* get grouped into a transaction list and signed with your own digital signature
* At this point the server, starting from his ID, will identify all the servers
* whose ID array only differs by one element (i.e. in a 4 groupSize configuration the server id might be x.y.z.t
* and it would send messages to [0-4].y.z.t, x.[0-4].z.t, x.y.[0-4].t, x.y.z.[0-4])
* and send them his own transaction list.
* Once he sent his transaction list, the server will wait for 30 seconds to receive the transactions list from
* all the servers he contacted earlier (which will be groupSize^2 connections).
* This server will add up every group's transaction, based on their subgroup, and validate plus resend that to
* all of the contacted servers. You should now have groupSize lists,
* which contain every transaction that originated from a specific subgroup, and they are all different except for
* the transactions you submitted.
* Now it's time to combine all of those transactions into one unique transactionlist, sign it and send it to
* the servers you are in contact with.
* You should receive the same list back from your nodes, and that would validate that the frame has been successfully processed
* In case it is coherent you can recalculate your id and proceed into the next frame
* If it is not coherent you can take the id of the servers which do not match the majority, and use that information
* to ping the other servers in that same group ([0-4].y.z.t, x.[0-4].z.t, x.y.[0-4].t, x.y.z.[0-4])
* and exchange the full list. If yours and theirs match you just found the imposter. The validators will all spread
* the news to their circle, making them lose their staking reward
*
*
* 4 - Special operations
* A Special Operations object can signal willingness to change a parameter.
* If more than half of the nodes agree then you can change the groupSize to improve efficiency
* or emit new currency
* 4b - new currency
* every service can put out a separate List<Payment>, where the payment_from is null
* and specify a public key for the payment_to.
*
*
* 5 - Tips for a better experience
* You really need at least 128 validators to call it decentralized
* If you have less than 20 validators set the groupSize to 20, so it will be one single big group
* This system scales better the more validators you have.
* In some cases it might make more sense to set the groupSize to a higher number,
* Maybe i should write a function to pick the best groupSize number based on the number of validators
*
*/
public class WebServiceMethods {
DataContract lastDataContract;
DataContract myCurrentDataContract;
List<DataContract> peerDataContracts;
List<MessageElement> pendingPayments;
ValidatorNode thisValidatorNode;
List<WebServiceMethods> peers;
String privateKey;
int id;
int groupSize;
int groupInstances;
public static void main(String[] args)
{
WebServiceMethods thisServer = new WebServiceMethods();
thisServer.connectToExistingLedger("","");
}
public void createNewLedger( ValidatorNode _self, String _privateKey, DataContract _startingData)
{
this.thisValidatorNode = _self;
this.lastDataContract = _startingData;
this.privateKey = _privateKey;
}
public void connectToExistingLedger(String _ipaddress, String _privateKey)
{
WebServiceMethods thatServer = new WebServiceMethods();
String authenticationString;
authenticationString = thatServer.authenticationRequest(WebServiceMethods.createPublicKey(_privateKey));
this.lastDataContract = thatServer.authenticationResponse(WebServiceMethods.encodeMessage(authenticationString,_privateKey),WebServiceMethods.createPublicKey(_privateKey));
//while validatornodelist.next if element.publicKey = this.createpublickey()
this.thisValidatorNode = this.lastDataContract.validatorNodeList.getLast();
this.privateKey = _privateKey;
//you are now authenticated, at the next frame the Validator list will be updated
//and at the next one you will be able to exchange messages
//with the other servers.
//the first frame you will listen and populate your startingLedger
//and from the subsequent you will be able to send your own transactions
}
public boolean meshExchangeData()
{
DataContract dataContract = new DataContract();
WebServiceMethods webServiceMethods = new WebServiceMethods();
//implements point 3b
//process the data sent to you in receiveData//done at receipt
//call receiveData on the records you need to call
//for every datacontract in peerdatacontracts
//evaluate if you need to wait at least 30 seconds to get a message
if(dataContract.version < this.myCurrentDataContract.version
&& Instant.now().minusSeconds(30).isBefore( dataContract.received ) )
{
return false;
}
//
//foreach webServiceMethods peer in peers
webServiceMethods.receiveData(this.thisValidatorNode.publicKey,this.myCurrentDataContract);
//once you have exchanged exponent messages with all your peers and all message are good
//you know that the frame is valid and everyone signed on it
return true;
}
public static String encodeMessage(String message, String privateKey)
{
return "encodedMessage";
}
public static String decodeMessage(String _encryptedMessage, String _publicKey)
{
return "decodedMessage";
}
public static String createPublicKey(String _privateKey)
{
return "publicKey";
}
public static boolean verifySignature(String _message, String _publicKey, String _signature)
{
return true;
}
private void setGroupParameters(int id)
{
Integer exponent = 1;
Integer base;
Integer groupValue;
Integer divisionValue;
Integer divisionReminder;
while (Math.pow(exponent,exponent) < id )
{
if(exponent >20)
{
throw new Error("we got lost");
}
exponent += 1;
}
exponent -= 1;
base = exponent;
while (Math.pow(base,exponent) <id )
{
base += 1;
}
base -= 1;
if(Math.pow(base,exponent) > Math.pow(exponent+1,exponent+1))
{
base = exponent + 1;
exponent = base;
}
this.groupSize = base;
this.groupInstances = exponent;
divisionValue = id;
while(divisionValue > 0)
{
divisionReminder = divisionValue - (Math.floorDiv(divisionValue,this.groupSize) * this.groupSize);
divisionValue = Math.floorDiv(divisionValue,this.groupSize);
//pop divisionReminder in the array
}
}
//exposed web service methods
public String authenticationRequest(String _publicKey)
{
//the caller asks for a message to sign and then gives it back signed.
//He will need to use a public key of a currency object he controls,
// which will then be used for staking
return "";
}
public DataContract authenticationResponse(String _publicKey, String signedMessage)
{
//to redo, i need to announce the new connection and assign its address
ValidatorNode validatorNode = new ValidatorNode();
//should probably order the validatornode by id first, and by publickey second
//and assign the new id in order, patching up the holes done by servers disconnecting
//once you do get this you can start to meshExchangeData
this.lastDataContract.validatorNodeList.add(validatorNode);
return this.lastDataContract;
}
public void notifySpendCurrency(List<Payment> _requestedPayments)
{
Payment payment = new Payment();
MessageElement messageElement = new MessageElement();
//while looping requestedPayments
payment = new Payment();
//validate single payment
if(WebServiceMethods.verifySignature(payment.getSignableMessage(),payment.publicKeyFrom, payment.signature))
//if validation fails blacklist a specific ip for 1h(maybe?)
{
throw new Error("invalid payment");
}
//if all ok add it to the list of validated transactions to be pushed
else
{
messageElement.payment = payment;
}
this.pendingPayments.add(messageElement);//find the singleton
}
public void receiveData(String _sender, DataContract _dataContract)
{
//listiterator on your pendingdatacontracts
ValidatorNode validatorNode = new ValidatorNode();
if (validatorNode.address.equals(_sender)) {
//someone gave you data, you have to process it and see if it is still good
this.myCurrentDataContract.mergeDataContract(_dataContract);
//remove old temporary datacontract and replace it with the newest iteration for that source
this.peerDataContracts.remove(1);
this.peerDataContracts.add(_dataContract);
//return
this.meshExchangeData();
} else {
//you can reduce the spamming maybe?
throw new Error("Unauthorized");
}
}
}

View File

@@ -0,0 +1 @@
spring.application.name=ledger

View File

@@ -0,0 +1,13 @@
package com.hypernode.ledger;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class LedgerApplicationTests {
@Test
void contextLoads() {
}
}