This guide will show you how to add cosignatures to an aggregate complete transaction without using the partial cache.
This can be useful for when you are keeping your private keys in an offline device (cold wallet) for security reasons. By signing adding the cosignatures offline, the cosignatories will be able to execute transactions from your cold wallet while keeping their private keys completely safe. Furthermore, it also allows users to avoid unnecessarily locking their funds in aggregate bonded transactions.
We will set up an aggregate complete transaction where Alice (TDPXWF2H5G7U2NKZRJD47QR4KZPRULPAMQ4O54IK
) will send 100 bitxor
to Bob (BXRS3AOXFGWGTN2QUUHDCXJ4SBYLIQIPNUPHHA2N
) in return for 1 collectible token.
Have 2 accounts account.
Load the accounts with enough bitxor
to pay for transaction fees.
One of the accounts must own a token other than the bitxor
.
Both your offline and online workstations are set up for Bitxor-CLI and Bitxor-SDK.
Open up a text editor. Then, construct the Aggregate Complete Transaction using Alice’s account.
const networkType = NetworkType.TEST_NET;
// replace with alice private key
const alicePrivatekey = '';
const aliceAccount = Account.createFromPrivateKey(alicePrivatekey, networkType);
// replace with bob public key
const bobPublicKey = '';
const bobPublicAccount = PublicAccount.createFromPublicKey(
bobPublicKey,
networkType,
);
const aliceTransferTransaction = TransferTransaction.create(
Deadline.create(epochAdjustment),
bobPublicAccount.address,
[NetworkCurrencies.PUBLIC.currency.createRelative(1000)],
PlainMessage.create('payout'),
networkType,
);
const bobTransferTransaction = TransferTransaction.create(
Deadline.create(epochAdjustment),
aliceAccount.address,
[new Token(new NamespaceId('collectible'), UInt64.fromUint(1))],
PlainMessage.create('payout'),
networkType,
);
const aggregateTransaction = AggregateTransaction.createComplete(
Deadline.create(epochAdjustment),
[
aliceTransferTransaction.toAggregate(aliceAccount.publicAccount),
bobTransferTransaction.toAggregate(bobPublicAccount),
],
networkType,
[],
UInt64.fromUint(2000000),
);
const networkType = bitxor_sdk_1.NetworkType.TEST_NET;
// replace with alice private key
const alicePrivatekey = '';
const aliceAccount = bitxor_sdk_1.Account.createFromPrivateKey(
alicePrivatekey,
networkType,
);
// replace with bob public key
const bobPublicKey = '';
const bobPublicAccount = bitxor_sdk_1.PublicAccount.createFromPublicKey(
bobPublicKey,
networkType,
);
const aliceTransferTransaction = bitxor_sdk_1.TransferTransaction.create(
bitxor_sdk_1.Deadline.create(epochAdjustment),
bobPublicAccount.address,
[bitxor_sdk_1.NetworkCurrencies.PUBLIC.currency.createRelative(1000)],
bitxor_sdk_1.PlainMessage.create('payout'),
networkType,
);
const bobTransferTransaction = bitxor_sdk_1.TransferTransaction.create(
bitxor_sdk_1.Deadline.create(epochAdjustment),
aliceAccount.address,
[
new bitxor_sdk_1.Token(
new bitxor_sdk_1.NamespaceId('collectible'),
bitxor_sdk_1.UInt64.fromUint(1),
),
],
bitxor_sdk_1.PlainMessage.create('payout'),
networkType,
);
const aggregateTransaction = bitxor_sdk_1.AggregateTransaction.createComplete(
bitxor_sdk_1.Deadline.create(epochAdjustment),
[
aliceTransferTransaction.toAggregate(aliceAccount.publicAccount),
bobTransferTransaction.toAggregate(bobPublicAccount),
],
networkType,
[],
bitxor_sdk_1.UInt64.fromUint(2000000),
);
NetworkType networkType = repositoryFactory.getNetworkType().toFuture().get();
NetworkCurrency networkCurrency = repositoryFactory.getNetworkCurrency().toFuture()
.get();
// replace with alice private key
String alicePrivatekey = "";
Account aliceAccount = Account.createFromPrivateKey(alicePrivatekey, networkType);
// replace with bob public key
String bobPublicKey = "";
PublicAccount bobPublicAccount = PublicAccount
.createFromPublicKey(bobPublicKey, networkType);
TransferTransaction aliceTransferTransaction = TransferTransactionFactory
.create(networkType, bobPublicAccount.getAddress(),
Collections
.singletonList(networkCurrency.createRelative(BigInteger.valueOf(1000))),
PlainMessage.create("payout")).build();
TransferTransaction bobTransferTransaction = TransferTransactionFactory
.create(networkType, aliceAccount.getAddress(),
Collections.singletonList(
new Token(new NamespaceId("collectible"), BigInteger.valueOf(1))),
PlainMessage.create("payout")).build();
AggregateTransaction aggregateTransaction = AggregateTransactionFactory
.createComplete(networkType, Arrays
.asList(aliceTransferTransaction.toAggregate(aliceAccount.getPublicAccount()),
bobTransferTransaction.toAggregate(bobPublicAccount)))
.maxFee(BigInteger.valueOf(2000000)).build();
Make sure to place Alice’s private key and Bob’s public key in the appropriate places.
Sign the transaction with Alice’s key.
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const generationHash =
'1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransactionNotComplete = aliceAccount.sign(
aggregateTransaction,
generationHash,
);
console.log(signedTransactionNotComplete.payload);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const generationHash =
'1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransactionNotComplete = aliceAccount.sign(
aggregateTransaction,
generationHash,
);
console.log(signedTransactionNotComplete.payload);
String generationHash = repositoryFactory.getGenerationHash().toFuture().get();
SignedTransaction signedTransactionNotComplete = aliceAccount
.sign(aggregateTransaction, generationHash);
System.out.println(signedTransactionNotComplete.getPayload());
3. Save the TypeScript file, then run it on your terminal. Copy the returned payload and send it over to Bob.
Bob cosigns the payload obtained from the previous step.
// replace with bob private key
const bobPrivateKey = '';
const bobAccount = Account.createFromPrivateKey(bobPrivateKey, networkType);
const cosignedTransactionBob = CosignatureTransaction.signTransactionPayload(
bobAccount,
signedTransactionNotComplete.payload,
generationHash,
);
console.log(cosignedTransactionBob.signature);
console.log(cosignedTransactionBob.parentHash);
// replace with bob private key
const bobPrivateKey = '';
const bobAccount = bitxor_sdk_1.Account.createFromPrivateKey(
bobPrivateKey,
networkType,
);
const cosignedTransactionBob = bitxor_sdk_1.CosignatureTransaction.signTransactionPayload(
bobAccount,
signedTransactionNotComplete.payload,
generationHash,
);
console.log(cosignedTransactionBob.signature);
console.log(cosignedTransactionBob.parentHash);
// replace with bob private key
String bobPrivateKey = "";
Account bobAccount = Account.createFromPrivateKey(bobPrivateKey, networkType);
CosignatureSignedTransaction cosignedTransactionBob = CosignatureTransaction
.create(aggregateTransaction)
.signWith(bobAccount);
System.out.println(cosignedTransactionBob.getSignature());
System.out.println(cosignedTransactionBob.getParentHash());
2. Bob runs the code snippet in the terminal and obtains the transaction signature and the parent hash. Finally, he shares the information back with Alice.
Using Bob’s public key, cosignature transaction hash, and signature, recreate the transaction and announce it to the network as complete.
const cosignatureSignedTransactions = [
new CosignatureSignedTransaction(
cosignedTransactionBob.parentHash,
cosignedTransactionBob.signature,
cosignedTransactionBob.signerPublicKey,
),
];
const rectreatedAggregateTransactionFromPayload = TransactionMapping.createFromPayload(
signedTransactionNotComplete.payload,
) as AggregateTransaction;
const signedTransactionComplete = aliceAccount.signTransactionGivenSignatures(
rectreatedAggregateTransactionFromPayload,
cosignatureSignedTransactions,
generationHash,
);
console.log(signedTransactionComplete.hash);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp.announce(signedTransactionComplete).subscribe(
(x) => console.log(x),
(err) => console.error(err),
);
const cosignatureSignedTransactions = [
new bitxor_sdk_1.CosignatureSignedTransaction(
cosignedTransactionBob.parentHash,
cosignedTransactionBob.signature,
cosignedTransactionBob.signerPublicKey,
),
];
const rectreatedAggregateTransactionFromPayload = bitxor_sdk_1.TransactionMapping.createFromPayload(
signedTransactionNotComplete.payload,
);
const signedTransactionComplete = aliceAccount.signTransactionGivenSignatures(
rectreatedAggregateTransactionFromPayload,
cosignatureSignedTransactions,
generationHash,
);
console.log(signedTransactionComplete.hash);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new bitxor_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp.announce(signedTransactionComplete).subscribe(
(x) => console.log(x),
(err) => console.error(err),
);
BinarySerialization serialization = BinarySerializationImpl.INSTANCE;
AggregateTransactionFactory rectreatedAggregateTransactionFromPayload = (AggregateTransactionFactory) serialization
.deserializeToFactory(
ConvertUtils.getBytes(signedTransactionNotComplete.getPayload()));
//Added a new cosignature.
rectreatedAggregateTransactionFromPayload.addCosignatures(cosignedTransactionBob);
SignedTransaction signedTransactionComplete = aliceAccount
.sign(rectreatedAggregateTransactionFromPayload.build(), generationHash);
System.out.println(signedTransactionComplete.getHash());
TransactionRepository transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp.announce(signedTransactionComplete).toFuture().get();
If successful, Alice will have sent 100 bitxor
to Bob and received 1 collectible
token in return.