Qucik Learning Symbol Section 9

Chapter 9 Multisignature

Symbol accounts can be converted to multisig.

9.0.1 Points

Multisig accounts can have up to 25 co-signatories. An account can be cosigner of up to 25 multisig accounts. Multisig accounts can be hierarchical and composed of up to 3 levels. This chapter explains single-level multisig.

9.0.2 Preparing an account

Create the accounts used in the sample source code in this chapter and output each secret key. Note that the Bob multisig account in this chapter will be unusable if Carol's secret key is lost.


$bobKey = $facade->createAccount(PrivateKey::random());
$carolKey1 = $facade->createAccount(PrivateKey::random());
$carolKey2 = $facade->createAccount(PrivateKey::random());
$carolKey3 = $facade->createAccount(PrivateKey::random());
$carolKey4 = $facade->createAccount(PrivateKey::random());
$carolKey5 = $facade->createAccount(PrivateKey::random());

echo "===秘密鍵と公開鍵の導出===" . PHP_EOL;
echo $bobKey->keyPair->privateKey() . PHP_EOL;
echo $carolKey1->keyPair->privateKey() . PHP_EOL;
echo $carolKey2->keyPair->privateKey() . PHP_EOL;
echo $carolKey3->keyPair->privateKey() . PHP_EOL;
echo $carolKey4->keyPair->privateKey() . PHP_EOL;
echo $carolKey5->keyPair->privateKey() . PHP_EOL;

Output URL


echo "https://testnet.symbol.tools/?recipient=" . $bobKey->address . "&amount=20" . PHP_EOL;
echo "https://testnet.symbol.tools/?recipient=" . $carolKey1->address . "&amount=20" . PHP_EOL;

When using a testnet, the equivalent of the network fee from the faucet should be available in the bob and carol1 accounts.

• Faucet – https://testnet.symbol.tools/

9.1 Multisig registration

Symbol does not need to create a new account when setting up a multisig. Instead co-signatories can be specified for an existing account. Creating a multisig account requires the consent signature (opt-in) of the account designated as the co-signatory. Aggregate Transactions are used to confirm this.


$multisigTx =  new EmbeddedMultisigAccountModificationTransactionV1(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $bobKey->publicKey,  // マルチシグ化したいアカウントの公開鍵を指定
  minApprovalDelta: 3, // minApproval:承認のために必要な最小署名者数増分
  minRemovalDelta: 3, // minRemoval:除名のために必要な最小署名者数増分
  addressAdditions: [
    $carolKey1->address,
    $carolKey2->address,
    $carolKey3->address,
    $carolKey4->address,
  ],
  addressDeletions: [] // 除名対象アドレスリスト
);

// マークルハッシュの算出
$embeddedTransactions = [$multisigTx];
$merkleHash = $facade->hashEmbeddedTransactions($embeddedTransactions);

// アグリゲートトランザクションの作成
$aggregateTx = new AggregateCompleteTransactionV2(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $bobKey->publicKey,  // マルチシグ化したいアカウントの公開鍵を指定
  deadline: new Timestamp($facade->now()->addHours(2)),
  transactionsHash: $merkleHash,
  transactions: $embeddedTransactions
);
$facade->setMaxFee($aggregateTx, 100, 4);  // 手数料

// マルチシグ化したいアカウントによる署名
$sig = $bobKey->signTransaction($aggregateTx);
$payload = $facade->attachSignature($aggregateTx, $sig);

// 追加・除外対象として指定したアカウントによる連署
$coSig1 = $facade->cosignTransaction($carolKey1->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig1);
$coSig2 = $facade->cosignTransaction($carolKey2->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig2);
$coSig3 = $facade->cosignTransaction($carolKey3->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig3);
$coSig4 = $facade->cosignTransaction($carolKey4->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig4);

// アナウンス
$payload = ["payload" => strtoupper(bin2hex($aggregateTx->serialize()))];

try {
  $result = $apiInstance->announceTransaction($payload);
  echo $result . PHP_EOL;
} catch (Exception $e) {
  echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}

9.2 Confirmation

9.2.1 Confirmation of multisig account


$multisigApiInstance = new MultisigRoutesApi($client, $config);
$multisigInfo = $multisigApiInstance->getAccountMultisig($bobKey->address);
echo "===マルチシグ情報===" . PHP_EOL;
echo $multisigInfo . PHP_EOL;

