Contracts

In the transactions overview guide we learned how to create and spend outputs that have been locked with a public key meaning they only require a valid signature to spend. Through Cryptokernel’s integration with Lua it is possible to specify practically arbitrary rules that define how an output can be spent called contracts or scripts. By orchestrating multiple contract scripts, transaction constructions and off-chain helper code together it is possible to create more complex applications that are sometimes referred to as dApps (decentralised applications). This allows you to embed much richer semantics into an existing Cryptokernel network without having to alter the core protocol.

Hello world contract

For our first contract we are going to require that the spender provides the pre-image to the SHA-256 hash 03675ac53ff9cd1535ccc7dfcdfa2c458c5218371f418dc136f2d19ac1fbe8a5 (which is the string “Hello, World”) in the data field of the spending input. You should already have a Python setup with RPC connection to your ckd instance which is covered in the transactions guide.

A contract in Cryptokernel consists of a Lua script which is evaluated at the time of transaction verification. If the script returns true then the spend is considered valid. All other outcomes including returning any other value or reaching execution limits causes the spend to be invalid. The simplest contract imagineable then is:

return false

This contract would make the output permanently unspendable as the Lua script never returns true. Equally a script that returns any value other than true such as "hello" or 1 would be unspendable.

In practice it is useful to access the fields of the transaction being verified as well as other aspects of the blockchain state. In our case we need to access the data field of the input that is trying to spend the output our contract enforces rules on in order to check the pre-image the spender has provided. The Lua environment provides a variable called thisInput which is a table representing the input that is currently being verified. Additionally we need the sha256 function which is also provided in the Lua environment. The hello world contract would therefore look like this:

return sha256(thisInput["data"]["preimage"]) == "03675ac53ff9cd1535ccc7dfcdfa2c458c5218371f418dc136f2d19ac1fbe8a5"

The code retrieves the value from the preimage field of the data field of the input, hashes it and compares it to the expected hash. If the actual hash and expected hash are equal then the contract returns true and the spend is considered valid.

Testing the contract on-chain

When included in an output a Lua script is stored as compressed bytecode rather than raw Lua source code. ckd's RPC interface includes a compilecontract command that handles this conversion for us. Once we have the bytecode for the contract the next step is to include it in an output for us to spend in a later transaction by providing the pre-image to the hash we locked the funds with. We will need to use the calculateoutputid RPC to calculate the new output’s ID so we can refer to it later when we spend it in an input. By augmenting the code from the transactions guide to include our contract in the new output instead of a public key we end up with the following code:

...
contractCode = "return sha256(thisInput[\"data\"][\"preimage\"]) == \"03675ac53ff9cd1535ccc7dfcdfa2c458c5218371f418dc136f2d19ac1fbe8a5\""

# compile the contract source code
compiledCode = request("compilecontract", {"code": contractCode})["result"]

# retrieve an output to spend
toSpend = request("listunspentoutputs", {"password": walletpassword, "account": walletaccount})["result"]["outputs"][0]

# input wrapper spending output
input = {"outputId": toSpend["id"]}

# new output for spent funds (minus fee), notice now includes a contract
# rather than a publicKey
newOutput = {"value": toSpend["value"] - fee,
             "nonce": 0,
             "data": {"contract": compiledCode}}

# find out the new output's ID so we can refer to it when
# we try to spend it later
outputId = request("calculateoutputid", {"output": newOutput})["result"]
print(outputId)

# the unsigned transaction
transaction = {
    "inputs": [input], 
    "outputs": [newOutput], 
    "timestamp": int(time.time()),
}

# have ckd sign the unsigned transaction for us
signed = request("signtransaction", {"transaction": transaction, "password": walletpassword})["result"]
print(json.dumps(signed, sort_keys=True, indent=4))

# broadcast the signed transaction on the network
success = request("sendrawtransaction", {"transaction": signed})["result"]
print(success)

The code is almost identical to the code in the transactions guide except for the additional RPC calls to compile the contract and calculate the new output’s ID and the replacement of the publicKey field in the new output with a contract field. The presence of a contract field in an output takes precedence over a publicKey, so if both are present in an output the contract is executed to check for spend validity rather than the signature for the public key being checked.

The output from running the script should look similar to this:

