Blockchain
NEM
catapult

nem catapult バイトレベルで理解する その3 トランザクション署名

Refference

[1] https://github.com/nemtech/nem2-library-js/blob/master/src/transactions/VerifiableTransaction.js

[2] https://github.com/nemtech/nem2-library-js/blob/master/src/crypto/keyPair.js

[3] https://nem.io/wp-content/themes/nem/files/NEM_techRef.pdf

[4] https://github.com/nemtech/nem2-library-js/blob/master/src/crypto/nacl_catapult.js

前置き

NEMは署名アルゴリズムにEdDSAのEd25519を使っている。さらに、ハッシュ関数にはKeccakのSha3を使っている。

(追記)プライベートチェーンでは、標準のSha3を使っているようです。一方、パブリックチェーン(現行NEM含むと思われる)ではKeccakが使われています。将来的にSDKでは、ネットワークタイプで場合分けをするようになるそうです。

この組み合わせについて、易しいドキュメントとかライブラリが見つけられなかったので、長らく避けてきたけど、そろそろ向き合わねば。

ここでは、TransferTransactionを例に挙げて、どのようなデータが署名対象となり、どのように署名は作成されるのかを調べる。

そして、実際に署名を作成してみる。

署名検証はやらない。

署名対象データ

[1]より。

VerifiableTransaction.js
/**
 * @param {KeyPair } keyPair KeyPair instance
 * @returns {module:model/TransactionPayload} - Signed Transaction Payload
 */
signTransaction(keyPair) {
    const byteBuffer = this.serialize();
    const signingBytes = byteBuffer.slice(4 + 64 + 32);
    const keyPairEncoded = KeyPair.createKeyPairFromPrivateKeyString(keyPair.privateKey);
    const signature = Array.from(KeyPair.sign(keyPair, new Uint8Array(signingBytes)));
    const signedTransactionBuffer = byteBuffer
        .splice(0, 4)
        .concat(signature)
        .concat(Array.from(keyPairEncoded.publicKey))
        .concat(byteBuffer
            .splice(64 + 32, byteBuffer.length));
    const payload = convert.uint8ToHex(signedTransactionBuffer);
    return {
        payload,
        hash: VerifiableTransaction.createTransactionHash(payload)
    };
}

serialize() {
    return this.schema.serialize(Array.from(this.bytes));
}

byteBufferが未署名トランザクション、
signingBytesが署名対象データ、
signatureが署名
だと思う。

そして、signedTransactionBufferのところが、未署名トランザクションに署名を入れていく処理。

未署名トランザクションデータ

ちょっとbyteBufferの中を覗いてみた

C500 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0390 5441 8096 9800 0000 0000 77A6 015D 1000 0000 9075 8EB4 7C28 D614 3BAA 3DE6 A8D9 C319 B503 A1BF D8E7 89E9 E201 0003 0043 B884 C898 706C 1380 9698 0000 0000 00C3 19EE 7712 43D8 3500 2D31 0100 0000 0029 CF5F D941 AD25 D580 9698 0000 0000 00

で、署名後のsignedTransaction.payloadはこうなってた

C500 0000 D44C B8AA 79C5 2D82 EA1B 7CE8 CAB8 6ACD 4CA6 5B83 DEF6 177C 42D8 6574 798A E380 368C 6AC3 FF45 8FC6 1855 7206 3AA7 E458 4A16 ADA1 5311 D3E1 B2B6 5EE9 13FD 0F0B 5D95 1328 2B65 A12A 1B68 DCB6 7DB6 4245 721F 7AE7 822B E441 FE81 3173 803C 512C 0390 5441 8096 9800 0000 0000 77A6 015D 1000 0000 9075 8EB4 7C28 D614 3BAA 3DE6 A8D9 C319 B503 A1BF D8E7 89E9 E201 0003 0043 B884 C898 706C 1380 9698 0000 0000 00C3 19EE 7712 43D8 3500 2D31 0100 0000 0029 CF5F D941 AD25 D580 9698 0000 0000 00

