Initial import

This commit is contained in:
Mike Hearn 2015-11-03 13:37:19 +01:00
commit c60db5544b
17 changed files with 1047 additions and 0 deletions

52
.gitignore vendored Normal file
View File

@ -0,0 +1,52 @@
# Created by .ignore support plugin (hsz.mobi)
.gradle
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties

31
build.gradle Normal file
View File

@ -0,0 +1,31 @@
group 'com.r3cev.experimental'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'kotlin'
repositories {
mavenCentral()
jcenter()
}
buildscript {
ext.kotlin_version = '1.0.0-beta-1103'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "com.google.guava:guava:18.0"
compile "org.funktionale:funktionale:0.6_1.0.0-beta"
}
sourceSets {
main.java.srcDirs += 'src'
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Thu Oct 29 18:30:55 CET 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-all.zip

164
gradlew vendored Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

195
notes/Design ideas.md Normal file
View File

@ -0,0 +1,195 @@
General design scratchpad
Do we need blocks at all? Blocks are an artifact of proof-of-work, which isn't acceptable on private block chains
due to the excessive energy usage, unclear incentives model and so on. They're also very useful for SPV operation,
but we have no such requirements here.
Possible alternative, blend of ideas from:
* Google Spanner
* Hawk
* Bitcoin
* Ethereum
* Intel/TCG
+ some of my own ideas
# Blockless operation
* A set of timestampers are set up around the world with clocks synchronised to GPS time (the most accurate clock
available as it's constantly recalibrated against the US Naval Observatory atomic clock). Public timestampers
are available already and can be easily used in the prototyping phase, but as they're intended for low traffic
applications eventually we'd want our own.
There is a standard protocol for timestamp servers (RFC 3161). It appears to include everything that we might want
and little more, i.e. it's a good place to start. A more modern version of it with the same features can be easily
generated later.
* All transactions submitted to the global network must be timestamped by a recognised TSP (i.e. signed by a root cert
owned by R3).
* Transactions are ordered according to these timestamps. They are assumed to be precise enough that conflicts where
two transactions have actually equal times can ~never happen: a trivial resolution algorithm (e.g. based on whichever
hash is lower) can be used in case that ever happens by fluke.
* If need be, clock uncertainty can be measured and overlapping intervals can result in conflict/reject states, as in
Spanner's TrueTime. The timestamping protocol (RFC 3161) exposes clock uncertainty.
* Transactions are timestamped as a group. This ensures that if multiple transactions are needed to carry out a trade,
individual transactions cannot be extracted by a malicious party and executed independently as the original bundle
will always be able to win, when broadcast.
* Nodes listen to a broadcast feed of timestamped transactions. They know how to roll back transactions and replay
new ones in case of conflict, but this is done independent of any block construct.
* Nodes that are catching up simply download all the transactions from peers that occur after the time they shut down.
They can be sure they didn't miss any by asking peers to calculate a UTXO set summary at a given time and then
verifying it against their own local calculations (this is slow, but shouldn't normally flag any issues so it can
be done asynchronously).
* Individual transactions/UTXOs can specify time bounds, e.g. "30 seconds". A node compares a transaction timestamp
to its own local clock and applies the specified bound to the local clock: if the transaction is out of bounds and
the node isn't catching up, then it is dropped. This prevents people timestamping a malicious transaction X and
keeping it private, then broadcasting a publicly timestamped transaction Y, then overriding Y with X long after the
underlying trade has become irreversible. Because time bounds are specified on a _per transaction_ basis, it is
arbitrarily controllable: traders that want very, very fast clearing can specify a small time boundary and it's up
to them to ensure their own systems are capable of getting an accurate trusted timestamp and broadcasting it within
that tight bound. Traders that care less, e.g. because the trade represents physical movement of real goods, can use
a much larger time bound and get more robustness against transient network/hardware hiccups.
* Like in Ethereum, transactions can update stored state (contracts? receipts? what is the right term?)
This can be called transaction-chains. All transactions are public.
For political expedience, we may wish to impose a (not strictly necessary) block quantisation anyway, so the popular
term 'block chain' can be applied and also for auditing/reporting convenience.
# Privacy
* Transactions can have two halves: the public side and the private side. The public side is a "normal" transaction that
includes a program of sufficient power to verify various kinds of signatures and proofs. The optional private side
is an arbitrary program which is executed by a third party. Various techniques are used to lower the trust required
in the third parties. We can call these notaries.
* It's up to the contract designer to decide how much they rely on notaries - if at all. They are technically not
required at all: the system would work (and scale) without them. But they can be used to improve privacy.
* Simplest "dummy" notary is just a machine that signs the output of the program to state it ran it properly. The notary
is trusted to execute the program correctly and privately. The signature is checked by the public side. This allows
traders to perform e.g. a Dutch auction with only the final results being reflected on the public network.
* Next best is an SGX based notary. This can provide both privacy and assurance that the code is executed correctly,
assuming Intel is trustworthy. Note: it's a safe assumption that if R3 becomes very popular with financial networks,
intelligence agencies will attempt to gain covert access to it given the NSA/GCHQ hacking of Western Union and clear
interest in SWIFT data. Thus care must be used to ensure the (entirely unprovable) SGX computers are not interdicted
during delivery.
* In addition, zero knowledge proofs can be considered as a supplement to SGX. They can give extra assurance against
corrupted notaries calculating incorrect results. However, unlike SGX, they cannot reduce the amount of information
the notary sees, and thus they are strictly a "backup". In addition they have _severe_ caveats, in particular, a
complex and expensive setup phase that must be executed for each contract (in fact for each version of each contract),
and execution of the private side is extremely slow.
This makes them suitable only for contracts that are basically finalised and in which the highest levels of assurance
are required, and fast or frequent trading is not required. The technology may well improve over time.
* In some cases homomorphic encryption could be used as a privacy supplement to SGX.
# Scaling
* Global broadcast systems are frequently attacked for 'not scaling'. But this is an absolute statement in a world of
tradeoffs: technically speaking the NASDAQ is a broadcast system as you can subscribe to data feeds via e.g. OPRA.
Some of these feeds can reach millions of messages per second. Nonetheless, financial firms are capable of digesting
them without issue. Even the largest feeds have finite traffic and predictable growth patterns.
* We can assume powerful hardware, as the primary users of this system would be financial institutions. There is no
requirement to run on people's laptops, outside of testing/devnet scenarios. For instance it's safe to assume SSD
based storage: we can simply tell institutions that want to get on the network to buy a proper server.
* There is no requirement for lightweight/mobile clients, unlike in Bitcoin.
* Transaction checking is highly parallelisable.
* Therefore, as long as transactions are kept computationally cheap, there should be no problem reaching even very high
levels of traffic.
Conclusion: scaling in a Bitcoin style manner should not be a problem, even if high level languages like Java or Kotlin
are in use.
# Programmability
* The public side of a transaction must use a globally agreed execution environment, like the EVM is for Ethereum.
The private sides can run anything: as the public side checks a proof of execution of the private side, there is
no requirement that the private side use any particular language or runtime.
* Inventing a custom VM and language doesn't make sense: there is only one special requirement that is different
to most VMs and that's the ability to impose hard CPU usage limits. But existing VMs can be extended to deliver
this functionality much more easily than entirely new VMs+languages can be created.
* For prototyping and possibly for production use, we should use the JVM:
* Sandboxing already available, easy to use
* Several languages available, developers are familiar
* If host environment also runs on the JVM, no time wasted on interop issues, see the Ethereum ABI issues
* HotSpot already has a CPU/memory tracking API and can interrupt threads (but lacks the ability to hard shut down
malicious code)
* Code annotations can be used to customise whatever languages are used for contract-specific use cases.
* Can be forced to run in interpreted mode at first, but if we need the extra performance later due to high traffic
the JIT compiler will automatically make contract code fast.
* Has industrial strength debugging/monitoring tools.
* Banks are already deeply familiar with it.
# Transaction design
Use a vaguely bitcoin-like design with "states" which are consumed and generated by "contracts" (programs). Everyone
runs the same programs simultaneously in order to verify state transitions. Transactions consist of input states,
output states and "commands" which represent signed auxiliary inputs to the transitions.
------
# Useful technologies
FIX SBE is a very (very) efficient binary encoding designed for HFT:
http://real-logic.github.io/simple-binary-encoding/
It's mostly analogous to protocol buffers but imposes some additional constraints and has an uglier API, in return for
much higher performance. It probably isn't useful during the prototyping phase. But it may be a useful optimisation
later.
CopyCat is an implementation of Raft (similar to Paxos), as an embeddable framework. Raft/Paxos type algorithms are not
suitable as the basis for a global distributed ledger due to tiny throughput, but may be useful as a subcomponent of
other algorithms. For instance possibly a multi-step contract protocol could use Raft/Paxos between a limited number of
counterparties to synchronise changes.
http://kuujo.github.io/copycat/user-manual/introduction/
------
# Prototyping
Stream 1:
1. Implement a simple star topology for message routing (full p2p can come later). Ensure it's got a clean modular API.
2. Implement a simple chat app on top of it. This will be useful later for sending commands to faucets, bots, etc.
3. Add auto-update
4. Design a basic transaction/transaction bundle abstraction and implement timestamping of the bundles. Make chat lines
into "transactions", so they are digitally signed and timestamped properly.
5. Implement detection of conflicts and rollbacks.
Stream 2: Design straw-man contracts and data structures (in Java or Kotlin) for
1. payments
2. simplified bond auctions
3. maybe a CDS

View File

@ -0,0 +1,19 @@
# Simple payment
CashState:
- Issuing institution
- Deposit reference (pointer into internal ledger)
- Currency code
- Claim size (initial state = size of original deposit)
- Public key of current owner
ExitCashState:
- Amount to reduce claim size by
- Signature signed by ownerPubKey
State transition function (contract):
1. If input states contains an ExitCashState, set reduceByAmount=state.amount
1. For all proposed output states, they must all be instances of CashState
For all proposed input states, they must all be instances of CashState
2. Sum claim sizes in all predecessor states. Sum claim sizes in all successor states
3. Accept if outputSum == inputSum - reduceByAmount

19
notes/Open questions.md Normal file
View File

@ -0,0 +1,19 @@
How to represent pointers to states in the type system? Opaque or exposed as hashes?
# Create states vs check states?
1. Derive output states entirely from input states + signed commands, *or*
2. Be given the output states and check they're valid
The advantage of 1 is that it feels safer: you can't forget to check something in the output state by accident. On
the other hand, then it's up to the platform to validate equality between the states (probably by serializing them
and comparing bit strings), and that would make unit testing harder as the generic machinery can't give good error
messages for a given mismatch. Also it means you can't do an equivalent of OP_RETURN and insert extra no-op states
in the output list that are ignored by all the input contracts. Does that matter if extensibility/tagging is built in
more elegantly? Is it better to prevent this for the usual spam reasons?
The advantage of 2 is that it seems somehow more extensible: old contracts would ignore fields added to new states if
they didn't understand them (or is that a disadvantage?)
# What precisely is signed at each point?

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'playground'

82
src/Cash.kt Normal file
View File

@ -0,0 +1,82 @@
import java.security.PublicKey
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Cash
// TODO: Implement multi-issuer case.
// TODO: Does multi-currency also make sense? Probably?
// TODO: Implement a generate function.
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
val CASH_PROGRAM_ID = SecureHash.sha256("cash")
/** A state representing a claim on the cash reserves of some institution */
data class CashState(
/** The institution that has this original cash deposit (propagated) */
val issuingInstitution: Institution,
/** Whatever internal ID the bank needs in order to locate that deposit, may be encrypted (propagated) */
val depositReference: ByteArray,
val amount: Amount,
/** There must be a MoveCommand signed by this key to claim the amount */
val owner: PublicKey
) : ContractState {
override val programRef = CASH_PROGRAM_ID
}
/** A command proving ownership of some input states, the signature covers the output states. */
class MoveCashCommand : Command
/** A command stating that money has been withdrawn from the shared ledger and is now accounted for in some other way */
class ExitCashCommand(val amount: Amount) : Command
class CashContract : Contract {
override fun verify(inStates: List<ContractState>, outStates: List<ContractState>, args: List<VerifiedSignedCommand>) {
// Select all input states that are cash states and ensure they are all denominated in the same currency and
// issued by the same issuer.
val inputs = inStates.filterIsInstance<CashState>()
val inputMoney = inputs.sumBy { it.amount.pennies }
requireThat {
"there is at least one cash input" by inputs.isNotEmpty()
"all inputs use the same currency" by (inputs.groupBy { it.amount.currency }.size == 1)
"all inputs come from the same issuer" by (inputs.groupBy { it.issuingInstitution }.size == 1)
"some money is actually moving" by (inputMoney > 0)
}
val issuer = inputs.first().issuingInstitution
val currency = inputs.first().amount.currency
val depositReference = inputs.first().depositReference
// Select all the output states that are cash states. There may be zero if all money is being withdrawn.
// If there are any though, check that the currencies and issuers match the inputs.
val outputs = outStates.filterIsInstance<CashState>()
val outputMoney = outputs.sumBy { it.amount.pennies }
requireThat {
"all outputs use the currency of the inputs" by outputs.all { it.amount.currency == currency }
"all outputs claim against the issuer of the inputs" by outputs.all { it.issuingInstitution == issuer }
"all outputs use the same deposit reference as the inputs" by outputs.all { it.depositReference == depositReference }
}
// If we have any commands, find the one that came from the issuer of the original cash deposit and
// check if it's an exit command.
val issuerCommand = args.find { it.signingInstitution == issuer }?.command as? ExitCashCommand
val amountExitingLedger = issuerCommand?.amount?.pennies ?: 0
requireThat("the value exiting the ledger is not more than the input value", amountExitingLedger <= outputMoney)
// Verify the books balance.
requireThat("the amounts balance", inputMoney == outputMoney + amountExitingLedger)
// Now check the digital signatures on the move commands. Every input has an owning public key, and we must
// see a signature from each of those keys. The actual signatures have been verified against the transaction
// data by the platform before execution.
val owningPubKeys = inputs.map { it.owner }.toSortedSet()
val keysThatSigned = args.filter { it.command is MoveCashCommand }.map { it.signer }.toSortedSet()
requireThat("the owning keys are the same as the signing keys", owningPubKeys == keysThatSigned)
// Accept.
}
}

36
src/Crypto.kt Normal file
View File

@ -0,0 +1,36 @@
import com.google.common.io.BaseEncoding
import java.security.MessageDigest
import java.security.PublicKey
// "sealed" here means there can't be any subclasses other than the ones defined here.
sealed class SecureHash(val bits: ByteArray) {
class SHA256(bits: ByteArray) : SecureHash(bits) {
init { require(bits.size == 32) }
}
// Like static methods in Java, except the 'companion' is a singleton that can have state.
companion object {
fun parse(str: String) = BaseEncoding.base16().decode(str.toLowerCase()).let {
when (it.size) {
32 -> SecureHash.SHA256(it)
else -> throw IllegalArgumentException("Provided string is not 32 bytes in base 16 (hex): $str")
}
}
fun sha256(bits: ByteArray) = SHA256(MessageDigest.getInstance("SHA-256").digest(bits))
fun sha256(str: String) = sha256(str.toByteArray())
}
// In future, maybe SHA3, truncated hashes etc.
}
/**
* A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the
* signature.
*/
sealed class DigitalSignature(val bits: ByteArray, val covering: Int) {
/** A digital signature that identifies who the public key is owned by */
open class WithKey(val by: PublicKey, bits: ByteArray, covering: Int) : DigitalSignature(bits, covering)
class LegallyIdentifiable(val signer: Institution, bits: ByteArray, covering: Int) : WithKey(signer.owningKey, bits, covering)
}

5
src/Main.kt Normal file
View File

@ -0,0 +1,5 @@
// TODO: Make a decision on basic timestamping approach (use external TSAs? use fake TSA?)
// TODO: Think about how to expose time to the contract
// TODO: Pick a serialisation solution for the prototype (kryo?)
// TODO: Store transactions to a database, design some conflict detection and rollback
// TODO: Implement a basic UI that shows contracts and very basic network layer (with chat?)

113
src/Structures.kt Normal file
View File

@ -0,0 +1,113 @@
import java.security.PublicKey
import java.security.Timestamp
import java.util.*
data class Amount(val pennies: Int, val currency: Currency) {
init {
// Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain
// contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance.
// TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system.
require(pennies >= 0) { "Negative amounts are not allowed: $pennies" }
}
operator fun plus(other: Amount): Amount {
require(other.currency == currency)
return Amount(pennies + other.pennies, currency)
}
operator fun minus(other: Amount): Amount {
require(other.currency == currency)
return Amount(pennies - other.pennies, currency)
}
}
/**
* A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
* file that the program can use to persist data across transactions. States are immutable: once created they are never
* updated, instead, any changes must generate a new successor state.
*/
interface ContractState {
/**
* Refers to a bytecode program that has previously been published to the network. This contract program
* will be executed any time this state is used in an input. It must accept in order for the
* transaction to proceed.
*/
val programRef: SecureHash
}
/**
* A stateref is a pointer to a state, normally a hash but this version is opaque.
*/
class ContractStateRef(private val hash: SecureHash.SHA256)
/**
* A transaction wraps the data needed to calculate one or more successor states from a set of input states.
* The data here is provided in lightly processed form to the verify method of each input states contract program.
* Specifically, the input state refs are dereferenced into real [ContractState]s and the args are signature checked
* and institutions are looked up (if known).
*/
class Transaction(
/** Arbitrary data passed to the program of each input state. */
val args: List<SignedCommand>,
/** The input states which will be consumed/invalidated by the execution of this transaction. */
val inputStates: List<ContractStateRef>,
/** The states that will be generated by the execution of this transaction. */
val outputStates: List<ContractState>
)
/**
* A transition groups one or more transactions together, and combines them with a signed timestamp. A transaction
* may not stand independent of a transition and all transactions are applied or reverted together as a unit.
*
* Consider the following attack: a malicious actor extracts a single transaction like "pay $X to me" from a transition
* and then broadcasts it with a fresh timestamp. This cannot work because the original transition will always take
* priority over the later attempt as it has an earlier timestamp. As long as both are visible, the first transition
* will always win.
*/
data class Transition(
val tx: Transaction,
/** Timestamp of the serialised transaction as fetched from a timestamping authority (RFC 3161) */
val signedTimestamp: Timestamp
)
class Institution(
val name: String,
val owningKey: PublicKey
) {
override fun toString() = name
}
interface Command
/** Provided as an input to a contract; converted to a [VerifiedSignedCommand] by the platform before execution. */
data class SignedCommand(
/** Signature over this object to prove who it came from */
val commandDataSignature: DigitalSignature.WithKey,
/** Command data, deserialized to an implementation of [Command] */
val serialized: ByteArray,
/** Identifies what command the serialized data contains (should maybe be a hash too) */
val classID: String,
/** Hash of a derivative of the transaction data, so this command can only ever apply to one transaction */
val txBindingHash: SecureHash.SHA256
)
/** Obtained from a [SignedCommand], deserialised and signature checked */
data class VerifiedSignedCommand(
val signer: PublicKey,
/** If the public key was recognised, the looked up institution is available here, otherwise it's null */
val signingInstitution: Institution?,
val command: Command
)
/**
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for
* every [Transaction] they see on the network, for every input state. All input states must accept the transaction
* for it to be accepted: failure of any aborts the entire thing.
*/
interface Contract {
/** Must throw an exception if there's a problem that should prevent state transition. */
fun verify(inStates: List<ContractState>, outStates: List<ContractState>, args: List<VerifiedSignedCommand>)
}

38
src/Utils.kt Normal file
View File

@ -0,0 +1,38 @@
import java.math.BigInteger
import java.security.PublicKey
import java.util.*
import kotlin.test.assertTrue
import kotlin.test.fail
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// REQUIREMENTS
fun requireThat(message: String, expression: Boolean) {
if (!expression) throw IllegalArgumentException(message)
}
// To understand how requireThat works, read the section "type safe builders" on the Kotlin website:
//
// https://kotlinlang.org/docs/reference/type-safe-builders.html
object Requirements {
infix fun String.by(expr: Boolean) {
if (!expr) throw IllegalArgumentException("Failed requirement: $this")
}
}
fun requireThat(body: Requirements.() -> Unit) {
Requirements.body()
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// CURRENCIES (convenience accessors)
val USD = Currency.getInstance("USD")
val GBP = Currency.getInstance("GBP")
val CHF = Currency.getInstance("CHF")
val Int.DOLLARS: Amount get() = Amount(this, USD)
val Int.POUNDS: Amount get() = Amount(this, GBP)
val Int.SWISS_FRANCS: Amount get() = Amount(this, CHF)

123
tests/CashTests.kt Normal file
View File

@ -0,0 +1,123 @@
import org.junit.Test
class CashTests {
val inState = CashState(
issuingInstitution = MEGA_CORP,
depositReference = byteArrayOf(1),
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1
)
val inState2 = inState.copy(
amount = 150.POUNDS,
owner = DUMMY_PUBKEY_2
)
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
@Test
fun trivial() {
CashContract().let {
transaction {
it `fails requirement` "there is at least one cash input"
}
transaction {
input { inState.copy(amount = 0.DOLLARS) }
it `fails requirement` "some money is actually moving"
}
transaction {
input { inState }
it `fails requirement` "the amounts balance"
transaction {
output { outState.copy(amount = 2000.DOLLARS )}
it `fails requirement` "the amounts balance"
}
transaction {
output { outState }
// No command arguments
it `fails requirement` "the owning keys are the same as the signing keys"
}
transaction {
output { outState }
arg(DUMMY_PUBKEY_2) { MoveCashCommand() }
it `fails requirement` "the owning keys are the same as the signing keys"
}
transaction {
output { outState }
arg(DUMMY_PUBKEY_1) { MoveCashCommand() }
it.accepts()
}
}
}
}
@Test
fun mismatches() {
CashContract().let {
transaction {
input { inState }
output { outState.copy(issuingInstitution = MINI_CORP) }
it `fails requirement` "all outputs claim against the issuer of the inputs"
}
transaction {
input { inState }
output { outState.copy(issuingInstitution = MEGA_CORP) }
output { outState.copy(issuingInstitution = MINI_CORP) }
it `fails requirement` "all outputs claim against the issuer of the inputs"
}
transaction {
input { inState }
output { outState.copy(depositReference = byteArrayOf(0)) }
output { outState.copy(depositReference = byteArrayOf(1)) }
it `fails requirement` "all outputs use the same deposit reference as the inputs"
}
transaction {
input { inState }
output { outState.copy(amount = 800.DOLLARS) }
output { outState.copy(amount = 200.POUNDS) }
it `fails requirement` "all outputs use the currency of the inputs"
}
transaction {
input { inState }
input { inState2 }
output { outState.copy(amount = 1150.DOLLARS) }
it `fails requirement` "all inputs use the same currency"
}
transaction {
input { inState }
input { inState.copy(issuingInstitution = MINI_CORP) }
output { outState }
it `fails requirement` "all inputs come from the same issuer"
}
}
}
@Test
fun exitLedger() {
CashContract().let {
transaction {
input { inState }
output { outState.copy(amount = inState.amount - 200.DOLLARS) }
transaction {
arg(MEGA_CORP_KEY) {
ExitCashCommand(100.DOLLARS)
}
it `fails requirement` "the amounts balance"
}
transaction {
arg(MEGA_CORP_KEY) {
ExitCashCommand(200.DOLLARS)
}
it `fails requirement` "the owning keys are the same as the signing keys" // No move command.
transaction {
arg(DUMMY_PUBKEY_1) { MoveCashCommand() }
it.accepts()
}
}
}
}
}
}

72
tests/TestUtils.kt Normal file
View File

@ -0,0 +1,72 @@
import java.math.BigInteger
import java.security.PublicKey
import kotlin.test.assertTrue
import kotlin.test.fail
class DummyPublicKey(private val s: String) : PublicKey, Comparable<PublicKey> {
override fun getAlgorithm() = "DUMMY"
override fun getEncoded() = s.toByteArray()
override fun getFormat() = "ASN.1"
override fun compareTo(other: PublicKey): Int = BigInteger(encoded).compareTo(BigInteger(other.encoded))
}
// A few dummy values for testing.
val MEGA_CORP_KEY = DummyPublicKey("mini")
val MINI_CORP_KEY = DummyPublicKey("mega")
val DUMMY_PUBKEY_1 = DummyPublicKey("x1")
val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
val MEGA_CORP = Institution("MegaCorp", MEGA_CORP_KEY)
val MINI_CORP = Institution("MiniCorp", MINI_CORP_KEY)
val keyToCorpMap: Map<PublicKey, Institution> = mapOf(
MEGA_CORP_KEY to MEGA_CORP,
MINI_CORP_KEY to MINI_CORP
)
// DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
// Corresponds to the args to Contract.verify
data class TransactionForTest(
private val inStates: MutableList<ContractState> = arrayListOf(),
private val outStates: MutableList<ContractState> = arrayListOf(),
private val args: MutableList<VerifiedSignedCommand> = arrayListOf()
) {
fun input(s: () -> ContractState) = inStates.add(s())
fun output(s: () -> ContractState) = outStates.add(s())
fun arg(key: PublicKey, c: () -> Command) = args.add(VerifiedSignedCommand(key, keyToCorpMap[key], c()))
infix fun Contract.`fails requirement`(msg: String) {
try {
verify(inStates, outStates, args)
} catch(e: Exception) {
val m = e.message
if (m == null)
fail("Threw exception without a message")
else
assertTrue(m.contains(msg), "Error was actually: $m")
}
}
// which is uglier?? :)
fun Contract.fails_requirement(msg: String) = this.`fails requirement`(msg)
fun Contract.accepts() {
verify(inStates, outStates, args)
}
// Allow customisation of partial transactions.
fun transaction(body: TransactionForTest.() -> Unit): TransactionForTest {
val tx = TransactionForTest()
tx.inStates.addAll(inStates)
tx.outStates.addAll(outStates)
tx.args.addAll(args)
tx.body()
return tx
}
}
fun transaction(body: TransactionForTest.() -> Unit): TransactionForTest {
val tx = TransactionForTest()
tx.body()
return tx
}