purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
bppp.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
11
12#include <algorithm>
13#include <cassert>
14#include <cstdint>
15#include <limits>
16#include <optional>
17
18#include "../detail/common.hpp"
19#include "purify/secp_bridge.h"
20
22
23namespace {
24
25Result<bool> nonce_proof_matches_nonce(const NonceProof& nonce_proof, purify_secp_context* secp_context) {
26 XOnly32 xonly{};
27 int parity = 0;
28 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "nonce_proof_matches_nonce:secp_context"),
29 "nonce_proof_matches_nonce:secp_context");
30 if (purify_bip340_xonly_from_point(secp_context, nonce_proof.commitment_point.data(), xonly.data(), &parity) == 0) {
32 "nonce_proof_matches_nonce:invalid_commitment_point");
33 }
34 (void)parity;
35 return xonly == nonce_proof.nonce.xonly;
36}
37
38} // namespace
39
41 Bytes out;
42 out.reserve(kSerializedSize);
43 std::array<unsigned char, 64> packed = purify_pubkey.to_bytes_be();
44 out.insert(out.end(), packed.begin(), packed.end());
45 out.insert(out.end(), bip340_pubkey.begin(), bip340_pubkey.end());
46 return out;
47}
48
49Result<PublicKey> PublicKey::deserialize(std::span<const unsigned char> serialized,
50 purify_secp_context* secp_context) {
51 if (serialized.size() != kSerializedSize) {
52 return unexpected_error(ErrorCode::InvalidFixedSize, "PublicKey::deserialize:size");
53 }
54 PublicKey out{};
55 out.purify_pubkey = UInt512::from_bytes_be(serialized.data(), 64);
56 PURIFY_RETURN_IF_ERROR(validate_public_key(out.purify_pubkey), "PublicKey::deserialize:validate_public_key");
57 std::copy(serialized.begin() + 64, serialized.end(), out.bip340_pubkey.begin());
58 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "PublicKey::deserialize:secp_context"),
59 "PublicKey::deserialize:secp_context");
60 if (purify_bip340_validate_xonly_pubkey(secp_context, out.bip340_pubkey.data()) == 0) {
62 "PublicKey::deserialize:bip340_validate_xonly_pubkey");
63 }
64 return out;
65}
66
68 return Bytes(xonly.begin(), xonly.end());
69}
70
71Result<Nonce> Nonce::deserialize(std::span<const unsigned char> serialized,
72 purify_secp_context* secp_context) {
73 if (serialized.size() != kSerializedSize) {
74 return unexpected_error(ErrorCode::InvalidFixedSize, "Nonce::deserialize:size");
75 }
76 Nonce out{};
77 std::copy(serialized.begin(), serialized.end(), out.xonly.begin());
78 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "Nonce::deserialize:secp_context"),
79 "Nonce::deserialize:secp_context");
80 if (purify_bip340_validate_xonly_pubkey(secp_context, out.xonly.data()) == 0) {
81 return unexpected_error(ErrorCode::BackendRejectedInput, "Nonce::deserialize:bip340_validate_xonly_pubkey");
82 }
83 return out;
84}
85
87 Nonce out{};
88 std::copy(bytes.begin(), bytes.begin() + 32, out.xonly.begin());
89 return out;
90}
91
93 Scalar32 out{};
94 std::copy(bytes.begin() + 32, bytes.end(), out.begin());
95 return out;
96}
97
99 return Bytes(bytes.begin(), bytes.end());
100}
101
102Result<Signature> Signature::deserialize(std::span<const unsigned char> serialized,
103 purify_secp_context* secp_context) {
104 if (serialized.size() != kSerializedSize) {
105 return unexpected_error(ErrorCode::InvalidFixedSize, "Signature::deserialize:size");
106 }
107 Signature out{};
108 std::copy(serialized.begin(), serialized.end(), out.bytes.begin());
109 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "Signature::deserialize:secp_context"),
110 "Signature::deserialize:secp_context");
111 if (purify_bip340_validate_signature(secp_context, out.bytes.data()) == 0) {
112 return unexpected_error(ErrorCode::BackendRejectedInput, "Signature::deserialize:bip340_validate_signature");
113 }
114 return out;
115}
116
118 PURIFY_ASSIGN_OR_RETURN(auto match, nonce_proof_matches_nonce(*this, secp_context),
119 "NonceProof::serialize:nonce_proof_matches_nonce");
120 if (!match) {
121 return unexpected_error(ErrorCode::BindingMismatch, "NonceProof::serialize:nonce_mismatch");
122 }
123 if (proof.proof.size() > static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) {
124 return unexpected_error(ErrorCode::UnexpectedSize, "NonceProof::serialize:proof_size");
125 }
126 std::size_t serialized_size = 0;
127 if (!checked_add_size(168, proof.proof.size(), serialized_size)) {
128 return unexpected_error(ErrorCode::Overflow, "NonceProof::serialize:reserve");
129 }
130
131 Bytes out;
132 out.reserve(serialized_size);
133 out.push_back(kSerializationVersion);
134 detail::append_u32_le(out, static_cast<std::uint32_t>(proof.proof.size()));
135 out.insert(out.end(), nonce.xonly.begin(), nonce.xonly.end());
136 out.insert(out.end(), commitment_point.begin(), commitment_point.end());
137 out.insert(out.end(), proof.a_commitment.begin(), proof.a_commitment.end());
138 out.insert(out.end(), proof.s_commitment.begin(), proof.s_commitment.end());
139 out.insert(out.end(), proof.t2.begin(), proof.t2.end());
140 out.insert(out.end(), proof.proof.begin(), proof.proof.end());
141 return out;
142}
143
144Result<NonceProof> NonceProof::deserialize(std::span<const unsigned char> serialized,
145 purify_secp_context* secp_context) {
146 constexpr std::size_t kHeaderSize = 1 + 4 + 32 + 33 + 33 + 33 + 32;
147 if (serialized.size() < kHeaderSize) {
148 return unexpected_error(ErrorCode::InvalidFixedSize, "NonceProof::deserialize:header");
149 }
150 if (serialized[0] != kSerializationVersion) {
151 return unexpected_error(ErrorCode::BackendRejectedInput, "NonceProof::deserialize:version");
152 }
153 std::optional<std::uint32_t> proof_size = detail::read_u32_le(serialized, 1);
154 assert(proof_size.has_value() && "header length check should guarantee a u32 proof size");
155 if (*proof_size != serialized.size() - kHeaderSize) {
156 return unexpected_error(ErrorCode::InvalidFixedSize, "NonceProof::deserialize:proof_size");
157 }
158
159 NonceProof out{};
160 std::copy_n(serialized.begin() + 5, 32, out.nonce.xonly.begin());
161 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "NonceProof::deserialize:secp_context"),
162 "NonceProof::deserialize:secp_context");
163 if (purify_bip340_validate_xonly_pubkey(secp_context, out.nonce.xonly.data()) == 0) {
164 return unexpected_error(ErrorCode::BackendRejectedInput, "NonceProof::deserialize:nonce");
165 }
166 std::copy_n(serialized.begin() + 37, 33, out.commitment_point.begin());
167 std::copy_n(serialized.begin() + 70, 33, out.proof.a_commitment.begin());
168 std::copy_n(serialized.begin() + 103, 33, out.proof.s_commitment.begin());
169 std::copy_n(serialized.begin() + 136, 32, out.proof.t2.begin());
170 out.proof.proof.assign(serialized.begin() + 168, serialized.end());
171
172 PURIFY_ASSIGN_OR_RETURN(auto match, nonce_proof_matches_nonce(out, secp_context),
173 "NonceProof::deserialize:nonce_proof_matches_nonce");
174 if (!match) {
175 return unexpected_error(ErrorCode::BindingMismatch, "NonceProof::deserialize:nonce_mismatch");
176 }
177 return out;
178}
179
181 PURIFY_ASSIGN_OR_RETURN(const auto& nonce_proof_bytes, nonce_proof.serialize(secp_context),
182 "ProvenSignature::serialize:nonce_proof");
183 if (nonce_proof_bytes.size() > static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) {
184 return unexpected_error(ErrorCode::UnexpectedSize, "ProvenSignature::serialize:nonce_proof_size");
185 }
186 std::size_t serialized_size = 0;
187 if (!checked_add_size(69, nonce_proof_bytes.size(), serialized_size)) {
188 return unexpected_error(ErrorCode::Overflow, "ProvenSignature::serialize:reserve");
189 }
190
191 Bytes out;
192 out.reserve(serialized_size);
193 out.push_back(kSerializationVersion);
194 detail::append_u32_le(out, static_cast<std::uint32_t>(nonce_proof_bytes.size()));
195 out.insert(out.end(), nonce_proof_bytes.begin(), nonce_proof_bytes.end());
196 out.insert(out.end(), signature.bytes.begin(), signature.bytes.end());
197 return out;
198}
199
200Result<ProvenSignature> ProvenSignature::deserialize(std::span<const unsigned char> serialized,
201 purify_secp_context* secp_context) {
202 if (serialized.size() < 69) {
203 return unexpected_error(ErrorCode::InvalidFixedSize, "ProvenSignature::deserialize:header");
204 }
205 if (serialized[0] != kSerializationVersion) {
206 return unexpected_error(ErrorCode::BackendRejectedInput, "ProvenSignature::deserialize:version");
207 }
208 std::optional<std::uint32_t> nonce_proof_size = detail::read_u32_le(serialized, 1);
209 assert(nonce_proof_size.has_value() && "header length check should guarantee a u32 nonce proof size");
210 const std::size_t payload_size = serialized.size() - 5;
211 if (*nonce_proof_size > payload_size || payload_size - *nonce_proof_size != 64) {
212 return unexpected_error(ErrorCode::InvalidFixedSize, "ProvenSignature::deserialize:size");
213 }
214
215 PURIFY_ASSIGN_OR_RETURN(auto nonce_proof_value,
216 NonceProof::deserialize(serialized.subspan(5, *nonce_proof_size), secp_context),
217 "ProvenSignature::deserialize:nonce_proof");
218 PURIFY_ASSIGN_OR_RETURN(auto signature_value,
219 Signature::deserialize(serialized.subspan(5 + *nonce_proof_size, 64), secp_context),
220 "ProvenSignature::deserialize:signature");
221 return ProvenSignature{signature_value, nonce_proof_value};
222}
223
224} // namespace purify::puresign_plusplus
Purify result carrier that either holds a value or an error.
Definition expected.hpp:64
#define PURIFY_RETURN_IF_ERROR(expr, context)
Evaluates an expected-like expression and returns the wrapped error on failure.
Definition error.hpp:329
#define PURIFY_ASSIGN_OR_RETURN(lhs, expr, context)
Evaluates an expected-like expression, binds the value to lhs, and propagates errors.
Definition error.hpp:338
std::optional< std::uint32_t > read_u32_le(std::span< const unsigned char > bytes, std::size_t offset)
Definition common.hpp:35
void append_u32_le(Bytes &out, std::uint32_t value)
Definition common.hpp:29
std::array< unsigned char, 32 > Scalar32
Definition bppp.hpp:22
std::array< unsigned char, 32 > XOnly32
Definition bppp.hpp:23
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
Status validate_public_key(const UInt512 &packed)
Validates the packed public-key encoding range.
Definition curve.cpp:314
std::vector< unsigned char > Bytes
Dynamically sized byte string used for messages, serialized witnesses, and proofs.
Definition common.hpp:99
bool checked_add_size(std::size_t lhs, std::size_t rhs, std::size_t &out) noexcept
Definition common.hpp:63
Experimental BPPP-backed PureSign proof(R) helpers.
Narrow C ABI exposing secp256k1 scalar and HMAC helpers to the C++ headers.
int purify_bip340_xonly_from_point(purify_secp_context *context, const unsigned char point33[33], unsigned char xonly32[32], int *parity_out)
Converts a compressed secp256k1 point into its x-only public key encoding.
int purify_bip340_validate_signature(purify_secp_context *context, const unsigned char sig64[64])
Returns nonzero when the 64-byte BIP340 signature has a syntactically valid encoding.
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.
static BigUInt from_bytes_be(const unsigned char *data, std::size_t size)
Parses a big-endian byte string into the fixed-width integer.
Definition numeric.hpp:235
std::array< unsigned char, Words *8 > to_bytes_be() const
Serializes the value to a fixed-width big-endian byte array.
Definition numeric.hpp:621
PointBytes a_commitment
Witness-only outer A commitment; verifiers re-anchor it to the public statement.
Definition bppp.hpp:259
static Result< NonceProof > deserialize(std::span< const unsigned char > serialized, purify_secp_context *secp_context)
Definition bppp.cpp:144
Result< Bytes > serialize(purify_secp_context *secp_context) const
Definition bppp.cpp:117
bppp::ExperimentalCircuitZkNormArgProof proof
Definition bppp.hpp:260
static constexpr unsigned char kSerializationVersion
Definition bppp.hpp:256
Public BIP340 nonce point in x-only form.
Definition bppp.hpp:182
static Result< Nonce > deserialize(std::span< const unsigned char > serialized, purify_secp_context *secp_context)
Parses a serialized x-only nonce.
Definition bppp.cpp:71
Bytes serialize() const
Serializes this x-only nonce into its fixed-size wire format.
Definition bppp.cpp:67
static constexpr std::size_t kSerializedSize
Definition bppp.hpp:183
Result< Bytes > serialize(purify_secp_context *secp_context) const
Definition bppp.cpp:180
static constexpr unsigned char kSerializationVersion
Definition bppp.hpp:268
static Result< ProvenSignature > deserialize(std::span< const unsigned char > serialized, purify_secp_context *secp_context)
Definition bppp.cpp:200
static Result< PublicKey > deserialize(std::span< const unsigned char > serialized, purify_secp_context *secp_context)
Parses a serialized PureSign++ public-key bundle.
Definition bppp.cpp:49
Bytes serialize() const
Serializes this PureSign++ public-key bundle into its fixed-size wire format.
Definition bppp.cpp:40
static constexpr std::size_t kSerializedSize
Definition bppp.hpp:35
Standard 64-byte BIP340 signature.
Definition bppp.hpp:195
Bytes serialize() const
Serializes this signature into its fixed-size wire format.
Definition bppp.cpp:98
static constexpr std::size_t kSerializedSize
Definition bppp.hpp:196
Scalar32 s() const
Returns the 32-byte Schnorr s scalar encoded in the last 32 signature bytes.
Definition bppp.cpp:92
Nonce nonce() const
Returns the x-only public nonce encoded in the first 32 signature bytes.
Definition bppp.cpp:86
static Result< Signature > deserialize(std::span< const unsigned char > serialized, purify_secp_context *secp_context)
Parses a serialized BIP340 signature.
Definition bppp.cpp:102