署名と公開鍵のところは0で埋まってる感じ

署名対象データ

以下から読み解ける

signingBytes = byteBuffer.slice(4 + 64 + 32);

サイズと署名と公開鍵以外のデータ全部。

ちょうど100byteより後ろのデータ全部。

署名

[2]より。

keyPair.js
sign: (m, pk, sk, hasher) => {
    const c = nacl.catapult;

    const d = new Uint8Array(Hash_Size);
    hasher.reset();
    hasher.update(sk);
    hasher.finalize(d);
    clamp(d);

    const r = new Uint8Array(Hash_Size);
    hasher.reset();
    hasher.update(d.subarray(Half_Hash_Size));
    hasher.update(m);
    hasher.finalize(r);

    const p = [c.gf(), c.gf(), c.gf(), c.gf()];
    const signature = new Uint8Array(Signature_Size);
    c.reduce(r);
    c.scalarbase(p, r);
    c.pack(signature, p);

    const h = new Uint8Array(Hash_Size);
    hasher.reset();
    hasher.update(signature.subarray(0, Half_Signature_Size));
    hasher.update(pk);
    hasher.update(m);
    hasher.finalize(h);

    c.reduce(h);

    // muladd
    const x = new Float64Array(Hash_Size);
    array.copy(x, r, Half_Hash_Size);

    for (let i = 0; i < Half_Hash_Size; ++i) {
        for (let j = 0; j < Half_Hash_Size; ++j)
            x[i + j] += h[i] * d[j];
    }

    c.modL(signature.subarray(Half_Signature_Size), x);
    encodedSChecker.requireValid(signature.subarray(Half_Signature_Size));
    return signature;
},

何をやっているのか、わからない。

NEMのホワイトペーパー[3]を見ながら、ゆっくりと読んでいく。

3.2 Signing and verification of a signatureを主軸に、3.1 Private and public keyも見ながら。

とにかく、目的は、署名には $R$ と $S$ という値が必要なので、これを導出していく。

その1

秘密鍵をハッシュして、計算する(説明しずらい)。

\begin{align}
H(k) &= (h_0,h_1,\dots,h_{511}) \\
a &= 2^{254}+\sum_{3\leq i \leq 254}{2^ih_i}
\end{align}

$H()$ はsha3_512、$k$ は秘密鍵、$a$ は名前がよくわからない。

keyPair.js
const d = new Uint8Array(Hash_Size);
hasher.reset();
hasher.update(sk);
hasher.finalize(d);
clamp(d);
clamp
function clamp(d) {
    d[0] &= 248;
    d[31] &= 127;
    d[31] |= 64;
}

hasher.update(sk);は秘密鍵のハッシュ。

clamp(d);は、・・・

248 = 1111 1000
127 = 0111 1111
64 = 0100 0000

なので、d[0]は下3bit分を0にし、d[31]は上1bitを0にし、d[31]の上2bit目を1にする。なんて言えばいいんだろう・・・

正解かはわからないど、$a$ はこんなイメージ。

a = (0, 0, 0, h_4,\dots,h_{253},1,0)

その2

r = H(h_{256},\dots,h_{511},M)

$M$ は署名対象データ

keyPair.js
const r = new Uint8Array(Hash_Size);
hasher.reset();
hasher.update(d.subarray(Half_Hash_Size));
hasher.update(m);
hasher.finalize(r);

dはclumpしたけど、ここではd[32:63]を使うのでclump処理は影響していない。なので、ここに限っては、秘密鍵のハッシュ $H(k)$ として使える。

秘密鍵のハッシュの一部と、署名対象データをつなぎ合わせてハッシュを取り、rに格納します。

その3

R = rB

$B$ は、Ed25519で定められた基点

keyPair.js
const p = [c.gf(), c.gf(), c.gf(), c.gf()];
const signature = new Uint8Array(Signature_Size);
c.reduce(r);
c.scalarbase(p, r);
c.pack(signature, p);

ちょっとここはよくわからないので、想像多めなのですが、

