-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathLibSchnorrExtended.sol
246 lines (204 loc) · 7.67 KB
/
LibSchnorrExtended.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import {console2} from "forge-std/console2.sol";
import {StdStyle} from "forge-std/StdStyle.sol";
import {LibSecp256k1} from "src/libs/LibSecp256k1.sol";
import {LibSecp256k1Extended} from "script/libs/LibSecp256k1Extended.sol";
/**
* @title LibSchnorrExtended
*
* @notice Extended library for Schnorr signatures as specified in
* `docs/Schnorr.md`
*/
library LibSchnorrExtended {
using LibSecp256k1 for LibSecp256k1.Point;
using LibSecp256k1 for LibSecp256k1.JacobianPoint;
using LibSecp256k1Extended for uint;
/// @dev Returns a Schnorr signature of type (signature, commitment) signing
/// `message` via `privKey`.
function signMessage(uint privKey, bytes32 message)
internal
returns (uint, address)
{
LibSecp256k1.Point memory pubKey = privKey.derivePublicKey();
// 1. Select secure nonce.
uint nonce = deriveNonce(privKey, message);
// 2. Compute noncePubKey.
LibSecp256k1.Point memory noncePubKey = computeNoncePublicKey(nonce);
// 3. Derive commitment from noncePubKey.
address commitment = deriveCommitment(noncePubKey);
// 4. Construct challenge.
bytes32 challenge = constructChallenge(pubKey, message, commitment);
// 5. Compute signature.
uint signature = computeSignature(privKey, nonce, challenge);
// BONUS: Make sure signature can be verified.
bool ok =
verifySignature(pubKey, message, bytes32(signature), commitment);
if (!ok) {
console2.log(
StdStyle.red(
"[INTERNAL ERROR] LibSchnorrExtended: could not verify own signature"
)
);
}
// => The public key signs the message via the signature and
// commitment.
return (signature, commitment);
}
/// @dev Returns a Schnorr multi-signature (aggregated signature) of type
/// (signature, commitment) signing `message` via `privKeys`.
function signMessage(uint[] memory privKeys, bytes32 message)
internal
returns (uint, address)
{
// 1. Collect list of pubKeys of signers.
LibSecp256k1.Point[] memory pubKeys =
new LibSecp256k1.Point[](privKeys.length);
for (uint i; i < privKeys.length; i++) {
pubKeys[i] = privKeys[i].derivePublicKey();
}
// 2. Compute aggPubKey.
LibSecp256k1.Point memory aggPubKey;
aggPubKey = aggregatePublicKeys(pubKeys);
// 3. Collect list of noncePubKeys from signers.
LibSecp256k1.Point[] memory noncePubKeys =
new LibSecp256k1.Point[](privKeys.length);
for (uint i; i < privKeys.length; i++) {
// 3.1. Derive secure nonce.
uint nonce = deriveNonce(privKeys[i], message);
// 3.2. Compute noncePubKey and append to list of noncePubKeys.
noncePubKeys[i] = computeNoncePublicKey(nonce);
}
// 4. Compute aggNoncePubKey.
LibSecp256k1.Point memory aggNoncePubKey;
aggNoncePubKey = aggregatePublicKeys(noncePubKeys);
// 5. Derive commitment from aggNoncePubKey.
address commitment = deriveCommitment(aggNoncePubKey);
// 6. Construct challenge.
bytes32 challenge = constructChallenge(aggPubKey, message, commitment);
// 7. Collect signatures from signers.
uint[] memory signatures = new uint[](privKeys.length);
for (uint i; i < privKeys.length; i++) {
// 7.1 Derive secure nonce.
uint nonce = deriveNonce(privKeys[i], message);
// 7.2 Compute signature.
signatures[i] = computeSignature(privKeys[i], nonce, challenge);
}
// 8. Compute aggSignature.
uint aggSignature;
for (uint i; i < privKeys.length; i++) {
// Note to keep aggSignature ∊ [0, Q).
aggSignature = addmod(aggSignature, signatures[i], LibSecp256k1.Q());
}
// BONUS: Make sure signature can be verified.
bool ok = verifySignature(
aggPubKey, message, bytes32(aggSignature), commitment
);
if (!ok) {
console2.log(
StdStyle.red(
"[INTERNAL ERROR] LibSchnorrExtended: could not verify own signature"
)
);
}
// => The aggregated public key signs the message via the aggregated
// signature and commitment.
return (aggSignature, commitment);
}
function verifySignature(
LibSecp256k1.Point memory pubKey,
bytes32 message,
bytes32 signature,
address commitment
) internal pure returns (bool) {
if (commitment == address(0) || signature == 0) {
return false;
}
uint challenge = uint(constructChallenge(pubKey, message, commitment));
// Compute msgHash = -sig * Pₓ (mod Q)
// = Q - (sig * Pₓ) (mod Q)
uint msgHash = LibSecp256k1.Q()
- mulmod(uint(signature), pubKey.x, LibSecp256k1.Q());
// Compute v = Pₚ + 27
uint v = pubKey.yParity() + 27;
// Set r = Pₓ
uint r = pubKey.x;
// Compute s = Q - (e * Pₓ) (mod Q)
uint s =
LibSecp256k1.Q() - mulmod(challenge, pubKey.x, LibSecp256k1.Q());
// Perform ecrecover call.
// Note to perform necessary castings.
address recovered =
ecrecover(bytes32(msgHash), uint8(v), bytes32(r), bytes32(s));
// Verification succeeds iff the ecrecover'ed address equals Rₑ, i.e.
// the commitment.
return commitment == recovered;
}
// -- Low-Level Functions --
function deriveNonce(uint privKey, bytes32 message)
internal
pure
returns (uint)
{
// k = H(x ‖ m) mod Q
return uint(keccak256(abi.encodePacked(privKey, message)))
% LibSecp256k1.Q();
}
function computeNoncePublicKey(uint nonce)
internal
returns (LibSecp256k1.Point memory)
{
// R = [k]G
return nonce.derivePublicKey();
}
function deriveCommitment(LibSecp256k1.Point memory noncePubKey)
internal
pure
returns (address)
{
// Rₑ = Ethereum address of R.
return noncePubKey.toAddress();
}
function aggregatePublicKeys(LibSecp256k1.Point[] memory pubKeys)
internal
pure
returns (LibSecp256k1.Point memory)
{
// aggPubKey = sum(signers)
// = pubKey₁ + pubKey₂ + ... + pubKeyₙ
require(pubKeys.length != 0);
LibSecp256k1.JacobianPoint memory aggPubKey = pubKeys[0].toJacobian();
for (uint i = 1; i < pubKeys.length; i++) {
aggPubKey.addAffinePoint(pubKeys[i]);
}
return aggPubKey.toAffine();
}
function constructChallenge(
LibSecp256k1.Point memory pubKey,
bytes32 message,
address commitment
) internal pure returns (bytes32) {
// e = H(Pₓ ‖ Pₚ ‖ m ‖ Rₑ) mod Q
return bytes32(
uint(
keccak256(
abi.encodePacked(
pubKey.x, uint8(pubKey.yParity()), message, commitment
)
)
) % LibSecp256k1.Q()
);
}
function computeSignature(uint privKey, uint nonce, bytes32 challenge)
internal
pure
returns (uint)
{
// s = k + (e * x) (mod Q)
return addmod(
nonce,
mulmod(uint(challenge), privKey, LibSecp256k1.Q()),
LibSecp256k1.Q()
);
}
}