NUT-14: Hashed Timelock Contracts (HTLCs)¶
optional
depends on: NUT-10
This NUT describes the use of Hashed Timelock Contracts (HTLCs) which defines a spending condition based on NUT-10's well-known Secret format. Using HTLCs, ecash proofs can be locked to the hash of a preimage and a timelock. This enables use cases such as atomic swaps of ecash between users, and atomic coupling of an ecash spending condition to a Lightning HTLC.
HTLC spending conditions can be thought of as an extension of P2PK locks (see NUT-11) but with a hash lock in Secret.data and a new Proof.witness.preimage witness in the locked inputs to be spent. The preimage used to spend a locked Proof can be retrieved using NUT-07.
Caution: applications that rely on being able to retrieve the witness independently of the spender must check, via the mint’s info endpoint, that NUT-07 is supported.
Caution: if the mint does not support this type of spending condition, proofs may be treated as regular anyone-can-spend proofs. Applications must ensure that the mint supports a specific kind of spending condition by checking the mint’s info endpoint.
HTLC Locked Proof¶
NUT-10 Secret kind: HTLC
If for a Proof, Proof.secret is a Secret of kind HTLC, the hash of the lock is in Proof.secret.data. The preimage for unlocking the HTLC is in the witness Proof.witness.preimage. All additional tags from P2PK locks can also be used here, allowing a locktime, signature flag, and multisig (see NUT-11).
Here is a concrete example of a Secret of kind HTLC:
[
"HTLC",
{
"nonce": "da62796403af76c80cd6ce9153ed3746",
"data": "023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c",
"tags": [
[
"pubkeys",
"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904"
],
["locktime", "1689418329"],
[
"refund",
"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e"
]
]
}
]
The hash lock in Secret.data and the preimage in Proof.witness.preimage are treated as 32-byte data, encoded as 64-character hexadecimal strings.
See NUT-11 for a description of the signature scheme, the additional use of signature flags, and how to require signatures from multiple public keys (Multisig).
Spending HTLC Proofs¶
A Proof with a Secret of kind HTLC can be spent in two ways.
Receiver Pathway (hash lock)¶
The receiver(s) listed in the pubkeys tag can spend the proof by providing BOTH of the following:
- The preimage to
Secret.datain theProof.witness - Signature(s) as per the NUT-11 rules for Locktime MultiSig.
This pathway is ALWAYS available to the receivers, as possession of the preimage confirms performance of the Sender's wishes.
Sender Pathway (timelocked refund)¶
The sender(s) listed in the refund tag can spend the proof once the locktime lock has "expired" by providing signature(s) as per the NUT-11 rules for Refund MultiSig.
NOTE: As per the NUT-11 rules, if the refund tag is not present, the HTLC proof would become "anyone can spend" after the locktime lock has "expired". Likewise, if the locktime is not present, or is not a valid unix timestamp, the HTLC proof will be permanently locked and can only be spent using the Receiver (hash lock) pathway.
Hash lock¶
Aligned with Bitcoin's HTLC construction, the hash lock in Secret.data represents the SHA-256 hash of a 32-byte preimage.
When an HTLC-locked proof is created, the Secret.data field must contain:
hash_hex = bytes_to_hex(SHA256(preimage_bytes))
where:
preimage_bytesis exactly 32 bytes of arbitrary data, commonly random and uniformly distributedhash_hexis the 32-byte SHA-256 digest of preimage_bytes, encoded as a 64-character lowercase hexadecimal string
To successfully spend a Proof via the Receiver Pathway, the spender must present the matching preimage_bytes, encoded as a 64-character lowercase hexadecimal string in the Proof.witness.preimage.
Mints and wallets must verify this equality before accepting the spend as valid:
SHA256(hex_to_bytes(Proof.witness.preimage)) == hex_to_bytes(Proof.secret.data)
Here is an example of a matching hash / preimage pair:
Proof.secret.data = 'ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5'
Proof.witness.preimage = '0000000000000000000000000000000000000000000000000000000000000001'
This hash-lock mechanism ensures that the Proof can only be spent once the secret preimage is revealed, allowing interoperability with external HTLC systems (such as Bitcoin or Lightning Network contracts).
Witness format¶
HTLCWitness is a serialized JSON string of the form
{
"preimage": <hex_str>,
"signatures": <Array[<hex_str>]>
}
The witness for a spent proof can be obtained with a Proof state check (see NUT-07).
Complex Example¶
This is an example Secret that locks a Proof with an HTLC condition that can be spent in either of the following ways:
Receiver Pathway - 2-of-3 signatures from the public keys in the pubkeys tag PLUS the preimage in Proof.witness.preimage of the hash in Secret.data.
Sender Pathway - One signature from the public keys in the refund tag, only available once the locktime has expired.
The signature flag sigflag indicates that signatures are necessary on the inputs and the outputs of the transaction this Proof is spent by.
[
"HTLC",
{
"nonce": "da62796403af76c80cd6ce9153ed3746",
"data": "023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c",
"tags": [
["sigflag", "SIG_ALL"],
["n_sigs", "2"],
["locktime", "1689418329"],
[
"refund",
"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
"02e2aeb97f47690e3c418592a5bcda77282d1339a3017f5558928c2441b7731d50"
],
[
"pubkeys",
"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7"
]
]
}
]
Mint info setting¶
The NUT-06 MintMethodSetting indicates support for this feature:
{
"14": {
"supported": true
}
}