diff --git a/NBitcoin.Tests/Secp256k1Tests.cs b/NBitcoin.Tests/Secp256k1Tests.cs index f94ebabaa..c30223074 100644 --- a/NBitcoin.Tests/Secp256k1Tests.cs +++ b/NBitcoin.Tests/Secp256k1Tests.cs @@ -4038,7 +4038,7 @@ public void musig_det() { var keys = Enumerable.Range(0, 5).Select(k => new ECPrivKey(random_scalar_order(), ctx, true)).ToArray(); var pks = keys.Select(k => k.CreatePubKey()).ToArray(); - var aggKeys = ECPubKey.MusigAggregate(pks, null); + var aggKeys = ECPubKey.MusigAggregate(pks); var nonces = new MusigPrivNonce[keys.Length]; var msg = RandomUtils.GetBytes(32); @@ -4522,6 +4522,7 @@ public void musig_key_sort_vectors() var pubkeys2 = GetArray(root["pubkeys"]).Select(p => ECPubKey.Create(Encoders.Hex.DecodeData(p))).ToArray(); var sorted_pubkeys2 = GetArray(root["sorted_pubkeys"]).Select(p => ECPubKey.Create(Encoders.Hex.DecodeData(p))).ToArray(); Array.Sort(pubkeys2); + AssertEx.CollectionEquals(pubkeys2, sorted_pubkeys2); } [Fact] diff --git a/NBitcoin/Secp256k1/Musig/ECPubKey.cs b/NBitcoin/Secp256k1/Musig/ECPubKey.cs index 882262924..3ce0b6e1b 100644 --- a/NBitcoin/Secp256k1/Musig/ECPubKey.cs +++ b/NBitcoin/Secp256k1/Musig/ECPubKey.cs @@ -74,17 +74,28 @@ internal static Scalar secp256k1_musig_keyaggcoef(MusigContext pre_session, ECPu const string MusigTag = "KeyAgg coefficient"; - public static ECPubKey MusigAggregate(ECPubKey[] pubkeys) + /// + /// Aggregate the public keys into a single one + /// + /// The public keys to aggregate + /// If true, the pubkeys will be sorted before being aggregated + /// + public static ECPubKey MusigAggregate(ECPubKey[] pubkeys, bool sort = false) { - return MusigAggregate(pubkeys, null); + return MusigAggregate(pubkeys, null, sort); } - internal static ECPubKey MusigAggregate(ECPubKey[] pubkeys, MusigContext? preSession) + internal static ECPubKey MusigAggregate(ECPubKey[] pubkeys, MusigContext? preSession, bool sort) { if (pubkeys == null) throw new ArgumentNullException(nameof(pubkeys)); if (pubkeys.Length is 0) throw new ArgumentNullException(nameof(pubkeys), "At least one pubkey should be passed"); + if (sort) + { + pubkeys = pubkeys.ToArray(); + Array.Sort(pubkeys); + } /* No point on the curve has an X coordinate equal to 0 */ var second_pk_x = FE.Zero; for (int i = 1; i < pubkeys.Length; i++) diff --git a/NBitcoin/Secp256k1/Musig/MusigContext.cs b/NBitcoin/Secp256k1/Musig/MusigContext.cs index 345cbd368..7f1519c58 100644 --- a/NBitcoin/Secp256k1/Musig/MusigContext.cs +++ b/NBitcoin/Secp256k1/Musig/MusigContext.cs @@ -54,7 +54,46 @@ class MusigContext private Context ctx; public ECPubKey? SigningPubKey { get; } + public MusigContext Clone() + { + return new MusigContext(this); + } + + + /// + public MusigContext(ECPubKey[] pubkeys, ReadOnlySpan msg32, ECPubKey? signingPubKey = null) + : this(pubkeys, false, msg32, signingPubKey) + { + } + /// + /// Create a new musig context + /// + /// + /// + /// The 32 bytes message to sign + /// The pubkey of the key that will sign in this context + /// + /// + /// + public MusigContext(ECPubKey[] pubkeys, bool sort, ReadOnlySpan msg32, ECPubKey? signingPubKey = null) + { + if (pubkeys == null) + throw new ArgumentNullException(nameof(pubkeys)); + if (pubkeys.Length is 0) + throw new ArgumentException(nameof(pubkeys), "There should be at least one pubkey in pubKeys"); + if (signingPubKey != null && !pubkeys.Contains(signingPubKey)) + throw new InvalidOperationException("The pubkeys do not contain the signing public key"); + this.SigningPubKey = signingPubKey; + this.aggregatePubKey = ECPubKey.MusigAggregate(pubkeys, this, sort); + this.ctx = pubkeys[0].ctx; + this.msg32 = msg32.ToArray(); + } + /// + /// Clone a musig context + /// + /// + /// public MusigContext(MusigContext musigContext) { if (musigContext == null) @@ -73,25 +112,6 @@ public MusigContext(MusigContext musigContext) SigningPubKey = musigContext.SigningPubKey; } - public MusigContext Clone() - { - return new MusigContext(this); - } - - public MusigContext(ECPubKey[] pubKeys, ReadOnlySpan msg32, ECPubKey? signingPubKey = null) - { - if (pubKeys == null) - throw new ArgumentNullException(nameof(pubKeys)); - if (pubKeys.Length is 0) - throw new ArgumentException(nameof(pubKeys), "There should be at least one pubkey in pubKeys"); - if (signingPubKey != null && !pubKeys.Contains(signingPubKey)) - throw new InvalidOperationException("The pubkeys do not contain the signing public key"); - this.SigningPubKey = signingPubKey; - this.aggregatePubKey = ECPubKey.MusigAggregate(pubKeys, this); - this.ctx = pubKeys[0].ctx; - this.msg32 = msg32.ToArray(); - } - /// /// Add tweak to the xonly aggregated pubkey /// @@ -275,12 +295,9 @@ public SecpSchnorrSignature AggregateSignatures(MusigPartialSignature[] partialS /// /// /// - /// /> + /// /// - public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey) - { - return DeterministicSign(privKey, null); - } + public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey) => DeterministicSign(privKey, null); /// /// Generates a deterministic nonce and sign with it. @@ -294,7 +311,7 @@ public SecpSchnorrSignature AggregateSignatures(MusigPartialSignature[] partialS /// The partial signature with the deterministic public nonce of this signer /// /// - public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey, byte[]? rand = null) + public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey, byte[]? rand) { var nonce = GenerateDeterministicNonce(privKey, rand); return (Sign(privKey, nonce), nonce.CreatePubNonce());