Skip to main content

Authentication

LINE Blockchain Developers API conducts authentication to validate a request. Refer to Introduction to API on how to issue an API key for authentication.

All API requests, except for the endpoint to retrieve the server time, must pass authentication information in the HTTP header. The server validates API requests with the given authentication information as follows. Invalid requests aren't processed.

  • An error is returned when the time discrepancy is larger than 5 minutes between the timestamp of the request and the current time (Unix Epoch time) of the server.
  • The same nonce can't be reused per service-api-key within 11 minutes. An error is returned when the nonce of the successful request is reused within 11 minutes.
  • A signature is effective only once.

The following authentication information must be included in the header of the request.

HeaderDescription
timestampTime of the request creation displayed in milliseconds since Unix Epoch at UTC. The time gap can’t be greater than 5 minutes against the server time.
nonceA random string of 8 characters, composed of uppercase or lowercase alphabets and numbers. nonce can't be reused within 11 minutes after the successful request.
service-api-keyAPI key of the service issued from the LINE Blockchain Developers console. It must be duly activated at LINE Blockchain Developers.
signatureResult of an API request signed with the API secret of the service issued from the LINE Blockchain Developers console. It must match the signature created on the server. Refer to Generating a signature.

To check your API key, go to Settings under the service of the LINE Blockchain Developers console.

Generating signature

Follow the next steps to generate a signature to be included in the header.

  1. Create a string by concatenating nonce, timestamp, HTTP method, request path, query string, and request body string in the order listed.
  2. Sign the string from step 1 with the API secret using HMAC-SHA512.
  3. Encode the result from step 2 with Base64.

Next is the description of each value used in the signature.

  • The nonce value and timestamp are the same as nonce and timestamp sent in the header.
  • The HTTP method is a string of the HTTP method of the API request, written in uppercase letters.
  • Request path refers to api-path of the API endpoint.
    For example, when the requested API endpoint is https://test-api.blockchain.line.me/v1/wallets/link000000000000000000000000000000000000000?page=2&msgType=MsgSend, its request path is /v1/wallets/link000000000000000000000000000000000000000.
  • Query string refers to the query parameter of the API endpoint. For example, when the requested API endpoint is https://test-api.blockchain.line.me/v1/wallets/link000000000000000000000000000000000000000?page=2&msgType=MsgSend, the query string is page=2&msg=MsgSend.
  • The request body string refers to the key and value included in the HTTP request body, connected in the form of 'key=value&', just like a query string. All keys should be sorted in ascending order and connected in the corresponding sequence.
  • For the array in the request body, the keys of the parent and its leaf child are concatenated with a period and used as a full key. The values of each array are concatenated with a comma and used as a full value. Refer to Example 4.
  • For the optional keys in the array, you must use an empty string for the unassigned values. Refer to Example 4.

JavaScript Sample

The following code shows how to generate a signature with JavaScript.

This is based on ES6. Use Babel to support older versions.

import CryptoJs from "crypto-js";
import _ from "lodash";
import RequestBodyFlattener from "./request-body-flattener";

export default class SignatureGenerator {
/*
* path has to include only path such as /v1/item-tokens/{contractId}/non-fungibles, without any query-string
* parameters is query-parameters
* body is request body of POST, PUT method
*/
static signature(apiSecret, method, path, timestamp, nonce, parameters = {}, body = {}) {
let obj = _.assignIn(parameters, body);
function createSignTarget() {
let signTarget = `${nonce}${timestamp}${method}${path}`;
if (obj && _.size(obj) > 0) {
if (signTarget.indexOf('?') < 0) {
signTarget += '?'
} else {
signTarget += '&'
}
}
return signTarget;
}

let signTarget = createSignTarget();
if (obj && _.size(obj) > 0) {
signTarget += RequestBodyFlattener.flatten(obj);
}
let hash = CryptoJs.HmacSHA512(signTarget, apiSecret);
return CryptoJs.enc.Base64.stringify(hash);
}
}

Python Sample

The following code shows how to generate a signature with Python.

import hmac
import hashlib
import base64
import logging
import sys
from sdk.request_flattener import RequestBodyFlattener

class SignatureGenerator:

def __createSignTarget(self, method, path, timestamp, nonce, parameters: dict = {}):
signTarget = f'{nonce}{str(timestamp)}{method}{path}'
if(len(parameters) > 0):
signTarget = signTarget + "?"

return signTarget

def generate(self, secret: str, method: str, path: str, timestamp: int, nonce: str, query_params: dict = {}, body: dict = {}):
body_flattener = RequestBodyFlattener()
all_parameters = {}
all_parameters.update(query_params)
all_parameters.update(body)

