Posted at

NEM2カタパルトとETHのアトミックスワップ


はじめに

この記事は、NEM寄りの記事です。

カタパルトのマイルストーンのCowが公開されました。

コミットのコメントにこんな記載があります。


SecretLockTransaction now supports Op_Sha3_256, Op_Keccak_256, Op_Hash_160, Op_Hash_256 hash algorithms.


Keccak256が使えるようになったようなので、イーサリアムとのアトミックスワップをやってみようと思います。


概要


環境


NEM

プライベート環境、MIJIN_TEST

nem2-sdk 0.10.1


ETH

Ropsten

HTLCコントラクト

https://github.com/chatch/hashed-timelock-contract-ethereum

(ハッシュ関数がsha256だったので、keccak256に変更して使いました)

web3 0.20.6


アカウント

単純化のために、1人1つの秘密鍵を使ってアドレスを導出します。


Alice

Key
Value

PrivKey
25B3F54217340F7061D02676C4B928ADB4395EB70A2A52D2A11E2F4AE011B03E

NEM
SCA7ZS2B7DEEBGU3THSILYHCRUR32YYE55ZBLYA2

ETH
0xd11690c03f36cf220f9a4fbbcfc1658f306e4c6a


Bob

Key
Value

PrivKey
0x1b31f0bbb87891e747501c2b79103f986bd6f0b12b892eb0acfb78adbf9b3df1

NEM
SBWUUQYUPEVBZSQ3DQOPAZM2IPET7UY6TGPXPX34

ETH
0x608f801a3ebf472c0a6503750d7829f4deebcfcd


手順

Aliceのcat.currency(100通貨)と、BobのETH(1wei)を交換します。その手順は下記です。

Aliceが秘密の値(シークレット)を作成し、そのハッシュを使ってSecret Lockトランザクションを送信します。

Bobはそのトランザクションからシークレットのハッシュを拾って、HTLCコントラクトに登録します。

Aliceは、コントラクトに登録されたのを確認したのち、HTLCコントラクトにシークレットを提出してETHを得ます。

Bobは、そのシークレットを拾ってSecret Proofトランザクションを送信し、cat.currencyを得ます。


詳細


Aliceのターン

32バイトのシークレットを作ります。

x   : 28D0A13C6ABAAB59A1EB94C6250FAE325C37C9B1EF2A7D9B37CEECFE59B84AB3

ハッシュ化します。H()はKeccak256です。

H(x): 48DC79553781ED80927E14609C3F256D17D802A7C88C6505AD14AF28F0131599

コード。

const secret = "28D0A13C6ABAAB59A1EB94C6250FAE325C37C9B1EF2A7D9B37CEECFE59B84AB3";

// nem2-sdk
const jssha3 = require('js-sha3');
const keccak256 = jssha3.keccak256;
console.log(keccak256.create().update(Buffer.from(secret, 'hex')).hex().toUpperCase());
// web3
const Web3 = require('web3');
const web3 = new Web3();
console.log(web3.sha3(secret ,{"encoding":"hex"}));

Secret Lockトランザクションを送信します。

const jssha3 = require('js-sha3');

const nem2lib = require("nem2-library");
const request = require('request');

const sha3_256 = jssha3.sha3_256
const keccak256 = jssha3.keccak256

const ENDPOINT = "http://13.112.187.85:3000"
const PRIVATE_KEY = "25B3F54217340F7061D02676C4B928ADB4395EB70A2A52D2A11E2F4AE011B03E"
const RECIPIENT_ADDRESS = "SBWUUQYUPEVBZSQ3DQOPAZM2IPET7UY6TGPXPX34"

// エンディアン変換する
function endian(hex) {
const uint8arr = nem2lib.convert.hexToUint8(hex)
return nem2lib.convert.uint8ToHex(uint8arr.reverse())
}

// ハッシュアルゴリズム指定
const hashAlgorithm = "01"

// シークレットのハッシュ
const secretHash = "48DC79553781ED80927E14609C3F256D17D802A7C88C6505AD14AF28F0131599"

