Download as pdf or txt
Download as pdf or txt
You are on page 1of 29

Practical 1

Aim: T o l
earnbasicsofcryptograph y by creatingmessage digestusingSH A - 2 5 6 .

Theory : SHA-256
All hashing algorithms are:
• Mathematical. Strict rules underlie the work an algorithm does, and those rules can’t be broken or
adjusted.
• Uniform. Choose one type of hashing algorithm, and data of any character count put through the system
will emerge at a length predetermined by the program.
• Consistent. The algorithm does just one thing (compress data) and nothing else.
• One way. Once transformed by the algorithm, it’s nearly impossible to revert the data to its original
state.

hashing algorithms follow this process:


• Create the message. A user determines what should be hashed.
• Choose the type. Dozens of hashing algorithms exist, and the user might decide which works best for
this message.
• Enter the message. The user taps out the message into a computer running the algorithm.
• Start the hash. The system transforms the message, which might be of any length, to a predetermined
bit size. Typically, programs break the message into a series of equal-sized blocks, and each one is compressed
in sequence.
• Store or share. The user sends the hash (also called the "message digest") to the intended recipient, or
the hashed data is saved in that form.
The process is complicated, but it works very quickly. In seconds, the hash is complete.

A cryptographic hash function takes an arbitrary block of data and calculates a fixed-size bit string (a digest),
such that different data results (with a high probability) in different digests.

classcryptography.hazmat.primitives.hashes.SHA256
SHA-256 is a cryptographic hash function from the SHA-2 family and is standardized by NIST. It produces a
256-bit message digest.
Program:

#Hashing.py
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(b"123")
digest.update(b"456")
digest.update(b"789")
digest.update(b"abc")
digest.update(b"xy2")
hash = digest.finalize()
print(hash)

digest = hashes.Hash(hashes.SHA256(), backend=default_backend())


digest.update(b"023")
digest.update(b"456")
digest.update(b"789")
digest.update(b"abc")
digest.update(b"xy2")
hash = digest.finalize()
print(hash)

Output:

Conclusion: Thus we have successfully created message digest for given messages and found out

that message digest of same messages is same but a small change in the message completely changes the

message digest.
Practical 2

Aim: To create and verify digital signatures.

Theory : Digital Signature

A digital signature is a mathematical technique used to validate the authenticity and integrity of a message,
software, or digital document.
Key Generation Algorithms: Digital signature is electronic signatures, which assure that the
message was sent by a particular sender. While performing digital transactions authenticity and integrity
should be assured, otherwise, the data can be altered or someone can also act as if he was the sender and
expect a reply.
Signing Algorithms: To create a digital signature, signing algorithms like email programs create a
one-way hash of the electronic data which is to be signed. The signing algorithm then encrypts the hash value
using the private key (signature key). This encrypted hash along with other information like the hashing
algorithm is the digital signature. This digital signature is appended with the data and sent to the verifier. The
reason for encrypting the hash instead of the entire message or document is that a hash function converts any
arbitrary input into a much shorter fixed-length value. This saves time as now instead of signing a long
message a shorter hash value has to be signed and moreover hashing is much faster than signing.
Signature Verification Algorithms : Verifier receives Digital Signature along with the data. It then
uses Verification algorithm to process on the digital signature and the public key (verification key) and
generates some value. It also applies the same hash function on the received data and generates a hash value.
Then the hash value and the output of the verification algorithm are compared. If they both are equal, then the
digital signature is valid else it is invalid.

Fig: Model of Digital Signature.


1) Each person adopting this scheme has a public-private key pair.
2) Generally, the key pairs used for encryption/decryption and signing/verifying are different. The private key
used for signing is referred to as the signature key and the public key as the verification key.
3) Signer feeds data to the hash function and generates hash of data.
4) Hash value and signature key are then fed to the signature algorithm which produces the digital signature
on given hash. Signature is appended to the data and then both are sent to the verifier.
5) Verifier feeds the digital signature and the verification key into the verification algorithm. The verification
algorithm gives some value as output.
6) Verifier also runs same hash function on received data to generate hash value.
7) For verification, this hash value and output of verification algorithm are compared. Based on the
comparison result, verifier decides whether the digital signature is valid.
8) Since digital signature is created by ‘private’ key of signer and no one else can have this key; the signer
cannot repudiate signing the data in future.

