Skip to content

Commit

Permalink
Musig: Simplfy implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasDorier committed Dec 22, 2024
1 parent d89e681 commit 9529a9a
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 42 deletions.
2 changes: 1 addition & 1 deletion NBitcoin.Tests/Secp256k1Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3987,7 +3987,7 @@ public void musig_tweaked_test()
var n2 = musig.GenerateNonce();
Assert.NotEqual(Encoders.Hex.EncodeData(n1.CreatePubNonce().ToBytes()), Encoders.Hex.EncodeData(n2.CreatePubNonce().ToBytes()));
//

musig = new MusigContext(ecPubKeys, msg32);
nonces = ecPrivateKeys.Select(c => musig.GenerateNonce(c)).ToArray();
musig.Tweak(treeInfo.OutputPubKey.Tweak.Span);
musig.ProcessNonces(nonces.Select(n => n.CreatePubNonce()).ToArray());
Expand Down
74 changes: 33 additions & 41 deletions NBitcoin/Secp256k1/Musig/MusigContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,14 @@ class MusigContext
internal Scalar scalar_tweak;
internal readonly byte[] msg32;
internal Scalar gacc;
internal bool processed_nonce;
internal SessionValues? SessionCache;
SessionValues GetSessionCacheOrThrow() => SessionCache ?? throw InvalidOperationCallNonceProcess();

private MusigPubNonce? aggregateNonce;
MusigPubNonce GetAggregateNonceOrThrow() => aggregateNonce ?? throw InvalidOperationCallNonceProcess();

private static Exception InvalidOperationCallNonceProcess() => new InvalidOperationException("You need to run MusigContext.Process or MusigContext.ProcessNonces first");

public MusigPubNonce? AggregateNonce => aggregateNonce;
public ECPubKey AggregatePubKey => aggregatePubKey;