Sample output


{
    "multisig": {
        "version": 1,
        "accountAddress": "98D215F1BB0566C26847D2612F94FD6769384D8C0DE3FAAB",
        "minApproval": 3,
        "minRemoval": 3,
        "cosignatoryAddresses": [
            "98402F5B7B45F3F5BC986FEA1DACE7AA4FEC48143371433C",
            "98429F73132C64A4676BD26B47CC574D68B3FC5E4C3B48A0",
            "9844ACF30F35DED7514DDD8651E718949786143ACE087EA1",
            "98F36944663F9EE70BC30AC39FDEDF6800356B5ED4CAA9B7"
        ],
        "multisigAddresses": []
    }
}

It shows that cosignatoryAddresses are registered as co-signatories. Also, minApproval:3 shows that the number of signatures required for a transaction to execute is 3. minRemoval: 3 shows that the number of signatories required to remove a cosignatory is 3.

9.2.2 Confirmation of co-signatory accounts


$multisigInfo = $multisigApiInstance->getAccountMultisig($carolKey1->address);
echo "===連署者1のマルチシグ情報===" . PHP_EOL;
echo $multisigInfo . PHP_EOL;

Sample output


{
    "multisig": {
        "version": 1,
        "accountAddress": "98402F5B7B45F3F5BC986FEA1DACE7AA4FEC48143371433C",
        "minApproval": 0,
        "minRemoval": 0,
        "cosignatoryAddresses": [],
        "multisigAddresses": [
            "98D215F1BB0566C26847D2612F94FD6769384D8C0DE3FAAB"
        ]
    }
}

It shows that the account is a cosignatory of the multisigAddresses.

9.3 Multisig signature

Send mosaics from a multisig account.

9.3.1 Transfer with an Aggregate Complete Transaction

In the case of Aggregate Complete Transaction, the transaction is created after collecting all the signatures of the cosignatories before announcing it to the nodes.


$tx = new EmbeddedTransferTransactionV1(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $bobKey->publicKey,  //マルチシグ化したアカウントの公開鍵
  recipientAddress: $aliceKey->address,
  mosaics: [
    new UnresolvedMosaic(
      mosaicId: new UnresolvedMosaicId($namespaceId), // モザイクID
      amount: new Amount(1000000) // 金額(1XYM)
    )
  ],
  message: "\0test"
);

// マークルハッシュの算出
$embeddedTransactions = [$tx];
$merkleHash = $facade->hashEmbeddedTransactions($embeddedTransactions);

// アグリゲートトランザクションの作成
$aggregateTx = new AggregateCompleteTransactionV2(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $carolKey1->publicKey,  // マルチシグ化したいアカウントの公開鍵を指定
  deadline: new Timestamp($facade->now()->addHours(2)),
  transactionsHash: $merkleHash,
  transactions: $embeddedTransactions
);
$facade->setMaxFee($aggregateTx, 100, 2);  // 手数料

// 起案者アカウントによる署名
$sig = $carolKey1->signTransaction($aggregateTx);
$payload = $facade->attachSignature($aggregateTx, $sig);

// 追加・除外対象として指定したアカウントによる連署
$coSig1 = $facade->cosignTransaction($carolKey2->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig1);
$coSig2 = $facade->cosignTransaction($carolKey3->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig2);

// アナウンス
$payload = ["payload" => strtoupper(bin2hex($aggregateTx->serialize()))];

try {
  $result = $apiInstance->announceTransaction($payload);
  echo $result . PHP_EOL;
} catch (Exception $e) {
  echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}

9.3.2 Transfer with an Aggregate Bonded Transaction

Aggregate bonded transactions can be announced without specifying co-signatories. It is completed by declaring that the transaction will be pre-stored with a hash lock, and the co-signer additionally signs the transaction once it has been stored on the network.


// アグリゲートTxに含めるTxを作成
$tx = new EmbeddedTransferTransactionV1(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $bobKey->publicKey,
  recipientAddress: $aliceKey->address,
  mosaics: [
    new UnresolvedMosaic(
      mosaicId: new UnresolvedMosaicId($namespaceId), // モザイクID
      amount: new Amount(1000000) // 金額(1XYM)
    )
  ],
  message: "\0test"
);

// マークルハッシュの算出
$embeddedTransactions = [$tx];
$merkleHash = $facade->hashEmbeddedTransactions($embeddedTransactions);

