--- robots: noindex, nofollow --- # `signtool` Command-Line Tool Blockchain Commons Wolf McNally, Lead Researcher October 26, 2024 **Note:** This is an early draft working document. There is no implementation of this at this time. ## Overview `signtool` is a command-line utility for secure, multiparty signing of [Gordian Envelopes](https://www.blockchaincommons.com/introduction/Envelope-Intro/) using two leading cryptographic protocols: [MuSig2](https://eprint.iacr.org/2020/1261) and [FROST](https://eprint.iacr.org/2020/852). Both protocols leverage [Schnorr signatures](https://en.wikipedia.org/wiki/Schnorr_signature), and (by default) [BIP-340 Schnorr signatures on the secp256k1 curve](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki), ensuring compatibility with Bitcoin and other systems using the same cryptographic standards. By integrating both n-of-n (MuSig2) and m-of-n threshold (FROST) signing methods into a single, unified tool, `signtool` allows users to select the optimal signing approach for their collaborative or decentralized applications built on Gordian Envelope. **Note:** The existing [`envelope` command line tool](https://crates.io/crates/bc-envelope-cli) provides support for a variety of single-signer and non-aggregated multisignature schemes for Envelope. ## Key Features ### Session Context A Session Context (`SESSION`) is an immutable data structure that contains all persistent attributes of the session, including: - Unique session identifier (SID) - Participant Identifiers (XIDs or XID documents) - Threshold (m) for FROST sessions - Envelope to be signed This structured approach ensures that all necessary information is preserved and used consistently throughout the signing process. A `SESSION` is not associated with partial keys or nonces. It can be thought of as an invitation to participate in a signing session. ### Participant Identifiers Participant Identifiers (XIDs) are embedded within data structures, such as partial keys, nonces, and partial signatures, to ensure that each participant’s contributions are uniquely identified and ordered. XIDs also provide a cryptographic foundation for identity and privacy in the signing process. ### Unified Interface With top-level `musig2` and `frost` subcommands, `signtool` provides a coherent experience across both protocols. A single, algorithm-agnostic `verify` command (essentially the same as the `envelope` tool's `verify` command) provides signature validation, allowing any generated signature to be authenticated regardless of the signing method used. ## `musig2` Subcommand Implements the MuSig2 multisignature protocol (n-of-n signing). ### `musig2` Overview ``` signtool musig2 <command> [options] ``` | **Step** | **Command** | **Description** | |--------------------------------|---------------------------|-----------------| | **Session Setup** | `musig2 setup-session` | Initializes the `SESSION` with the message and participant XIDs. | | **Key Pair Generation** | `musig2 genkey` | Participants generate their partial keys. | | **Group Public Key** | `musig2 aggregate-pubkey` | Computes the group public key from all partial public keys. | | **Nonce Generation** | `musig2 gen-nonce` | Each participant generates a nonce pair. | | **Partial Signature Creation** | `musig2 partial-sign` | Creates each participant's partial signature using their nonce and partial key. | | **Aggregate Signing** | `musig2 aggregate-sign` | Aggregates all partial signatures into a final signature for the message. | ### 1.1. `musig2 setup-session` Initialize a new MuSig2 signing session and output the SESSION. ``` signtool musig2 setup-session \ --message <ur:envelope> \ --xid <ur:xid> [--xid <ur:xid>]... ``` - `--message <ur:envelope>`: The envelope to be signed. - `--xid <ur:xid>`: Participant Identifier XID or XID document. Repeat for each participant. Output: - Session Context (`ur:musig2-session`) ### 1.2. `musig2 genkey` Generate a partial key pair with an embedded XID. ``` signtool musig2 genkey \ --xid <ur:xid> ``` - `--xid <ur:xid>`: Your Participant Identifier (`ur:xid`). Output: - Partial private key: <ur:musig2-partial-private-key> - Partial public key: <ur:musig2-partial-public-key> The partial key pair may be used across multiple sessions, or may be ephemeral for a single session. ### 1.3. `musig2 aggregate-pubkey` Compute the group public key. ``` signtool musig2 aggregate-pubkey \ --public-key <ur:musig2-partial-public-key> \ [--public-key <ur:musig2-partial-public-key>]... ``` - `--public-key <ur:musig2-partial-public-key>`: Partial public key. All participants must provide their partial public keys. Keys may be provided in any order, as XIDs are inherently ordered and embedded in the keys. Output: - Group public key: <ur:signing-public-key> ### 1.4. `musig2 gen-nonce` Generate a nonce pair for signing. ``` signtool musig2 gen-nonce \ --session <ur:musig2-session> \ --private-key <ur:musig2-partial-private-key> ``` - `--session <ur:musig2-session>`: The Session Context. - `--private-key <ur:musig2-partial-private-key>`: Your private key. Output: - Private Nonce: <ur:musig2-private-nonce> - Public Nonce: <ur:musig2-public-nonce> ### 1.5. `musig2 partial-sign` Create a partial signature. ``` signtool musig2 partial-sign \ --session <ur:musig2-session> \ --private-key <ur:musig2-partial-private-key> \ --private-nonce <ur:musig2-private-nonce> \ --aggregated-pubkey <ur:signing-public-key> \ --public-key <ur:musig2-partial-public-key> \ [--public-key <ur:musig2-partial-public-key>]... \ --public-nonce <ur:musig2-public-nonce> \ [--public-nonce <ur:musig2-public-nonce>]... ``` - `--session <ur:musig2-session>`: The Session Context. - `--private-key <ur:musig2-partial-private-key>`: Your partial private key. - `--private-nonce <ur:musig2-private-nonce>`: Your private nonce. - `--aggregated-pubkey <ur:signing-public-key>`: Group public key. - `--public-key <ur:musig2-partial-public-key>`: Partial public keys of all participants. - `--public-nonce <ur:musig2-public-nonce>`: Public nonces of all participants. Output: - Partial Signature: <ur:musig2-partial-signature> ### 1.6. `musig2 aggregate-sign` Produce a final signed envelope by aggregating partial signatures into the final signature. This signature can be verified using the signed envelope and the group public key. ``` signtool musig2 aggregate-sign \ --session <ur:musig2-session> \ --aggregated-pubkey <ur:signing-public-key> \ --partial-signature <ur:musig2-partial-signature> \ [--partial-signature <ur:musig2-partial-signature>]... \ --public-nonce <ur:musig2-public-nonce> \ [--public-nonce <ur:musig2-public-nonce>]... ``` - `--session <ur:musig2-session>`: The Session Context. - `--aggregated-pubkey <ur:signing-public-key>`: Group public key. - `--partial-signature <ur:musig2-partial-signature>`: Partial signatures with embedded PIDs. - `--public-nonce <ur:musig2-public-nonce>`: Public nonces with embedded PIDs. Output: - Final Signature: <ur:signature> ## `frost` Subcommand Implements the FROST threshold signature protocol (m-of-n signing). ### `frost` Overview ``` signtool frost <command> [options] ``` | **Step** | **Command** | **Description** | |--------------------------------|----------------------------|-----------------| | **Session Setup** | `frost setup-session` | Initializes the `SESSION` with the message, threshold, and participant XIDs. | | **Key Share Generation** | `frost gen-key-share` | Participants generate secret key shares and public commitments. | | **Combine Key Shares** | `frost combine-key-shares` | Computes the group public key by combining all participants’ commitments. | | **Nonce Generation** | `frost gen-nonce` | Each participant generates a nonce pair. | | **Partial Signature Creation** | `frost partial-sign` | Creates each participant's partial signature using their nonce and key share. | | **Aggregate Signing** | `frost aggregate-sign` | Aggregates all partial signatures into a final signature for the message. | ### 2.1. `frost setup-session` Initialize a new FROST signing session and output the SESSION. ``` signtool frost setup-session \ --message <ur:envelope> \ --threshold <t> \ --xid <ur:xid> [--xid <ur:xid>]... ``` - `--message <ur:envelope>`: The envelope to be signed. - `--threshold <t>`: Threshold value t, where t is the number of signatures required for the final signature and must be less than or equal to the number of participants. - `--xid <ur:xid>`: Participant Identifier XID or XID document. Repeat for each participant. Output: - Session Context (`ur:frost-session`) ### 2.2. `frost genkey` Generate a partial key pair with an embedded XID. ``` signtool frost genkey \ --xid <ur:xid> ``` - `--xid <ur:xid>`: Your Participant Identifier (`ur:xid`). Output: - Partial private key: <ur:frost-partial-private-key> - Partial public key: <ur:frost-partial-public-key> The partial key pair may be used across multiple sessions, or may be ephemeral for a single session. ### 2.3. `frost gen-key-share` Generate your secret key share and public commitments. ``` signtool frost gen-key-share \ --session <ur:frost-session> \ --private-key <ur:frost-partial-private-key> ``` - `--session <ur:frost-session>`: The Session Context. - `--private-key <ur:frost-partial-private-key>`: Your Partial Private Key. Output: - Secret Key Share: <ur:frost-secret-key-share> - Verification Commitments with PID: <ur:frost-commitments> ### 2.4. `frost combine-key-shares` Validates that each commitment is correctly derived and aligns with the session and and its participants to prevent malformed or incorrect contributions. Then combines key shares to compute the group public key. The set of participants in this step MUST be a subset of the participants in the session, and this subset MUST be the same for all subsequent steps. ``` signtool frost combine-key-shares \ --session <ur:frost-session> \ --commitments <ur:frost-commitments> \ [--commitments <ur:frost-commitments>]... ``` - `--session <ur:frost-session>`: The Session Context. - `--commitments <ur:frost-commitments>`: The commitments of all participants. Output: - Group Public Key: <ur:signing-public-key> ### 2.5. `frost gen-nonce` Generate a nonce pair for signing. ``` signtool frost gen-nonce \ --session <ur:frost-session> ``` - `--session <ur:frost-session>`: The Session Context. Output: - Private Nonce: <ur:frost-private-nonce> - Public Nonce: <ur:frost-public-nonce> ### 2.6. `frost partial-sign` Create a partial signature. The set of participants in this step MUST be the same as the set of participants in the `combine-key-shares` step. ``` signtool frost partial-sign \ --session <ur:frost-session> \ --secret-key-share <ur:frost-secret-key-share> \ --private-nonce <ur:frost-private-nonce> \ --public-nonce <ur:frost-public-nonce> \ [--public-nonce <ur:frost-public-nonce>]... \ --group-public-key <ur:signing-public-key> ``` - `--session <ur:frost-session>`: The Session Context. - `--secret-key-share <ur:frost-secret-key-share>`: Your secret key share. - `--private-nonce <ur:frost-private-nonce>`: Your private nonce. - `--public-nonce <ur:frost-public-nonce>`: Public nonces for all participants. - `--group-public-key <ur:signing-public-key>`: The group public key. Output: - Your partial signature: <ur:frost-partial-signature> ### 2.7. `frost aggregate-sign` Produce a final signed envelope by aggregating partial signatures into the final signature. This signature can be verified using the signed envelope and the group public key. The set of participants in this step MUST be the same as the set of participants in the `combine-key-shares` step. ``` signtool frost aggregate-sign \ --session <ur:frost-session> \ --group-public-key <ur:signing-public-key> \ --partial-signature <ur:frost-partial-signature> \ [--partial-signature <ur:frost-partial-signature>]... \ --public-nonce <ur:frost-public-nonce> \ [--public-nonce <ur:frost-public-nonce>]... ``` - `--session <ur:frost-session>`: The Session Context. - `--group-public-key <ur:signing-public-key>`: Group public key. - `--partial-signature <ur:frost-partial-signature>`: Partial signatures from all participants. - `--public-nonce <ur:frost-public-nonce>`: Public nonces from all participants. Output: - Final Signature: <ur:signature> ## `verify` Subcommand An algorithm-agnostic command to verify signatures produced by musig2, FROST, or other signing protocols. ``` signtool verify \ --message <ur:envelope> \ --public-key <ur:signing-public-key> -s --silent ``` - `--message <ur:envelope>`: The signed envelope. - `--public-key <ur:signing-public-key>`: The verifying public key, which may be a group public key generated by musig2 or FROST, or a single public key. - `-s`, `--silent`: Don't output the envelope's UR on success. Output: - If the signature is valid, the tool will output the envelope's UR (or nothing if the `--silent` option is present). - If the signature is invalid, the tool will output an error message and return a non-zero exit code. ## Musig2 Example In this example, we demonstrate how to perform an n-of-n multisignature using the signtool musig2 subcommands. Assume there are three participants: Alice, Bob, and Carol. Each participant has a unique Participant Identifier (XID). The goal is to collectively sign a message (an Envelope) using the MuSig2 protocol. ### Step 1: Session Setup Alice initializes a new MuSig2 session by specifying the message to be signed and the XIDs of all participants. ``` $ signtool musig2 setup-session \ --message ur:envelope/unsigned \ --xid ur:xid/alice \ --xid ur:xid/bob \ --xid ur:xid/carol ur:musig2-session ``` ### Step 2: Generate Partial Keys Each participant generates their own partial key pair with their embedded XID. For Alice: ``` $ signtool musig2 genkey \ --xid ur:xid/alice ur:musig2-partial-private-key/alice ur:musig2-partial-public-key/alice ``` ### Step 3: Compute Group Public Key Participants exchange their partial public keys. Any participant can compute the group public key. ``` $ signtool musig2 aggregate-pubkey \ --public-key ur:musig2-partial-public-key/alice \ --public-key ur:musig2-partial-public-key/bob \ --public-key ur:musig2-partial-public-key/carol ur:signing-public-key ``` ### Step 4: Generate Nonces Each participant generates a nonce pair for signing using their partial private key and the session context. For Alice: ``` $ signtool musig2 gen-nonce \ --session ur:musig2-session \ --private-key ur:musig2-partial-private-key/alice ur:musig2-private-nonce/alice ur:musig2-public-nonce/alice ``` ### Step 5: Exchange Public Nonces Participants exchange their public nonces. Each participant should now have all public nonces: ``` ur:musig2-public-nonce/alice ur:musig2-public-nonce/bob ur:musig2-public-nonce/carol ``` ### Step 6: Create Partial Signatures Each participant creates their partial signature using their private key, private nonce, the group public key, all partial public keys, and all public nonces. For Alice: ``` $ signtool musig2 partial-sign \ --session ur:musig2-session \ --private-key ur:musig2-partial-private-key/alice \ --private-nonce ur:musig2-private-nonce/alice \ --aggregated-pubkey ur:signing-public-key \ --public-key ur:musig2-partial-public-key/alice \ --public-key ur:musig2-partial-public-key/bob \ --public-key ur:musig2-partial-public-key/carol \ --public-nonce ur:musig2-public-nonce/alice \ --public-nonce ur:musig2-public-nonce/bob \ --public-nonce ur:musig2-public-nonce/carol ur:musig2-partial-signature/alice ``` ### Step 7: Aggregate Partial Signatures Any participant (or a designated aggregator) can aggregate the partial signatures to produce the final signature. ``` $ signtool musig2 aggregate-sign \ --session ur:musig2-session \ --aggregated-pubkey ur:signing-public-key \ --partial-signature ur:musig2-partial-signature/alice \ --partial-signature ur:musig2-partial-signature/bob \ --partial-signature ur:musig2-partial-signature/carol \ --public-nonce ur:musig2-public-nonce/alice \ --public-nonce ur:musig2-public-nonce/bob \ --public-nonce ur:musig2-public-nonce/carol ur:envelope/signed ``` ### Step 8: Verify the Signature Anyone can verify the signature using the message (envelope) and the group public key. If the signature is valid, the tool outputs the envelope's UR, facilitating command pipelining. ``` $ signtool verify \ --message ur:envelope/signed \ --public-key ur:signing-public-key ur:envelope/signed ``` ## FROST Example In this example, we demonstrate how to perform an m-of-n threshold signature using the signtool frost subcommands. Assume there are five participants: Alice, Bob, Carol, Dave, and Eve. Each participant has a unique Participant Identifier (XID). The threshold is set to 3, meaning any combination of 3 participants can collaboratively sign a message (an Envelope) using the FROST protocol. ### Step 1: Session Setup Alice initializes a new FROST session by specifying the message to be signed, the threshold value, and the XIDs of all participants. ``` $ signtool frost setup-session \ --message ur:envelope/unsigned \ --threshold 3 \ --xid ur:xid/alice \ --xid ur:xid/bob \ --xid ur:xid/carol \ --xid ur:xid/dave \ --xid ur:xid/eve ur:frost-session ``` ### Step 2: Generate Partial Keys Each participant generates their own partial key pair with their embedded XID. For Alice: ``` $ signtool frost genkey \ --xid ur:xid/alice ur:frost-partial-private-key/alice ur:frost-partial-public-key/alice ``` ### Step 3: Generate Key Shares and Commitments Each participant generates their secret key share and public commitments using their partial private key and the session context. For Alice: ``` $ signtool frost gen-key-share \ --session ur:frost-session \ --private-key ur:frost-partial-private-key/alice ur:frost-secret-key-share/alice ur:frost-commitments/alice ``` ### Step 4: Combine Key Shares Participants exchange their commitments. Any participant can combine the key shares to compute the group public key. ``` $ signtool frost combine-key-shares \ --session ur:frost-session \ --commitments ur:frost-commitments/alice \ --commitments ur:frost-commitments/bob \ --commitments ur:frost-commitments/carol \ --commitments ur:frost-commitments/dave \ --commitments ur:frost-commitments/eve ur:signing-public-key ``` ### Step 5: Generate Nonces Participants who are part of the signing group generate nonce pairs using the session context. Since the threshold is 3, let’s assume Alice, Bob, and Carol decide to sign the message. For Alice: ``` $ signtool frost gen-nonce \ --session ur:frost-session ur:frost-private-nonce/alice ur:frost-public-nonce/alice ``` ### Step 6: Exchange Public Nonces The signing participants exchange their public nonces. Each signer should now have all public nonces. ``` ur:frost-public-nonce/alice ur:frost-public-nonce/bob ur:frost-public-nonce/carol ``` ### Step 7: Create Partial Signatures Each signer creates their partial signature using their secret key share, private nonce, group public key, and the public nonces of all signers. For Alice: ``` $ signtool frost partial-sign \ --session ur:frost-session \ --secret-key-share ur:frost-secret-key-share/alice \ --private-nonce ur:frost-private-nonce/alice \ --public-nonce ur:frost-public-nonce/alice \ --public-nonce ur:frost-public-nonce/bob \ --public-nonce ur:frost-public-nonce/carol \ --group-public-key ur:signing-public-key ur:frost-partial-signature/alice ``` ### Step 8: Aggregate Partial Signatures Any participant (or a designated aggregator) can aggregate the partial signatures to produce the final signature. ``` $ signtool frost aggregate-sign \ --session ur:frost-session \ --group-public-key ur:signing-public-key \ --partial-signature ur:frost-partial-signature/alice \ --partial-signature ur:frost-partial-signature/bob \ --partial-signature ur:frost-partial-signature/carol \ --public-nonce ur:frost-public-nonce/alice \ --public-nonce ur:frost-public-nonce/bob \ --public-nonce ur:frost-public-nonce/carol ur:envelope/signed ``` ### Step 9: Verify the Signature Anyone can verify the signature using the signed envelope and the group public key. If the signature is valid, the tool outputs the envelope’s UR, facilitating command pipelining. ``` $ signtool verify \ --message ur:envelope/signed \ --public-key ur:signing-public-key ur:envelope/signed ```