const keypair = nem2lib.KeyPair.createKeyPairFromPrivateKeyString(PRIVATE_KEY)
const recipient = nem2lib.convert.uint8ToHex(nem2lib.address.stringToAddress(RECIPIENT_ADDRESS))
const mosaicId = "85BBEA6CC462B244" // cat.currency
const amount = "0000000005F5E100" // 100000000
const duration = "00000000000003E8" // 1000

function lockTx() {
// 未署名トランザクション
const txPayload =
"CA000000" +
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
nem2lib.convert.uint8ToHex(keypair.publicKey) +
"0190" + "5241" + "0000000000000000" +
nem2lib.convert.uint8ToHex(new Uint8Array(new Uint32Array(nem2lib.deadline(2 * 60 * 60 * 1000)).buffer)) +
endian(mosaicId) +
endian(amount) +
endian(duration) +
hashAlgorithm + // hashtype
secretHash + // secret
recipient // recipient

// 署名する
const txPayloadSigningBytes = txPayload.substr(100*2)
const signatureByte = nem2lib.KeyPair.sign(keypair, txPayloadSigningBytes)
const signature = nem2lib.convert.uint8ToHex(signatureByte)

const signedTxPayload =
txPayload.substr(0,4*2) +
signature +
txPayload.substr((4+64)*2)

// 送信する
request({
url: `${ENDPOINT}/transaction`,
method: 'PUT',
headers: {
'Content-Type':'application/json'
},
json: {"payload": signedTxPayload}
}, (error, response, body) => {
console.log(body);
});
}

lockTx()

送信しました。

image.png

ブロックエクスプローラーには、シークレットのハッシュが表示されています。


Bobのターン

AliceのSecret Lockトランザクションからシークレットハッシュを確認します。

require('dotenv').config();

const fs = require('fs');
const Web3 = require('web3');
const PrivateKeyProvider = require("truffle-privatekey-provider");

// web3
const privateKey = process.env.PRIVATE_KEY_4;
const provider = new PrivateKeyProvider(privateKey, process.env.HOST);
const web3 = new Web3(provider);

const recipient = "0xd11690c03f36cf220f9a4fbbcfc1658f306e4c6a";
const secretHash = "0x48dc79553781ed80927e14609c3f256d17d802a7c88c6505ad14af28f0131599";

async function exec() {
const account = await new Promise((resolve, reject) => {
web3.eth.getAccounts((error,result) => {
resolve(result[0]);
})
});
// コントラクトインスタンス作成
const abi = JSON.parse(fs.readFileSync('abi/HashedTimelock.json')).abi;
const ct = web3.eth.contract(abi).at('0x21C0750C4bb2b38c52D997A06EAcBc7Dc48b90eE');

// コントラクトにシークレットのハッシュを登録
const tx = await new Promise((resolve, reject) => {
ct.newContract(
recipient,
secretHash,
Math.floor(Date.now() / 1000) + 3600,
{
from: account,
value: 1,
},
(error, result) => {
if (error) reject(error)
resolve(result);
}
)
})
console.log(tx)
}

exec();

送信しました。

image.png

イベントタブを見ます。

image.png

Key
Value

contractId
0x49c35ec3b1da83a4495293202bc58bc7e31ed0b39c416a557c9ca365c1e51a3a

sender
0x000000000000000000000000608f801a3ebf472c0a6503750d7829f4deebcfcd

receiver
0x000000000000000000000000d11690c03f36cf220f9a4fbbcfc1658f306e4c6a

amount
0x0000000000000000000000000000000000000000000000000000000000000001

hashlock
0x48dc79553781ed80927e14609c3f256d17d802a7c88c6505ad14af28f0131599

timelock
0x000000000000000000000000000000000000000000000000000000005c617614

contractIdが、スワップ毎に発行されるIDのようです。timelockは、UNIX時間での指定のようです。


再びAliceのターン

Bobのトランザクションを確認し、hashlockが自分のと同一かを確認します。合っていれば、コントラクトにシークレットを明かし、ETHを得ます。

require('dotenv').config()

const fs = require('fs');
const Web3 = require('web3');
const PrivateKeyProvider = require("truffle-privatekey-provider");

