├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── benches ├── sign.rs └── verify.rs ├── src ├── key_sort.rs ├── test_vectors │ ├── key_sort_vectors.json │ ├── nonce_agg_vectors.json │ ├── nonce_gen_vectors.json │ ├── key_agg_vectors.json │ ├── tweak_vectors.json │ ├── sig_agg_vectors.json │ ├── bip340_vectors.csv │ └── sign_verify_vectors.json ├── lib.rs ├── testhex.rs ├── deterministic.rs ├── tagged_hashes.rs ├── sig_agg.rs ├── binary_encoding.rs ├── errors.rs ├── signature.rs ├── bip340.rs └── signing.rs ├── LICENSE ├── Makefile ├── Cargo.toml ├── README.md ├── doc ├── adaptor_signatures.md └── API.md └── tests └── fuzz_against_reference_impl.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | __pycache__ 3 | /Cargo.lock 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" 11 | -------------------------------------------------------------------------------- /benches/sign.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | use secp::Scalar; 3 | 4 | extern crate test; 5 | 6 | #[bench] 7 | fn bip340_verify_single(b: &mut test::Bencher) { 8 | let seckey: Scalar = "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9" 9 | .parse() 10 | .unwrap(); 11 | let pubkey = seckey.base_point_mul(); 12 | let message = b"hey there"; 13 | b.iter(|| { 14 | let signature: [u8; 64] = musig2::deterministic::sign_solo(seckey, message); 15 | signature 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /benches/verify.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | use secp::Scalar; 3 | 4 | extern crate test; 5 | 6 | #[bench] 7 | fn bip340_verify_single(b: &mut test::Bencher) { 8 | let seckey: Scalar = "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9" 9 | .parse() 10 | .unwrap(); 11 | let pubkey = seckey.base_point_mul(); 12 | let message = b"hey there"; 13 | let signature: [u8; 64] = musig2::deterministic::sign_solo(seckey, message); 14 | b.iter(|| { 15 | let _ = musig2::verify_single(pubkey, signature, message); 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/key_sort.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use secp::Point; 4 | 5 | #[test] 6 | fn test_sort_public_keys() { 7 | const KEY_SORT_VECTORS: &[u8] = include_bytes!("test_vectors/key_sort_vectors.json"); 8 | 9 | #[derive(serde::Deserialize)] 10 | struct KeySortVectors { 11 | pubkeys: Vec, 12 | sorted_pubkeys: Vec, 13 | } 14 | 15 | let vectors: KeySortVectors = serde_json::from_slice(KEY_SORT_VECTORS) 16 | .expect("failed to decode key_sort_vectors.json"); 17 | 18 | let mut pubkeys = vectors.pubkeys; 19 | pubkeys.sort(); 20 | assert_eq!(pubkeys, vectors.sorted_pubkeys); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test_vectors/key_sort_vectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkeys": [ 3 | "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", 4 | "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", 5 | "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 6 | "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", 7 | "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF", 8 | "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8" 9 | ], 10 | "sorted_pubkeys": [ 11 | "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", 12 | "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", 13 | "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", 14 | "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF", 15 | "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", 16 | "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: check check-* test test-* 2 | check: check-default check-mixed check-secp256k1 check-k256 3 | 4 | # Checks the source code with default features enabled. 5 | check-default: 6 | cargo clippy -- -D warnings 7 | 8 | # Checks the source code with all features enabled. 9 | check-mixed: 10 | cargo clippy --all-features -- -D warnings 11 | cargo clippy --all-features --tests -- -D warnings 12 | 13 | # Checks the source code with variations of libsecp256k1 feature sets. 14 | check-secp256k1: 15 | cargo clippy --no-default-features --features secp256k1 -- -D warnings 16 | cargo clippy --no-default-features --features secp256k1,serde -- -D warnings 17 | cargo clippy --no-default-features --features secp256k1,serde,rand -- -D warnings 18 | cargo clippy --no-default-features --features secp256k1,serde,rand --tests -- -D warnings 19 | 20 | # Checks the source code with variations of pure-rust feature sets. 21 | check-k256: 22 | cargo clippy --no-default-features --features k256 -- -D warnings 23 | cargo clippy --no-default-features --features k256,serde -- -D warnings 24 | cargo clippy --no-default-features --features k256,serde,rand -- -D warnings 25 | cargo clippy --no-default-features --features k256,serde,rand --tests -- -D warnings 26 | 27 | 28 | test: test-default test-mixed test-secp256k1 test-k256 29 | 30 | test-default: 31 | cargo test 32 | 33 | test-mixed: 34 | cargo test --all-features 35 | 36 | test-secp256k1: 37 | cargo test --no-default-features --features secp256k1,serde,rand 38 | 39 | test-k256: 40 | cargo test --no-default-features --features k256,serde,rand 41 | 42 | .PHONY: docwatch 43 | docwatch: 44 | watch -n 5 cargo doc --all-features 45 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![doc = include_str!("../doc/API.md")] 3 | #![allow(non_snake_case)] 4 | #![warn(missing_docs)] 5 | 6 | #[cfg(all(not(feature = "secp256k1"), not(feature = "k256")))] 7 | compile_error!("At least one of the `secp256k1` or `k256` features must be enabled."); 8 | 9 | #[macro_use] 10 | mod binary_encoding; 11 | 12 | mod bip340; 13 | mod key_agg; 14 | mod key_sort; 15 | mod nonces; 16 | mod rounds; 17 | mod sig_agg; 18 | mod signature; 19 | mod signing; 20 | 21 | #[doc = include_str!("../doc/adaptor_signatures.md")] 22 | pub mod adaptor { 23 | pub use crate::bip340::sign_solo_adaptor as sign_solo; 24 | pub use crate::bip340::verify_single_adaptor as verify_single; 25 | pub use crate::sig_agg::aggregate_partial_adaptor_signatures as aggregate_partial_signatures; 26 | pub use crate::signature::AdaptorSignature; 27 | pub use crate::signing::sign_partial_adaptor as sign_partial; 28 | pub use crate::signing::verify_partial_adaptor as verify_partial; 29 | } 30 | 31 | pub mod deterministic; 32 | pub mod errors; 33 | pub mod tagged_hashes; 34 | 35 | pub use binary_encoding::*; 36 | pub use bip340::{sign_solo, verify_single}; 37 | pub use key_agg::*; 38 | pub use nonces::*; 39 | pub use rounds::*; 40 | pub use sig_agg::aggregate_partial_signatures; 41 | pub use signature::*; 42 | pub use signing::{ 43 | compute_challenge_hash_tweak, sign_partial, verify_partial, PartialSignature, 44 | PARTIAL_SIGNATURE_SIZE, 45 | }; 46 | 47 | #[cfg(test)] 48 | pub(crate) mod testhex; 49 | 50 | /// Re-export of the inner types used to represent curve points and scalars. 51 | pub use secp; 52 | 53 | #[cfg(feature = "secp256k1")] 54 | pub use secp256k1; 55 | 56 | #[cfg(feature = "k256")] 57 | pub use k256; 58 | 59 | #[cfg(any(test, feature = "rand"))] 60 | pub use bip340::{verify_batch, BatchVerificationRow}; 61 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "musig2" 3 | version = "0.3.1" 4 | edition = "2021" 5 | authors = ["conduition "] 6 | description = "Flexible Rust implementation of the MuSig2 multisignature protocol, compatible with Bitcoin." 7 | readme = "README.md" 8 | license = "Unlicense" 9 | repository = "https://github.com/conduition/musig2" 10 | keywords = ["musig", "schnorr", "bitcoin", "multisignature", "musig2"] 11 | include = ["/src", "!/src/test_vectors", "*.md"] 12 | 13 | [dependencies] 14 | base16ct = { version = "0.2", default-features = false, features = ["alloc"] } 15 | hmac = { version = "0.12", default-features = false, features = [] } 16 | k256 = { version = "0.13", default-features = false, optional = true } 17 | once_cell = { version = "1.21", default-features = false } 18 | rand = { version = "0.9", optional = true, default-features = false, features = ["std_rng"] } 19 | secp = { version = "0.6", default-features = false } 20 | secp256k1 = { version = "0.31", optional = true, default-features = false } 21 | serde = { version = "1.0", default-features = false, optional = true } 22 | serdect = { version = "0.3", default-features = false, optional = true, features = ["alloc"] } 23 | sha2 = { version = "0.10", default-features = false } 24 | subtle = { version = "2", default-features = false } 25 | 26 | [dev-dependencies] 27 | serde = { version = "1.0", features = ["serde_derive"] } 28 | serde_json = "1.0" 29 | csv = "1.3" 30 | serdect = "0.3" 31 | rand = "0.9" 32 | secp = { version = "0.6", default-features = false, features = ["serde", "rand", "secp256k1-invert"] } 33 | 34 | [features] 35 | default = ["secp256k1"] 36 | secp256k1 = ["dep:secp256k1", "secp/secp256k1"] 37 | k256 = ["dep:k256", "secp/k256"] 38 | serde = ["dep:serde", "secp/serde", "dep:serdect"] 39 | rand = ["dep:rand", "secp/rand"] 40 | 41 | [package.metadata.docs.rs] 42 | all-features = true 43 | rustdoc-args = ["--cfg", "docsrs"] 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: {} 8 | workflow_dispatch: null 9 | 10 | jobs: 11 | check: 12 | name: "Check the code compiles" 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | - name: Setup Rust 18 | uses: dtolnay/rust-toolchain@stable 19 | - name: Check 20 | run: cargo check 21 | 22 | fmt: 23 | name: "Cargo fmt" 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v4 28 | - name: Setup Rust 29 | uses: dtolnay/rust-toolchain@stable 30 | with: 31 | components: rustfmt 32 | - name: rustfmt 33 | run: cargo fmt --all --check 34 | 35 | clippy: 36 | name: "Clippy" 37 | needs: [check, fmt] 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout code 41 | uses: actions/checkout@v4 42 | - name: Setup Rust 43 | uses: dtolnay/rust-toolchain@stable 44 | with: 45 | components: clippy 46 | - name: Clippy default 47 | run: make check-default 48 | - name: Clippy all features 49 | run: make check-mixed 50 | - name: Clippy libsecp feature set 51 | run: make check-secp256k1 52 | - name: Clippy pure-rust feature set 53 | run: make check-k256 54 | 55 | docs: 56 | needs: [check, fmt] 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Checkout code 60 | uses: actions/checkout@v4 61 | - name: Setup Rust 62 | uses: dtolnay/rust-toolchain@stable 63 | - name: cargo doc 64 | env: 65 | RUSTDOCFLAGS: "-D rustdoc::all -A rustdoc::private-doc-tests" 66 | run: cargo doc --all-features --no-deps 67 | 68 | test: 69 | needs: [check, fmt, clippy] 70 | runs-on: ubuntu-latest 71 | strategy: 72 | matrix: 73 | rust: [stable, beta] 74 | recipe: 75 | - "test-default" 76 | - "test-mixed" 77 | - "test-secp256k1" 78 | - "test-k256" 79 | 80 | steps: 81 | - name: Checkout code 82 | uses: actions/checkout@v4 83 | - name: Setup Rust 84 | uses: dtolnay/rust-toolchain@stable 85 | with: 86 | toolchain: ${{ matrix.rust }} 87 | - name: Run tests 88 | run: make ${{ matrix.recipe }} 89 | -------------------------------------------------------------------------------- /src/test_vectors/nonce_agg_vectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "pnonces": [ 3 | "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641", 4 | "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", 5 | "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 6 | "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 7 | "04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", 8 | "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831", 9 | "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" 10 | ], 11 | "valid_test_cases": [ 12 | { 13 | "pnonce_indices": [0, 1], 14 | "expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8" 15 | }, 16 | { 17 | "pnonce_indices": [2, 3], 18 | "expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000", 19 | "comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes" 20 | } 21 | ], 22 | "error_test_cases": [ 23 | { 24 | "pnonce_indices": [0, 4], 25 | "error": { 26 | "type": "invalid_contribution", 27 | "signer": 1, 28 | "contrib": "pubnonce" 29 | }, 30 | "comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half" 31 | }, 32 | { 33 | "pnonce_indices": [5, 1], 34 | "error": { 35 | "type": "invalid_contribution", 36 | "signer": 0, 37 | "contrib": "pubnonce" 38 | }, 39 | "comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate" 40 | }, 41 | { 42 | "pnonce_indices": [6, 1], 43 | "error": { 44 | "type": "invalid_contribution", 45 | "signer": 0, 46 | "contrib": "pubnonce" 47 | }, 48 | "comment": "Public nonce from signer 0 is invalid because second half exceeds field size" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/testhex.rs: -------------------------------------------------------------------------------- 1 | //! This code is used to assist in deserializing test vectors. 2 | 3 | use serde::Deserialize; 4 | 5 | #[derive(Debug)] 6 | pub struct FromBytesError { 7 | pub unexpected: String, 8 | pub expected: String, 9 | } 10 | 11 | pub trait TryFromBytes: Sized { 12 | fn try_from_bytes(bytes: &[u8]) -> Result; 13 | } 14 | 15 | impl TryFromBytes for Vec { 16 | fn try_from_bytes(bytes: &[u8]) -> Result { 17 | Ok(Vec::from(bytes)) 18 | } 19 | } 20 | 21 | impl TryFromBytes for [u8; SIZE] { 22 | fn try_from_bytes(bytes: &[u8]) -> Result { 23 | if bytes.len() != SIZE { 24 | return Err(FromBytesError { 25 | expected: format!("byte vector of length {}", SIZE), 26 | unexpected: format!("byte vector of length {}", bytes.len()), 27 | }); 28 | } 29 | 30 | let mut array = [0; SIZE]; 31 | array[..].clone_from_slice(bytes); 32 | Ok(array) 33 | } 34 | } 35 | 36 | struct HexVisitor; 37 | 38 | impl serde::de::Visitor<'_> for HexVisitor { 39 | type Value = Vec; 40 | 41 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 42 | formatter.write_str("a hex string") 43 | } 44 | 45 | fn visit_str(self, v: &str) -> Result 46 | where 47 | E: serde::de::Error, 48 | { 49 | base16ct::mixed::decode_vec(v) 50 | .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &"a hex string")) 51 | } 52 | } 53 | 54 | struct HexString(pub T); 55 | 56 | impl<'de, T: TryFromBytes> Deserialize<'de> for HexString { 57 | fn deserialize(deserializer: D) -> Result 58 | where 59 | D: serde::Deserializer<'de>, 60 | { 61 | let vec = deserializer.deserialize_str(HexVisitor)?; 62 | 63 | let parsed = T::try_from_bytes(&vec).map_err(|e| { 64 | ::invalid_value( 65 | serde::de::Unexpected::Other(&e.unexpected), 66 | &e.expected.as_str(), 67 | ) 68 | })?; 69 | 70 | Ok(HexString(parsed)) 71 | } 72 | } 73 | 74 | pub fn deserialize<'de, D, T>(deserializer: D) -> Result 75 | where 76 | D: serde::Deserializer<'de>, 77 | T: TryFromBytes, 78 | { 79 | let HexString(value) = HexString::::deserialize(deserializer)?; 80 | Ok(value) 81 | } 82 | 83 | pub fn deserialize_vec<'de, D, T>(deserializer: D) -> Result, D::Error> 84 | where 85 | D: serde::Deserializer<'de>, 86 | T: TryFromBytes, 87 | { 88 | let items = >>::deserialize(deserializer)? 89 | .into_iter() 90 | .map(|HexString(value)| value) 91 | .collect(); 92 | Ok(items) 93 | } 94 | -------------------------------------------------------------------------------- /src/test_vectors/nonce_gen_vectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_cases": [ 3 | { 4 | "rand": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", 5 | "sk": "0202020202020202020202020202020202020202020202020202020202020202", 6 | "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", 7 | "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", 8 | "msg": "0101010101010101010101010101010101010101010101010101010101010101", 9 | "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", 10 | "expected_secnonce": "B114E502BEAA4E301DD08A50264172C84E41650E6CB726B410C0694D59EFFB6495B5CAF28D045B973D63E3C99A44B807BDE375FD6CB39E46DC4A511708D0E9D2024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", 11 | "expected_pubnonce": "02F7BE7089E8376EB355272368766B17E88E7DB72047D05E56AA881EA52B3B35DF02C29C8046FDD0DED4C7E55869137200FBDBFE2EB654267B6D7013602CAED3115A" 12 | }, 13 | { 14 | "rand": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", 15 | "sk": "0202020202020202020202020202020202020202020202020202020202020202", 16 | "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", 17 | "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", 18 | "msg": "", 19 | "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", 20 | "expected_secnonce": "E862B068500320088138468D47E0E6F147E01B6024244AE45EAC40ACE5929B9F0789E051170B9E705D0B9EB49049A323BBBBB206D8E05C19F46C6228742AA7A9024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", 21 | "expected_pubnonce": "023034FA5E2679F01EE66E12225882A7A48CC66719B1B9D3B6C4DBD743EFEDA2C503F3FD6F01EB3A8E9CB315D73F1F3D287CAFBB44AB321153C6287F407600205109" 22 | }, 23 | { 24 | "rand": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", 25 | "sk": "0202020202020202020202020202020202020202020202020202020202020202", 26 | "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", 27 | "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", 28 | "msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626", 29 | "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", 30 | "expected_secnonce": "3221975ACBDEA6820EABF02A02B7F27D3A8EF68EE42787B88CBEFD9AA06AF3632EE85B1A61D8EF31126D4663A00DD96E9D1D4959E72D70FE5EBB6E7696EBA66F024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", 31 | "expected_pubnonce": "02E5BBC21C69270F59BD634FCBFA281BE9D76601295345112C58954625BF23793A021307511C79F95D38ACACFF1B4DA98228B77E65AA216AD075E9673286EFB4EAF3" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /src/test_vectors/key_agg_vectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkeys": [ 3 | "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", 4 | "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 5 | "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", 6 | "020000000000000000000000000000000000000000000000000000000000000005", 7 | "02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", 8 | "04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", 9 | "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" 10 | ], 11 | "tweaks": [ 12 | "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 13 | "252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B" 14 | ], 15 | "valid_test_cases": [ 16 | { 17 | "key_indices": [0, 1, 2], 18 | "expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C" 19 | }, 20 | { 21 | "key_indices": [2, 1, 0], 22 | "expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B" 23 | }, 24 | { 25 | "key_indices": [0, 0, 0], 26 | "expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935" 27 | }, 28 | { 29 | "key_indices": [0, 0, 1, 1], 30 | "expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E" 31 | } 32 | ], 33 | "error_test_cases": [ 34 | { 35 | "key_indices": [0, 3], 36 | "tweak_indices": [], 37 | "is_xonly": [], 38 | "error": { 39 | "type": "invalid_contribution", 40 | "signer": 1, 41 | "contrib": "pubkey" 42 | }, 43 | "comment": "Invalid public key" 44 | }, 45 | { 46 | "key_indices": [0, 4], 47 | "tweak_indices": [], 48 | "is_xonly": [], 49 | "error": { 50 | "type": "invalid_contribution", 51 | "signer": 1, 52 | "contrib": "pubkey" 53 | }, 54 | "comment": "Public key exceeds field size" 55 | }, 56 | { 57 | "key_indices": [5, 0], 58 | "tweak_indices": [], 59 | "is_xonly": [], 60 | "error": { 61 | "type": "invalid_contribution", 62 | "signer": 0, 63 | "contrib": "pubkey" 64 | }, 65 | "comment": "First byte of public key is not 2 or 3" 66 | }, 67 | { 68 | "key_indices": [0, 1], 69 | "tweak_indices": [0], 70 | "is_xonly": [true], 71 | "error": { 72 | "type": "value", 73 | "message": "The tweak must be less than n." 74 | }, 75 | "comment": "Tweak is out of range" 76 | }, 77 | { 78 | "key_indices": [6], 79 | "tweak_indices": [1], 80 | "is_xonly": [false], 81 | "error": { 82 | "type": "value", 83 | "message": "The result of tweaking cannot be infinity." 84 | }, 85 | "comment": "Intermediate tweaking result is point at infinity" 86 | } 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /src/test_vectors/tweak_vectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", 3 | "pubkeys": [ 4 | "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", 5 | "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", 6 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" 7 | ], 8 | "secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", 9 | "pnonces": [ 10 | "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", 11 | "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 12 | "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046" 13 | ], 14 | "aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", 15 | "tweaks": [ 16 | "E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB", 17 | "AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455", 18 | "F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0", 19 | "1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D", 20 | "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" 21 | ], 22 | "msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", 23 | "valid_test_cases": [ 24 | { 25 | "key_indices": [1, 2, 0], 26 | "nonce_indices": [1, 2, 0], 27 | "tweak_indices": [0], 28 | "is_xonly": [true], 29 | "signer_index": 2, 30 | "expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91", 31 | "comment": "A single x-only tweak" 32 | }, 33 | { 34 | "key_indices": [1, 2, 0], 35 | "nonce_indices": [1, 2, 0], 36 | "tweak_indices": [0], 37 | "is_xonly": [false], 38 | "signer_index": 2, 39 | "expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D", 40 | "comment": "A single plain tweak" 41 | }, 42 | { 43 | "key_indices": [1, 2, 0], 44 | "nonce_indices": [1, 2, 0], 45 | "tweak_indices": [0, 1], 46 | "is_xonly": [false, true], 47 | "signer_index": 2, 48 | "expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408", 49 | "comment": "A plain tweak followed by an x-only tweak" 50 | }, 51 | { 52 | "key_indices": [1, 2, 0], 53 | "nonce_indices": [1, 2, 0], 54 | "tweak_indices": [0, 1, 2, 3], 55 | "is_xonly": [false, false, true, true], 56 | "signer_index": 2, 57 | "expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435", 58 | "comment": "Four tweaks: plain, plain, x-only, x-only." 59 | }, 60 | { 61 | "key_indices": [1, 2, 0], 62 | "nonce_indices": [1, 2, 0], 63 | "tweak_indices": [0, 1, 2, 3], 64 | "is_xonly": [true, false, true, false], 65 | "signer_index": 2, 66 | "expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239", 67 | "comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error." 68 | } 69 | ], 70 | "error_test_cases": [ 71 | { 72 | "key_indices": [1, 2, 0], 73 | "nonce_indices": [1, 2, 0], 74 | "tweak_indices": [4], 75 | "is_xonly": [false], 76 | "signer_index": 2, 77 | "error": { 78 | "type": "value", 79 | "message": "The tweak must be less than n." 80 | }, 81 | "comment": "Tweak is invalid because it exceeds group size" 82 | } 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MuSig2 2 | 3 | This crate provides a flexible rust implementation of [MuSig2](https://eprint.iacr.org/2020/1261), an optimized digital signature aggregation protocol, on the `secp256k1` elliptic curve. 4 | 5 | MuSig2 allows groups of mutually distrusting parties to cooperatively sign data and aggregate their signatures into a single aggregated signature which is indistinguishable from a signature made by a single private key. The group collectively controls an _aggregated public key_ which can only create signatures if everyone in the group cooperates (AKA an N-of-N multisignature scheme). MuSig2 is optimized to support secure signature aggregation with only **two round-trips of network communication.** 6 | 7 | Specifically, this crate implements [BIP-0327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki), for creating and verifying signatures which validate under Bitcoin consensus rules, but the protocol is flexible and can be applied to any N-of-N multisignature use-case. 8 | 9 | ## ⚠️ Beta Status ⚠️ 10 | 11 | This crate is in beta status. The latest release is a `v0.x.y` version number. Expect breaking changes and security fixes. Once this crate is stabilized, we will tag and release `v1.0.0`. 12 | 13 | ## Overview 14 | 15 | If you're not already familiar with MuSig2, the process of cooperative signing runs like so: 16 | 17 | 1. All signers share their public keys with one-another. The group computes an _aggregated public key_ which they collectively control. 18 | 2. In the **first signing round,** signers generate and share _nonces_ (random numbers) with one-another. These nonces have both secret and public versions. Only the public nonce (AKA `PubNonce`) should be shared, while the corresponding secret nonce (AKA `SecNonce`) must be kept secret. 19 | 3. Once every signer has received the public nonces of every other signer, each signer makes a _partial signature_ for a message using their secret key and secret nonce. 20 | 4. In the **second signing round,** signers share their partial signatures with one-another. Partial signatures can be verified to place blame on misbehaving signers (but are not themselves unforgeable). 21 | 5. A valid set of partial signatures can be aggregated into a final signature, which is just a normal [Schnorr signature](https://en.wikipedia.org/wiki/Schnorr_signature), valid under the aggregated public key. 22 | 23 | ## Choice of Backbone 24 | 25 | This crate does not implement elliptic curve point math directly. Instead we depend on one of two reputable libraries: 26 | 27 | - C bindings to [`libsecp256k1`](https://github.com/bitcoin-core/secp256k1), via [the `secp256k1` crate](https://crates.io/crates/secp256k1), maintained by the Bitcoin Core team. 28 | - A pure-rust implementation via [the `k256` crate](https://crates.io/crates/k256), maintained by the [RustCrypto](https://github.com/RustCrypto) team. 29 | 30 | One or the other can be used. By default, this crate prefers to rely on `libsecp256k1`, as this is the most vetted and publicly trusted implementation of secp256k1 curve math available anywhere. However, if you need a pure-rust implementation, you can install this crate without it, and use the pure-rust `k256` crate instead. 31 | 32 | ```notrust 33 | cargo add musig2 --no-default-features --features k256 34 | ``` 35 | 36 | If both `k256` and `secp256k1` features are enabled, then we default to using `libsecp256k1` bindings for the actual math, but still provide trait implementations to make this crate interoperable with `k256`. 37 | 38 | This crate internally represents elliptic curve points (e.g. public keys) and scalars (e.g. private keys) using the [`secp` crate](https://crates.io/crates/secp) and its types: 39 | 40 | - [`secp::Scalar`](https://docs.rs/secp/struct.Scalar.html) for non-zero scalar values. 41 | - [`secp::Point`](https://docs.rs/secp/struct.Point.html) for non-infinity curve points 42 | - [`secp::MaybeScalar`](https://docs.rs/secp/enum.Point.html) for possibly-zero scalars. 43 | - [`secp::MaybePoint`](https://docs.rs/secp/enum.Point.html) for possibly-infinity curve points. 44 | 45 | Depending on which features of this crate are enabled, conversion traits are implemented between these types and higher-level types such as [`secp256k1::PublicKey`](https://docs.rs/secp256k1/struct.PublicKey.html) or [`k256::SecretKey`](https://docs.rs/k256/type.SecretKey.html). Generally, our API can accept or return any type that converts to/from the equivalent `secp` representations, although callers are also welcome to use `secp` directly too. 46 | 47 | ## Documentation 48 | 49 | [Head on over to docs.rs to see the full API documentation and usage examples.](https://docs.rs/musig2) 50 | -------------------------------------------------------------------------------- /src/deterministic.rs: -------------------------------------------------------------------------------- 1 | //! This module provides determinstic BIP340-compatible single-signer logic using 2 | //! [RFC6979](https://www.rfc-editor.org/rfc/rfc6979). 3 | //! 4 | //! This approach produces a synthetic nonce by deriving it from a 5 | //! chained hash of the private key and and the message to be signed. 6 | //! Generating nonces in this way makes signatatures deterministic. 7 | //! 8 | //! Technically RFC6979 is not part of the BIP340 spec, but it is entirely valid 9 | //! to use deterministic nonce generation, provided you can guarantee that the 10 | //! `(seckey, message)` pair are never used for other deterministic signatures 11 | //! outside of BIP340. 12 | //! 13 | //! This is safe in a single-signer environment only (not for MuSig). 14 | //! For deterministic nonces in a multi-signer environment, you will need 15 | //! zero-knowledge proofs. See [this paper for details](https://eprint.iacr.org/2020/1057.pdf). 16 | use secp::{MaybePoint, Scalar}; 17 | 18 | use crate::{AdaptorSignature, LiftedSignature}; 19 | 20 | use hmac::digest::FixedOutput as _; 21 | use hmac::Mac as _; 22 | use sha2::Digest as _; 23 | 24 | fn hmac_sha256(key: &[u8; 32], msg: &[u8]) -> [u8; 32] { 25 | hmac::Hmac::::new_from_slice(key.as_ref()) 26 | .expect("Hmac::new_from_slice never fails") 27 | .chain_update(msg) 28 | .finalize_fixed() 29 | .into() 30 | } 31 | 32 | /// Derive a nonce from a given `(seckey, message)` pair. Follows the procedure 33 | /// laid out in [this section of the RFC](https://www.rfc-editor.org/rfc/rfc6979#section-3.2). 34 | pub fn derive_nonce_rfc6979(seckey: impl Into, message: impl AsRef<[u8]>) -> Scalar { 35 | let seckey = seckey.into(); 36 | 37 | let h1 = sha2::Sha256::new() 38 | .chain_update(message.as_ref()) 39 | .finalize(); 40 | 41 | let mut V = [1u8; 32]; 42 | let mut K = [0u8; 32]; 43 | 44 | // Step D: 45 | // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1)) 46 | let mut buf = vec![0u8; 32 + 1 + 32 + 32]; 47 | buf[..32].copy_from_slice(&V); 48 | buf[32] = 0; 49 | buf[33..65].copy_from_slice(&seckey.serialize()); 50 | buf[65..].copy_from_slice(&h1); 51 | K = hmac_sha256(&K, &buf); 52 | 53 | // Step E: 54 | // V = HMAC_K(V) 55 | V = hmac_sha256(&K, &V); 56 | 57 | // Step F: 58 | // K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1)) 59 | buf[..32].copy_from_slice(&V); 60 | buf[32] = 1; 61 | K = hmac_sha256(&K, &buf); 62 | 63 | // Step G: 64 | // V = HMAC_K(V) 65 | V = hmac_sha256(&K, &V); 66 | 67 | loop { 68 | // Step H2: 69 | // V = HMAC_K(V) 70 | V = hmac_sha256(&K, &V); 71 | 72 | // Step H3: 73 | // k = bits2int(V) 74 | if let Ok(k) = Scalar::from_slice(&V) { 75 | return k; 76 | } 77 | 78 | buf[..32].copy_from_slice(&V); 79 | buf[32] = 0; 80 | K = hmac_sha256(&K, &buf[..33]); 81 | V = hmac_sha256(&K, &V); 82 | } 83 | } 84 | 85 | /// This module provides a determinstic flavor of adaptor signature creation for single-signer contexts. 86 | pub mod adaptor { 87 | use super::*; 88 | 89 | /// This is the same as [`adaptor::sign_solo`][crate::adaptor::sign_solo] except using 90 | /// deterministic nonce generation. 91 | pub fn sign_solo( 92 | seckey: impl Into, 93 | message: impl AsRef<[u8]>, 94 | adaptor_point: impl Into, 95 | ) -> AdaptorSignature { 96 | let seckey = seckey.into(); 97 | let aux = derive_nonce_rfc6979(seckey, &message).serialize(); 98 | crate::adaptor::sign_solo(seckey, message, aux, adaptor_point) 99 | } 100 | } 101 | 102 | /// This is the same as [`sign_solo`][crate::sign_solo] except using deterministic nonce generation. 103 | pub fn sign_solo(seckey: impl Into, message: impl AsRef<[u8]>) -> T 104 | where 105 | T: From, 106 | { 107 | let seckey = seckey.into(); 108 | let aux = derive_nonce_rfc6979(seckey, &message).serialize(); 109 | crate::sign_solo(seckey, message, aux) 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::*; 115 | 116 | #[test] 117 | fn test_rfc6979_nonces() { 118 | struct TestVector { 119 | seckey: Scalar, 120 | message: &'static str, 121 | expected_nonce: &'static str, 122 | } 123 | 124 | let test_vectors = [ 125 | // from https://www.rfc-editor.org/rfc/rfc6979#appendix-A.2.5 126 | TestVector { 127 | seckey: "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721" 128 | .parse() 129 | .unwrap(), 130 | message: "sample", 131 | expected_nonce: "A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60", 132 | }, 133 | TestVector { 134 | seckey: "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721" 135 | .parse() 136 | .unwrap(), 137 | message: "test", 138 | expected_nonce: "D16B6AE827F17175E040871A1C7EC3500192C4C92677336EC2537ACAEE0008E0", 139 | }, 140 | ]; 141 | 142 | for test in test_vectors { 143 | let nonce = derive_nonce_rfc6979(test.seckey, test.message); 144 | assert_eq!(format!("{:X}", nonce), test.expected_nonce); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/test_vectors/sig_agg_vectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkeys": [ 3 | "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", 4 | "02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05", 5 | "03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C", 6 | "02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581" 7 | ], 8 | "pnonces": [ 9 | "036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E", 10 | "03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00", 11 | "02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6", 12 | "031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9", 13 | "023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A", 14 | "02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00" 15 | ], 16 | "tweaks": [ 17 | "B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C", 18 | "A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC", 19 | "75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8" 20 | ], 21 | "psigs": [ 22 | "B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB", 23 | "6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64", 24 | "9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505", 25 | "66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15", 26 | "4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE", 27 | "DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4", 28 | "97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC", 29 | "53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971", 30 | "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" 31 | ], 32 | "msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869", 33 | "valid_test_cases": [ 34 | { 35 | "aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B", 36 | "nonce_indices": [ 37 | 0, 38 | 1 39 | ], 40 | "key_indices": [ 41 | 0, 42 | 1 43 | ], 44 | "tweak_indices": [], 45 | "is_xonly": [], 46 | "psig_indices": [ 47 | 0, 48 | 1 49 | ], 50 | "expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E" 51 | }, 52 | { 53 | "aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20", 54 | "nonce_indices": [ 55 | 0, 56 | 2 57 | ], 58 | "key_indices": [ 59 | 0, 60 | 2 61 | ], 62 | "tweak_indices": [], 63 | "is_xonly": [], 64 | "psig_indices": [ 65 | 2, 66 | 3 67 | ], 68 | "expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9" 69 | }, 70 | { 71 | "aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D", 72 | "nonce_indices": [ 73 | 0, 74 | 3 75 | ], 76 | "key_indices": [ 77 | 0, 78 | 2 79 | ], 80 | "tweak_indices": [ 81 | 0 82 | ], 83 | "is_xonly": [ 84 | false 85 | ], 86 | "psig_indices": [ 87 | 4, 88 | 5 89 | ], 90 | "expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC" 91 | }, 92 | { 93 | "aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD", 94 | "nonce_indices": [ 95 | 0, 96 | 4 97 | ], 98 | "key_indices": [ 99 | 0, 100 | 3 101 | ], 102 | "tweak_indices": [ 103 | 0, 104 | 1, 105 | 2 106 | ], 107 | "is_xonly": [ 108 | true, 109 | false, 110 | true 111 | ], 112 | "psig_indices": [ 113 | 6, 114 | 7 115 | ], 116 | "expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E" 117 | } 118 | ], 119 | "error_test_cases": [ 120 | { 121 | "aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD", 122 | "nonce_indices": [ 123 | 0, 124 | 4 125 | ], 126 | "key_indices": [ 127 | 0, 128 | 3 129 | ], 130 | "tweak_indices": [ 131 | 0, 132 | 1, 133 | 2 134 | ], 135 | "is_xonly": [ 136 | true, 137 | false, 138 | true 139 | ], 140 | "psig_indices": [ 141 | 7, 142 | 8 143 | ], 144 | "error": { 145 | "type": "invalid_contribution", 146 | "signer": 1 147 | }, 148 | "comment": "Partial signature is invalid because it exceeds group size" 149 | } 150 | ] 151 | } 152 | -------------------------------------------------------------------------------- /src/test_vectors/bip340_vectors.csv: -------------------------------------------------------------------------------- 1 | index,secret key,public key,aux_rand,message,signature,verification result,comment 2 | 0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE, 3 | 1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE, 4 | 2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE, 5 | 3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n 6 | 4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE, 7 | 5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve 8 | 6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false 9 | 7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message 10 | 8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value 11 | 9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0 12 | 10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1 13 | 11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve 14 | 12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size 15 | 13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order 16 | 14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size 17 | 15,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,,71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63,TRUE,message of size 0 (added 2022-12) 18 | 16,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,11,08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF,TRUE,message of size 1 (added 2022-12) 19 | 17,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,0102030405060708090A0B0C0D0E0F1011,5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5,TRUE,message of size 17 (added 2022-12) 20 | 18,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999,403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367,TRUE,message of size 100 (added 2022-12) 21 | -------------------------------------------------------------------------------- /src/tagged_hashes.rs: -------------------------------------------------------------------------------- 1 | //! This module holds declarations for computing 2 | //! [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki)-style 3 | //! tagged hashes. 4 | //! 5 | //! A tagged hash is a SHA256 hash which has been prefixed with two copies of 6 | //! the SHA256 hash of a given fixed constant byte string. This has the effect 7 | //! of namespacing the hash to reduce the possibility of collisions. 8 | //! 9 | //! You probably won't need to use these hashes yourself, but if you want to 10 | //! produce a tagged hash, simply clone one of the lazily allocated hash engines 11 | //! declared as statics in this module. This will give you an instance of 12 | //! `sha2::Sha256`. 13 | //! 14 | //! ``` 15 | //! use musig2::tagged_hashes; 16 | //! use sha2::Sha256; 17 | //! use sha2::Digest as _; // Brings trait methods into scope 18 | //! 19 | //! let hash = tagged_hashes::KEYAGG_LIST_TAG_HASHER 20 | //! .clone() 21 | //! .chain_update(b"SomeData") 22 | //! .finalize(); 23 | //! 24 | //! let expected = { 25 | //! let tag_digest = Sha256::digest("KeyAgg list"); 26 | //! Sha256::new() 27 | //! .chain_update(&tag_digest) 28 | //! .chain_update(&tag_digest) 29 | //! .chain_update(b"SomeData") 30 | //! .finalize() 31 | //! }; 32 | //! 33 | //! assert_eq!(hash, expected); 34 | //! ``` 35 | 36 | use once_cell::sync::Lazy; 37 | use sha2::Sha256; 38 | 39 | use sha2::Digest as _; 40 | 41 | fn with_tag_hash_prefix(tag_hash: [u8; 32]) -> Sha256 { 42 | Sha256::new().chain_update(tag_hash).chain_update(tag_hash) 43 | } 44 | 45 | /// sha256(b"KeyAgg list") 46 | const KEYAGG_LIST_TAG_DIGEST: [u8; 32] = [ 47 | 0x48, 0x1C, 0x97, 0x1C, 0x3C, 0x0B, 0x46, 0xD7, 0xF0, 0xB2, 0x75, 0xAE, 0x59, 0x8D, 0x4E, 0x2C, 48 | 0x7E, 0xD7, 0x31, 0x9C, 0x59, 0x4A, 0x5C, 0x6E, 0xC7, 0x9E, 0xA0, 0xD4, 0x99, 0x02, 0x94, 0xF0, 49 | ]; 50 | 51 | /// sha256(b"KeyAgg coefficient") 52 | const KEYAGG_COEFF_TAG_DIGEST: [u8; 32] = [ 53 | 0xBF, 0xC9, 0x04, 0x03, 0x4D, 0x1C, 0x88, 0xE8, 0xC8, 0x0E, 0x22, 0xE5, 0x3D, 0x24, 0x56, 0x6D, 54 | 0x64, 0x82, 0x4E, 0xD6, 0x42, 0x72, 0x81, 0xC0, 0x91, 0x00, 0xF9, 0x4D, 0xCD, 0x52, 0xC9, 0x81, 55 | ]; 56 | 57 | /// sha256(b"MuSig/aux") 58 | const MUSIG_AUX_TAG_DIGEST: [u8; 32] = [ 59 | 0x40, 0x8F, 0x8C, 0x1F, 0x29, 0x24, 0x21, 0xB5, 0x56, 0x9E, 0xBC, 0x6C, 0xB5, 0xF2, 0xE2, 0x0C, 60 | 0xF1, 0xE3, 0x84, 0x1B, 0x47, 0x43, 0x9F, 0xCC, 0x58, 0x7D, 0x20, 0xE3, 0xC1, 0x7F, 0x08, 0x37, 61 | ]; 62 | 63 | /// sha256(b"MuSig/nonce") 64 | const MUSIG_NONCE_TAG_DIGEST: [u8; 32] = [ 65 | 0xF8, 0xC1, 0x0C, 0xBC, 0x61, 0x4E, 0xD1, 0xA0, 0x84, 0xB4, 0x37, 0x05, 0x2B, 0x5D, 0x2C, 0x4B, 66 | 0x50, 0x1A, 0x9D, 0xE7, 0xAA, 0xFB, 0xE3, 0x48, 0xAC, 0xE8, 0x02, 0x6C, 0xA7, 0xFC, 0xB1, 0x7B, 67 | ]; 68 | 69 | /// sha256(b"MuSig/noncecoef") 70 | const MUSIG_NONCECOEF_TAG_DIGEST: [u8; 32] = [ 71 | 0x5A, 0x6D, 0x45, 0xF6, 0xDA, 0x29, 0xE6, 0x51, 0xCB, 0x1B, 0xA2, 0xB8, 0xAC, 0x2C, 0xDD, 0x4E, 72 | 0xBC, 0x15, 0xC2, 0xFB, 0xB2, 0x89, 0xF0, 0xCC, 0x82, 0x1B, 0xBF, 0x0A, 0x34, 0x09, 0x5F, 0x32, 73 | ]; 74 | 75 | /// sha256(b"BIP0340/aux") 76 | const BIP0340_AUX_TAG_DIGEST: [u8; 32] = [ 77 | 0xF1, 0xEF, 0x4E, 0x5E, 0xC0, 0x63, 0xCA, 0xDA, 0x6D, 0x94, 0xCA, 0xFA, 0x9D, 0x98, 0x7E, 0xA0, 78 | 0x69, 0x26, 0x58, 0x39, 0xEC, 0xC1, 0x1F, 0x97, 0x2D, 0x77, 0xA5, 0x2E, 0xD8, 0xC1, 0xCC, 0x90, 79 | ]; 80 | 81 | /// sha256(b"BIP0340/nonce") 82 | const BIP0340_NONCE_TAG_DIGEST: [u8; 32] = [ 83 | 0x07, 0x49, 0x77, 0x34, 0xA7, 0x9B, 0xCB, 0x35, 0x5B, 0x9B, 0x8C, 0x7D, 0x03, 0x4F, 0x12, 0x1C, 84 | 0xF4, 0x34, 0xD7, 0x3E, 0xF7, 0x2D, 0xDA, 0x19, 0x87, 0x00, 0x61, 0xFB, 0x52, 0xBF, 0xEB, 0x2F, 85 | ]; 86 | 87 | /// sha256(b"BIP0340/challenge") 88 | const BIP0340_CHALLENGE_TAG_DIGEST: [u8; 32] = [ 89 | 0x7B, 0xB5, 0x2D, 0x7A, 0x9F, 0xEF, 0x58, 0x32, 0x3E, 0xB1, 0xBF, 0x7A, 0x40, 0x7D, 0xB3, 0x82, 90 | 0xD2, 0xF3, 0xF2, 0xD8, 0x1B, 0xB1, 0x22, 0x4F, 0x49, 0xFE, 0x51, 0x8F, 0x6D, 0x48, 0xD3, 0x7C, 91 | ]; 92 | 93 | /// sha256(b"BIP0340/batch") 94 | const BIP0340_BATCH_TAG_DIGEST: [u8; 32] = [ 95 | 0x77, 0x06, 0x39, 0x59, 0x84, 0x1F, 0xFA, 0x7B, 0x06, 0x15, 0x4E, 0xE0, 0x47, 0x50, 0x19, 0x40, 96 | 0x36, 0x48, 0x7A, 0xB8, 0x91, 0x96, 0xD0, 0x6E, 0xC7, 0x3E, 0x75, 0x82, 0x90, 0x98, 0x41, 0xB5, 97 | ]; 98 | 99 | /// sha256(b"TapTweak") 100 | const TAPROOT_TWEAK_TAG_DIGEST: [u8; 32] = [ 101 | 0xe8, 0x0f, 0xe1, 0x63, 0x9c, 0x9c, 0xa0, 0x50, 0xe3, 0xaf, 0x1b, 0x39, 0xc1, 0x43, 0xc6, 0x3e, 102 | 0x42, 0x9c, 0xbc, 0xeb, 0x15, 0xd9, 0x40, 0xfb, 0xb5, 0xc5, 0xa1, 0xf4, 0xaf, 0x57, 0xc5, 0xe9, 103 | ]; 104 | 105 | /// A `sha2::Sha256` hash engine with its state initialized to: 106 | /// 107 | /// ```notrust 108 | /// sha256(b"KeyAgg list") || sha256(b"KeyAgg list") 109 | /// ``` 110 | pub static KEYAGG_LIST_TAG_HASHER: Lazy = 111 | Lazy::new(|| with_tag_hash_prefix(KEYAGG_LIST_TAG_DIGEST)); 112 | 113 | /// A `sha2::Sha256` hash engine with its state initialized to: 114 | /// 115 | /// ```notrust 116 | /// sha256(b"KeyAgg coefficient") || sha256(b"KeyAgg coefficient") 117 | /// ``` 118 | pub static KEYAGG_COEFF_TAG_HASHER: Lazy = 119 | Lazy::new(|| with_tag_hash_prefix(KEYAGG_COEFF_TAG_DIGEST)); 120 | 121 | /// A `sha2::Sha256` hash engine with its state initialized to: 122 | /// 123 | /// ```notrust 124 | /// sha256(b"MuSig/aux") || sha256(b"MuSig/aux") 125 | /// ``` 126 | pub static MUSIG_AUX_TAG_HASHER: Lazy = 127 | Lazy::new(|| with_tag_hash_prefix(MUSIG_AUX_TAG_DIGEST)); 128 | 129 | /// A `sha2::Sha256` hash engine with its state initialized to: 130 | /// 131 | /// ```notrust 132 | /// sha256(b"MuSig/nonce") || sha256(b"MuSig/nonce") 133 | /// ``` 134 | pub static MUSIG_NONCE_TAG_HASHER: Lazy = 135 | Lazy::new(|| with_tag_hash_prefix(MUSIG_NONCE_TAG_DIGEST)); 136 | 137 | /// A `sha2::Sha256` hash engine with its state initialized to: 138 | /// 139 | /// ```notrust 140 | /// sha256(b"MuSig/noncecoef") || sha256(b"MuSig/noncecoef") 141 | /// ``` 142 | pub static MUSIG_NONCECOEF_TAG_HASHER: Lazy = 143 | Lazy::new(|| with_tag_hash_prefix(MUSIG_NONCECOEF_TAG_DIGEST)); 144 | 145 | /// A `sha2::Sha256` hash engine with its state initialized to: 146 | /// 147 | /// ```notrust 148 | /// sha256(b"BIP0340/aux") || sha256(b"BIP0340/aux") 149 | /// ``` 150 | pub static BIP0340_AUX_TAG_HASHER: Lazy = 151 | Lazy::new(|| with_tag_hash_prefix(BIP0340_AUX_TAG_DIGEST)); 152 | 153 | /// A `sha2::Sha256` hash engine with its state initialized to: 154 | /// 155 | /// ```notrust 156 | /// sha256(b"BIP0340/nonce") || sha256(b"BIP0340/nonce") 157 | /// ``` 158 | pub static BIP0340_NONCE_TAG_HASHER: Lazy = 159 | Lazy::new(|| with_tag_hash_prefix(BIP0340_NONCE_TAG_DIGEST)); 160 | 161 | /// A `sha2::Sha256` hash engine with its state initialized to: 162 | /// 163 | /// ```notrust 164 | /// sha256(b"BIP0340/challenge") || sha256(b"BIP0340/challenge") 165 | /// ``` 166 | pub static BIP0340_CHALLENGE_TAG_HASHER: Lazy = 167 | Lazy::new(|| with_tag_hash_prefix(BIP0340_CHALLENGE_TAG_DIGEST)); 168 | 169 | /// A `sha2::Sha256` hash engine with its state initialized to: 170 | /// 171 | /// ```notrust 172 | /// sha256(b"BIP0340/batch") || sha256(b"BIP0340/batch") 173 | /// ``` 174 | pub static BIP0340_BATCH_TAG_HASHER: Lazy = 175 | Lazy::new(|| with_tag_hash_prefix(BIP0340_BATCH_TAG_DIGEST)); 176 | 177 | /// A `sha2::Sha256` hash engine with its state initialized to: 178 | /// 179 | /// ```notrust 180 | /// sha256(b"TapTweak") || sha256(b"TapTweak") 181 | /// ``` 182 | pub static TAPROOT_TWEAK_TAG_HASHER: Lazy = 183 | Lazy::new(|| with_tag_hash_prefix(TAPROOT_TWEAK_TAG_DIGEST)); 184 | 185 | #[cfg(test)] 186 | mod tests { 187 | use super::*; 188 | 189 | #[test] 190 | fn test_tagged_hash() { 191 | let test_cases = [ 192 | ("KeyAgg list", KEYAGG_LIST_TAG_DIGEST), 193 | ("KeyAgg coefficient", KEYAGG_COEFF_TAG_DIGEST), 194 | ("MuSig/aux", MUSIG_AUX_TAG_DIGEST), 195 | ("MuSig/nonce", MUSIG_NONCE_TAG_DIGEST), 196 | ("MuSig/noncecoef", MUSIG_NONCECOEF_TAG_DIGEST), 197 | ("BIP0340/aux", BIP0340_AUX_TAG_DIGEST), 198 | ("BIP0340/nonce", BIP0340_NONCE_TAG_DIGEST), 199 | ("BIP0340/challenge", BIP0340_CHALLENGE_TAG_DIGEST), 200 | ("BIP0340/batch", BIP0340_BATCH_TAG_DIGEST), // custom 201 | ("TapTweak", TAPROOT_TWEAK_TAG_DIGEST), 202 | ]; 203 | for (tag, declared_hash) in test_cases { 204 | let actual_hash = <[u8; 32]>::from(sha2::Sha256::digest(tag)); 205 | assert_eq!(declared_hash, actual_hash); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/test_vectors/sign_verify_vectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", 3 | "pubkeys": [ 4 | "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", 5 | "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", 6 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661", 7 | "020000000000000000000000000000000000000000000000000000000000000007" 8 | ], 9 | "secnonces": [ 10 | "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", 11 | "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" 12 | ], 13 | "pnonces": [ 14 | "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", 15 | "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16 | "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046", 17 | "0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", 18 | "0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480" 19 | ], 20 | "aggnonces": [ 21 | "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", 22 | "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 23 | "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", 24 | "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009", 25 | "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" 26 | ], 27 | "msgs": [ 28 | "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", 29 | "", 30 | "2626262626262626262626262626262626262626262626262626262626262626262626262626" 31 | ], 32 | "valid_test_cases": [ 33 | { 34 | "key_indices": [0, 1, 2], 35 | "nonce_indices": [0, 1, 2], 36 | "aggnonce_index": 0, 37 | "msg_index": 0, 38 | "signer_index": 0, 39 | "expected": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB" 40 | }, 41 | { 42 | "key_indices": [1, 0, 2], 43 | "nonce_indices": [1, 0, 2], 44 | "aggnonce_index": 0, 45 | "msg_index": 0, 46 | "signer_index": 1, 47 | "expected": "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52" 48 | }, 49 | { 50 | "key_indices": [1, 2, 0], 51 | "nonce_indices": [1, 2, 0], 52 | "aggnonce_index": 0, 53 | "msg_index": 0, 54 | "signer_index": 2, 55 | "expected": "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900" 56 | }, 57 | { 58 | "key_indices": [0, 1], 59 | "nonce_indices": [0, 3], 60 | "aggnonce_index": 1, 61 | "msg_index": 0, 62 | "signer_index": 0, 63 | "expected": "AE386064B26105404798F75DE2EB9AF5EDA5387B064B83D049CB7C5E08879531", 64 | "comment": "Both halves of aggregate nonce correspond to point at infinity" 65 | }, 66 | { 67 | "key_indices": [0, 1, 2], 68 | "nonce_indices": [0, 1, 2], 69 | "aggnonce_index": 0, 70 | "msg_index": 1, 71 | "signer_index": 0, 72 | "expected": "D7D63FFD644CCDA4E62BC2BC0B1D02DD32A1DC3030E155195810231D1037D82D", 73 | "comment": "Empty message" 74 | }, 75 | { 76 | "key_indices": [0, 1, 2], 77 | "nonce_indices": [0, 1, 2], 78 | "aggnonce_index": 0, 79 | "msg_index": 2, 80 | "signer_index": 0, 81 | "expected": "E184351828DA5094A97C79CABDAAA0BFB87608C32E8829A4DF5340A6F243B78C", 82 | "comment": "38-byte message" 83 | } 84 | ], 85 | "sign_error_test_cases": [ 86 | { 87 | "key_indices": [1, 2], 88 | "aggnonce_index": 0, 89 | "msg_index": 0, 90 | "secnonce_index": 0, 91 | "error": { 92 | "type": "value", 93 | "message": "The signer's pubkey must be included in the list of pubkeys." 94 | }, 95 | "comment": "The signers pubkey is not in the list of pubkeys. This test case is optional: it can be skipped by implementations that do not check that the signer's pubkey is included in the list of pubkeys." 96 | }, 97 | { 98 | "key_indices": [1, 0, 3], 99 | "aggnonce_index": 0, 100 | "msg_index": 0, 101 | "secnonce_index": 0, 102 | "error": { 103 | "type": "invalid_contribution", 104 | "signer": 2, 105 | "contrib": "pubkey" 106 | }, 107 | "comment": "Signer 2 provided an invalid public key" 108 | }, 109 | { 110 | "key_indices": [1, 2, 0], 111 | "aggnonce_index": 2, 112 | "msg_index": 0, 113 | "secnonce_index": 0, 114 | "error": { 115 | "type": "invalid_contribution", 116 | "signer": null, 117 | "contrib": "aggnonce" 118 | }, 119 | "comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half" 120 | }, 121 | { 122 | "key_indices": [1, 2, 0], 123 | "aggnonce_index": 3, 124 | "msg_index": 0, 125 | "secnonce_index": 0, 126 | "error": { 127 | "type": "invalid_contribution", 128 | "signer": null, 129 | "contrib": "aggnonce" 130 | }, 131 | "comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate" 132 | }, 133 | { 134 | "key_indices": [1, 2, 0], 135 | "aggnonce_index": 4, 136 | "msg_index": 0, 137 | "secnonce_index": 0, 138 | "error": { 139 | "type": "invalid_contribution", 140 | "signer": null, 141 | "contrib": "aggnonce" 142 | }, 143 | "comment": "Aggregate nonce is invalid because second half exceeds field size" 144 | }, 145 | { 146 | "key_indices": [0, 1, 2], 147 | "aggnonce_index": 0, 148 | "msg_index": 0, 149 | "signer_index": 0, 150 | "secnonce_index": 1, 151 | "error": { 152 | "type": "value", 153 | "message": "first secnonce value is out of range." 154 | }, 155 | "comment": "Secnonce is invalid which may indicate nonce reuse" 156 | } 157 | ], 158 | "verify_fail_test_cases": [ 159 | { 160 | "sig": "97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406", 161 | "key_indices": [0, 1, 2], 162 | "nonce_indices": [0, 1, 2], 163 | "msg_index": 0, 164 | "signer_index": 0, 165 | "comment": "Wrong signature (which is equal to the negation of valid signature)" 166 | }, 167 | { 168 | "sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B", 169 | "key_indices": [0, 1, 2], 170 | "nonce_indices": [0, 1, 2], 171 | "msg_index": 0, 172 | "signer_index": 1, 173 | "comment": "Wrong signer" 174 | }, 175 | { 176 | "sig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 177 | "key_indices": [0, 1, 2], 178 | "nonce_indices": [0, 1, 2], 179 | "msg_index": 0, 180 | "signer_index": 0, 181 | "comment": "Signature exceeds group size" 182 | } 183 | ], 184 | "verify_error_test_cases": [ 185 | { 186 | "sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B", 187 | "key_indices": [0, 1, 2], 188 | "nonce_indices": [4, 1, 2], 189 | "msg_index": 0, 190 | "signer_index": 0, 191 | "error": { 192 | "type": "invalid_contribution", 193 | "signer": 0, 194 | "contrib": "pubnonce" 195 | }, 196 | "comment": "Invalid pubnonce" 197 | }, 198 | { 199 | "sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B", 200 | "key_indices": [3, 1, 2], 201 | "nonce_indices": [0, 1, 2], 202 | "msg_index": 0, 203 | "signer_index": 0, 204 | "error": { 205 | "type": "invalid_contribution", 206 | "signer": 0, 207 | "contrib": "pubkey" 208 | }, 209 | "comment": "Invalid pubkey" 210 | } 211 | ] 212 | } 213 | -------------------------------------------------------------------------------- /doc/adaptor_signatures.md: -------------------------------------------------------------------------------- 1 | This module exports [adaptor signature](https://bitcoinops.org/en/topics/adaptor-signatures/) implementations of BIP340 signing and MuSig signing. 2 | 3 | Adaptor signatures allow signers to create Schnorr signatures which can be verified, but do not pass BIP340 verification logic unless a specific secret scalar is added to the signature. 4 | 5 | [Further reading](https://conduition.io/scriptless/adaptorsigs/). 6 | 7 | ## MuSig Example 8 | 9 | Here we demonstrate a group of MuSig2 signers adaptor-signing the same message. The final signature which the group constructs is an [`AdaptorSignature`], which they cannot use until it has been decrypted (AKA 'adapted') by the correct adaptor secret (a scalar). 10 | 11 | ```rust 12 | use secp::{MaybeScalar, Point, Scalar}; 13 | use musig2::{AdaptorSignature, KeyAggContext, PartialSignature, PubNonce}; 14 | 15 | let seckeys = [ 16 | Scalar::from_slice(&[0x11; 32]).unwrap(), 17 | Scalar::from_slice(&[0x22; 32]).unwrap(), 18 | Scalar::from_slice(&[0x33; 32]).unwrap(), 19 | ]; 20 | 21 | let pubkeys = [ 22 | seckeys[0].base_point_mul(), 23 | seckeys[1].base_point_mul(), 24 | seckeys[2].base_point_mul(), 25 | ]; 26 | 27 | let key_agg_ctx = KeyAggContext::new(pubkeys).unwrap(); 28 | let aggregated_pubkey: Point = key_agg_ctx.aggregated_pubkey(); 29 | 30 | let message = "danger, will robinson!"; 31 | 32 | let adaptor_secret = Scalar::random(&mut rand::rng()); 33 | let adaptor_point = adaptor_secret.base_point_mul(); 34 | 35 | // Using the functional API. 36 | { 37 | use musig2::{AggNonce, SecNonce}; 38 | 39 | let secnonces = [ 40 | SecNonce::build([0x11; 32]).build(), 41 | SecNonce::build([0x22; 32]).build(), 42 | SecNonce::build([0x33; 32]).build(), 43 | ]; 44 | 45 | let pubnonces = [ 46 | secnonces[0].public_nonce(), 47 | secnonces[1].public_nonce(), 48 | secnonces[2].public_nonce(), 49 | ]; 50 | 51 | let aggnonce = AggNonce::sum(&pubnonces); 52 | 53 | let partial_signatures: Vec = seckeys 54 | .into_iter() 55 | .zip(secnonces) 56 | .map(|(seckey, secnonce)| { 57 | musig2::adaptor::sign_partial( 58 | &key_agg_ctx, 59 | seckey, 60 | secnonce, 61 | &aggnonce, 62 | adaptor_point, 63 | &message, 64 | ) 65 | }) 66 | .collect::, _>>() 67 | .expect("failed to create partial adaptor signatures"); 68 | 69 | let adaptor_signature: AdaptorSignature = musig2::adaptor::aggregate_partial_signatures( 70 | &key_agg_ctx, 71 | &aggnonce, 72 | adaptor_point, 73 | partial_signatures.iter().copied(), 74 | &message, 75 | ) 76 | .expect("failed to aggregate partial adaptor signatures"); 77 | 78 | // Verify the adaptor signature is valid for the given adaptor point and pubkey. 79 | musig2::adaptor::verify_single( 80 | aggregated_pubkey, 81 | &adaptor_signature, 82 | &message, 83 | adaptor_point, 84 | ) 85 | .expect("invalid aggregated adaptor signature"); 86 | 87 | // Decrypt the signature with the adaptor secret. 88 | let valid_signature = adaptor_signature.adapt(adaptor_secret).unwrap(); 89 | 90 | musig2::verify_single( 91 | aggregated_pubkey, 92 | valid_signature, 93 | &message, 94 | ) 95 | .expect("invalid decrypted adaptor signature"); 96 | 97 | // The decrypted signature and the adaptor signature allow an 98 | // observer to deduce the adaptor secret. 99 | let revealed: MaybeScalar = adaptor_signature 100 | .reveal_secret(&valid_signature) 101 | .expect("should compute adaptor secret from decrypted signature"); 102 | 103 | assert_eq!(revealed, MaybeScalar::Valid(adaptor_secret)); 104 | } 105 | 106 | // Using the state-machine API 107 | { 108 | use musig2::{FirstRound, SecNonceSpices, SecondRound}; 109 | 110 | let spiced = |i| SecNonceSpices::new() 111 | .with_seckey(seckeys[i]) 112 | .with_message(&message); 113 | 114 | let mut first_rounds = vec![ 115 | FirstRound::new(key_agg_ctx.clone(), [0x11; 32], 0, spiced(0)).unwrap(), 116 | FirstRound::new(key_agg_ctx.clone(), [0x22; 32], 1, spiced(1)).unwrap(), 117 | FirstRound::new(key_agg_ctx.clone(), [0x33; 32], 2, spiced(2)).unwrap(), 118 | ]; 119 | 120 | let public_nonces = [ 121 | first_rounds[0].our_public_nonce(), 122 | first_rounds[1].our_public_nonce(), 123 | first_rounds[2].our_public_nonce(), 124 | ]; 125 | 126 | for round in first_rounds.iter_mut() { 127 | round.receive_nonce(0, public_nonces[0].clone()).unwrap(); 128 | round.receive_nonce(1, public_nonces[1].clone()).unwrap(); 129 | round.receive_nonce(2, public_nonces[2].clone()).unwrap(); 130 | } 131 | 132 | // The `finalize_adaptor` method must be used instead of `finalize`, on 133 | // both first and second rounds. 134 | let mut second_rounds: Vec> = first_rounds 135 | .into_iter() 136 | .enumerate() 137 | .map(|(i, round)| round.finalize_adaptor(seckeys[i], adaptor_point, message).unwrap()) 138 | .collect(); 139 | 140 | let partial_sigs: [PartialSignature; 3] = [ 141 | second_rounds[0].our_signature(), 142 | second_rounds[1].our_signature(), 143 | second_rounds[2].our_signature(), 144 | ]; 145 | 146 | for round in second_rounds.iter_mut() { 147 | round.receive_signature(0, partial_sigs[0]).unwrap(); 148 | round.receive_signature(1, partial_sigs[1]).unwrap(); 149 | round.receive_signature(2, partial_sigs[2]).unwrap(); 150 | } 151 | 152 | for second_round in second_rounds.into_iter() { 153 | let adaptor_signature = second_round.finalize_adaptor::().unwrap(); 154 | 155 | // Verify the adaptor signature is valid for the given adaptor point and pubkey. 156 | musig2::adaptor::verify_single( 157 | aggregated_pubkey, 158 | &adaptor_signature, 159 | &message, 160 | adaptor_point, 161 | ) 162 | .expect("invalid aggregated adaptor signature"); 163 | 164 | // Decrypt the signature with the adaptor secret. 165 | let valid_signature = adaptor_signature.adapt(adaptor_secret).unwrap(); 166 | musig2::verify_single( 167 | aggregated_pubkey, 168 | valid_signature, 169 | &message, 170 | ) 171 | .expect("invalid decrypted adaptor signature"); 172 | 173 | // The decrypted signature and the adaptor signature allow an 174 | // observer to deduce the adaptor secret. 175 | let revealed: MaybeScalar = adaptor_signature 176 | .reveal_secret(&valid_signature) 177 | .expect("should compute adaptor secret from decrypted signature"); 178 | 179 | assert_eq!(revealed, MaybeScalar::Valid(adaptor_secret)); 180 | } 181 | } 182 | ``` 183 | 184 | ## Single Signer Example 185 | 186 | We also export single-signer adaptor signing logic. 187 | 188 | ```rust 189 | use secp::{MaybeScalar, Scalar}; 190 | 191 | let seckey = Scalar::random(&mut rand::rng()); 192 | let message = "hello world!"; 193 | 194 | // Create an adaptor signature, encrypted under a specific adaptor point. 195 | let adaptor_secret = Scalar::random(&mut rand::rng()); 196 | let adaptor_point = adaptor_secret.base_point_mul(); 197 | let aux_rand = [0xAA; 32]; // Should use an actual RNG. 198 | let adaptor_signature = 199 | musig2::adaptor::sign_solo(seckey, message, aux_rand, adaptor_point); 200 | 201 | // Verify the adaptor signature is valid for the given adaptor point and pubkey. 202 | let pubkey = seckey.base_point_mul(); 203 | musig2::adaptor::verify_single(pubkey, &adaptor_signature, message, adaptor_point) 204 | .expect("valid adaptor signature should verify"); 205 | 206 | // Decrypt the signature with the adaptor secret. 207 | let valid_sig: musig2::LiftedSignature = adaptor_signature 208 | .adapt(adaptor_secret) 209 | .expect("invalid adaptor secret"); 210 | 211 | musig2::verify_single(pubkey, valid_sig, message) 212 | .expect("decrypted adaptor signature is valid"); 213 | 214 | // The decrypted signature and the adaptor signature allow an 215 | // observer to deduce the adaptor secret. 216 | let revealed: MaybeScalar = adaptor_signature.reveal_secret(&valid_sig) 217 | .expect("decrypted sig should reveal adaptor secret"); 218 | assert_eq!(revealed, MaybeScalar::Valid(adaptor_secret)); 219 | ``` 220 | 221 | ## Encrypting Signatures 222 | 223 | The above examples create signatures by committing to an adaptor point as part of the signing process. This is the way most adaptor signatures are created. 224 | 225 | However, you can also encrypt existing signatures (including existing adaptor signatures) by tweaking them with an adaptor secret. This requires knowing the adaptor secret though, so you can't do this if you only know the public adaptor point. 226 | 227 | ```rust 228 | let signature = musig2::LiftedSignature::from_hex( 229 | "e565f19755422162cf7dc69ed8a4f4a27d81363d024a3de355644003da33ed2f\ 230 | 0cdd95945c6d28841192867842c104391b9cc31f25706ee302a96204a1d43eb7" 231 | ) 232 | .unwrap(); 233 | 234 | let adaptor_secret_1 = secp::Scalar::from_slice(&[0x55; 32]).unwrap(); 235 | let mut adaptor_signature: musig2::AdaptorSignature = signature.encrypt(adaptor_secret_1); 236 | 237 | // We can re-encrypt the same adaptor signature twice, so that it is locked behind 238 | // two different points. Both secrets must be learned to compute the valid signature. 239 | let adaptor_secret_2 = secp::Scalar::from_slice(&[0x66; 32]).unwrap(); 240 | adaptor_signature = adaptor_signature.encrypt(adaptor_secret_2); 241 | 242 | let decrypted: [u8; 64] = adaptor_signature 243 | .adapt(adaptor_secret_1 + adaptor_secret_2) 244 | .expect("valid decrypted adaptor signature"); 245 | assert_eq!(decrypted, signature.serialize()); 246 | ``` 247 | -------------------------------------------------------------------------------- /src/sig_agg.rs: -------------------------------------------------------------------------------- 1 | use secp::{MaybePoint, MaybeScalar, Point, G}; 2 | 3 | use crate::errors::VerifyError; 4 | use crate::{ 5 | compute_challenge_hash_tweak, AdaptorSignature, AggNonce, KeyAggContext, LiftedSignature, 6 | PartialSignature, 7 | }; 8 | 9 | /// Aggregate a collection of partial adaptor signatures together into a final 10 | /// adaptor signature on a given `message`, under the aggregated public key in 11 | /// `key_agg_ctx`. 12 | /// 13 | /// The resulting signature will not be valid unless adapted with the discrete log 14 | /// of the `adaptor_point`. 15 | /// 16 | /// Returns an error if the resulting signature would not be valid. 17 | pub fn aggregate_partial_adaptor_signatures>( 18 | key_agg_ctx: &KeyAggContext, 19 | aggregated_nonce: &AggNonce, 20 | adaptor_point: impl Into, 21 | partial_signatures: impl IntoIterator, 22 | message: impl AsRef<[u8]>, 23 | ) -> Result { 24 | let adaptor_point: MaybePoint = adaptor_point.into(); 25 | let aggregated_pubkey = key_agg_ctx.pubkey; 26 | 27 | let b: MaybeScalar = aggregated_nonce.nonce_coefficient(aggregated_pubkey, &message); 28 | let final_nonce: Point = aggregated_nonce.final_nonce(b); 29 | let adapted_nonce = final_nonce + adaptor_point; 30 | let nonce_x_bytes = adapted_nonce.serialize_xonly(); 31 | let e: MaybeScalar = compute_challenge_hash_tweak(&nonce_x_bytes, &aggregated_pubkey, &message); 32 | 33 | let aggregated_signature = partial_signatures 34 | .into_iter() 35 | .map(|sig| sig.into()) 36 | .sum::() 37 | + (e * key_agg_ctx.tweak_acc).negate_if(aggregated_pubkey.parity()); 38 | 39 | let effective_nonce = if adapted_nonce.has_even_y() { 40 | final_nonce 41 | } else { 42 | -final_nonce 43 | }; 44 | 45 | // Ensure the signature will verify as valid. 46 | if aggregated_signature * G != effective_nonce + e * aggregated_pubkey.to_even_y() { 47 | return Err(VerifyError::BadSignature); 48 | } 49 | 50 | let adaptor_sig = AdaptorSignature { 51 | R: MaybePoint::Valid(final_nonce), 52 | s: aggregated_signature, 53 | }; 54 | Ok(adaptor_sig) 55 | } 56 | 57 | /// Aggregate a collection of partial signatures together into a final 58 | /// signature on a given `message`, valid under the aggregated public 59 | /// key in `key_agg_ctx`. 60 | /// 61 | /// Returns an error if the resulting signature would not be valid. 62 | pub fn aggregate_partial_signatures( 63 | key_agg_ctx: &KeyAggContext, 64 | aggregated_nonce: &AggNonce, 65 | partial_signatures: impl IntoIterator, 66 | message: impl AsRef<[u8]>, 67 | ) -> Result 68 | where 69 | S: Into, 70 | T: From, 71 | { 72 | let sig = aggregate_partial_adaptor_signatures( 73 | key_agg_ctx, 74 | aggregated_nonce, 75 | MaybePoint::Infinity, 76 | partial_signatures, 77 | message, 78 | )? 79 | .adapt(MaybeScalar::Zero) 80 | .map(T::from) 81 | .expect("aggregating with empty adaptor should never result in an adaptor failure"); 82 | 83 | Ok(sig) 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::*; 89 | use crate::testhex; 90 | use crate::{verify_single, CompactSignature, PubNonce, SecNonce}; 91 | 92 | use secp::{Point, Scalar}; 93 | 94 | #[test] 95 | fn test_aggregate_partial_signatures() { 96 | const SIG_AGG_VECTORS: &[u8] = include_bytes!("test_vectors/sig_agg_vectors.json"); 97 | 98 | #[derive(serde::Deserialize)] 99 | struct ValidSigAggTestCase { 100 | #[serde(rename = "aggnonce")] 101 | aggregated_nonce: AggNonce, 102 | nonce_indices: Vec, 103 | key_indices: Vec, 104 | tweak_indices: Vec, 105 | is_xonly: Vec, 106 | psig_indices: Vec, 107 | 108 | #[serde(rename = "expected")] 109 | aggregated_signature: CompactSignature, 110 | } 111 | 112 | #[derive(serde::Deserialize)] 113 | struct SigAggVectors { 114 | pubkeys: Vec, 115 | 116 | #[serde(rename = "pnonces")] 117 | public_nonces: Vec, 118 | 119 | tweaks: Vec, 120 | 121 | #[serde(rename = "psigs", deserialize_with = "testhex::deserialize_vec")] 122 | partial_signatures: Vec>, 123 | 124 | #[serde(rename = "msg", deserialize_with = "testhex::deserialize")] 125 | message: Vec, 126 | 127 | valid_test_cases: Vec, 128 | } 129 | 130 | let vectors: SigAggVectors = serde_json::from_slice(SIG_AGG_VECTORS) 131 | .expect("failed to parse test vectors from sig_agg_vectors.json"); 132 | 133 | for test_case in vectors.valid_test_cases { 134 | let pubkeys = test_case 135 | .key_indices 136 | .into_iter() 137 | .map(|i| vectors.pubkeys[i]); 138 | 139 | let public_nonces = test_case 140 | .nonce_indices 141 | .into_iter() 142 | .map(|i| &vectors.public_nonces[i]); 143 | 144 | let aggregated_nonce = AggNonce::sum(public_nonces); 145 | 146 | assert_eq!( 147 | &aggregated_nonce, &test_case.aggregated_nonce, 148 | "aggregated nonce does not match test vector" 149 | ); 150 | 151 | let mut key_agg_ctx = 152 | KeyAggContext::new(pubkeys).expect("error constructing key aggregation context"); 153 | 154 | key_agg_ctx = test_case 155 | .tweak_indices 156 | .into_iter() 157 | .map(|i| vectors.tweaks[i]) 158 | .zip(test_case.is_xonly) 159 | .fold(key_agg_ctx, |ctx, (tweak, is_xonly)| { 160 | ctx.with_tweak(tweak, is_xonly).unwrap_or_else(|_| { 161 | panic!("failed to tweak key agg context with {:x}", tweak) 162 | }) 163 | }); 164 | 165 | let partial_signatures: Vec = test_case 166 | .psig_indices 167 | .into_iter() 168 | .map(|i| { 169 | Scalar::try_from(vectors.partial_signatures[i].as_slice()) 170 | .expect("failed to parse partial signature") 171 | }) 172 | .collect(); 173 | 174 | let aggregated_signature: CompactSignature = aggregate_partial_signatures( 175 | &key_agg_ctx, 176 | &aggregated_nonce, 177 | partial_signatures, 178 | &vectors.message, 179 | ) 180 | .expect("failed to aggregate partial signatures"); 181 | 182 | assert_eq!( 183 | &aggregated_signature, &test_case.aggregated_signature, 184 | "incorrect aggregated signature" 185 | ); 186 | 187 | verify_single(key_agg_ctx.pubkey, aggregated_signature, &vectors.message) 188 | .unwrap_or_else(|_| { 189 | panic!( 190 | "aggregated signature {} should be valid BIP340 signature", 191 | aggregated_signature 192 | ) 193 | }); 194 | } 195 | } 196 | 197 | #[test] 198 | fn test_adaptor_signature_aggregation() { 199 | const ITERATIONS: usize = 10; 200 | 201 | for _ in 0..ITERATIONS { 202 | let seckeys = [ 203 | Scalar::random(&mut rand::rng()), 204 | Scalar::random(&mut rand::rng()), 205 | Scalar::random(&mut rand::rng()), 206 | ]; 207 | 208 | let pubkeys = [ 209 | seckeys[0].base_point_mul(), 210 | seckeys[1].base_point_mul(), 211 | seckeys[2].base_point_mul(), 212 | ]; 213 | 214 | let key_agg_ctx = KeyAggContext::new(pubkeys).unwrap(); 215 | 216 | let message = b"danger, will robinson!"; 217 | 218 | let secnonces = [ 219 | SecNonce::random(&mut rand::rng()), 220 | SecNonce::random(&mut rand::rng()), 221 | SecNonce::random(&mut rand::rng()), 222 | ]; 223 | 224 | let pubnonces = [ 225 | secnonces[0].public_nonce(), 226 | secnonces[1].public_nonce(), 227 | secnonces[2].public_nonce(), 228 | ]; 229 | 230 | let aggnonce = AggNonce::sum(&pubnonces); 231 | 232 | let adaptor_secret = Scalar::random(&mut rand::rng()); 233 | let adaptor_point = adaptor_secret.base_point_mul(); 234 | 235 | let partial_signatures: Vec = seckeys 236 | .into_iter() 237 | .zip(secnonces) 238 | .map(|(seckey, secnonce)| { 239 | crate::adaptor::sign_partial( 240 | &key_agg_ctx, 241 | seckey, 242 | secnonce, 243 | &aggnonce, 244 | adaptor_point, 245 | message, 246 | ) 247 | }) 248 | .collect::, _>>() 249 | .expect("failed to create partial adaptor signatures"); 250 | 251 | let adaptor_signature: AdaptorSignature = crate::adaptor::aggregate_partial_signatures( 252 | &key_agg_ctx, 253 | &aggnonce, 254 | adaptor_point, 255 | partial_signatures.iter().copied(), 256 | message, 257 | ) 258 | .expect("failed to aggregate partial adaptor signatures"); 259 | 260 | crate::adaptor::verify_single( 261 | key_agg_ctx.aggregated_pubkey::(), 262 | &adaptor_signature, 263 | message, 264 | adaptor_point, 265 | ) 266 | .expect("invalid aggregated adaptor signature"); 267 | 268 | let valid_signature = adaptor_signature.adapt(adaptor_secret).unwrap(); 269 | verify_single( 270 | key_agg_ctx.aggregated_pubkey::(), 271 | valid_signature, 272 | message, 273 | ) 274 | .expect("invalid decrypted adaptor signature"); 275 | 276 | let revealed: MaybeScalar = adaptor_signature 277 | .reveal_secret(&valid_signature) 278 | .expect("should compute adaptor secret from decrypted signature"); 279 | 280 | assert_eq!(revealed, MaybeScalar::Valid(adaptor_secret)); 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /tests/fuzz_against_reference_impl.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | use secp::{Point, Scalar}; 3 | 4 | use musig2::{AggNonce, KeyAggContext, PartialSignature, SecNonce, SCHNORR_SIGNATURE_SIZE}; 5 | 6 | fn run_reference_code(code: &str) -> Vec { 7 | let script = [ 8 | "import reference", 9 | "from binascii import hexlify, unhexlify", 10 | "from sys import stdout\n\n", 11 | ] 12 | .join("\n") 13 | + code; 14 | 15 | let error_message = format!("failed to run reference code:\n{}", code); 16 | 17 | let output = std::process::Command::new("python3") 18 | .arg("-c") 19 | .arg(script) 20 | .output() 21 | .expect(&error_message); 22 | 23 | let stderr = String::from_utf8(output.stderr).unwrap(); 24 | 25 | assert!( 26 | output.status.success(), 27 | "{}\nstderr: {}", 28 | error_message, 29 | stderr 30 | ); 31 | 32 | output.stdout 33 | } 34 | 35 | #[test] 36 | fn test_python_interop() { 37 | let output = String::from_utf8(run_reference_code("print('hello world')")).unwrap(); 38 | assert_eq!(output, "hello world\n"); 39 | } 40 | 41 | fn random_sample_indexes( 42 | rng: &mut R, 43 | iterations: usize, 44 | max_count: usize, 45 | index_ceil: usize, 46 | ) -> Vec> { 47 | (0..iterations) 48 | .map(|_| { 49 | let count = rng.random_range(1..max_count); 50 | (0..count) 51 | .map(|_| rng.random_range(0..index_ceil)) 52 | .collect::>() 53 | }) 54 | .collect() 55 | } 56 | 57 | /// Runs our key aggregation code against the reference implementation using randomly 58 | /// chosen pubkey inputs. 59 | #[test] 60 | fn test_key_aggregation() { 61 | let mut rng = rand::rng(); 62 | 63 | // Initialize a random array of pubkeys. 64 | let mut all_pubkeys = [Point::generator(); 6]; 65 | for pubkey in &mut all_pubkeys { 66 | *pubkey *= Scalar::random(&mut rng); 67 | } 68 | 69 | const ITERATIONS: usize = 5; 70 | const MAX_PUBKEYS: usize = 8; 71 | 72 | // randomly sample indexes into that array, with at least length 1 73 | let generated_indexes: Vec> = 74 | random_sample_indexes(&mut rng, ITERATIONS, MAX_PUBKEYS, all_pubkeys.len()); 75 | 76 | let all_pubkeys_json = serde_json::to_string(&all_pubkeys).unwrap(); 77 | let generated_indexes_json = serde_json::to_string(&generated_indexes).unwrap(); 78 | 79 | let reference_code_output = run_reference_code(&format!( 80 | r#" 81 | all_pubkeys = [unhexlify(key) for key in {all_pubkeys_json}] 82 | generated_indexes = {generated_indexes_json} 83 | 84 | for indexes in generated_indexes: 85 | pubkeys = [all_pubkeys[i] for i in indexes] 86 | Q = reference.key_agg(pubkeys)[0] 87 | stdout.buffer.write(reference.cbytes(Q)) 88 | "# 89 | )); 90 | assert_eq!( 91 | reference_code_output.len(), 92 | ITERATIONS * 33, 93 | "expected to receive exactly {} * 33 bytes back from reference impl", 94 | ITERATIONS 95 | ); 96 | 97 | for i in 0..ITERATIONS { 98 | let pubkeys: Vec = generated_indexes[i] 99 | .iter() 100 | .map(|&j| all_pubkeys[j]) 101 | .collect(); 102 | 103 | let expected_pubkey_bytes = &reference_code_output[(i * 33)..(i * 33 + 33)]; 104 | let expected_pubkey = Point::from_slice(expected_pubkey_bytes).unwrap_or_else(|_| { 105 | panic!( 106 | "error decoding aggregated public key from reference implementation: {}", 107 | base16ct::lower::encode_string(expected_pubkey_bytes) 108 | ) 109 | }); 110 | 111 | let pubkeys_json = serde_json::to_string(&pubkeys).unwrap(); 112 | 113 | let our_pubkey: Point = KeyAggContext::new(pubkeys) 114 | .unwrap_or_else(|_| panic!("failed to aggregate pubkeys: {}", pubkeys_json)) 115 | .aggregated_pubkey(); 116 | 117 | assert_eq!( 118 | our_pubkey, expected_pubkey, 119 | "aggregated pubkey does not match reference impl for inputs: {}", 120 | pubkeys_json 121 | ); 122 | } 123 | } 124 | 125 | /// Runs our partial signing and signature aggregation code against 126 | /// the reference implementation. 127 | #[test] 128 | fn test_signing() { 129 | let mut rng = rand::rng(); 130 | 131 | let mut all_seckeys = [Scalar::one(); 4]; 132 | for seckey in &mut all_seckeys { 133 | *seckey = Scalar::random(&mut rng); 134 | } 135 | 136 | let all_pubkeys = all_seckeys 137 | .into_iter() 138 | .map(|sk| sk.base_point_mul()) 139 | .collect::>(); 140 | 141 | let all_nonce_seeds = (0..all_seckeys.len()) 142 | .map(|_| Scalar::random(&mut rng)) 143 | .collect::>(); 144 | 145 | let message = "Welcome to MuSig"; 146 | 147 | const ITERATIONS: usize = 5; 148 | const MAX_SIGNERS: usize = 5; 149 | 150 | let all_key_indexes = 151 | random_sample_indexes(&mut rng, ITERATIONS, MAX_SIGNERS, all_seckeys.len()); 152 | 153 | let all_aggregated_pubkeys = (0..ITERATIONS) 154 | .map(|i| { 155 | let pubkeys = all_key_indexes[i].iter().map(|&j| all_pubkeys[j]); 156 | KeyAggContext::new(pubkeys).unwrap().aggregated_pubkey() 157 | }) 158 | .collect::>(); 159 | 160 | let all_seckeys_json = serde_json::to_string(&all_seckeys).unwrap(); 161 | let all_pubkeys_json = serde_json::to_string(&all_pubkeys).unwrap(); 162 | let all_nonce_seeds_json = serde_json::to_string(&all_nonce_seeds).unwrap(); 163 | let all_key_indexes_json = serde_json::to_string(&all_key_indexes).unwrap(); 164 | let all_aggregated_pubkeys_json = serde_json::to_string(&all_aggregated_pubkeys).unwrap(); 165 | 166 | let reference_code_output = run_reference_code(&format!( 167 | r#" 168 | all_seckeys = [unhexlify(key) for key in {all_seckeys_json}] 169 | all_pubkeys = [unhexlify(key) for key in {all_pubkeys_json}] 170 | all_nonce_seeds = [unhexlify(seed) for seed in {all_nonce_seeds_json}] 171 | all_key_indexes = {all_key_indexes_json} 172 | all_aggregated_pubkeys = [unhexlify(b) for b in {all_aggregated_pubkeys_json}] 173 | 174 | message = b"{message}" 175 | 176 | for i in range({ITERATIONS}): 177 | aggregated_pubkey = all_aggregated_pubkeys[i] 178 | key_indexes = all_key_indexes[i] 179 | seckeys = [all_seckeys[j] for j in key_indexes] 180 | pubkeys = [all_pubkeys[j] for j in key_indexes] 181 | nonce_seeds = [all_nonce_seeds[j] for j in key_indexes] 182 | 183 | nonces = [ 184 | reference.nonce_gen_internal( 185 | nonce_seeds[k], 186 | seckeys[k], 187 | pubkeys[k], 188 | aggregated_pubkey[1:], 189 | message, 190 | k.to_bytes(4, 'big') 191 | ) 192 | for k in range(len(key_indexes)) 193 | ] 194 | 195 | aggnonce = reference.nonce_agg([pubnonce for (_, pubnonce) in nonces]) 196 | session_ctx = (aggnonce, pubkeys, [], [], message) 197 | 198 | partial_signatures = [] 199 | for ((secnonce, _), seckey) in zip(nonces, seckeys): 200 | partial_signature = reference.sign(secnonce, seckey, session_ctx) 201 | stdout.buffer.write(partial_signature) 202 | partial_signatures.append(partial_signature) 203 | 204 | final_signature = reference.partial_sig_agg(partial_signatures, session_ctx) 205 | stdout.buffer.write(final_signature) 206 | "# 207 | )); 208 | 209 | let n_partial_signatures = all_key_indexes 210 | .iter() 211 | .map(|indexes| indexes.len()) 212 | .sum::(); 213 | 214 | assert_eq!( 215 | reference_code_output.len(), 216 | n_partial_signatures * 32 + SCHNORR_SIGNATURE_SIZE * ITERATIONS, 217 | "expected {} partial signatures and {} aggregated signatures from reference \ 218 | implementation, got {} bytes", 219 | n_partial_signatures, 220 | ITERATIONS, 221 | reference_code_output.len() 222 | ); 223 | 224 | let mut cursor = 0usize; 225 | 226 | for i in 0..ITERATIONS { 227 | let key_indexes = all_key_indexes[i].clone(); 228 | let seckeys = key_indexes 229 | .iter() 230 | .map(|&j| all_seckeys[j]) 231 | .collect::>(); 232 | let pubkeys = key_indexes 233 | .iter() 234 | .map(|&j| all_pubkeys[j]) 235 | .collect::>(); 236 | let nonce_seeds = key_indexes 237 | .iter() 238 | .map(|&j| all_nonce_seeds[j]) 239 | .collect::>(); 240 | 241 | let debug_json = serde_json::to_string(&serde_json::json!({ 242 | "seckeys": &seckeys, 243 | "nonce_seeds": &nonce_seeds, 244 | })) 245 | .unwrap(); 246 | 247 | let aggregated_pubkey = all_aggregated_pubkeys[i]; 248 | let key_agg_ctx = KeyAggContext::new(pubkeys).unwrap(); 249 | assert_eq!(key_agg_ctx.aggregated_pubkey::(), aggregated_pubkey); 250 | 251 | let secnonces: Vec = nonce_seeds 252 | .into_iter() 253 | .enumerate() 254 | .map(|(k, seed)| { 255 | SecNonce::generate( 256 | seed.serialize(), 257 | seckeys[k], 258 | aggregated_pubkey, 259 | message, 260 | (k as u32).to_be_bytes(), 261 | ) 262 | }) 263 | .collect(); 264 | 265 | let aggnonce = secnonces 266 | .iter() 267 | .map(|secnonce| secnonce.public_nonce()) 268 | .sum::(); 269 | 270 | let mut partial_signatures = Vec::with_capacity(seckeys.len()); 271 | for k in 0..seckeys.len() { 272 | let our_partial_signature: PartialSignature = musig2::sign_partial( 273 | &key_agg_ctx, 274 | seckeys[k], 275 | secnonces[k].clone(), 276 | &aggnonce, 277 | message, 278 | ) 279 | .unwrap_or_else(|_| { 280 | panic!( 281 | "failed to sign with randomly chosen keys and nonces: {}", 282 | debug_json 283 | ) 284 | }); 285 | 286 | let expected_partial_signature_bytes = &reference_code_output[cursor..cursor + 32]; 287 | cursor += 32; 288 | 289 | assert_eq!( 290 | &our_partial_signature.serialize(), 291 | expected_partial_signature_bytes, 292 | "incorrect partial signature for signer index {} using keys and nonces: {}", 293 | k, 294 | debug_json, 295 | ); 296 | 297 | partial_signatures.push(our_partial_signature); 298 | } 299 | let expected_signature_bytes = 300 | &reference_code_output[cursor..cursor + SCHNORR_SIGNATURE_SIZE]; 301 | cursor += SCHNORR_SIGNATURE_SIZE; 302 | 303 | let our_signature: [u8; SCHNORR_SIGNATURE_SIZE] = musig2::aggregate_partial_signatures( 304 | &key_agg_ctx, 305 | &aggnonce, 306 | partial_signatures, 307 | message, 308 | ) 309 | .expect("error aggregating partial signatures"); 310 | 311 | assert_eq!( 312 | &our_signature, expected_signature_bytes, 313 | "incorrect aggregated signature using keys and nonces: {}", 314 | debug_json 315 | ); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/binary_encoding.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::DecodeError; 2 | 3 | /// Marks a type which can be serialized to and from a binary encoding of either 4 | /// fixed or variable length. 5 | pub trait BinaryEncoding: Sized { 6 | /// The binary type which is returned by serialization. Should either 7 | /// be `[u8; N]` or `Vec`. 8 | type Serialized; 9 | 10 | /// Serialize this data structure to its binary representation. 11 | fn to_bytes(&self) -> Self::Serialized; 12 | 13 | /// Deserialize this data structure from a binary representation. 14 | fn from_bytes(bytes: &[u8]) -> Result>; 15 | } 16 | 17 | /// Implements various binary encoding traits for both fixed or 18 | /// variable-length encoded data structures. 19 | /// 20 | /// Use this macro by first implementing [`BinaryEncoding`] on a type, 21 | /// and then invoking `impl_encoding_traits` on the type. 22 | macro_rules! impl_encoding_traits { 23 | // Fixed length encoding 24 | ($typename:ty, $byte_len:expr $(, $max_byte_len:expr)?) => { 25 | /// assert that $typename implements `BinaryEncoding` 26 | const _: () = { 27 | fn __( 28 | x: $typename, 29 | ) -> impl BinaryEncoding 30 | { 31 | x 32 | } 33 | }; 34 | 35 | impl std::fmt::LowerHex for $typename { 36 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 37 | let mut buffer = [0; $byte_len * 2]; 38 | let encoded = base16ct::lower::encode_str(&self.to_bytes(), &mut buffer).unwrap(); 39 | f.write_str(encoded) 40 | } 41 | } 42 | 43 | impl std::fmt::UpperHex for $typename { 44 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 45 | let mut buffer = [0; $byte_len * 2]; 46 | let encoded = base16ct::upper::encode_str(&self.to_bytes(), &mut buffer).unwrap(); 47 | f.write_str(encoded) 48 | } 49 | } 50 | 51 | impl std::str::FromStr for $typename { 52 | type Err = DecodeError; 53 | 54 | /// Parses this type from a hex string, which can be either upper or 55 | /// lower case. The binary format of the decoded hex data should 56 | /// match that returned by [`to_bytes`][Self::to_bytes]. 57 | /// 58 | /// Same as [`Self::from_hex`]. 59 | fn from_str(hex: &str) -> Result { 60 | let mut buffer = [0; $byte_len]; 61 | let bytes = base16ct::mixed::decode(hex, &mut buffer)?; 62 | Self::from_bytes(bytes) 63 | } 64 | } 65 | 66 | impl TryFrom<&[u8]> for $typename { 67 | type Error = DecodeError; 68 | 69 | /// Parse this type from a variable-length byte slice. 70 | /// 71 | /// Same as [`Self::from_bytes`][Self::from_bytes]. 72 | fn try_from(bytes: &[u8]) -> Result { 73 | Self::from_bytes(bytes) 74 | } 75 | } 76 | 77 | impl TryFrom<[u8; $byte_len]> for $typename { 78 | type Error = DecodeError; 79 | 80 | /// Parse this type from its fixed-length binary representation. 81 | fn try_from(bytes: [u8; $byte_len]) -> Result { 82 | Self::from_bytes(&bytes) 83 | } 84 | } 85 | 86 | impl TryFrom<&[u8; $byte_len]> for $typename { 87 | type Error = DecodeError; 88 | 89 | /// Parse this type from its fixed-length binary representation. 90 | /// 91 | /// Same as [`Self::from_bytes`][Self::from_bytes]. 92 | fn try_from(bytes: &[u8; $byte_len]) -> Result { 93 | Self::from_bytes(bytes) 94 | } 95 | } 96 | 97 | $( 98 | impl TryFrom<&[u8; $max_byte_len]> for $typename { 99 | type Error = DecodeError; 100 | 101 | /// Parse this type from its maximum-length binary representation. 102 | /// Throws away unused data. 103 | /// 104 | /// Same as [`Self::from_bytes`][Self::from_bytes]. 105 | fn try_from(bytes: &[u8; $max_byte_len]) -> Result { 106 | Self::from_bytes(bytes) 107 | } 108 | } 109 | )? 110 | 111 | impl From<$typename> for [u8; $byte_len] { 112 | /// Serialize this type to a fixed-length byte array. 113 | fn from(value: $typename) -> Self { 114 | value.to_bytes() 115 | } 116 | } 117 | 118 | impl From<$typename> for Vec { 119 | /// Serialize this type to a heap-allocated byte vector. 120 | fn from(value: $typename) -> Self { 121 | Vec::from(value.to_bytes()) 122 | } 123 | } 124 | 125 | impl $typename { 126 | /// Alias to [the `BinaryEncoding` trait implementation of `to_bytes`][Self::to_bytes]. 127 | pub fn serialize(&self) -> [u8; $byte_len] { 128 | ::to_bytes(self) 129 | } 130 | 131 | /// Alias to [the `BinaryEncoding` trait implementation of `from_bytes`][Self::from_bytes]. 132 | pub fn from_bytes(bytes: &[u8]) -> Result> { 133 | ::from_bytes(bytes) 134 | } 135 | 136 | /// Parses this type from a hex string, which can be either upper or 137 | /// lower case. The binary format of the decoded hex data should 138 | /// match that returned by [`to_bytes`][Self::to_bytes]. 139 | /// 140 | /// Same as [`Self::from_str`](#method.from_str). 141 | pub fn from_hex(hex: &str) -> Result> { 142 | hex.parse() 143 | } 144 | } 145 | 146 | #[cfg(any(test, feature = "serde"))] 147 | impl serde::Serialize for $typename { 148 | fn serialize(&self, serializer: S) -> Result { 149 | let bytes = self.to_bytes(); 150 | serdect::array::serialize_hex_lower_or_bin(&bytes, serializer) 151 | } 152 | } 153 | 154 | #[cfg(any(test, feature = "serde"))] 155 | impl<'de> serde::Deserialize<'de> for $typename { 156 | /// Deserializes this type from a byte array or a hex 157 | /// string, depending on the human-readability of the data format. 158 | fn deserialize>(deserializer: D) -> Result { 159 | #[allow(unused_mut, unused_variables)] 160 | let mut buffer = [0u8; $byte_len]; 161 | 162 | // Used for a type like SecNonce where we need to accept a longer encoding 163 | // and throw away the unused bytes. 164 | $(let mut buffer = [0u8; $max_byte_len];)? 165 | 166 | let bytes = serdect::slice::deserialize_hex_or_bin(&mut buffer, deserializer)?; 167 | <$typename>::from_bytes(bytes).map_err(|_| { 168 | serde::de::Error::invalid_value( 169 | serde::de::Unexpected::Bytes(&bytes), 170 | &concat!("a byte array representing ", stringify!($typename)), 171 | ) 172 | }) 173 | } 174 | } 175 | }; 176 | 177 | // Variable-length encoding 178 | ($typename:ty) => { 179 | /// assert that $typename implements `BinaryEncoding` 180 | const _: () = { 181 | fn __( 182 | x: $typename, 183 | ) -> impl BinaryEncoding> { 184 | x 185 | } 186 | }; 187 | 188 | impl std::fmt::LowerHex for $typename { 189 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 190 | let bytes = self.to_bytes(); 191 | let mut buffer = vec![0; bytes.len() * 2]; 192 | let encoded = base16ct::lower::encode_str(&bytes, &mut buffer).unwrap(); 193 | f.write_str(encoded) 194 | } 195 | } 196 | 197 | impl std::fmt::UpperHex for $typename { 198 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 199 | let bytes = self.to_bytes(); 200 | let mut buffer = vec![0; bytes.len() * 2]; 201 | let encoded = base16ct::upper::encode_str(&bytes, &mut buffer).unwrap(); 202 | f.write_str(encoded) 203 | } 204 | } 205 | 206 | impl std::str::FromStr for $typename { 207 | type Err = DecodeError; 208 | 209 | /// Parses this type from a hex string, which can be either upper or 210 | /// lower case. The binary format of the decoded hex data should 211 | /// match that returned by [`to_bytes`][Self::to_bytes]. 212 | /// 213 | /// Same as [`Self::from_hex`]. 214 | fn from_str(hex: &str) -> Result { 215 | let bytes = base16ct::mixed::decode_vec(hex)?; 216 | Self::from_bytes(&bytes) 217 | } 218 | } 219 | 220 | impl TryFrom<&[u8]> for $typename { 221 | type Error = DecodeError; 222 | 223 | /// Parse this type from a variable-length byte slice. 224 | /// 225 | /// Same as [`Self::from_bytes`][Self::from_bytes]. 226 | fn try_from(bytes: &[u8]) -> Result { 227 | Self::from_bytes(bytes) 228 | } 229 | } 230 | 231 | impl From<$typename> for Vec { 232 | /// Serialize this type to a heap-allocated byte vector. 233 | fn from(value: $typename) -> Self { 234 | value.to_bytes() 235 | } 236 | } 237 | 238 | impl $typename { 239 | /// Alias to [the `BinaryEncoding` trait implementation of `to_bytes`][Self::to_bytes]. 240 | pub fn serialize(&self) -> Vec { 241 | ::to_bytes(self) 242 | } 243 | 244 | /// Alias to [the `BinaryEncoding` trait implementation of `from_bytes`][Self::from_bytes]. 245 | pub fn from_bytes(bytes: &[u8]) -> Result> { 246 | ::from_bytes(bytes) 247 | } 248 | 249 | /// Parses this type from a hex string, which can be either upper or 250 | /// lower case. The binary format of the decoded hex data should 251 | /// match that returned by [`to_bytes`][Self::to_bytes]. 252 | /// 253 | /// Same as [`Self::from_str`](#method.from_str). 254 | pub fn from_hex(hex: &str) -> Result> { 255 | hex.parse() 256 | } 257 | } 258 | 259 | #[cfg(any(test, feature = "serde"))] 260 | impl serde::Serialize for $typename { 261 | fn serialize(&self, serializer: S) -> Result { 262 | let bytes = self.to_bytes(); 263 | serdect::slice::serialize_hex_lower_or_bin(&bytes, serializer) 264 | } 265 | } 266 | 267 | #[cfg(any(test, feature = "serde"))] 268 | impl<'de> serde::Deserialize<'de> for $typename { 269 | /// Deserializes this type from a byte vector or a hex 270 | /// string, depending on the human-readability of the data format. 271 | fn deserialize>(deserializer: D) -> Result { 272 | let bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?; 273 | <$typename>::from_bytes(&bytes).map_err(|_| { 274 | serde::de::Error::invalid_value( 275 | serde::de::Unexpected::Bytes(&bytes), 276 | &concat!("a byte vector representing ", stringify!($typename)), 277 | ) 278 | }) 279 | } 280 | } 281 | }; 282 | } 283 | 284 | /// Implements the Display trait for a type by formatting it as a lower-case 285 | /// hex string. 286 | macro_rules! impl_hex_display { 287 | ($typename:ident) => { 288 | impl std::fmt::Display for $typename { 289 | /// Formats this type as a lower-case hex string. 290 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 291 | write!(f, "{:x}", self) 292 | } 293 | } 294 | }; 295 | } 296 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Various error types for different kinds of failures. 2 | 3 | use crate::KeyAggContext; 4 | 5 | use std::error::Error; 6 | use std::fmt; 7 | 8 | /// Returned when aggregating a collection of public keys with [`KeyAggContext`] 9 | /// results in the point at infinity. 10 | #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] 11 | pub struct KeyAggError; 12 | impl fmt::Display for KeyAggError { 13 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 14 | f.write_str("computed an invalid aggregated key from a collection of public keys") 15 | } 16 | } 17 | impl Error for KeyAggError {} 18 | impl From for KeyAggError { 19 | fn from(_: secp::errors::InfinityPointError) -> Self { 20 | KeyAggError 21 | } 22 | } 23 | 24 | /// Returned when aggregating a collection of secret keys with [`KeyAggContext`], 25 | /// but some secret keys are missing, or the keys are not the correct secret keys 26 | /// for the pubkeys contained in the key agg context. 27 | #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] 28 | pub struct InvalidSecretKeysError; 29 | impl fmt::Display for InvalidSecretKeysError { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | f.write_str("missing or invalid secret keys provided for aggregation") 32 | } 33 | } 34 | impl Error for InvalidSecretKeysError {} 35 | impl From for InvalidSecretKeysError { 36 | fn from(_: secp::errors::ZeroScalarError) -> Self { 37 | InvalidSecretKeysError 38 | } 39 | } 40 | 41 | /// Returned when tweaking a [`KeyAggContext`] results in the point 42 | /// at infinity, or if using [`KeyAggContext::with_taproot_tweak`] 43 | /// when the tweak input results in a hash which exceeds the curve 44 | /// order (exceedingly unlikely)" 45 | #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] 46 | pub struct TweakError; 47 | impl fmt::Display for TweakError { 48 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 49 | f.write_str("tweak value is invalid") 50 | } 51 | } 52 | impl Error for TweakError {} 53 | impl From for TweakError { 54 | fn from(_: secp::errors::InfinityPointError) -> Self { 55 | TweakError 56 | } 57 | } 58 | 59 | /// Returned when passing a signer index which is out of range for a 60 | /// group of signers 61 | #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] 62 | pub struct SignerIndexError { 63 | /// The index of the signer we did not expect to receive. 64 | pub index: usize, 65 | 66 | /// The total size of the signing group. 67 | pub n_signers: usize, 68 | } 69 | impl fmt::Display for SignerIndexError { 70 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 71 | write!( 72 | f, 73 | "signer index {} is out of range for group of {} signers", 74 | self.index, self.n_signers 75 | ) 76 | } 77 | } 78 | impl Error for SignerIndexError {} 79 | 80 | impl SignerIndexError { 81 | /// Construct a new `SignerIndexError` indicating we received an 82 | /// invalid index for the given group size of signers. 83 | pub(crate) fn new(index: usize, n_signers: usize) -> SignerIndexError { 84 | SignerIndexError { index, n_signers } 85 | } 86 | } 87 | 88 | /// Error returned when (partial) signing fails. 89 | #[derive(Debug, PartialEq, Eq, Clone)] 90 | pub enum SigningError { 91 | /// Indicates an unknown secret key was provided when 92 | /// using [`sign_partial`][crate::sign_partial] or 93 | /// finalizing the [`FirstRound`][crate::FirstRound]. 94 | UnknownKey, 95 | 96 | /// We could not verify the signature we produced. 97 | /// This may indicate a malicious actor attempted to make us 98 | /// produce a signature which could reveal our secret key. The 99 | /// signing session should be aborted and retried with new nonces. 100 | SelfVerifyFail, 101 | } 102 | impl fmt::Display for SigningError { 103 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 104 | write!( 105 | f, 106 | "failed to create signature: {}", 107 | match self { 108 | Self::UnknownKey => "signing key is not a member of the group", 109 | Self::SelfVerifyFail => "failed to verify our own signature; something is wrong", 110 | } 111 | ) 112 | } 113 | } 114 | impl Error for SigningError {} 115 | 116 | /// Error returned when verification fails. 117 | #[derive(Debug, PartialEq, Eq, Clone)] 118 | pub enum VerifyError { 119 | /// Indicates a public key was provided which is not 120 | /// a member of the signing group, and thus partial 121 | /// signature verification on this key has no meaning. 122 | UnknownKey, 123 | 124 | /// The signature is not valid for the given key and message. 125 | BadSignature, 126 | } 127 | impl fmt::Display for VerifyError { 128 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 129 | write!( 130 | f, 131 | "failed to verify signature: {}", 132 | match self { 133 | Self::UnknownKey => "public key is not a member of the group", 134 | Self::BadSignature => "signature is invalid", 135 | } 136 | ) 137 | } 138 | } 139 | impl Error for VerifyError {} 140 | 141 | impl From for SigningError { 142 | fn from(_: VerifyError) -> Self { 143 | SigningError::SelfVerifyFail 144 | } 145 | } 146 | 147 | /// Enumerates the causes for why receiving a contribution from a peer 148 | /// might fail. 149 | #[derive(Debug, PartialEq, Eq, Clone)] 150 | pub enum ContributionFaultReason { 151 | /// The signer's index is out of range for the given 152 | /// number of signers in the group. Embeds `n_signers` 153 | /// (the number of signers). 154 | OutOfRange(usize), 155 | 156 | /// Indicates we received different contribution values from 157 | /// this peer for the same round. If we receive the same 158 | /// nonce or signature from this peer more than once this is 159 | /// acceptable and treated as a no-op, but receiving inconsistent 160 | /// contributions from the same signer may indicate there is 161 | /// malicious behavior occurring. 162 | InconsistentContribution, 163 | 164 | /// Indicates we received an invalid partial signature. Only returned by 165 | /// [`SecondRound::receive_signature`][crate::SecondRound::receive_signature]. 166 | InvalidSignature, 167 | } 168 | 169 | /// This error is returned by when a peer provides an invalid contribution 170 | /// to one of the signing rounds. 171 | /// 172 | /// This is either because the signer's index exceeds the maximum, or 173 | /// because we received an invalid contribution from this signer. 174 | #[derive(Debug, PartialEq, Eq, Clone)] 175 | pub struct RoundContributionError { 176 | /// The erroneous signer index. 177 | pub index: usize, 178 | 179 | /// The reason why the signer's contribution was rejected. 180 | pub reason: ContributionFaultReason, 181 | } 182 | 183 | impl RoundContributionError { 184 | /// Create a new out of range signer index error. 185 | pub fn out_of_range(index: usize, n_signers: usize) -> RoundContributionError { 186 | RoundContributionError { 187 | index, 188 | reason: ContributionFaultReason::OutOfRange(n_signers), 189 | } 190 | } 191 | 192 | /// Create an error caused by an inconsistent contribution. 193 | pub fn inconsistent_contribution(index: usize) -> RoundContributionError { 194 | RoundContributionError { 195 | index, 196 | reason: ContributionFaultReason::InconsistentContribution, 197 | } 198 | } 199 | 200 | /// Create a new error caused by an invalid partial signature. 201 | pub fn invalid_signature(index: usize) -> RoundContributionError { 202 | RoundContributionError { 203 | index, 204 | reason: ContributionFaultReason::InvalidSignature, 205 | } 206 | } 207 | } 208 | 209 | impl fmt::Display for RoundContributionError { 210 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 211 | use ContributionFaultReason::*; 212 | write!( 213 | f, 214 | "invalid signer index {}: {}", 215 | self.index, 216 | match self.reason { 217 | OutOfRange(n_signers) => format!("exceeds max index for {} signers", n_signers), 218 | InconsistentContribution => 219 | "received inconsistent contributions from same signer".to_string(), 220 | InvalidSignature => "received invalid partial signature from peer".to_string(), 221 | } 222 | ) 223 | } 224 | } 225 | 226 | impl Error for RoundContributionError {} 227 | 228 | /// Returned when finalizing [`FirstRound`][crate::FirstRound] or 229 | /// [`SecondRound`][crate::SecondRound] fails. 230 | #[derive(Debug, PartialEq, Eq, Clone)] 231 | pub enum RoundFinalizeError { 232 | /// Contributions from all signers in the group are required to finalize 233 | /// a signing round. This error is returned if attempting to finalize 234 | /// a round before all contributions are received. 235 | Incomplete, 236 | 237 | /// Indicates partial signing failed unexpectedly. This is likely because 238 | /// the wrong secret key was provided. Only returned by 239 | /// [`FirstRound::finalize`][crate::FirstRound::finalize]. 240 | SigningError(SigningError), 241 | 242 | /// Indicates the final aggregated signature is invalid. Only returned by 243 | /// [`SecondRound::finalize`][crate::SecondRound::finalize]. 244 | InvalidAggregatedSignature(VerifyError), 245 | } 246 | 247 | impl fmt::Display for RoundFinalizeError { 248 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 249 | write!( 250 | f, 251 | "cannot finalize round: {}", 252 | match self { 253 | Self::Incomplete => "not all signers have contributed".to_string(), 254 | Self::SigningError(e) => format!("signing failed, {}", e), 255 | Self::InvalidAggregatedSignature(e) => 256 | format!("could not verify aggregated signature: {}", e), 257 | } 258 | ) 259 | } 260 | } 261 | 262 | impl Error for RoundFinalizeError {} 263 | 264 | impl From for RoundFinalizeError { 265 | fn from(e: SigningError) -> Self { 266 | RoundFinalizeError::SigningError(e) 267 | } 268 | } 269 | 270 | impl From for RoundFinalizeError { 271 | fn from(e: VerifyError) -> Self { 272 | RoundFinalizeError::InvalidAggregatedSignature(e) 273 | } 274 | } 275 | 276 | /// Enumerates the various reasons why binary or hex decoding 277 | /// could fail. 278 | #[derive(Debug, PartialEq, Eq, Clone)] 279 | pub enum DecodeFailureReason { 280 | /// The hex string's format was incorrect, which could mean 281 | /// it either was the wrong length or held invalid characters. 282 | BadHexFormat(base16ct::Error), 283 | 284 | /// The byte slice we tried to deserialize had the wrong length. 285 | BadLength(usize), 286 | 287 | /// The bytes contained coordinates to a point that is not on 288 | /// the secp256k1 curve. 289 | InvalidPoint, 290 | 291 | /// The bytes slice contained a representation of a scalar which 292 | /// is outside the required finite field's range. 293 | InvalidScalar, 294 | 295 | /// Custom error reason. 296 | Custom(String), 297 | } 298 | 299 | /// Returned when decoding a certain data structure of type `T` fails. 300 | /// 301 | /// The type `T` only serves as a compile-time safety check; no 302 | /// data of type `T` is actually owned by this error. 303 | #[derive(Debug, PartialEq, Eq, Clone)] 304 | pub struct DecodeError { 305 | /// The reason for the decoding failure. 306 | pub reason: DecodeFailureReason, 307 | phantom: std::marker::PhantomData, 308 | } 309 | 310 | impl DecodeError { 311 | /// Construct a new decoding error for type `T` given a cause 312 | /// for the failure. 313 | pub fn new(reason: DecodeFailureReason) -> Self { 314 | DecodeError { 315 | reason, 316 | phantom: std::marker::PhantomData, 317 | } 318 | } 319 | 320 | /// Create a decoding error caused by an incorrect input byte 321 | /// slice length. 322 | pub fn bad_length(size: usize) -> Self { 323 | let reason = DecodeFailureReason::BadLength(size); 324 | DecodeError::new(reason) 325 | } 326 | 327 | /// Create a custom decoding failure. 328 | pub fn custom(s: impl fmt::Display) -> Self { 329 | let reason = DecodeFailureReason::Custom(s.to_string()); 330 | DecodeError::new(reason) 331 | } 332 | 333 | /// Converts the decoding error for one type into that of another type. 334 | pub fn convert(self) -> DecodeError { 335 | DecodeError::new(self.reason) 336 | } 337 | } 338 | 339 | impl fmt::Display for DecodeError { 340 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 341 | use DecodeFailureReason::*; 342 | 343 | write!( 344 | f, 345 | "error decoding {}: {}", 346 | std::any::type_name::(), 347 | match &self.reason { 348 | BadHexFormat(e) => format!("hex decoding error: {}", e), 349 | BadLength(size) => format!("unexpected length {}", size), 350 | InvalidPoint => secp::errors::InvalidPointBytes.to_string(), 351 | InvalidScalar => secp::errors::InvalidScalarBytes.to_string(), 352 | Custom(s) => s.to_string(), 353 | } 354 | ) 355 | } 356 | } 357 | 358 | impl std::error::Error for DecodeError {} 359 | 360 | impl From for DecodeError { 361 | fn from(_: secp::errors::InvalidPointBytes) -> Self { 362 | DecodeError::new(DecodeFailureReason::InvalidPoint) 363 | } 364 | } 365 | 366 | impl From for DecodeError { 367 | fn from(_: secp::errors::InvalidScalarBytes) -> Self { 368 | DecodeError::new(DecodeFailureReason::InvalidScalar) 369 | } 370 | } 371 | 372 | impl From for DecodeError { 373 | fn from(e: base16ct::Error) -> Self { 374 | DecodeError::new(DecodeFailureReason::BadHexFormat(e)) 375 | } 376 | } 377 | 378 | impl From for DecodeError { 379 | fn from(e: KeyAggError) -> Self { 380 | DecodeError::custom(e) 381 | } 382 | } 383 | 384 | impl From for DecodeError { 385 | fn from(_: TweakError) -> Self { 386 | DecodeError::custom("serialized KeyAggContext contains an invalid tweak") 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/signature.rs: -------------------------------------------------------------------------------- 1 | use secp::{MaybePoint, MaybeScalar, Point, Scalar, G}; 2 | 3 | use crate::errors::DecodeError; 4 | use crate::BinaryEncoding; 5 | 6 | /// The number of bytes in a binary-serialized Schnorr signature. 7 | pub const SCHNORR_SIGNATURE_SIZE: usize = 64; 8 | 9 | /// Represents a compacted Schnorr signature, either 10 | /// from an aggregated signing session or a single signer. 11 | /// 12 | /// It differs from [`LiftedSignature`] in that a `CompactSignature` 13 | /// contains the X-only serialized coordinate of the signature's nonce 14 | /// point `R`, whereas a [`LiftedSignature`] contains the parsed curve 15 | /// point `R`. 16 | /// 17 | /// Parsing a curve point from a byte array requires some computations which 18 | /// can be optimized away during verification. This is why `CompactSignature` 19 | /// is its own separate type. 20 | /// 21 | /// Rules for when to use each signature type during verification: 22 | /// 23 | /// - Prefer using [`CompactSignature`] when parsing and verifying single 24 | /// signatures. That will produce faster results as you won't need to 25 | /// lift the X-only coordinate of the nonce-point to verify the signature. 26 | /// - Prefer using [`LiftedSignature`] when using batch verification, 27 | /// because lifted signatures are required for batch verification 28 | /// so you might as well keep the signatures in lifted form. 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 30 | pub struct CompactSignature { 31 | /// The X-only byte representation of the public nonce point `R`. 32 | pub rx: [u8; 32], 33 | 34 | /// The signature scalar which proves knowledge of the secret key and nonce. 35 | pub s: MaybeScalar, 36 | } 37 | 38 | impl CompactSignature { 39 | /// Constructs a `CompactSignature` from a signature pair `(R, s)`. 40 | pub fn new(R: impl Into, s: impl Into) -> CompactSignature { 41 | CompactSignature { 42 | rx: R.into().serialize_xonly(), 43 | s: s.into(), 44 | } 45 | } 46 | 47 | /// Lifts the nonce point X coordinate to a proper point with even parity, 48 | /// returning an error if the coordinate was not on the curve. 49 | pub fn lift_nonce(&self) -> Result { 50 | let R = Point::lift_x(self.rx)?; 51 | Ok(LiftedSignature { R, s: self.s }) 52 | } 53 | } 54 | 55 | /// A representation of a Schnorr signature point+scalar pair `(R, s)`. 56 | /// 57 | /// Differs from [`CompactSignature`] in that a `LiftedSignature` 58 | /// contains the full nonce point `R`, which is parsed as a valid 59 | /// curve point. 60 | /// 61 | /// Rules for when to use each signature type during verification: 62 | /// 63 | /// - Prefer using [`CompactSignature`] when parsing and verifying single 64 | /// signatures. That will produce faster results as you won't need to 65 | /// lift the X-only coordinate of the nonce-point to verify the signature. 66 | /// - Prefer using [`LiftedSignature`] when using batch verification, 67 | /// because lifted signatures are required for batch verification 68 | /// so you might as well keep the signatures in lifted form. 69 | /// 70 | /// A `LiftedSignature` has the exact sime binary serialization 71 | /// format as a [`CompactSignature`], because the Y-coordinate 72 | /// of the nonce point is implicit - It is always assumed to be 73 | /// the even-parity point. 74 | /// 75 | /// To construct a `LiftedSignature`, use [`LiftedSignature::new`] 76 | /// to ensure the Y-coordinate of the nonce point is always converted 77 | /// to even-parity. 78 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 79 | pub struct LiftedSignature { 80 | pub(crate) R: Point, 81 | pub(crate) s: MaybeScalar, 82 | } 83 | 84 | impl LiftedSignature { 85 | /// Constructs a new lifted signature by converting the nonce point `R` 86 | /// to even parity. 87 | /// 88 | /// Accepts any types which convert to a [`secp::Point`] and 89 | /// [`secp::MaybeScalar`]. 90 | pub fn new(R: impl Into, s: impl Into) -> LiftedSignature { 91 | LiftedSignature { 92 | R: R.into().to_even_y(), 93 | s: s.into(), 94 | } 95 | } 96 | 97 | /// Compact the finalized signature by serializing the 98 | /// nonce point as an X-coordinate-only byte array. 99 | pub fn compact(&self) -> CompactSignature { 100 | CompactSignature::new(self.R, self.s) 101 | } 102 | 103 | /// Encrypts an existing valid signature by subtracting a given adaptor secret. 104 | pub fn encrypt(&self, adaptor_secret: impl Into) -> AdaptorSignature { 105 | AdaptorSignature::new(self.R, self.s).encrypt(adaptor_secret) 106 | } 107 | 108 | /// Unzip this signature pair into a tuple of any two types 109 | /// which convert from [`secp::Point`] and [`secp::MaybeScalar`]. 110 | /// 111 | /// ``` 112 | /// // This allows us to use `R` as a variable name. 113 | /// #![allow(non_snake_case)] 114 | /// 115 | /// let signature = "c1de0db357c5d780c69624d0ab266a3b6866301adc85b66cc9fce26d2a60b72c\ 116 | /// 659c15ed9bc81df681e1e0607cf44cc08e77396f74359de1e6e6ff365ca94dae" 117 | /// .parse::() 118 | /// .unwrap(); 119 | /// 120 | /// let (R, s): ([u8; 33], [u8; 32]) = signature.unzip(); 121 | /// let (R, s): (secp::Point, secp::MaybeScalar) = signature.unzip(); 122 | /// # #[cfg(feature = "k256")] 123 | /// # { 124 | /// let (R, s): (k256::PublicKey, k256::Scalar) = signature.unzip(); 125 | /// # } 126 | /// # #[cfg(feature = "secp256k1")] 127 | /// # { 128 | /// let (R, s): (secp256k1::PublicKey, secp::MaybeScalar) = signature.unzip(); 129 | /// # } 130 | /// ``` 131 | pub fn unzip(&self) -> (P, S) 132 | where 133 | P: From, 134 | S: From, 135 | { 136 | (P::from(self.R), S::from(self.s)) 137 | } 138 | } 139 | 140 | /// A representation of a Schnorr adaptor signature point+scalar pair `(R', s')`. 141 | /// 142 | /// Differs from [`LiftedSignature`] in that an `AdaptorSignature` is explicitly 143 | /// modified with by specific scalar offset called the _adaptor secret,_ so that 144 | /// only by learning the adaptor secret can its holder convert it 145 | /// into a valid BIP340 signature. 146 | /// 147 | /// Since `AdaptorSignature` is not meant for on-chain consensus, the nonce 148 | /// point `R` can have either even or odd parity, and so `AdaptorSignature` 149 | /// is encoded as a 65 byte array which includes the compressed `R` point. 150 | /// 151 | /// To learn more about adaptor signatures and how to use them, see the docs 152 | /// in [the adaptor module][crate::adaptor]. 153 | /// 154 | /// To construct an `AdaptorSignature`, use [`LiftedSignature::encrypt`], 155 | /// [`adaptor::sign_solo`][crate::adaptor::sign_solo], or 156 | /// [`adaptor::aggregate_partial_signatures`][crate::adaptor::aggregate_partial_signatures]. 157 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 158 | pub struct AdaptorSignature { 159 | pub(crate) R: MaybePoint, 160 | pub(crate) s: MaybeScalar, 161 | } 162 | 163 | impl AdaptorSignature { 164 | /// Constructs a new adaptor signature from a nonce and scalar pair. 165 | /// 166 | /// Accepts any types which convert to a [`secp::MaybePoint`] and 167 | /// [`secp::MaybeScalar`]. 168 | pub fn new(R: impl Into, s: impl Into) -> AdaptorSignature { 169 | AdaptorSignature { 170 | R: R.into(), 171 | s: s.into(), 172 | } 173 | } 174 | 175 | /// Adapts the signature into a lifted signature with a given adaptor secret. 176 | /// 177 | /// Returns `None` if the nonce resulting from adding the adaptor point is the 178 | /// point at infinity. 179 | /// 180 | /// The resulting signature is not guaranteed to be valid unless the 181 | ///`AdaptorSignature` was already verified with 182 | /// [`adaptor::verify_single`][crate::adaptor::verify_single]. 183 | /// If not, make sure to verify the resulting lifted signature 184 | /// using [`verify_single`][crate::verify_single]. 185 | pub fn adapt>( 186 | &self, 187 | adaptor_secret: impl Into, 188 | ) -> Option { 189 | let adaptor_secret: MaybeScalar = adaptor_secret.into(); 190 | let adapted_nonce = (self.R + adaptor_secret * G).into_option()?; 191 | let adapted_sig = self.s + adaptor_secret.negate_if(adapted_nonce.parity()); 192 | Some(T::from(LiftedSignature::new(adapted_nonce, adapted_sig))) 193 | } 194 | 195 | /// Encrypts an existing adaptor signature again, by subtracting another adaptor secret. 196 | pub fn encrypt(&self, adaptor_secret: impl Into) -> AdaptorSignature { 197 | let adaptor_secret: Scalar = adaptor_secret.into(); 198 | AdaptorSignature { 199 | R: self.R - adaptor_secret * G, 200 | s: self.s - adaptor_secret, 201 | } 202 | } 203 | 204 | /// Using a decrypted signature `final_sig`, this method computes the 205 | /// adaptor secret used to encrypt this signature. 206 | /// 207 | /// Returns `None` if `final_sig` is not related to this adaptor signature. 208 | pub fn reveal_secret(&self, final_sig: &LiftedSignature) -> Option 209 | where 210 | S: From, 211 | { 212 | let t = final_sig.s - self.s; 213 | let T = t * G; 214 | 215 | if T == final_sig.R - self.R { 216 | Some(S::from(t)) 217 | } else if T == final_sig.R + self.R { 218 | Some(S::from(-t)) 219 | } else { 220 | None 221 | } 222 | } 223 | 224 | /// Unzip this signature pair into a tuple of any two types 225 | /// which convert from [`secp::MaybePoint`] and [`secp::MaybeScalar`]. 226 | /// 227 | /// ``` 228 | /// // This allows us to use `R` as a variable name. 229 | /// #![allow(non_snake_case)] 230 | /// 231 | /// let signature = "02c1de0db357c5d780c69624d0ab266a3b6866301adc85b66cc9fce26d2a60b72c\ 232 | /// 659c15ed9bc81df681e1e0607cf44cc08e77396f74359de1e6e6ff365ca94dae" 233 | /// .parse::() 234 | /// .unwrap(); 235 | /// 236 | /// let (R, s): ([u8; 33], [u8; 32]) = signature.unzip(); 237 | /// let (R, s): (secp::MaybePoint, secp::MaybeScalar) = signature.unzip(); 238 | /// # #[cfg(feature = "k256")] 239 | /// # { 240 | /// let (R, s): (k256::AffinePoint, k256::Scalar) = signature.unzip(); 241 | /// # } 242 | /// ``` 243 | pub fn unzip(&self) -> (P, S) 244 | where 245 | P: From, 246 | S: From, 247 | { 248 | (P::from(self.R), S::from(self.s)) 249 | } 250 | } 251 | 252 | mod encodings { 253 | use super::*; 254 | 255 | impl BinaryEncoding for CompactSignature { 256 | type Serialized = [u8; SCHNORR_SIGNATURE_SIZE]; 257 | 258 | /// Serializes the signature to a compact 64-byte encoding, 259 | /// including the X coordinate of the `R` point and the 260 | /// serialized `s` scalar. 261 | fn to_bytes(&self) -> Self::Serialized { 262 | let mut serialized = [0u8; SCHNORR_SIGNATURE_SIZE]; 263 | serialized[..SCHNORR_SIGNATURE_SIZE / 2].clone_from_slice(&self.rx); 264 | serialized[SCHNORR_SIGNATURE_SIZE / 2..].clone_from_slice(&self.s.serialize()); 265 | serialized 266 | } 267 | 268 | /// Deserialize a compact Schnorr signature from a byte slice. This 269 | /// slice must be exactly [`SCHNORR_SIGNATURE_SIZE`] bytes long. 270 | fn from_bytes(signature_bytes: &[u8]) -> Result> { 271 | if signature_bytes.len() != SCHNORR_SIGNATURE_SIZE { 272 | return Err(DecodeError::bad_length(signature_bytes.len())); 273 | } 274 | let rx = <[u8; 32]>::try_from(&signature_bytes[..32]).unwrap(); 275 | let s = MaybeScalar::try_from(&signature_bytes[32..])?; 276 | Ok(CompactSignature { rx, s }) 277 | } 278 | } 279 | 280 | impl BinaryEncoding for LiftedSignature { 281 | type Serialized = [u8; SCHNORR_SIGNATURE_SIZE]; 282 | 283 | /// Serializes the signature to a compact 64-byte encoding, 284 | /// including the X coordinate of the `R` point and the 285 | /// serialized `s` scalar. 286 | fn to_bytes(&self) -> Self::Serialized { 287 | CompactSignature::from(*self).to_bytes() 288 | } 289 | 290 | /// Deserialize a compact Schnorr signature from a byte slice. This 291 | /// slice must be exactly [`SCHNORR_SIGNATURE_SIZE`] bytes long. 292 | fn from_bytes(bytes: &[u8]) -> Result> { 293 | let compact_signature = CompactSignature::from_bytes(bytes).map_err(|e| e.convert())?; 294 | Ok(compact_signature.lift_nonce()?) 295 | } 296 | } 297 | 298 | impl BinaryEncoding for AdaptorSignature { 299 | type Serialized = [u8; 65]; 300 | 301 | /// Serializes the signature to a compressed 65-byte encoding, 302 | /// including the compressed `R` point and the serialized `s` scalar. 303 | fn to_bytes(&self) -> Self::Serialized { 304 | let mut serialized = [0u8; 65]; 305 | serialized[..33].clone_from_slice(&self.R.serialize()); 306 | serialized[33..].clone_from_slice(&self.s.serialize()); 307 | serialized 308 | } 309 | 310 | /// Deserialize an adaptor signature from a byte slice. This 311 | /// slice must be exactly 65 bytes long. 312 | fn from_bytes(signature_bytes: &[u8]) -> Result> { 313 | if signature_bytes.len() != 65 { 314 | return Err(DecodeError::bad_length(signature_bytes.len())); 315 | } 316 | let R = MaybePoint::try_from(&signature_bytes[..33])?; 317 | let s = MaybeScalar::try_from(&signature_bytes[33..])?; 318 | Ok(AdaptorSignature { R, s }) 319 | } 320 | } 321 | 322 | impl_encoding_traits!(CompactSignature, SCHNORR_SIGNATURE_SIZE); 323 | impl_encoding_traits!(LiftedSignature, SCHNORR_SIGNATURE_SIZE); 324 | impl_encoding_traits!(AdaptorSignature, 65); 325 | 326 | impl_hex_display!(CompactSignature); 327 | impl_hex_display!(LiftedSignature); 328 | impl_hex_display!(AdaptorSignature); 329 | } 330 | 331 | mod internal_conversions { 332 | use super::*; 333 | 334 | impl TryFrom for LiftedSignature { 335 | type Error = secp::errors::InvalidPointBytes; 336 | 337 | /// Convert the compact signature into an `(R, s)` pair by lifting 338 | /// the nonce point's X-coordinate representation. Fails if the 339 | /// X-coordinate bytes do not represent a valid curve point. 340 | fn try_from(signature: CompactSignature) -> Result { 341 | signature.lift_nonce() 342 | } 343 | } 344 | 345 | impl From for CompactSignature { 346 | /// Converts a pair `(R, s)` into a schnorr signature struct. 347 | fn from(signature: LiftedSignature) -> Self { 348 | signature.compact() 349 | } 350 | } 351 | } 352 | 353 | #[cfg(feature = "secp256k1")] 354 | mod secp256k1_conversions { 355 | use super::*; 356 | 357 | impl TryFrom for CompactSignature { 358 | type Error = DecodeError; 359 | fn try_from(signature: secp256k1::schnorr::Signature) -> Result { 360 | Self::try_from(signature.to_byte_array()) 361 | } 362 | } 363 | 364 | impl TryFrom for LiftedSignature { 365 | type Error = DecodeError; 366 | fn try_from(signature: secp256k1::schnorr::Signature) -> Result { 367 | Self::try_from(signature.to_byte_array()) 368 | } 369 | } 370 | 371 | impl From for secp256k1::schnorr::Signature { 372 | fn from(signature: CompactSignature) -> Self { 373 | Self::from_byte_array(signature.to_bytes()) 374 | } 375 | } 376 | 377 | impl From for secp256k1::schnorr::Signature { 378 | fn from(signature: LiftedSignature) -> Self { 379 | Self::from_byte_array(signature.to_bytes()) 380 | } 381 | } 382 | } 383 | 384 | #[cfg(feature = "k256")] 385 | mod k256_conversions { 386 | use super::*; 387 | 388 | impl From<(k256::PublicKey, k256::Scalar)> for CompactSignature { 389 | fn from((R, s): (k256::PublicKey, k256::Scalar)) -> Self { 390 | CompactSignature::new(R, s) 391 | } 392 | } 393 | 394 | impl From<(k256::PublicKey, k256::Scalar)> for LiftedSignature { 395 | fn from((R, s): (k256::PublicKey, k256::Scalar)) -> Self { 396 | LiftedSignature::new(R, s) 397 | } 398 | } 399 | 400 | impl TryFrom for (k256::PublicKey, k256::Scalar) { 401 | type Error = secp::errors::InvalidPointBytes; 402 | fn try_from(signature: CompactSignature) -> Result { 403 | Ok(signature.lift_nonce()?.unzip()) 404 | } 405 | } 406 | 407 | impl From for (k256::PublicKey, k256::Scalar) { 408 | fn from(signature: LiftedSignature) -> Self { 409 | signature.unzip() 410 | } 411 | } 412 | 413 | impl From for (k256::AffinePoint, k256::Scalar) { 414 | fn from(signature: LiftedSignature) -> Self { 415 | signature.unzip() 416 | } 417 | } 418 | 419 | #[cfg(feature = "k256")] 420 | impl From for k256::WideBytes { 421 | fn from(signature: CompactSignature) -> Self { 422 | <[u8; SCHNORR_SIGNATURE_SIZE]>::from(signature).into() 423 | } 424 | } 425 | 426 | #[cfg(feature = "k256")] 427 | impl From for k256::WideBytes { 428 | fn from(signature: LiftedSignature) -> Self { 429 | <[u8; SCHNORR_SIGNATURE_SIZE]>::from(signature).into() 430 | } 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /src/bip340.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::VerifyError; 2 | use crate::{ 3 | compute_challenge_hash_tweak, tagged_hashes, xor_bytes, AdaptorSignature, CompactSignature, 4 | LiftedSignature, NonceSeed, 5 | }; 6 | 7 | use secp::{MaybePoint, MaybeScalar, Point, Scalar, G}; 8 | 9 | #[cfg(any(test, feature = "rand"))] 10 | use rand::SeedableRng as _; 11 | 12 | use sha2::Digest as _; 13 | use subtle::ConstantTimeEq as _; 14 | 15 | /// Create a [BIP340-compatible](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) 16 | /// Schnorr adaptor signature on the given message with a single private key. 17 | /// 18 | /// The resulting signature is verifiably encrypted under the given `adaptor_point`, 19 | /// such that it can only be considered valid under BIP340 if it is then 20 | /// _adapted_ using the discrete log of `adaptor_point`. See 21 | /// [`AdaptorSignature::adapt`] to decrypt it once you know the adaptor secret. 22 | /// 23 | /// You can also compute the adaptor secret from the final decrypted signature, 24 | /// if you can find it. 25 | /// 26 | /// This is provided in case MuSig implementations may wish to make use of 27 | /// signatures to non-interactively prove the origin of a message. For example, 28 | /// if all messages between co-signers are signed, then peers can assign blame 29 | /// to any dishonest signers by sharing a copy of their dishonest message, which 30 | /// will bear their signature. 31 | pub fn sign_solo_adaptor( 32 | seckey: impl Into, 33 | message: impl AsRef<[u8]>, 34 | nonce_seed: impl Into, 35 | adaptor_point: impl Into, 36 | ) -> AdaptorSignature { 37 | let seckey: Scalar = seckey.into(); 38 | let nonce_seed: NonceSeed = nonce_seed.into(); 39 | 40 | let pubkey = seckey.base_point_mul(); 41 | let d = seckey.negate_if(pubkey.parity()); 42 | 43 | let h: [u8; 32] = tagged_hashes::BIP0340_AUX_TAG_HASHER 44 | .clone() 45 | .chain_update(nonce_seed.0) 46 | .finalize() 47 | .into(); 48 | 49 | let t = xor_bytes(&h, &d.serialize()); 50 | 51 | let rand: [u8; 32] = tagged_hashes::BIP0340_NONCE_TAG_HASHER 52 | .clone() 53 | .chain_update(t) 54 | .chain_update(pubkey.serialize_xonly()) 55 | .chain_update(message.as_ref()) 56 | .finalize() 57 | .into(); 58 | 59 | // BIP340 says to fail if we get a nonce reducing to zero, but this is so 60 | // unlikely that the failure condition is not worth it. Default to 1 instead. 61 | let prenonce = match MaybeScalar::reduce_from(&rand) { 62 | MaybeScalar::Zero => Scalar::one(), 63 | MaybeScalar::Valid(k) => k, 64 | }; 65 | 66 | let R = prenonce * G; // encrypted nonce 67 | let adapted_nonce = R + adaptor_point.into(); 68 | 69 | // If the adapted nonce is odd-parity, we must negate our nonce and 70 | // later also negate the adaptor secret at decryption time. 71 | let k = prenonce.negate_if(adapted_nonce.parity()); 72 | 73 | let nonce_x_bytes = adapted_nonce.serialize_xonly(); 74 | let e: MaybeScalar = compute_challenge_hash_tweak(&nonce_x_bytes, &pubkey, message); 75 | 76 | let s = k + e * d; 77 | 78 | AdaptorSignature::new(R, s) 79 | } 80 | 81 | /// Create a [BIP340-compatible](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) 82 | /// Schnorr signature on the given message with a single private key. 83 | /// 84 | /// This is provided in case MuSig implementations may wish to make use of 85 | /// signatures to non-interactively prove the origin of a message. For example, 86 | /// if all messages between co-signers are signed, then peers can assign blame 87 | /// to any dishonest signers by sharing a copy of their dishonest message, which 88 | /// will bear their signature. 89 | /// 90 | /// This function is effectively the same as [`sign_solo_adaptor`] but passing 91 | /// [`MaybePoint::Infinity`] as the adaptor point. 92 | pub fn sign_solo( 93 | seckey: impl Into, 94 | message: impl AsRef<[u8]>, 95 | nonce_seed: impl Into, 96 | ) -> T 97 | where 98 | T: From, 99 | { 100 | sign_solo_adaptor(seckey, message, nonce_seed, MaybePoint::Infinity) 101 | .adapt(MaybeScalar::Zero) 102 | .map(T::from) 103 | .expect("signing with empty adaptor should never result in an adaptor failure") 104 | } 105 | 106 | /// Verifies a [BIP340-compatible](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) 107 | /// Schnorr adaptor signature, which could be aggregated or from a single-signer. 108 | /// 109 | /// The signature will verify only if it is encrypted under the given adaptor point. 110 | /// 111 | /// The `signature` argument is parsed as a [`LiftedSignature`]. You may pass any 112 | /// type which converts fallibly to a [`LiftedSignature`], including `&[u8]`, `[u8; 64]`, 113 | /// [`CompactSignature`], and so on. 114 | /// 115 | /// Returns an error if the adaptor signature is invalid, which includes 116 | /// if the signature has been decrypted and is a fully valid signature. 117 | pub fn verify_single_adaptor( 118 | pubkey: impl Into, 119 | adaptor_signature: &AdaptorSignature, 120 | message: impl AsRef<[u8]>, 121 | adaptor_point: impl Into, 122 | ) -> Result<(), VerifyError> { 123 | use VerifyError::BadSignature; 124 | 125 | let pubkey: Point = pubkey.into().to_even_y(); // lift_x(x(P)) 126 | 127 | let &AdaptorSignature { R, s } = adaptor_signature; 128 | 129 | let adapted_nonce = R + adaptor_point.into(); 130 | let nonce_x_bytes = adapted_nonce.serialize_xonly(); 131 | let e: MaybeScalar = compute_challenge_hash_tweak(&nonce_x_bytes, &pubkey, message); 132 | 133 | // If the adapted nonce is odd-parity, the signer should have negated their nonce 134 | // when signing. 135 | let effective_nonce = if adapted_nonce.has_even_y() { R } else { -R }; 136 | 137 | // sG = R + eD 138 | if s * G != effective_nonce + e * pubkey { 139 | return Err(BadSignature); 140 | } 141 | 142 | Ok(()) 143 | } 144 | 145 | /// Verifies a [BIP340-compatible](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) 146 | /// Schnorr signature, which could be aggregated or from a single-signer. 147 | /// 148 | /// The `signature` argument is parsed as a [`CompactSignature`]. You may pass any 149 | /// type which converts fallibly to a [`CompactSignature`], including `&[u8]`, `[u8; 64]`, 150 | /// [`LiftedSignature`], and so on. 151 | /// 152 | /// Returns an error if the signature is invalid. 153 | pub fn verify_single( 154 | pubkey: impl Into, 155 | signature: impl TryInto, 156 | message: impl AsRef<[u8]>, 157 | ) -> Result<(), VerifyError> { 158 | use VerifyError::BadSignature; 159 | 160 | let pubkey: Point = pubkey.into().to_even_y(); // lift_x(x(P)) 161 | let CompactSignature { rx, s } = signature.try_into().map_err(|_| BadSignature)?; 162 | let e: MaybeScalar = compute_challenge_hash_tweak(&rx, &pubkey, message); 163 | 164 | // Instead of the usual sG = R + eD schnorr equation, we swap things around 165 | // slightly, thus avoiding the need to lift the x-only nonce. 166 | // 167 | // sG = R + eD 168 | // R = sG - eD 169 | let verification_point = (s * G - e * pubkey).not_inf().map_err(|_| BadSignature)?; 170 | if verification_point.has_odd_y() { 171 | return Err(BadSignature); 172 | } 173 | 174 | let valid = verification_point.serialize_xonly().ct_eq(&rx); 175 | if bool::from(valid) { 176 | Ok(()) 177 | } else { 178 | Err(BadSignature) 179 | } 180 | } 181 | 182 | /// Represents a pre-processed entry in a batch of signatures to be verified. 183 | /// This can encapsulate either a normal BIP340 signature, or an adaptor signature. 184 | /// 185 | /// To verify a large number of signatures efficiently, pass a slice of 186 | /// [`BatchVerificationRow`] to [`verify_batch`]. 187 | #[cfg(any(test, feature = "rand"))] 188 | #[derive(Clone, Debug, PartialEq, Eq)] 189 | pub struct BatchVerificationRow { 190 | pubkey: Point, 191 | challenge: MaybeScalar, 192 | R: MaybePoint, 193 | s: MaybeScalar, 194 | } 195 | 196 | #[cfg(any(test, feature = "rand"))] 197 | impl BatchVerificationRow { 198 | /// Construct a row in a batch verification table from a given BIP340 signature. 199 | pub fn from_signature>( 200 | pubkey: impl Into, 201 | message: M, 202 | signature: LiftedSignature, 203 | ) -> Self { 204 | let pubkey = pubkey.into(); 205 | let challenge = 206 | compute_challenge_hash_tweak(&signature.R.serialize_xonly(), &pubkey, message.as_ref()); 207 | 208 | BatchVerificationRow { 209 | pubkey, 210 | challenge, 211 | R: MaybePoint::Valid(signature.R), 212 | s: signature.s, 213 | } 214 | } 215 | 216 | /// Construct a row in a batch verification table from a given BIP340 adaptor signature. 217 | pub fn from_adaptor_signature>( 218 | pubkey: impl Into, 219 | message: M, 220 | adaptor_signature: AdaptorSignature, 221 | adaptor_point: MaybePoint, 222 | ) -> Self { 223 | let pubkey = pubkey.into(); 224 | let adapted_nonce = adaptor_signature.R + adaptor_point; 225 | 226 | // If the adapted nonce is odd-parity, the signer should have negated their nonce 227 | // when signing. 228 | let effective_nonce = if adapted_nonce.has_even_y() { 229 | adaptor_signature.R 230 | } else { 231 | -adaptor_signature.R 232 | }; 233 | 234 | let challenge = compute_challenge_hash_tweak( 235 | &adapted_nonce.serialize_xonly(), 236 | &pubkey, 237 | message.as_ref(), 238 | ); 239 | 240 | BatchVerificationRow { 241 | pubkey, 242 | challenge, 243 | R: effective_nonce, 244 | s: adaptor_signature.s, 245 | } 246 | } 247 | } 248 | 249 | /// Runs [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) 250 | /// batch verification on a collection of schnorr signatures. 251 | /// 252 | /// Batch verification checks a table of pubkeys, messages, and 253 | /// signatures and returns an error if any signatures in the 254 | /// collection are not valid for the corresponding `(pubkey, message)` 255 | /// pair. 256 | /// 257 | /// Batch verification enables noteworthy speedups when verifying 258 | /// large numbers of signatures, but does not give any indication 259 | /// of _which_ signature(s) were invalid upon failure. Manual 260 | /// investigation would be needed to narrow down which signature(s) 261 | /// caused the verification to fail. 262 | /// 263 | /// This requires the `rand` library for access to a seedable CSPRNG. 264 | /// The RNG is seeded with all the pubkeys, messages, and signatures 265 | /// rather than being truly random. 266 | #[cfg(any(test, feature = "rand"))] 267 | pub fn verify_batch(rows: &[BatchVerificationRow]) -> Result<(), VerifyError> { 268 | // Seed the CSPRNG 269 | let mut rng = { 270 | let mut seed_hash = tagged_hashes::BIP0340_BATCH_TAG_HASHER.clone(); 271 | 272 | // Challenges commit to the pubkey, nonce, and message. That's why 273 | // we're not explicitly seeding the RNG with the pubkey, nonce, and message 274 | // as suggested by BIP340. 275 | for row in rows { 276 | seed_hash.update(row.challenge.serialize()); 277 | } 278 | 279 | for row in rows { 280 | seed_hash.update(row.s.serialize()); 281 | } 282 | rand::rngs::StdRng::from_seed(seed_hash.finalize().into()) 283 | }; 284 | 285 | let mut lhs = MaybeScalar::Zero; 286 | let mut rhs_terms = Vec::::with_capacity(rows.len() * 2); 287 | 288 | for (i, row) in rows.iter().enumerate() { 289 | let random = if i == 0 { 290 | Scalar::one() 291 | } else { 292 | Scalar::random(&mut rng) 293 | }; 294 | 295 | let pubkey = row.pubkey.to_even_y(); // lift_x on all pubkeys 296 | 297 | lhs += row.s * random; 298 | rhs_terms.push(row.R * random); 299 | rhs_terms.push((random * row.challenge) * pubkey); 300 | } 301 | 302 | // (s1*a1 + s2*a2 + ... + sn*an)G ?= (a1*R1) + (a2*R2) + ... + (an*Rn) + 303 | // (a1*e1*P1) + (a2*e2*P2) + ... + (an*en*Pn) 304 | let rhs = MaybePoint::sum(rhs_terms); 305 | if lhs * G == rhs { 306 | Ok(()) 307 | } else { 308 | Err(VerifyError::BadSignature) 309 | } 310 | } 311 | 312 | #[cfg(test)] 313 | mod tests { 314 | use super::*; 315 | use crate::{testhex, BinaryEncoding, CompactSignature}; 316 | use secp::Scalar; 317 | 318 | #[test] 319 | fn test_bip340_signatures() { 320 | const BIP340_TEST_VECTORS: &[u8] = include_bytes!("test_vectors/bip340_vectors.csv"); 321 | 322 | #[derive(serde::Deserialize)] 323 | struct TestVectorRecord { 324 | index: usize, 325 | #[serde(rename = "secret key")] 326 | seckey: Option, 327 | #[serde(rename = "public key", deserialize_with = "testhex::deserialize")] 328 | pubkey_x: [u8; 32], 329 | #[serde(deserialize_with = "testhex::deserialize")] 330 | aux_rand: Vec, 331 | #[serde(deserialize_with = "testhex::deserialize")] 332 | message: Vec, 333 | signature: String, 334 | #[serde(rename = "verification result")] 335 | verification_result: String, 336 | comment: String, 337 | } 338 | 339 | let mut csv_reader = csv::Reader::from_reader(BIP340_TEST_VECTORS); 340 | 341 | let mut valid_sigs_batch = Vec::::new(); 342 | 343 | for result in csv_reader.deserialize() { 344 | let record: TestVectorRecord = result.expect("failed to parse BIP340 test vector"); 345 | 346 | let pubkey = match Point::lift_x(record.pubkey_x) { 347 | Ok(p) => p, 348 | Err(_) => { 349 | if record.verification_result == "TRUE" { 350 | panic!( 351 | "expected verification to succeed on invalid public key {}", 352 | base16ct::lower::encode_string(&record.pubkey_x) 353 | ); 354 | } 355 | continue; // not a test case we have to worry about. 356 | } 357 | }; 358 | 359 | let test_vec_signature: [u8; 64] = base16ct::mixed::decode_vec(&record.signature) 360 | .unwrap_or_else(|_| panic!("invalid signature hex: {}", record.signature)) 361 | .try_into() 362 | .expect("invalid signature length"); 363 | 364 | if let Some(seckey) = record.seckey { 365 | let aux_rand = 366 | <[u8; 32]>::try_from(record.aux_rand.as_slice()).unwrap_or_else(|_| { 367 | panic!( 368 | "invalid aux_rand: {}", 369 | base16ct::lower::encode_string(&record.aux_rand) 370 | ) 371 | }); 372 | 373 | let created_signature: CompactSignature = 374 | sign_solo(seckey, &record.message, aux_rand); 375 | 376 | assert_eq!( 377 | created_signature.to_bytes(), 378 | test_vec_signature, 379 | "test vector signature does not match for test vector {}; {}", 380 | record.index, 381 | &record.comment 382 | ); 383 | 384 | // Test adaptor signatures 385 | { 386 | let adaptor_secret = MaybeScalar::Valid(seckey); // arbitrary secret 387 | let adaptor_point = adaptor_secret * G; 388 | let adaptor_signature = 389 | sign_solo_adaptor(seckey, &record.message, aux_rand, adaptor_point); 390 | 391 | verify_single_adaptor( 392 | pubkey, 393 | &adaptor_signature, 394 | &record.message, 395 | adaptor_point, 396 | ) 397 | .expect("failed to verify valid adaptor signature"); 398 | 399 | // Ensure the decrypted signature is valid. 400 | let valid_sig = adaptor_signature.adapt(adaptor_secret).unwrap(); 401 | verify_single(pubkey, valid_sig, &record.message) 402 | .expect("failed to verify decrypted adaptor signature"); 403 | 404 | // Ensure observers can learn the adaptor secret from published signatures. 405 | let revealed: MaybeScalar = adaptor_signature 406 | .reveal_secret(&valid_sig) 407 | .expect("decrypted signature should reveal adaptor secret"); 408 | assert_eq!(revealed, adaptor_secret); 409 | } 410 | } 411 | 412 | let verify_result = verify_single(pubkey, test_vec_signature, &record.message); 413 | match record.verification_result.as_str() { 414 | "TRUE" => { 415 | verify_result.unwrap_or_else(|_| { 416 | panic!( 417 | "verification should pass for signature {} - {}", 418 | &record.signature, record.comment 419 | ) 420 | }); 421 | valid_sigs_batch.push(BatchVerificationRow::from_signature( 422 | pubkey, 423 | record.message, 424 | LiftedSignature::try_from(test_vec_signature).unwrap(), 425 | )); 426 | } 427 | 428 | "FALSE" => { 429 | assert_eq!( 430 | verify_result, 431 | Err(VerifyError::BadSignature), 432 | "verification should fail for signature {} - {}", 433 | &record.signature, 434 | record.comment 435 | ); 436 | } 437 | 438 | s => panic!("unexpected verification result column value: {}", s), 439 | }; 440 | } 441 | 442 | // test batch verification 443 | verify_batch(&valid_sigs_batch).expect("batch verification failed"); 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /src/signing.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{SigningError, VerifyError}; 2 | use crate::{tagged_hashes, AggNonce, KeyAggContext, PubNonce, SecNonce}; 3 | 4 | use secp::{MaybePoint, MaybeScalar, Point, Scalar, G}; 5 | 6 | use sha2::Digest as _; 7 | 8 | /// The size of a serialized partial signature in bytes. 9 | pub const PARTIAL_SIGNATURE_SIZE: usize = 32; 10 | 11 | /// Partial signatures are just scalars in the range `[0, n)`. 12 | /// 13 | /// See the documentation of [`secp::MaybeScalar`] for the 14 | /// parsing, serializing, and conversion traits available 15 | /// on this type. 16 | pub type PartialSignature = MaybeScalar; 17 | 18 | /// Computes the challenge hash `e` for for a signature. You probably don't need 19 | /// to call this directly. Instead use [`sign_solo`][crate::sign_solo] or 20 | /// [`sign_partial`][crate::sign_partial]. 21 | pub fn compute_challenge_hash_tweak>( 22 | final_nonce_xonly: &[u8; 32], 23 | aggregated_pubkey: &Point, 24 | message: impl AsRef<[u8]>, 25 | ) -> S { 26 | let hash: [u8; 32] = tagged_hashes::BIP0340_CHALLENGE_TAG_HASHER 27 | .clone() 28 | .chain_update(final_nonce_xonly) 29 | .chain_update(aggregated_pubkey.serialize_xonly()) 30 | .chain_update(message.as_ref()) 31 | .finalize() 32 | .into(); 33 | 34 | S::from(MaybeScalar::reduce_from(&hash)) 35 | } 36 | 37 | /// Compute a partial signature on a message encrypted under an adaptor point. 38 | /// 39 | /// The partial signature returned from this function is a potentially-zero 40 | /// scalar value which can then be passed to other signers for verification 41 | /// and aggregation. 42 | /// 43 | /// Once aggregated, the signature must be adapted with the discrete log 44 | /// (secret key) of `adaptor_point` for the signature to be considered valid. 45 | /// 46 | /// Returns an error if the given secret key does not belong to this 47 | /// `key_agg_ctx`. As an added safety, we also verify the partial signature 48 | /// before returning it. 49 | pub fn sign_partial_adaptor>( 50 | key_agg_ctx: &KeyAggContext, 51 | seckey: impl Into, 52 | secnonce: SecNonce, 53 | aggregated_nonce: &AggNonce, 54 | adaptor_point: impl Into, 55 | message: impl AsRef<[u8]>, 56 | ) -> Result { 57 | let adaptor_point: MaybePoint = adaptor_point.into(); 58 | let seckey: Scalar = seckey.into(); 59 | let pubkey = seckey.base_point_mul(); 60 | 61 | // As a side-effect, looking up the cached key coefficient also confirms 62 | // the individual key is indeed part of the aggregated key. 63 | let key_coeff = key_agg_ctx 64 | .key_coefficient(pubkey) 65 | .ok_or(SigningError::UnknownKey)?; 66 | 67 | let aggregated_pubkey = key_agg_ctx.pubkey; 68 | let pubnonce = secnonce.public_nonce(); 69 | 70 | let b: MaybeScalar = aggregated_nonce.nonce_coefficient(aggregated_pubkey, &message); 71 | let final_nonce: Point = aggregated_nonce.final_nonce(b); 72 | let adapted_nonce = final_nonce + adaptor_point; 73 | 74 | // `d` is negated if only one of the parity accumulator OR the aggregated pubkey 75 | // has odd parity. 76 | let d = seckey.negate_if(aggregated_pubkey.parity() ^ key_agg_ctx.parity_acc); 77 | 78 | let nonce_x_bytes = adapted_nonce.serialize_xonly(); 79 | let e: MaybeScalar = compute_challenge_hash_tweak(&nonce_x_bytes, &aggregated_pubkey, &message); 80 | 81 | // if has_even_Y(R): 82 | // k = k1 + b*k2 83 | // else: 84 | // k = (n-k1) + b(n-k2) 85 | // = n - (k1 + b*k2) 86 | let secnonce_sum = (secnonce.k1 + b * secnonce.k2).negate_if(adapted_nonce.parity()); 87 | 88 | // s = k + e*a*d 89 | let partial_signature = secnonce_sum + (e * key_coeff * d); 90 | 91 | verify_partial_adaptor( 92 | key_agg_ctx, 93 | partial_signature, 94 | aggregated_nonce, 95 | adaptor_point, 96 | pubkey, 97 | &pubnonce, 98 | &message, 99 | )?; 100 | 101 | Ok(T::from(partial_signature)) 102 | } 103 | 104 | /// Compute a partial signature on a message. 105 | /// 106 | /// The partial signature returned from this function is a potentially-zero 107 | /// scalar value which can then be passed to other signers for verification 108 | /// and aggregation. 109 | /// 110 | /// Returns an error if the given secret key does not belong to this 111 | /// `key_agg_ctx`. As an added safety, we also verify the partial signature 112 | /// before returning it. 113 | /// 114 | /// This is equivalent to invoking [`sign_partial_adaptor`], but passing 115 | /// [`MaybePoint::Infinity`] as the adaptor point. 116 | pub fn sign_partial>( 117 | key_agg_ctx: &KeyAggContext, 118 | seckey: impl Into, 119 | secnonce: SecNonce, 120 | aggregated_nonce: &AggNonce, 121 | message: impl AsRef<[u8]>, 122 | ) -> Result { 123 | sign_partial_adaptor( 124 | key_agg_ctx, 125 | seckey, 126 | secnonce, 127 | aggregated_nonce, 128 | MaybePoint::Infinity, 129 | message, 130 | ) 131 | } 132 | 133 | /// Verify a partial signature, usually from an untrusted co-signer, 134 | /// which has been encrypted under an adaptor point. 135 | /// 136 | /// If `verify_partial_adaptor` succeeds for every signature in 137 | /// a signing session, the resulting aggregated signature is guaranteed 138 | /// to be valid once it is adapted with the discrete log (secret key) 139 | /// of `adaptor_point`. 140 | /// 141 | /// Note that partial signatures are _not_ unforgeable! 142 | /// Validity of a partial signature should not be relied on for this property. 143 | /// See for details. 144 | /// 145 | /// Returns an error if the given public key doesn't belong to the 146 | /// `key_agg_ctx`, or if the signature is invalid. 147 | pub fn verify_partial_adaptor( 148 | key_agg_ctx: &KeyAggContext, 149 | partial_signature: impl Into, 150 | aggregated_nonce: &AggNonce, 151 | adaptor_point: impl Into, 152 | individual_pubkey: impl Into, 153 | individual_pubnonce: &PubNonce, 154 | message: impl AsRef<[u8]>, 155 | ) -> Result<(), VerifyError> { 156 | let partial_signature: MaybeScalar = partial_signature.into(); 157 | 158 | // As a side-effect, looking up the cached effective key also confirms 159 | // the individual key is indeed part of the aggregated key. 160 | let effective_pubkey: MaybePoint = key_agg_ctx 161 | .effective_pubkey(individual_pubkey) 162 | .ok_or(VerifyError::UnknownKey)?; 163 | 164 | let aggregated_pubkey = key_agg_ctx.pubkey; 165 | 166 | let b: MaybeScalar = aggregated_nonce.nonce_coefficient(aggregated_pubkey, &message); 167 | let final_nonce: Point = aggregated_nonce.final_nonce(b); 168 | let adapted_nonce = final_nonce + adaptor_point.into(); 169 | 170 | let mut effective_nonce = individual_pubnonce.R1 + b * individual_pubnonce.R2; 171 | 172 | // Don't need constant time ops here as adapted_nonce is public. 173 | if adapted_nonce.has_odd_y() { 174 | effective_nonce = -effective_nonce; 175 | } 176 | 177 | let nonce_x_bytes = adapted_nonce.serialize_xonly(); 178 | let e: MaybeScalar = compute_challenge_hash_tweak(&nonce_x_bytes, &aggregated_pubkey, &message); 179 | 180 | // s * G == R + (g * gacc * e * a * P) 181 | let challenge_parity = aggregated_pubkey.parity() ^ key_agg_ctx.parity_acc; 182 | let challenge_point = (e * effective_pubkey).negate_if(challenge_parity); 183 | 184 | if partial_signature * G != effective_nonce + challenge_point { 185 | return Err(VerifyError::BadSignature); 186 | } 187 | 188 | Ok(()) 189 | } 190 | 191 | /// Verify a partial signature, usually from an untrusted co-signer. 192 | /// 193 | /// If `verify_partial` succeeds for every signature in 194 | /// a signing session, the resulting aggregated signature is guaranteed 195 | /// to be valid. 196 | /// 197 | /// Note that partial signatures are _not_ unforgeable! 198 | /// Validity of a partial signature should not be relied on for this property. 199 | /// See for details. 200 | /// 201 | /// This function is effectively the same as invoking [`verify_partial_adaptor`] 202 | /// but passing [`MaybePoint::Infinity`] as the adaptor point. 203 | /// 204 | /// Returns an error if the given public key doesn't belong to the 205 | /// `key_agg_ctx`, or if the signature is invalid. 206 | pub fn verify_partial( 207 | key_agg_ctx: &KeyAggContext, 208 | partial_signature: impl Into, 209 | aggregated_nonce: &AggNonce, 210 | individual_pubkey: impl Into, 211 | individual_pubnonce: &PubNonce, 212 | message: impl AsRef<[u8]>, 213 | ) -> Result<(), VerifyError> { 214 | verify_partial_adaptor( 215 | key_agg_ctx, 216 | partial_signature, 217 | aggregated_nonce, 218 | MaybePoint::Infinity, 219 | individual_pubkey, 220 | individual_pubnonce, 221 | message, 222 | ) 223 | } 224 | 225 | #[cfg(test)] 226 | mod tests { 227 | use super::*; 228 | use crate::errors::DecodeError; 229 | use crate::testhex; 230 | 231 | #[test] 232 | fn test_partial_sign_and_verify() { 233 | const SIGN_VERIFY_VECTORS: &[u8] = include_bytes!("test_vectors/sign_verify_vectors.json"); 234 | 235 | #[derive(serde::Deserialize)] 236 | struct ValidSignVerifyTestCase { 237 | key_indices: Vec, 238 | nonce_indices: Vec, 239 | aggnonce_index: usize, 240 | msg_index: usize, 241 | signer_index: usize, 242 | expected: MaybeScalar, 243 | } 244 | 245 | #[derive(serde::Deserialize, Clone)] 246 | struct SignError { 247 | signer: Option, 248 | } 249 | 250 | #[derive(serde::Deserialize, Clone)] 251 | struct SignErrorTestCase { 252 | key_indices: Vec, 253 | aggnonce_index: usize, 254 | msg_index: usize, 255 | secnonce_index: usize, 256 | error: SignError, 257 | comment: String, 258 | } 259 | 260 | #[derive(serde::Deserialize)] 261 | struct VerifyFailTestCase { 262 | #[serde(rename = "sig", deserialize_with = "testhex::deserialize")] 263 | partial_signature: Vec, 264 | key_indices: Vec, 265 | nonce_indices: Vec, 266 | msg_index: usize, 267 | signer_index: usize, 268 | comment: String, 269 | } 270 | 271 | #[derive(serde::Deserialize)] 272 | struct SignVerifyVectors { 273 | #[serde(rename = "sk")] 274 | seckey: Scalar, 275 | 276 | #[serde(deserialize_with = "testhex::deserialize_vec")] 277 | pubkeys: Vec<[u8; 33]>, 278 | 279 | #[serde(rename = "secnonces", deserialize_with = "testhex::deserialize_vec")] 280 | secret_nonces: Vec<[u8; 97]>, 281 | 282 | #[serde(rename = "pnonces", deserialize_with = "testhex::deserialize_vec")] 283 | public_nonces: Vec<[u8; 66]>, 284 | 285 | #[serde(rename = "aggnonces", deserialize_with = "testhex::deserialize_vec")] 286 | aggregated_nonces: Vec<[u8; 66]>, 287 | 288 | #[serde(rename = "msgs", deserialize_with = "testhex::deserialize_vec")] 289 | messages: Vec>, 290 | 291 | valid_test_cases: Vec, 292 | sign_error_test_cases: Vec, 293 | verify_fail_test_cases: Vec, 294 | } 295 | 296 | let vectors: SignVerifyVectors = serde_json::from_slice(SIGN_VERIFY_VECTORS) 297 | .expect("error parsing test vectors from sign_verify.json"); 298 | 299 | let secnonce = SecNonce::try_from(vectors.secret_nonces[0].as_ref()) 300 | .expect("error parsing secret nonce"); 301 | 302 | for (test_index, test_case) in vectors.valid_test_cases.into_iter().enumerate() { 303 | let pubkeys: Vec = test_case 304 | .key_indices 305 | .into_iter() 306 | .map(|i| { 307 | Point::try_from(&vectors.pubkeys[i]).unwrap_or_else(|_| { 308 | panic!( 309 | "invalid pubkey used in valid test: {}", 310 | base16ct::lower::encode_string(&vectors.pubkeys[i]) 311 | ) 312 | }) 313 | }) 314 | .collect(); 315 | 316 | let signer_pubkey = pubkeys[test_case.signer_index]; 317 | assert_eq!(signer_pubkey, vectors.seckey.base_point_mul()); 318 | 319 | let aggnonce_bytes = &vectors.aggregated_nonces[test_case.aggnonce_index]; 320 | let aggregated_nonce = AggNonce::from_bytes(aggnonce_bytes).unwrap_or_else(|_| { 321 | panic!( 322 | "invalid aggregated nonce used in valid test case: {}", 323 | base16ct::lower::encode_string(aggnonce_bytes) 324 | ) 325 | }); 326 | 327 | let key_agg_ctx = 328 | KeyAggContext::new(pubkeys).expect("error constructing key aggregation context"); 329 | 330 | let message = &vectors.messages[test_case.msg_index]; 331 | 332 | let partial_signature: PartialSignature = sign_partial( 333 | &key_agg_ctx, 334 | vectors.seckey, 335 | secnonce.clone(), 336 | &aggregated_nonce, 337 | message, 338 | ) 339 | .expect("error during partial signing"); 340 | 341 | assert_eq!( 342 | partial_signature, test_case.expected, 343 | "partial signature does not match expected for test case {}", 344 | test_index, 345 | ); 346 | 347 | let adaptor_secret = MaybeScalar::Valid(vectors.seckey); 348 | let adaptor_point = adaptor_secret * G; 349 | let partial_adaptor_signature: PartialSignature = sign_partial_adaptor( 350 | &key_agg_ctx, 351 | vectors.seckey, 352 | secnonce.clone(), 353 | &aggregated_nonce, 354 | adaptor_point, 355 | message, 356 | ) 357 | .expect("error during partial adaptor signing"); 358 | 359 | let public_nonces: Vec = test_case 360 | .nonce_indices 361 | .into_iter() 362 | .map(|i| { 363 | PubNonce::from_bytes(&vectors.public_nonces[i]).unwrap_or_else(|_| { 364 | panic!( 365 | "invalid pubnonce in valid test: {}", 366 | base16ct::lower::encode_string(&vectors.public_nonces[i]) 367 | ) 368 | }) 369 | }) 370 | .collect(); 371 | 372 | // Ensure the aggregated nonce in the test vector is correct 373 | assert_eq!(&AggNonce::sum(&public_nonces), &aggregated_nonce); 374 | 375 | verify_partial( 376 | &key_agg_ctx, 377 | partial_signature, 378 | &aggregated_nonce, 379 | signer_pubkey, 380 | &public_nonces[test_case.signer_index], 381 | message, 382 | ) 383 | .expect("failed to verify valid partial signature"); 384 | 385 | verify_partial_adaptor( 386 | &key_agg_ctx, 387 | partial_adaptor_signature, 388 | &aggregated_nonce, 389 | adaptor_point, 390 | signer_pubkey, 391 | &public_nonces[test_case.signer_index], 392 | message, 393 | ) 394 | .expect("failed to verify valid partial signature"); 395 | } 396 | 397 | // invalid input test case 0: signer's pubkey is not in the key_agg_ctx 398 | { 399 | let test_case = vectors.sign_error_test_cases[0].clone(); 400 | 401 | let pubkeys: Vec = test_case 402 | .key_indices 403 | .into_iter() 404 | .map(|i| Point::try_from(&vectors.pubkeys[i]).unwrap()) 405 | .collect(); 406 | 407 | let aggnonce_bytes = &vectors.aggregated_nonces[test_case.aggnonce_index]; 408 | let aggregated_nonce = AggNonce::from_bytes(aggnonce_bytes).unwrap(); 409 | 410 | let key_agg_ctx = 411 | KeyAggContext::new(pubkeys).expect("error constructing key aggregation context"); 412 | 413 | let message = &vectors.messages[test_case.msg_index]; 414 | 415 | assert_eq!( 416 | sign_partial::( 417 | &key_agg_ctx, 418 | vectors.seckey, 419 | secnonce, 420 | &aggregated_nonce, 421 | message, 422 | ), 423 | Err(SigningError::UnknownKey), 424 | "partial signing should fail for pubkey not in key_agg_ctx", 425 | ); 426 | } 427 | 428 | // invalid input test case 1: invalid pubkey 429 | { 430 | let test_case = &vectors.sign_error_test_cases[1]; 431 | for (signer_index, &key_index) in test_case.key_indices.iter().enumerate() { 432 | let result = Point::try_from(&vectors.pubkeys[key_index]); 433 | if signer_index == test_case.error.signer.unwrap() { 434 | assert_eq!( 435 | result, 436 | Err(secp::errors::InvalidPointBytes), 437 | "expected invalid signer pubkey" 438 | ); 439 | } else { 440 | result.expect("expected valid signer pubkey"); 441 | } 442 | } 443 | } 444 | 445 | // invalid input test case 2, 3, and 4: invalid aggnonce 446 | { 447 | for test_case in vectors.sign_error_test_cases[2..5].iter() { 448 | let result = 449 | AggNonce::from_bytes(&vectors.aggregated_nonces[test_case.aggnonce_index]); 450 | 451 | assert_eq!( 452 | result, 453 | Err(DecodeError::from(secp::errors::InvalidPointBytes)), 454 | "{} - invalid AggNonce should fail to decode", 455 | &test_case.comment 456 | ); 457 | } 458 | } 459 | 460 | // invalid input test case 5: invalid secnonce 461 | { 462 | let test_case = &vectors.sign_error_test_cases[5]; 463 | let result = SecNonce::from_bytes(&vectors.secret_nonces[test_case.secnonce_index]); 464 | assert_eq!( 465 | result, 466 | Err(DecodeError::from(secp::errors::InvalidScalarBytes)), 467 | "invalid SecNonce should fail to decode" 468 | ); 469 | } 470 | 471 | // Verification failure test cases 0 and 1: fake signatures 472 | { 473 | for test_case in vectors.verify_fail_test_cases[..2].iter() { 474 | let pubkeys: Vec = test_case 475 | .key_indices 476 | .iter() 477 | .map(|&i| Point::try_from(&vectors.pubkeys[i]).unwrap()) 478 | .collect(); 479 | 480 | let signer_pubkey = pubkeys[test_case.signer_index]; 481 | 482 | let key_agg_ctx = KeyAggContext::new(pubkeys) 483 | .expect("error constructing key aggregation context"); 484 | 485 | let public_nonces: Vec = test_case 486 | .nonce_indices 487 | .iter() 488 | .map(|&i| PubNonce::from_bytes(&vectors.public_nonces[i]).unwrap()) 489 | .collect(); 490 | 491 | let aggregated_nonce = AggNonce::sum(&public_nonces); 492 | 493 | let message = &vectors.messages[test_case.msg_index]; 494 | 495 | let partial_signature = 496 | MaybeScalar::try_from(test_case.partial_signature.as_slice()) 497 | .expect("unexpected invalid partial signature"); 498 | 499 | assert_eq!( 500 | verify_partial( 501 | &key_agg_ctx, 502 | partial_signature, 503 | &aggregated_nonce, 504 | signer_pubkey, 505 | &public_nonces[test_case.signer_index], 506 | message, 507 | ), 508 | Err(VerifyError::BadSignature), 509 | "{} - unexpected success while verifying invalid partial signature", 510 | test_case.comment, 511 | ); 512 | } 513 | } 514 | 515 | // Verification failure test case 2: invalid signature 516 | { 517 | let test_case = &vectors.verify_fail_test_cases[2]; 518 | let result = PartialSignature::try_from(test_case.partial_signature.as_slice()); 519 | assert_eq!( 520 | result, 521 | Err(secp::errors::InvalidScalarBytes), 522 | "unexpected valid partial signature" 523 | ); 524 | } 525 | } 526 | 527 | #[test] 528 | fn test_sign_with_tweaks() { 529 | const TWEAK_VECTORS: &[u8] = include_bytes!("test_vectors/tweak_vectors.json"); 530 | 531 | #[derive(serde::Deserialize)] 532 | struct ValidTweakTestCase { 533 | key_indices: Vec, 534 | nonce_indices: Vec, 535 | tweak_indices: Vec, 536 | is_xonly: Vec, 537 | signer_index: usize, 538 | #[serde(rename = "expected")] 539 | partial_signature: MaybeScalar, 540 | } 541 | 542 | #[derive(serde::Deserialize)] 543 | struct TweakVectors { 544 | #[serde(rename = "sk")] 545 | seckey: Scalar, 546 | pubkeys: Vec, 547 | 548 | #[serde(rename = "secnonce")] 549 | secret_nonces: SecNonce, 550 | 551 | #[serde(rename = "pnonces")] 552 | public_nonces: Vec, 553 | 554 | #[serde(rename = "aggnonce")] 555 | aggregated_nonce: AggNonce, 556 | 557 | #[serde(deserialize_with = "testhex::deserialize_vec")] 558 | tweaks: Vec>, 559 | 560 | #[serde(rename = "msg", deserialize_with = "testhex::deserialize")] 561 | message: Vec, 562 | 563 | valid_test_cases: Vec, 564 | } 565 | 566 | let vectors: TweakVectors = 567 | serde_json::from_slice(TWEAK_VECTORS).expect("failed to parse test_vectors/tweak.json"); 568 | 569 | for test_case in vectors.valid_test_cases { 570 | let pubkeys: Vec = test_case 571 | .key_indices 572 | .into_iter() 573 | .map(|i| vectors.pubkeys[i]) 574 | .collect(); 575 | 576 | let signer_pubkey = pubkeys[test_case.signer_index]; 577 | 578 | let mut key_agg_ctx = 579 | KeyAggContext::new(pubkeys).expect("error creating key aggregation context"); 580 | 581 | key_agg_ctx = test_case 582 | .tweak_indices 583 | .into_iter() 584 | .map(|i| { 585 | Scalar::try_from(vectors.tweaks[i].as_slice()) 586 | .expect("failed to parse valid tweak value") 587 | }) 588 | .zip(test_case.is_xonly) 589 | .fold(key_agg_ctx, |ctx, (tweak, is_xonly)| { 590 | ctx.with_tweak(tweak, is_xonly).unwrap_or_else(|_| { 591 | panic!("failed to tweak key agg context with {:x}", tweak) 592 | }) 593 | }); 594 | 595 | let partial_signature: PartialSignature = sign_partial( 596 | &key_agg_ctx, 597 | vectors.seckey, 598 | vectors.secret_nonces.clone(), 599 | &vectors.aggregated_nonce, 600 | &vectors.message, 601 | ) 602 | .expect("error during partial signing"); 603 | 604 | assert_eq!( 605 | partial_signature, test_case.partial_signature, 606 | "incorrect tweaked partial signature", 607 | ); 608 | 609 | let public_nonces: Vec<&PubNonce> = test_case 610 | .nonce_indices 611 | .into_iter() 612 | .map(|i| &vectors.public_nonces[i]) 613 | .collect(); 614 | 615 | verify_partial( 616 | &key_agg_ctx, 617 | partial_signature, 618 | &vectors.aggregated_nonce, 619 | signer_pubkey, 620 | public_nonces[test_case.signer_index], 621 | &vectors.message, 622 | ) 623 | .expect("failed to verify valid partial signature"); 624 | } 625 | } 626 | } 627 | -------------------------------------------------------------------------------- /doc/API.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | | Feature | Description | Dependencies | Enabled by Default | 4 | |---------|-------------|--------------|:------------------:| 5 | | `secp256k1` | Use [`libsecp256k1`](https://github.com/bitcoin-core/secp256k1) bindings for elliptic curve math. Include trait implementations for converting to and from types in [the `secp256k1` crate][secp256k1]. This feature supercedes the `k256` feature if that one is enabled. | [`secp256k1`] | ✅ | 6 | | `k256` | Use [the `k256` crate][k256] for elliptic curve math. This allows a pure-rust implementation of MuSig2. Include trait implementations for types from [`k256`]. If the `secp256k1` feature is enabled, then [`k256`] will still be brought in and trait implementations will be included, but the actual curve math will be done by `libsecp256k1`. | [`k256`] | ❌ | 7 | | `serde` | Implement serialization and deserialization for types in this crate. | [`serde`](https://docs.rs/serde) | ❌ | 8 | | `rand` | Enable support for accepting a CSPRNG as input, via [the `rand` crate][rand] | [`rand`] | ❌ | 9 | 10 | # Key Aggregation 11 | 12 | Once all signers know each other's public keys (out of scope for this crate), they can construct a [`KeyAggContext`] which aggregates their public keys together, along with optional _tweak values_ (see [`KeyAggContext::with_tweak`] to learn more). 13 | 14 | 15 |
16 |

Example

17 | 18 | ```rust 19 | # #[cfg(feature = "secp256k1")] 20 | use secp256k1::{SecretKey, PublicKey}; 21 | # 22 | # // k256::SecretKey and k256::PublicKey don't have string parsing traits, 23 | # // so I'll just use our own representations for this example. 24 | # #[cfg(not(feature = "secp256k1"))] 25 | # use musig2::secp::{Point as PublicKey, Scalar as SecretKey}; 26 | use musig2::KeyAggContext; 27 | 28 | let pubkeys = [ 29 | "026e14224899cf9c780fef5dd200f92a28cc67f71c0af6fe30b5657ffc943f08f4" 30 | .parse::() 31 | .unwrap(), 32 | "02f3b071c064f115ca762ed88c3efd1927ea657c7949698b77255ea25751331f0b" 33 | .parse::() 34 | .unwrap(), 35 | "03204ea8bc3425b2cbc9cb20617f67dc6b202467591d0b26d059e370b71ee392eb" 36 | .parse::() 37 | .unwrap(), 38 | ]; 39 | 40 | let signer_index = 2; 41 | let seckey: SecretKey = "10e7721a3aa6de7a98cecdbd7c706c836a907ca46a43235a7b498b12498f98f0" 42 | .parse() 43 | .unwrap(); 44 | 45 | let key_agg_ctx = KeyAggContext::new(pubkeys).unwrap(); 46 | 47 | 48 | // This is the key which the group has control over. 49 | let aggregated_pubkey: PublicKey = key_agg_ctx.aggregated_pubkey(); 50 | assert_eq!( 51 | aggregated_pubkey, 52 | "02e272de44ea720667aba55341a1a761c0fc8fbe294aa31dbaf1cff80f1c2fd940" 53 | .parse() 54 | .unwrap() 55 | ); 56 | ``` 57 |
58 |
59 | 60 | A handy property of the MuSig2 protocol is that signers do not need proof that the other signers in the group know their own secret keys. They can simply exchange public keys and continue once all signers agree on an aggregated pubkey. 61 | 62 | Once you have a [`KeyAggContext`], you may choose between two sets of APIs for running the MuSig2 protocol, covering both **Functional** and **State-Machine** approaches. 63 | 64 | ## State-Machine API 65 | 66 | A state machine is a stateful object which manipulates its internal state based on external input, fed to it by the caller (you). 67 | 68 | This crate's _State-Machine_-based signing API is safer, but may not be as flexible as the _Functional_ API. It is constructed around two stateful types, [`FirstRound`] and [`SecondRound`], which handle storing partial nonces and partial signatures. 69 | 70 | [`FirstRound`] is analagous to the first signing round of MuSig2, wherein signers generate and send nonces to one-another, or to a [designated aggregator](#single-aggregator). 71 | 72 | [`SecondRound`] is analagous to the second signing round where signers share and verify their partial signatures. Once the [`SecondRound`] complete, it can be finalized into a valid aggregated Schnorr signature. 73 | 74 | 75 |
76 |

Example

77 | 78 | ```rust 79 | # #[cfg(feature = "secp256k1")] 80 | # use secp256k1::{SecretKey, PublicKey}; 81 | # #[cfg(not(feature = "secp256k1"))] 82 | # use musig2::secp::{Point as PublicKey, Scalar as SecretKey}; 83 | # use musig2::KeyAggContext; 84 | # 85 | # /// Same pubkeys as in previous example 86 | # let key_agg_ctx = 87 | # "0000000003026e14224899cf9c780fef5dd200f92a28cc67f71c0af6fe30b5657ffc943f08f402f3\ 88 | # b071c064f115ca762ed88c3efd1927ea657c7949698b77255ea25751331f0b03204ea8bc3425b2cb\ 89 | # c9cb20617f67dc6b202467591d0b26d059e370b71ee392eb" 90 | # .parse::() 91 | # .unwrap(); 92 | # 93 | # let signer_index = 2; 94 | # let seckey: SecretKey = "10e7721a3aa6de7a98cecdbd7c706c836a907ca46a43235a7b498b12498f98f0" 95 | # .parse() 96 | # .unwrap(); 97 | # 98 | # let aggregated_pubkey: PublicKey = key_agg_ctx.aggregated_pubkey(); 99 | # 100 | use musig2::{ 101 | CompactSignature, FirstRound, PartialSignature, PubNonce, SecNonceSpices, SecondRound, 102 | }; 103 | 104 | // The group wants to sign something! 105 | let message = "hello interwebz!"; 106 | 107 | // Normally this should be sampled securely from a CSPRNG. 108 | // let mut nonce_seed = [0u8; 32] 109 | // rand::rng().fill_bytes(&mut nonce_seed); 110 | let nonce_seed = [0xACu8; 32]; 111 | 112 | let mut first_round = FirstRound::new( 113 | key_agg_ctx, 114 | nonce_seed, 115 | signer_index, 116 | SecNonceSpices::new() 117 | .with_seckey(seckey) 118 | .with_message(&message), 119 | ) 120 | .unwrap(); 121 | 122 | // We would share our public nonce with our peers. 123 | assert_eq!( 124 | first_round.our_public_nonce(), 125 | "02d1e90616ea78a612dddfe97de7b5e7e1ceef6e64b7bc23b922eae30fa2475cca\ 126 | 02e676a3af322965d53cc128597897ef4f84a8d8080b456e27836db70e5343a2bb" 127 | .parse() 128 | .unwrap(), 129 | "Our public nonce should match" 130 | ); 131 | 132 | // We can see a list of which signers (by index) have yet to provide us 133 | // with a nonce. 134 | assert_eq!(first_round.holdouts(), &[0, 1]); 135 | 136 | // We receive the public nonces from our peers one at a time. 137 | first_round.receive_nonce( 138 | 0, 139 | "02af252206259fc1bf588b1f847e15ac78fa840bfb06014cdbddcfcc0e5876f9c9\ 140 | 0380ab2fc9abe84ef42a8d87062d5094b9ab03f4150003a5449846744a49394e45" 141 | .parse::() 142 | .unwrap() 143 | ) 144 | .unwrap(); 145 | 146 | // `is_complete` provides a quick check to see whether we have nonces from 147 | // every signer yet. 148 | assert!(!first_round.is_complete()); 149 | 150 | // ...once we receive all their nonces... 151 | first_round.receive_nonce( 152 | 1, 153 | "020ab52d58f00887d5082c41dc85fd0bd3aaa108c2c980e0337145ac7003c28812\ 154 | 03956ec5bd53023261e982ac0c6f5f2e4b6c1e14e9b1992fb62c9bdfcf5b27dc8d" 155 | .parse::() 156 | .unwrap() 157 | ) 158 | .unwrap(); 159 | 160 | // ... the round will be complete. 161 | assert!(first_round.is_complete()); 162 | 163 | let mut second_round: SecondRound<&str> = first_round.finalize(seckey, message).unwrap(); 164 | 165 | // We could now send our partial signature to our peers. 166 | // Be careful not to send your signature first if your peers 167 | // might run away without surrendering their signatures in exchange! 168 | let our_partial_signature: PartialSignature = second_round.our_signature(); 169 | assert_eq!( 170 | our_partial_signature, 171 | "efd62850b959a76a462f1e42eb3cecc77a5a0982742fff2901456b7d1453a817" 172 | .parse() 173 | .unwrap() 174 | ); 175 | 176 | second_round.receive_signature( 177 | 0, 178 | "5a476e0126583e9e0ceebb01a34bdd342c72eab92efbe8a1c7f07e793fd88f96" 179 | .parse::() 180 | .unwrap() 181 | ) 182 | .expect("signer 0's partial signature should be valid"); 183 | 184 | // Same methods as on FirstRound are available for SecondRound. 185 | assert!(!second_round.is_complete()); 186 | assert_eq!(second_round.holdouts(), &[1]); 187 | 188 | // Receive a partial signature from one of our cosigners. This 189 | // automatically verifies the partial signature and returns an 190 | // error if the signature is invalid. 191 | second_round.receive_signature( 192 | 1, 193 | "45ac8a698fc9e82408367e28a2d257edf6fc49f14dcc8a98c43e9693e7265e7e" 194 | .parse::() 195 | .unwrap() 196 | ) 197 | .expect("signer 1's partial signature should be valid"); 198 | 199 | assert!(second_round.is_complete()); 200 | 201 | // If all signatures were received successfully, finalizing the second round 202 | // should succeed with overwhelming probability. 203 | let final_signature: CompactSignature = second_round.finalize().unwrap(); 204 | 205 | assert_eq!( 206 | final_signature.to_string(), 207 | "38fbd82d1d27bb3401042062acfd4e7f54ce93ddf26a4ae87cf71568c1d4e8bb\ 208 | 8fca20bb6f7bce2c5b54576d315b21eae31a614641afd227cda221fd6b1c54ea" 209 | ); 210 | 211 | musig2::verify_single( 212 | aggregated_pubkey, 213 | final_signature, 214 | message 215 | ) 216 | .expect("aggregated signature must be valid"); 217 | ``` 218 |
219 |
220 | 221 | ## Functional API 222 | 223 | The _Functional_ API exposes the MuSig2 protocol through pure functions which accept read-only inputs and produce deterministic outputs. This obviously lacks internal state and it is thus entirely dependent on the caller to securely handle nonce state management. The caller is free to implement nonce state management however they like with this API. [Please read the warning below about nonce-reuse BEFORE attempting to use the Functional API](#nonce-reuse). 224 | 225 | Instead of using [`FirstRound`] and [`SecondRound`], the Functional API is exposed through these pure functions: 226 | 227 | - [`SecNonce::generate`] - Generate a secret nonce. 228 | - [`AggNonce::sum`] - Aggregate public nonces together. 229 | - [`sign_partial`] - Create a partial signature on a message. 230 | - [`verify_partial`] - Verify a partial signature. 231 | - [`aggregate_partial_signatures`] - Aggregate a collection of partial signatures into a final valid signature. 232 | 233 |
234 |

Example

235 | 236 | ```rust 237 | # #[cfg(feature = "secp256k1")] 238 | # use secp256k1::{SecretKey, PublicKey}; 239 | # #[cfg(not(feature = "secp256k1"))] 240 | # use musig2::secp::{Point as PublicKey, Scalar as SecretKey}; 241 | # use musig2::{KeyAggContext, PartialSignature, PubNonce}; 242 | # 243 | # let signer_index = 2; 244 | # let seckey: SecretKey = "10e7721a3aa6de7a98cecdbd7c706c836a907ca46a43235a7b498b12498f98f0" 245 | # .parse() 246 | # .unwrap(); 247 | # 248 | # /// Same pubkeys as in previous example 249 | # let key_agg_ctx = 250 | # "0000000003026e14224899cf9c780fef5dd200f92a28cc67f71c0af6fe30b5657ffc943f08f402f3\ 251 | # b071c064f115ca762ed88c3efd1927ea657c7949698b77255ea25751331f0b03204ea8bc3425b2cb\ 252 | # c9cb20617f67dc6b202467591d0b26d059e370b71ee392eb" 253 | # .parse::() 254 | # .unwrap(); 255 | # 256 | # let aggregated_pubkey: PublicKey = key_agg_ctx.aggregated_pubkey(); 257 | # let message = "hello interwebz!"; 258 | # let nonce_seed = [0xACu8; 32]; 259 | use musig2::{AggNonce, SecNonce}; 260 | 261 | // This is how `FirstRound` derives the nonce internally. 262 | let secnonce = SecNonce::build(nonce_seed) 263 | .with_seckey(seckey) 264 | .with_message(&message) 265 | .with_aggregated_pubkey(aggregated_pubkey) 266 | .with_extra_input(&(signer_index as u32).to_be_bytes()) 267 | .build(); 268 | 269 | let our_public_nonce = secnonce.public_nonce(); 270 | assert_eq!( 271 | our_public_nonce, 272 | "02d1e90616ea78a612dddfe97de7b5e7e1ceef6e64b7bc23b922eae30fa2475cca\ 273 | 02e676a3af322965d53cc128597897ef4f84a8d8080b456e27836db70e5343a2bb" 274 | .parse() 275 | .unwrap() 276 | ); 277 | 278 | // ...Exchange nonces with peers... 279 | 280 | let public_nonces = [ 281 | "02af252206259fc1bf588b1f847e15ac78fa840bfb06014cdbddcfcc0e5876f9c9\ 282 | 0380ab2fc9abe84ef42a8d87062d5094b9ab03f4150003a5449846744a49394e45" 283 | .parse::() 284 | .unwrap(), 285 | 286 | "020ab52d58f00887d5082c41dc85fd0bd3aaa108c2c980e0337145ac7003c28812\ 287 | 03956ec5bd53023261e982ac0c6f5f2e4b6c1e14e9b1992fb62c9bdfcf5b27dc8d" 288 | .parse::() 289 | .unwrap(), 290 | 291 | our_public_nonce, 292 | ]; 293 | 294 | // We manually aggregate the nonces together and then construct our partial signature. 295 | let aggregated_nonce: AggNonce = public_nonces.iter().sum(); 296 | let our_partial_signature: PartialSignature = musig2::sign_partial( 297 | &key_agg_ctx, 298 | seckey, 299 | secnonce, 300 | &aggregated_nonce, 301 | message 302 | ) 303 | .expect("error creating partial signature"); 304 | 305 | let partial_signatures = [ 306 | "5a476e0126583e9e0ceebb01a34bdd342c72eab92efbe8a1c7f07e793fd88f96" 307 | .parse::() 308 | .unwrap(), 309 | "45ac8a698fc9e82408367e28a2d257edf6fc49f14dcc8a98c43e9693e7265e7e" 310 | .parse::() 311 | .unwrap(), 312 | our_partial_signature, 313 | ]; 314 | 315 | /// Signatures should be verified upon receipt and invalid signatures 316 | /// should be blamed on the signer who sent them. 317 | for (i, partial_signature) in partial_signatures.into_iter().enumerate() { 318 | if i == signer_index { 319 | // Don't bother verifying our own signature 320 | continue; 321 | } 322 | 323 | let their_pubkey: PublicKey = key_agg_ctx.get_pubkey(i).unwrap(); 324 | let their_pubnonce = &public_nonces[i]; 325 | 326 | musig2::verify_partial( 327 | &key_agg_ctx, 328 | partial_signature, 329 | &aggregated_nonce, 330 | their_pubkey, 331 | their_pubnonce, 332 | message 333 | ) 334 | .expect("received invalid signature from a peer"); 335 | } 336 | 337 | let final_signature: [u8; 64] = musig2::aggregate_partial_signatures( 338 | &key_agg_ctx, 339 | &aggregated_nonce, 340 | partial_signatures, 341 | message, 342 | ) 343 | .expect("error aggregating signatures"); 344 | 345 | assert_eq!( 346 | final_signature, 347 | [ 348 | 0x38, 0xFB, 0xD8, 0x2D, 0x1D, 0x27, 0xBB, 0x34, 0x01, 0x04, 0x20, 0x62, 0xAC, 0xFD, 349 | 0x4E, 0x7F, 0x54, 0xCE, 0x93, 0xDD, 0xF2, 0x6A, 0x4A, 0xE8, 0x7C, 0xF7, 0x15, 0x68, 350 | 0xC1, 0xD4, 0xE8, 0xBB, 0x8F, 0xCA, 0x20, 0xBB, 0x6F, 0x7B, 0xCE, 0x2C, 0x5B, 0x54, 351 | 0x57, 0x6D, 0x31, 0x5B, 0x21, 0xEA, 0xE3, 0x1A, 0x61, 0x46, 0x41, 0xAF, 0xD2, 0x27, 352 | 0xCD, 0xA2, 0x21, 0xFD, 0x6B, 0x1C, 0x54, 0xEA 353 | ] 354 | ); 355 | 356 | musig2::verify_single( 357 | aggregated_pubkey, 358 | &final_signature, 359 | message 360 | ) 361 | .expect("aggregated signature must be valid"); 362 | ``` 363 |
364 |
365 | 366 | ## Single Aggregator 367 | 368 | As an alternative to a many-to-many topology where each signer must collect nonces and partial signatures from everyone else in the group, the group can instead opt to nominate an _aggregator node_ whose duty is to collect nonces and signatures from all other signers, and then broadcast the aggregated signature once they receive all partial signatures. 369 | 370 | This dramatically decreases the number of network round-trips required for large groups of signers, and doesn't require any trust in the aggregator node beyond the possibility that they may refuse to reveal the final signature. 371 | 372 | Here's an example of how to use the State-Machine API to interact with an untrusted remote aggregator node. 373 | 374 |
375 |

Example

376 | 377 | ```rust 378 | # #[cfg(feature = "secp256k1")] 379 | # use secp256k1::{SecretKey, PublicKey}; 380 | # #[cfg(not(feature = "secp256k1"))] 381 | # use musig2::secp::{Point as PublicKey, Scalar as SecretKey}; 382 | # use musig2::KeyAggContext; 383 | # 384 | # /// Same pubkeys as in previous example 385 | # let key_agg_ctx = 386 | # "0000000003026e14224899cf9c780fef5dd200f92a28cc67f71c0af6fe30b5657ffc943f08f402f3\ 387 | # b071c064f115ca762ed88c3efd1927ea657c7949698b77255ea25751331f0b03204ea8bc3425b2cb\ 388 | # c9cb20617f67dc6b202467591d0b26d059e370b71ee392eb" 389 | # .parse::() 390 | # .unwrap(); 391 | # 392 | # let signer_index = 2; 393 | # let seckey: SecretKey = "10e7721a3aa6de7a98cecdbd7c706c836a907ca46a43235a7b498b12498f98f0" 394 | # .parse() 395 | # .unwrap(); 396 | # 397 | # let aggregated_pubkey: PublicKey = key_agg_ctx.aggregated_pubkey(); 398 | # 399 | use musig2::{ 400 | AggNonce, FirstRound, PartialSignature, PubNonce, SecNonceSpices, SecondRound, 401 | }; 402 | 403 | let message = "hello interwebz!"; 404 | 405 | // Normally this should be sampled securely from a CSPRNG. 406 | let nonce_seed = [0xACu8; 32]; 407 | 408 | let first_round = FirstRound::new( 409 | key_agg_ctx.clone(), 410 | nonce_seed, 411 | signer_index, 412 | SecNonceSpices::new() 413 | .with_seckey(seckey) 414 | .with_message(&message), 415 | ) 416 | .unwrap(); 417 | 418 | // We would share our public nonce with the aggregator. 419 | // The aggregator aggregates the group's nonces together 420 | // and sends us the resulting `AggNonce`. 421 | let aggregated_nonce = AggNonce::sum([ 422 | "02af252206259fc1bf588b1f847e15ac78fa840bfb06014cdbddcfcc0e5876f9c9\ 423 | 0380ab2fc9abe84ef42a8d87062d5094b9ab03f4150003a5449846744a49394e45" 424 | .parse::() 425 | .unwrap(), 426 | 427 | "020ab52d58f00887d5082c41dc85fd0bd3aaa108c2c980e0337145ac7003c28812\ 428 | 03956ec5bd53023261e982ac0c6f5f2e4b6c1e14e9b1992fb62c9bdfcf5b27dc8d" 429 | .parse::() 430 | .unwrap(), 431 | 432 | first_round.our_public_nonce(), 433 | ]); 434 | 435 | // Once we have the aggregated nonce, we can sign the message, 436 | // and send the partial signature to the aggregator. 437 | let our_partial_signature = first_round 438 | .sign_for_aggregator(seckey, message, &aggregated_nonce) 439 | .unwrap(); 440 | 441 | let partial_signatures = [ 442 | "5a476e0126583e9e0ceebb01a34bdd342c72eab92efbe8a1c7f07e793fd88f96" 443 | .parse::() 444 | .unwrap(), 445 | "45ac8a698fc9e82408367e28a2d257edf6fc49f14dcc8a98c43e9693e7265e7e" 446 | .parse::() 447 | .unwrap(), 448 | our_partial_signature, 449 | ]; 450 | 451 | // The aggregator aggregates the group's partial signatures, 452 | // either using `SecondRound` or the functional API. 453 | let final_signature: [u8; 64] = musig2::aggregate_partial_signatures( 454 | &key_agg_ctx, 455 | &aggregated_nonce, 456 | partial_signatures, 457 | message, 458 | ) 459 | .unwrap(); 460 | 461 | musig2::verify_single( 462 | aggregated_pubkey, 463 | &final_signature, 464 | message 465 | ) 466 | .expect("aggregated signature must be valid"); 467 | ``` 468 |
469 |
470 | 471 | The partial signatures can also be created using the functional API, as long as `SecNonce` is [managed carefully so that it is not accidentally reused.](#nonce-reuse) 472 | 473 | ## Signatures 474 | 475 | Partial signatures are represented as a [`secp::MaybeScalar`], which is just a scalar in the range `[0, n)` (where `n` is the number of points on the curve). This is aliased as [`PartialSignature`] for clarity. `PartialSignature` implements `Serialize` and `Deserialize` if the `serde` feature is enabled. 476 | 477 | The final output of a signature aggregation is a tuple of numbers `(R, s)` where `R` is a point and `s` is a scalar. This output type is represented by the [`LiftedSignature`] type. The return value of [`SecondRound::finalize`] or [`aggregate_partial_signatures`] can be converted to any type that implements `From`. 478 | 479 |
480 |

Example

481 | 482 | ```rust 483 | # use musig2::{AggNonce, KeyAggContext, PartialSignature}; 484 | # 485 | # fn main() -> Result<(), Box> { 486 | # /// Same pubkeys as in previous example 487 | # let key_agg_ctx = 488 | # "0000000003026e14224899cf9c780fef5dd200f92a28cc67f71c0af6fe30b5657ffc943f08f402f3\ 489 | # b071c064f115ca762ed88c3efd1927ea657c7949698b77255ea25751331f0b03204ea8bc3425b2cb\ 490 | # c9cb20617f67dc6b202467591d0b26d059e370b71ee392eb" 491 | # .parse::() 492 | # .unwrap(); 493 | # let message = "hello interwebz!"; 494 | # let partial_signatures = [ 495 | # "5a476e0126583e9e0ceebb01a34bdd342c72eab92efbe8a1c7f07e793fd88f96" 496 | # .parse::() 497 | # .unwrap(), 498 | # "45ac8a698fc9e82408367e28a2d257edf6fc49f14dcc8a98c43e9693e7265e7e" 499 | # .parse::() 500 | # .unwrap(), 501 | # "efd62850b959a76a462f1e42eb3cecc77a5a0982742fff2901456b7d1453a817" 502 | # .parse::() 503 | # .unwrap(), 504 | # ]; 505 | # let aggregated_nonce = "03f9ce0458831f7f8104f014d940db4048c4e045c369c207ec38530360ce7bfd3e\ 506 | # 023f5d6a34513458188503e7c48c1a6efd75f52e77da57587f372be8f839ecc1f9" 507 | # .parse::() 508 | # .unwrap(); 509 | # 510 | use musig2::{aggregate_partial_signatures, CompactSignature, LiftedSignature}; 511 | 512 | // Represents a compacted signature with an X-only nonce point. 513 | let final_signature: CompactSignature = aggregate_partial_signatures( 514 | // ... 515 | # &key_agg_ctx, 516 | # &aggregated_nonce, 517 | # partial_signatures, 518 | # message, 519 | )?; 520 | 521 | // Represents a fully parsed `(R, s)` signature pair. 522 | let final_signature: LiftedSignature = aggregate_partial_signatures( 523 | // ... 524 | # &key_agg_ctx, 525 | # &aggregated_nonce, 526 | # partial_signatures, 527 | # message, 528 | )?; 529 | 530 | // Or you can convert it directly to a byte array. 531 | let final_signature: [u8; 64] = aggregate_partial_signatures( 532 | // ... 533 | # &key_agg_ctx, 534 | # &aggregated_nonce, 535 | # partial_signatures, 536 | # message, 537 | )?; 538 | 539 | # #[cfg(feature = "secp256k1")] 540 | let final_signature: secp256k1::schnorr::Signature = aggregate_partial_signatures( 541 | // ... 542 | # &key_agg_ctx, 543 | # &aggregated_nonce, 544 | # partial_signatures, 545 | # message, 546 | )?; 547 | 548 | // allows us to use `R` as a variable name in this block 549 | #[allow(non_snake_case)] 550 | { 551 | // You can also unzip signatures into their individual components `(R, s)`. 552 | let signature: LiftedSignature = aggregate_partial_signatures( 553 | // ... 554 | # &key_agg_ctx, 555 | # &aggregated_nonce, 556 | # partial_signatures, 557 | # message, 558 | )?; 559 | 560 | // `R` can be any type that impls `From`. 561 | // `s` can be any type that impls `From`. 562 | let (R, s): (secp::Point, secp::MaybeScalar) = signature.unzip(); 563 | let (R, s): ([u8; 33], [u8; 32]) = signature.unzip(); 564 | # #[cfg(feature = "secp256k1")] 565 | let (R, s): (secp256k1::PublicKey, secp::MaybeScalar) = signature.unzip(); 566 | # #[cfg(feature = "k256")] 567 | let (R, s): (k256::PublicKey, k256::Scalar) = signature.unzip(); 568 | # #[cfg(feature = "k256")] 569 | let (R, s): (k256::AffinePoint, k256::Scalar) = signature.unzip(); 570 | } 571 | # 572 | # Ok(()) 573 | # } 574 | ``` 575 |
576 |
577 | 578 | This crate exports [BIP-0340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki)-compatible compact Schnorr signature functionality as well. 579 | 580 | - [`verify_single`] - Single Schnorr signature verification. 581 | - [`verify_batch`] - Efficient batched signature verification. 582 | - [`sign_solo`] - Single-key message signing. 583 | 584 | ## Serialization 585 | 586 | Binary and hex serialization with is implemented for the following types. 587 | 588 | - [`KeyAggContext`] 589 | - [`SecNonce`] 590 | - [`PubNonce`] 591 | - [`AggNonce`] 592 | - [`LiftedSignature`] 593 | - [`CompactSignature`] 594 | 595 | This is accomplished through the [`BinaryEncoding`] trait. Aliases to the methods of [`BinaryEncoding`] are declared on the vanilla implementations of each type. In addition, these types all implement common standard library traits: 596 | 597 | - [`std::fmt::LowerHex`] 598 | - [`std::fmt::UpperHex`] 599 | - [`std::str::FromStr`] 600 | - [`std::convert::TryFrom<&[u8]>`][std::convert::TryFrom] 601 | - [`std::convert::TryFrom<[u8; N]>`][std::convert::TryFrom] (except [`KeyAggContext`]) 602 | - [`std::convert::TryFrom<&[u8; N]>`][std::convert::TryFrom] (except [`KeyAggContext`]) 603 | 604 | They can also be infallibly converted to [`Vec`][Vec] using [`std::convert::From`], or to `[u8; N]` for fixed-length encodable types. 605 | 606 | If the `serde` feature is enabled, the above types implement [`serde::Serialize`] and [`serde::Deserialize`] for both binary and hex representations in constant time using the [`serdect`] crate. 607 | 608 |
609 |

Example

610 | 611 | ```rust 612 | # #[cfg(feature = "serde")] 613 | # { 614 | use musig2::{KeyAggContext, PubNonce, SecNonce}; 615 | 616 | #[derive(serde::Deserialize)] 617 | struct CustomSigningSession { 618 | key_agg_ctx: KeyAggContext, 619 | pubnonces: Vec, 620 | secnonce: SecNonce, 621 | message: String, 622 | } 623 | 624 | let json_data = "{ 625 | \"key_agg_ctx\": \"034191a1714ff295b6bc1008aaab813ac5c47bb7d4e64065c0d488b35ead12e0ba\ 626 | 000000020355d7de59c20355f9d8b14ccb60998983e9d73b38f64c9b9f2c4f868c\ 627 | 0a7ac02f039582f6f17f99784bc6de6e5664ef5f69eb1bf0dc151d824b19481ab0717c0cd5\", 628 | \"pubnonces\": [ 629 | \"02af252206259fc1bf588b1f847e15ac78fa840bfb06014cdbddcfcc0e5876f9c9\ 630 | 0380ab2fc9abe84ef42a8d87062d5094b9ab03f4150003a5449846744a49394e45\", 631 | \"020ab52d58f00887d5082c41dc85fd0bd3aaa108c2c980e0337145ac7003c28812\ 632 | 03956ec5bd53023261e982ac0c6f5f2e4b6c1e14e9b1992fb62c9bdfcf5b27dc8d\" 633 | ], 634 | \"secnonce\": \"B114E502BEAA4E301DD08A50264172C84E41650E6CB726B410C0694D59EFFB64\ 635 | 95B5CAF28D045B973D63E3C99A44B807BDE375FD6CB39E46DC4A511708D0E9D2\", 636 | \"message\": \"attack at dawn\" 637 | }"; 638 | 639 | let session: CustomSigningSession = serde_json::from_str(json_data).unwrap(); 640 | 641 | use musig2::BinaryEncoding; 642 | 643 | let key_agg_bytes: Vec = session.key_agg_ctx.to_bytes(); 644 | let first_pubnonce_bytes: [u8; 66] = session.pubnonces[0].to_bytes(); 645 | let secnonce_bytes = <[u8; 64]>::from(session.secnonce); 646 | 647 | let decoded_key_agg_ctx = KeyAggContext::from_bytes(&key_agg_bytes).unwrap(); 648 | let decoded_pubnonce = PubNonce::try_from(&first_pubnonce_bytes).unwrap(); 649 | let decoded_secnonce = SecNonce::try_from(secnonce_bytes).unwrap(); 650 | # } 651 | ``` 652 |
653 |
654 | 655 | # Security 656 | 657 | ## Nonce Reuse 658 | 659 | The easiest pitfall for downstream instantiations of the MuSig2 protocol is accidental nonce reuse. If you ever reuse a [`SecNonce`] for two different signing sessions, [a co-signer can trick you into exposing your private key](https://medium.com/blockstream/musig-dn-schnorr-multisignatures-with-verifiably-deterministic-nonces-27424b5df9d6#e3b6). 660 | 661 |
662 |

But how?

663 | 664 | The malicious co-signer opens two signing sessions on the same message, and provides different nonces to the victim in both sessions. Even if the victim reuses _their_ secret nonce, a different nonce from a co-signer will result in different _aggregated_ nonces `R` and `R'` for both signing sessions. See [the Nonce Generation Algorithm in BIP327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki#user-content-nonce-aggregation) for more details on why this is. 665 | 666 | The challenge hash `e` is computed as `e = H(R, Q, m)` (where `Q` is the aggregated pubkey and `m` is the message). Since the aggregated nonce `R'` of the second session is different, this results in a new challenge hash `e' = H(R', Q, m)` for the second signing session. 667 | 668 | The victim's partial signatures `s` and `s'` for both signing sessions would be computed as: 669 | 670 | ```notrust 671 | s = k + e * a * d 672 | s' = k + e' * a * d 673 | ``` 674 | 675 | ...Where `d` is their secret key, `a` is a publicly known key-coefficient, and `k` is their secret nonce. 676 | 677 | Given both `s` and `s'` from the victim, the attacker can then solve for and compute the victim's private key `d`. 678 | 679 | ```notrust 680 | k = s - e * a * d 681 | s' = k + e' * a * d 682 | s' = s - e * a * d + e' * a * d 683 | s' = s - a * d * (e + e') 684 | a * d * (e + e') = s - s' 685 | d = (s - s') / a * (e + e') 686 | ``` 687 |
688 |
689 | 690 | 691 | The [State-Machine API](#state-machine-api) is designed to avoid this possibility by computing and storing the [`SecNonce`] inside the [`FirstRound`] struct, and never exposing it directly to the downstream consumer. 692 | 693 | When using the `FirstRound` API, we recommend enabling the `rand` feature on this crate, and passing [`&mut rand::rng()`][rand::rngs::ThreadRng] as the `nonce_seed` argument to [`FirstRound::new`]. This reduces the risk of accidental nonce reuse significantly. 694 | 695 |
696 |

Example

697 | 698 | ```rust 699 | # #[cfg(feature = "secp256k1")] 700 | # use secp256k1::{SecretKey, PublicKey}; 701 | # #[cfg(not(feature = "secp256k1"))] 702 | # use musig2::secp::{Point as PublicKey, Scalar as SecretKey}; 703 | # use musig2::KeyAggContext; 704 | # 705 | # /// Same pubkeys as in previous example 706 | # let key_agg_ctx = 707 | # "0000000003026e14224899cf9c780fef5dd200f92a28cc67f71c0af6fe30b5657ffc943f08f402f3\ 708 | # b071c064f115ca762ed88c3efd1927ea657c7949698b77255ea25751331f0b03204ea8bc3425b2cb\ 709 | # c9cb20617f67dc6b202467591d0b26d059e370b71ee392eb" 710 | # .parse::() 711 | # .unwrap(); 712 | # 713 | # let signer_index = 2; 714 | # let seckey: SecretKey = "10e7721a3aa6de7a98cecdbd7c706c836a907ca46a43235a7b498b12498f98f0" 715 | # .parse() 716 | # .unwrap(); 717 | # 718 | # let aggregated_pubkey: PublicKey = key_agg_ctx.aggregated_pubkey(); 719 | # 720 | # // The group wants to sign something! 721 | # let message = "hello interwebz!"; 722 | use musig2::{FirstRound, SecNonceSpices}; 723 | 724 | # #[cfg(feature = "rand")] 725 | let mut first_round = FirstRound::new( 726 | key_agg_ctx, 727 | &mut rand::rng(), 728 | signer_index, 729 | SecNonceSpices::new() 730 | .with_seckey(seckey) 731 | .with_message(&message), 732 | ) 733 | .unwrap(); 734 | ``` 735 | 736 |
737 | 738 | If you decide to use the Functional API instead for any reason, **you must ensure your code is adequately protected against accidental nonce reuse.** 739 | 740 | ## Constant Time Operations 741 | 742 | All sensitive operations in this library endeavor to act in constant-time, independent of secret input. We mostly depend on the upstream [`k256`] and [`secp256k1`] crates for this functionality though, and no independent testing has confirmed this yet. 743 | --------------------------------------------------------------------------------