From d89e681128ef4731275b7a888d121bcb8c1a79e8 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 22 Dec 2024 13:16:49 +0900 Subject: [PATCH] Improve documentation for musig --- NBitcoin.Tests/Secp256k1Tests.cs | 12 +-- NBitcoin/Secp256k1/Musig/MusigContext.cs | 118 +++++++++++++---------- 2 files changed, 75 insertions(+), 55 deletions(-) diff --git a/NBitcoin.Tests/Secp256k1Tests.cs b/NBitcoin.Tests/Secp256k1Tests.cs index 70c8967f2..0f3440bfc 100644 --- a/NBitcoin.Tests/Secp256k1Tests.cs +++ b/NBitcoin.Tests/Secp256k1Tests.cs @@ -3962,7 +3962,7 @@ public void musig_tweaked_test() var ecPubKeys = ecPrivateKeys.Select(c => c.CreatePubKey()).ToArray(); var musig = new MusigContext(ecPubKeys, msg32); - var nonces = ecPubKeys.Select(c => musig.GenerateNonce(c)).ToArray(); + var nonces = ecPrivateKeys.Select(c => musig.GenerateNonce(c)).ToArray(); var aggregatedKey = ECPubKey.MusigAggregate(ecPubKeys); @@ -3980,15 +3980,15 @@ public void musig_tweaked_test() var builder = new TaprootBuilder(); // Add the scripts there var treeInfo = builder.Finalize(new TaprootInternalPubKey(aggregatedKey.ToXOnlyPubKey().ToBytes())); - musig = new MusigContext(ecPubKeys, msg32); + musig = new MusigContext(ecPubKeys, msg32, ecPubKeys[0]); // Sanity check that GenerateNonce do not reuse nonces - var n1 = musig.GenerateNonce(ecPubKeys[0]); - var n2 = musig.GenerateNonce(ecPubKeys[0]); + var n1 = musig.GenerateNonce(); + var n2 = musig.GenerateNonce(); Assert.NotEqual(Encoders.Hex.EncodeData(n1.CreatePubNonce().ToBytes()), Encoders.Hex.EncodeData(n2.CreatePubNonce().ToBytes())); // - nonces = ecPubKeys.Select(c => musig.GenerateNonce(c)).ToArray(); + nonces = ecPrivateKeys.Select(c => musig.GenerateNonce(c)).ToArray(); musig.Tweak(treeInfo.OutputPubKey.Tweak.Span); musig.ProcessNonces(nonces.Select(n => n.CreatePubNonce()).ToArray()); sigs = ecPrivateKeys.Select((c, i) => musig.Sign(c, nonces[i])).ToArray(); @@ -4048,7 +4048,7 @@ public void musig_det() if (i == detSigner) continue; var musig = new MusigContext(pks, msg, pks[i]); - nonces[i] = musig.GenerateNonce(pks[i]); + nonces[i] = musig.GenerateNonce(); } var sigs = new MusigPartialSignature[keys.Length]; diff --git a/NBitcoin/Secp256k1/Musig/MusigContext.cs b/NBitcoin/Secp256k1/Musig/MusigContext.cs index 233a12a70..588f9cb9a 100644 --- a/NBitcoin/Secp256k1/Musig/MusigContext.cs +++ b/NBitcoin/Secp256k1/Musig/MusigContext.cs @@ -48,6 +48,7 @@ class MusigContext private ECPubKey aggregatePubKey; private Context ctx; + public ECPubKey? SigningPubKey { get; } public MusigContext(MusigContext musigContext) @@ -66,6 +67,7 @@ public MusigContext(MusigContext musigContext) aggregatePubKey = musigContext.aggregatePubKey; ctx = musigContext.ctx; msg32 = musigContext.msg32; + SigningPubKey = musigContext.SigningPubKey; } public MusigContext Clone() @@ -81,6 +83,7 @@ public MusigContext(ECPubKey[] pubKeys, ReadOnlySpan msg32, ECPubKey? sign 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(); @@ -130,6 +133,11 @@ public ECPubKey Tweak(ReadOnlySpan tweak32, bool xOnly) ECPubKey? adaptor; private Scalar tacc = Scalar.Zero; + /// + /// The aggregated signature will need to be adapted by the adaptor secret to be valid signature for the message. + /// + /// The adaptor + /// public void UseAdaptor(ECPubKey adaptor) { if (processed_nonce) @@ -274,15 +282,36 @@ public SecpSchnorrSignature AggregateSignatures(MusigPartialSignature[] partialS } /// - /// To deterministically sign, you need to call or with the nonces of all other participants. - /// See the BIP for more information about deterministic signer. + /// Generates a deterministic nonce and sign with it. + /// You need to call or with the nonces of all other participants. + /// After calling , the generated nonce is processed with the other nonces of this . + /// You need to share the public nonce with other signers before they can sign. + /// See BIP0327 for more information about deterministic signer. /// /// The private key of the stateless signer - /// An optional random data - /// The partial signature with the derived public nonce of this signer + /// Additional entropy + /// The partial signature with the deterministic public nonce of this signer /// /// - public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey, byte[]? rand) + public (MusigPartialSignature Signature, MusigPubNonce PubNonce) DeterministicSign(ECPrivKey privKey, byte[]? rand = null) + { + var nonce = GenerateDeterministicNonce(privKey, rand); + return (Sign(privKey, nonce), nonce.CreatePubNonce()); + } + + /// + /// Generates a deterministic nonce for a signer. + /// You need to call or with the nonces of all other participants. + /// After calling , the generated nonce is processed with the other nonces of this , so you can proceed to sign with + /// You need to share the public nonce with other signers before they can sign. + /// See BIP0327 for more information about deterministic signer. + /// + /// + /// Additional entropy + /// The deterministic private nonce of this signer + /// + /// + public MusigPrivNonce GenerateDeterministicNonce(ECPrivKey privKey, byte[]? rand = null) { if (!processed_nonce) throw new InvalidOperationException("You need to call Process or ProcessNonces with the nonces of all other participants"); @@ -304,7 +333,7 @@ public SecpSchnorrSignature AggregateSignatures(MusigPartialSignature[] partialS var pubnonce = secnonce.CreatePubNonce(); processed_nonce = false; ProcessNonces(new[] { pubnonce, aggregateNonce }); - return (Sign(privKey, secnonce), pubnonce); + return secnonce; } private Scalar det_nonce_hash(byte[] sk_, MusigPubNonce aggothernonce, ECPubKey aggregatePubKey, ReadOnlySpan msg, int i) @@ -422,6 +451,14 @@ public MusigPartialSignature Sign(ECPrivKey privKey, MusigPrivNonce privNonce) return sig; } + /// + /// Adapt a with the (ie. private key of the key passed to ) to be a valid signature for the message. + /// + /// The published signature + /// The adaptor secret retrieved by + /// A valid signature for the message + /// + /// public SecpSchnorrSignature Adapt(SecpSchnorrSignature signature, ECPrivKey adaptorSecret) { if (adaptorSecret == null) @@ -430,6 +467,8 @@ public SecpSchnorrSignature Adapt(SecpSchnorrSignature signature, ECPrivKey adap throw new ArgumentNullException(nameof(signature)); if (!processed_nonce || SessionCache is null) throw new InvalidOperationException("You need to run MusigContext.Process first"); + if (adaptor != null && adaptor != adaptorSecret.CreatePubKey()) + throw new ArgumentException("The adaptor secret is not the one used in UseAdaptor", paramName: nameof(adaptorSecret)); var s = signature.s; var t = adaptorSecret.sec; if (SessionCache.r.y.IsOdd) @@ -440,6 +479,14 @@ public SecpSchnorrSignature Adapt(SecpSchnorrSignature signature, ECPrivKey adap return new SecpSchnorrSignature(signature.rx, s); } + /// + /// Extract adaptor secret from and . + /// + /// Published signature + /// Partial signatures + /// The adaptor + /// + /// public ECPrivKey Extract(SecpSchnorrSignature signature, MusigPartialSignature[] partialSignatures) { if (partialSignatures == null) @@ -459,35 +506,15 @@ public ECPrivKey Extract(SecpSchnorrSignature signature, MusigPartialSignature[] return new ECPrivKey(t, this.ctx, true); } - /// - /// This function derives a random secret nonce that will be required for signing and - /// creates a private nonce whose public part intended to be sent to other signers. - /// - /// The individual signing public key (see BIP Modifications to Nonce Generation for the reason that this argument is mandatory) - /// A private nonce whose public part intended to be sent to other signers - public MusigPrivNonce GenerateNonce(ECPubKey signingPubKey) - { - return GenerateNonce(signingPubKey, null); - } - /// - /// This function derives a secret nonce that will be required for signing and - /// creates a private nonce whose public part intended to be sent to other signers. - /// - /// The individual signing public key (see BIP Modifications to Nonce Generation for the reason that this argument is mandatory) - /// A unique session_id. It is a "number used once". If null, it will be randomly generated. - /// A private nonce whose public part intended to be sent to other signers - public MusigPrivNonce GenerateNonce(ECPubKey signingPubKey, byte[]? sessionId) - { - return GenerateNonce(signingPubKey, sessionId, null, null); - } + /// + public MusigPrivNonce GenerateNonce() => GenerateNonce(null, null, null); - /// - /// This function derives a secret nonce that will be required for signing and - /// creates a private nonce whose public part intended to be sent to other signers. - /// + /// + public MusigPrivNonce GenerateNonce(byte[]? sessionId) => GenerateNonce(sessionId, null, null); + + + /// /// A unique counter. Never reuse the same value twice for the same msg32/pubkeys. - /// Provide the message to be signed to increase misuse-resistance. If you do provide a signingKey, sessionId32 can instead be a counter (that must never repeat!). However, it is recommended to always choose session_id32 uniformly at random. Can be null. - /// A private nonce whose public part intended to be sent to other signers public MusigPrivNonce GenerateNonce(ulong counter, ECPrivKey signingKey) { if (signingKey == null) @@ -496,33 +523,26 @@ public MusigPrivNonce GenerateNonce(ulong counter, ECPrivKey signingKey) byte[] sessionId = new byte[32]; for (int i = 0; i < 8; i++) sessionId[i] = (byte)(counter >> (8*i)); - return GenerateNonce(signingKey.CreatePubKey(), sessionId, signingKey, Array.Empty()); + return GenerateNonce(sessionId, signingKey, null); } + /// + public MusigPrivNonce GenerateNonce(byte[]? sessionId, ECPrivKey? signingKey) => GenerateNonce(sessionId, signingKey, null); + + /// + public MusigPrivNonce GenerateNonce(ECPrivKey? signingKey) => GenerateNonce(null, signingKey, null); /// /// This function derives a secret nonce that will be required for signing and /// creates a private nonce whose public part intended to be sent to other signers. /// - /// The individual signing public key (see BIP Modifications to Nonce Generation for the reason that this argument is mandatory) - /// A unique session_id32. It is a "number used once". If null, it will be randomly generated. - /// Provide the message to be signed to increase misuse-resistance. If you do provide a signingKey, sessionId32 can instead be a counter (that must never repeat!). However, it is recommended to always choose session_id32 uniformly at random. Can be null. - /// A private nonce whose public part intended to be sent to other signers - public MusigPrivNonce GenerateNonce(ECPubKey signingPubKey, byte[]? sessionId, ECPrivKey? signingKey) - { - return GenerateNonce(signingPubKey, sessionId, signingKey, Array.Empty()); - } - /// - /// This function derives a secret nonce that will be required for signing and - /// creates a private nonce whose public part intended to be sent to other signers. - /// - /// The individual signing public key (see BIP Modifications to Nonce Generation for the reason that this argument is mandatory) /// A unique session_id. It is a "number used once". If null, it will be randomly generated. /// Provide the message to be signed to increase misuse-resistance. If you do provide a signingKey, sessionId32 can instead be a counter (that must never repeat!). However, it is recommended to always choose session_id32 uniformly at random. Can be null. /// Provide the message to be signed to increase misuse-resistance. The extra_input32 argument can be used to provide additional data that does not repeat in normal scenarios, such as the current time. Can be null. /// A private nonce whose public part intended to be sent to other signers - public MusigPrivNonce GenerateNonce(ECPubKey signingPubKey, byte[]? sessionId, ECPrivKey? signingKey, byte[]? extraInput) + public MusigPrivNonce GenerateNonce(byte[]? sessionId, ECPrivKey? signingKey, byte[]? extraInput) { - return MusigPrivNonce.GenerateMusigNonce(signingPubKey, ctx, sessionId, signingKey, this.msg32, this.aggregatePubKey.ToXOnlyPubKey(), extraInput); + var pubkey = signingKey?.CreatePubKey() ?? SigningPubKey ?? throw new InvalidOperationException("SigningPubKey or signingKey isn't passed to this context"); + return MusigPrivNonce.GenerateMusigNonce(pubkey, ctx, sessionId, signingKey, this.msg32, this.aggregatePubKey.ToXOnlyPubKey(), extraInput); } } }