// web3
const privateKey = process.env.PRIVATE_KEY_B;
const provider = new PrivateKeyProvider(privateKey, process.env.HOST);
const web3 = new Web3(provider);

const secret = "0x28d0a13c6abaab59a1eb94c6250fae325c37c9b1ef2a7d9b37ceecfe59b84ab3";
const contractId = "0x49c35ec3b1da83a4495293202bc58bc7e31ed0b39c416a557c9ca365c1e51a3a";

async function exec() {
const account = await new Promise((resolve, reject) => {
web3.eth.getAccounts((error,result) => {
resolve(result[0]);
})
});
// コントラクトのインスタンス作成
const abi = JSON.parse(fs.readFileSync('abi/HashedTimelock.json')).abi;
const ct = web3.eth.contract(abi).at('0x21C0750C4bb2b38c52D997A06EAcBc7Dc48b90eE');

// コントラクトにシークレットを明かす
const tx = await new Promise((resolve, reject) => {
ct.withdraw(
contractId,
secret,
{
from: account,
gas: 100000,
gasPrice: 1000000000
},
(error, result) => {
if (error) reject(error)
resolve(result);
}
)
})
console.log(tx)
}

exec();

送信しました。

image.png

1 weiが送信されていることが、小さく表示されています。

下にスクロールすると、送信したデータが載っています。

image.png

28d0...がシークレットです。


最後のBobのターン

Aliceがシークレットを明かしたので、Bobも引き出し可能になりました。

const jssha3 = require('js-sha3');

const nem2lib = require("nem2-library");
const request = require('request');

const proof = "28D0A13C6ABAAB59A1EB94C6250FAE325C37C9B1EF2A7D9B37CEECFE59B84AB3"
const hashAlgorithm = "01";
const secret = "48DC79553781ED80927E14609C3F256D17D802A7C88C6505AD14AF28F0131599"

const PRIVATE_KEY = "1B31F0BBB87891E747501C2B79103F986BD6F0B12B892EB0ACFB78ADBF9B3DF1"
const ENDPOINT = "http://13.112.187.85:3000";
const keypair = nem2lib.KeyPair.createKeyPairFromPrivateKeyString(PRIVATE_KEY)

function proofTx() {
// 未署名トランザクション作成
const txPayload =
"BB000000" +
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
nem2lib.convert.uint8ToHex(keypair.publicKey) +
"0190" + "5242" + "0000000000000000" +
nem2lib.convert.uint8ToHex(new Uint8Array(new Uint32Array(nem2lib.deadline(2 * 60 * 60 * 1000)).buffer)) +
hashAlgorithm +
secret +
"2000" +
proof

// 署名する
const txPayloadSigningBytes = txPayload.substr(100*2)
const signatureByte = nem2lib.KeyPair.sign(keypair, txPayloadSigningBytes)
const signature = nem2lib.convert.uint8ToHex(signatureByte)

const signedTxPayload =
txPayload.substr(0,4*2) +
signature +
txPayload.substr((4+64)*2)

// 送信する
request({
url: `${ENDPOINT}/transaction`,
method: 'PUT',
headers: {
'Content-Type':'application/json'
},
json: {"payload": signedTxPayload}
}, (error, response, body) => {
console.log(body);
});

}

proofTx()

送りました。

image.png


レシート

せっかくなので、新しく追加された機能であるレシートを見てみます。APIから取得できなかったので、DBを直接みます。


AliceのSecret Lock

image.png

詳しくはわかりませんが、2番目のやつが、Aliceのアカウントの残高が減ったことが書いてある気がします。

image.png

これは何でしょう。


BobのSecret Proof

image.png

詳しくはわかりませんが、2番目のやつから、Bobのアカウントの残高が増えたことが書いてある気がします。


おわりに

本当は、HTLCの有効期限について気を配る必要があります。

Truffleは環境構築でつまづきました。

Windowsでやったので、Web3は古い0.20です。

モザイクIDがリトルエンディアンでシリアライズするところでずっと引っかかってました。

レシートがAPIから取れないのは、mongoDBのインデックス作成が正しくないからだと思います。

ERC20のHTLCもあるので、いつか。

サブマリンスワップも、いつか。

ビットコインも、いつか。