def test_6_recover_y(self): """ There are two y points for every x """ for _ in range(0, 10): p = self._point_r() q = Point.from_x(p.x) self.assertEqual(p.x, q.x) self.assertTrue(p.y in [q.y, -q.y]) # These confirm compatibility across implementations known_test_cases = [ (20616554786359396897066290204264220576319536076538991133935783866206841138898, 10592275084648178561464128859907688344447649297734555224341876545305639835999), (11610117029953798428826613242669939481045605849364609771767823351326159443609, 3722409228507723418678713896319610332389736117851027921973860155000856891140), (21680045038775759642189425577922609025982451102460978847266452551495203884482, 6168854640927408084732268325506202000962285527703379133980054444068219727690), (18879782252170350866370777185563748782908354718484814019474117245310535071541, 2946855428411022359321514310392164228862398839132752152798293872913224129374) ] for x, y in known_test_cases: x, y = FQ(x), FQ(y) q = Point.from_y(y) self.assertEqual(q.x, x)
def test_random(self): # Randomized tests for _ in range(0, 10): alpha = [FQ.random() for _ in range(0, 4)] points = [(FQ(i), shamirs_poly(FQ(i), alpha)) for i in range(0, len(alpha))] assert alpha[0] == lagrange(points, 0) assert alpha[0] != lagrange(points[1:], 0) assert alpha[0] != lagrange(points[2:], 0)
def __init__(self, var, coeff=None): assert isinstance(var, Variable) self.var = var if coeff is None: coeff = FQ(1) elif isinstance(coeff, int_types): coeff = FQ(coeff) if not isinstance(coeff, FQ): raise TypeError( 'Coefficient expected to be field element, but got %r' % (type(coeff), )) self.coeff = coeff
def message(self): msg_parts = [ FQ(int(self.realmID), 1 << 32), FQ(int(self.accountID), 1 << 20), FQ(int(self.tokenID), 1 << 8), FQ(int(self.amountRequested), 1 << 96), FQ(int(self.walletAccountID), 1 << 20), FQ(int(self.feeTokenID), 1 << 8), FQ(int(self.fee), 1 << 96), FQ(int(self.walletSplitPercentage), 1 << 7), FQ(int(self.nonce), 1 << 32), FQ(int(0), 1 << 1) ] return PureEdDSA.to_bits(*msg_parts)
def test_random_small(self): q = 100003 for _ in range(0, 10): alpha = [FQ.random(q) for _ in range(0, 4)] points = [(FQ(i, q), shamirs_poly(FQ(i, q), alpha)) for i in range(0, len(alpha))] assert alpha[0] == lagrange(points, 0) assert alpha[0] != lagrange(points[1:], 0) assert alpha[0] != lagrange(points[2:], 0) # XXX: scipy's lagrange has floating point precision for large numbers points_x, points_y = unzip(points) interpolation = scipy_lagrange([_.n for _ in points_x], [_.n for _ in points_y]) assert int(interpolation.c[-1]) == alpha[0]
def message(self): msg_parts = [ FQ(int(self.realmID), 1 << 32), FQ(int(self.accountID), 1 << 20), FQ(int(self.orderTokenID), 1 << 8), FQ(int(self.orderID), 1 << 32), FQ(int(self.dualAuthorAccountID), 1 << 20), FQ(int(self.feeTokenID), 1 << 8), FQ(int(self.fee), 1 << 96), FQ(int(self.walletSplitPercentage), 1 << 7), FQ(int(self.nonce), 1 << 32), FQ(int(0), 1 << 2) ] return PureEdDSA.to_bits(*msg_parts)
def test_fromdocs(self): p = 100003 k = 4 a = [FQ(6257, p), FQ(85026, p), FQ(44499, p), FQ(14701, p)] F = lambda i, x: a[i] * (x**i) X = lambda x: a[0] + F(1, x) + F(2, x) + F(3, x) # Create the shares Sx = [_ for _ in range(1, 5)] Sy = [X(_) for _ in Sx] for x, y in zip(Sx, Sy): z = shamirs_poly(FQ(x, p), a) assert z == y # Then recover secret result = int(scipy_lagrange(Sx, [_.n for _ in Sy]).c[-1]) % p assert a[0] == result
def test_mult_all_known(self): rp = self._point_a() all_points = [rp, rp.as_proj(), rp.as_etec(), rp.as_mont()] expected = Point( FQ(6317123931401941284657971611369077243307682877199795030160588338302336995127 ), FQ(17705894757276775630165779951991641206660307982595100429224895554788146104270 )) for p in all_points: q = p.mult( 6890855772600357754907169075114257697580319025794532037257385534741338397365 ) r = q.as_point() self.assertEqual(r.x, expected.x) self.assertEqual(r.y, expected.y)
def orderFromJSON(jOrder, state): realmID = int(jOrder["realmID"]) orderID = int(jOrder["orderID"]) accountID = int(jOrder["accountID"]) walletAccountID = int(jOrder["walletAccountID"]) dualAuthPublicKeyX = int(jOrder["dualAuthPublicKeyX"]) dualAuthPublicKeyY = int(jOrder["dualAuthPublicKeyY"]) dualAuthSecretKey = int(jOrder["dualAuthSecretKey"]) tokenS = int(jOrder["tokenIdS"]) tokenB = int(jOrder["tokenIdB"]) tokenF = int(jOrder["tokenIdF"]) amountS = int(jOrder["amountS"]) amountB = int(jOrder["amountB"]) amountF = int(jOrder["amountF"]) allOrNone = int(jOrder["allOrNone"]) validSince = int(jOrder["validSince"]) validUntil = int(jOrder["validUntil"]) walletSplitPercentage = int(jOrder["walletSplitPercentage"]) waiveFeePercentage = int(jOrder["waiveFeePercentage"]) account = state.getAccount(accountID) walletAccount = state.getAccount(walletAccountID) order = Order(Point(account.publicKeyX, account.publicKeyY), Point(walletAccount.publicKeyX, walletAccount.publicKeyY), Point(dualAuthPublicKeyX, dualAuthPublicKeyY), dualAuthSecretKey, realmID, orderID, accountID, walletAccountID, tokenS, tokenB, tokenF, amountS, amountB, amountF, allOrNone, validSince, validUntil, walletSplitPercentage, waiveFeePercentage) order.sign(FQ(int(account.secretKey))) return order
def __init__(self): """ The state holds all variables and linear combinations used by the program - Variables hold a single field element - Linear combinations hold a combination of variables Linear combinations can be used to combine multiple operations on variables into a single statement, whenever a variable is multiplied by a constant or two variables (optionally multipled by constants) are added together etc. This forms the basis of many optimisations, think of linear combinations as temporary variables, where intermediate results which don't require a constraint of their own can be calculated, stored and used in the same way as normal variables. Each variable or linear combination is addressed by an index, an index can only be a linear combination *or* a variable, but none will have the same index as another. When writing a function it can accept zero or more inputs, and emit zero or more outputs. For example, an `assert` statement takes one or more inputs and emits none, an `add` statement takes two or more inputs and emits one output. The inputs can be any combination of variables or linear combinations, as can the outputs. If a linear combination or variable is unused by any constraints then it has no purpose. """ self._vars = OrderedDict() self._lcs = dict() self._values = dict() self.var_new('ONE', value=FQ(1))
def offchainWithdraw(self, realmID, accountID, tokenID, amountRequested, operatorAccountID, walletAccountID, feeTokenID, fee, walletSplitPercentage): feeToWallet = int(fee) * walletSplitPercentage // 100 feeToOperator = int(fee) - feeToWallet # Update account rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(accountID)) nonce = accountBefore.nonce proof = self._accountsTree.createProof(accountID) balanceUpdateF_A = self.getAccount(accountID).updateBalance( feeTokenID, -fee) balance = int(self.getAccount(accountID).getBalance(tokenID)) amountWithdrawn = int(amountRequested) if ( int(amountRequested) < balance) else balance balanceUpdateW_A = self.getAccount(accountID).updateBalance( tokenID, -amountWithdrawn) self.getAccount(accountID).nonce += 1 self.updateAccountTree(accountID) accountAfter = copyAccountInfo(self.getAccount(accountID)) rootAfter = self._accountsTree._root accountUpdate_A = AccountUpdateData(accountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Update wallet rootBefore = self._accountsTree._root accountBefore = copyAccountInfo(self.getAccount(walletAccountID)) proof = self._accountsTree.createProof(walletAccountID) balanceUpdateF_W = self.getAccount(walletAccountID).updateBalance( feeTokenID, feeToWallet) self.updateAccountTree(walletAccountID) accountAfter = copyAccountInfo(self.getAccount(walletAccountID)) rootAfter = self._accountsTree._root accountUpdate_W = AccountUpdateData(walletAccountID, proof, rootBefore, rootAfter, accountBefore, accountAfter) ### # Operator payment balanceUpdateF_O = self.getAccount(operatorAccountID).updateBalance( feeTokenID, feeToOperator) account = self.getAccount(accountID) withdrawal = OffchainWithdrawal( realmID, accountID, tokenID, amountRequested, amountWithdrawn, walletAccountID, feeTokenID, fee, walletSplitPercentage, balanceUpdateF_A, balanceUpdateW_A, accountUpdate_A, balanceUpdateF_W, accountUpdate_W, balanceUpdateF_O, nonce) withdrawal.sign(FQ(int(account.secretKey))) return withdrawal
def message(self): msg_parts = [ FQ(int(self.orderA.hash), 1 << 254), FQ(int(self.orderB.hash), 1 << 254), FQ(int(self.orderA.waiveFeePercentage), 1 << 7), FQ(int(self.orderB.waiveFeePercentage), 1 << 7), FQ(int(self.minerAccountID), 1 << 20), FQ(int(self.tokenID), 1 << 8), FQ(int(self.fee), 1 << 96), FQ(int(self.feeRecipientAccountID), 1 << 20), FQ(int(self.nonce), 1 << 32) ] return PureEdDSA.to_bits(*msg_parts)
def test_mont_double(self): """ Verified in Sage, using `ejubjub.py` Ensure that addition laws remain the same between Montgomery and Edwards coordinates """ q = Point.from_hash(b'x') mq = MontPoint(FQ(4828722366376575650251607168518886976429844446767098803596167689250506416759), FQ(12919092401030192644826086113396919334232812611316996694878363256143428656958)) self.assertEqual(q.as_mont(), mq) q2 = MontPoint(FQ(760569539648116659146730905587051427168718890872716379895718021693339839266), FQ(19523163946365579499783218718995636854804792079073783994015125253921919723342)) self.assertEqual(q.double().as_mont(), q2) for _ in range(0, 10): p = Point.from_hash(urandom(32)) self.assertEqual(p.as_mont().double().as_edwards_yz().as_point().as_edwards_yz(), p.double().as_edwards_yz())
def set_value(self, idx, value): if not isinstance(value, FQ): if not isinstance(value, int_types): raise ProgramError("Value (%r=%r) is of wrong type: %r" % (idx, value, type(value))) value = FQ(value) if idx not in self.inputs and idx not in self.secrets: raise ProgramError("Cannot set a value (%r=%r) that's neither an input nor a secret" % (idx, value)) self.state.var_value_set(idx, value)
def loopring_sign(input_message, private_key): print(f"loopring sign message {input_message}") hasher = hashlib.sha256() hasher.update(input_message.encode('utf-8')) msgHash = int(hasher.hexdigest(), 16) % SNARK_SCALAR_FIELD signed = PoseidonEdDSA.sign(msgHash, FQ(int(private_key))) signature = ','.join( str(_) for _ in [signed.sig.R.x, signed.sig.R.y, signed.sig.s]) return signature
def ringFromJSON(jRing, state): orderA = orderFromJSON(jRing["orderA"], state) orderB = orderFromJSON(jRing["orderB"], state) minerAccountID = int(jRing["minerAccountID"]) feeRecipientAccountID = int(jRing["feeRecipientAccountID"]) tokenID = int(jRing["tokenID"]) fee = int(jRing["fee"]) minerAccount = state.getAccount(minerAccountID) ring = Ring(orderA, orderB, minerAccountID, feeRecipientAccountID, tokenID, fee, minerAccount.nonce) ring.sign(FQ(int(minerAccount.secretKey)), FQ(int(orderA.dualAuthSecretKey)), FQ(int(orderB.dualAuthSecretKey))) return ring
def test_ifft(): p = 337 domain = [FQ(i) for i in [1, 85, 148, 111, 336, 252, 189, 226]] poly = [3, 1, 4, 1, 5, 9, 2, 6] result = [] p_x = fft(p, domain, poly) result = ifft(p, domain, p_x) assert result == poly
def test_fft(): p = 337 _domain = [1, 85, 148, 111, 336, 252, 189, 226] domain = [FQ(i) for i in _domain] poly = [3, 1, 4, 1, 5, 9, 2, 6] result = [] p_x = fft(p, domain, poly) for x in _domain: result.append(polynomial_eval_prime(poly, x, p, 1, 0)) assert p_x == result
def test_degree_reduction(): a = [1, 0, 0, 0, 0, 0, 0, 1] b = [0, 1, 0, 0, 0, 0, 0, 0] domain = [FQ(i) for i in [1, 85, 148, 111, 336, 252, 189, 226]] p = 337 a_fft = fft(p, domain, a) b_fft = fft(p, domain, b) c = [a * b for a, b in zip(a_fft, b_fft)] res = ifft(p, domain, c) assert res == [1, 1, 0, 0, 0, 0, 0, 0]
def test_mul_poly(): # (x + 1) **2 a = [1, 1, 0, 0, 0, 0, 0, 0] b = [1, 1, 0, 0, 0, 0, 0, 0] domain = [FQ(i) for i in [1, 85, 148, 111, 336, 252, 189, 226]] p = 337 a_fft = fft(p, domain, a) b_fft = fft(p, domain, b) c = [a * b for a, b in zip(a_fft, b_fft)] res = ifft(p, domain, c) assert res == [1, 2, 1, 0, 0, 0, 0, 0]
def test_12_nonsquares(self): """ If (A+2)*(A-2) is a square (e.g. if `ad` is a square) then there are two more points with v=0. These points have order 2 """ try: x = (MONT_A + 2) * (MONT_A - 2) FQ(int(x)).sqrt() self.assertTrue(False) except SquareRootError: pass """ If (A-2)/B is a square (e.g. if `d` is a square) then there are two points with `u=-1`. These points have order 4. These points correspond to two points of order 4 at infinity of the desingularization of E_{E,a,d} """ try: x = int((MONT_A - 2) / MONT_B) FQ(x).sqrt() self.assertTrue(False) except SquareRootError: pass
def _create_order(self, base_token, quote_token, buy, price, volume): if buy: tokenS = self.market_info_map[quote_token] tokenB = self.market_info_map[base_token] amountS = str(int(10 ** tokenS['decimals'] * price * volume)) amountB = str(int(10 ** tokenB['decimals'] * volume)) else: tokenS = self.market_info_map[base_token] tokenB = self.market_info_map[quote_token] amountS = str(int(10 ** tokenS['decimals'] * volume)) amountB = str(int(10 ** tokenB['decimals'] * price * volume)) tokenSId = tokenS['tokenId'] tokenBId = tokenB['tokenId'] orderId = self.orderId[tokenSId] assert orderId < self.MAX_ORDER_ID self.orderId[tokenSId] += 1 # make valid time ahead 1 hour validSince = int(time()) - self.time_offset - 3600 # order base order = { "exchangeId" : self.exchangeId, "orderId" : orderId, "accountId" : self.accountId, "tokenSId" : tokenSId, "tokenBId" : tokenBId, "amountS" : amountS, "amountB" : amountB, "allOrNone" : "false", "validSince" : validSince, "validUntil" : validSince + 60 * 24 * 60 * 60, "maxFeeBips" : 50, "label" : 211, "buy" : "true" if buy else "false", "clientOrderId" : "SampleOrder" + str(int(time()*1000)) } order_message = self._serialize_order(order) msgHash = poseidon(order_message, self.order_sign_param) signedMessage = PoseidonEdDSA.sign(msgHash, FQ(int(self.private_key))) # update signaure order.update({ "hash" : str(msgHash), "signatureRx" : str(signedMessage.sig.R.x), "signatureRy" : str(signedMessage.sig.R.y), "signatureS" : str(signedMessage.sig.s) }) return order
def test_11_exceptional_points(self): """ The point (0,0) on E_{M,A,B} corresponds to the affine point of order 2 on E_{E,a,d}, namely (0, -1). This point and (0,1) are the only exception points of the inverse map of (x,y) -> ((1+y)/(1-y),(1+y)/(1-y)x), where (0,1) is mapped to the point at infinity. """ p = MontPoint(FQ(0),FQ(0)) q = p.as_point() self.assertEqual(q.x, FQ(0)) self.assertEqual(q.y, FQ(-1)) r = MontPoint(FQ(0), FQ(1)) s = r.as_point() self.assertEqual(s.x, FQ(0)) self.assertEqual(s.y, FQ(-1))
def var_value_set(self, idx, value): if isinstance(idx, Variable): idx = idx.idx if not isinstance(value, FQ): if isinstance(value, int_types): value = FQ(value) else: raise TypeError( "Value (%r=%r) of type %r is required to be a field element" % (idx, value, type(value))) if idx not in self._vars: raise RuntimeError('Unknown variable %r' % (idx, )) self._values[idx] = value
def message(self, nonce): """ Return an array of bits representing the on-chain transaction details that will be included in the signature. This is 104 bits long: +-----------+---------+---------+---------+ | from_idx | to_idx | amount | nonce | +-----------+---------+---------+---------+ | 24 bits | 24 bits | 32 bits | 24 bits | +-----------+---------+---------+---------+ Each integer is encoded in little-endian form, with the least significant bit first. """ assert self.from_idx < (1<<TREE_SIZE) assert self.to_idx < (1<<TREE_SIZE) assert self.amount < (1<<AMOUNT_BITS) assert nonce < (1<<TREE_SIZE) msg_parts = [FQ(self.from_idx, 1<<TREE_SIZE), FQ(self.to_idx, 1<<TREE_SIZE), FQ(self.amount, 1<<AMOUNT_BITS), FQ(nonce, 1<<TREE_SIZE)] return eddsa_tobits(*msg_parts)
def test_poly_add(): a = [1, 1, 0, 0, 0, 0, 0, 0] b = [1, 1, 0, 0, 0, 0, 0, 0] domain = [FQ(i) for i in [1, 85, 148, 111, 336, 252, 189, 226]] p = 337 a_fft = fft(p, domain, a) b_fft = fft(p, domain, b) c = [a * b for a, b in zip(a_fft, b_fft)] c = [a + b for a, b in zip(c, a_fft)] res = ifft(p, domain, c) assert res == [2, 3, 1, 0, 0, 0, 0, 0]
def test_mul(): a = [3, 5, 2, 1, 0, 0, 0, 0] b = [5, 9, 8, 1, 0, 0, 0, 0] domain = [FQ(i) for i in [1, 85, 148, 111, 336, 252, 189, 226]] p = 337 a_fft = fft(p, domain, a) b_fft = fft(p, domain, b) a_b_fft = [a * b for a, b in zip(a_fft, b_fft)] res = ifft(p, domain, a_b_fft) for i, x in enumerate(res): res[i] = x % 10 if int(x / 10) != 0: res[i + 1] += int(x / 10) assert res == [5, 3, 4, 4, 7, 3, 2, 0]
def test_constant_add(): a = [1, 1, 0, 0, 0, 0, 0, 0] b = [1, 1, 0, 0, 0, 0, 0, 0] domain = [FQ(i) for i in [1, 85, 148, 111, 336, 252, 189, 226]] p = 337 constant = 330 a_fft = fft(p, domain, a) b_fft = fft(p, domain, b) c = [a * b for a, b in zip(a_fft, b_fft)] c = [a + constant for a in c] res = ifft(p, domain, c) assert res == [331, 2, 1, 0, 0, 0, 0, 0]
def sign(self, request): """ Generate LOOPRING signature. """ security = request.data.pop("security", Security.NONE) if security == Security.NONE: if request.method == "POST": request.data = request.params request.params = {} return request if request.params: path = request.path + "?" + urllib.parse.urlencode(request.params) else: request.params = dict() path = request.path # request headers headers = { "Content-Type" : "application/x-www-form-urlencoded", "Accept" : "application/json", "X-API-KEY" : self.api_key, } if request.headers != None: headers.update(request.headers) if security == Security.SIGNED: ordered_data = self._encode_request(request) hasher = hashlib.sha256() hasher.update(ordered_data.encode('utf-8')) msgHash = int(hasher.hexdigest(), 16) % SNARK_SCALAR_FIELD signed = PoseidonEdDSA.sign(msgHash, FQ(int(self.private_key))) signature = ','.join(str(_) for _ in [signed.sig.R.x, signed.sig.R.y, signed.sig.s]) headers.update({"X-API-SIG": signature}) request.path = path if request.method != "GET": request.data = json.dumps(request.data) if len(request.data) != 0 else request.params request.params = {} else: request.data = {} request.headers = headers # print(f"finish sign {request}") return request
def from_statement(cls, stmt, line): if not isinstance(stmt, TableStatement): raise InvalidCommandError('Must be TableStatement', stmt, line) if len(stmt.out_vars) != 1: raise InvalidCommandError('Requires only one output variable', stmt, line) # Require 2^n LUT entries, where each input is binary lut_n_expected = (2**len(stmt.in_vars)) if len(stmt.lut) != lut_n_expected: raise InvalidCommandError( "Lookup table count mismatch, expected %d, got %d" % (lut_n_expected, len(lut)), stmt, line) sub_cls = cls.cls_for_n_inputs(len(stmt.in_vars), stmt, line) lut = [FQ(int(_)) for _ in stmt.lut] return sub_cls(lut, stmt.in_vars, stmt.out_vars)