commit c60db5544ba5c901c4afce7e23f813be203934d1 Author: Mike Hearn Date: Tue Nov 3 13:37:19 2015 +0100 Initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..255773ec4a --- /dev/null +++ b/.gitignore @@ -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 + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..7810e801d1 --- /dev/null +++ b/build.gradle @@ -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' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..2322723c7e Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..206b3b7d19 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/gradlew @@ -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 "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/notes/Design ideas.md b/notes/Design ideas.md new file mode 100644 index 0000000000..90f5ca505c --- /dev/null +++ b/notes/Design ideas.md @@ -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 \ No newline at end of file diff --git a/notes/Example contracts.md b/notes/Example contracts.md new file mode 100644 index 0000000000..6fd07bce7c --- /dev/null +++ b/notes/Example contracts.md @@ -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 diff --git a/notes/Open questions.md b/notes/Open questions.md new file mode 100644 index 0000000000..8c1d316274 --- /dev/null +++ b/notes/Open questions.md @@ -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? + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..a74a8ce471 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'playground' + diff --git a/src/Cash.kt b/src/Cash.kt new file mode 100644 index 0000000000..3817daaed4 --- /dev/null +++ b/src/Cash.kt @@ -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, outStates: List, args: List) { + // 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() + 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() + 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. + } +} \ No newline at end of file diff --git a/src/Crypto.kt b/src/Crypto.kt new file mode 100644 index 0000000000..aa79c67409 --- /dev/null +++ b/src/Crypto.kt @@ -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) +} + diff --git a/src/Main.kt b/src/Main.kt new file mode 100644 index 0000000000..76cc9d5ec0 --- /dev/null +++ b/src/Main.kt @@ -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?) \ No newline at end of file diff --git a/src/Structures.kt b/src/Structures.kt new file mode 100644 index 0000000000..1d195206f3 --- /dev/null +++ b/src/Structures.kt @@ -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, + /** The input states which will be consumed/invalidated by the execution of this transaction. */ + val inputStates: List, + /** The states that will be generated by the execution of this transaction. */ + val outputStates: List +) + +/** + * 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, outStates: List, args: List) +} + diff --git a/src/Utils.kt b/src/Utils.kt new file mode 100644 index 0000000000..54287b7843 --- /dev/null +++ b/src/Utils.kt @@ -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) \ No newline at end of file diff --git a/tests/CashTests.kt b/tests/CashTests.kt new file mode 100644 index 0000000000..ffed374830 --- /dev/null +++ b/tests/CashTests.kt @@ -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() + } + } + } + } + } +} diff --git a/tests/TestUtils.kt b/tests/TestUtils.kt new file mode 100644 index 0000000000..eec864728c --- /dev/null +++ b/tests/TestUtils.kt @@ -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 { + 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 = 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 = arrayListOf(), + private val outStates: MutableList = arrayListOf(), + private val args: MutableList = 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 +}