// アグリゲートボンデッドTx作成
$aggregateTx = new AggregateBondedTransactionV2(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $carolKey1->publicKey,  // 起案者アカウントの公開鍵
  deadline: new Timestamp($facade->now()->addHours(2)),
  transactionsHash: $merkleHash,
  transactions: $embeddedTransactions
);
$facade->setMaxFee($aggregateTx, 100, 2);  // 手数料

// 署名
$sig = $carolKey1->signTransaction($aggregateTx);
$payload = $facade->attachSignature($aggregateTx, $sig);

// ハッシュロックTx作成
$hashLockTx = new HashLockTransactionV1(
  signerPublicKey: $carolKey1->publicKey,  // 署名者公開鍵
  network: new NetworkType(NetworkType::TESTNET),
  deadline: new Timestamp($facade->now()->addHours(2)), // 有効期限
  duration: new BlockDuration(480), // 有効期限
  hash: new Hash256($facade->hashTransaction($aggregateTx)), // ペイロードのハッシュ
  mosaic: new UnresolvedMosaic(
    mosaicId: new UnresolvedMosaicId($namespaceId), // モザイクID
    amount: new Amount(10 * 1000000) // 金額(10XYM)
  )
);
$facade->setMaxFee($hashLockTx, 100);  // 手数料

// 署名
$hashLockSig = $carolKey1->signTransaction($hashLockTx);
$hashLockJsonPayload = $facade->attachSignature($hashLockTx, $hashLockSig);

// ハッシュロックをアナウンス
try {
  $result = $apiInstance->announceTransaction($hashLockJsonPayload);
  echo $result . PHP_EOL;
} catch (Exception $e) {
  echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}

//ハッシュロックの承認を確認した後、ボンデッドTXをアナウンス
try {
  $result = $apiInstance->announcePartialTransaction($payload);
  echo $result . PHP_EOL;
} catch (Exception $e) {
  echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}

When a bonded transaction is known by a node, it will be a partial signature state and will be signed with a multisig account, using the co-signature introduced in chapter 8. Locking. It can also be confirmed by a wallet that supports co-signatures.

Co-signatories example


/**
 * 連署
 */
// トランザクションの取得
$txInfo = $apiInstance->getPartialTransaction($facade->hashTransaction($aggregateTx));


/**
 * carolKey2の連署
 */
$signTxHash = new Hash256($txInfo->getMeta()->getHash());
$signature = new Signature($carolKey2->keyPair->sign($signTxHash->binaryData));
$body = [
    'parentHash' => $signTxHash->__toString(),
    'signature' => $signature->__toString(),
    'signerPublicKey' => $carolKey2->publicKey->__toString(),
    'version' => '0'
];

//アナウンス
try {
  $result = $apiInstance->announceCosignatureTransaction($body);
  echo $result . PHP_EOL;
} catch (Exception $e) {
  echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}
echo 'TxHash' . PHP_EOL;
echo $signTxHash->__toString() . PHP_EOL;

sleep(1);

/**
 * carolKey3の連署
 */
$signature = new Signature($carolKey3->keyPair->sign($signTxHash->binaryData));
$body = [
    'parentHash' => $signTxHash->__toString(),
    'signature' => $signature->__toString(),
    'signerPublicKey' => $carolKey3->publicKey->__toString(),
    'version' => '0'
];

//アナウンス
try {
  $result = $apiInstance->announceCosignatureTransaction($body);
  echo $result . PHP_EOL;
} catch (Exception $e) {
  echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}
echo 'TxHash' . PHP_EOL;
echo $signTxHash->__toString() . PHP_EOL;

9.4 Confirmation of multisig transfer

Check the results of a multisig transfer transaction.


$aggregateHash = $facade->hashTransaction($aggregateTx);
$txInfo = $apiInstance->getConfirmedTransaction($aggregateHash);
console.log(txInfo);

Sample output


