From 1ab7cd80525d9f47b99626ff1f6d2b03793bffbc Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Wed, 17 Jan 2024 11:56:49 +0100 Subject: [PATCH 1/3] halfagg: Extract computation of z into function randomizer() --- hacspec-halfagg/src/halfagg.rs | 24 ++++++++++++------------ hacspec-halfagg/tests/tests.rs | 7 +------ 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/hacspec-halfagg/src/halfagg.rs b/hacspec-halfagg/src/halfagg.rs index 318c1a7..6bd1d59 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,15 @@ 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 { + // 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 +69,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 +118,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..3f49447 100644 --- a/hacspec-halfagg/tests/tests.rs +++ b/hacspec-halfagg/tests/tests.rs @@ -154,12 +154,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 From 5f9a3d612ff6699378a3a86a9b3cda77d2a8413d Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Wed, 17 Jan 2024 12:05:30 +0100 Subject: [PATCH 2/3] halfagg: Fix z_0 = 1 as in CZ22 --- hacspec-halfagg/src/halfagg.rs | 16 ++++++++++------ hacspec-halfagg/tests/tests.rs | 10 +++------- half-aggregation.mediawiki | 12 +++++++++--- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/hacspec-halfagg/src/halfagg.rs b/hacspec-halfagg/src/halfagg.rs index 6bd1d59..074ac47 100644 --- a/hacspec-halfagg/src/halfagg.rs +++ b/hacspec-halfagg/src/halfagg.rs @@ -31,12 +31,16 @@ pub fn hash_halfagg(input: &Seq<(PublicKey, Message, Bytes32)>) -> Bytes32 { } pub fn randomizer(pmr: &Seq<(PublicKey, Message, Bytes32)>, index: usize) -> Scalar { - // 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), - )) + 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; diff --git a/hacspec-halfagg/tests/tests.rs b/hacspec-halfagg/tests/tests.rs index 3f49447..7a60639 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: 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. From f268acfb992b5a8bf483476c7c5f1f9dc373000a Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Wed, 17 Jan 2024 13:11:11 +0100 Subject: [PATCH 3/3] halfagg: Run rustfmt --- hacspec-halfagg/tests/tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hacspec-halfagg/tests/tests.rs b/hacspec-halfagg/tests/tests.rs index 7a60639..7ad007d 100644 --- a/hacspec-halfagg/tests/tests.rs +++ b/hacspec-halfagg/tests/tests.rs @@ -211,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 + ); }