Skip to content

Commit e4c0829

Browse files
committed
Can sort pubkeys prior to aggregation in musig
1 parent 9529a9a commit e4c0829

File tree

3 files changed

+58
-29
lines changed

3 files changed

+58
-29
lines changed

NBitcoin.Tests/Secp256k1Tests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4038,7 +4038,7 @@ public void musig_det()
40384038
{
40394039
var keys = Enumerable.Range(0, 5).Select(k => new ECPrivKey(random_scalar_order(), ctx, true)).ToArray();
40404040
var pks = keys.Select(k => k.CreatePubKey()).ToArray();
4041-
var aggKeys = ECPubKey.MusigAggregate(pks, null);
4041+
var aggKeys = ECPubKey.MusigAggregate(pks);
40424042
var nonces = new MusigPrivNonce[keys.Length];
40434043
var msg = RandomUtils.GetBytes(32);
40444044

@@ -4522,6 +4522,7 @@ public void musig_key_sort_vectors()
45224522
var pubkeys2 = GetArray<string>(root["pubkeys"]).Select(p => ECPubKey.Create(Encoders.Hex.DecodeData(p))).ToArray();
45234523
var sorted_pubkeys2 = GetArray<string>(root["sorted_pubkeys"]).Select(p => ECPubKey.Create(Encoders.Hex.DecodeData(p))).ToArray();
45244524
Array.Sort(pubkeys2);
4525+
AssertEx.CollectionEquals(pubkeys2, sorted_pubkeys2);
45254526
}
45264527

45274528
[Fact]

