Description of HTLC support in Zano
This short article describes a process called Atomic Swaps, an exchange of coins between two different parties, using two different blockchains/networks and in a trustless way (i.e. without the need for a trusted third party). The term "Atomic" is used to stress that this process happens in an "indissoluble" manner: at no point in the process does one party have the ability to compromise the interests of the other party.
To support Atomic Swaps we implemented HTLC - Hash-Time Locked-Contracts, which were originally introduced in BTC( Bitcoin Wiki ) and became a standard for cross-chain exchange operations.
Despite the fact that the cryptography in the CryptoNote family of coins and BTC is based on different elliptic curves, the HTLC mechanism allows you to easily exchange coins based on the identity of the hash function (in the case of bitcoin SHA256). The HTLC mechanism requires two projects to have the same hash function for validating HTLC redeem operations, so we implemented support of SHA256 (and RIPEMD-160).
We assume that the reader is familiar with the concept of HTLC and the typical process of atomic swaps based on HTLC, so here we provide a list of steps which show a process that would work specifically for our project.
As an example we’ve created pseudo-code that describe the process of an exchange between Alice and Bob. Alice has 1 BTC, and Bob has 100 ZANO, they want to exchange 1 BTC to 100 ZANO (Let's not focus on how realistic this exchange rate is, it’s not related to subject, right?).
So, Alice has 1 BTC, and she wants to get 100 ZANO. She has wallets and addresses both in the BTC network and in the ZANO network. Bob has 100 ZANO and he wants to get 1 BTC. He also has wallets and addresses in both networks.
Here is a code example(full example project can be found here: https://github.com/hyle-team/atomic_swap_example) which shows the swap process initiated by Alice, the BTC owner. In this process, before Alice creates the HTLC, she needs to generate the secret (which she should not reveal). Bob and Alice agreed on conditions and shared with each other following information:
Alice's Secret hash
Amount of BTC traded
Amount of Zano traded
Bob's Bitcoin PublicKey
Alice's Bitcoin PublicKey
Alice's Zano Address
TimeLock for BTC
Timelock for Zano
By knowing Alice's Secret hash, Amount of BTC traded, Alice's Bitcoin PublicKey and TimeLock, both Alice and Bob is able to derive a special BTC address for this HTLC(P2SH address), so Alice know to what address send HTLC, and Bob can check that the HTLC contract created and its corresponding conditions are those that were agreed with Alice.
async function perform_swap()
{
const partyAlice = new SwapBundle();
const partyBob = new SwapBundle();
await partyAlice.btc.init('primary', "", 'default');
await partyBob.btc.init('wallet_2', "12345", 'default');
await partyAlice.zano.init(12335); //Alice's zano wallet instance running on port 12335
await partyBob.zano.init(12334); //Bob's's zano wallet instance running on port 12334
const amount_btc = 1000;
const amount_zano = 1000000000000;
const swap_time_btc = 60*60*2; //seconds, 2 hour for swap
const swap_time_zano = 60; //blocks, 1 block per minutes, 1 hour
//Generate a secrete for Aclice
const alice_secret = partyAlice.btc.getSecret();
console.log("Secrete.hash: " + alice_secret.hash.toString('hex'));
//Preparation step:
//needed to make Bob's watch only wallet already initiated to catch next step transaction "on the fly"
await partyBob.btc.prepare_htlc_watchonly_address(alice_secret.hash, swap_time_btc, partyAlice.btc.get_public_key());
console.log("[ALICE]: CREATING HTLC IN BTC NTEWORK FOR BOB....");
const res_alice_sent_htlc = await partyAlice.btc.send_htlc(partyBob.btc.get_public_key(), amount_btc, swap_time_btc, alice_secret.hash);
console.log("[ALICE]: CREATED, txid:" + JSON.stringify(res_alice_sent_htlc.txid));
console.log("[BOB]: CHECKING BTC HTLC CONFIRMED....");
var sleep_count = 0;
let check_res = undefined;
while(true)
{
check_res = await partyBob.btc.check_htlc_proposed();
if(check_res !== undefined && check_res.txid !== undefined)
{
break;
}
console.log("Sleeping..." + sleep_count);
sleep_count += 1;
await sleep(1000);
}
console.log("[BOB]: CONFIRMED(" + check_res.txid.toString('hex') + ")");
// ------ SKIP ------
// Bob make sure amount corresponds to agreed
// ------------------
console.log("[BOB]: CREATING HTLC IN ZANO NTEWORK FOR ALICE.....");
const bob_send_htlc_res = await partyBob.zano.send_htlc(await partyAlice.zano.get_address(), amount_zano, swap_time_zano, alice_secret.hash);
console.log("[BOB]: CREATED, txid: " + JSON.stringify(bob_send_htlc_res.result.result_tx_id));
console.log("[ALICE]: CHECKING ZANO HTLC CONFIRMED....");
let alice_check_res = undefined;
sleep_count = 0;
while(true)
{
alice_check_res = await partyAlice.zano.check_htlc_proposed(await partyBob.zano.get_address(), alice_secret.hash);
if(alice_check_res !== undefined && alice_check_res.found !== undefined && alice_check_res.found === true)
{
break;
}
console.log("Sleeping..." + sleep_count);
sleep_count += 1;
await sleep(1000);
}
console.log("[ALICE]: CONFIRMED: txid" + alice_check_res.info.tx_id);
// ------ SKIP ------
// Alice make sure amount corresponds to agreed
// ------------------
console.log("[ALICE]: REDEEM ZANO HTLC...");
const alice_redeem_res = await partyAlice.zano.redeem_htlc(alice_check_res.info.tx_id, alice_secret.secret);
console.log("[ALICE]: REDEEM RESULT: txid" + alice_redeem_res.result.result_tx_id);
console.log("[BOB]: CHECK IS ZANO HTLC REDEEMED....");
let bob_check_redeemed_res = undefined;
sleep_count = 0;
while(true)
{
bob_check_redeemed_res = await partyBob.zano.check_htlc_redeemed(bob_send_htlc_res.result.result_tx_id);
if(bob_check_redeemed_res.result !== undefined
&& bob_check_redeemed_res.result.origin_secrete_as_hex !== undefined
&& bob_check_redeemed_res.result.origin_secrete_as_hex !== ''
)
{
break;
}
console.log("Sleeping..." + sleep_count);
sleep_count += 1;
await sleep(1000);
}
console.log("[BOB]: CHECK IS ZANO HTLC REDEEMED. txid: " + bob_check_redeemed_res.result.redeem_tx_id);
console.log("[BOB]: REDEEMING BTC HTLC.....");
const bob_redeem_res = await partyBob.btc.redeem_htlc(check_res.fundingTx, bob_check_redeemed_res.result.origin_secrete_as_hex, check_res.fundingOutput, swap_time_btc, partyAlice.btc.get_public_key());
if(bob_redeem_res.txid === undefined)
{
console.log("[BOB]: ERROR");
process.exit(0);
}
console.log("[BOB]: DONE");
console.log("SWAP SUCCESSFULLY DONE");
process.exit(1);
}
Then, when Bob sees the HTLC transaction addressed to him created in the BTC network(and confirmed), he creates a HTLC transaction in the Zano network, addressed to Alice's Zano address(he does this by calling atomics_create_htlc_proposal API, and he passes to this API a hash from HTLC created by Alice in BTC network ). After that, Alice checks if she has received the HTLC transaction addressed to her wallet in the Zano network (she does this by calling atomics_get_list_of_active_htlc and looking for an item where the counterparty_address matches Bob's address and sha256_hash matches the hash of her secret).
After Alice detects the HTLC in the Zano network, she creates a transaction to redeem the funds in the HTLC to her Zano wallet, and by doing so reveals the secret. With the secret, Bob is now able to redeem the funds in the HTLC on the BTC network to his wallet.
To figure out if the HTLC created by Bob in the Zano network has been redeemed, Bob calls atomics_check_htlc_redeemed, and as soon as Alice's transaction has its first confirmation, this API returns to Bob the "secret" that opens the HTLC addressed to him in the BTC network .
Finally Bob redeems the HTLC in the BTC network to his BTC wallet and the swap is finished.
Typical script output looks like that:
[BOB]: [prepare_htlc_watchonly_address]: Prepared with BTC P2SH address:
2NGBpJkpmbPANbRGD4oSNhgkZZJQFpABJ7r
[ALICE]: [createHTLC]: BTC P2SH address:
2NGBpJkpmbPANbRGD4oSNhgkZZJQFpABJ7r
[ALICE]: BTC funding TX sent:
8745f5364619d73b954319e1d6257e53c843877d05e1b044852a4f948e47edf3
[BOB]: CHECKING BTC HTLC CONFIRMED....
[BOB]: [check_htlc_proposed]: Detected transaction: 8745f5364619d73b954319e1d6257e53c843877d05e1b044852a4f948e47edf3
[BOB]: CREATING HTLC IN ZANO NETWORK FOR ALICE.....
[BOB]: CREATED, txid: 0f1c2600ef8656a48db809062a1080f41eb395a33cef66fd56f2e271ff57e357
[ALICE]: CHECKING ZANO HTLC CONFIRMED....
[ALICE]: CONFIRMED: txid 0f1c2600ef8656a48db809062a1080f41eb395a33cef66fd56f2e271ff57e357
[ALICE]: REDEEM ZANO HTLC...
[ALICE]: REDEEM RESULT: txid b8a8ecf857b9c50b4956bf7fdceeb0917e8404d2818fe7b07a955c7a90c2aaae
[BOB]: CHECK IS ZANO HTLC REDEEMED. txid: b8a8ecf857b9c50b4956bf7fdceeb0917e8404d2818fe7b07a955c7a90c2aaae
[BOB]: REDEEMING BTC HTLC.....
[BOB]: [redeem_htlc] BTC: swap-sweep TX:
1e1a670e4369ab08caf0809b9dbe2f6a5bcb07c50e2dd41b01414539de960b4d
BTC broadcasting swepp TX: {success: true}
Please note that at the very beginning Bob generates an HTLC script and converts it into a P2SH address (2NGBpJkpmbPANbRGD4oSNhgkZZJQFpABJ7r), which he starts to watch for an incoming HTLC. With the next line Alice generates an HTLC script for her transaction, and she gets exactly the same script, and hence P2SH address (2NGBpJkpmbPANbRGD4oSNhgkZZJQFpABJ7r)that Bob got in the previous step. From there, Alice creates a transaction to this P2SH address (the funding transaction). After he sees that Alice has sent the funding transaction, Bob creates an HTLC in the Zano network. When Alice sees that there is a corresponding HTLC transaction on the Zano network, she creates a redeem transaction on the Zano network (and reveals her secret), which Bob then uses to create a redeem transaction on the BTC network. Thus the deal is done.
A few words about the case where both parties have the same goals, but Bob initiates this swap on Zano side (function perform_swap_zano_first). He creates a HTLC in the Zano network by calling the atomics_create_htlc_proposal API, but in this case the htlc_hash field is left empty, so this call returns to Bob the secret(encoded in HEX).
Then Alice sees this HTLC from Bob in her Zano wallet (from atomics_get_list_of_active_htlc), and using the htlc_hash from the Zano wallet she creates a HTLC in the BTC network. Bob can derive the same P2SH address by knowing the same htlc_hash, the amount, and his and her addresses (and time lock), so they can continue the process in nearly the same way as in previous example. The only difference is that in case of the swap process started in the Zano network, Bob and Alice don't need to share the htlc_hash manually, all they have to do is to agree on the amount and time frame, and exchange Zano and BTC addresses.