Skip to content

Commit e970504

Browse files
Merge BlockstreamResearch/cross-input-aggregation#14: halfagg: Fix z_0 = 1 as in CZ22
f268acfb992b5a8bf483476c7c5f1f9dc373000a halfagg: Run rustfmt (Tim Ruffing) 5f9a3d612ff6699378a3a86a9b3cda77d2a8413d halfagg: Fix z_0 = 1 as in CZ22 (Tim Ruffing) 1ab7cd80525d9f47b99626ff1f6d2b03793bffbc halfagg: Extract computation of z into function randomizer() (Tim Ruffing) Pull request description: ACKs for top commit: jonasnick: ACK f268acfb992b5a8bf483476c7c5f1f9dc373000a Tree-SHA512: d66cd077dc7c41f2b5beb0197ff5965cc58e5067df1040e3b4863d579159b9e32e4b22dff1b7d527dc9a0c2142bbb51aaf559e401095baacd261ba76860c5dfc
2 parents c9e167f + 2ca56d0 commit e970504

File tree

3 files changed

+32
-29
lines changed

3 files changed

+32
-29
lines changed

hacspec-halfagg/src/halfagg.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const HALFAGG_RANDOMIZER: TaggedHashHalfAggPrefix = TaggedHashHalfAggPrefix([
2020
0x48u8, 0x61u8, 0x6cu8, 0x66u8, 0x41u8, 0x67u8, 0x67u8, 0x2fu8, 0x72u8, 0x61u8, 0x6eu8, 0x64u8,
2121
0x6fu8, 0x6du8, 0x69u8, 0x7au8, 0x65u8, 0x72u8,
2222
]);
23+
2324
pub fn hash_halfagg(input: &Seq<(PublicKey, Message, Bytes32)>) -> Bytes32 {
2425
let mut c = ByteSeq::new(0);
2526
for i in 0..input.len() {
@@ -29,6 +30,19 @@ pub fn hash_halfagg(input: &Seq<(PublicKey, Message, Bytes32)>) -> Bytes32 {
2930
tagged_hash(&PublicByteSeq::from_seq(&HALFAGG_RANDOMIZER), &c)
3031
}
3132

33+
pub fn randomizer(pmr: &Seq<(PublicKey, Message, Bytes32)>, index: usize) -> Scalar {
34+
if index == 0 {
35+
Scalar::ONE()
36+
} else {
37+
// TODO: The following line hashes i elements and therefore leads to
38+
// quadratic runtime. Instead, we should cache the intermediate result
39+
// and only hash the new element.
40+
scalar_from_bytes(hash_halfagg(
41+
&Seq::<(PublicKey, Message, Bytes32)>::from_slice(pmr, 0, index + 1),
42+
))
43+
}
44+
}
45+
3246
pub type AggregateResult = Result<AggSig, Error>;
3347
pub fn aggregate(pms: &Seq<(PublicKey, Message, Signature)>) -> AggregateResult {
3448
let aggsig = AggSig::new(32);
@@ -59,12 +73,7 @@ pub fn inc_aggregate(
5973
for i in v..v + u {
6074
let (pk, msg, sig) = pms_to_agg[i - v];
6175
pmr[i] = (pk, msg, Bytes32::from_slice(&sig, 0, 32));
62-
// TODO: The following line hashes i elements and therefore leads to
63-
// quadratic runtime. Instead, we should cache the intermediate result
64-
// and only hash the new element.
65-
let z = scalar_from_bytes(hash_halfagg(
66-
&Seq::<(PublicKey, Message, Bytes32)>::from_slice(&pmr, 0, i + 1),
67-
));
76+
let z = randomizer(&pmr, i);
6877
s = s + z * Scalar::from_byte_seq_be(&Bytes32::from_slice(&sig, 32, 32));
6978
}
7079
let mut ret = Seq::<U8>::new(0);
@@ -113,12 +122,7 @@ pub fn verify_aggregate(aggsig: &AggSig, pm_aggd: &Seq<(PublicKey, Message)>) ->
113122
let r = r_res.unwrap();
114123
let e = scalar_from_bytes(hash_challenge(rx, bytes_from_point(p), msg));
115124
pmr[i] = (pk, msg, rx);
116-
// TODO: The following line hashes i elements and therefore leads to
117-
// quadratic runtime. Instead, we should cache the intermediate result
118-
// and only hash the new element.
119-
let z = scalar_from_bytes(hash_halfagg(
120-
&Seq::<(PublicKey, Message, Bytes32)>::from_slice(&pmr, 0, i + 1),
121-
));
125+
let z = randomizer(&pmr, i);
122126
terms[2 * i] = (z, r);
123127
terms[2 * i + 1] = (z * e, p);
124128
}

hacspec-halfagg/tests/tests.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,9 @@ fn test_verify_vectors_process(
7979
fn test_verify_vectors() {
8080
#[rustfmt::skip]
8181
let vectors_raw = vec![
82-
(vec![],
83-
"0000000000000000000000000000000000000000000000000000000000000000"),
84-
(vec![("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "0202020202020202020202020202020202020202020202020202020202020202"),],
85-
"b070aafcea439a4f6f1bbfc2eb66d29d24b0cab74d6b745c3cfb009cc8fe4aa8108f33907612fb748419ebc4004b3169e16e35d5f12b693b6bbc3d4a6982f2f6"),
86-
(vec![("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "0202020202020202020202020202020202020202020202020202020202020202"),
87-
("462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b", "0505050505050505050505050505050505050505050505050505050505050505"),],
88-
"b070aafcea439a4f6f1bbfc2eb66d29d24b0cab74d6b745c3cfb009cc8fe4aa8a3afbdb45a6a34bf7c8c00f1b6d7e7d375b54540f13716c87b62e51e2f4f22ffc211db48479c2f546d52b07955e764eb6a142d577245f40a44f5dee468da4244"),
82+
(vec![], "0000000000000000000000000000000000000000000000000000000000000000"),
83+
(vec![("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "0202020202020202020202020202020202020202020202020202020202020202"),], "b070aafcea439a4f6f1bbfc2eb66d29d24b0cab74d6b745c3cfb009cc8fe4aa80e066c34819936549ff49b6fd4d41edfc401a367b87ddd59fee38177961c225f"),
84+
(vec![("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "0202020202020202020202020202020202020202020202020202020202020202"),("462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b", "0505050505050505050505050505050505050505050505050505050505050505"),], "b070aafcea439a4f6f1bbfc2eb66d29d24b0cab74d6b745c3cfb009cc8fe4aa8a3afbdb45a6a34bf7c8c00f1b6d7e7d375b54540f13716c87b62e51e2f4f22ffbf8913ec53226a34892d60252a7052614ca79ae939986828d81d2311957371ad"),
8985
];
9086
let vectors = test_verify_vectors_process(&vectors_raw);
9187
// Uncomment to generate and print test vectors:
@@ -154,12 +150,7 @@ fn test_aggregate_verify_strange() {
154150
for i in 0..2 {
155151
let (pk, msg, sig) = pms_triples[i];
156152
pmr = pmr.push(&(pk, msg, Bytes32::from_slice(&sig, 0, 32)));
157-
// TODO: The following line hashes i elements and therefore leads to
158-
// quadratic runtime. Instead, we should cache the intermediate result
159-
// and only hash the new element.
160-
z = z.push(&scalar_from_bytes(hash_halfagg(
161-
&Seq::<(PublicKey, Message, Bytes32)>::from_slice(&pmr, 0, i + 1),
162-
)));
153+
z = z.push(&randomizer(&pmr, i));
163154
}
164155

165156
// Shift signatures appropriately
@@ -220,5 +211,7 @@ fn test_edge_cases() {
220211
inc_aggregate(&aggsig, &empty_pm, &big_pms).unwrap_err()
221212
== hacspec_halfagg::Error::AggSigTooBig
222213
);
223-
assert!(verify_aggregate(&aggsig, &big_pm).unwrap_err() == hacspec_halfagg::Error::AggSigTooBig);
214+
assert!(
215+
verify_aggregate(&aggsig, &big_pm).unwrap_err() == hacspec_halfagg::Error::AggSigTooBig
216+
);
224217
}

half-aggregation.mediawiki

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Moreover, they came up with an elegant approach to incremental aggregation that
6161
* Incremental aggregation allows non-interactively aggregating additional BIP 340 signatures into an existing half-aggregate signature.
6262
* A half-aggregate signature of ''u'' BIP 340 input signatures is serialized as the ''(u+1)⋅32''-byte array ''r<sub>1</sub> || ... || r<sub>u</sub> || bytes(s)'' where ''r<sub>i</sub>'' is a 32-byte array from input signature ''i'' and ''s'' is a scalar aggregate (see below for details).
6363
* This document does ''not'' specify the aggregation of multiple aggregate signatures (yet). It is possible, but requires changing the encoding of an aggregate signature. Since it is not possible to undo the aggregation of the s-values, when verifying of such an aggregate signature the randomizers need to be the same as when verifying the individual aggregate signature. Therefore, the aggregate signature needs to encode a tree that reveals how the individual signatures were aggregated and how the resulting aggregate signatures were reaggregated.
64-
* There is a possible optimization where the first randomizer ''z<sub>0</sub>'' is set to the constant ''1'' which speeds up verification because ''z<sub>0</sub>⋅R<sub>0</sub> = R<sub>0</sub>''. This specification does not make use of this optimization yet (TODO).
64+
* The first randomizer ''z<sub>0</sub>'' is fixed to the constant ''1'', which speeds up verification because ''z<sub>0</sub>⋅R<sub>0</sub> = R<sub>0</sub>''. This optimization has been suggested and proven secure by [https://eprint.iacr.org/2022/222.pdf Chen and Zhao].
6565
* The maximum number of signatures that can be aggregated is ''2<sup>16</sup> - 1''. Having a maximum value is supposed to prevent integer overflows. This specific value was a conservative choice and may be raised in the future (TODO).
6666
6767
== Description ==
@@ -147,7 +147,10 @@ Input:
147147
** Let ''(pk<sub>i</sub>, m<sub>i</sub>, sig<sub>i</sub>) = pms_to_agg<sub>i-v</sub>''
148148
** Let ''r<sub>i</sub> = sig<sub>i</sub>[0:32]''
149149
** Let ''s<sub>i</sub> = int(sig<sub>i</sub>[32:64])''
150-
** Let ''z<sub>i</sub> = int(hash<sub>HalfAgg/randomizer</sub>(r<sub>0</sub> || pk<sub>0</sub> || m<sub>0</sub> || ... || r<sub>i</sub> || pk<sub>i</sub> || m<sub>i</sub>)) mod n''
150+
** If ''i = 0'':
151+
*** Let ''z<sub>i</sub> = 1''
152+
** Else:
153+
*** Let ''z<sub>i</sub> = int(hash<sub>HalfAgg/randomizer</sub>(r<sub>0</sub> || pk<sub>0</sub> || m<sub>0</sub> || ... || r<sub>i</sub> || pk<sub>i</sub> || m<sub>i</sub>)) mod n''
151154
* Let ''s = int(aggsig[(v⋅32:(v+1)⋅32]) + z<sub>v</sub>⋅s<sub>v</sub> + ... + z<sub>v+u-1</sub>⋅s<sub>v+u-1</sub> mod n''
152155
* Return ''r<sub>0</sub> || ... || r<sub>v+u-1</sub> || bytes(s)''
153156
@@ -169,7 +172,10 @@ The algorithm ''VerifyAggregate(aggsig, pm_aggd<sub>0..u-1</sub>)'' is defined a
169172
** Let ''r<sub>i</sub> = aggsig[i⋅32:(i+1)⋅32]''
170173
** Let ''R<sub>i</sub> = lift_x(int(r<sub>i</sub>))''; fail if that fails
171174
** Let ''e<sub>i</sub> = int(hash<sub>BIP0340/challenge</sub>(bytes(r<sub>i</sub>) || pk<sub>i</sub> || m<sub>i</sub>)) mod n''
172-
** Let ''z<sub>i</sub> = int(hash<sub>HalfAgg/randomizer</sub>(r<sub>0</sub> || pk<sub>0</sub> || m<sub>0</sub> || ... || r<sub>i</sub> || pk<sub>i</sub> || m<sub>i</sub>)) mod n''
175+
** If ''i = 0'':
176+
*** Let ''z<sub>i</sub> = 1''
177+
** Else:
178+
*** Let ''z<sub>i</sub> = int(hash<sub>HalfAgg/randomizer</sub>(r<sub>0</sub> || pk<sub>0</sub> || m<sub>0</sub> || ... || r<sub>i</sub> || pk<sub>i</sub> || m<sub>i</sub>)) mod n''
173179
* Let ''s = int(aggsig[u⋅32:(u+1)⋅32]); fail if ''s &ge; n''
174180
* Fail if ''s⋅G &ne; z<sub>0</sub>⋅(R<sub>0</sub> + e<sub>0</sub>⋅P<sub>0</sub>) + ... + z<sub>u-1</sub>⋅(R<sub>u-1</sub> + e<sub>u-1</sub>⋅P<sub>u-1</sub>)''
175181
* Return success iff no failure occurred before reaching this point.

0 commit comments

Comments
 (0)