purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
accountable_roster.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Judica, Inc.
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or https://opensource.org/license/mit/.
4
6
7#include <algorithm>
8#include <optional>
9
10#include "purify/curve.hpp"
11#include "bppp_bridge.h"
12
13namespace purify::accountable {
14
16 std::span<const std::array<unsigned char, 33>> ordered_public_keys,
17 purify_secp_context* secp_context) {
18 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "accountable:roster"),
19 "accountable:aggregate_roster");
20 if (ordered_public_keys.empty()) {
21 return unexpected_error(ErrorCode::EmptyInput, "accountable:empty_roster");
22 }
23 std::size_t encoded_size = 0;
24 if (!size_fits_u32(ordered_public_keys.size()) ||
25 !checked_mul_size(ordered_public_keys.size(), 33, encoded_size)) {
26 return unexpected_error(ErrorCode::RangeViolation, "accountable:roster_size");
27 }
28 for (const auto& key : ordered_public_keys) {
29 if ((key[0] != 0x02 && key[0] != 0x03) ||
30 purify_bip340_validate_xonly_pubkey(secp_context, key.data() + 1) == 0) {
31 return unexpected_error(ErrorCode::RangeViolation, "accountable:member_key");
32 }
33 }
34 // Sorting a copy detects duplicates without changing the authenticated order.
35 auto sorted = std::vector(ordered_public_keys.begin(), ordered_public_keys.end());
36 std::sort(sorted.begin(), sorted.end());
37 if (std::adjacent_find(sorted.begin(), sorted.end()) != sorted.end()) {
38 return unexpected_error(ErrorCode::BindingMismatch, "accountable:duplicate_member_key");
39 }
40
41 Bytes encoded;
42 encoded.reserve(encoded_size);
43 for (const auto& key : ordered_public_keys) {
44 encoded.insert(encoded.end(), key.begin(), key.end());
45 }
46 static const TaggedHash list_hash("KeyAgg list");
47 static const TaggedHash coefficient_hash("KeyAgg coefficient");
48 SigningRoster out;
49 out.key_list_hash_ = list_hash.digest(encoded);
50 out.members_.reserve(ordered_public_keys.size());
51 std::optional<std::array<unsigned char, 33>> sum;
52 for (std::size_t i = 0; i < ordered_public_keys.size(); ++i) {
53 const auto& key = ordered_public_keys[i];
54 // Duplicates are excluded, so index one is the second distinct key.
55 auto coefficient = FieldElement::from_u64(1);
56 if (i != 1) {
57 const auto digest = coefficient_hash.digest_many<2>({out.key_list_hash_, key});
58 purify_scalar raw;
59 purify_scalar_set_b32(&raw, digest.data(), nullptr);
61 }
62 if (coefficient.is_zero()) {
63 return unexpected_error(ErrorCode::RangeViolation, "accountable:zero_key_coefficient");
64 }
65 const auto scalar = coefficient.to_bytes_be();
66 std::array<unsigned char, 33> term{};
67 if (purify_point_scale(secp_context, key.data(), scalar.data(), term.data()) == 0) {
68 return unexpected_error(ErrorCode::InternalMismatch, "accountable:weighted_member_key");
69 }
70 out.members_.push_back({key, coefficient});
71 if (!sum) {
72 sum = term;
73 } else if ((*sum)[0] != term[0] && std::equal(sum->begin() + 1, sum->end(), term.begin() + 1)) {
74 // Intermediate cancellation is valid; only the final sum must be nonzero.
75 sum.reset();
76 } else {
77 std::array<unsigned char, 33> next{};
78 if (purify_point_add(secp_context, sum->data(), term.data(), next.data()) == 0) {
79 return unexpected_error(ErrorCode::InternalMismatch, "accountable:aggregate_key_sum");
80 }
81 sum = next;
82 }
83 }
84 if (!sum) {
85 return unexpected_error(ErrorCode::RangeViolation, "accountable:aggregate_key_at_infinity");
86 }
87 out.aggregate_was_negated_ = (*sum)[0] == 0x03;
88 std::copy(sum->begin() + 1, sum->end(), out.signing_key_.begin());
89 if (out.aggregate_was_negated_) {
90 for (auto& member : out.members_) member.coefficient = member.coefficient.negate();
91 }
92 return out;
93}
94
95} // namespace purify::accountable
int purify_point_scale(purify_secp_context *context, const unsigned char point33[33], const unsigned char scalar32[32], unsigned char out33[33])
int purify_point_add(purify_secp_context *context, const unsigned char lhs33[33], const unsigned char rhs33[33], unsigned char out33[33])
C ABI bridging Purify C++ code to secp256k1-zkp BPPP functionality.
static FieldElement from_u64(std::uint64_t value)
Constructs a field element from an unsigned 64-bit integer.
Definition numeric.cpp:40
Reusable BIP340-style tagged SHA-256 helper.
Definition curve.hpp:111
std::array< unsigned char, 32 > digest_many(const std::array< std::span< const unsigned char >, Segments > &segments) const
Definition curve.hpp:123
std::array< unsigned char, 32 > digest(std::span< const unsigned char > data) const
Definition curve.hpp:117
static Result< SigningRoster > aggregate(std::span< const std::array< unsigned char, 33 > > ordered_public_keys, purify_secp_context *secp_context)
Elliptic-curve helpers, fixed parameters, and hash-to-curve utilities for Purify.
#define PURIFY_RETURN_IF_ERROR(expr, context)
Evaluates an expected-like expression and returns the wrapped error on failure.
Definition error.hpp:329
Status require_secp_context(const purify_secp_context *context, const char *error_context)
Definition common.hpp:56
constexpr Unexpected< Error > unexpected_error(ErrorCode code, const char *context=nullptr)
Constructs an unexpected Error value from a machine-readable code.
Definition error.hpp:293
bool checked_mul_size(std::size_t lhs, std::size_t rhs, std::size_t &out) noexcept
Definition common.hpp:71
Expected< T, Error > Result
Expected-returning convenience alias for Purify value-producing APIs.
Definition error.hpp:99
std::vector< unsigned char > Bytes
Dynamically sized byte string used for messages, serialized witnesses, and proofs.
Definition common.hpp:99
bool size_fits_u32(std::size_t value) noexcept
Definition common.hpp:79
int purify_bip340_validate_xonly_pubkey(purify_secp_context *context, const unsigned char xonly_pubkey32[32])
Returns nonzero when the x-only public key encoding parses successfully.
void purify_scalar_set_b32(purify_scalar *out, const unsigned char input32[32], int *overflow)
Parses a big-endian 32-byte scalar.
static FieldElement from_raw(const purify_scalar &raw) noexcept
Definition numeric.hpp:925
Opaque scalar storage compatible with secp256k1-zkp internal scalar storage.
Definition secp_bridge.h:22