def verify(self, commitment, index_range): """For an object created without a private key, check that the opened commitment verifies for at least one NUMS point as defined by the range in index_range. """ if not all([self.P, self.P2, self.s, self.e]): raise PoDLEError("Verify called without sufficient data") if not self.get_commitment() == commitment: return False for J in [getNUMS(i) for i in index_range]: sig_priv = podle_PrivateKey(self.s) sG = sig_priv.pub sJ = multiply(self.s, J) e_int = int.from_bytes(self.e, byteorder="big") minus_e = (-e_int % N).to_bytes(32, byteorder="big") minus_e_P = multiply(minus_e, self.P) minus_e_P2 = multiply(minus_e, self.P2) KGser = add_pubkeys([sG, minus_e_P]) KJser = add_pubkeys([sJ, minus_e_P2]) #check 2: e =?= H(K_G || K_J || P || P2) e_check = hashlib.sha256(KGser + KJser + self.P + self.P2).digest() if e_check == self.e: return True #commitment fails for any NUMS in the provided range return False
def verify(self, commitment, index_range): """For an object created without a private key, check that the opened commitment verifies for at least one NUMS point as defined by the range in index_range """ if not all([self.P, self.P2, self.s, self.e]): raise PoDLEError("Verify called without sufficient data") if not self.get_commitment() == commitment: return False for J in [getNUMS(i) for i in index_range]: sig_priv = podle_PrivateKey(self.s) sG = sig_priv.public_key sJ = multiply(self.s, J.format(), False) e_int = decode(self.e, 256) minus_e = encode(-e_int % N, 256, minlen=32) minus_e_P = multiply(minus_e, self.P.format(), False) minus_e_P2 = multiply(minus_e, self.P2.format(), False) KGser = add_pubkeys([sG.format(), minus_e_P], False) KJser = add_pubkeys([sJ, minus_e_P2], False) #check 2: e =?= H(K_G || K_J || P || P2) e_check = hashlib.sha256(KGser + KJser + self.P.format() + self.P2.format()).digest() if e_check == self.e: return True #commitment fails for any NUMS in the provided range return False
def ecmult(scalar, point, usehex, rawpub=True, return_serialized=True): if isinstance(scalar, (int, long)): scalar = encode(scalar, 256, minlen=32) if decode(scalar, 256) == 0: return None return multiply(scalar, point, usehex, rawpub=rawpub, return_serialized=return_serialized)
def ecies_encrypt(message, pubkey): """ Take a privkey in raw byte serialization, and a pubkey serialized in compressed, binary format (33 bytes), and output the shared secret as a 32 byte hash digest output. The exact calculation is: shared_secret = SHA256(privkey * pubkey) .. where * is elliptic curve scalar multiplication. See https://github.com/bitcoin/bitcoin/blob/master/src/secp256k1/src/modules/ecdh/main_impl.h for implementation details. """ # create an ephemeral pubkey for this encryption: while True: r = os.urandom(32) # use compressed serialization of the pubkey R: try: R = btc.privkey_to_pubkey(r + b"\x01") break except: # accounts for improbable overflow: continue # note that this is *not* ECDH as in the secp256k1_ecdh module, # since it uses sha512: ecdh_key = btc.multiply(r, pubkey) key = hashlib.sha512(ecdh_key).digest() iv, key_e, key_m = key[0:16], key[16:32], key[32:] ciphertext = aes_encrypt(key_e, message, iv=iv) encrypted = ECIES_MAGIC_BYTES + R + ciphertext mac = hmac.new(key_m, encrypted, hashlib.sha256).digest() return base64.b64encode(encrypted + mac)
def getP2(priv, nums_pt): """Given a secp256k1.PrivateKey priv and a secp256k1.PublicKey nums_pt, an alternate generator point (note: it's in no sense a pubkey, its privkey is unknowable - that's just the most easy way to manipulate it in the library), calculate priv*nums_pt """ priv_raw = priv.secret_bytes return multiply(priv_raw, nums_pt, return_serialized=False)
def donation_address(reusable_donation_pubkey=None): #pragma: no cover #Donation code currently disabled, so not tested. if not reusable_donation_pubkey: reusable_donation_pubkey = ('02be838257fbfddabaea03afbb9f16e852' '9dfe2de921260a5c46036d97b5eacf2a') sign_k = binascii.hexlify(os.urandom(32)).decode('ascii') c = btc.sha256(btc.multiply(sign_k, reusable_donation_pubkey, True)) sender_pubkey = btc.add_pubkeys( [reusable_donation_pubkey, btc.privtopub(c + '01', True)], True) sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte()) log.debug('sending coins to ' + sender_address) return sender_address, sign_k
def test_ecdh(): """Using private key test vectors from Bitcoin Core. 1. Import a set of private keys from the json file. 2. Calculate the corresponding public keys. 3. Do ECDH on the cartesian product (x, Y), with x private and Y public keys, for all combinations. 4. Compare the result from CoinCurve with the manual multiplication xY following by hash (sha256). Note that sha256(xY) is the default hashing function used for ECDH in libsecp256k1. Since there are about 20 private keys in the json file, this creates around 400 test cases (note xX is still valid). """ with open(os.path.join(testdir, "base58_keys_valid.json"), "r") as f: json_data = f.read() valid_keys_list = json.loads(json_data) extracted_privkeys = [] for a in valid_keys_list: key, hex_key, prop_dict = a if prop_dict["isPrivkey"]: c, k = btc.read_privkey(hextobin(hex_key)) extracted_privkeys.append(k) extracted_pubkeys = [btc.privkey_to_pubkey(x) for x in extracted_privkeys] for p in extracted_privkeys: for P in extracted_pubkeys: c, k = btc.read_privkey(p) shared_secret = btc.ecdh(k, P) assert len(shared_secret) == 32 # try recreating the shared secret manually: pre_secret = btc.multiply(p, P) derived_secret = hashlib.sha256(pre_secret).digest() assert derived_secret == shared_secret # test some important failure cases; null key, overflow case privkeys_invalid = [ b'\x00' * 32, hextobin( 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141') ] for p in privkeys_invalid: with pytest.raises(Exception) as e_info: shared_secret = btc.ecdh(p, extracted_pubkeys[0]) pubkeys_invalid = [b'0xff' + extracted_pubkeys[0][1:], b'0x00' * 12] for p in extracted_privkeys: with pytest.raises(Exception) as e_info: shared_secret = btc.ecdh(p, pubkeys_invalid[0]) with pytest.raises(Exception) as e_info: shared_secret = btc.ecdh(p, pubkeys_invalid[1])
def encrypt_message(message, pubkey_hex): alice_r = binascii.hexlify(os.urandom(32)) alice_R = btc.privkey_to_pubkey(alice_r + "01") #use compression flag for pubkey ecdh_key = btc.multiply(alice_r, pubkey_hex, True, rawpub=True, return_serialized=True) key = hashlib.sha512(binascii.unhexlify(ecdh_key)).digest() iv, key_e, key_m = key[0:16], key[16:32], key[32:] ciphertext = encryptData(key_e, message, iv=iv) encrypted = b'BIE1' + binascii.unhexlify(alice_R) + ciphertext mac = hmac.new(key_m, encrypted, hashlib.sha256).digest() return base64.b64encode(encrypted + mac)
def get_proof_recursive(self, a, b, P, g, h, n): """The prover starts with the full a*, b*, then recursively constructs the case n=1 where the proof is output in the form a', b', these are scalars, and c' = a' * b'. This will be checked by the verifier against the modified P', which the verifier can calculate independently, and it should satisfy P' = a'*G_1 + b'*H_1 + c'*U. So the prover must provide (L[], R[], a', b') as output to the verifier. The verifier checks against the pre-known P and c. """ if n == 1: #return the tuple: a', b', L[], R[] #note total size is 2 * scalar_size + log(n) * 2 * point_size return (a[0], b[0], self.L, self.R) #Split the existing vectors into halves aL, aR = halves(a) bL, bR = halves(b) gL, gR = halves(g) hL, hR = halves(h) self.L.append(IPC(aL, bR, g=gR, h=hL, u=self.U).get_commitment()) self.R.append(IPC(aR, bL, g=gL, h=hR, u=self.U).get_commitment()) x, xb, x_sq, x_sqb, xinv, xinvb, x_sq_inv, x_sq_invb = self.fiat_shamir( self.L[-1], self.R[-1], P) #Construct change of coordinates for base points, and for vector terms gprime = [] hprime = [] aprime = [] bprime = [] for i in range(n / 2): gprime.append( add_pubkeys([ multiply(xinvb, g[i], False), multiply(xb, g[i + n / 2], False) ], False)) hprime.append( add_pubkeys([ multiply(xb, h[i], False), multiply(xinvb, h[i + n / 2], False) ], False)) aprime.append( encode( (x * decode(a[i], 256) + xinv * decode(a[i + n / 2], 256)) % N, 256, 32)) bprime.append( encode( (xinv * decode(b[i], 256) + x * decode(b[i + n / 2], 256)) % N, 256, 32)) Pprime = add_pubkeys([ P, multiply(x_sqb, self.L[-1], False), multiply(x_sq_invb, self.R[-1], False) ], False) return self.get_proof_recursive(aprime, bprime, Pprime, gprime, hprime, n / 2)
def generate_podle(self, index=0, k=None): """Given a raw private key, in hex format, construct a commitment sha256(P2), which is the hash of the value x*J, where x is the private key as a raw scalar, and J is a NUMS alternative basepoint on the Elliptic Curve; we use J(i) where i is an index, so as to be able to create multiple commitments against the same privkey. The procedure for generating the J(i) value is shown in getNUMS(). Also construct a signature (s,e) of Schnorr type, which will serve as a zero knowledge proof that the private key of P2 is the same as the private key of P (=x*G). Signature is constructed as: s = k + x*e where k is a standard 32 byte nonce and: e = sha256(k*G || k*J || P || P2) Possibly Joinmarket specific comment: Users *should* generate with lower indices first, since verifiers will give preference to lower indices (each verifier may have their own policy about how high an index to allow, which really means how many reuses of utxos to allow in Joinmarket). Returns a commitment of form H(P2) which, note, will depend on the index choice. Repeated calls will reset the commitment and the associated signature data that can be used to open the commitment. """ #TODO nonce could be rfc6979? if not k: k = os.urandom(32) J = getNUMS(index) KG = podle_PrivateKey(k).public_key KJ = multiply(k, J.format(), False, return_serialized=False) self.P2 = getP2(self.priv, J) self.get_commitment() self.e = hashlib.sha256(b''.join( [x.format() for x in [KG, KJ, self.P, self.P2]])).digest() k_int = decode(k, 256) priv_int = decode(self.priv.secret, 256) e_int = decode(self.e, 256) sig_int = (k_int + priv_int * e_int) % N self.s = encode(sig_int, 256, minlen=32) return self.reveal()
def ecies_decrypt(privkey, encrypted): if len(privkey) == 33 and privkey[-1] == 1: privkey = privkey[:32] encrypted = base64.b64decode(encrypted) if len(encrypted) < 85: raise Exception('invalid ciphertext: length') magic = encrypted[:4] if magic != ECIES_MAGIC_BYTES: raise ECIESDecryptionError() ephemeral_pubkey = encrypted[4:37] testR = CPubKey(ephemeral_pubkey) if not testR.is_fullyvalid(): raise ECIESDecryptionError() ciphertext = encrypted[37:-32] mac = encrypted[-32:] ecdh_key = btc.multiply(privkey, ephemeral_pubkey) key = hashlib.sha512(ecdh_key).digest() iv, key_e, key_m = key[0:16], key[16:32], key[32:] if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest(): raise ECIESDecryptionError() return aes_decrypt(key_e, ciphertext, iv=iv)
def verify_proof_recursive(self, P, L, R, a, b, g, h, n): """The verifier starts with the lists of L and R values, then recursively constructs the case n=1 where the the verifier calculates the modified P', and checks it satisfies P' = a*G_1 + b*H_1 + c*U, where c = a*b So the prover must provide (L[], R[], a, b) as output to the verifier. Note here 'a' and 'b' are scalars, the final step of recursive reduction from the prover's original a* and b* vectors; they are passed through the recursion but here, unlike for the proof function, only used in the final step. """ if n == 1: Pprime = IPC([a], [b], g=g, h=h, u=self.U).get_commitment() #print("Finished recursive verify; now comparing original P: ", # binascii.hexlify(P)) #print("..with calculated P': ", binascii.hexlify(Pprime)) return P == Pprime x, xb, x_sq, x_sqb, xinv, xinvb, x_sq_inv, x_sq_invb = self.fiat_shamir( L[self.verif_iter], R[self.verif_iter], P) #Construct change of coordinates for base points, and for vector terms gprime = [] hprime = [] for i in range(n / 2): gprime.append( add_pubkeys([ multiply(xinvb, g[i], False), multiply(xb, g[i + n / 2], False) ], False)) hprime.append( add_pubkeys([ multiply(xb, h[i], False), multiply(xinvb, h[i + n / 2], False) ], False)) Pprime = add_pubkeys([ P, multiply(x_sqb, L[self.verif_iter], False), multiply(x_sq_invb, R[self.verif_iter], False) ], False) self.verif_iter += 1 return self.verify_proof_recursive(Pprime, L, R, a, b, gprime, hprime, n / 2)