Program:

#Signatures.py

from cryptography.hazmat.backends import default_backend


from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.exceptions import InvalidSignature

# Generation of Private and Public keys using RSA algorithm

def generate_keys():
private = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public = private.public_key()
return private, public

# Creating a signature

def sign(message, private):


message = bytes(str(message), 'utf-8')
sig = private.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return sig

# Verification of Strings
def verify(message, sig, public) :
message = bytes(str(message), 'utf-8')
try:
retval = public.verify(
sig,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return True
except InvalidSignature:
return False
except:
print("Error Executing public_key.verify")
return False

# Main Function

if __name__ == '__main__' :
pr, pu = generate_keys()
print(pr)
print(pu)
message = "This is a secret message"
sig = sign(message, pr)
print(sig)
correct = verify(message, sig, pu)
print(correct)
if correct:
print("Success! Good Sig")
else:
print("Error! Signature is bad")
Output:

Conclusion: Thus we have successfully created digital signature and verified it.
Practical 3

Aim: To create a blockchain having 5 blocks and implement


the working of Merkle Tree.

Theory: Blockchain

Blocks are data structures within the blockchain database, where transaction data in a

cryptocurrency blockchain are permanently recorded. A block records some or all of

the most recent transactions not yet validated by the network. Once the data are

validated, the block is closed. Then, a new block is created for new transactions to be

entered into and validated.

A block is thus a permanent store of records that, once written, cannot be

altered or removed.

A block is a place in a blockchain where information is stored and encrypted.

Blocks are identified by long numbers that include encrypted transaction

information from previous blocks and new transaction information.

Blocks and the information within them must be verified by a network before

new blocks can be created.


Merkle Trees

A Merkle tree totals all transactions in a block and generates a digital fingerprint of the entire set of
operations, allowing the user to verify whether it includes a transaction in the block.

Merkle trees are made by hashing pairs of nodes repeatedly until only one hash remains; this
hash is known as the Merkle Root or the Root Hash.
They're built from the bottom, using Transaction IDs, which are hashes of individual
transactions.
Each non-leaf node is a hash of its previous hash, and every leaf node is a hash of
transactional data.

Working of Merkle Trees

Consider the following scenario: A, B, C, and D are four transactions, all executed on the same
block. Each transaction is then hashed, leaving you with:
Hash A
Hash B
Hash C
Hash D
The hashes are paired together, resulting in:
Hash AB
and
Hash CD
And therefore, your Merkle Root is formed by combining these two hashes: Hash ABCD.

Program:

from cryptography.hazmat.backends import default_backend

from cryptography.hazmat.primitives import hashes

class CBlock :

data = None

previousHash = None

previousBlock = None

def __init__(self, data, previousBlock) :

self.data = data

self.previousBlock = previousBlock
if previousBlock != None:

self.previousHash = previousBlock.computeHash()

def computeHash(self) :

digest = hashes.Hash(hashes.SHA256(), backend =

default_backend() )

digest.update(bytes(str(self.data),'utf8'))

digest.update(bytes(str(self.previousHash),'utf8'))

return digest.finalize()

if __name__=='__main__' :

root = CBlock('I am root', None)

B1 = CBlock(b'I am a child of root', root)

B2 = CBlock('I am B1s Brother and child of root', root)

B3 = CBlock(12345, B1)

B4 = CBlock("Hi there!, I am child of B3", B3)

B5 = CBlock("I am the Top Block child of B4", B4)

n=0

for b in [B1, B2, B3, B4, B5] :

n=n+1

if b.previousBlock.computeHash() == b.previousHash:

print("Data in block ",n, " = ", b.data)

print (“Success! Hash of Block”, n, “== Hash of Block”, n+1)

else:
print ("ERROR! Hash is not equal.")

Output:
Practical 4

Aim: To learn basics of cryptography by testing the digital


signature against the hackers.

Theory : Digital Signature Testing

A digital signature is a mathematical technique used to validate the authenticity and integrity of a
message, software, or digital document.

1) Each person adopting this scheme has a public-private key pair.