p = [c.gf(), c.gf(), c.gf(), c.gf()];で、エドワーズ曲線上の座標の入れ物を用意します。

[4]あたりを詳しくみていけば、わかるかも?

c.reduce(r);はたぶんだけど、位数を超えないように割り算している気がする。

c.scalarbase(p, r);によって、 $rB$ が計算され、結果 $R$ がpに格納される気がする。

$R$ は、署名に必要な2つの値のうち1つです。

c.pack(signature, p);で、signaturepを格納するんだと思う。

その4

S = ( r + H(\underline{R},\underline{A},M)a) \mod q

$\underline{R}$ は $R$ の圧縮表現、$\underline{A}$ は圧縮公開鍵、$q$ はEd25519で定められた位数です。

keyPair.js
const h = new Uint8Array(Hash_Size);
hasher.reset();
hasher.update(signature.subarray(0, Half_Signature_Size));
hasher.update(pk);
hasher.update(m);
hasher.finalize(h);

c.reduce(h);

// muladd
const x = new Float64Array(Hash_Size);
array.copy(x, r, Half_Hash_Size);

for (let i = 0; i < Half_Hash_Size; ++i) {
    for (let j = 0; j < Half_Hash_Size; ++j)
        x[i + j] += h[i] * d[j];
}


c.modL(signature.subarray(Half_Signature_Size), x);
encodedSChecker.requireValid(signature.subarray(Half_Signature_Size));
return signature;

まず、$H(\underline{R},\underline{A},M)$ を計算し、hに格納します。

hasher.update(signature.subarray(0, Half_Signature_Size));が$\underline{R}$ 。すでに署名の変数に格納してあるので、これを使うみたいです。

hasher.update(pk);が$\underline{A}$ 。

hasher.update(m);が$M$ 。

c.reduce(h);はわからん。

array.copy(x, r, Half_Hash_Size);rxにコピー。

x[i + j] += h[i] * d[j];では、xにはすでにrが入っているので、$H(\underline{R},\underline{A},M)a$ を加算する。これで、$S$ がxに格納される。

c.modL(signature.subarray(Half_Signature_Size), x);で、$\mod q$ をしつつ、xを署名に格納するんだろう。

感想

何をやっているのか、なんとなくわかった。

なんでこのような処理が必要なのかのところについては、あまりよくわかっていない。

圧縮表現について。座標上の点は、(x, y)でそれぞれ256bitの数値なので、合計512bitの領域が必要になります。けど、Ed25519の場合は、xの先頭1bitは必ず0になる。また、曲線の方程式は決まっているので、xがわかれば、yは2つに定まる。それで、どっちのyなのかをxの先頭1bitで表現することにより、(x,y)が256bitで表現できるようになる。っていうのが[3]の[2]に書いてあるそうな。

署名アルゴリズムに乱数がまったく出てこないので、署名対象データが同じなら、同じ署名が作成される。ECDSAは乱数kによって署名が変わってくる。

私はこのあたりの知識を、現代暗号入門 いかにして秘密は守られるのか (ブルーバックス) で勉強させていただきました。手のひらサイズで240ページなのに、本質がつまっていてわかりやすく読みやすかったです。ただし、EdDSAについては書いてないので、そこは自分でやる必要があります。

ちょっと署名やってみる

データ

正解のpayload

C500 0000 D44C B8AA 79C5 2D82 EA1B 7CE8 CAB8 6ACD 4CA6 5B83 DEF6 177C 42D8 6574 798A E380 368C 6AC3 FF45 8FC6 1855 7206 3AA7 E458 4A16 ADA1 5311 D3E1 B2B6 5EE9 13FD 0F0B 5D95 1328 2B65 A12A 1B68 DCB6 7DB6 4245 721F 7AE7 822B E441 FE81 3173 803C 512C 0390 5441 8096 9800 0000 0000 77A6 015D 1000 0000 9075 8EB4 7C28 D614 3BAA 3DE6 A8D9 C319 B503 A1BF D8E7 89E9 E201 0003 0043 B884 C898 706C 1380 9698 0000 0000 00C3 19EE 7712 43D8 3500 2D31 0100 0000 0029 CF5F D941 AD25 D580 9698 0000 0000 00

