Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add non-sequential multi-sig support #1710

Merged
merged 11 commits into from
Jun 30, 2024
Merged

Conversation

janniks
Copy link
Collaborator

@janniks janniks commented Jun 22, 2024

This PR was published to npm with the version 6.15.0
e.g. npm install @stacks/common@6.15.0 --save-exact

  • Fixes incorrect compression check in signing algorithm
  • Fixes incorrect compression in deserialization
  • Fixes incorrect field order for legacy (sequential) multisig
  • Cleans up opts types/interfaces for less duplication

Related:


Adds a useNonSequentialMultiSig flag to multi-sig transaction creation methods. (Along with the internals to support it)

So far this opt is marked @experimental, since the interface of the default could change. (And we likely want to see how the authorization type plays out on mainnet, before switching fully)

Example

      const tx = await makeSTXTokenTransfer({
        recipient: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
        amount: 12_345n,
        fee: 1_000n,
        nonce: 2n,
        network: 'testnet',

        numSignatures: required,
        publicKeys: [k1, k2, k3],
        signerKeys: [k3, k1], // could sign in any order (even if serializing in between and not sharing state)

        useNonSequentialMultiSig: true, // <--- NEW OPTIONAL FLAG
      });

Comment

The separate signing test really shows there is more work needed to make this easy to use for folks building a "real" multi-sig experience -- with serializing the transaction in between signatures. This could be added as a separate issue or addressed in a breaking change (there is an opportunity to clean up the TransactionSigner as well, which is intertwined with the StacksTransaction class).

Follow up thoughts: This can probably be addressed with one mutating public method on the StacksTransaction called something like .finalize() or .finalizeMultiSig(), which ensures the transaction-auth-fields are in the intended order. (Might need the list of public-keys again, or can do an opinionated .sort first, but technically should work for legacy multi-sig as well).

Verified

This commit was signed with the committer’s verified signature.
crazy-max CrazyMax
…y change in the future)
Copy link

vercel bot commented Jun 22, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
stacksjs-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 28, 2024 6:50pm

@janniks janniks self-assigned this Jun 22, 2024
Copy link

codecov bot commented Jun 22, 2024

Codecov Report

Attention: Patch coverage is 95.09804% with 5 lines in your changes missing coverage. Please review.

Files Patch % Lines
packages/transactions/src/builders.ts 88.09% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

numSignatures: number;
publicKeys: string[];
}
export type UnsignedMultiSigTokenTransferOptions = TokenTransferOptions & UnsignedMultiSigOptions;
Copy link
Collaborator Author

@janniks janniks Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored these types to be more reusable. Does anybody think the change from interface to type is a problem?

signer.appendOrigin(publicKeyFromBytes(hexToBytes(publicKey)));
}
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbencin you'll notice this now differs from the blockchain make methods, but I think is more correct. We try to sign/append in order of public-keys, rather than first all signs and then all appends (not exactly sure why that was in the code previously, but seems to be inspired by stacks-core methods)

const stacksPublicKeys = pubKeys.map(createStacksPublicKey);
const publicKeys = pubKeys.slice();

// sort public keys (only for newer non-sequential multi-sig for backwards compatibility)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be handled the same way in all multisig transaction types: We recommend that the user sort their pubkeys when creating, but we can't force it because users may have funds in accounts that don't use the recommended ordering

You could add a boolean argument, or create another version of this function, createMultiSigSpendingConditionSorted, if you want to provide sorting functionality to the user. But you can't just assume the user wants it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I would like to be more opinionated about it, so maybe sorting can be the default and we add a disablePublicKeySorting flag. But yeah, this should be a part of all the helpers then as well.. Will change 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe not, 🤔 technically the developer can still update and replace conditions/modes/etc. on any transaction fairly easily in Stacks.js (assuming they know what they're doing). Similar to how we don't really expose p2wsh multi-sig, we can "hide" the unsorted variant a bit more as well 🤷🏻‍♀️ trying to find a balance for users to be able to get less wrong by accident.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even with sequential multisig transactions, it was customary to sort the pubkeys before creating a transaction, but it was never enforced. So new multisig types should not handle this differently

One thing you could do, which would work for both types and would be backwards compatible, is to add an optional address parameter. Users would be encouraged to supply the address of the multisig account, and you could check if the pubkeys generate that address, first in the order given, then sort them and try in that order. If it doesn't match in either order, return an error

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely approach!!! I really like this more than another config flag!! 👏

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking about next breaking release, I would like to change the default behavior to "sorted" to guide the ecosystem to the preferred way
.

@janniks
Copy link
Collaborator Author

janniks commented Jun 28, 2024

Ready for re-review @jbencin 🙏

@janniks janniks changed the title feat: Add non-sequential multi-sig support (interface and defaults ma… feat: Add non-sequential multi-sig support Jun 28, 2024
Comment on lines +175 to +177
privateKey.data.byteLength === PRIVATE_KEY_COMPRESSED_LENGTH
? PubKeyEncoding.Compressed
: PubKeyEncoding.Uncompressed,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be safer to have a switch here, and fail if privateKey.data.byteLength isn't PRIVATE_KEY_COMPRESSED_LENGTH or PRIVATE_KEY_UNCOMPRESSED_LENGTH

Copy link
Member

@jbencin jbencin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work here! Any of the discussions that are still open I consider very minor and won't hold up approval for them

@janniks janniks merged commit 879263c into main Jun 30, 2024
4 checks passed
@janniks janniks deleted the non-sequential-multi-sig branch June 30, 2024 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Multisig transaction signing order
2 participants