~$ python3 run.py
df273ca616af3e28bfdcdf769c284a2951d5c73fa0bdee71aab5a1cf4d952c89
{
    "inputs": [
        {
            "data": {
                "signature": "MEUCIDISim15zQtivKV+KMID8XKW8rJ5CUopDZ348Y77XhHKAiEA6186OrVmFDLuITMoJSd3g0a+2OV4tGqbGgZLz6z0dww="
            },
            "outputId": "180f35bb70a3a590e64370c3f8b955088e2fba3980179ae614539000b3594649"
        }
    ],
    "outputs": [
        {
            "data": {
                "contract": "BCJNGGBAgv8AAAD2BRtMdWFTABmTDQoaCgQIBAgIeFYAAQD1aCh3QAFzcmV0dXJuIHNoYTI1Nih0aGlzSW5wdXRbImRhdGEiXVsicHJlaW1hZ2UiXSkgPT0gIjAzNjc1YWM1M2ZmOWNkMTUzNWNjYzdkZmNkZmEyYzQ1OGM1MjE4MzcxZjQxOGRjMTM2ZjJkMTlhYzFmYmU4YTUigADyKQECCwAAAAYAQABGQEAAR4DAAEfAwAAkgAABXwBBAB4AAIADQAAAAwCAACYAAAEmAIAABQAAAAQHrAAlBAqtACAEBa0AJAQJqwAvFEGlAC0QAZ8AAaUAEwsKAA8EABUB2QCAAAAABV9FTlYAAAAA"
            },
            "nonce": 0,
            "value": 7633474894
        }
    ],
    "timestamp": 1534885886
}
True

Once the transaction has been included in a block we can spend our newly created output by referencing its ID that was printed as part of the previous code block and include the pre-image Hello, World in the data field of the input.

e.g.

...
# spend the output we created. Fill in the outputId
# with the actual ID from the previous step
input = {
    "outputId": "~new_output_id_from_last_step~",
    "data": {
        "preimage": "Hello, World",
    },
}

# any arbitrary output to send the funds to. 
# Replace the value with the desired amount that's
# less than the input value
newOutput = {
    "value": ~value_of_output_minus_fee~,
    "nonce": 0,
    "data": {"publicKey": publicKey},
}

transaction = {
    "inputs": [input], 
    "outputs": [newOutput], 
    "timestamp": int(time.time()),
}
print(json.dumps(transaction, sort_keys=True, indent=4))

# broadcast the transaction on the network
success = request("sendrawtransaction", {"transaction": transaction})["result"]
print(success)

Note that we did not need to sign the transaction by calling signtransaction because the output we are spending is not locked with a public key. The output of the above code should be of the following form:

~$ python3 run.py
{
    "inputs": [
        {
            "data": {
                "preimage": "Hello, World"
            },
            "outputId": "df273ca616af3e28bfdcdf769c284a2951d5c73fa0bdee71aab5a1cf4d952c89"
        }
    ],
    "outputs": [
        {
            "data": {
                "publicKey": "BNhtisPytzKtucZww4lA5mxgZG1qrd4vOBx8DJ7niFX8it3Jub7V2WG7Tc7WAg/SuTDYA7OJ624kfkb83Yg3hT8="
            },
            "nonce": 0,
            "value": 7632996472
        }
    ],
    "timestamp": 1534886485
}
True

Replicating pay-to-public-key

In this section we will replicate the semantics of a regular pay-to-public-key output using a contract. This is useful to demonstrate the cryptography functions of the scripting language and the algorithm to safely verify signatures. Signature verification is an important building block for more advanced contracts as it allows you to authenticate users.

In our script we must first retrieve the output being spent so we can read the public key defined there. Then we create an instance of the Crypto class accessible from the Lua environment, set the public key of the Crypto instance to the one defined in the output being spent and then verify the signature provided in the spending input. The payload being signed is the output ID being spent concatenated with the merkle root of the set of new output IDs in the transaction. A commitment to the new output IDs is required to ensure the signature cannot be replayed for a transaction with a different set of outputs that you have not approved.

The resulting contract source code is as follows:

-- retrieve the output this input is spending
local output = Blockchain.getOutput(thisInput["outputId"])

-- create a new instance of the Crypto class
local crypto = Crypto.new()

-- set the public key of the class instance to the public key
-- specified in the output being spent
crypto:setPublicKey(output["data"]["publicKey"])

-- return true if the given signature is valid
return crypto:verify(thisInput["outputId"] .. outputSetId, thisInput["data"]["signature"])

Using the Python code from the previous section and changing the contractCode variable to the contract source code above you can create an output with this contract which can be spent as if it were a regular pay-to-public-key output without a contract.