これの、100byteより後ろが署名対象なので、

0390 5441 8096 9800 0000 0000 77A6 015D 1000 0000 9075 8EB4 7C28 D614 3BAA 3DE6 A8D9 C319 B503 A1BF D8E7 89E9 E201 0003 0043 B884 C898 706C 1380 9698 0000 0000 00C3 19EE 7712 43D8 3500 2D31 0100 0000 0029 CF5F D941 AD25 D580 9698 0000 0000 00

これに署名をし、以下が得られれば完了。

D44C B8AA 79C5 2D82 EA1B 7CE8 CAB8 6ACD 4CA6 5B83 DEF6 177C 42D8 6574 798A E380 368C 6AC3 FF45 8FC6 1855 7206 3AA7 E458 4A16 ADA1 5311 D3E1 B2B6 5EE9 13FD 0F0B

コード

sign.js
const nem2lib = require("nem2-library");

/*
private: 7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4
public: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
address: SBWEUWON6IBHCW5IC4EI6V6SMTVJGCJWGLF57UGK
*/

const signData = `03905441809698000000000077A6015D1000000090758EB47C28D6143BAA3DE6A8D9C319B503A1BFD8E789E9E20100030043B884C898706C138096980000000000C319EE771243D835002D31010000000029CF5FD941AD25D58096980000000000`;

const privateKey = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
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: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
signature: D44CB8AA79C52D82EA1B7CE8CAB86ACD4CA65B83DEF6177C42D86574798AE380368C6AC3FF458FC6185572063AA7E4584A16ADA15311D3E1B2B65EE913FD0F0B

正解!おめでとう!

けっこう署名やってみる

以下のトランザクション全部に、それぞれ署名を作成してみる。

  • Transfer Transaction
  • Aggregate Complete Transaction
  • Aggregate Bonded Transaction
  • LockFunds Transaction
  • SecretLock Transaction
  • SecretProof Transaction
  • ModifyMultisigAccount Transaction
  • RegisterNamespace Transaction
  • ModifyMultisigAccount Transaction
  • MosaicSupplyChange Transaction
  • Cosignature Transaction

基本的に、4+64+32byteより後ろのデータに対して署名を行います。
Cosignature Txだけ例外で、Parent Hashに対して署名を行います。

その後に、payloadから抜き出した署名を表示して、一致していれば正解。

また、Cosignature Txは、元となるトランザクションがアナウンスされていないとエラーになるので、必要な分だけアナウンスしてます。

コード

sign2.js
const nem2lib = require("nem2-library");
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
    ModifyMultisigAccountTransaction = nem2Sdk.ModifyMultisigAccountTransaction,
    MultisigCosignatoryModificationType = nem2Sdk.MultisigCosignatoryModificationType,
    MultisigCosignatoryModification = nem2Sdk.MultisigCosignatoryModification,
    RegisterNamespaceTransaction = nem2Sdk.RegisterNamespaceTransaction,
    MosaicDefinitionTransaction = nem2Sdk.MosaicDefinitionTransaction,
    MosaicSupplyChangeTransaction = nem2Sdk.MosaicSupplyChangeTransaction,
    MosaicProperties = nem2Sdk.MosaicProperties,
    MosaicSupplyType = nem2Sdk.MosaicSupplyType;

const sha3_512 = jssha3.sha3_512;

/*
Alice:
  private: 7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4
  public: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
  address: SBWEUWON6IBHCW5IC4EI6V6SMTVJGCJWGLF57UGK
Bob:
  private: 31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E
  public: 3390BF02D2BB59C8722297FF998CE89183D0906E469873284C091A5CDC22FD57
  address: SB2Y5ND4FDLBIO5KHXTKRWODDG2QHIN73DTYT2PC
Multisig:
  private: E0BC90AA55A8D78A1DC8CE203A95C31A62E1EAF43D7BF94379709B79D90E0545
  public: 4CA83B9053C907AC7D9A5A0C26895489C8D07ADC57EF9AF1B8045775E0C91D31
  address: SBHAN5NVIMDZBYTYPBIHCZ5I5EZZO2WSGJ6PGKFI
*/

