OP_RETURN vouts are often used to attach arbitrary data to a transaction. If this method of adding data is used in an Antara module, there should be a validation rule that says the OP_RETURN vout's nValue must be 0. This is generally a safe rule to apply to all modules unless there is some need to burn coins in this type of vout. If this need does exist, code it specific to a funcid and apply this rule to all other funcids in the module. If this rule is not accounted for, any funcid that has loose validation of vins may allow a bad actor to burn coins that do not belong to them.

These OP_RETURN vouts often have data critical to the Antara module's functionality. The validation code must not assume there is not multiple OP_RETURN vouts in a transaction. Having multiple OP_RETURN vouts in a transaction will result in the transaction being "non-standard" and being rejected from miners' mempools, but this does not prevent a miner from tweaking their daemon to include transactions with multiple OP_RETURN vouts in a block.

It is generally safe to hardcode validation to look at vout[-1] for the OP_RETURN vout, but if this is done, it is critically important to be consistent with this throughout the validation code. For example, if a transaction has the following vout structure, vout 2 must never be considered in the validation.

vouts:

[0] <pubkey> OP_CHECKSIG - normal p2pk vout
[1] <CC scriptPubKey> OP_CRYPTOCONDITION - arbitrary CC vout
[2] OP_RETURN <malformed op_return data > - malformed data that could potential break the functionality of the CC
[3] OP_RETUTN <expected op_return data> - typical expected data for this type of transaction.

Another example,

[0] <pubkey> OP_CHECKSIG - normal p2pk vout
[1] <CC scriptPubKey> OP_CRYPTOCONDITION - arbitrary CC vout
[2] OP_RETUTN <expected op_return data> - typical expected data for this type of transaction.
[3] <CC scriptPubKey> OP_CRYPTOCONDITION - arbitrary CC vout potentially stealing funds or doing something else unexpected

In this case, if the validation code would simply iterate over the vouts until it found an OP_RETURN, it's possible it may not consider vout 3 in validation at all. If we assume OP_RETURN vout is always vout[-1], this transaction will fail validation as expected. Let me just reiterate, if validation assumes vout[-1] is an OP_RETURN vout anywhere in the validation, it must make this assumption everywhere in the validation.

 

There are often rpc commands that will return a list of transactions for a specific module. For example, the oracleslist command from the oracles module will list all oracles currently on the chain. The way these rpc commands typically work is that when the oraclescreate transaction is created, it will send a "marker vout" to a specific address. The oracleslist rpc command will then iterate over this address to find each oracle. If a similar rpc command is implemented, it must not assume there are valid inputs to this transaction. This is because a coinbase transaction can be created to follow the characteristics of an oraclescreate transaction. A coinbase's single input has a NULL scriptSig which if not accounted for could break the list rpc command and potentially result in the daemon crashing.

If a list command similar to oracleslist is implemented, it's generally best to track marker vouts of a transaction type that spends CC utxos. This will mitigate some spam attack vectors. By tracking only marker vouts of transactions that spent CC utxos, we know that this transaction has passed CC validation. If the list command tracks marker vouts of a transaction type that has no CC inputs, this means the transaction does not have to follow any validation. As a result of this, the transaction might add many vouts to the address the list command is iterating over.

For example,

A tokencreate transaction requires no CC inputs, therefore its vout structure can be anything. The marker vout for a typical tokencreate transaction looks like this:

{
 "value": 0.00010000,
 "valueZat": 10000,
 "n": 0,
 "scriptPubKey": {
   "asm": "a22c8020432de388aabcb6b4e3326351d1d815cee8be9a8d37b055cd1c0cf8782e5c50c08103120c008203000401 OP_CHECKCRYPTOCONDITION",
   "hex": "2ea22c8020432de388aabcb6b4e3326351d1d815cee8be9a8d37b055cd1c0cf8782e5c50c08103120c008203000401cc",
   "reqSigs": 1,
   "type": "cryptocondition",
   "addresses": [
     "RAMvUfoyURBRxAdVeTMHxn3giJZCFWeha2"
   ]
 }
},

This address, RAMvUfoyURBRxAdVeTMHxn3giJZCFWeha2, is the "TokensCCAddress". Now if someone intending to spam the tokenlist were to add many of these vouts to a single tokencreate transaction. This transaction will show multiple times in the tokenlist output.

{
 "value": 0.00000000,
 "valueSat": 0,
 "n": 0,
 "scriptPubKey": {
   "asm": "a22c8020432de388aabcb6b4e3326351d1d815cee8be9a8d37b055cd1c0cf8782e5c50c08103120c008203000401 OP_CHECKCRYPTOCONDITION",
   "hex": "2ea22c8020432de388aabcb6b4e3326351d1d815cee8be9a8d37b055cd1c0cf8782e5c50c08103120c008203000401cc",
   "reqSigs": 1,
   "type": "cryptocondition",
   "addresses": [
     "RAMvUfoyURBRxAdVeTMHxn3giJZCFWeha2"
   ]
 }
},
[...]
{
 "value": 0.00000000,
 "valueSat": 0,
 "n": 4,
 "scriptPubKey": {
   "asm": "a22c8020432de388aabcb6b4e3326351d1d815cee8be9a8d37b055cd1c0cf8782e5c50c08103120c008203000401 OP_CHECKCRYPTOCONDITION",
   "hex": "2ea22c8020432de388aabcb6b4e3326351d1d815cee8be9a8d37b055cd1c0cf8782e5c50c08103120c008203000401cc",
   "reqSigs": 1,
   "type": "cryptocondition",
   "addresses": [
     "RAMvUfoyURBRxAdVeTMHxn3giJZCFWeha2"
   ]
 }
},

 