Expand All @@ -61,7 +65,6 @@ public MusigContext(MusigContext musigContext)
scalar_tweak = musigContext.scalar_tweak;
gacc = musigContext.gacc;
tacc = musigContext.tacc;
processed_nonce = musigContext.processed_nonce;
SessionCache = musigContext.SessionCache?.Clone();
aggregateNonce = musigContext.aggregateNonce;
aggregatePubKey = musigContext.aggregatePubKey;
Expand Down Expand Up @@ -111,8 +114,7 @@ public ECPubKey Tweak(ReadOnlySpan<byte> tweak32)
/// <exception cref="ArgumentException"></exception>
public ECPubKey Tweak(ReadOnlySpan<byte> tweak32, bool xOnly)
{
if (processed_nonce)
throw new InvalidOperationException("This function can only be called before MusigContext.Process");
AssertNonceNotProcessed();
if (tweak32.Length != 32)
throw new ArgumentException(nameof(tweak32), "The tweak should have a size of 32 bytes");
scalar_tweak = new Scalar(tweak32, out int overflow);
Expand Down Expand Up @@ -140,21 +142,23 @@ public ECPubKey Tweak(ReadOnlySpan<byte> tweak32, bool xOnly)
/// <exception cref="InvalidOperationException"></exception>
public void UseAdaptor(ECPubKey adaptor)
{
if (processed_nonce)
throw new InvalidOperationException("This function can only be called before MusigContext.Process");
AssertNonceNotProcessed();
this.adaptor = adaptor;
}

private void AssertNonceNotProcessed()
{
if (SessionCache is not null)
throw new InvalidOperationException("This function can only be called before MusigContext.Process or MusigContext.ProcessNonces");
}

public void ProcessNonces(MusigPubNonce[] nonces)
{
Process(MusigPubNonce.Aggregate(nonces));
}
public void Process(MusigPubNonce aggregatedNonce)
{
if (processed_nonce)
throw new InvalidOperationException($"Nonce already processed");
var q = this.AggregatePubKey;

SessionValues session_cache = new SessionValues();
Span<byte> qbytes = stackalloc byte[32];
Span<GEJ> aggnonce_ptj = stackalloc GEJ[2];
Expand Down Expand Up @@ -183,7 +187,6 @@ public void Process(MusigPubNonce aggregatedNonce)
session_cache.r = r;

SessionCache = session_cache;
processed_nonce = true;
this.aggregateNonce = aggregatedNonce;
}

Expand Down Expand Up @@ -237,38 +240,36 @@ public bool Verify(ECPubKey pubKey, MusigPubNonce pubNonce, MusigPartialSignatur
throw new ArgumentNullException(nameof(pubNonce));
if (pubKey == null)
throw new ArgumentNullException(nameof(pubKey));
if (SessionCache is null)
throw new InvalidOperationException("You need to run MusigContext.Process first");
var sessionCache = GetSessionCacheOrThrow();

var s = partialSignature.E;
var R_s1 = pubNonce.K1;
var R_s2 = pubNonce.K2;
var Re_s_ = ctx.EcMultContext.Mult(R_s2.ToGroupElementJacobian(), SessionCache.b, null).AddVariable(R_s1).ToGroupElement().NormalizeVariable();
var Re_s = SessionCache.r.y.IsOdd ? Re_s_.Negate() : Re_s_;
var Re_s_ = ctx.EcMultContext.Mult(R_s2.ToGroupElementJacobian(), sessionCache.b, null).AddVariable(R_s1).ToGroupElement().NormalizeVariable();
var Re_s = sessionCache.r.y.IsOdd ? Re_s_.Negate() : Re_s_;
var P = pubKey.Q;
var a = ECPubKey.secp256k1_musig_keyaggcoef(this, pubKey);
var g = aggregatePubKey.Q.y.IsOdd ? Scalar.MinusOne : Scalar.One;
var g_ = g * gacc;

/* Compute -s*G + e*pkj + rj */
var res = ctx.EcMultContext.Mult(P.ToGroupElementJacobian(), SessionCache.e * a * g_, s.Negate()).AddVariable(Re_s);
var res = ctx.EcMultContext.Mult(P.ToGroupElementJacobian(), sessionCache.e * a * g_, s.Negate()).AddVariable(Re_s);
return res.IsInfinity;
}

public SecpSchnorrSignature AggregateSignatures(MusigPartialSignature[] partialSignatures)
{
if (partialSignatures == null)
throw new ArgumentNullException(nameof(partialSignatures));
if (this.SessionCache is null)
throw new InvalidOperationException("You need to run MusigContext.Process first");
var sessionCache = GetSessionCacheOrThrow();
var s = Scalar.Zero;
foreach (var sig in partialSignatures)
{
s = s + sig.E;
}
var g = pk_parity ? Scalar.MinusOne : Scalar.One;
s = s + g * SessionCache.e * tacc;
return new SecpSchnorrSignature(this.SessionCache.r.x, s);
s = s + g * sessionCache.e * tacc;
return new SecpSchnorrSignature(sessionCache.r.x, s);
}

/// <summary>
Expand Down Expand Up @@ -313,12 +314,10 @@ public SecpSchnorrSignature AggregateSignatures(MusigPartialSignature[] partialS
/// <exception cref="ArgumentNullException"></exception>
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");
if (privKey is null)
throw new ArgumentNullException(nameof(privKey));
if (SessionCache is null || aggregateNonce is null)
throw new InvalidOperationException("You need to run MusigContext.Process first");
var sessionCache = GetSessionCacheOrThrow();
var aggregateNonce = GetAggregateNonceOrThrow();

var sk_ = rand is null ? privKey.sec.ToBytes() : xor32(privKey.sec.ToBytes(), tagged_hash("MuSig/aux", rand));
var aggothernonce = aggregateNonce;
Expand All @@ -331,7 +330,6 @@ public MusigPrivNonce GenerateDeterministicNonce(ECPrivKey privKey, byte[]? rand

var secnonce = new MusigPrivNonce(new ECPrivKey(k_1, ctx, false), new ECPrivKey(k_2, ctx, false), privKey.CreatePubKey());
var pubnonce = secnonce.CreatePubNonce();
processed_nonce = false;
ProcessNonces(new[] { pubnonce, aggregateNonce });
return secnonce;
}
Expand Down Expand Up @@ -383,17 +381,6 @@ public MusigPartialSignature Sign(ECPrivKey privKey, MusigPrivNonce privNonce)
if (SessionCache is null)
throw new InvalidOperationException("You need to run MusigContext.Process first");

//{
// /* Check in constant time if secnonce has been zeroed. */
// size_t i;
// unsigned char secnonce_acc = 0;
// for (i = 0; i < sizeof(*secnonce) ; i++) {
// secnonce_acc |= secnonce->data[i];
// }
// secp256k1_declassify(ctx, &secnonce_acc, sizeof(secnonce_acc));
// ARG_CHECK(secnonce_acc != 0);
//}

var pre_session = this;
var session_cache = SessionCache;
Span<Scalar> k = stackalloc Scalar[2];
Expand Down Expand Up @@ -465,13 +452,12 @@ public SecpSchnorrSignature Adapt(SecpSchnorrSignature signature, ECPrivKey adap
throw new ArgumentNullException(nameof(adaptorSecret));
if (signature == null)
throw new ArgumentNullException(nameof(signature));
if (!processed_nonce || SessionCache is null)
throw new InvalidOperationException("You need to run MusigContext.Process first");
var sessionCache = GetSessionCacheOrThrow();
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)
if (sessionCache.r.y.IsOdd)
{
t = t.Negate();
}
Expand Down Expand Up @@ -522,7 +508,7 @@ 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));
sessionId[i] = (byte)(counter >> (8 * i));
return GenerateNonce(sessionId, signingKey, null);
}

Expand All @@ -541,8 +527,14 @@ public MusigPrivNonce GenerateNonce(ulong counter, ECPrivKey signingKey)
/// <returns>A private nonce whose public part intended to be sent to other signers</returns>
public MusigPrivNonce GenerateNonce(byte[]? sessionId, ECPrivKey? signingKey, byte[]? 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);
ECPubKey pk = (signingKey?.CreatePubKey(), SigningPubKey) switch
{
({ } k1, null) => k1,
(null, { } k2) => k2,
({ } k1, { } k2) => k1 == k2 ? k1 : throw new ArgumentException(paramName: nameof(signingKey), message: "The signing pubkey passed in the context's constructor doesn't match the public key of the signingKey"),
(null, null) => throw new ArgumentNullException(paramName: nameof(signingKey), message: "Either the signing pubkey should be passed in the context's constructor or the signingKey should be passed"),
};
return MusigPrivNonce.GenerateMusigNonce(pk, ctx, sessionId, signingKey, this.msg32, this.aggregatePubKey.ToXOnlyPubKey(), extraInput);
}
}
}
Expand Down

0 comments on commit 9529a9a

Please sign in to comment.