m-of-n multisig

Multi-signature outputs require a subset of multiple entities to approve a transaction for it to be considered valid. “m-of-n” means there are up to n signers and at least m of them are required for a valid spend. A 2-of-3 multisig output then requires two out of three potential signers to sign. Since Lua supports loops the contract code is mostly a repitition of the pay-to-public-key contract;

-- retrieve the output this input is spending
local output = Blockchain.getOutput(thisInput["outputId"])

-- create a new instance of the Crypto class
local crypto = Crypto.new()

local nSigs = 0

-- iterate over each possible signer to check if they signed
for i, pk in ipairs(output["data"]["publicKeys"]) do 

    -- is there a signature in the input signatures table for this public key?
    local signature = thisInput["data"]["signatures"][tostring(i)]
    if signature ~= nil then 
        crypto:setPublicKey(pk)
        if crypto:verify(thisInput["outputId"] .. outputSetId, signature) ~= true then
            return "Invalid signature"
        end

        nSigs = nSigs + 1
    end
end

return nSigs >= output["data"]["m"]

As well as the compiled contract, the multisig output we create needs two additional fields: an array of public keys which enumerates the set of possible signers (called publicKeys in the above script) and a number to specify the amount of signatures required for a valid spend (called m in the above script). In order to spend the output, the input must contain a signatures map in its data field where they keys are the index of the public key being signed for in the publicKeys array of the output and the values are the signatures themselves.

Example 2-of-3 multisig output:

{
    "data": {
        "m": 2,
        "publicKeys": ["BEzg0/o/kQ7vRSuEKi9bN+69wEEhRulchB5dDp8qeDr0x3qZVTerPQXpb1J8v0rp4kpccx2fY/UICziijjV31XU=",
                       "BC07/bUj/6IicuGCjgkg+QW8Ny/AXn4xqEkBguG21h9vkjpJsfoNM1qNlK/uHxZ3cJWpt3TSjWmcFNO5uwD85bU=",
                       "BBSMn/8D0KXwRvJNEL+rxMXX54vaN/J1pv/Hs/klMIJeyJ9Z7/LkPM1WkJ6tXO7sSDzbg0fksLGJKDAopCzzU4Q="],
        "contract": "BCJNGGBAgvkCAAD2BRtMdWFTABmTDQoaCgQIBAgIeFYAAQByKHdAAf+4AQ0A8Q9sb2NhbCBvdXRwdXQgPSBCbG9ja2NoYWluLmdldE8XAMIodGhpc0lucHV0WyIpAGJJZCJdKSA7AKFjcnlwdG8gPSBDCQBULm5ldygcAPIPblNpZ3MgPSAwIGZvciBpLCBwayBpbiBpcGFpcnMoTADwBVsiZGF0YSJdWyJwdWJsaWNLZXlzXgAjZG9FAMdzaWduYXR1cmUgPSCKAAQ2AAUfAPcEcyJdW3Rvc3RyaW5nKGkpXSBpZjwAs349IG5pbCB0aGVuswBUOnNldFBzAEAocGspMQADGwBvdmVyaWZ5+QADMyAuLjABZlNldElkLGUAEClmAEJ0cnVlZwD2AHJldHVybiAiSW52YWxpZCgAVSIgZW5kHQECCAAxKyAxFgAABAADOQACGQA8Pj0gKQE1bSJdzQH2vQENMAAAAAYAQAAHQEAARoBAAEfAwAAkgAABRgBBAEdAwQBkgIAAgYABAMbAQQAHAUIAB0FCAuQAAQEewAWABoJAAAcCQgQHgkIERsJCAIACAANkggABB0ICBF8AQwQegAOATELDAMACgANkQoABTILDAMaCQADHwsAFBsNDAN0CgwUAAwAEZIIAAl8AxAQeQACAQUIEAGYCAAGNgEQB6YAAAGpB+X/HAEIAx8DEAWGAgAEeAACAw0AAAMMAgADmAAABJgCAABQAAAAEC30CJQQKfgIlBAqGASQECYYBIgQHbgJkBARuZXcTDAEiBAdaAiAEBSoBJgQLUQImBAsnAiQECSYCOAAEDQQCIgQH9wEnBAzfAU0BAQQSwQEkEwFyACACbQsAAg8AEzAKAA8EAKlSCQAAAAf/ABMF0wASBxQDEwgPABEGlwITCQ4AIBAo2gOwZ2VuZXJhdG9yKQ1gBEEAAAAMGABVc3RhdGUUABEOFAB1Y29udHJvbBYA1AJpDgAAACYAAAADcGsLABUKawETFRIAkAEAAAAFX0VOVgAAAAA="
    },
    "nonce": 0,
    "value": 500000
}

