公式ドキュメントにあるアトミックスワップの手順をやってみます。
公式ドキュメント
Writing your first application
Using secret lock transaction for atomic cross-chain swap
概要
ここに異なる2つのMIJIN_TESTチェーンがあるとします。それぞれ、「Public Chain」「Private Chain」とします。
片方で、AliceからBobへXEMを送信し、もう片方で、BobからAliceへモザイクを送信します。
AliceとBobに信頼関係があれば、それぞれのチェーンでTransferトランザクションを送ればいいのです。
もし、そうでない場合に、信頼せずに取引が完了するようにしたい。
その時に使うのが、secret lockとsecret proofトランザクションです。
秘密の値を介して、モザイクをロックする、アンロックする
Secret Lockトランザクションは、モザイクを送信するトランザクションです。Transferトランザクションと違う点は、Secret Proofトランザクションとセットになってなければならない点です。
Secret Lockトランザクションには、秘密の値のハッシュが埋め込まれています。Secret Proofトランザクションにより、秘密の値を知っていることを証明できれば、晴れてモザイクを受け取れます。
秘密の値は、トランザクションで教える
AliceはBobにXEMを送るSecret lockトランザクションを作成します。
同じく、BobはAliceにモザイクを送るSecret lockトランザクションを作成します。
ここで、Aliceは、自分宛てのSecret lockトランザクションを解除すると同時に、秘密の値をBobに開示します。
するとBobも、自分宛てのSecret lockトランザクションを解除できます。
これがsecret lockとsecret proofトランザクションを使ったアトミックスワップです。
これらの図は、draw.ioで作成しました。オリジナルファイルは、githubにあります。
環境
- ubuntu 18.04
- git 2.17.0
- jq 1.5.1
- nodejs 8.11.2
- nem2-sdk 0.9.3
- nem2-cli
- docker 18.05
- docker-compose 1.21.2
- tech-bureau/catapult-service-bootstrap
ディレクトリ構成
<root>
├── catapult-service-bootstrap-public
│ ├── LICENSE
│ ├── README.md
│ ├── bin
│ ├── build
│ ├── clean-all
│ ├── clean-data
│ ├── data
│ ├── docker-compose-mmapv1.yml
│ ├── docker-compose.yml
│ ├── dockerfiles
│ ├── ruby
│ └── static-config
├── catapult-service-bootstrap-private
│ ├── LICENSE
│ ├── README.ja.md
│ ├── README.md
│ ├── bin
│ ├── build
│ ├── clean-all
│ ├── clean-data
│ ├── data
│ ├── docker-compose-mmapv1.yml
│ ├── docker-compose.yml
│ ├── dockerfiles
│ ├── ruby
│ └── static-config
└── nem2-sdk
├── node_modules
├── package-lock.json
├── secretlock1.js
├── secretlock2.js
├── secretlock3.js
├── secretlock4.js
└── secretlock_disp.js
使用するアカウント
Public:
Alice:
private: 7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4
public: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
address: SBWEUWON6IBHCW5IC4EI6V6SMTVJGCJWGLF57UGK
Bob:
private: 31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E
public: 3390BF02D2BB59C8722297FF998CE89183D0906E469873284C091A5CDC22FD57
address: SB2Y5ND4FDLBIO5KHXTKRWODDG2QHIN73DTYT2PC
Private:
Alice:
private: 8CBA73B9DF31D85DCFA4B9E1D3B7A88E5A5F32C930FCC45FE5A457619F3E6687
public: C36F5BDDE8B2B586D17A4E6F4B999DD36EBD114023C1231E38ABCB1976B938C0
address: SBNRDTMZ3MF4RFNQA2ZA33G74EGTINCKX2EHYYHY
Bob:
private: BA46F91D7BCD40B6482E138F0D3D53A29F0A6097B5C4586C6ABD81A3BA3A2DAD
public: 1C650F49DD67EC50BFDEA40906D32CDE3C969BDF58837C7DA320829BDDE96150
address: SCBCMLVDJBXARCOI6XSKEU3ER2L6HH7UBEPTENGQ
準備
ライブラリなど
ubuntu立ち上げ(仮想でも物理でも)
jqをインストール
gitをインストール
nodejsをインストール
dockerをインストール
docker-composeをインストール
nem2-sdkをインストール
# mkdir nem2-sdk && cd nem2-sdk
# npm i nem2-sdk
nem2-cliをインストール
# npm i -g nem2-cli
tech-bureau/catapult-service-bootstrapをインストール
publicとprivateの2つを作ります。
# git clone https://github.com/tech-bureau/catapult-service-bootstrap.git catapult-service-bootstrap-public
# cp -r catapult-service-bootstrap-public catapult-service-bootstrap-private
publicのチェーンを立ち上げます。
# cd catapult-service-bootstrap-public
# docker-compose up -d
Creating catapult-service-bootstrap-public_generate-raw-addresses_1 ... done
Creating catapult-service-bootstrap-public_db_1 ... done
Creating catapult-service-bootstrap-public_generate-configs_1 ... done
Creating catapult-service-bootstrap-public_store-addresses_1 ... done
Creating catapult-service-bootstrap-public_init-db_1 ... done
Creating catapult-service-bootstrap-public_peer-node-1-nemgen_1 ... done
Creating catapult-service-bootstrap-public_peer-node-0-nemgen_1 ... done
Creating catapult-service-bootstrap-public_rest-gateway_1 ... done
Creating catapult-service-bootstrap-public_api-node-0-nemgen_1 ... done
Creating catapult-service-bootstrap-public_api-node-0_1 ... done
Creating catapult-service-bootstrap-public_peer-node-1_1 ... done
Creating catapult-service-bootstrap-public_peer-node-0_1 ... done
# docker-compose ps
Name Command State Ports
-----------------------------------------------------------------------------------------------------------------------------
catapult-service-bootstrap-public_api-node-0-nemgen_1 bash -c /bin-mount/wait /s ... Exit 0
catapult-service-bootstrap-public_api-node-0_1 bash -c /bin-mount/wait /s ... Up
catapult-service-bootstrap-public_db_1 docker-entrypoint.sh bash ... Up 27017/tcp
catapult-service-bootstrap-public_generate-configs_1 ash -c /bin-mount/wait /ad ... Exit 0
catapult-service-bootstrap-public_generate-raw-addresses_1 bash -c /bin-mount/generat ... Exit 0
catapult-service-bootstrap-public_init-db_1 docker-entrypoint.sh bash ... Exit 0
catapult-service-bootstrap-public_peer-node-0-nemgen_1 bash -c /bin-mount/wait /s ... Exit 0
catapult-service-bootstrap-public_peer-node-0_1 bash -c /bin-mount/wait /s ... Up
catapult-service-bootstrap-public_peer-node-1-nemgen_1 bash -c /bin-mount/wait /s ... Exit 0
catapult-service-bootstrap-public_peer-node-1_1 bash -c /bin-mount/wait /s ... Up
catapult-service-bootstrap-public_rest-gateway_1 ash -c /bin-mount/wait /st ... Up 0.0.0.0:3000->3000/tcp
catapult-service-bootstrap-public_store-addresses_1 ash -c /bin-mount/wait /ad ... Exit 0
privateのほうのrest-gatewayのportsを、3100:3000
にします。
# cd catapult-service-bootstrap-private
# vi docker-compose.yml
rest-gateway:
ports:
- "3100:3000"
privateのチェーンを立ち上げます
# docker-compose up -d
# docker-compose ps
Name Command State Ports
------------------------------------------------------------------------------------------------------------------------------
catapult-service-bootstrap-private_api-node-0-nemgen_1 bash -c /bin-mount/wait /s ... Exit 0
catapult-service-bootstrap-private_api-node-0_1 bash -c /bin-mount/wait /s ... Up
catapult-service-bootstrap-private_db_1 docker-entrypoint.sh bash ... Up 27017/tcp
catapult-service-bootstrap-private_generate-configs_1 ash -c /bin-mount/wait /ad ... Exit 0
catapult-service-bootstrap-private_generate-raw-addresses_1 bash -c /bin-mount/generat ... Exit 0
catapult-service-bootstrap-private_init-db_1 docker-entrypoint.sh bash ... Exit 0
catapult-service-bootstrap-private_peer-node-0-nemgen_1 bash -c /bin-mount/wait /s ... Exit 0
catapult-service-bootstrap-private_peer-node-0_1 bash -c /bin-mount/wait /s ... Up
catapult-service-bootstrap-private_peer-node-1-nemgen_1 bash -c /bin-mount/wait /s ... Exit 0
catapult-service-bootstrap-private_peer-node-1_1 bash -c /bin-mount/wait /s ... Up
catapult-service-bootstrap-private_rest-gateway_1 ash -c /bin-mount/wait /st ... Up 0.0.0.0:3100->3000/tcp
catapult-service-bootstrap-private_store-addresses_1 ash -c /bin-mount/wait /ad ... Exit 0
0.0.0.0:3100->3000/tcp
と、ホストの3100とコンテナの3000がつながっていることがわかります。
nem2-cliのプロファイル作成
# nem2-cli profile create -p 7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4 -n MIJIN_TEST -u http://localhost:3000 --profile alicepub
# nem2-cli profile create -p 31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E -n MIJIN_TEST -u http://localhost:3000 --profile bobpub
# nem2-cli profile create -p 8CBA73B9DF31D85DCFA4B9E1D3B7A88E5A5F32C930FCC45FE5A457619F3E6687 -n MIJIN_TEST -u http://localhost:3100 --profile alicepriv
# nem2-cli profile create -p BA46F91D7BCD40B6482E138F0D3D53A29F0A6097B5C4586C6ABD81A3BA3A2DAD -n MIJIN_TEST -u http://localhost:3100 --profile bobpriv
ネームスペースとモザイク作成
PrivateチェーンのBobアカウントで、foo:barモザイクを作成します。
# nem2-cli transaction namespace -n foo -r -d 1000 --profile bobpriv
Transaction announced correctly
Hash: 9673687AFE91EE61155E6013B5ACE1FBB9918AD433E3D1A302F912EE77E9542D
Signer: 1C650F49DD67EC50BFDEA40906D32CDE3C969BDF58837C7DA320829BDDE96150
# nem2-cli transaction mosaic -m bar -n foo -a 100 -t -d 0 -u 1000 --profile bobpriv
Do you want mosaic to have supply mutable? [y/n]: n
Do you want mosaic to have levy mutable? [y/n]: n
Transaction announced correctly
Hash: E876DE6EACA8ECDC41C664B3E690F0BFDA537CB277FFEBB2E446FD4956950BC9
Signer: 1C650F49DD67EC50BFDEA40906D32CDE3C969BDF58837C7DA320829BDDE96150
コード
ライブラリ参照と固定情報
これから登場するコードには、先頭部分にこれがくっついています。
const nem2Sdk = require("nem2-sdk");
const crypto = require("crypto");
const jssha3 = require('js-sha3');
const Address = nem2Sdk.Address,
Deadline = nem2Sdk.Deadline,
Account = nem2Sdk.Account,
UInt64 = nem2Sdk.UInt64,
NetworkType = nem2Sdk.NetworkType,
PlainMessage = nem2Sdk.PlainMessage,
TransferTransaction = nem2Sdk.TransferTransaction,
Mosaic = nem2Sdk.Mosaic,
MosaicId = nem2Sdk.MosaicId,
TransactionHttp = nem2Sdk.TransactionHttp,
AccountHttp = nem2Sdk.AccountHttp,
MosaicHttp = nem2Sdk.MosaicHttp,
NamespaceHttp = nem2Sdk.NamespaceHttp,
MosaicService = nem2Sdk.MosaicService,
XEM = nem2Sdk.XEM,
AggregateTransaction = nem2Sdk.AggregateTransaction,
PublicAccount = nem2Sdk.PublicAccount,
LockFundsTransaction = nem2Sdk.LockFundsTransaction,
Listener = nem2Sdk.Listener,
CosignatureTransaction = nem2Sdk.CosignatureTransaction,
SecretLockTransaction = nem2Sdk.SecretLockTransaction,
SecretProofTransaction = nem2Sdk.SecretProofTransaction,
HashType = nem2Sdk.HashType;
const sha3_512 = jssha3.sha3_512;
const urlPublic = 'http://localhost:3000';
const urlPrivate = 'http://localhost:3100';
const transactionHttpPublic = new TransactionHttp(urlPublic);
const transactionHttpPrivate = new TransactionHttp(urlPrivate);
const aliceAddressPrivate = Address.createFromRawAddress('SBNRDTMZ3MF4RFNQA2ZA33G74EGTINCKX2EHYYHY');
const aliceAddressPublic = Address.createFromRawAddress('SBWEUWON6IBHCW5IC4EI6V6SMTVJGCJWGLF57UGK');
const bobAddressPrivate = Address.createFromRawAddress('SCBCMLVDJBXARCOI6XSKEU3ER2L6HH7UBEPTENGQ');
const bobAddressPublic = Address.createFromRawAddress('SB2Y5ND4FDLBIO5KHXTKRWODDG2QHIN73DTYT2PC');
secretlock_disp.js
AliceとBobの保有モザイクを表示するコード。
MosaicService
を使えば、すべてのモザイクの保有量を表示してくれます。
const alicePrivateKeyPrivate = '8CBA73B9DF31D85DCFA4B9E1D3B7A88E5A5F32C930FCC45FE5A457619F3E6687';
const aliceAccountPrivate = Account.createFromPrivateKey(alicePrivateKeyPrivate, NetworkType.MIJIN_TEST);
const alicePrivateKeyPublic = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
const aliceAccountPublic = Account.createFromPrivateKey(alicePrivateKeyPublic, NetworkType.MIJIN_TEST);
const bobPrivateKeyPrivate = 'BA46F91D7BCD40B6482E138F0D3D53A29F0A6097B5C4586C6ABD81A3BA3A2DAD';
const bobAccountPrivate = Account.createFromPrivateKey(bobPrivateKeyPrivate, NetworkType.MIJIN_TEST);
const bobPrivateKeyPublic = '31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E';
const bobAccountPublic = Account.createFromPrivateKey(bobPrivateKeyPublic, NetworkType.MIJIN_TEST);
const accountHttpPublic = new AccountHttp(urlPublic);
const mosaicHttpPublic = new MosaicHttp(urlPublic);
const namespaceHttpPublic = new NamespaceHttp(urlPublic);
const mosaicServicePublic = new MosaicService(accountHttpPublic, mosaicHttpPublic, namespaceHttpPublic);
const accountHttpPrivate = new AccountHttp(urlPrivate);
const mosaicHttpPrivate = new MosaicHttp(urlPrivate);
const namespaceHttpPrivate = new NamespaceHttp(urlPrivate);
const mosaicServicePrivate = new MosaicService(accountHttpPrivate, mosaicHttpPrivate, namespaceHttpPrivate);
const alisBobMosaicsAmountView = () => {
const mosaicsAmountViewFromAddress = (logPrefix, mosaicService, address) => {
mosaicService.mosaicsAmountViewFromAddress(address)
.flatMap((_) => _)
.subscribe(
mosaic => console.log(logPrefix, mosaic.relativeAmount(), mosaic.fullName()),
err => console.error(err)
);
};
const timestamp = new Date().getTime();
mosaicsAmountViewFromAddress('[' + timestamp + '] Alice(Public) have', mosaicServicePublic, aliceAccountPublic.address);
mosaicsAmountViewFromAddress('[' + timestamp + '] Alice(Private) have', mosaicServicePrivate, aliceAccountPrivate.address);
mosaicsAmountViewFromAddress('[' + timestamp + '] Bob(Public) have', mosaicServicePublic, bobAccountPublic.address);
mosaicsAmountViewFromAddress('[' + timestamp + '] Bob(Private) have', mosaicServicePrivate, bobAccountPrivate.address);
};
alisBobMosaicsAmountView();
もしくは、こんなシェルスクリプトでもよいと思います。
# !/bin/bash
nem2-cli account info --profile alicepub
nem2-cli account info --profile bobpub
nem2-cli account info --profile alicepriv
nem2-cli account info --profile bobpriv
secretlock1.js
Publicチェーンで、AliceがBobへXEMを送信するトランザクションです。これをTX1
とします。
Aliceは、秘密の値proof
を作成し、そのハッシュ値secret
をトランザクションに埋め込みます。
Bobは、このXEMを受け取るには、秘密の値proof
を埋め込んだSecretProofトランザクションを作成しなければなりません。しかし、まだBobはproofの値を知りません。
const alicePrivateKeyPrivate = '8CBA73B9DF31D85DCFA4B9E1D3B7A88E5A5F32C930FCC45FE5A457619F3E6687';
const aliceAccountPrivate = Account.createFromPrivateKey(alicePrivateKeyPrivate, NetworkType.MIJIN_TEST);
const alicePrivateKeyPublic = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
const aliceAccountPublic = Account.createFromPrivateKey(alicePrivateKeyPublic, NetworkType.MIJIN_TEST);
// const bobPrivateKeyPrivate = 'BA46F91D7BCD40B6482E138F0D3D53A29F0A6097B5C4586C6ABD81A3BA3A2DAD';
// const bobAccountPrivate = Account.createFromPrivateKey(bobPrivateKeyPrivate, NetworkType.MIJIN_TEST);
// const bobPrivateKeyPublic = '31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E';
// const bobAccountPublic = Account.createFromPrivateKey(bobPrivateKeyPublic, NetworkType.MIJIN_TEST);
// ***************************************************
// Alice Create Secret
// ***************************************************
// Alice picks a random number and hashes it.
const random = crypto.randomBytes(10);
const hash = sha3_512.create();
const secret = hash.update(random).hex().toUpperCase();
const proof = random.toString('hex');
console.log('x (proof) : ' + proof);
console.log('H(x) (secret) : ' + secret);
// ***************************************************
// Alice to Bob (Public)
// ***************************************************
// Alice creates creates TX1 SecretLockTransaction{ H(x), B, MosaicId, Amount, valid for 96h }
const tx1 = SecretLockTransaction.create(
Deadline.create(),
new Mosaic(new MosaicId('nem:xem'), UInt64.fromUint(100000000)),
UInt64.fromUint(60), //officially 96h
HashType.SHA3_512,
secret,
bobAddressPublic, // send to bob public
NetworkType.MIJIN_TEST
);
// Alice sends TX1 to network (PUBLIC)
const tx1Signed = aliceAccountPublic.sign(tx1);
console.log('tx1Signed.hash : ' + tx1Signed.hash);
console.log('tx1Signed.signer : ' + tx1Signed.signer);
transactionHttpPublic.announce(tx1Signed).subscribe(
x => console.log(x),
err => console.error(err)
);
secretlock2.js
Privateチェーンで、BobがAliceへモザイクを送信するトランザクションです。これをTX2
とします。
Bobは、PublicチェーンでTX1
を見て、secret
を入手できますので、これをトランザクションに埋め込みます。
Aliceは、このモザイクを受け取るには、秘密の値proof
を埋め込んだSecretProofトランザクションを作成しなければなりません。
今回、secret
の値は、base64エンコードの文字列を引数で渡す形にしました。
// const alicePrivateKeyPrivate = '8CBA73B9DF31D85DCFA4B9E1D3B7A88E5A5F32C930FCC45FE5A457619F3E6687';
// const aliceAccountPrivate = Account.createFromPrivateKey(alicePrivateKeyPrivate, NetworkType.MIJIN_TEST);
// const alicePrivateKeyPublic = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
// const aliceAccountPublic = Account.createFromPrivateKey(alicePrivateKeyPublic, NetworkType.MIJIN_TEST);
const bobPrivateKeyPrivate = 'BA46F91D7BCD40B6482E138F0D3D53A29F0A6097B5C4586C6ABD81A3BA3A2DAD';
const bobAccountPrivate = Account.createFromPrivateKey(bobPrivateKeyPrivate, NetworkType.MIJIN_TEST);
const bobPrivateKeyPublic = '31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E';
const bobAccountPublic = Account.createFromPrivateKey(bobPrivateKeyPublic, NetworkType.MIJIN_TEST);
// ***************************************************
// Bob to Alice (Private)
// ***************************************************
if (process.argv.length <= 2) {
throw new Error("need secret (base64 string)");
}
const secret = Buffer.from(process.argv[2], "base64").toString('hex').toUpperCase();
// B creates TX2 SecretLockTransaction{ H(x), A, MosaicId, Amount, valid for 84h }
const tx2 = SecretLockTransaction.create(
Deadline.create(),
new Mosaic(new MosaicId('foo:bar'), UInt64.fromUint(1)),
UInt64.fromUint(10), //officially 84h
HashType.SHA3_512,
secret,
aliceAddressPrivate,
NetworkType.MIJIN_TEST
);
// Bob sends TX2 to network (PRIVATE)
const tx2Signed = bobAccountPrivate.sign(tx2);
console.log('tx2Signed.hash : ' + tx2Signed.hash);
console.log('tx2Signed.signer : ' + tx2Signed.signer);
// const transactionHttpPrivate = new TransactionHttp('http://192.168.11.77:3100');
transactionHttpPrivate.announce(tx2Signed).subscribe(
x => console.log(x),
err => console.error(err)
);
secretlock3.js
Privateチェーンで、Aliceがモザイクを引き出すトランザクションです。これをTX3
とします。
Aliceは、BobのTX2
をチェーン上で確認します。TX2
のモザイクを受け取るには、秘密の値proof
を埋め込んだSecretProofトランザクションを作成しなければなりません。
Aliceはproof
を知っています(自分で作った)ので、これを使ってSecretProofトランザクションを作成します。
今回、proof
の値は、文字列を引数で渡すようにしました。
const alicePrivateKeyPrivate = '8CBA73B9DF31D85DCFA4B9E1D3B7A88E5A5F32C930FCC45FE5A457619F3E6687';
const aliceAccountPrivate = Account.createFromPrivateKey(alicePrivateKeyPrivate, NetworkType.MIJIN_TEST);
const alicePrivateKeyPublic = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
const aliceAccountPublic = Account.createFromPrivateKey(alicePrivateKeyPublic, NetworkType.MIJIN_TEST);
// const bobPrivateKeyPrivate = 'BA46F91D7BCD40B6482E138F0D3D53A29F0A6097B5C4586C6ABD81A3BA3A2DAD';
// const bobAccountPrivate = Account.createFromPrivateKey(bobPrivateKeyPrivate, NetworkType.MIJIN_TEST);
// const bobPrivateKeyPublic = '31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E';
// const bobAccountPublic = Account.createFromPrivateKey(bobPrivateKeyPublic, NetworkType.MIJIN_TEST);
// ***************************************************
// Alice Unlock (Private)
// ***************************************************
if (process.argv.length <= 2) {
throw new Error("need proof (hex string)");
}
const proof = process.argv[2];
const hash = sha3_512.create();
const secret = hash.update(Buffer.from(proof, 'hex')).hex().toUpperCase();
// Alice waits until Txs are confirmed
// Alice spends TX2 transaction by sending SecretProofTransaction (in PRIVATE network)
// const alicePrivateKeyPrivate = '8CBA73B9DF31D85DCFA4B9E1D3B7A88E5A5F32C930FCC45FE5A457619F3E6687';
// const aliceAccountPrivate = Account.createFromPrivateKey(alicePrivateKeyPrivate, NetworkType.MIJIN_TEST);
const tx3 = SecretProofTransaction.create(
Deadline.create(),
HashType.SHA3_512,
secret,
proof,
NetworkType.MIJIN_TEST
);
const tx3Signed = aliceAccountPrivate.sign(tx3);
console.log('tx3Signed.hash : ' + tx3Signed.hash);
console.log('tx3Signed.signer : ' + tx3Signed.signer);
transactionHttpPrivate.announce(tx3Signed).subscribe(
x => console.log(x),
err => console.error(err)
);
secretlock4.js
Publicチェーンで、BobがXEMを引き出すトランザクションです。これをTX4
とします。
Bobは、AliceのTX3
をチェーン上で確認します。TX3
のXEMを受け取るには、秘密の値proof
を埋め込んだSecretProofトランザクションを作成しなければなりません。
BobはTX3
に埋め込まれたproof
を見ることができます。これを使ってSecretProofトランザクションを作成します。
今回、proof
の値は、base64エンコードされた文字列を引数で渡すようにしました。
// const alicePrivateKeyPrivate = '8CBA73B9DF31D85DCFA4B9E1D3B7A88E5A5F32C930FCC45FE5A457619F3E6687';
// const aliceAccountPrivate = Account.createFromPrivateKey(alicePrivateKeyPrivate, NetworkType.MIJIN_TEST);
// const alicePrivateKeyPublic = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
// const aliceAccountPublic = Account.createFromPrivateKey(alicePrivateKeyPublic, NetworkType.MIJIN_TEST);
const bobPrivateKeyPrivate = 'BA46F91D7BCD40B6482E138F0D3D53A29F0A6097B5C4586C6ABD81A3BA3A2DAD';
const bobAccountPrivate = Account.createFromPrivateKey(bobPrivateKeyPrivate, NetworkType.MIJIN_TEST);
const bobPrivateKeyPublic = '31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E';
const bobAccountPublic = Account.createFromPrivateKey(bobPrivateKeyPublic, NetworkType.MIJIN_TEST);
// ***************************************************
// Bob Unlock (Public)
// ***************************************************
if (process.argv.length <= 2) {
throw new Error("need proof (base64 string)");
}
const proof = Buffer.from(process.argv[2], "base64").toString('hex').toUpperCase();
const hash = sha3_512.create();
const secret = hash.update(Buffer.from(proof, 'hex')).hex().toUpperCase();
// Bob spends TX1 transaction by sending SecretProofTransaction (in PUBLIC network)
// const bobPrivateKeyPublic = '31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E';
// const bobAccountPublic = Account.createFromPrivateKey(bobPrivateKeyPublic, NetworkType.MIJIN_TEST);
const tx4 = SecretProofTransaction.create(
Deadline.create(),
HashType.SHA3_512,
secret,
proof,
NetworkType.MIJIN_TEST
);
const tx4Signed = bobAccountPublic.sign(tx4);
console.log('tx4Signed.hash : ' + tx4Signed.hash);
console.log('tx4Signed.signer : ' + tx4Signed.signer);
transactionHttpPublic.announce(tx4Signed).subscribe(
x => console.log(x),
err => console.error(err)
);
実行結果
初期の状態
Public | Private | |
---|---|---|
Alice | 409089785 nem:xem | 818181668 nem:xem 0 foo:bar |
Bob | 409090163 nem:xem | 100 foo:bar |
# node secretlock_disp.js
[1528285555168] Alice(Private) have 818181668 nem:xem
[1528285555168] Bob(Private) have 100 foo:bar
[1528285555168] Alice(Public) have 409089785 nem:xem
[1528285555168] Bob(Public) have 409090163 nem:xem
TX1
Publicチェーンにて、AliceからBobへ10XEM送りました。
# node secretlock1.js
x (proof) : 095b4fcd1f88f1785e59
H(x) (secret) : B271D49970445F078CAD6979B75642B55C14DFAADF8067FE450332C63F60DE622B9DC1E6C02C96E690D4BC2E50BA8E8A0F3C065D98668D545C20E1A97B141B9D
tx1Signed.hash : 7006B34B0C2397004CA37E1723EDA985093613549382EB96D9CB620B1519C4E3
tx1Signed.signer : 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
TransactionAnnounceResponse {
message: 'packet 9 was pushed to the network via /transaction' }
Bob気づく
Bobは、TX1の存在に気づくことができるようになります。
ここでは、Bobの公開鍵を指定して、トランザクションを検索しています。secretが何かを把握することができます。
# curl http://localhost:3000/account/3390BF02D2BB59C8722297FF998CE89183D0906E469873284C091A5CDC22FD57/transactions | jq .[0]
{
"meta": {
"height": [
61098,
0
],
"hash": "7006B34B0C2397004CA37E1723EDA985093613549382EB96D9CB620B1519C4E3",
"merkleComponentHash": "7006B34B0C2397004CA37E1723EDA985093613549382EB96D9CB620B1519C4E3",
"index": 0,
"id": "5B17CC0A80FDB30001360936"
},
"transaction": {
"signature": "2D27C913CEFB82671A69B39421B78DE4EB66431216005D4789ED6A4E9F6E22265F87E3DC2A4D99EE7456B78835938B7E599C2EC641A922D5B8BD7861E9A2370F",
"signer": "5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C",
"version": 36867,
"type": 16972,
"fee": [
0,
0
],
"deadline": [
104496673,
16
],
"duration": "60",
"mosaicId": "-3087871471161192663",
"amount": "100000000",
"hashAlgorithm": 0,
"secret": "snHUmXBEXweMrWl5t1ZCtVwU36rfgGf+RQMyxj9g3mIrncHmwCyW5pDUvC5Quo6KDzwGXZhmjVRcIOGpexQbnQ==",
"recipient": "kHWOtHwo1hQ7qj3mqNnDGbUDob/Y54np4g=="
}
}
TX2
Privateチェーンで、BobからAliceへfoo:bar
を送ります。その時に、先ほどゲットしたsecretを使います。
# node secretlock2.js snHUmXBEXweMrWl5t1ZCtVwU36rfgGf+RQMyxj9g3mIrncHmwCyW5pDUvC5Quo6KDzwGXZhmjVRcIOGpexQbnQ==
tx2Signed.hash : 3ED705A3DD796C12A8F69CB29315576943E97C4996EAB430D3077CC546EB09DB
tx2Signed.signer : 1C650F49DD67EC50BFDEA40906D32CDE3C969BDF58837C7DA320829BDDE96150
TransactionAnnounceResponse {
message: 'packet 9 was pushed to the network via /transaction' }
Alice気づく
Aliceの公開鍵を指定して、トランザクションを検索。secretが一致していること、signerがBobであること、もしくはトランザクションハッシュ値をBobから教えてもらう、など。
# curl http://localhost:3100/account/C36F5BDDE8B2B586D17A4E6F4B999DD36EBD114023C1231E38ABCB1976B938C0/transactions | jq .[0]
{
"meta": {
"height": [
50499,
0
],
"hash": "3ED705A3DD796C12A8F69CB29315576943E97C4996EAB430D3077CC546EB09DB",
"merkleComponentHash": "3ED705A3DD796C12A8F69CB29315576943E97C4996EAB430D3077CC546EB09DB",
"index": 0,
"id": "5B17CC4EFABE9E0001404C3F"
},
"transaction": {
"signature": "CE674E5378D3237311915613CB41C087F3A1A67210BFEA0BD607580B617A22226C4D0F13E7F6126AB9C4143982418658CED082BC909EB5518918CB2200215B05",
"signer": "1C650F49DD67EC50BFDEA40906D32CDE3C969BDF58837C7DA320829BDDE96150",
"version": 36867,
"type": 16972,
"fee": [
0,
0
],
"deadline": [
104554127,
16
],
"duration": "10",
"mosaicId": "-1412091718288690897",
"amount": "1",
"hashAlgorithm": 0,
"secret": "snHUmXBEXweMrWl5t1ZCtVwU36rfgGf+RQMyxj9g3mIrncHmwCyW5pDUvC5Quo6KDzwGXZhmjVRcIOGpexQbnQ==",
"recipient": "kFsRzZnbC8iVsAayDezf4Q00NEq+iHxg+A=="
}
}
状況
お互いにSecret Lockトランザクションを送りあった状況です。
それぞれ、送った量のモザイクが減っているだけです。
Public | Private | |
---|---|---|
Alice | 409089785 → 409089685 nem:xem | 818181668 nem:xem 0 foo:bar |
Bob | 409090163 nem:xem | 100 → 99 foo:bar |
# node secretlock_disp.js
[1528285679620] Bob(Private) have 99 foo:bar
[1528285679620] Alice(Private) have 818181668 nem:xem
[1528285679620] Alice(Public) have 409089685 nem:xem
[1528285679620] Bob(Public) have 409090163 nem:xem
TX3
Privateチェーンで、AliceはSecret Proofトランザクションを作ります。同時に、チェーン上でproofを公開することになります。
# node secretlock3.js 095b4fcd1f88f1785e59
tx3Signed.hash : 924D2FEAB00ECFB4A0C726C82AEA05FEE92125EBD54729E1C1E1E1FA66C5BEA8
tx3Signed.signer : C36F5BDDE8B2B586D17A4E6F4B999DD36EBD114023C1231E38ABCB1976B938C0
TransactionAnnounceResponse {
message: 'packet 9 was pushed to the network via /transaction' }
Bob気づく
Bobは、Privateチェーン上に送信されたTX3を見て、proofが何かを知ることができます。
Bobの公開鍵を指定して、トランザクションを検索。signerがAliceで、secretが一致しているかどうか。もしくはトランザクションハッシュ値をAliceから教えてもらうか。もしくは、Aliceから直接proofを教えてもらってもよいと思います。
# curl http://localhost:3100/account/C36F5BDDE8B2B586D17A4E6F4B999DD36EBD114023C1231E38ABCB1976B938C0/transactions | jq .[0]
{
"meta": {
"height": [
50504,
0
],
"hash": "924D2FEAB00ECFB4A0C726C82AEA05FEE92125EBD54729E1C1E1E1FA66C5BEA8",
"merkleComponentHash": "924D2FEAB00ECFB4A0C726C82AEA05FEE92125EBD54729E1C1E1E1FA66C5BEA8",
"index": 0,
"id": "5B17CCA3FABE9E0001404C46"
},
"transaction": {
"signature": "89B1840CFF796447698D7703ED0D25894817E05E97579C63DA9B143B4AF11F6CA820F1D5D3AB402E2EF33C7D74849216959AC257656809176ECC579D35901507",
"signer": "C36F5BDDE8B2B586D17A4E6F4B999DD36EBD114023C1231E38ABCB1976B938C0",
"version": 36867,
"type": 17228,
"fee": [
0,
0
],
"deadline": [
104639747,
16
],
"hashAlgorithm": 0,
"secret": "snHUmXBEXweMrWl5t1ZCtVwU36rfgGf+RQMyxj9g3mIrncHmwCyW5pDUvC5Quo6KDzwGXZhmjVRcIOGpexQbnQ==",
"proof": "CVtPzR+I8XheWQ=="
}
}
TX4
Publicチェーン上で、BobがSecret Proofトランザクションを作ります。
# node secretlock4.js CVtPzR+I8XheWQ==
tx4Signed.hash : C2CA04BA34FDEE75302788FA409A2584D3E1EEBDF42DF0373B235FA29EE0CA70
tx4Signed.signer : 3390BF02D2BB59C8722297FF998CE89183D0906E469873284C091A5CDC22FD57
TransactionAnnounceResponse {
message: 'packet 9 was pushed to the network via /transaction' }
Alice気づく
Aliceは、Bobがちゃんと引き出したかどうかを確認できます。しなくてもよいです。
# curl http://localhost:3000/account/3390BF02D2BB59C8722297FF998CE89183D0906E469873284C091A5CDC22FD57/transactions | jq .[0]
{
"meta": {
"height": [
61110,
0
],
"hash": "C2CA04BA34FDEE75302788FA409A2584D3E1EEBDF42DF0373B235FA29EE0CA70",
"merkleComponentHash": "C2CA04BA34FDEE75302788FA409A2584D3E1EEBDF42DF0373B235FA29EE0CA70",
"index": 0,
"id": "5B17CCD280FDB30001360944"
},
"transaction": {
"signature": "24D508D89AC0BA07CEBE95657E6428101A1818000CC8C428578FEA2CB6249757F9F585D0474AE2C264F69D2434DD2D1AADA96236750A080616CD99EC983D250A",
"signer": "3390BF02D2BB59C8722297FF998CE89183D0906E469873284C091A5CDC22FD57",
"version": 36867,
"type": 17228,
"fee": [
0,
0
],
"deadline": [
104692571,
16
],
"hashAlgorithm": 0,
"secret": "snHUmXBEXweMrWl5t1ZCtVwU36rfgGf+RQMyxj9g3mIrncHmwCyW5pDUvC5Quo6KDzwGXZhmjVRcIOGpexQbnQ==",
"proof": "CVtPzR+I8XheWQ=="
}
}
最終状態
いい感じになりました。
Public | Private | |
---|---|---|
Alice | 409089785 → 409089685 nem:xem | 818181668 nem:xem 0 → 1 foo:bar |
Bob | 409090163 → 409090263 nem:xem | 100 → 99 foo:bar |
# node secretlock_disp.js
[1528285803662] Bob(Private) have 99 foo:bar
[1528285803662] Alice(Private) have 1 foo:bar
[1528285803662] Alice(Private) have 818181668 nem:xem
[1528285803662] Bob(Public) have 409090263 nem:xem
[1528285803662] Alice(Public) have 409089685 nem:xem
Secret Lockトランザクションの有効期限
これまでまったく触れてきませんでしたが、Secret Lockトランザクションには、Deadlineとは別にDurationという期間を設定します。単位はブロック数だったはず。
これを過ぎると、そのSecret Lockトランザクションは無効になり、持ち主のもとへ返されます。
なので、有効期限は、TX1 > TX2 でなければなりません。TX3によってproofが開示された時点で、TX1が期限切れだった場合、TX4が送信できないからです。