self.__logger.debug("query_params: " + str(query_params))

signTarget = self.__createSignTarget(method.upper(), path, timestamp, nonce, all_parameters)

if (len(query_params) > 0):
signTarget += '&'.join('%s=%s' % (key, value) for (key, value) in query_params.items())

if (len(body) > 0):
if (len(query_params) > 0):
signTarget += "&" + body_flattener.flatten(body)
else:
signTarget += body_flattener.flatten(body)

raw_hmac = hmac.new(bytes(secret, 'utf-8'), bytes(signTarget, 'utf-8'), hashlib.sha512)
result = base64.b64encode(raw_hmac.digest()).decode('utf-8')

return result

Kotlin Sample

The following code shows how to generate a signature with Kotlin.

import org.apache.commons.codec.binary.Base64
import java.util.TreeMap
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

interface SignatureGenerator {
fun generate(
serviceApiSecret: String,
httpMethod: String,
path: String,
timestamp: Long,
nonce: String,
flatQueryParam: String,
body: Map<String, Any?> = emptyMap(),
): String

fun generate(
serviceApiSecret: String,
httpMethod: String,
path: String,
timestamp: Long,
nonce: String,
queryParam: Map<String, List<String?>> = emptyMap(),
body: Map<String, Any?> = emptyMap(),
): String
}

class DefaultSignatureGenerator(
private val queryParameterFlattener: QueryParameterFlattener,
private val requestBodyFlattener: RequestBodyFlattener,
) : SignatureGenerator {
override fun generate(
serviceApiSecret: String,
httpMethod: String,
path: String,
timestamp: Long,
nonce: String,
queryParam: Map<String, List<String?>>,
body: Map<String, Any?>,
): String {
val flattenQueryParam = queryParameterFlattener.flatten(queryParam)

return generate(
serviceApiSecret,
httpMethod,
path,
timestamp,
nonce,
flattenQueryParam,
body,
)
}

override fun generate(
serviceApiSecret: String,
httpMethod: String,
path: String,
timestamp: Long,
nonce: String,
flatQueryParam: String,
body: Map<String, Any?>,
): String {
val data = signatureTarget(body, nonce, timestamp, httpMethod, path, flatQueryParam)
val rawHmac = rawSignature(serviceApiSecret, data)
return Base64.encodeBase64String(rawHmac)
}

private fun rawSignature(serviceApiSecret: String, data: String): ByteArray? {
val signingKey = SecretKeySpec(serviceApiSecret.toByteArray(), HMAC_512_SECRET_ALGORITHM)
val mac = Mac.getInstance(HMAC_512_SECRET_ALGORITHM)
mac.init(signingKey)
return mac.doFinal(data.toByteArray())
}

private fun signatureTarget(body: Map<String, Any?>, nonce: String, timestamp: Long, httpMethod: String, path: String, flatQueryParam: String): String {
val bodyTreeMap = sortBody(body)
val flattenBody = requestBodyFlattener.flatten(bodyTreeMap)
val stringBuilder = StringBuilder()
stringBuilder.append("$nonce$timestamp$httpMethod$path")
if (flatQueryParam.isNotBlank()) {
if ("?" in flatQueryParam) {
stringBuilder.append(flatQueryParam)
} else {
stringBuilder.append("?").append(flatQueryParam)
}
}
if (body.isNotEmpty()) {
if (!stringBuilder.contains('?')) {
stringBuilder.append("?").append(flattenBody)
} else {
stringBuilder.append("&").append(flattenBody)
}
}

return stringBuilder.toString()
}

private fun sortBody(body: Map<String, Any?>): TreeMap<String, Any?> {
val bodyTreeMap = TreeMap<String, Any?>()
bodyTreeMap.putAll(body)
return bodyTreeMap
}

companion object {
private const val HMAC_512_SECRET_ALGORITHM = "HmacSHA512"
}
}

Go Sample

The following code shows how to generate a signature with Go-lang.

import (
"fmt"
"strings"
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
)

func __createSignTarget(method string, path string, timestamp int, nonce string, parameters map[string]interface{}) string {
s := fmt.Sprint(timestamp)
signTarget := fmt.Sprintf("%s%s%s%s", nonce, s, method, path)

if len(parameters) > 0 {
signTarget = fmt.Sprintf("%s?", signTarget)
}

return signTarget;
}

