myl7/fss 2.0.0
Function secret sharing (FSS) primitives including distributed point/comparison function (DPF/DCF)
Loading...
Searching...
No Matches
vdpf.cuh
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
36
37#pragma once
38#include <cuda_runtime.h>
39#include <cuda/std/array>
40#include <cuda/std/span>
41#include <cuda/std/tuple>
42#include <type_traits>
43#include <cstddef>
44#include <cassert>
45#include <vector>
46#ifdef _OPENMP
47#include <omp.h>
48#endif
49#include <fss/group.cuh>
50#include <fss/prg.cuh>
51#include <fss/hash.cuh>
52#include <fss/util.cuh>
53#include <fss/constraints.cuh>
54#include <fss/dpf.cuh>
55#include <fss/compat.cuh>
56#include <stdexcept>
57
58namespace fss {
59
60namespace detail {
61class SchemeAccess;
62} // namespace detail
63
64namespace detail {
65
66// Split correction-word view over the caller's arrays. Layer i is read from
67// cws[i*stride] and the right-control correction is bit i of packed_tr. The
68// terminal is rebuilt as a plain Cw with no control bit, so bits of packed_tr at
69// or above in_bits are never read. The layer LSB keeps the left-control
70// correction exactly as the Cw layout does. operator[] recovers the Cw the shared
71// evaluation consumes, so its body stays identical on the Cw and split paths.
72template <typename Cw, typename In, int in_bits>
73struct VdpfCwsView {
74 const int4 *s;
75 In packed_tr;
76 size_t stride;
77
78 __host__ __device__ inline Cw operator[](int i) const {
79 const int4 s_cw = s[static_cast<size_t>(i) * stride];
80 if (i >= in_bits) return Cw{s_cw, false};
81 return Cw{s_cw, ((packed_tr >> i) & 1) != 0};
82 }
83};
84
85} // namespace detail
86
112template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In = uint>
114 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
115class Vdpf {
116public:
129 struct __align__(32) Cw {
130 int4 s;
131 bool tr;
132 };
133 // For only 1 and aligned memory access on GPU
134 static_assert(sizeof(Cw) == 32);
135
137 static constexpr int kCwCount = in_bits + 1;
138
139 using Input = In;
140
148 __host__ __device__ explicit Vdpf(Prg prg, XorHash xor_hash, Hash hash);
149
192 __host__ __device__ int Gen(cuda::std::span<int4> cws, In &tr, cuda::std::span<int4, 4> cs,
193 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf, size_t stride = 1);
194
225 __host__ __device__ cuda::std::array<int4, 4> Eval(bool b, int4 s0, cuda::std::span<const int4> cws,
226 In tr, cuda::std::span<const int4, 4> cs, In x, int4 &y, size_t stride = 1);
227
240 void Prove(cuda::std::span<const cuda::std::array<int4, 4>> pi_tildes, cuda::std::span<const int4, 4> cs,
241 cuda::std::span<int4, 4> pi);
242
250 __host__ __device__ static bool Verify(cuda::std::span<const int4, 4> pi0, cuda::std::span<const int4, 4> pi1);
251
278 void EvalAll(bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
279 cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi,
280 int par_depth = 0, size_t stride = 1);
281
317 void EvalRange(bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws,
318 In tr, cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys,
319 cuda::std::span<int4, 4> pi, int par_depth = 0, size_t stride = 1);
320
340 void ProveMerkle(cuda::std::span<const cuda::std::array<int4, 4>> pi_tildes, cuda::std::span<const int4, 4> cs,
341 cuda::std::span<int4, 4> pi, int par_depth = 0);
342
388 void BatchEval(bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
389 cuda::std::span<const int4, 4> cs, cuda::std::span<const In> xs,
390 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, size_t stride = 1);
391
438 void BatchEvalMerkle(bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
439 cuda::std::span<const int4, 4> cs, cuda::std::span<const In> xs,
440 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, size_t stride = 1);
441
473 void EvalAllMerkle(bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
474 cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi,
475 int par_depth = 0, size_t stride = 1);
476
515 template <int frontier_depth = 16, int threads_per_block = 256>
516 __host__ cudaError_t EvalAllMerkleGpu(bool b, int4 s0,
517 cuda::std::span<const int4> cws, In tr, cuda::std::span<const int4, 4> cs,
518 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi,
519 cudaStream_t stream = 0, size_t stride = 1) const
520 requires(in_bits < sizeof(size_t) * 8);
521
557 void EvalRangeMerkle(bool b, In first, In last, int4 s0,
558 cuda::std::span<const int4> cws, In tr, cuda::std::span<const int4, 4> cs,
559 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, int par_depth = 0,
560 size_t stride = 1);
561
606 template <int frontier_depth = 16, int threads_per_block = 256>
607 __host__ cudaError_t EvalRangeMerkleGpu(bool b, In first, In last, int4 s0,
608 cuda::std::span<const int4> cws, In tr, cuda::std::span<const int4, 4> cs,
609 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi,
610 cudaStream_t stream = 0, size_t stride = 1) const;
611
669 template <int threads_per_block = 256>
670 __host__ cudaError_t BatchEvalMerkleGpu(bool b, int4 s0,
671 cuda::std::span<const int4> cws, In tr, cuda::std::span<const int4, 4> cs,
672 cuda::std::span<const In> xs,
673 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi,
674 cudaStream_t stream = 0, size_t stride = 1) const;
675
676#if FSS_ENABLE_V1_COMPAT
677 // Deprecated v1.3.0 aggregate-Cw compatibility entries; see fss/compat.cuh.
678 // Each converts through a compile-time-sized scratch buffer and the current
679 // split-component method above at stride 1. cws has in_bits entries, and the
680 // output correction word leaves through the separate ocw parameter.
681
689 [[deprecated("use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<int4, 4>, cuda::std::span<const int4, 2>, In, int4, size_t) overload")]]
690 __host__ __device__ int Gen(Cw cws[], cuda::std::array<int4, 4> &cs, int4 &ocw,
691 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf);
692
694 [[deprecated("use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, cuda::std::span<const int4, 4>, In, int4&, size_t) overload")]]
695 __host__ __device__ cuda::std::array<int4, 4> Eval(bool b, int4 s0,
696 cuda::std::span<const Cw> cws, cuda::std::span<const int4, 4> cs, int4 ocw,
697 In x, int4 &y);
698
700 [[deprecated("use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, cuda::std::span<const int4, 4>, cuda::std::span<int4>, cuda::std::span<int4, 4>, int, size_t) overload")]]
701 void EvalAll(bool b, int4 s0, cuda::std::span<const Cw> cws,
702 cuda::std::span<const int4, 4> cs, int4 ocw, cuda::std::span<int4> ys,
703 cuda::std::array<int4, 4> &pi);
704
705 // Deprecated v1.3.0 aggregate-Key entries; see fss/compat.cuh. The Key and
706 // EvalResult aliases expose compat records under the legacy nested names,
707 // while the method declarations spell the record types so a deprecated name
708 // never enters a member signature. The aggregate-Key Gen() of the release,
709 // with its automatic retry, is not restored; generate through the
710 // split-component Gen() and keep the components, or assemble this Key form
711 // from its outputs. Evaluations and proofs follow the current protocol; the
712 // old proof wire identity is not preserved.
713
715 using Key [[deprecated("use the split-component Gen/Eval/EvalAll with separate cws, tr, and cs arrays")]] =
716 compat::detail::VdpfKeyRecord<Vdpf, Cw, in_bits>;
717
719 using EvalResult [[deprecated("use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, cuda::std::span<const int4, 4>, In, int4&, size_t) overload")]] =
720 compat::detail::EvalResultRecord<Vdpf>;
721
723 [[deprecated("use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, cuda::std::span<const int4, 4>, In, int4&, size_t) overload")]]
724 __host__ __device__ compat::detail::EvalResultRecord<Vdpf> Eval(
725 bool b, const compat::detail::VdpfKeyRecord<Vdpf, Cw, in_bits> &key, In x);
726
728 [[deprecated("use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, cuda::std::span<const int4, 4>, cuda::std::span<int4>, cuda::std::span<int4, 4>, int, size_t) overload")]]
729 void EvalAll(bool b, const compat::detail::VdpfKeyRecord<Vdpf, Cw, in_bits> &key,
730 cuda::std::span<int4> ys, cuda::std::array<int4, 4> &pi);
731
733 [[deprecated("use the split-component EvalAllMerkle(bool, int4, cuda::std::span<const int4>, In, cuda::std::span<const int4, 4>, cuda::std::span<int4>, cuda::std::span<int4, 4>, int, size_t) overload")]]
734 void EvalAllMerkle(bool b, const compat::detail::VdpfKeyRecord<Vdpf, Cw, in_bits> &key,
735 cuda::std::span<int4> ys, cuda::std::array<int4, 4> &pi);
736#endif // FSS_ENABLE_V1_COMPAT
737
738private:
739 friend class detail::SchemeAccess;
740
741 Prg prg_;
742 XorHash xor_hash_;
743 Hash hash_;
744
745 template <int frontier_depth, int threads_per_block>
746 struct GpuLaunchShape;
747
748 // Split correction-word view over the caller's arrays; see detail::VdpfCwsView.
749 using CwsView = detail::VdpfCwsView<Cw, In, in_bits>;
750
751 // Writes Cw slots in the legacy AoS layout, covering each full 32B slot.
752 struct AosWriter {
753 Cw *cws;
754
755 __host__ __device__ void Write(int i, int4 s_cw, bool tr_cw) const {
756 // Both 16B halves written so the full 32B slot gets unmasked stores.
757 // {s_cw, tr_cw} alone leaves padding undefined: NVCC emits a masked
758 // 256-bit store, and the partial-sector write forces a write-allocate
759 // round trip per level on GPU (measured ~2x Gen time).
760 int4 *cw_slot = reinterpret_cast<int4 *>(&cws[i]);
761 cw_slot[0] = s_cw;
762 cw_slot[1] = make_int4(tr_cw, 0, 0, 0);
763 }
764
765 __host__ __device__ void Finish(int4 terminal) const {
766 cws[in_bits].s = terminal;
767 cws[in_bits].tr = false;
768 }
769 };
770
771 // Writes strided seed components and accumulates packed right-control bits locally.
772 struct CwsWriter {
773 int4 *cws;
774 size_t stride;
775 In &tr;
776 In packed_tr = 0;
777
778 __host__ __device__ void Write(int i, int4 s_cw, bool tr_cw) {
779 cws[static_cast<size_t>(i) * stride] = s_cw;
780 packed_tr |= static_cast<In>(tr_cw) << i;
781 }
782
783 __host__ __device__ void Finish(int4 terminal) {
784 cws[static_cast<size_t>(in_bits) * stride] = terminal;
785 tr = packed_tr;
786 }
787 };
788
789 // Shared correction generation and verification-hash computation. The writer
790 // is passed by value and owns only output access, so the existing algorithm
791 // and backend use stay unchanged.
792 template <typename Writer>
793 __host__ __device__ int Generate(Writer writer, cuda::std::span<int4, 4> cs, const int4 *s0s, In a, int4 b_buf);
794
795 // Shared scalar evaluation over the Cw array or the split view.
796 template <typename Cws>
797 __host__ __device__ cuda::std::array<int4, 4> EvalCws(
798 bool b, int4 s0, Cws cws, cuda::std::span<const int4, 4> cs, In x, int4 &y);
799
800 // Shared range evaluation over the Cw array or the split view. Reads the
801 // terminal and checks its Debug precondition before any traversal.
802 template <typename Cws>
803 void EvalRangeCws(bool b, In first, In last, int4 s0, Cws cws,
804 cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys,
805 cuda::std::span<int4, 4> pi, int par_depth);
806
807 // Shared Merkle range evaluation over the Cw array or the split view. Reads
808 // the terminal and checks its Debug precondition before any traversal.
809 template <typename Cws>
810 void EvalRangeMerkleCws(bool b, In first, In last, int4 s0, Cws cws,
811 cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys,
812 cuda::std::span<int4, 4> pi, int par_depth);
813
814 // Shared batch evaluation over the Cw array or the split view.
815 template <bool merkle, typename Cws>
816 void EvalBatchCws(bool b, int4 s0, Cws cws, cuda::std::span<const int4, 4> cs,
817 cuda::std::span<const In> xs, cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi);
818
819 template <typename Cws>
820 void EvalTree(
821 int4 st, Cws cws, cuda::std::span<int4> ys, size_t l, size_t r, int i, int par_depth_);
822
823 template <typename Cws>
824 void EvalPrunedTree(int4 st, Cws cws, cuda::std::span<int4> ys, In l, In r, int i,
825 int par_depth_, In first, In last);
826};
827
828// -----------------------------------------------------------------------------
829
830namespace detail {
831
832template <typename Hash>
833__host__ __device__ inline cuda::std::array<int4, 2> VdpfMerkleFoldNode(
834 Hash &hash, const cuda::std::array<int4, 2> *nodes, size_t count, size_t index) {
835 const size_t left = 2 * index;
836 if (left + 1 == count) return nodes[left];
837 const int4 msg[4] = {nodes[left][0], nodes[left][1], nodes[left + 1][0], nodes[left + 1][1]};
838 return hash.Hash(cuda::std::span<const int4, 4>(msg));
839}
840
841} // namespace detail
842
843namespace gpu::detail {
844
845#if defined(__CUDACC__)
846
847// Strided correction-word readers shared by the split VDPF kernels: layer i is
848// cws[i*stride] with right-control bit i of packed_tr, and the terminal is the
849// plain int4 at cws[in_bits*stride].
850__device__ inline int4 VdpfCwSeed(const int4 *cws, size_t stride, int level) {
851 return cws[static_cast<size_t>(level) * stride];
852}
853
854template <typename In>
855__device__ inline bool VdpfCwRight(const In &packed_tr, int level) {
856 return ((packed_tr >> level) & 1) != 0;
857}
858
859template <int in_bits, int threads_per_block, typename Group, typename Prg, typename XorHash,
860 typename Hash, typename In>
861__device__ void VdpfRangeMerkleLeavesWork(bool b, In first,
862 int subtree_depth, int frontier_depth, int block_root_depth, int4 s0, const int4 *cws,
863 In packed_tr, size_t stride, const int4 *cs,
864 int4 *ys, cuda::std::array<int4, 2> *leaves, Prg prg,
865 XorHash xor_hash, Hash hash) {
866 using MathScheme = Dpf<in_bits, Group, Prg, In>;
867 const int root_depth = in_bits - subtree_depth;
868 const int block_subtree_depth = frontier_depth - block_root_depth;
869 const int tail_depth = subtree_depth - frontier_depth;
870 __shared__ int4 frontier[threads_per_block];
871 if (threadIdx.x == 0) {
872 int4 st = util::SetLsb(s0, b);
873 for (int level = 0; level < root_depth; ++level) {
874 const bool right = (first >> (in_bits - level - 1)) & 1;
875 st = fss::detail::SchemeAccess::DpfPathStep<MathScheme>(
876 prg, st, VdpfCwSeed(cws, stride, level), VdpfCwRight(packed_tr, level), right);
877 }
878 for (int level = 0; level < block_root_depth; ++level) {
879 const bool right = (blockIdx.x >> (block_root_depth - level - 1)) & 1;
880 st = fss::detail::SchemeAccess::DpfPathStep<MathScheme>(prg, st,
881 VdpfCwSeed(cws, stride, root_depth + level),
882 VdpfCwRight(packed_tr, root_depth + level), right);
883 }
884 frontier[0] = st;
885 }
886 __syncthreads();
887 for (int level = 0; level < block_subtree_depth; ++level) {
888 const bool active = threadIdx.x < (1u << (level + 1));
889 int4 st = {0, 0, 0, 0};
890 if (active) st = frontier[threadIdx.x >> 1];
891 __syncthreads();
892 if (active) {
893 int4 left, right;
894 const int cw_index = root_depth + block_root_depth + level;
895 fss::detail::SchemeAccess::DpfExpandNode<MathScheme>(prg, st,
896 VdpfCwSeed(cws, stride, cw_index), VdpfCwRight(packed_tr, cw_index), left, right);
897 frontier[threadIdx.x] = (threadIdx.x & 1) ? right : left;
898 }
899 __syncthreads();
900 }
901 int4 pending[8];
902 int4 node = frontier[threadIdx.x];
903 const int frontier_level = root_depth + frontier_depth;
904 int level = frontier_level;
905 const int count = 1 << tail_depth;
906 const size_t thread_index = static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
907 const size_t offset = thread_index << tail_depth;
908 const auto cw = Group::From(cws[static_cast<size_t>(in_bits) * stride]);
909 for (int i = 0; i < count; ++i) {
910 while (level < in_bits) {
911 int4 left, right;
912 fss::detail::SchemeAccess::DpfExpandNode<MathScheme>(prg, node,
913 VdpfCwSeed(cws, stride, level), VdpfCwRight(packed_tr, level), left, right);
914 pending[level - frontier_level] = right;
915 node = left;
916 ++level;
917 }
918 const bool t = util::GetLsb(node);
919 const int4 seed = util::SetLsb(node, false);
920 auto value = Group::From(seed);
921 if (t) value = value + cw;
922 if (b) value = -value;
923 ys[offset + i] = value.Into();
924 const In x = static_cast<In>(first + offset + i);
925 auto corrected = xor_hash.Hash(cuda::std::tuple<int4, const int4>{fss::detail::Pack(x), seed});
926 if (t) corrected = util::Xor(cuda::std::span<const int4, 4>(corrected),
927 cuda::std::span<const int4, 4>(cs, 4));
928 leaves[offset + i] = hash.Hash(cuda::std::span<const int4, 4>(corrected));
929 if (i + 1 < count) {
930 int next = i + 1;
931 level = in_bits - 1;
932 while ((next & 1) == 0) {
933 --level;
934 next >>= 1;
935 }
936 node = pending[level - frontier_level];
937 ++level;
938 }
939 }
940}
941template <int in_bits, int threads_per_block, typename Group, typename Prg, typename XorHash,
942 typename Hash, typename In>
943__global__ void VdpfRangeMerkleLeavesKernel(bool b, In first, int subtree_depth,
944 int frontier_depth, int block_root_depth, int4 s0, const int4 *cws,
945 In packed_tr, size_t stride, const int4 *cs,
946 int4 *ys, cuda::std::array<int4, 2> *leaves, Prg prg,
947 XorHash xor_hash, Hash hash) {
948 VdpfRangeMerkleLeavesWork<in_bits, threads_per_block, Group, Prg, XorHash, Hash, In>(b, first, subtree_depth,
949 frontier_depth, block_root_depth, s0, cws, packed_tr, stride, cs, ys, leaves, prg,
950 xor_hash, hash);
951}
952
953// Device-key counterpart: the party seed and the packed control are loaded
954// from caller device storage; the shared traversal is unchanged.
955template <int in_bits, int threads_per_block, typename Group, typename Prg, typename XorHash,
956 typename Hash, typename In>
957__global__ void VdpfRangeMerkleLeavesDeviceKeyKernel(bool b, In first,
958 int subtree_depth, int frontier_depth, int block_root_depth, const int4 *s0,
959 const int4 *cws, const In *tr, size_t stride, const int4 *cs,
960 int4 *ys, cuda::std::array<int4, 2> *leaves, Prg prg,
961 XorHash xor_hash, Hash hash) {
962 VdpfRangeMerkleLeavesWork<in_bits, threads_per_block, Group, Prg, XorHash, Hash, In>(b, first, subtree_depth,
963 frontier_depth, block_root_depth, s0[0], cws, tr[0], stride, cs, ys, leaves, prg,
964 xor_hash, hash);
965}
966
967
968template <typename Hash>
969__global__ void VdpfRangeMerkleFoldKernel(const cuda::std::array<int4, 2> *nodes,
970 size_t count, cuda::std::array<int4, 2> *next, Hash hash) {
971 const size_t index = static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
972 if (index < count / 2 + count % 2)
973 next[index] = fss::detail::VdpfMerkleFoldNode(hash, nodes, count, index);
974}
975
976template <typename Node>
977__global__ void VdpfRangeMerkleProofKernel(const Node *root,
978 const int4 *cs, int4 *pi) {
979 pi[0] = (*root)[0];
980 pi[1] = (*root)[1];
981 pi[2] = cs[2];
982 pi[3] = cs[3];
983}
984
985// One thread per query: walks the raw-key path of xs[index] from the root and
986// writes the output share and the hashed corrected per-point hash as the leaf
987// of the batch Merkle fold. The same per-point math as Vdpf::Eval, fed by the
988// split cws/packed-tr layout.
989template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
990__device__ void VdpfBatchMerkleLeavesWork(bool b, int4 s0, const int4 *cws,
991 In packed_tr, size_t stride, const int4 *cs,
992 const In *xs, int4 *ys, cuda::std::array<int4, 2> *leaves, size_t count,
993 Prg prg, XorHash xor_hash,
994 Hash hash) {
995 using MathScheme = Dpf<in_bits, Group, Prg, In>;
996 const size_t index = static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
997 if (index >= count) return;
998 const In x = xs[index];
999 int4 node = util::SetLsb(s0, b);
1000 for (int level = 0; level < in_bits; ++level) {
1001 const bool right = (x >> (in_bits - level - 1)) & 1;
1002 node = fss::detail::SchemeAccess::DpfPathStep<MathScheme>(
1003 prg, node, VdpfCwSeed(cws, stride, level), VdpfCwRight(packed_tr, level), right);
1004 }
1005 const bool t = util::GetLsb(node);
1006 const int4 seed = util::SetLsb(node, false);
1007 auto value = Group::From(seed);
1008 const auto cw = Group::From(cws[static_cast<size_t>(in_bits) * stride]);
1009 if (t) value = value + cw;
1010 if (b) value = -value;
1011 ys[index] = value.Into();
1012 auto corrected = xor_hash.Hash(cuda::std::tuple<int4, const int4>{fss::detail::Pack(x), seed});
1013 if (t) corrected = util::Xor(cuda::std::span<const int4, 4>(corrected),
1014 cuda::std::span<const int4, 4>(cs, 4));
1015 leaves[index] = hash.Hash(cuda::std::span<const int4, 4>(corrected));
1016}
1017template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1018__global__ void VdpfBatchMerkleLeavesKernel(bool b, int4 s0, const int4 *cws,
1019 In packed_tr, size_t stride, const int4 *cs,
1020 const In *xs, int4 *ys, cuda::std::array<int4, 2> *leaves, size_t count,
1021 Prg prg, XorHash xor_hash,
1022 Hash hash) {
1023 VdpfBatchMerkleLeavesWork<in_bits, Group, Prg, XorHash, Hash, In>(b, s0, cws, packed_tr, stride, cs, xs, ys,
1024 leaves, count, prg, xor_hash, hash);
1025}
1026
1027// Device-key counterpart of the batch leaves kernel; see above.
1028template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1029__global__ void VdpfBatchMerkleLeavesDeviceKeyKernel(bool b, const int4 *s0, const int4 *cws,
1030 const In *tr, size_t stride, const int4 *cs,
1031 const In *xs, int4 *ys, cuda::std::array<int4, 2> *leaves,
1032 size_t count, Prg prg, XorHash xor_hash,
1033 Hash hash) {
1034 VdpfBatchMerkleLeavesWork<in_bits, Group, Prg, XorHash, Hash, In>(b, s0[0], cws, tr[0], stride, cs, xs, ys,
1035 leaves, count, prg, xor_hash, hash);
1036}
1037
1038
1039template <int in_bits, int frontier_depth, int block_root_depth, typename Group, typename Prg,
1040 typename XorHash, typename Hash, typename In>
1041__device__ void VdpfMerkleFrontierWork(bool b, int4 s0, const int4 *cws,
1042 In packed_tr, size_t stride, const int4 *cs,
1043 int4 *ys, cuda::std::array<int4, 2> *roots, Prg prg,
1044 XorHash xor_hash, Hash hash) {
1045 using MathScheme = Dpf<in_bits, Group, Prg, In>;
1046 constexpr int kTailDepth = in_bits - frontier_depth;
1047 constexpr int kBlockSubtreeDepth = frontier_depth - block_root_depth;
1048 const int thread_index = blockIdx.x * blockDim.x + threadIdx.x;
1049 if (thread_index >= (1 << frontier_depth)) return;
1050
1051 // Phase 0: block root walk (top block_root_depth levels) by thread 0.
1052 __shared__ int4 shared_frontier[1 << kBlockSubtreeDepth];
1053 if (threadIdx.x == 0) {
1054 int4 st = util::SetLsb(s0, b);
1055#pragma unroll
1056 for (int i = 0; i < block_root_depth; ++i) {
1057 bool x_bit = (blockIdx.x >> (block_root_depth - 1 - i)) & 1;
1058 {
1059 auto node_prg = prg;
1060 st = fss::detail::SchemeAccess::DpfPathStep<MathScheme>(
1061 node_prg, st, VdpfCwSeed(cws, stride, i), VdpfCwRight(packed_tr, i), x_bit);
1062 }
1063 }
1064 shared_frontier[0] = st;
1065 }
1066 __syncthreads();
1067
1068 // Phase 1: breadth-parallel expansion of the block's subtree, levels block_root_depth..frontier_depth.
1069#pragma unroll
1070 for (int i = 0; i < kBlockSubtreeDepth; ++i) {
1071 const bool active = threadIdx.x < (1 << (i + 1));
1072 int4 n = {0, 0, 0, 0};
1073 if (active) n = shared_frontier[threadIdx.x >> 1];
1074 // All parent reads must finish before any child overwrites the frontier.
1075 __syncthreads();
1076 if (active) {
1077 int4 left, right;
1078 {
1079 auto node_prg = prg;
1080 fss::detail::SchemeAccess::DpfExpandNode<MathScheme>(node_prg, n,
1081 VdpfCwSeed(cws, stride, block_root_depth + i),
1082 VdpfCwRight(packed_tr, block_root_depth + i), left, right);
1083 }
1084 shared_frontier[threadIdx.x] = (threadIdx.x & 1) ? right : left;
1085 }
1086 __syncthreads();
1087 }
1088
1089 // Phase 2: expand the subtree below depth frontier_depth, convert each leaf to the output
1090 // share and the corrected verification hash, and fold the leaf hashes into
1091 // the thread's Merkle subtree on the fly.
1092 int4 nodes[1 << kTailDepth];
1093 nodes[0] = shared_frontier[threadIdx.x];
1094 int count = 1;
1095#pragma unroll
1096 for (int lev = frontier_depth; lev < in_bits; ++lev) {
1097#pragma unroll
1098 for (int k = count - 1; k >= 0; --k) {
1099 int4 left, right;
1100 {
1101 auto node_prg = prg;
1102 fss::detail::SchemeAccess::DpfExpandNode<MathScheme>(node_prg, nodes[k],
1103 VdpfCwSeed(cws, stride, lev), VdpfCwRight(packed_tr, lev), left, right);
1104 }
1105 nodes[2 * k] = left;
1106 nodes[2 * k + 1] = right;
1107 }
1108 count *= 2;
1109 }
1110
1111 auto ocw_group = Group::From(cws[static_cast<size_t>(in_bits) * stride]);
1112 const int output_offset = thread_index << kTailDepth;
1113 cuda::std::array<int4, 2> stack[kTailDepth + 1];
1114#pragma unroll
1115 for (int k = 0; k < count; ++k) {
1116 int4 st_cur = nodes[k];
1117 bool t_cur = util::GetLsb(st_cur);
1118 int4 s_cur = util::SetLsb(st_cur, false);
1119
1120 // Output share
1121 auto g = Group::From(s_cur);
1122 if (t_cur) g = g + ocw_group;
1123 if (b) g = -g;
1124 ys[output_offset + k] = g.Into();
1125
1126 // Leaf: the corrected verification hash of the point
1127 int4 x_buf = fss::detail::Pack(static_cast<In>(output_offset + k));
1128 auto pi_tilde = xor_hash.Hash(cuda::std::tuple<int4, const int4>{x_buf, s_cur});
1129 if (t_cur) {
1130 pi_tilde = util::Xor(cuda::std::span<const int4, 4>(pi_tilde), cuda::std::span<const int4, 4>(cs, 4));
1131 }
1132 int4 leaf_msg[4] = {pi_tilde[0], pi_tilde[1], pi_tilde[2], pi_tilde[3]};
1133 cuda::std::array<int4, 2> node = hash.Hash(cuda::std::span<const int4, 4>(leaf_msg));
1134
1135 // Stream the leaf into the thread's subtree: combine with the pending
1136 // sibling stack entries while the leaf index has trailing one bits.
1137 int rest = k;
1138 int lvl = 0;
1139#pragma unroll
1140 while (rest != 0 && (rest & 1) == 1) {
1141 int4 fold_msg[4] = {stack[lvl][0], stack[lvl][1], node[0], node[1]};
1142 node = hash.Hash(cuda::std::span<const int4, 4>(fold_msg));
1143 rest >>= 1;
1144 ++lvl;
1145 }
1146 stack[lvl] = node;
1147 }
1148
1149 // Block fold of the threads' subtree roots, in thread order: adjacent pairs
1150 // at each stride, slot idx combining with idx + stride.
1151 __shared__ cuda::std::array<int4, 2> s_node[1 << kBlockSubtreeDepth];
1152 s_node[threadIdx.x] = stack[kTailDepth];
1153 __syncthreads();
1154#pragma unroll
1155 for (int stride = 1; stride < (1 << kBlockSubtreeDepth); stride <<= 1) {
1156 int idx = 2 * stride * threadIdx.x;
1157 if (idx < (1 << kBlockSubtreeDepth)) {
1158 int4 fold_msg[4] = {s_node[idx][0], s_node[idx][1], s_node[idx + stride][0], s_node[idx + stride][1]};
1159 s_node[idx] = hash.Hash(cuda::std::span<const int4, 4>(fold_msg));
1160 }
1161 __syncthreads();
1162 }
1163 if (threadIdx.x == 0) roots[blockIdx.x] = s_node[0];
1164}
1165template <int in_bits, int frontier_depth, int block_root_depth, typename Group, typename Prg,
1166 typename XorHash, typename Hash, typename In>
1167__global__ void VdpfMerkleFrontierKernel(bool b, int4 s0, const int4 *cws,
1168 In packed_tr, size_t stride, const int4 *cs,
1169 int4 *ys, cuda::std::array<int4, 2> *roots, Prg prg,
1170 XorHash xor_hash, Hash hash) {
1171 VdpfMerkleFrontierWork<in_bits, frontier_depth, block_root_depth, Group, Prg, XorHash, Hash, In>(
1172 b, s0, cws, packed_tr, stride, cs, ys, roots, prg, xor_hash, hash);
1173}
1174
1175// Device-key counterpart of the frontier kernel; see above.
1176template <int in_bits, int frontier_depth, int block_root_depth, typename Group, typename Prg,
1177 typename XorHash, typename Hash, typename In>
1178__global__ void VdpfMerkleFrontierDeviceKeyKernel(bool b, const int4 *s0, const int4 *cws,
1179 const In *tr, size_t stride, const int4 *cs,
1180 int4 *ys, cuda::std::array<int4, 2> *roots, Prg prg,
1181 XorHash xor_hash, Hash hash) {
1182 VdpfMerkleFrontierWork<in_bits, frontier_depth, block_root_depth, Group, Prg, XorHash, Hash, In>(
1183 b, s0[0], cws, tr[0], stride, cs, ys, roots, prg, xor_hash, hash);
1184}
1185
1186
1187// Folds the per-block subtree roots into the final 64B proof. The root count
1188// is (1 << frontier_depth) / threads_per_block = 2^block_root_depth, at most one block wide; only the first nroots
1189// shared slots participate, so the rest stay unwritten and unread.
1190template <int threads_per_block, typename Hash>
1191__global__ void VdpfMerkleFinalizeKernel(const cuda::std::array<int4, 2> *roots, int nroots, const int4 *cs,
1192 int4 *pi, Hash hash) {
1193 __shared__ cuda::std::array<int4, 2> s_node[threads_per_block];
1194 if (threadIdx.x < nroots) s_node[threadIdx.x] = roots[threadIdx.x];
1195 __syncthreads();
1196 for (int stride = 1; stride < nroots; stride <<= 1) {
1197 int idx = 2 * stride * threadIdx.x;
1198 if (idx < nroots) {
1199 int4 fold_msg[4] = {s_node[idx][0], s_node[idx][1], s_node[idx + stride][0], s_node[idx + stride][1]};
1200 s_node[idx] = hash.Hash(cuda::std::span<const int4, 4>(fold_msg));
1201 }
1202 __syncthreads();
1203 }
1204 if (threadIdx.x == 0) {
1205 pi[0] = s_node[0][0];
1206 pi[1] = s_node[0][1];
1207 pi[2] = cs[2];
1208 pi[3] = cs[3];
1209 }
1210}
1211
1212// Point-evaluate in_bits-level VDPF keys, one thread per key. Same seed walk as
1213// the DPF kernel plus the per-point verification hash; the walk carries no hash
1214// cost, and the tail applies the verification hash and, when t, folds in cs. cws
1215// holds dense level-major correction words, the layout the split form of Gen()
1216// writes: layer i of key k is cws[i*nkeys + k] and the terminal is
1217// cws[in_bits*nkeys + k]. tr[k] carries key k's packed right-control bits, bit i
1218// for layer i. cs stays per-key: it is read once per evaluation, not once per level.
1219template <int in_bits, int unroll, bool b, typename Group, typename Prg, typename XorHash,
1220 typename In, typename Query>
1221__global__ void VdpfEvalPointKernel(int4 *ys, cuda::std::array<int4, 4> *pi, const int4 *seeds,
1222 const int4 *cws, const In *tr, const cuda::std::array<int4, 4> *cs, const Query *xs,
1223 int nkeys, Prg prg, XorHash xor_hash) {
1224 static_assert(in_bits <= sizeof(In) * 8, "In too narrow for in_bits");
1225 int tid = blockIdx.x * blockDim.x + threadIdx.x;
1226 if (tid >= nkeys) return;
1227 int4 s = fss::util::SetLsb(seeds[tid], false);
1228 bool t = b;
1229 In x = static_cast<In>(xs[tid]);
1230 const In packed_tr = tr[tid];
1231
1232#pragma unroll unroll
1233 for (int i = 0; i < in_bits; ++i) {
1234 int4 s_cw = cws[static_cast<size_t>(i) * nkeys + tid];
1235 bool tl_cw = fss::util::GetLsb(s_cw);
1236 s_cw = fss::util::SetLsb(s_cw, false);
1237 bool tr_cw = ((packed_tr >> i) & 1) != 0;
1238
1239 auto g = prg.Gen(s);
1240 int4 sl = g[0], sr = g[1];
1241 bool tl = fss::util::GetLsb(sl);
1242 sl = fss::util::SetLsb(sl, false);
1243 bool tr = fss::util::GetLsb(sr);
1244 sr = fss::util::SetLsb(sr, false);
1245
1246 if (t) {
1247 sl = fss::util::Xor(sl, s_cw);
1248 sr = fss::util::Xor(sr, s_cw);
1249 tl = tl ^ tl_cw;
1250 tr = tr ^ tr_cw;
1251 }
1252
1253 bool x_bit = GetBit(x, in_bits - 1 - i);
1254 if (x_bit) {
1255 s = sr;
1256 t = tr;
1257 } else {
1258 s = sl;
1259 t = tl;
1260 }
1261 }
1262
1263 auto y = Group::From(s);
1264 if (t) y = y + Group::From(cws[static_cast<size_t>(in_bits) * nkeys + tid]);
1265 if constexpr (b) y = -y;
1266 ys[tid] = y.Into();
1267
1268 int4 x_buf = fss::detail::Pack(x);
1269 auto pi_tilde = xor_hash.Hash(cuda::std::tuple<int4, const int4>{x_buf, s});
1270 if (t) {
1271 pi_tilde = util::Xor(cuda::std::span<const int4, 4>(pi_tilde), cuda::std::span<const int4, 4>(cs[tid]));
1272 }
1273#pragma unroll
1274 for (int j = 0; j < 4; ++j) pi[tid][j] = pi_tilde[j];
1275}
1276
1298template <int unroll = 4, int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In,
1299 typename Query = In>
1300 requires QueryFor<Query, in_bits>
1301void VdpfEvalPointGpu(bool b, cuda::std::span<const int4> seeds, cuda::std::span<const int4> cws,
1302 cuda::std::span<const In> tr, cuda::std::span<const cuda::std::array<int4, 4>> cs,
1303 cuda::std::span<const Query> xs, int4 *ys, cuda::std::span<cuda::std::array<int4, 4>> pi, int nkeys,
1304 const Vdpf<in_bits, Group, Prg, XorHash, Hash, In> &vdpf, cudaStream_t stream = 0) {
1305 if (nkeys < 0)
1306 throw std::invalid_argument("VdpfEvalPointGpu: nkeys must be nonnegative");
1307 if (nkeys == 0) return;
1308 const size_t count = static_cast<size_t>(nkeys);
1309 constexpr size_t kLevels = static_cast<size_t>(in_bits) + 1;
1310 if (count > SIZE_MAX / kLevels || cws.size() < count * kLevels)
1311 throw std::invalid_argument("VdpfEvalPointGpu: cws must hold (in_bits+1)*nkeys elements");
1312 if (seeds.size() < count || tr.size() < count || cs.size() < count || xs.size() < count ||
1313 ys == nullptr || pi.size() < count)
1314 throw std::invalid_argument("VdpfEvalPointGpu: seeds, tr, cs, xs, ys, and pi must cover nkeys entries");
1315 constexpr int kBs = 256;
1316 int blocks = (nkeys + kBs - 1) / kBs;
1317 if (b) {
1318 VdpfEvalPointKernel<in_bits, unroll, true, Group, Prg, XorHash, In, Query><<<blocks, kBs, 0, stream>>>(
1319 ys, pi.data(), seeds.data(), cws.data(), tr.data(), cs.data(), xs.data(), nkeys,
1320 fss::detail::SchemeAccess::GetPrg(vdpf), fss::detail::SchemeAccess::GetXorHash(vdpf));
1321 } else {
1322 VdpfEvalPointKernel<in_bits, unroll, false, Group, Prg, XorHash, In, Query><<<blocks, kBs, 0, stream>>>(
1323 ys, pi.data(), seeds.data(), cws.data(), tr.data(), cs.data(), xs.data(), nkeys,
1324 fss::detail::SchemeAccess::GetPrg(vdpf), fss::detail::SchemeAccess::GetXorHash(vdpf));
1325 }
1326}
1327
1328// Shared Merkle fold: reduce the paired node buffer to one root and write the
1329// four-block proof from the correction seed.
1330template <int threads_per_block, typename Hash>
1331__host__ inline cudaError_t VdpfFoldNodesToProof(cuda::std::array<int4, 2> *nodes,
1332 cuda::std::array<int4, 2> *next, size_t count, const int4 *cs, int4 *pi, const Hash &hash,
1333 cudaStream_t stream, DeviceScratch *first, DeviceScratch *second) {
1334 cudaError_t status = cudaSuccess;
1335 while (count > 1) {
1336 const size_t next_count = count / 2 + count % 2;
1337 const size_t blocks = next_count / threads_per_block + (next_count % threads_per_block != 0);
1338 if (blocks > static_cast<size_t>(INT_MAX)) return cudaErrorInvalidConfiguration;
1339 VdpfRangeMerkleFoldKernel<<<static_cast<int>(blocks), threads_per_block, 0, stream>>>(
1340 nodes, count, next, hash);
1341 status = cudaGetLastError();
1342 if (status != cudaSuccess) return status;
1343 auto *previous = nodes;
1344 nodes = next;
1345 next = previous;
1346 count = next_count;
1347 }
1348 VdpfRangeMerkleProofKernel<<<1, 1, 0, stream>>>(nodes, cs, pi);
1349 return ReleaseScratch(cudaGetLastError(), {first, second});
1350}
1351
1352// Host launchers for the device-key kernels. The party seed and the packed
1353// control are device addresses loaded by the kernels; the backends arrive by
1354// value from a host mirror of the device scheme, matching the public methods.
1355template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash,
1356 typename In, int frontier_depth = 16, int threads_per_block = 256>
1357__host__ inline cudaError_t VdpfEvalAllMerkleGpuDeviceKey(bool b, const int4 *s0,
1358 const int4 *cws, size_t cws_count, const In *tr, size_t stride, const int4 *cs,
1359 int4 *ys, size_t ys_count, int4 *pi, const Prg &prg, const XorHash &xor_hash,
1360 const Hash &hash, cudaStream_t stream) {
1361 constexpr size_t kOutputCount = size_t{1} << in_bits;
1362 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1363 constexpr int kFrontierDepth = frontier_depth < in_bits ? frontier_depth : in_bits;
1364 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
1365 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
1366 (threads_per_block & (threads_per_block - 1)) == 0,
1367 "gpu block size must be a power of two in [1,1024]");
1368 static_assert(in_bits - kFrontierDepth <= 8,
1369 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
1370 constexpr int kThreadsPerBlockLog2 = [] {
1371 int requested_log2 = 0;
1372 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
1373 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
1374 }();
1375 constexpr int kThreadsPerBlock = 1 << kThreadsPerBlockLog2;
1376 constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
1377 static_assert(kBlockRootDepth <= 30, "gpu block index exceeds the signed-int range");
1378 constexpr size_t kBlockCount = size_t{1} << kBlockRootDepth;
1379 static_assert(kBlockCount <= kThreadsPerBlock,
1380 "merkle block roots must fit within one final block");
1381 if (stride == 0 || stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
1382 cws_count < static_cast<size_t>(in_bits) * stride + 1 || ys_count < kOutputCount ||
1383 !fss::detail::FitsElementCount<int4>(kOutputCount) ||
1384 !fss::detail::FitsElementCount<cuda::std::array<int4, 2>>(kBlockCount) ||
1385 s0 == nullptr || cws == nullptr || tr == nullptr || cs == nullptr || ys == nullptr ||
1386 pi == nullptr) return cudaErrorInvalidValue;
1387 DeviceScratch roots_storage(stream);
1388 cudaError_t status = roots_storage.Allocate<cuda::std::array<int4, 2>>(kBlockCount);
1389 if (status != cudaSuccess) return status;
1390 auto *block_roots = roots_storage.Get<cuda::std::array<int4, 2>>();
1391 VdpfMerkleFrontierDeviceKeyKernel<in_bits, kFrontierDepth, kBlockRootDepth, Group, Prg, XorHash, Hash, In>
1392 <<<kBlockCount, kThreadsPerBlock, 0, stream>>>(
1393 b, s0, cws, tr, stride, cs, ys, block_roots, prg, xor_hash, hash);
1394 status = cudaGetLastError();
1395 if (status != cudaSuccess) return status;
1396 VdpfMerkleFinalizeKernel<kThreadsPerBlock, Hash>
1397 <<<1, kThreadsPerBlock, 0, stream>>>(
1398 block_roots, static_cast<int>(kBlockCount), cs, pi, hash);
1399 return ReleaseScratch(cudaGetLastError(), {&roots_storage});
1400}
1401
1402template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash,
1403 typename In, int frontier_depth = 16, int threads_per_block = 256>
1404__host__ inline cudaError_t VdpfEvalRangeMerkleGpuDeviceKey(bool b, In first, In last,
1405 const int4 *s0, const int4 *cws, size_t cws_count, const In *tr, size_t stride,
1406 const int4 *cs, int4 *ys, size_t ys_count, int4 *pi, const Prg &prg,
1407 const XorHash &xor_hash, const Hash &hash, cudaStream_t stream) {
1408 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
1409 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
1410 (threads_per_block & (threads_per_block - 1)) == 0,
1411 "gpu block size must be a power of two in [1,1024]");
1412 using Node = cuda::std::array<int4, 2>;
1413 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1414 size_t count = 0;
1415 if (!fss::detail::RangeOutputCount<in_bits>(first, last, ys_count, count) || stride == 0 ||
1416 stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
1417 cws_count < static_cast<size_t>(in_bits) * stride + 1 ||
1418 s0 == nullptr || cws == nullptr || tr == nullptr || cs == nullptr ||
1419 ys == nullptr || pi == nullptr) return cudaErrorInvalidValue;
1420 const size_t second_count = count / 2 + count % 2;
1421 constexpr size_t kMaxNodes = size_t(-1) / sizeof(Node);
1422 if (count > kMaxNodes || second_count > kMaxNodes - count) return cudaErrorInvalidValue;
1423 constexpr int kThreadBits = [] {
1424 int bits = 0;
1425 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++bits;
1426 return bits;
1427 }();
1428 constexpr int kTailCap = frontier_depth < 30 + kThreadBits ? frontier_depth : 30 + kThreadBits;
1429 constexpr int kChunkDepth = kTailCap + 8 < 30 + kThreadBits ? kTailCap + 8 : 30 + kThreadBits;
1430 const size_t fold_count = count / 2 + count % 2;
1431 const size_t fold_blocks = fold_count / threads_per_block + (fold_count % threads_per_block != 0);
1432 if (fold_blocks > static_cast<size_t>(INT_MAX)) return cudaErrorInvalidConfiguration;
1433 DeviceScratch first_storage(stream), second_storage(stream);
1434 cudaError_t status = first_storage.Allocate<Node>(count);
1435 if (status != cudaSuccess) return status;
1436 status = second_storage.Allocate<Node>(second_count);
1437 if (status != cudaSuccess) return status;
1438 auto *nodes = first_storage.Get<Node>();
1439 auto *next = second_storage.Get<Node>();
1440 status = fss::detail::ForEachAlignedRange<in_bits>(first, last, kChunkDepth,
1441 [&](In chunk_first, int depth, size_t offset, size_t) {
1442 const int frontier = depth < frontier_depth ? depth : frontier_depth;
1443 const int thread_bits = frontier < kThreadBits ? frontier : kThreadBits;
1444 const int block_root_depth = frontier - thread_bits;
1445 const size_t blocks = size_t{1} << block_root_depth;
1446 if (blocks > static_cast<size_t>(INT_MAX)) return cudaErrorInvalidConfiguration;
1447 VdpfRangeMerkleLeavesDeviceKeyKernel<in_bits, threads_per_block, Group, Prg, XorHash, Hash, In>
1448 <<<blocks, 1 << thread_bits, 0, stream>>>(b, chunk_first, depth,
1449 frontier, block_root_depth, s0, cws, tr, stride, cs,
1450 ys + offset, nodes + offset, prg, xor_hash, hash);
1451 return cudaGetLastError();
1452 });
1453 if (status != cudaSuccess) return status;
1454 return VdpfFoldNodesToProof<threads_per_block>(nodes, next, count, cs, pi, hash,
1455 stream, &first_storage, &second_storage);
1456}
1457
1458template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash,
1459 typename In, int threads_per_block = 256>
1460__host__ inline cudaError_t VdpfBatchEvalMerkleGpuDeviceKey(bool b, const int4 *s0,
1461 const int4 *cws, size_t cws_count, const In *tr, size_t stride, const int4 *cs,
1462 const In *xs, size_t xs_count, int4 *ys, size_t ys_count, int4 *pi, const Prg &prg,
1463 const XorHash &xor_hash, const Hash &hash, cudaStream_t stream) {
1464 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
1465 (threads_per_block & (threads_per_block - 1)) == 0,
1466 "gpu block size must be a power of two in [1,1024]");
1467 using Node = cuda::std::array<int4, 2>;
1468 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1469 const size_t count = xs_count;
1470 if (count == 0 || ys_count < count || stride == 0 ||
1471 stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
1472 cws_count < static_cast<size_t>(in_bits) * stride + 1 ||
1473 s0 == nullptr || cws == nullptr || tr == nullptr || cs == nullptr ||
1474 xs == nullptr || ys == nullptr || pi == nullptr) return cudaErrorInvalidValue;
1475 const size_t second_count = count / 2 + count % 2;
1476 constexpr size_t kMaxNodes = size_t(-1) / sizeof(Node);
1477 if (count > kMaxNodes || second_count > kMaxNodes - count) return cudaErrorInvalidValue;
1478 const size_t leaf_blocks = count / threads_per_block + (count % threads_per_block != 0);
1479 if (leaf_blocks > static_cast<size_t>(INT_MAX)) return cudaErrorInvalidConfiguration;
1480 DeviceScratch first_storage(stream), second_storage(stream);
1481 cudaError_t status = first_storage.Allocate<Node>(count);
1482 if (status != cudaSuccess) return status;
1483 status = second_storage.Allocate<Node>(second_count);
1484 if (status != cudaSuccess) return status;
1485 auto *nodes = first_storage.Get<Node>();
1486 auto *next = second_storage.Get<Node>();
1487 VdpfBatchMerkleLeavesDeviceKeyKernel<in_bits, Group, Prg, XorHash, Hash, In>
1488 <<<static_cast<int>(leaf_blocks), threads_per_block, 0, stream>>>(
1489 b, s0, cws, tr, stride, cs, xs, ys, nodes, count, prg, xor_hash, hash);
1490 status = cudaGetLastError();
1491 if (status != cudaSuccess) return status;
1492 return VdpfFoldNodesToProof<threads_per_block>(nodes, next, count, cs, pi, hash,
1493 stream, &first_storage, &second_storage);
1494}
1495
1496#endif
1497
1498} // namespace gpu::detail
1499
1500template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1502 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
1503inline __host__ __device__ Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::Vdpf(Prg prg, XorHash xor_hash, Hash hash)
1504 : prg_(prg), xor_hash_(xor_hash), hash_(hash) {}
1505
1506template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1509inline __host__ __device__ cuda::std::array<int4, 4> Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::Eval(
1510 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
1511 cuda::std::span<const int4, 4> cs, In x, int4 &y, size_t stride) {
1512 util::RequireArg(stride > 0, "Vdpf::Eval: stride must be positive");
1513 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1514 util::RequireArg(stride <= (kMaxElements - 1) / static_cast<size_t>(in_bits),
1515 "Vdpf::Eval: stride exceeds the correction array limit");
1516 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1517 util::RequireArg(cws.size() >= required,
1518 "Vdpf::Eval: cws must hold in_bits*stride+1 elements");
1519 return EvalCws(b, s0, CwsView{cws.data(), tr, stride}, cs, x, y);
1520}
1521
1522template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1525template <typename Cws>
1526inline __host__ __device__ cuda::std::array<int4, 4> Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::EvalCws(
1527 bool b, int4 s0, Cws cws, cuda::std::span<const int4, 4> cs, In x, int4 &y) {
1528 int4 s = s0;
1529 s = util::SetLsb(s, false);
1530 bool t = b;
1531
1532 for (int i = 0; i < in_bits; ++i) {
1533 Cw cw = cws[i];
1534 int4 s_cw = cw.s;
1535 bool tl_cw = util::GetLsb(s_cw);
1536 s_cw = util::SetLsb(s_cw, false);
1537 bool tr_cw = cw.tr;
1538
1539 bool x_bit = (x >> (in_bits - 1 - i)) & 1;
1540 auto [child] = detail::GenHalf<2>(prg_, s, x_bit);
1541 bool child_t = util::GetLsb(child);
1542 child = util::SetLsb(child, false);
1543 if (t) {
1544 child = util::Xor(child, s_cw);
1545 child_t = child_t ^ (x_bit ? tr_cw : tl_cw);
1546 }
1547 s = child;
1548 t = child_t;
1549 }
1550
1551 // Output share
1552 auto g = Group::From(s);
1553 int4 ocw = cws[in_bits].s;
1554 util::RequireArg((ocw.w & 1) == 0,
1555 "Vdpf::Eval: the terminal group value must have a zero LSB");
1556 if (t) g = g + Group::From(ocw);
1557 if (b) g = -g;
1558
1559 // Corrected verification hash
1560 int4 x_buf = fss::detail::Pack(x);
1561
1562 auto pi_tilde = xor_hash_.Hash(cuda::std::tuple<int4, const int4>{x_buf, s});
1563 if (t) {
1564 pi_tilde = util::Xor(cuda::std::span<const int4, 4>(pi_tilde), cuda::std::span<const int4, 4>(cs));
1565 }
1566 y = g.Into();
1567 return pi_tilde;
1568}
1569
1570template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1572 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
1574 cuda::std::span<const cuda::std::array<int4, 4>> pi_tildes, cuda::std::span<const int4, 4> cs,
1575 cuda::std::span<int4, 4> pi) {
1576 pi[0] = cs[0];
1577 pi[1] = cs[1];
1578 pi[2] = cs[2];
1579 pi[3] = cs[3];
1580 for (size_t i = 0; i < pi_tildes.size(); ++i) {
1581 cuda::std::array<int4, 4> h_input =
1582 util::Xor(cuda::std::span<const int4, 4>(pi), cuda::std::span<const int4, 4>(pi_tildes[i]));
1583 auto h_out = hash_.Hash(cuda::std::span<const int4, 4>(h_input));
1584 pi[0] = util::Xor(pi[0], h_out[0]);
1585 pi[1] = util::Xor(pi[1], h_out[1]);
1586 }
1587}
1588
1589template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1593 cuda::std::span<const int4, 4> pi0, cuda::std::span<const int4, 4> pi1) {
1594 for (int i = 0; i < 4; ++i) {
1595 if (pi0[i].x != pi1[i].x || pi0[i].y != pi1[i].y || pi0[i].z != pi1[i].z || pi0[i].w != pi1[i].w) return false;
1596 }
1597 return true;
1598}
1599
1600template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1604 bool b, int4 s0, cuda::std::span<const int4> cws, In tr, cuda::std::span<const int4, 4> cs,
1605 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, int par_depth, size_t stride) {
1606 assert(par_depth >= 0);
1607 constexpr size_t kOutputCount = detail::FullDomainSize<int4, in_bits>();
1608 EvalRange(b, In{0}, static_cast<In>(kOutputCount - 1), s0, cws, tr, cs, ys, pi, par_depth, stride);
1609}
1610
1611template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1615 bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws, In tr,
1616 cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys,
1617 cuda::std::span<int4, 4> pi, int par_depth, size_t stride) {
1618 if (stride == 0) throw std::invalid_argument("Vdpf::EvalRange: stride must be positive");
1619 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1620 if (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits))
1621 throw std::invalid_argument("Vdpf::EvalRange: stride exceeds the correction array limit");
1622 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1623 if (cws.size() < required)
1624 throw std::invalid_argument("Vdpf::EvalRange: cws must hold in_bits*stride+1 elements");
1625 EvalRangeCws(b, first, last, s0, CwsView{cws.data(), tr, stride}, cs, ys, pi, par_depth);
1626}
1627
1628template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1631template <typename Cws>
1632inline void Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::EvalRangeCws(
1633 bool b, In first, In last, int4 s0, Cws cws, cuda::std::span<const int4, 4> cs,
1634 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, int par_depth) {
1635 assert(par_depth >= 0);
1636 In max_input = static_cast<In>(~In{0});
1637 if constexpr (in_bits < sizeof(In) * 8) {
1638 max_input = static_cast<In>((In{1} << in_bits) - 1);
1639 }
1640 if (first > last)
1641 throw std::invalid_argument("Vdpf::EvalRange: first must not exceed last");
1642 if (last > max_input)
1643 throw std::invalid_argument("Vdpf::EvalRange: last must stay inside the input domain");
1644 const __uint128_t distance = static_cast<__uint128_t>(last) - first;
1645 if (distance >= static_cast<size_t>(-1) / sizeof(int4))
1646 throw std::invalid_argument("Vdpf::EvalRange: the interval is too large for size_t");
1647 const size_t num_inputs = static_cast<size_t>(distance) + 1;
1648 if (ys.size() < num_inputs)
1649 throw std::invalid_argument("Vdpf::EvalRange: ys must hold last-first+1 entries");
1650
1651 // The terminal LSB is a caller precondition. Read it and check it before any
1652 // traversal, so a Debug violation aborts on the calling thread; Release
1653 // builds do not check it.
1654 int4 ocw = cws[in_bits].s;
1655 util::RequireArg((ocw.w & 1) == 0,
1656 "Vdpf::EvalRange: the terminal group value must have a zero LSB");
1657 auto ocw_group = Group::From(ocw);
1658
1659 const int4 st = util::SetLsb(s0, b);
1660 const int par_depth_ = fss::detail::ResolveParDepth(par_depth, in_bits);
1661
1662 // Traversal stores the packed seed and control bit in output storage temporarily.
1663 if (par_depth_ == 0) {
1664 EvalPrunedTree(st, cws, ys, In{0}, max_input, 0, 0, first, last);
1665 } else {
1666 const int threads = fss::detail::ResolveParThreads(par_depth_, num_inputs);
1667
1668#pragma omp parallel num_threads(threads)
1669#pragma omp single
1670 EvalPrunedTree(st, cws, ys, In{0}, max_input, 0, par_depth_, first, last);
1671 }
1672 // Phase 2: sequential output computation and proof accumulation
1673 pi[0] = cs[0];
1674 pi[1] = cs[1];
1675 pi[2] = cs[2];
1676 pi[3] = cs[3];
1677
1678 for (size_t offset = 0; offset < num_inputs; ++offset) {
1679 const In x = static_cast<In>(first + offset);
1680 int4 sj = ys[offset];
1681 bool tj = util::GetLsb(sj);
1682 sj = util::SetLsb(sj, false);
1683
1684 // Output share
1685 auto g = Group::From(sj);
1686 if (tj) g = g + ocw_group;
1687 if (b) g = -g;
1688 ys[offset] = g.Into();
1689
1690 // Proof accumulation
1691 int4 x_buf = fss::detail::Pack(x);
1692
1693 auto pi_tilde = xor_hash_.Hash(cuda::std::tuple<int4, const int4>{x_buf, sj});
1694 if (tj) {
1695 pi_tilde = util::Xor(cuda::std::span<const int4, 4>(pi_tilde), cuda::std::span<const int4, 4>(cs));
1696 }
1697
1698 cuda::std::array<int4, 4> h_input =
1699 util::Xor(cuda::std::span<const int4, 4>(pi), cuda::std::span<const int4, 4>(pi_tilde));
1700 auto h_out = hash_.Hash(cuda::std::span<const int4, 4>(h_input));
1701 pi[0] = util::Xor(pi[0], h_out[0]);
1702 pi[1] = util::Xor(pi[1], h_out[1]);
1703 }
1704}
1705
1706template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1708 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
1710 cuda::std::span<const cuda::std::array<int4, 4>> pi_tildes, cuda::std::span<const int4, 4> cs,
1711 cuda::std::span<int4, 4> pi, int par_depth) {
1712 assert(par_depth >= 0);
1713 size_t count = pi_tildes.size();
1714 if (count < 1)
1715 throw std::invalid_argument("Vdpf::ProveMerkle: pi_tildes must not be empty");
1716
1717 std::vector<cuda::std::array<int4, 2>> nodes(count);
1718 if constexpr (HasHash4<Hash>) {
1719 const size_t groups = count / 4;
1720 fss::detail::ParallelFor(par_depth, groups, [&](size_t g) {
1721 auto out =
1722 hash_.Hash4(cuda::std::span<const cuda::std::array<int4, 4>, 4>(pi_tildes.data() + 4 * g, 4));
1723 for (size_t j = 0; j < 4; ++j) nodes[4 * g + j] = out[j];
1724 });
1725 for (size_t i = 4 * groups; i < count; ++i) {
1726 nodes[i] = hash_.Hash(cuda::std::span<const int4, 4>(pi_tildes[i]));
1727 }
1728 } else {
1729 fss::detail::ParallelFor(par_depth, count, [&](size_t i) {
1730 nodes[i] = hash_.Hash(cuda::std::span<const int4, 4>(pi_tildes[i]));
1731 });
1732 }
1733
1734 while (count > 1) {
1735 const size_t next_count = count / 2 + count % 2;
1736 std::vector<cuda::std::array<int4, 2>> next(next_count);
1737 if constexpr (HasHash4<Hash>) {
1738 const size_t pairs = count / 2;
1739 const size_t groups = pairs / 4;
1740 fss::detail::ParallelFor(par_depth, groups, [&](size_t g) {
1741 cuda::std::array<cuda::std::array<int4, 4>, 4> msgs;
1742 for (size_t j = 0; j < 4; ++j) {
1743 const auto *p = nodes.data() + 8 * g + 2 * j;
1744 msgs[j] = {p[0][0], p[0][1], p[1][0], p[1][1]};
1745 }
1746 auto out = hash_.Hash4(cuda::std::span<const cuda::std::array<int4, 4>, 4>(msgs));
1747 for (size_t j = 0; j < 4; ++j) next[4 * g + j] = out[j];
1748 });
1749 // Remaining complete pairs; an odd final node is carried by the helper.
1750 for (size_t i = 4 * groups; i < next_count; ++i) {
1751 next[i] = detail::VdpfMerkleFoldNode(hash_, nodes.data(), count, i);
1752 }
1753 } else {
1754 fss::detail::ParallelFor(par_depth, next_count, [&](size_t i) {
1755 next[i] = detail::VdpfMerkleFoldNode(hash_, nodes.data(), count, i);
1756 });
1757 }
1758 nodes = std::move(next);
1759 count = nodes.size();
1760 }
1761
1762 pi[0] = nodes[0][0];
1763 pi[1] = nodes[0][1];
1764 pi[2] = cs[2];
1765 pi[3] = cs[3];
1766}
1767
1768template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1772 bool b, int4 s0, cuda::std::span<const int4> cws, In tr, cuda::std::span<const int4, 4> cs,
1773 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, int par_depth, size_t stride) {
1774 constexpr size_t kOutputCount = detail::FullDomainSize<cuda::std::array<int4, 4>, in_bits>();
1775 EvalRangeMerkle(b, In{0}, static_cast<In>(kOutputCount - 1), s0, cws, tr, cs, ys, pi, par_depth, stride);
1776}
1777
1778template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1782 bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws, In tr,
1783 cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys,
1784 cuda::std::span<int4, 4> pi, int par_depth, size_t stride) {
1785 if (stride == 0) throw std::invalid_argument("Vdpf::EvalRangeMerkle: stride must be positive");
1786 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1787 if (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits))
1788 throw std::invalid_argument("Vdpf::EvalRangeMerkle: stride exceeds the correction array limit");
1789 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1790 if (cws.size() < required)
1791 throw std::invalid_argument("Vdpf::EvalRangeMerkle: cws must hold in_bits*stride+1 elements");
1792 EvalRangeMerkleCws(b, first, last, s0, CwsView{cws.data(), tr, stride}, cs, ys, pi, par_depth);
1793}
1794
1795template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1798template <typename Cws>
1799inline void Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::EvalRangeMerkleCws(
1800 bool b, In first, In last, int4 s0, Cws cws,
1801 cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys,
1802 cuda::std::span<int4, 4> pi, int par_depth) {
1803 assert(par_depth >= 0);
1804 In max_input = static_cast<In>(~In{0});
1805 if constexpr (in_bits < sizeof(In) * 8) {
1806 max_input = static_cast<In>((In{1} << in_bits) - 1);
1807 }
1808 if (first > last)
1809 throw std::invalid_argument("Vdpf::EvalRangeMerkle: first must not exceed last");
1810 if (last > max_input)
1811 throw std::invalid_argument("Vdpf::EvalRangeMerkle: last must stay inside the input domain");
1812 const __uint128_t distance = static_cast<__uint128_t>(last) - first;
1813 // The bounds below share one named size_t limit across three checks.
1814 constexpr size_t kMaxBytes = size_t(-1);
1815 if (distance >= kMaxBytes / sizeof(cuda::std::array<int4, 4>))
1816 throw std::invalid_argument("Vdpf::EvalRangeMerkle: the interval is too large for size_t");
1817 const size_t kOutputCount = static_cast<size_t>(distance) + 1;
1818 const size_t next_count = kOutputCount / 2 + kOutputCount % 2;
1819 if (kOutputCount > kMaxBytes / (sizeof(cuda::std::array<int4, 4>) + sizeof(cuda::std::array<int4, 2>)))
1820 throw std::invalid_argument("Vdpf::EvalRangeMerkle: the proof scratch is too large for size_t");
1821 const size_t initial_bytes = kOutputCount *
1822 (sizeof(cuda::std::array<int4, 4>) + sizeof(cuda::std::array<int4, 2>));
1823 if (next_count > (kMaxBytes - initial_bytes) / sizeof(cuda::std::array<int4, 2>))
1824 throw std::invalid_argument("Vdpf::EvalRangeMerkle: the proof scratch is too large for size_t");
1825 if (ys.size() < kOutputCount)
1826 throw std::invalid_argument("Vdpf::EvalRangeMerkle: ys must hold last-first+1 entries");
1827
1828 // The terminal LSB is a caller precondition. Read it and check it before any
1829 // traversal, so a Debug violation aborts on the calling thread; Release
1830 // builds do not check it.
1831 int4 ocw = cws[in_bits].s;
1832 util::RequireArg((ocw.w & 1) == 0,
1833 "Vdpf::EvalRangeMerkle: the terminal group value must have a zero LSB");
1834 auto ocw_group = Group::From(ocw);
1835
1836 const int4 st = util::SetLsb(s0, b);
1837 int par_depth_ = fss::detail::ResolveParDepth(par_depth, in_bits);
1838
1839 // Traversal stores the packed seed and control bit in output storage temporarily.
1840 if (par_depth_ == 0) {
1841 EvalPrunedTree(st, cws, ys, In{0}, max_input, 0, 0, first, last);
1842 } else {
1843 int threads = fss::detail::ResolveParThreads(par_depth_, kOutputCount);
1844#pragma omp parallel num_threads(threads)
1845#pragma omp single
1846 EvalPrunedTree(st, cws, ys, In{0}, max_input, 0, par_depth_, first, last);
1847 }
1848
1849 const size_t n = kOutputCount;
1850 std::vector<cuda::std::array<int4, 4>> leaves(n);
1851 fss::detail::ParallelFor(par_depth, n, [&](size_t j) {
1852 int4 sj = ys[j];
1853 bool tj = util::GetLsb(sj);
1854 sj = util::SetLsb(sj, false);
1855
1856 auto g = Group::From(sj);
1857 if (tj) g = g + ocw_group;
1858 if (b) g = -g;
1859 ys[j] = g.Into();
1860
1861 int4 x_buf = fss::detail::Pack(static_cast<In>(first + j));
1862
1863 auto pi_tilde = xor_hash_.Hash(cuda::std::tuple<int4, const int4>{x_buf, sj});
1864 if (tj) {
1865 leaves[j] = util::Xor(cuda::std::span<const int4, 4>(pi_tilde), cuda::std::span<const int4, 4>(cs));
1866 } else {
1867 leaves[j] = pi_tilde;
1868 }
1869 });
1870 ProveMerkle(cuda::std::span<const cuda::std::array<int4, 4>>(leaves), cuda::std::span<const int4, 4>(cs), pi, par_depth);
1871}
1872
1873template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1875 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
1877 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
1878 cuda::std::span<const int4, 4> cs, cuda::std::span<const In> xs,
1879 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, size_t stride) {
1880 if (stride == 0) throw std::invalid_argument("Vdpf::BatchEval: stride must be positive");
1881 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1882 if (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits))
1883 throw std::invalid_argument("Vdpf::BatchEval: stride exceeds the correction array limit");
1884 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1885 if (cws.size() < required)
1886 throw std::invalid_argument("Vdpf::BatchEval: cws must hold in_bits*stride+1 elements");
1887 EvalBatchCws<false>(b, s0, CwsView{cws.data(), tr, stride}, cs, xs, ys, pi);
1888}
1889
1890template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1893template <bool merkle, typename Cws>
1894inline void Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::EvalBatchCws(
1895 bool b, int4 s0, Cws cws, cuda::std::span<const int4, 4> cs, cuda::std::span<const In> xs,
1896 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi) {
1897 if (xs.empty())
1898 throw std::invalid_argument(merkle ? "Vdpf::BatchEvalMerkle: xs must not be empty"
1899 : "Vdpf::BatchEval: xs must not be empty");
1900 if (ys.size() < xs.size())
1901 throw std::invalid_argument(merkle ? "Vdpf::BatchEvalMerkle: ys must hold xs.size() entries"
1902 : "Vdpf::BatchEval: ys must hold xs.size() entries");
1903 std::vector<cuda::std::array<int4, 4>> pi_tildes(xs.size());
1904 for (size_t i = 0; i < xs.size(); ++i) {
1905 pi_tildes[i] = EvalCws(b, s0, cws, cs, xs[i], ys[i]);
1906 }
1907 if constexpr (merkle) {
1908 ProveMerkle(cuda::std::span<const cuda::std::array<int4, 4>>(pi_tildes), cs, pi);
1909 } else {
1910 Prove(cuda::std::span<const cuda::std::array<int4, 4>>(pi_tildes), cs, pi);
1911 }
1912}
1913
1914template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1916 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
1918 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
1919 cuda::std::span<const int4, 4> cs, cuda::std::span<const In> xs,
1920 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, size_t stride) {
1921 if (stride == 0) throw std::invalid_argument("Vdpf::BatchEvalMerkle: stride must be positive");
1922 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1923 if (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits))
1924 throw std::invalid_argument("Vdpf::BatchEvalMerkle: stride exceeds the correction array limit");
1925 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1926 if (cws.size() < required)
1927 throw std::invalid_argument("Vdpf::BatchEvalMerkle: cws must hold in_bits*stride+1 elements");
1928 EvalBatchCws<true>(b, s0, CwsView{cws.data(), tr, stride}, cs, xs, ys, pi);
1929}
1930
1931template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1935 cuda::std::span<int4> cws, In &tr, cuda::std::span<int4, 4> cs,
1936 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf, size_t stride) {
1937 util::RequireArg(stride > 0, "Vdpf::Gen: stride must be positive");
1938 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1939 util::RequireArg(stride <= (kMaxElements - 1) / static_cast<size_t>(in_bits),
1940 "Vdpf::Gen: stride exceeds the correction array limit");
1941 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1942 util::RequireArg(cws.size() >= required,
1943 "Vdpf::Gen: cws must hold in_bits*stride+1 elements");
1944 return Generate(CwsWriter{cws.data(), stride, tr}, cs, s0s.data(), a, b_buf);
1945}
1946
1947template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
1950template <typename Writer>
1951inline __host__ __device__ int Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::Generate(
1952 Writer writer, cuda::std::span<int4, 4> cs, const int4 *s0s, In a, int4 b_buf) {
1953 int4 s0 = s0s[0];
1954 s0 = util::SetLsb(s0, false);
1955 int4 s1 = s0s[1];
1956 s1 = util::SetLsb(s1, false);
1957 bool t0 = false;
1958 bool t1 = true;
1959 b_buf = util::SetLsb(b_buf, false);
1960
1961 for (int i = 0; i < in_bits; ++i) {
1962 auto [s0l, s0r] = prg_.Gen(s0);
1963 auto [s1l, s1r] = prg_.Gen(s1);
1964
1965 bool t0l = util::GetLsb(s0l);
1966 s0l = util::SetLsb(s0l, false);
1967 bool t0r = util::GetLsb(s0r);
1968 s0r = util::SetLsb(s0r, false);
1969 bool t1l = util::GetLsb(s1l);
1970 s1l = util::SetLsb(s1l, false);
1971 bool t1r = util::GetLsb(s1r);
1972 s1r = util::SetLsb(s1r, false);
1973
1974 bool a_bit = (a >> (in_bits - 1 - i)) & 1;
1975
1976 int4 s_cw;
1977 if (!a_bit) s_cw = util::Xor(s0r, s1r);
1978 else s_cw = util::Xor(s0l, s1l);
1979
1980 bool tl_cw = t0l ^ t1l ^ a_bit ^ 1;
1981 bool tr_cw = t0r ^ t1r ^ a_bit;
1982
1983 if (!a_bit) {
1984 s0 = s0l;
1985 if (t0) s0 = util::Xor(s0, s_cw);
1986 s1 = s1l;
1987 if (t1) s1 = util::Xor(s1, s_cw);
1988
1989 if (t0) t0 = t0l ^ tl_cw;
1990 else t0 = t0l;
1991 if (t1) t1 = t1l ^ tl_cw;
1992 else t1 = t1l;
1993 } else {
1994 s0 = s0r;
1995 if (t0) s0 = util::Xor(s0, s_cw);
1996 s1 = s1r;
1997 if (t1) s1 = util::Xor(s1, s_cw);
1998
1999 if (t0) t0 = t0r ^ tr_cw;
2000 else t0 = t0r;
2001 if (t1) t1 = t1r ^ tr_cw;
2002 else t1 = t1r;
2003 }
2004
2005 s_cw = util::SetLsb(s_cw, tl_cw);
2006 writer.Write(i, s_cw, tr_cw);
2007 }
2008
2009 // Verification hash
2010 int4 a_buf = fss::detail::Pack(a);
2011
2012 auto pi_tilde_0 = xor_hash_.Hash(cuda::std::tuple<int4, const int4>{a_buf, s0});
2013 auto pi_tilde_1 = xor_hash_.Hash(cuda::std::tuple<int4, const int4>{a_buf, s1});
2014 for (int i = 0; i < 4; ++i) cs[i] = util::Xor(pi_tilde_0[i], pi_tilde_1[i]);
2015
2016 // A zero cs would let adversarial keys keep both parties' per-point hashes equal while
2017 // their control bits differ everywhere (see Assumption 2 of doc/vdpf_merkle_fold_security.md).
2018 if ((cs[0].x | cs[0].y | cs[0].z | cs[0].w | cs[1].x | cs[1].y | cs[1].z | cs[1].w | cs[2].x | cs[2].y |
2019 cs[2].z | cs[2].w | cs[3].x | cs[3].y | cs[3].z | cs[3].w) == 0) {
2020 return 1;
2021 }
2022
2023 // Reject a failed generation attempt
2024 if (t0 == t1) return 1;
2025
2026 // Output correction word
2027 auto v_cw = Group::From(b_buf) + (-Group::From(s0)) + Group::From(s1);
2028 if (t1) v_cw = -v_cw;
2029 writer.Finish(v_cw.Into());
2030
2031 return 0;
2032}
2033
2034template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2036 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2037template <typename Cws>
2038inline void Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::EvalTree(
2039 int4 st, Cws cws, cuda::std::span<int4> ys, size_t l, size_t r, int i, int par_depth_) {
2040 if (i == in_bits) {
2041 assert(l + 1 == r);
2042 ys[l] = st;
2043 return;
2044 }
2045
2046 bool t = util::GetLsb(st);
2047 int4 s = st;
2048 s = util::SetLsb(s, false);
2049
2050 Cw cw = cws[i];
2051 int4 s_cw = cw.s;
2052 bool tl_cw = util::GetLsb(s_cw);
2053 s_cw = util::SetLsb(s_cw, false);
2054 bool tr_cw = cw.tr;
2055
2056 auto [sl, sr] = prg_.Gen(s);
2057
2058 bool tl = util::GetLsb(sl);
2059 sl = util::SetLsb(sl, false);
2060 bool tr = util::GetLsb(sr);
2061 sr = util::SetLsb(sr, false);
2062
2063 if (t) {
2064 sl = util::Xor(sl, s_cw);
2065 sr = util::Xor(sr, s_cw);
2066 tl = tl ^ tl_cw;
2067 tr = tr ^ tr_cw;
2068 }
2069
2070 int4 stl = sl;
2071 stl = util::SetLsb(stl, tl);
2072 int4 str = sr;
2073 str = util::SetLsb(str, tr);
2074
2075 size_t mid = (l + r) / 2;
2076
2077 if (i < par_depth_) {
2078#pragma omp task
2079 EvalTree(stl, cws, ys, l, mid, i + 1, par_depth_);
2080#pragma omp task
2081 EvalTree(str, cws, ys, mid, r, i + 1, par_depth_);
2082#pragma omp taskwait
2083 } else {
2084 EvalTree(stl, cws, ys, l, mid, i + 1, par_depth_);
2085 EvalTree(str, cws, ys, mid, r, i + 1, par_depth_);
2086 }
2087}
2088
2089template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2091 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2092template <typename Cws>
2093inline void Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::EvalPrunedTree(
2094 int4 st, Cws cws, cuda::std::span<int4> ys, In l, In r, int i,
2095 int par_depth_, In first, In last) {
2096 // this subtree is outside the range [first, last]
2097 if (r < first || l > last) {
2098 return;
2099 }
2100 // this is a full subtree
2101 if (l >= first && r <= last) {
2102 const size_t offset = static_cast<size_t>(l - first);
2103 const size_t count = static_cast<size_t>(r - l) + 1;
2104 EvalTree(st, cws, ys.subspan(offset, count), 0, count, i, par_depth_);
2105 return;
2106 }
2107 if (i == in_bits) {
2108 assert(l == r);
2109 // we only write leaves from [first, last]
2110 ys[static_cast<size_t>(l - first)] = st;
2111 return;
2112 }
2113
2114 bool t = util::GetLsb(st);
2115 int4 s = st;
2116 s = util::SetLsb(s, false);
2117
2118 Cw cw = cws[i];
2119 int4 s_cw = cw.s;
2120 bool tl_cw = util::GetLsb(s_cw);
2121 s_cw = util::SetLsb(s_cw, false);
2122 bool tr_cw = cw.tr;
2123
2124 auto [sl, sr] = prg_.Gen(s);
2125
2126 bool tl = util::GetLsb(sl);
2127 sl = util::SetLsb(sl, false);
2128 bool tr = util::GetLsb(sr);
2129 sr = util::SetLsb(sr, false);
2130
2131 if (t) {
2132 sl = util::Xor(sl, s_cw);
2133 sr = util::Xor(sr, s_cw);
2134 tl = tl ^ tl_cw;
2135 tr = tr ^ tr_cw;
2136 }
2137
2138 int4 stl = sl;
2139 stl = util::SetLsb(stl, tl);
2140 int4 str = sr;
2141 str = util::SetLsb(str, tr);
2142
2143 const In mid = static_cast<In>(l + (r - l) / 2);
2144
2145 if (i < par_depth_) {
2146 if (mid >= first) {
2147#pragma omp task
2148 EvalPrunedTree(stl, cws, ys, l, mid, i + 1, par_depth_, first, last);
2149 }
2150 if (mid < last) {
2151#pragma omp task
2152 EvalPrunedTree(str, cws, ys, static_cast<In>(mid + 1), r, i + 1, par_depth_, first, last);
2153 }
2154#pragma omp taskwait
2155 } else {
2156 if (mid >= first) {
2157 EvalPrunedTree(stl, cws, ys, l, mid, i + 1, par_depth_, first, last);
2158 }
2159 if (mid < last) {
2160 EvalPrunedTree(str, cws, ys, static_cast<In>(mid + 1), r, i + 1, par_depth_, first, last);
2161 }
2162 }
2163}
2164
2165template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2167 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2168template <int frontier_depth, int threads_per_block>
2170struct Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::GpuLaunchShape {
2171 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
2172 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
2173 (threads_per_block & (threads_per_block - 1)) == 0,
2174 "gpu block size must be a power of two in [1,1024]");
2175 static constexpr int kFrontierDepth = frontier_depth < in_bits ? frontier_depth : in_bits;
2176 static_assert(in_bits - kFrontierDepth <= 8,
2177 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
2178 static constexpr int kThreadsPerBlockLog2 = [] {
2179 int requested_log2 = 0;
2180 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
2181 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
2182 }();
2183 static constexpr int kThreadsPerBlock = 1 << kThreadsPerBlockLog2;
2184 static constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
2185 static_assert(kBlockRootDepth <= 30, "gpu block index exceeds the signed-int range");
2186 static constexpr size_t kBlockCount = size_t{1} << (kBlockRootDepth <= 30 ? kBlockRootDepth : 0);
2187 static_assert(kBlockCount <= kThreadsPerBlock,
2188 "merkle block roots must fit within one final block");
2189};
2190
2191template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2193 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2194template <int frontier_depth, int threads_per_block>
2196 bool b, int4 s0, cuda::std::span<const int4> cws, In tr, cuda::std::span<const int4, 4> cs,
2197 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, cudaStream_t stream, size_t stride) const
2198 requires(in_bits < sizeof(size_t) * 8) {
2199#if defined(__CUDACC__)
2200 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
2201 constexpr size_t kOutputCount = size_t{1} << in_bits;
2202 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
2203 if (stride == 0 || stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
2204 cws.size() < static_cast<size_t>(in_bits) * stride + 1 || ys.size() < kOutputCount ||
2205 !detail::FitsElementCount<int4>(kOutputCount) ||
2206 !detail::FitsElementCount<cuda::std::array<int4, 2>>(LaunchShape::kBlockCount) ||
2207 cws.data() == nullptr || cs.data() == nullptr || ys.data() == nullptr ||
2208 pi.data() == nullptr) return cudaErrorInvalidValue;
2209 gpu::detail::DeviceScratch roots_storage(stream);
2210 cudaError_t status = roots_storage.Allocate<cuda::std::array<int4, 2>>(LaunchShape::kBlockCount);
2211 if (status != cudaSuccess) return status;
2212 auto *block_roots = roots_storage.Get<cuda::std::array<int4, 2>>();
2213 gpu::detail::VdpfMerkleFrontierKernel<in_bits, LaunchShape::kFrontierDepth,
2214 LaunchShape::kBlockRootDepth, Group, Prg, XorHash, Hash, In>
2215 <<<LaunchShape::kBlockCount, LaunchShape::kThreadsPerBlock, 0, stream>>>(
2216 b, s0, cws.data(), tr, stride, cs.data(), ys.data(), block_roots, prg_, xor_hash_, hash_);
2217 status = cudaGetLastError();
2218 if (status != cudaSuccess) return status;
2219 gpu::detail::VdpfMerkleFinalizeKernel<LaunchShape::kThreadsPerBlock, Hash>
2220 <<<1, LaunchShape::kThreadsPerBlock, 0, stream>>>(
2221 block_roots, static_cast<int>(LaunchShape::kBlockCount), cs.data(), pi.data(), hash_);
2222 return gpu::detail::ReleaseScratch(cudaGetLastError(), {&roots_storage});
2223#else
2224 static_assert(sizeof(Prg) == 0, "gpu launch requires a CUDA compiler");
2225 return cudaErrorNotSupported;
2226#endif
2227}
2228
2229template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2232template <int frontier_depth, int threads_per_block>
2234 bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws, In tr,
2235 cuda::std::span<const int4, 4> cs, cuda::std::span<int4> ys,
2236 cuda::std::span<int4, 4> pi, cudaStream_t stream, size_t stride) const {
2237 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
2238 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
2239 (threads_per_block & (threads_per_block - 1)) == 0,
2240 "gpu block size must be a power of two in [1,1024]");
2241#if defined(__CUDACC__)
2242 using Node = cuda::std::array<int4, 2>;
2243 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
2244 size_t count = 0;
2245 if (!detail::RangeOutputCount<in_bits>(first, last, ys.size(), count) || stride == 0 ||
2246 stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
2247 cws.size() < static_cast<size_t>(in_bits) * stride + 1 ||
2248 cws.data() == nullptr || cs.data() == nullptr ||
2249 ys.data() == nullptr || pi.data() == nullptr) return cudaErrorInvalidValue;
2250 const size_t second_count = count / 2 + count % 2;
2251 constexpr size_t kMaxNodes = size_t(-1) / sizeof(Node);
2252 if (count > kMaxNodes || second_count > kMaxNodes - count) return cudaErrorInvalidValue;
2253 constexpr int kThreadBits = [] {
2254 int bits = 0;
2255 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++bits;
2256 return bits;
2257 }();
2258 constexpr int kTailCap = frontier_depth < 30 + kThreadBits ? frontier_depth : 30 + kThreadBits;
2259 constexpr int kChunkDepth = kTailCap + 8 < 30 + kThreadBits ? kTailCap + 8 : 30 + kThreadBits;
2260 const size_t fold_count = count / 2 + count % 2;
2261 const size_t fold_blocks = fold_count / threads_per_block + (fold_count % threads_per_block != 0);
2262 if (fold_blocks > static_cast<size_t>(INT_MAX)) return cudaErrorInvalidConfiguration;
2263 gpu::detail::DeviceScratch first_storage(stream), second_storage(stream);
2264 cudaError_t status = first_storage.Allocate<Node>(count);
2265 if (status != cudaSuccess) return status;
2266 status = second_storage.Allocate<Node>(second_count);
2267 if (status != cudaSuccess) return status;
2268 auto *nodes = first_storage.Get<Node>();
2269 auto *next = second_storage.Get<Node>();
2270 status = detail::ForEachAlignedRange<in_bits>(first, last, kChunkDepth,
2271 [&](In chunk_first, int depth, size_t offset, size_t) {
2272 const int frontier = depth < frontier_depth ? depth : frontier_depth;
2273 const int thread_bits = frontier < kThreadBits ? frontier : kThreadBits;
2274 const int block_root_depth = frontier - thread_bits;
2275 const size_t blocks = size_t{1} << block_root_depth;
2276 if (blocks > static_cast<size_t>(INT_MAX)) return cudaErrorInvalidConfiguration;
2277 gpu::detail::VdpfRangeMerkleLeavesKernel<in_bits, threads_per_block, Group, Prg, XorHash, Hash, In>
2278 <<<blocks, 1 << thread_bits, 0, stream>>>(b, chunk_first, depth,
2279 frontier, block_root_depth, s0, cws.data(), tr, stride, cs.data(),
2280 ys.data() + offset, nodes + offset, prg_, xor_hash_, hash_);
2281 return cudaGetLastError();
2282 });
2283 if (status != cudaSuccess) return status;
2284 return gpu::detail::VdpfFoldNodesToProof<threads_per_block>(nodes, next, count, cs.data(),
2285 pi.data(), hash_, stream, &first_storage, &second_storage);
2286#else
2287 static_assert(sizeof(Prg) == 0, "gpu launch requires a CUDA compiler");
2288 return cudaErrorNotSupported;
2289#endif
2290}
2291
2292template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2295template <int threads_per_block>
2297 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
2298 cuda::std::span<const int4, 4> cs, cuda::std::span<const In> xs,
2299 cuda::std::span<int4> ys, cuda::std::span<int4, 4> pi, cudaStream_t stream, size_t stride) const {
2300 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
2301 (threads_per_block & (threads_per_block - 1)) == 0,
2302 "gpu block size must be a power of two in [1,1024]");
2303#if defined(__CUDACC__)
2304 using Node = cuda::std::array<int4, 2>;
2305 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
2306 const size_t count = xs.size();
2307 if (count == 0 || ys.size() < count || stride == 0 ||
2308 stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
2309 cws.size() < static_cast<size_t>(in_bits) * stride + 1 ||
2310 cws.data() == nullptr || cs.data() == nullptr || xs.data() == nullptr ||
2311 ys.data() == nullptr || pi.data() == nullptr) return cudaErrorInvalidValue;
2312 const size_t second_count = count / 2 + count % 2;
2313 constexpr size_t kMaxNodes = size_t(-1) / sizeof(Node);
2314 if (count > kMaxNodes || second_count > kMaxNodes - count) return cudaErrorInvalidValue;
2315 const size_t leaf_blocks = count / threads_per_block + (count % threads_per_block != 0);
2316 if (leaf_blocks > static_cast<size_t>(INT_MAX)) return cudaErrorInvalidConfiguration;
2317 gpu::detail::DeviceScratch first_storage(stream), second_storage(stream);
2318 cudaError_t status = first_storage.Allocate<Node>(count);
2319 if (status != cudaSuccess) return status;
2320 status = second_storage.Allocate<Node>(second_count);
2321 if (status != cudaSuccess) return status;
2322 auto *nodes = first_storage.Get<Node>();
2323 auto *next = second_storage.Get<Node>();
2324 gpu::detail::VdpfBatchMerkleLeavesKernel<in_bits, Group, Prg, XorHash, Hash, In>
2325 <<<static_cast<int>(leaf_blocks), threads_per_block, 0, stream>>>(
2326 b, s0, cws.data(), tr, stride, cs.data(), xs.data(), ys.data(), nodes, count,
2327 prg_, xor_hash_, hash_);
2328 status = cudaGetLastError();
2329 if (status != cudaSuccess) return status;
2330 return gpu::detail::VdpfFoldNodesToProof<threads_per_block>(nodes, next, count, cs.data(),
2331 pi.data(), hash_, stream, &first_storage, &second_storage);
2332#else
2333 static_assert(sizeof(Prg) == 0, "gpu launch requires a CUDA compiler");
2334 return cudaErrorNotSupported;
2335#endif
2336}
2337
2338#if FSS_ENABLE_V1_COMPAT
2339
2340// Deprecated v1.3.0 aggregate-Cw entries. Every wrapper copies between the
2341// caller's Cw layout and a compile-time-sized split-component scratch buffer,
2342// then runs the current public method at stride 1. The old layout holds only
2343// the in_bits tree correction words, so a caller span of at least in_bits
2344// entries suffices and the output correction word travels through ocw. On a
2345// failed single-attempt Gen() the scratch is unspecified and is never read to
2346// fabricate outputs; the failure code passes through unchanged.
2347template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2350__host__ __device__ inline int Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::Gen(
2351 Cw cws[], cuda::std::array<int4, 4> &cs, int4 &ocw,
2352 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf) {
2353 int4 seed[in_bits + 1];
2354 In tr;
2355 const int res = Gen(cuda::std::span<int4>(seed, in_bits + 1), tr,
2356 cuda::std::span<int4, 4>(cs), s0s, a, b_buf);
2357 if (res != 0) return res;
2358 for (int i = 0; i < in_bits; ++i) cws[i] = {seed[i], ((tr >> i) & 1) != 0};
2359 ocw = seed[in_bits];
2360 return res;
2361}
2362
2363template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2365 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2366__host__ __device__ inline cuda::std::array<int4, 4> Vdpf<in_bits, Group, Prg, XorHash, Hash, In>::Eval(
2367 bool b, int4 s0, cuda::std::span<const Cw> cws, cuda::std::span<const int4, 4> cs,
2368 int4 ocw, In x, int4 &y) {
2369 int4 seed[in_bits + 1];
2370 In tr = 0;
2371 for (int i = 0; i < in_bits; ++i) {
2372 seed[i] = cws[i].s;
2373 tr |= static_cast<In>(cws[i].tr) << i;
2374 }
2375 seed[in_bits] = ocw;
2376 return Eval(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, cs, x, y);
2377}
2378
2379template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2381 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2383 bool b, int4 s0, cuda::std::span<const Cw> cws, cuda::std::span<const int4, 4> cs,
2384 int4 ocw, cuda::std::span<int4> ys, cuda::std::array<int4, 4> &pi) {
2385 int4 seed[in_bits + 1];
2386 In tr = 0;
2387 for (int i = 0; i < in_bits; ++i) {
2388 seed[i] = cws[i].s;
2389 tr |= static_cast<In>(cws[i].tr) << i;
2390 }
2391 seed[in_bits] = ocw;
2392 EvalAll(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, cs, ys, pi);
2393}
2394
2395// Deprecated v1.3.0 aggregate-Key entries. Same conversion as the Cw entries
2396// above, with the correction seed and output correction word carried by the
2397// Key record. The old Key layout holds exactly in_bits tree correction words,
2398// so the conversion never reads a terminal entry from cws.
2399template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2401 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2402__host__ __device__ inline compat::detail::EvalResultRecord<Vdpf<in_bits, Group, Prg, XorHash, Hash, In>>
2404 bool b, const compat::detail::VdpfKeyRecord<Vdpf, Cw, in_bits> &key, In x) {
2405 int4 seed[in_bits + 1];
2406 In tr = 0;
2407 for (int i = 0; i < in_bits; ++i) {
2408 seed[i] = key.cws[i].s;
2409 tr |= static_cast<In>(key.cws[i].tr) << i;
2410 }
2411 seed[in_bits] = key.ocw;
2412 compat::detail::EvalResultRecord<Vdpf> result;
2413 result.pi_tilde = Eval(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr,
2414 cuda::std::span<const int4, 4>(key.cs), x, result.y);
2415 return result;
2416}
2417
2418template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2420 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2422 bool b, const compat::detail::VdpfKeyRecord<Vdpf, Cw, in_bits> &key,
2423 cuda::std::span<int4> ys, cuda::std::array<int4, 4> &pi) {
2424 int4 seed[in_bits + 1];
2425 In tr = 0;
2426 for (int i = 0; i < in_bits; ++i) {
2427 seed[i] = key.cws[i].s;
2428 tr |= static_cast<In>(key.cws[i].tr) << i;
2429 }
2430 seed[in_bits] = key.ocw;
2431 EvalAll(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr,
2432 cuda::std::span<const int4, 4>(key.cs), ys, cuda::std::span<int4, 4>(pi));
2433}
2434
2435template <int in_bits, typename Group, typename Prg, typename XorHash, typename Hash, typename In>
2437 Groupable<Group> && Prgable<Prg, 2> && XorHashable<XorHash> && Hashable<Hash>)
2439 bool b, const compat::detail::VdpfKeyRecord<Vdpf, Cw, in_bits> &key,
2440 cuda::std::span<int4> ys, cuda::std::array<int4, 4> &pi) {
2441 int4 seed[in_bits + 1];
2442 In tr = 0;
2443 for (int i = 0; i < in_bits; ++i) {
2444 seed[i] = key.cws[i].s;
2445 tr |= static_cast<In>(key.cws[i].tr) << i;
2446 }
2447 seed[in_bits] = key.ocw;
2448 EvalAllMerkle(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr,
2449 cuda::std::span<const int4, 4>(key.cs), ys, cuda::std::span<int4, 4>(pi));
2450}
2451
2452#endif // FSS_ENABLE_V1_COMPAT
2453
2454} // namespace fss
2-party VDPF scheme.
Definition vdpf.cuh:115
cudaError_t EvalRangeMerkleGpu(bool b, __uint128_t first, __uint128_t last, int4 s0, cuda::std::span< const int4 > cws, __uint128_t tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, cudaStream_t stream=0, size_t stride=1) const
static bool Verify(cuda::std::span< const int4, 4 > pi0, cuda::std::span< const int4, 4 > pi1)
Verification method.
Definition vdpf.cuh:1592
cuda::std::array< int4, 4 > Eval(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< const int4, 4 > cs, In x, int4 &y, size_t stride=1)
Evaluate one input from separate correction components.
Definition vdpf.cuh:1509
Vdpf(Prg prg, XorHash xor_hash, Hash hash)
Construct a scheme with configured backends.
Definition vdpf.cuh:1503
static constexpr int kCwCount
Definition vdpf.cuh:137
void ProveMerkle(cuda::std::span< const cuda::std::array< int4, 4 > > pi_tildes, cuda::std::span< const int4, 4 > cs, cuda::std::span< int4, 4 > pi, int par_depth=0)
Proof accumulation method (Merkle fold).
Definition vdpf.cuh:1709
void EvalAllMerkle(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, int par_depth=0, size_t stride=1)
Full-domain evaluation with the Merkle-fold proof from split correction words.
Definition vdpf.cuh:1771
void BatchEval(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< const In > xs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, size_t stride=1)
Evaluate an arbitrary ordered query sequence and produce a chain proof from split correction words.
Definition vdpf.cuh:1876
cudaError_t EvalAllMerkleGpu(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, cudaStream_t stream=0, size_t stride=1) const
Launch full-domain CUDA evaluation with the canonical Merkle-fold proof from split device arrays.
Definition vdpf.cuh:2195
int Gen(cuda::std::span< int4 > cws, In &tr, cuda::std::span< int4, 4 > cs, cuda::std::span< const int4, 2 > s0s, In a, int4 b_buf, size_t stride=1)
Generate correction words in a caller-strided layout and packed control bits.
Definition vdpf.cuh:1934
void BatchEvalMerkle(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< const In > xs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, size_t stride=1)
Evaluate an arbitrary ordered query sequence and produce a Merkle proof from split correction words.
Definition vdpf.cuh:1917
void EvalAll(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, int par_depth=0, size_t stride=1)
Evaluate the full domain and produce a chain proof from split correction words.
Definition vdpf.cuh:1603
void EvalRange(bool b, In first, In last, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, int par_depth=0, size_t stride=1)
Evaluate the inclusive range [first, last] and produce a chain proof from split correction words.
Definition vdpf.cuh:1614
void EvalRangeMerkle(bool b, __uint128_t first, __uint128_t last, int4 s0, cuda::std::span< const int4 > cws, __uint128_t tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, int par_depth=0, size_t stride=1)
void Prove(cuda::std::span< const cuda::std::array< int4, 4 > > pi_tildes, cuda::std::span< const int4, 4 > cs, cuda::std::span< int4, 4 > pi)
Proof accumulation method.
Definition vdpf.cuh:1573
cudaError_t BatchEvalMerkleGpu(bool b, int4 s0, cuda::std::span< const int4 > cws, __uint128_t tr, cuda::std::span< const int4, 4 > cs, cuda::std::span< const __uint128_t > xs, cuda::std::span< int4 > ys, cuda::std::span< int4, 4 > pi, cudaStream_t stream=0, size_t stride=1) const
Single build-time switch for the deprecated v1 Cw-form compatibility entries.
Group interface.
Definition group.cuh:49
Optional four-message batch extension of Hashable.
Definition hash.cuh:50
Collision-resistant hash interface.
Definition hash.cuh:20
Pseudorandom generator (PRG) interface.
Definition prg.cuh:24
Collision-resistant and XOR-collision-resistant hash interface.
Definition hash.cuh:72
Unsigned input storage wide enough for an input domain of in_bits bits.
Definition constraints.cuh:29
2-party distributed point function (DPF).
bool GetLsb(int4 val)
Return bit 0 of the fourth word of val.
Definition util.cuh:58
void RequireArg(bool ok, const char *msg)
Check a caller precondition.
Definition util.cuh:73
int4 SetLsb(int4 val, bool bit)
Return val with bit 0 of its fourth word set to bit.
Definition util.cuh:51
int4 Xor(int4 lhs, int4 rhs)
Return the bitwise XOR of two 128-bit blocks.
Definition util.cuh:29
Correction word.
Definition vdpf.cuh:129
bool tr
Right control bit of the correction word. False and ignored in the final output word.
Definition vdpf.cuh:131
int4 s
Tree seed correction word, or the final output correction word at cws[in_bits].
Definition vdpf.cuh:130