2) Hacker also generates a public-private key pair for himself.
3) Hash value and Hacker’s signature key are then fed to the signature algorithm which produces the
hacker’s digital signature on given hash. Signature is appended to the data and then both are sent to the
verifier.
4) Verifier feeds the Hacker’s digital signature and the verification key into the verification algorithm. The
verification algorithm gives some value as output.
5) Verifier also runs same hash function on received data to generate hash value.
6) For verification, this hash value and output of verification algorithm are compared. Based on the
comparison result, verifier decides whether the digital signature is valid.
7) Since digital signature is created by ‘private’ key of signer and no one else can have this key; the signer
cannot repudiate signing the data in future.
8) Hence, the hacker’s signature is not decrypted using signers public key since the keys are used in pairs.
And so, wrong message is generated that gives wrong hash as a result of which fake signature is detected.

Program:

#Testing.py

import Signatures

pr, pu = Signatures.generate_keys()
message = "This is a secret message"
sig = Signatures.sign(message, pr)

print("Trying to sign message with hackers signature")


pr2, pu2 = Signatures.generate_keys()
sig2 = Signatures.sign(message, pr2)
correct = Signatures.verify(message, sig2, pu)
if correct:
print("ERROR! Bad signature checks out!")
else:
print("SUCCESS! Bad signature Detected!")
print("Tampering with messages")
badmess = message + "Q"
correct = Signatures.verify(badmess, sig, pu)
if correct:
print("ERROR! Tampered message checks out!")
else:
print("SUCCESS! Tampering Detected!")

Output:

Conclusion: Thus we have successfully tested the digital signature against hackers using Fruad

Signature and Tampered Message and found that both the Fruad Signature and Tampered Message are
detected during verification of signature.
Practical 5

Aim: To learn basics of blockchain technology by creating


the transaction class.

Theory : The Transaction class.


● Create a transaction class that represents the transfer of coins from one public address to another. These
transactions will be signed and verified using the signatures on those with the tools that were created in
the Signatures.py in Practical 2.
● So then we'll teach transactions to verify themselves and we'll write some tests to make sure that no one
can game the system.
● We created a signature module that generates key pairs, that sign statements --creates a digital signature
for statements--and verifies digital signatures.
● In asymmetric encryption, only a person with the private key can digitally sign a statement and if someone
tries to sign a statement without the correct private key it's obvious to everyone that the signature is fake.
● In the blockchain transaction ledger your public key is your address it's the way that others identify you in
the blockchain world. So when coins have been sent to your public address the only way to get them out is
to use your private key to sign a transaction sending them somewhere else.
● So when we say coins are sent to you we mean that they're reserved in the public ledger in such a way
that the only way to transfer them is to sign a transaction using your private key.
● Of course, one person may have many addresses. You'll remember that it's quite easy to generate these
public private key pairs and as long as you keep a list of the corresponding private keys you can have as
many public addresses as you like. There's really a lot of space in the public address space for all of these
crypto coins.

Program:

#Transaction.py
import Signatures
#Signatures.sign
#Signatures.verify

class Tx:
inputs = None
outputs =None
sigs = None
reqd = None
def __init__(self):
self.inputs = []
self.outputs = []
self.sigs = []
self.reqd = []
def add_input(self, from_addr, amount):
self.inputs.append((from_addr, amount))
def add_output(self, to_addr, amount):
self.outputs.append((to_addr, amount))
def add_reqd(self, addr):
self.reqd.append(addr)
def sign(self, private):
message = self.__gather()
newsig = Signatures.sign(message, private)
self.sigs.append(newsig)
def is_valid(self):
total_in = 0
total_out = 0
message = self.__gather()
for addr,amount in self.inputs:
found = False
for s in self.sigs:
if Signatures.verify(message, s, addr) :
found = True
if not found:
print ("Reason for Failure: No good sig found for given message")
return False
if amount < 0:
return False
total_in = total_in + amount
for addr in self.reqd:
found = False
for s in self.sigs:
if Signatures.verify(message, s, addr) :
found = True
if not found:
return False
for addr,amount in self.outputs:
if amount < 0:
return False
total_out = total_out + amount
if total_out > total_in:
print("Reason for Failure: Outputs exceed inputs")
return False
return True
def __gather(self):
data=[]
data.append(self.inputs)
data.append(self.outputs)
data.append(self.reqd)
return data

Conclusion: Thus we have successfully created the transaction class.


Practical 6

Aim: To check the tampering in blockchain using previous


hash.

