Security of hash time locked contracts

Hi,
Once a txn submitted to the network with the correct hash preimage, the bad guys could intercept this transaction having no encryption whatsoever, and could submit THEIR transaction, too, with the correct hash preimage taken from the originally submitted txn, and divert the funds to their account.

So, it seems to me that hash time lock contracts are fundamentally insecure.
What is the opinion of the community?

Hello @Maugli this is a great question.

Let’s look at the Algorand reference HTLC TEAL contract, specifically where TMPL_RCV is defined in the Hash Lock portion of the code:

Each time this template is used, the TMPL_OWN will define fields including TMPL_RCV and a unique TMPL_HASHIMG which will result in a distinct contract account when compiled by goal clerk compile htlc.teal. Thus the funds are secure within the HTLC and may only exit to the defined TMPL_RCV account with the TMPL_HASHIMG (Hash Lock) -OR- to TMPL_OWN after TMPL_TIMEOUT (Time Lock).

@ryanRFox
Thank you.
I rewrote my faucet contract to make additional checks…


$ cat teal_badge_v1.2.teal
// Make faucet contract account
// Params:
// TMPL_FEE, uint8, fee in microalgos, format: integer
// TMPL_MAX_OUTPUT, uint8, max. faucet output in microalgos, format: integer
// TMPL_ROUNDS, uint8, minimal rounds between to call, format: integer
// TMPL_LEASE, [32]bytes, lease bytes, format: base64
// TMPL_HASH, [32]bytes, sha256(preimage), format: base64
// TMPL_OWN, addr,  address of faucet owner, format: addr

// V1, 16-Dec-2019
// V1.1, 17-Dec-2019, added check of txn.Fee
// V1.2, 20-Dec-2019, added various checks and params desc

// Guard agains malicious user specifying huge fee
// txn.Fee <= 0.001 Algo ?
txn Fee
int 1000  // TMPL_FEE
<=

// Guard against malicious user specifying txn.CloseRemainderTo
txn CloseRemainderTo
global ZeroAddress
==

&&

// Limit faucet output to max. 0.1 Algo
// txn.Amount <= 0.1 Algo ?
txn Amount
int 100000  // TMPL_MAX_OUTPUT
<=

&&

// Limit too frequent use of faucet.
// Faucet can be used again only after at least 68 round, i.e. 5 minutes
// txn.LastValid - txn.FirstValid >= floor(300/4.4) ?  
txn LastValid 
txn FirstValid
-
int 68  // TMPL_ROUNDS
>=

&&

// txn.Lease is given and valid? 
txn Lease
byte base64 XlTiv+2SHsY9dttPpSXC+mj4m0rihuavdMKhYjsXDdU=  // TMPL_LEASE
==

&&

// OR

// owner may empty contract account, using a hash preimage
arg_0
sha256
byte base64 PF62+e9yBNw+5oJhaRZ1t7pLtUYA8Bm4sdeJukxzoJs=  // TMPL_HASH
==

// but check txn.Receiver, so bad guys intercepting the 
// hash preimage can't give their address.
txn Receiver
addr JW6L2ZCQT3UIQH5AFM3CVW3C7M3QFYHXA3EU4WHREOATRWMXP6MBFNKILM  // TMPL_OWN
==
&&

// add txn.CloseRemainderTo, to guard against bad guys
txn CloseRemainderTo
addr JW6L2ZCQT3UIQH5AFM3CVW3C7M3QFYHXA3EU4WHREOATRWMXP6MBFNKILM  // TMPL_OWN
==
&&

// combine
end:
||


My problems:

  • faucet seems to work more often than every 68 rounds, please see on Testnet txn
    Q3FIVIAJO7KHMOO27KHTPSOZEXXSNY4VZNNKAF23J6N7WIJQKNOA
    and
    LD4BX3T7OCMM2KBPRW4PVWLE36FWETKPKDX4KWW3M6TXFCCHRGIA

So lease seems to be broken.

And my very last problem is, that I was unable to get back funds. When I issued the txn with hash preimage, in dryrun mode it passed, but in reality it began looping:


