Reputation: 21
Can an entity running a Corda node verify the issued amount of a certain token in the network, without knowing who owns how many of these tokens?
(E.g. some party is issuing gold tokens, backed by physical gold. How can an investor verify the total supply of the gold tokens in the network? Currently you either lose privacy or have to trust a third party, like a regulator node.)
Upvotes: 1
Views: 264
Reputation: 23140
Here is a possible solution.
In the code below, TokenState
defines a standard token with an additional issuingHash
field. TokenContract
enforces that this field is set to the hash of the transaction that originally issued the token after the token has been issued but before it can be transferred. Once set, the issuingHash
can never be modified.
For example, suppose there is some issuance transaction with the hash 7925679A6414AEBF69ED1A250E3E1E4452A4384529E3B690A4B47DD6A9918B93
that generates 1,000,000 tokens. TokenContract
enforces that this is set to the tokens' issuingHash
in the next transaction.
Now, if the original issuance transaction is broadly shared, everyone can be sure that there can only ever be 1,000,000 tokens with this issuingHash
in existence. The notary pool will reject any future attempts to set the issuingHash
of the outputs of an issuance transaction to the same hash (it would constitute a double-spend attempt), and no tokens can be transferred until the issuingHash
is set.
You can then say that you're only willing to be paid in tokens with issuingHash
7925679A6414AEBF69ED1A250E3E1E4452A4384529E3B690A4B47DD6A9918B93
, knowing that only 1,000,000 exist.
data class TokenState(val owner: Party, val amount: Int, val issuingHash: SecureHash?) : ContractState {
override val participants: List<AbstractParty> = listOf()
}
interface TokenCommands : CommandData {
class Issue : TokenCommands
class SetIssuingHash : TokenCommands
class Transfer : TokenCommands
}
class TokenContract : Contract {
override fun verify(tx: LedgerTransaction) {
val tokenCommand = tx.commandsOfType<TokenCommands>().singleOrNull() ?: throw IllegalArgumentException()
val tokenInputs = tx.inputsOfType<TokenState>()
val tokenOutputs = tx.outputsOfType<TokenState>()
when (tokenCommand.value) {
is TokenCommands.Issue -> {
if (tokenInputs.isNotEmpty()) throw IllegalArgumentException()
val tokenOutput = tokenOutputs.singleOrNull() ?: throw IllegalArgumentException()
if (tokenOutput.issuingHash != null) throw IllegalArgumentException()
}
is TokenCommands.SetIssuingHash -> {
val tokenInput = tokenInputs.singleOrNull() ?: throw IllegalArgumentException()
val tokenOutput = tokenOutputs.singleOrNull() ?: throw IllegalArgumentException()
if (tokenInput.issuingHash != null) throw IllegalArgumentException()
if (tokenOutput.issuingHash != tx.inputs[0].ref.txhash) throw IllegalArgumentException()
if (tokenOutput.copy(issuingHash = null) != tokenInput) throw IllegalArgumentException()
}
// Extend this logic to allow tokens with different `issuingHash`s to be used in the same transaction.
is TokenCommands.Transfer -> {
if ((tokenInputs + tokenOutputs).any { it.issuingHash == null }) throw IllegalArgumentException()
if ((tokenInputs + tokenOutputs).map { it.issuingHash }.toSet().size != 1) throw IllegalArgumentException()
if (tokenInputs.sumBy { it.amount } != tokenOutputs.sumBy { it.amount }) throw IllegalArgumentException()
}
}
}
}
Upvotes: 0