{
    "id": "66A4BF0F527B051AC20AF327",
    "meta": {
        "height": "1608331",
        "hash": "6122124B72EEE31237F0CBF1A9A133E3D94AAB0C15B8582CCBA7187082E5DAD4",
        "merkleComponentHash": "593B38945C4FF97085686285A409797ADA0EC3C5B167150F898C624E8C98E8AE",
        "index": 0,
        "timestamp": "54822380100",
        "feeMultiplier": 100
    },
    "transaction": {
        "size": 480,
        "signature": "B899E6F18C51927E35126D87803AD560C29FBD9F38A403F6DF7DDAF7E44A9E83CF83555DC3B6D90F93A288DB8083145FB9EDE4F2B4E247ED65A460EC63F53108",
        "signerPublicKey": "8D1516F8F7C8352680C19F578238D8C258A281E9FD66A72A384C10776FD42DE1",
        "version": 2,
        "network": 152,
        "type": 16961,
        "maxFee": "48000",
        "deadline": "54829501706",
        "transactionsHash": "CC4EEE544A2376EF9F7951627971E339638DBEAC4102D9173B4ECD814E6E0EFB",
        "cosignatures": [
            {
                "signature": "3E52A9D398BDD97022F7A69CEA0F38EC0A208E1726372879DD4A3A64209AB5517F7E769B89D4C05BD920325AE461A597E6A4FF04DA82C32008E8086D11670908",
                "version": "0",
                "signerPublicKey": "4AA192AE9F4FD262E489BCA3B79490FB39F61D1CD7206D50F856891CAA3CCAF7"
            },
            {
                "signature": "5B1AA912AEBDDAAAA7DD4EDAD03A29B5A9FEF6CAA52E9DA1961AA6CB0A1CCAD5059DACBCD6380425912F982AD31BD08479BB95EC88C5A8110B389FAFF2DB0A08",
                "version": "0",
                "signerPublicKey": "D1C49DF9CA73E0BAA204EFDF88F714548FD40B2E1D9A8EE31FE98AD7E3C877FD"
            }
        ],
        "transactions": [
            {
                "id": "66A4BF0F527B051AC20AF328",
                "meta": {
                    "height": "1608331",
                    "aggregateHash": "6122124B72EEE31237F0CBF1A9A133E3D94AAB0C15B8582CCBA7187082E5DAD4",
                    "aggregateId": "66A4BF0F527B051AC20AF327",
                    "index": 0,
                    "timestamp": "54822380100",
                    "feeMultiplier": 100
                },
                "transaction": {
                    "signerPublicKey": "AD4D43687EFDE2A15CF316FCCB209F80DC07498090725FE9C9C2A239E7B38E39",
                    "version": 1,
                    "network": 152,
                    "type": 16724,
                    "recipientAddress": "98E521BD0F024F58E670A023BF3A14F3BECAF0280396BED0",
                    "mosaics": [
                        {
                            "id": "E74B99BA41F4AFEE",
                            "amount": "1000000"
                        }
                    ],
                    "message": "0074657374"
                }
            }
        ]
    }
}
  • マルチシグアカウント
    • Bob
      • txInfo.transaction.transactions[0].transaction.signerPublicKey
      • AD4D43687EFDE2A15CF316FCCB209F80DC07498090725FE9C9C2A239E7B38E39
  • 起案者アカウント
    • Carol1
      • txInfo.transaction.signerPublicKey
      • 8D1516F8F7C8352680C19F578238D8C258A281E9FD66A72A384C10776FD42DE1
  • 連署者アカウント
    • Carol2
      • txInfo.transaction.cosignatures[0].signerPublicKey
      • 4AA192AE9F4FD262E489BCA3B79490FB39F61D1CD7206D50F856891CAA3CCAF7
    • Carol3
      • txInfo.transaction.cosignatures[1].signerPublicKey
      • D1C49DF9CA73E0BAA204EFDF88F714548FD40B2E1D9A8EE31FE98AD7E3C877FD

9.5 Modifying a multisig account min approval

9.5.1 Editing multisig configuration

To reduce the number of co-signatories, specify the address to remove and adjust the number of co-signatories so that the minimum number of signatories is not exceeded and then announce the transaction. It is not necessary to include the account subject to remove as a co-signatory.


$multisigTx = new EmbeddedMultisigAccountModificationTransactionV1(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $bobKey->publicKey,  // マルチシグ化したいアカウントの公開鍵を指定
  minApprovalDelta: -1, // minApproval:承認のために必要な最小署名者数増分
  minRemovalDelta: -1, // minRemoval:除名のために必要な最小署名者数増分
  addressAdditions: [], //追加対象アドレスリスト
  addressDeletions: [
    $carolKey3->address,
  ] // 除名対象アドレスリスト
);