func Generate(secret string,
method string,
path string,
timestamp int,
nonce string,
query_params map[string]interface{},
body map[string]interface{}) string {
//
// Generate signature with given arguments.
//
// Args:
// -secret- api-secret
// -method- http method
// -path- api path
// -timestamp- Unix timestamp value
// -nonce- random string with 8 length
// -query_params- query parameters
// -body- request body
//
// Returns:
// -signature- generated signature
//
//

all_parameters := make(map[string]interface{})

for k, v := range query_params {
all_parameters[k] = v
}

for k, v := range body {
all_parameters[k] = v
}

signTarget := __createSignTarget(strings.ToUpper(method), path, timestamp, nonce, all_parameters);

if len(all_parameters) > 0 {
signTarget = fmt.Sprintf("%s%s", signTarget, Flatten(all_parameters))
}

raw_hmac := hmac.New(sha512.New, []byte(secret))
raw_hmac.Write([]byte(signTarget))

result := base64.StdEncoding.EncodeToString(raw_hmac.Sum(nil))

return result
}

PHP Sample

The following code shows how to generate a signature with PHP.

<?php

require_once("request_body_flattener.php");

class SignatureGenerator {
// This is to generate signature with flatten request.

function __createSignTarget($method, $path, $timestamp, $nonce, $parameters) {
$s = strval($timestamp);
$signTarget = "{$nonce}{$s}{$method}{$path}";

if (count($parameters) > 0)
$signTarget = $signTarget."?";

return $signTarget;
}

public function generate($secret, $method, $path, $timestamp, $nonce, $query_params = array(), $body = array()) {
//
// Generate signature with given arguments.
//
// Args:
// -secret- api-secret
// -method- http method
// -path- api path
// -timestamp- Unix timestamp value
// -nonce- random string with 8 length
// -query_params- query parameters
// -body- request body
//
// Returns:
// -signature- generated signature
//
//

$body_flattener = new RequestBodyFlattener();
$all_parameters = array_replace($query_params, $body);

$signTarget = $this->__createSignTarget(strtoupper($method), $path, $timestamp, $nonce, $all_parameters);

if (count($all_parameters) > 0)
$signTarget = $signTarget.$body_flattener->flatten($all_parameters);

$raw_hmac = hash_hmac("sha512", $signTarget, $secret, true);
$result = base64_encode($raw_hmac);

return $result;
}
}

?>

Examples

Let's take a look at some examples to understand how to generate a signature and send an API request. We'll use the following information for all examples.

  • API Key: 136db0ad-0fe1-456f-96a4-329be3f93036
  • API Secret: 9256bf8a-2b86-42fe-b3e0-d3079d0141fe
  • Timestamp: 1581850266351
  • Nonce: Bp0IqgXE

Example 1. When there is a request path only

The first example is an API request with the request path only.

  • HTTP method: GET
  • Request path: /v1/wallets

Generate the string required for the signature as follows.

Bp0IqgXE1581850266351GET/v1/wallets

Create an HMAC SHA512 signature using echo and OpenSSL, and encode with Base64.

echo -n "Bp0IqgXE1581850266351GET/v1/wallets" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64

> 2LtyRNI16y/5/RdoTB65sfLkO0OSJ4pCuz2+ar0npkRbk1/dqq1fbt1FZo7fueQl1umKWWlBGu/53KD2cptcCA==

Send the generated signature using curl via the header of the API request.

curl -i GET 'https://test-api.blockchain.line.me/v1/wallets' \
--header 'service-api-key: 136db0ad-0fe1-456f-96a4-329be3f93036' \
--header 'nonce: Bp0IqgXE' \
--header 'timestamp: 1581850266351' \
--header 'signature: 2LtyRNI16y/5/RdoTB65sfLkO0OSJ4pCuz2+ar0npkRbk1/dqq1fbt1FZo7fueQl1umKWWlBGu/53KD2cptcCA=='

Example 2. When there are request path and query parameter

The second example is an API request with the request path and query parameter.

  • HTTP method: GET
  • Request URI: /v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend
  • Request path: /v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions
  • Query string: page=2&msgType=coin/MsgSend

Generate the string required for the signature as follows. Note that '?' is used between the request path and the query string. Also, you must keep the original sequence when entering query parameters.

Bp0IqgXE1581850266351GET/v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend  

Create an HMAC SHA512 signature using echo and OpenSSL, and encode with Base64.

echo -n "Bp0IqgXE1581850266351GET/v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64

> fasfnqKVVClFam+Dov+YN+rUfOo/PMZfgKx8E36YBtPh7gB2C+YJv4Hxl0Ey3g8lGD0ErEGnD0gqAt85iEhklQ==

