def verify(public_key, signature, message): try: lib_public_key = ed25519.VerifyingKey(public_key) lib_public_key.verify(signature, message) return True except ed25519.BadSignatureError: return False
def _verify_sig(self, data): if self.app_key is None: log.debug("App key is None") return # Checking to see if there is a signature key in the version file. if "signature" in data.keys(): signature = data["signature"] log.debug("Deleting signature from update data") # We are removing the signature to prepare the data # for verification. del data["signature"] update_data = json.dumps(data, sort_keys=True) pub_key = ed25519.VerifyingKey(self.app_key, encoding="base64") if six.PY3: if not isinstance(update_data, bytes): update_data = bytes(update_data, encoding="utf-8") try: pub_key.verify(signature, update_data, encoding="base64") except Exception as err: log.debug("Version file not verified") log.debug(err, exc_info=True) else: log.debug("Version file verified") self.verified = True else: log.debug("Signature not in update data")
def verify_file(pubkey='pubkey.json', datafile=None): if len(sys.argv) > 2: datafile = sys.argv[2] if datafile and datafile != pubkey: print('Load public key data from', pubkey) print('Verify any data json from', datafile) else: print('Verify public key data from', pubkey) with open(pubkey, 'r', encoding='utf-8') as fp: data = json.loads(fp.read()) vkey_hex = data['envelope']['payload']['publicKey'] vk = ed25519.VerifyingKey(vkey_hex, encoding="hex") if datafile and datafile != pubkey: with open(datafile, 'r', encoding='utf-8') as fp: data = json.loads(fp.read()) try: data_verify(data, vk) print("Result OK") except Exception as e: print("Can't verify:", e) return 1 return 0
def from_dict(cls, d): hdr = from_dict(cls, d) decoded = from_base64(hdr.pubkey) hdr.pubkey = ed25519.VerifyingKey(decoded) hdr.signature = from_base64(hdr.signature) hdr.nonce = from_base64(hdr.nonce) return hdr
def check_password(username, password): """检查账户密码是否正确,返回bool""" sig = bytes(password, encoding="utf8") vkey_hex = bytes(username, encoding="utf8") signature_key = ed25519.SigningKey(sig, encoding="hex") verifying_key = ed25519.VerifyingKey(vkey_hex, encoding="hex") return verifying_key == signature_key.get_verifying_key()
def verify_files(message: str, signature: str, pubkey: str) -> bool: """Verify a message using signify. Equivalent to `signify -V ...`. Does not (yet) support embedded signatures. Takes: message: str - File path containing message plaintext to verify against signature: str - File path containing base64-encoded message signature (detached signature) pubkey: str - File path containing base64-encoded public key to verify against Returns: bool - Validity of signature """ sig_data = _read_signify_file(signature) pubkey_data = _read_signify_file(pubkey) sig = _sig_dict(sig_data) pub = _pubkey_dict(pubkey_data) if pub['keynum'] != sig['keynum']: raise Exception('verification failed: checked against wrong key') with open(message, 'rb') as message_file: message_data = message_file.read() key = ed25519.VerifyingKey(pub['pubkey']) try: key.verify(sig['sig'], message_data) except ed25519.BadSignatureError: return False return True
def cmd_verify(argv): """ Verify digital signature """ description = inspect.getdoc(cmd_verify) parser = ArgumentParser(description=description) parser.add_argument("-s", "--sig", action="store", dest="sig", default=None, help="signature file") parser.add_argument("-k", "--key", action="store", dest="key", default="dhall_key.pub", help="verifying key") parser.add_argument("files", action="store", nargs="+", default=None, help="Files to verify") args = parser.parse_args(argv) if not args.key: parser.error("A verifying key is needed") try: vkey = ed25519.VerifyingKey(open(args.key, 'rb').read(), encoding="hex") except Exception, e: logger.error("Error loading verification key %s" % args.key) raise
def main(): private_key = None algo = ES256 with open(sys.argv[1], 'rb') as fd: priv_key_bytes = fd.read() try: private_key = serialization.load_pem_private_key( priv_key_bytes, password=None, backend=default_backend()) except ValueError: algo = EDDSA private_key = ed25519.SigningKey( eddsa.parse_privkey(priv_key_bytes)) public_key = None with open(sys.argv[2], 'rb') as fd: pub_key_bytes = fd.read() try: public_key = serialization.load_pem_public_key( pub_key_bytes, backend=default_backend()) except ValueError: public_key = ed25519.VerifyingKey( eddsa.parse_pubkey(pub_key_bytes)) # Read the input file doc = None with open(sys.argv[3], 'rb') as fd: doc = fd.read() outDoc = signWrapper(algo, private_key, public_key, doc) with open(sys.argv[4], 'wb') as fd: fd.write(cbor.dumps(outDoc, sort_keys=True))
def cry_verify_signature(buf, pubkey_data, signature_data): key_algo = pubkey_data["algo"] sig_algo = signature_data["algo"] if sigalgo_to_keyalgo.get(sig_algo, sig_algo) != key_algo: raise UnsupportedKeyType(key_algo) if key_algo == "ssh-rsa": from Crypto.Hash import SHA, SHA256, SHA512 from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 if sig_algo == "ssh-rsa": dg = SHA.new(buf) elif sig_algo == "rsa-sha2-256": dg = SHA256.new(buf) elif sig_algo == "rsa-sha2-512": dg = SHA512.new(buf) else: raise UnsupportedSignatureType(sig_algo) pk = RSA.construct((pubkey_data["n"], pubkey_data["e"])) sg = PKCS1_v1_5.new(pk) return sg.verify(dg, signature_data["s"]) elif key_algo == "ssh-ed25519": import ed25519 pk = ed25519.VerifyingKey(pubkey_data["key"]) try: pk.verify(signature_data["sig"], buf) return True except ed25519.BadSignatureError: return False elif key_algo.startswith("ecdsa-sha2-"): import ecdsa import hashlib curves = { "ecdsa-sha2-nistp256": ecdsa.NIST256p, "ecdsa-sha2-nistp384": ecdsa.NIST384p, "ecdsa-sha2-nistp521": ecdsa.NIST521p, } digests = { # https://tools.ietf.org/html/rfc5656#section-6.2.1 "ecdsa-sha2-nistp256": hashlib.sha256, "ecdsa-sha2-nistp384": hashlib.sha384, "ecdsa-sha2-nistp521": hashlib.sha512, } if pubkey_data["Q"][0] == 0x04: # complete point pk = ecdsa.VerifyingKey.from_string(pubkey_data["Q"][1:], curve=curves[key_algo], hashfunc=digests[key_algo]) else: # probably compressed point? raise UnsupportedKeyType("%s{B0=%02x}" % (key_algo, pubkey_data["Q"][0])) try: pk.verify(signature_data, buf, sigdecode=lambda sig, order: (sig["r"], sig["s"])) return True except ecdsa.BadSignatureError: return False else: raise UnsupportedKeyType(key_algo)
def _get_signing_key(self): key_data_str = self._download_key() if key_data_str is None: return key_data = json.loads(key_data_str.decode('utf-8')) pub_key = key_data['app_public'] if six.PY3: if not isinstance(pub_key, bytes): pub_key = bytes(pub_key, encoding='utf-8') else: pub_key = str(pub_key) sig = key_data['signature'] signing_key = ed25519.VerifyingKey(self.root_key, encoding='base64') try: signing_key.verify(sig, pub_key, encoding='base64') except Exception as err: log.warning('Key file not verified') log.error(err) log.debug(err, exc_info=True) else: log.info('Key file verified') self.app_key = pub_key
def get(vk): id = vk.to_ascii(encoding='base64').decode() url = '%s/%s' % (base, id) headers = { 'User-Agent': settings.USER_AGENT } try: opener = tor_request.get_opener() opener.addheaders = list(zip(headers.keys(), headers.values())) r = opener.open(url) except: logger.info('get failed %s', url, exc_info=1) return None sig = r.headers.get('X-Ed25519-Signature') data = r.read() if sig and data: vk = ed25519.VerifyingKey(id, encoding='base64') try: vk.verify(sig, data, encoding='base64') data = json.loads(data.decode('utf-8')) except ed25519.BadSignatureError: logger.debug('invalid signature') data = None return data
def verify(public_key: str, message: str, signature: str) -> bool: """ Verify Bytom signature by public key. :param public_key: Bytom public key. :type public_key: str. :param message: Message data. :type message: str. :param signature: Signed message data. :type signature: str. :return: bool -- verified signature. >>> from pybytom.signature import verify >>> verify(public_key="91ff7f525ff40874c4f47f0cab42e46e3bf53adad59adef9558ad1b6448f22e2", message="1246b84985e1ab5f83f4ec2bdf271114666fd3d9e24d12981a3c861b9ed523c6", signature="f6624fea84fadccbc1bc72dc384f662468e271c4e32d846bc0a1524470549992c8ffcc3ca43891a30de4235392b0868c506ed254f0f77cc1f2b9c1a2385ddb05") True """ result = False verifying_key = ed25519.VerifyingKey( public_key.encode(), encoding="hex" ) try: verifying_key.verify( signature.encode(), bytes.fromhex(message), encoding="hex" ) result = True except ed25519.BadSignatureError: result = False return result
def load_secrets(self) -> None: if not config.get("ed25519.signing_key"): # Generating keys on demand. This is useful for unit tests self.signing_key, self.verifying_key = ed25519.create_keypair( entropy=os.urandom ) return signing_key_file = os.path.expanduser(config.get("ed25519.signing_key")) try: with open(signing_key_file, "rb") as signing_file: signing_key_str: str = signing_file.read() except FileNotFoundError: msg = "Unable to load signing key" log.error(msg, exc_info=True) raise Exception(msg) self.signing_key = ed25519.SigningKey(signing_key_str) verifying_key_file = config.get("ed25519.verifying_key") try: verifying_key_str = open(verifying_key_file, "rb").read() except FileNotFoundError: msg = "Unable to load verifying key" log.error(msg, exc_info=True) raise Exception(msg) self.verifying_key = ed25519.VerifyingKey(verifying_key_str)
def load_pkdb(self): i = 0 try: #with open(self.cnf.trustdb,'rt') as f: # for line in f: for line in open(self.cnf.trustdb, 'rt'): i = i + 1 l = line.strip() if l.startswith("#") or len(l) == 0: continue (node, bs64pubkey) = line.split() self.pubkeys[node] = ed25519.VerifyingKey(bs64pubkey, encoding="base64") self.logger.debug("Read {} TOFU keys from {}.".format( len(self.pubkeys), self.cnf.trustdb)) return True except FileNotFoundError: self.logger.critical( "Could not find trustdb file {} (did you create it with 'touch')" .format(self.cnf.trustdb)) except Exception as e: self.logger.critical( "Could not read line {} in trustdb file {}: {}".format( i, self.cnf.trustdb, str(e))) return False
def test_OOP(self): sk_s = unhexlify("4ba96b0b5303328c7405220598a587c4" "acb06ed9a9601d149f85400195f1ec3d" "a66d161e090652b054740748f059f92a" "5b731f1c27b05571f6d942e4f8b7b264") sk = ed25519.SigningKey(sk_s) self.failUnlessEqual(len(sk.to_bytes()), 64) self.failUnlessEqual(sk.to_bytes(), sk_s) sk2_seed = unhexlify("4ba96b0b5303328c7405220598a587c4" "acb06ed9a9601d149f85400195f1ec3d") sk2 = ed25519.SigningKey(sk2_seed) self.failUnlessEqual(sk2.to_bytes(), sk.to_bytes()) vk = sk.get_verifying_key() self.failUnlessEqual(len(vk.to_bytes()), 32) exp_vks = unhexlify("a66d161e090652b054740748f059f92a" "5b731f1c27b05571f6d942e4f8b7b264") self.failUnlessEqual(vk.to_bytes(), exp_vks) self.failUnlessEqual(ed25519.VerifyingKey(vk.to_bytes()), vk) msg = b"hello world" sig = sk.sign(msg) self.failUnlessEqual(len(sig), 64) exp_sig = unhexlify("6eaffe94f2972b35158b6aaa9b69c1da" "97f0896aca29c41b1dd7b32e6c9e2ff6" "76fc8d8b034709cdcc37d8aeb86bebfb" "173ace3c319e211ea1d7e8d8884c1808") self.failUnlessEqual(sig, exp_sig) self.failUnlessEqual(vk.verify(sig, msg), None) # also, don't throw self.failUnlessRaises(ed25519.BadSignatureError, vk.verify, sig, msg + b".. NOT!")
def check2(encoding, expected): PREFIX = "public0-" p = vk1.to_ascii(PREFIX, encoding) self.failUnlessEqual(p, expected) vk2 = ed25519.VerifyingKey(p, prefix=PREFIX, encoding=encoding) self.failUnlessEqual(repr(vk1.to_bytes()), repr(vk2.to_bytes())) self.failUnlessEqual(vk1, vk2)
def verify(): """ Verify ed25519 message signature. """ if len(sys.argv) != 4: result = { 'error': 'Expected 3 args: msg, signed, and public key'} else: try: msg = sys.argv[1].encode() signed_b = binascii.a2b_base64( sys.argv[2]) pub_b = binascii.a2b_base64( sys.argv[3]) pub_ = ed25519.VerifyingKey( pub_b) verify_ = True try: pub_.verify( signed_b, msg) except ed25519.BadSignatureError: verify_ = False result = { 'verify': verify_} except Exception as exc: result = { 'error': str(exc)} return result
def _get_signing_key(self): # Here we will download the keys.gz file, decompress it then return # its contents. If an error happens you'll get None. key_data_str = self._get_key_data() if key_data_str is None: return # Key data dict key_data = json.loads(key_data_str.decode("utf-8")) # Get the public key so we can verify it's authenticity with the # root public key. pub_key = key_data["app_public"] if not isinstance(pub_key, bytes): pub_key = bytes(pub_key, encoding="utf-8") # The signature that we'll validate sig = key_data["signature"] # Let's generate our signing key. signing_key = ed25519.VerifyingKey(self.root_key, encoding="base64") try: signing_key.verify(sig, pub_key, encoding="base64") except Exception as err: # This is bad. Very bad. # Create another keypack.pyu & import it not your repo. log.debug("Key file not verified") log.debug(err, exc_info=True) else: # Everything checks out log.debug("Key file verified") self.app_key = pub_key
def _pairing_four(self, client_username, client_ltpk, client_proof, encryption_key): """Expand the SRP session key to obtain a new key. Use it to verify that the client's proof of the private key. Continue to step five. @param client_username: The client's username. @type client_username: bytes. @param client_ltpk: The client's public key. @type client_ltpk: bytes @param client_proof: The client's proof of password. @type client_proof: bytes @param encryption_key: The encryption key for this step. @type encryption_key: bytes """ logger.debug("Pairing [4/5]") session_key = self.accessory_handler.srp_verifier.get_session_key() output_key = hap_hkdf(long_to_bytes(session_key), self.PAIRING_4_SALT, self.PAIRING_4_INFO) data = output_key + client_username + client_ltpk verifying_key = ed25519.VerifyingKey(client_ltpk) try: verifying_key.verify(client_proof, data) except ed25519.BadSignatureError: logger.error("Bad signature, abort.") raise self._pairing_five(client_username, client_ltpk, encryption_key)
def _verify_sig(self, data): if self.app_key is None: log.debug('App key is None') return # Checking to see if there is a signature key in the version file. if 'signature' in data.keys(): signature = data['signature'] log.debug('Deleting signature from update data') del data['signature'] # After removing the signatures we turn the json data back # into a string to use as data to verify the sig. update_data = json.dumps(data, sort_keys=True) pub_key = ed25519.VerifyingKey(self.app_key, encoding='base64') if six.PY3: if not isinstance(update_data, bytes): update_data = bytes(update_data, encoding='utf-8') try: pub_key.verify(signature, update_data, encoding='base64') except Exception as err: log.warning('Version file not verified') log.error(err) log.debug(err, exc_info=True) else: log.info('Version file verified') self.verified = True else: log.debug('Signature not in update data')
def _decompress_ed25519(pubkey): """Load public key from the serialized blob (stripping the prefix byte).""" if pubkey[:1] == b'\x00': # set by Trezor fsm_msgSignIdentity() and fsm_msgGetPublicKey() return ed25519.VerifyingKey(pubkey[1:]) else: return None
def validateSignature(response, keys): answer = {} args = ['stellar_address', 'account_id', 'memo_type', 'memo'] resp_filtered = {k: v for k, v in response.iteritems() if k in args} try: fedResp = FederationResponse(**resp_filtered) except: return {'error': 'invalid Federation response'} if 'signature' not in response: answer['verified'] = False answer['error'] = "No signature in response" return answer signature = response['signature'] signature = base64.b64decode(signature) hash = hashlib.sha256(fedResp.xdr).digest() for key in keys: try: pubkey = base64.b32decode(key) pubkey = ed25519.VerifyingKey(pubkey[1:-2]) pubkey.verify(signature, hash) # throws exception if fails answer['signed'] = key return answer except: pass return answer
def main(): if len(sys.argv) < 3: usage() return if sys.argv[1] == "vk": # Generate verify key with open(sys.argv[2], "rb") as f: secret = f.read() if len(secret) != 32: print("ERROR: Secret must be 32 bytes!") return sk = ed25519.SigningKey(secret) vk = sk.get_verifying_key() print(vk.to_ascii(encoding='base64').decode('ascii')) elif sys.argv[1] == "sign": # Sign a command with open(sys.argv[2], "rb") as f: secret = f.read() if len(secret) != 32: print("ERROR: Secret must be 32 bytes!") return sk = ed25519.SigningKey(secret) is_special = sys.argv[4] == "!!" bytes_to_sign = b'\x00' if not is_special else b'\x01' # Nonce nonce = binascii.unhexlify(sys.argv[3]) bytes_to_sign += nonce bytes_to_sign += sys.argv[5].encode('utf-8') sig = sk.sign(bytes_to_sign, encoding='base64').decode('ascii') print(sig) elif sys.argv[1] == "verify": # Verify a command vk = ed25519.VerifyingKey(sys.argv[2], encoding='base64') is_special = sys.argv[5] == "!!" bytes_to_sign = b'\x00' if not is_special else b'\x01' # Nonce nonce = binascii.unhexlify(sys.argv[4]) bytes_to_sign += nonce bytes_to_sign += sys.argv[6].encode('utf-8') try: vk.verify(sys.argv[3], bytes_to_sign, encoding='base64') print("Signature OK!") except ed25519.BadSignatureError: print("Signature invalid!") else: usage()
def verify(self, signature: Signature, message: bytes) -> bool: vk = ed25519.VerifyingKey(bytes(self)) digest = hashlib.sha256(message).digest() try: vk.verify(bytes(signature), digest) return True except ed25519.BadSignatureError: return False
def _check_signature(self, idk, client, server, ids): verifying_key = ed25519.VerifyingKey(sqrl_base64_decode(idk)) try: verifying_key.verify(sqrl_base64_decode(ids), client + server) return True except ed25519.BadSignatureError: return False
def _verify_ed25519(key: bytes, message: bytes, signature: bytes) -> bool: verifying_key = ed25519.VerifyingKey(key) try: verifying_key.verify(signature, message) return True except ed25519.BadSignatureError: return False
def verify(self): try: verifier = ed25519.VerifyingKey(self.get_public_key()) msg_digest = self.get_hashing_algorithm()(self._entity_hash).digest() verifier.verify(self._entity_hash_signature, msg_digest) return True except Exception: return False
def _pair_verify_two(self, tlv_objects): """Verify the client proof and upgrade to encrypted transport. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logger.debug("Pair verify [2/2]") encrypted_data = tlv_objects[HAP_TLV_TAGS.ENCRYPTED_DATA] cipher = ChaCha20Poly1305(self.enc_context["pre_session_key"]) decrypted_data = cipher.decrypt(self.PVERIFY_2_NONCE, bytes(encrypted_data), b"") assert decrypted_data is not None # TODO: dec_tlv_objects = tlv.decode(bytes(decrypted_data)) client_username = dec_tlv_objects[HAP_TLV_TAGS.USERNAME] material = (self.enc_context["client_public"] + client_username + self.enc_context["public_key"].serialize()) client_uuid = uuid.UUID(str(client_username, "ascii")) perm_client_public = self.state.paired_clients.get(client_uuid) if perm_client_public is None: logger.debug( "Client %s attempted pair verify without being paired first.", client_uuid, ) self.send_response(200) self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE) data = tlv.encode(HAP_TLV_TAGS.ERROR_CODE, HAP_OPERATION_CODE.INVALID_REQUEST) self.end_response(data) return verifying_key = ed25519.VerifyingKey(perm_client_public) try: verifying_key.verify(dec_tlv_objects[HAP_TLV_TAGS.PROOF], material) except ed25519.BadSignatureError: logger.error("Bad signature, abort.") self.send_response(200) self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE) data = tlv.encode(HAP_TLV_TAGS.ERROR_CODE, HAP_OPERATION_CODE.INVALID_REQUEST) self.end_response(data) return logger.debug( "Pair verify with client '%s' completed. Switching to " "encrypted transport.", self.client_address, ) data = tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b"\x04") self.send_response(200) self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE) self.end_response(data) self.response.shared_key = self.enc_context["shared_key"] self.is_encrypted = True del self.enc_context
def from_stream(cls, stream): '''Parses a given stream, verifies it and creates a Header object''' buf = bytearray(16) if stream.readinto(buf) != 16: raise ValueError('Header too small') # Magic number, 8 bytes magic_number = bytes(buf[:8]) # 8 bytes if magic_number != MAGIC_NUMBER: raise ValueError('Not a CRYPT4GH formatted file') # Version, 4 bytes version = int.from_bytes(bytes(buf[8:12]), byteorder='little') if version != __version__: # only version 1, so far raise ValueError('Unsupported CRYPT4GH version') # Verifying key, 4 bytes verifying_key_len = int.from_bytes(bytes(buf[12:16]), byteorder='little') LOG.debug('Verifying Key Length: %d', verifying_key_len) verifying_key = None if verifying_key_len > 0: verifying_key = stream.read(verifying_key_len) buf += verifying_key vkey = ed25519.VerifyingKey(verifying_key) # Header length header_len = stream.read(4) # 4 bytes buf += header_len header_len = int.from_bytes(header_len, byteorder='little') encrypted_len = header_len - 16 - verifying_key_len - 4 if verifying_key: encrypted_len -= 64 # discounting the mac from the length if encrypted_len <= 0: # Should be at least 16 bytes long raise ValueError('Invalid Header Length') # Encrypted part encrypted_part = bytearray(encrypted_len) if stream.readinto(encrypted_part) != encrypted_len: raise ValueError('Header too small') buf += encrypted_part # If case it is Ed25519 signed mac = None if verifying_key: mac = bytearray(64) if stream.readinto(mac) != 64: raise ValueError('Invalid MAC length') try: vkey.verify(bytes(mac), bytes(buf)) # raw bytes except ed25519.BadSignatureError: raise ValueError('Invalid Ed25519 signature') # Make the object obj = cls(version=version, encrypted_part=bytes(encrypted_part), mac=mac, verifying_key=verifying_key) return obj
def _derive_signing_and_verifying_key(self, public_key, private_key): # If neither the public, nor the private key is set, generate the key pair and return if public_key is None and private_key is None: self.signing_key, self.verifying_key = ed25519.create_keypair() return # If both the public key and the private key are set, construct the objects, verify # that the keys are matching and return if public_key is not None and private_key is not None: try: self.signing_key = ed25519.SigningKey(private_key) except ValueError: raise ValueError("Invalid Ed25519 private key. Must be a 32-byte seed.") self.verifying_key = self.signing_key.get_verifying_key() non_matching_public_key_msg = ( "The provided public key does not match the one derived " "from the provided private key" ) assert ( self.verifying_key.to_bytes() == public_key ), non_matching_public_key_msg return elif public_key is not None and private_key is None: try: self.signing_key = None self.verifying_key = ed25519.VerifyingKey(public_key) except ValueError: raise ValueError("Invalid Ed25519 public key. Must be a 32-byte value.") # At this point, either only the public key is set or only the private key is set if public_key is not None: try: self.signing_key = None self.verifying_key = ed25519.VerifyingKey(public_key) except ValueError: raise ValueError("Invalid Ed25519 public key. Must be a 32-byte value.") else: # Private key is not None try: self.signing_key = ed25519.SigningKey(private_key) self.verifying_key = self.signing_key.get_verifying_key() except ValueError: raise ValueError("Invalid Ed25519 private key. Must be a 32-byte seed.")