Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions hacspec-halfagg/src/halfagg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const HALFAGG_RANDOMIZER: TaggedHashHalfAggPrefix = TaggedHashHalfAggPrefix([
0x48u8, 0x61u8, 0x6cu8, 0x66u8, 0x41u8, 0x67u8, 0x67u8, 0x2fu8, 0x72u8, 0x61u8, 0x6eu8, 0x64u8,
0x6fu8, 0x6du8, 0x69u8, 0x7au8, 0x65u8, 0x72u8,
]);

pub fn hash_halfagg(input: &Seq<(PublicKey, Message, Bytes32)>) -> Bytes32 {
let mut c = ByteSeq::new(0);
for i in 0..input.len() {
Expand All @@ -29,6 +30,19 @@ pub fn hash_halfagg(input: &Seq<(PublicKey, Message, Bytes32)>) -> Bytes32 {
tagged_hash(&PublicByteSeq::from_seq(&HALFAGG_RANDOMIZER), &c)
}

pub fn randomizer(pmr: &Seq<(PublicKey, Message, Bytes32)>, index: usize) -> Scalar {
if index == 0 {
Scalar::ONE()
} else {
// TODO: The following line hashes i elements and therefore leads to
// quadratic runtime. Instead, we should cache the intermediate result
// and only hash the new element.
scalar_from_bytes(hash_halfagg(
&Seq::<(PublicKey, Message, Bytes32)>::from_slice(pmr, 0, index + 1),
))
}
}

pub type AggregateResult = Result<AggSig, Error>;
pub fn aggregate(pms: &Seq<(PublicKey, Message, Signature)>) -> AggregateResult {
let aggsig = AggSig::new(32);
Expand Down Expand Up @@ -59,12 +73,7 @@ pub fn inc_aggregate(
for i in v..v + u {
let (pk, msg, sig) = pms_to_agg[i - v];
pmr[i] = (pk, msg, Bytes32::from_slice(&sig, 0, 32));
// TODO: The following line hashes i elements and therefore leads to
// quadratic runtime. Instead, we should cache the intermediate result
// and only hash the new element.
let z = scalar_from_bytes(hash_halfagg(
&Seq::<(PublicKey, Message, Bytes32)>::from_slice(&pmr, 0, i + 1),
));
let z = randomizer(&pmr, i);
s = s + z * Scalar::from_byte_seq_be(&Bytes32::from_slice(&sig, 32, 32));
}
let mut ret = Seq::<U8>::new(0);
Expand Down Expand Up @@ -113,12 +122,7 @@ pub fn verify_aggregate(aggsig: &AggSig, pm_aggd: &Seq<(PublicKey, Message)>) ->
let r = r_res.unwrap();
let e = scalar_from_bytes(hash_challenge(rx, bytes_from_point(p), msg));
pmr[i] = (pk, msg, rx);
// TODO: The following line hashes i elements and therefore leads to
// quadratic runtime. Instead, we should cache the intermediate result
// and only hash the new element.
let z = scalar_from_bytes(hash_halfagg(
&Seq::<(PublicKey, Message, Bytes32)>::from_slice(&pmr, 0, i + 1),
));
let z = randomizer(&pmr, i);
terms[2 * i] = (z, r);
terms[2 * i + 1] = (z * e, p);
}
Expand Down
21 changes: 7 additions & 14 deletions hacspec-halfagg/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,9 @@ fn test_verify_vectors_process(
fn test_verify_vectors() {
#[rustfmt::skip]
let vectors_raw = vec![
(vec![],
"0000000000000000000000000000000000000000000000000000000000000000"),
(vec![("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "0202020202020202020202020202020202020202020202020202020202020202"),],
"b070aafcea439a4f6f1bbfc2eb66d29d24b0cab74d6b745c3cfb009cc8fe4aa8108f33907612fb748419ebc4004b3169e16e35d5f12b693b6bbc3d4a6982f2f6"),
(vec![("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "0202020202020202020202020202020202020202020202020202020202020202"),
("462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b", "0505050505050505050505050505050505050505050505050505050505050505"),],
"b070aafcea439a4f6f1bbfc2eb66d29d24b0cab74d6b745c3cfb009cc8fe4aa8a3afbdb45a6a34bf7c8c00f1b6d7e7d375b54540f13716c87b62e51e2f4f22ffc211db48479c2f546d52b07955e764eb6a142d577245f40a44f5dee468da4244"),
(vec![], "0000000000000000000000000000000000000000000000000000000000000000"),
(vec![("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "0202020202020202020202020202020202020202020202020202020202020202"),], "b070aafcea439a4f6f1bbfc2eb66d29d24b0cab74d6b745c3cfb009cc8fe4aa80e066c34819936549ff49b6fd4d41edfc401a367b87ddd59fee38177961c225f"),
(vec![("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "0202020202020202020202020202020202020202020202020202020202020202"),("462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b", "0505050505050505050505050505050505050505050505050505050505050505"),], "b070aafcea439a4f6f1bbfc2eb66d29d24b0cab74d6b745c3cfb009cc8fe4aa8a3afbdb45a6a34bf7c8c00f1b6d7e7d375b54540f13716c87b62e51e2f4f22ffbf8913ec53226a34892d60252a7052614ca79ae939986828d81d2311957371ad"),
];
let vectors = test_verify_vectors_process(&vectors_raw);
// Uncomment to generate and print test vectors:
Expand Down Expand Up @@ -154,12 +150,7 @@ fn test_aggregate_verify_strange() {
for i in 0..2 {
let (pk, msg, sig) = pms_triples[i];
pmr = pmr.push(&(pk, msg, Bytes32::from_slice(&sig, 0, 32)));
// TODO: The following line hashes i elements and therefore leads to
// quadratic runtime. Instead, we should cache the intermediate result
// and only hash the new element.
z = z.push(&scalar_from_bytes(hash_halfagg(
&Seq::<(PublicKey, Message, Bytes32)>::from_slice(&pmr, 0, i + 1),
)));
z = z.push(&randomizer(&pmr, i));
}

// Shift signatures appropriately
Expand Down Expand Up @@ -220,5 +211,7 @@ fn test_edge_cases() {
inc_aggregate(&aggsig, &empty_pm, &big_pms).unwrap_err()
== hacspec_halfagg::Error::AggSigTooBig
);
assert!(verify_aggregate(&aggsig, &big_pm).unwrap_err() == hacspec_halfagg::Error::AggSigTooBig);
assert!(
verify_aggregate(&aggsig, &big_pm).unwrap_err() == hacspec_halfagg::Error::AggSigTooBig
);
}
12 changes: 9 additions & 3 deletions half-aggregation.mediawiki
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Moreover, they came up with an elegant approach to incremental aggregation that
* Incremental aggregation allows non-interactively aggregating additional BIP 340 signatures into an existing half-aggregate signature.
* 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).
* 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.
* 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).
* 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].
* 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).

== Description ==
Expand Down Expand Up @@ -147,7 +147,10 @@ Input:
** Let ''(pk<sub>i</sub>, m<sub>i</sub>, sig<sub>i</sub>) = pms_to_agg<sub>i-v</sub>''
** Let ''r<sub>i</sub> = sig<sub>i</sub>[0:32]''
** Let ''s<sub>i</sub> = int(sig<sub>i</sub>[32:64])''
** 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''
** If ''i = 0'':
*** Let ''z<sub>i</sub> = 1''
** Else:
*** 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''
* 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''
* Return ''r<sub>0</sub> || ... || r<sub>v+u-1</sub> || bytes(s)''

Expand All @@ -169,7 +172,10 @@ The algorithm ''VerifyAggregate(aggsig, pm_aggd<sub>0..u-1</sub>)'' is defined a
** Let ''r<sub>i</sub> = aggsig[i⋅32:(i+1)⋅32]''
** Let ''R<sub>i</sub> = lift_x(int(r<sub>i</sub>))''; fail if that fails
** 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''
** 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''
** If ''i = 0'':
*** Let ''z<sub>i</sub> = 1''
** Else:
*** 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''
* Let ''s = int(aggsig[u⋅32:(u+1)⋅32]); fail if ''s &ge; n''
* 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>)''
* Return success iff no failure occurred before reaching this point.
Expand Down