NBitcoin/Secp256k1/Musig/ECPubKey.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,28 @@ internal static Scalar secp256k1_musig_keyaggcoef(MusigContext pre_session, ECPu
7474

7575
const string MusigTag = "KeyAgg coefficient";
7676

77-
public static ECPubKey MusigAggregate(ECPubKey[] pubkeys)
77+
/// <summary>
78+
/// Aggregate the public keys into a single one
79+
/// </summary>
80+
/// <param name="pubkeys">The public keys to aggregate</param>
81+
/// <param name="sort">If true, the pubkeys will be sorted before being aggregated</param>
82+
/// <returns></returns>
83+
public static ECPubKey MusigAggregate(ECPubKey[] pubkeys, bool sort = false)
7884
{
79-
return MusigAggregate(pubkeys, null);
85+
return MusigAggregate(pubkeys, null, sort);
8086
}
8187

82-
internal static ECPubKey MusigAggregate(ECPubKey[] pubkeys, MusigContext? preSession)
88+
internal static ECPubKey MusigAggregate(ECPubKey[] pubkeys, MusigContext? preSession, bool sort)
8389
{
8490
if (pubkeys == null)
8591
throw new ArgumentNullException(nameof(pubkeys));
8692
if (pubkeys.Length is 0)
8793
throw new ArgumentNullException(nameof(pubkeys), "At least one pubkey should be passed");
94+
if (sort)
95+
{
96+
pubkeys = pubkeys.ToArray();
97+
Array.Sort(pubkeys);
98+
}
8899
/* No point on the curve has an X coordinate equal to 0 */
89100
var second_pk_x = FE.Zero;
90101
for (int i = 1; i < pubkeys.Length; i++)

NBitcoin/Secp256k1/Musig/MusigContext.cs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,46 @@ class MusigContext
5454
private Context ctx;
5555
public ECPubKey? SigningPubKey { get; }
5656

57+
public MusigContext Clone()
58+
{
59+
return new MusigContext(this);
60+
}
61+
62+
63+
/// <inheritdoc cref="MusigContext.MusigContext(ECPubKey[], bool, ReadOnlySpan{byte}, ECPubKey?)"/>
64+
public MusigContext(ECPubKey[] pubkeys, ReadOnlySpan<byte> msg32, ECPubKey? signingPubKey = null)
65+
: this(pubkeys, false, msg32, signingPubKey)
66+
{
67+
}
68+
/// <summary>
69+
/// Create a new musig context
70+
/// </summary>
71+
/// <param name="pubkeys"><inheritdoc cref="ECPubKey.MusigAggregate(ECPubKey[], bool)" path="/param[@name='pubkeys']"/></param>
72+
/// <param name="sort"><inheritdoc cref="ECPubKey.MusigAggregate(ECPubKey[], bool)" path="/param[@name='sort']"/></param>
73+
/// <param name="msg32">The 32 bytes message to sign</param>
74+
/// <param name="signingPubKey">The pubkey of the key that will sign in this context</param>
75+
/// <exception cref="ArgumentNullException"></exception>
76+
/// <exception cref="ArgumentException"></exception>
77+
/// <exception cref="InvalidOperationException"></exception>
78+
public MusigContext(ECPubKey[] pubkeys, bool sort, ReadOnlySpan<byte> msg32, ECPubKey? signingPubKey = null)
79+
{
80+
if (pubkeys == null)
81+
throw new ArgumentNullException(nameof(pubkeys));
82+
if (pubkeys.Length is 0)
83+
throw new ArgumentException(nameof(pubkeys), "There should be at least one pubkey in pubKeys");
84+
if (signingPubKey != null && !pubkeys.Contains(signingPubKey))
85+
throw new InvalidOperationException("The pubkeys do not contain the signing public key");
86+
this.SigningPubKey = signingPubKey;
87+
this.aggregatePubKey = ECPubKey.MusigAggregate(pubkeys, this, sort);
88+
this.ctx = pubkeys[0].ctx;
89+
this.msg32 = msg32.ToArray();
90+
}
5791

92+
/// <summary>
93+
/// Clone a musig context
94+
/// </summary>
95+
/// <param name="musigContext"></param>
96+
/// <exception cref="ArgumentNullException"></exception>
5897
public MusigContext(MusigContext musigContext)
5998
{
6099
if (musigContext == null)
@@ -73,25 +112,6 @@ public MusigContext(MusigContext musigContext)
73112
SigningPubKey = musigContext.SigningPubKey;
74113
}
75114

76-
public MusigContext Clone()
77-
{
78-
return new MusigContext(this);
79-
}
80-
81-
public MusigContext(ECPubKey[] pubKeys, ReadOnlySpan<byte> msg32, ECPubKey? signingPubKey = null)
82-
{
83-
if (pubKeys == null)
84-
throw new ArgumentNullException(nameof(pubKeys));
85-
if (pubKeys.Length is 0)
86-
throw new ArgumentException(nameof(pubKeys), "There should be at least one pubkey in pubKeys");
87-
if (signingPubKey != null && !pubKeys.Contains(signingPubKey))
88-
throw new InvalidOperationException("The pubkeys do not contain the signing public key");
89-
this.SigningPubKey = signingPubKey;
90-
this.aggregatePubKey = ECPubKey.MusigAggregate(pubKeys, this);
91-
this.ctx = pubKeys[0].ctx;
92-
this.msg32 = msg32.ToArray();
93-
}
94-
95115
/// <summary>
96116
/// Add tweak to the xonly aggregated pubkey
97117
/// </summary>
@@ -275,12 +295,9 @@ public SecpSchnorrSignature AggregateSignatures(MusigPartialSignature[] partialS
275295
/// <summary>
276296
/// <inheritdoc cref="DeterministicSign(ECPrivKey, byte[])"/>
277297
/// </summary>
278-
/// <param name="privKey"><inheritdoc cref="DeterministicSign(ECPrivKey, byte[])" path="/param/[@name='privKey']"></inheritdoc>/></param>
298+
/// <param name="privKey"><inheritdoc cref="DeterministicSign(ECPrivKey, byte[])" path="/param[@name='privKey']"></inheritdoc></param>
279299
/// <returns></returns>
280-
public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey)
281-
{
282-
return DeterministicSign(privKey, null);
283-
}
300+
public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey) => DeterministicSign(privKey, null);
284301

285302
/// <summary>
286303
/// <para>Generates a deterministic nonce and sign with it.</para>
@@ -294,7 +311,7 @@ public SecpSchnorrSignature AggregateSignatures(MusigPartialSignature[] partialS
294311
/// <returns>The partial signature with the deterministic public nonce of this signer</returns>
295312
/// <exception cref="ArgumentNullException"></exception>
296313
/// <exception cref="InvalidOperationException"></exception>
297-
public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey, byte[]? rand = null)
314+
public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey, byte[]? rand)
298315
{
299316
var nonce = GenerateDeterministicNonce(privKey, rand);
300317
return (Sign(privKey, nonce), nonce.CreatePubNonce());

0 commit comments

Comments
 (0)