Send the generated signature using curl via the header of the API request.

curl -i GET 'https://test-api.blockchain.line.me/v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend' \
--header 'service-api-key: 136db0ad-0fe1-456f-96a4-329be3f93036' \
--header 'nonce: Bp0IqgXE' \
--header 'timestamp: 1581850266351' \
--header 'signature: fasfnqKVVClFam+Dov+YN+rUfOo/PMZfgKx8E36YBtPh7gB2C+YJv4Hxl0Ey3g8lGD0ErEGnD0gqAt85iEhklQ=='

Example 3. When there are request path and request body (excluding an array)

The third example is an API request with the request path and request body.

  • HTTP method: PUT
  • Request path: /v1/item-tokens/61e14383/non-fungibles/10000001/00000001
  • Request body string:
{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"name": "NewName"
}

String required for the signature can be generated as follows.

Bp0IqgXE1581850266351PUT/v1/item-tokens/61e14383/non-fungibles/10000001/00000001?name=NewName&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=

As shown above, note that '?' is used between the request path and the request body string, even without the query string. In addition, the request body should be added after the keys are sorted in ascending order.

You can use a sorting function provided by the programming language you're using.

Create an HMAC SHA512 signature using echo and OpenSSL, and encode with Base64.

echo -n "Bp0IqgXE1581850266351PUT/v1/item-tokens/61e14383/non-fungibles/10000001/00000001?name=NewName&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64

> 4L5BU0Ml/ejhzTg6Du12BDdElv8zoE7XD/iyOaZ2BHJIJG0SUOuCZWXu0YaF4i4C2CFJhjZoJFsje4CJn/wyyw==

Send the generated signature using curl via the header of the API request.

curl -i -X PUT 'https://test-api.blockchain.line.me/v1/item-tokens/61e14383/non-fungibles/10000001/00000001' \
--header 'service-api-key: 136db0ad-0fe1-456f-96a4-329be3f93036' \
--header 'nonce: Bp0IqgXE' \
--header 'timestamp: 1581850266351' \
--header 'signature: 4L5BU0Ml/ejhzTg6Du12BDdElv8zoE7XD/iyOaZ2BHJIJG0SUOuCZWXu0YaF4i4C2CFJhjZoJFsje4CJn/wyyw==' \
--header 'Content-Type: application/json' \
--data-raw '{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"name": "NewName"
}'

Example 4. When there are request path and request body (including an array)

The last example is an API request with the request path and request body including an array.

  • HTTP method: POST
  • Request path: /v1/item-tokens/61e14383/non-fungibles/multi-mint
  • Request body:
{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2",
"meta": "New nft 2 meta information"
}
]
}

String required for the signature can be generated as follows.

Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.meta=,New nft 2 meta information&mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp

As shown above, note that '?' is included between the request path and the request body string, even without the query string. In addition, the request body should be added after keys are sorted in the ascending order.

You can use a sorting function provided by the programming language you're using.

In the example above, the first element in mintList doesn't have meta as it's optional. Then, use an empty string for meta of the first element like "mintList.meta=,New nft 2 meta information." If the second element doesn't have meta either, exclude the "mintList.meta" from the string.

Create a HMAC SHA512 signature, using echo and OpenSSL, and encode with Base64.

echo -n "Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.meta=,New nft 2 meta information&mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64

> vhr5c3y2PAP5rmt+4YN1ojbMnT9IkYnIIB1yvWYM9OdECB2Y11fGTLDLRybB3lLKv0kvJQMAelSkQYBKdhSXbg==

Send the generated signature using curl via the header of the API request.

curl -i -X POST 'https://test-api.blockchain.line.me/v1/item-tokens/61e14383/non-fungibles/multi-mint' \
--header 'service-api-key: 136db0ad-0fe1-456f-96a4-329be3f93036' \
--header 'nonce: Bp0IqgXE' \
--header 'timestamp: 1581850266351' \
--header 'signature: vhr5c3y2PAP5rmt+4YN1ojbMnT9IkYnIIB1yvWYM9OdECB2Y11fGTLDLRybB3lLKv0kvJQMAelSkQYBKdhSXbg==' \
--header 'Content-Type: application/json' \
--data-raw '{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2",
"meta": "New nft 2 meta information"
}
]
}'

What if the mintList in the request body doesn't use meta as shown below?

{    
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2"
}
]
}

Exclude mintList.meta key from the string to be signed as shown below.

Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp

Let's take a look at meta, an optional field in mintList, is given null.

{    
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2",
"meta": null
}
]
}

In this case, use the following string excluding null for signature.

Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp