12.Firme a freddo (offline)

Nel capitolo sui Lock, sono state descritte le transazioni con lock il cui parametro è un determinato valore hash, e le transazioni di gruppo Aggregate transaction, la cui esecuzione richiede firme multiple (online signatures).
Questo capitolo descrive come firmare a freddo (offline), che consiste nel raccogliere le firme in anticipo e poi propagare la transazione ai nodi della rete blockchain.

Procedura

Alice crea e firma una transazione, quindi Bob ne appone la firma e la restituisce ad Alice. Ora Alice è pronta a combinare le due transazioni e propagarle alla rete in un colpo solo.

12.1 Creazione della Transazione

bob = sym.Account.generateNewAccount(networkType);

innerTx1 = sym.TransferTransaction.create(
  undefined,
  bob.address,
  [],
  sym.PlainMessage.create("tx1"),
  networkType
);

innerTx2 = sym.TransferTransaction.create(
  undefined,
  alice.address,
  [],
  sym.PlainMessage.create("tx2"),
  networkType
);

aggregateTx = sym.AggregateTransaction.createComplete(
  sym.Deadline.create(epochAdjustment),
  [
    innerTx1.toAggregate(alice.publicAccount),
    innerTx2.toAggregate(bob.publicAccount),
  ],
  networkType,
  []
).setMaxFeeForAggregate(100, 1);

signedTx = alice.sign(aggregateTx, generationHash);
signedHash = signedTx.hash;
signedPayload = signedTx.payload;

console.log(signedPayload);
Output esemplificativo
>580100000000000039A6555133357524A8F4A832E1E596BDBA39297BC94CD1D0728572EE14F66AA71ACF5088DB6F0D1031FF65F2BBA7DA9EE3A8ECF242C2A0FE41B6A00A2EF4B9020E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26000000000198414100AF000000000000D4641CD902000000306771D758886F1529F9B61664B0450ED138B27CC5E3AE579C16D550EDEE5791B00000000000000054000000000000000E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26000000000198544198A1BE13194C0D18897DD88FE3BC4860B8EEF79C6BC8C8720400000000000000007478310000000054000000000000003C4ADF83264FF73B4EC1DD05B490723A8CFFAE1ABBD4D4190AC4CAC1E6505A5900000000019854419850BF0FD1A45FCEE211B57D0FE2B6421EB81979814F629204000000000000000074783200000000

Alice ha firmato e l'output è signedHash, signedPayload. A questo punto passare signedPayload a Bob chiedendogli di firmare.

12.2 Firma del cointestatario Bob

Per ricostruire la transazione partendo dal signedPayload che gli ha comunicato Alice.

tx = sym.TransactionMapping.createFromPayload(signedPayload);
console.log(tx);
Output esemplificativo
> AggregateTransaction
    cosignatures: []
    deadline: Deadline {adjustedValue: 12197090355}
> innerTransactions: Array(2)
      0: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, }
      1: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, }
    maxFee: UInt64 {lower: 44800, higher: 0}
    networkType: 152
    payloadSize: undefined
    signature: "4999A8437DA1C339280ED19BE0814965B73D60A1A6AF2F3856F69FBFF9C7123427757247A231EB89BB8844F37AC6F7559F859E2FDE39B8FA58A57F36DDB3B505"
    signer: PublicAccount
      address: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152}
      publicKey: "D4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD2"
    transactionInfo: undefined
    type: 16705
    version: 1

Assicurarsi che il corpo della transazione payload sia stato già firmato da Alice.

Buffer = require("/node_modules/buffer").Buffer;
res = tx.signer.verifySignature(
  tx.getSigningBytes(
    [...Buffer.from(signedPayload, "hex")],
    [...Buffer.from(generationHash, "hex")]
  ),
  tx.signature
);
console.log(res);
Output esemplificativo
> true

Una volta verificato che il payload è stato effettivamente firmato da Alice, Bob potrà a sua volta apporre la propria firma di cofirmatario.

bobSignedTx = sym.CosignatureTransaction.signTransactionPayload(
  bob,
  signedPayload,
  generationHash
);
bobSignedTxSignature = bobSignedTx.signature;
bobSignedTxSignerPublicKey = bobSignedTx.signerPublicKey;

Dopo aver apposto la firma Bob comunica bobSignedTxSignature e bobSignedTxSignerPublicKey ad Alice.

Se Bob potesse creare tutte le firme necessarie, allora potrebbe anche eseguire la propagazione senza dover comunicare più nulla ad Alice.

12.3 Propagazione fatta da Alice

Dopo aver ricevuto bobSignedTxSignature e bobSignedTxSignerPublicKey da Bob, Alice prepara il payload della transazione che li contiene e firmando in anticipo a proprio nome.

signedHash = sym.Transaction.createTransactionHash(
  signedPayload,
  Buffer.from(generationHash, "hex")
);
cosignSignedTxs = [
  new sym.CosignatureSignedTransaction(
    signedHash,
    bobSignedTxSignature,
    bobSignedTxSignerPublicKey
  ),
];

recreatedTx = sym.TransactionMapping.createFromPayload(signedPayload);

cosignSignedTxs.forEach((cosignedTx) => {
  signedPayload +=
    cosignedTx.version.toHex() +
    cosignedTx.signerPublicKey +
    cosignedTx.signature;
});

size = `00000000${(signedPayload.length / 2).toString(16)}`;
formatedSize = size.substr(size.length - 8, size.length);
littleEndianSize =
  formatedSize.substr(6, 2) +
  formatedSize.substr(4, 2) +
  formatedSize.substr(2, 2) +
  formatedSize.substr(0, 2);

signedPayload =
  littleEndianSize + signedPayload.substr(8, signedPayload.length - 8);
signedTx = new sym.SignedTransaction(
  signedPayload,
  signedHash,
  alice.publicKey,
  recreatedTx.type,
  recreatedTx.networkType
);

await txRepo.announce(signedTx).toPromise();

L'ultima parte, nel ciclo che consiste nell'aggiungere una serie di firme è più complessa perchè modifica direttamente il payload cambiandone la dimensione. Se la chiave privata di Alice potesse essere usata per firmare ancora la transazione, si potrebbero generare transazioni cointestate cosignSignedTxs nel modo seguente.

resignedTx = recreatedTx.signTransactionGivenSignatures(
  alice,
  cosignSignedTxs,
  generationHash
);
await txRepo.announce(resignedTx).toPromise();

12.4 Consigli pratici

Oltre il mercato di scambio

Diversamente dalle transazioni di tipo bonded (legate), non è necessario pagare commissioni (10 XYM), per gli hashlock. Essendo il payload condivisibile con altri, un venditore potrà creare preventivamente tanti payload quanti i possibili acquirenti potenziali ed attendere che partano le negoziazioni. (Andrebbero applicati dei meccanismi di esclusione. Per esempio, includendo una ricevuta NFT completa nella transazione di gruppo, per impedire che più transazioni vengano eseguite separatamente). Non è necessario creare un mercato di scambio dedicato per queste negoziazioni. Gli utenti possono usare una politica di tipo social network oppure sviluppare un'istanza di mercato dedicato istantanea. Fare attenzione in particolare al rischio di intercettazione maligna, degli hash delle richieste di firma. Essendo questi scambiati in modalità offline, è necessario generare e firmare i payload da una fonte verificabile).