// マークルハッシュの算出
$embeddedTransactions = [$multisigTx];
$merkleHash = $facade->hashEmbeddedTransactions($embeddedTransactions);

// アグリゲートトランザクションの作成
$aggregateTx = new AggregateCompleteTransactionV2(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $carolKey1->publicKey,  // 起案者アカウントの公開鍵
  deadline: new Timestamp($facade->now()->addHours(2)),
  transactionsHash: $merkleHash,
  transactions: $embeddedTransactions
);
$facade->setMaxFee($aggregateTx, 100, 2);  // 手数料

// 起案者アカウントによる署名
$sig = $carolKey1->signTransaction($aggregateTx);
$payload = $facade->attachSignature($aggregateTx, $sig);

// 連署者アカウントによる連署
$coSig1 = $facade->cosignTransaction($carolKey2->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig1);
$coSig4 = $facade->cosignTransaction($carolKey4->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig4);

// アナウンス
$payload = ["payload" => strtoupper(bin2hex($aggregateTx->serialize()))];

try {
  $result = $apiInstance->announceTransaction($payload);
  echo $result . PHP_EOL;
} catch (Exception $e) {
  echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}

9.5.2 Replacement of co-signatories

To replace a co-signatory, specify the address to be added and the address to be removed. The co-signature of the new additionally designated account is always required.


$multisigTx = new EmbeddedMultisigAccountModificationTransactionV1(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $bobKey->publicKey,  // 構成変更したいマルチシグアカウントの公開鍵を指定
  minApprovalDelta: 0, // minApproval:承認のために必要な最小署名者数増分
  minRemovalDelta: 0, // minRemoval:除名のために必要な最小署名者数増分
  addressAdditions: [
    $carolKey5->address,
  ],
  addressDeletions: [
    $carolKey4->address,
  ] // 除名対象アドレスリスト
);

// マークルハッシュの算出
$embeddedTransactions = [$multisigTx];
$merkleHash = $facade->hashEmbeddedTransactions($embeddedTransactions);

// アグリゲートトランザクションの作成
$aggregateTx = new AggregateCompleteTransactionV2(
  network: new NetworkType(NetworkType::TESTNET),
  signerPublicKey: $carolKey1->publicKey,  // 起案者アカウントの公開鍵
  deadline: new Timestamp($facade->now()->addHours(2)),
  transactionsHash: $merkleHash,
  transactions: $embeddedTransactions
);
$facade->setMaxFee($aggregateTx, 100, 2);  // 手数料

// 起案者アカウントによる署名
$sig = $carolKey1->signTransaction($aggregateTx);
$payload = $facade->attachSignature($aggregateTx, $sig);

// 連署者アカウントによる連署
$coSig2 = $facade->cosignTransaction($carolKey2->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig2);
$coSig5 = $facade->cosignTransaction($carolKey5->keyPair, $aggregateTx);
array_push($aggregateTx->cosignatures, $coSig5);

// アナウンス
$payload = ["payload" => strtoupper(bin2hex($aggregateTx->serialize()))];

try {
  $result = $apiInstance->announceTransaction($payload);
  echo $result . PHP_EOL;
} catch (Exception $e) {
  echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}

9.6 Tips for use

9.6.1 Multi-factor authorisation

The management of private keys can be distributed across multiple terminals. Multisigs can be used to ensure safe recovery in the event of a lost or compromised key. If the key is lost then the user can access funds through co-signatories and if stolen then the attacker cannot transfer funds without cosignatory approval.

- In case of theft: There are other people who can use the private key.

- When lost: No one can use the private key.

9.6.2 Account ownership

The private key of a multisig account is deactivated and unless the multisig is removed on the account sending mosaics will no longer be possible. As explained in Chapter 5. Mosaics, possession is “the state of being able to give it up at will', it can be said the owner of the assets on a multisig account are the co-signatories. Symbol allows replacement of co-signatories in a multisig configuration, so account ownership can be securely replaceable to another co-signatory.

9.6.3 Workflow

Symbol allows you to configure up to 3 levels of multisig (multi-level multisig). The use of multi-level multisig accounts prevents the use of stolen backup keys to complete a multisig, or the use of only an approver and an auditor to complete a signature. This allows the existence of transactions on the blockchain to be presented as evidence that certain operations and conditions have been met.