Theory : Hash
Each block also includes a Hash- a unique identifier for the block and all of its contents. To better understand,
you can assume a hash to be equivalent to a fingerprint. It is always unique, and no two blocks can have the
same hash, just like in the case of fingerprints. As soon as a block is created, its hash gets generated
simultaneously. Tampering with a block changes its hash. Simply put, if fingerprints or hash of the block
change, it indicates that the block has been tampered with and is no longer the same block. So hash can be a
very powerful tool to detect any changes made in the block.

Tampering with block changes its hash.

Hash of the previous block

Another important element that every block contains is the hash of the previous block. This piece of
information is what links one block to another and makes the whole network safe and secure.

In the figure, block 4 contains the hash of block 3, block 3 contains the hash of block 2, and so on. As
discussed earlier, any change in the data of a block leads to a change in its hash. In the given figure, when
we change the data in block 2, the hash of block 2 gets changed as well, which makes the whole blockchain
unstable. This happens because each block contains hash of the previous block. When block 2 is tampered
with, the old hash becomes invalid, and a new hash is generated for the block. This affects all the subsequent
blocks in the chain and thus making all of them invalid. This unique property of the blockchain makes it
transparent and secure, as in any case of data tampering whole network gets to know which block got
compromised in the blockchain. Hash of the previous block links the block to another block in the Blockchain

Program:

#Blockchain.py
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes

class someClass :
string = None
num = 328965
def __init__(self, mystring) :
self.string = mystring
def __repr__(self) :
return self.string + "^^^" + str(self.num)

class CBlock :
data = None
previousHash = None
previousBlock = None
def __init__(self, data, previousBlock) :
self.data = data
self.previousBlock = previousBlock
if previousBlock != None:
self.previousHash = previousBlock.computeHash()
def computeHash(self) :
digest = hashes.Hash(hashes.SHA256(), backend = default_backend() )
digest.update(bytes(str(self.data),'utf8'))
digest.update(bytes(str(self.previousHash),'utf8'))
return digest.finalize()

if __name__=='__main__' :
root = CBlock('I am root', None)
B1 = CBlock(b'I am a child', root)
B2 = CBlock('I am B1s Brother', root)
B3 = CBlock(12345, B1)
B4 = CBlock(someClass('Hi there!'), B3)
B5 = CBlock("Top Block", B4)
n=0
for b in [B1, B2, B3, B4, B5] :
n=n+1
if b.previousBlock.computeHash() == b.previousHash:
print("Data in block ",n, " = ", b.data)
print ("Success! Hash is good.")
else:
print ("ERROR! Hash is not good.")

print ("To check if data of block B3 is tampered")


B3.data=12354
print ("Previous block data of B4 i.e. B3 before tampering",B4.previousBlock.data)
if B4.previousBlock.computeHash() == B4.previousHash:
print ("ERROR! Couldn't detect Tampering")
else:
print ("Success! Tampereing Detected")

print("hash value of B3 before tampering" , B4.previousBlock.computeHash())


print("hash value of B3 after tampering=" , B4.previousHash)

print ("To check if data of class someclass from block B4 is tampered")


print(B4.data)
B4.data.num = 99999
print(B4.data)
if B5.previousBlock.computeHash() == B5.previousHash:
print ("ERROR! Couldn't detect Tampering")
else:
print ("Success! Tampereing Detected.")

Output:
Conclusion: Thus we have found out that any change in the data of a block leads to a change in its

hash, if fingerprints or hash of the block change, it indicates that the block has been tampered with and is no

longer the same block, which ultimately makes the whole blockchain unstable.
Practical 7

Aim: To implement and test transactions


with wrong signature and multiple inputs.

Theory :
● Here, we import Signatures.py to use the function generate_keys() through which we generate four public
and private key pairs for testing transactions.
● We also import Transactions.py to create transaction instances Tx1 and Tx2, to add input using add_input()
function, to add output using add_output function, to sign a message after gathering all the information of
the message we use sign() function.
● Thus, we create a transaction by first creating instance of a transaction, then by adding input (senders
public key, amount to be sent), by adding output (receivers public key, amount to be received) and lastly
by adding signature of sender.
● The validity of the transaction is tested by is_valid() function from Transactions.py.

Program :

#Test 7.py
#testing transactions with wrong signature
#testing transactions with multiple inputs but only one signature.

import Signatures
import Transactions

