Qucik Learning Symbol Section 12
Chapter 12 Offline Signatures
The chapter on Locks, explained the Lock transactions with a hash value specification and the Aggregate transaction, which collects multiple signatures (online signatures). This chapter explains offline signing, which involves collecting signatures in advance and announcing the transaction to the node.
12.0.1 Procedure
Alice creates and signs the transaction. Then Bob signs it and returns it to Alice. Finally, Alice combines the transactions and announces them to the network.
12.1 Transaction creation
$bobPrivateKey = 'B34C8DEEADF5FE608CB2FD245C9ECF8A70DAD7F7E66CB22614BAF90E******';
$bobKey = $facade->createAccount(new PrivateKey($bobPrivateKey));
// アグリゲートTxに含めるTxを作成
$innerTx1 = new EmbeddedTransferTransactionV1(
network: new NetworkType(NetworkType::TESTNET),
signerPublicKey: $aliceKey->publicKey,
recipientAddress: $bobKey->address,
mosaics:[],
message: "\0tx1",
);
$innerTx2 = new EmbeddedTransferTransactionV1(
network: new NetworkType(NetworkType::TESTNET),
signerPublicKey: $bobKey->publicKey,
recipientAddress: $aliceKey->address,
mosaics:[],
message: "\0tx2",
);
// マークルハッシュの算出
$embeddedTransactions = [$innerTx1, $innerTx2];
$merkleHash = $facade->hashEmbeddedTransactions($embeddedTransactions);
// アグリゲートTx作成
$aggregateTx = new AggregateCompleteTransactionV2(
network: new NetworkType(NetworkType::TESTNET),
signerPublicKey: $aliceKey->publicKey,
deadline: new Timestamp($facade->now()->addHours(2)),
transactionsHash: $merkleHash,
transactions: $embeddedTransactions,
);
$facade->setMaxFee($aggregateTx, 100, 2);
// 署名
$signedHash = $aliceKey->signTransaction($aggregateTx);
$signedPayload = $facade->attachSignature($aggregateTx, $signedHash);
echo "\n===payload===" . PHP_EOL;
echo $signedPayload['payload'] . PHP_EOL;
Sample output
5801000000000000C3D9A9649B042203011BF8D6EFC912562FED813FCAE4A9861D42C9B6C397E00DF54A8634A9AC8AAD06AD882BA4841E60AF8FE9F1D2521110B30B3099B83F630825189135BF2307DCBCD1657A34ABC3FDEEC04A126D4572876BCA4F514DB5AC9B00000000029841416086000000000000D1B2F5CE0C0000008BD1718CB31EF65217DD6263D65D44F02CCA55DA429EDE4641F4FF0C97ECABBEB000000000000000550000000000000025189135BF2307DCBCD1657A34ABC3FDEEC04A126D4572876BCA4F514DB5AC9B000000000198544198B4A75DD1ADA8144D60BD8107002C2FEB02DD2EFD5C788E05000000000000005C307478310000005500000000000000A3378BE54307C0A814CDDEB2F9BEC1ACBEA44F298063A16A06B5C4ACE0AD28B4000000000198544198E521BD0F024F58E670A023BF3A14F3BECAF0280396BED005000000000000005C30747832000000
Sign and output signedHash, signedPayload. Pass signedPayload to Bob to prompt him to sign.
12.2 Cosignature by Bob
Restore the transaction with the signedPayload received from Alice.
$tx = TransactionFactory::deserialize(hex2bin($signedPayload['payload'])); // バイナリデータにする
echo "\n===tx===" . PHP_EOL;
print_r($tx);
Sample output
SymbolSdk\Symbol\Models\AggregateCompleteTransactionV2 Object
(
[transactionsHash] => SymbolSdk\Symbol\Models\Hash256 Object
(
[binaryData] => ��q���R�bc�]D�,�U�B��FA��
�쫾
)
[transactions] => Array
(
[0] => SymbolSdk\Symbol\Models\EmbeddedTransferTransactionV1 Object
(
[recipientAddress] => SymbolSdk\Symbol\Models\UnresolvedAddress Object
(
[binaryData] => ���]ѭ�M`��,/��.�\x�
)
[mosaics] => Array
(
)
[message] => \0tx1
[transferTransactionBodyReserved_1:SymbolSdk\Symbol\Models\EmbeddedTransferTransactionV1:private] => 0
[transferTransactionBodyReserved_2:SymbolSdk\Symbol\Models\EmbeddedTransferTransactionV1:private] => 0
[signerPublicKey] => SymbolSdk\Symbol\Models\PublicKey Object
(
[binaryData] => %�5�#ܼ�ez4�����JmEr�k�OQM���
)
[version] => 1
[network] => SymbolSdk\Symbol\Models\NetworkType Object
(
[value] => 152
)
[type] => SymbolSdk\Symbol\Models\TransactionType Object
(
[value] => 16724
)
[embeddedTransactionHeaderReserved_1:SymbolSdk\Symbol\Models\EmbeddedTransaction:private] => 0
[entityBodyReserved_1:SymbolSdk\Symbol\Models\EmbeddedTransaction:private] => 0
)
[1] => SymbolSdk\Symbol\Models\EmbeddedTransferTransactionV1 Object
(
[recipientAddress] => SymbolSdk\Symbol\Models\UnresolvedAddress Object
(
[binaryData] => ��!�OX�p�#�:���(���
)
[mosaics] => Array
(
)
[message] => \0tx2
[transferTransactionBodyReserved_1:SymbolSdk\Symbol\Models\EmbeddedTransferTransactionV1:private] => 0
[transferTransactionBodyReserved_2:SymbolSdk\Symbol\Models\EmbeddedTransferTransactionV1:private] => 0
[signerPublicKey] => SymbolSdk\Symbol\Models\PublicKey Object
(
[binaryData] => �7��C���������O)�c�j�Ĭ�(�
)
[version] => 1
[network] => SymbolSdk\Symbol\Models\NetworkType Object
(
[value] => 152
)
[type] => SymbolSdk\Symbol\Models\TransactionType Object
(
[value] => 16724
)
[embeddedTransactionHeaderReserved_1:SymbolSdk\Symbol\Models\EmbeddedTransaction:private] => 0
[entityBodyReserved_1:SymbolSdk\Symbol\Models\EmbeddedTransaction:private] => 0
)
)
[cosignatures] => Array
(
)
[aggregateTransactionHeaderReserved_1:SymbolSdk\Symbol\Models\AggregateCompleteTransactionV2:private] => 0
[signature] => SymbolSdk\Symbol\Models\Signature Object
(
[binaryData] => �����FI�Q�Q��9#�{���k�-��Z�bb�
m�`�@u�^V�~cA'�74�Fv
)
[signerPublicKey] => SymbolSdk\Symbol\Models\PublicKey Object
(
[binaryData] => %�5�#ܼ�ez4�����JmEr�k�OQM���
)
[version] => 2
[network] => SymbolSdk\Symbol\Models\NetworkType Object
(
[value] => 152
)
[type] => SymbolSdk\Symbol\Models\TransactionType Object
(
[value] => 16705
)
[fee] => SymbolSdk\Symbol\Models\Amount Object
(
[size] => 8
[value] => 34400
)
[deadline] => SymbolSdk\Symbol\Models\Timestamp Object
(
[size] => 8
[value] => 55012380245
)
[verifiableEntityHeaderReserved_1:SymbolSdk\Symbol\Models\Transaction:private] => 0
[entityBodyReserved_1:SymbolSdk\Symbol\Models\Transaction:private] => 0
)
To make sure, verify whether the transaction (payload) has already been signed by Alice.
$signature = new Signature($tx->signature);
$res = $facade->verifyTransaction($tx, $signature);
var_dump($res);
Sample output
true
It has been verified that the payload is signed by the signer Alice, then Bob co-signs.
$bobCosignature = $facade->cosignTransaction($bobKey->keyPair, $tx, true);
$bobSignedTxSignature = $bobCosignature->signature;
$bobSignedTxSignerPublicKey = $bobCosignature->signerPublicKey;
Bob signs with the signatureCosignatureTransaction and outputs bobSignedTxSignature, bobSignedTxSignerPublicKey then returns these to Alice. If Bob can create all of the signatures then Bob can also make the announcement without having to return it to Alice.
12.3 Announcement by Alice
Alice receives bobSignedTxSignature, bobSignedTxSignerPublicKey from Bob. Also, prepare a signedPayload created by Alice herself in advance.
$recreatedTx = TransactionFactory::deserialize(hex2bin($signedPayload['payload']));
// 連署者の署名を追加
$cosignature = new Cosignature();
$signTxHash = $facade->hashTransaction($aggregateTx);
$cosignature->parentHash = new Hash256($signTxHash);
$cosignature->version = 0;
$cosignature->signerPublicKey = $bobSignedTxSignerPublicKey;
$cosignature->signature = $bobSignedTxSignature;
array_push($recreatedTx->cosignatures, $cosignature);
$signedPayload = ["payload" => strtoupper(bin2hex($recreatedTx->serialize()))];
echo $signedPayload;
try {
$result = $apiInstance->announceTransaction($signedPayload);
echo $result . PHP_EOL;
} catch (Exception $e) {
echo 'Exception when calling TransactionRoutesApi->announceTransaction: ', $e->getMessage(), PHP_EOL;
}
12.4 Tips for use
12.4.1 Beyond the marketplace
Unlike Bonded Transactions, there is no need to pay fees (10XYM) for hashlocks. If the payload can be shared, the seller can create payloads for all possible potential buyers and wait for negotiations to start. (Exclusion control should be used, e.g. by mixing only one existing receipt NFT into the Aggregate Transaction, so that multiple transactions are not executed separately). There is no need to build a dedicated marketplace for these negotiations. Users can use a social networking timeline as a marketplace, or develop a one-time marketplace at any time or in any space as required.
Just be careful of spoofed hash signature requests, as signatures are exchanged offline (always generate and sign a hash from a verifiable payload).