はじめに
Symbolブロックチェーンを使用したブラウザゲーム「The Tower」をUnityにて開発しました。
その中で、疑似的なDEX(分散型取引所)を作成しましたのでトランザクション等共有します。
なぜ「疑似」という言い方をしているかと言うと、ゲームアカウントの秘密鍵を使用しているからです。
以下、仕組みを説明し、最後にソースコード全文を貼り付けておきます。
※なお、ゲーム内では金銭的価値の無いモザイクのみの交換、かつ取引所(ゲーム運営)は一切の手数料を徴収していません。必要な手数料はSymbolのネットワーク手数料のみです。
使用するトランザクション
Symbolのシークレットロック&プルーフを主に利用します。あと、暗号化メッセージも大切です。
シークレットロック&プルーフについては公式はおそらくここしかなかった…
シークレットロック&プルーフは主にクロスチェーンスワップに利用することを目的としているようで公式では以下のような記事は見つかりました。
シークレットロックトランザクションとは、 簡単に言うとトランザクションをアナウンスした時には仮で送金がロックされている状態になり、決められた期限を超えるか、受け取り者が送金者しか知らない文字列を含むトランザクションをアナウンスするまでロックされます。(つまり送金者が受信を認めた状態)
具体的には、トランザクション発行時に送金者がプルーフをランダムに、そのプルーフを元にシークレットをSHA3というハッシュ関数で生成します。そのうちシークレットはチェーン上に記録され公開されていますが、プルーフは公開されていません。(シークレットなのに…)
受け取り者が何らかの条件を満たし、送金者がプルーフを教えれば受け取ることができます。そしてそれがシークレットプルーフトランザクションです。(送金者がプルーフトランザクションをアナウンスすることも可能)
仕組み
それでは、このシークレットロック&プルーフを使ってどのように擬似取引所を作成するか解説します。
取引を希望するのがアリス。その取引に応じるのがボブ。そして取引所の3者が出現します。
板の作成
まず、アリス(板取引を始める人)が自分が欲しいモザイクと自分が提供可能なモザイク、またそれらの数量を決めます。
例)
モザイクA1つを提供し、その交換としてモザイクBを1つ希望する場合。
トランザクションを3つ作成し、それらをアグリゲートトランザクションでアナウンスします。
※全て受け取り手は取引所アカウントで、署名者はアリスです。
1−1.シークレットロックトランザクション
アリスが提供するモザイクAとその数量を取引所アカウントに送金するシークレットロックトランザクション。
1−2.プルーフの送信用トランザクション
シークレットロック生成時に作成したプルーフを暗号化しメッセージとして添えて取引所アカウントに送信するトランスファートランザクション。
1−3.取引希望の詳細を明記したトランザクション
希望するモザイクBと数量の情報を記載したメッセージ付きのトランスファートランザクション。
これらをアグリゲートトランザクションでアナウンスすれば板は作成されます。
あとは、WEBアプリケーションなどで公開されている1と3のトランザクションを元に板一覧などを作成します。
取引に応じる
板一覧を見たユーザーは自分が欲しいモザイクを提供してくれる板を探し、良いものが見つかれば取引に応じます。
そこでも同じように3つのトランザクションをアグリゲートトランザクションでアナウンスします。
順番を間違えると送金が成立しません、これがSymbolのアグリゲートトランザクションの素晴らしい所でもあるので後述します。
2−1.シークレットプルーフトランザクション
取引所アカウントはアリスから受け取った暗号化されたプルーフを自身の秘密鍵で復号します。そしてそのプルーフを持ってトランザクションを生成します。
※署名者は取引所アカウント
2−2.取引に応じるトランザクション
ボブの署名でアリスへモザイクBを送金するトランスファートランザクション
2−3.最後のトランザクション
取引所の署名でボブへモザイクAを送金するトランスファートランザクション
これらをアグリゲートトランザクションでアナウンスします。
面白いのが取引所アカウントはこのアグリゲートトランザクションを生成する時点ではモザイクAを所有していません。しかし、トランザクション1で瞬間的に所有することになるので、最後の3で送金が可能です。なので1は3より前に来る必要があります。
以上で取引は完了です。アリスはモザイクBを、ボブはモザイクAを入手しました。ただ、途中に取引所が介入しており、自動で成立させるためにはどこかに取引所の秘密鍵を置いておく必要があるため、疑似という表現をします。
他に何か良い案などあれば教えていただければ幸いです。
ソースコード全文
1のアグリゲートトランザクション
function aliceTransactions(){
const deadLine = Deadline.create(ea);
const networkType = NetworkType.TEST_NET;
const aliceAccount = Account.createFromPrivateKey("alicePrivateKey", networkType);
const exchangePublicAccount = PublicAccount.createFromPublicKey("exchangePublicKey", networkType);
const random = Crypto.randomBytes(20);
// proofの作成
const proof = random.toString('hex');
const hash = sha3_256.create();
// secretの作成
const secret = hash.update(random).hex().toUpperCase();
const order = {
orderMozaicId : "mosaicBのID",
orderMozaicQuantitiy : 1,
}
// orderの内容をJson化
const jsonOrder = JSON.stringify(order);
// 1-1
const lockTx = SecretLockTransaction.create(
deadLine,
new Mosaic(new MosaicId("mosaicAのID"), UInt64.fromUint(1)),
UInt64.fromUint(10000),
LockHashAlgorithm.Op_Sha3_256,
secret,
exchangePublicAccount.address,
networkType
)
// 1-2
const sendProofTx = TransferTransaction.create(
deadLine,
exchangePublicAccount.address,
[],
aliceAccount.encryptMessage(proof, exchangePublicAccount),
networkType
)
// 1-3
const infoTx = TransferTransaction.create(
deadLine,
exchangePublicAccount.address,
[],
PlainMessage.create(jsonOrder),
networkType
)
// Aggregate
const agg = AggregateTransaction.createComplete(
deadLine,
[
lockTx.toAggregate(aliceAccount.publicAccount),
sendProofTx.toAggregate(aliceAccount.publicAccount),
infoTx.toAggregate(aliceAccount.publicAccount),
],
networkType,
[]
).setMaxFeeForAggregate(100, 0);
const sighedTx = aliceAccount.sign(agg, networkGenerationHash)
}
2のアグリゲートトランザクション
1のトランザクションHashから各情報は取得しておいてください。
function bobTransactions(){
const exchangeAccount = Account.createFromPrivateKey("exchangePrivateKey", networkType);
const alicePublicAccount = PublicAccount.createFromPublicKey("alicePublicKey", networkType);
const bobAccount = Account.createFromPrivateKey("bobPrivateKey", networkType);
const encryptedMessage = new EncryptedMessage("暗号化されたProof");
const decryptedProof = exchangeAccount.decryptMessage(encryptedMessage, exchangeAccount.publicAccount).payload;
const secret = "secret取得しておく";
const proofTx = SecretProofTransaction.create(
deadLine,
LockHashAlgorithm.Op_Sha3_256,
secret,
exchangeAccount.address,
decryptedProof,
networkType
)
const returnTx = TransferTransaction.create(
deadLine,
alicePublicAccount.address,
[new Mosaic(new MosaicId("mosaicBのID"), UInt64.fromUint(1))],
EmptyMessage,
networkType
)
const lastTx = TransferTransaction.create(
deadLine,
bobAccount.address,
[new Mosaic(new MosaicId("mosaicAのID"), UInt64.fromUint(1))],
EmptyMessage,
networkType
)
const aggTx = AggregateTransaction.createComplete(
deadLine,
[
proofTx.toAggregate(exchangeAccount.publicAccount),
returnTx.toAggregate(bobAccount.publicAccount),
lastTx.toAggregate(exchangeAccount.publicAccount)
],
networkType,
[]
).setMaxFeeForAggregate(100, 1)
const signedTx = bobAccount.signTransactionWithCosignatories(
aggTx,
[exchangeAccount],
networkGenerationHash
)
}
最後に
以上がトランザクションのソースコードです。
2のトランザクションを作成するには1のトランザクションから情報を取得する必要がありますのでそれらはご自身でお願いします。
SymbolはEthereumと比較すると自由度は低いのかもしれませんが、色々なトランザクションがプロトコルレベルで実装されているためアイデアを練ればある程度のことはできると思います。
自分でスマコンを書くのはおそらくとてつもない責任感が生じる(すみません、書いたことないです)のではないか?と思っていて、それに引き換えSymbolに関してはブロックチェーン側は何もせず用意されたものを使い、自分の得意な範囲で開発できるので、とてもおすすめです。
読みにくい文章を最後までお読みいただきありがとうございました。