Skip to content
Permalink
Browse files

Simple blockchain implementation with secure wallet, verified transac…

…tions and proof-of-work
  • Loading branch information
Magnus Nevstad Magnus Nevstad
Magnus Nevstad authored and Magnus Nevstad committed Apr 7, 2019
0 parents commit 907920c2d6a658f4dad154c51d7b506438324452
@@ -0,0 +1,4 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
@@ -0,0 +1,23 @@
// swift-tools-version:4.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
name: "BlockchainSwift",
products: [
.library(
name: "BlockchainSwift",
targets: ["BlockchainSwift"]),
],
dependencies: [
],
targets: [
.target(
name: "BlockchainSwift",
dependencies: []),
.testTarget(
name: "BlockchainSwiftTests",
dependencies: ["BlockchainSwift"]),
]
)
@@ -0,0 +1,24 @@
# ⛓ A simple Blockchain implementation for macOS, written in Swift.

A Blockchain implementation that loosely mimics Bitcoin's key features.

## ✅ Features

* 🎭 Secure and anonymous Wallets
* 🔐 Verified Transations
* 🛠 A Proof-of-Work system


## ⛔️ Missing features:

* 🗄 Persistent block store
* 🌐 Decentralization

## 🚦 Requirements

* ✓ Swift 4.0+
* ✓ macOS 10.12+, iOS 10.0+, tvOS 10.0+


