extern "C" { #include #include #include } #include #include #include #include #include #include #include #include #include #include #include #include #include "evutils.h" #include "subproblem.h" NTL_CLIENT struct SubproblemProgress; typedef std::set BESet; typedef std::map BEMap; void besetdump(const BESet &bes, ostream &os) { BESet::const_iterator besit; os << hex << " "; for (besit = bes.begin(); besit != bes.end(); ++besit) { os << *besit << " "; } os << dec << "\n"; } void bemapdump(const BEMap &bem, ostream &os) { BEMap::const_iterator bemit; os << hex << " "; for (bemit = bem.begin(); bemit != bem.end(); ++bemit) { os << bemit->first << "->" << bemit->second << " "; } os << dec << "\n"; } struct Statuses { BESet idle; BEMap working; // Dump the state for debug purposes void dump(ostream &os) const { os << " idle (" << idle.size() << "):\n"; besetdump(idle, os); os << " working (" << working.size() << "):\n"; bemapdump(working, os); } }; struct FactorDecomp { ZZ factor; vec_ZZ fvec; }; void vsppdump(const vector &spv, ostream &os); static struct ControllerState { ZZ rho; FactorDecomp p, q; int working; vector subproblems_p, subproblems_q; Statuses dpnodes, workers; ControllerState() : working(0) {} // Dump the state for debug purposes void dump(ostream &os) const { if (!working) { os << "Not working\n"; return; } os << "P:\n"; vsppdump(subproblems_p, os); os << "Q:\n"; vsppdump(subproblems_q, os); os << "dpnodes:\n"; dpnodes.dump(os); os << "workers:\n"; workers.dump(os); } } ctrlstate; struct IPPort { unsigned char ipport[6]; IPPort(unsigned char *ipp) { memmove(ipport, ipp, 6); } void dump(ostream &os) const { os << int(ipport[0]) << "." << int(ipport[1]) << "." << int(ipport[2]) << "." << int(ipport[3]) << ":" << ((ipport[4] << 8) + ipport[5]) << " "; } }; typedef vector IPPortSet; void ipportsetdump(const IPPortSet &ipps, ostream &os) { IPPortSet::const_iterator ippsit; os << " "; for (ippsit = ipps.begin(); ippsit != ipps.end(); ++ippsit) { ippsit->dump(os); } os << "\n"; } struct SubproblemProgress : Subproblem { // The sets of dpnodes and workers currently working on this subproblem BESet dpnodes, workers; // The dpnode IPPorts registered for this subproblem IPPortSet ipports; // The desired number of DPnodes for this subproblem unsigned short desired_dpnodes; // The maximum number of workers useful for this subproblem unsigned int max_workers; // Have we found a solution? int solved; // The solution, if found. ZZ solution; SubproblemProgress(unsigned short id, const ZZ &b, const ZZ &t, const ZZ &m, const ZZ &o, unsigned int dpf) : Subproblem(id, b, t, m, o, dpf), solved(0) { // How many DPnodes should we use for a problem of this size? desired_dpnodes = 2; // How many workers would we like to use? ZZ sorder = SqrRoot(order >> 46); if (NumBits(sorder) > 30) { // Just use all the workers we can find max_workers = 4294967295U; // 2^32 - 1 } else { max_workers = trunc_long(sorder,31) + 1; } } // Stop all dpnodes and workers and reset to unstarted state void reset(void) { BESet::iterator iter; unsigned char stopcmd[1] = { 'S' }; for (BESet::iterator iter = dpnodes.begin(); iter != dpnodes.end(); ++iter) { bufferevent_write(*iter, stopcmd, 1); ctrlstate.dpnodes.working.erase(*iter); ctrlstate.dpnodes.idle.insert(*iter); } for (BESet::iterator iter = workers.begin(); iter != workers.end(); ++iter) { bufferevent_write(*iter, stopcmd, 1); ctrlstate.workers.working.erase(*iter); ctrlstate.workers.idle.insert(*iter); } dpnodes.clear(); workers.clear(); ipports.clear(); } // Dump for debugging purposes void dump(ostream &os) const { os << " dpnodes (" << dpnodes.size() << "):\n"; besetdump(dpnodes, os); os << " workers (" << workers.size() << "):\n"; besetdump(workers, os); os << " ipports (" << ipports.size() << "):\n"; ipportsetdump(ipports, os); } void worker_write(struct bufferevent *bev) { bev_write(bev); unsigned short num_ipports = ipports.size(); bufferevent_write(bev, &num_ipports, 2); for (unsigned short i = 0; i < num_ipports; ++i) { bufferevent_write(bev, ipports[i].ipport, 6); } } }; // Dump the state for debug purposes void vsppdump(const vector &spv, ostream &os) { vector::const_iterator spiter; int count = 0; for (spiter = spv.begin(); spiter != spv.end(); ++spiter) { ++count; os << " " << count << ":\n"; spiter->dump(os); } os << "\n"; } // Find a subproblem in the given vector that could use another DPnode, // and give it one of the idle ones. Only allocate it to a subproblem // with no current DPnodes if consider_empty is true. static void find_subproblem_for_dpnode(vector &spv, bool consider_empty) { vector::iterator spiter; for (spiter = spv.begin(); spiter != spv.end(); ++spiter) { if (spiter->solved) continue; if (spiter->dpnodes.size() == 0 && consider_empty == false) continue; // How many DPnodes would we like to have for this subproblem? while (spiter->dpnodes.size() < spiter->desired_dpnodes && ctrlstate.dpnodes.idle.size() > 0) { // Get the first idle DPnode BESet::iterator beviter = ctrlstate.dpnodes.idle.begin(); // Allocate it to the subproblem spiter->dpnodes.insert(*beviter); ctrlstate.dpnodes.working[*beviter] = &(*spiter); ctrlstate.dpnodes.idle.erase(*beviter); // Tell it to start listening for DPs spiter->bev_write(*beviter); } } } // Find a subproblem in the given vector that has all of its DPnodes and // could use another worker, and give it one of the idle ones. static void find_subproblem_for_worker(vector &spv) { vector::iterator spiter; for (spiter = spv.begin(); spiter != spv.end(); ++spiter) { if (spiter->solved) continue; while (spiter->ipports.size() == spiter->desired_dpnodes && spiter->workers.size() < spiter->max_workers && ctrlstate.workers.idle.size() > 0) { // Get the first idle worker BESet::iterator beviter = ctrlstate.workers.idle.begin(); // Allocate it to the subproblem spiter->workers.insert(*beviter); ctrlstate.workers.working[*beviter] = &(*spiter); ctrlstate.workers.idle.erase(*beviter); // Tell it to start working on the subproblem spiter->worker_write(*beviter); } } } // See if there are any idle DPnodes or workers we can put to use void schedule(void) { cerr << "Before schedule:\n"; ctrlstate.dump(cerr); // Check the DPnodes // Iterate through the subproblems, looking for one that can use // another DPnode. First look for subproblems that already have // some, but not all, of their DPnodes if (ctrlstate.dpnodes.idle.size() > 0) { find_subproblem_for_dpnode(ctrlstate.subproblems_p, false); } if (ctrlstate.dpnodes.idle.size() > 0) { find_subproblem_for_dpnode(ctrlstate.subproblems_q, false); } // If there are still more dpnodes to place, start assigning them to // subproblems with no current dpnodes if (ctrlstate.dpnodes.idle.size() > 0) { find_subproblem_for_dpnode(ctrlstate.subproblems_p, true); } if (ctrlstate.dpnodes.idle.size() > 0) { find_subproblem_for_dpnode(ctrlstate.subproblems_q, true); } // Check the workers // Iterate through the subproblems, looking for one that can use // another worker. if (ctrlstate.workers.idle.size() > 0) { find_subproblem_for_worker(ctrlstate.subproblems_p); } if (ctrlstate.workers.idle.size() > 0) { find_subproblem_for_worker(ctrlstate.subproblems_q); } cerr << "After schedule:\n"; ctrlstate.dump(cerr); } typedef enum { CCSTATE_START, CCSTATE_DPWAITRESP, CCSTATE_DPLISTENING, CCSTATE_END } CCState; struct ControllerConnInfo { CCState state; ControllerConnInfo() : state(CCSTATE_DPWAITRESP) {} }; static void controller_dpnode_reader(struct bufferevent *bev, void *ctx) { struct evbuffer *input = bufferevent_get_input(bev); ControllerConnInfo *info = (ControllerConnInfo *)ctx; unsigned char cmd[1]; while(1) { size_t len = evbuffer_get_length(input); switch (info->state) { case CCSTATE_START: case CCSTATE_DPWAITRESP: if (len < 1) return; bufferevent_read(bev, cmd, 1); switch (cmd[0]) { case 'L': info->state = CCSTATE_DPLISTENING; break; default: /* Unknown DPnode command received */ fprintf(stderr, "Unknown command in " "controller_dpnode_reader: " "%c\n", cmd[0]); info->state = CCSTATE_END; break; } break; case CCSTATE_DPLISTENING: // Read 6 bytes if (len < 6) return; unsigned char ipport[6]; unsigned int DPip; unsigned short DPport; bufferevent_read(bev, ipport, 6); memmove(&DPip, ipport, 4); memmove(&DPport, ipport+4, 2); { struct in_addr DPaddr = { DPip }; printf("DP node at %s:%d\n", inet_ntoa(DPaddr), ntohs(DPport)); if (ctrlstate.dpnodes.working.count(bev) > 0) { ctrlstate.dpnodes.working[bev]->ipports.push_back( IPPort(ipport)); schedule(); } } info->state = CCSTATE_DPWAITRESP; break; case CCSTATE_END: // Shut down the connection delete info; bufferevent_free(bev); return; } } } static void controller_worker_reader(struct bufferevent *bev, void *ctx) { struct evbuffer *input = bufferevent_get_input(bev); size_t len = evbuffer_get_length(input); } static void controller_dpnode_event_cb(struct bufferevent *bev, short events, void *ctx) { if (events & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { ControllerConnInfo *info = (ControllerConnInfo*)ctx; fprintf(stderr, "Closing dpnode connection\n"); if (ctrlstate.dpnodes.working.count(bev)) { // If we lose a dpnode from an active computation, the // computation is useless. SubproblemProgress *spp = ctrlstate.dpnodes.working[bev]; ctrlstate.dpnodes.working.erase(bev); spp->dpnodes.erase(bev); spp->reset(); } else { ctrlstate.dpnodes.idle.erase(bev); } delete info; bufferevent_free(bev); schedule(); } } static void controller_worker_event_cb(struct bufferevent *bev, short events, void *ctx) { if (events & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { ControllerConnInfo *info = (ControllerConnInfo*)ctx; fprintf(stderr, "Closing worker connection\n"); if (ctrlstate.workers.working.count(bev)) { SubproblemProgress *spp = ctrlstate.workers.working[bev]; ctrlstate.workers.working.erase(bev); spp->workers.erase(bev); } else { ctrlstate.workers.idle.erase(bev); } delete info; bufferevent_free(bev); schedule(); } } static void controller_event_cb(struct bufferevent *bev, short events, void *ctx) { if (events & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { fprintf(stderr, "Closing connection\n"); ControllerConnInfo *info = (ControllerConnInfo*)ctx; delete info; bufferevent_free(bev); } } // We're just going to read a single byte that will tell us whether the // peer is a DPnode or a Worker static void controller_master_reader(struct bufferevent *bev, void *ctx) { struct evbuffer *input = bufferevent_get_input(bev); size_t len = evbuffer_get_length(input); if (len < 1) return; char indata[1]; bufferevent_read(bev, indata, 1); switch(indata[0]) { case 'D': printf("DPnode\n"); /* Add this DPnode to the list of available ones */ ctrlstate.dpnodes.idle.insert(bev); bufferevent_setcb(bev, controller_dpnode_reader, NULL, controller_dpnode_event_cb, ctx); controller_dpnode_reader(bev, ctx); schedule(); return; case 'W': printf("Worker\n"); ctrlstate.workers.idle.insert(bev); bufferevent_setcb(bev, controller_worker_reader, NULL, controller_worker_event_cb, ctx); controller_worker_reader(bev, ctx); schedule(); return; default: fprintf(stderr, "Unknown command in controller_master_reader: " "%c\n", indata[0]); ControllerConnInfo *info = (ControllerConnInfo*)ctx; delete info; bufferevent_free(bev); return; } } static void controller_accept_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx) { // Create the state of the new connection ControllerConnInfo *info = new ControllerConnInfo(); // Create a bufferevent for the new connection struct event_base *base = evconnlistener_get_base(listener); struct bufferevent *bev = bufferevent_socket_new( base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, controller_master_reader, NULL, controller_event_cb, info); bufferevent_enable(bev, EV_READ|EV_WRITE); } // Create a new controller socket. bindport is the port to bind to (in // host byte order), or 0 if any port will do. ip and boundport are set // to the IP and port of the socket, in network byte order. void *controller_create(struct event_base *evbase, unsigned short bindport, unsigned int *ip, unsigned short *boundport) { return listener_create(evbase, bindport, controller_accept_cb, NULL, ip, boundport); } static unsigned short curproblemid = 0; // Take base and target mod f.factor, then decompose that into small // subproblems given our knowledge of the factors of phi(f.factor) static vector decomp(const ZZ_p &base, const ZZ_p &target, const FactorDecomp &f) { vector ret; // Compute phi(factor) const int fveclen = f.fvec.length(); ZZ phi = to_ZZ(2); for (int i = 0; i < fveclen; ++i) { phi *= f.fvec[i]; } ZZ_p::init(f.factor); for (int i = 0; i < fveclen; ++i) { const ZZ& order = f.fvec[i]; ZZ quotient = phi / order; ZZ_p subgroup_base = to_ZZ_p(rep(base)); subgroup_base = power(subgroup_base, quotient); ZZ_p subgroup_target = to_ZZ_p(rep(target)); subgroup_target = power(subgroup_target, quotient); if (subgroup_base == 1) { // The original base wasn't a generator of the whole group if (subgroup_target == 1) { // But the target is in the subgroup. Lucky us. continue; } else { ret.clear(); return ret; } } // By default, 1 in 1000 points are distinguihed points. The // number in the next line is 2^32/1000 unsigned int dpfreq = 4294967; if (order < 1000) { // Just make every point a DP dpfreq = 4294967295U; } else if (NumBits(order) < 27) { // The frequency of DPs should be 10/sqrt(order) to avoid // a DP-free cycle, so dpfreq = (10*2^32)/sqrt(order) ZZ f = (to_ZZ(10) << 32) / SqrRoot(order); dpfreq = trunc_long(f, 31); } ret.push_back(SubproblemProgress(curproblemid++, rep(subgroup_base), rep(subgroup_target), f.factor, order, dpfreq)); } return ret; } static int generate_problem(struct event_base *evbase) { // If there's already a problem on the go, don't generate another one if (ctrlstate.working == 1) { return -1; } ctrlstate.working = 1; // Generate a DLP mod rho (in the large odd-order subgroup) ZZ_p::init(ctrlstate.rho); ZZ_p base = power(random_ZZ_p(), 2); ZZ_p target = power(random_ZZ_p(), 2); // Decompose it mod p and mod q ctrlstate.subproblems_p = decomp(base, target, ctrlstate.p); ctrlstate.subproblems_q = decomp(base, target, ctrlstate.q); schedule(); return 0; } int main(int argc, char **argv) { // Initialize the prng with some randomness from the kernel unsigned char randbuf[1024]; ifstream urand("/dev/urandom"); urand.read((char *)randbuf, sizeof(randbuf)); urand.close(); ZZ randzz = ZZFromBytes(randbuf, sizeof(randbuf)); SetSeed(randzz); // Read the modulus and the factorization of its totient from cin cin >> ctrlstate.rho >> ctrlstate.p.factor >> ctrlstate.p.fvec >> ctrlstate.q.factor >> ctrlstate.q.fvec; unsigned short bindport = 0; if (argc > 1) { bindport = strtoul(argv[1], NULL, 10); } struct event_base *evbase = event_base_new(); unsigned int myip; unsigned short myport; controller_create(evbase, bindport, &myip, &myport); struct in_addr myaddr = { myip }; printf("Bound to %s:%d\n", inet_ntoa(myaddr), ntohs(myport)); // Kick off the first problem to solve generate_problem(evbase); event_base_dispatch(evbase); return 0; }