Example generation code:

...
contractCode = "local output = Blockchain.getOutput(thisInput[\"outputId\"]) local crypto = Crypto.new() local nSigs = 0 for i, pk in ipairs(output[\"data\"][\"publicKeys\"]) do local signature = thisInput[\"data\"][\"signatures\"][tostring(i)] if signature ~= nil then crypto:setPublicKey(pk) if crypto:verify(thisInput[\"outputId\"] .. outputSetId, signature) ~= true then return \"Invalid signature\" end nSigs = nSigs + 1 end end return nSigs >= output[\"data\"][\"m\"]"

# set of public keys that can sign
multisigKeys = ["BBSMn/8D0KXwRvJNEL+rxMXX54vaN/J1pv/Hs/klMIJeyJ9Z7/LkPM1WkJ6tXO7sSDzbg0fksLGJKDAopCzzU4Q=",
                "BC07/bUj/6IicuGCjgkg+QW8Ny/AXn4xqEkBguG21h9vkjpJsfoNM1qNlK/uHxZ3cJWpt3TSjWmcFNO5uwD85bU=",
                "BEzg0/o/kQ7vRSuEKi9bN+69wEEhRulchB5dDp8qeDr0x3qZVTerPQXpb1J8v0rp4kpccx2fY/UICziijjV31XU="]
# the number of keys required to sign for a valid spend
m = 2

# compile the contract source code
compiledCode = request("compilecontract", {"code": contractCode})["result"]
#print(compiledCode)

# retrieve an output to spend
toSpend = request("listunspentoutputs", {"password": walletpassword, "account": walletaccount})["result"]["outputs"][0]

# input wrapper spending output
input = {"outputId": toSpend["id"]}

# new output for spent funds (minus fee), notice now includes a contract
# rather than a publicKey
newOutput = {"value": toSpend["value"] - fee,
             "nonce": 0,
             "data": {"contract": compiledCode,
                      "publicKeys": multisigKeys,
                       "m": m}}

# calculate and print output ID so we can reference it later
outputId = request("calculateoutputid", {"output": newOutput})["result"]
print(outputId)

# the unsigned transaction
transaction = {
    "inputs": [input], 
    "outputs": [newOutput], 
    "timestamp": int(time.time()),
}

# have ckd sign the unsigned transaction for us
signed = request("signtransaction", {"transaction": transaction, "password": walletpassword})["result"]
print(json.dumps(signed, sort_keys=True, indent=4))

# broadcast the signed transaction on the network
success = request("sendrawtransaction", {"transaction": signed})["result"]
print(success)

Signing the output

Unfortunately the signtransaction RPC that we’ve used in previous sections does not know how to interpret our custom output format so we have to sign the output manually in order to spend it. To sign the output we will be using the getoutputsetid RPC which calculates the merkle root of a set of outputs which we use to create the message to sign, and the signmessage RPC which signs a given message with a given public key that the ckd wallet has the private key for.

Example signing code:

...
# the output ID of the multisig output created in the last step
outputId = "b352a38d2846a3aed7d0a38630a175e64c07ef9a51d4ebbe0a17b45307e734f2"

# any arbitrary output to send the funds to. 
# Replace the value with the desired amount that's
# less than the input value
newOutput = {
    "value": ~value_of_output_minus_fee~,
    "nonce": 0,
    "data": {"publicKey": ~destination_pubkey~},
}

# caclulate the output set merkle root to sign the multisig input
outputSetId = request("getoutputsetid", {"outputs": [newOutput]})["result"]

# spend the output we created. Fill in the outputId
# with the actual ID from the previous step
input = {
    "outputId": outputId,
    "data": {
        "signatures": {} # empty signatures map
    },
}

# generate m signatures (just the first m in the multisigKeys array as an example)
for i in range(m):
    # we sign the output ID being spent concatenated with the new output set merkle root 
    signature = request("signmessage", {"message": outputId + outputSetId, 
                                        "publickey": multisigKeys[i], 
                                        "password": walletpassword})["result"]
    
    # add the signature to the signatures map in the input
    # note: Lua arrays start at 1 so we must add 1 to the 
    # index to reference the correct public key
    input["data"]["signatures"][str(i+1)] = signature

