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