## 📣 Shoutout
Big thanks for inspiration and learning from [Ivan Kuznetsov's Building Blockchain in Go](https://github.com/Jeiwan/blockchain_go/tree/part_4) and [BitcoinKit](https://github.com/yenom/BitcoinKit).
@@ -0,0 +1,35 @@
//
// Block.swift
// App
//
// Created by Magnus Nevstad on 01/04/2019.
//
import Foundation

public struct Block: Serializable {
/// The timestamp for when the block was generated and added to the chain
public let timestamp: UInt32

/// The transactions on this block
public let transactions: [Transaction]

/// The proof used to solve the Proof of Work for this block
public let nonce: UInt32

/// The hash of this block on the chain, becomes `previousHash` for the next block.
/// THe hash is a SHA256 generated hash bashed on all other instance variables.
public let hash: Data

/// The hash of the previous block
public let previousHash: Data

public func serialized() -> Data {
var data = Data()
data += previousHash
data += timestamp
data += nonce
data += transactions.flatMap { $0.serialized() }
return data
}
}
@@ -0,0 +1,180 @@
//
// Blockchain.swift
// App
//
// Created by Magnus Nevstad on 01/04/2019.
//
import Foundation

public class Blockchain {
// Coin specifics, stolen from Bitcoin
public enum Coin {
static let satosis: UInt64 = 100_000_000
static let subsidy = 50 * satosis
static let halvingInterval: UInt64 = 210_000

/// Get the block value, or the block reward, at a specified block height
/// - Parameter blockHeight: The block height (number of blocks)
static func blockReward(at blockHeight: UInt64) -> UInt64 {
let halvings = blockHeight / halvingInterval
return subsidy / (1 + halvings)
}
}

/// Transaction error types
public enum TxError: Error {
case invalidValue
case insufficientBalance
case unverifiedTransaction
}

/// The blockchain
public private(set) var chain: [Block] = []

/// Proof of Work Algorithm
public private(set) var pow = ProofOfWork(difficulty: 3)

/// Transation pool holds all transactions to go into the next block
public private(set) var mempool = [Transaction]()

/// Unspent Transaction Outputs
/// - This class keeps track off all current UTXOs, providing a quick lookup for balances and creating new transactions.
/// - For now, any transaction will use all available utxos for that address, meaning we have an easier job of things.
/// - Also, since we have no decentralization, we don't have to worry about reloading this based on the current blockchain whenver we have to sync blocks with other nodes.
public private(set) var utxos = [TransactionOutput]()

/// Explicitly define Codable properties
private enum CodingKeys: CodingKey {
case mempool
case chain
}


/// Initialises our blockchain with a genesis block
public init(minerAddress: Data) {
mineGenesisBlock(minerAddress: minerAddress)
}

/// Creates a coinbase transaction
/// - Parameter address: The miner's address to be awarded a block reward
/// - Returns: The index of the block to whitch this transaction will be added
@discardableResult
private func createCoinbaseTransaction(for address: Data) -> Int {
// Generate a coinbase tx to reward block miner
let coinbaseTx = Transaction.coinbase(address: address, blockValue: currentBlockValue())
self.mempool.append(coinbaseTx)
self.utxos.append(contentsOf: coinbaseTx.outputs)
return self.chain.count + 1
}

/// Create a transaction to be added to the next block.
/// - Parameters:
/// - sender: The sender
/// - recipient: The recipient
/// - value: The value to transact
/// - Returns: The index of the block to whitch this transaction will be added
@discardableResult
public func createTransaction(sender: Wallet, recipientAddress: Data, value: UInt64) throws -> Int {
// You cannot send nothing
if value == 0 {
throw TxError.invalidValue
}

// Calculate transaction value and change, based on the sender's balance and the transaction's value
// - All utxos for the sender must be spent, and are indivisible.
let balance = self.balance(for: sender.address)
if value > balance {
throw TxError.insufficientBalance
}
let change = balance - value

// Create a transaction and sign it, making sure first the sender has the right to claim the spendale outputs
let spendableOutputs = self.utxos.filter { $0.address == sender.address }
guard let signedTxIns = try? sender.sign(utxos: spendableOutputs) else { return -1 }
for (i, txIn) in signedTxIns.enumerated() {
let originalOutputData = spendableOutputs[i].serialized().sha256()
if !ECDSA.verify(publicKey: sender.publicKey, data: originalOutputData, signature: txIn.signature) {
throw TxError.unverifiedTransaction
}
}

// Add transaction to the pool
let txOuts = [
TransactionOutput(value: value, address: recipientAddress),
TransactionOutput(value: change, address: sender.address)
]
self.mempool.append(Transaction(inputs: signedTxIns, outputs: txOuts))

// All spendable outputs for sender must be spent, and all outputs added
self.utxos.removeAll { $0.address == sender.address }
self.utxos.append(contentsOf: txOuts)

return self.chain.count + 1
}

/// Finds a transaction by id, iterating through every block (to optimize this, look into Merkle trees).
/// - Parameter txId: The txId
public func findTransaction(txId: String) -> Transaction? {
for block in chain {
for transaction in block.transactions {
if transaction.txId == txId {
return transaction
}
}
}
return nil
}

/// Create a new block in the chain, adding transactions curently in the mempool to the block
/// - Parameter proof: The proof of the PoW
@discardableResult
public func createBlock(nonce: UInt32, hash: Data, previousHash: Data, timestamp: UInt32, transactions: [Transaction]) -> Block {
let block = Block(timestamp: timestamp, transactions: transactions, nonce: nonce, hash: hash, previousHash: previousHash)
self.chain.append(block)
return block
}

/// Mines our genesis block placing circulating supply in the reward pool,
/// and awarding the first block to Magnus
@discardableResult
private func mineGenesisBlock(minerAddress: Data) -> Block {
return mineBlock(previousHash: Data(), minerAddress: minerAddress)
}

/// Mines the next block using Proof of Work
/// - Parameter recipient: The miners address for block reward
public func mineBlock(previousHash: Data, minerAddress: Data) -> Block {
// Generate a coinbase tx to reward block miner
createCoinbaseTransaction(for: minerAddress)

// Do Proof of Work to mine block with all currently registered transactions, the create our block
let transactions = mempool
mempool.removeAll()
let timestamp = UInt32(Date().timeIntervalSince1970)
let proof = pow.work(prevHash: previousHash, timestamp: timestamp, transactions: transactions)
return createBlock(nonce: proof.nonce, hash: proof.hash, previousHash: previousHash, timestamp: timestamp, transactions: transactions)
}

/// Returns the last block in the blockchain. Fatal error if we have no blocks.
public func lastBlock() -> Block {
guard let last = chain.last else {
fatalError("Blockchain needs at least a genesis block!")
}
return last
}

/// Get the block value, or the block reward, at current block height
public func currentBlockValue() -> UInt64 {
return Coin.blockReward(at: UInt64(self.chain.count))
}

/// Returns the balannce for a specified address, defined by the sum of its unspent outputs
public func balance(for address: Data) -> UInt64 {
var balance: UInt64 = 0
for output in utxos.filter({ $0.address == address }) {
balance += output.value
}
return balance
}
}
@@ -0,0 +1,55 @@
//
// Serialization.swift
// App
//
// Created by Magnus Nevstad on 04/04/2019.
//
import Foundation

protocol Serializable {
func serialized() -> Data
}
protocol Deserializable {
func deserialized() -> Self
}

protocol BinaryConvertible {
static func +(lhs: Data, rhs: Self) -> Data
static func +=(lhs: inout Data, rhs: Self)
}

extension BinaryConvertible {
static func +(lhs: Data, rhs: Self) -> Data {
var value = rhs
let data = Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
return lhs + data
}

static func +=(lhs: inout Data, rhs: Self) {
lhs = lhs + rhs
}
}

extension UInt8: BinaryConvertible {}
extension UInt16: BinaryConvertible {}
extension UInt32: BinaryConvertible {}
extension UInt64: BinaryConvertible {}
extension Int8: BinaryConvertible {}
extension Int16: BinaryConvertible {}
extension Int32: BinaryConvertible {}
extension Int64: BinaryConvertible {}
extension Int: BinaryConvertible {}

extension Bool: BinaryConvertible {
static func +(lhs: Data, rhs: Bool) -> Data {
return lhs + (rhs ? UInt8(0x01) : UInt8(0x00)).littleEndian
}
}

extension String: BinaryConvertible {
static func +(lhs: Data, rhs: String) -> Data {
guard let data = rhs.data(using: .ascii) else { return lhs }
return lhs + data
}
}
@@ -0,0 +1,50 @@
//
// Transaction.swift
// App
//
// Created by Magnus Nevstad on 01/04/2019.
//
import Foundation

public struct Transaction: Serializable {
/// Transaction inputs, which are sources for coins
public let inputs: [TransactionInput]

/// Transaction outputs, which are destinations for coins
public let outputs: [TransactionOutput]

/// Transaction hash
public var txHash: Data {
get {
return self.serialized().sha256()
}
}

/// Transaction ID
public var txId: String {
return Data(txHash.reversed()).hex
}

/// Coinbase transactions have only one TransactionInput which itself has no previus output reference
public var isCoinbase: Bool {
get {
return inputs.count == 1 && inputs[0].isCoinbase
}
}

public func serialized() -> Data {
var data = Data()
data += inputs.flatMap { $0.serialized() }
data += outputs.flatMap { $0.serialized() }
return data
}

public static func coinbase(address: Data, blockValue: UInt64) -> Transaction {
let coinbaseTxOutPoint = TransactionOutPoint(hash: Data(), index: 0)
let coinbaseTxIn = TransactionInput(previousOutput: coinbaseTxOutPoint, publicKey: address, signature: Data())
let txIns:[TransactionInput] = [coinbaseTxIn]
let txOuts:[TransactionOutput] = [TransactionOutput(value: blockValue, address: address)]
return Transaction(inputs: txIns, outputs: txOuts)
}
}

0 comments on commit 907920c

Please sign in to comment.
You can’t perform that action at this time.