komodo-cli -ac_name=DOC tokenlist
[
 "d4931b7153c8afc57341cd48e3ac8367e6c02a9e07d4abe5a6595dc5a0acba2a",
 "d4931b7153c8afc57341cd48e3ac8367e6c02a9e07d4abe5a6595dc5a0acba2a",
 "d4931b7153c8afc57341cd48e3ac8367e6c02a9e07d4abe5a6595dc5a0acba2a",
 "d4931b7153c8afc57341cd48e3ac8367e6c02a9e07d4abe5a6595dc5a0acba2a"
]

Notice how each of these vouts have an nValue of 0 coins. This means it cost the spammer no additional coins.

 

Some modules will will have funcids that allow a user to create and broadcast a transaction while having literally 0 coins in the wallet. Some examples of this are faucetget, marmarasettle and rewardsunlock. If a similar mechanism is used in a funcid of a module, this funcid should check that no additional data was added to this transaction. If this is not the case, it can allow someone to spam the blockchain at literally no cost.

Let's look at the faucetget transaction type as an example of this.

A typical faucetget transaction's vouts will look like this:

"vout": [
 {
   "value": 9.89990000,
   "valueZat": 989990000,
   "n": 0,
   "scriptPubKey": {
     "asm": "a22c8020e029c511da55523565835887e412e5a0c9b920801b007000df45e545f25028248103120c008203000401 OP_CHECKCRYPTOCONDITION",
     "hex": "2ea22c8020e029c511da55523565835887e412e5a0c9b920801b007000df45e545f25028248103120c008203000401cc",
     "reqSigs": 1,
     "type": "cryptocondition",
     "addresses": [
       "R9zHrofhRbub7ER77B7NrVch3A63R39GuC"
     ]
   }
 },
 {
   "value": 0.10000000,
   "valueZat": 10000000,
   "n": 1,
   "scriptPubKey": {
     "asm": "0332e1a4c25ef6f355c4e39425803f924cebf0982a40fd2ba794c52cf05e051399 OP_CHECKSIG",
     "hex": "210332e1a4c25ef6f355c4e39425803f924cebf0982a40fd2ba794c52cf05e051399ac",
     "reqSigs": 1,
     "type": "pubkey",
     "addresses": [
       "RKYmrjJAqdCn89g4j7YEQfzRUoubdbnQJd"
     ]
   }
 },
 {
   "value": 0.00000000,
   "valueZat": 0,
   "n": 2,
   "scriptPubKey": {
     "asm": "OP_RETURN e447ef568b0b",
     "hex": "6a06e447ef568b0b",
     "type": "nulldata"
   }
 }
]

However, there are no checks as to whether additional data was added to vout 0, so someone intent on spamming the chain could create a structure similar to this...

"vout": [
 {
   "value": 9.89990000,
   "valueZat": 989990000,
   "n": 0,
   "scriptPubKey": {
     "asm": "a22c8020e029c511da55523565835887e412e5a0c9b920801b007000df45e545f25028248103120c008203000401 OP_CHECKCRYPTOCONDITION 0401e40101066a04deadbeef OP_DROP",
     "hex": "2ea22c8020e029c511da55523565835887e412e5a0c9b920801b007000df45e545f25028248103120c008203000401cc0c0401e40101066a04deadbeef75",
     "reqSigs": 1,
     "type": "cryptocondition",
     "addresses": [
       "R9zHrofhRbub7ER77B7NrVch3A63R39GuC"
     ]
   }
 },
 {
   "value": 0.10000000,
   "valueZat": 10000000,
   "n": 1,
   "scriptPubKey": {
     "asm": "03b81a18cf73837c345322df98ef0fefa3be184d8af72e955ad36675b4653cdcb2 OP_CHECKSIG",
     "hex": "2103b81a18cf73837c345322df98ef0fefa3be184d8af72e955ad36675b4653cdcb2ac",
     "reqSigs": 1,
     "type": "pubkey",
     "addresses": [
       "RMBw8UVVUjRn3Hq1iwM8oB8LoiZ86XjTMv"
     ]
   }
 },
 {
   "value": 0.00000000,
   "valueZat": 0,
   "n": 2,
   "scriptPubKey": {
     "asm": "OP_RETURN e447f8ea8c0b",
     "hex": "6a06e447f8ea8c0b",
     "type": "nulldata"
   }
 }
],

Take note of 0401e40101066a04deadbeef OP_DROP, this is data that was added to the blockchain for free. In this case, this could have been up to about 10k of data. In other cases, it could result in up to 200k of data added to the blockchain for free.

This same concept applies to adding additional data via an OP_RETURN vout.


That's it for my first post. Any feedback or questions is greatly appreciated. 
 

Next up: "limiting vin/vout structure"