transaction = {
    "inputs": [input], 
    "outputs": [newOutput], 
    "timestamp": int(time.time()),
}
print(json.dumps(transaction, sort_keys=True, indent=4))

# broadcast the signed transaction on the network
success = request("sendrawtransaction", {"transaction": transaction})["result"]
print(success)

Example signed multisig transaction:

{
    "inputs": [
        {
            "data": {
                "signatures": {
                    "1": "MEQCIDb9kqeUoKDXN3dPBf913LLuf8t8a+psb7Zv8FFoQR7vAiASGNVGvLjQTnb9MoQoYR6V0jANNHyO8DUABwDTSSO8Rw==",
                    "2": "MEUCIQDAHhLEXvyHiQ/TtyMTvpA91vXAtRhFbnowC6RwAhOzCAIgKhq7zu9EK7WbtmCXK4SgFVN1BQHCZ3nwgr0BW/bp4cw="
                }
            },
            "outputId": "b352a38d2846a3aed7d0a38630a175e64c07ef9a51d4ebbe0a17b45307e734f2"
        }
    ],
    "outputs": [
        {
            "data": {
                "publicKey": "BNhtisPytzKtucZww4lA5mxgZG1qrd4vOBx8DJ7niFX8it3Jub7V2WG7Tc7WAg/SuTDYA7OJ624kfkb83Yg3hT8="
            },
            "nonce": 0,
            "value": 7632906364
        }
    ],
    "timestamp": 1535128022
}

Time locked outputs

In systems such as a Lightning Network and for contracts like futures, the ability to prevent the spending of an output until a given block height is required. In an options contract it might also be useful to prevent spending an output after a certain block height to create a limited time window in which the option can be exercised. There are two types of time lock: a relative lock and an absolute lock. The former locks an output based on the difference between the current block height and the block height the output being spent was included in. The latter only takes into account the current block height. The method of time locking in Cryptokernel involves retrieving the current blockchain tip (and optionally the block the output being spent was included in) and comparing its height to the rules defined in the contract.

Example of an absolute time lock:

-- retrieve the current tip block
local tipBlock = Blockchain.getBlock("tip")

-- spendable once the block height is greater than 500
return tipBlock["height"] > 500

Example of a relative time lock:

-- retrieve the current tip block
local tipBlock = Blockchain.getBlock("tip")

-- retrieve the output being spent
local thisOutput = Blockchain.getOutput(thisInput["outputId"])

-- retrieve the transaction the output was created in 
local creationTx = Blockchain.getTransaction(thisOutput["creationTx"])

-- retrieve the block the transaction was included in
local confirmingBlock = Blockchain.getBlock(creationTx["confirmingBlock"])

-- spendable once more than 500 blocks have elapsed since the output was created
return tipBlock["height"] - confirmingBlock["height"] > 500

Covenants

With Cryptokernel contracts it is possible define rules about how coins can be spent rather than just when they can be spent. Such rules are called covenants. A covenant can define arbitrary rules such as which public keys coins are allowed to be sent to, the set of contracts any new outputs created from the spent outputs must contain or the value of the outputs. An example covenant forces the spender of the output to include the same contract in any subsequent outputs.

Forcing contract propagation

The script below gives an example of forced contract propagation. It enforces that all new outputs in a transaction that spends the output with the contract can subsequently be spent by anyone and that those new outputs re-enforce the same rule.

-- retrieve the output this input is spending
local thisOutput = Blockchain.getOutput(thisInput["outputId"])

-- for each of the new outputs in the transaction
for i, output in ipairs(thisTransaction["outputs"]) do
    -- check the contract of the new output is the same as this contract
    if output["data"]["contract"] ~= thisOutput["data"]["contract"] then
        -- if not, the spend is invalid
        return "Contract not propagated"
    end
end

-- anyone can spend
return true

Pre-defining spend destinations

Your contract might need to lock funds so they can only be sent to a set of pre-defined destination public keys. This is useful when you want to give control over when and how many coin are sent but want to control what the coins are spent on. This might be useful for guardians issuing pocket money to their children, the provision of welfare or foreign aid or as a component of a larger contract.

e.g.

-- Get the output associated with the input we're verifying
local thisOutput = Blockchain.getOutput(thisInput["outputId"])

-- Check the public key is valid
local crypto = Crypto.new()
if not crypto:setPublicKey(output["data"]["publicKey"]) then
    return "Invalid public key"
end