if __name__ == "__main__":
pr1, pu1 = Signatures.generate_keys()
pr2, pu2 = Signatures.generate_keys()
pr3, pu3 = Signatures.generate_keys()
pr4, pu4 = Signatures.generate_keys()

# Regular Transaction 1 input 1 output


Tx1 = Transactions.Tx()
Tx1.add_input(pu1, 1)
Tx1.add_output(pu2, 1)
Tx1.sign(pr1)

# Regular Transaction 1 input 2 outputs


Tx2 = Transactions.Tx()
Tx2.add_input(pu1, 2)
Tx2.add_output(pu2, 1)
Tx2.add_output(pu3, 1)
Tx2.sign(pr1)

# Two input addrs, signed by one.


# Signature of pu3 is missing.
Tx3 = Transactions.Tx()
Tx3.add_input(pu3, 1)
Tx3.add_input(pu4, 0.1)
Tx3.add_output(pu1, 1.1)
Tx3.sign(pr4)

# Three input addrs, signed by two


# Signature of pu4 is missing.
Tx4 = Transactions.Tx()
Tx4.add_input(pu2, 1)
Tx4.add_input(pu3, 2)
Tx4.add_input(pu4, 0.1)
Tx4.add_output(pu1, 3.1)
Tx4.sign(pr2)
Tx4.sign(pr3)

# Wrong signature
Tx5 = Transactions.Tx()
Tx5.add_input(pu1, 1)
Tx5.add_output(pu2, 1)
Tx5.sign(pr2)

n=0
for t in [Tx1, Tx2, Tx3, Tx4, Tx5]:
n=n+1
print("")
print ("Output for transaction", n)
if t.is_valid():
print("Success! Tx is valid")
else:
print("ERROR! Tx is invalid")
Output :

Conclusion: Thus we have found out that, by testing transactions with wrong signature and with

multiple inputs one signature


Practical 8

Aim: To study and execute the program of


negative and modified transactions.

Theory :
● Here, we import Signatures.py to use the function generate_keys() through which we generate four public
and private key pairs for testing transactions.
● We also import Transactions.py to create transaction instances Tx1 and Tx2, to add input using add_input()
function, to add output using add_output function, to sign a message after gathering all the information of
the message we use sign() function.
● Thus, we create a transaction by first creating instance of a transaction, then by adding input (senders
public key, amount to be sent), by adding output (receivers public key, amount to be received) and lastly
by adding signature of sender.
● The validity of the transaction is tested by is_valid() function from Transactions.py.

Program:

#Test10.py
#testing negative
#testing modified transactions
import Signatures
import Transactions

if __name__ == "__main__":
pr1, pu1 = Signatures.generate_keys()
pr2, pu2 = Signatures.generate_keys()
pr3, pu3 = Signatures.generate_keys()
pr4, pu4 = Signatures.generate_keys()

# Negative values
Tx1 = Transactions.Tx()
Tx1.add_input(pu2, -1)
Tx1.add_output(pu1, 1)
Tx1.sign(pr2)

# Negative values
Tx2 = Transactions.Tx()
Tx2.add_input(pu2, 1)
Tx2.add_output(pu1, -1)
Tx2.sign(pr2)
# Modified Tx
Tx3 = Transactions.Tx()
Tx3.add_input(pu1, 1)
Tx3.add_output(pu2, 1)
Tx3.sign(pr1)
# outputs = [(pu2,1)]
# change to [(pu3,1)]
Tx3.outputs[0] = (pu3,1)

# Modified Tx
Tx4 = Transactions.Tx()
Tx4.add_input(pu2, 5)
Tx4.add_output(pu3, 5)
Tx4.sign(pr2)
# inputs = [(pu2,1)]
# change to [(pu3,1)]
Tx4.inputs[0] = (pu2,10)

n=0
for t in [Tx1, Tx2, Tx3, Tx4]:
n=n+1
print("")
print ("Output for transaction", n)
if t.is_valid():
print("ERROR! Bad Transaction is valid")
else:
print("Success! Bad Transaction is invalid")

Output:
Practical 9

Aim: To implement and test the program of


escrow transactions.

Theory : Testing ESCROW Transactions.


