1+ package fr.acinq.bitcoin.crypto.musig2
2+
3+ import fr.acinq.bitcoin.*
4+ import fr.acinq.secp256k1.Hex
5+ import fr.acinq.secp256k1.Secp256k1
6+ import kotlin.jvm.JvmStatic
7+
8+ /* *
9+ * Musig2 key aggregation cache
10+ * Keeps track of an aggregate of public keys, that can optionally be tweaked
11+ */
12+ public data class KeyAggCache (val data : ByteVector ) {
13+ public constructor (data: ByteArray ) : this (data.byteVector())
14+
15+ init {
16+ require(data.size() == Secp256k1 .MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE ) { " musig2 keyagg cache must be ${Secp256k1 .MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE } bytes" }
17+ }
18+
19+ public fun toByteArray (): ByteArray = data.toByteArray()
20+
21+ /* *
22+ * @param tweak tweak to apply
23+ * @param isXonly true if the tweak is an x-only tweak
24+ * @return an updated cache, and the tweaked aggregated public key
25+ */
26+ public fun tweak (tweak : ByteVector32 , isXonly : Boolean ): Pair <KeyAggCache , PublicKey > {
27+ val localCache = toByteArray()
28+ val tweaked = if (isXonly) {
29+ Secp256k1 .musigPubkeyXonlyTweakAdd(localCache, tweak.toByteArray())
30+ } else {
31+ Secp256k1 .musigPubkeyTweakAdd(localCache, tweak.toByteArray())
32+ }
33+ return Pair (KeyAggCache (localCache), PublicKey .parse(tweaked))
34+ }
35+
36+ public companion object {
37+ /* *
38+ * @param pubkeys public keys to aggregate
39+ * @param cache an optional key aggregation cache
40+ * @return a new (if cache was null) or updated cache, and the aggregated public key
41+ */
42+ @JvmStatic
43+ public fun add (pubkeys : List <PublicKey >, cache : KeyAggCache ? ): Pair <XonlyPublicKey , KeyAggCache > {
44+ val localCache = cache?.data?.toByteArray() ? : ByteArray (Secp256k1 .MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE )
45+ val aggkey = Secp256k1 .musigPubkeyAdd(pubkeys.map { it.value.toByteArray() }.toTypedArray(), localCache)
46+ return Pair (XonlyPublicKey (aggkey.byteVector32()), KeyAggCache (localCache.byteVector()))
47+ }
48+ }
49+ }
50+
51+ /* *
52+ * Musig2 signing session
53+ */
54+ public data class Session (val data : ByteVector ) {
55+ init {
56+ require(data.size() == Secp256k1 .MUSIG2_PUBLIC_SESSION_SIZE ) { " musig2 session must be ${Secp256k1 .MUSIG2_PUBLIC_SESSION_SIZE } bytes" }
57+ }
58+
59+ public fun toByteArray (): ByteArray = data.toByteArray()
60+
61+ /* *
62+ * @param secretNonce secret nonce
63+ * @param pk private key
64+ * @param aggCache key aggregation cache
65+ * @return a Musig2 partial signature
66+ */
67+ public fun sign (secretNonce : SecretNonce , pk : PrivateKey , aggCache : KeyAggCache ): ByteVector32 {
68+ return Secp256k1 .musigPartialSign(secretNonce.data.toByteArray(), pk.value.toByteArray(), aggCache.data.toByteArray(), toByteArray()).byteVector32()
69+ }
70+
71+ /* *
72+ * @param psig musig2 partial signature
73+ * @param pubnonce public nonce, that must match the secret nonce psig was generated with
74+ * @param pubkey public key, that must match the private key psig was generated with
75+ * @param cache key aggregation cache
76+ * @return true if the partial signature is valid
77+ */
78+ public fun verify (psig : ByteVector32 , pubnonce : IndividualNonce , pubkey : PublicKey , cache : KeyAggCache ): Boolean {
79+ return Secp256k1 .musigPartialSigVerify(psig.toByteArray(), pubnonce.toByteArray(), pubkey.value.toByteArray(), cache.data.toByteArray(), toByteArray()) == 1
80+ }
81+
82+ /* *
83+ * @param psigs partial signatures
84+ * @return the aggregate of all input partial signatures
85+ */
86+ public fun add (psigs : List <ByteVector32 >): ByteVector64 {
87+ return Secp256k1 .musigPartialSigAgg(toByteArray(), psigs.map { it.toByteArray() }.toTypedArray()).byteVector64()
88+ }
89+
90+ public companion object {
91+ /* *
92+ * @param aggregatedNonce aggregated public nonce
93+ * @param msg message to sign
94+ * @param cache key aggregation cache
95+ * @return a Musig signing session
96+ */
97+ @JvmStatic
98+ public fun build (aggregatedNonce : AggregatedNonce , msg : ByteVector32 , cache : KeyAggCache ): Session {
99+ val session = Secp256k1 .musigNonceProcess(aggregatedNonce.toByteArray(), msg.toByteArray(), cache.data.toByteArray(), null )
100+ return Session (session.byteVector())
101+ }
102+ }
103+ }
104+
105+ /* *
106+ * Musig2 secret nonce. Not meant to be reused !!
107+ */
108+ public data class SecretNonce (val data : ByteVector ) {
109+ public constructor (bin: ByteArray ) : this (bin.byteVector())
110+
111+ public constructor (hex: String ) : this (Hex .decode(hex))
112+
113+ init {
114+ require(data.size() == Secp256k1 .MUSIG2_SECRET_NONCE_SIZE ) { " musig2 secret nonce must be ${Secp256k1 .MUSIG2_SECRET_NONCE_SIZE } bytes" }
115+ }
116+
117+ public companion object {
118+ /* *
119+ * @param sessionId random session id. Must not be reused !!
120+ * @param seckey optional private key
121+ * @param pubkey public key
122+ * @param msg optional message to sign
123+ * @param cache optional key aggregation cache
124+ * @param extraInput optional extra input value
125+ * @return a (secret nonce, public nonce) tuple
126+ */
127+ @JvmStatic
128+ public fun generate (sessionId : ByteVector32 , seckey : PrivateKey ? , pubkey : PublicKey , msg : ByteVector32 ? , cache : KeyAggCache ? , extraInput : ByteVector32 ? ): Pair <SecretNonce , IndividualNonce > {
129+ val nonce = Secp256k1 .musigNonceGen(sessionId.toByteArray(), seckey?.value?.toByteArray(), pubkey.value.toByteArray(), msg?.toByteArray(), cache?.data?.toByteArray(), extraInput?.toByteArray())
130+ return Pair (SecretNonce (nonce.copyOfRange(0 , Secp256k1 .MUSIG2_SECRET_NONCE_SIZE )), IndividualNonce (nonce.copyOfRange(Secp256k1 .MUSIG2_SECRET_NONCE_SIZE , Secp256k1 .MUSIG2_SECRET_NONCE_SIZE + Secp256k1 .MUSIG2_PUBLIC_NONCE_SIZE )))
131+ }
132+ }
133+ }
134+
135+ /* *
136+ * Musig2 public nonce
137+ */
138+ public data class IndividualNonce (val data : ByteVector ) {
139+ public constructor (bin: ByteArray ) : this (bin.byteVector())
140+
141+ public constructor (hex: String ) : this (Hex .decode(hex))
142+
143+ init {
144+ require(data.size() == Secp256k1 .MUSIG2_PUBLIC_NONCE_SIZE ) { " individual musig2 public nonce must be ${Secp256k1 .MUSIG2_PUBLIC_NONCE_SIZE } bytes" }
145+ }
146+
147+ public fun toByteArray (): ByteArray = data.toByteArray()
148+
149+ public companion object {
150+ @JvmStatic
151+ public fun aggregate (nonces : List <IndividualNonce >): AggregatedNonce {
152+ val agg = Secp256k1 .musigNonceAgg(nonces.map { it.toByteArray() }.toTypedArray())
153+ return AggregatedNonce (agg)
154+ }
155+ }
156+ }
157+
158+ /* *
159+ * Musig2 aggregated nonce
160+ */
161+ public data class AggregatedNonce (val data : ByteVector ) {
162+ public constructor (bin: ByteArray ) : this (bin.byteVector())
163+
164+ public constructor (hex: String ) : this (Hex .decode(hex))
165+
166+ init {
167+ require(data.size() == Secp256k1 .MUSIG2_PUBLIC_NONCE_SIZE ) { " aggregated musig2 public nonce must be ${Secp256k1 .MUSIG2_PUBLIC_NONCE_SIZE } bytes" }
168+ }
169+
170+ public fun toByteArray (): ByteArray = data.toByteArray()
171+ }
0 commit comments