前置き
Cosignature Transactionは、Aggregate Bonded Transactionに署名する時に使います。
このトランザクションを作成するときは、はじめに引数としてAggregate Transactionをとります。
次に、秘密鍵で署名をすると、Cosignature Sined Transactionになり、アナウンス可能になります[1]。
アナウンスするときも、それ専用のものが用意されています[2]。
これがどういう処理になっているのか気になったので、調べてみます。
手法がわかったら、なるべくデータを扱う方法で、実行してみます。
環境
Alpaca版
Aggregate Bonded Transactionの基本的な流れ
このようなシーンを想定します。トランジションを作るのはAliceです。
- Aliceは、必要な内部トランザクションを作成する
- Aliceは、Aggregate Bonded Transactionを作成する
- Aliceは、LockFunds Transactionを作成し、送信する
- Aliceは、Aggregate Bonded Transactionを送信する
- Carolは、ネットワークからAggregate Bonded Transactionを取りに行く
- Carolは、Cosignature Transactionを作成する
- Carolは、署名してCosignature Signed Transactionを作成する
- Carolは、Cosignature Signed Transactionを送信する
今回は、6~8を詳しく見ていきます
調べてみた
Cosignature Transactionを作成する
/**
* Create a cosignature transaction
* @param transactionToCosign - Transaction to cosign.
* @returns {CosignatureTransaction}
*/
public static create(transactionToCosign: AggregateTransaction) {
if (transactionToCosign.isUnannounced()) {
throw new Error('transaction to cosign should be announced first');
}
return new CosignatureTransaction(transactionToCosign);
}
まず、CosignatureTransaction.create
によって作成します。
ここで、引数にとったAggregate Transactionは、isUnannounced()
で、TransactionInfoがあるかチェックされます。オフラインでトランザクション作成しただけでは、ここではじかれます。
なので、AccountHttpを使って、ネットワークから取得するのが通常の方法のようです。
そのあと、コンストラクタが呼ばれます。
/**
* @param transactionToCosign
*/
constructor(/**
* Transaction to cosign.
*/
public readonly transactionToCosign: AggregateTransaction) {
}
何もないっぽい。
署名してCosignature Signed Transactionを作成する
/**
* @internal
* Serialize and sign transaction creating a new SignedTransaction
* @param account
* @returns {CosignatureSignedTransaction}
*/
public signWith(account: Account): CosignatureSignedTransaction {
const aggregateSignatureTransaction = new CosignaturetransactionLibrary(this.transactionToCosign.transactionInfo!.hash);
const signedTransactionRaw = aggregateSignatureTransaction.signCosignatoriesTransaction(account);
return new CosignatureSignedTransaction(signedTransactionRaw.parentHash,
signedTransactionRaw.signature,
signedTransactionRaw.signer);
}
Cosignature Transactionが作成されたので、署名を作成してCosignature Signed Transactionにします。
CosignatureTransaction.signWith
によって署名が作成されます。まずは、これの流れを見ていきます。
最初に、Aggregate TransactionのHashを引数にして、CosignaturetransactionLibrary
のインスタンスを作成し、aggregateSignatureTransaction
に格納する。
ソースを追っていったけど、基本的には変数を格納する処理でした。
次に、署名を作成する処理。
CosignaturetransactionLibrary.signCosignatoriesTransaction
を呼び出している。
CosignaturetransactionLibrary
は、下記のimport文を見ると、nem2-libraryのCosignatureTransactionってことのようだ。
import {CosignatureTransaction as CosignaturetransactionLibrary} from 'nem2-library';
なので、実際の処理は、[4] CosignatureTransaction.jsの親クラス[5] VerifiableTransaction.jsに書いてある。
/**
* @param {KeyPair} keyPair KeyPair instance
* @returns {module:model/TransactionPayload} Returns TransactionPayload instance
*/
signCosignatoriesTransaction(keyPair) {
const signature = KeyPair.sign(keyPair, new Uint8Array(this.bytes));
return {
parentHash: convert.uint8ToHex(this.bytes),
signature: convert.uint8ToHex(signature),
signer: keyPair.publicKey
};
}
これを見ると、単純にAggregate Transactionのハッシュ値に対して署名をしているようです。
戻り値には、parentHash
、signature
、signer
が揃っているので、これを使ってCosignatureSignedTransactionを作成する。
Cosignature Signed Transactionを送信する
/**
* Send a cosignature signed transaction of an already announced transaction
* @param cosignatureSignedTransaction - Cosignature signed transaction
* @returns Observable<TransactionAnnounceResponse>
*/
public announceAggregateBondedCosignature(
cosignatureSignedTransaction: CosignatureSignedTransaction): Observable<TransactionAnnounceResponse> {
return Observable.fromPromise(this.transactionRoutesApi.announceCosignatureTransaction(cosignatureSignedTransaction))
.map((transactionAnnounceResponseDTO) => {
return new TransactionAnnounceResponse(transactionAnnounceResponseDTO.message);
});
}
アナウンスしているのはこの箇所かな
this.transactionRoutesApi.announceCosignatureTransaction(cosignatureSignedTransaction)
/**
* Creates cosignature transaction
* Announce a cosignature transaction to the network
* @param {module:model/TransactionPayload} payload Transaction payload
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing data of type {@link Object} and HTTP response
*/
announceCosignatureTransactionWithHttpInfo(payload) {
let postBody = payload;
// verify the required parameter 'payload' is set
if (payload === undefined || payload === null) {
throw new Error("Missing the required parameter 'payload' when calling announceCosignatureTransaction");
}
let pathParams = {};
let queryParams = {};
let headerParams = {};
let formParams = {};
let authNames = [];
let contentTypes = [];
let accepts = ['application/json'];
let returnType = Object;
return this.apiClient.callApi(
'/transaction/cosignature', 'PUT',
pathParams, queryParams, headerParams, formParams, postBody,
authNames, contentTypes, accepts, returnType
);
}
APIのエンドポイントは、/transaction/cosignature
にPUTで、
送信データはpostBody
、これはCosignatureSignedTransaction
のインスタンスそのもの。
CosignatureSignedTransaction
は、parentHash
、signature
、signer
を持ってる
やってみる
Aggregate Transactionの作成と送信
まず、Aggregate TransactionとLockFunds Transactionの作成と送信。
コードはこれを使う。
$ node aggregate.js
lockFundsTransactionSigned.hash : 4FA5CF0063BE9577F0A39DB0D69B99404108021C1D6E5EE44A9F686922019546
lockFundsTransactionSigned.signer: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
aggregateTransactionSigned.hash : 222C1C99033BC372A655042C311CA5251279AD47907A6E2804DDAB606851CB3A
aggregateTransactionSigned.signer: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
TransactionAnnounceResponse {
message: 'packet 9 was pushed to the network via /transaction' }
TransactionAnnounceResponse {
message: 'packet 500 was pushed to the network via /transaction/partial' }
送信したAggregate Transactionのハッシュ値は、このような値になります。
222C1C99033BC372A655042C311CA5251279AD47907A6E2804DDAB606851CB3A
これにCosignerの秘密鍵で署名を作成します。
Cosignature Signed Transactionを作成する
コードはこれ
const nem2lib = require("nem2-library");
const signData = `222C1C99033BC372A655042C311CA5251279AD47907A6E2804DDAB606851CB3A`;
const privateKey = 'B332E3CA7B31D0BC663232B66D7C282BC2FE1DC0C01BB0159586A2CBEADD6B2A';
const keypair = nem2lib.KeyPair.createKeyPairFromPrivateKeyString(privateKey);
const signature = nem2lib.KeyPair.sign(keypair, signData);
console.log('publicKey: ' + nem2lib.convert.uint8ToHex(keypair.publicKey));
console.log('signature: ' + nem2lib.convert.uint8ToHex(signature));
# node sign.js
publicKey: 543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174
signature: B3641A69BEC08631036B984A6206503EE928DDD3902D083D9C28030B88FD36D40EC397ACB0FFEADCC03225CF9288F08D50F5D1B506FBB0C0A47B469A3C4B4E0C
Aggregate Transactionのハッシュ値は、これ。
222C1C99033BC372A655042C311CA5251279AD47907A6E2804DDAB606851CB3A
そして、これに署名をした結果は、このようになりました。
B3641A69BEC08631036B984A6206503EE928DDD3902D083D9C28030B88FD36D40EC397ACB0FFEADCC03225CF9288F08D50F5D1B506FBB0C0A47B469A3C4B4E0C
署名者の公開鍵は、こちら。
543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174
この3つのデータをそのまま送信してもよいし、nem2-sdkでCosignatureSignedTransactionインスタンスを作ってもよいと思う。
今回はそのまま送信する。
Cosignature Signed Transactionを送信する
それでは、このデータを送信します。
{ "parentHash": "222C1C99033BC372A655042C311CA5251279AD47907A6E2804DDAB606851CB3A",
"signature": "B3641A69BEC08631036B984A6206503EE928DDD3902D083D9C28030B88FD36D40EC397ACB0FFEADCC03225CF9288F08D50F5D1B506FBB0C0A47B469A3C4B4E0C",
"signer": "543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174" }
PUTしなければならないので、今回はcurl
を使います。
REST用の便利なツールがたくさんあるので探してみるのもいいかもしれません。
Insomniaとかも便利だと思います。
$ curl -X PUT -H 'Content-Type:application/json' -d '{ "parentHash": "222C1C99033BC372A655042C311CA5251279AD47907A6E2804DDAB606851CB3A", "signature": "B3641A69BEC08631036B984A6206503EE928DDD3902D083D9C28030B88FD36D40EC397ACB0FFEADCC03225CF9288F08D50F5D1B506FBB0C0A47B469A3C4B4E0C", "signer": "543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174" }' http://localhost:3000/transaction/cosignature
{"message":"packet 501 was pushed to the network via /transaction/cosignature"}
ちゃんとconfirmedになったかどうか
$ curl http://localhost:3000/transaction/222C1C99033BC372A655042C311CA5251279AD47907A6E2804DDAB606851CB3A/status
{"group":"confirmed","status":"Success","hash":"222C1C99033BC372A655042C311CA5251279AD47907A6E2804DDAB606851CB3A","deadline":[1650699429,16],"height":[161822,0]}
confirmedになってるし、ブロック高161822に取り込まれたことがわかります
Refference
[4] https://github.com/nemtech/nem2-library-js/blob/master/src/transactions/CosignatureTransaction.js
[5] https://github.com/nemtech/nem2-library-js/blob/master/src/transactions/VerifiableTransaction.js
[7] https://github.com/nemtech/nem2-library-js/blob/master/src/api/TransactionRoutesApi.js
関連
nem catapult バイトレベルで理解する その1 トランザクションハッシュ
https://qiita.com/planethouki/items/d7a7fc7adf8c8bc48668
nem catapult バイトレベルで理解する その2 TransferTransaction
https://qiita.com/planethouki/items/025d44f2ebe6cfcb4b1f
nem catapult バイトレベルで理解する その3 トランザクション署名
https://qiita.com/planethouki/items/932d96ee17bd4f305368
nem catapult バイトレベルで理解する その4 Cosignature Transaction
https://qiita.com/planethouki/items/fcac3c9d329b0278c4b7
nem catapult バイトレベルで理解する その5 SecretLock/Proof Transaction
https://qiita.com/planethouki/items/4c91c2e6c722283c50f1
nem catapult バイトレベルで理解する その6 空ブロック
https://qiita.com/planethouki/items/da717595a1ff58d0d84f