Cosign at cosigner's device

Trying to convert account to multisig account. Would like to do co-signing at cosigner device:

After listen to the bonded transaction:

listener
        .aggregateBondedAdded(multisigAccount.address)
        .pipe(
          filter(
            transaction =>
              transaction.transactionInfo !== undefined && transaction.transactionInfo.hash === signedTransaction.hash,
          ),
        )
        .subscribe(
          transaction => {
            console.log(JSON.stringify(transaction)); // Use this?
            console.log(transaction.serialize()); // or use this?

Then how do I sign it at co-signer device?

I tried this:

const cosignAggregateBondedTransaction = (transaction: AggregateTransaction, account: Account): CosignatureSignedTransaction => {
    const cosignatureSignedTransaction = CosignatureTransaction.create(transaction);
    return account.signCosignatureTransaction(cosignatureSignedTransaction);
}

but how can I convert the serialize transaction to AggregateTransaction?

Then I tried this, but not working, dont’t know if I use the right function to do it:

const hash = '7B25CD68A3D6837941D4E0C2A48A0C680B842C013A6C140DD53B0049C8590D45';
const txSerialize = 'E9000000CA054D47A726CAA0......; // result from transaction.serialize()
const signed = CosignatureTransaction.signTransactionPayload(acocunt, txSerialize , hash);

** Just to add, the cosinger device is not directly connected to the NEM2 blockchain. So it is signed at device using the private key, and then send to a server and continue to announce the transaction from the server.

Hey there,

you can use following example cosigHelper function: https://github.com/evias/nem2-sandbox/blob/master/src/commands/transaction/cosignMultisig.ts#L101

This will create a cosignature transaction for said aggregate bonded, of course then map this function to a list of aggregate transactions as done here: https://github.com/evias/nem2-sandbox/blob/master/src/commands/transaction/cosignMultisig.ts#L126

:+1:

I think my question is how to convert serialize transaction to AggregateTransaction object.

const cosignatureTransaction = CosignatureTransaction.create(transaction); // <= transaction is serialize, how to convert recreate back to AggregateTransaction
    return account.signCosignatureTransaction(cosignatureTransaction);

then you will need to collect the transaction hash + payload after initiating it.

Say you have an aggregate bonded transaction named “AB1” and inside of it a MultisigAccountModificationTransaction named “M1” that will need cosignatures from Alice (A), Bob (B) and Carol ©.

You have to initiate the transaction in your code, then sign it once with the multi-signature account itself (the one that will get converted). So you do something along these lines:

const AB1 = AggregateTransaction.createBonded(.., [M1], ...);
const signed = multisigAccount.sign(AB1, ...);

// you could also already sign with 1 of the 3 cosigs if you wish: signWithCosignatories(.., [M, A], ..)

const txHash = signed.hash;
const txPayload = signed.payload;

With this txHash and txPayload, in a secondary process you can now cosign the transaction offline.

In your offline device code:

const txPayload = 'XXX'; // from above
const txHash = 'XXX'; // from above

const AB1 = TransactionMapping.createFromPayload(txPayload);
const cosigBob = cosignHelper(AB1, Bob);
const cosigCarol = cosignHelper(AB1, Carol);

And in some collection/merge process then you would retrieve the offline signatures somehow and merge to your bonded:

const txPayload = 'XXX'; // this is signed only by multisig
const cosigBob = 'XXX'; // this is signed by bob
const cosigCarol = 'XXX'; // ...

const AB1 = TransactionMapping.createFromPayload(txPayload);
const fullySigned = AB.signTransactionGivenSignatures(M, [cosigBob, cosigCarol], ..);

Update:
This help: https://github.com/nemtech/nem2-sdk-typescript-javascript/pull/131


CosigHelper 1st param require Aggregatetransaction, this is where I stuck with.

const AB1 = TransactionMapping.createFromPayload(txPayload); // This generate Transaction
const cosigBob = cosignHelper(AB1, Bob); // cosigHelper 1 param require Aggregatetransaction?

const cosignHelper = (
            transaction: AggregateTransaction,
            account: Account
        ): CosignatureSignedTransaction => {
            const cosignatureTransaction = CosignatureTransaction.create(transaction);
            return account.signCosignatureTransaction(cosignatureTransaction);
        };

So I converted it to

const AB1 = <AggregateTransaction> TransactionMapping.createFromPayload(txPayload);

But get error:

Error: transaction to cosign should be announced first

Should it like below?

const txHash = '..';
const txPayload  = '"..';
const signedTxAlice = CosignatureTransaction.signTransactionPayload(aliceAccount, txPayload, txHash);
const signedTxBob = CosignatureTransaction.signTransactionPayload(bobAccount, txPayload, txHash);

const cosignatureSignedTransactions = [
    new CosignatureSignedTransaction(signedTxAlice.parentHash, signedTxAlice.signature, signedTxAlice.signer),
    new CosignatureSignedTransaction(signedTxBob.parentHash, signedTxBob.signature, signedTxBob.signer),
];

const AB1 = TransactionMapping.createFromPayload(txPayloa
const fullySigned = multisigAccount.signTransactionGivenSignatures(AB1, [cosigBob, cosigCarol], ..);

I get Failure_Signature_Not_Verifiable

Thats a bug being addressed in the SDK. try to use the sdk master branch, the first piece of code is correct, the second not.

this is the correct piece of code.

Bug is in SDK: https://github.com/nemtech/nem2-sdk-typescript-javascript/issues/251

I think its solved in master, but im not sure. As a draft fix, you can mock the TransactionInfo to be present in your aggregate :

const AB1 = ...;
AB1.transactionInfo = new TransactionInfo(...);

// now cosign

Hopefully this bug will be solved in sdk soon.

Since this is a multisig conversion, it require lock hash. I got this Failure_LockHash_Hash_Does_Not_Exist.

I made a HashLockTransaction and announce it to the network, retrieve the transaction from the listener.

 listener
        .aggregateBondedAdded(multisigAccount.address)
        .pipe(
          filter(
            transaction =>
              transaction.transactionInfo !== undefined && transaction.transactionInfo.hash === signedTransaction.hash,
          ),
        )
        .subscribe(
          transaction => {
            console.log(transaction.serialize()); // <= use this to sign at cosigner devices. 

Use the same method to sign :

const txPayload  = signedTransaction.payload; // above payload (transaction.serialize())
const signedTxAlice = CosignatureTransaction.signTransactionPayload(aliceAccount, txPayload, networkGenerationHash);
const signedTxBob = CosignatureTransaction.signTransactionPayload(bobAccount, txPayload, networkGenerationHash);

const cosignatureSignedTransactions = [
    new CosignatureSignedTransaction(signedTxAlice.parentHash, signedTxAlice.signature, signedTxAlice.signer),
    new CosignatureSignedTransaction(signedTxBob.parentHash, signedTxBob.signature, signedTxBob.signer),
]; 

const recreatedTx = TransactionMapping.createFromPayload(txPayload) as AggregateTransaction;
const fullySigned  = multisigAccount.signTransactionGivenSignatures(recreatedTx, cosignatureSignedTransactions, networkGenerationHash);
transactionHttp.announce(fullySigned).subscribe(
    x => console.log(x),
    err => {
        console.error(err);
        listener.close();
    },
); 

announce it successfully, but didn’t get any error from status and it is not uncomfimed and comfirmed, nothing happen.

Yes use this, but get Error: transaction to cosign should be announced first, I am using version 0.13.1.

I saw some PR and merging, but don’t know is it solved.

it should be fixed if you use dev-master in package.json :+1:

I can’t see the dev-master, just master branch.

put dev-master in your package.json, that will use the master branch of the package.

Okay I am using master branch, get this error:

TypeError: Cannot read property 'hash' of undefined
    at CosignatureTransaction.signWith (C:\...\node_modules\nem2-sdk\src\model\transaction\CosignatureTransaction.ts:81:56)
    at Account.signCosignatureTransaction (C:\...\node_modules\nem2-sdk\src\model\account\Account.ts:197:39)

When at:

account.signCosignatureTransaction(cosignatureSignedTransaction);

Where do put the transaction hash or the payload generated don’t have hash?

I saw txHash, but it never be use?

I think the recreate aggregate transaction transactionInfo is missing

hash needs to be in the TransactionInfo

Step 1: Create aggregate + construct transaction on device A

const M = multisigAccount_that_will_be_converted
const A = cosignatory_A
const B = coosignatory_B

// construct modification transaction
const M1 = MultisigAccountModificationTransaction.create(...)

// create aggregate
const AB1 = AggregateTransaction.createBonded(.., [M1], ...);
const signed = AB1.signWithCosignatories([M, A], ...);

const txHash = signed.hash;
const txPayload = signed.payload;

Step 2: Share txHash and txPayload with device B

Step 3: Re-create aggregate and cosign on device B

const txPayload = 'XXX'; // from above
const txHash = 'XXX'; // from above

// re-create aggregate
const AB1 = TransactionMapping.createFromPayload(txPayload);
AB.transactionInfo = new TransactionInfo(..., txHash, ...);

// cosign with B
const cosigB = cosignHelper(AB1, B);

Step 4: Execute step 2 and step 3 with all required cosignatories

Step 5: Collect all cosignatures from Step 3 and Merge to get transaction with all cosignatures

const txPayload = 'XXX'; // this is signed by M and A
const cosigB = 'XXX'; // this is signed by B (Step 3)

// re-create aggregate
const AB1 = TransactionMapping.createFromPayload(txPayload);
AB.transactionInfo = new TransactionInfo(..., txHash, ...);

// merge cosignatures
const fullySigned = AB.signTransactionGivenSignatures(M, [cosigBob, cosigCarol], ..);

Now fullySigned can be broadcast to the network. Since you are using aggregate bonded, you could also broadcast other parts already, before its fully signed.

Because you are not making use of aggregate bonded (partial) transactions endpoint, I would recommend to use AggregateTransaction.createComplete because at the time you will broadcast the transaction, it will have all signatures (“is complete”).

Cannot assign to ‘transactionInfo’ because it is a read-only property…

So this solution is not workable?

Sorry it will need a bit of debug on your side. The latest SDK release has a fix for this (v0.13.2) when transactionInfo is missing

If you use latest release, you should be able to cosign without adding the “transactionInfo” field

I am using https://registry.npmjs.org/nem2-sdk/-/nem2-sdk-0.13.2.tgz, which has released 2 days ago…

I am still facing the same error:

TypeError: Cannot read property ‘hash’ of undefined