$ ./goal clerk send -a 1  --from-program teal_badge_v1.2.teal -t JW6L2ZCQT3UIQH5AFM3CVW3C7M3QFYHXA3EU4WHREOATRWMXP6MBFNKILM -c JW6L2ZCQT3UIQH5AFM3CVW3C7M3QFYHXA3EU4WHREOATRWMXP6MBFNKILM  -w w2 --argb64 /nW/7bqA5zLw3TZ/Qq8IiU/G6hxDGsKCfHqkO8LwhkA= -x XlTiv+2SHsY9dttPpSXC+mj4m0rihuavdMKhYjsXDdU= -o tx1.txn -d data 

$ ./goal clerk dryrun -t tx1.txn -d data
tx[0] cost=42 trace:
  1 intcblock => 
  9 bytecblock => 
110 txn => 1000 0x3e8
112 intc_0 => 1000 0x3e8
113 <= => 1 0x1
114 txn => 4dbcbd64509ee8881fa02b362adb62fb3702e0f706c94e58f1238138d9977f98
116 global => 0000000000000000000000000000000000000000000000000000000000000000
118 == => 0 0x0
119 && => 0 0x0
120 txn => 1 0x1
122 intc_1 => 100000 0x186a0
123 <= => 1 0x1
124 && => 0 0x0
125 txn => 3779893 0x39ad35
127 txn => 3778893 0x39a94d
129 - => 1000 0x3e8
130 intc_2 => 68 0x44
131 >= => 1 0x1
132 && => 0 0x0
133 txn => 5e54e2bfed921ec63d76db4fa525c2fa68f89b4ae286e6af74c2a1623b170dd5
135 bytec_0 => 5e54e2bfed921ec63d76db4fa525c2fa68f89b4ae286e6af74c2a1623b170dd5
136 == => 1 0x1
137 && => 0 0x0
138 arg_0 => fe75bfedba80e732f0dd367f42af08894fc6ea1c431ac2827c7aa43bc2f08640
139 sha256 => 3c5eb6f9ef7204dc3ee68261691675b7ba4bb54600f019b8b1d789ba4c73a09b
140 bytec_1 => 3c5eb6f9ef7204dc3ee68261691675b7ba4bb54600f019b8b1d789ba4c73a09b
141 == => 1 0x1
142 txn => 4dbcbd64509ee8881fa02b362adb62fb3702e0f706c94e58f1238138d9977f98
144 bytec_2 => 4dbcbd64509ee8881fa02b362adb62fb3702e0f706c94e58f1238138d9977f98
145 == => 1 0x1
146 && => 1 0x1
147 txn => 4dbcbd64509ee8881fa02b362adb62fb3702e0f706c94e58f1238138d9977f98
149 bytec_2 => 4dbcbd64509ee8881fa02b362adb62fb3702e0f706c94e58f1238138d9977f98
150 == => 1 0x1
151 && => 1 0x1
152 || => 1 0x1

 - pass -
$ ./goal clerk rawsend  -f tx1.txn -d data
Raw transaction ID S5NQGUIOFSO6VJXAG4YSEI3PZHBKS6OATWLIIVAUGEKIUO4HPPBQ issued
Transaction S5NQGUIOFSO6VJXAG4YSEI3PZHBKS6OATWLIIVAUGEKIUO4HPPBQ still pending as of round 3778892

---snipped

Transaction S5NQGUIOFSO6VJXAG4YSEI3PZHBKS6OATWLIIVAUGEKIUO4HPPBQ still pending as of round 3779020

---snipped

HELP, PLEASE!

Hi Maugli, txn lease does not work when you equate it with >= or =<. It has to be an equal sign “==” condition. At least that is what I see it working. Your code can be re written as

txn TypeEnum
int 1
==
txn Fee
int 1000
<=
&&
txn FirstValid
int 100 //TMPL_PERIOD - I picked 100 so that this condition passes every 100 rounds
%
int 0
==
&&
txn LastValid
int 100 //TMPL_DURATION - I picked 100 so that this condition passes every 100 rounds
txn FirstValid
+
==
&&
txn Lease
byte base64 AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ=
==
&&
txn Amount
int 1000
==
&&

Here I made the round duration to 100 so that the first condition pass instead of 68. Try out and let me know.