diff --git a/hacspec-halfagg/src/halfagg.rs b/hacspec-halfagg/src/halfagg.rs index 318c1a7..074ac47 100644 --- a/hacspec-halfagg/src/halfagg.rs +++ b/hacspec-halfagg/src/halfagg.rs @@ -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() { @@ -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; pub fn aggregate(pms: &Seq<(PublicKey, Message, Signature)>) -> AggregateResult { let aggsig = AggSig::new(32); @@ -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::::new(0); @@ -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); } diff --git a/hacspec-halfagg/tests/tests.rs b/hacspec-halfagg/tests/tests.rs index 8f197ba..7ad007d 100644 --- a/hacspec-halfagg/tests/tests.rs +++ b/hacspec-halfagg/tests/tests.rs @@ -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: @@ -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 @@ -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 + ); } diff --git a/half-aggregation.mediawiki b/half-aggregation.mediawiki index 12c0a6e..0fb7ab6 100644 --- a/half-aggregation.mediawiki +++ b/half-aggregation.mediawiki @@ -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 ''r1 || ... || ru || bytes(s)'' where ''ri'' 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 ''z0'' is set to the constant ''1'' which speeds up verification because ''z0⋅R0 = R0''. This specification does not make use of this optimization yet (TODO). +* The first randomizer ''z0'' is fixed to the constant ''1'', which speeds up verification because ''z0⋅R0 = R0''. 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 ''216 - 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 == @@ -147,7 +147,10 @@ Input: ** Let ''(pki, mi, sigi) = pms_to_aggi-v'' ** Let ''ri = sigi[0:32]'' ** Let ''si = int(sigi[32:64])'' -** Let ''zi = int(hashHalfAgg/randomizer(r0 || pk0 || m0 || ... || ri || pki || mi)) mod n'' +** If ''i = 0'': +*** Let ''zi = 1'' +** Else: +*** Let ''zi = int(hashHalfAgg/randomizer(r0 || pk0 || m0 || ... || ri || pki || mi)) mod n'' * Let ''s = int(aggsig[(v⋅32:(v+1)⋅32]) + zv⋅sv + ... + zv+u-1⋅sv+u-1 mod n'' * Return ''r0 || ... || rv+u-1 || bytes(s)'' @@ -169,7 +172,10 @@ The algorithm ''VerifyAggregate(aggsig, pm_aggd0..u-1)'' is defined a ** Let ''ri = aggsig[i⋅32:(i+1)⋅32]'' ** Let ''Ri = lift_x(int(ri))''; fail if that fails ** Let ''ei = int(hashBIP0340/challenge(bytes(ri) || pki || mi)) mod n'' -** Let ''zi = int(hashHalfAgg/randomizer(r0 || pk0 || m0 || ... || ri || pki || mi)) mod n'' +** If ''i = 0'': +*** Let ''zi = 1'' +** Else: +*** Let ''zi = int(hashHalfAgg/randomizer(r0 || pk0 || m0 || ... || ri || pki || mi)) mod n'' * Let ''s = int(aggsig[u⋅32:(u+1)⋅32]); fail if ''s ≥ n'' * Fail if ''s⋅G ≠ z0⋅(R0 + e0⋅P0) + ... + zu-1⋅(Ru-1 + eu-1⋅Pu-1)'' * Return success iff no failure occurred before reaching this point.