I'm running into the most bizarre issue regarding datestring parsing in golang using the time package.
The Error:
parsing time "07-20-2018" as "2006-01-02": cannot parse "0-2018" as "2006"
The Code Block:
log.Println(datestring) //07-20-2018
date, err := time.Parse("2006-01-02", datestring)
log.Println(err) //parsing time "07-20-2018" as "2006-01-02": cannot parse "0-2018" as "2006"
log.Println(date) //parsing time "07-20-2018" as "2006-01-02": cannot parse "0-2018" as "2006"
I'm completely at a loss to what this issue is referring to, the string is parsed from the URI in golang with gorilla mux.
datestring, _ := vars["date"] //some/path/{date}, date is 07-20-2018
Any ideas?
It is obvious. You are trying to parse mm-dd-yyyy as yyyy-mm-dd.
A simple fix:
package main
import (
"fmt"
"time"
)
func main() {
datestring := "07-20-2018"
fmt.Println(datestring)
date, err := time.Parse("01-02-2006", datestring)
fmt.Println(date, err)
}
Playground: https://play.golang.org/p/gK7cMAkrP7l
Output:
07-20-2018
2018-07-20 00:00:00 +0000 UTC <nil>
See Go Package time.
Ok so I have this moment object I'm looking at in Developer Tools:
amoment: Moment
_a: (7) [2017, 4, 21, 20, 15, 0, 0]
_d: Sun May 21 2017 15:15:00 GMT-0500 (Central Daylight Time) {}
_f: "MM/DD/YYYY h:mm A"
_i: "5/21/2017 8:15:00 PM"
_isAMomentObject: true
_isUTC: true
_isValid: true
_locale: Locale {_calendar: {…}, _longDateFormat: {…}, _invalidDate: "Invalid date", ordinal: ƒ, _dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, …}
_offset: -180
_pf: {empty: false, unusedTokens: Array(0), unusedInput: Array(1), overflow: -1, charsLeftOver: 3, …}
_strict: false
_z: Zone {name: "America/Halifax", abbrs: Array(229), untils: Array(229), offsets: Array(229), population: 390000}
__proto__: Object
As you can see, the text representation is 5/21/2017 8:15:00 pm. This is the correct time in UTC and I notice "_isUTC" is "true".
The _d property shows that datetime in CDT, where I am now.
The _z property shows a Zone object set to "America/Halifax"--this is dynamically set elsewhere, but also correct.
I would like to return a moment reading "5/21/2017 5:15:00 pm" because that's what that datetime is in "America/Halifax."
There are so many moment.js functions I haven't found the right way to do this.
All of the fields that begin with underscores (_) are meant to be internal to Moment. You should not be using them at all. More on this here, and here.
To output a string in a particular format from a moment object, use the format function. For example:
yourMomentObject.format("M/D/YYYY h:mm:ss a") //=> "5/21/2017 5:17:00 pm"
I ended up doing new Date(amoment.format('YYYY-MM-DDTHH:mm:ss+00:00')); which worked for my purposes. The zone kept wanting to render the date in that zone, so I just had to omit it.
I am working on a simple R package to submit hashes for trusted timestamping and to get timestamp info back through Origin Timestamps. I manage to get the information, but I do not manage to POST it OpenTimestamp post hash.
I am using the http package in R. My package ROriginStamp is on github, and the function which I do not get to work is store_hash_info().
Whenever I execute it, I get:
> store_hash(hash = "c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e")
Error in store_hash(hash = "c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e") :
Bad Request (HTTP 400).
3.
stop(http_condition(x, "error", task = task, call = call))
2.
httr::stop_for_status(result$response) at store_hash.R#29
1.
store_hash(hash = "c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e")
>
The function is defined as follow:
store_hash <- function(
hash,
error_on_fail = TRUE,
information = NULL
) {
result <- new_OriginStampResponse()
##
url <- paste0("https://api.originstamp.org/api/", hash)
request_body_json <- jsonlite::toJSON( information, auto_unbox = TRUE )
result$response <- httr::POST(
url,
httr::add_headers(
Authorization = get_option("api_key"),
body = request_body_json
),
httr::content_type_json()
)
if (error_on_fail) {
httr::stop_for_status(result$response)
}
##
try(
{
result$content <- httr::content(
x = result$response,
as = "text"
)
result$content <- jsonlite::fromJSON( result$content )
},
silent = TRUE
)
##
return(result)
}
The function get_option("api_key") just returns my api key.
Any suggestions what I am doing wrong?
Edits
Thanks to Thomas Hepp, Here is a curl command which does work:
curl 'https://api.originstamp.org/api/ff55d7bc3fe6cb2958e4bdda3d4a4a8e528fb67d9194991e9539d97a55cda2a3' \
-H 'authorization: YOUR API KEY' \
-H 'content-type: application/json' \
-H 'accept: application/json' \
-H 'user-agent: OriginStamp cURL Test' \
--data-binary '{"url":null,"email":null,"comment":"this is a test","submit_ops":["multi_seed"]}'
I’m not familiar with R. But, it’s possible to timestamp a file using opentimestamps.org, by posting a hash of the file to one of their calendar servers, using the following methodology. There is no need for an API key, and this procedure can be used to prove the existence of the file at a point in time, via a reference to a value stored in the OP_RETURN field of a bitcoin transaction, in a block in the bitcoin blockchain.
As an example, first, create a test file:
$ echo -n 'this is a test... this is only a test...' > file.txt
Now, take a sha256 hash of the file:
$ sha256sum file.txt
This produces:
c16d7c8e23baf68525cf0a42fff6b394fdba1791db9817fd601b3f73e2f5fbca
Now, to create a timestamp of the file, post the raw bytes of the hash to one of the calendar servers (e.g. https://a.pool.opentimestamps.org/). This can be done using curl, like so:
$ echo -n 'c16d7c8e23baf68525cf0a42fff6b394fdba1791db9817fd601b3f73e2f5fbca' | xxd -r -p | curl -X POST --data-binary - https://a.pool.opentimestamps.org/digest > out.ots
[Optional: If you don’t want to disclose the hash of the file that you are timestamping to Opentimestamps, you can add a random salt to the file hash, then do sha256(original file hash + salt) and post the result of this.]
The response from the above request is redirected to a file out.ots. To get the status of the timestamp, we need to parse the raw bytes of out.ots.
First, view the raw bytes of the file using a hex editor, or xxd:
$ xxd out.ots
00000000: f010 95ee b35a b002 5b8b 5e76 3522 6970 .....Z..[.^v5"ip
00000010: 886c 08f1 0462 be3e 32f0 081a ff1c ae94 .l...b.>2.......
00000020: 4f00 4600 83df e30d 2ef9 0c8e 2e2d 6874 O.F..........-ht
00000030: 7470 733a 2f2f 616c 6963 652e 6274 632e tps://alice.btc.
00000040: 6361 6c65 6e64 6172 2e6f 7065 6e74 696d calendar.opentim
00000050: 6573 7461 6d70 732e 6f72 67 estamps.org
Some of the bytes represent instructions, as follows:
f0 xx: append xx bytes
f1 xx: prepend xx bytes
80: sha256 hash
00: stop
Start with the hash that we timestamped:
c16d7c8e23baf68525cf0a42fff6b394fdba1791db9817fd601b3f73e2f5fbca
then, proceed by parsing the response of the POST request. The first two bytes are f0 10. This means append the next 16 bytes (10 in hex is 16 in decimal). This produces:
c16d7c8e23baf68525cf0a42fff6b394fdba1791db9817fd601b3f73e2f5fbca95eeb35ab0025b8b5e7635226970886c
Continuing parsing the POST response, the next byte is 80. This means take the sha256 hash of the above.
echo -n ‘c16d7c8e23baf68525cf0a42fff6b394fdba1791db9817fd601b3f73e2f5fbca95eeb35ab0025b8b5e7635226970886c’ | xxd -p -r | sha256sum
produces:
f7d0917a163a8df26066cd669eb12e2d0d59bb5f454aaee338dcc0694ef35090
Continuing, we have f1 04. This means prepend the next 4 bytes to the above. This produces:
62be3e32f7d0917a163a8df26066cd669eb12e2d0d59bb5f454aaee338dcc0694ef35090
Next, we have f0 08. Append the next 8 bytes. This produces:
62be3e32f7d0917a163a8df26066cd669eb12e2d0d59bb5f454aaee338dcc0694ef350901aff1cae944f0046
Finally, we have 00. This means stop. At this point, skip the next 10 bytes, then extract starting from this point to get the URL that we’ll need to get the status of the timestamp:
https://alice.btc.calendar.opentimestamps.org
then, concatenate ‘/timestamp/’ followed by the result above:
https://alice.btc.calendar.opentimestamps.org/timestamp/62be3e32f7d0917a163a8df26066cd669eb12e2d0d59bb5f454aaee338dcc0694ef350901aff1cae944f0046
The status of the timestamp can be accessed by making a GET request to the above URL:
curl https://alice.btc.calendar.opentimestamps.org/timestamp/62be3e32f7d0917a163a8df26066cd669eb12e2d0d59bb5f454aaee338dcc0694ef350901aff1cae944f0046
returns:
Pending confirmation in Bitcoin blockchain
If you wait a few hours, the confirmation will be written to the bitcoin blockchain, and the above GET request will return a much longer proof like the one above, eventually chaining-up to a value that is written to the OP_RETURN field of a bitcoin transaction, in a block in the bitcoin blockchain. By saving this proof, you can verify the existence of the file at the point in time that the block was written to the blockchain, without the need to query the Opentimestamps servers.
The following python script automates the above procedure:
import hashlib
import requests
filehash=bytes.fromhex('c16d7c8e23baf68525cf0a42fff6b394fdba1791db9817fd601b3f73e2f5fbca')
server='https://a.pool.opentimestamps.org/digest'
print('posting ' + filehash.hex() + ' to ' + server)
response=requests.post(url=server, data=filehash)
bytearray=response.content
print('saving response to ./out.ots')
f=open('./out.ots', 'wb')
f.write(bytearray)
f.close()
print('analysing response')
i=0
ptr=0x00
result=filehash
while(True):
print(i)
print('ptr:', hex(ptr))
nextinstruction=bytearray[ptr]
if(nextinstruction==0xf0):
#append
ptr+=1
numberofbytes=bytearray[ptr]
ptr+=1
bytesegment=bytearray[ptr:ptr+numberofbytes]
print('append ', bytesegment.hex())
result=result+bytesegment
ptr+=numberofbytes
elif(nextinstruction==0xf1):
#prepend
ptr+=1
numberofbytes=bytearray[ptr]
ptr+=1
bytesegment=bytearray[ptr:ptr+numberofbytes]
print('prepend ', bytesegment.hex())
result=bytesegment+result
ptr+=numberofbytes
elif(nextinstruction==0x08):
#sha256
print('sha256')
result=hashlib.sha256(result).digest()
ptr+=1
elif(nextinstruction==0xff):
#fork
print('fork')
ptr+=1
elif(nextinstruction==0x00):
#stop
print('stop')
ptr+=11
url=bytearray[ptr:].decode()
url=url + '/timestamp/' + result.hex()
print('url: ', url)
else:
print('invalid ots file format')
quit()
print('result:', result.hex())
print('-----')
i+=1
if(nextinstruction==0x00): break
print('to get status of timestamp, make a GET request to ' + url)
print('GET ' + url)
response=requests.get(url)
print(response.text)
I am trying to query a table called "user_path_summary" for a value that is equal to my hashkey, "username" and also equal to my global secondary index hashkey, "full_path". I am hoping to get back one attribute, "articles_read". The parameters that I am using are:
var params = {
"TableName": "user_path_summary",
"IndexName": "full_path_index",
"KeyConditionExpression": "full_path = :v_full_path AND username = v:username",
"ExpressionAttributeValues": {
":v_username": {"S" : username},
":v_full_path": {"S": full_path}
},
"ProjectionExpression": "articles_read",
"ScanIndexForward": false
};
I get the following error.
{ [MultipleValidationErrors: There were 2 validation errors:
* MissingRequiredParameter: Missing required key 'KeyConditions' in params
* UnexpectedParameter: Unexpected key 'KeyConditionExpression' found in params]
message: 'There were 2 validation errors:\n* MissingRequiredParameter: Missing required key \'KeyConditions\' in params\n* UnexpectedParameter: Unexpected key \'KeyConditionExpression\' found in params',
code: 'MultipleValidationErrors',
errors:
[ { [MissingRequiredParameter: Missing required key 'KeyConditions' in params]
message: 'Missing required key \'KeyConditions\' in params',
code: 'MissingRequiredParameter',
time: Sat Aug 01 2015 14:54:52 GMT-0400 (EDT) },
{ [UnexpectedParameter: Unexpected key 'KeyConditionExpression' found in params]
message: 'Unexpected key \'KeyConditionExpression\' found in params',
code: 'UnexpectedParameter',
time: Sat Aug 01 2015 14:54:52 GMT-0400 (EDT) } ],
time: Sat Aug 01 2015 14:54:52 GMT-0400 (EDT) }
I am confused because the error pertains to the missing parameter "KeyConditions", which is identified as a legacy parameter in the query API Reference, and also to the unexpected parameter "KeyConditionExpression" which the api reference says to use instead of "KeyConditions".
Is there a setting somewhere that I am missing? Is my syntax messed up somewhere. Please let me know, I am clearly missing something.
I am attempting to work with Azure storage via the REST API in R. I'm using the package httr which overlays Curl.
Setup
You can use R-fiddle: http://www.r-fiddle.org/#/fiddle?id=vh8uqGmM
library(httr)
requestdate<-format(Sys.time(),"%a, %d %b %Y %H:%M:%S GMT")
url<-"https://preconstuff.blob.core.windows.net/pings?restype=container&comp=list"
sak<-"Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ=="
signaturestring<-paste0("GET",paste(rep("\n",12),collapse=""),
"x-ms-date:",requestdate,"
x-ms-version:2009-09-19
/preconstuff/pings
comp:list
restype:container")
headerstuff<-add_headers(Authorization=paste0("SharedKey preconstuff:",
RCurl::base64(digest::hmac(key=sak,
object=enc2utf8(signaturestring),
algo= "sha256"))),
`x-ms-date`=requestdate,
`x-ms-version`= "2009-09-19")
Trying to list blobs:
content(GET(url,config = headerstuff, verbose() ))
Error
Top level message
The MAC signature found in the HTTP request 'Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ==' is not the same as any computed signature.
Response content
[1] "<?xml version=\"1.0\" encoding=\"utf-8\"?><Error>
<Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\nRequestId:1ab26da5-0001-00dc-6ddb-15e35c000000\nTime:2015-03-26T17:51:42.7190620Z</Message>
<AuthenticationErrorDetail>The MAC signature found in the HTTP request 'NTM1ODZjMjhhZmMyZGM3NDM0YTFjZDgwNGE0ODVmMzVjNDhkNjBkNzk1ZjNkZjJjOTNlNjUxYTMwMjRhNzNlYw==' is not the same as any computed signature. Server used following string to sign:
'GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Thu, 26 Mar 2015 17:52:37 GMT\nx-ms-version:2009-09-19\n/preconstuff/pings\ncomp:list\nrestype:container'.
</AuthenticationErrorDetail></Error>"
Verbose output
-> GET /pings?restype=container&comp=list HTTP/1.1
-> User-Agent: curl/7.39.0 Rcurl/1.95.4.5 httr/0.6.1
-> Host: preconstuff.blob.core.windows.net
-> Accept-Encoding: gzip
-> Accept: application/json, text/xml, application/xml, */*
-> Authorization: SharedKey preconstuff:OTRhNTgzYmY3OTY3M2UzNjk3ODdjMzk3OWM3ZmU0OTA4MWU5NTE2OGYyZGU3YzRjNjQ1M2NkNzY0ZTcyZDRhYQ==
-> x-ms-date: Thu, 26 Mar 2015 17:56:27 GMT
-> x-ms-version: 2009-09-19
->
<- HTTP/1.1 403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
<- Content-Length: 719
<- Content-Type: application/xml
<- Server: Microsoft-HTTPAPI/2.0
<- x-ms-request-id: 3d47770c-0001-0085-2313-6d466f000000
<- Date: Thu, 26 Mar 2015 17:56:27 GMT
<-
Resolving the error
Googling for this issue doesn't seem to yield a consistent cause, but it's likely due to bad formatting / request structure on my part. To that end I checked:
I've verified my key is correct (it's just c&p from portal)
I've made sure the date is correctly formatted
There was a recent documentDB SO that suggested it could be to be a clock skew issue and I do note that my x-ms-date is a second ahead of the Date in the response. I've tried sending a fixed value that was definitely in the past, but within the 15 mins tolerance. Didn't get a change in message.
Added encoding="Base64" in headerstuff further to an MSDN forum question but the same error message was returned
Further to #Serdar's answer, I incorporated the construction of a signature string (I've verified that this matches the one provided in the error message so it), then encoding in base64 a hmac-sha256 (using the secondary access key (sak) as the encryption key) version of the UTF8 converted signaturestring as the value to be used in the SharedKey authorisation.
Further to #Serdar's comment, the date used in the signature string and for the main request must be the same, so defined once and reused
Is there anything obviously wrong? Are there other things to check? Does the code work for others?
Looks like your problem is with the key. The string of the key you have provided is actually base64 encoded. You need to decode that to the raw vector before you use it to sign the request. For example:
url<-"https://preconstuff.blob.core.windows.net/pings?restype=container&comp=list"
sak<-"Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ=="
requestdate<-format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
signaturestring<-paste0("GET",paste(rep("\n",12),collapse=""),
"x-ms-date:",requestdate,"
x-ms-version:2009-09-19
/preconstuff/pings
comp:list
restype:container")
headerstuff<-add_headers(Authorization=paste0("SharedKey preconstuff:",
RCurl::base64(digest::hmac(key=RCurl::base64Decode(sak, mode="raw"),
object=enc2utf8(signaturestring),
algo= "sha256", raw=TRUE))),
`x-ms-date`=requestdate,
`x-ms-version`= "2009-09-19")
content(GET(url,config = headerstuff, verbose() ))
There are no more authentication errors this way, though no blobs are listed. Perhaps that's a different issue.
Also, I changed the way the date/time was created to more "safely" change the local time to GMT.
It looks like you are using the key of your account directly in the Authorization header. To authenticate a request, you must sign the request with the key for the account that is making the request and pass that signature as part of the request. Please see Authentication for the Azure Storage Services for more information on how to construct the Authorization header.
Please also note that the service returns StringToSign in the error response. So, what your code should have done is to apply the following formula to StringToSign="GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Wed, 25 Mar 2015 22:24:12 GMT\nx-ms-version:2014-02-14\n/preconstuff/pings\ncomp:list\nrestype:container" (without quotes):
Signature=Base64(HMAC-SHA256(AccountKey, UTF8(StringToSign)))
How the service calculates StringToSign is explained in detail in the link shared above.
Worked trough the example code by MrFlick above and to get it to work I had to change a few things.
The date string has to be set in an English locale, for example:
lct <- Sys.getlocale("LC_TIME")
Sys.setlocale("LC_TIME", "us")
requestdate <- format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
Sys.setlocale("LC_TIME", lct)
The 'signaturestring' should be formated with \n between parameters:
signaturestring <- paste0("GET", paste(rep("\n", 12), collapse=""),
"x-ms-date:", requestdate,
"\nx-ms-version:2009-09-19\n/preconstuff/pings\ncomp:list\nrestype:container")
EDIT: Following procedure works for me. Based on Steph Locke example.
library(httr)
library(RCurl)
azureBlobCall <- function(url, verb, key, requestBody=NULL, headers=NULL, ifMatch="", md5="") {
urlcomponents <- httr::parse_url(url)
account <- gsub(".blob.core.windows.net", "", urlcomponents$hostname, fixed = TRUE)
container <- urlcomponents$path
# get timestamp in us locale
lct <- Sys.getlocale("LC_TIME"); Sys.setlocale("LC_TIME", "us")
`x-ms-date` <- format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
Sys.setlocale("LC_TIME", lct)
# if requestBody exist get content length in bytes and content type
`Content-Length` <- ""; `Content-Type` <- ""
if(!is.null(requestBody)) {
if(class(requestBody) == "form_file") {
`Content-Length` <- (file.info(requestBody$path))$size
`Content-Type` <- requestBody$type
} else {
requestBody <- enc2utf8(as.character(requestBody))
`Content-Length` <- nchar(requestBody, "bytes")
`Content-Type` <- "text/plain; charset=UTF-8"
}
}
# combine timestamp and version headers with any input headers, order and create the CanonicalizedHeaders
headers <- setNames(c(`x-ms-date`, "2015-04-05", unlist(headers)),
c("x-ms-date", "x-ms-version", unclass(names(unlist(headers)))))
headers <- headers[order(names(headers))]
CanonicalizedHeaders <- paste(names(headers), headers, sep=":", collapse = "\n")
# create CanonicalizedResource headers and add any queries to it
if(!is.null(urlcomponents$query)) {
components <- setNames(unlist(urlcomponents$query), unclass(names(unlist(urlcomponents$query))))
componentstring <- paste0("\n", paste(names(components[order(names(components))]),
components[order(names(components))], sep=":", collapse = "\n"))
} else componentstring <- ""
CanonicalizedResource <- paste0("/",account,"/",container, componentstring)
# create the authorizationtoken
signaturestring <- paste0(verb, "\n\n\n", `Content-Length`, "\n", md5, "\n", `Content-Type`, "\n\n\n",
ifMatch, "\n\n\n\n", CanonicalizedHeaders, "\n", CanonicalizedResource)
requestspecificencodedkey <- RCurl::base64(
digest::hmac(key=RCurl::base64Decode(key, mode="raw"),
object=enc2utf8(signaturestring),
algo= "sha256", raw=TRUE)
)
authorizationtoken <- paste0("SharedKey ", account, ":", requestspecificencodedkey)
# make the call
headers_final <- add_headers(Authorization=authorizationtoken, headers, `Content-Type` = `Content-Type`)
call <- httr::VERB(verb=verb, url=url, config=headers_final, body=requestBody, verbose())
print("signaturestring");print(signaturestring);
print(headers_final); print(call)
return(content(call))
}
## Tests. Replace 'key' and 'accountName' with yours
key <- "YowThr***********RDw=="
# Creates a container named 'test'
azureBlobCall("https://accountName.blob.core.windows.net/test?restype=container", "PUT", key)
# Creates a blob named 'blob' under container 'test' with the content of "Hej världen!"
azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "PUT", key,
headers = c("x-ms-blob-type"="BlockBlob"), requestBody = "Hej världen!") #upload_file("blob.txt"))
# List all blob in the container 'test'
azureBlobCall("https://accountName.blob.core.windows.net/test?comp=list&restype=container", "GET", key)
# deletes the blobl named 'blob'
azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "DELETE", key)
# Creates a blob named 'blob' under container 'test' with and upload the file 'blob.txt'
azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "PUT", key,
headers = c("x-ms-blob-type"="BlockBlob"), requestBody = upload_file("blob.txt"))
# deletes the container named 'test'
azureBlobCall("https://accountName.blob.core.windows.net/test?restype=container", "DELETE", key)