-- Verify the signature is correct 
if not crypto:verify(thisInput["outputId"] .. outputSetId, thisInput["data"]["signature"]) then
    return "Invalid signature"
end

-- Ensure each of the outputs are sending to an approved pubkey
for _, out in ipairs(thisTransaction["outputs"]) do
    if thisOutput["data"]["destinations"][out["data"]["publicKey"]] ~= true then
        return false
    end

    -- check there is no contract field in the output that would bypass the public key check
    if out["data"]["contract"] ~= nil then
        return false
    end
end

return true

The sender of the funds would include in the data field of the output the above contract, the public key of the recipient in the publicKey field and the set of legal destination public keys as a map in a destinations field. To spend the funds the receiver only needs to sign the output as if it were a regular pay-to-public-key output and the spend will be valid as long as the new outputs send to an approved public key.

Example output:

{
    "data": {
        "contract": "BCJNGGBAghgDAAD2BRtMdWFTABmTDQoaCgQIBAgIeFYAAQByKHdAAf8RAg0A8hJsb2NhbCB0aGlzT3V0cHV0ID0gQmxvY2tjaGFpbi5nZXQXABAoIgCBSW5wdXRbIm8SAGJJZCJdKSA/AKFjcnlwdG8gPSBDCQDTLm5ldygpIGlmIG5vdB0A4jpzZXRQdWJsaWNLZXkoQwC0WyJkYXRhIl1bInAaAABUAPIGdGhlbiByZXR1cm4gIkludmFsaWQgIgCbIGtleSIgZW5kXQBvdmVyaWZ5pQADQiAuLiAOAHdTZXRJZCwgJgAEgACfc2lnbmF0dXJlgAAGBiIAAX8AYGZvciBfLFgAoSBpbiBpcGFpcnN/ALRUcmFuc2FjdGlvboUAEHNVACBkb7MABmgBBvwAcGRlc3RpbmEyAF9zIl1bbxgBBJldIH49IHRydWWgAFRmYWxzZREBCTsAkWNvbnRyYWN0IjkAP25pbDgABAAEAAMVAABeAAUmAvZfAQk3AAAABgBAAAdAQABGgEAAR8DAACSAAAFGAEEAR0DBAGSAgACMgMEABsFBAAcBQgIHQUICpICAAaJAAAAeQACAgYACAKYAAAGMwMIABoFAAAfBQAJGAUMAHUEBAkaBQABHAcICR0HDAqSAAAI0ABEDNADwHYbAQwDGAEQAx0DEAaQAAQEegAOAxwFCAMeBxAMHAkIDB0JCBMcBggNfwMQDOAD5BMMBAADmAQABxwFCA8cBxQNfQMUYALGpgAAAKoH7f4MAgGAApiYAgAAWAAAABAvuAiUECu8CJQQKJQIkBAlLAiIEB98CeAQEbmV3BA3RAiIEByUAIAQFfwElBAq4AS4EE8ECIgQHrwInBAyXAiUECmICLQQSdQIiBAdjAisEEGQCIwQIZAIoBA1IAkQBAQQJAQJBAAEAAAQAMwAANwoADwQAxVYHAAAAC2QDEwXzABIHJgQTCA8AIBAovgPxBGdlbmVyYXRvcikiAAAANAAAAAwYAFVzdGF0ZRQAEQ4UAAFLASVvbBYAcAJfIwAAADJAAjRvdXQMAJABAAAABV9FTlYAAAAA",
        "destinations": {
            "BBSMn/8D0KXwRvJNEL+rxMXX54vaN/J1pv/Hs/klMIJeyJ9Z7/LkPM1WkJ6tXO7sSDzbg0fksLGJKDAopCzzU4Q=": true,
            "BC07/bUj/6IicuGCjgkg+QW8Ny/AXn4xqEkBguG21h9vkjpJsfoNM1qNlK/uHxZ3cJWpt3TSjWmcFNO5uwD85bU=": true,
            "BEzg0/o/kQ7vRSuEKi9bN+69wEEhRulchB5dDp8qeDr0x3qZVTerPQXpb1J8v0rp4kpccx2fY/UICziijjV31XU=": true
        },
        "publicKey": "BNhtisPytzKtucZww4lA5mxgZG1qrd4vOBx8DJ7niFX8it3Jub7V2WG7Tc7WAg/SuTDYA7OJ624kfkb83Yg3hT8="
    },
    "nonce": 0,
    "value": 7632996472
}