Auto merge of #908 - ebfull:trafford, r=ebfull
libzcash and new zkSNARK circuit implementation
This PR completes [`libzcash`](https://github.com/zcash/zcash/tree/zc.v0.11.2.latest/src/zcash), the implementation of the [Zcash protocol specification](9bb4410e45/protocol/protocol.pdf) and replacement of [`libzerocash`](https://github.com/Zerocash/libzerocash), our old Zerocash protocol implementation. The new spec comes with some improvements to security and terminology, with minimal differences from the original academic design.
This implementation includes:
* A rewrite of the zkSNARK circuit for `JoinSplit` operations. This rewrite is cleaner, broken up into separate gadgets, easier to audit and review, and fixes some security bugs. (Closes #822, Closes #809, Closes #500, Closes #854)
* A minimal API for interacting with `JoinSplit`s and surrounding primitives. This PR removes almost twice as much code as it introduces. (Closes #877, Closes #315, Closes #824, Closes #798, Closes #707, Closes #512, Closes #247, Closes #128, Closes #514)
This commit is contained in:
78
src/gtest/test_circuit.cpp
Normal file
78
src/gtest/test_circuit.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "uint256.h"
|
||||
|
||||
#include "zerocash/utils/util.h"
|
||||
#include "zcash/util.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "libsnark/common/default_types/r1cs_ppzksnark_pp.hpp"
|
||||
#include "libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp"
|
||||
#include "libsnark/gadgetlib1/gadgets/hashes/sha256/sha256_gadget.hpp"
|
||||
#include "libsnark/gadgetlib1/gadgets/merkle_tree/merkle_tree_check_read_gadget.hpp"
|
||||
|
||||
using namespace libsnark;
|
||||
using namespace libzerocash;
|
||||
|
||||
#include "zcash/circuit/utils.tcc"
|
||||
|
||||
template<typename FieldT>
|
||||
void test_value_equals(uint64_t i) {
|
||||
protoboard<FieldT> pb;
|
||||
pb_variable_array<FieldT> num;
|
||||
num.allocate(pb, 64, "");
|
||||
num.fill_with_bits(pb, uint64_to_bool_vector(i));
|
||||
pb.add_r1cs_constraint(r1cs_constraint<FieldT>(
|
||||
packed_addition(num),
|
||||
FieldT::one(),
|
||||
FieldT::one() * i
|
||||
), "");
|
||||
ASSERT_TRUE(pb.is_satisfied());
|
||||
}
|
||||
|
||||
TEST(circuit, values)
|
||||
{
|
||||
default_r1cs_ppzksnark_pp::init_public_params();
|
||||
typedef Fr<default_r1cs_ppzksnark_pp> FieldT;
|
||||
test_value_equals<FieldT>(0);
|
||||
test_value_equals<FieldT>(1);
|
||||
test_value_equals<FieldT>(3);
|
||||
test_value_equals<FieldT>(5391);
|
||||
test_value_equals<FieldT>(883128374);
|
||||
test_value_equals<FieldT>(173419028459);
|
||||
test_value_equals<FieldT>(2205843009213693953);
|
||||
}
|
||||
|
||||
TEST(circuit, endianness)
|
||||
{
|
||||
std::vector<unsigned char> before = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31,
|
||||
32, 33, 34, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 44, 45, 46, 47,
|
||||
48, 49, 50, 51, 52, 53, 54, 55,
|
||||
56, 57, 58, 59, 60, 61, 62, 63
|
||||
};
|
||||
auto result = swap_endianness_u64(before);
|
||||
|
||||
std::vector<unsigned char> after = {
|
||||
56, 57, 58, 59, 60, 61, 62, 63,
|
||||
48, 49, 50, 51, 52, 53, 54, 55,
|
||||
40, 41, 42, 43, 44, 45, 46, 47,
|
||||
32, 33, 34, 35, 36, 37, 38, 39,
|
||||
24, 25, 26, 27, 28, 29, 30, 31,
|
||||
16, 17, 18, 19, 20, 21, 22, 23,
|
||||
8, 9, 10, 11, 12, 13, 14, 15,
|
||||
0, 1, 2, 3, 4, 5, 6, 7
|
||||
};
|
||||
|
||||
EXPECT_EQ(after, result);
|
||||
|
||||
std::vector<unsigned char> bad = {0, 1, 2, 3};
|
||||
|
||||
ASSERT_THROW(swap_endianness_u64(bad), std::length_error);
|
||||
}
|
||||
294
src/gtest/test_joinsplit.cpp
Normal file
294
src/gtest/test_joinsplit.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "utilstrencodings.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "zcash/prf.h"
|
||||
|
||||
#include "zcash/JoinSplit.hpp"
|
||||
#include "zcash/Note.hpp"
|
||||
#include "zcash/NoteEncryption.hpp"
|
||||
#include "zcash/IncrementalMerkleTree.hpp"
|
||||
|
||||
using namespace libzcash;
|
||||
|
||||
void test_full_api(ZCJoinSplit* js)
|
||||
{
|
||||
// The recipient's information.
|
||||
SpendingKey recipient_key = SpendingKey::random();
|
||||
PaymentAddress recipient_addr = recipient_key.address();
|
||||
|
||||
// Create the commitment tree
|
||||
ZCIncrementalMerkleTree tree;
|
||||
|
||||
// Set up a JoinSplit description
|
||||
uint256 ephemeralKey;
|
||||
uint256 randomSeed;
|
||||
uint64_t vpub_old = 10;
|
||||
uint64_t vpub_new = 0;
|
||||
uint256 pubKeyHash = random_uint256();
|
||||
boost::array<uint256, 2> macs;
|
||||
boost::array<uint256, 2> nullifiers;
|
||||
boost::array<uint256, 2> commitments;
|
||||
uint256 rt = tree.root();
|
||||
boost::array<ZCNoteEncryption::Ciphertext, 2> ciphertexts;
|
||||
std::string proof;
|
||||
|
||||
{
|
||||
boost::array<JSInput, 2> inputs = {
|
||||
JSInput(), // dummy input
|
||||
JSInput() // dummy input
|
||||
};
|
||||
|
||||
boost::array<JSOutput, 2> outputs = {
|
||||
JSOutput(recipient_addr, 10),
|
||||
JSOutput() // dummy output
|
||||
};
|
||||
|
||||
boost::array<Note, 2> output_notes;
|
||||
|
||||
// Perform the proof
|
||||
proof = js->prove(
|
||||
inputs,
|
||||
outputs,
|
||||
output_notes,
|
||||
ciphertexts,
|
||||
ephemeralKey,
|
||||
pubKeyHash,
|
||||
randomSeed,
|
||||
macs,
|
||||
nullifiers,
|
||||
commitments,
|
||||
vpub_old,
|
||||
vpub_new,
|
||||
rt
|
||||
);
|
||||
}
|
||||
|
||||
// Verify the transaction:
|
||||
ASSERT_TRUE(js->verify(
|
||||
proof,
|
||||
pubKeyHash,
|
||||
randomSeed,
|
||||
macs,
|
||||
nullifiers,
|
||||
commitments,
|
||||
vpub_old,
|
||||
vpub_new,
|
||||
rt
|
||||
));
|
||||
|
||||
// Recipient should decrypt
|
||||
// Now the recipient should spend the money again
|
||||
auto h_sig = js->h_sig(randomSeed, nullifiers, pubKeyHash);
|
||||
ZCNoteDecryption decryptor(recipient_key.viewing_key());
|
||||
|
||||
auto note_pt = NotePlaintext::decrypt(
|
||||
decryptor,
|
||||
ciphertexts[0],
|
||||
ephemeralKey,
|
||||
h_sig,
|
||||
0
|
||||
);
|
||||
|
||||
auto decrypted_note = note_pt.note(recipient_addr);
|
||||
|
||||
ASSERT_TRUE(decrypted_note.value == 10);
|
||||
|
||||
// Insert the commitments from the last tx into the tree
|
||||
tree.append(commitments[0]);
|
||||
auto witness_recipient = tree.witness();
|
||||
tree.append(commitments[1]);
|
||||
witness_recipient.append(commitments[1]);
|
||||
vpub_old = 0;
|
||||
vpub_new = 1;
|
||||
rt = tree.root();
|
||||
pubKeyHash = random_uint256();
|
||||
|
||||
{
|
||||
boost::array<JSInput, 2> inputs = {
|
||||
JSInput(), // dummy input
|
||||
JSInput(witness_recipient, decrypted_note, recipient_key)
|
||||
};
|
||||
|
||||
SpendingKey second_recipient = SpendingKey::random();
|
||||
PaymentAddress second_addr = second_recipient.address();
|
||||
|
||||
boost::array<JSOutput, 2> outputs = {
|
||||
JSOutput(second_addr, 9),
|
||||
JSOutput() // dummy output
|
||||
};
|
||||
|
||||
boost::array<Note, 2> output_notes;
|
||||
|
||||
// Perform the proof
|
||||
proof = js->prove(
|
||||
inputs,
|
||||
outputs,
|
||||
output_notes,
|
||||
ciphertexts,
|
||||
ephemeralKey,
|
||||
pubKeyHash,
|
||||
randomSeed,
|
||||
macs,
|
||||
nullifiers,
|
||||
commitments,
|
||||
vpub_old,
|
||||
vpub_new,
|
||||
rt
|
||||
);
|
||||
}
|
||||
|
||||
// Verify the transaction:
|
||||
ASSERT_TRUE(js->verify(
|
||||
proof,
|
||||
pubKeyHash,
|
||||
randomSeed,
|
||||
macs,
|
||||
nullifiers,
|
||||
commitments,
|
||||
vpub_old,
|
||||
vpub_new,
|
||||
rt
|
||||
));
|
||||
}
|
||||
|
||||
TEST(joinsplit, h_sig)
|
||||
{
|
||||
auto js = ZCJoinSplit::Unopened();
|
||||
|
||||
/*
|
||||
// by Taylor Hornby
|
||||
|
||||
import pyblake2
|
||||
import binascii
|
||||
|
||||
def hSig(randomSeed, nf1, nf2, pubKeyHash):
|
||||
return pyblake2.blake2b(
|
||||
data=(randomSeed + nf1 + nf2 + pubKeyHash),
|
||||
digest_size=32,
|
||||
person=b"ZcashComputehSig"
|
||||
).digest()
|
||||
|
||||
INCREASING = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
|
||||
|
||||
TEST_VECTORS = [
|
||||
[b"a" * 32, b"b" * 32, b"c" * 32, b"d" * 32],
|
||||
[b"\x00" * 32, b"\x00" * 32, b"\x00" * 32, b"\x00" * 32],
|
||||
[b"\xFF" * 32, b"\xFF" * 32, b"\xFF" * 32, b"\xFF" * 32],
|
||||
[INCREASING, INCREASING, INCREASING, INCREASING]
|
||||
]
|
||||
|
||||
for test_input in TEST_VECTORS:
|
||||
print "---"
|
||||
print "\"" + binascii.hexlify(test_input[0][::-1]) + "\""
|
||||
print "\"" + binascii.hexlify(test_input[1][::-1]) + "\""
|
||||
print "\"" + binascii.hexlify(test_input[2][::-1]) + "\""
|
||||
print "\"" + binascii.hexlify(test_input[3][::-1]) + "\""
|
||||
print "\"" + binascii.hexlify(hSig(test_input[0], test_input[1], test_input[2], test_input[3])[::-1]) + "\""
|
||||
*/
|
||||
|
||||
std::vector<std::vector<std::string>> tests = {
|
||||
{
|
||||
"6161616161616161616161616161616161616161616161616161616161616161",
|
||||
"6262626262626262626262626262626262626262626262626262626262626262",
|
||||
"6363636363636363636363636363636363636363636363636363636363636363",
|
||||
"6464646464646464646464646464646464646464646464646464646464646464",
|
||||
"a8cba69f1fa329c055756b4af900f8a00b61e44f4cb8a1824ceb58b90a5b8113"
|
||||
},
|
||||
{
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"697322276b5dd93b12fb1fcbd2144b2960f24c73aac6c6a0811447be1e7f1e19"
|
||||
},
|
||||
{
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"4961048919f0ca79d49c9378c36a91a8767060001f4212fe6f7d426f3ccf9f32"
|
||||
},
|
||||
{
|
||||
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
|
||||
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
|
||||
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
|
||||
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
|
||||
"b61110ec162693bc3d9ca7fb0eec3afd2e278e2f41394b3ff11d7cb761ad4b27"
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_FOREACH(std::vector<std::string>& v, tests) {
|
||||
auto expected = js->h_sig(
|
||||
uint256S(v[0]),
|
||||
{uint256S(v[1]), uint256S(v[2])},
|
||||
uint256S(v[3])
|
||||
);
|
||||
|
||||
EXPECT_EQ(expected, uint256S(v[4]));
|
||||
}
|
||||
|
||||
delete js;
|
||||
}
|
||||
|
||||
TEST(joinsplit, full_api_test)
|
||||
{
|
||||
auto js = ZCJoinSplit::Generate();
|
||||
|
||||
test_full_api(js);
|
||||
|
||||
js->saveProvingKey("./zcashTest.pk");
|
||||
js->saveVerifyingKey("./zcashTest.vk");
|
||||
|
||||
delete js;
|
||||
|
||||
js = ZCJoinSplit::Unopened();
|
||||
|
||||
js->setProvingKeyPath("./zcashTest.pk");
|
||||
js->loadProvingKey();
|
||||
js->loadVerifyingKey("./zcashTest.vk");
|
||||
|
||||
test_full_api(js);
|
||||
|
||||
delete js;
|
||||
}
|
||||
|
||||
TEST(joinsplit, note_plaintexts)
|
||||
{
|
||||
uint256 a_sk = uint256S("f6da8716682d600f74fc16bd0187faad6a26b4aa4c24d5c055b216d94516847e");
|
||||
uint256 a_pk = PRF_addr_a_pk(a_sk);
|
||||
uint256 sk_enc = ZCNoteEncryption::generate_privkey(a_sk);
|
||||
uint256 pk_enc = ZCNoteEncryption::generate_pubkey(sk_enc);
|
||||
PaymentAddress addr_pk(a_pk, pk_enc);
|
||||
|
||||
uint256 h_sig;
|
||||
|
||||
ZCNoteEncryption encryptor(h_sig);
|
||||
uint256 epk = encryptor.get_epk();
|
||||
|
||||
Note note(a_pk,
|
||||
1945813,
|
||||
random_uint256(),
|
||||
random_uint256()
|
||||
);
|
||||
|
||||
boost::array<unsigned char, ZC_MEMO_SIZE> memo;
|
||||
|
||||
NotePlaintext note_pt(note, memo);
|
||||
|
||||
ZCNoteEncryption::Ciphertext ct = note_pt.encrypt(encryptor, pk_enc);
|
||||
|
||||
ZCNoteDecryption decryptor(sk_enc);
|
||||
|
||||
auto decrypted = NotePlaintext::decrypt(decryptor, ct, epk, h_sig, 0);
|
||||
auto decrypted_note = decrypted.note(addr_pk);
|
||||
|
||||
ASSERT_TRUE(decrypted_note.a_pk == note.a_pk);
|
||||
ASSERT_TRUE(decrypted_note.rho == note.rho);
|
||||
ASSERT_TRUE(decrypted_note.r == note.r);
|
||||
ASSERT_TRUE(decrypted_note.value == note.value);
|
||||
|
||||
ASSERT_TRUE(decrypted.memo == note_pt.memo);
|
||||
}
|
||||
@@ -40,7 +40,6 @@ read_json(const std::string& jsondata)
|
||||
}
|
||||
|
||||
#include "zcash/IncrementalMerkleTree.hpp"
|
||||
#include "zerocash/IncrementalMerkleTree.h"
|
||||
#include "zerocash/utils/util.h"
|
||||
|
||||
//#define PRINT_JSON 1
|
||||
@@ -106,107 +105,11 @@ void expect_test_vector(T& it, const U& expected)
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
This is a wrapper around the old incremental merkle tree which
|
||||
attempts to mimic the new API as much as possible so that its
|
||||
behavior can be compared with the test vectors we use.
|
||||
*/
|
||||
class OldIncrementalMerkleTree {
|
||||
private:
|
||||
libzerocash::IncrementalMerkleTree* tree;
|
||||
boost::optional<std::vector<bool>> index;
|
||||
bool witnessed;
|
||||
|
||||
public:
|
||||
OldIncrementalMerkleTree() : index(boost::none), witnessed(false) {
|
||||
this->tree = new IncrementalMerkleTree(INCREMENTAL_MERKLE_TREE_DEPTH_TESTING);
|
||||
}
|
||||
|
||||
~OldIncrementalMerkleTree()
|
||||
{
|
||||
delete tree;
|
||||
}
|
||||
|
||||
OldIncrementalMerkleTree (const OldIncrementalMerkleTree& other) : index(boost::none), witnessed(false)
|
||||
{
|
||||
this->tree = new IncrementalMerkleTree(INCREMENTAL_MERKLE_TREE_DEPTH_TESTING);
|
||||
this->tree->setTo(*other.tree);
|
||||
index = other.index;
|
||||
witnessed = other.witnessed;
|
||||
}
|
||||
|
||||
OldIncrementalMerkleTree& operator= (const OldIncrementalMerkleTree& other)
|
||||
{
|
||||
OldIncrementalMerkleTree tmp(other); // re-use copy-constructor
|
||||
*this = std::move(tmp); // re-use move-assignment
|
||||
return *this;
|
||||
}
|
||||
|
||||
OldIncrementalMerkleTree& operator= (OldIncrementalMerkleTree&& other)
|
||||
{
|
||||
tree->setTo(*other.tree);
|
||||
index = other.index;
|
||||
witnessed = other.witnessed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
libzcash::MerklePath path() {
|
||||
assert(witnessed);
|
||||
|
||||
if (!index) {
|
||||
throw std::runtime_error("can't create an authentication path for the beginning of the tree");
|
||||
}
|
||||
|
||||
merkle_authentication_path path(INCREMENTAL_MERKLE_TREE_DEPTH_TESTING);
|
||||
tree->getWitness(*index, path);
|
||||
|
||||
libzcash::MerklePath ret;
|
||||
ret.authentication_path = path;
|
||||
ret.index = *index;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint256 root() {
|
||||
std::vector<unsigned char> newrt_v(32);
|
||||
tree->getRootValue(newrt_v);
|
||||
return uint256(newrt_v);
|
||||
}
|
||||
|
||||
void append(uint256 obj) {
|
||||
std::vector<bool> new_index;
|
||||
std::vector<unsigned char> obj_bv(obj.begin(), obj.end());
|
||||
|
||||
std::vector<bool> commitment_bv(256);
|
||||
libzerocash::convertBytesVectorToVector(obj_bv, commitment_bv);
|
||||
|
||||
tree->insertElement(commitment_bv, new_index);
|
||||
|
||||
if (!witnessed) {
|
||||
index = new_index;
|
||||
}
|
||||
}
|
||||
|
||||
OldIncrementalMerkleTree witness() {
|
||||
OldIncrementalMerkleTree ret;
|
||||
ret.tree->setTo(*tree);
|
||||
ret.index = index;
|
||||
ret.witnessed = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename A, typename B, typename C>
|
||||
void expect_ser_test_vector(B& b, const C& c, const A& tree) {
|
||||
expect_test_vector<B, C>(b, c);
|
||||
}
|
||||
|
||||
template<typename B, typename C>
|
||||
void expect_ser_test_vector(B& b, const C& c, const OldIncrementalMerkleTree& tree) {
|
||||
// Don't perform serialization tests on the old tree.
|
||||
}
|
||||
|
||||
template<typename Tree, typename Witness>
|
||||
void test_tree(Array root_tests, Array ser_tests, Array witness_ser_tests, Array path_tests) {
|
||||
Array::iterator root_iterator = root_tests.begin();
|
||||
@@ -250,13 +153,7 @@ void test_tree(Array root_tests, Array ser_tests, Array witness_ser_tests, Array
|
||||
} else {
|
||||
auto path = wit.path();
|
||||
|
||||
// The old tree has some serious bugs which make it
|
||||
// fail some of these test vectors.
|
||||
//
|
||||
// The new tree is strictly more correct in its
|
||||
// behavior, as we demonstrate by constructing and
|
||||
// evaluating the tree over a dummy circuit.
|
||||
if (typeid(Tree) != typeid(OldIncrementalMerkleTree)) {
|
||||
{
|
||||
expect_test_vector(path_iterator, path);
|
||||
|
||||
typedef Fr<default_r1cs_ppzksnark_pp> FieldT;
|
||||
@@ -316,8 +213,7 @@ void test_tree(Array root_tests, Array ser_tests, Array witness_ser_tests, Array
|
||||
}
|
||||
}
|
||||
|
||||
// The old tree would silently ignore appending when it was full.
|
||||
if (typeid(Tree) != typeid(OldIncrementalMerkleTree)) {
|
||||
{
|
||||
// Tree should be full now
|
||||
ASSERT_THROW(tree.append(uint256()), std::runtime_error);
|
||||
|
||||
@@ -337,7 +233,6 @@ TEST(merkletree, vectors) {
|
||||
Array path_tests = read_json(std::string(json_tests::merkle_path, json_tests::merkle_path + sizeof(json_tests::merkle_path)));
|
||||
|
||||
test_tree<ZCTestingIncrementalMerkleTree, ZCTestingIncrementalWitness>(root_tests, ser_tests, witness_ser_tests, path_tests);
|
||||
test_tree<OldIncrementalMerkleTree, OldIncrementalMerkleTree>(root_tests, ser_tests, witness_ser_tests, path_tests);
|
||||
}
|
||||
|
||||
TEST(merkletree, deserializeInvalid) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "zcash/NoteEncryption.hpp"
|
||||
#include "zcash/prf.h"
|
||||
#include "crypto/sha256.h"
|
||||
|
||||
class TestNoteDecryption : public ZCNoteDecryption {
|
||||
public:
|
||||
@@ -28,8 +29,8 @@ TEST(noteencryption, api)
|
||||
ASSERT_TRUE(b.get_epk() != c.get_epk());
|
||||
}
|
||||
|
||||
boost::array<unsigned char, 216> message;
|
||||
for (unsigned char i = 0; i < 216; i++) {
|
||||
boost::array<unsigned char, 201> message;
|
||||
for (unsigned char i = 0; i < 201; i++) {
|
||||
// Fill the message with dummy data
|
||||
message[i] = (unsigned char) i;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user