const signAndDisplay = (signData, privateKey)  => {
    const keypair = nem2lib.KeyPair.createKeyPairFromPrivateKeyString(privateKey);
    const signature = nem2lib.KeyPair.sign(keypair, signData);
    console.log('signature: ' + nem2lib.convert.uint8ToHex(signature));
};

const payloadDisplay = (sinedTransaction) => {
    console.log('payload  : ' + sinedTransaction.payload.substring(8,136));
};


const alicePrivateKey = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
const aliceAccount = Account.createFromPrivateKey(alicePrivateKey,NetworkType.MIJIN_TEST);
const bobPrivateKey = '31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E';
const bobAccount = Account.createFromPrivateKey(bobPrivateKey,NetworkType.MIJIN_TEST);
const multisigPrivateKey = 'E0BC90AA55A8D78A1DC8CE203A95C31A62E1EAF43D7BF94379709B79D90E0545';
const multisigAccount = Account.createFromPrivateKey(multisigPrivateKey,NetworkType.MIJIN_TEST);


// ***************************************************
//             Transfer Transaction
// ***************************************************
// Alice to Bob
const transferTransaction = TransferTransaction.create(
    Deadline.create(),
    bobAccount.address,
    [new Mosaic(new MosaicId('nem:xem'), UInt64.fromUint(10000000))],
    PlainMessage.create(''),
    NetworkType.MIJIN_TEST,
);
const signedTransferTransaction = aliceAccount.sign(transferTransaction);
console.log('Transfer Transaction');
signAndDisplay(signedTransferTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(signedTransferTransaction);


// ***************************************************
//             Aggregate Complete Transaction
// ***************************************************
// Alice to Alice
const transferTransaction2 = TransferTransaction.create(
    Deadline.create(),
    aliceAccount.address,
    [new Mosaic(new MosaicId('nem:xem'), UInt64.fromUint(10000000))],
    PlainMessage.create(''),
    NetworkType.MIJIN_TEST,
);

const aggregateCompleteTransaction = AggregateTransaction.createComplete(
    Deadline.create(),
    [
        transferTransaction.toAggregate(aliceAccount.publicAccount),        // Alice to Bob
        transferTransaction2.toAggregate(aliceAccount.publicAccount)        // Alice to Alice
    ],
    NetworkType.MIJIN_TEST,
    []
);
const signedAggregateCompleteTransaction = aliceAccount.sign(aggregateCompleteTransaction);
console.log('Aggregate Complete Transaction');
signAndDisplay(signedAggregateCompleteTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(signedAggregateCompleteTransaction);


// ***************************************************
//             Aggregate Bonded Transaction
// ***************************************************
// Bob to Alice
const transferTransaction3 = TransferTransaction.create(
    Deadline.create(),
    aliceAccount.address,
    [new Mosaic(new MosaicId('nem:xem'), UInt64.fromUint(10000000))],
    PlainMessage.create(''),
    NetworkType.MIJIN_TEST,
);

const aggregateBondedTransaction = AggregateTransaction.createBonded(Deadline.create(),
    [
        transferTransaction.toAggregate(aliceAccount.publicAccount),        // Alice to Bob
        transferTransaction3.toAggregate(bobAccount.publicAccount),         // Bob to Alice
    ],
    NetworkType.MIJIN_TEST
);
const sinedAggregateBondedTransaction = aliceAccount.sign(aggregateBondedTransaction);
console.log('Aggregate Bonded Transaction');
signAndDisplay(sinedAggregateBondedTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(sinedAggregateBondedTransaction);


// ***************************************************
//             LockFunds Transaction
// ***************************************************
const lockFundsTransaction = LockFundsTransaction.create(
    Deadline.create(),
    new Mosaic( new MosaicId('nem:xem'), UInt64.fromUint(10000000)),
    UInt64.fromUint(480),
    sinedAggregateBondedTransaction,
    NetworkType.MIJIN_TEST
);
const signedLockFundsTransaction = aliceAccount.sign(lockFundsTransaction);
console.log('LockFunds Transaction');
signAndDisplay(signedLockFundsTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(signedLockFundsTransaction);


// ***************************************************
//             SecretLock Transaction
// ***************************************************
const random = crypto.randomBytes(10);
const hash = sha3_512.create();
const secret = hash.update(random).hex().toUpperCase();
const proof = random.toString('hex');

// Alice to Alice
const secretLockTransaction = SecretLockTransaction.create(
    Deadline.create(),
    new Mosaic( new MosaicId('nem:xem'), UInt64.fromUint(10000000)),
    UInt64.fromUint(60), //officially 96h
    HashType.SHA3_512,
    secret,
    aliceAccount.address,
    NetworkType.MIJIN_TEST
);
const signedSecretLockTransaction = aliceAccount.sign(secretLockTransaction);
console.log('SecretLock Transaction');
signAndDisplay(signedSecretLockTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(signedSecretLockTransaction);


// ***************************************************
//             SecretProof Transaction
// ***************************************************
const secretProofTransaction = SecretProofTransaction.create(
    Deadline.create(),
    HashType.SHA3_512,
    secret,
    proof,
    NetworkType.MIJIN_TEST
);
const signedSecretProofTransaction = aliceAccount.sign(secretProofTransaction);
console.log('SecretProof Transaction');
signAndDisplay(signedSecretProofTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(signedSecretProofTransaction);


// ***************************************************
//             ModifyMultisigAccount Transaction
// ***************************************************
const modifyMultisigAccountTransaction = ModifyMultisigAccountTransaction.create(
    Deadline.create(),
    2,
    1,
    [
        new MultisigCosignatoryModification(
            MultisigCosignatoryModificationType.Add,
            aliceAccount.publicAccount,
        ),
        new MultisigCosignatoryModification(
            MultisigCosignatoryModificationType.Add,
            bobAccount.publicAccount,
        )],
    NetworkType.MIJIN_TEST
);
const signedModifyMultisigAccountTransaction = multisigAccount.sign(modifyMultisigAccountTransaction);
console.log('ModifyMultisigAccount Transaction');
signAndDisplay(signedModifyMultisigAccountTransaction.payload.substring(200), multisigPrivateKey);
payloadDisplay(signedModifyMultisigAccountTransaction);


// ***************************************************
//             RegisterNamespace Transaction
// ***************************************************
const registerNamespaceTransaction = RegisterNamespaceTransaction.createRootNamespace(
    Deadline.create(),
    'mynamespace',
    UInt64.fromUint(1000),
    NetworkType.MIJIN_TEST,
);
const signedRegisterNamespaceTransaction = aliceAccount.sign(registerNamespaceTransaction);
console.log('RegisterNamespace Transaction');
signAndDisplay(signedRegisterNamespaceTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(signedRegisterNamespaceTransaction);


// ***************************************************
//             ModifyMultisigAccount Transaction
// ***************************************************
const mosaicDefinitionTransaction = MosaicDefinitionTransaction.create(
    Deadline.create(),
    'mymosaic',
    'mynamespace',
    MosaicProperties.create({
        supplyMutable: true,
        transferable: true,
        levyMutable: false,
        divisibility: 0,
        duration: UInt64.fromUint(1000),
    }),
    NetworkType.MIJIN_TEST,
);
const signedMosaicDefinitionTransaction = aliceAccount.sign(mosaicDefinitionTransaction);
console.log('ModifyMultisigAccount Transaction');
signAndDisplay(signedMosaicDefinitionTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(signedMosaicDefinitionTransaction);


// ***************************************************
//             MosaicSupplyChange Transaction
// ***************************************************
const mosaicSupplyChangeTransaction = MosaicSupplyChangeTransaction.create(
    Deadline.create(),
    mosaicDefinitionTransaction.mosaicId,
    MosaicSupplyType.Increase,
    UInt64.fromUint(1000000),
    NetworkType.MIJIN_TEST,
);
const signedMosaicSupplyChangeTransaction = aliceAccount.sign(mosaicSupplyChangeTransaction);

console.log('MosaicSupplyChange Transaction');
signAndDisplay(signedMosaicSupplyChangeTransaction.payload.substring(200), alicePrivateKey);
payloadDisplay(signedMosaicSupplyChangeTransaction);



// ***************************************************
//             Cosignature Transaction
// ***************************************************
const accountHttp = new AccountHttp('http://localhost:3000');
const transactionHttp = new TransactionHttp('http://localhost:3000');

setTimeout(() => {
    transactionHttp.announce(signedLockFundsTransaction).subscribe(
        x => x,
        err => console.error(err)
    )}
, 0);

setTimeout(() => {
    transactionHttp.announceAggregateBonded(sinedAggregateBondedTransaction).subscribe(
        x => x,
        err => console.error(err)
    )}
, 20000);

console.log('Cosignature Transaction');
signAndDisplay(sinedAggregateBondedTransaction.hash, bobPrivateKey);

const cosignAggregateBondedTransaction = (transaction, account)  => {
    const cosignatureTransaction = CosignatureTransaction.create(transaction);
    const signedTransaction = account.signCosignatureTransaction(cosignatureTransaction);
    console.log('payload  : ' + signedTransaction.signature);
    return signedTransaction;
};

setTimeout(() => {
    accountHttp.aggregateBondedTransactions(bobAccount.publicAccount)
    .flatMap((_) => _)
    .filter((_) => !_.signedByAccount(bobAccount.publicAccount))
    .map(transaction => cosignAggregateBondedTransaction(transaction, bobAccount))
    .flatMap(cosignatureSignedTransaction => transactionHttp.announceAggregateBondedCosignature(cosignatureSignedTransaction))
    .subscribe(x => x,
        err => console.error(err));
    }
, 21000);

結果

# node sign2.js
Transfer Transaction
signature: 0DF013F373DEA45571A2A21F4B116E81D6DF1CBD1BAE5BCED13998FED1D464B3E77DDA83B2B8B7C8EC97C9E57C5216E0E66631AD32E99081A276A2A0AAD6510F
payload  : 0DF013F373DEA45571A2A21F4B116E81D6DF1CBD1BAE5BCED13998FED1D464B3E77DDA83B2B8B7C8EC97C9E57C5216E0E66631AD32E99081A276A2A0AAD6510F
Aggregate Complete Transaction
signature: 5617D4F7F26F79E1F6C40FA017BBC60405A3E7670C276037FBE9CC1143861359A10B3EB5344E26ABDF7D84DA7E4EF86FBF23482D5A62CBEC90332C0F8493260F
payload  : 5617D4F7F26F79E1F6C40FA017BBC60405A3E7670C276037FBE9CC1143861359A10B3EB5344E26ABDF7D84DA7E4EF86FBF23482D5A62CBEC90332C0F8493260F
Aggregate Bonded Transaction
signature: FFC49BBEB3E1A0459F63738D9A92353C53F5FAD07E8E8C02B31F95699E45EF917C552E9BA071CA0DCA13531EC803B425399018422DC554A3EFEFE43997B15005
payload  : FFC49BBEB3E1A0459F63738D9A92353C53F5FAD07E8E8C02B31F95699E45EF917C552E9BA071CA0DCA13531EC803B425399018422DC554A3EFEFE43997B15005
LockFunds Transaction
signature: 4051A3278DC8CFF59E344FA8C02E5810C4C8A155FC6B7927910C6E1879E6A16FDB9F1B555DE9E05B0BDE74B5669E771A84D24D779B1D85C66387BEADA524FF0A
payload  : 4051A3278DC8CFF59E344FA8C02E5810C4C8A155FC6B7927910C6E1879E6A16FDB9F1B555DE9E05B0BDE74B5669E771A84D24D779B1D85C66387BEADA524FF0A
SecretLock Transaction
signature: 30D1F7287CA6CF553C121FFF7661AE7450A36F8F0ED26884FCDEC9888184854E4523D50F3B7ED639E4799FB7BF8192346E2EC01A400814FE2A299C3EA3D4CB07
payload  : 30D1F7287CA6CF553C121FFF7661AE7450A36F8F0ED26884FCDEC9888184854E4523D50F3B7ED639E4799FB7BF8192346E2EC01A400814FE2A299C3EA3D4CB07
SecretProof Transaction
signature: 1183B8A224C04FF32B4BD9D33E5EC28561C05D34D8DDD42B1EE8780F032AE29EBA9D997D5F00BCBE3040A08D30AC1400C0BCFF94849654F4C171FB3FDD297008
payload  : 1183B8A224C04FF32B4BD9D33E5EC28561C05D34D8DDD42B1EE8780F032AE29EBA9D997D5F00BCBE3040A08D30AC1400C0BCFF94849654F4C171FB3FDD297008
ModifyMultisigAccount Transaction
signature: AB60D2C78391F0BE75978E0A7423139F184EBAD4F09EA0E39A98DACA27A85AC7CEF70C7258F14B41896F3D2783CBCE94F18AAEE9D93075DAE0DD955A642F4303
payload  : AB60D2C78391F0BE75978E0A7423139F184EBAD4F09EA0E39A98DACA27A85AC7CEF70C7258F14B41896F3D2783CBCE94F18AAEE9D93075DAE0DD955A642F4303
RegisterNamespace Transaction
signature: FA3A9341B7DF2F49B45E0D9DCD17F93F6974A4356F235766C05964F7D3817298F10E5DB69B7E7BE9C3F9665A2FFFD5C880A237121FB00A38737D2CA87409760A
payload  : FA3A9341B7DF2F49B45E0D9DCD17F93F6974A4356F235766C05964F7D3817298F10E5DB69B7E7BE9C3F9665A2FFFD5C880A237121FB00A38737D2CA87409760A
ModifyMultisigAccount Transaction
signature: 89EB7F8D6208C74041C9B6595B1BF0FA85C4C613E1D0C41FC9DD22451AE673A97AA382AB5A17B266E70DA8B17AD8DA26B01A488AF8F99CBB1B85FAB2E453B50B
payload  : 89EB7F8D6208C74041C9B6595B1BF0FA85C4C613E1D0C41FC9DD22451AE673A97AA382AB5A17B266E70DA8B17AD8DA26B01A488AF8F99CBB1B85FAB2E453B50B
MosaicSupplyChange Transaction
signature: AF3E70D9441A49E494B7E07CD1C102A1A5F4AB72658842A3882D6BE3FB02FD6E51762B4D08949590B1A5F1D8979A309B1513126F2A3390758B1C3A61DD240009
payload  : AF3E70D9441A49E494B7E07CD1C102A1A5F4AB72658842A3882D6BE3FB02FD6E51762B4D08949590B1A5F1D8979A309B1513126F2A3390758B1C3A61DD240009
Cosignature Transaction
signature: 716FAE6B5BB67FCB7971093A7CE11B71B3E3D38BF3AB9DDE01D5857E311BD9E5656F6157C703F8A183CA732CDED34C5EAD1F75656D454F355A7658C024EB9907
payload  : 716FAE6B5BB67FCB7971093A7CE11B71B3E3D38BF3AB9DDE01D5857E311BD9E5656F6157C703F8A183CA732CDED34C5EAD1F75656D454F355A7658C024EB9907

正解!おめでとう!

関連

nem catapult バイトレベルで理解する その1 トランザクションハッシュ

nem catapult バイトレベルで理解する その2 TransferTransaction

nem catapult バイトレベルで理解する その4 Cosignature Transaction