● Here, we import Signatures.py to use the function generate_keys() through which we generate four public
and private key pairs for testing transactions.
● We also import Transactions.py to create transaction instances Tx1, Tx2 and Tx3, to add input using
add_input() function, to add output using add_output function, to sign a message after gathering all the
information of the message we use sign() function, to add arbiters signature we use add_reqd() function.
● Thus, we create a transaction by first creating instance of a transaction, then by adding input (senders
public key, amount to be sent), by adding output (receivers public key, amount to be received) and lastly
by adding signature of sender and arbiter for escrow transactions.
● The validity of the transaction is tested by is_valid() function from Transactions.py.

ESCROW Transaction: An Escrow is an arrangement for a third party to hold the assets of a transaction
temporarily. The assets are kept in a third-party account and are only released when all terms of
the agreement have been met. The use of an escrow account in a transaction adds a degree of safety for both
parties.
The main purpose of an escrow is to ensure that everybody sticks to their end of the bargain. It can be
seen as a mediator of the transaction. It asserts that the transfer of assets only happens when all the
obligations of the transaction have been met.

Escrow is commonly used for contracts that have the following characteristics:
● A large transaction value – such as the purchase of a home
● The buyer needs to confirm the quality of work before payment
● The seller doesn’t want to undertake massive amounts of work without assurance of payment.
Program:

#Test 09.py
#testing transactions for output exceeding input

import Signatures
import Transactions

if __name__ == "__main__":
pr1, pu1 = Signatures.generate_keys()
pr2, pu2 = Signatures.generate_keys()
pr3, pu3 = Signatures.generate_keys()
pr4, pu4 = Signatures.generate_keys()

# Escrow Tx not signed by the arbiter


# Escrow Tx signed by the sender
Tx1 = Transactions.Tx()
Tx1.add_input(pu3, 1.2)
Tx1.add_output(pu1, 1.1)
Tx1.add_reqd(pu4)
Tx1.sign(pr3)

# Escrow Tx signed by the arbiter


# Escrow Tx signed by the sender
Tx2 = Transactions.Tx()
Tx2.add_input(pu3, 1.2)
Tx2.add_output(pu1, 1.1)
Tx2.add_reqd(pu4)
Tx2.sign(pr3)
Tx2.sign(pr4)

# Escrow Tx not signed by the arbiter


# Escrow Tx not signed by the arbiter
Tx3 = Transactions.Tx()
Tx3.add_input(pu3, 1.2)
Tx3.add_output(pu1, 1.1)
Tx3.add_reqd(pu4)
Tx3.sign(pr4)

n=0
for t in [Tx1, Tx2, Tx3]:
n=n+1
print("")
print ("Output for transaction", n)
if t.is_valid():
print("ERROR! Bad Transaction is valid")
else:
print("Success! Bad Transaction is invalid")
Output:
Practical 10

Aim: To test the validity of transactions


where output exceeds input.

Theory :

● Here, we import Signatures.py to use the function generate_keys() through which we generate four public
and private key pairs for testing transactions.
● We also import Transactions.py to create transaction instances Tx1 and Tx2, to add input using add_input()
function, to add output using add_output function, to sign a message after gathering all the information of
the message we use sign() function.
● Thus, we create a transaction by first creating instance of a transaction, then by adding input (senders
public key, amount to be sent), by adding output (receivers public key, amount to be received) and lastly
by adding signature of sender.
● The validity of the transaction is tested by is_valid() function from Transactions.py.

Program:

#Test10.py
#testing transactions for output exceeding input

import Signatures
import Transactions

if __name__ == "__main__":
pr1, pu1 = Signatures.generate_keys()
pr2, pu2 = Signatures.generate_keys()
pr3, pu3 = Signatures.generate_keys()
pr4, pu4 = Signatures.generate_keys()

# Outputs exceed inputs


Tx1 = Transactions.Tx()
Tx1.add_input(pu4, 2)
Tx1.add_output(pu1, 3)
Tx1.add_output(pu2, 1)
Tx8.sign(pr4)

# Outputs exceed inputs


Tx2 = Transactions.Tx()
Tx2.add_input(pu4, 1.2)
Tx2.add_output(pu1, 1)
Tx2.add_output(pu2, 2)
Tx2.sign(pr4)

n=0
for t in [Tx1, Tx2]:
n=n+1
print("")
print ("Output for transaction", n)
if t.is_valid():
print("ERROR! Bad Transaction is valid")
else:
print("Success! Bad Transaction is invalid")

Output:

You might also like